|
|
- from pims import FramesSequenceND, Frame
- from nd2reader.parser import Parser
- import numpy as np
-
-
- class ND2Reader(FramesSequenceND):
- """PIMS wrapper for the ND2 parser.
- This is the main class: use this to process your .nd2 files.
- """
-
- def __init__(self, filename):
- self.filename = filename
-
- # first use the parser to parse the file
- self._fh = open(filename, "rb")
- self._parser = Parser(self._fh)
-
- # Setup metadata
- self.metadata = self._parser.metadata
-
- # Set data type
- self._dtype = self._parser.get_dtype_from_metadata()
-
- # Setup the axes
- self._setup_axes()
-
- @classmethod
- def class_exts(cls):
- """Let PIMS open function use this reader for opening .nd2 files
-
- """
- return {'nd2'} | super(ND2Reader, cls).class_exts()
-
- def close(self):
- """Correctly close the file handle
-
- """
- if self._fh is not None:
- self._fh.close()
-
- def get_frame(self, i):
- """Return one frame
-
- Args:
- i: The frame number
-
- Returns:
- numpy.ndarray: The requested frame
-
- """
- fetch_all_channels = 'c' in self.bundle_axes
-
- if fetch_all_channels:
- return self._get_frame_all_channels(i)
- else:
- return self.get_frame_2D(self.default_coords['c'], i, self.default_coords['z'])
-
- def _get_frame_all_channels(self, i):
- """Get all color channels for this frame
-
- Args:
- i: The frame number
-
- Returns:
- numpy.ndarray: The requested frame, with all color channels.
-
- """
- frames = None
- for c in range(len(self.metadata["channels"])):
- frame = self.get_frame_2D(c, i, self.default_coords['z'])
- if frames is None:
- frames = Frame([frame])
- else:
- frames = np.concatenate((frames, [frame]), axis=0)
- return frames
-
- def get_frame_2D(self, c, t, z):
- """Gets a given frame using the parser
-
- Args:
- c: The color channel number
- t: The frame number
- z: The z stack number
-
- Returns:
- numpy.ndarray: The requested frame
-
- """
- c_name = self.metadata["channels"][c]
- return self._parser.get_image_by_attributes(t, 0, c_name, z, self.metadata["height"], self.metadata["width"])
-
- @property
- def parser(self):
- """
- Returns the parser object.
- Returns:
- Parser: the parser object
- """
- return self._parser
-
- @property
- def pixel_type(self):
- """Return the pixel data type
-
- Returns:
- dtype: the pixel data type
-
- """
- return self._dtype
-
- def _get_metadata_property(self, key, default=None):
- if self.metadata is None:
- return default
-
- if key not in self.metadata:
- return default
-
- if self.metadata[key] is None:
- return default
-
- return self.metadata[key]
-
- def _setup_axes(self):
- """Setup the xyctz axes, iterate over t axis by default
-
- """
- self._init_axis('x', self._get_metadata_property("width", default=0))
- self._init_axis('y', self._get_metadata_property("height", default=0))
- self._init_axis('c', len(self._get_metadata_property("channels", default=[])))
- self._init_axis('t', len(self._get_metadata_property("frames", default=[])))
- self._init_axis('z', len(self._get_metadata_property("z_levels", default=[])))
-
- # provide the default
- self.iter_axes = 't'
-
- def get_timesteps(self):
- """Get the timesteps of the experiment
-
- Returns:
- np.ndarray: an array of times in milliseconds.
-
- """
- timesteps = np.array([])
- current_time = 0.0
- for loop in self.metadata['experiment']['loops']:
- if loop['stimulation']:
- continue
-
- timesteps = np.concatenate(
- (timesteps, np.arange(current_time, current_time + loop['duration'], loop['sampling_interval'])))
- current_time += loop['duration']
-
- # if experiment did not finish, number of timesteps is wrong. Take correct amount of leading timesteps.
- return timesteps[:self.metadata['num_frames']]
|