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.

190 lines
7.1 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. # -*- coding: utf-8 -*-
  2. import array
  3. from datetime import datetime
  4. import logging
  5. from nd2reader.model import Image, ImageSet
  6. from nd2reader.parser import Nd2Parser
  7. import re
  8. import struct
  9. log = logging.getLogger(__name__)
  10. log.addHandler(logging.StreamHandler())
  11. log.setLevel(logging.WARNING)
  12. class Nd2(Nd2Parser):
  13. def __init__(self, filename):
  14. super(Nd2, self).__init__(filename)
  15. self._filename = filename
  16. def __repr__(self):
  17. return "\n".join(["ND2: %s" % self._filename,
  18. "Created: %s" % self.absolute_start.strftime("%Y-%m-%d %H:%M:%S"),
  19. "Image size: %sx%s (HxW)" % (self.height, self.width),
  20. "Image cycles: %s" % self.time_index_count,
  21. "Channels: %s" % ", ".join(["'%s'" % channel for channel in self.channels]),
  22. "Fields of View: %s" % self.field_of_view_count,
  23. "Z-Levels: %s" % self.z_level_count
  24. ])
  25. def __iter__(self):
  26. for i in range(self._image_count):
  27. for fov in range(self.field_of_view_count):
  28. for z_level in range(self.z_level_count):
  29. for channel_name in self.channels:
  30. image = self.get_image(i, fov, channel_name, z_level)
  31. if image is not None:
  32. yield image
  33. @property
  34. def image_sets(self):
  35. for time_index in xrange(self.time_index_count):
  36. image_set = ImageSet()
  37. for fov in range(self.field_of_view_count):
  38. for channel_name in self.channels:
  39. for z_level in xrange(self.z_level_count):
  40. image = self.get_image(time_index, fov, channel_name, z_level)
  41. if image is not None:
  42. image_set.add(image)
  43. yield image_set
  44. def get_image(self, time_index, fov, channel_name, z_level):
  45. image_set_number = self._calculate_image_set_number(time_index, fov, z_level)
  46. try:
  47. timestamp, raw_image_data = self._get_raw_image_data(image_set_number, self._channel_offset[channel_name])
  48. image = Image(timestamp, raw_image_data, fov, channel_name, z_level, self.height, self.width)
  49. except TypeError:
  50. return None
  51. else:
  52. return image
  53. @property
  54. def channels(self):
  55. metadata = self.metadata['ImageMetadataSeq']['SLxPictureMetadata']['sPicturePlanes']
  56. try:
  57. validity = self.metadata['ImageMetadata']['SLxExperiment']['ppNextLevelEx'][''][0]['ppNextLevelEx'][''][0]['pItemValid']
  58. except KeyError:
  59. # If none of the channels have been deleted, there is no validity list, so we just make one
  60. validity = [True for _ in metadata]
  61. # Channel information is contained in dictionaries with the keys a0, a1...an where the number
  62. # indicates the order in which the channel is stored. So by sorting the dicts alphabetically
  63. # we get the correct order.
  64. for (label, chan), valid in zip(sorted(metadata['sPlaneNew'].items()), validity):
  65. if not valid:
  66. continue
  67. yield chan['sDescription']
  68. @property
  69. def height(self):
  70. """
  71. :return: height of each image, in pixels
  72. """
  73. return self.metadata['ImageAttributes']['SLxImageAttributes']['uiHeight']
  74. @property
  75. def width(self):
  76. """
  77. :return: width of each image, in pixels
  78. """
  79. return self.metadata['ImageAttributes']['SLxImageAttributes']['uiWidth']
  80. @property
  81. def absolute_start(self):
  82. for line in self.metadata['ImageTextInfo']['SLxImageTextInfo'].values():
  83. absolute_start_12 = None
  84. absolute_start_24 = None
  85. # ND2s seem to randomly switch between 12- and 24-hour representations.
  86. try:
  87. absolute_start_24 = datetime.strptime(line, "%m/%d/%Y %H:%M:%S")
  88. except ValueError:
  89. pass
  90. try:
  91. absolute_start_12 = datetime.strptime(line, "%m/%d/%Y %I:%M:%S %p")
  92. except ValueError:
  93. pass
  94. if not absolute_start_12 and not absolute_start_24:
  95. continue
  96. return absolute_start_12 if absolute_start_12 else absolute_start_24
  97. raise ValueError("This ND2 has no recorded start time. This is probably a bug.")
  98. @property
  99. def channel_count(self):
  100. pattern = r""".*?λ\((\d+)\).*?"""
  101. try:
  102. count = int(re.match(pattern, self._dimensions).group(1))
  103. except AttributeError:
  104. return 1
  105. else:
  106. return count
  107. @property
  108. def field_of_view_count(self):
  109. """
  110. The metadata contains information about fields of view, but it contains it even if some fields
  111. of view were cropped. We can't find anything that states which fields of view are actually
  112. in the image data, so we have to calculate it. There probably is something somewhere, since
  113. NIS Elements can figure it out, but we haven't found it yet.
  114. """
  115. pattern = r""".*?XY\((\d+)\).*?"""
  116. try:
  117. count = int(re.match(pattern, self._dimensions).group(1))
  118. except AttributeError:
  119. return 1
  120. else:
  121. return count
  122. @property
  123. def time_index_count(self):
  124. """
  125. The number of cycles.
  126. :rtype: int
  127. """
  128. pattern = r""".*?T'\((\d+)\).*?"""
  129. try:
  130. count = int(re.match(pattern, self._dimensions).group(1))
  131. except AttributeError:
  132. return 1
  133. else:
  134. return count
  135. @property
  136. def z_level_count(self):
  137. pattern = r""".*?Z\((\d+)\).*?"""
  138. try:
  139. count = int(re.match(pattern, self._dimensions).group(1))
  140. except AttributeError:
  141. return 1
  142. else:
  143. return count
  144. @property
  145. def _channel_offset(self):
  146. """
  147. Image data is interleaved for each image set. That is, if there are four images in a set, the first image
  148. will consist of pixels 1, 5, 9, etc, the second will be pixels 2, 6, 10, and so forth. Why this would be the
  149. case is beyond me, but that's how it works.
  150. """
  151. channel_offset = {}
  152. for n, channel in enumerate(self.channels):
  153. channel_offset[channel] = n
  154. return channel_offset
  155. def _get_raw_image_data(self, image_set_number, channel_offset):
  156. chunk = self._label_map["ImageDataSeq|%d!" % image_set_number]
  157. data = self._read_chunk(chunk)
  158. timestamp = struct.unpack("d", data[:8])[0]
  159. # The images for the various channels are interleaved within each other.
  160. image_data = array.array("H", data)
  161. image_data_start = 4 + channel_offset
  162. if any(image_data):
  163. return timestamp, image_data[image_data_start::self.channel_count]
  164. return None
  165. def _calculate_image_set_number(self, time_index, fov, z_level):
  166. return time_index * self.field_of_view_count * self.z_level_count + (fov * self.z_level_count + z_level)