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.

151 lines
5.8 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
9 years ago
10 years ago
9 years ago
10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. # -*- coding: utf-8 -*-
  2. from nd2reader.model import Image, ImageSet
  3. from nd2reader.parser import Nd2Parser
  4. import six
  5. class Nd2(Nd2Parser):
  6. """
  7. Allows easy access to NIS Elements .nd2 image files.
  8. """
  9. def __init__(self, filename):
  10. super(Nd2, self).__init__(filename)
  11. self._filename = filename
  12. def __repr__(self):
  13. return "\n".join(["<ND2 %s>" % self._filename,
  14. "Created: %s" % self.absolute_start.strftime("%Y-%m-%d %H:%M:%S"),
  15. "Image size: %sx%s (HxW)" % (self.height, self.width),
  16. "Image cycles: %s" % len(self.time_indexes),
  17. "Channels: %s" % ", ".join(["'%s'" % str(channel) for channel in self.channels]),
  18. "Fields of View: %s" % len(self.fields_of_view),
  19. "Z-Levels: %s" % len(self.z_levels)
  20. ])
  21. def __len__(self):
  22. """
  23. This should be the total number of images in the ND2, but it may be inaccurate. If the ND2 contains a
  24. different number of images in a cycle (i.e. there are "gap" images) it will be higher than reality.
  25. :rtype: int
  26. """
  27. return self._total_images_per_channel * len(self.channels)
  28. def __getitem__(self, item):
  29. """
  30. Allows slicing ND2s.
  31. >>> nd2 = Nd2("my_images.nd2")
  32. >>> image = nd2[16] # gets 17th frame
  33. >>> for image in nd2[100:200]: # iterate over the 100th to 200th images
  34. >>> do_something(image.data)
  35. >>> for image in nd2[::-1]: # iterate backwards
  36. >>> do_something(image.data)
  37. >>> for image in nd2[37:422:17]: # do something super weird if you really want to
  38. >>> do_something(image.data)
  39. :type item: int or slice
  40. :rtype: nd2reader.model.Image() or generator
  41. """
  42. if isinstance(item, int):
  43. try:
  44. channel_offset = item % len(self.channels)
  45. fov = self._calculate_field_of_view(item)
  46. channel = self._calculate_channel(item)
  47. z_level = self._calculate_z_level(item)
  48. timestamp, raw_image_data = self._get_raw_image_data(item, channel_offset)
  49. image = Image(timestamp, raw_image_data, fov, channel, z_level, self.height, self.width)
  50. except (TypeError, ValueError):
  51. return None
  52. except KeyError:
  53. raise IndexError("Invalid frame number.")
  54. else:
  55. return image
  56. elif isinstance(item, slice):
  57. return self._slice(item.start, item.stop, item.step)
  58. raise IndexError
  59. def _slice(self, start, stop, step):
  60. """
  61. Allows for iteration over a selection of the entire dataset.
  62. :type start: int
  63. :type stop: int
  64. :type step: int
  65. :rtype: nd2reader.model.Image() or None
  66. """
  67. start = start if start is not None else 0
  68. step = step if step is not None else 1
  69. stop = stop if stop is not None else len(self)
  70. # This weird thing with the step allows you to iterate backwards over the images
  71. for i in range(start, stop)[::step]:
  72. yield self[i]
  73. @property
  74. def image_sets(self):
  75. """
  76. Iterates over groups of related images. This is useful if your ND2 contains multiple fields of view.
  77. A typical use case might be that you have, say, four areas of interest that you're monitoring, and every
  78. minute you take a bright field and GFP image of each one. For each cycle, this method would produce four
  79. ImageSet objects, each containing one bright field and one GFP image.
  80. :return: model.ImageSet()
  81. """
  82. for time_index in self.time_indexes:
  83. image_set = ImageSet()
  84. for fov in self.fields_of_view:
  85. for channel_name in self.channels:
  86. for z_level in self.z_levels:
  87. image = self.get_image(time_index, fov, channel_name, z_level)
  88. if image is not None:
  89. image_set.add(image)
  90. yield image_set
  91. @property
  92. def height(self):
  93. """
  94. :return: height of each image, in pixels
  95. :rtype: int
  96. """
  97. return self.metadata[six.b('ImageAttributes')][six.b('SLxImageAttributes')][six.b('uiHeight')]
  98. @property
  99. def width(self):
  100. """
  101. :return: width of each image, in pixels
  102. :rtype: int
  103. """
  104. return self.metadata[six.b('ImageAttributes')][six.b('SLxImageAttributes')][six.b('uiWidth')]
  105. def get_image(self, time_index, field_of_view, channel_name, z_level):
  106. """
  107. Returns an Image if data exists for the given parameters, otherwise returns None. In general, you should avoid
  108. using this method unless you're very familiar with the structure of ND2 files.
  109. :param time_index: the frame number
  110. :type time_index: int
  111. :param field_of_view: the label for the place in the XY-plane where this image was taken.
  112. :type field_of_view: int
  113. :param channel_name: the name of the color of this image
  114. :type channel_name: str
  115. :param z_level: the label for the location in the Z-plane where this image was taken.
  116. :type z_level: int
  117. :rtype: nd2reader.model.Image() or None
  118. """
  119. image_group_number = self._calculate_image_group_number(time_index, field_of_view, z_level)
  120. try:
  121. timestamp, raw_image_data = self._get_raw_image_data(image_group_number, self._channel_offset[channel_name])
  122. image = Image(timestamp, raw_image_data, field_of_view, channel_name, z_level, self.height, self.width)
  123. except TypeError:
  124. return None
  125. else:
  126. return image