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.

157 lines
5.2 KiB

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