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.

262 lines
7.9 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
10 years ago
  1. import numpy as np
  2. import skimage.io
  3. import logging
  4. from io import BytesIO
  5. import array
  6. import struct
  7. log = logging.getLogger("nd2reader")
  8. class Channel(object):
  9. def __init__(self, name, camera, exposure_time):
  10. self._name = name
  11. self._camera = camera
  12. self._exposure_time = exposure_time
  13. @property
  14. def name(self):
  15. return self._name
  16. @property
  17. def camera(self):
  18. return self._camera
  19. @property
  20. def exposure_time(self):
  21. return self._exposure_time
  22. class ImageSet(object):
  23. """
  24. A group of images that share the same timestamp. NIS Elements doesn't store a unique timestamp for every
  25. image, rather, it stores one for each set of images that share the same field of view and z-axis level.
  26. """
  27. def __init__(self):
  28. self._images = []
  29. def add(self, image):
  30. """
  31. :type image: nd2reader.model.Image()
  32. """
  33. self._images.append(image)
  34. def __iter__(self):
  35. for image in self._images:
  36. yield image
  37. class Image(object):
  38. def __init__(self, timestamp, raw_array, field_of_view, channel, z_level, height, width):
  39. self._timestamp = timestamp
  40. self._raw_data = raw_array
  41. self._field_of_view = field_of_view
  42. self._channel = channel
  43. self._z_level = z_level
  44. self._height = height
  45. self._width = width
  46. self._data = None
  47. @property
  48. def timestamp(self):
  49. """
  50. The number of seconds after the beginning of the acquisition that the image was taken. Note that for a given field
  51. of view and z-level offset, if you have images of multiple channels, they will all be given the same timestamp.
  52. No, this doesn't make much sense. But that's how ND2s are structured, so if your experiment depends on millisecond
  53. accuracy, you need to find an alternative imaging system.
  54. """
  55. return self._timestamp / 1000.0
  56. @property
  57. def channel(self):
  58. return self._channel
  59. @property
  60. def z_level(self):
  61. return self._z_level
  62. @property
  63. def data(self):
  64. if self._data is None:
  65. # The data is just a flat, 1-dimensional array. We convert it to a 2D array and cast the data points as 16-bit integers
  66. self._data = np.reshape(self._raw_data, (self._height, self._width)).astype(np.int64).astype(np.uint16)
  67. return self._data
  68. @property
  69. def is_valid(self):
  70. return np.any(self.data)
  71. def show(self):
  72. skimage.io.imshow(self.data)
  73. skimage.io.show()
  74. class MetadataItem(object):
  75. def __init__(self, start, data):
  76. self._datatype = ord(data[start])
  77. self._label_length = 2 * ord(data[start + 1])
  78. self._data = data
  79. @property
  80. def is_valid(self):
  81. return self._datatype > 0
  82. @property
  83. def key(self):
  84. return self._data[2:self._label_length].decode("utf16").encode("utf8")
  85. @property
  86. def length(self):
  87. return self._length
  88. @property
  89. def data_start(self):
  90. return self._label_length + 2
  91. @property
  92. def _body(self):
  93. """
  94. All data after the header.
  95. """
  96. return self._data[self.data_start:]
  97. def _get_bytes(self, count):
  98. return self._data[self.data_start: self.data_start + count]
  99. @property
  100. def value(self):
  101. parser = {1: self._parse_unsigned_char,
  102. 2: self._parse_unsigned_int,
  103. 3: self._parse_unsigned_int,
  104. 5: self._parse_unsigned_long,
  105. 6: self._parse_double,
  106. 8: self._parse_string,
  107. 9: self._parse_char_array,
  108. 11: self._parse_metadata_item
  109. }
  110. return parser[self._datatype]()
  111. def _parse_unsigned_char(self):
  112. self._length = 1
  113. return self._unpack("B", self._get_bytes(self._length))
  114. def _parse_unsigned_int(self):
  115. self._length = 4
  116. return self._unpack("I", self._get_bytes(self._length))
  117. def _parse_unsigned_long(self):
  118. self._length = 8
  119. return self._unpack("Q", self._get_bytes(self._length))
  120. def _parse_double(self):
  121. self._length = 8
  122. return self._unpack("d", self._get_bytes(self._length))
  123. def _parse_string(self):
  124. # the string is of unknown length but ends at the first instance of \x00\x00
  125. stop = self._body.index("\x00\x00")
  126. self._length = stop
  127. return self._body[:stop - 1].decode("utf16").encode("utf8")
  128. def _parse_char_array(self):
  129. array_length = self._unpack("Q", self._get_bytes(8))
  130. self._length = array_length + 8
  131. return array.array("B", self._body[8:array_length])
  132. def _parse_metadata_item(self):
  133. count, length = struct.unpack("<IQ", self._get_bytes(12))
  134. metadata_set = MetadataSet(self._body, 0, count)
  135. def _unpack(self, kind, data):
  136. """
  137. :param kind: the datatype to interpret the bytes as (see: https://docs.python.org/2/library/struct.html#struct-format-strings)
  138. :type kind: str
  139. :param data: the bytes to be converted
  140. :type data: bytes
  141. Parses a sequence of bytes and converts them to a Python data type.
  142. struct.unpack() returns a tuple but we only want the first element.
  143. """
  144. return struct.unpack(kind, data)[0]
  145. class MetadataSet(object):
  146. """
  147. A container of metadata items. Can contain other MetadataSet objects.
  148. """
  149. def __init__(self, data, start, item_count):
  150. self._items = []
  151. self._parse(data, start, item_count)
  152. def _parse(self, data, start, item_count):
  153. for item in range(item_count):
  154. metadata_item = MetadataItem(start, data)
  155. if not metadata_item.is_valid:
  156. break
  157. start += metadata_item.length
  158. class Chunkmap(object):
  159. def __init__(self):
  160. pass
  161. def read(self, filename):
  162. with open(filename, "rb") as f:
  163. data = f.read(-1)
  164. self.parse(data, 1)
  165. def parse(self, data, count):
  166. data = BytesIO(data)
  167. res = {}
  168. total_count = 0
  169. for c in range(count):
  170. lastpos = data.tell()
  171. total_count += 1
  172. hdr = data.read(2)
  173. if not hdr:
  174. break
  175. typ = ord(hdr[0])
  176. bname = data.read(2*ord(hdr[1]))
  177. name = bname.decode("utf16")[:-1].encode("utf8")
  178. if typ == 1:
  179. value, = struct.unpack("B", data.read(1))
  180. elif typ in [2, 3]:
  181. value, = struct.unpack("I", data.read(4))
  182. elif typ == 5:
  183. value, = struct.unpack("Q", data.read(8))
  184. elif typ == 6:
  185. value, = struct.unpack("d", data.read(8))
  186. elif typ == 8:
  187. value = data.read(2)
  188. while value[-2:] != "\x00\x00":
  189. value += data.read(2)
  190. value = value.decode("utf16")[:-1].encode("utf8")
  191. elif typ == 9:
  192. cnt, = struct.unpack("Q", data.read(8))
  193. value = array.array("B", data.read(cnt))
  194. elif typ == 11:
  195. curpos = data.tell()
  196. newcount, length = struct.unpack("<IQ", data.read(12))
  197. curpos = data.tell()
  198. length -= data.tell()-lastpos
  199. nextdata = data.read(length)
  200. value = self.parse(nextdata, newcount)
  201. # Skip some offsets
  202. data.read(newcount * 8)
  203. else:
  204. assert 0, "%s hdr %x:%x unknown" % (name, ord(hdr[0]), ord(hdr[1]))
  205. if not name in res:
  206. res[name] = value
  207. else:
  208. if not isinstance(res[name], list):
  209. res[name] = [res[name]]
  210. res[name].append(value)
  211. x = data.read()
  212. assert not x, "skip %d %s" % (len(x), repr(x[:30]))
  213. return res