You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

215 lines
6.3 KiB

5 years ago
8 years ago
8 years ago
8 years ago
8 years ago
7 years ago
  1. from pims.base_frames import FramesSequenceND
  2. from nd2reader.exceptions import EmptyFileError
  3. from nd2reader.parser import Parser
  4. import numpy as np
  5. class ND2Reader(FramesSequenceND):
  6. """PIMS wrapper for the ND2 parser.
  7. This is the main class: use this to process your .nd2 files.
  8. """
  9. class_priority = 12
  10. def __init__(self, filename):
  11. super(self.__class__, self).__init__()
  12. self.filename = filename
  13. # first use the parser to parse the file
  14. self._fh = open(filename, "rb")
  15. self._parser = Parser(self._fh)
  16. # Setup metadata
  17. self.metadata = self._parser.metadata
  18. # Set data type
  19. self._dtype = self._parser.get_dtype_from_metadata()
  20. # Setup the axes
  21. self._setup_axes()
  22. # Other properties
  23. self._timesteps = None
  24. @classmethod
  25. def class_exts(cls):
  26. """Let PIMS open function use this reader for opening .nd2 files
  27. """
  28. return {'nd2'} | super(ND2Reader, cls).class_exts()
  29. def close(self):
  30. """Correctly close the file handle
  31. """
  32. if self._fh is not None:
  33. self._fh.close()
  34. def _get_default(self, coord):
  35. try:
  36. return self.default_coords[coord]
  37. except KeyError:
  38. return 0
  39. def get_frame_2D(self, c=0, t=0, z=0, x=0, y=0, v=0):
  40. """Fallback function for backwards compatibility
  41. """
  42. return self.get_frame_vczyx(v=v, c=c, t=t, z=z, x=x, y=y)
  43. def get_frame_vczyx(self, v=None, c=None, t=None, z=None, x=None, y=None):
  44. x = self.metadata["width"] if x <= 0 else x
  45. y = self.metadata["height"] if y <= 0 else y
  46. result = []
  47. for v in self._get_possible_coords('v', v):
  48. for c in self._get_possible_coords('c', c):
  49. for z in self._get_possible_coords('z', z):
  50. result.append(self._parser.get_image_by_attributes(t, v, c, z, y, x))
  51. return np.squeeze(np.array(result, dtype=self._dtype))
  52. def _get_possible_coords(self, dim, default):
  53. if dim in self.sizes:
  54. if dim in self.bundle_axes:
  55. return range(self.sizes[dim])
  56. else:
  57. return [default] if default is not None else range(self.sizes[dim])
  58. return [None]
  59. @property
  60. def parser(self):
  61. """
  62. Returns the parser object.
  63. Returns:
  64. Parser: the parser object
  65. """
  66. return self._parser
  67. @property
  68. def pixel_type(self):
  69. """Return the pixel data type
  70. Returns:
  71. dtype: the pixel data type
  72. """
  73. return self._dtype
  74. @property
  75. def timesteps(self):
  76. """Get the timesteps of the experiment
  77. Returns:
  78. np.ndarray: an array of times in milliseconds.
  79. """
  80. if self._timesteps is None:
  81. return self.get_timesteps()
  82. return self._timesteps
  83. @property
  84. def events(self):
  85. """Get the events of the experiment
  86. Returns:
  87. iterator of events as dict
  88. """
  89. return self._get_metadata_property("events")
  90. @property
  91. def frame_rate(self):
  92. """The (average) frame rate
  93. Returns:
  94. float: the (average) frame rate in frames per second
  95. """
  96. total_duration = 0.0
  97. for loop in self.metadata['experiment']['loops']:
  98. total_duration += loop['duration']
  99. if total_duration == 0:
  100. raise ValueError('Total measurement duration could not be determined from loops')
  101. return self.metadata['num_frames'] / (total_duration/1000.0)
  102. def _get_metadata_property(self, key, default=None):
  103. if self.metadata is None:
  104. return default
  105. if key not in self.metadata:
  106. return default
  107. if self.metadata[key] is None:
  108. return default
  109. return self.metadata[key]
  110. def _setup_axes(self):
  111. """Setup the xyctz axes, iterate over t axis by default
  112. """
  113. self._init_axis_if_exists('x', self._get_metadata_property("width", default=0))
  114. self._init_axis_if_exists('y', self._get_metadata_property("height", default=0))
  115. self._init_axis_if_exists('c', len(self._get_metadata_property("channels", default=[])), min_size=2)
  116. self._init_axis_if_exists('t', len(self._get_metadata_property("frames", default=[])))
  117. self._init_axis_if_exists('z', len(self._get_metadata_property("z_levels", default=[])), min_size=2)
  118. self._init_axis_if_exists('v', len(self._get_metadata_property("fields_of_view", default=[])), min_size=2)
  119. if len(self.sizes) == 0:
  120. raise EmptyFileError("No axes were found for this .nd2 file.")
  121. # provide the default
  122. self.iter_axes = self._guess_default_iter_axis()
  123. self._register_get_frame(self.get_frame_vczyx, 'vczyx')
  124. self._register_get_frame(self.get_frame_vczyx, 'vzyx')
  125. self._register_get_frame(self.get_frame_vczyx, 'vcyx')
  126. self._register_get_frame(self.get_frame_vczyx, 'vyx')
  127. self._register_get_frame(self.get_frame_vczyx, 'czyx')
  128. self._register_get_frame(self.get_frame_vczyx, 'cyx')
  129. self._register_get_frame(self.get_frame_vczyx, 'zyx')
  130. self._register_get_frame(self.get_frame_vczyx, 'yx')
  131. def _init_axis_if_exists(self, axis, size, min_size=1):
  132. if size >= min_size:
  133. self._init_axis(axis, size)
  134. def _guess_default_iter_axis(self):
  135. """
  136. Guesses the default axis to iterate over based on axis sizes.
  137. Returns:
  138. the axis to iterate over
  139. """
  140. priority = ['t', 'z', 'c', 'v']
  141. found_axes = []
  142. for axis in priority:
  143. try:
  144. current_size = self.sizes[axis]
  145. except KeyError:
  146. continue
  147. if current_size > 1:
  148. return axis
  149. found_axes.append(axis)
  150. return found_axes[0]
  151. def get_timesteps(self):
  152. """Get the timesteps of the experiment
  153. Returns:
  154. np.ndarray: an array of times in milliseconds.
  155. """
  156. if self._timesteps is not None and len(self._timesteps) > 0:
  157. return self._timesteps
  158. self._timesteps = np.array(list(self._parser._raw_metadata.acquisition_times), dtype=np.float) * 1000.0
  159. return self._timesteps