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.

180 lines
7.7 KiB

  1. # -*- coding: utf-8 -*-
  2. import array
  3. import numpy as np
  4. import struct
  5. from nd2reader.model.image import Image
  6. from nd2reader.common.v3 import read_chunk
  7. from nd2reader.exc import NoImageError
  8. class V3Driver(object):
  9. """
  10. Accesses images from ND2 files made with NIS Elements 4.x. Confusingly, files of this type have a version number of 3.0+.
  11. """
  12. def __init__(self, metadata, label_map, file_handle):
  13. """
  14. :param metadata: a Metadata object
  15. :param label_map: a raw dictionary of pointers to image locations
  16. :param file_handle: an open file handle to the ND2
  17. """
  18. self._metadata = metadata
  19. self._label_map = label_map
  20. self._file_handle = file_handle
  21. def calculate_image_properties(self, index):
  22. field_of_view = self._calculate_field_of_view(index)
  23. channel = self._calculate_channel(index)
  24. z_level = self._calculate_z_level(index)
  25. return field_of_view, channel, z_level
  26. def get_image(self, index):
  27. """
  28. Creates an Image object and adds its metadata, based on the index (which is simply the order in which the image was acquired). May return None if the ND2 contains
  29. multiple channels and not all were taken in each cycle (for example, if you take bright field images every minute, and GFP images every five minutes, there will be some
  30. indexes that do not contain an image. The reason for this is complicated, but suffice it to say that we hope to eliminate this possibility in future releases. For now,
  31. you'll need to check if your image is None if you're doing anything out of the ordinary.
  32. :type index: int
  33. :rtype: Image or None
  34. """
  35. field_of_view, channel, z_level = self.calculate_image_properties(index)
  36. channel_offset = index % len(self._metadata.channels)
  37. image_group_number = int(index / len(self._metadata.channels))
  38. frame_number = self._calculate_frame_number(image_group_number, field_of_view, z_level)
  39. try:
  40. timestamp, image = self._get_raw_image_data(image_group_number, channel_offset, self._metadata.height, self._metadata.width)
  41. except NoImageError:
  42. return None
  43. else:
  44. image.add_params(index, timestamp, frame_number, field_of_view, channel, z_level)
  45. return image
  46. def get_image_by_attributes(self, frame_number, field_of_view, channel_name, z_level, height, width):
  47. """
  48. Attempts to get Image based on attributes alone.
  49. :type frame_number: int
  50. :type field_of_view: int
  51. :type channel_name: str
  52. :type z_level: int
  53. :type height: int
  54. :type width: int
  55. :rtype: Image or None
  56. """
  57. image_group_number = self._calculate_image_group_number(frame_number, field_of_view, z_level)
  58. try:
  59. timestamp, raw_image_data = self._get_raw_image_data(image_group_number,
  60. self._channel_offset[channel_name],
  61. height,
  62. width)
  63. image = Image(raw_image_data)
  64. image.add_params(image_group_number, timestamp, frame_number, field_of_view, channel_name, z_level)
  65. except (TypeError, NoImageError):
  66. return None
  67. else:
  68. return image
  69. def _calculate_field_of_view(self, index):
  70. """
  71. Determines what field of view was being imaged for a given image.
  72. :type index: int
  73. :rtype: int
  74. """
  75. images_per_cycle = len(self._metadata.z_levels) * len(self._metadata.channels)
  76. return int((index - (index % images_per_cycle)) / images_per_cycle) % len(self._metadata.fields_of_view)
  77. def _calculate_channel(self, index):
  78. """
  79. Determines what channel a particular image is.
  80. :type index: int
  81. :rtype: str
  82. """
  83. return self._metadata.channels[index % len(self._metadata.channels)]
  84. def _calculate_z_level(self, index):
  85. """
  86. Determines the plane in the z-axis a given image was taken in. In the future, this will be replaced with the actual offset in micrometers.
  87. :type index: int
  88. :rtype: int
  89. """
  90. return self._metadata.z_levels[int(((index - (index % len(self._metadata.channels))) / len(self._metadata.channels)) % len(self._metadata.z_levels))]
  91. def _calculate_image_group_number(self, frame_number, fov, z_level):
  92. """
  93. Images are grouped together if they share the same time index, field of view, and z-level.
  94. :type frame_number: int
  95. :type fov: int
  96. :type z_level: int
  97. :rtype: int
  98. """
  99. return frame_number * len(self._metadata.fields_of_view) * len(self._metadata.z_levels) + (fov * len(self._metadata.z_levels) + z_level)
  100. def _calculate_frame_number(self, image_group_number, field_of_view, z_level):
  101. """
  102. Images are in the same frame if they share the same group number and field of view and are taken sequentially.
  103. :type image_group_number: int
  104. :type field_of_view: int
  105. :type z_level: int
  106. :rtype: int
  107. """
  108. return (image_group_number - (field_of_view * len(self._metadata.z_levels) + z_level)) / (len(self._metadata.fields_of_view) * len(self._metadata.z_levels))
  109. @property
  110. def _channel_offset(self):
  111. """
  112. Image data is interleaved for each image set. That is, if there are four images in a set, the first image
  113. will consist of pixels 1, 5, 9, etc, the second will be pixels 2, 6, 10, and so forth.
  114. :rtype: dict
  115. """
  116. return {channel: n for n, channel in enumerate(self._metadata.channels)}
  117. def _get_raw_image_data(self, image_group_number, channel_offset, height, width):
  118. """
  119. Reads the raw bytes and the timestamp of an image.
  120. :param image_group_number: groups are made of images with the same time index, field of view and z-level
  121. :type image_group_number: int
  122. :param channel_offset: the offset in the array where the bytes for this image are found
  123. :type channel_offset: int
  124. :rtype: (int, Image)
  125. :raises: NoImageError
  126. """
  127. chunk = self._label_map.get_image_data_location(image_group_number)
  128. data = read_chunk(self._file_handle, chunk)
  129. # All images in the same image group share the same timestamp! So if you have complicated image data,
  130. # your timestamps may not be entirely accurate. Practically speaking though, they'll only be off by a few
  131. # seconds unless you're doing something super weird.
  132. timestamp = struct.unpack("d", data[:8])[0]
  133. image_group_data = array.array("H", data)
  134. image_data_start = 4 + channel_offset
  135. # The images for the various channels are interleaved within the same array. For example, the second image
  136. # of a four image group will be composed of bytes 2, 6, 10, etc. If you understand why someone would design
  137. # a data structure that way, please send the author of this library a message.
  138. image_data = np.reshape(image_group_data[image_data_start::len(self._metadata.channels)], (height, width))
  139. # Skip images that are all zeros! This is important, since NIS Elements creates blank "gap" images if you
  140. # don't have the same number of images each cycle. We discovered this because we only took GFP images every
  141. # other cycle to reduce phototoxicity, but NIS Elements still allocated memory as if we were going to take
  142. # them every cycle.
  143. if np.any(image_data):
  144. return timestamp, Image(image_data)
  145. raise NoImageError