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.

184 lines
7.0 KiB

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