surfacetopography 0.91.2__py3.6-linux-x86_64.egg

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. EGG-INFO/PKG-INFO +11 -0
  2. EGG-INFO/SOURCES.txt +96 -0
  3. EGG-INFO/dependency_links.txt +1 -0
  4. EGG-INFO/native_libs.txt +1 -0
  5. EGG-INFO/not-zip-safe +1 -0
  6. EGG-INFO/requires.txt +6 -0
  7. EGG-INFO/top_level.txt +3 -0
  8. SurfaceTopography/ChangeLog.md +358 -0
  9. SurfaceTopography/Converters.py +197 -0
  10. SurfaceTopography/Generation.py +440 -0
  11. SurfaceTopography/HeightContainer.py +315 -0
  12. SurfaceTopography/IO/DI.py +261 -0
  13. SurfaceTopography/IO/FromFile.py +802 -0
  14. SurfaceTopography/IO/H5.py +79 -0
  15. SurfaceTopography/IO/IBW.py +176 -0
  16. SurfaceTopography/IO/MI.py +307 -0
  17. SurfaceTopography/IO/Matlab.py +106 -0
  18. SurfaceTopography/IO/NC.py +228 -0
  19. SurfaceTopography/IO/NPY.py +139 -0
  20. SurfaceTopography/IO/OPDx.py +885 -0
  21. SurfaceTopography/IO/Reader.py +389 -0
  22. SurfaceTopography/IO/__init__.py +241 -0
  23. SurfaceTopography/IO/__pycache__/DI.cpython-36.pyc +0 -0
  24. SurfaceTopography/IO/__pycache__/FromFile.cpython-36.pyc +0 -0
  25. SurfaceTopography/IO/__pycache__/H5.cpython-36.pyc +0 -0
  26. SurfaceTopography/IO/__pycache__/IBW.cpython-36.pyc +0 -0
  27. SurfaceTopography/IO/__pycache__/MI.cpython-36.pyc +0 -0
  28. SurfaceTopography/IO/__pycache__/Matlab.cpython-36.pyc +0 -0
  29. SurfaceTopography/IO/__pycache__/NC.cpython-36.pyc +0 -0
  30. SurfaceTopography/IO/__pycache__/NPY.cpython-36.pyc +0 -0
  31. SurfaceTopography/IO/__pycache__/OPDx.cpython-36.pyc +0 -0
  32. SurfaceTopography/IO/__pycache__/Reader.cpython-36.pyc +0 -0
  33. SurfaceTopography/IO/__pycache__/__init__.cpython-36.pyc +0 -0
  34. SurfaceTopography/Interpolation.py +30 -0
  35. SurfaceTopography/Nonuniform/Autocorrelation.py +161 -0
  36. SurfaceTopography/Nonuniform/Detrending.py +87 -0
  37. SurfaceTopography/Nonuniform/PowerSpectrum.py +178 -0
  38. SurfaceTopography/Nonuniform/ScalarParameters.py +140 -0
  39. SurfaceTopography/Nonuniform/VariableBandwidth.py +155 -0
  40. SurfaceTopography/Nonuniform/__init__.py +28 -0
  41. SurfaceTopography/Nonuniform/__pycache__/Autocorrelation.cpython-36.pyc +0 -0
  42. SurfaceTopography/Nonuniform/__pycache__/Detrending.cpython-36.pyc +0 -0
  43. SurfaceTopography/Nonuniform/__pycache__/PowerSpectrum.cpython-36.pyc +0 -0
  44. SurfaceTopography/Nonuniform/__pycache__/ScalarParameters.cpython-36.pyc +0 -0
  45. SurfaceTopography/Nonuniform/__pycache__/VariableBandwidth.cpython-36.pyc +0 -0
  46. SurfaceTopography/Nonuniform/__pycache__/__init__.cpython-36.pyc +0 -0
  47. SurfaceTopography/Nonuniform/__pycache__/common.cpython-36.pyc +0 -0
  48. SurfaceTopography/Nonuniform/common.py +95 -0
  49. SurfaceTopography/NonuniformLineScan.py +310 -0
  50. SurfaceTopography/Special.py +225 -0
  51. SurfaceTopography/Tools/__init__.py +28 -0
  52. SurfaceTopography/Tools/__pycache__/__init__.cpython-36.pyc +0 -0
  53. SurfaceTopography/Tools/__pycache__/common.cpython-36.pyc +0 -0
  54. SurfaceTopography/Tools/common.py +75 -0
  55. SurfaceTopography/Uniform/Autocorrelation.py +211 -0
  56. SurfaceTopography/Uniform/Detrending.py +155 -0
  57. SurfaceTopography/Uniform/Filtering.py +226 -0
  58. SurfaceTopography/Uniform/Interpolation.py +159 -0
  59. SurfaceTopography/Uniform/PowerSpectrum.py +197 -0
  60. SurfaceTopography/Uniform/ScalarParameters.py +173 -0
  61. SurfaceTopography/Uniform/VariableBandwidth.py +169 -0
  62. SurfaceTopography/Uniform/__init__.py +28 -0
  63. SurfaceTopography/Uniform/__pycache__/Autocorrelation.cpython-36.pyc +0 -0
  64. SurfaceTopography/Uniform/__pycache__/Detrending.cpython-36.pyc +0 -0
  65. SurfaceTopography/Uniform/__pycache__/Filtering.cpython-36.pyc +0 -0
  66. SurfaceTopography/Uniform/__pycache__/Interpolation.cpython-36.pyc +0 -0
  67. SurfaceTopography/Uniform/__pycache__/PowerSpectrum.cpython-36.pyc +0 -0
  68. SurfaceTopography/Uniform/__pycache__/ScalarParameters.cpython-36.pyc +0 -0
  69. SurfaceTopography/Uniform/__pycache__/VariableBandwidth.cpython-36.pyc +0 -0
  70. SurfaceTopography/Uniform/__pycache__/__init__.cpython-36.pyc +0 -0
  71. SurfaceTopography/Uniform/__pycache__/common.cpython-36.pyc +0 -0
  72. SurfaceTopography/Uniform/common.py +260 -0
  73. SurfaceTopography/UniformLineScanAndTopography.py +887 -0
  74. SurfaceTopography/__init__.py +62 -0
  75. SurfaceTopography/__pycache__/Converters.cpython-36.pyc +0 -0
  76. SurfaceTopography/__pycache__/Generation.cpython-36.pyc +0 -0
  77. SurfaceTopography/__pycache__/HeightContainer.cpython-36.pyc +0 -0
  78. SurfaceTopography/__pycache__/Interpolation.cpython-36.pyc +0 -0
  79. SurfaceTopography/__pycache__/NonuniformLineScan.cpython-36.pyc +0 -0
  80. SurfaceTopography/__pycache__/Special.cpython-36.pyc +0 -0
  81. SurfaceTopography/__pycache__/UniformLineScanAndTopography.cpython-36.pyc +0 -0
  82. SurfaceTopography/__pycache__/__init__.cpython-36.pyc +0 -0
  83. SurfaceTopography/__pycache__/common.cpython-36.pyc +0 -0
  84. SurfaceTopography/common.py +111 -0
  85. _SurfaceTopography.cpython-36m-x86_64-linux-gnu.so +0 -0
  86. _SurfaceTopography.py +8 -0
  87. __pycache__/_SurfaceTopography.cpython-36.pyc +0 -0
  88. test/IO/__init__.py +28 -0
  89. test/IO/__pycache__/__init__.cpython-36.pyc +0 -0
  90. test/IO/__pycache__/test_ibw.cpython-36.pyc +0 -0
  91. test/IO/__pycache__/test_io.cpython-36.pyc +0 -0
  92. test/IO/__pycache__/test_mi.cpython-36.pyc +0 -0
  93. test/IO/__pycache__/test_netcdf.cpython-36.pyc +0 -0
  94. test/IO/__pycache__/test_npy.cpython-36.pyc +0 -0
  95. test/IO/__pycache__/test_opdx.cpython-36.pyc +0 -0
  96. test/IO/test_ibw.py +178 -0
  97. test/IO/test_io.py +401 -0
  98. test/IO/test_mi.py +103 -0
  99. test/IO/test_netcdf.py +134 -0
  100. test/IO/test_npy.py +135 -0
  101. test/IO/test_opdx.py +441 -0
  102. test/__init__.py +26 -0
  103. test/__pycache__/__init__.cpython-36.pyc +0 -0
  104. test/__pycache__/compare-orientation-with-gwyddion.cpython-36.pyc +0 -0
  105. test/__pycache__/conftest.cpython-36.pyc +0 -0
  106. test/__pycache__/test_autocorrelation.cpython-36.pyc +0 -0
  107. test/__pycache__/test_bicubic.cpython-36.pyc +0 -0
  108. test/__pycache__/test_filtering.cpython-36.pyc +0 -0
  109. test/__pycache__/test_generation.cpython-36.pyc +0 -0
  110. test/__pycache__/test_import.cpython-36.pyc +0 -0
  111. test/__pycache__/test_make_sphere.cpython-36.pyc +0 -0
  112. test/__pycache__/test_power_spectrum.cpython-36.pyc +0 -0
  113. test/__pycache__/test_scalar_parameters.cpython-36.pyc +0 -0
  114. test/__pycache__/test_topography1.cpython-36.pyc +0 -0
  115. test/__pycache__/test_topography2.cpython-36.pyc +0 -0
  116. test/__pycache__/test_variable_bandwidth.cpython-36.pyc +0 -0
  117. test/compare-orientation-with-gwyddion.py +102 -0
  118. test/conftest.py +80 -0
  119. test/test_autocorrelation.py +311 -0
  120. test/test_bicubic.py +190 -0
  121. test/test_filtering.py +69 -0
  122. test/test_generation.py +259 -0
  123. test/test_import.py +54 -0
  124. test/test_make_sphere.py +108 -0
  125. test/test_power_spectrum.py +259 -0
  126. test/test_scalar_parameters.py +176 -0
  127. test/test_topography1.py +1287 -0
  128. test/test_topography2.py +328 -0
  129. test/test_variable_bandwidth.py +107 -0
