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.

196 lines
5.5 KiB

7 years ago
7 years ago
7 years ago
7 years ago
  1. from pims import FramesSequenceND, Frame
  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. def __init__(self, filename):
  10. super(self.__class__, self).__init__()
  11. self.filename = filename
  12. # first use the parser to parse the file
  13. self._fh = open(filename, "rb")
  14. self._parser = Parser(self._fh)
  15. # Setup metadata
  16. self.metadata = self._parser.metadata
  17. # Set data type
  18. self._dtype = self._parser.get_dtype_from_metadata()
  19. # Setup the axes
  20. self._setup_axes()
  21. # Other properties
  22. self._timesteps = None
  23. @classmethod
  24. def class_exts(cls):
  25. """Let PIMS open function use this reader for opening .nd2 files
  26. """
  27. return {'nd2'} | super(ND2Reader, cls).class_exts()
  28. def close(self):
  29. """Correctly close the file handle
  30. """
  31. if self._fh is not None:
  32. self._fh.close()
  33. def _get_default(self, coord):
  34. try:
  35. return self.default_coords[coord]
  36. except KeyError:
  37. return 0
  38. def get_frame_2D(self, c=0, t=0, z=0, x=0, y=0):
  39. """Gets a given frame using the parser
  40. Args:
  41. x: The x-index (pims expects this)
  42. y: The y-index (pims expects this)
  43. c: The color channel number
  44. t: The frame number
  45. z: The z stack number
  46. Returns:
  47. numpy.ndarray: The requested frame
  48. """
  49. try:
  50. c_name = self.metadata["channels"][c]
  51. except KeyError:
  52. c_name = self.metadata["channels"][0]
  53. x = self.metadata["width"] if x <= 0 else x
  54. y = self.metadata["height"] if y <= 0 else y
  55. return self._parser.get_image_by_attributes(t, 0, c_name, 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 frame_rate(self):
  82. """The (average) frame rate
  83. Returns:
  84. float: the (average) frame rate in frames per second
  85. """
  86. return 1000. / np.mean(np.diff(self.timesteps))
  87. def _get_metadata_property(self, key, default=None):
  88. if self.metadata is None:
  89. return default
  90. if key not in self.metadata:
  91. return default
  92. if self.metadata[key] is None:
  93. return default
  94. return self.metadata[key]
  95. def _setup_axes(self):
  96. """Setup the xyctz axes, iterate over t axis by default
  97. """
  98. self._init_axis_if_exists('x', self._get_metadata_property("width", default=0))
  99. self._init_axis_if_exists('y', self._get_metadata_property("height", default=0))
  100. self._init_axis_if_exists('c', len(self._get_metadata_property("channels", default=[])), min_size=2)
  101. self._init_axis_if_exists('t', len(self._get_metadata_property("frames", default=[])))
  102. self._init_axis_if_exists('z', len(self._get_metadata_property("z_levels", default=[])), min_size=2)
  103. if len(self.sizes) == 0:
  104. raise EmptyFileError("No axes were found for this .nd2 file.")
  105. # provide the default
  106. self.iter_axes = self._guess_default_iter_axis()
  107. def _init_axis_if_exists(self, axis, size, min_size=1):
  108. if size >= min_size:
  109. self._init_axis(axis, size)
  110. def _guess_default_iter_axis(self):
  111. """
  112. Guesses the default axis to iterate over based on axis sizes.
  113. Returns:
  114. the axis to iterate over
  115. """
  116. priority = ['t', 'z', 'c']
  117. found_axes = []
  118. for axis in priority:
  119. try:
  120. current_size = self.sizes[axis]
  121. except KeyError:
  122. continue
  123. if current_size > 1:
  124. return axis
  125. found_axes.append(axis)
  126. return found_axes[0]
  127. def get_timesteps(self):
  128. """Get the timesteps of the experiment
  129. Returns:
  130. np.ndarray: an array of times in milliseconds.
  131. """
  132. if self._timesteps is not None:
  133. return self._timesteps
  134. timesteps = np.array([])
  135. current_time = 0.0
  136. for loop in self.metadata['experiment']['loops']:
  137. if loop['stimulation']:
  138. continue
  139. if loop['sampling_interval'] == 0:
  140. # This is a loop were no data is acquired
  141. current_time += loop['duration']
  142. continue
  143. timesteps = np.concatenate(
  144. (timesteps, np.arange(current_time, current_time + loop['duration'], loop['sampling_interval'])))
  145. current_time += loop['duration']
  146. # if experiment did not finish, number of timesteps is wrong. Take correct amount of leading timesteps.
  147. self._timesteps = timesteps[:self.metadata['num_frames']]
  148. return self._timesteps