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.

164 lines
4.7 KiB

8 years ago
8 years ago
8 years ago
8 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. c_name = self.metadata["channels"][c]
  48. x = self.metadata["width"] if x <= 0 else x
  49. y = self.metadata["height"] if y <= 0 else y
  50. return self._parser.get_image_by_attributes(t, 0, c_name, z, y, x)
  51. @property
  52. def parser(self):
  53. """
  54. Returns the parser object.
  55. Returns:
  56. Parser: the parser object
  57. """
  58. return self._parser
  59. @property
  60. def pixel_type(self):
  61. """Return the pixel data type
  62. Returns:
  63. dtype: the pixel data type
  64. """
  65. return self._dtype
  66. def _get_metadata_property(self, key, default=None):
  67. if self.metadata is None:
  68. return default
  69. if key not in self.metadata:
  70. return default
  71. if self.metadata[key] is None:
  72. return default
  73. return self.metadata[key]
  74. def _setup_axes(self):
  75. """Setup the xyctz axes, iterate over t axis by default
  76. """
  77. self._init_axis_if_exists('x', self._get_metadata_property("width", default=0))
  78. self._init_axis_if_exists('y', self._get_metadata_property("height", default=0))
  79. self._init_axis_if_exists('c', len(self._get_metadata_property("channels", default=[])))
  80. self._init_axis_if_exists('t', len(self._get_metadata_property("frames", default=[])))
  81. self._init_axis_if_exists('z', len(self._get_metadata_property("z_levels", default=[])))
  82. if len(self.sizes) == 0:
  83. raise EmptyFileError("No axes were found for this .nd2 file.")
  84. # provide the default
  85. self.iter_axes = self._guess_default_iter_axis()
  86. def _init_axis_if_exists(self, axis, size):
  87. if size > 0:
  88. self._init_axis(axis, size)
  89. def _guess_default_iter_axis(self):
  90. """
  91. Guesses the default axis to iterate over based on axis sizes.
  92. Returns:
  93. the axis to iterate over
  94. """
  95. priority = ['t', 'z', 'c']
  96. found_axes = []
  97. for axis in priority:
  98. try:
  99. current_size = self.sizes[axis]
  100. except KeyError:
  101. continue
  102. if current_size > 1:
  103. return axis
  104. found_axes.append(axis)
  105. return found_axes[0]
  106. def get_timesteps(self):
  107. """Get the timesteps of the experiment
  108. Returns:
  109. np.ndarray: an array of times in milliseconds.
  110. """
  111. timesteps = np.array([])
  112. current_time = 0.0
  113. for loop in self.metadata['experiment']['loops']:
  114. if loop['stimulation']:
  115. continue
  116. if loop['sampling_interval'] == 0:
  117. # This is a loop were no data is acquired
  118. current_time += loop['duration']
  119. continue
  120. timesteps = np.concatenate(
  121. (timesteps, np.arange(current_time, current_time + loop['duration'], loop['sampling_interval'])))
  122. current_time += loop['duration']
  123. # if experiment did not finish, number of timesteps is wrong. Take correct amount of leading timesteps.
  124. return timesteps[:self.metadata['num_frames']]