@@ -0,0 +1,885 @@
1
+ #
2
+ # Copyright 2019-2020 Lars Pastewka
3
+ # 2019-2020 Michael Röttger
4
+ # 2019-2020 Antoine Sanner
5
+ # 2019-2020 Kai Haase
6
+ #
7
+ # ### MIT license
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ # of this software and associated documentation files (the "Software"), to deal
11
+ # in the Software without restriction, including without limitation the rights
12
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ # copies of the Software, and to permit persons to whom the Software is
14
+ # furnished to do so, subject to the following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be included in
17
+ # all copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ # SOFTWARE.
26
+ #
27
+
28
+ from collections import OrderedDict
29
+ from io import TextIOBase
30
+ import numpy as np
31
+
32
+ from .. import Topography
33
+ from .Reader import ReaderBase, ChannelInfo
34
+ from .FromFile import get_unit_conversion_factor, mangle_height_unit
35
+
36
+ MAGIC = "VCA DATA\x01\x00\x00\x55"
37
+ MAGIC_SIZE = 12
38
+
39
+ DEKTAK_MATRIX = 0x00 # Too lazy to assign an actual type id?
40
+ DEKTAK_BOOLEAN = 0x01 # Takes value 0 and 1
41
+ DEKTAK_SINT32 = 0x06
42
+ DEKTAK_UINT32 = 0x07
43
+ DEKTAK_SINT64 = 0x0a
44
+ DEKTAK_UINT64 = 0x0b
45
+ DEKTAK_FLOAT = 0x0c # Single precision float
46
+ DEKTAK_DOUBLE = 0x0d # Double precision float
47
+ DEKTAK_TYPE_ID = 0x0e # Compound type holding some kind of type id
48
+ DEKTAK_STRING = 0x12 # Free-form string value
49
+ DEKTAK_QUANTITY = 0x13 # Value with units (compound type)
50
+ DEKTAK_TIME_STAMP = 0x15 # Datetime (string/9-byte binary)
51
+ DEKTAK_UNITS = 0x18 # Units (compound type)
52
+ DEKTAK_DOUBLE_ARRAY = 0x40 # Raw data array, in XML Base64-encoded
53
+ DEKTAK_STRING_LIST = 0x42 # List of Str
54
+ DEKTAK_RAW_DATA = 0x46 # Parent/wrapper tag of raw data
55
+ DEKTAK_RAW_DATA_2D = 0x47 # Parent/wrapper tag of raw data
56
+ # Base64-encoded positions, not sure how it differs from 64
57
+ DEKTAK_POS_RAW_DATA = 0x7c
58
+ DEKTAK_CONTAINER = 0x7d # General nested data structure
59
+ # Always the last item. Usually a couple of 0xff bytes inside.
60
+ DEKTAK_TERMINATOR = 0x7f
61
+
62
+ TIMESTAMP_SIZE = 9
63
+ UNIT_EXTRA = 12
64
+ DOUBLE_ARRAY_EXTRA = 5
65
+
66
+ MEAS_SETTINGS = "/MetaData/MeasurementSettings"
67
+ RAW_1D_DATA = "/1D_Data/Raw"
68
+ ANY_2D_DATA = "/2D_Data/"
69
+
70
+
71
+ class OPDxReader(ReaderBase):
72
+ _format = 'opdx'
73
+ _name = 'Dektak OPDx'
74
+ _description = '''
75
+ File format of the Bruker Dektak XT* series stylus profilometer.
76
+ '''
77
+
78
+ # Reads in the positions of all the data and metadata
79
+ def __init__(self, file_path):
80
+
81
+ # depending from where this function is called, file_path might already
82
+ # be a filestream
83
+ already_open = False
84
+ if not hasattr(file_path, 'read'):
85
+ f = open(file_path, "rb")
86
+ else:
87
+ already_open = True
88
+ if isinstance(file_path, TextIOBase):
89
+ # file was opened without the 'b' option, so read its buffer
90
+ # to get the binary data
91
+ f = file_path.buffer
92
+ else:
93
+ f = file_path
94
+
95
+ try:
96
+
97
+ # read topography in file as hexadecimal
98
+ self.buffer = [chr(byte) for byte in f.read()]
99
+
100
+ # length of file
101
+ physical_sizes = len(self.buffer)
102
+
103
+ # check if correct header
104
+ if physical_sizes < MAGIC_SIZE or ''.join(
105
+ self.buffer[:MAGIC_SIZE]) != MAGIC:
106
+ raise ValueError('Invalid file format for Dektak OPDx.')
107
+
108
+ pos = MAGIC_SIZE
109
+ hash_table = OrderedDict()
110
+ while pos < physical_sizes:
111
+ buf, pos, hash_table, path = read_item(buf=self.buffer,
112
+ pos=pos,
113
+ hash_table=hash_table,
114
+ path="")
115
+
116
+ # Make a list of channels containing metadata about the
117
+ # topographies but not reading them in directly yet
118
+ all_channels_data = find_2d_data(hash_table, self.buffer)
119
+
120
+ # Reformat the resulting dicts
121
+ for channel_name in all_channels_data.keys():
122
+ all_channels_data[channel_name][
123
+ -1], default_channel_name = reformat_dict(
124
+ channel_name, all_channels_data[channel_name][-1])
125
+
126
+ self._default_channel_index = list(all_channels_data.keys()).index(
127
+ default_channel_name)
128
+
129
+ #
130
+ # Build channel info objects and additional metadata for extracting
131
+ # data
132
+ #
133
+ self._channels = []
134
+ self._channels_xres_yres_start_stop_q = []
135
+ for channel_index, channel_name in enumerate(all_channels_data):
136
+
137
+ channel_data = all_channels_data[channel_name]
138
+ *xres_yres_start_stop_q, metadata = channel_data
139
+
140
+ #
141
+ # find out physical sizes in a common unit
142
+ # without touching the meta data, scale heights accordingly
143
+ #
144
+ unit_x = mangle_height_unit(metadata['Width_unit'])
145
+ size_x = metadata['Width_value']
146
+
147
+ unit_y = mangle_height_unit(metadata['Height_unit'])
148
+ size_y = metadata['Height_value']
149
+
150
+ unit_z = mangle_height_unit(metadata['z_unit'])
151
+
152
+ # we want value in unit_x units
153
+ unit_factor_y = get_unit_conversion_factor(unit_y,
154
+ unit_x)
155
+ if unit_factor_y is None:
156
+ raise ValueError(
157
+ 'Units for size in x ("{}") and y ("{}") direction '
158
+ 'are incompatible.'.format(unit_x, unit_y))
159
+ size_y *= unit_factor_y
160
+
161
+ if unit_z is None:
162
+ # No unit given for heights. Since we can only return one
163
+ # common unit, no unit should be returned
164
+ try:
165
+ del metadata['unit']
166
+ except KeyError:
167
+ pass
168
+ else:
169
+ # we want value in unit_x units
170
+ unit_factor_z = get_unit_conversion_factor(unit_z,
171
+ unit_x)
172
+ if unit_factor_z is None:
173
+ raise ValueError(
174
+ 'Units for width ("{}") and data units ("{}") '
175
+ 'are incompatible.'.format(unit_x, unit_y))
176
+
177
+ # we have converted everything to this unit
178
+ metadata['unit'] = unit_x
179
+
180
+ ch_info = ChannelInfo(self, channel_index,
181
+ name=metadata['Name'], dim=2,
182
+ nb_grid_pts=(metadata['ImageWidth'],
183
+ metadata['ImageHeight']),
184
+ physical_sizes=(size_x, size_y),
185
+ info=metadata)
186
+
187
+ self._channels.append(ch_info)
188
+ self._channels_xres_yres_start_stop_q.append(
189
+ xres_yres_start_stop_q) # needed for building heights
190
+
191
+ finally:
192
+ if not already_open:
193
+ f.close()
194
+
195
+ def topography(self, channel_index=None, physical_sizes=None,
196
+ height_scale_factor=None, info={},
197
+ periodic=False, subdomain_locations=None,
198
+ nb_subdomain_grid_pts=None):
199
+ if channel_index is None:
200
+ channel_index = self._default_channel_index
201
+
202
+ if subdomain_locations is not None or \
203
+ nb_subdomain_grid_pts is not None:
204
+ raise RuntimeError('This reader does not support MPI '
205
+ 'parallelization.')
206
+
207
+ channel_info = self._channels[channel_index]
208
+ res_x, res_y, start, end, q = self._channels_xres_yres_start_stop_q[
209
+ channel_index]
210
+
211
+ data = build_matrix(res_x, res_y, self.buffer[start:end], q).T
212
+
213
+ unit_z = channel_info.info['z_unit']
214
+
215
+ if 'unit' in channel_info.info:
216
+ common_unit = channel_info.info['unit']
217
+
218
+ if (common_unit is not None) and (unit_z != common_unit):
219
+ # There is a common unit, but the z-unit in the file differs
220
+ # from that common unit. So we need to scale the data
221
+ # accordingly, such that also the height are represented in
222
+ # common units.
223
+ unit_factor_z = get_unit_conversion_factor(unit_z, common_unit)
224
+ if unit_factor_z is None:
225
+ raise ValueError(
226
+ 'Common unit ("{}") derived from lateral units '
227
+ 'and data units ("{}") are incompatible.'.format(
228
+ common_unit, unit_z))
229
+ data *= unit_factor_z
230
+
231
+ physical_sizes = self._check_physical_sizes(
232
+ physical_sizes, channel_info.physical_sizes)
233
+
234
+ info = info.copy()
235
+ info.update(channel_info.info)
236
+
237
+ return Topography(heights=data, physical_sizes=physical_sizes,
238
+ info=info, periodic=periodic)
239
+
240
+ @property
241
+ def channels(self):
242
+ return self._channels
243
+
244
+ channels.__doc__ = ReaderBase.channels.__doc__
245
+ topography.__doc__ = ReaderBase.topography.__doc__
246
+
247
+
248
+ def reformat_dict(name, metadata):
249
+ """
250
+ Reformat the metadata dict from c convention to a more readable format and
251
+ remove artefacts. Also gets and returns the default channel.
252
+ :param name: The name of the current channel.
253
+ :param metadata: The metadata dict
254
+ :return:
255
+ new dict: The nicer dict
256
+ primary_channel_name: The name of the primary channel.
257
+ """
258
+ new_dict = OrderedDict()
259
+
260
+ primary_channel_name = None
261
+ for key in metadata.keys():
262
+
263
+ if key == '::MetaData::PrimaryData2D':
264
+ primary_channel_name = metadata[key]
265
+
266
+ if key.startswith('::MetaData::'):
267
+ # These are in here for no reason and the value is None
268
+ if not key[12:].endswith('::'):
269
+ new_dict[key[12:].replace('::', '_').replace(' ', '_')] = \
270
+ metadata[key]
271
+
272
+ if key.startswith(str(name) + '::'):
273
+ name_len = 2 + len(name)
274
+ new_dict[key[name_len:].replace('::', '_').replace(' ', '_')] = \
275
+ metadata[key]
276
+
277
+ if '' in new_dict.keys():
278
+ new_dict.pop('')
279
+
280
+ if 'xres' in new_dict.keys():
281
+ new_dict.pop('xres')
282
+
283
+ if 'yres' in new_dict.keys():
284
+ new_dict.pop('yres')
285
+
286
+ # Correct weird characters in units
287
+ for unit_key in ['z_unit', 'Height_unit', 'Width_unit']:
288
+ if unit_key in new_dict.keys():
289
+ unit = new_dict.pop(unit_key)
290
+ if unit.startswith("Â"):
291
+ unit = unit[1:]
292
+ new_dict[unit_key] = unit
293
+
294
+ new_dict['Name'] = name
295
+
296
+ return new_dict, primary_channel_name
297
+
298
+
299
+ class DektakItemData:
300
+ def __init__(self):
301
+ self.b = None
302
+ self.ui = None
303
+ self.si = None
304
+ self.uq = None
305
+ self.sq = None
306
+ self.d = None
307
+ self.timestamp = []
308
+ self.buf = None
309
+ self.qun = None
310
+ self.rawpos1d = DektakRawPos1D()
311
+ self.rawpos2d = DektakRawPos2D()
312
+ self.matrix = DektakMatrix()
313
+ self.strlist = None
314
+
315
+
316
+ class DektakItem:
317
+ def __init__(self):
318
+ self.typename = None
319
+ self.typeid = None
320
+ self.data = DektakItemData()
321
+
322
+
323
+ class DektakRawPos1D:
324
+ def __init__(self):
325
+ self.unit = DektakQuantUnit()
326
+ self.divisor = None
327
+ self.count = None
328
+ self.buf = DektakBuf
329
+
330
+
331
+ class DektakRawPos2D:
332
+ def __init__(self):
333
+ self.unitx = DektakQuantUnit()
334
+ self.unity = DektakQuantUnit()
335
+ self.divisorx = None
336
+ self.divisory = None
337
+
338
+
339
+ class DektakQuantUnit:
340
+ def __init__(self):
341
+ self.name = None
342
+ self.symbol = None
343
+ self.value = None
344
+ self.extra = None
345
+
346
+
347
+ class DektakMatrix:
348
+ def __init__(self):
349
+ self.another_name = None
350
+ self.some_int = None
351
+ self.xres = None
352
+ self.yres = None
353
+ self.buf = DektakBuf()
354
+
355
+
356
+ class DektakBuf:
357
+ def __init__(self, position=None, length=None):
358
+ self.position = position
359
+ self.length = length
360
+
361
+
362
+ def find_1d_data(hash_table, buf):
363
+ """ THIS HAS NOT BEEN TESTED DUE TO NO FILES WITH 1D DATA AVAILABLE."""
364
+
365
+ item = hash_table.pop("/MetaData/MeasurementSettings/SamplesToLog", None)
366
+
367
+ if item is None:
368
+ return None
369
+ else:
370
+ raise NotImplementedError
371
+
372
+
373
+ def find_2d_data(hash_table, buf):
374
+ """ Get all the 2d data channels out of the previously filled hash table.
375
+
376
+ :param hash_table: The filled hash table
377
+ :param buf: The raw hex data
378
+ :return: Dictionary with all names, data and metadata of the different
379
+ channels
380
+ """
381
+ output = OrderedDict()
382
+ channels = []
383
+
384
+ # Get a list of all channels containing 2d data matrices
385
+ for key in hash_table.keys():
386
+ found = find_2d_data_matrix(key, hash_table[key])
387
+ if found is not None:
388
+ channels.append(found)
389
+
390
+ for channel in channels:
391
+ meta_data = create_meta(hash_table)
392
+
393
+ string = ANY_2D_DATA
394
+ string += channel
395
+
396
+ length = len(string)
397
+
398
+ # Get position and res of data matrix
399
+ string += "/Matrix"
400
+ item = hash_table[string]
401
+ start = item.data.matrix.buf.p
402
+
403
+ end = start + item.data.matrix.buf.length
404
+ xres = item.data.matrix.xres
405
+ yres = item.data.matrix.yres
406
+
407
+ meta_data[channel + "::xres"] = xres
408
+ meta_data[channel + "::yres"] = yres
409
+
410
+ # TODO: multiply value by interpret(item.data.qun.symbol)
411
+ string = string[:length]
412
+ string += "/Dimension1Extent"
413
+ item = hash_table[string]
414
+ yreal = item.data.qun.value
415
+ yunit = item.data.qun.symbol
416
+
417
+ meta_data[channel + "::Height value"] = yreal
418
+ meta_data[channel + "::Height unit"] = yunit
419
+
420
+ string = string[:length]
421
+ string += "/Dimension2Extent"
422
+ item = hash_table[string]
423
+ xreal = item.data.qun.value
424
+ xunit = item.data.qun.symbol
425
+
426
+ meta_data[channel + "::Width value"] = xreal
427
+ meta_data[channel + "::Width unit"] = xunit
428
+
429
+ string = string[:length]
430
+ string += "/DataScale"
431
+ item = hash_table[string]
432
+ q = item.data.qun.value
433
+ zunit = item.data.qun.symbol
434
+
435
+ meta_data[channel + "::z scale"] = q
436
+ meta_data[channel + "::z unit"] = zunit
437
+
438
+ output[channel] = [xres, yres, start, end, q, meta_data]
439
+ return output
440
+
441
+
442
+ def find_2d_data_matrix(name, item):
443
+ """ Checks if an item is a matrix and if it is, returns it's channel name.
444
+ :param name: The name (key) of a found item
445
+ :param item: The item itself
446
+ :return: The name of the matrix data channel
447
+ """
448
+ if item.typeid != DEKTAK_MATRIX:
449
+ return
450
+ if name[:9] != ANY_2D_DATA:
451
+ return
452
+ s = 9 + name[9:].find('/')
453
+ if s == -1:
454
+ return
455
+ if not name[s + 1:] == "Matrix":
456
+ return
457
+ return name[9:s]
458
+
459
+
460
+ def create_meta(hash_table):
461
+ """
462
+ Gets all the metadata out of a hash table.
463
+ :param hash_table: The hash table
464
+ :return: Hash table with all metadata names and values
465
+ """
466
+ container = OrderedDict()
467
+ for key in hash_table.keys():
468
+ if not key.startswith('/MetaData/'):
469
+ continue
470
+ item = hash_table[key]
471
+
472
+ if item.typeid == DEKTAK_BOOLEAN:
473
+ metavalue = item.data.b
474
+ elif item.typeid == DEKTAK_SINT32:
475
+ metavalue = item.data.si
476
+ elif item.typeid == DEKTAK_UINT32:
477
+ metavalue = item.data.ui
478
+ elif item.typeid == DEKTAK_SINT64:
479
+ metavalue = item.data.sq
480
+ elif item.typeid == DEKTAK_UINT64:
481
+ metavalue = item.data.uq
482
+ elif item.typeid == DEKTAK_DOUBLE or item.typeid == DEKTAK_FLOAT:
483
+ metavalue = item.data.d
484
+ elif item.typeid == DEKTAK_STRING:
485
+ metavalue = "".join(item.data.buf)
486
+ elif item.typeid == DEKTAK_QUANTITY:
487
+ metavalue = str(item.data.qun.value) + item.data.qun.symbol
488
+ elif item.typeid == DEKTAK_STRING_LIST:
489
+ metavalue = "; ".join(item.data.strlist)
490
+ elif item.typeid == DEKTAK_TERMINATOR:
491
+ metavalue = None
492
+ else:
493
+ # Not really meta data
494
+ continue
495
+ metakey = key.replace("/", "::")
496
+ container[metakey] = metavalue
497
+ return container
498
+
499
+
500
+ def read_item(buf, pos, hash_table, path, abspos=0):
501
+ """
502
+ Reads in the next item out of the buffer and saves it in the hash table.
503
+ May recursively call itself for containers.
504
+ :param buf: The raw data buffer
505
+ :param pos: Current position in the buffer
506
+ :param hash_table: The output hash table
507
+ :param path: Current name to save
508
+ :param abspos: Absolute position in buffer to keep track when calling
509
+ itself
510
+ :return:
511
+ Buffer, new position, hash table with new item in it, new path
512
+ """
513
+ orig_path_len = len(path)
514
+ item = DektakItem()
515
+ itempos = 0
516
+ name, pos = read_name(buf, pos)
517
+
518
+ path += '/'
519
+ path += name
520
+
521
+ item.typeid, pos = read_with_check(buf, pos, 1)
522
+ item.typeid = ord(item.typeid[0])
523
+
524
+ # simple types
525
+ if item.typeid == DEKTAK_BOOLEAN:
526
+ b8, pos = read_with_check(buf, pos, 1)
527
+ if b8 == '\x01':
528
+ item.data.b = True
529
+ elif b8 == '\x00':
530
+ item.data.b = False
531
+ else:
532
+ raise ValueError("Something went wrong.")
533
+
534
+ elif item.typeid == DEKTAK_SINT32:
535
+ item.data.si, pos = read_int32(buf, pos, signed=True)
536
+
537
+ elif item.typeid == DEKTAK_UINT32:
538
+ item.data.ui, pos = read_int32(buf, pos, signed=False)
539
+
540
+ elif item.typeid == DEKTAK_SINT64:
541
+ item.data.sq, pos = read_int64(buf, pos, signed=True)
542
+
543
+ elif item.typeid == DEKTAK_UINT64:
544
+ item.data.uq, pos = read_int64(buf, pos, signed=False)
545
+
546
+ elif item.typeid == DEKTAK_FLOAT:
547
+ item.data.d, pos = read_float(buf, pos)
548
+
549
+ elif item.typeid == DEKTAK_DOUBLE:
550
+ item.data.d, pos = read_double(buf, pos)
551
+
552
+ elif item.typeid == DEKTAK_TIME_STAMP:
553
+ time, pos = read_with_check(buf, pos, TIMESTAMP_SIZE)
554
+ item.data.timestamp.append(time)
555
+
556
+ elif item.typeid == DEKTAK_STRING:
557
+ item.data.buf, _, pos = read_structured(buf, pos)
558
+
559
+ elif item.typeid == DEKTAK_QUANTITY:
560
+ content, _, pos = read_structured(buf, pos)
561
+ item.data.qun, itempos = read_quantunit_content(content, itempos,
562
+ False)
563
+
564
+ elif item.typeid == DEKTAK_UNITS:
565
+ content, _, pos = read_structured(buf, pos)
566
+ item.data.qun, itempos = read_quantunit_content(content, itempos, True)
567
+
568
+ elif item.typeid == DEKTAK_TERMINATOR:
569
+ pos = len(buf)
570
+
571
+ # Container types.
572
+ elif item.typeid == DEKTAK_CONTAINER or \
573
+ item.typeid == DEKTAK_RAW_DATA or \
574
+ item.typeid == DEKTAK_RAW_DATA_2D:
575
+ # TODO find out if maybe better place somewhere else
576
+ content, start, pos = read_structured(buf, pos)
577
+ abspos += start
578
+ while itempos < len(content):
579
+ content, itempos, hash_table, path = read_item(
580
+ buf=content, pos=itempos, hash_table=hash_table,
581
+ path=path, abspos=abspos)
582
+
583
+ # Types with string type name
584
+ elif item.typeid == DEKTAK_DOUBLE_ARRAY:
585
+ item.typename, item.data.buf, _, pos = read_named_struct(buf, pos)
586
+
587
+ elif item.typeid == DEKTAK_STRING_LIST:
588
+ item.typename, content, start, pos = read_named_struct(buf, pos)
589
+ item.data.strlist = []
590
+ while itempos < len(content):
591
+ s, itempos = read_name(content, itempos)
592
+ item.data.strlist.append(s)
593
+
594
+ elif item.typeid == DEKTAK_TYPE_ID:
595
+ item.typename, item.data.buf, _, pos = read_named_struct(buf, pos)
596
+
597
+ elif item.typeid == DEKTAK_POS_RAW_DATA:
598
+ if path.startswith('/2D_Data'):
599
+ item.typename, content, _, pos = read_named_struct(buf, pos)
600
+
601
+ item.data.rawpos2d.unitx, item.data.rawpos2d.divisorx, itempos = \
602
+ read_dimension2d_content(content, itempos,
603
+ item.data.rawpos2d.unitx)
604
+ item.data.rawpos2d.unity, item.data.rawpos2d.divisory, itempos = \
605
+ read_dimension2d_content(content, itempos,
606
+ item.data.rawpos2d.unity)
607
+
608
+ elif path.startswith('/1_Data'):
609
+ item.typename, content, _, pos = read_named_struct(buf, pos)
610
+ content.position += buf.position
611
+
612
+ item.data.rawpos1d.unit, itempos = read_quantunit_content(
613
+ content, itempos, True)
614
+ item.data.rawpos1d.count, itempos = read_int64(content, itempos)
615
+
616
+ item.data.rawpos1d.buf = content
617
+ item.data.rawpos1d.buf.position += itempos
618
+ item.data.rawpos1d.buf.length -= itempos
619
+
620
+ else:
621
+ # TODO check if should assume 1D here like gwyddion
622
+ raise ValueError
623
+
624
+ elif item.typeid == DEKTAK_MATRIX:
625
+ item.typename, pos = read_name(buf, pos)
626
+ item.data.matrix.some_int, pos = read_int32(buf, pos)
627
+ item.data.matrix.another_name, pos = read_name(buf, pos)
628
+ item.data.matrix.buf.length, pos = read_varlen(buf, pos)
629
+ item.data.matrix.yres, pos = read_int32(buf, pos)
630
+ item.data.matrix.xres, pos = read_int32(buf, pos)
631
+
632
+ if item.data.matrix.buf.length < 8: # 2 * sizeof int32
633
+ raise ValueError
634
+ item.data.matrix.buf.length -= 8
635
+ item.data.matrix.buf.p = pos + abspos
636
+
637
+ if len(buf) - pos < item.data.matrix.buf.length:
638
+ raise ValueError
639
+ pos += item.data.matrix.buf.length
640
+
641
+ else:
642
+ raise ValueError
643
+ hash_table[path] = item
644
+ path = path[:orig_path_len]
645
+ return buf, pos, hash_table, path
646
+
647
+
648
+ def read_quantunit_content(buf, pos, is_unit):
649
+ """
650
+ Reads in a quantity unit: Value, name and symbol.
651
+ :param buf: The buffer
652
+ :param pos: The position in the buffer
653
+ :param is_unit: Whether or not it is a unit
654
+ :return: A quantunit item, filled with value, name and symbol
655
+ """
656
+ quantunit = DektakQuantUnit()
657
+ quantunit.extra = []
658
+
659
+ if not is_unit:
660
+ quantunit.value, pos = read_double(buf, pos)
661
+
662
+ quantunit.name, pos = read_name(buf, pos)
663
+ quantunit.symbol, pos = read_name(buf, pos)
664
+
665
+ if is_unit:
666
+ quantunit.value, pos = read_double(buf, pos)
667
+ res, pos = read_with_check(buf, pos, UNIT_EXTRA)
668
+ quantunit.extra += res
669
+
670
+ return quantunit, pos
671
+
672
+
673
+ def read_dimension2d_content(buf, pos, unit):
674
+ """
675
+ Reads in information about a 2d dimension.
676
+ :param buf: The buffer
677
+ :param pos: The position in the buffer
678
+ :param unit: The unit
679
+ :return: The open_topography unit, divisor and new position in the buffer
680
+ """
681
+ unit.value, pos = read_double(buf, pos)
682
+ unit.name, pos = read_name(buf, pos)
683
+ unit.symbol, pos = read_name(buf, pos)
684
+ divisor, pos = read_double(buf, pos)
685
+ unit.extra, pos = read_with_check(buf, pos, UNIT_EXTRA)
686
+ return unit, divisor, pos
687
+
688
+
689
+ def build_matrix(xres, yres, data, q=1):
690
+ """
691
+ Reads a float matrix of given dimensions and multiplies with a scale.
692
+ :param xres: Resolution along x-axis
693
+ :param yres: Resolution along y-axis
694
+ :param data: The raw hex data
695
+ :param q: The scale of the data, a double
696
+ :return: A numpy array, now doubles aswell
697
+ """
698
+ data = ''.join(data)
699
+
700
+ # build correct type: 4byte flat, little endian
701
+ dt = np.dtype('f4') # double
702
+ dt = dt.newbyteorder('<') # little-endian
703
+
704
+ data = np.frombuffer(str.encode(data, "raw_unicode_escape"), dt)
705
+ data = data.copy().reshape((yres, xres))
706
+ data *= q
707
+ return data
708
+
709
+
710
+ def read_name(buf, pos):
711
+ """
712
+ Reads a name.
713
+ :param buf: The buffer
714
+ :param pos: Position in buffer
715
+ :return:
716
+ name, new position in buffer
717
+ """
718
+
719
+ # Names always have a physical_sizes of 4 bytes
720
+ length, pos = read_int32(buf, pos)
721
+ if len(buf) < length or pos > len(buf) - length:
722
+ raise ValueError("Some sizes went wrong.")
723
+ position = pos
724
+
725
+ name = buf[position:position + length]
726
+ name = "".join(s for s in name)
727
+ pos += length
728
+ return name, pos
729
+
730
+
731
+ def read_structured(buf, pos):
732
+ """
733
+ Reads a length and returns a part of the buffer that long.
734
+ :param buf: The buffer
735
+ :param pos: Position in buffer
736
+ :return:
737
+ The slice of buffer, where it starts and the new position in the buffer
738
+ """
739
+ length, pos = read_varlen(buf, pos)
740
+ if len(buf) < length or pos > len(buf) - length:
741
+ raise ValueError("Some sizes went wrong.")
742
+ start = pos
743
+ pos += length
744
+ return buf[start:start + length], start, pos
745
+
746
+
747
+ def read_named_struct(buf, pos):
748
+ """
749
+ Same as read_structured but there is a name to it.
750
+ :param buf: The buffer
751
+ :param pos: Position in buffer
752
+ :return:
753
+ Name of the buffer, that buffer, its start and the new position in the
754
+ buffer
755
+ """
756
+ typename, pos = read_name(buf, pos)
757
+ content, start, pos = read_structured(buf, pos)
758
+ return typename, content, start, pos
759
+
760
+
761
+ def read_varlen(buf, pos):
762
+ """
763
+ Reads a length of variable length itself
764
+ :param buf: The buffer
765
+ :param pos: Position in the buffer
766
+ :return:
767
+ The open_topography length and new position in the buffer
768
+ """
769
+ lenlen, pos = read_with_check(buf, pos, 1)
770
+ lenlen = np.frombuffer(str.encode(lenlen, "raw_unicode_escape"), "<u1")[0]
771
+ if lenlen == 1:
772
+ length, pos = read_with_check(buf, pos, 1)
773
+ length = \
774
+ np.frombuffer(str.encode(length, "raw_unicode_escape"), "<u1")[0]
775
+ elif lenlen == 2:
776
+ length, pos = read_int16(buf, pos)
777
+ elif lenlen == 4:
778
+ length, pos = read_int32(buf, pos)
779
+ else:
780
+ raise ValueError
781
+ return length, pos
782
+
783
+
784
+ def read_int64(buf, pos, signed=False):
785
+ """
786
+ Reads a 64bit int.
787
+ :param buf: The buffer
788
+ :param pos: Position in the buffer
789
+ :param signed: Whether of not the int is signed
790
+ :return:
791
+ The int and the new position in the buffer
792
+ """
793
+ out, pos = read_with_check(buf=buf, pos=pos, nbytes=8)
794
+ out = ''.join(out)
795
+ dt = "<i8" if signed else "<u8"
796
+ out = np.frombuffer(str.encode(out, "raw_unicode_escape"), dt)[
797
+ 0] # interpret hexadecimal -> int (little-endian)
798
+ return out, pos
799
+
800
+
801
+ def read_int32(buf, pos, signed=False):
802
+ """
803
+ Reads a 32bit int.
804
+ :param buf: The buffer
805
+ :param pos: Position in the buffer
806
+ :param signed: Whether of not the int is signed
807
+ :return:
808
+ The int and the new position in the buffer
809
+ """
810
+ out, pos = read_with_check(buf=buf, pos=pos, nbytes=4)
811
+ out = ''.join(out)
812
+ dt = "<i4" if signed else "<u4"
813
+ out = np.frombuffer(str.encode(out, "raw_unicode_escape"), dt)[
814
+ 0] # interpret hexadecimal -> int (little-endian)
815
+ return out, pos
816
+
817
+
818
+ def read_int16(buf, pos, signed=False):
819
+ """
820
+ Reads a 16bit int.
821
+ :param buf: The buffer
822
+ :param pos: Position in the buffer
823
+ :param signed: Whether of not the int is signed
824
+ :return:
825
+ The int and the new position in the buffer
826
+ """
827
+ out, pos = read_with_check(buf=buf, pos=pos, nbytes=2)
828
+ out = ''.join(out)
829
+ dt = "<i2" if signed else "<u2"
830
+ out = np.frombuffer(str.encode(out, "raw_unicode_escape"), dt)[
831
+ 0] # interpret hexadecimal -> int (little-endian)
832
+ return out, pos
833
+
834
+
835
+ def read_double(buf, pos):
836
+ """
837
+ Reads a double (64bit)
838
+ :param buf: The buffer
839
+ :param pos: Position in the buffer
840
+ :return:
841
+ The double and the new position in the buffer
842
+ """
843
+ out, pos = read_with_check(buf=buf, pos=pos, nbytes=8)
844
+ out = ''.join(out)
845
+ dt = np.dtype('d') # double
846
+ dt = dt.newbyteorder('<') # little-endian
847
+ out = np.frombuffer(str.encode(out, "raw_unicode_escape"), dt)[
848
+ 0] # interpret hexadecimal -> int (little-endian)
849
+ return out, pos
850
+
851
+
852
+ def read_float(buf, pos):
853
+ """
854
+ Reads a float (32bit)
855
+ :param buf: The buffer
856
+ :param pos: Position in the buffer
857
+ :return:
858
+ The float and the new position in the buffer
859
+ """
860
+ out, pos = read_with_check(buf=buf, pos=pos, nbytes=4)
861
+ out = ''.join(out)
862
+ dt = np.dtype('f') # double
863
+ dt = dt.newbyteorder('<') # little-endian
864
+ out = np.frombuffer(str.encode(out, "raw_unicode_escape"), dt)[
865
+ 0] # interpret hexadecimal -> int (little-endian)
866
+ return out, pos
867
+
868
+
869
+ def read_with_check(buf, pos, nbytes):
870
+ """
871
+ Reads and returns n bytes.
872
+ :param buf: The input buffer
873
+ :param pos: The current position
874
+ :param nbytes: number of bytes to open_topography in
875
+ :return: The bytes and the new position in the buffer
876
+ """
877
+
878
+ if len(buf) < nbytes or len(buf) - nbytes < pos:
879
+ raise ValueError("Some sizes went wrong.")
880
+
881
+ out = buf[pos:pos + nbytes]
882
+ pos += int(nbytes)
883
+
884
+ out = out[0] if nbytes == 1 else out
885
+ return out, pos