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,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__