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.

200 lines
5.5 KiB

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