Module iaf.io.readers
Custom image readers.
Classes
class HuygensHDF5Reader (filename: Union[pathlib.Path, str] = None)
-
Huygens HDF5 Reader.
from iaf.io.readers import HuygensHDF5Reader # Initialize the reader reader = HuygensHDF5Reader("file.h5") # To load a specific (timepoint, channel) image or stack: stack = reader.load(timepoint=0, channel=0)
Stacks are lazily loaded only on access.
The reader exposes metadata information via properties:
Property Explanation reader.channel_names
Tuple of names for each of the acquisition channels. reader.filename
Full file name of the opened file. reader.num_channels
Number of channels. reader.num_planes
Number of planes (z levels). reader.num_timepoints
Number of time points. reader.voxel_sizes
Voxel sizes in units (nm) (x, y, z)
.reader.metadata
Dictionary of microscopy parameters. reader.hdf5_dataset
HDF5 dataset for advanced use. Constructor.
Parameters
filename
:Union[Path, str]
- Full path to the file to open
Expand source code
class HuygensHDF5Reader: """Huygens HDF5 Reader. ``` from iaf.io.readers import HuygensHDF5Reader # Initialize the reader reader = HuygensHDF5Reader("file.h5") # To load a specific (timepoint, channel) image or stack: stack = reader.load(timepoint=0, channel=0) ``` Stacks are lazily loaded only on access. The reader exposes metadata information via properties: | Property | Explanation | | ----------------------- | ------------------------------------------------------------- | | `reader.channel_names` | Tuple of names for each of the acquisition channels. | | `reader.filename` | Full file name of the opened file. | | `reader.num_channels` | Number of channels. | | `reader.num_planes` | Number of planes (z levels). | | `reader.num_timepoints` | Number of time points. | | `reader.voxel_sizes` | Voxel sizes in units (nm) `(x, y, z)`. | | `reader.metadata` | Dictionary of microscopy parameters. | | `reader.hdf5_dataset` | HDF5 dataset for advanced use. | """ def __init__(self, filename: Union[Path, str] = None): """Constructor. Parameters ---------- filename: Union[Path, str] Full path to the file to open """ self._file = None # Make sure a filename was passed if filename is None: raise ValueError("Please specify a file name!") self._filename: Path = Path(filename).absolute() if not self._filename.exists(): raise IOError(f"File {filename} does not exist.") if not self._filename.suffix.lower() == ".h5": raise ValueError(f"The _file {self._filename} is not an .h5 file.") # Initialize _geometry self._num_timepoints: int = 1 self._num_channels: int = 1 self._num_planes: int = 1 self._sizeX: int = 0 self._sizeY: int = 0 self._sizeZ: int = 0 self._channels_names: tuple = () # Metadata dictionary self._metadata = {} # Try opening file self._file = h5py.File(self._filename, "r") # Parse the geometry of the file and sets the appropriate bundle_ and iter_axis self._parse_geometry() # Parse metadata self._parse_metadata() # Parse voxel sizes self._voxel_sizes = (0.0, 0.0, 0.0) self._parse_voxel_sizes() # Parse channel names self._parse_channel_names() def _parse_geometry(self): """ Parse geometry of IMS file and set properties. """ if self._file is None: return # Get dataset image_dataset = self._file.get(list(self._file.keys())[0])["ImageData"]["Image"] dimensions = image_dataset.shape self._num_channels = dimensions[0] self._num_timepoints = dimensions[1] self._num_planes = dimensions[2] self._sizeX = dimensions[4] self._sizeY = dimensions[3] self._sizeZ = dimensions[2] def _parse_voxel_sizes(self): """ Parse metadata of H5 file and extracts pixel size. """ if self._file is None: return # Get dataset image_data = self._file.get(list(self._file.keys())[0])["ImageData"] # Store the pixel size self._voxel_sizes = ( float(image_data["DimensionScaleX"][()]), float(image_data["DimensionScaleY"][()]), float(image_data["DimensionScaleZ"][()]), ) def _parse_channel_names(self): """ Parse the channel names. """ if self._file is None: return # Get physical data group physical_data_group = self._file.get(list(self._file.keys())[0])["PhysicalData"] # Extract channel descriptions channel_names_list = [] for c in range(len(physical_data_group["ChannelDescription"])): name = str(physical_data_group["ChannelDescription"][c], encoding="utf-8") channel_names_list.append(name) self._channels_names = tuple(channel_names_list) def _parse_metadata(self): """Extract the metadata.""" if self._file is None: return # Get physical data group physical_data_group = self._file.get(list(self._file.keys())[0])["PhysicalData"] # Clear the dictionary self._metadata.clear() for key in physical_data_group.keys(): if key.lower().endswith("enumeration"): continue # Initialize data -- it will be overwritten data = "" try: data = physical_data_group[key][()] if type(data) is bytes: data = data.decode("utf-8") elif type(data) is np.ndarray: if data.dtype == object: data = data.astype(str) else: data = tuple(data.tolist()) else: print(f"Unsupported entry {key}: ({type(data)})") continue except Exception: print(f"{key}: could not extract data!") # Store the processed entry self._metadata[key] = data def load(self, timepoint: int = 0, channel: int = 0): """Load specific timepoint and channel. Parameters ---------- timepoint: int Index of the timepoint to load, 0-based. (Optional, default 0). channel: int Index of the channel to load, 0-based. (Optional, default 0). Returns ------- dataset: Union[None, DataSet, np.ndarray] Dataset at the requested `timepoint` and `channel`. """ if self._file is None: return # Get dataset image_dataset = self._file.get(list(self._file.keys())[0])["ImageData"]["Image"] # Check that the requested timepoint and channel are compatible with the dataset if timepoint > image_dataset.shape[0] - 1: raise ValueError("Timepoint out of bounds!") if channel > image_dataset.shape[1] - 1: raise ValueError("Channel out of bounds!") # Extract and return stack stack = image_dataset[channel, timepoint, :] # If z is 1, we drop to two dimensions if stack.shape[0] == 1: stack = stack.squeeze() return stack def __repr__(self): """Return human-readable representation of the file.""" if self._file is None: return "HuygensHDF5Reader()" return ( f'HuygensHDF5Reader("{Path(self._filename).name}")\n' f" - Dimensions: (" f"t={self.num_timepoints}, " f"c={self.num_channels}, " f"z={self.num_planes}, " f"y={self.size[0]}, " f"x={self.size[1]})\n" f" - Voxel size: (" f"x={1e6 * self.voxel_sizes[0]:.4f}µm, " f"y={1e6 * self.voxel_sizes[1]:.4f}µm, " f"z={1e6 * self.voxel_sizes[2]:.4f}µm)\n" ) def __str__(self): """Return human-readable representation of the file.""" return self.__repr__() @property def filename(self) -> str: """Return the file name with full path. Returns ------- filename: str Full file name, or empty string if not set. """ if self._file is None: return "" return str(self._filename) @property def channel_names(self) -> tuple: """Return the names of the channels. Returns ------- channel_names: tuple Names of the channels """ if self._file is None: return () return self._channels_names @property def num_timepoints(self) -> int: """Return the number of timepoints. Returns ------- num_timepoints: int Number of timepoints per series in the file. If no file is open, `num_timepoints` is 0. """ if self._file is None: return 0 return self._num_timepoints @property def num_channels(self) -> int: """Return the number of channels. Returns ------- num_channels: int Number of channels per series in the file. If no file is open, `num_channels` is 0. """ if self._file is None: return 0 return self._num_channels @property def num_planes(self) -> int: """Return the number of planes. Returns ------- num_planes: int Number of planes per series in the file. If no file is open, `num_planes` is 0. """ if self._file is None: return 0 return self._num_planes @property def size(self) -> tuple: """Return the XYZ size of the dataset. Returns ------- size: tuple Size `(x, y, z)` """ return self._sizeX, self._sizeY, self._sizeZ @property def voxel_sizes(self) -> tuple: """Return the global voxel sizes for the acquisition. Returns ------- voxel_sizes: tuple Voxel sizes `(vx, vy, vz)` """ return self._voxel_sizes @property def metadata(self) -> dict: """Return the metadata dictionary. Returns ------- metadata: dict Metadata dictionary. """ if self._file is None: return {} return self._metadata @property def hdf5_dataset(self) -> Union[Dataset, None]: """Return the underlying HDF5 dataset. Returns ------- hdf5_dataset: h5py.Dataset HD5 dataset if a file is open, or None otherwise. """ if self._file is None: return None # Get HDF5 dataset try: dataset = self._file.get(list(self._file.keys())[0])["ImageData"]["Image"] except Exception: return None return dataset def __del__(self): """Destructor.""" if self._file is not None: try: self._file.close() except Exception as _: pass
Instance variables
prop channel_names : tuple
-
Return the names of the channels.
Returns
channel_names
:tuple
- Names of the channels
Expand source code
@property def channel_names(self) -> tuple: """Return the names of the channels. Returns ------- channel_names: tuple Names of the channels """ if self._file is None: return () return self._channels_names
prop filename : str
-
Return the file name with full path.
Returns
filename
:str
- Full file name, or empty string if not set.
Expand source code
@property def filename(self) -> str: """Return the file name with full path. Returns ------- filename: str Full file name, or empty string if not set. """ if self._file is None: return "" return str(self._filename)
prop hdf5_dataset : Optional[h5py._hl.dataset.Dataset]
-
Return the underlying HDF5 dataset.
Returns
hdf5_dataset
:h5py.Dataset
- HD5 dataset if a file is open, or None otherwise.
Expand source code
@property def hdf5_dataset(self) -> Union[Dataset, None]: """Return the underlying HDF5 dataset. Returns ------- hdf5_dataset: h5py.Dataset HD5 dataset if a file is open, or None otherwise. """ if self._file is None: return None # Get HDF5 dataset try: dataset = self._file.get(list(self._file.keys())[0])["ImageData"]["Image"] except Exception: return None return dataset
prop metadata : dict
-
Return the metadata dictionary.
Returns
metadata
:dict
- Metadata dictionary.
Expand source code
@property def metadata(self) -> dict: """Return the metadata dictionary. Returns ------- metadata: dict Metadata dictionary. """ if self._file is None: return {} return self._metadata
prop num_channels : int
-
Return the number of channels.
Returns
num_channels
:int
- Number of channels per series in the file. If no file is open,
num_channels
is 0.
Expand source code
@property def num_channels(self) -> int: """Return the number of channels. Returns ------- num_channels: int Number of channels per series in the file. If no file is open, `num_channels` is 0. """ if self._file is None: return 0 return self._num_channels
prop num_planes : int
-
Return the number of planes.
Returns
num_planes
:int
- Number of planes per series in the file. If no file is open,
num_planes
is 0.
Expand source code
@property def num_planes(self) -> int: """Return the number of planes. Returns ------- num_planes: int Number of planes per series in the file. If no file is open, `num_planes` is 0. """ if self._file is None: return 0 return self._num_planes
prop num_timepoints : int
-
Return the number of timepoints.
Returns
num_timepoints
:int
- Number of timepoints per series in the file. If no file is open,
num_timepoints
is 0.
Expand source code
@property def num_timepoints(self) -> int: """Return the number of timepoints. Returns ------- num_timepoints: int Number of timepoints per series in the file. If no file is open, `num_timepoints` is 0. """ if self._file is None: return 0 return self._num_timepoints
prop size : tuple
-
Return the XYZ size of the dataset.
Returns
size
:tuple
- Size
(x, y, z)
Expand source code
@property def size(self) -> tuple: """Return the XYZ size of the dataset. Returns ------- size: tuple Size `(x, y, z)` """ return self._sizeX, self._sizeY, self._sizeZ
prop voxel_sizes : tuple
-
Return the global voxel sizes for the acquisition.
Returns
voxel_sizes
:tuple
- Voxel sizes
(vx, vy, vz)
Expand source code
@property def voxel_sizes(self) -> tuple: """Return the global voxel sizes for the acquisition. Returns ------- voxel_sizes: tuple Voxel sizes `(vx, vy, vz)` """ return self._voxel_sizes
Methods
def load(self, timepoint: int = 0, channel: int = 0)
-
Load specific timepoint and channel.
Parameters
timepoint
:int
- Index of the timepoint to load, 0-based. (Optional, default 0).
channel
:int
- Index of the channel to load, 0-based. (Optional, default 0).
Returns
dataset
:Union[None, DataSet, np.ndarray]
- Dataset at the requested
timepoint
andchannel
.
class ImarisReader (filename: Union[pathlib.Path, str] = None)
-
Imaris Reader.
Examples:
from iaf.io.readers import ImarisReader # Initialize the reader reader = ImarisReader("file.ims") # To load a specific (timepoint, channel) image or stack: stack = reader.load(timepoint=0, channel=0)
Stacks are lazily loaded only on access.
The reader exposes metadata information via properties:
Property Explanation reader.channel_names
Tuple of names for each of the acquisition channels. reader.filename
Full file name of the opened file. reader.num_channels
Number of channels. reader.num_planes
Number of planes (z levels). reader.num_timepoints
Number of time points. reader.voxel_sizes
Voxel sizes in units (um) (x, y, z)
.reader.extends
Extends in units (um) (minX, maxX, minY, maxY, minZ, maxZ)
.Constructor.
Parameters
filename
:Union[Path, str]
- Full path to the file to open
Expand source code
class ImarisReader: """Imaris Reader. Examples: ``` from iaf.io.readers import ImarisReader # Initialize the reader reader = ImarisReader("file.ims") # To load a specific (timepoint, channel) image or stack: stack = reader.load(timepoint=0, channel=0) ``` Stacks are lazily loaded only on access. The reader exposes metadata information via properties: | Property | Explanation | | ----------------------- | ------------------------------------------------------------- | | `reader.channel_names` | Tuple of names for each of the acquisition channels. | | `reader.filename` | Full file name of the opened file. | | `reader.num_channels` | Number of channels. | | `reader.num_planes` | Number of planes (z levels). | | `reader.num_timepoints` | Number of time points. | | `reader.voxel_sizes` | Voxel sizes in units (um) `(x, y, z)`. | | `reader.extends` | Extends in units (um) `(minX, maxX, minY, maxY, minZ, maxZ)`. | """ def __init__(self, filename: Union[Path, str] = None): """Constructor. Parameters ---------- filename: Union[Path, str] Full path to the file to open """ self._file = None # Make sure a filename was passed if filename is None: raise ValueError("Please specify a file name!") self._filename: Path = Path(filename).absolute() if not self._filename.exists(): raise IOError(f"File {filename} does not exist.") if not self._filename.suffix.lower() == ".ims": raise ValueError(f"The _file {self._filename} is not an .ims file.") # Initialize _geometry self._num_timepoints: int = 1 self._num_channels: int = 1 self._num_planes: int = 1 self._sizeX: int = 0 self._sizeY: int = 0 self._sizeZ: int = 0 self._channels_names: tuple = () self._extents: tuple = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0) # Try opening file self._file = h5py.File(self._filename, "r") # Parse the geometry of the file and sets the appropriate bundle_ and iter_axis self._parse_geometry() # Parse voxel sizes self._voxel_sizes = (0.0, 0.0, 0.0) self._parse_voxel_sizes() # Parse channel names self._parse_channel_names() def _parse_geometry(self): """ Parse geometry of IMS file and set properties. """ self._num_timepoints = len(self._file["DataSet"]["ResolutionLevel 0"].keys()) self._num_channels = len( self._file["DataSet"]["ResolutionLevel 0"]["TimePoint 0"].keys() ) self._num_planes = self._file["DataSet"]["ResolutionLevel 0"]["TimePoint 0"][ "Channel 0" ]["Data"].shape[0] self._sizeX = int( str(self._file["DataSetInfo"]["Image"].attrs["X"], encoding="utf-8") ) self._sizeY = int( str(self._file["DataSetInfo"]["Image"].attrs["Y"], encoding="utf-8") ) self._sizeZ = int( str(self._file["DataSetInfo"]["Image"].attrs["Z"], encoding="utf-8") ) def _parse_voxel_sizes(self): """ Parse metadata of IMS file and extracts pixel size and z step. """ # Extract the extends extmin_x = float( str(self._file["DataSetInfo"]["Image"].attrs["ExtMin0"], encoding="utf-8") ) extmax_x = float( str(self._file["DataSetInfo"]["Image"].attrs["ExtMax0"], encoding="utf-8") ) extmin_y = float( str(self._file["DataSetInfo"]["Image"].attrs["ExtMin1"], encoding="utf-8") ) extmax_y = float( str(self._file["DataSetInfo"]["Image"].attrs["ExtMax1"], encoding="utf-8") ) extmin_z = float( str(self._file["DataSetInfo"]["Image"].attrs["ExtMin2"], encoding="utf-8") ) extmax_z = float( str(self._file["DataSetInfo"]["Image"].attrs["ExtMax2"], encoding="utf-8") ) # Set the extends property self._extends: tuple = ( extmin_x, extmax_x, extmin_y, extmax_y, extmin_z, extmax_z, ) # Calculate and store the voxel sizes voxel_x = (extmax_x - extmin_x) / self._sizeX voxel_y = (extmax_y - extmin_y) / self._sizeY voxel_z = (extmax_z - extmin_z) / self._sizeZ self._voxel_sizes = (voxel_x, voxel_y, voxel_z) def _parse_channel_names(self): """ Parse the channel names. """ channel_names_list = [] for i in range(self.num_channels): name = str( self._file["DataSetInfo"][f"Channel {i}"].attrs["Name"], encoding="utf-8", ) channel_names_list.append(name) self._channels_names = tuple(channel_names_list) def load(self, timepoint: int = 0, channel: int = 0, resolution_level: int = 0): """Load specific timepoint and channel. Parameters ---------- timepoint: int Index of the timepoint to load, 0-based. (Optional, default 0). channel: int Index of the channel to load, 0-based. (Optional, default 0). resolution_level: int Resolution level to load, with 0 being full resolution. (Optional, default 0). Returns ------- dataset: Union[None, DataSet, np.ndarray] Dataset at the requested `timepoint` and `channel`. """ # Build keys from indices resolution_level_key = f"ResolutionLevel {resolution_level}" timepoint_key = f"TimePoint {timepoint}" channel_key = f"Channel {channel}" try: # Get the dataset dataset = self._file["DataSet"][resolution_level_key][timepoint_key][ channel_key ]["Data"] # Because of chunk size, the dataset size may not be exactly the same as the underlying # stack size. We extract the relevant subset. dataset = dataset[: self._sizeZ, : self._sizeY, : self._sizeX] # If z is 1, we drop to two dimensions if self._sizeZ == 1: dataset = dataset.squeeze() except Exception: dataset = None return dataset def __repr__(self): """Return human-readable representation of the file.""" if self._file is None: return "ImarisReader()" return ( f'ImarisReader("{Path(self._filename).name}")\n' f" - Dimensions: (" f"t={self.num_timepoints}, " f"c={self.num_channels}, " f"z={self.num_planes}, " f"y={self.size[0]}, " f"x={self.size[1]})\n" f" - Voxel size: (" f"x={self.voxel_sizes[0]:.4f}µm, " f"y={self.voxel_sizes[1]:.4f}µm, " f"z={self.voxel_sizes[2]:.4f}µm)\n" ) def __str__(self): """Return human-readable representation of the file.""" return self.__repr__() @property def filename(self) -> str: """Return the file name with full path. Returns ------- filename: str Full file name, or empty string if not set. """ if self._file is None: return "" return str(self._filename) @property def extends(self) -> tuple: """Return the dataset extends. Returns ------- extends: tuple Dataset extends: `(extMinX, extMaxX, extMinY, extMaxY, extMinZ, extMaxZ)` """ if self._file is None: return () return self._extends @property def channel_names(self) -> tuple: """Return the names of the channels. Returns ------- channel_names: tuple Names of the channels """ if self._file is None: return () return self._channels_names @property def num_timepoints(self) -> int: """Return the number of timepoints. Returns ------- num_timepoints: int Number of timepoints per series in the file. If no file is open, `num_timepoints` is 0. """ if self._file is None: return 0 return self._num_timepoints @property def num_channels(self) -> int: """Return the number of channels. Returns ------- num_channels: int Number of channels per series in the file. If no file is open, `num_channels` is 0. """ if self._file is None: return 0 return self._num_channels @property def num_planes(self) -> int: """Return the number of planes. Returns ------- num_planes: int Number of planes per series in the file. If no file is open, `num_planes` is 0. """ if self._file is None: return 0 return self._num_planes @property def size(self) -> tuple: """Return the XYZ size of the dataset. Returns ------- size: tuple Size `(x, y, z)` """ return self._sizeX, self._sizeY, self._sizeZ @property def voxel_sizes(self) -> tuple: """Return the global voxel sizes for the acquisition. Returns ------- voxel_sizes: tuple Voxel sizes `(vx, vy, vz)` """ return self._voxel_sizes def __del__(self): """Destructor.""" if self._file is not None: try: self._file.close() except Exception as _: pass
Instance variables
prop channel_names : tuple
-
Return the names of the channels.
Returns
channel_names
:tuple
- Names of the channels
Expand source code
@property def channel_names(self) -> tuple: """Return the names of the channels. Returns ------- channel_names: tuple Names of the channels """ if self._file is None: return () return self._channels_names
prop extends : tuple
-
Return the dataset extends.
Returns
extends
:tuple
- Dataset extends:
(extMinX, extMaxX, extMinY, extMaxY, extMinZ, extMaxZ)
Expand source code
@property def extends(self) -> tuple: """Return the dataset extends. Returns ------- extends: tuple Dataset extends: `(extMinX, extMaxX, extMinY, extMaxY, extMinZ, extMaxZ)` """ if self._file is None: return () return self._extends
prop filename : str
-
Return the file name with full path.
Returns
filename
:str
- Full file name, or empty string if not set.
Expand source code
@property def filename(self) -> str: """Return the file name with full path. Returns ------- filename: str Full file name, or empty string if not set. """ if self._file is None: return "" return str(self._filename)
prop num_channels : int
-
Return the number of channels.
Returns
num_channels
:int
- Number of channels per series in the file. If no file is open,
num_channels
is 0.
Expand source code
@property def num_channels(self) -> int: """Return the number of channels. Returns ------- num_channels: int Number of channels per series in the file. If no file is open, `num_channels` is 0. """ if self._file is None: return 0 return self._num_channels
prop num_planes : int
-
Return the number of planes.
Returns
num_planes
:int
- Number of planes per series in the file. If no file is open,
num_planes
is 0.
Expand source code
@property def num_planes(self) -> int: """Return the number of planes. Returns ------- num_planes: int Number of planes per series in the file. If no file is open, `num_planes` is 0. """ if self._file is None: return 0 return self._num_planes
prop num_timepoints : int
-
Return the number of timepoints.
Returns
num_timepoints
:int
- Number of timepoints per series in the file. If no file is open,
num_timepoints
is 0.
Expand source code
@property def num_timepoints(self) -> int: """Return the number of timepoints. Returns ------- num_timepoints: int Number of timepoints per series in the file. If no file is open, `num_timepoints` is 0. """ if self._file is None: return 0 return self._num_timepoints
prop size : tuple
-
Return the XYZ size of the dataset.
Returns
size
:tuple
- Size
(x, y, z)
Expand source code
@property def size(self) -> tuple: """Return the XYZ size of the dataset. Returns ------- size: tuple Size `(x, y, z)` """ return self._sizeX, self._sizeY, self._sizeZ
prop voxel_sizes : tuple
-
Return the global voxel sizes for the acquisition.
Returns
voxel_sizes
:tuple
- Voxel sizes
(vx, vy, vz)
Expand source code
@property def voxel_sizes(self) -> tuple: """Return the global voxel sizes for the acquisition. Returns ------- voxel_sizes: tuple Voxel sizes `(vx, vy, vz)` """ return self._voxel_sizes
Methods
def load(self, timepoint: int = 0, channel: int = 0, resolution_level: int = 0)
-
Load specific timepoint and channel.
Parameters
timepoint
:int
- Index of the timepoint to load, 0-based. (Optional, default 0).
channel
:int
- Index of the channel to load, 0-based. (Optional, default 0).
resolution_level
:int
- Resolution level to load, with 0 being full resolution. (Optional, default 0).
Returns
dataset
:Union[None, DataSet, np.ndarray]
- Dataset at the requested
timepoint
andchannel
.
class NikonND2Reader (filename: Union[pathlib.Path, str] = None)
-
Nikon ND2 reader that internally uses nd2reader by Ruben Verweij.
Examples:
from iaf.io.readers import NikonND2Reader # Initialize the reader reader = NikonND2Reader("file.nd2") # To iterate over all series (or timepoints): for img in reader: pass # To access a specific series (or timepoint): stack = reader[0]
Series (or timepoints) are lazily loaded only on access.
The reader exposes metadata information via properties:
Property Explanation reader.channel_names
Tuple of names for each of the acquisition channels. reader.filename
Full file name of the opened file. reader.geometry
Geometry for each series. reader.iter_axis
Axis over which the iteration occurs (one of "v"
or"t"
).reader.metadata
Processed file metadata (dictionary). reader.num_channels
Number of channels. reader.num_planes
Number of planes (z levels). reader.num_series
Number of series (acquisitions) in the file. reader.num_timepoints
Number of time points. reader.voxel_sizes
Voxel sizes in units (um) (x, y, z)
.If a file contains several series,
iter_axis
will be"v"
: that is, the iterator will return one series at a time. If there is only one series, however,iter_axis
will point to the next dimension, that is"t"
. In this case, the iterator will return one time point at a time. No more granularity is supported. In all cases, thegeometry
property will indicate the dimensionality of the array returned by the iterator (e.g.,"czyx"
).Constructor.
Parameters
filename
:Union[Path, str]
- Full path to the file to open
Expand source code
class NikonND2Reader: """Nikon ND2 reader that internally uses [nd2reader](https://open-science-tools.github.io/nd2reader/) by Ruben Verweij. Examples: ``` from iaf.io.readers import NikonND2Reader # Initialize the reader reader = NikonND2Reader("file.nd2") # To iterate over all series (or timepoints): for img in reader: pass # To access a specific series (or timepoint): stack = reader[0] ``` Series (or timepoints) are lazily loaded only on access. The reader exposes metadata information via properties: | Property | Explanation | | ----------------------- | ------------------------------------------------------------- | | `reader.channel_names` | Tuple of names for each of the acquisition channels. | | `reader.filename` | Full file name of the opened file. | | `reader.geometry` | Geometry for each series. | | `reader.iter_axis` | Axis over which the iteration occurs (one of `"v"` or `"t"`). | | `reader.metadata` | Processed file metadata (dictionary). | | `reader.num_channels` | Number of channels. | | `reader.num_planes` | Number of planes (z levels). | | `reader.num_series` | Number of series (acquisitions) in the file. | | `reader.num_timepoints` | Number of time points. | | `reader.voxel_sizes` | Voxel sizes in units (um) `(x, y, z)`. | If a file contains several series, `iter_axis` will be `"v"`: that is, the iterator will return one series at a time. If there is only one series, however, `iter_axis` will point to the next dimension, that is `"t"`. In this case, the iterator will return one time point at a time. No more granularity is supported. In all cases, the `geometry` property will indicate the dimensionality of the array returned by the iterator (e.g., `"czyx"`). """ def __init__(self, filename: Union[Path, str] = None): """Constructor. Parameters ---------- filename: Union[Path, str] Full path to the file to open """ self._file: Optional[ND2Reader] = None # Make sure a filename was passed if filename is None: raise ValueError("Please specify a file name!") self._filename: Path = Path(filename).absolute() if not self._filename.exists(): raise IOError(f"File {filename} does not exist.") if not self._filename.suffix.lower() == ".nd2": raise ValueError(f"The _file {self._filename} is not an .nd2 file.") # Initialize _geometry self._num_series: int = 1 self._num_timepoints: int = 1 self._num_channels: int = 1 self._num_planes: int = 1 self._geometry: str = "" self._iter_axis: str = "" # Open the file self._file = ND2Reader(str(self._filename)) # Set the counter for the iterator self._current_index = 0 # Parse the geometry of the file and sets the appropriate bundle_ and iter_axis self._parse_geometry() # Parse voxel sizes self._voxel_sizes = (0.0, 0.0, 0.0) self._parse_voxel_sizes() def __repr__(self): """Return human-readable representation of the file.""" if self._file is None: return "NikonND2Reader()" return ( f'NikonND2Reader("{Path(self._filename).name}")\n' f" - Dimensions: (" f"v={self.num_series}, " f"t={self.num_timepoints}, " f"c={self.num_channels}, " f"z={self.num_planes}, " f"y={self.metadata['height']}, " f"x={self.metadata['width']})\n" f" - Voxel size: (" f"x={self.voxel_sizes[0]:.4f}µm, " f"y={self.voxel_sizes[1]:.4f}µm, " f"z={self.voxel_sizes[2]:.4f}µm)\n" f' - Series geometry: "{self.geometry}"' ) def __str__(self): """Return human-readable representation of the file.""" return self.__repr__() @property def filename(self) -> str: """Return the file name with full path. Returns ------- filename: str Full file name, or empty string if not set. """ if self._file is None: return "" return str(self._filename) @property def metadata(self) -> dict: """Return the processed file metadata. Returns ------- matadata: dict Metadata dictionary. If no file is open, `metadata` is {}. """ if self._file is None: return {} return self._file.metadata @property def raw_metadata(self) -> Union[None, dict]: """Return the raw file metadata. Returns ------- raw_metadata: object of type [`RawMetadata`](https://open-science-tools.github.io/nd2reader/nd2reader.html#module-nd2reader.raw_metadata). If no file is open, `raw_metadata` is None. """ if self._file is None: return None return self._file.parser._raw_metadata @property def num_series(self) -> int: """Return the number of series. Returns ------- num_series: int Number of series in the file. If no file is open, `num_series` is 0. """ if self._file is None: return 0 return self._num_series @property def num_timepoints(self) -> int: """Return the number of timepoints. Returns ------- num_timepoints: int Number of timepoints per series in the file. If no file is open, `num_timepoints` is 0. """ if self._file is None: return 0 return self._num_timepoints @property def num_channels(self) -> int: """Return the number of channels. Returns ------- num_channels: int Number of channels per series in the file. If no file is open, `num_channels` is 0. """ if self._file is None: return 0 return self._num_channels @property def num_planes(self) -> int: """Return the number of planes. Returns ------- num_planes: int Number of planes per series in the file. If no file is open, `num_planes` is 0. """ if self._file is None: return 0 return self._num_planes @property def geometry(self) -> str: """Return the geometry of the individual image or stack. Returns ------- geometry: str Geometry of the individual image or stack. If no file is open, `geometry` is "". """ if self._file is None: return "" return self._geometry @property def iter_axis(self) -> str: """Return the axis used for iterating over individual images or stacks. Returns ------- iter_axis: str Axis used for iterating over individual images of stacks. If no file is open, `iter_axis` is "". """ if self._file is None: return "" return self._iter_axis @property def voxel_sizes(self) -> tuple: """Return the global voxel sizes for the acquisition. Returns ------- voxel_sizes: tuple Voxel sizes `(vx, vy, vz)` """ return self._voxel_sizes @property def channel_names(self) -> tuple: """Return the names of the channels. Returns ------- channel_names: tuple Voxel sizes (x, y, z) """ if self._file is None: return () return tuple(self._file.metadata["channels"]) def __del__(self): """Destructor.""" if self._file is not None: try: self._file.close() except Exception as _: pass def __getitem__(self, series: int) -> Frame: """Allows accessing the reader with the `[]` notation to get the next image/stack. Parameters ---------- series: int Index of the series to be retrieved. Returns ------- frame: Frame Requested image or stack. Please notice that the type of the data is ALWAYS `float64`! """ if self._file is None: raise ValueError("Please load a file first.") if series < 0 or series > (self._num_series - 1): raise ValueError(f"Index value {series} is out of bounds.") # Get and return the image img = self._file[series] return img def __iter__(self): """Return the iterator. Returns ------- iterator """ self._current_index = 0 return self def __next__(self): if self._current_index < self._num_series: img = self.__getitem__(self._current_index) self._current_index += 1 return img else: raise StopIteration def _parse_geometry(self) -> None: """ Parse geometry of ND2 file and sets `bundle_axis` and `_iter_axis` properties of ND2Reader. """ self._geometry = "yx" if "z" in self._file.sizes and self._file.sizes["z"] > 1: self._num_planes = self._file.sizes["z"] self._geometry = "z" + self._geometry if "c" in self._file.sizes and self._file.sizes["c"] > 1: self._num_channels = self._file.sizes["c"] self._geometry = "c" + self._geometry if "t" in self._file.sizes and self._file.sizes["t"] > 1: self._num_timepoints = self._file.sizes["t"] self._geometry = "t" + self._geometry self._file.bundle_axes = self._geometry # Axis to iterate upon if "z" in self._file.sizes: self._file.iter_axes = ["z"] if "c" in self._file.sizes: self._file.iter_axes = ["c"] if "t" in self._file.sizes: self._file.iter_axes = ["t"] if "v" in self._file.sizes: self._num_series = self._file.sizes["v"] self._file.iter_axes = ["v"] # Set the main axis for iteration self._iter_axis = self._file.iter_axes[0] def _parse_voxel_sizes(self) -> None: """ Parse metadata of ND2 file and extracts pixel size and z step. """ # Build the array step by step voxel_sizes = [0.0, 0.0, 0.0] if "pixel_microns" in self._file.metadata: p = self._file.metadata["pixel_microns"] voxel_sizes[0] = p voxel_sizes[1] = p # If there is only one plane, we leave the z step to 0.0 z_coords = None if self.num_planes > 1: if ( "z_coordinates" in self._file.metadata and self._file.metadata["z_coordinates"] is not None ): z_coords = np.array(self._file.metadata["z_coordinates"]) elif ( hasattr(self._file._parser._raw_metadata, "z_data") and self._file.parser._raw_metadata.z_data is not None ): z_coords = np.array(self._file.parser._raw_metadata.z_data) else: print("Could not read z coordinates!") if z_coords is not None: z_steps = np.zeros(self.num_series * self.num_timepoints) for i, z in enumerate(range(0, len(z_coords), self.num_planes)): z_range = z_coords[z : z + self.num_planes] z_steps[i] = np.mean(np.diff(z_range)) voxel_sizes[2] = z_steps.mean() # Set the voxel sizes self._voxel_sizes = tuple(voxel_sizes)
Instance variables
prop channel_names : tuple
-
Return the names of the channels.
Returns
channel_names
:tuple
- Voxel sizes (x, y, z)
Expand source code
@property def channel_names(self) -> tuple: """Return the names of the channels. Returns ------- channel_names: tuple Voxel sizes (x, y, z) """ if self._file is None: return () return tuple(self._file.metadata["channels"])
prop filename : str
-
Return the file name with full path.
Returns
filename
:str
- Full file name, or empty string if not set.
Expand source code
@property def filename(self) -> str: """Return the file name with full path. Returns ------- filename: str Full file name, or empty string if not set. """ if self._file is None: return "" return str(self._filename)
prop geometry : str
-
Return the geometry of the individual image or stack.
Returns
geometry
:str
- Geometry of the individual image or stack. If no file is open,
geometry
is "".
Expand source code
@property def geometry(self) -> str: """Return the geometry of the individual image or stack. Returns ------- geometry: str Geometry of the individual image or stack. If no file is open, `geometry` is "". """ if self._file is None: return "" return self._geometry
prop iter_axis : str
-
Return the axis used for iterating over individual images or stacks.
Returns
iter_axis
:str
- Axis used for iterating over individual images of stacks. If no file is open,
iter_axis
is "".
Expand source code
@property def iter_axis(self) -> str: """Return the axis used for iterating over individual images or stacks. Returns ------- iter_axis: str Axis used for iterating over individual images of stacks. If no file is open, `iter_axis` is "". """ if self._file is None: return "" return self._iter_axis
prop metadata : dict
-
Return the processed file metadata.
Returns
matadata
:dict
- Metadata dictionary. If no file is open,
metadata
is {}.
Expand source code
@property def metadata(self) -> dict: """Return the processed file metadata. Returns ------- matadata: dict Metadata dictionary. If no file is open, `metadata` is {}. """ if self._file is None: return {} return self._file.metadata
prop num_channels : int
-
Return the number of channels.
Returns
num_channels
:int
- Number of channels per series in the file. If no file is open,
num_channels
is 0.
Expand source code
@property def num_channels(self) -> int: """Return the number of channels. Returns ------- num_channels: int Number of channels per series in the file. If no file is open, `num_channels` is 0. """ if self._file is None: return 0 return self._num_channels
prop num_planes : int
-
Return the number of planes.
Returns
num_planes
:int
- Number of planes per series in the file. If no file is open,
num_planes
is 0.
Expand source code
@property def num_planes(self) -> int: """Return the number of planes. Returns ------- num_planes: int Number of planes per series in the file. If no file is open, `num_planes` is 0. """ if self._file is None: return 0 return self._num_planes
prop num_series : int
-
Return the number of series.
Returns
num_series
:int
- Number of series in the file. If no file is open,
num_series
is 0.
Expand source code
@property def num_series(self) -> int: """Return the number of series. Returns ------- num_series: int Number of series in the file. If no file is open, `num_series` is 0. """ if self._file is None: return 0 return self._num_series
prop num_timepoints : int
-
Return the number of timepoints.
Returns
num_timepoints
:int
- Number of timepoints per series in the file. If no file is open,
num_timepoints
is 0.
Expand source code
@property def num_timepoints(self) -> int: """Return the number of timepoints. Returns ------- num_timepoints: int Number of timepoints per series in the file. If no file is open, `num_timepoints` is 0. """ if self._file is None: return 0 return self._num_timepoints
prop raw_metadata : Optional[None]
-
Return the raw file metadata.
Returns
raw_metadata: object of type
RawMetadata
. If no file is open,raw_metadata
is None.Expand source code
@property def raw_metadata(self) -> Union[None, dict]: """Return the raw file metadata. Returns ------- raw_metadata: object of type [`RawMetadata`](https://open-science-tools.github.io/nd2reader/nd2reader.html#module-nd2reader.raw_metadata). If no file is open, `raw_metadata` is None. """ if self._file is None: return None return self._file.parser._raw_metadata
prop voxel_sizes : tuple
-
Return the global voxel sizes for the acquisition.
Returns
voxel_sizes
:tuple
- Voxel sizes
(vx, vy, vz)
Expand source code
@property def voxel_sizes(self) -> tuple: """Return the global voxel sizes for the acquisition. Returns ------- voxel_sizes: tuple Voxel sizes `(vx, vy, vz)` """ return self._voxel_sizes