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.

307 lines
10 KiB

  1. """Functions to create artificial nd2 data for testing purposes
  2. """
  3. import six
  4. import numpy as np
  5. import struct
  6. from nd2reader.common import check_or_make_dir
  7. from os import path
  8. class ArtificialND2(object):
  9. """Artificial ND2 class (for testing purposes)
  10. """
  11. header = 0xabeceda
  12. relative_offset = 0
  13. data_types = {'unsigned_char': 1,
  14. 'unsigned_int': 2,
  15. 'unsigned_int_2': 3,
  16. 'unsigned_long': 5,
  17. 'double': 6,
  18. 'string': 8,
  19. 'char_array': 9,
  20. 'metadata_item': 11,
  21. }
  22. def __init__(self, file, version=(3, 0), skip_blocks=None):
  23. self.version = version
  24. self.raw_text, self.locations, self.data = b'', None, None
  25. check_or_make_dir(path.dirname(file))
  26. self._fh = open(file, 'w+b', 0)
  27. self.write_file(skip_blocks)
  28. def __enter__(self):
  29. return self
  30. def __exit__(self, exc_type, exc_value, traceback):
  31. self.close()
  32. @property
  33. def file_handle(self):
  34. """The file handle to the binary file
  35. Returns:
  36. file: the file handle
  37. """
  38. return self._fh
  39. def close(self):
  40. """Correctly close the file handle
  41. """
  42. if self._fh is not None:
  43. self._fh.close()
  44. def write_file(self, skip_blocks=None):
  45. if skip_blocks is None:
  46. skip_blocks = []
  47. if 'version' not in skip_blocks:
  48. # write version header at start of the file
  49. self.write_version()
  50. if 'label_map' not in skip_blocks:
  51. # write label map + data in the center
  52. self.locations, self.data = self.write_label_map()
  53. if 'label_map_marker' not in skip_blocks:
  54. # write start position of label map at the end of the file
  55. self.write_label_map_info()
  56. # write all to file
  57. self._fh.write(self.raw_text)
  58. def write_version(self):
  59. """Write file header
  60. """
  61. # write 16 empty bytes
  62. self.raw_text += bytearray(16)
  63. # write version info
  64. self.raw_text += self._get_version_string()
  65. def write_label_map_info(self):
  66. """Write the location of the start of the label map at the end of the file
  67. """
  68. location = self._get_version_byte_length()
  69. self.raw_text += struct.pack("Q", location)
  70. def _get_version_string(self):
  71. return six.b('ND2 FILE SIGNATURE CHUNK NAME01!Ver%s.%s' % self.version)
  72. def _get_version_byte_length(self):
  73. return 16 + len(self._get_version_string())
  74. def write_label_map(self):
  75. raw_text, locations, data = self.create_label_map_bytes()
  76. self.raw_text += raw_text
  77. return locations, data
  78. def create_label_map_bytes(self):
  79. """Construct a binary label map
  80. Returns:
  81. tuple: (binary data, dictionary data)
  82. """
  83. raw_text = six.b('')
  84. labels = [
  85. 'image_attributes',
  86. 'image_text_info',
  87. 'image_metadata',
  88. 'image_metadata_sequence',
  89. 'image_calibration',
  90. 'x_data',
  91. 'y_data',
  92. 'z_data',
  93. 'roi_metadata',
  94. 'pfs_status',
  95. 'pfs_offset',
  96. 'guid',
  97. 'description',
  98. 'camera_exposure_time',
  99. 'camera_temp',
  100. 'acquisition_times',
  101. 'acquisition_times_2',
  102. 'acquisition_frames',
  103. 'lut_data',
  104. 'grabber_settings',
  105. 'custom_data',
  106. 'app_info',
  107. 'image_frame_0'
  108. ]
  109. file_labels = [
  110. "ImageAttributesLV!",
  111. "ImageTextInfoLV!",
  112. "ImageMetadataLV!",
  113. "ImageMetadataSeqLV|0!",
  114. "ImageCalibrationLV|0!",
  115. "CustomData|X!",
  116. "CustomData|Y!",
  117. "CustomData|Z!",
  118. "CustomData|RoiMetadata_v1!",
  119. "CustomData|PFS_STATUS!",
  120. "CustomData|PFS_OFFSET!",
  121. "CustomData|GUIDStore!",
  122. "CustomData|CustomDescriptionV1_0!",
  123. "CustomData|Camera_ExposureTime1!",
  124. "CustomData|CameraTemp1!",
  125. "CustomData|AcqTimesCache!",
  126. "CustomData|AcqTimes2Cache!",
  127. "CustomData|AcqFramesCache!",
  128. "CustomDataVar|LUTDataV1_0!",
  129. "CustomDataVar|GrabberCameraSettingsV1_0!",
  130. "CustomDataVar|CustomDataV2_0!",
  131. "CustomDataVar|AppInfo_V1_0!",
  132. "ImageDataSeq|0!"
  133. ]
  134. file_data, file_data_dict = self._get_file_data(labels)
  135. locations = {}
  136. # generate random positions and lengths
  137. version_length = self._get_version_byte_length()
  138. # calculate data length
  139. label_length = np.sum([len(six.b(l)) + 16 for l in file_labels])
  140. # write label map
  141. cur_pos = version_length + label_length
  142. for label, file_label, data in zip(labels, file_labels, file_data):
  143. raw_text += six.b(file_label)
  144. data_length = len(data)
  145. raw_text += struct.pack('QQ', cur_pos, data_length)
  146. locations[label] = (cur_pos, data_length)
  147. cur_pos += data_length
  148. # write data
  149. raw_text += six.b('').join(file_data)
  150. return raw_text, locations, file_data_dict
  151. def _pack_data_with_metadata(self, data):
  152. packed_data = self._pack_raw_data_with_metadata(data)
  153. raw_data = struct.pack("IIQ", self.header, self.relative_offset, len(packed_data))
  154. raw_data += packed_data
  155. return raw_data
  156. def _pack_raw_data_with_metadata(self, data):
  157. raw_data = b''
  158. if isinstance(data, dict):
  159. raw_data = self._pack_dict_with_metadata(data)
  160. elif isinstance(data, int):
  161. raw_data = struct.pack('I', data)
  162. elif isinstance(data, float):
  163. raw_data = struct.pack('d', data)
  164. elif isinstance(data, str):
  165. raw_data = self._str_to_padded_bytes(data)
  166. return raw_data
  167. def _get_data_type(self, data):
  168. if isinstance(data, dict):
  169. return self.data_types['metadata_item']
  170. elif isinstance(data, int):
  171. return self.data_types['unsigned_int']
  172. elif isinstance(data, str):
  173. return self.data_types['string']
  174. else:
  175. return self.data_types['double']
  176. @staticmethod
  177. def _str_to_padded_bytes(data):
  178. return six.b('').join([struct.pack('cx', six.b(s)) for s in data]) + struct.pack('xx')
  179. def _pack_dict_with_metadata(self, data):
  180. raw_data = b''
  181. for data_key in data.keys():
  182. # names have always one character extra and are padded in zero bytes???
  183. b_data_key = self._str_to_padded_bytes(data_key)
  184. # header consists of data type and length of key name, it is represented by 2 unsigned chars
  185. raw_data += struct.pack('BB', self._get_data_type(data[data_key]), len(data_key) + 1)
  186. raw_data += b_data_key
  187. sub_data = self._pack_raw_data_with_metadata(data[data_key])
  188. if isinstance(data[data_key], dict):
  189. # Pack: the number of keys and the length of raw data until now, sub data
  190. # and the 12 bytes that we add now
  191. raw_data += struct.pack("<IQ", len(data[data_key].keys()), len(sub_data) + len(raw_data) + 12)
  192. raw_data += sub_data
  193. if isinstance(data[data_key], dict):
  194. # apparently there is also a huge empty space
  195. raw_data += b''.join([struct.pack('x')] * len(data[data_key].keys()) * 8)
  196. return raw_data
  197. def _get_file_data(self, labels):
  198. file_data = [
  199. {
  200. 'SLxImageAttributes':
  201. {
  202. 'uiWidth': 128,
  203. 'uiWidthBytes': 256,
  204. 'uiHeight': 128,
  205. 'uiComp': 1,
  206. 'uiBpcInMemory': 16,
  207. 'uiBpcSignificant': 12,
  208. 'uiSequenceCount': 70,
  209. 'uiTileWidth': 128,
  210. 'uiTileHeight': 128,
  211. 'eCompression': 2,
  212. 'dCompressionParam': -1.0,
  213. 'ePixelType': 1,
  214. 'uiVirtualComponents': 1
  215. }
  216. }, # ImageAttributesLV!",
  217. 7, # ImageTextInfoLV!",
  218. 7, # ImageMetadataLV!",
  219. {
  220. 'SLxPictureMetadata':
  221. {
  222. 'sPicturePlanes':
  223. {
  224. 'sPlaneNew': {
  225. # channels are numbered a0, a1, ..., aN
  226. 'a0': {
  227. 'sDescription': 'TRITC'
  228. }
  229. }
  230. }
  231. }
  232. }, # ImageMetadataSeqLV|0!",
  233. 7, # ImageCalibrationLV|0!",
  234. 7, # CustomData|X!",
  235. 7, # CustomData|Y!",
  236. 7, # CustomData|Z!",
  237. 7, # CustomData|RoiMetadata_v1!",
  238. 7, # CustomData|PFS_STATUS!",
  239. 7, # CustomData|PFS_OFFSET!",
  240. 7, # CustomData|GUIDStore!",
  241. 7, # CustomData|CustomDescriptionV1_0!",
  242. 7, # CustomData|Camera_ExposureTime1!",
  243. 7, # CustomData|CameraTemp1!",
  244. 7, # CustomData|AcqTimesCache!",
  245. 7, # CustomData|AcqTimes2Cache!",
  246. 7, # CustomData|AcqFramesCache!",
  247. 7, # CustomDataVar|LUTDataV1_0!",
  248. 7, # CustomDataVar|GrabberCameraSettingsV1_0!",
  249. 7, # CustomDataVar|CustomDataV2_0!",
  250. 7, # CustomDataVar|AppInfo_V1_0!",
  251. 7, # ImageDataSeq|0!"
  252. ]
  253. file_data_dict = {l: d for l, d in zip(labels, file_data)}
  254. # convert to bytes
  255. file_data = [self._pack_data_with_metadata(d) for d in file_data]
  256. return file_data, file_data_dict