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.
- EGG-INFO/PKG-INFO +11 -0
- EGG-INFO/SOURCES.txt +96 -0
- EGG-INFO/dependency_links.txt +1 -0
- EGG-INFO/native_libs.txt +1 -0
- EGG-INFO/not-zip-safe +1 -0
- EGG-INFO/requires.txt +6 -0
- EGG-INFO/top_level.txt +3 -0
- SurfaceTopography/ChangeLog.md +358 -0
- SurfaceTopography/Converters.py +197 -0
- SurfaceTopography/Generation.py +440 -0
- SurfaceTopography/HeightContainer.py +315 -0
- SurfaceTopography/IO/DI.py +261 -0
- SurfaceTopography/IO/FromFile.py +802 -0
- SurfaceTopography/IO/H5.py +79 -0
- SurfaceTopography/IO/IBW.py +176 -0
- SurfaceTopography/IO/MI.py +307 -0
- SurfaceTopography/IO/Matlab.py +106 -0
- SurfaceTopography/IO/NC.py +228 -0
- SurfaceTopography/IO/NPY.py +139 -0
- SurfaceTopography/IO/OPDx.py +885 -0
- SurfaceTopography/IO/Reader.py +389 -0
- SurfaceTopography/IO/__init__.py +241 -0
- SurfaceTopography/IO/__pycache__/DI.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/FromFile.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/H5.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/IBW.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/MI.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/Matlab.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/NC.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/NPY.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/OPDx.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/Reader.cpython-36.pyc +0 -0
- SurfaceTopography/IO/__pycache__/__init__.cpython-36.pyc +0 -0
- SurfaceTopography/Interpolation.py +30 -0
- SurfaceTopography/Nonuniform/Autocorrelation.py +161 -0
- SurfaceTopography/Nonuniform/Detrending.py +87 -0
- SurfaceTopography/Nonuniform/PowerSpectrum.py +178 -0
- SurfaceTopography/Nonuniform/ScalarParameters.py +140 -0
- SurfaceTopography/Nonuniform/VariableBandwidth.py +155 -0
- SurfaceTopography/Nonuniform/__init__.py +28 -0
- SurfaceTopography/Nonuniform/__pycache__/Autocorrelation.cpython-36.pyc +0 -0
- SurfaceTopography/Nonuniform/__pycache__/Detrending.cpython-36.pyc +0 -0
- SurfaceTopography/Nonuniform/__pycache__/PowerSpectrum.cpython-36.pyc +0 -0
- SurfaceTopography/Nonuniform/__pycache__/ScalarParameters.cpython-36.pyc +0 -0
- SurfaceTopography/Nonuniform/__pycache__/VariableBandwidth.cpython-36.pyc +0 -0
- SurfaceTopography/Nonuniform/__pycache__/__init__.cpython-36.pyc +0 -0
- SurfaceTopography/Nonuniform/__pycache__/common.cpython-36.pyc +0 -0
- SurfaceTopography/Nonuniform/common.py +95 -0
- SurfaceTopography/NonuniformLineScan.py +310 -0
- SurfaceTopography/Special.py +225 -0
- SurfaceTopography/Tools/__init__.py +28 -0
- SurfaceTopography/Tools/__pycache__/__init__.cpython-36.pyc +0 -0
- SurfaceTopography/Tools/__pycache__/common.cpython-36.pyc +0 -0
- SurfaceTopography/Tools/common.py +75 -0
- SurfaceTopography/Uniform/Autocorrelation.py +211 -0
- SurfaceTopography/Uniform/Detrending.py +155 -0
- SurfaceTopography/Uniform/Filtering.py +226 -0
- SurfaceTopography/Uniform/Interpolation.py +159 -0
- SurfaceTopography/Uniform/PowerSpectrum.py +197 -0
- SurfaceTopography/Uniform/ScalarParameters.py +173 -0
- SurfaceTopography/Uniform/VariableBandwidth.py +169 -0
- SurfaceTopography/Uniform/__init__.py +28 -0
- SurfaceTopography/Uniform/__pycache__/Autocorrelation.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/__pycache__/Detrending.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/__pycache__/Filtering.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/__pycache__/Interpolation.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/__pycache__/PowerSpectrum.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/__pycache__/ScalarParameters.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/__pycache__/VariableBandwidth.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/__pycache__/__init__.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/__pycache__/common.cpython-36.pyc +0 -0
- SurfaceTopography/Uniform/common.py +260 -0
- SurfaceTopography/UniformLineScanAndTopography.py +887 -0
- SurfaceTopography/__init__.py +62 -0
- SurfaceTopography/__pycache__/Converters.cpython-36.pyc +0 -0
- SurfaceTopography/__pycache__/Generation.cpython-36.pyc +0 -0
- SurfaceTopography/__pycache__/HeightContainer.cpython-36.pyc +0 -0
- SurfaceTopography/__pycache__/Interpolation.cpython-36.pyc +0 -0
- SurfaceTopography/__pycache__/NonuniformLineScan.cpython-36.pyc +0 -0
- SurfaceTopography/__pycache__/Special.cpython-36.pyc +0 -0
- SurfaceTopography/__pycache__/UniformLineScanAndTopography.cpython-36.pyc +0 -0
- SurfaceTopography/__pycache__/__init__.cpython-36.pyc +0 -0
- SurfaceTopography/__pycache__/common.cpython-36.pyc +0 -0
- SurfaceTopography/common.py +111 -0
- _SurfaceTopography.cpython-36m-x86_64-linux-gnu.so +0 -0
- _SurfaceTopography.py +8 -0
- __pycache__/_SurfaceTopography.cpython-36.pyc +0 -0
- test/IO/__init__.py +28 -0
- test/IO/__pycache__/__init__.cpython-36.pyc +0 -0
- test/IO/__pycache__/test_ibw.cpython-36.pyc +0 -0
- test/IO/__pycache__/test_io.cpython-36.pyc +0 -0
- test/IO/__pycache__/test_mi.cpython-36.pyc +0 -0
- test/IO/__pycache__/test_netcdf.cpython-36.pyc +0 -0
- test/IO/__pycache__/test_npy.cpython-36.pyc +0 -0
- test/IO/__pycache__/test_opdx.cpython-36.pyc +0 -0
- test/IO/test_ibw.py +178 -0
- test/IO/test_io.py +401 -0
- test/IO/test_mi.py +103 -0
- test/IO/test_netcdf.py +134 -0
- test/IO/test_npy.py +135 -0
- test/IO/test_opdx.py +441 -0
- test/__init__.py +26 -0
- test/__pycache__/__init__.cpython-36.pyc +0 -0
- test/__pycache__/compare-orientation-with-gwyddion.cpython-36.pyc +0 -0
- test/__pycache__/conftest.cpython-36.pyc +0 -0
- test/__pycache__/test_autocorrelation.cpython-36.pyc +0 -0
- test/__pycache__/test_bicubic.cpython-36.pyc +0 -0
- test/__pycache__/test_filtering.cpython-36.pyc +0 -0
- test/__pycache__/test_generation.cpython-36.pyc +0 -0
- test/__pycache__/test_import.cpython-36.pyc +0 -0
- test/__pycache__/test_make_sphere.cpython-36.pyc +0 -0
- test/__pycache__/test_power_spectrum.cpython-36.pyc +0 -0
- test/__pycache__/test_scalar_parameters.cpython-36.pyc +0 -0
- test/__pycache__/test_topography1.cpython-36.pyc +0 -0
- test/__pycache__/test_topography2.cpython-36.pyc +0 -0
- test/__pycache__/test_variable_bandwidth.cpython-36.pyc +0 -0
- test/compare-orientation-with-gwyddion.py +102 -0
- test/conftest.py +80 -0
- test/test_autocorrelation.py +311 -0
- test/test_bicubic.py +190 -0
- test/test_filtering.py +69 -0
- test/test_generation.py +259 -0
- test/test_import.py +54 -0
- test/test_make_sphere.py +108 -0
- test/test_power_spectrum.py +259 -0
- test/test_scalar_parameters.py +176 -0
- test/test_topography1.py +1287 -0
- test/test_topography2.py +328 -0
- 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
|
+
''')
|