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,802 @@
1
+ #
2
+ # Copyright 2018, 2020 Lars Pastewka
3
+ # 2019-2020 Antoine Sanner
4
+ # 2018, 2020 Michael Röttger
5
+ # 2019 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
+ """
29
+ SurfaceTopography profile from file input
30
+ """
31
+
32
+ import io
33
+ import re
34
+ import xml.etree.ElementTree as ElementTree
35
+ from io import TextIOWrapper
36
+ from struct import unpack
37
+ from zipfile import ZipFile
38
+ import numpy as np
39
+ import numpy.ma as ma
40
+
41
+ from .Reader import ReaderBase, ChannelInfo
42
+ from ..UniformLineScanAndTopography import Topography, UniformLineScan
43
+ from ..NonuniformLineScan import NonuniformLineScan
44
+
45
+ ###
46
+
47
+ height_units = {'m': 1.0, 'mm': 1e-3, 'µm': 1e-6, 'nm': 1e-9, 'Å': 1e-10}
48
+ voltage_units = {'kV': 1000.0, 'V': 1.0, 'mV': 1e-3, 'µV': 1e-6, 'nV': 1e-9}
49
+
50
+ units = dict(height=height_units, voltage=voltage_units)
51
+
52
+ CHANNEL_NAME_INFO_KEY = 'channel_name'
53
+
54
+
55
+ ###
56
+
57
+ def binary(func):
58
+ def func_wrapper(fobj, *args, **kwargs):
59
+ close_file = False
60
+ if not hasattr(fobj, 'read'):
61
+ fobj = open(fobj, 'rb')
62
+ close_file = True
63
+ try:
64
+ retvals = func(fobj, *args, **kwargs)
65
+ finally:
66
+ if close_file:
67
+ fobj.close()
68
+ return retvals
69
+
70
+ return func_wrapper
71
+
72
+
73
+ def text(func):
74
+ def func_wrapper(fobj, *args, **kwargs):
75
+ close_file = False
76
+ if not hasattr(fobj, 'read'):
77
+ fobj = open(fobj, 'r', encoding='utf-8')
78
+ fobj_text = fobj
79
+ close_file = True
80
+ elif is_binary_stream(fobj):
81
+ fobj_text = TextIOWrapper(fobj, encoding='utf-8')
82
+ else:
83
+ fobj_text = fobj
84
+
85
+ try:
86
+ retvals = func(fobj_text, *args, **kwargs)
87
+ finally:
88
+ if is_binary_stream(fobj):
89
+ fobj_text.detach()
90
+ fobj_text = fobj
91
+ if close_file:
92
+ fobj_text.close()
93
+ return retvals
94
+
95
+ return func_wrapper
96
+
97
+
98
+ def is_binary_stream(fobj):
99
+ """
100
+
101
+ :param fobj:
102
+ :return:
103
+ """
104
+ return isinstance(fobj, io.BytesIO) or (
105
+ hasattr(fobj, 'mode') and 'b' in fobj.mode)
106
+
107
+
108
+ ###
109
+
110
+ def get_unit_conversion_factor(unit1_str, unit2_str):
111
+ """
112
+ Compute factor for conversion from unit1 to unit2. Return None if units are
113
+ incompatible.
114
+ """
115
+ if unit1_str == unit2_str:
116
+ return 1
117
+ unit1_kind = None
118
+ unit2_kind = None
119
+ unit_scales = None
120
+ for key, values in units.items():
121
+ if unit1_str in values:
122
+ unit1_kind = key
123
+ unit_scales = values
124
+ if unit2_str in values:
125
+ unit2_kind = key
126
+ unit_scales = values
127
+ if unit1_kind is None or unit2_kind is None or unit1_kind != unit2_kind:
128
+ return None
129
+ return unit_scales[unit1_str] / unit_scales[unit2_str]
130
+
131
+
132
+ def mangle_height_unit(unit):
133
+ unit = unit.strip()
134
+ if unit == '':
135
+ return None
136
+ elif unit == 'A':
137
+ return 'Å'
138
+ elif unit == 'μm' or unit == 'um' or unit == '~m':
139
+ return 'µm'
140
+ else:
141
+ return unit
142
+
143
+
144
+ def mask_undefined(data, maxval=1e32):
145
+ """
146
+ If data contains undefined points, then return a masked array with all
147
+ undefined points masked.
148
+
149
+ The following heuristics is applied to identify undefined points:
150
+ - Remove points that are +/-inf or nan
151
+ - Remove points that are >+maxval or <-maxval
152
+ """
153
+ # First, we mask all points that are infinite or nan
154
+ mask = np.logical_and(np.isfinite(data), np.abs(data) < maxval)
155
+ if mask.sum() < len(mask):
156
+ return ma.masked_array(data, mask=np.logical_not(mask))
157
+ else:
158
+ return data
159
+
160
+
161
+ def make_wrapped_reader(reader_func, class_name='WrappedReader', format=None,
162
+ name=None, description=None):
163
+ class WrappedReader(ReaderBase):
164
+ """
165
+ emulates the new implementation of the readers
166
+ """
167
+
168
+ _format = format
169
+ _name = name
170
+ _description = description
171
+
172
+ def __init__(self, fobj):
173
+ self._fobj = fobj
174
+ self._file_position = 0
175
+ if hasattr(fobj, 'tell'):
176
+ self._file_position = fobj.tell()
177
+ self._topography = reader_func(fobj)
178
+ if CHANNEL_NAME_INFO_KEY in self._topography.info:
179
+ self._channel_name = self._topography.info[
180
+ CHANNEL_NAME_INFO_KEY]
181
+ del self._topography.info[CHANNEL_NAME_INFO_KEY]
182
+ else:
183
+ self._channel_name = "Default"
184
+
185
+ @property
186
+ def channels(self):
187
+ return [ChannelInfo(
188
+ self, 0,
189
+ name=self._channel_name,
190
+ dim=self._topography.dim,
191
+ info=self._topography.info,
192
+ nb_grid_pts=self._topography.nb_grid_pts,
193
+ physical_sizes=self._topography.physical_sizes)]
194
+
195
+ def topography(self, channel_index=None, physical_sizes=None,
196
+ height_scale_factor=None, info={}, periodic=False,
197
+ subdomain_locations=None, nb_subdomain_grid_pts=None):
198
+ if channel_index is None:
199
+ channel_index = self._default_channel_index
200
+
201
+ if subdomain_locations is not None or \
202
+ nb_subdomain_grid_pts is not None:
203
+ raise RuntimeError(
204
+ 'This reader does not support MPI parallelization.')
205
+
206
+ if channel_index != 0:
207
+ raise RuntimeError('Reader supports only a single channel 0.')
208
+
209
+ # Rewind to position where the data is. Otherwise this method
210
+ # cannot be called twice.
211
+ if hasattr(self._fobj, 'seek'):
212
+ self._fobj.seek(self._file_position)
213
+ return reader_func(self._fobj, physical_sizes=physical_sizes,
214
+ height_scale_factor=height_scale_factor,
215
+ info=info.copy(), periodic=periodic)
216
+
217
+ channels.__doc__ = ReaderBase.channels.__doc__
218
+ topography.__doc__ = ReaderBase.topography.__doc__
219
+
220
+ WrappedReader.__name__ = class_name
221
+ return WrappedReader
222
+
223
+
224
+ @text
225
+ def read_matrix(fobj, physical_sizes=None, factor=None, periodic=False):
226
+ """
227
+ Reads a surface profile from a text file and presents in in a
228
+ SurfaceTopography-conformant manner. No additional parsing of
229
+ meta-information is carried out.
230
+
231
+ Keyword Arguments:
232
+ fobj -- filename or file object
233
+ """
234
+ arr = np.loadtxt(fobj)
235
+ if physical_sizes is None:
236
+ surface = Topography(arr, arr.shape, periodic=periodic)
237
+ else:
238
+ surface = Topography(arr, physical_sizes, periodic=periodic)
239
+ if factor is not None:
240
+ surface = surface.scale(factor)
241
+ return surface
242
+
243
+
244
+ MatrixReader = make_wrapped_reader(
245
+ read_matrix, class_name="MatrixReader", format='matrix',
246
+ name='Plain text (matrix)')
247
+
248
+
249
+ @text
250
+ def read_asc(fobj, physical_sizes=None, height_scale_factor=None, x_factor=1.0,
251
+ z_factor=None, info={}, periodic=False):
252
+ # pylint: disable=too-many-branches,too-many-statements,invalid-name
253
+ """
254
+ Reads a surface profile (topography) from an generic asc file and presents
255
+ it in a surface-conformant manner. Applies some heuristic to extract
256
+ meta-information for different file formats.
257
+
258
+ The info dict of the topography returned is a copy from the given info dict
259
+ and may have some extra keys added:
260
+ - "unit": a common unit for the data, for dimensions and heights
261
+ - "channel_name": the name of the channel (if unknown "Default" is used")
262
+
263
+ Keyword Arguments:
264
+ fobj_in -- filename or file object
265
+ unit -- name of surface units, one of m, mm, μm/um, nm, A
266
+ x_factor -- multiplication factor for physical_sizes
267
+ z_factor -- multiplication factor for height
268
+ """
269
+ unit = info['unit'] if 'unit' in info else None
270
+
271
+ _float_regex = r'[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?'
272
+
273
+ checks = list()
274
+ # Resolution keywords
275
+ checks.append((re.compile(r"\b(?:x-pixels|h)\b\s*=\s*([0-9]+)"), int,
276
+ "yres"))
277
+ checks.append((re.compile(r"\b(?:y-pixels|w)\b\s*=\s*([0-9]+)"), int,
278
+ "xres"))
279
+
280
+ # Size keywords
281
+ checks.append(
282
+ (re.compile(r"\b(?:x-length|Width|Breite)\b\s*(?:=|\:)\s*(?P<value>" +
283
+ _float_regex + ")(?P<unit>.*)"), float, "xsiz"))
284
+ checks.append(
285
+ (re.compile(r"\b(?:y-length|Height|Höhe)\b\s*(?:=|\:)\s*(?P<value>" +
286
+ _float_regex + ")(?P<unit>.*)"), float, "ysiz"))
287
+
288
+ # Unit keywords
289
+ checks.append(
290
+ (re.compile(r"\b(?:x-unit)\b\s*(?:=|\:)\s*(\w+)"), str, "xunit"))
291
+ checks.append(
292
+ (re.compile(r"\b(?:y-unit)\b\s*(?:=|\:)\s*(\w+)"), str, "yunit"))
293
+ checks.append(
294
+ (re.compile(r"\b(?:z-unit|Value units)\b\s*(?:=|\:)\s*(\w+)"),
295
+ str, "zunit"))
296
+
297
+ # Scale factor keywords
298
+ checks.append(
299
+ (re.compile(r"(?:pixel\s+size)\s*=\s*(?P<value>" + _float_regex +
300
+ ")(?P<unit>.*)"), float, "xfac"))
301
+ checks.append((
302
+ re.compile((
303
+ r"(?:height\s+conversion\s+factor\s+\(->\s+(?P<unit>.*)\))\s*="
304
+ r"\s*(?P<value>" + _float_regex + ")")),
305
+ float, "zfac"))
306
+ # Channel name keywords
307
+ checks.append((
308
+ re.compile(r"\b(?:Channel|Kanal)\b\s*(?:=|\:)\s*([\w|\s]+)"),
309
+ str, "channel_name"))
310
+
311
+ xres = yres = xsiz = ysiz = xunit = yunit = zunit = xfac = yfac = None
312
+ zfac = None
313
+ channel_name = "Default"
314
+
315
+ def process_comment(line):
316
+ "Find and interpret known comments in the header of the asc file"
317
+ nonlocal xres, yres, xsiz, ysiz, xunit, yunit, zunit, data, xfac, yfac
318
+ nonlocal zfac
319
+ nonlocal channel_name
320
+ # TODO Is 'fun' needed here? No used so far..
321
+ for reg, fun, key in checks:
322
+ match = reg.search(line)
323
+ if match is None:
324
+ continue
325
+ if key == 'xres':
326
+ xres = int(match.group(1))
327
+ elif key == 'yres':
328
+ yres = int(match.group(1))
329
+ elif key == 'xsiz':
330
+ xsiz = float(match.group('value'))
331
+ x = match.group('unit')
332
+ if x:
333
+ xunit = mangle_height_unit(x)
334
+ elif key == 'ysiz':
335
+ ysiz = float(match.group('value'))
336
+ y = match.group('unit')
337
+ if y:
338
+ yunit = mangle_height_unit(y)
339
+ elif key == 'xunit':
340
+ xunit = mangle_height_unit(match.group(1))
341
+ elif key == 'yunit':
342
+ yunit = mangle_height_unit(match.group(1))
343
+ elif key == 'zunit':
344
+ zunit = mangle_height_unit(match.group(1))
345
+ elif key == 'xfac':
346
+ xfac = float(match.group('value'))
347
+ xunit = mangle_height_unit(match.group('unit'))
348
+ elif key == 'zfac':
349
+ zfac = float(match.group('value'))
350
+ zunit = mangle_height_unit(match.group('unit'))
351
+ elif key == 'channel_name':
352
+ channel_name = match.group(1).strip()
353
+
354
+ data = []
355
+ for line in fobj:
356
+ line_elements = line.strip().split()
357
+ if len(line_elements) > 0:
358
+ try:
359
+ data += [[float(strval) for strval in line_elements]]
360
+ except ValueError:
361
+ process_comment(line)
362
+
363
+ data = np.array(data).T
364
+ nx, ny = data.shape
365
+ if nx == 2 or ny == 2:
366
+ raise Exception(
367
+ "This file has just two rows or two columns and is more likely a "
368
+ "line scan than a map.")
369
+ if xres is not None and xres != nx:
370
+ raise Exception(
371
+ "The number of rows (={}) open_topography from the file '{}' "
372
+ "does not match the nb_grid_pts in the file's metadata (={})."
373
+ .format(nx, fobj, xres))
374
+ if yres is not None and yres != ny:
375
+ raise Exception(
376
+ "The number of columns (={}) open_topography from the file '{}' "
377
+ "does not match the nb_grid_pts in the file's metadata "
378
+ "(={}).".format(ny, fobj, yres))
379
+
380
+ # Handle scale factors
381
+ if xfac is not None and yfac is None:
382
+ yfac = xfac
383
+ elif xfac is None and yfac is not None:
384
+ xfac = yfac
385
+ if xfac is not None:
386
+ if xsiz is None:
387
+ xsiz = xfac * nx
388
+ else:
389
+ xsiz *= xfac
390
+ if yfac is not None:
391
+ if ysiz is None:
392
+ ysiz = yfac * ny
393
+ else:
394
+ ysiz *= yfac
395
+ if z_factor is not None:
396
+ zfac = z_factor if zfac is None else zfac * z_factor
397
+
398
+ info = info.copy()
399
+
400
+ # Handle units -> convert to target unit
401
+ if xunit is None and zunit is not None:
402
+ xunit = zunit
403
+ if yunit is None and zunit is not None:
404
+ yunit = zunit
405
+
406
+ if unit is None:
407
+ unit = zunit
408
+ if unit is not None:
409
+ if xunit is not None:
410
+ xsiz *= height_units[xunit] / height_units[unit]
411
+ if yunit is not None:
412
+ ysiz *= height_units[yunit] / height_units[unit]
413
+ if zunit is not None:
414
+ if zfac is None:
415
+ zfac = height_units[zunit] / height_units[unit]
416
+ else:
417
+ zfac *= height_units[zunit] / height_units[unit]
418
+ info['unit'] = unit
419
+
420
+ # handle channel name
421
+ # we use the info dict here to transfer the channel name
422
+ info[CHANNEL_NAME_INFO_KEY] = channel_name
423
+
424
+ # calculate physical sizes and generate topography
425
+ if xsiz is not None and ysiz is not None and physical_sizes is None:
426
+ physical_sizes = (x_factor * xsiz, x_factor * ysiz)
427
+ if data.shape[0] == 1:
428
+ if physical_sizes is not None and len(physical_sizes) > 1:
429
+ physical_sizes = physical_sizes[0]
430
+ surface = UniformLineScan(data[0, :], physical_sizes, info=info,
431
+ periodic=periodic)
432
+ else:
433
+ surface = Topography(data, physical_sizes, info=info,
434
+ periodic=periodic)
435
+ if height_scale_factor is not None:
436
+ zfac = height_scale_factor
437
+ if zfac is not None and zfac != 1:
438
+ surface = surface.scale(zfac)
439
+ return surface
440
+
441
+
442
+ AscReader = make_wrapped_reader(read_asc, class_name="AscReader", format='asc',
443
+ name='Plain text (with headers)',
444
+ description='''
445
+ SurfaceTopography data stored in plain text (ASCII) format needs to be stored
446
+ in a matrix format. Each row contains the height information for subsequent
447
+ points in x-direction separated by a whitespace. The next row belong to the
448
+ following y-coordinate. Note that if the file has three or less columns, it
449
+ will be interpreted as a topography stored in a coordinate format (the three
450
+ columns contain the x, y and z coordinates of the same points). The smallest
451
+ topography that can be provided in this format is therefore 4 x 1.
452
+
453
+ The reader supports parsing file headers for additional metadata. This allows
454
+ to specify the physical size of the topography and the unit. In particular, it
455
+ supports reading ASCII files exported from SPIP and Gwyddion.
456
+
457
+ When writing your own ASCII files, we recommend to prepent the header with a
458
+ '#'. The following file is an example that contains 4 x 3 data points:
459
+ ```
460
+ # Channel: Main
461
+ # Width: 10 µm
462
+ # Height: 10 µm
463
+ # Value units: m
464
+ 1.0 2.0 3.0 4.0
465
+ 5.0 6.0 7.0 8.0
466
+ 9.0 10.0 11.0 12.0
467
+ ```
468
+ ''')
469
+
470
+
471
+ @text
472
+ def read_xyz(fobj, physical_sizes=None, height_scale_factor=None, info={},
473
+ periodic=False, tol=1e-6):
474
+ """
475
+ Load xyz-file. These files contain line scan information in terms of
476
+ (x,y)-positions.
477
+
478
+ Parameters
479
+ ----------
480
+ fobj : str or file object
481
+ File name or stream.
482
+ unit : str
483
+ Physical unit.
484
+ tol : float
485
+ Tolerance for detecting uniform grids
486
+
487
+ Returns
488
+ -------
489
+ topography : Topography
490
+ SurfaceTopography object.
491
+ """
492
+ # pylint: disable=invalid-name
493
+ data = np.loadtxt(fobj, unpack=True)
494
+
495
+ if len(data) == 2:
496
+ # This is a line scan.
497
+ x, z = data
498
+ x -= np.min(x)
499
+
500
+ d_uniform = (x[-1] - x[0]) / (len(x) - 1)
501
+ if np.max(np.abs(np.diff(x) - d_uniform)) < tol:
502
+ if physical_sizes is None:
503
+ physical_sizes = d_uniform * len(x)
504
+ t = UniformLineScan(z, physical_sizes, info=info,
505
+ periodic=periodic)
506
+ else:
507
+ if physical_sizes is not None:
508
+ raise ValueError(
509
+ 'XYZ reader found nonuniform data. Manually setting the'
510
+ 'physical size is not possible for this type of data.')
511
+
512
+ t = NonuniformLineScan(x, z, info=info, periodic=periodic)
513
+ elif len(data) == 3:
514
+ # This is a topography map.
515
+ x, y, z = data
516
+
517
+ # Sort x-values into bins. Assume points on surface are equally spaced.
518
+ dx = x[1] - x[0]
519
+ binx = np.array(x / dx + 0.5, dtype=int)
520
+ n = np.bincount(binx)
521
+ ny = n[0]
522
+ assert np.all(n == ny) # FIXME: Turn assert into exception
523
+
524
+ # Sort y-values into bins.
525
+ dy = y[binx == 0][1] - y[binx == 0][0]
526
+ biny = np.array(y / dy + 0.5, dtype=int)
527
+ n = np.bincount(biny)
528
+ nx = n[0]
529
+ assert np.all(n == nx) # FIXME: Turn assert into exception
530
+
531
+ # Sort data into bins.
532
+ data = np.zeros((nx, ny))
533
+ data[binx, biny] = z
534
+
535
+ # Sanity check. Should be covered by above asserts.
536
+ value_present = np.zeros((nx, ny), dtype=bool)
537
+ value_present[binx, biny] = True
538
+ assert np.all(value_present) # FIXME: Turn assert into exception
539
+
540
+ if physical_sizes is None:
541
+ physical_sizes = (dx * nx, dy * ny)
542
+ t = Topography(data, physical_sizes, info=info, periodic=periodic)
543
+ else:
544
+ raise Exception(
545
+ 'Expected two or three columns for topography that is a list of '
546
+ 'positions and heights.')
547
+
548
+ if height_scale_factor is not None:
549
+ t = t.scale(height_scale_factor)
550
+ return t
551
+
552
+
553
+ XYZReader = make_wrapped_reader(
554
+ read_xyz, class_name="XYZReader", format='xyz',
555
+ name='Plain text (x,y,z coordinates)', description='''
556
+ SurfaceTopography information can be provided as coordinate data. This is a
557
+ text file that contains either two columns (for line scans) or three columns
558
+ (for two-dimensional topographies) of data. The parser does not support
559
+ reading header information. Units can therefore not be provided directly
560
+ within this file format.
561
+
562
+ Line scans can be provided on a non-uniform grid. The x-coordinates do not
563
+ need to be equally spaced and the surface specified in this format can be
564
+ reentrant. The code interprets such topographies as piecewise linear between
565
+ the points that are specified in the file.
566
+
567
+ Two-dimensional topography maps need to reside on a regular grid. The x- and
568
+ y-coordinates need to be equally spaced.
569
+ ''')
570
+
571
+
572
+ def read_x3p(fobj, physical_sizes=None, height_scale_factor=None, info={},
573
+ periodic=False):
574
+ """
575
+ Load x3p-file.
576
+ See: http://opengps.eu
577
+
578
+ FIXME: Descriptive error messages. Probably needs to be made more robust.
579
+
580
+ Keyword Arguments:
581
+ fobj -- filename or file object
582
+ """
583
+
584
+ # Data types of binary container
585
+ # See: https://sourceforge.net/p/open-gps/mwiki/X3p/
586
+ dtype_map = {'I': np.dtype('<u2'),
587
+ 'L': np.dtype('<u4'),
588
+ 'F': np.dtype('f4'),
589
+ 'D': np.dtype('f8')}
590
+
591
+ with ZipFile(fobj, 'r') as x3p:
592
+ xmlroot = ElementTree.parse(x3p.open('main.xml')).getroot()
593
+ record1 = xmlroot.find('Record1')
594
+ record3 = xmlroot.find('Record3')
595
+
596
+ if record1 is None:
597
+ raise IOError("'Record1' not found in XML.")
598
+ if record3 is None:
599
+ raise IOError("'Record3' not found in XML.")
600
+
601
+ # Parse record1
602
+
603
+ feature_type = record1.find('FeatureType')
604
+ if feature_type.text != 'SUR':
605
+ raise ValueError("FeatureType must be 'SUR'.")
606
+ axes = record1.find('Axes')
607
+ cx = axes.find('CX')
608
+ cy = axes.find('CY')
609
+ cz = axes.find('CZ')
610
+
611
+ if cx.find('AxisType').text != 'I':
612
+ raise ValueError(
613
+ "CX AxisType is not 'I'. Don't know how to handle "
614
+ "this.")
615
+ if cy.find('AxisType').text != 'I':
616
+ raise ValueError(
617
+ "CY AxisType is not 'I'. Don't know how to handle "
618
+ "this.")
619
+ if cz.find('AxisType').text != 'A':
620
+ raise ValueError(
621
+ "CZ AxisType is not 'A'. Don't know how to handle "
622
+ "this.")
623
+
624
+ xinc = float(cx.find('Increment').text)
625
+ yinc = float(cy.find('Increment').text)
626
+
627
+ datatype = cz.find('DataType').text
628
+ dtype = dtype_map[datatype]
629
+
630
+ # Parse record3
631
+ matrix_dimension = record3.find('MatrixDimension')
632
+ nx = int(matrix_dimension.find('SizeX').text)
633
+ ny = int(matrix_dimension.find('SizeY').text)
634
+ nz = int(matrix_dimension.find('SizeZ').text)
635
+
636
+ if nz != 1:
637
+ raise ValueError('Z dimension has extend != 1. Volumetric data '
638
+ 'is not supported.')
639
+
640
+ data_link = record3.find('DataLink')
641
+ binfn = data_link.find('PointDataLink').text
642
+
643
+ rawdata = x3p.open(binfn).read(nx * ny * dtype.itemsize)
644
+ data = np.frombuffer(rawdata, count=nx * ny * nz,
645
+ dtype=dtype).reshape(nx, ny).T
646
+ if physical_sizes is None:
647
+ physical_sizes = (xinc * nx, yinc * ny)
648
+ t = Topography(data, physical_sizes, info=info, periodic=periodic)
649
+ if height_scale_factor is not None:
650
+ t = t.scale(height_scale_factor)
651
+ return t
652
+
653
+
654
+ X3PReader = make_wrapped_reader(
655
+ read_x3p, class_name="X3PReader", format='x3p',
656
+ name='XML 3D surface profile (X3P)', description='''
657
+ X3P is a container format conforming to the ISO 5436-2 (Geometrical Product
658
+ Specifications — Surface texture) standard. The format is defined in ISO
659
+ 25178 and is a standardized format for the exchange of surface topography
660
+ data. The full specification of the format can be found
661
+ [here](http://www.opengps.eu/).
662
+ ''')
663
+
664
+
665
+ @binary
666
+ def read_opd(fobj, physical_sizes=None, height_scale_factor=None, info={},
667
+ periodic=False):
668
+ """
669
+ Load Wyko Vision OPD file.
670
+
671
+ FIXME: Descriptive error messages. Probably needs to be made more robust.
672
+
673
+ Keyword Arguments:
674
+ fobj -- filename or file object
675
+ """
676
+
677
+ BLOCK_SIZE = 24
678
+
679
+ def read_block(fobj):
680
+ blkname = fobj.read(16).split(b'\0', 1)[0].decode('latin-1')
681
+ blktype, blklen, blkattr = unpack('<hlH', fobj.read(8))
682
+ return blkname, blktype, blklen, blkattr
683
+
684
+ # Skip header
685
+ fobj.read(2)
686
+
687
+ # Read directory block
688
+ dirname, dirtype, dirlen, dirattr = read_block(fobj)
689
+ if dirname != 'Directory':
690
+ raise IOError("Error reading directory block. "
691
+ "Header is '{}', expected 'Directory'".format(dirname))
692
+ num_blocks = dirlen // BLOCK_SIZE
693
+ if num_blocks * BLOCK_SIZE != dirlen:
694
+ raise IOError(
695
+ 'Directory length is not a multiple of the block physical_sizes.')
696
+
697
+ blocks = []
698
+ for i in range(num_blocks - 1):
699
+ blocks += [read_block(fobj)]
700
+
701
+ data = None
702
+ nx = None
703
+ ny = None
704
+ pixel_size = 1.0
705
+ aspect = 1.0
706
+ mult = 1.0
707
+ for n, t, L, a in blocks:
708
+ if L <= 0:
709
+ continue
710
+ if n == 'RAW DATA' or n == 'RAW_DATA' or n == 'OPD' or n == 'Raw':
711
+ if data is not None:
712
+ raise IOError('Multiple data blocks encountered.')
713
+
714
+ nx, ny, elsize = unpack('<HHH', fobj.read(6))
715
+ if elsize == 1:
716
+ dtype = np.dtype('c')
717
+ elif elsize == 2:
718
+ dtype = np.dtype('<i2')
719
+ elif elsize == 4:
720
+ dtype = np.dtype('f4')
721
+ else:
722
+ raise IOError("Don't know how to handle element of size {}."
723
+ .format(elsize))
724
+ rawdata = fobj.read(nx * ny * dtype.itemsize)
725
+ data = np.frombuffer(rawdata, count=nx * ny, dtype=dtype)
726
+ elif n == 'Wavelength':
727
+ wavelength, = unpack('<f', fobj.read(4))
728
+ elif n == 'Mult':
729
+ mult, = unpack('<H', fobj.read(2))
730
+ elif n == 'Aspect':
731
+ aspect, = unpack('<f', fobj.read(4))
732
+ elif n == 'Pixel_size':
733
+ pixel_size, = unpack('<f', fobj.read(4))
734
+ else:
735
+ fobj.read(L)
736
+
737
+ if data is None:
738
+ raise IOError('No data block encountered.')
739
+
740
+ data = mask_undefined(data)
741
+ data.shape = (nx, ny)
742
+
743
+ # Height are in nm, width in mm
744
+ if physical_sizes is None:
745
+ physical_sizes = (nx * pixel_size, ny * pixel_size * aspect)
746
+ surface = Topography(np.fliplr(data), physical_sizes,
747
+ info={**info, **dict(unit='mm')}, periodic=periodic)
748
+ if height_scale_factor is None:
749
+ surface = surface.scale(wavelength / mult * 1e-6)
750
+ else:
751
+ surface = surface.scale(height_scale_factor)
752
+ return surface
753
+
754
+
755
+ OPDReader = make_wrapped_reader(read_opd, class_name="OPDReader", format='opd',
756
+ name='Wyko OPD', description='''
757
+ Files generated by the Vision software of the Bruker Wyko white-light
758
+ interferometer.
759
+ ''')
760
+
761
+
762
+ @binary
763
+ def read_hgt(fobj, physical_sizes=None, height_scale_factor=None, info={},
764
+ periodic=False):
765
+ """
766
+ Read Shuttle Radar SurfaceTopography Mission (SRTM) topography data
767
+ (.hgt extension).
768
+
769
+ Keyword Arguments:
770
+ fobj -- filename or file object
771
+ """
772
+ fobj.seek(0, 2)
773
+ fsize = fobj.tell()
774
+ fobj.seek(0)
775
+
776
+ dim = int(np.sqrt(fsize / 2))
777
+ if dim * dim * 2 != fsize:
778
+ raise RuntimeError(
779
+ 'File physical_sizes of {0} bytes does not match file '
780
+ 'physical_sizes for a map of dimension {1}x{1}.'
781
+ .format(fsize, dim))
782
+ data = np.fromfile(fobj, dtype=np.dtype('>i2'),
783
+ count=dim * dim).reshape((dim, dim))
784
+
785
+ if physical_sizes is None:
786
+ topography = Topography(data, physical_sizes=data.shape, info=info,
787
+ periodic=periodic)
788
+ else:
789
+ topography = Topography(data, physical_sizes=physical_sizes, info=info,
790
+ periodic=periodic)
791
+ if height_scale_factor is not None:
792
+ topography = topography.scale(height_scale_factor)
793
+ return topography
794
+
795
+
796
+ HGTReader = make_wrapped_reader(
797
+ read_hgt, class_name="HGTReader", format='hgt',
798
+ name='NASA shuttle radar topography mission', description='''
799
+ Data format of the NASA shuttle radar topography mission that recorded the '
800
+ earths topography. More information can be found
801
+ [here](https://www2.jpl.nasa.gov/srtm/).
802
+ ''')