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.

280 lines
9.7 KiB

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