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,79 @@
1
+ #
2
+ # Copyright 2019-2020 Lars Pastewka
3
+ # 2019 Antoine Sanner
4
+ #
5
+ # ### MIT license
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the "Software"), to deal
9
+ # in the Software without restriction, including without limitation the rights
10
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ # copies of the Software, and to permit persons to whom the Software is
12
+ # furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in
15
+ # all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ # SOFTWARE.
24
+ #
25
+
26
+ from .. import Topography
27
+ from .Reader import ReaderBase, ChannelInfo
28
+
29
+
30
+ class H5Reader(ReaderBase):
31
+ _format = 'h5'
32
+ _name = 'Hierarchical data format (HDF5)'
33
+ _description = '''
34
+ Import filter for [HDF5](https://support.hdfgroup.org/HDF5/) files provided
35
+ within the contact mechanics challenge. The reader looks for a two-dimensional
36
+ array named `surface`. HDF5 files do not store units or physical sizes. These
37
+ need to be manually provided by the user.
38
+
39
+ The original contact mechanics challenge data can be downloaded
40
+ [here](https://www.lmp.uni-saarland.de/index.php/research-topics/contact-mechanics-challenge-announcement/).
41
+ ''' # noqa: E501
42
+
43
+ def __init__(self, fobj):
44
+ self._h5 = None
45
+ import h5py
46
+ self._h5 = h5py.File(fobj, 'r')
47
+
48
+ def close(self):
49
+ if self._h5 is not None:
50
+ self._h5.close()
51
+
52
+ @property
53
+ def channels(self):
54
+ return [ChannelInfo(self,
55
+ 0, # channel index
56
+ name='Default',
57
+ dim=len(self._h5['surface'].shape),
58
+ nb_grid_pts=self._h5['surface'].shape)]
59
+
60
+ def topography(self, channel_index=None, physical_sizes=None,
61
+ height_scale_factor=None, info={}, periodic=False,
62
+ subdomain_locations=None, nb_subdomain_grid_pts=None):
63
+ if subdomain_locations is not None or \
64
+ nb_subdomain_grid_pts is not None:
65
+ raise RuntimeError(
66
+ 'This reader does not support MPI parallelization.')
67
+ if channel_index is None:
68
+ channel_index = self._default_channel_index
69
+ if channel_index != 0:
70
+ raise RuntimeError('HDF5 reader only supports a single channel')
71
+ size = self._check_physical_sizes(physical_sizes)
72
+ t = Topography(self._h5['surface'][...], size, info=info,
73
+ periodic=periodic)
74
+ if height_scale_factor is not None:
75
+ t = t.scale(height_scale_factor)
76
+ return t
77
+
78
+ channels.__doc__ = ReaderBase.channels.__doc__
79
+ topography.__doc__ = ReaderBase.topography.__doc__
@@ -0,0 +1,176 @@
1
+ #
2
+ # Copyright 2019-2020 Lars Pastewka
3
+ # 2019-2020 Michael Röttger
4
+ # 2019 Kai Haase
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
+ import numpy as np
28
+ from io import TextIOBase
29
+
30
+ from igor.binarywave import load as loadibw
31
+
32
+ from .. import Topography
33
+ from .Reader import ReaderBase, ChannelInfo
34
+
35
+
36
+ class IBWReader(ReaderBase):
37
+ _format = 'ibw'
38
+ _name = 'Igor binary wave'
39
+ _description = '''
40
+ Igor binary wave is a container format of the
41
+ [Igor Pro](https://www.wavemetrics.com/products/igorpro/programming)
42
+ language. This format is used by AFMs from Asylum Research (now Oxford
43
+ Instruments) to store topography information. This format contains information
44
+ on the physical size of the topography map as well as its units.
45
+ '''
46
+
47
+ # Reads in the positions of all the data and metadata
48
+ def __init__(self, file_path):
49
+
50
+ # depending from where this function is called, file_path might already
51
+ # be a filestream
52
+ already_open = False
53
+ if not hasattr(file_path, 'read'):
54
+ f = open(file_path, "rb")
55
+ else:
56
+ already_open = True
57
+ if isinstance(file_path, TextIOBase):
58
+ # file was opened without the 'b' option, so read its buffer to
59
+ # get the binary data
60
+ f = file_path.buffer
61
+ else:
62
+ f = file_path
63
+
64
+ # This catches and closes on its own
65
+ try:
66
+ file = loadibw(f)
67
+ except Exception:
68
+ if not f.closed:
69
+ if not already_open:
70
+ f.close()
71
+ raise RuntimeError('Invalid file format.')
72
+
73
+ if file['version'] != 5:
74
+ raise RuntimeError('Only IBW version 5 supported!')
75
+
76
+ self.data = file['wave']
77
+
78
+ # the first two labels are of x and y axis. we cannot read those
79
+ self._channel_names = [channel.decode() for channel in
80
+ self.data['labels'][2][1:]]
81
+ self._default_channel = 0
82
+
83
+ #
84
+ # Read physical sizes from file, since all data
85
+ # has already been loaded, we can calculate it here
86
+ #
87
+ height_data = self.data['wData']
88
+
89
+ # TODO is it always like this?
90
+ assert len(height_data.shape) == 3, \
91
+ "We expect all channels being coded in to one wave and all are " \
92
+ "2D. This is not true somehow, cannot proceed."
93
+ nx, ny, num_channels = height_data.shape
94
+
95
+ # ensure that there are not too many channel names
96
+ self._channel_names = self._channel_names[:num_channels]
97
+
98
+ # add channel names for all channels without name
99
+ no_name_idx = 1
100
+ while len(self._channel_names) < num_channels:
101
+ self._channel_names.append("no name ({})".format(no_name_idx))
102
+ no_name_idx += 1
103
+
104
+ #
105
+ # Decode units
106
+ #
107
+ def decode_unit_entry(u):
108
+ return u.tobytes().partition(b'\0')[0].decode('latin-1')
109
+
110
+ data_unit = decode_unit_entry(self.data['wave_header']['dataUnits'])
111
+ x_unit = decode_unit_entry(self.data['wave_header']['dimUnits'][0])
112
+ y_unit = decode_unit_entry(self.data['wave_header']['dimUnits'][1])
113
+
114
+ # the following is not necessary, we could handle different units by
115
+ # rescaling, however I'll leave it like this for now, print some
116
+ # message so we know what to do if a file occurs which does not fulfill
117
+ # this assumption
118
+ assert data_unit == x_unit == y_unit, \
119
+ "So far, data units and dimension units must be all the same. " +\
120
+ "data unit: '{}', x unit: '{}', y unit: '{}'".format(data_unit,
121
+ x_unit,
122
+ y_unit)
123
+
124
+ self._data_unit = data_unit
125
+ #
126
+ # Decode sizes
127
+ #
128
+ sfA = self.data['wave_header']['sfA']
129
+ self._physical_sizes = (nx * sfA[0], ny * sfA[1])
130
+ # Comment in C header file on these fields: Index value for element e
131
+ # of dimension d = sfA[d]*e + sfB[d]. sfB is left out here, because we
132
+ # are interested in the width and height, not the absolute offsets.
133
+
134
+ #
135
+ # Build channel information
136
+ #
137
+ self._channels = [
138
+ ChannelInfo(self, i, name=cn, dim=2, nb_grid_pts=(nx, ny),
139
+ physical_sizes=self._physical_sizes)
140
+ for i, cn in enumerate(self._channel_names)]
141
+
142
+ # Shall we use the channel names in order to assign a unit as Gwyddion
143
+ # does?
144
+
145
+ @property
146
+ def channels(self):
147
+ return self._channels
148
+
149
+ def topography(self, channel_index=None, physical_sizes=None,
150
+ height_scale_factor=None, info={},
151
+ periodic=False, subdomain_locations=None,
152
+ nb_subdomain_grid_pts=None):
153
+ if channel_index is None:
154
+ channel_index = self._default_channel_index
155
+
156
+ if subdomain_locations is not None or \
157
+ nb_subdomain_grid_pts is not None:
158
+ raise RuntimeError(
159
+ 'This reader does not support MPI parallelization.')
160
+
161
+ height_data = self.data['wData']
162
+ height_data = np.fliplr(height_data[:, :, channel_index].copy())
163
+
164
+ if physical_sizes is None:
165
+ physical_sizes = self._physical_sizes
166
+
167
+ topo = Topography(height_data, physical_sizes,
168
+ info=info,
169
+ periodic=periodic)
170
+ # we could pass the data units here, but they dont seem to be always
171
+ # correct for all channels?!
172
+
173
+ if height_scale_factor is not None:
174
+ topo = topo.scale(height_scale_factor)
175
+
176
+ return topo
@@ -0,0 +1,307 @@
1
+ #
2
+ # Copyright 2019-2020 Lars Pastewka
3
+ # 2020 Michael Röttger
4
+ # 2019-2020 Kai Haase
5
+ # 2019 Antoine Sanner
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
+ import numpy as np
29
+
30
+ from .. import Topography
31
+ from .Reader import ReaderBase, CorruptFile, ChannelInfo
32
+ from .FromFile import mangle_height_unit
33
+ from io import TextIOBase
34
+
35
+ image_head = b'fileType Image\n'
36
+ spec_head = b'fileType Spectroscopy\n'
37
+
38
+ magic_data = b'data \n'
39
+ magic_data_binary = b'data BINARY\n'
40
+ magic_data_binary32 = b'data BINARY_32\n'
41
+ magic_data_ascii = b'data ASCII'
42
+
43
+
44
+ class MIReader(ReaderBase):
45
+ _format = 'mi'
46
+ _name = 'Molecular imaging data file'
47
+ _description = '''
48
+ This reader opens Agilent Technologies (Molecular Imaging) AFM files saved
49
+ in the MI format. This format contains information on the physical size of the
50
+ topography map as well as its units.
51
+ '''
52
+
53
+ # Reads in the positions of all the data and metadata
54
+ def __init__(self, file_path):
55
+ self.file_path = file_path
56
+
57
+ # Start of the data in the file
58
+ self.data_start = None
59
+ # If image oder spectroscopy
60
+ self.is_image = None
61
+ # Format of the saved data
62
+ self.data_type = None
63
+
64
+ self.mifile = None
65
+
66
+ # Process the header and read in metadata
67
+ self.process_header()
68
+
69
+ def process_header(self):
70
+
71
+ already_open = False
72
+ if not hasattr(self.file_path, 'read'):
73
+ f = open(self.file_path, "rb")
74
+ else:
75
+ already_open = True
76
+ if isinstance(self.file_path, TextIOBase):
77
+ # file was opened without the 'b' option, so read its buffer
78
+ # to get the binary data
79
+ f = self.file_path.buffer
80
+ else:
81
+ f = self.file_path
82
+
83
+ try:
84
+ self.lines = f.readlines()
85
+
86
+ # Find out if image or spectroscopy
87
+ if self.lines[0] == image_head:
88
+ self.is_image = True
89
+ elif self.lines[0] == spec_head:
90
+ self.is_image = False
91
+ else:
92
+ raise CorruptFile
93
+
94
+ # Find the start of the height data and denote data type
95
+ if magic_data in self.lines:
96
+ header_size = self.lines.index(magic_data)
97
+ self.data_type = 'text'
98
+ elif magic_data_binary in self.lines:
99
+ header_size = self.lines.index(magic_data_binary)
100
+ self.data_type = 'binary'
101
+ elif magic_data_binary32 in self.lines:
102
+ header_size = self.lines.index(magic_data_binary32)
103
+ self.data_type = 'binary32'
104
+ elif magic_data_ascii in self.lines:
105
+ header_size = self.lines.index(magic_data_ascii)
106
+ self.data_type = 'ascii'
107
+ else:
108
+ raise CorruptFile
109
+
110
+ # Save start of data for later reading of the matrix
111
+ self.data_start = header_size + 1
112
+
113
+ # Create mifile from header, reading out meta data and channel info
114
+ if self.is_image:
115
+ self.mifile = read_header_image(self.lines[1:header_size])
116
+ else: # TODO
117
+ self.mifile = read_header_spect(self.lines[1:header_size])
118
+
119
+ # Reformat the metadata
120
+ for buf in self.mifile.channels:
121
+ buf.meta['name'] = buf.name
122
+ buf.meta['unit'] = mangle_height_unit(
123
+ buf.meta.pop('bufferUnit'))
124
+ buf.meta['range'] = buf.meta.pop('bufferRange')
125
+ buf.meta['label'] = buf.meta.pop('bufferLabel')
126
+
127
+ self._physical_sizes = float(self.mifile.meta['xLength']), \
128
+ float(self.mifile.meta['yLength'])
129
+ self._nb_grid_pts = int(self.mifile.meta['xPixels']), \
130
+ int(self.mifile.meta['yPixels'])
131
+
132
+ finally:
133
+ if not already_open:
134
+ f.close()
135
+
136
+ def topography(self, channel_index=None, physical_sizes=None,
137
+ height_scale_factor=None, info={}, periodic=False,
138
+ subdomain_locations=None, nb_subdomain_grid_pts=None):
139
+ if channel_index is None:
140
+ channel_index = self._default_channel_index
141
+
142
+ if subdomain_locations is not None or \
143
+ nb_subdomain_grid_pts is not None:
144
+ raise RuntimeError(
145
+ 'This reader does not support MPI parallelization.')
146
+
147
+ output_channel = self.mifile.channels[channel_index]
148
+
149
+ buffer = b''.join(self.lines[self.data_start:])
150
+ buffer = [chr(_) for _ in buffer]
151
+
152
+ # Read height data
153
+ if self.is_image:
154
+ if self.data_type == 'binary':
155
+ dt = "i2"
156
+ encode_length = 2
157
+ type_range = 32768
158
+ elif self.data_type == 'binary32':
159
+ dt = "i4"
160
+ encode_length = 4
161
+ type_range = 2147483648
162
+ else: # text or ascii
163
+ type_range = 32768
164
+
165
+ start = int(self.mifile.xres) * int(
166
+ self.mifile.yres) * encode_length * channel_index
167
+ end = int(self.mifile.xres) * int(
168
+ self.mifile.yres) * encode_length * (channel_index + 1)
169
+
170
+ data = ''.join(buffer[start:end])
171
+ out = np.frombuffer(str.encode(data, "raw_unicode_escape"), dt)
172
+ out = out.reshape((int(self.mifile.xres), int(self.mifile.yres)))
173
+
174
+ # Undo normalizing with range of data type
175
+ out = out / type_range
176
+
177
+ # If scan direction is upwards, flip the height map vertically
178
+ if self.mifile.meta['scanUp']:
179
+ out = np.flip(out, 0)
180
+
181
+ # Multiply the heights with the bufferRange
182
+ out *= float(output_channel.meta['range'])
183
+ else:
184
+ pass # TODO
185
+
186
+ joined_meta = {**self.mifile.meta, **output_channel.meta}
187
+
188
+ # Initialize heights with transposed array in order to match Gwdyydion
189
+ # when plotted with pcolormesh(t.heights().T), except that the y axis
190
+ # is flipped because the origin is in lower left with pcolormesh;
191
+ # imshow(t.heights().T) shows the image like gwyddion
192
+ t = Topography(heights=out.T,
193
+ physical_sizes=self._check_physical_sizes(
194
+ physical_sizes, self._physical_sizes),
195
+ info=joined_meta, periodic=periodic)
196
+ if height_scale_factor is not None:
197
+ t.scale(height_scale_factor)
198
+ return t
199
+
200
+ @property
201
+ def channels(self):
202
+ return [ChannelInfo(self, i, name=channel.meta['name'],
203
+ dim=len(self._nb_grid_pts),
204
+ nb_grid_pts=self._nb_grid_pts,
205
+ physical_sizes=self._physical_sizes,
206
+ info=channel.meta)
207
+ for i, channel in enumerate(self.mifile.channels)]
208
+
209
+ @property
210
+ def info(self):
211
+ """ Return all the available metadata as a dict. """
212
+ return self.mifile.meta
213
+
214
+ channels.__doc__ = ReaderBase.channels.__doc__
215
+ topography.__doc__ = ReaderBase.topography.__doc__
216
+
217
+
218
+ def read_header_image(header):
219
+ """
220
+ Reads in global metadata and information about about included channels as
221
+ well as their metadata.
222
+ :param header: The header as a line of text.
223
+ :return:
224
+ MIFile item containing the metadata, with the channels as a list of
225
+ 'buffers'.
226
+ """
227
+ # This object will store the file-wide metadata
228
+ mifile = MIFile()
229
+
230
+ # False while reading global metadata, gets true if we start to read in the
231
+ # channel info
232
+ reading_buffers = False
233
+
234
+ for line in header:
235
+ line = line.decode("utf-8")
236
+
237
+ # As soon as we see a line starting with 'bufferLabel', we know we are
238
+ # now reading in channels
239
+ if line.startswith('bufferLabel'):
240
+ # Create a new channel with the id as name
241
+ channel = Channel(name=str.strip(line[14:]))
242
+ mifile.channels.append(channel)
243
+ reading_buffers = True
244
+
245
+ if line[0] == ' ':
246
+ continue
247
+
248
+ # For all key value pairs in the file:
249
+ # Append to global metadata oder channel metadata, depending on our
250
+ # state
251
+ key = str.strip(line[:14])
252
+ value = str.strip(line[14:])
253
+
254
+ if reading_buffers:
255
+ mifile.channels[-1].meta[key] = value
256
+ else:
257
+ mifile.meta[key] = value
258
+
259
+ # Catch 'special' metadata
260
+ if key == "xPixels":
261
+ mifile.xres = value
262
+ elif key == "yPixels":
263
+ mifile.yres = value
264
+ return mifile
265
+
266
+
267
+ def read_header_spect(header):
268
+ """
269
+ Reads in metadata out of the header.
270
+ :param header: The header.
271
+ :return:
272
+ MIFile item containing the metadata.
273
+ """
274
+ pass
275
+
276
+
277
+ class Channel:
278
+ """
279
+ Class structure for a channel contained in the file.
280
+ Has a name and metadata (height data is not needed since it is returned
281
+ directly).
282
+ """
283
+
284
+ def __init__(self, name=None, meta=None):
285
+ if meta is None:
286
+ meta = dict()
287
+
288
+ self.name = name
289
+ self.meta = meta
290
+
291
+
292
+ class MIFile:
293
+ """
294
+ Class structure for the while file. Has a list of channels, global metadata
295
+ and a nb_grid_pts.
296
+ """
297
+
298
+ def __init__(self, res=(0, 0), channels=None, meta=None):
299
+ if channels is None:
300
+ channels = list()
301
+ if meta is None:
302
+ meta = dict()
303
+
304
+ self.xres = res[0]
305
+ self.yres = res[1]
306
+ self.channels = channels
307
+ self.meta = meta
@@ -0,0 +1,106 @@
1
+ #
2
+ # Copyright 2019-2020 Lars Pastewka
3
+ # 2019 Antoine Sanner
4
+ #
5
+ # ### MIT license
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the "Software"), to deal
9
+ # in the Software without restriction, including without limitation the rights
10
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ # copies of the Software, and to permit persons to whom the Software is
12
+ # furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in
15
+ # all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ # SOFTWARE.
24
+ #
25
+
26
+ from .. import Topography
27
+ from .Reader import ReaderBase, ChannelInfo
28
+
29
+
30
+ class MatReader(ReaderBase):
31
+ _format = 'mat'
32
+ _name = 'MATLAB'
33
+ _description = '''
34
+ Imports topography data stored in MATLAB workspace files. The reader
35
+ automatically extracts all 2D arrays stored in the file and interprets those
36
+ as height information. Matlab files do not store units or physical sizes.
37
+ These need to be manually provided by the user.
38
+ '''
39
+
40
+ def __init__(self, fobj):
41
+ """
42
+ Reads a surface profile from a Matlab file and presents in in a
43
+ SurfaceTopography-conformant manner.
44
+
45
+ All two-dimensional arrays present in the matlab data file are
46
+ returned.
47
+
48
+ Parameters
49
+ ----------
50
+ fobj: filename or file object
51
+ File to read.
52
+ """
53
+ from scipy.io import loadmat
54
+
55
+ close_file = False
56
+ if not hasattr(fobj, 'read'):
57
+ fobj = open(fobj, 'rb')
58
+ close_file = True
59
+ try:
60
+ data = loadmat(fobj)
61
+ self._channels = []
62
+ self._height_data = []
63
+ for key, value in data.items():
64
+ is_2darray = False
65
+ try:
66
+ nx, ny = value.shape
67
+ is_2darray = True
68
+ except (AttributeError, ValueError):
69
+ pass
70
+ if is_2darray:
71
+ channelinfo = ChannelInfo(self,
72
+ len(self._channels),
73
+ name=key,
74
+ dim=len(value.shape),
75
+ nb_grid_pts=value.shape)
76
+
77
+ self._channels.append(channelinfo)
78
+ self._height_data.append(value)
79
+ finally:
80
+ if close_file:
81
+ fobj.close()
82
+
83
+ @property
84
+ def channels(self):
85
+ return self._channels
86
+
87
+ def topography(self, channel_index=None, physical_sizes=None,
88
+ height_scale_factor=None, info={}, periodic=False,
89
+ subdomain_locations=None, nb_subdomain_grid_pts=None):
90
+ if channel_index is None:
91
+ channel_index = self._default_channel_index
92
+
93
+ if subdomain_locations is not None or \
94
+ nb_subdomain_grid_pts is not None:
95
+ raise RuntimeError(
96
+ 'This reader does not support MPI parallelization.')
97
+
98
+ return Topography(self._height_data[channel_index],
99
+ physical_sizes=self._check_physical_sizes(
100
+ physical_sizes),
101
+ info=dict(
102
+ data_source=self.channels[channel_index].name),
103
+ periodic=periodic)
104
+
105
+ channels.__doc__ = ReaderBase.channels.__doc__
106
+ topography.__doc__ = ReaderBase.topography.__doc__