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,315 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2019-2020 Lars Pastewka
|
|
3
|
+
# 2019-2020 Antoine Sanner
|
|
4
|
+
# 2019 Michael Röttger
|
|
5
|
+
#
|
|
6
|
+
# ### MIT license
|
|
7
|
+
#
|
|
8
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
# in the Software without restriction, including without limitation the rights
|
|
11
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
# furnished to do so, subject to the following conditions:
|
|
14
|
+
#
|
|
15
|
+
# The above copyright notice and this permission notice shall be included in
|
|
16
|
+
# all copies or substantial portions of the Software.
|
|
17
|
+
#
|
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
# SOFTWARE.
|
|
25
|
+
#
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
Base class for geometric topogography descriptions
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import abc
|
|
32
|
+
|
|
33
|
+
from NuMPI import MPI
|
|
34
|
+
|
|
35
|
+
# Standardized entries for the info dictionary
|
|
36
|
+
|
|
37
|
+
# Length unit of the measurement. The unit applies to both lateral and heights.
|
|
38
|
+
# Data type: str
|
|
39
|
+
INFO_UNIT = 'unit'
|
|
40
|
+
# Data and time of the data acquisition.
|
|
41
|
+
# Data type: datetime.datetime
|
|
42
|
+
INFO_ACQUISITION_TIME = 'acquisition_time'
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AbstractHeightContainer(object):
|
|
46
|
+
"""
|
|
47
|
+
Base class for all containers storing height information.
|
|
48
|
+
|
|
49
|
+
The member dictionary `_functions` contains a list of functions that
|
|
50
|
+
can be executed on this specific container.
|
|
51
|
+
|
|
52
|
+
The dictionary itself is owned by the interface,
|
|
53
|
+
`UniformTopographyInterface` and `NonuniformLineScanInterface`. This is
|
|
54
|
+
because the functions are determined by the type of topography that is
|
|
55
|
+
represented, not by the pipeline hierarchy. For example, convertes that
|
|
56
|
+
convert uniform to nonuniform and vice versa need to have the respective
|
|
57
|
+
interface of the format they are converting to.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
class Error(Exception):
|
|
61
|
+
# pylint: disable=missing-docstring
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
def __init__(self, info={}, communicator=MPI.COMM_WORLD):
|
|
65
|
+
self._info = info.copy()
|
|
66
|
+
self._communicator = communicator
|
|
67
|
+
|
|
68
|
+
def apply(self, name, *args, **kwargs):
|
|
69
|
+
self._functions[name](self, *args, **kwargs)
|
|
70
|
+
|
|
71
|
+
def __getattr__(self, name):
|
|
72
|
+
if name in self._functions:
|
|
73
|
+
def func(*args, **kwargs):
|
|
74
|
+
return self._functions[name](self, *args, **kwargs)
|
|
75
|
+
|
|
76
|
+
func.__doc__ = self._functions[name].__doc__
|
|
77
|
+
return func
|
|
78
|
+
else:
|
|
79
|
+
raise AttributeError(
|
|
80
|
+
"Unkown attribute '{}' and no analysis or pipeline function "
|
|
81
|
+
"of this name registered (class {}). Available functions: {}"
|
|
82
|
+
.format(name, self.__class__.__name__,
|
|
83
|
+
', '.join(self._functions.keys())))
|
|
84
|
+
|
|
85
|
+
def __dir__(self):
|
|
86
|
+
return sorted(super().__dir__() + [*self._functions])
|
|
87
|
+
|
|
88
|
+
def __getstate__(self):
|
|
89
|
+
"""
|
|
90
|
+
Upon pickling, it is called and the returned object is pickled as the
|
|
91
|
+
contents for the instance.
|
|
92
|
+
"""
|
|
93
|
+
return self._info
|
|
94
|
+
|
|
95
|
+
def __setstate__(self, state):
|
|
96
|
+
"""
|
|
97
|
+
Upon unpickling, it is called with the unpickled state.
|
|
98
|
+
The argument `state` is the result of `__getstate__`.
|
|
99
|
+
"""
|
|
100
|
+
self._info = state
|
|
101
|
+
self._communicator = MPI.COMM_WORLD
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
@abc.abstractmethod
|
|
105
|
+
def is_periodic(self):
|
|
106
|
+
"""Return whether the topography is periodically repeated at the
|
|
107
|
+
boundaries."""
|
|
108
|
+
raise NotImplementedError
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
@abc.abstractmethod
|
|
112
|
+
def dim(self):
|
|
113
|
+
"""Returns 1 for line scans and 2 for topography maps."""
|
|
114
|
+
raise NotImplementedError
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
@abc.abstractmethod
|
|
118
|
+
def physical_sizes(self, ):
|
|
119
|
+
"""Return the physical sizes of the topography."""
|
|
120
|
+
raise NotImplementedError
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def info(self, ):
|
|
124
|
+
"""
|
|
125
|
+
Return the info dictionary. The info dictionary contains auxiliary data
|
|
126
|
+
found in the topography data file but not directly used by PyCo.
|
|
127
|
+
|
|
128
|
+
The dictionary can contain any type of information. There are a few
|
|
129
|
+
standardized keys, listed in the following.
|
|
130
|
+
|
|
131
|
+
Standardized keys:
|
|
132
|
+
unit : str
|
|
133
|
+
Unit of the topography. The unit information applies to the lateral
|
|
134
|
+
units (the physical size) as well as to heights units. Examples:
|
|
135
|
+
'µm', 'nm'.
|
|
136
|
+
datetime : :obj:`datetime`
|
|
137
|
+
Date and time of the measurement.
|
|
138
|
+
"""
|
|
139
|
+
return self._info
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def communicator(self):
|
|
143
|
+
"""Return the MPI communicator object."""
|
|
144
|
+
return self._communicator
|
|
145
|
+
|
|
146
|
+
def pipeline(self):
|
|
147
|
+
return [self]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class DecoratedTopography(AbstractHeightContainer):
|
|
151
|
+
"""
|
|
152
|
+
Base class of topographies with parent. Having a parent means that the
|
|
153
|
+
data is owned by the parent, but the present class performs
|
|
154
|
+
transformations on that data. This is a simple realization of a
|
|
155
|
+
processing pipeline. Note that child topographies don't store their
|
|
156
|
+
own physical_sizes etc. but pass this information through to the parent.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
def __init__(self, topography, info={}):
|
|
160
|
+
"""
|
|
161
|
+
Arguments
|
|
162
|
+
---------
|
|
163
|
+
topography : SurfaceTopography
|
|
164
|
+
The parent topography.
|
|
165
|
+
"""
|
|
166
|
+
super().__init__(info=info)
|
|
167
|
+
assert isinstance(topography, AbstractHeightContainer)
|
|
168
|
+
self.parent_topography = topography
|
|
169
|
+
self._communicator = self.parent_topography.communicator
|
|
170
|
+
|
|
171
|
+
def __getstate__(self):
|
|
172
|
+
""" is called and the returned object is pickled as the contents for
|
|
173
|
+
the instance
|
|
174
|
+
"""
|
|
175
|
+
state = super().__getstate__(), self.parent_topography
|
|
176
|
+
return state
|
|
177
|
+
|
|
178
|
+
def __setstate__(self, state):
|
|
179
|
+
""" Upon unpickling, it is called with the unpickled state
|
|
180
|
+
Keyword Arguments:
|
|
181
|
+
state -- result of __getstate__
|
|
182
|
+
"""
|
|
183
|
+
superstate, self.parent_topography = state
|
|
184
|
+
super().__setstate__(superstate)
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def info(self):
|
|
188
|
+
""" Return info dictionary """
|
|
189
|
+
info = self.parent_topography.info.copy()
|
|
190
|
+
info.update(self._info)
|
|
191
|
+
return info
|
|
192
|
+
|
|
193
|
+
def pipeline(self):
|
|
194
|
+
return self.parent_topography.pipeline() + [self]
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class TopographyInterface(object):
|
|
198
|
+
@classmethod
|
|
199
|
+
def register_function(cls, name, function):
|
|
200
|
+
cls._functions.update({name: function})
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class UniformTopographyInterface(TopographyInterface, metaclass=abc.ABCMeta):
|
|
204
|
+
_functions = {}
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def is_uniform(self):
|
|
208
|
+
return True
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def is_domain_decomposed(self):
|
|
212
|
+
return self.nb_grid_pts != self.nb_subdomain_grid_pts
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def communicator(self):
|
|
216
|
+
# default value is COMM_SELF because sometimes NON-MPI readers that
|
|
217
|
+
# do not set the communicator value are used in MPI Programs.
|
|
218
|
+
# See discussion in issue #166
|
|
219
|
+
return MPI.COMM_SELF
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
@abc.abstractmethod
|
|
223
|
+
def nb_grid_pts(self):
|
|
224
|
+
raise NotImplementedError
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
@abc.abstractmethod
|
|
228
|
+
def pixel_size(self):
|
|
229
|
+
return NotImplementedError
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
@abc.abstractmethod
|
|
233
|
+
def area_per_pt(self):
|
|
234
|
+
return NotImplementedError
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
@abc.abstractmethod
|
|
238
|
+
def has_undefined_data(self):
|
|
239
|
+
return NotImplementedError
|
|
240
|
+
|
|
241
|
+
@abc.abstractmethod
|
|
242
|
+
def positions(self):
|
|
243
|
+
"""
|
|
244
|
+
Returns array containing the lateral positions.
|
|
245
|
+
"""
|
|
246
|
+
raise NotImplementedError
|
|
247
|
+
|
|
248
|
+
@abc.abstractmethod
|
|
249
|
+
def heights(self):
|
|
250
|
+
"""
|
|
251
|
+
Returns array containing the topography data.
|
|
252
|
+
"""
|
|
253
|
+
return NotImplementedError
|
|
254
|
+
|
|
255
|
+
def positions_and_heights(self):
|
|
256
|
+
"""
|
|
257
|
+
Returns array containing the lateral positions and the topography
|
|
258
|
+
data.
|
|
259
|
+
"""
|
|
260
|
+
p = self.positions()
|
|
261
|
+
h = self.heights()
|
|
262
|
+
try:
|
|
263
|
+
x, y = p
|
|
264
|
+
return x, y, h
|
|
265
|
+
except ValueError:
|
|
266
|
+
return p, h
|
|
267
|
+
|
|
268
|
+
def __getitem__(self, i):
|
|
269
|
+
return self.heights()[i]
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class NonuniformLineScanInterface(TopographyInterface, metaclass=abc.ABCMeta):
|
|
273
|
+
_functions = {}
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
def is_uniform(self):
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
@abc.abstractmethod
|
|
281
|
+
def nb_grid_pts(self):
|
|
282
|
+
raise NotImplementedError
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
@abc.abstractmethod
|
|
286
|
+
def x_range(self):
|
|
287
|
+
raise NotImplementedError
|
|
288
|
+
|
|
289
|
+
@abc.abstractmethod
|
|
290
|
+
def positions(self):
|
|
291
|
+
"""
|
|
292
|
+
Returns array containing the lateral positions.
|
|
293
|
+
"""
|
|
294
|
+
raise NotImplementedError
|
|
295
|
+
|
|
296
|
+
@abc.abstractmethod
|
|
297
|
+
def heights(self):
|
|
298
|
+
"""
|
|
299
|
+
Returns array containing the topography data.
|
|
300
|
+
"""
|
|
301
|
+
raise NotImplementedError
|
|
302
|
+
|
|
303
|
+
def positions_and_heights(self):
|
|
304
|
+
"""
|
|
305
|
+
Returns array containing the lateral positions and the topography
|
|
306
|
+
data.
|
|
307
|
+
"""
|
|
308
|
+
return self.positions(), self.heights()
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def is_MPI(self):
|
|
312
|
+
return False
|
|
313
|
+
|
|
314
|
+
def __getitem__(self, i):
|
|
315
|
+
return self.positions()[i], self.heights()[i]
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2019-2020 Lars Pastewka
|
|
3
|
+
# 2020 Michael Röttger
|
|
4
|
+
# 2019 Antoine Sanner
|
|
5
|
+
#
|
|
6
|
+
# ### MIT license
|
|
7
|
+
#
|
|
8
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
# in the Software without restriction, including without limitation the rights
|
|
11
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
# furnished to do so, subject to the following conditions:
|
|
14
|
+
#
|
|
15
|
+
# The above copyright notice and this permission notice shall be included in
|
|
16
|
+
# all copies or substantial portions of the Software.
|
|
17
|
+
#
|
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
# SOFTWARE.
|
|
25
|
+
#
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# The DI file format is described in detail here:
|
|
29
|
+
# http://www.physics.arizona.edu/~smanne/DI/software/fileformats.html
|
|
30
|
+
#
|
|
31
|
+
|
|
32
|
+
import re
|
|
33
|
+
from datetime import datetime
|
|
34
|
+
|
|
35
|
+
import numpy as np
|
|
36
|
+
|
|
37
|
+
from SurfaceTopography import Topography
|
|
38
|
+
|
|
39
|
+
from .FromFile import get_unit_conversion_factor, height_units, \
|
|
40
|
+
mangle_height_unit
|
|
41
|
+
from .Reader import ReaderBase, ChannelInfo
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
###
|
|
45
|
+
|
|
46
|
+
class DIReader(ReaderBase):
|
|
47
|
+
_format = 'di'
|
|
48
|
+
_name = 'Veeco (Digital Instruments) Nanoscope'
|
|
49
|
+
_description = '''
|
|
50
|
+
Digitial Instruments Nanoscope files typically have a three-digit number as
|
|
51
|
+
the file extension (.001, .002, .003, ...). This format contains information
|
|
52
|
+
on the physical size of the topography map as well as its units. The reader
|
|
53
|
+
supports V4.3 and later version of the format.
|
|
54
|
+
'''
|
|
55
|
+
|
|
56
|
+
def __init__(self, fobj):
|
|
57
|
+
"""
|
|
58
|
+
Load Digital Instrument's Nanoscope files.
|
|
59
|
+
|
|
60
|
+
Arguments
|
|
61
|
+
---------
|
|
62
|
+
fobj : filename or file object
|
|
63
|
+
File or data stream to open.
|
|
64
|
+
"""
|
|
65
|
+
self._fobj = fobj
|
|
66
|
+
close_file = False
|
|
67
|
+
if not hasattr(fobj, 'read'):
|
|
68
|
+
fobj = open(fobj, 'rb')
|
|
69
|
+
close_file = True
|
|
70
|
+
try:
|
|
71
|
+
parameters = []
|
|
72
|
+
section_name = None
|
|
73
|
+
section_dict = {}
|
|
74
|
+
|
|
75
|
+
L = fobj.readline().decode('latin-1').strip()
|
|
76
|
+
while L and L.lower() != r'\*file list end':
|
|
77
|
+
if L.startswith('\\*'):
|
|
78
|
+
if section_name is not None:
|
|
79
|
+
parameters += [(section_name, section_dict)]
|
|
80
|
+
new_section_name = L[2:].lower()
|
|
81
|
+
if section_name is None:
|
|
82
|
+
if new_section_name != 'file list':
|
|
83
|
+
raise IOError("Header must start with the "
|
|
84
|
+
"'File list' section.")
|
|
85
|
+
section_name = new_section_name
|
|
86
|
+
section_dict = {}
|
|
87
|
+
elif L.startswith('\\'):
|
|
88
|
+
if section_name is None:
|
|
89
|
+
raise IOError('Encountered key before section '
|
|
90
|
+
'header.')
|
|
91
|
+
s = L[1:].split(': ', 1)
|
|
92
|
+
try:
|
|
93
|
+
key, value = s
|
|
94
|
+
except ValueError:
|
|
95
|
+
key, = s
|
|
96
|
+
value = ''
|
|
97
|
+
section_dict[key.lower()] = value.strip()
|
|
98
|
+
else:
|
|
99
|
+
raise IOError(
|
|
100
|
+
"Header line '{}' does not start with a slash."
|
|
101
|
+
.format(L))
|
|
102
|
+
L = fobj.readline().decode('latin-1').strip()
|
|
103
|
+
if section_name is None:
|
|
104
|
+
raise IOError('No sections found in header.')
|
|
105
|
+
parameters += [(section_name, section_dict)]
|
|
106
|
+
|
|
107
|
+
self._channels = []
|
|
108
|
+
self._offsets = []
|
|
109
|
+
|
|
110
|
+
scanner = {}
|
|
111
|
+
info = {}
|
|
112
|
+
for n, p in parameters:
|
|
113
|
+
if n == 'file list':
|
|
114
|
+
if 'date' in p:
|
|
115
|
+
info['acquisition_time'] = \
|
|
116
|
+
datetime.strptime(p['date'],
|
|
117
|
+
'%I:%M:%S %p %a %b %d %Y')
|
|
118
|
+
elif n == 'scanner list' or n == 'ciao scan list':
|
|
119
|
+
scanner.update(p)
|
|
120
|
+
elif n == 'ciao image list':
|
|
121
|
+
image_data_key = re.match(r'^S \[(.*?)\] ',
|
|
122
|
+
p['@2:image data']).group(1)
|
|
123
|
+
|
|
124
|
+
nx = int(p['samps/line'])
|
|
125
|
+
ny = int(p['number of lines'])
|
|
126
|
+
|
|
127
|
+
s = p['scan size'].split(' ', 2)
|
|
128
|
+
sx = float(s[0])
|
|
129
|
+
sy = float(s[1])
|
|
130
|
+
|
|
131
|
+
xy_unit = mangle_height_unit(s[2])
|
|
132
|
+
offset = int(p['data offset'])
|
|
133
|
+
self._offsets.append(offset)
|
|
134
|
+
|
|
135
|
+
length = int(p['data length'])
|
|
136
|
+
elsize = int(p['bytes/pixel'])
|
|
137
|
+
if elsize != 2:
|
|
138
|
+
raise IOError(
|
|
139
|
+
"Don't know how to handle {} bytes per pixel "
|
|
140
|
+
"data.".format(elsize))
|
|
141
|
+
if nx * ny * elsize != length:
|
|
142
|
+
raise IOError(
|
|
143
|
+
'Data block physical_sizes differs from extend '
|
|
144
|
+
'of surface.')
|
|
145
|
+
|
|
146
|
+
scale_re = re.match(
|
|
147
|
+
r'^V \[(.*?)\] \(([0-9\.]+) (.*)\/LSB\) (.*) '
|
|
148
|
+
r'(.*)', p['@2:z scale'])
|
|
149
|
+
quantity = scale_re.group(1).lower()
|
|
150
|
+
hard_scale = float(scale_re.group(4)) / 65536
|
|
151
|
+
hard_unit = scale_re.group(5)
|
|
152
|
+
|
|
153
|
+
s = scanner['@' + quantity].split()
|
|
154
|
+
if s[0] != 'V' or len(s) < 2:
|
|
155
|
+
raise ValueError('Malformed Nanoscope DI file.')
|
|
156
|
+
soft_scale = float(s[1])
|
|
157
|
+
|
|
158
|
+
height_unit = None
|
|
159
|
+
hard_to_soft = 1.0
|
|
160
|
+
if len(s) > 2:
|
|
161
|
+
# Check units
|
|
162
|
+
height_unit, soft_unit = s[2].split('/')
|
|
163
|
+
hard_to_soft = get_unit_conversion_factor(hard_unit,
|
|
164
|
+
soft_unit)
|
|
165
|
+
if hard_to_soft is None:
|
|
166
|
+
raise ValueError(
|
|
167
|
+
"Units for hard (={}) and soft (={}) "
|
|
168
|
+
"scale differ for '{}'. Don't know how "
|
|
169
|
+
"to handle this.".format(hard_unit,
|
|
170
|
+
soft_unit,
|
|
171
|
+
image_data_key))
|
|
172
|
+
if height_unit in height_units:
|
|
173
|
+
height_unit = mangle_height_unit(height_unit)
|
|
174
|
+
if xy_unit != height_unit:
|
|
175
|
+
fac = get_unit_conversion_factor(xy_unit,
|
|
176
|
+
height_unit)
|
|
177
|
+
sx *= fac
|
|
178
|
+
sy *= fac
|
|
179
|
+
xy_unit = height_unit
|
|
180
|
+
unit = height_unit
|
|
181
|
+
else:
|
|
182
|
+
unit = (xy_unit, height_unit)
|
|
183
|
+
|
|
184
|
+
channel_info = info.copy()
|
|
185
|
+
channel_info.update(dict(
|
|
186
|
+
unit=unit,
|
|
187
|
+
height_scale_factor=hard_scale * hard_to_soft *
|
|
188
|
+
soft_scale))
|
|
189
|
+
channel = ChannelInfo(self,
|
|
190
|
+
len(self._channels),
|
|
191
|
+
name=image_data_key,
|
|
192
|
+
dim=2,
|
|
193
|
+
nb_grid_pts=(nx, ny),
|
|
194
|
+
physical_sizes=(sx, sy),
|
|
195
|
+
periodic=False,
|
|
196
|
+
info=channel_info)
|
|
197
|
+
self._channels.append(channel)
|
|
198
|
+
finally:
|
|
199
|
+
if close_file:
|
|
200
|
+
fobj.close()
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def channels(self):
|
|
204
|
+
return self._channels
|
|
205
|
+
|
|
206
|
+
def topography(self, channel_index=None, physical_sizes=None,
|
|
207
|
+
height_scale_factor=None, info={}, periodic=False,
|
|
208
|
+
subdomain_locations=None, nb_subdomain_grid_pts=None):
|
|
209
|
+
if channel_index is None:
|
|
210
|
+
channel_index = self._default_channel_index
|
|
211
|
+
|
|
212
|
+
if subdomain_locations is not None or \
|
|
213
|
+
nb_subdomain_grid_pts is not None:
|
|
214
|
+
raise RuntimeError(
|
|
215
|
+
'This reader does not support MPI parallelization.')
|
|
216
|
+
close_file = False
|
|
217
|
+
if not hasattr(self._fobj, 'read'):
|
|
218
|
+
fobj = open(self._fobj, 'rb')
|
|
219
|
+
close_file = True
|
|
220
|
+
else:
|
|
221
|
+
fobj = self._fobj
|
|
222
|
+
|
|
223
|
+
channel = self._channels[channel_index]
|
|
224
|
+
sx, sy = self._check_physical_sizes(physical_sizes,
|
|
225
|
+
channel.physical_sizes)
|
|
226
|
+
|
|
227
|
+
nx, ny = channel.nb_grid_pts
|
|
228
|
+
|
|
229
|
+
offset = self._offsets[channel_index]
|
|
230
|
+
dtype = np.dtype('<i2')
|
|
231
|
+
|
|
232
|
+
###################################
|
|
233
|
+
|
|
234
|
+
fobj.seek(offset)
|
|
235
|
+
rawdata = fobj.read(nx * ny * dtype.itemsize)
|
|
236
|
+
unscaleddata = np.frombuffer(rawdata, count=nx * ny,
|
|
237
|
+
dtype=dtype).reshape(nx, ny)
|
|
238
|
+
|
|
239
|
+
# internal information from file
|
|
240
|
+
_info = dict(unit=channel.info["unit"], data_source=channel.name)
|
|
241
|
+
_info.update(info)
|
|
242
|
+
if 'acquisition_time' in channel.info:
|
|
243
|
+
_info['acquisition_time'] = channel.info['acquisition_time']
|
|
244
|
+
|
|
245
|
+
# the orientation of the heights is modified in order to match
|
|
246
|
+
# the image of gwyddion when plotted with imshow(t.heights().T)
|
|
247
|
+
# or pcolormesh(t.heights().T) for origin in lower left and
|
|
248
|
+
# with inverted y axis (cartesian coordinate system)
|
|
249
|
+
surface = Topography(np.fliplr(unscaleddata.T), (sx, sy), info=_info,
|
|
250
|
+
periodic=periodic)
|
|
251
|
+
if height_scale_factor is None:
|
|
252
|
+
height_scale_factor = channel.info["height_scale_factor"]
|
|
253
|
+
surface = surface.scale(height_scale_factor)
|
|
254
|
+
|
|
255
|
+
if close_file:
|
|
256
|
+
fobj.close()
|
|
257
|
+
|
|
258
|
+
return surface
|
|
259
|
+
|
|
260
|
+
channels.__doc__ = ReaderBase.channels.__doc__
|
|
261
|
+
topography.__doc__ = ReaderBase.topography.__doc__
|