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.

271 lines
8.7 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)):
  23. self.version = version
  24. self.raw_text, self.locations, self.data = None, None, None
  25. check_or_make_dir(path.dirname(file))
  26. self._fh = open(file, 'w+b', 0)
  27. self.write_file()
  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):
  45. self.write_version()
  46. self.raw_text, self.locations, self.data = self.write_label_map()
  47. def write_version(self):
  48. """Write file header
  49. """
  50. # write 16 empty bytes
  51. self._fh.write(bytearray(16))
  52. # write version info
  53. self._fh.write(self._get_version_string())
  54. def _get_version_string(self):
  55. return six.b('ND2 FILE SIGNATURE CHUNK NAME01!Ver%s.%s' % self.version)
  56. def _get_version_byte_length(self):
  57. return 16 + len(self._get_version_string())
  58. def write_label_map(self):
  59. raw_text, locations, data = self.create_label_map_bytes()
  60. self._fh.write(raw_text)
  61. return raw_text, locations, data
  62. def create_label_map_bytes(self):
  63. """Construct a binary label map
  64. Returns:
  65. tuple: (binary data, dictionary data)
  66. """
  67. raw_text = six.b('')
  68. labels = [
  69. 'image_attributes',
  70. 'image_text_info',
  71. 'image_metadata',
  72. 'image_metadata_sequence',
  73. 'image_calibration',
  74. 'x_data',
  75. 'y_data',
  76. 'z_data',
  77. 'roi_metadata',
  78. 'pfs_status',
  79. 'pfs_offset',
  80. 'guid',
  81. 'description',
  82. 'camera_exposure_time',
  83. 'camera_temp',
  84. 'acquisition_times',
  85. 'acquisition_times_2',
  86. 'acquisition_frames',
  87. 'lut_data',
  88. 'grabber_settings',
  89. 'custom_data',
  90. 'app_info',
  91. 'image_frame_0'
  92. ]
  93. file_labels = [
  94. "ImageAttributesLV!",
  95. "ImageTextInfoLV!",
  96. "ImageMetadataLV!",
  97. "ImageMetadataSeqLV|0!",
  98. "ImageCalibrationLV|0!",
  99. "CustomData|X!",
  100. "CustomData|Y!",
  101. "CustomData|Z!",
  102. "CustomData|RoiMetadata_v1!",
  103. "CustomData|PFS_STATUS!",
  104. "CustomData|PFS_OFFSET!",
  105. "CustomData|GUIDStore!",
  106. "CustomData|CustomDescriptionV1_0!",
  107. "CustomData|Camera_ExposureTime1!",
  108. "CustomData|CameraTemp1!",
  109. "CustomData|AcqTimesCache!",
  110. "CustomData|AcqTimes2Cache!",
  111. "CustomData|AcqFramesCache!",
  112. "CustomDataVar|LUTDataV1_0!",
  113. "CustomDataVar|GrabberCameraSettingsV1_0!",
  114. "CustomDataVar|CustomDataV2_0!",
  115. "CustomDataVar|AppInfo_V1_0!",
  116. "ImageDataSeq|0!"
  117. ]
  118. file_data, file_data_dict = self._get_file_data(labels)
  119. locations = {}
  120. # generate random positions and lengths
  121. version_length = self._get_version_byte_length()
  122. # calculate data length
  123. label_length = np.sum([len(six.b(l)) + 16 for l in file_labels])
  124. # write label map
  125. cur_pos = version_length + label_length
  126. for label, file_label, data in zip(labels, file_labels, file_data):
  127. raw_text += six.b(file_label)
  128. data_length = len(data)
  129. raw_text += struct.pack('QQ', cur_pos, data_length)
  130. locations[label] = (cur_pos, data_length)
  131. cur_pos += data_length
  132. # write data
  133. raw_text += six.b('').join(file_data)
  134. return raw_text, locations, file_data_dict
  135. def _pack_data_with_metadata(self, data):
  136. packed_data = self._pack_raw_data_with_metadata(data)
  137. raw_data = struct.pack("IIQ", self.header, self.relative_offset, len(packed_data))
  138. raw_data += packed_data
  139. return raw_data
  140. def _pack_raw_data_with_metadata(self, data):
  141. raw_data = b''
  142. if isinstance(data, dict):
  143. raw_data = self._pack_dict_with_metadata(data)
  144. elif isinstance(data, int):
  145. raw_data = struct.pack('I', data)
  146. elif isinstance(data, float):
  147. raw_data = struct.pack('d', data)
  148. elif isinstance(data, str):
  149. raw_data = bytes(data, 'utf-8')
  150. return raw_data
  151. def _get_data_type(self, data):
  152. if isinstance(data, dict):
  153. return self.data_types['metadata_item']
  154. elif isinstance(data, int):
  155. return self.data_types['unsigned_int']
  156. elif isinstance(data, float):
  157. return self.data_types['double']
  158. elif isinstance(data, str):
  159. return self.data_types['string']
  160. return np.max(self.data_types.values()) + 1
  161. def _pack_dict_with_metadata(self, data):
  162. raw_data = b''
  163. for data_key in data.keys():
  164. # names have always one character extra and are padded in zero bytes???
  165. b_data_key = b''.join([struct.pack('cx', bytes(s, 'utf-8')) for s in data_key]) + struct.pack('xx')
  166. # header consists of data type and length of key name, it is represented by 2 unsigned chars
  167. raw_data += struct.pack('BB', self._get_data_type(data[data_key]), len(data_key) + 1)
  168. raw_data += b_data_key
  169. sub_data = self._pack_raw_data_with_metadata(data[data_key])
  170. if isinstance(data[data_key], dict):
  171. # Pack: the number of keys and the length of raw data until now, sub data
  172. # and the 12 bytes that we add now
  173. raw_data += struct.pack("<IQ", len(data[data_key].keys()), len(sub_data) + len(raw_data) + 12)
  174. raw_data += sub_data
  175. if isinstance(data[data_key], dict):
  176. # apparently there is also a huge empty space
  177. raw_data += b''.join([struct.pack('x')] * len(data[data_key].keys()) * 8)
  178. return raw_data
  179. def _get_file_data(self, labels):
  180. file_data = [
  181. {
  182. 'SLxImageAttributes':
  183. {
  184. 'uiWidth': 128,
  185. 'uiWidthBytes': 256,
  186. 'uiHeight': 128,
  187. 'uiComp': 1,
  188. 'uiBpcInMemory': 16,
  189. 'uiBpcSignificant': 12,
  190. 'uiSequenceCount': 70,
  191. 'uiTileWidth': 128,
  192. 'uiTileHeight': 128,
  193. 'eCompression': 2,
  194. 'dCompressionParam': -1.0,
  195. 'ePixelType': 1,
  196. 'uiVirtualComponents': 1
  197. }
  198. }, # ImageAttributesLV!",
  199. 7, # ImageTextInfoLV!",
  200. 7, # ImageMetadataLV!",
  201. 7, # ImageMetadataSeqLV|0!",
  202. 7, # ImageCalibrationLV|0!",
  203. 7, # CustomData|X!",
  204. 7, # CustomData|Y!",
  205. 7, # CustomData|Z!",
  206. 7, # CustomData|RoiMetadata_v1!",
  207. 7, # CustomData|PFS_STATUS!",
  208. 7, # CustomData|PFS_OFFSET!",
  209. 7, # CustomData|GUIDStore!",
  210. 7, # CustomData|CustomDescriptionV1_0!",
  211. 7, # CustomData|Camera_ExposureTime1!",
  212. 7, # CustomData|CameraTemp1!",
  213. 7, # CustomData|AcqTimesCache!",
  214. 7, # CustomData|AcqTimes2Cache!",
  215. 7, # CustomData|AcqFramesCache!",
  216. 7, # CustomDataVar|LUTDataV1_0!",
  217. 7, # CustomDataVar|GrabberCameraSettingsV1_0!",
  218. 7, # CustomDataVar|CustomDataV2_0!",
  219. 7, # CustomDataVar|AppInfo_V1_0!",
  220. 7, # ImageDataSeq|0!"
  221. ]
  222. file_data_dict = {l: d for l, d in zip(labels, file_data)}
  223. # convert to bytes
  224. file_data = [self._pack_data_with_metadata(d) for d in file_data]
  225. return file_data, file_data_dict