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.

119 lines
3.3 KiB

7 years ago
7 years ago
  1. from pims import FramesSequenceND, Frame
  2. from nd2reader.parser import Parser
  3. import numpy as np
  4. class ND2Reader(FramesSequenceND):
  5. """
  6. PIMS wrapper for the ND2 parser
  7. """
  8. def __init__(self, filename):
  9. self.filename = filename
  10. # first use the parser to parse the file
  11. self._fh = open(filename, "rb")
  12. self._parser = Parser(self._fh)
  13. # Setup metadata
  14. self.metadata = self._parser.metadata
  15. # Set data type
  16. self._dtype = self._parser.get_dtype_from_metadata()
  17. # Setup the axes
  18. self._setup_axes()
  19. @classmethod
  20. def class_exts(cls):
  21. """
  22. So PIMS open function can use this reader for opening .nd2 files
  23. :return:
  24. """
  25. return {'nd2'} | super(ND2Reader, cls).class_exts()
  26. def close(self):
  27. """
  28. Correctly close the file handle
  29. :return:
  30. """
  31. if self._fh is not None:
  32. self._fh.close()
  33. def get_frame(self, i):
  34. """
  35. Return one frame
  36. :param i:
  37. :return:
  38. """
  39. fetch_all_channels = 'c' in self.bundle_axes
  40. if fetch_all_channels:
  41. return self._get_frame_all_channels(i)
  42. else:
  43. return self.get_frame_2D(self.default_coords['c'], i, self.default_coords['z'])
  44. def _get_frame_all_channels(self, i):
  45. """
  46. Get all color channels for this frame
  47. :return:
  48. """
  49. frames = None
  50. for c in range(len(self.metadata["channels"])):
  51. frame = self.get_frame_2D(c, i, self.default_coords['z'])
  52. if frames is None:
  53. frames = Frame([frame])
  54. else:
  55. frames = np.concatenate((frames, [frame]), axis=0)
  56. return frames
  57. def get_frame_2D(self, c, t, z):
  58. """
  59. Gets a given frame using the parser
  60. :param c:
  61. :param t:
  62. :param z:
  63. :return:
  64. """
  65. c_name = self.metadata["channels"][c]
  66. return self._parser.get_image_by_attributes(t, 0, c_name, z, self.metadata["height"], self.metadata["width"])
  67. @property
  68. def pixel_type(self):
  69. """
  70. Return the pixel data type
  71. :return:
  72. """
  73. return self._dtype
  74. def _setup_axes(self):
  75. """
  76. Setup the xyctz axes, iterate over t axis by default
  77. :return:
  78. """
  79. self._init_axis('x', self.metadata["width"])
  80. self._init_axis('y', self.metadata["height"])
  81. self._init_axis('c', len(self.metadata["channels"]))
  82. self._init_axis('t', len(self.metadata["frames"]))
  83. self._init_axis('z', len(self.metadata["z_levels"]))
  84. # provide the default
  85. self.iter_axes = 't'
  86. def get_timesteps(self):
  87. """
  88. Get the timesteps of the experiment
  89. :return:
  90. """
  91. timesteps = np.array([])
  92. current_time = 0.0
  93. for loop in self.metadata['experiment']['loops']:
  94. if loop['stimulation']:
  95. continue
  96. timesteps = np.concatenate(
  97. (timesteps, np.arange(current_time, current_time + loop['duration'], loop['sampling_interval'])))
  98. current_time += loop['duration']
  99. # if experiment did not finish, number of timesteps is wrong. Take correct amount of leading timesteps.
  100. return timesteps[:self.metadata['num_frames']]