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.

168 lines
4.8 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. @classmethod
  22. def class_exts(cls):
  23. """Let PIMS open function use this reader for opening .nd2 files
  24. """
  25. return {'nd2'} | super(ND2Reader, cls).class_exts()
  26. def close(self):
  27. """Correctly close the file handle
  28. """
  29. if self._fh is not None:
  30. self._fh.close()
  31. def _get_default(self, coord):
  32. try:
  33. return self.default_coords[coord]
  34. except KeyError:
  35. return 0
  36. def get_frame_2D(self, c=0, t=0, z=0, x=0, y=0):
  37. """Gets a given frame using the parser
  38. Args:
  39. x: The x-index (pims expects this)
  40. y: The y-index (pims expects this)
  41. c: The color channel number
  42. t: The frame number
  43. z: The z stack number
  44. Returns:
  45. numpy.ndarray: The requested frame
  46. """
  47. try:
  48. c_name = self.metadata["channels"][c]
  49. except KeyError:
  50. c_name = self.metadata["channels"][0]
  51. x = self.metadata["width"] if x <= 0 else x
  52. y = self.metadata["height"] if y <= 0 else y
  53. return self._parser.get_image_by_attributes(t, 0, c_name, z, y, x)
  54. @property
  55. def parser(self):
  56. """
  57. Returns the parser object.
  58. Returns:
  59. Parser: the parser object
  60. """
  61. return self._parser
  62. @property
  63. def pixel_type(self):
  64. """Return the pixel data type
  65. Returns:
  66. dtype: the pixel data type
  67. """
  68. return self._dtype
  69. def _get_metadata_property(self, key, default=None):
  70. if self.metadata is None:
  71. return default
  72. if key not in self.metadata:
  73. return default
  74. if self.metadata[key] is None:
  75. return default
  76. return self.metadata[key]
  77. def _setup_axes(self):
  78. """Setup the xyctz axes, iterate over t axis by default
  79. """
  80. self._init_axis_if_exists('x', self._get_metadata_property("width", default=0))
  81. self._init_axis_if_exists('y', self._get_metadata_property("height", default=0))
  82. self._init_axis_if_exists('c', len(self._get_metadata_property("channels", default=[])), min_size=2)
  83. self._init_axis_if_exists('t', len(self._get_metadata_property("frames", default=[])))
  84. self._init_axis_if_exists('z', len(self._get_metadata_property("z_levels", default=[])), min_size=2)
  85. if len(self.sizes) == 0:
  86. raise EmptyFileError("No axes were found for this .nd2 file.")
  87. # provide the default
  88. self.iter_axes = self._guess_default_iter_axis()
  89. def _init_axis_if_exists(self, axis, size, min_size=1):
  90. if size >= min_size:
  91. self._init_axis(axis, size)
  92. def _guess_default_iter_axis(self):
  93. """
  94. Guesses the default axis to iterate over based on axis sizes.
  95. Returns:
  96. the axis to iterate over
  97. """
  98. priority = ['t', 'z', 'c']
  99. found_axes = []
  100. for axis in priority:
  101. try:
  102. current_size = self.sizes[axis]
  103. except KeyError:
  104. continue
  105. if current_size > 1:
  106. return axis
  107. found_axes.append(axis)
  108. return found_axes[0]
  109. def get_timesteps(self):
  110. """Get the timesteps of the experiment
  111. Returns:
  112. np.ndarray: an array of times in milliseconds.
  113. """
  114. timesteps = np.array([])
  115. current_time = 0.0
  116. for loop in self.metadata['experiment']['loops']:
  117. if loop['stimulation']:
  118. continue
  119. if loop['sampling_interval'] == 0:
  120. # This is a loop were no data is acquired
  121. current_time += loop['duration']
  122. continue
  123. timesteps = np.concatenate(
  124. (timesteps, np.arange(current_time, current_time + loop['duration'], loop['sampling_interval'])))
  125. current_time += loop['duration']
  126. # if experiment did not finish, number of timesteps is wrong. Take correct amount of leading timesteps.
  127. return timesteps[:self.metadata['num_frames']]