def __init__(self, fh): """ Arguments: fh {str} -- absolute path to .nd2 file fh {IO} -- input buffer handler (opened with "rb" mode) """ super(ND2Reader, self).__init__() self.filename = "" if isinstance(fh, str): if not fh.endswith(".nd2"): raise InvalidFileType( ("The file %s you want to read with nd2reader" % fh) + " does not have extension .nd2." ) self.filename = fh fh = open(fh, "rb") self._fh = fh 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() # Other properties self._timesteps = None
def __init__(self, filename, memmap=False): super(self.__class__, self).__init__() self.filename = filename # first use the parser to parse the file self._fh = MemmappableFile(self.filename, memmap=memmap) self._parser = Parser(self._fh) # Set data type self._dtype = self._parser.get_dtype_from_metadata()
def test_get_image(self): stitched_path = "test_data/test_stitched.nd2" if not path.isfile(stitched_path): file_name, header = urllib.request.urlretrieve( "https://downloads.openmicroscopy.org/images/ND2/karl/sample_image.nd2", stitched_path, ) with open(stitched_path, "rb") as fh: parser = Parser(fh) parser.get_image(0)
def __init__(self, filename, memmap=False): super(self.__class__, self).__init__() self.filename = filename # first use the parser to parse the file self._fh = MemmappableFile(self.filename, memmap=memmap) self._parser = Parser(self._fh) # Set data type self._dtype = self._parser.get_dtype_from_metadata() # Setup the axes self._setup_axes() # Other properties self._timesteps = None
def __init__(self, filename): super(ND2Reader, self).__init__() 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() # Other properties self._timesteps = None
def __init__(self, filename): super(ND2Reader, self).__init__() if not filename.endswith(".nd2"): raise InvalidFileType("The file %s you want to read with nd2reader does not have extension .nd2." % 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() # Other properties self._timesteps = None
class ND2ReaderForMetadata(ND2Reader): def __init__(self, filename): super(ND2Reader, self).__init__() 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() # Other properties self._timesteps = None def get_metadata(self): metadata_dict = self.metadata metadata_dict.pop('rois') metadata_dict.pop('z_levels') metadata_dict.pop('frames') metadata_dict.pop('date') metadata_dict['pfs_status'] = self._parser._raw_metadata.pfs_status metadata_dict['pfs_offset'] = self._parser._raw_metadata.pfs_offset metadata_dict['timesteps'] = self.timesteps metadata_dict['frame_rate'] = self.frame_rate return metadata_dict
class ND2Reader(FramesSequenceND): """PIMS wrapper for the ND2 parser. This is the main class: use this to process your .nd2 files. """ class_priority = 12 def __init__(self, filename): super(ND2Reader, self).__init__() if not filename.endswith(".nd2"): raise InvalidFileType("The file %s you want to read with nd2reader does not have extension .nd2." % 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() # Other properties self._timesteps = None @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_default(self, coord): try: return self.default_coords[coord] except KeyError: return 0 def get_frame_2D(self, c=0, t=0, z=0, x=0, y=0, v=0): """Gets a given frame using the parser Args: x: The x-index (pims expects this) y: The y-index (pims expects this) c: The color channel number t: The frame number z: The z stack number v: The field of view index Returns: pims.Frame: The requested frame """ # This needs to be set to width/height to return an image x = self.metadata["width"] y = self.metadata["height"] return self._parser.get_image_by_attributes(t, v, c, z, y, x) @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 @property def timesteps(self): """Get the timesteps of the experiment Returns: np.ndarray: an array of times in milliseconds. """ if self._timesteps is None: return self.get_timesteps() return self._timesteps @property def events(self): """Get the events of the experiment Returns: iterator of events as dict """ return self._get_metadata_property("events") @property def frame_rate(self): """The (average) frame rate Returns: float: the (average) frame rate in frames per second """ total_duration = 0.0 for loop in self.metadata['experiment']['loops']: total_duration += loop['duration'] if total_duration == 0: total_duration = self.timesteps[-1] if total_duration == 0: raise ValueError('Total measurement duration could not be determined from loops') return self.metadata['num_frames'] / (total_duration/1000.0) 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_if_exists('x', self._get_metadata_property("width", default=0)) self._init_axis_if_exists('y', self._get_metadata_property("height", default=0)) self._init_axis_if_exists('c', len(self._get_metadata_property("channels", default=[])), min_size=2) self._init_axis_if_exists('t', len(self._get_metadata_property("frames", default=[]))) self._init_axis_if_exists('z', len(self._get_metadata_property("z_levels", default=[])), min_size=2) self._init_axis_if_exists('v', len(self._get_metadata_property("fields_of_view", default=[])), min_size=2) if len(self.sizes) == 0: raise EmptyFileError("No axes were found for this .nd2 file.") # provide the default self.iter_axes = self._guess_default_iter_axis() self._register_get_frame(self.get_frame_2D, 'yx') def _init_axis_if_exists(self, axis, size, min_size=1): if size >= min_size: self._init_axis(axis, size) def _guess_default_iter_axis(self): """ Guesses the default axis to iterate over based on axis sizes. Returns: the axis to iterate over """ priority = ['t', 'z', 'c', 'v'] found_axes = [] for axis in priority: try: current_size = self.sizes[axis] except KeyError: continue if current_size > 1: return axis found_axes.append(axis) return found_axes[0] def get_timesteps(self): """Get the timesteps of the experiment Returns: np.ndarray: an array of times in milliseconds. """ if self._timesteps is not None and len(self._timesteps) > 0: return self._timesteps self._timesteps = np.array(list(self._parser._raw_metadata.acquisition_times), dtype=np.float) * 1000.0 return self._timesteps
class ND2ReaderSelfV3(ND2Reader): def __init__(self, filename): super(ND2Reader, self).__init__() 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() # Other properties self._timesteps = None def get_metadata(self): metadata_dict = self.metadata metadata_dict.pop('rois') try: metadata_dict['z_levels'] = list(metadata_dict.pop('z_levels')) metadata_dict['z_coordinates'] = metadata_dict.pop('z_coordinates') except Exception: pass metadata_dict.pop('frames') metadata_dict.pop('date') metadata_dict['pfs_status'] = self._parser._raw_metadata.pfs_status metadata_dict['pfs_offset'] = self._parser._raw_metadata.pfs_offset metadata_dict['timesteps'] = self.timesteps metadata_dict['frame_rate'] = self.frame_rate info_to_parse = self.parser._raw_metadata.image_text_info metadata_text_dict = self.parse_text_info(info_to_parse) metadata_dict = {**metadata_dict, **metadata_text_dict} info_to_parse = self.parser._raw_metadata.image_metadata_sequence metadata_dict_sequence = self.parse_sequence_info(info_to_parse) try: metadata_dict['EnableGainMultiplier'] = metadata_dict_sequence.pop('EnableGainMultiplier') metadata_dict['GainMultiplier'] = metadata_dict.pop('Multiplier') metadata_dict['Conversion_Gain'] = metadata_dict.pop('Conversion_Gain') except Exception: pass metadata_dict['Others'] = metadata_dict_sequence return metadata_dict def recursive_add_to_dict(self, dictionary): metadata_text_dict = {} for key_decoded, value_decoded in dictionary.items(): if type(key_decoded) is bytes: key_decoded = key_decoded.decode("utf-8") if type(value_decoded) is bytes: value_decoded = value_decoded.decode("utf-8") if type(value_decoded) == dict: return_dict = self.recursive_add_to_dict(value_decoded) metadata_text_dict = {**metadata_text_dict, **return_dict} elif type(value_decoded) != str: metadata_text_dict[key_decoded] = value_decoded else: pass return metadata_text_dict def parse_sequence_info(self, info_to_parse): main_part = info_to_parse[b'SLxPictureMetadata'] metadata_text_dict = self.recursive_add_to_dict(main_part) return metadata_text_dict @staticmethod def parse_text_info(info_to_parse): main_part = info_to_parse[b'SLxImageTextInfo'] metadata_text_dict = {} for key, value in main_part.items(): value_string = value.decode("utf-8") if value_string != '': split_string = value_string.split('\r\n') for line_number, line in enumerate(split_string): if line == '': continue if line_number == 0: # these are the headers, they do not have a value, only key key = key.decode("utf-8") if '5' in key or '6' in key: continue elif '9' in key: value = value_string key = 'date' elif '13' in key: value = value_string key = 'Objective' metadata_text_dict[key] = value continue try: key, value = line.split(':') # try to split, works for most except Exception as e: if "too many" in str(e): split_line = line.split(':') # microscope name has a : in it key = split_line[0] value = ":".join(split_line[1:]) elif "not enough" in str(e): continue if key == 'Metadata:' or key == '': # remove emtpy stuff and the metadata header key continue key = key.lstrip() # remove the spaces at the start of some keys if type(value) is str: value = value.lstrip() # remove the spaces at the start of some value that are strings key = key.replace(", ", "_") key = key.replace(" ", "_") # prevent spaces in key name, matlab does not like that if ',' in value: value = float(value.replace(',', '.')) metadata_text_dict[key] = value else: try: metadata_text_dict[key] = int(value) except Exception: metadata_text_dict[key] = value return metadata_text_dict
class ND2Reader(FramesSequenceND): """PIMS wrapper for the ND2 parser. This is the main class: use this to process your .nd2 files. """ class_priority = 12 def __init__(self, filename): super(self.__class__, self).__init__() 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() # Other properties self._timesteps = None @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_default(self, coord): try: return self.default_coords[coord] except KeyError: return 0 def get_frame_vczyx(self, v=None, c=None, t=None, z=None, x=None, y=None): x = self.metadata["width"] if x <= 0 else x y = self.metadata["height"] if y <= 0 else y result = [] for v in self._get_possible_coords('v', v): for c in self._get_possible_coords('c', c): for z in self._get_possible_coords('z', z): result.append( self._parser.get_image_by_attributes(t, v, c, z, y, x)) return np.squeeze(np.array(result, dtype=self._dtype)) def _get_possible_coords(self, dim, default): if dim in self.sizes: return [default] if default is not None else range(self.sizes[dim]) return [None] @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 @property def timesteps(self): """Get the timesteps of the experiment Returns: np.ndarray: an array of times in milliseconds. """ if self._timesteps is None: return self.get_timesteps() return self._timesteps @property def events(self): """Get the events of the experiment Returns: iterator of events as dict """ return self._get_metadata_property("events") @property def frame_rate(self): """The (average) frame rate Returns: float: the (average) frame rate in frames per second """ total_duration = 0.0 for loop in self.metadata['experiment']['loops']: total_duration += loop['duration'] if total_duration == 0: raise ValueError( 'Total measurement duration could not be determined from loops' ) return self.metadata['num_frames'] / (total_duration / 1000.0) 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_if_exists( 'x', self._get_metadata_property("width", default=0)) self._init_axis_if_exists( 'y', self._get_metadata_property("height", default=0)) self._init_axis_if_exists('c', len( self._get_metadata_property("channels", default=[])), min_size=2) self._init_axis_if_exists( 't', len(self._get_metadata_property("frames", default=[]))) self._init_axis_if_exists('z', len( self._get_metadata_property("z_levels", default=[])), min_size=2) self._init_axis_if_exists( 'v', len(self._get_metadata_property("fields_of_view", default=[])), min_size=2) if len(self.sizes) == 0: raise EmptyFileError("No axes were found for this .nd2 file.") # provide the default self.iter_axes = self._guess_default_iter_axis() self._register_get_frame(self.get_frame_vczyx, 'vczyx') self._register_get_frame(self.get_frame_vczyx, 'vzyx') self._register_get_frame(self.get_frame_vczyx, 'vcyx') self._register_get_frame(self.get_frame_vczyx, 'vyx') self._register_get_frame(self.get_frame_vczyx, 'czyx') self._register_get_frame(self.get_frame_vczyx, 'cyx') self._register_get_frame(self.get_frame_vczyx, 'zyx') self._register_get_frame(self.get_frame_vczyx, 'yx') def _init_axis_if_exists(self, axis, size, min_size=1): if size >= min_size: self._init_axis(axis, size) def _guess_default_iter_axis(self): """ Guesses the default axis to iterate over based on axis sizes. Returns: the axis to iterate over """ priority = ['t', 'z', 'c', 'v'] found_axes = [] for axis in priority: try: current_size = self.sizes[axis] except KeyError: continue if current_size > 1: return axis found_axes.append(axis) return found_axes[0] def get_timesteps(self): """Get the timesteps of the experiment Returns: np.ndarray: an array of times in milliseconds. """ if self._timesteps is not None and len(self._timesteps) > 0: return self._timesteps self._timesteps = np.array(list( self._parser._raw_metadata.acquisition_times), dtype=np.float) * 1000.0 return self._timesteps
class ND2Reader(FramesSequenceND): """PIMS wrapper for the ND2 parser. This is the main class: use this to process your .nd2 files. """ class_priority = 12 def __init__(self, filename): super(self.__class__, self).__init__() 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() # Other properties self._timesteps = None @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_default(self, coord): try: return self.default_coords[coord] except KeyError: return 0 def get_frame_2D(self, c=0, t=0, z=0, x=0, y=0, v=0): """Gets a given frame using the parser Args: x: The x-index (pims expects this) y: The y-index (pims expects this) c: The color channel number t: The frame number z: The z stack number v: The field of view index Returns: numpy.ndarray: The requested frame """ try: c_name = self.metadata["channels"][c] except KeyError: c_name = self.metadata["channels"][0] x = self.metadata["width"] if x <= 0 else x y = self.metadata["height"] if y <= 0 else y return self._parser.get_image_by_attributes(t, v, c_name, z, y, x) @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 @property def timesteps(self): """Get the timesteps of the experiment Returns: np.ndarray: an array of times in milliseconds. """ if self._timesteps is None: return self.get_timesteps() return self._timesteps @property def frame_rate(self): """The (average) frame rate Returns: float: the (average) frame rate in frames per second """ return 1000. / np.mean(np.diff(self.timesteps)) 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_if_exists( 'x', self._get_metadata_property("width", default=0)) self._init_axis_if_exists( 'y', self._get_metadata_property("height", default=0)) self._init_axis_if_exists('c', len( self._get_metadata_property("channels", default=[])), min_size=2) self._init_axis_if_exists( 't', len(self._get_metadata_property("frames", default=[]))) self._init_axis_if_exists('z', len( self._get_metadata_property("z_levels", default=[])), min_size=2) self._init_axis_if_exists( 'v', len(self._get_metadata_property("fields_of_view", default=[])), min_size=2) if len(self.sizes) == 0: raise EmptyFileError("No axes were found for this .nd2 file.") # provide the default self.iter_axes = self._guess_default_iter_axis() def _init_axis_if_exists(self, axis, size, min_size=1): if size >= min_size: self._init_axis(axis, size) def _guess_default_iter_axis(self): """ Guesses the default axis to iterate over based on axis sizes. Returns: the axis to iterate over """ priority = ['t', 'z', 'c', 'v'] found_axes = [] for axis in priority: try: current_size = self.sizes[axis] except KeyError: continue if current_size > 1: return axis found_axes.append(axis) return found_axes[0] def get_timesteps(self): """Get the timesteps of the experiment Returns: np.ndarray: an array of times in milliseconds. """ if self._timesteps is not None and len(timesteps) > 0: return self._timesteps timesteps = np.array([]) current_time = 0.0 for loop in self.metadata['experiment']['loops']: if loop['stimulation']: continue if loop['sampling_interval'] == 0: # This is a loop were no data is acquired current_time += loop['duration'] 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. self._timesteps = timesteps[:self.metadata['num_frames']] return self._timesteps
class ND2Reader: """PIMS wrapper for the ND2 parser. This is the main class: use this to process your .nd2 files. """ class_priority = 12 def __init__(self, filename, memmap=False): super(self.__class__, self).__init__() self.filename = filename # first use the parser to parse the file self._fh = MemmappableFile(self.filename, memmap=memmap) self._parser = Parser(self._fh) # Set data type self._dtype = self._parser.get_dtype_from_metadata() # Setup the axes # self._setup_axes() # Other properties # self._timesteps = None def reopen(self): # TODO: this is incredibly clunky fh = MemmappableFile(self.filename, memmap=self._fh.is_memmap) reader = copy(self) reader._fh = fh parser = copy(self._parser) parser._fh = fh raw_metadata = copy(parser._raw_metadata) raw_metadata._fh = fh parser._raw_metadata = raw_metadata reader._parser = parser return reader # @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_default(self, coord): try: return self.default_coords[coord] except KeyError: return 0 def get_frame_2D(self, c=0, t=0, z=0, x=0, y=0, v=0, memmap=False): """Gets a given frame using the parser Args: x: The x-index (pims expects this) y: The y-index (pims expects this) c: The color channel number t: The frame number z: The z stack number v: The field of view index Returns: numpy.ndarray: The requested frame """ try: c_name = self.metadata["channels"][c] except KeyError: c_name = self.metadata["channels"][0] x = self.metadata["width"] if x <= 0 else x y = self.metadata["height"] if y <= 0 else y return self._parser.get_image_by_attributes( t, v, c_name, z, y, x, memmap=memmap ) @property def parser(self): """ Returns the parser object. Returns: Parser: the parser object """ return self._parser @property def metadata(self): return self._parser.metadata # @property # def pixel_type(self): # """Return the pixel data type # Returns: # dtype: the pixel data type # """ # return self._dtype # @property # def timesteps(self): # """Get the timesteps of the experiment # Returns: # np.ndarray: an array of times in milliseconds. # """ # if self._timesteps is None: # return self.get_timesteps() # return self._timesteps # @property # def frame_rate(self): # """The (average) frame rate # Returns: # float: the (average) frame rate in frames per second # """ # total_duration = 0.0 # for loop in self.metadata["experiment"]["loops"]: # total_duration += loop["duration"] # if total_duration == 0: # raise ValueError( # "Total measurement duration could not be determined from loops" # ) # return self.metadata["num_frames"] / (total_duration / 1000.0) @property def sizes(self): return { "x": self.metadata["height"], "y": self.metadata["width"], "c": len(self.metadata["channels"]), "v": len(self.metadata["fields_of_view"]), "t": len(self.metadata["frames"]), } 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_if_exists("x", self._get_metadata_property("width", default=0)) # self._init_axis_if_exists("y", self._get_metadata_property("height", default=0)) # self._init_axis_if_exists( # "c", len(self._get_metadata_property("channels", default=[])), min_size=2 # ) # self._init_axis_if_exists( # "t", len(self._get_metadata_property("frames", default=[])) # ) # self._init_axis_if_exists( # "z", len(self._get_metadata_property("z_levels", default=[])), min_size=2 # ) # self._init_axis_if_exists( # "v", # len(self._get_metadata_property("fields_of_view", default=[])), # min_size=2, # ) # if len(self.sizes) == 0: # raise EmptyFileError("No axes were found for this .nd2 file.") # # provide the default # self.iter_axes = self._guess_default_iter_axis() # def _init_axis_if_exists(self, axis, size, min_size=1): # if size >= min_size: # self._init_axis(axis, size) # def _guess_default_iter_axis(self): # """ # Guesses the default axis to iterate over based on axis sizes. # Returns: # the axis to iterate over # """ # priority = ["t", "z", "c", "v"] # found_axes = [] # for axis in priority: # try: # current_size = self.sizes[axis] # except KeyError: # continue # if current_size > 1: # return axis # found_axes.append(axis) # return found_axes[0] def get_timesteps(self): """Get the timesteps of the experiment Returns: np.ndarray: an array of times in milliseconds. """ if self._timesteps is not None and len(self._timesteps) > 0: return self._timesteps self._timesteps = ( np.array(list(self._parser._raw_metadata.acquisition_times), dtype=np.float) * 1000.0 ) return self._timesteps
def test_can_open_test_file(self): self.create_test_nd2() with open(self.test_file, "rb") as fh: parser = Parser(fh) self.assertTrue(parser.supported)
class ND2ReaderForMetadata(ND2Reader): """ The ND2Reader based reader purely for metadata """ def __init__(self, filename): super(ND2Reader, self).__init__() 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() # Other properties self._timesteps = None def get_metadata(self, verbose=True): """ Get metadata. Reads out nd2 and returns the metadata """ # get base metadata metadata_dict = self.metadata # remove ROIs metadata_dict.pop('rois', None) # try to find z levels try: metadata_dict['z_levels'] = list(metadata_dict.pop('z_levels')) metadata_dict['z_coordinates'] = metadata_dict.pop('z_coordinates') except Exception: if verbose: logger.warning("ND2: Z-levels missing from metadata") # remove frames and date (for now) metadata_dict.pop('frames', None) metadata_dict.pop('date', None) # check pfs status try: metadata_dict['pfs_status'] = self._parser._raw_metadata.pfs_status metadata_dict['pfs_offset'] = self._parser._raw_metadata.pfs_offset except Exception: if verbose: logger.warning("ND2: PFS data missing from metadata") # check timesteps and frame rate try: metadata_dict['timesteps'] = self.timesteps / 1000 # divide by 1000 to convert to seconds metadata_dict['frame_rate'] = self.frame_rate except Exception: if verbose: logger.warning("ND2: Timestep data missing from metadata") # add more info try: info_to_parse = self.parser._raw_metadata.image_text_info metadata_text_dict = self.parse_text_info(info_to_parse) metadata_dict = {**metadata_dict, **metadata_text_dict} except Exception: if verbose: logger.warning("ND2: Detailed metadata missing") # add raw metadata try: info_to_parse = self.parser._raw_metadata.image_metadata_sequence metadata_dict_sequence = self.parse_sequence_info(info_to_parse) try: # move some important stuff to main metadata metadata_dict['EnableGainMultiplier'] = metadata_dict_sequence.pop('EnableGainMultiplier') metadata_dict['GainMultiplier'] = metadata_dict.pop('Multiplier') metadata_dict['Conversion_Gain'] = metadata_dict.pop('Conversion_Gain') except Exception: pass # move rest to others metadata_dict['Others'] = metadata_dict_sequence except Exception: if verbose: logger.warning("ND2: Raw metadata missing") # prevent None values by making None string for key, value in metadata_dict.items(): if value is None: metadata_dict[key] = str(value) return metadata_dict def recursive_add_to_dict(self, dictionary): """ Function to parse dictionaries in metadata since many include dictionaries in dictionaries ---------------------------------------------- :param dictionary: the dictionary to be parsed :return: the result of the dictionary """ metadata_text_dict = {} for key_decoded, value_decoded in dictionary.items(): # decode keys and values if type(key_decoded) is bytes: key_decoded = key_decoded.decode("utf-8") if type(value_decoded) is bytes: value_decoded = value_decoded.decode("utf-8") # if dict, add to restart function. Otherwise to add if type(value_decoded) == dict: return_dict = self.recursive_add_to_dict(value_decoded) metadata_text_dict = {**metadata_text_dict, **return_dict} elif type(value_decoded) != str: metadata_text_dict[key_decoded] = value_decoded else: pass return metadata_text_dict def parse_sequence_info(self, info_to_parse): """ Parses the metadata info of the sequence -------------------------------------------- :param info_to_parse: the info to parse from nd2 file :return: the parsed metadata """ main_part = info_to_parse[b'SLxPictureMetadata'] metadata_text_dict = self.recursive_add_to_dict(main_part) return metadata_text_dict @staticmethod def parse_text_info(info_to_parse): """ Parses the metadata info of the image -------------------------------------------- :param info_to_parse: the info to parse from nd2 file :return: the parsed metadata """ main_part = info_to_parse[b'SLxImageTextInfo'] metadata_text_dict = {} for key, value in main_part.items(): # decode value_string = value.decode("utf-8") if value_string != '': # if not empty, split split_string = value_string.split('\r\n') for line_number, line in enumerate(split_string): if line == '': continue if line_number == 0: # these are the headers, they do not have a value, only key key = key.decode("utf-8") # we do not want those if '5' in key or '6' in key: continue elif '9' in key: # this is the date value = value_string key = 'date' elif '13' in key: # this is the objective value = value_string key = 'Objective' # otherwise just add metadata_text_dict[key] = value continue try: key, value = line.split(':') # try to split, works for most except Exception as e: if "too many" in str(e): split_line = line.split(':') # microscope name has a : in it key = split_line[0] value = ":".join(split_line[1:]) elif "not enough" in str(e): continue if key == 'Metadata:' or key == '' or key.count(' ') == len(key): # remove emtpy stuff and the metadata header key. Last check is to see if only spaces continue key = key.lstrip() # remove the spaces at the start of some keys if type(value) is str: value = value.lstrip() # remove the spaces at the start of some value that are strings key = key.replace(", ", "_") key = key.replace(" ", "_") # prevent spaces in key name, matlab does not like that if ',' in value: value = float(value.replace(',', '.')) metadata_text_dict[key] = value else: try: metadata_text_dict[key] = int(value) except Exception: metadata_text_dict[key] = value return metadata_text_dict