empix 0.0.1__py3-none-any.whl
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.
- empix/__init__.py +3732 -0
- empix/version.py +16 -0
- empix-0.0.1.dist-info/LICENSE +674 -0
- empix-0.0.1.dist-info/METADATA +61 -0
- empix-0.0.1.dist-info/RECORD +7 -0
- empix-0.0.1.dist-info/WHEEL +5 -0
- empix-0.0.1.dist-info/top_level.txt +1 -0
empix/__init__.py
ADDED
|
@@ -0,0 +1,3732 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright 2024 Matthew Fitzpatrick.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify it under
|
|
5
|
+
# the terms of the GNU General Public License as published by the Free Software
|
|
6
|
+
# Foundation, version 3.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
10
|
+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
11
|
+
#
|
|
12
|
+
# You should have received a copy of the GNU General Public License along with
|
|
13
|
+
# this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
|
|
14
|
+
"""``empix`` is a Python library that contains tools for analyzing electron
|
|
15
|
+
microscopy data that are not available in `hyperspy
|
|
16
|
+
<https://hyperspy.org/hyperspy-doc/current/index.html>`_.
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
#####################################
|
|
23
|
+
## Load libraries/packages/modules ##
|
|
24
|
+
#####################################
|
|
25
|
+
|
|
26
|
+
# For accessing attributes of functions.
|
|
27
|
+
import inspect
|
|
28
|
+
|
|
29
|
+
# For randomly selecting items in dictionaries.
|
|
30
|
+
import random
|
|
31
|
+
|
|
32
|
+
# For performing deep copies.
|
|
33
|
+
import copy
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# For general array handling.
|
|
38
|
+
import numpy as np
|
|
39
|
+
|
|
40
|
+
# For interpolating data.
|
|
41
|
+
import scipy.interpolate
|
|
42
|
+
|
|
43
|
+
# For validating and converting objects.
|
|
44
|
+
import czekitout.check
|
|
45
|
+
import czekitout.convert
|
|
46
|
+
|
|
47
|
+
# For defining classes that support enforced validation, updatability,
|
|
48
|
+
# pre-serialization, and de-serialization.
|
|
49
|
+
import fancytypes
|
|
50
|
+
|
|
51
|
+
# For creating hyperspy signals.
|
|
52
|
+
import hyperspy.signals
|
|
53
|
+
import hyperspy.axes
|
|
54
|
+
|
|
55
|
+
# For azimuthally integrating 2D hyperspy signals.
|
|
56
|
+
import pyFAI.detectors
|
|
57
|
+
import pyFAI.azimuthalIntegrator
|
|
58
|
+
|
|
59
|
+
# For downsampling hyperspy signals.
|
|
60
|
+
import skimage.measure
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Get version of current package.
|
|
65
|
+
from fancytypes.version import __version__
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
##################################
|
|
70
|
+
## Define classes and functions ##
|
|
71
|
+
##################################
|
|
72
|
+
|
|
73
|
+
# List of public objects in package.
|
|
74
|
+
__all__ = ["abs_sq",
|
|
75
|
+
"OptionalAzimuthalAveragingParams",
|
|
76
|
+
"azimuthally_average",
|
|
77
|
+
"OptionalAzimuthalIntegrationParams",
|
|
78
|
+
"azimuthally_integrate",
|
|
79
|
+
"OptionalAnnularAveragingParams",
|
|
80
|
+
"annularly_average",
|
|
81
|
+
"OptionalAnnularIntegrationParams",
|
|
82
|
+
"annularly_integrate",
|
|
83
|
+
"OptionalCumulative1dIntegrationParams",
|
|
84
|
+
"cumulatively_integrate_1d",
|
|
85
|
+
"OptionalCroppingParams",
|
|
86
|
+
"crop",
|
|
87
|
+
"OptionalDownsamplingParams",
|
|
88
|
+
"downsample",
|
|
89
|
+
"OptionalResamplingParams",
|
|
90
|
+
"resample"]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _check_and_convert_action_to_apply_to_input_signal(params):
|
|
95
|
+
current_func_name = inspect.stack()[0][3]
|
|
96
|
+
char_idx = 19
|
|
97
|
+
obj_name = current_func_name[char_idx:]
|
|
98
|
+
action_to_apply_to_input_signal = params[obj_name]
|
|
99
|
+
|
|
100
|
+
return action_to_apply_to_input_signal
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _check_and_convert_input_signal(params):
|
|
105
|
+
current_func_name = inspect.stack()[0][3]
|
|
106
|
+
char_idx = 19
|
|
107
|
+
obj_name = current_func_name[char_idx:]
|
|
108
|
+
input_signal = params[obj_name]
|
|
109
|
+
|
|
110
|
+
action_to_apply_to_input_signal = params["action_to_apply_to_input_signal"]
|
|
111
|
+
|
|
112
|
+
if action_to_apply_to_input_signal == "abs_sq":
|
|
113
|
+
accepted_types = (hyperspy.signals.Signal1D,
|
|
114
|
+
hyperspy.signals.Signal2D,
|
|
115
|
+
hyperspy.signals.ComplexSignal1D,
|
|
116
|
+
hyperspy.signals.ComplexSignal2D)
|
|
117
|
+
elif action_to_apply_to_input_signal == "cumulatively_integrate_1d":
|
|
118
|
+
accepted_types = (hyperspy.signals.Signal1D,
|
|
119
|
+
hyperspy.signals.ComplexSignal1D)
|
|
120
|
+
else:
|
|
121
|
+
accepted_types = (hyperspy.signals.Signal2D,
|
|
122
|
+
hyperspy.signals.ComplexSignal2D)
|
|
123
|
+
|
|
124
|
+
kwargs = {"obj": input_signal,
|
|
125
|
+
"obj_name": obj_name,
|
|
126
|
+
"accepted_types": accepted_types}
|
|
127
|
+
czekitout.check.if_instance_of_any_accepted_types(**kwargs)
|
|
128
|
+
|
|
129
|
+
return input_signal
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _check_and_convert_title(params):
|
|
134
|
+
current_func_name = inspect.stack()[0][3]
|
|
135
|
+
char_idx = 19
|
|
136
|
+
obj_name = current_func_name[char_idx:]
|
|
137
|
+
obj = params[obj_name]
|
|
138
|
+
|
|
139
|
+
accepted_types = (str, type(None))
|
|
140
|
+
|
|
141
|
+
if isinstance(obj, accepted_types[1]):
|
|
142
|
+
if "input_signal" in params:
|
|
143
|
+
param_name_subset = ("input_signal",
|
|
144
|
+
"action_to_apply_to_input_signal")
|
|
145
|
+
kwargs = {param_name: params[param_name]
|
|
146
|
+
for param_name
|
|
147
|
+
in param_name_subset}
|
|
148
|
+
title = _generate_title(**kwargs)
|
|
149
|
+
else:
|
|
150
|
+
title = obj
|
|
151
|
+
else:
|
|
152
|
+
try:
|
|
153
|
+
kwargs = {"obj": obj, "obj_name": obj_name}
|
|
154
|
+
title = czekitout.convert.to_str_from_str_like(**kwargs)
|
|
155
|
+
except:
|
|
156
|
+
kwargs["accepted_types"] = accepted_types
|
|
157
|
+
czekitout.check.if_instance_of_any_accepted_types(**kwargs)
|
|
158
|
+
|
|
159
|
+
return title
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _generate_title(input_signal, action_to_apply_to_input_signal):
|
|
164
|
+
prefixes = {"abs_sq": "Modulus Squared of ",
|
|
165
|
+
"azimuthally_average": "Azimuthally Averaged ",
|
|
166
|
+
"azimuthally_integrate": "Azimuthally Integrated ",
|
|
167
|
+
"annularly_average": "Annularly Averaged ",
|
|
168
|
+
"annularly_integrate": "Annularly Integrated ",
|
|
169
|
+
"cumulatively_integrate_1d": "CDF(",
|
|
170
|
+
"crop": "Cropped ",
|
|
171
|
+
"downsample": "Downsampled ",
|
|
172
|
+
"resample": "Resampled "}
|
|
173
|
+
prefix = prefixes[action_to_apply_to_input_signal]
|
|
174
|
+
|
|
175
|
+
input_signal_title = input_signal.metadata.get_item("General.title",
|
|
176
|
+
"signal")
|
|
177
|
+
|
|
178
|
+
suffix = ")" if (prefix[-1] == "(") else ""
|
|
179
|
+
|
|
180
|
+
title = prefix + input_signal_title + suffix
|
|
181
|
+
|
|
182
|
+
return title
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _pre_serialize_title(title):
|
|
187
|
+
obj_to_pre_serialize = random.choice(list(locals().values()))
|
|
188
|
+
serializable_rep = obj_to_pre_serialize
|
|
189
|
+
|
|
190
|
+
return serializable_rep
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _de_pre_serialize_title(serializable_rep):
|
|
195
|
+
title = serializable_rep
|
|
196
|
+
|
|
197
|
+
return title
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
_default_title = None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def abs_sq(input_signal, title=_default_title):
|
|
206
|
+
r"""The modulus squared of a given input ``hyperspy`` signal.
|
|
207
|
+
|
|
208
|
+
Parameters
|
|
209
|
+
----------
|
|
210
|
+
input_signal : :class:`hyperspy._signals.signal1d.Signal1D` | :class:`hyperspy._signals.complex_signal1d.ComplexSignal1D` | :class:`hyperspy._signals.signal2d.Signal2D` | :class:`hyperspy._signals.complex_signal2d.ComplexSignal2D`
|
|
211
|
+
The input ``hyperspy`` signal.
|
|
212
|
+
title : `str` | `None`, optional
|
|
213
|
+
If ``title`` is set to ``None``, then the title of the output signal
|
|
214
|
+
``output_signal`` is set to ``"Modulus Squared of " +
|
|
215
|
+
input_signal.metadata.General.title``, where ``input_signal`` is the
|
|
216
|
+
input ``hyperspy`` signal. Otherwise, if ``title`` is a `str`, then the
|
|
217
|
+
``output_signal.metadata.General.title`` is set to the value of
|
|
218
|
+
``title``.
|
|
219
|
+
|
|
220
|
+
Returns
|
|
221
|
+
-------
|
|
222
|
+
output_signal : :class:`hyperspy._signals.signal1d.Signal1D` | :class:`hyperspy._signals.signal2d.Signal2D`
|
|
223
|
+
The output ``hyperspy`` signal that stores the modulus squared of the
|
|
224
|
+
input signal ``input_signal``. Note that the metadata of the input
|
|
225
|
+
signal is copied over to the output signal, with the title being
|
|
226
|
+
overwritten.
|
|
227
|
+
|
|
228
|
+
"""
|
|
229
|
+
params = locals()
|
|
230
|
+
params["action_to_apply_to_input_signal"] = inspect.stack()[0][3]
|
|
231
|
+
for param_name in params:
|
|
232
|
+
func_name = "_check_and_convert_" + param_name
|
|
233
|
+
func_alias = globals()[func_name]
|
|
234
|
+
params[param_name] = func_alias(params)
|
|
235
|
+
|
|
236
|
+
func_name = "_" + inspect.stack()[0][3]
|
|
237
|
+
func_alias = globals()[func_name]
|
|
238
|
+
kwargs = params
|
|
239
|
+
del kwargs["action_to_apply_to_input_signal"]
|
|
240
|
+
output_signal = func_alias(**kwargs)
|
|
241
|
+
|
|
242
|
+
return output_signal
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _abs_sq(input_signal, title):
|
|
247
|
+
if isinstance(input_signal, hyperspy.signals.ComplexSignal):
|
|
248
|
+
output_signal = input_signal.amplitude
|
|
249
|
+
output_signal *= output_signal
|
|
250
|
+
else:
|
|
251
|
+
output_signal = input_signal * input_signal
|
|
252
|
+
|
|
253
|
+
output_signal.metadata.set_item("General.title", title)
|
|
254
|
+
|
|
255
|
+
return output_signal
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _check_and_convert_center(params):
|
|
260
|
+
current_func_name = inspect.stack()[0][3]
|
|
261
|
+
char_idx = 19
|
|
262
|
+
obj_name = current_func_name[char_idx:]
|
|
263
|
+
obj = params[obj_name]
|
|
264
|
+
|
|
265
|
+
param_name = "action_to_apply_to_input_signal"
|
|
266
|
+
action_to_apply_to_input_signal = params.get(param_name, None)
|
|
267
|
+
|
|
268
|
+
param_name = "input_signal"
|
|
269
|
+
input_signal = params.get(param_name, None)
|
|
270
|
+
|
|
271
|
+
if obj is not None:
|
|
272
|
+
try:
|
|
273
|
+
kwargs = {"obj": obj, "obj_name": obj_name}
|
|
274
|
+
center = czekitout.convert.to_pair_of_floats(**kwargs)
|
|
275
|
+
except:
|
|
276
|
+
err_msg = globals()[current_func_name+"_err_msg_1"]
|
|
277
|
+
raise TypeError(err_msg)
|
|
278
|
+
|
|
279
|
+
if input_signal is not None:
|
|
280
|
+
if action_to_apply_to_input_signal != "crop":
|
|
281
|
+
h_range, v_range = _calc_h_and_v_ranges(signal=input_signal)
|
|
282
|
+
center_is_within_h_range = h_range[0] <= center[0] <= h_range[1]
|
|
283
|
+
center_is_within_v_range = v_range[0] <= center[1] <= v_range[1]
|
|
284
|
+
center_is_invalid = ((not center_is_within_h_range)
|
|
285
|
+
or (not center_is_within_v_range))
|
|
286
|
+
if center_is_invalid:
|
|
287
|
+
err_msg = globals()[current_func_name+"_err_msg_2"]
|
|
288
|
+
raise ValueError(err_msg)
|
|
289
|
+
else:
|
|
290
|
+
if input_signal is not None:
|
|
291
|
+
if action_to_apply_to_input_signal != "crop":
|
|
292
|
+
h_range, v_range = _calc_h_and_v_ranges(signal=input_signal)
|
|
293
|
+
center = ((h_range[0]+h_range[1])/2, (v_range[0]+v_range[1])/2)
|
|
294
|
+
else:
|
|
295
|
+
N_v, N_h = input_signal.data.shape[-2:]
|
|
296
|
+
|
|
297
|
+
h_scale = input_signal.axes_manager[-2].scale
|
|
298
|
+
v_scale = input_signal.axes_manager[-1].scale
|
|
299
|
+
|
|
300
|
+
h_offset = input_signal.axes_manager[-2].offset
|
|
301
|
+
v_offset = input_signal.axes_manager[-1].offset
|
|
302
|
+
|
|
303
|
+
center = (h_offset + h_scale*((N_h-1)//2),
|
|
304
|
+
v_offset + v_scale*((N_v-1)//2))
|
|
305
|
+
else:
|
|
306
|
+
center = obj
|
|
307
|
+
|
|
308
|
+
return center
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _calc_h_and_v_ranges(signal):
|
|
313
|
+
h_scale = signal.axes_manager[-2].scale
|
|
314
|
+
v_scale = signal.axes_manager[-1].scale
|
|
315
|
+
|
|
316
|
+
N_v, N_h = signal.data.shape[-2:]
|
|
317
|
+
|
|
318
|
+
h_offset = signal.axes_manager[-2].offset
|
|
319
|
+
v_offset = signal.axes_manager[-1].offset
|
|
320
|
+
|
|
321
|
+
h_min = min(h_offset, h_offset + (N_h-1)*h_scale)
|
|
322
|
+
h_max = max(h_offset, h_offset + (N_h-1)*h_scale)
|
|
323
|
+
h_range = (h_min, h_max)
|
|
324
|
+
|
|
325
|
+
v_min = min(v_offset, v_offset + (N_v-1)*v_scale)
|
|
326
|
+
v_max = max(v_offset, v_offset + (N_v-1)*v_scale)
|
|
327
|
+
v_range = (v_min, v_max)
|
|
328
|
+
|
|
329
|
+
return h_range, v_range
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _pre_serialize_center(center):
|
|
334
|
+
obj_to_pre_serialize = random.choice(list(locals().values()))
|
|
335
|
+
serializable_rep = obj_to_pre_serialize
|
|
336
|
+
|
|
337
|
+
return serializable_rep
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _de_pre_serialize_center(serializable_rep):
|
|
342
|
+
center = serializable_rep
|
|
343
|
+
|
|
344
|
+
return center
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _check_and_convert_radial_range(params):
|
|
349
|
+
current_func_name = inspect.stack()[0][3]
|
|
350
|
+
char_idx = 19
|
|
351
|
+
obj_name = current_func_name[char_idx:]
|
|
352
|
+
obj = params[obj_name]
|
|
353
|
+
|
|
354
|
+
param_name = "input_signal"
|
|
355
|
+
input_signal = params.get(param_name, None)
|
|
356
|
+
|
|
357
|
+
if obj is not None:
|
|
358
|
+
try:
|
|
359
|
+
func_alias = czekitout.convert.to_pair_of_nonnegative_floats
|
|
360
|
+
kwargs = {"obj": obj, "obj_name": obj_name}
|
|
361
|
+
radial_range = func_alias(**kwargs)
|
|
362
|
+
except:
|
|
363
|
+
err_msg = globals()[current_func_name+"_err_msg_1"]
|
|
364
|
+
raise TypeError(err_msg)
|
|
365
|
+
|
|
366
|
+
if radial_range[0] >= radial_range[1]:
|
|
367
|
+
err_msg = globals()[current_func_name+"_err_msg_1"]
|
|
368
|
+
raise ValueError(err_msg)
|
|
369
|
+
else:
|
|
370
|
+
if input_signal is not None:
|
|
371
|
+
center = _check_and_convert_center(params)
|
|
372
|
+
h_range, v_range = _calc_h_and_v_ranges(signal=input_signal)
|
|
373
|
+
temp_1 = min(abs(center[0]-h_range[0]), abs(center[0]-h_range[1]))
|
|
374
|
+
temp_2 = min(abs(center[1]-v_range[0]), abs(center[1]-v_range[1]))
|
|
375
|
+
radial_range = (0, min(temp_1, temp_2))
|
|
376
|
+
else:
|
|
377
|
+
radial_range = obj
|
|
378
|
+
|
|
379
|
+
return radial_range
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _pre_serialize_radial_range(radial_range):
|
|
384
|
+
obj_to_pre_serialize = random.choice(list(locals().values()))
|
|
385
|
+
serializable_rep = obj_to_pre_serialize
|
|
386
|
+
|
|
387
|
+
return serializable_rep
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _de_pre_serialize_radial_range(serializable_rep):
|
|
392
|
+
radial_range = serializable_rep
|
|
393
|
+
|
|
394
|
+
return radial_range
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def _check_and_convert_num_bins(params):
|
|
399
|
+
current_func_name = inspect.stack()[0][3]
|
|
400
|
+
char_idx = 19
|
|
401
|
+
obj_name = current_func_name[char_idx:]
|
|
402
|
+
obj = params[obj_name]
|
|
403
|
+
|
|
404
|
+
param_name = "input_signal"
|
|
405
|
+
input_signal = params.get(param_name, None)
|
|
406
|
+
|
|
407
|
+
if obj is not None:
|
|
408
|
+
try:
|
|
409
|
+
kwargs = {"obj": obj, "obj_name": obj_name}
|
|
410
|
+
num_bins = czekitout.convert.to_positive_int(**kwargs)
|
|
411
|
+
except:
|
|
412
|
+
err_msg = globals()[current_func_name+"_err_msg_1"]
|
|
413
|
+
raise TypeError(err_msg)
|
|
414
|
+
else:
|
|
415
|
+
if input_signal is not None:
|
|
416
|
+
num_bins = (input_signal.data.shape[-1]
|
|
417
|
+
if _signal_is_1d(signal=input_signal)
|
|
418
|
+
else min(input_signal.data.shape[-2:]))
|
|
419
|
+
else:
|
|
420
|
+
num_bins = obj
|
|
421
|
+
|
|
422
|
+
return num_bins
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _signal_is_1d(signal):
|
|
427
|
+
signal_1d_types = (hyperspy.signals.Signal1D,
|
|
428
|
+
hyperspy.signals.ComplexSignal1D)
|
|
429
|
+
result = isinstance(signal, signal_1d_types)
|
|
430
|
+
|
|
431
|
+
return result
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _pre_serialize_num_bins(num_bins):
|
|
436
|
+
obj_to_pre_serialize = random.choice(list(locals().values()))
|
|
437
|
+
serializable_rep = obj_to_pre_serialize
|
|
438
|
+
|
|
439
|
+
return serializable_rep
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _de_pre_serialize_num_bins(serializable_rep):
|
|
444
|
+
num_bins = serializable_rep
|
|
445
|
+
|
|
446
|
+
return num_bins
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
_default_center = None
|
|
451
|
+
_default_radial_range = None
|
|
452
|
+
_default_num_bins = None
|
|
453
|
+
_default_skip_validation_and_conversion = False
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
_cls_alias = fancytypes.PreSerializableAndUpdatable
|
|
458
|
+
class OptionalAzimuthalAveragingParams(_cls_alias):
|
|
459
|
+
r"""The set of optional parameters for the function
|
|
460
|
+
:func:`empix.azimuthally_average`.
|
|
461
|
+
|
|
462
|
+
The Python function :func:`empix.azimuthally_average` averages
|
|
463
|
+
azimuthally a given input 2D ``hyperspy`` signal. The Python function
|
|
464
|
+
assumes that the input 2D ``hyperspy`` signal samples from a mathematical
|
|
465
|
+
function :math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` which is piecewise
|
|
466
|
+
continuous in :math:`u_{x}` and :math:`u_{y}`, where :math:`u_{x}` and
|
|
467
|
+
:math:`u_{y}` are the horizontal and vertical coordinates in the signal
|
|
468
|
+
space of the input signal, and :math:`\mathbf{m}` is a vector of integers
|
|
469
|
+
representing the navigation indices of the input signal. The Python function
|
|
470
|
+
approximates the azimuthal average of
|
|
471
|
+
:math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` given the input signal. We
|
|
472
|
+
define the azimuthal average of
|
|
473
|
+
:math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` as
|
|
474
|
+
|
|
475
|
+
.. math ::
|
|
476
|
+
&\overline{S}_{\mathbf{m}}\left(U_{r}=
|
|
477
|
+
u_{r}\left|0\le U_{\phi}<2\pi;c_{x},c_{y}\right.\right)
|
|
478
|
+
\\&\quad=\frac{1}{2\pi}\int_{0}^{2\pi}du_{\phi}\,
|
|
479
|
+
F_{\mathbf{m}}\left(c_{x}+u_{r}\cos\left(u_{\phi}\right),
|
|
480
|
+
c_{y}+u_{r}\sin\left(u_{\phi}\right)\right),
|
|
481
|
+
:label: azimuthal_average__1
|
|
482
|
+
|
|
483
|
+
where :math:`\left(c_{x},c_{y}\right)` is the reference point from which the
|
|
484
|
+
radial distance :math:`u_r` is defined for the azimuthal averaging.
|
|
485
|
+
|
|
486
|
+
Parameters
|
|
487
|
+
----------
|
|
488
|
+
center : `array_like` (`float`, shape=(2,)) | `None`, optional
|
|
489
|
+
If ``center`` is set to ``None``, then the reference point
|
|
490
|
+
:math:`\left(c_{x},c_{y}\right)`, from which the radial distance is
|
|
491
|
+
defined for the azimuthal averaging, is set to the signal space
|
|
492
|
+
coordinates corresponding to the center signal space pixel. Otherwise,
|
|
493
|
+
if ``center`` is set to a pair of floating-point numbers, then
|
|
494
|
+
``center[0]`` and ``center[1]`` specify :math:`c_{x}` and :math:`c_{y}`
|
|
495
|
+
respectively, in the same units of the corresponding signal space axes
|
|
496
|
+
of the input signal.
|
|
497
|
+
radial_range : `array_like` (`float`, shape=(2,)) | `None`, optional
|
|
498
|
+
If ``radial_range`` is set to ``None``, then the radial range, over
|
|
499
|
+
which the azimuthal averaging is performed, is from zero to the largest
|
|
500
|
+
radial distance within the signal space boundaries of the input signal
|
|
501
|
+
for all azimuthal angles. Otherwise, if ``radial_range`` is set to a
|
|
502
|
+
pair of floating-point numbers, then ``radial_range[0]`` and
|
|
503
|
+
``radial_range[1]`` specify the minimum and maximum radial distances of
|
|
504
|
+
the radial range respectively, in the same units of the horizontal and
|
|
505
|
+
vertical axes respectively of the signal space of the input signal. Note
|
|
506
|
+
that in this case ``radial_range`` must satisfy
|
|
507
|
+
``0<=radial_range[0]<radial_range[1]``. Moreover, the function
|
|
508
|
+
represented by the input signal is assumed to be equal to zero
|
|
509
|
+
everywhere outside of the signal space boundaries of said input signal.
|
|
510
|
+
num_bins : `int` | `None`, optional
|
|
511
|
+
``num_bins`` must either be a positive integer or of the `NoneType`: if
|
|
512
|
+
the former, then the dimension of the signal space of the output signal
|
|
513
|
+
``output_signal`` is set to ``num_bins``; if the latter, then the
|
|
514
|
+
dimension of the signal space of ``output_signal`` is set to
|
|
515
|
+
``min(input_signal.data.shape[-2:])``, where ``input_signal`` is the
|
|
516
|
+
input ``hyperspy`` signal.
|
|
517
|
+
title : `str` | `None`, optional
|
|
518
|
+
If ``title`` is set to ``None``, then the title of the output signal
|
|
519
|
+
``output_signal`` is set to ``"Azimuthally Averaged " +
|
|
520
|
+
input_signal.metadata.General.title``, where ``input_signal`` is the
|
|
521
|
+
input ``hyperspy`` signal. Otherwise, if ``title`` is a `str`, then the
|
|
522
|
+
``output_signal.metadata.General.title`` is set to the value of
|
|
523
|
+
``title``.
|
|
524
|
+
skip_validation_and_conversion : `bool`, optional
|
|
525
|
+
Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
|
|
526
|
+
attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
|
|
527
|
+
and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
|
|
528
|
+
being `dict` objects.
|
|
529
|
+
|
|
530
|
+
Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
|
|
531
|
+
representation of the constructor parameters excluding the parameter
|
|
532
|
+
``skip_validation_and_conversion``, where each `dict` key ``key`` is a
|
|
533
|
+
different constructor parameter name, excluding the name
|
|
534
|
+
``"skip_validation_and_conversion"``, and
|
|
535
|
+
``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
|
|
536
|
+
constructor parameter with the name given by ``key``.
|
|
537
|
+
|
|
538
|
+
If ``skip_validation_and_conversion`` is set to ``False``, then for each
|
|
539
|
+
key ``key`` in ``params_to_be_mapped_to_core_attrs``,
|
|
540
|
+
``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
|
|
541
|
+
(params_to_be_mapped_to_core_attrs)``.
|
|
542
|
+
|
|
543
|
+
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
|
|
544
|
+
then ``core_attrs`` is set to
|
|
545
|
+
``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
|
|
546
|
+
primarily when the user wants to avoid potentially expensive deep copies
|
|
547
|
+
and/or conversions of the `dict` values of
|
|
548
|
+
``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
|
|
549
|
+
copies or conversions are made in this case.
|
|
550
|
+
|
|
551
|
+
"""
|
|
552
|
+
ctor_param_names = ("center", "radial_range", "num_bins", "title")
|
|
553
|
+
kwargs = {"namespace_as_dict": globals(),
|
|
554
|
+
"ctor_param_names": ctor_param_names}
|
|
555
|
+
|
|
556
|
+
_validation_and_conversion_funcs_ = \
|
|
557
|
+
fancytypes.return_validation_and_conversion_funcs(**kwargs)
|
|
558
|
+
_pre_serialization_funcs_ = \
|
|
559
|
+
fancytypes.return_pre_serialization_funcs(**kwargs)
|
|
560
|
+
_de_pre_serialization_funcs_ = \
|
|
561
|
+
fancytypes.return_de_pre_serialization_funcs(**kwargs)
|
|
562
|
+
|
|
563
|
+
del ctor_param_names, kwargs
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def __init__(self,
|
|
568
|
+
center=\
|
|
569
|
+
_default_center,
|
|
570
|
+
radial_range=\
|
|
571
|
+
_default_radial_range,
|
|
572
|
+
num_bins=\
|
|
573
|
+
_default_num_bins,
|
|
574
|
+
title=\
|
|
575
|
+
_default_title,
|
|
576
|
+
skip_validation_and_conversion=\
|
|
577
|
+
_default_skip_validation_and_conversion):
|
|
578
|
+
ctor_params = {key: val
|
|
579
|
+
for key, val in locals().items()
|
|
580
|
+
if (key not in ("self", "__class__"))}
|
|
581
|
+
kwargs = ctor_params
|
|
582
|
+
kwargs["skip_cls_tests"] = True
|
|
583
|
+
fancytypes.PreSerializableAndUpdatable.__init__(self, **kwargs)
|
|
584
|
+
|
|
585
|
+
return None
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
@classmethod
|
|
590
|
+
def get_validation_and_conversion_funcs(cls):
|
|
591
|
+
validation_and_conversion_funcs = \
|
|
592
|
+
cls._validation_and_conversion_funcs_.copy()
|
|
593
|
+
|
|
594
|
+
return validation_and_conversion_funcs
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
@classmethod
|
|
599
|
+
def get_pre_serialization_funcs(cls):
|
|
600
|
+
pre_serialization_funcs = \
|
|
601
|
+
cls._pre_serialization_funcs_.copy()
|
|
602
|
+
|
|
603
|
+
return pre_serialization_funcs
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
@classmethod
|
|
608
|
+
def get_de_pre_serialization_funcs(cls):
|
|
609
|
+
de_pre_serialization_funcs = \
|
|
610
|
+
cls._de_pre_serialization_funcs_.copy()
|
|
611
|
+
|
|
612
|
+
return de_pre_serialization_funcs
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def _check_and_convert_optional_params(params):
|
|
617
|
+
current_func_name = inspect.stack()[0][3]
|
|
618
|
+
char_idx = 19
|
|
619
|
+
obj_name = current_func_name[char_idx:]
|
|
620
|
+
obj = params[obj_name]
|
|
621
|
+
|
|
622
|
+
param_name = "action_to_apply_to_input_signal"
|
|
623
|
+
action_to_apply_to_input_signal = params.get(param_name, None)
|
|
624
|
+
|
|
625
|
+
optional_params_cls_set = \
|
|
626
|
+
{"azimuthally_average": OptionalAzimuthalAveragingParams,
|
|
627
|
+
"azimuthally_integrate": OptionalAzimuthalIntegrationParams,
|
|
628
|
+
"annularly_average": OptionalAnnularAveragingParams,
|
|
629
|
+
"annularly_integrate": OptionalAnnularIntegrationParams,
|
|
630
|
+
"cumulatively_integrate_1d": OptionalCumulative1dIntegrationParams,
|
|
631
|
+
"crop": OptionalCroppingParams,
|
|
632
|
+
"downsample": OptionalDownsamplingParams,
|
|
633
|
+
"resample": OptionalResamplingParams}
|
|
634
|
+
optional_params_cls = \
|
|
635
|
+
optional_params_cls_set[action_to_apply_to_input_signal]
|
|
636
|
+
|
|
637
|
+
optional_params = optional_params_cls() if (obj is None) else obj
|
|
638
|
+
|
|
639
|
+
kwargs = {"obj": optional_params,
|
|
640
|
+
"obj_name": obj_name,
|
|
641
|
+
"accepted_types": (optional_params_cls, type(None))}
|
|
642
|
+
czekitout.check.if_instance_of_any_accepted_types(**kwargs)
|
|
643
|
+
|
|
644
|
+
optional_params_core_attrs = \
|
|
645
|
+
optional_params.get_core_attrs(deep_copy=False)
|
|
646
|
+
params = \
|
|
647
|
+
{"input_signal": _check_and_convert_input_signal(params),
|
|
648
|
+
"action_to_apply_to_input_signal": action_to_apply_to_input_signal,
|
|
649
|
+
**optional_params_core_attrs}
|
|
650
|
+
|
|
651
|
+
for param_name in optional_params_core_attrs:
|
|
652
|
+
func_name = "_check_and_convert_" + param_name
|
|
653
|
+
func_alias = globals()[func_name]
|
|
654
|
+
optional_params_core_attrs[param_name] = func_alias(params)
|
|
655
|
+
|
|
656
|
+
kwargs = {"skip_validation_and_conversion": True,
|
|
657
|
+
**optional_params_core_attrs}
|
|
658
|
+
optional_params = optional_params_cls(**kwargs)
|
|
659
|
+
|
|
660
|
+
return optional_params
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
_default_optional_params = None
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def azimuthally_average(input_signal, optional_params=_default_optional_params):
|
|
669
|
+
r"""Average azimuthally a given input 2D ``hyperspy`` signal.
|
|
670
|
+
|
|
671
|
+
This Python function assumes that the input 2D ``hyperspy`` signal samples
|
|
672
|
+
from a mathematical function :math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)`
|
|
673
|
+
which is piecewise continuous in :math:`u_{x}` and :math:`u_{y}`, where
|
|
674
|
+
:math:`u_{x}` and :math:`u_{y}` are the horizontal and vertical coordinates
|
|
675
|
+
in the signal space of the input signal, and :math:`\mathbf{m}` is a vector
|
|
676
|
+
of integers representing the navigation indices of the input signal. The
|
|
677
|
+
Python function approximates the azimuthal average of
|
|
678
|
+
:math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` given the input signal. We
|
|
679
|
+
define the azimuthal average of
|
|
680
|
+
:math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` as
|
|
681
|
+
|
|
682
|
+
.. math ::
|
|
683
|
+
&\overline{S}_{\mathbf{m}}\left(U_{r}=
|
|
684
|
+
u_{r}\left|0\le U_{\phi}<2\pi;c_{x},c_{y}\right.\right)
|
|
685
|
+
\\&\quad=\frac{1}{2\pi}\int_{0}^{2\pi}du_{\phi}\,
|
|
686
|
+
F_{\mathbf{m}}\left(c_{x}+u_{r}\cos\left(u_{\phi}\right),
|
|
687
|
+
c_{y}+u_{r}\sin\left(u_{\phi}\right)\right),
|
|
688
|
+
:label: azimuthal_average__2
|
|
689
|
+
|
|
690
|
+
where :math:`\left(c_{x},c_{y}\right)` is the reference point from which the
|
|
691
|
+
radial distance :math:`u_r` is defined for the azimuthal averaging.
|
|
692
|
+
|
|
693
|
+
Parameters
|
|
694
|
+
----------
|
|
695
|
+
input_signal : :class:`hyperspy._signals.signal2d.Signal2D` | :class:`hyperspy._signals.complex_signal2d.ComplexSignal2D`
|
|
696
|
+
The input ``hyperspy`` signal.
|
|
697
|
+
optional_params : :class:`empix.OptionalAzimuthalAveragingParams` | `None`, optional
|
|
698
|
+
The set of optional parameters. See the documentation for the class
|
|
699
|
+
:class:`empix.OptionalAzimuthalAveragingParams` for details. If
|
|
700
|
+
``optional_params`` is set to ``None``, rather than an instance of
|
|
701
|
+
:class:`empix.OptionalAzimuthalAveragingParams`, then the default
|
|
702
|
+
values of the optional parameters are chosen.
|
|
703
|
+
|
|
704
|
+
Returns
|
|
705
|
+
-------
|
|
706
|
+
output_signal : :class:`hyperspy._signals.signal1d.Signal1D` | :class:`hyperspy._signals.complex_signal1d.ComplexSignal1D`
|
|
707
|
+
The output ``hyperspy`` signal that samples the azimuthal average of the
|
|
708
|
+
input signal ``input_signal``. Note that the metadata of the input
|
|
709
|
+
signal is copied over to the output signal, with the title being
|
|
710
|
+
overwritten.
|
|
711
|
+
|
|
712
|
+
"""
|
|
713
|
+
params = locals()
|
|
714
|
+
params["action_to_apply_to_input_signal"] = inspect.stack()[0][3]
|
|
715
|
+
for param_name in params:
|
|
716
|
+
func_name = "_check_and_convert_" + param_name
|
|
717
|
+
func_alias = globals()[func_name]
|
|
718
|
+
params[param_name] = func_alias(params)
|
|
719
|
+
|
|
720
|
+
func_name = "_" + inspect.stack()[0][3]
|
|
721
|
+
func_alias = globals()[func_name]
|
|
722
|
+
kwargs = params
|
|
723
|
+
del kwargs["action_to_apply_to_input_signal"]
|
|
724
|
+
output_signal = func_alias(**kwargs)
|
|
725
|
+
|
|
726
|
+
return output_signal
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def _azimuthally_average(input_signal, optional_params):
|
|
731
|
+
optional_params_core_attrs = optional_params.get_core_attrs(deep_copy=False)
|
|
732
|
+
center = optional_params_core_attrs["center"]
|
|
733
|
+
radial_range = optional_params_core_attrs["radial_range"]
|
|
734
|
+
num_bins = optional_params_core_attrs["num_bins"]
|
|
735
|
+
title = optional_params_core_attrs["title"]
|
|
736
|
+
|
|
737
|
+
azimuthal_integrator = _construct_azimuthal_integrator(input_signal, center)
|
|
738
|
+
|
|
739
|
+
func_alias = _apply_azimuthal_integrator_to_input_signal
|
|
740
|
+
kwargs = {"azimuthal_integrator": azimuthal_integrator,
|
|
741
|
+
"input_signal": input_signal,
|
|
742
|
+
"num_bins": num_bins,
|
|
743
|
+
"radial_range": radial_range}
|
|
744
|
+
bin_coords, output_signal_data = func_alias(**kwargs)
|
|
745
|
+
|
|
746
|
+
kwargs = {"data": output_signal_data,
|
|
747
|
+
"metadata": input_signal.metadata.as_dictionary()}
|
|
748
|
+
if np.isrealobj(output_signal_data):
|
|
749
|
+
output_signal = hyperspy.signals.Signal1D(**kwargs)
|
|
750
|
+
else:
|
|
751
|
+
output_signal = hyperspy.signals.ComplexSignal1D(**kwargs)
|
|
752
|
+
output_signal.metadata.set_item("General.title", title)
|
|
753
|
+
|
|
754
|
+
kwargs = {"input_signal": input_signal,
|
|
755
|
+
"optional_params": optional_params,
|
|
756
|
+
"bin_coords": bin_coords,
|
|
757
|
+
"output_signal": output_signal}
|
|
758
|
+
_update_output_signal_axes(**kwargs)
|
|
759
|
+
|
|
760
|
+
return output_signal
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
def _construct_azimuthal_integrator(signal, center):
|
|
765
|
+
detector = _construct_pyfai_detector(signal)
|
|
766
|
+
|
|
767
|
+
h_scale = signal.axes_manager[-2].scale
|
|
768
|
+
v_scale = signal.axes_manager[-1].scale
|
|
769
|
+
|
|
770
|
+
# ``pone_1`` and ``poni_2`` are the vertical and horizontal displacements
|
|
771
|
+
# of the reference point, from which to perform the azimuthal integration,
|
|
772
|
+
# from the top left corner of the input signal.
|
|
773
|
+
h_range, v_range = _calc_h_and_v_ranges(signal)
|
|
774
|
+
poni_1 = center[1]-v_range[0] if (v_scale > 0) else v_range[1]-center[1]
|
|
775
|
+
poni_2 = center[0]-h_range[0] if (h_scale > 0) else h_range[1]-center[0]
|
|
776
|
+
|
|
777
|
+
# We require ``L >> max(v_pixel_size, h_pixel_size)``.
|
|
778
|
+
h_pixel_size = abs(h_scale)
|
|
779
|
+
v_pixel_size = abs(v_scale)
|
|
780
|
+
L = 10000 * max(v_pixel_size, h_pixel_size)
|
|
781
|
+
|
|
782
|
+
AzimuthalIntegrator = pyFAI.azimuthalIntegrator.AzimuthalIntegrator
|
|
783
|
+
kwargs = {"dist": L,
|
|
784
|
+
"poni1": poni_1,
|
|
785
|
+
"poni2": poni_2,
|
|
786
|
+
"detector": detector}
|
|
787
|
+
azimuthal_integrator = AzimuthalIntegrator(**kwargs)
|
|
788
|
+
|
|
789
|
+
return azimuthal_integrator
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
def _construct_pyfai_detector(signal):
|
|
794
|
+
h_pixel_size = abs(signal.axes_manager[-2].scale)
|
|
795
|
+
v_pixel_size = abs(signal.axes_manager[-1].scale)
|
|
796
|
+
|
|
797
|
+
kwargs = {"pixel1": v_pixel_size, "pixel2": h_pixel_size}
|
|
798
|
+
detector = pyFAI.detectors.Detector(**kwargs)
|
|
799
|
+
|
|
800
|
+
return detector
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
def _apply_azimuthal_integrator_to_input_signal(azimuthal_integrator,
|
|
805
|
+
input_signal,
|
|
806
|
+
num_bins,
|
|
807
|
+
radial_range):
|
|
808
|
+
navigation_dims = input_signal.data.shape[:-2]
|
|
809
|
+
output_signal_data_shape = navigation_dims + (num_bins,)
|
|
810
|
+
output_signal_data = np.zeros(output_signal_data_shape,
|
|
811
|
+
dtype=input_signal.data.dtype)
|
|
812
|
+
|
|
813
|
+
num_patterns = int(np.prod(navigation_dims))
|
|
814
|
+
for pattern_idx in range(num_patterns):
|
|
815
|
+
navigation_indices = np.unravel_index(pattern_idx, navigation_dims)
|
|
816
|
+
input_signal_datasubset = input_signal.data[navigation_indices]
|
|
817
|
+
|
|
818
|
+
func_alias = _apply_azimuthal_integrator_to_input_signal_datasubset
|
|
819
|
+
kwargs = {"azimuthal_integrator": azimuthal_integrator,
|
|
820
|
+
"input_signal_datasubset": input_signal_datasubset,
|
|
821
|
+
"num_bins": num_bins,
|
|
822
|
+
"radial_range": radial_range}
|
|
823
|
+
bin_coords, output_signal_datasubset = func_alias(**kwargs)
|
|
824
|
+
output_signal_data[navigation_indices] = output_signal_datasubset
|
|
825
|
+
|
|
826
|
+
bin_coords /= 1000 # Because of the artificial "r_mm" units.
|
|
827
|
+
|
|
828
|
+
return bin_coords, output_signal_data
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
def _apply_azimuthal_integrator_to_input_signal_datasubset(
|
|
833
|
+
azimuthal_integrator,
|
|
834
|
+
input_signal_datasubset,
|
|
835
|
+
num_bins,
|
|
836
|
+
radial_range):
|
|
837
|
+
# Need to multiply range by 1000 because of the units used in pyFAI's
|
|
838
|
+
# azimuthal integrator.
|
|
839
|
+
method_alias = azimuthal_integrator.integrate1d
|
|
840
|
+
kwargs = {"npt": num_bins,
|
|
841
|
+
"radial_range": (radial_range[0]*1000, radial_range[1]*1000),
|
|
842
|
+
"unit": "r_mm"}
|
|
843
|
+
|
|
844
|
+
if np.isrealobj(input_signal_datasubset):
|
|
845
|
+
kwargs["data"] = input_signal_datasubset
|
|
846
|
+
bin_coords, output_signal_datasubset = method_alias(**kwargs)
|
|
847
|
+
else:
|
|
848
|
+
kwargs["data"] = input_signal_datasubset.real
|
|
849
|
+
bin_coords, real_output_signal_datasubset = method_alias(**kwargs)
|
|
850
|
+
|
|
851
|
+
kwargs["data"] = input_signal_datasubset.imag
|
|
852
|
+
bin_coords, imag_output_signal_datasubset = method_alias(**kwargs)
|
|
853
|
+
|
|
854
|
+
output_signal_datasubset = (real_output_signal_datasubset
|
|
855
|
+
+ 1j*imag_output_signal_datasubset)
|
|
856
|
+
|
|
857
|
+
return bin_coords, output_signal_datasubset
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def _update_output_signal_axes(input_signal,
|
|
862
|
+
optional_params,
|
|
863
|
+
bin_coords,
|
|
864
|
+
output_signal):
|
|
865
|
+
kwargs = {"input_signal": input_signal,
|
|
866
|
+
"optional_params": optional_params}
|
|
867
|
+
output_signal_axes_names = _calc_output_signal_axes_names(**kwargs)
|
|
868
|
+
output_signal_axes_units = _calc_output_signal_axes_units(**kwargs)
|
|
869
|
+
|
|
870
|
+
kwargs["bin_coords"] = bin_coords
|
|
871
|
+
output_signal_axes_offsets = _calc_output_signal_axes_offsets(**kwargs)
|
|
872
|
+
output_signal_axes_scales = _calc_output_signal_axes_scales(**kwargs)
|
|
873
|
+
|
|
874
|
+
num_axes = len(output_signal.data.shape)
|
|
875
|
+
|
|
876
|
+
output_signal_axes_sizes = tuple(output_signal.axes_manager[idx].size
|
|
877
|
+
for idx
|
|
878
|
+
in range(num_axes))
|
|
879
|
+
|
|
880
|
+
for axis_idx in range(num_axes):
|
|
881
|
+
kwargs = {"size": output_signal_axes_sizes[axis_idx],
|
|
882
|
+
"scale": output_signal_axes_scales[axis_idx],
|
|
883
|
+
"offset": output_signal_axes_offsets[axis_idx],
|
|
884
|
+
"units": output_signal_axes_units[axis_idx]}
|
|
885
|
+
new_output_signal_axis = hyperspy.axes.UniformDataAxis(**kwargs)
|
|
886
|
+
|
|
887
|
+
name = output_signal_axes_names[axis_idx]
|
|
888
|
+
units = output_signal_axes_units[axis_idx]
|
|
889
|
+
|
|
890
|
+
output_signal.axes_manager[axis_idx].update_from(new_output_signal_axis)
|
|
891
|
+
output_signal.axes_manager[axis_idx].name = name
|
|
892
|
+
output_signal.axes_manager[axis_idx].units = units
|
|
893
|
+
|
|
894
|
+
return None
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
def _calc_output_signal_axes_names(input_signal, optional_params):
|
|
899
|
+
num_axes = len(input_signal.data.shape)
|
|
900
|
+
|
|
901
|
+
optional_params_core_attrs = optional_params.get_core_attrs(deep_copy=False)
|
|
902
|
+
|
|
903
|
+
input_signal_axes_names = tuple(input_signal.axes_manager[idx].name
|
|
904
|
+
for idx
|
|
905
|
+
in range(num_axes))
|
|
906
|
+
input_signal_axes_units = tuple(input_signal.axes_manager[idx].units
|
|
907
|
+
for idx
|
|
908
|
+
in range(num_axes))
|
|
909
|
+
|
|
910
|
+
if "num_bins" in optional_params_core_attrs:
|
|
911
|
+
if "limits" in optional_params_core_attrs:
|
|
912
|
+
unit_to_axis_name_map = {"Å": "$r$", "1/Å": "$k$"}
|
|
913
|
+
|
|
914
|
+
args = (input_signal_axes_units[-1], "")
|
|
915
|
+
output_signal_axis_name = unit_to_axis_name_map.get(*args)
|
|
916
|
+
|
|
917
|
+
output_signal_axes_names = (input_signal_axes_names[:-1]
|
|
918
|
+
+ (output_signal_axis_name,))
|
|
919
|
+
else:
|
|
920
|
+
unit_to_axis_name_map = {"Å": "$r_{xy}$", "1/Å": "$k_{xy}$"}
|
|
921
|
+
|
|
922
|
+
args = (input_signal_axes_units[-1], "")
|
|
923
|
+
output_signal_axis_name = unit_to_axis_name_map.get(*args)
|
|
924
|
+
|
|
925
|
+
output_signal_axes_names = (input_signal_axes_names[:-2]
|
|
926
|
+
+ (output_signal_axis_name,))
|
|
927
|
+
else:
|
|
928
|
+
output_signal_axes_names = input_signal_axes_names
|
|
929
|
+
|
|
930
|
+
return output_signal_axes_names
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
def _calc_output_signal_axes_units(input_signal, optional_params):
|
|
935
|
+
num_axes = len(input_signal.data.shape)
|
|
936
|
+
|
|
937
|
+
optional_params_core_attrs = optional_params.get_core_attrs(deep_copy=False)
|
|
938
|
+
|
|
939
|
+
input_signal_axes_units = tuple(input_signal.axes_manager[idx].units
|
|
940
|
+
for idx
|
|
941
|
+
in range(num_axes))
|
|
942
|
+
|
|
943
|
+
if "num_bins" in optional_params_core_attrs:
|
|
944
|
+
if "limits" in optional_params_core_attrs:
|
|
945
|
+
output_signal_axes_units = input_signal_axes_units
|
|
946
|
+
else:
|
|
947
|
+
output_signal_axes_units = (input_signal_axes_units[:-2]
|
|
948
|
+
+ (input_signal_axes_units[-1],))
|
|
949
|
+
else:
|
|
950
|
+
output_signal_axes_units = input_signal_axes_units
|
|
951
|
+
|
|
952
|
+
return output_signal_axes_units
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
def _calc_output_signal_axes_offsets(input_signal, optional_params, bin_coords):
|
|
957
|
+
num_axes = len(input_signal.data.shape)
|
|
958
|
+
|
|
959
|
+
optional_params_core_attrs = optional_params.get_core_attrs(deep_copy=False)
|
|
960
|
+
|
|
961
|
+
input_signal_axes_offsets = tuple(input_signal.axes_manager[idx].offset
|
|
962
|
+
for idx
|
|
963
|
+
in range(num_axes))
|
|
964
|
+
|
|
965
|
+
if "pad_mode" in optional_params_core_attrs:
|
|
966
|
+
func_alias = _calc_output_signal_axes_offsets_resulting_from_crop
|
|
967
|
+
kwargs = {"input_signal": input_signal,
|
|
968
|
+
"optional_params": optional_params}
|
|
969
|
+
output_signal_axes_offsets = func_alias(**kwargs)
|
|
970
|
+
elif "num_bins" in optional_params_core_attrs:
|
|
971
|
+
if "limits" in optional_params_core_attrs:
|
|
972
|
+
output_signal_axes_offsets = (input_signal_axes_offsets[:-1]
|
|
973
|
+
+ (bin_coords[0],))
|
|
974
|
+
else:
|
|
975
|
+
output_signal_axes_offsets = (input_signal_axes_offsets[:-2]
|
|
976
|
+
+ (bin_coords[0],))
|
|
977
|
+
else:
|
|
978
|
+
input_signal_axes_scales = tuple(input_signal.axes_manager[idx].scale
|
|
979
|
+
for idx
|
|
980
|
+
in range(num_axes))
|
|
981
|
+
|
|
982
|
+
output_signal_axes_offsets = list(input_signal_axes_offsets)
|
|
983
|
+
|
|
984
|
+
if "block_dims" in optional_params_core_attrs:
|
|
985
|
+
block_dims = optional_params_core_attrs["block_dims"]
|
|
986
|
+
|
|
987
|
+
for idx in (-2, -1):
|
|
988
|
+
output_signal_axes_offsets[idx] += \
|
|
989
|
+
0.5*(block_dims[idx]-1)*input_signal_axes_scales[idx]
|
|
990
|
+
|
|
991
|
+
else:
|
|
992
|
+
for idx in (-2, -1):
|
|
993
|
+
output_signal_axes_offsets[idx] = \
|
|
994
|
+
optional_params_core_attrs["new_signal_space_offsets"][idx]
|
|
995
|
+
|
|
996
|
+
output_signal_axes_offsets = tuple(output_signal_axes_offsets)
|
|
997
|
+
|
|
998
|
+
return output_signal_axes_offsets
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
def _calc_output_signal_axes_offsets_resulting_from_crop(input_signal,
|
|
1003
|
+
optional_params):
|
|
1004
|
+
func_alias = _calc_input_signal_datasubset_cropping_params
|
|
1005
|
+
input_signal_datasubset_cropping_params = func_alias(input_signal,
|
|
1006
|
+
optional_params)
|
|
1007
|
+
|
|
1008
|
+
multi_dim_slice_for_cropping = \
|
|
1009
|
+
input_signal_datasubset_cropping_params["multi_dim_slice_for_cropping"]
|
|
1010
|
+
|
|
1011
|
+
num_axes = len(input_signal.data.shape)
|
|
1012
|
+
|
|
1013
|
+
input_signal_axes_scales = tuple(input_signal.axes_manager[idx].scale
|
|
1014
|
+
for idx
|
|
1015
|
+
in range(num_axes))
|
|
1016
|
+
input_signal_axes_offsets = tuple(input_signal.axes_manager[idx].offset
|
|
1017
|
+
for idx
|
|
1018
|
+
in range(num_axes))
|
|
1019
|
+
|
|
1020
|
+
num_spatial_dims = len(multi_dim_slice_for_cropping)
|
|
1021
|
+
|
|
1022
|
+
output_signal_axes_offsets = list(input_signal_axes_offsets)
|
|
1023
|
+
for spatial_dim_idx in range(num_spatial_dims):
|
|
1024
|
+
input_signal_axes_scale = \
|
|
1025
|
+
input_signal_axes_scales[-2+spatial_dim_idx]
|
|
1026
|
+
input_signal_axes_offset = \
|
|
1027
|
+
input_signal_axes_offsets[-2+spatial_dim_idx]
|
|
1028
|
+
|
|
1029
|
+
single_dim_slice_for_cropping = \
|
|
1030
|
+
multi_dim_slice_for_cropping[-(spatial_dim_idx+1)]
|
|
1031
|
+
|
|
1032
|
+
output_signal_axes_offset = \
|
|
1033
|
+
(input_signal_axes_offset
|
|
1034
|
+
+ input_signal_axes_scale*single_dim_slice_for_cropping.start)
|
|
1035
|
+
output_signal_axes_offsets[-2+spatial_dim_idx] = \
|
|
1036
|
+
output_signal_axes_offset
|
|
1037
|
+
|
|
1038
|
+
output_signal_axes_offsets = tuple(output_signal_axes_offsets)
|
|
1039
|
+
|
|
1040
|
+
return output_signal_axes_offsets
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
def _calc_crop_window_center_in_pixel_coords(input_signal,
|
|
1045
|
+
approximate_crop_window_center):
|
|
1046
|
+
h_scale = input_signal.axes_manager[-2].scale
|
|
1047
|
+
v_scale = input_signal.axes_manager[-1].scale
|
|
1048
|
+
|
|
1049
|
+
h_offset = input_signal.axes_manager[-2].offset
|
|
1050
|
+
v_offset = input_signal.axes_manager[-1].offset
|
|
1051
|
+
|
|
1052
|
+
crop_window_center_in_pixel_coords = \
|
|
1053
|
+
tuple()
|
|
1054
|
+
crop_window_center_in_pixel_coords += \
|
|
1055
|
+
(round((approximate_crop_window_center[0]-h_offset) / h_scale),)
|
|
1056
|
+
crop_window_center_in_pixel_coords += \
|
|
1057
|
+
(round((approximate_crop_window_center[1]-v_offset) / v_scale),)
|
|
1058
|
+
|
|
1059
|
+
return crop_window_center_in_pixel_coords
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
def _calc_output_signal_axes_scales(input_signal, optional_params, bin_coords):
|
|
1064
|
+
num_axes = len(input_signal.data.shape)
|
|
1065
|
+
|
|
1066
|
+
optional_params_core_attrs = optional_params.get_core_attrs(deep_copy=False)
|
|
1067
|
+
|
|
1068
|
+
input_signal_axes_scales = tuple(input_signal.axes_manager[idx].scale
|
|
1069
|
+
for idx
|
|
1070
|
+
in range(num_axes))
|
|
1071
|
+
|
|
1072
|
+
if "pad_mode" in optional_params_core_attrs:
|
|
1073
|
+
output_signal_axes_scales = input_signal_axes_scales
|
|
1074
|
+
elif "num_bins" in optional_params_core_attrs:
|
|
1075
|
+
if "limits" in optional_params_core_attrs:
|
|
1076
|
+
output_signal_axes_scales = (input_signal_axes_scales[:-1]
|
|
1077
|
+
+ (bin_coords[1]-bin_coords[0],))
|
|
1078
|
+
else:
|
|
1079
|
+
output_signal_axes_scales = (input_signal_axes_scales[:-2]
|
|
1080
|
+
+ (bin_coords[1]-bin_coords[0],))
|
|
1081
|
+
else:
|
|
1082
|
+
output_signal_axes_scales = list(input_signal_axes_scales)
|
|
1083
|
+
|
|
1084
|
+
if "block_dims" in optional_params_core_attrs:
|
|
1085
|
+
block_dims = optional_params_core_attrs["block_dims"]
|
|
1086
|
+
|
|
1087
|
+
for idx in (-2, -1):
|
|
1088
|
+
output_signal_axes_scales[idx] *= \
|
|
1089
|
+
block_dims[idx]
|
|
1090
|
+
|
|
1091
|
+
else:
|
|
1092
|
+
for idx in (-2, -1):
|
|
1093
|
+
output_signal_axes_scales[idx] = \
|
|
1094
|
+
optional_params_core_attrs["new_signal_space_scales"][idx]
|
|
1095
|
+
|
|
1096
|
+
output_signal_axes_scales = tuple(output_signal_axes_scales)
|
|
1097
|
+
|
|
1098
|
+
return output_signal_axes_scales
|
|
1099
|
+
|
|
1100
|
+
|
|
1101
|
+
|
|
1102
|
+
_cls_alias = fancytypes.PreSerializableAndUpdatable
|
|
1103
|
+
class OptionalAzimuthalIntegrationParams(_cls_alias):
|
|
1104
|
+
r"""The set of optional parameters for the function
|
|
1105
|
+
:func:`empix.azimuthally_integrate`.
|
|
1106
|
+
|
|
1107
|
+
The Python function :func:`empix.azimuthally_integrate` integrates
|
|
1108
|
+
azimuthally a given input 2D ``hyperspy`` signal. The Python function
|
|
1109
|
+
assumes that the input 2D ``hyperspy`` signal samples from a mathematical
|
|
1110
|
+
function :math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` which is piecewise
|
|
1111
|
+
continuous in :math:`u_{x}` and :math:`u_{y}`, where :math:`u_{x}` and
|
|
1112
|
+
:math:`u_{y}` are the horizontal and vertical coordinates in the signal
|
|
1113
|
+
space of the input signal, and :math:`\mathbf{m}` is a vector of integers
|
|
1114
|
+
representing the navigation indices of the input signal. The Python function
|
|
1115
|
+
approximates the azimuthal integral of
|
|
1116
|
+
:math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` given the input signal. We
|
|
1117
|
+
define the azimuthal integral of
|
|
1118
|
+
:math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` as
|
|
1119
|
+
|
|
1120
|
+
.. math ::
|
|
1121
|
+
&S_{\mathbf{m}}\left(U_{r}=
|
|
1122
|
+
u_{r}\left|0\le U_{\phi}<2\pi;c_{x},c_{y}\right.\right)
|
|
1123
|
+
\\&\quad=\int_{0}^{2\pi}du_{\phi}\,u_{r}
|
|
1124
|
+
F_{\mathbf{m}}\left(c_{x}+u_{r}\cos\left(u_{\phi}\right),
|
|
1125
|
+
c_{y}+u_{r}\sin\left(u_{\phi}\right)\right),
|
|
1126
|
+
:label: azimuthal_integral__1
|
|
1127
|
+
|
|
1128
|
+
where :math:`\left(c_{x},c_{y}\right)` is the reference point from which the
|
|
1129
|
+
radial distance :math:`u_r` is defined for the azimuthal integration.
|
|
1130
|
+
|
|
1131
|
+
Parameters
|
|
1132
|
+
----------
|
|
1133
|
+
center : `array_like` (`float`, shape=(2,)) | `None`, optional
|
|
1134
|
+
If ``center`` is set to ``None``, then the reference point
|
|
1135
|
+
:math:`\left(c_{x},c_{y}\right)`, from which the radial distance is
|
|
1136
|
+
defined for the azimuthal integration, is set to the signal space
|
|
1137
|
+
coordinates corresponding to the center signal space pixel. Otherwise,
|
|
1138
|
+
if ``center`` is set to a pair of floating-point numbers, then
|
|
1139
|
+
``center[0]`` and ``center[1]`` specify :math:`c_{x}` and :math:`c_{y}`
|
|
1140
|
+
respectively, in the same units of the corresponding signal space axes
|
|
1141
|
+
of the input signal.
|
|
1142
|
+
radial_range : `array_like` (`float`, shape=(2,)) | `None`, optional
|
|
1143
|
+
If ``radial_range`` is set to ``None``, then the radial range, over
|
|
1144
|
+
which the azimuthal integration is performed, is from zero to the largest
|
|
1145
|
+
radial distance within the signal space boundaries of the input signal
|
|
1146
|
+
for all azimuthal angles. Otherwise, if ``radial_range`` is set to a
|
|
1147
|
+
pair of floating-point numbers, then ``radial_range[0]`` and
|
|
1148
|
+
``radial_range[1]`` specify the minimum and maximum radial distances of
|
|
1149
|
+
the radial range respectively, in the same units of the horizontal and
|
|
1150
|
+
vertical axes respectively of the signal space of the input signal. Note
|
|
1151
|
+
that in this case ``radial_range`` must satisfy
|
|
1152
|
+
``0<=radial_range[0]<radial_range[1]``. Moreover, the function
|
|
1153
|
+
represented by the input signal is assumed to be equal to zero
|
|
1154
|
+
everywhere outside of the signal space boundaries of said input signal.
|
|
1155
|
+
num_bins : `int` | `None`, optional
|
|
1156
|
+
``num_bins`` must either be a positive integer or of the `NoneType`: if
|
|
1157
|
+
the former, then the dimension of the signal space of the output signal
|
|
1158
|
+
``output_signal`` is set to ``num_bins``; if the latter, then the
|
|
1159
|
+
dimension of the signal space of ``output_signal`` is set to
|
|
1160
|
+
``min(input_signal.data.shape[-2:])``, where ``input_signal`` is the
|
|
1161
|
+
input ``hyperspy`` signal.
|
|
1162
|
+
title : `str` | `None`, optional
|
|
1163
|
+
If ``title`` is set to ``None``, then the title of the output signal
|
|
1164
|
+
``output_signal`` is set to ``"Azimuthally Integrated " +
|
|
1165
|
+
input_signal.metadata.General.title``, where ``input_signal`` is the
|
|
1166
|
+
input ``hyperspy`` signal. Otherwise, if ``title`` is a `str`, then the
|
|
1167
|
+
``output_signal.metadata.General.title`` is set to the value of
|
|
1168
|
+
``title``.
|
|
1169
|
+
skip_validation_and_conversion : `bool`, optional
|
|
1170
|
+
Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
|
|
1171
|
+
attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
|
|
1172
|
+
and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
|
|
1173
|
+
being `dict` objects.
|
|
1174
|
+
|
|
1175
|
+
Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
|
|
1176
|
+
representation of the constructor parameters excluding the parameter
|
|
1177
|
+
``skip_validation_and_conversion``, where each `dict` key ``key`` is a
|
|
1178
|
+
different constructor parameter name, excluding the name
|
|
1179
|
+
``"skip_validation_and_conversion"``, and
|
|
1180
|
+
``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
|
|
1181
|
+
constructor parameter with the name given by ``key``.
|
|
1182
|
+
|
|
1183
|
+
If ``skip_validation_and_conversion`` is set to ``False``, then for each
|
|
1184
|
+
key ``key`` in ``params_to_be_mapped_to_core_attrs``,
|
|
1185
|
+
``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
|
|
1186
|
+
(params_to_be_mapped_to_core_attrs)``.
|
|
1187
|
+
|
|
1188
|
+
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
|
|
1189
|
+
then ``core_attrs`` is set to
|
|
1190
|
+
``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
|
|
1191
|
+
primarily when the user wants to avoid potentially expensive deep copies
|
|
1192
|
+
and/or conversions of the `dict` values of
|
|
1193
|
+
``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
|
|
1194
|
+
copies or conversions are made in this case.
|
|
1195
|
+
|
|
1196
|
+
"""
|
|
1197
|
+
ctor_param_names = ("center", "radial_range", "num_bins", "title")
|
|
1198
|
+
kwargs = {"namespace_as_dict": globals(),
|
|
1199
|
+
"ctor_param_names": ctor_param_names}
|
|
1200
|
+
|
|
1201
|
+
_validation_and_conversion_funcs_ = \
|
|
1202
|
+
fancytypes.return_validation_and_conversion_funcs(**kwargs)
|
|
1203
|
+
_pre_serialization_funcs_ = \
|
|
1204
|
+
fancytypes.return_pre_serialization_funcs(**kwargs)
|
|
1205
|
+
_de_pre_serialization_funcs_ = \
|
|
1206
|
+
fancytypes.return_de_pre_serialization_funcs(**kwargs)
|
|
1207
|
+
|
|
1208
|
+
del ctor_param_names, kwargs
|
|
1209
|
+
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
def __init__(self,
|
|
1213
|
+
center=\
|
|
1214
|
+
_default_center,
|
|
1215
|
+
radial_range=\
|
|
1216
|
+
_default_radial_range,
|
|
1217
|
+
num_bins=\
|
|
1218
|
+
_default_num_bins,
|
|
1219
|
+
title=\
|
|
1220
|
+
_default_title,
|
|
1221
|
+
skip_validation_and_conversion=\
|
|
1222
|
+
_default_skip_validation_and_conversion):
|
|
1223
|
+
ctor_params = {key: val
|
|
1224
|
+
for key, val in locals().items()
|
|
1225
|
+
if (key not in ("self", "__class__"))}
|
|
1226
|
+
kwargs = ctor_params
|
|
1227
|
+
kwargs["skip_cls_tests"] = True
|
|
1228
|
+
fancytypes.PreSerializableAndUpdatable.__init__(self, **kwargs)
|
|
1229
|
+
|
|
1230
|
+
return None
|
|
1231
|
+
|
|
1232
|
+
|
|
1233
|
+
|
|
1234
|
+
@classmethod
|
|
1235
|
+
def get_validation_and_conversion_funcs(cls):
|
|
1236
|
+
validation_and_conversion_funcs = \
|
|
1237
|
+
cls._validation_and_conversion_funcs_.copy()
|
|
1238
|
+
|
|
1239
|
+
return validation_and_conversion_funcs
|
|
1240
|
+
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
@classmethod
|
|
1244
|
+
def get_pre_serialization_funcs(cls):
|
|
1245
|
+
pre_serialization_funcs = \
|
|
1246
|
+
cls._pre_serialization_funcs_.copy()
|
|
1247
|
+
|
|
1248
|
+
return pre_serialization_funcs
|
|
1249
|
+
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
@classmethod
|
|
1253
|
+
def get_de_pre_serialization_funcs(cls):
|
|
1254
|
+
de_pre_serialization_funcs = \
|
|
1255
|
+
cls._de_pre_serialization_funcs_.copy()
|
|
1256
|
+
|
|
1257
|
+
return de_pre_serialization_funcs
|
|
1258
|
+
|
|
1259
|
+
|
|
1260
|
+
|
|
1261
|
+
def azimuthally_integrate(input_signal,
|
|
1262
|
+
optional_params=_default_optional_params):
|
|
1263
|
+
r"""Integrate azimuthally a given input 2D ``hyperspy`` signal.
|
|
1264
|
+
|
|
1265
|
+
This Python function assumes that the input 2D ``hyperspy`` signal samples
|
|
1266
|
+
from a mathematical function :math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)`
|
|
1267
|
+
which is piecewise continuous in :math:`u_{x}` and :math:`u_{y}`, where
|
|
1268
|
+
:math:`u_{x}` and :math:`u_{y}` are the horizontal and vertical coordinates
|
|
1269
|
+
in the signal space of the input signal, and :math:`\mathbf{m}` is a vector
|
|
1270
|
+
of integers representing the navigation indices of the input signal. The
|
|
1271
|
+
Python function approximates the azimuthal integral of
|
|
1272
|
+
:math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` given the input signal. We
|
|
1273
|
+
define the azimuthal integral of
|
|
1274
|
+
:math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` as
|
|
1275
|
+
|
|
1276
|
+
.. math ::
|
|
1277
|
+
&S_{\mathbf{m}}\left(U_{r}=
|
|
1278
|
+
u_{r}\left|0\le U_{\phi}<2\pi;c_{x},c_{y}\right.\right)
|
|
1279
|
+
\\&\quad=\int_{0}^{2\pi}du_{\phi}\,u_{r}
|
|
1280
|
+
F_{\mathbf{m}}\left(c_{x}+u_{r}\cos\left(u_{\phi}\right),
|
|
1281
|
+
c_{y}+u_{r}\sin\left(u_{\phi}\right)\right),
|
|
1282
|
+
:label: azimuthal_integral__2
|
|
1283
|
+
|
|
1284
|
+
where :math:`\left(c_{x},c_{y}\right)` is the reference point from which the
|
|
1285
|
+
radial distance :math:`u_r` is defined for the azimuthal integration.
|
|
1286
|
+
|
|
1287
|
+
Parameters
|
|
1288
|
+
----------
|
|
1289
|
+
input_signal : :class:`hyperspy._signals.signal2d.Signal2D` | :class:`hyperspy._signals.complex_signal2d.ComplexSignal2D`
|
|
1290
|
+
The input ``hyperspy`` signal.
|
|
1291
|
+
optional_params : :class:`empix.OptionalAzimuthalIntegrationParams` | `None`, optional
|
|
1292
|
+
The set of optional parameters. See the documentation for the class
|
|
1293
|
+
:class:`empix.OptionalAzimuthalIntegrationParams` for details. If
|
|
1294
|
+
``optional_params`` is set to ``None``, rather than an instance of
|
|
1295
|
+
:class:`empix.OptionalAzimuthalIntegrationParams`, then the default
|
|
1296
|
+
values of the optional parameters are chosen.
|
|
1297
|
+
|
|
1298
|
+
Returns
|
|
1299
|
+
-------
|
|
1300
|
+
output_signal : :class:`hyperspy._signals.signal1d.Signal1D` | :class:`hyperspy._signals.complex_signal1d.ComplexSignal1D`
|
|
1301
|
+
The output ``hyperspy`` signal that samples the azimuthal integral of
|
|
1302
|
+
the input signal ``input_signal``. Note that the metadata of the input
|
|
1303
|
+
signal is copied over to the output signal, with the title being
|
|
1304
|
+
overwritten.
|
|
1305
|
+
|
|
1306
|
+
"""
|
|
1307
|
+
params = locals()
|
|
1308
|
+
params["action_to_apply_to_input_signal"] = inspect.stack()[0][3]
|
|
1309
|
+
for param_name in params:
|
|
1310
|
+
func_name = "_check_and_convert_" + param_name
|
|
1311
|
+
func_alias = globals()[func_name]
|
|
1312
|
+
params[param_name] = func_alias(params)
|
|
1313
|
+
|
|
1314
|
+
func_name = "_" + inspect.stack()[0][3]
|
|
1315
|
+
func_alias = globals()[func_name]
|
|
1316
|
+
kwargs = params
|
|
1317
|
+
del kwargs["action_to_apply_to_input_signal"]
|
|
1318
|
+
output_signal = func_alias(**kwargs)
|
|
1319
|
+
|
|
1320
|
+
return output_signal
|
|
1321
|
+
|
|
1322
|
+
|
|
1323
|
+
|
|
1324
|
+
def _azimuthally_integrate(input_signal, optional_params):
|
|
1325
|
+
optional_params_core_attrs = optional_params.get_core_attrs(deep_copy=False)
|
|
1326
|
+
|
|
1327
|
+
kwargs = {"skip_validation_and_conversion": True,
|
|
1328
|
+
**optional_params_core_attrs}
|
|
1329
|
+
optional_params = OptionalAzimuthalIntegrationParams(**kwargs)
|
|
1330
|
+
|
|
1331
|
+
output_signal = _azimuthally_average(input_signal, optional_params)
|
|
1332
|
+
|
|
1333
|
+
bin_coords = _calc_bin_coords_from_signal(signal=output_signal)
|
|
1334
|
+
navigation_rank = len(output_signal.data.shape) - 1
|
|
1335
|
+
|
|
1336
|
+
for idx, r_xy in enumerate(bin_coords):
|
|
1337
|
+
multi_dim_slice = tuple([slice(None)]*navigation_rank + [idx])
|
|
1338
|
+
output_signal.data[multi_dim_slice] *= 2*np.pi*r_xy
|
|
1339
|
+
|
|
1340
|
+
return output_signal
|
|
1341
|
+
|
|
1342
|
+
|
|
1343
|
+
|
|
1344
|
+
def _calc_bin_coords_from_signal(signal):
|
|
1345
|
+
offset = signal.axes_manager[-1].offset
|
|
1346
|
+
scale = signal.axes_manager[-1].scale
|
|
1347
|
+
size = signal.axes_manager[-1].size
|
|
1348
|
+
bin_coords = np.arange(offset, offset + size*scale - 1e-10, scale)
|
|
1349
|
+
|
|
1350
|
+
return bin_coords
|
|
1351
|
+
|
|
1352
|
+
|
|
1353
|
+
|
|
1354
|
+
_cls_alias = fancytypes.PreSerializableAndUpdatable
|
|
1355
|
+
class OptionalAnnularAveragingParams(_cls_alias):
|
|
1356
|
+
r"""The set of optional parameters for the function
|
|
1357
|
+
:func:`empix.annularly_average`.
|
|
1358
|
+
|
|
1359
|
+
The Python function :func:`empix.annularly_average` averages annularly a
|
|
1360
|
+
given input 2D ``hyperspy`` signal. The Python function assumes that the
|
|
1361
|
+
input 2D ``hyperspy`` signal samples from a mathematical function
|
|
1362
|
+
:math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` which is piecewise continuous
|
|
1363
|
+
in :math:`u_{x}` and :math:`u_{y}`, where :math:`u_{x}` and :math:`u_{y}`
|
|
1364
|
+
are the horizontal and vertical coordinates in the signal space of the input
|
|
1365
|
+
signal, and :math:`\mathbf{m}` is a vector of integers representing the
|
|
1366
|
+
navigation indices of the input signal. The Python function approximates the
|
|
1367
|
+
azimuthal average of :math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` given
|
|
1368
|
+
the input signal. We define the annular average of
|
|
1369
|
+
:math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` as
|
|
1370
|
+
|
|
1371
|
+
.. math ::
|
|
1372
|
+
&\overline{S}_{\mathbf{m}}\left(u_{r,i}\le U_{r}<u_{r,f}
|
|
1373
|
+
\left|0\le U_{\phi}<2\pi;c_{x},c_{y}\right.\right)
|
|
1374
|
+
\\&\quad=\frac{1}{\pi\left(u_{r,f}^{2}-u_{r,i}^{2}\right)}
|
|
1375
|
+
\int_{u_{r,i}}^{u_{r,f}}du_{r}\,\int_{0}^{2\pi}du_{\phi}\,u_{r}
|
|
1376
|
+
F_{\mathbf{m}}\left(c_{x}+u_{r}\cos\left(u_{\phi}\right),
|
|
1377
|
+
c_{y}+u_{r}\sin\left(u_{\phi}\right)\right),
|
|
1378
|
+
:label: annular_average__1
|
|
1379
|
+
|
|
1380
|
+
where :math:`\left(c_{x},c_{y}\right)` is the reference point from which the
|
|
1381
|
+
radial distance :math:`u_r` is defined for the annular averaging.
|
|
1382
|
+
|
|
1383
|
+
Parameters
|
|
1384
|
+
----------
|
|
1385
|
+
center : `array_like` (`float`, shape=(2,)) | `None`, optional
|
|
1386
|
+
If ``center`` is set to ``None``, then the reference point
|
|
1387
|
+
:math:`\left(c_{x},c_{y}\right)`, from which the radial distance is
|
|
1388
|
+
defined for the annular averaging, is set to the signal space
|
|
1389
|
+
coordinates corresponding to the center signal space pixel. Otherwise,
|
|
1390
|
+
if ``center`` is set to a pair of floating-point numbers, then
|
|
1391
|
+
``center[0]`` and ``center[1]`` specify :math:`c_{x}` and :math:`c_{y}`
|
|
1392
|
+
respectively, in the same units of the corresponding signal space axes
|
|
1393
|
+
of the input signal.
|
|
1394
|
+
radial_range : `array_like` (`float`, shape=(2,)) | `None`, optional
|
|
1395
|
+
If ``radial_range`` is set to ``None``, then the radial range, over
|
|
1396
|
+
which the annular averaging is performed, is from zero to the largest
|
|
1397
|
+
radial distance within the signal space boundaries of the input signal
|
|
1398
|
+
for all azimuthal angles. Otherwise, if ``radial_range`` is set to a
|
|
1399
|
+
pair of floating-point numbers, then ``radial_range[0]`` and
|
|
1400
|
+
``radial_range[1]`` specify the minimum and maximum radial distances of
|
|
1401
|
+
the radial range respectively, in the same units of the horizontal and
|
|
1402
|
+
vertical axes respectively of the signal space of the input signal. Note
|
|
1403
|
+
that in this case ``radial_range`` must satisfy
|
|
1404
|
+
``0<=radial_range[0]<radial_range[1]``. Moreover, the function
|
|
1405
|
+
represented by the input signal is assumed to be equal to zero
|
|
1406
|
+
everywhere outside of the signal space boundaries of said input signal.
|
|
1407
|
+
title : `str` | `None`, optional
|
|
1408
|
+
If ``title`` is set to ``None``, then the title of the output signal
|
|
1409
|
+
``output_signal`` is set to ``"Annularly Averaged " +
|
|
1410
|
+
input_signal.metadata.General.title``, where ``input_signal`` is the
|
|
1411
|
+
input ``hyperspy`` signal. Otherwise, if ``title`` is a `str`, then the
|
|
1412
|
+
``output_signal.metadata.General.title`` is set to the value of
|
|
1413
|
+
``title``.
|
|
1414
|
+
skip_validation_and_conversion : `bool`, optional
|
|
1415
|
+
Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
|
|
1416
|
+
attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
|
|
1417
|
+
and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
|
|
1418
|
+
being `dict` objects.
|
|
1419
|
+
|
|
1420
|
+
Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
|
|
1421
|
+
representation of the constructor parameters excluding the parameter
|
|
1422
|
+
``skip_validation_and_conversion``, where each `dict` key ``key`` is a
|
|
1423
|
+
different constructor parameter name, excluding the name
|
|
1424
|
+
``"skip_validation_and_conversion"``, and
|
|
1425
|
+
``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
|
|
1426
|
+
constructor parameter with the name given by ``key``.
|
|
1427
|
+
|
|
1428
|
+
If ``skip_validation_and_conversion`` is set to ``False``, then for each
|
|
1429
|
+
key ``key`` in ``params_to_be_mapped_to_core_attrs``,
|
|
1430
|
+
``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
|
|
1431
|
+
(params_to_be_mapped_to_core_attrs)``.
|
|
1432
|
+
|
|
1433
|
+
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
|
|
1434
|
+
then ``core_attrs`` is set to
|
|
1435
|
+
``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
|
|
1436
|
+
primarily when the user wants to avoid potentially expensive deep copies
|
|
1437
|
+
and/or conversions of the `dict` values of
|
|
1438
|
+
``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
|
|
1439
|
+
copies or conversions are made in this case.
|
|
1440
|
+
|
|
1441
|
+
"""
|
|
1442
|
+
ctor_param_names = ("center", "radial_range", "title")
|
|
1443
|
+
kwargs = {"namespace_as_dict": globals(),
|
|
1444
|
+
"ctor_param_names": ctor_param_names}
|
|
1445
|
+
|
|
1446
|
+
_validation_and_conversion_funcs_ = \
|
|
1447
|
+
fancytypes.return_validation_and_conversion_funcs(**kwargs)
|
|
1448
|
+
_pre_serialization_funcs_ = \
|
|
1449
|
+
fancytypes.return_pre_serialization_funcs(**kwargs)
|
|
1450
|
+
_de_pre_serialization_funcs_ = \
|
|
1451
|
+
fancytypes.return_de_pre_serialization_funcs(**kwargs)
|
|
1452
|
+
|
|
1453
|
+
del ctor_param_names, kwargs
|
|
1454
|
+
|
|
1455
|
+
|
|
1456
|
+
|
|
1457
|
+
def __init__(self,
|
|
1458
|
+
center=\
|
|
1459
|
+
_default_center,
|
|
1460
|
+
radial_range=\
|
|
1461
|
+
_default_radial_range,
|
|
1462
|
+
title=\
|
|
1463
|
+
_default_title,
|
|
1464
|
+
skip_validation_and_conversion=\
|
|
1465
|
+
_default_skip_validation_and_conversion):
|
|
1466
|
+
ctor_params = {key: val
|
|
1467
|
+
for key, val in locals().items()
|
|
1468
|
+
if (key not in ("self", "__class__"))}
|
|
1469
|
+
kwargs = ctor_params
|
|
1470
|
+
kwargs["skip_cls_tests"] = True
|
|
1471
|
+
fancytypes.PreSerializableAndUpdatable.__init__(self, **kwargs)
|
|
1472
|
+
|
|
1473
|
+
return None
|
|
1474
|
+
|
|
1475
|
+
|
|
1476
|
+
|
|
1477
|
+
@classmethod
|
|
1478
|
+
def get_validation_and_conversion_funcs(cls):
|
|
1479
|
+
validation_and_conversion_funcs = \
|
|
1480
|
+
cls._validation_and_conversion_funcs_.copy()
|
|
1481
|
+
|
|
1482
|
+
return validation_and_conversion_funcs
|
|
1483
|
+
|
|
1484
|
+
|
|
1485
|
+
|
|
1486
|
+
@classmethod
|
|
1487
|
+
def get_pre_serialization_funcs(cls):
|
|
1488
|
+
pre_serialization_funcs = \
|
|
1489
|
+
cls._pre_serialization_funcs_.copy()
|
|
1490
|
+
|
|
1491
|
+
return pre_serialization_funcs
|
|
1492
|
+
|
|
1493
|
+
|
|
1494
|
+
|
|
1495
|
+
@classmethod
|
|
1496
|
+
def get_de_pre_serialization_funcs(cls):
|
|
1497
|
+
de_pre_serialization_funcs = \
|
|
1498
|
+
cls._de_pre_serialization_funcs_.copy()
|
|
1499
|
+
|
|
1500
|
+
return de_pre_serialization_funcs
|
|
1501
|
+
|
|
1502
|
+
|
|
1503
|
+
|
|
1504
|
+
def annularly_average(input_signal, optional_params=_default_optional_params):
|
|
1505
|
+
r"""Average annularly a given input 2D ``hyperspy`` signal.
|
|
1506
|
+
|
|
1507
|
+
This Python function assumes that the input 2D ``hyperspy`` signal samples
|
|
1508
|
+
from a mathematical function :math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)`
|
|
1509
|
+
which is piecewise continuous in :math:`u_{x}` and :math:`u_{y}`, where
|
|
1510
|
+
:math:`u_{x}` and :math:`u_{y}` are the horizontal and vertical coordinates
|
|
1511
|
+
in the signal space of the input signal, and :math:`\mathbf{m}` is a vector
|
|
1512
|
+
of integers representing the navigation indices of the input signal. The
|
|
1513
|
+
Python function approximates the annular average of
|
|
1514
|
+
:math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` given the input signal. We
|
|
1515
|
+
define the annular average of :math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)`
|
|
1516
|
+
as
|
|
1517
|
+
|
|
1518
|
+
.. math ::
|
|
1519
|
+
&\overline{S}_{\mathbf{m}}\left(u_{r,i}\le U_{r}<u_{r,f}
|
|
1520
|
+
\left|0\le U_{\phi}<2\pi;c_{x},c_{y}\right.\right)
|
|
1521
|
+
\\&\quad=\frac{1}{\pi\left(u_{r,f}^{2}-u_{r,i}^{2}\right)}
|
|
1522
|
+
\int_{u_{r,i}}^{u_{r,f}}du_{r}\,\int_{0}^{2\pi}du_{\phi}\,u_{r}
|
|
1523
|
+
F_{\mathbf{m}}\left(c_{x}+u_{r}\cos\left(u_{\phi}\right),
|
|
1524
|
+
c_{y}+u_{r}\sin\left(u_{\phi}\right)\right),
|
|
1525
|
+
:label: annular_average__2
|
|
1526
|
+
|
|
1527
|
+
where :math:`\left(c_{x},c_{y}\right)` is the reference point from which the
|
|
1528
|
+
radial distance :math:`u_r` is defined for the annular averaging.
|
|
1529
|
+
|
|
1530
|
+
Parameters
|
|
1531
|
+
----------
|
|
1532
|
+
input_signal : :class:`hyperspy._signals.signal2d.Signal2D` | :class:`hyperspy._signals.complex_signal2d.ComplexSignal2D`
|
|
1533
|
+
The input ``hyperspy`` signal.
|
|
1534
|
+
optional_params : :class:`empix.OptionalAnnularAveragingParams` | `None`, optional
|
|
1535
|
+
The set of optional parameters. See the documentation for the class
|
|
1536
|
+
:class:`empix.OptionalAnnularAveragingParams` for details. If
|
|
1537
|
+
``optional_params`` is set to ``None``, rather than an instance of
|
|
1538
|
+
:class:`empix.OptionalAnnularAveragingParams`, then the default values
|
|
1539
|
+
of the optional parameters are chosen.
|
|
1540
|
+
|
|
1541
|
+
Returns
|
|
1542
|
+
-------
|
|
1543
|
+
output_signal : :class:`hyperspy.signal.BaseSignal` | :class:`hyperspy._signals.complex_signal.ComplexSignal`
|
|
1544
|
+
The output ``hyperspy`` signal that samples the annular average of the
|
|
1545
|
+
input signal ``input_signal``. Note that the metadata of the input
|
|
1546
|
+
signal is copied over to the output signal, with the title being
|
|
1547
|
+
overwritten.
|
|
1548
|
+
|
|
1549
|
+
"""
|
|
1550
|
+
params = locals()
|
|
1551
|
+
params["action_to_apply_to_input_signal"] = inspect.stack()[0][3]
|
|
1552
|
+
for param_name in params:
|
|
1553
|
+
func_name = "_check_and_convert_" + param_name
|
|
1554
|
+
func_alias = globals()[func_name]
|
|
1555
|
+
params[param_name] = func_alias(params)
|
|
1556
|
+
|
|
1557
|
+
func_name = "_" + inspect.stack()[0][3]
|
|
1558
|
+
func_alias = globals()[func_name]
|
|
1559
|
+
kwargs = params
|
|
1560
|
+
del kwargs["action_to_apply_to_input_signal"]
|
|
1561
|
+
output_signal = func_alias(**kwargs)
|
|
1562
|
+
|
|
1563
|
+
return output_signal
|
|
1564
|
+
|
|
1565
|
+
|
|
1566
|
+
|
|
1567
|
+
def _annularly_average(input_signal, optional_params):
|
|
1568
|
+
optional_params_core_attrs = optional_params.get_core_attrs(deep_copy=False)
|
|
1569
|
+
optional_params_core_attrs["num_bins"] = 2*min(input_signal.data.shape[-2:])
|
|
1570
|
+
|
|
1571
|
+
kwargs = {"skip_validation_and_conversion": True,
|
|
1572
|
+
**optional_params_core_attrs}
|
|
1573
|
+
optional_params = OptionalAzimuthalIntegrationParams(**kwargs)
|
|
1574
|
+
|
|
1575
|
+
output_signal = _azimuthally_integrate(input_signal, optional_params)
|
|
1576
|
+
|
|
1577
|
+
r_xy_i, r_xy_f = optional_params_core_attrs["radial_range"]
|
|
1578
|
+
area_of_annulus = np.pi*(r_xy_f**2 - r_xy_i**2)
|
|
1579
|
+
|
|
1580
|
+
r_xy_scale = output_signal.axes_manager[-1].scale
|
|
1581
|
+
|
|
1582
|
+
output_signal = output_signal.sum(axis=-1)
|
|
1583
|
+
output_signal.data *= (r_xy_scale/area_of_annulus)
|
|
1584
|
+
|
|
1585
|
+
return output_signal
|
|
1586
|
+
|
|
1587
|
+
|
|
1588
|
+
|
|
1589
|
+
_cls_alias = fancytypes.PreSerializableAndUpdatable
|
|
1590
|
+
class OptionalAnnularIntegrationParams(_cls_alias):
|
|
1591
|
+
r"""The set of optional parameters for the function
|
|
1592
|
+
:func:`empix.annularly_integrate`.
|
|
1593
|
+
|
|
1594
|
+
The Python function :func:`empix.annularly_integrate` integrates annularly a
|
|
1595
|
+
given input 2D ``hyperspy`` signal. The Python function assumes that the
|
|
1596
|
+
input 2D ``hyperspy`` signal samples from a mathematical function
|
|
1597
|
+
:math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` which is piecewise continuous
|
|
1598
|
+
in :math:`u_{x}` and :math:`u_{y}`, where :math:`u_{x}` and :math:`u_{y}`
|
|
1599
|
+
are the horizontal and vertical coordinates in the signal space of the input
|
|
1600
|
+
signal, and :math:`\mathbf{m}` is a vector of integers representing the
|
|
1601
|
+
navigation indices of the input signal. The Python function approximates the
|
|
1602
|
+
azimuthal average of :math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` given
|
|
1603
|
+
the input signal. We define the annular average of
|
|
1604
|
+
:math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` as
|
|
1605
|
+
|
|
1606
|
+
.. math ::
|
|
1607
|
+
&S_{\mathbf{m}}\left(u_{r,i}\le U_{r}<u_{r,f}
|
|
1608
|
+
\left|0\le U_{\phi}<2\pi;c_{x},c_{y}\right.\right)
|
|
1609
|
+
\\&\quad=\int_{u_{r,i}}^{u_{r,f}}du_{r}\,\int_{0}^{2\pi}
|
|
1610
|
+
du_{\phi}\,u_{r}
|
|
1611
|
+
F_{\mathbf{m}}\left(c_{x}+u_{r}\cos\left(u_{\phi}\right),
|
|
1612
|
+
c_{y}+u_{r}\sin\left(u_{\phi}\right)\right),
|
|
1613
|
+
:label: annular_integral__1
|
|
1614
|
+
|
|
1615
|
+
where :math:`\left(c_{x},c_{y}\right)` is the reference point from which the
|
|
1616
|
+
radial distance :math:`u_r` is defined for the annular integration.
|
|
1617
|
+
|
|
1618
|
+
Parameters
|
|
1619
|
+
----------
|
|
1620
|
+
center : `array_like` (`float`, shape=(2,)) | `None`, optional
|
|
1621
|
+
If ``center`` is set to ``None``, then the reference point
|
|
1622
|
+
:math:`\left(c_{x},c_{y}\right)`, from which the radial distance is
|
|
1623
|
+
defined for the annular integration, is set to the signal space
|
|
1624
|
+
coordinates corresponding to the center signal space pixel. Otherwise,
|
|
1625
|
+
if ``center`` is set to a pair of floating-point numbers, then
|
|
1626
|
+
``center[0]`` and ``center[1]`` specify :math:`c_{x}` and :math:`c_{y}`
|
|
1627
|
+
respectively, in the same units of the corresponding signal space axes
|
|
1628
|
+
of the input signal.
|
|
1629
|
+
radial_range : `array_like` (`float`, shape=(2,)) | `None`, optional
|
|
1630
|
+
If ``radial_range`` is set to ``None``, then the radial range, over
|
|
1631
|
+
which the annular integration is performed, is from zero to the largest
|
|
1632
|
+
radial distance within the signal space boundaries of the input signal
|
|
1633
|
+
for all azimuthal angles. Otherwise, if ``radial_range`` is set to a
|
|
1634
|
+
pair of floating-point numbers, then ``radial_range[0]`` and
|
|
1635
|
+
``radial_range[1]`` specify the minimum and maximum radial distances of
|
|
1636
|
+
the radial range respectively, in the same units of the horizontal and
|
|
1637
|
+
vertical axes respectively of the signal space of the input signal. Note
|
|
1638
|
+
that in this case ``radial_range`` must satisfy
|
|
1639
|
+
``0<=radial_range[0]<radial_range[1]``. Moreover, the function
|
|
1640
|
+
represented by the input signal is assumed to be equal to zero
|
|
1641
|
+
everywhere outside of the signal space boundaries of said input signal.
|
|
1642
|
+
title : `str` | `None`, optional
|
|
1643
|
+
If ``title`` is set to ``None``, then the title of the output signal
|
|
1644
|
+
``output_signal`` is set to ``"Annularly Integrated " +
|
|
1645
|
+
input_signal.metadata.General.title``, where ``input_signal`` is the
|
|
1646
|
+
input ``hyperspy`` signal. Otherwise, if ``title`` is a `str`, then the
|
|
1647
|
+
``output_signal.metadata.General.title`` is set to the value of
|
|
1648
|
+
``title``.
|
|
1649
|
+
skip_validation_and_conversion : `bool`, optional
|
|
1650
|
+
Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
|
|
1651
|
+
attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
|
|
1652
|
+
and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
|
|
1653
|
+
being `dict` objects.
|
|
1654
|
+
|
|
1655
|
+
Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
|
|
1656
|
+
representation of the constructor parameters excluding the parameter
|
|
1657
|
+
``skip_validation_and_conversion``, where each `dict` key ``key`` is a
|
|
1658
|
+
different constructor parameter name, excluding the name
|
|
1659
|
+
``"skip_validation_and_conversion"``, and
|
|
1660
|
+
``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
|
|
1661
|
+
constructor parameter with the name given by ``key``.
|
|
1662
|
+
|
|
1663
|
+
If ``skip_validation_and_conversion`` is set to ``False``, then for each
|
|
1664
|
+
key ``key`` in ``params_to_be_mapped_to_core_attrs``,
|
|
1665
|
+
``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
|
|
1666
|
+
(params_to_be_mapped_to_core_attrs)``.
|
|
1667
|
+
|
|
1668
|
+
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
|
|
1669
|
+
then ``core_attrs`` is set to
|
|
1670
|
+
``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
|
|
1671
|
+
primarily when the user wants to avoid potentially expensive deep copies
|
|
1672
|
+
and/or conversions of the `dict` values of
|
|
1673
|
+
``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
|
|
1674
|
+
copies or conversions are made in this case.
|
|
1675
|
+
|
|
1676
|
+
"""
|
|
1677
|
+
ctor_param_names = ("center", "radial_range", "title")
|
|
1678
|
+
kwargs = {"namespace_as_dict": globals(),
|
|
1679
|
+
"ctor_param_names": ctor_param_names}
|
|
1680
|
+
|
|
1681
|
+
_validation_and_conversion_funcs_ = \
|
|
1682
|
+
fancytypes.return_validation_and_conversion_funcs(**kwargs)
|
|
1683
|
+
_pre_serialization_funcs_ = \
|
|
1684
|
+
fancytypes.return_pre_serialization_funcs(**kwargs)
|
|
1685
|
+
_de_pre_serialization_funcs_ = \
|
|
1686
|
+
fancytypes.return_de_pre_serialization_funcs(**kwargs)
|
|
1687
|
+
|
|
1688
|
+
del ctor_param_names, kwargs
|
|
1689
|
+
|
|
1690
|
+
|
|
1691
|
+
|
|
1692
|
+
def __init__(self,
|
|
1693
|
+
center=\
|
|
1694
|
+
_default_center,
|
|
1695
|
+
radial_range=\
|
|
1696
|
+
_default_radial_range,
|
|
1697
|
+
title=\
|
|
1698
|
+
_default_title,
|
|
1699
|
+
skip_validation_and_conversion=\
|
|
1700
|
+
_default_skip_validation_and_conversion):
|
|
1701
|
+
ctor_params = {key: val
|
|
1702
|
+
for key, val in locals().items()
|
|
1703
|
+
if (key not in ("self", "__class__"))}
|
|
1704
|
+
kwargs = ctor_params
|
|
1705
|
+
kwargs["skip_cls_tests"] = True
|
|
1706
|
+
fancytypes.PreSerializableAndUpdatable.__init__(self, **kwargs)
|
|
1707
|
+
|
|
1708
|
+
return None
|
|
1709
|
+
|
|
1710
|
+
|
|
1711
|
+
|
|
1712
|
+
@classmethod
|
|
1713
|
+
def get_validation_and_conversion_funcs(cls):
|
|
1714
|
+
validation_and_conversion_funcs = \
|
|
1715
|
+
cls._validation_and_conversion_funcs_.copy()
|
|
1716
|
+
|
|
1717
|
+
return validation_and_conversion_funcs
|
|
1718
|
+
|
|
1719
|
+
|
|
1720
|
+
|
|
1721
|
+
@classmethod
|
|
1722
|
+
def get_pre_serialization_funcs(cls):
|
|
1723
|
+
pre_serialization_funcs = \
|
|
1724
|
+
cls._pre_serialization_funcs_.copy()
|
|
1725
|
+
|
|
1726
|
+
return pre_serialization_funcs
|
|
1727
|
+
|
|
1728
|
+
|
|
1729
|
+
|
|
1730
|
+
@classmethod
|
|
1731
|
+
def get_de_pre_serialization_funcs(cls):
|
|
1732
|
+
de_pre_serialization_funcs = \
|
|
1733
|
+
cls._de_pre_serialization_funcs_.copy()
|
|
1734
|
+
|
|
1735
|
+
return de_pre_serialization_funcs
|
|
1736
|
+
|
|
1737
|
+
|
|
1738
|
+
|
|
1739
|
+
def annularly_integrate(input_signal, optional_params=_default_optional_params):
|
|
1740
|
+
r"""Integrate annularly a given input 2D ``hyperspy`` signal.
|
|
1741
|
+
|
|
1742
|
+
This Python function assumes that the input 2D ``hyperspy`` signal samples
|
|
1743
|
+
from a mathematical function :math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)`
|
|
1744
|
+
which is piecewise continuous in :math:`u_{x}` and :math:`u_{y}`, where
|
|
1745
|
+
:math:`u_{x}` and :math:`u_{y}` are the horizontal and vertical coordinates
|
|
1746
|
+
in the signal space of the input signal, and :math:`\mathbf{m}` is a vector
|
|
1747
|
+
of integers representing the navigation indices of the input signal. The
|
|
1748
|
+
Python function approximates the annular integral of
|
|
1749
|
+
:math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` given the input signal. We
|
|
1750
|
+
define the annular integral of
|
|
1751
|
+
:math:`F_{\mathbf{m}}\left(u_{x},u_{y}\right)` as
|
|
1752
|
+
|
|
1753
|
+
.. math ::
|
|
1754
|
+
&S_{\mathbf{m}}\left(u_{r,i}\le U_{r}<u_{r,f}
|
|
1755
|
+
\left|0\le U_{\phi}<2\pi;c_{x},c_{y}\right.\right)
|
|
1756
|
+
\\&\quad=\int_{u_{r,i}}^{u_{r,f}}du_{r}\,\int_{0}^{2\pi}
|
|
1757
|
+
du_{\phi}\,u_{r}
|
|
1758
|
+
F_{\mathbf{m}}\left(c_{x}+u_{r}\cos\left(u_{\phi}\right),
|
|
1759
|
+
c_{y}+u_{r}\sin\left(u_{\phi}\right)\right),
|
|
1760
|
+
:label: annular_integral__2
|
|
1761
|
+
|
|
1762
|
+
where :math:`\left(c_{x},c_{y}\right)` is the reference point from which the
|
|
1763
|
+
radial distance :math:`` is defined for the annular integration.
|
|
1764
|
+
|
|
1765
|
+
Parameters
|
|
1766
|
+
----------
|
|
1767
|
+
input_signal : :class:`hyperspy._signals.signal2d.Signal2D` | :class:`hyperspy._signals.complex_signal2d.ComplexSignal2D`
|
|
1768
|
+
The input ``hyperspy`` signal.
|
|
1769
|
+
optional_params : :class:`empix.OptionalAnnularIntegrationParams` | `None`, optional
|
|
1770
|
+
The set of optional parameters. See the documentation for the class
|
|
1771
|
+
:class:`empix.OptionalAnnularIntegrationParams` for details. If
|
|
1772
|
+
``optional_params`` is set to ``None``, rather than an instance of
|
|
1773
|
+
:class:`empix.OptionalAnnularIntegrationParams`, then the default values
|
|
1774
|
+
of the optional parameters are chosen.
|
|
1775
|
+
|
|
1776
|
+
Returns
|
|
1777
|
+
-------
|
|
1778
|
+
output_signal : :class:`hyperspy.signal.BaseSignal` | :class:`hyperspy._signals.complex_signal.ComplexSignal`
|
|
1779
|
+
The output ``hyperspy`` signal that samples the annular integral of the
|
|
1780
|
+
input signal ``input_signal``. Note that the metadata of the input
|
|
1781
|
+
signal is copied over to the output signal, with the title being
|
|
1782
|
+
overwritten.
|
|
1783
|
+
|
|
1784
|
+
"""
|
|
1785
|
+
params = locals()
|
|
1786
|
+
params["action_to_apply_to_input_signal"] = inspect.stack()[0][3]
|
|
1787
|
+
for param_name in params:
|
|
1788
|
+
func_name = "_check_and_convert_" + param_name
|
|
1789
|
+
func_alias = globals()[func_name]
|
|
1790
|
+
params[param_name] = func_alias(params)
|
|
1791
|
+
|
|
1792
|
+
func_name = "_" + inspect.stack()[0][3]
|
|
1793
|
+
func_alias = globals()[func_name]
|
|
1794
|
+
kwargs = params
|
|
1795
|
+
del kwargs["action_to_apply_to_input_signal"]
|
|
1796
|
+
output_signal = func_alias(**kwargs)
|
|
1797
|
+
|
|
1798
|
+
return output_signal
|
|
1799
|
+
|
|
1800
|
+
|
|
1801
|
+
|
|
1802
|
+
def _annularly_integrate(input_signal, optional_params):
|
|
1803
|
+
optional_params_core_attrs = optional_params.get_core_attrs(deep_copy=False)
|
|
1804
|
+
optional_params_core_attrs["num_bins"] = 2*min(input_signal.data.shape[-2:])
|
|
1805
|
+
|
|
1806
|
+
kwargs = {"skip_validation_and_conversion": True,
|
|
1807
|
+
**optional_params_core_attrs}
|
|
1808
|
+
optional_params = OptionalAzimuthalIntegrationParams(**kwargs)
|
|
1809
|
+
|
|
1810
|
+
output_signal = _azimuthally_integrate(input_signal, optional_params)
|
|
1811
|
+
|
|
1812
|
+
r_xy_scale = output_signal.axes_manager[-1].scale
|
|
1813
|
+
|
|
1814
|
+
output_signal = output_signal.sum(axis=-1)
|
|
1815
|
+
output_signal.data *= r_xy_scale
|
|
1816
|
+
|
|
1817
|
+
return output_signal
|
|
1818
|
+
|
|
1819
|
+
|
|
1820
|
+
|
|
1821
|
+
def _check_and_convert_limits(params):
|
|
1822
|
+
current_func_name = inspect.stack()[0][3]
|
|
1823
|
+
char_idx = 19
|
|
1824
|
+
obj_name = current_func_name[char_idx:]
|
|
1825
|
+
obj = params[obj_name]
|
|
1826
|
+
|
|
1827
|
+
param_name = "input_signal"
|
|
1828
|
+
input_signal = params.get(param_name, None)
|
|
1829
|
+
|
|
1830
|
+
if obj is not None:
|
|
1831
|
+
try:
|
|
1832
|
+
kwargs = {"obj": obj, "obj_name": obj_name}
|
|
1833
|
+
limits = czekitout.convert.to_pair_of_floats(**kwargs)
|
|
1834
|
+
except:
|
|
1835
|
+
err_msg = globals()[current_func_name+"_err_msg_1"]
|
|
1836
|
+
raise TypeError(err_msg)
|
|
1837
|
+
|
|
1838
|
+
if limits[0] == limits[1]:
|
|
1839
|
+
err_msg = globals()[current_func_name+"_err_msg_1"]
|
|
1840
|
+
raise ValueError(err_msg)
|
|
1841
|
+
else:
|
|
1842
|
+
if input_signal is not None:
|
|
1843
|
+
u_coords = _calc_u_coords_1d(signal=input_signal)
|
|
1844
|
+
u_i = np.amin(u_coords)
|
|
1845
|
+
u_f = np.amax(u_coords)
|
|
1846
|
+
limits = (u_i, u_f)
|
|
1847
|
+
else:
|
|
1848
|
+
limits = obj
|
|
1849
|
+
|
|
1850
|
+
return limits
|
|
1851
|
+
|
|
1852
|
+
|
|
1853
|
+
|
|
1854
|
+
def _calc_u_coords_1d(signal):
|
|
1855
|
+
offset = signal.axes_manager[-1].offset
|
|
1856
|
+
scale = signal.axes_manager[-1].scale
|
|
1857
|
+
size = signal.axes_manager[-1].size
|
|
1858
|
+
u_coords = offset + scale*np.arange(size)
|
|
1859
|
+
|
|
1860
|
+
return u_coords
|
|
1861
|
+
|
|
1862
|
+
|
|
1863
|
+
|
|
1864
|
+
def _pre_serialize_limits(limits):
|
|
1865
|
+
obj_to_pre_serialize = random.choice(list(locals().values()))
|
|
1866
|
+
serializable_rep = obj_to_pre_serialize
|
|
1867
|
+
|
|
1868
|
+
return serializable_rep
|
|
1869
|
+
|
|
1870
|
+
|
|
1871
|
+
|
|
1872
|
+
def _de_pre_serialize_limits(serializable_rep):
|
|
1873
|
+
limits = serializable_rep
|
|
1874
|
+
|
|
1875
|
+
return limits
|
|
1876
|
+
|
|
1877
|
+
|
|
1878
|
+
|
|
1879
|
+
def _check_and_convert_normalize(params):
|
|
1880
|
+
current_func_name = inspect.stack()[0][3]
|
|
1881
|
+
char_idx = 19
|
|
1882
|
+
obj_name = current_func_name[char_idx:]
|
|
1883
|
+
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
|
|
1884
|
+
normalize = czekitout.convert.to_bool(**kwargs)
|
|
1885
|
+
|
|
1886
|
+
return normalize
|
|
1887
|
+
|
|
1888
|
+
|
|
1889
|
+
|
|
1890
|
+
def _pre_serialize_normalize(normalize):
|
|
1891
|
+
obj_to_pre_serialize = random.choice(list(locals().values()))
|
|
1892
|
+
serializable_rep = obj_to_pre_serialize
|
|
1893
|
+
|
|
1894
|
+
return serializable_rep
|
|
1895
|
+
|
|
1896
|
+
|
|
1897
|
+
|
|
1898
|
+
def _de_pre_serialize_normalize(serializable_rep):
|
|
1899
|
+
normalize = serializable_rep
|
|
1900
|
+
|
|
1901
|
+
return normalize
|
|
1902
|
+
|
|
1903
|
+
|
|
1904
|
+
|
|
1905
|
+
_default_limits = None
|
|
1906
|
+
_default_normalize = False
|
|
1907
|
+
|
|
1908
|
+
|
|
1909
|
+
|
|
1910
|
+
_cls_alias = fancytypes.PreSerializableAndUpdatable
|
|
1911
|
+
class OptionalCumulative1dIntegrationParams(_cls_alias):
|
|
1912
|
+
r"""The set of optional parameters for the function
|
|
1913
|
+
:func:`empix.cumulatively_integrate_1d`.
|
|
1914
|
+
|
|
1915
|
+
The Python function :func:`empix.cumulatively_integrate_1d` integrates
|
|
1916
|
+
cumulatively a given input 1D ``hyperspy`` signal. The Python function
|
|
1917
|
+
assumes that the input 1D ``hyperspy`` signal samples from a mathematical
|
|
1918
|
+
function :math:`F_{\mathbf{m}}\left(u\right)` which is piecewise continuous
|
|
1919
|
+
in :math:`u`, where :math:`u` is the signal space coordinate of the input
|
|
1920
|
+
signal, and :math:`\mathbf{m}` is a vector of integers representing the
|
|
1921
|
+
navigation indices of the input signal. The Python function approximates the
|
|
1922
|
+
cumulative integral of :math:`F_{\mathbf{m}}\left(u\right)` given the input
|
|
1923
|
+
signal. We define the cumulative integral of
|
|
1924
|
+
:math:`F_{\mathbf{m}}\left(u\right)` as
|
|
1925
|
+
|
|
1926
|
+
.. math ::
|
|
1927
|
+
\text{CDF}_{\text{1D}}\left(u\right)&=\frac{1}{\Gamma}
|
|
1928
|
+
\int_{u_{i}}^{u}du^{\prime}\,F_{\mathbf{m}}\left(u^{\prime}\right),
|
|
1929
|
+
\\&\quad
|
|
1930
|
+
u\in\left[\min\left(u_{i},u_{f}\right),
|
|
1931
|
+
\max\left(u_{i},u_{f}\right)\right],
|
|
1932
|
+
:label: cumulative_integral_1d__1
|
|
1933
|
+
|
|
1934
|
+
where
|
|
1935
|
+
|
|
1936
|
+
.. math ::
|
|
1937
|
+
\Gamma=\begin{cases}
|
|
1938
|
+
1, & \text{if }\mathcal{N}\le 10^{-10} \text{ or }
|
|
1939
|
+
\text{normalize}=\text{False},
|
|
1940
|
+
\\\left|\int_{u_{i}}^{u_{f}}du^{\prime}\,
|
|
1941
|
+
F_{\mathbf{m}}\left(u^{\prime}\right)\right|, & \text{otherwise},
|
|
1942
|
+
\end{cases}
|
|
1943
|
+
:label: Gamma_of_cumulative_integral_1d__1
|
|
1944
|
+
|
|
1945
|
+
.. math ::
|
|
1946
|
+
\mathcal{N}=\left|\int_{u_{i}}^{u_{f}}du^{\prime}
|
|
1947
|
+
\,F_{\mathbf{m}}\left(u^{\prime}\right)\right|,
|
|
1948
|
+
:label: N_of_cumulative_integral_1d__1
|
|
1949
|
+
|
|
1950
|
+
:math:`u_i` and :math:`u_f` specify the interval over which cumulative
|
|
1951
|
+
integration is performed, the interval being
|
|
1952
|
+
:math:`\left[\min\left(u_{i},u_{f}\right),
|
|
1953
|
+
\max\left(u_{i},u_{f}\right)\right]`, and ``normalize`` is an optional
|
|
1954
|
+
boolean parameter that determines whether normalization is enabled or not.
|
|
1955
|
+
|
|
1956
|
+
Parameters
|
|
1957
|
+
----------
|
|
1958
|
+
limits : `array_like` (`float`, shape=(2,)) | `None`, optional
|
|
1959
|
+
If ``limits`` is set to ``None``, then the cumulative integration is
|
|
1960
|
+
performed over the entire input signal, with :math:`u_i<u_f`. Otherwise,
|
|
1961
|
+
if ``limits`` is set to a pair of floating-point numbers, then
|
|
1962
|
+
``limits[0]`` and ``limits[1]`` are :math:`u_i` and :math:`u_f`
|
|
1963
|
+
respectively, in the same units of the signal space coordinate
|
|
1964
|
+
:math:`u`. Note that the function represented by the input signal is
|
|
1965
|
+
assumed to be equal to zero everywhere outside of the bounds of said
|
|
1966
|
+
input signal.
|
|
1967
|
+
num_bins : `int` | `None`, optional
|
|
1968
|
+
``num_bins`` must either be a positive integer or of the `NoneType`: if
|
|
1969
|
+
the former, then the dimension of the signal space of the output signal
|
|
1970
|
+
``output_signal`` is set to ``num_bins``; if the latter, then the
|
|
1971
|
+
dimension of the signal space of ``output_signal`` is set to
|
|
1972
|
+
``input_signal.data[-1]``, where ``input_signal`` is the input
|
|
1973
|
+
``hyperspy`` signal.
|
|
1974
|
+
normalize : `bool`, optional
|
|
1975
|
+
The boolean optional parameter referenced in
|
|
1976
|
+
Eq. :eq:`Gamma_of_cumulative_integral_1d__1`.
|
|
1977
|
+
title : `str` | `None`, optional
|
|
1978
|
+
If ``title`` is set to ``None``, then the title of the output signal
|
|
1979
|
+
``output_signal`` is set to ``"CDF("+
|
|
1980
|
+
input_signal.metadata.General.title+")"``, where ``input_signal`` is the
|
|
1981
|
+
input ``hyperspy`` signal. Otherwise, if ``title`` is a `str`, then the
|
|
1982
|
+
``output_signal.metadata.General.title`` is set to the value of
|
|
1983
|
+
``title``.
|
|
1984
|
+
skip_validation_and_conversion : `bool`, optional
|
|
1985
|
+
Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
|
|
1986
|
+
attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
|
|
1987
|
+
and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
|
|
1988
|
+
being `dict` objects.
|
|
1989
|
+
|
|
1990
|
+
Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
|
|
1991
|
+
representation of the constructor parameters excluding the parameter
|
|
1992
|
+
``skip_validation_and_conversion``, where each `dict` key ``key`` is a
|
|
1993
|
+
different constructor parameter name, excluding the name
|
|
1994
|
+
``"skip_validation_and_conversion"``, and
|
|
1995
|
+
``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
|
|
1996
|
+
constructor parameter with the name given by ``key``.
|
|
1997
|
+
|
|
1998
|
+
If ``skip_validation_and_conversion`` is set to ``False``, then for each
|
|
1999
|
+
key ``key`` in ``params_to_be_mapped_to_core_attrs``,
|
|
2000
|
+
``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
|
|
2001
|
+
(params_to_be_mapped_to_core_attrs)``.
|
|
2002
|
+
|
|
2003
|
+
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
|
|
2004
|
+
then ``core_attrs`` is set to
|
|
2005
|
+
``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
|
|
2006
|
+
primarily when the user wants to avoid potentially expensive deep copies
|
|
2007
|
+
and/or conversions of the `dict` values of
|
|
2008
|
+
``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
|
|
2009
|
+
copies or conversions are made in this case.
|
|
2010
|
+
|
|
2011
|
+
"""
|
|
2012
|
+
ctor_param_names = ("limits", "num_bins", "normalize", "title")
|
|
2013
|
+
kwargs = {"namespace_as_dict": globals(),
|
|
2014
|
+
"ctor_param_names": ctor_param_names}
|
|
2015
|
+
|
|
2016
|
+
_validation_and_conversion_funcs_ = \
|
|
2017
|
+
fancytypes.return_validation_and_conversion_funcs(**kwargs)
|
|
2018
|
+
_pre_serialization_funcs_ = \
|
|
2019
|
+
fancytypes.return_pre_serialization_funcs(**kwargs)
|
|
2020
|
+
_de_pre_serialization_funcs_ = \
|
|
2021
|
+
fancytypes.return_de_pre_serialization_funcs(**kwargs)
|
|
2022
|
+
|
|
2023
|
+
del ctor_param_names, kwargs
|
|
2024
|
+
|
|
2025
|
+
|
|
2026
|
+
|
|
2027
|
+
def __init__(self,
|
|
2028
|
+
limits=\
|
|
2029
|
+
_default_limits,
|
|
2030
|
+
num_bins=\
|
|
2031
|
+
_default_num_bins,
|
|
2032
|
+
normalize=\
|
|
2033
|
+
_default_normalize,
|
|
2034
|
+
title=\
|
|
2035
|
+
_default_title,
|
|
2036
|
+
skip_validation_and_conversion=\
|
|
2037
|
+
_default_skip_validation_and_conversion):
|
|
2038
|
+
ctor_params = {key: val
|
|
2039
|
+
for key, val in locals().items()
|
|
2040
|
+
if (key not in ("self", "__class__"))}
|
|
2041
|
+
kwargs = ctor_params
|
|
2042
|
+
kwargs["skip_cls_tests"] = True
|
|
2043
|
+
fancytypes.PreSerializableAndUpdatable.__init__(self, **kwargs)
|
|
2044
|
+
|
|
2045
|
+
return None
|
|
2046
|
+
|
|
2047
|
+
|
|
2048
|
+
|
|
2049
|
+
@classmethod
|
|
2050
|
+
def get_validation_and_conversion_funcs(cls):
|
|
2051
|
+
validation_and_conversion_funcs = \
|
|
2052
|
+
cls._validation_and_conversion_funcs_.copy()
|
|
2053
|
+
|
|
2054
|
+
return validation_and_conversion_funcs
|
|
2055
|
+
|
|
2056
|
+
|
|
2057
|
+
|
|
2058
|
+
@classmethod
|
|
2059
|
+
def get_pre_serialization_funcs(cls):
|
|
2060
|
+
pre_serialization_funcs = \
|
|
2061
|
+
cls._pre_serialization_funcs_.copy()
|
|
2062
|
+
|
|
2063
|
+
return pre_serialization_funcs
|
|
2064
|
+
|
|
2065
|
+
|
|
2066
|
+
|
|
2067
|
+
@classmethod
|
|
2068
|
+
def get_de_pre_serialization_funcs(cls):
|
|
2069
|
+
de_pre_serialization_funcs = \
|
|
2070
|
+
cls._de_pre_serialization_funcs_.copy()
|
|
2071
|
+
|
|
2072
|
+
return de_pre_serialization_funcs
|
|
2073
|
+
|
|
2074
|
+
|
|
2075
|
+
|
|
2076
|
+
def cumulatively_integrate_1d(input_signal,
|
|
2077
|
+
optional_params=_default_optional_params):
|
|
2078
|
+
r"""Integrate cumulatively a given input 1D ``hyperspy`` signal.
|
|
2079
|
+
|
|
2080
|
+
This Python function assumes that the input 1D ``hyperspy`` signal samples
|
|
2081
|
+
from a mathematical function :math:`F_{\mathbf{m}}\left(u\right)` which is
|
|
2082
|
+
piecewise continuous in :math:`u`, where :math:`u` is the signal space
|
|
2083
|
+
coordinate of the input signal, and :math:`\mathbf{m}` is a vector of
|
|
2084
|
+
integers representing the navigation indices of the input signal. The Python
|
|
2085
|
+
function approximates the cumulative integral of
|
|
2086
|
+
:math:`F_{\mathbf{m}}\left(u\right)` given the input signal. We define the
|
|
2087
|
+
cumulative integral of :math:`F_{\mathbf{m}}\left(u\right)` as
|
|
2088
|
+
|
|
2089
|
+
.. math ::
|
|
2090
|
+
\text{CDF}_{\text{1D}}\left(u\right)&=\frac{1}{\Gamma}
|
|
2091
|
+
\int_{u_{i}}^{u}du^{\prime}\,F_{\mathbf{m}}\left(u^{\prime}\right),
|
|
2092
|
+
\\&\quad
|
|
2093
|
+
u\in\left[\min\left(u_{i},u_{f}\right),
|
|
2094
|
+
\max\left(u_{i},u_{f}\right)\right],
|
|
2095
|
+
:label: cumulative_integral_1d__2
|
|
2096
|
+
|
|
2097
|
+
where
|
|
2098
|
+
|
|
2099
|
+
.. math ::
|
|
2100
|
+
\Gamma=\begin{cases}
|
|
2101
|
+
1, & \text{if }\mathcal{N}\le 10^{-10} \text{ or }
|
|
2102
|
+
\text{normalize}=\text{False},
|
|
2103
|
+
\\\left|\int_{u_{i}}^{u_{f}}du^{\prime}\,
|
|
2104
|
+
F_{\mathbf{m}}\left(u^{\prime}\right)\right|, & \text{otherwise},
|
|
2105
|
+
\end{cases}
|
|
2106
|
+
:label: Gamma_of_cumulative_integral_1d__2
|
|
2107
|
+
|
|
2108
|
+
.. math ::
|
|
2109
|
+
\mathcal{N}=\left|\int_{u_{i}}^{u_{f}}du^{\prime}
|
|
2110
|
+
\,F_{\mathbf{m}}\left(u^{\prime}\right)\right|,
|
|
2111
|
+
:label: N_of_cumulative_integral_1d__2
|
|
2112
|
+
|
|
2113
|
+
:math:`u_i` and :math:`u_f` specify the interval over which cumulative
|
|
2114
|
+
integration is performed, the interval being
|
|
2115
|
+
:math:`\left[\min\left(u_{i},u_{f}\right),
|
|
2116
|
+
\max\left(u_{i},u_{f}\right)\right]`, and ``normalize`` is an optional
|
|
2117
|
+
boolean parameter that determines whether normalization is enabled or not.
|
|
2118
|
+
|
|
2119
|
+
Parameters
|
|
2120
|
+
----------
|
|
2121
|
+
input_signal : :class:`hyperspy._signals.signal1d.Signal1D` | :class:`hyperspy._signals.complex_signal1d.ComplexSignal1D`
|
|
2122
|
+
The input ``hyperspy`` signal.
|
|
2123
|
+
optional_params : :class:`empix.OptionalCumulative1dIntegrationParams` | `None`, optional
|
|
2124
|
+
The set of optional parameters. See the documentation for the class
|
|
2125
|
+
:class:`empix.OptionalCumulative1dIntegrationParams` for details. If
|
|
2126
|
+
``optional_params`` is set to ``None``, rather than an instance of
|
|
2127
|
+
:class:`empix.OptionalCumulative1dIntegrationParams`, then the default
|
|
2128
|
+
values of the optional parameters are chosen.
|
|
2129
|
+
|
|
2130
|
+
Returns
|
|
2131
|
+
-------
|
|
2132
|
+
output_signal : :class:`hyperspy._signals.signal1d.Signal1D` | :class:`hyperspy._signals.complex_signal1d.ComplexSignal1D`
|
|
2133
|
+
The output ``hyperspy`` signal that samples the cumulative integral of
|
|
2134
|
+
the input signal ``input_signal``. Note that the metadata of the input
|
|
2135
|
+
signal is copied over to the output signal, with the title being
|
|
2136
|
+
overwritten.
|
|
2137
|
+
|
|
2138
|
+
"""
|
|
2139
|
+
params = locals()
|
|
2140
|
+
params["action_to_apply_to_input_signal"] = inspect.stack()[0][3]
|
|
2141
|
+
for param_name in params:
|
|
2142
|
+
func_name = "_check_and_convert_" + param_name
|
|
2143
|
+
func_alias = globals()[func_name]
|
|
2144
|
+
params[param_name] = func_alias(params)
|
|
2145
|
+
|
|
2146
|
+
func_name = "_" + inspect.stack()[0][3]
|
|
2147
|
+
func_alias = globals()[func_name]
|
|
2148
|
+
kwargs = params
|
|
2149
|
+
del kwargs["action_to_apply_to_input_signal"]
|
|
2150
|
+
output_signal = func_alias(**kwargs)
|
|
2151
|
+
|
|
2152
|
+
return output_signal
|
|
2153
|
+
|
|
2154
|
+
|
|
2155
|
+
|
|
2156
|
+
def _cumulatively_integrate_1d(input_signal, optional_params):
|
|
2157
|
+
optional_params_core_attrs = optional_params.get_core_attrs(deep_copy=False)
|
|
2158
|
+
limits = optional_params_core_attrs["limits"]
|
|
2159
|
+
num_bins = optional_params_core_attrs["num_bins"]
|
|
2160
|
+
normalize = optional_params_core_attrs["normalize"]
|
|
2161
|
+
title = optional_params_core_attrs["title"]
|
|
2162
|
+
|
|
2163
|
+
u_coords = \
|
|
2164
|
+
_calc_u_coords_1d(signal=input_signal)
|
|
2165
|
+
beg_u_coord_idx, end_u_coord_idx = \
|
|
2166
|
+
_calc_beg_and_end_u_coord_indices(u_coords, limits)
|
|
2167
|
+
|
|
2168
|
+
navigation_dims = input_signal.data.shape[:-1]
|
|
2169
|
+
output_signal_data_shape = navigation_dims + (num_bins,)
|
|
2170
|
+
output_signal_data = np.zeros(output_signal_data_shape,
|
|
2171
|
+
dtype=input_signal.data.dtype)
|
|
2172
|
+
|
|
2173
|
+
num_patterns = int(np.prod(navigation_dims))
|
|
2174
|
+
for pattern_idx in range(num_patterns):
|
|
2175
|
+
navigation_indices = np.unravel_index(pattern_idx, navigation_dims)
|
|
2176
|
+
input_signal_datasubset = input_signal.data[navigation_indices]
|
|
2177
|
+
|
|
2178
|
+
kwargs = \
|
|
2179
|
+
{"input_signal_datasubset": input_signal_datasubset,
|
|
2180
|
+
"u_coords": u_coords,
|
|
2181
|
+
"beg_u_coord_idx": beg_u_coord_idx,
|
|
2182
|
+
"end_u_coord_idx": end_u_coord_idx,
|
|
2183
|
+
"limits": limits,
|
|
2184
|
+
"num_bins": num_bins,
|
|
2185
|
+
"normalize": normalize}
|
|
2186
|
+
bin_coords, output_signal_datasubset = \
|
|
2187
|
+
_cumulatively_integrate_input_signal_datasubset(**kwargs)
|
|
2188
|
+
output_signal_data[navigation_indices] = \
|
|
2189
|
+
output_signal_datasubset
|
|
2190
|
+
|
|
2191
|
+
kwargs = {"data": output_signal_data,
|
|
2192
|
+
"metadata": input_signal.metadata.as_dictionary()}
|
|
2193
|
+
if np.isrealobj(output_signal_data):
|
|
2194
|
+
output_signal = hyperspy.signals.Signal1D(**kwargs)
|
|
2195
|
+
else:
|
|
2196
|
+
output_signal = hyperspy.signals.ComplexSignal1D(**kwargs)
|
|
2197
|
+
output_signal.metadata.set_item("General.title", title)
|
|
2198
|
+
|
|
2199
|
+
kwargs = {"input_signal": input_signal,
|
|
2200
|
+
"optional_params": optional_params,
|
|
2201
|
+
"bin_coords": bin_coords,
|
|
2202
|
+
"output_signal": output_signal}
|
|
2203
|
+
_update_output_signal_axes(**kwargs)
|
|
2204
|
+
|
|
2205
|
+
return output_signal
|
|
2206
|
+
|
|
2207
|
+
|
|
2208
|
+
|
|
2209
|
+
def _calc_beg_and_end_u_coord_indices(u_coords, limits):
|
|
2210
|
+
d_u = u_coords[1]-u_coords[0]
|
|
2211
|
+
|
|
2212
|
+
idx_1 = np.abs(u_coords-min(limits)).argmin()
|
|
2213
|
+
idx_1 = max(idx_1-1, 0) if (d_u > 0) else min(idx_1+1, u_coords.size-1)
|
|
2214
|
+
|
|
2215
|
+
idx_2 = np.abs(u_coords-max(limits)).argmin()
|
|
2216
|
+
idx_2 = min(idx_2+1, u_coords.size-1) if d_u > 0 else max(idx_2-1, 0)
|
|
2217
|
+
|
|
2218
|
+
beg_u_coord_idx = min(idx_1, idx_2)
|
|
2219
|
+
end_u_coord_idx = max(idx_1, idx_2)
|
|
2220
|
+
|
|
2221
|
+
return beg_u_coord_idx, end_u_coord_idx
|
|
2222
|
+
|
|
2223
|
+
|
|
2224
|
+
|
|
2225
|
+
def _cumulatively_integrate_input_signal_datasubset(input_signal_datasubset,
|
|
2226
|
+
u_coords,
|
|
2227
|
+
beg_u_coord_idx,
|
|
2228
|
+
end_u_coord_idx,
|
|
2229
|
+
limits,
|
|
2230
|
+
num_bins,
|
|
2231
|
+
normalize):
|
|
2232
|
+
d_u = u_coords[1]-u_coords[0]
|
|
2233
|
+
x = u_coords[beg_u_coord_idx:end_u_coord_idx+1]
|
|
2234
|
+
y = input_signal_datasubset[beg_u_coord_idx:end_u_coord_idx+1]
|
|
2235
|
+
|
|
2236
|
+
if d_u < 0:
|
|
2237
|
+
x = x[::-1]
|
|
2238
|
+
y = y[::-1]
|
|
2239
|
+
|
|
2240
|
+
kwargs = {"x": x,
|
|
2241
|
+
"y": y,
|
|
2242
|
+
"kind": "cubic",
|
|
2243
|
+
"copy": False,
|
|
2244
|
+
"bounds_error": False,
|
|
2245
|
+
"fill_value": 0,
|
|
2246
|
+
"assume_sorted": True}
|
|
2247
|
+
F = scipy.interpolate.interp1d(**kwargs)
|
|
2248
|
+
|
|
2249
|
+
u_i, u_f = limits
|
|
2250
|
+
bin_coords = np.linspace(u_i, u_f, num_bins)
|
|
2251
|
+
F_data = F(bin_coords)
|
|
2252
|
+
|
|
2253
|
+
output_signal_datasubset = (bin_coords[1]-bin_coords[0]) * np.cumsum(F_data)
|
|
2254
|
+
|
|
2255
|
+
tol = 1e-10
|
|
2256
|
+
N = abs(output_signal_datasubset[-1].item())
|
|
2257
|
+
normalize = (not bool((N <= tol) + (normalize == False)))
|
|
2258
|
+
Gamma = normalize*(N-1.0) + 1.0
|
|
2259
|
+
|
|
2260
|
+
output_signal_datasubset /= Gamma
|
|
2261
|
+
|
|
2262
|
+
return bin_coords, output_signal_datasubset
|
|
2263
|
+
|
|
2264
|
+
|
|
2265
|
+
|
|
2266
|
+
def _check_and_convert_window_dims(params):
|
|
2267
|
+
current_func_name = inspect.stack()[0][3]
|
|
2268
|
+
char_idx = 19
|
|
2269
|
+
obj_name = current_func_name[char_idx:]
|
|
2270
|
+
obj = params[obj_name]
|
|
2271
|
+
|
|
2272
|
+
param_name = "input_signal"
|
|
2273
|
+
input_signal = params.get(param_name, None)
|
|
2274
|
+
|
|
2275
|
+
if obj is not None:
|
|
2276
|
+
try:
|
|
2277
|
+
kwargs = {"obj": obj, "obj_name": obj_name}
|
|
2278
|
+
window_dims = czekitout.convert.to_pair_of_positive_ints(**kwargs)
|
|
2279
|
+
except:
|
|
2280
|
+
err_msg = globals()[current_func_name+"_err_msg_1"]
|
|
2281
|
+
raise TypeError(err_msg)
|
|
2282
|
+
else:
|
|
2283
|
+
if input_signal is not None:
|
|
2284
|
+
N_v, N_h = input_signal.data.shape[-2:]
|
|
2285
|
+
window_dims = (N_h, N_v)
|
|
2286
|
+
else:
|
|
2287
|
+
window_dims = obj
|
|
2288
|
+
|
|
2289
|
+
return window_dims
|
|
2290
|
+
|
|
2291
|
+
|
|
2292
|
+
|
|
2293
|
+
def _pre_serialize_window_dims(window_dims):
|
|
2294
|
+
obj_to_pre_serialize = random.choice(list(locals().values()))
|
|
2295
|
+
serializable_rep = obj_to_pre_serialize
|
|
2296
|
+
|
|
2297
|
+
return serializable_rep
|
|
2298
|
+
|
|
2299
|
+
|
|
2300
|
+
|
|
2301
|
+
def _de_pre_serialize_window_dims(serializable_rep):
|
|
2302
|
+
window_dims = serializable_rep
|
|
2303
|
+
|
|
2304
|
+
return window_dims
|
|
2305
|
+
|
|
2306
|
+
|
|
2307
|
+
|
|
2308
|
+
def _check_and_convert_pad_mode(params):
|
|
2309
|
+
current_func_name = inspect.stack()[0][3]
|
|
2310
|
+
char_idx = 19
|
|
2311
|
+
obj_name = current_func_name[char_idx:]
|
|
2312
|
+
obj = params[obj_name]
|
|
2313
|
+
|
|
2314
|
+
kwargs = {"obj": obj, "obj_name": obj_name}
|
|
2315
|
+
pad_mode = czekitout.convert.to_str_from_str_like(**kwargs)
|
|
2316
|
+
|
|
2317
|
+
kwargs["accepted_strings"] = ("no-padding", "wrap", "zeros")
|
|
2318
|
+
czekitout.check.if_one_of_any_accepted_strings(**kwargs)
|
|
2319
|
+
|
|
2320
|
+
return pad_mode
|
|
2321
|
+
|
|
2322
|
+
|
|
2323
|
+
|
|
2324
|
+
def _pre_serialize_pad_mode(pad_mode):
|
|
2325
|
+
obj_to_pre_serialize = random.choice(list(locals().values()))
|
|
2326
|
+
serializable_rep = obj_to_pre_serialize
|
|
2327
|
+
|
|
2328
|
+
return serializable_rep
|
|
2329
|
+
|
|
2330
|
+
|
|
2331
|
+
|
|
2332
|
+
def _de_pre_serialize_pad_mode(serializable_rep):
|
|
2333
|
+
pad_mode = serializable_rep
|
|
2334
|
+
|
|
2335
|
+
return pad_mode
|
|
2336
|
+
|
|
2337
|
+
|
|
2338
|
+
|
|
2339
|
+
def _check_and_convert_apply_symmetric_mask(params):
|
|
2340
|
+
current_func_name = inspect.stack()[0][3]
|
|
2341
|
+
char_idx = 19
|
|
2342
|
+
obj_name = current_func_name[char_idx:]
|
|
2343
|
+
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
|
|
2344
|
+
apply_symmetric_mask = czekitout.convert.to_bool(**kwargs)
|
|
2345
|
+
|
|
2346
|
+
return apply_symmetric_mask
|
|
2347
|
+
|
|
2348
|
+
|
|
2349
|
+
|
|
2350
|
+
def _pre_serialize_apply_symmetric_mask(apply_symmetric_mask):
|
|
2351
|
+
obj_to_pre_serialize = random.choice(list(locals().values()))
|
|
2352
|
+
serializable_rep = obj_to_pre_serialize
|
|
2353
|
+
|
|
2354
|
+
return serializable_rep
|
|
2355
|
+
|
|
2356
|
+
|
|
2357
|
+
|
|
2358
|
+
def _de_pre_serialize_apply_symmetric_mask(serializable_rep):
|
|
2359
|
+
apply_symmetric_mask = serializable_rep
|
|
2360
|
+
|
|
2361
|
+
return apply_symmetric_mask
|
|
2362
|
+
|
|
2363
|
+
|
|
2364
|
+
|
|
2365
|
+
_default_window_dims = None
|
|
2366
|
+
_default_pad_mode = "no-padding"
|
|
2367
|
+
_default_apply_symmetric_mask = False
|
|
2368
|
+
|
|
2369
|
+
|
|
2370
|
+
|
|
2371
|
+
_cls_alias = fancytypes.PreSerializableAndUpdatable
|
|
2372
|
+
class OptionalCroppingParams(_cls_alias):
|
|
2373
|
+
r"""The set of optional parameters for the function :func:`empix.crop`.
|
|
2374
|
+
|
|
2375
|
+
The Python function :func:`empix.crop` applies a series of optional
|
|
2376
|
+
transformations to a given input 2D ``hyperspy`` signal. Let us denote the
|
|
2377
|
+
input 2D ``hyperspy`` signal by :math:`F_{\mathbf{m}; l_x, l_y}`, where
|
|
2378
|
+
:math:`l_x` and :math:`l_y` are integers indexing the sampled horizontal and
|
|
2379
|
+
vertical coordinates respectively in the signal space of the input signal,
|
|
2380
|
+
and :math:`\mathbf{m}` is a vector of integers representing the navigation
|
|
2381
|
+
indices of the input signal. The Python function effectively does the
|
|
2382
|
+
following:
|
|
2383
|
+
|
|
2384
|
+
1. Copies the input signal and optionally pads the copy along the horizontal
|
|
2385
|
+
and vertical axes in signal space according to the parameter ``pad_mode``;
|
|
2386
|
+
|
|
2387
|
+
2. Constructs a cropping window in the signal space of the (optionally
|
|
2388
|
+
padded) copy of the input signal, with the cropping window dimensions being
|
|
2389
|
+
determined by the parameter ``window_dims``;
|
|
2390
|
+
|
|
2391
|
+
3. Shifts the center of the cropping window to coordinates determined by the
|
|
2392
|
+
parameter ``center``;
|
|
2393
|
+
|
|
2394
|
+
4. Shifts the center of the cropping window again to the coordinates of the
|
|
2395
|
+
pixel closest to the aforementioned coordinates in the previous step;
|
|
2396
|
+
|
|
2397
|
+
5. Crops the (optionally padded) copy of the input signal along the
|
|
2398
|
+
horizontal and vertical dimensions of the signal space according to the
|
|
2399
|
+
placement of the cropping window in the previous two steps;
|
|
2400
|
+
|
|
2401
|
+
6. Optionally applies a symmetric mask to the cropped signal resulting from
|
|
2402
|
+
the previous step according to the parameter ``apply_symmetric_mask``.
|
|
2403
|
+
|
|
2404
|
+
See the description below of the optional parameters for more details.
|
|
2405
|
+
|
|
2406
|
+
Parameters
|
|
2407
|
+
----------
|
|
2408
|
+
center : `array_like` (`float`, shape=(2,)) | `None`, optional
|
|
2409
|
+
If ``center`` is set to ``None``, then the center of the cropping window
|
|
2410
|
+
is set to the signal space coordinates corresponding to the pixel that
|
|
2411
|
+
is ``(h_dim+1)//2 -1`` pixels to the right of the upper left corner in
|
|
2412
|
+
signal space, and ``(v_dim+1)//2-1`` pixels below the same corner, where
|
|
2413
|
+
``h_dim`` and ``v_dim`` are the horizontal and vertical dimensions of
|
|
2414
|
+
the signal space. Otherwise, if ``center`` is set to a pair of
|
|
2415
|
+
floating-point numbers, then ``center[0]`` and ``center[1]`` specify the
|
|
2416
|
+
horizontal and vertical signal space coordinates of the center of the
|
|
2417
|
+
cropping window prior to the subpixel shift to the nearest pixel, in the
|
|
2418
|
+
same units of the corresponding axes of the input signal.
|
|
2419
|
+
|
|
2420
|
+
We define the center of the cropping window to be ``(N_W_h+1)//2 - 1``
|
|
2421
|
+
pixels to the right of the upper left corner of the cropping window, and
|
|
2422
|
+
``(N_W_v+1)//2 - 1`` pixels below the same corner, where ``N_W_h`` and
|
|
2423
|
+
``N_W_v`` are the horizontal and vertical dimensions of the cropping
|
|
2424
|
+
window in units of pixels.
|
|
2425
|
+
window_dims : `array_like` (`int`, shape=(2,)) | `None`, optional
|
|
2426
|
+
If ``window_dims`` is set to ``None``, then the dimensions of the
|
|
2427
|
+
cropping window are set to the dimensions of the signal space of the
|
|
2428
|
+
input signal. Otherwise, if ``window_dims`` is set to a pair of
|
|
2429
|
+
positive integers, then ``window_dims[0]`` and ``window_dims[1]``
|
|
2430
|
+
specify the horizontal and vertical dimensions of the cropping window in
|
|
2431
|
+
units of pixels.
|
|
2432
|
+
pad_mode : ``"no-padding"`` | ``"wrap"`` | ``"zeros"``, optional
|
|
2433
|
+
If ``pad_mode`` is set to ``"no-padding"``, then no padding is performed
|
|
2434
|
+
prior to the crop. If ``pad_mode`` is set to ``"wrap"``, then the copy
|
|
2435
|
+
of the input signal is effectively padded along the horizontal and
|
|
2436
|
+
vertical axes in signal space by tiling the copy both horizontally and
|
|
2437
|
+
vertically in signal space such that the cropping window lies completely
|
|
2438
|
+
within the signal space boundaries of the resulting padded signal upon
|
|
2439
|
+
performing the crop. If ``pad_mode`` is set to ``"zeros"``, then the
|
|
2440
|
+
copy of the input signal is effectively padded with zeros such that the
|
|
2441
|
+
cropping window lies completely within the signal space boundaries of
|
|
2442
|
+
the resulting padded signal upon performing the crop.
|
|
2443
|
+
apply_symmetric_mask : `bool`, optional
|
|
2444
|
+
If ``apply_symmetric_mask`` is set to ``True`` and ``pad_mode`` to
|
|
2445
|
+
``"zeros"``, then for every signal space pixel in the cropped signal
|
|
2446
|
+
that has a value of zero due to padding and a corresponding pixel with
|
|
2447
|
+
coordinates equal to the former after a rotation of 180 degrees about
|
|
2448
|
+
the center of the cropped signal, the latter i.e. the aforementioned
|
|
2449
|
+
corresponding pixel is effectively set to zero. The effective procedure
|
|
2450
|
+
is equivalent to applying a symmetric mask. Otherwise, no mask is
|
|
2451
|
+
effectively applied after cropping.
|
|
2452
|
+
title : `str` | `None`, optional
|
|
2453
|
+
If ``title`` is set to ``None``, then the title of the output signal
|
|
2454
|
+
``output_signal`` is set to ``"Cropped "+
|
|
2455
|
+
input_signal.metadata.General.title``, where ``input_signal`` is the
|
|
2456
|
+
input ``hyperspy`` signal. Otherwise, if ``title`` is a `str`, then the
|
|
2457
|
+
``output_signal.metadata.General.title`` is set to the value of
|
|
2458
|
+
``title``.
|
|
2459
|
+
skip_validation_and_conversion : `bool`, optional
|
|
2460
|
+
Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
|
|
2461
|
+
attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
|
|
2462
|
+
and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
|
|
2463
|
+
being `dict` objects.
|
|
2464
|
+
|
|
2465
|
+
Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
|
|
2466
|
+
representation of the constructor parameters excluding the parameter
|
|
2467
|
+
``skip_validation_and_conversion``, where each `dict` key ``key`` is a
|
|
2468
|
+
different constructor parameter name, excluding the name
|
|
2469
|
+
``"skip_validation_and_conversion"``, and
|
|
2470
|
+
``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
|
|
2471
|
+
constructor parameter with the name given by ``key``.
|
|
2472
|
+
|
|
2473
|
+
If ``skip_validation_and_conversion`` is set to ``False``, then for each
|
|
2474
|
+
key ``key`` in ``params_to_be_mapped_to_core_attrs``,
|
|
2475
|
+
``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
|
|
2476
|
+
(params_to_be_mapped_to_core_attrs)``.
|
|
2477
|
+
|
|
2478
|
+
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
|
|
2479
|
+
then ``core_attrs`` is set to
|
|
2480
|
+
``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
|
|
2481
|
+
primarily when the user wants to avoid potentially expensive deep copies
|
|
2482
|
+
and/or conversions of the `dict` values of
|
|
2483
|
+
``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
|
|
2484
|
+
copies or conversions are made in this case.
|
|
2485
|
+
|
|
2486
|
+
"""
|
|
2487
|
+
ctor_param_names = ("center",
|
|
2488
|
+
"window_dims",
|
|
2489
|
+
"pad_mode",
|
|
2490
|
+
"apply_symmetric_mask",
|
|
2491
|
+
"title")
|
|
2492
|
+
kwargs = {"namespace_as_dict": globals(),
|
|
2493
|
+
"ctor_param_names": ctor_param_names}
|
|
2494
|
+
|
|
2495
|
+
_validation_and_conversion_funcs_ = \
|
|
2496
|
+
fancytypes.return_validation_and_conversion_funcs(**kwargs)
|
|
2497
|
+
_pre_serialization_funcs_ = \
|
|
2498
|
+
fancytypes.return_pre_serialization_funcs(**kwargs)
|
|
2499
|
+
_de_pre_serialization_funcs_ = \
|
|
2500
|
+
fancytypes.return_de_pre_serialization_funcs(**kwargs)
|
|
2501
|
+
|
|
2502
|
+
del ctor_param_names, kwargs
|
|
2503
|
+
|
|
2504
|
+
|
|
2505
|
+
|
|
2506
|
+
def __init__(self,
|
|
2507
|
+
center=\
|
|
2508
|
+
_default_center,
|
|
2509
|
+
window_dims=\
|
|
2510
|
+
_default_window_dims,
|
|
2511
|
+
pad_mode=\
|
|
2512
|
+
_default_pad_mode,
|
|
2513
|
+
apply_symmetric_mask=\
|
|
2514
|
+
_default_apply_symmetric_mask,
|
|
2515
|
+
title=\
|
|
2516
|
+
_default_title,
|
|
2517
|
+
skip_validation_and_conversion=\
|
|
2518
|
+
_default_skip_validation_and_conversion):
|
|
2519
|
+
ctor_params = {key: val
|
|
2520
|
+
for key, val in locals().items()
|
|
2521
|
+
if (key not in ("self", "__class__"))}
|
|
2522
|
+
kwargs = ctor_params
|
|
2523
|
+
kwargs["skip_cls_tests"] = True
|
|
2524
|
+
fancytypes.PreSerializableAndUpdatable.__init__(self, **kwargs)
|
|
2525
|
+
|
|
2526
|
+
return None
|
|
2527
|
+
|
|
2528
|
+
|
|
2529
|
+
|
|
2530
|
+
@classmethod
|
|
2531
|
+
def get_validation_and_conversion_funcs(cls):
|
|
2532
|
+
validation_and_conversion_funcs = \
|
|
2533
|
+
cls._validation_and_conversion_funcs_.copy()
|
|
2534
|
+
|
|
2535
|
+
return validation_and_conversion_funcs
|
|
2536
|
+
|
|
2537
|
+
|
|
2538
|
+
|
|
2539
|
+
@classmethod
|
|
2540
|
+
def get_pre_serialization_funcs(cls):
|
|
2541
|
+
pre_serialization_funcs = \
|
|
2542
|
+
cls._pre_serialization_funcs_.copy()
|
|
2543
|
+
|
|
2544
|
+
return pre_serialization_funcs
|
|
2545
|
+
|
|
2546
|
+
|
|
2547
|
+
|
|
2548
|
+
@classmethod
|
|
2549
|
+
def get_de_pre_serialization_funcs(cls):
|
|
2550
|
+
de_pre_serialization_funcs = \
|
|
2551
|
+
cls._de_pre_serialization_funcs_.copy()
|
|
2552
|
+
|
|
2553
|
+
return de_pre_serialization_funcs
|
|
2554
|
+
|
|
2555
|
+
|
|
2556
|
+
|
|
2557
|
+
def crop(input_signal, optional_params=_default_optional_params):
|
|
2558
|
+
r"""Crop a given input 2D ``hyperspy`` signal.
|
|
2559
|
+
|
|
2560
|
+
This Python function applies a series of optional transformations to a given
|
|
2561
|
+
input 2D ``hyperspy`` signal. Let us denote the input 2D ``hyperspy`` signal
|
|
2562
|
+
by :math:`F_{\mathbf{m}; l_x, l_y}`, where :math:`l_x` and :math:`l_y` are
|
|
2563
|
+
integers indexing the sampled horizontal and vertical coordinates
|
|
2564
|
+
respectively in the signal space of the input signal, and :math:`\mathbf{m}`
|
|
2565
|
+
is a vector of integers representing the navigation indices of the input
|
|
2566
|
+
signal. The Python function effectively does the following:
|
|
2567
|
+
|
|
2568
|
+
1. Copies the input signal and optionally pads the copy along the horizontal
|
|
2569
|
+
and vertical axes in signal space according to the parameter ``pad_mode``;
|
|
2570
|
+
|
|
2571
|
+
2. Constructs a cropping window in the signal space of the (optionally
|
|
2572
|
+
padded) copy of the input signal, with the cropping window dimensions being
|
|
2573
|
+
determined by the parameter ``window_dims``;
|
|
2574
|
+
|
|
2575
|
+
3. Shifts the center of the cropping window to coordinates determined by the
|
|
2576
|
+
parameter ``center``;
|
|
2577
|
+
|
|
2578
|
+
4. Shifts the center of the cropping window again to the coordinates of the
|
|
2579
|
+
pixel closest to the aforementioned coordinates in the previous step;
|
|
2580
|
+
|
|
2581
|
+
5. Crops the (optionally padded) copy of the input signal along the
|
|
2582
|
+
horizontal and vertical dimensions of the signal space according to the
|
|
2583
|
+
placement of the cropping window in the previous two steps;
|
|
2584
|
+
|
|
2585
|
+
6. Optionally applies a symmetric mask to the cropped signal resulting from
|
|
2586
|
+
the previous step according to the parameter ``apply_symmetric_mask``.
|
|
2587
|
+
|
|
2588
|
+
See the description below of the optional parameters for more details.
|
|
2589
|
+
|
|
2590
|
+
Parameters
|
|
2591
|
+
----------
|
|
2592
|
+
input_signal : :class:`hyperspy._signals.signal2d.Signal2D` | :class:`hyperspy._signals.complex_signal2d.ComplexSignal2D`
|
|
2593
|
+
The input ``hyperspy`` signal.
|
|
2594
|
+
optional_params : :class:`empix.OptionalCroppingParams` | `None`, optional
|
|
2595
|
+
The set of optional parameters. See the documentation for the class
|
|
2596
|
+
:class:`empix.OptionalCroppingParams` for details. If
|
|
2597
|
+
``optional_params`` is set to ``None``, rather than an instance of
|
|
2598
|
+
:class:`empix.OptionalCroppingParams`, then the default values of the
|
|
2599
|
+
optional parameters are chosen.
|
|
2600
|
+
|
|
2601
|
+
Returns
|
|
2602
|
+
-------
|
|
2603
|
+
output_signal : :class:`hyperspy._signals.signal2d.Signal2D` | :class:`hyperspy._signals.complex_signal2d.ComplexSignal2D`
|
|
2604
|
+
The output ``hyperspy`` signal that results from the applied
|
|
2605
|
+
transformations, described above. Note that the metadata of the input
|
|
2606
|
+
signal is copied over to the output signal, with the title being
|
|
2607
|
+
overwritten.
|
|
2608
|
+
|
|
2609
|
+
"""
|
|
2610
|
+
params = locals()
|
|
2611
|
+
params["action_to_apply_to_input_signal"] = inspect.stack()[0][3]
|
|
2612
|
+
for param_name in params:
|
|
2613
|
+
func_name = "_check_and_convert_" + param_name
|
|
2614
|
+
func_alias = globals()[func_name]
|
|
2615
|
+
params[param_name] = func_alias(params)
|
|
2616
|
+
|
|
2617
|
+
func_name = "_" + inspect.stack()[0][3]
|
|
2618
|
+
func_alias = globals()[func_name]
|
|
2619
|
+
kwargs = params
|
|
2620
|
+
del kwargs["action_to_apply_to_input_signal"]
|
|
2621
|
+
output_signal = func_alias(**kwargs)
|
|
2622
|
+
|
|
2623
|
+
return output_signal
|
|
2624
|
+
|
|
2625
|
+
|
|
2626
|
+
|
|
2627
|
+
def _crop(input_signal, optional_params):
|
|
2628
|
+
current_func_name = inspect.stack()[0][3]
|
|
2629
|
+
|
|
2630
|
+
optional_params_core_attrs = optional_params.get_core_attrs(deep_copy=False)
|
|
2631
|
+
title = optional_params_core_attrs["title"]
|
|
2632
|
+
|
|
2633
|
+
func_alias = _calc_input_signal_datasubset_cropping_params
|
|
2634
|
+
input_signal_datasubset_cropping_params = func_alias(input_signal,
|
|
2635
|
+
optional_params)
|
|
2636
|
+
|
|
2637
|
+
navigation_dims = input_signal.data.shape[:-2]
|
|
2638
|
+
num_patterns = int(np.prod(navigation_dims))
|
|
2639
|
+
|
|
2640
|
+
for pattern_idx in range(0, num_patterns):
|
|
2641
|
+
navigation_indices = np.unravel_index(pattern_idx, navigation_dims)
|
|
2642
|
+
input_signal_datasubset = input_signal.data[navigation_indices]
|
|
2643
|
+
|
|
2644
|
+
kwargs = {"input_signal_datasubset": \
|
|
2645
|
+
input_signal_datasubset,
|
|
2646
|
+
"input_signal_datasubset_cropping_params": \
|
|
2647
|
+
input_signal_datasubset_cropping_params}
|
|
2648
|
+
output_signal_datasubset = _crop_input_signal_datasubset(**kwargs)
|
|
2649
|
+
|
|
2650
|
+
if pattern_idx == 0:
|
|
2651
|
+
output_signal_data_shape = (navigation_dims
|
|
2652
|
+
+ output_signal_datasubset.shape)
|
|
2653
|
+
output_signal_data = np.zeros(output_signal_data_shape,
|
|
2654
|
+
dtype=input_signal.data.dtype)
|
|
2655
|
+
|
|
2656
|
+
if np.prod(output_signal_data.shape) == 0:
|
|
2657
|
+
err_msg = globals()[current_func_name+"_err_msg_1"]
|
|
2658
|
+
raise ValueError(err_msg)
|
|
2659
|
+
|
|
2660
|
+
output_signal_data[navigation_indices] = output_signal_datasubset
|
|
2661
|
+
|
|
2662
|
+
kwargs = {"data": output_signal_data,
|
|
2663
|
+
"metadata": input_signal.metadata.as_dictionary()}
|
|
2664
|
+
if np.isrealobj(output_signal_data):
|
|
2665
|
+
output_signal = hyperspy.signals.Signal2D(**kwargs)
|
|
2666
|
+
else:
|
|
2667
|
+
output_signal = hyperspy.signals.ComplexSignal2D(**kwargs)
|
|
2668
|
+
output_signal.metadata.set_item("General.title", title)
|
|
2669
|
+
|
|
2670
|
+
kwargs = {"input_signal": input_signal,
|
|
2671
|
+
"optional_params": optional_params,
|
|
2672
|
+
"bin_coords": None,
|
|
2673
|
+
"output_signal": output_signal}
|
|
2674
|
+
_update_output_signal_axes(**kwargs)
|
|
2675
|
+
|
|
2676
|
+
return output_signal
|
|
2677
|
+
|
|
2678
|
+
|
|
2679
|
+
|
|
2680
|
+
def _calc_input_signal_datasubset_cropping_params(input_signal,
|
|
2681
|
+
optional_params):
|
|
2682
|
+
optional_params_core_attrs = optional_params.get_core_attrs(deep_copy=False)
|
|
2683
|
+
approximate_crop_window_center = optional_params_core_attrs["center"]
|
|
2684
|
+
crop_window_dims = optional_params_core_attrs["window_dims"]
|
|
2685
|
+
pad_mode = optional_params_core_attrs["pad_mode"]
|
|
2686
|
+
apply_symmetric_mask = optional_params_core_attrs["apply_symmetric_mask"]
|
|
2687
|
+
|
|
2688
|
+
func_alias = _calc_crop_window_center_in_pixel_coords
|
|
2689
|
+
kwargs = {"input_signal": input_signal,
|
|
2690
|
+
"approximate_crop_window_center": approximate_crop_window_center}
|
|
2691
|
+
crop_window_center_in_pixel_coords = func_alias(**kwargs)
|
|
2692
|
+
|
|
2693
|
+
kwargs = \
|
|
2694
|
+
{"crop_window_dims": \
|
|
2695
|
+
crop_window_dims,
|
|
2696
|
+
"crop_window_center_in_pixel_coords": \
|
|
2697
|
+
crop_window_center_in_pixel_coords,
|
|
2698
|
+
"pad_mode": \
|
|
2699
|
+
pad_mode,
|
|
2700
|
+
"input_signal": \
|
|
2701
|
+
input_signal,
|
|
2702
|
+
"apply_symmetric_mask": \
|
|
2703
|
+
apply_symmetric_mask}
|
|
2704
|
+
multi_dim_slice_for_cropping, multi_dim_slice_for_masking = \
|
|
2705
|
+
_calc_multi_dim_slices_for_cropping_and_masking(**kwargs)
|
|
2706
|
+
|
|
2707
|
+
if pad_mode == "zeros":
|
|
2708
|
+
mask_to_apply_for_crop = np.ones(crop_window_dims[::-1], dtype=bool)
|
|
2709
|
+
mask_to_apply_for_crop[multi_dim_slice_for_masking] = False
|
|
2710
|
+
else:
|
|
2711
|
+
mask_to_apply_for_crop = None
|
|
2712
|
+
|
|
2713
|
+
input_signal_datasubset_cropping_params = {"multi_dim_slice_for_cropping": \
|
|
2714
|
+
multi_dim_slice_for_cropping,
|
|
2715
|
+
"mask_to_apply_for_crop": \
|
|
2716
|
+
mask_to_apply_for_crop}
|
|
2717
|
+
|
|
2718
|
+
return input_signal_datasubset_cropping_params
|
|
2719
|
+
|
|
2720
|
+
|
|
2721
|
+
|
|
2722
|
+
def _calc_multi_dim_slices_for_cropping_and_masking(
|
|
2723
|
+
crop_window_dims,
|
|
2724
|
+
crop_window_center_in_pixel_coords,
|
|
2725
|
+
pad_mode,
|
|
2726
|
+
input_signal,
|
|
2727
|
+
apply_symmetric_mask):
|
|
2728
|
+
num_spatial_dims = len(crop_window_dims)
|
|
2729
|
+
|
|
2730
|
+
multi_dim_slice_for_cropping = tuple()
|
|
2731
|
+
multi_dim_slice_for_masking = tuple()
|
|
2732
|
+
|
|
2733
|
+
for spatial_dim_idx in range(num_spatial_dims):
|
|
2734
|
+
start = (crop_window_center_in_pixel_coords[spatial_dim_idx]
|
|
2735
|
+
- ((crop_window_dims[spatial_dim_idx]+1)//2 - 1))
|
|
2736
|
+
|
|
2737
|
+
stop = start + crop_window_dims[spatial_dim_idx]
|
|
2738
|
+
|
|
2739
|
+
if pad_mode == "no-padding":
|
|
2740
|
+
start = max(start, 0)
|
|
2741
|
+
stop = min(stop, input_signal.data.shape[-(spatial_dim_idx+1)])
|
|
2742
|
+
|
|
2743
|
+
single_dim_slice_for_cropping = slice(start, stop)
|
|
2744
|
+
multi_dim_slice_for_cropping = ((single_dim_slice_for_cropping,)
|
|
2745
|
+
+ multi_dim_slice_for_cropping)
|
|
2746
|
+
|
|
2747
|
+
diff_1 = 0-single_dim_slice_for_cropping.start
|
|
2748
|
+
diff_2 = (single_dim_slice_for_cropping.stop
|
|
2749
|
+
- input_signal.data.shape[-(spatial_dim_idx+1)])
|
|
2750
|
+
|
|
2751
|
+
if apply_symmetric_mask:
|
|
2752
|
+
mask_frame_width_1 = max(diff_1, diff_2, 0)
|
|
2753
|
+
mask_frame_width_2 = mask_frame_width_1
|
|
2754
|
+
else:
|
|
2755
|
+
mask_frame_width_1 = max(diff_1, 0)
|
|
2756
|
+
mask_frame_width_2 = max(diff_2, 0)
|
|
2757
|
+
|
|
2758
|
+
start = mask_frame_width_1
|
|
2759
|
+
stop = crop_window_dims[spatial_dim_idx] - mask_frame_width_2
|
|
2760
|
+
|
|
2761
|
+
single_dim_slice_for_masking = slice(start, stop)
|
|
2762
|
+
multi_dim_slice_for_masking = ((single_dim_slice_for_masking,)
|
|
2763
|
+
+ multi_dim_slice_for_masking)
|
|
2764
|
+
|
|
2765
|
+
return multi_dim_slice_for_cropping, multi_dim_slice_for_masking
|
|
2766
|
+
|
|
2767
|
+
|
|
2768
|
+
|
|
2769
|
+
def _crop_input_signal_datasubset(input_signal_datasubset,
|
|
2770
|
+
input_signal_datasubset_cropping_params):
|
|
2771
|
+
multi_dim_slice_for_cropping = \
|
|
2772
|
+
input_signal_datasubset_cropping_params["multi_dim_slice_for_cropping"]
|
|
2773
|
+
mask_to_apply_for_crop = \
|
|
2774
|
+
input_signal_datasubset_cropping_params["mask_to_apply_for_crop"]
|
|
2775
|
+
|
|
2776
|
+
num_spatial_dims = len(input_signal_datasubset.shape)
|
|
2777
|
+
|
|
2778
|
+
cropped_input_signal_datasubset = input_signal_datasubset
|
|
2779
|
+
for spatial_dim_idx in range(num_spatial_dims):
|
|
2780
|
+
single_dim_slice_for_cropping = \
|
|
2781
|
+
multi_dim_slice_for_cropping[-(spatial_dim_idx+1)]
|
|
2782
|
+
|
|
2783
|
+
indices = np.arange(single_dim_slice_for_cropping.start,
|
|
2784
|
+
single_dim_slice_for_cropping.stop,
|
|
2785
|
+
dtype="int")
|
|
2786
|
+
|
|
2787
|
+
kwargs = {"a": cropped_input_signal_datasubset,
|
|
2788
|
+
"indices": indices,
|
|
2789
|
+
"axis": 1-spatial_dim_idx,
|
|
2790
|
+
"mode": "wrap"}
|
|
2791
|
+
cropped_input_signal_datasubset = np.take(**kwargs)
|
|
2792
|
+
|
|
2793
|
+
if mask_to_apply_for_crop is not None:
|
|
2794
|
+
cropped_input_signal_datasubset *= (~mask_to_apply_for_crop)
|
|
2795
|
+
|
|
2796
|
+
return cropped_input_signal_datasubset
|
|
2797
|
+
|
|
2798
|
+
|
|
2799
|
+
|
|
2800
|
+
def _check_and_convert_block_dims(params):
|
|
2801
|
+
current_func_name = inspect.stack()[0][3]
|
|
2802
|
+
char_idx = 19
|
|
2803
|
+
obj_name = current_func_name[char_idx:]
|
|
2804
|
+
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
|
|
2805
|
+
block_dims = czekitout.convert.to_pair_of_positive_ints(**kwargs)
|
|
2806
|
+
|
|
2807
|
+
return block_dims
|
|
2808
|
+
|
|
2809
|
+
|
|
2810
|
+
|
|
2811
|
+
def _pre_serialize_block_dims(block_dims):
|
|
2812
|
+
obj_to_pre_serialize = random.choice(list(locals().values()))
|
|
2813
|
+
serializable_rep = obj_to_pre_serialize
|
|
2814
|
+
|
|
2815
|
+
return serializable_rep
|
|
2816
|
+
|
|
2817
|
+
|
|
2818
|
+
|
|
2819
|
+
def _de_pre_serialize_block_dims(serializable_rep):
|
|
2820
|
+
block_dims = serializable_rep
|
|
2821
|
+
|
|
2822
|
+
return block_dims
|
|
2823
|
+
|
|
2824
|
+
|
|
2825
|
+
|
|
2826
|
+
def _check_and_convert_padding_const(params):
|
|
2827
|
+
current_func_name = inspect.stack()[0][3]
|
|
2828
|
+
char_idx = 19
|
|
2829
|
+
obj_name = current_func_name[char_idx:]
|
|
2830
|
+
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
|
|
2831
|
+
padding_const = czekitout.convert.to_float(**kwargs)
|
|
2832
|
+
|
|
2833
|
+
return padding_const
|
|
2834
|
+
|
|
2835
|
+
|
|
2836
|
+
|
|
2837
|
+
def _pre_serialize_padding_const(padding_const):
|
|
2838
|
+
obj_to_pre_serialize = random.choice(list(locals().values()))
|
|
2839
|
+
serializable_rep = obj_to_pre_serialize
|
|
2840
|
+
|
|
2841
|
+
return serializable_rep
|
|
2842
|
+
|
|
2843
|
+
|
|
2844
|
+
|
|
2845
|
+
def _de_pre_serialize_padding_const(serializable_rep):
|
|
2846
|
+
padding_const = serializable_rep
|
|
2847
|
+
|
|
2848
|
+
return padding_const
|
|
2849
|
+
|
|
2850
|
+
|
|
2851
|
+
|
|
2852
|
+
def _check_and_convert_downsample_mode(params):
|
|
2853
|
+
current_func_name = inspect.stack()[0][3]
|
|
2854
|
+
char_idx = 19
|
|
2855
|
+
obj_name = current_func_name[char_idx:]
|
|
2856
|
+
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
|
|
2857
|
+
downsample_mode = czekitout.convert.to_str_from_str_like(**kwargs)
|
|
2858
|
+
|
|
2859
|
+
kwargs["accepted_strings"] = ("sum", "mean", "median", "amin", "amax")
|
|
2860
|
+
czekitout.check.if_one_of_any_accepted_strings(**kwargs)
|
|
2861
|
+
|
|
2862
|
+
return downsample_mode
|
|
2863
|
+
|
|
2864
|
+
|
|
2865
|
+
|
|
2866
|
+
def _pre_serialize_downsample_mode(downsample_mode):
|
|
2867
|
+
obj_to_pre_serialize = random.choice(list(locals().values()))
|
|
2868
|
+
serializable_rep = obj_to_pre_serialize
|
|
2869
|
+
|
|
2870
|
+
return serializable_rep
|
|
2871
|
+
|
|
2872
|
+
|
|
2873
|
+
|
|
2874
|
+
def _de_pre_serialize_downsample_mode(serializable_rep):
|
|
2875
|
+
downsample_mode = serializable_rep
|
|
2876
|
+
|
|
2877
|
+
return downsample_mode
|
|
2878
|
+
|
|
2879
|
+
|
|
2880
|
+
|
|
2881
|
+
_default_block_dims = (2, 2)
|
|
2882
|
+
_default_padding_const = 0
|
|
2883
|
+
_default_downsample_mode = "sum"
|
|
2884
|
+
|
|
2885
|
+
|
|
2886
|
+
|
|
2887
|
+
_cls_alias = fancytypes.PreSerializableAndUpdatable
|
|
2888
|
+
class OptionalDownsamplingParams(_cls_alias):
|
|
2889
|
+
r"""The set of optional parameters for the function
|
|
2890
|
+
:func:`empix.downsample`.
|
|
2891
|
+
|
|
2892
|
+
The Python function :func:`empix.downsample` copies a given input 2D
|
|
2893
|
+
``hyperspy`` signal and downsamples the copy along the axes in signal space.
|
|
2894
|
+
The Python function effectively does the following:
|
|
2895
|
+
|
|
2896
|
+
1. Groups the pixels of the copy of the input signal into so-called
|
|
2897
|
+
downsampling blocks along the axes in signal space, with dimensions
|
|
2898
|
+
determined by the parameter ``block_dims``, padding the copy with a constant
|
|
2899
|
+
value of ``padding_const`` in the case that either the horizontal or
|
|
2900
|
+
vertical dimensions of the signal space of the original input signal are not
|
|
2901
|
+
divisible by the corresponding dimensions of the downsampling blocks.
|
|
2902
|
+
|
|
2903
|
+
2. For each downsampling block, the Python function calls a ``numpy``
|
|
2904
|
+
function determined by the parameter ``downsample_mode``, wherein the input
|
|
2905
|
+
is the array data of the downsampling block, and the output is the value of
|
|
2906
|
+
the corresponding pixel of the downsampled signal.
|
|
2907
|
+
|
|
2908
|
+
Parameters
|
|
2909
|
+
----------
|
|
2910
|
+
block_dims : `array_like` (`int`, shape=(2,)), optional
|
|
2911
|
+
``block_dims[0]`` and ``block_dims[1]`` specify the horizontal and
|
|
2912
|
+
vertical dimensions of the downsampling blocks in units of pixels.
|
|
2913
|
+
padding_const : `float`, optional
|
|
2914
|
+
``padding_const`` is the padding constant to be applied in the case that
|
|
2915
|
+
either the horizontal or vertical dimensions of the signal space of the
|
|
2916
|
+
original input signal are not divisible by the corresponding dimensions
|
|
2917
|
+
of the downsampling blocks.
|
|
2918
|
+
downsample_mode : ``"sum"`` | ``"mean"`` | ``"median"`` | ``"amin"`` | ``"amax"``, optional
|
|
2919
|
+
``downsample_mode == numpy_func.__name__`` where ``numpy_func`` is the
|
|
2920
|
+
``numpy`` function to be applied to the downsampling blocks.
|
|
2921
|
+
title : `str` | `None`, optional
|
|
2922
|
+
If ``title`` is set to ``None``, then the title of the output signal
|
|
2923
|
+
``output_signal`` is set to ``"Downsampled "+
|
|
2924
|
+
input_signal.metadata.General.title``, where ``input_signal`` is the
|
|
2925
|
+
input ``hyperspy`` signal. Otherwise, if ``title`` is a `str`, then the
|
|
2926
|
+
``output_signal.metadata.General.title`` is set to the value of
|
|
2927
|
+
``title``.
|
|
2928
|
+
skip_validation_and_conversion : `bool`, optional
|
|
2929
|
+
Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
|
|
2930
|
+
attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
|
|
2931
|
+
and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
|
|
2932
|
+
being `dict` objects.
|
|
2933
|
+
|
|
2934
|
+
Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
|
|
2935
|
+
representation of the constructor parameters excluding the parameter
|
|
2936
|
+
``skip_validation_and_conversion``, where each `dict` key ``key`` is a
|
|
2937
|
+
different constructor parameter name, excluding the name
|
|
2938
|
+
``"skip_validation_and_conversion"``, and
|
|
2939
|
+
``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
|
|
2940
|
+
constructor parameter with the name given by ``key``.
|
|
2941
|
+
|
|
2942
|
+
If ``skip_validation_and_conversion`` is set to ``False``, then for each
|
|
2943
|
+
key ``key`` in ``params_to_be_mapped_to_core_attrs``,
|
|
2944
|
+
``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
|
|
2945
|
+
(params_to_be_mapped_to_core_attrs)``.
|
|
2946
|
+
|
|
2947
|
+
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
|
|
2948
|
+
then ``core_attrs`` is set to
|
|
2949
|
+
``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
|
|
2950
|
+
primarily when the user wants to avoid potentially expensive deep copies
|
|
2951
|
+
and/or conversions of the `dict` values of
|
|
2952
|
+
``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
|
|
2953
|
+
copies or conversions are made in this case.
|
|
2954
|
+
|
|
2955
|
+
"""
|
|
2956
|
+
ctor_param_names = ("block_dims",
|
|
2957
|
+
"padding_const",
|
|
2958
|
+
"downsample_mode",
|
|
2959
|
+
"title")
|
|
2960
|
+
kwargs = {"namespace_as_dict": globals(),
|
|
2961
|
+
"ctor_param_names": ctor_param_names}
|
|
2962
|
+
|
|
2963
|
+
_validation_and_conversion_funcs_ = \
|
|
2964
|
+
fancytypes.return_validation_and_conversion_funcs(**kwargs)
|
|
2965
|
+
_pre_serialization_funcs_ = \
|
|
2966
|
+
fancytypes.return_pre_serialization_funcs(**kwargs)
|
|
2967
|
+
_de_pre_serialization_funcs_ = \
|
|
2968
|
+
fancytypes.return_de_pre_serialization_funcs(**kwargs)
|
|
2969
|
+
|
|
2970
|
+
del ctor_param_names, kwargs
|
|
2971
|
+
|
|
2972
|
+
|
|
2973
|
+
|
|
2974
|
+
def __init__(self,
|
|
2975
|
+
block_dims=\
|
|
2976
|
+
_default_block_dims,
|
|
2977
|
+
padding_const=\
|
|
2978
|
+
_default_padding_const,
|
|
2979
|
+
downsample_mode=\
|
|
2980
|
+
_default_downsample_mode,
|
|
2981
|
+
title=\
|
|
2982
|
+
_default_title,
|
|
2983
|
+
skip_validation_and_conversion=\
|
|
2984
|
+
_default_skip_validation_and_conversion):
|
|
2985
|
+
ctor_params = {key: val
|
|
2986
|
+
for key, val in locals().items()
|
|
2987
|
+
if (key not in ("self", "__class__"))}
|
|
2988
|
+
kwargs = ctor_params
|
|
2989
|
+
kwargs["skip_cls_tests"] = True
|
|
2990
|
+
fancytypes.PreSerializableAndUpdatable.__init__(self, **kwargs)
|
|
2991
|
+
|
|
2992
|
+
return None
|
|
2993
|
+
|
|
2994
|
+
|
|
2995
|
+
|
|
2996
|
+
@classmethod
|
|
2997
|
+
def get_validation_and_conversion_funcs(cls):
|
|
2998
|
+
validation_and_conversion_funcs = \
|
|
2999
|
+
cls._validation_and_conversion_funcs_.copy()
|
|
3000
|
+
|
|
3001
|
+
return validation_and_conversion_funcs
|
|
3002
|
+
|
|
3003
|
+
|
|
3004
|
+
|
|
3005
|
+
@classmethod
|
|
3006
|
+
def get_pre_serialization_funcs(cls):
|
|
3007
|
+
pre_serialization_funcs = \
|
|
3008
|
+
cls._pre_serialization_funcs_.copy()
|
|
3009
|
+
|
|
3010
|
+
return pre_serialization_funcs
|
|
3011
|
+
|
|
3012
|
+
|
|
3013
|
+
|
|
3014
|
+
@classmethod
|
|
3015
|
+
def get_de_pre_serialization_funcs(cls):
|
|
3016
|
+
de_pre_serialization_funcs = \
|
|
3017
|
+
cls._de_pre_serialization_funcs_.copy()
|
|
3018
|
+
|
|
3019
|
+
return de_pre_serialization_funcs
|
|
3020
|
+
|
|
3021
|
+
|
|
3022
|
+
|
|
3023
|
+
def downsample(input_signal, optional_params=_default_optional_params):
|
|
3024
|
+
r"""Downsample a given input 2D ``hyperspy`` signal.
|
|
3025
|
+
|
|
3026
|
+
This Python function copies a given input 2D ``hyperspy`` signal and
|
|
3027
|
+
downsamples the copy along the axes in signal space. The Python function
|
|
3028
|
+
effectively does the following:
|
|
3029
|
+
|
|
3030
|
+
1. Groups the pixels of the copy of the input signal into so-called
|
|
3031
|
+
downsampling blocks along the axes in signal space, with dimensions
|
|
3032
|
+
determined by the parameter ``block_dims``, padding the copy with a constant
|
|
3033
|
+
value of ``padding_const`` in the case that either the horizontal or
|
|
3034
|
+
vertical dimensions of the signal space of the original input signal are not
|
|
3035
|
+
divisible by the corresponding dimensions of the downsampling blocks.
|
|
3036
|
+
|
|
3037
|
+
2. For each downsampling block, the Python function calls a ``numpy``
|
|
3038
|
+
function determined by the parameter ``downsample_mode``, wherein the input
|
|
3039
|
+
is the array data of the downsampling block, and the output is the value of
|
|
3040
|
+
the corresponding pixel of the downsampled signal.
|
|
3041
|
+
|
|
3042
|
+
Parameters
|
|
3043
|
+
----------
|
|
3044
|
+
input_signal : :class:`hyperspy._signals.signal2d.Signal2D` | :class:`hyperspy._signals.complex_signal2d.ComplexSignal2D`
|
|
3045
|
+
The input ``hyperspy`` signal.
|
|
3046
|
+
optional_params : :class:`empix.OptionalDownsamplingParams` | `None`, optional
|
|
3047
|
+
The set of optional parameters. See the documentation for the class
|
|
3048
|
+
:class:`empix.OptionalDownsamplingParams` for details. If
|
|
3049
|
+
``optional_params`` is set to ``None``, rather than an instance of
|
|
3050
|
+
:class:`empix.OptionalDownsamplingParams`, then the default values of
|
|
3051
|
+
the optional parameters are chosen.
|
|
3052
|
+
|
|
3053
|
+
Returns
|
|
3054
|
+
-------
|
|
3055
|
+
output_signal : :class:`hyperspy._signals.signal2d.Signal2D` | :class:`hyperspy._signals.complex_signal2d.ComplexSignal2D`
|
|
3056
|
+
The output ``hyperspy`` signal that results from the downsampling. Note
|
|
3057
|
+
that the metadata of the input signal is copied over to the output
|
|
3058
|
+
signal, with the title being overwritten.
|
|
3059
|
+
|
|
3060
|
+
"""
|
|
3061
|
+
params = locals()
|
|
3062
|
+
params["action_to_apply_to_input_signal"] = inspect.stack()[0][3]
|
|
3063
|
+
for param_name in params:
|
|
3064
|
+
func_name = "_check_and_convert_" + param_name
|
|
3065
|
+
func_alias = globals()[func_name]
|
|
3066
|
+
params[param_name] = func_alias(params)
|
|
3067
|
+
|
|
3068
|
+
func_name = "_" + inspect.stack()[0][3]
|
|
3069
|
+
func_alias = globals()[func_name]
|
|
3070
|
+
kwargs = params
|
|
3071
|
+
del kwargs["action_to_apply_to_input_signal"]
|
|
3072
|
+
output_signal = func_alias(**kwargs)
|
|
3073
|
+
|
|
3074
|
+
return output_signal
|
|
3075
|
+
|
|
3076
|
+
|
|
3077
|
+
|
|
3078
|
+
def _downsample(input_signal, optional_params):
|
|
3079
|
+
optional_params_core_attrs = optional_params.get_core_attrs(deep_copy=False)
|
|
3080
|
+
title = optional_params_core_attrs["title"]
|
|
3081
|
+
|
|
3082
|
+
navigation_dims = input_signal.data.shape[:-2]
|
|
3083
|
+
num_patterns = int(np.prod(navigation_dims))
|
|
3084
|
+
|
|
3085
|
+
for pattern_idx in range(0, num_patterns):
|
|
3086
|
+
navigation_indices = np.unravel_index(pattern_idx, navigation_dims)
|
|
3087
|
+
input_signal_datasubset = input_signal.data[navigation_indices]
|
|
3088
|
+
|
|
3089
|
+
kwargs = {"input_signal_datasubset": input_signal_datasubset,
|
|
3090
|
+
"optional_params": optional_params}
|
|
3091
|
+
output_signal_datasubset = _downsample_input_signal_datasubset(**kwargs)
|
|
3092
|
+
|
|
3093
|
+
if pattern_idx == 0:
|
|
3094
|
+
output_signal_data_shape = (navigation_dims
|
|
3095
|
+
+ output_signal_datasubset.shape)
|
|
3096
|
+
output_signal_data = np.zeros(output_signal_data_shape,
|
|
3097
|
+
dtype=input_signal.data.dtype)
|
|
3098
|
+
|
|
3099
|
+
output_signal_data[navigation_indices] = output_signal_datasubset
|
|
3100
|
+
|
|
3101
|
+
kwargs = {"data": output_signal_data,
|
|
3102
|
+
"metadata": input_signal.metadata.as_dictionary()}
|
|
3103
|
+
if np.isrealobj(output_signal_data):
|
|
3104
|
+
output_signal = hyperspy.signals.Signal2D(**kwargs)
|
|
3105
|
+
else:
|
|
3106
|
+
output_signal = hyperspy.signals.ComplexSignal2D(**kwargs)
|
|
3107
|
+
output_signal.metadata.set_item("General.title", title)
|
|
3108
|
+
|
|
3109
|
+
kwargs = {"input_signal": input_signal,
|
|
3110
|
+
"optional_params": optional_params,
|
|
3111
|
+
"bin_coords": None,
|
|
3112
|
+
"output_signal": output_signal}
|
|
3113
|
+
_update_output_signal_axes(**kwargs)
|
|
3114
|
+
|
|
3115
|
+
return output_signal
|
|
3116
|
+
|
|
3117
|
+
|
|
3118
|
+
|
|
3119
|
+
def _downsample_input_signal_datasubset(input_signal_datasubset,
|
|
3120
|
+
optional_params):
|
|
3121
|
+
optional_params_core_attrs = optional_params.get_core_attrs(deep_copy=False)
|
|
3122
|
+
block_dims = optional_params_core_attrs["block_dims"]
|
|
3123
|
+
padding_const = optional_params_core_attrs["padding_const"]
|
|
3124
|
+
downsample_mode = optional_params_core_attrs["downsample_mode"]
|
|
3125
|
+
|
|
3126
|
+
kwargs = {"image": input_signal_datasubset,
|
|
3127
|
+
"block_size": block_dims[::-1],
|
|
3128
|
+
"cval": padding_const,
|
|
3129
|
+
"func": getattr(np, downsample_mode)}
|
|
3130
|
+
downsampled_input_signal_datasubset = skimage.measure.block_reduce(**kwargs)
|
|
3131
|
+
|
|
3132
|
+
return downsampled_input_signal_datasubset
|
|
3133
|
+
|
|
3134
|
+
|
|
3135
|
+
|
|
3136
|
+
def _check_and_convert_new_signal_space_sizes(params):
|
|
3137
|
+
current_func_name = inspect.stack()[0][3]
|
|
3138
|
+
char_idx = 19
|
|
3139
|
+
obj_name = current_func_name[char_idx:]
|
|
3140
|
+
obj = params[obj_name]
|
|
3141
|
+
|
|
3142
|
+
param_name = "input_signal"
|
|
3143
|
+
input_signal = params.get(param_name, None)
|
|
3144
|
+
|
|
3145
|
+
if obj is not None:
|
|
3146
|
+
try:
|
|
3147
|
+
func_alias = czekitout.convert.to_pair_of_positive_ints
|
|
3148
|
+
kwargs = {"obj": obj, "obj_name": obj_name}
|
|
3149
|
+
new_signal_space_sizes = func_alias(**kwargs)
|
|
3150
|
+
except:
|
|
3151
|
+
err_msg = globals()[current_func_name+"_err_msg_1"]
|
|
3152
|
+
raise TypeError(err_msg)
|
|
3153
|
+
else:
|
|
3154
|
+
if input_signal is not None:
|
|
3155
|
+
N_v, N_h = input_signal.data.shape[-2:]
|
|
3156
|
+
new_signal_space_sizes = (N_h, N_v)
|
|
3157
|
+
else:
|
|
3158
|
+
new_signal_space_sizes = obj
|
|
3159
|
+
|
|
3160
|
+
return new_signal_space_sizes
|
|
3161
|
+
|
|
3162
|
+
|
|
3163
|
+
|
|
3164
|
+
def _pre_serialize_new_signal_space_sizes(new_signal_space_sizes):
|
|
3165
|
+
obj_to_pre_serialize = random.choice(list(locals().values()))
|
|
3166
|
+
serializable_rep = obj_to_pre_serialize
|
|
3167
|
+
|
|
3168
|
+
return serializable_rep
|
|
3169
|
+
|
|
3170
|
+
|
|
3171
|
+
|
|
3172
|
+
def _de_pre_serialize_new_signal_space_sizes(serializable_rep):
|
|
3173
|
+
new_signal_space_sizes = serializable_rep
|
|
3174
|
+
|
|
3175
|
+
return new_signal_space_sizes
|
|
3176
|
+
|
|
3177
|
+
|
|
3178
|
+
|
|
3179
|
+
def _check_and_convert_new_signal_space_scales(params):
|
|
3180
|
+
current_func_name = inspect.stack()[0][3]
|
|
3181
|
+
char_idx = 19
|
|
3182
|
+
obj_name = current_func_name[char_idx:]
|
|
3183
|
+
obj = params[obj_name]
|
|
3184
|
+
|
|
3185
|
+
param_name = "input_signal"
|
|
3186
|
+
input_signal = params.get(param_name, None)
|
|
3187
|
+
|
|
3188
|
+
if obj is not None:
|
|
3189
|
+
try:
|
|
3190
|
+
func_alias = czekitout.convert.to_pair_of_floats
|
|
3191
|
+
kwargs = {"obj": obj, "obj_name": obj_name}
|
|
3192
|
+
new_signal_space_scales = func_alias(**kwargs)
|
|
3193
|
+
except:
|
|
3194
|
+
err_msg = globals()[current_func_name+"_err_msg_1"]
|
|
3195
|
+
raise TypeError(err_msg)
|
|
3196
|
+
|
|
3197
|
+
if np.prod(new_signal_space_scales) == 0:
|
|
3198
|
+
err_msg = globals()[current_func_name+"_err_msg_1"]
|
|
3199
|
+
raise ValueError(err_msg)
|
|
3200
|
+
else:
|
|
3201
|
+
if input_signal is not None:
|
|
3202
|
+
new_signal_space_scales = (input_signal.axes_manager[-2].scale,
|
|
3203
|
+
input_signal.axes_manager[-1].scale)
|
|
3204
|
+
else:
|
|
3205
|
+
new_signal_space_scales = obj
|
|
3206
|
+
|
|
3207
|
+
return new_signal_space_scales
|
|
3208
|
+
|
|
3209
|
+
|
|
3210
|
+
|
|
3211
|
+
def _pre_serialize_new_signal_space_scales(new_signal_space_scales):
|
|
3212
|
+
obj_to_pre_serialize = random.choice(list(locals().values()))
|
|
3213
|
+
serializable_rep = obj_to_pre_serialize
|
|
3214
|
+
|
|
3215
|
+
return serializable_rep
|
|
3216
|
+
|
|
3217
|
+
|
|
3218
|
+
|
|
3219
|
+
def _de_pre_serialize_new_signal_space_scales(serializable_rep):
|
|
3220
|
+
new_signal_space_scales = serializable_rep
|
|
3221
|
+
|
|
3222
|
+
return new_signal_space_scales
|
|
3223
|
+
|
|
3224
|
+
|
|
3225
|
+
|
|
3226
|
+
def _check_and_convert_new_signal_space_offsets(params):
|
|
3227
|
+
current_func_name = inspect.stack()[0][3]
|
|
3228
|
+
char_idx = 19
|
|
3229
|
+
obj_name = current_func_name[char_idx:]
|
|
3230
|
+
obj = params[obj_name]
|
|
3231
|
+
|
|
3232
|
+
param_name = "input_signal"
|
|
3233
|
+
input_signal = params.get(param_name, None)
|
|
3234
|
+
|
|
3235
|
+
if obj is not None:
|
|
3236
|
+
try:
|
|
3237
|
+
func_alias = czekitout.convert.to_pair_of_floats
|
|
3238
|
+
kwargs = {"obj": obj, "obj_name": obj_name}
|
|
3239
|
+
new_signal_space_offsets = func_alias(**kwargs)
|
|
3240
|
+
except:
|
|
3241
|
+
err_msg = globals()[current_func_name+"_err_msg_1"]
|
|
3242
|
+
raise TypeError(err_msg)
|
|
3243
|
+
else:
|
|
3244
|
+
if input_signal is not None:
|
|
3245
|
+
new_signal_space_offsets = (input_signal.axes_manager[-2].offset,
|
|
3246
|
+
input_signal.axes_manager[-1].offset)
|
|
3247
|
+
else:
|
|
3248
|
+
new_signal_space_offsets = obj
|
|
3249
|
+
|
|
3250
|
+
return new_signal_space_offsets
|
|
3251
|
+
|
|
3252
|
+
|
|
3253
|
+
|
|
3254
|
+
def _pre_serialize_new_signal_space_offsets(new_signal_space_offsets):
|
|
3255
|
+
obj_to_pre_serialize = random.choice(list(locals().values()))
|
|
3256
|
+
serializable_rep = obj_to_pre_serialize
|
|
3257
|
+
|
|
3258
|
+
return serializable_rep
|
|
3259
|
+
|
|
3260
|
+
|
|
3261
|
+
|
|
3262
|
+
def _de_pre_serialize_new_signal_space_offsets(serializable_rep):
|
|
3263
|
+
new_signal_space_offsets = serializable_rep
|
|
3264
|
+
|
|
3265
|
+
return new_signal_space_offsets
|
|
3266
|
+
|
|
3267
|
+
|
|
3268
|
+
|
|
3269
|
+
def _check_and_convert_spline_degrees(params):
|
|
3270
|
+
current_func_name = inspect.stack()[0][3]
|
|
3271
|
+
char_idx = 19
|
|
3272
|
+
obj_name = current_func_name[char_idx:]
|
|
3273
|
+
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
|
|
3274
|
+
spline_degrees = czekitout.convert.to_pair_of_positive_ints(**kwargs)
|
|
3275
|
+
|
|
3276
|
+
if (spline_degrees[0] > 5) or (spline_degrees[1] > 5):
|
|
3277
|
+
err_msg = globals()[current_func_name+"_err_msg_1"]
|
|
3278
|
+
raise ValueError(err_msg)
|
|
3279
|
+
|
|
3280
|
+
return spline_degrees
|
|
3281
|
+
|
|
3282
|
+
|
|
3283
|
+
|
|
3284
|
+
def _pre_serialize_spline_degrees(spline_degrees):
|
|
3285
|
+
obj_to_pre_serialize = random.choice(list(locals().values()))
|
|
3286
|
+
serializable_rep = obj_to_pre_serialize
|
|
3287
|
+
|
|
3288
|
+
return serializable_rep
|
|
3289
|
+
|
|
3290
|
+
|
|
3291
|
+
|
|
3292
|
+
def _de_pre_serialize_spline_degrees(serializable_rep):
|
|
3293
|
+
spline_degrees = serializable_rep
|
|
3294
|
+
|
|
3295
|
+
return spline_degrees
|
|
3296
|
+
|
|
3297
|
+
|
|
3298
|
+
|
|
3299
|
+
def _check_and_convert_interpolate_polar_cmpnts(params):
|
|
3300
|
+
current_func_name = inspect.stack()[0][3]
|
|
3301
|
+
char_idx = 19
|
|
3302
|
+
obj_name = current_func_name[char_idx:]
|
|
3303
|
+
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
|
|
3304
|
+
interpolate_polar_cmpnts = czekitout.convert.to_bool(**kwargs)
|
|
3305
|
+
|
|
3306
|
+
return interpolate_polar_cmpnts
|
|
3307
|
+
|
|
3308
|
+
|
|
3309
|
+
|
|
3310
|
+
def _pre_serialize_interpolate_polar_cmpnts(interpolate_polar_cmpnts):
|
|
3311
|
+
obj_to_pre_serialize = random.choice(list(locals().values()))
|
|
3312
|
+
serializable_rep = obj_to_pre_serialize
|
|
3313
|
+
|
|
3314
|
+
return serializable_rep
|
|
3315
|
+
|
|
3316
|
+
|
|
3317
|
+
|
|
3318
|
+
def _de_pre_serialize_interpolate_polar_cmpnts(serializable_rep):
|
|
3319
|
+
interpolate_polar_cmpnts = serializable_rep
|
|
3320
|
+
|
|
3321
|
+
return interpolate_polar_cmpnts
|
|
3322
|
+
|
|
3323
|
+
|
|
3324
|
+
|
|
3325
|
+
_default_new_signal_space_sizes = None
|
|
3326
|
+
_default_new_signal_space_scales = None
|
|
3327
|
+
_default_new_signal_space_offsets = None
|
|
3328
|
+
_default_spline_degrees = (3, 3)
|
|
3329
|
+
_default_interpolate_polar_cmpnts = True
|
|
3330
|
+
|
|
3331
|
+
|
|
3332
|
+
|
|
3333
|
+
_cls_alias = fancytypes.PreSerializableAndUpdatable
|
|
3334
|
+
class OptionalResamplingParams(fancytypes.PreSerializableAndUpdatable):
|
|
3335
|
+
r"""The set of optional parameters for the function :func:`empix.resample`.
|
|
3336
|
+
|
|
3337
|
+
The Python function :func:`empix.resample` copies a given input 2D
|
|
3338
|
+
``hyperspy`` signal and resamples the copy along the axes in signal space by
|
|
3339
|
+
interpolating the original input signal using bivariate spines. Effectively,
|
|
3340
|
+
:func:`empix.resample` resamples the input signal.
|
|
3341
|
+
|
|
3342
|
+
Parameters
|
|
3343
|
+
----------
|
|
3344
|
+
new_signal_space_sizes : array_like` (`int`, shape=(2,)) | `None`, optional
|
|
3345
|
+
If ``new_signal_space_sizes`` is set to ``None``, then
|
|
3346
|
+
``output_signal.data.shape`` will be equal to
|
|
3347
|
+
``input_signal.data.shape``, where ``input_signal`` is the input signal,
|
|
3348
|
+
and ``output_signal`` is the output signal to result from the
|
|
3349
|
+
resampling. Otherwise, if ``new_signal_space_sizes`` is set to a pair of
|
|
3350
|
+
positive integers, then ``output_signal.data.shape[-1]`` and
|
|
3351
|
+
``output_signal.data.shape[-2]`` will be equal to
|
|
3352
|
+
``new_signal_space_sizes[0]`` and ``new_signal_space_sizes[1]``
|
|
3353
|
+
respectively.
|
|
3354
|
+
new_signal_space_scales : `array_like` (`float`, shape=(2,)) | `None`, optional
|
|
3355
|
+
Continuing from above, if ``new_signal_space_scales`` is set to
|
|
3356
|
+
``None``, then ``output_signal.axes_manager[-1].scale`` and
|
|
3357
|
+
``output_signal.axes_manager[-2].scale`` will be equal to
|
|
3358
|
+
``input_signal.axes_manager[-1].scale`` and
|
|
3359
|
+
``input_signal.axes_manager[-2].scale`` respectively. If
|
|
3360
|
+
``new_signal_space_scales`` is set to a pair of non-zero floating-point
|
|
3361
|
+
numbers, then ``output_signal.axes_manager[-1].scale`` and
|
|
3362
|
+
``output_signal.axes_manager[-2].scale`` will be equal to
|
|
3363
|
+
``new_signal_space_scales[0]`` and ``new_signal_space_scales[1]``
|
|
3364
|
+
respectively. Otherwise, an error is raised.
|
|
3365
|
+
new_signal_space_offsets : `array_like` (`float`, shape=(2,)) | `None`, optional
|
|
3366
|
+
Continuing from above, if ``new_signal_space_offsets`` is set to
|
|
3367
|
+
``None``, then ``output_signal.axes_manager[-1].offset`` and
|
|
3368
|
+
``output_signal.axes_manager[-2].offset`` will be equal to
|
|
3369
|
+
``input_signal.axes_manager[-1].offset`` and
|
|
3370
|
+
``input_signal.axes_manager[-2].offset`` respectively. Otherwise, if
|
|
3371
|
+
``new_signal_space_offsets`` is set to a pair of floating-point numbers,
|
|
3372
|
+
then ``output_signal.axes_manager[-1].offset`` and
|
|
3373
|
+
``output_signal.axes_manager[-2].offset`` will be equal to
|
|
3374
|
+
``new_signal_space_offsets[0]`` and ``new_signal_space_offsets[1]``
|
|
3375
|
+
respectively.
|
|
3376
|
+
spline_degrees : `array_like` (`int`, shape=(2,)), optional
|
|
3377
|
+
``spline_degrees[0]`` and ``spline_degrees[1]`` are the horizontal and
|
|
3378
|
+
vertical degrees of the bivariate splines used to interpolate the input
|
|
3379
|
+
signal. Note that ``spline_degrees`` is expected to satisfy both
|
|
3380
|
+
``1<=spline_degrees[0]<=5`` and ``1<=spline_degrees[1]<=5``.
|
|
3381
|
+
interpolate_polar_cmpnts : `bool`, optional
|
|
3382
|
+
If ``interpolate_polar_cmpnts`` is set to ``True``, then the polar
|
|
3383
|
+
components of the input signal are separately interpolated. Otherwise,
|
|
3384
|
+
if ``interpolate_polar_cmpnts`` is set to ``False``, then the real and
|
|
3385
|
+
imaginary components of the input signal are separately interpolated.
|
|
3386
|
+
Note that if the input signal is real-valued, then this parameter is
|
|
3387
|
+
effectively ignored.
|
|
3388
|
+
title : `str` | `None`, optional
|
|
3389
|
+
If ``title`` is set to ``None``, then the title of the output signal
|
|
3390
|
+
``output_signal`` is set to ``"Resampled "+
|
|
3391
|
+
input_signal.metadata.General.title``, where ``input_signal`` is the
|
|
3392
|
+
input ``hyperspy`` signal. Otherwise, if ``title`` is a `str`, then the
|
|
3393
|
+
``output_signal.metadata.General.title`` is set to the value of
|
|
3394
|
+
``title``.
|
|
3395
|
+
skip_validation_and_conversion : `bool`, optional
|
|
3396
|
+
Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
|
|
3397
|
+
attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
|
|
3398
|
+
and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
|
|
3399
|
+
being `dict` objects.
|
|
3400
|
+
|
|
3401
|
+
Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
|
|
3402
|
+
representation of the constructor parameters excluding the parameter
|
|
3403
|
+
``skip_validation_and_conversion``, where each `dict` key ``key`` is a
|
|
3404
|
+
different constructor parameter name, excluding the name
|
|
3405
|
+
``"skip_validation_and_conversion"``, and
|
|
3406
|
+
``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
|
|
3407
|
+
constructor parameter with the name given by ``key``.
|
|
3408
|
+
|
|
3409
|
+
If ``skip_validation_and_conversion`` is set to ``False``, then for each
|
|
3410
|
+
key ``key`` in ``params_to_be_mapped_to_core_attrs``,
|
|
3411
|
+
``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
|
|
3412
|
+
(params_to_be_mapped_to_core_attrs)``.
|
|
3413
|
+
|
|
3414
|
+
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
|
|
3415
|
+
then ``core_attrs`` is set to
|
|
3416
|
+
``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
|
|
3417
|
+
primarily when the user wants to avoid potentially expensive deep copies
|
|
3418
|
+
and/or conversions of the `dict` values of
|
|
3419
|
+
``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
|
|
3420
|
+
copies or conversions are made in this case.
|
|
3421
|
+
|
|
3422
|
+
"""
|
|
3423
|
+
ctor_param_names = ("new_signal_space_sizes",
|
|
3424
|
+
"new_signal_space_scales",
|
|
3425
|
+
"new_signal_space_offsets",
|
|
3426
|
+
"spline_degrees",
|
|
3427
|
+
"interpolate_polar_cmpnts",
|
|
3428
|
+
"title")
|
|
3429
|
+
kwargs = {"namespace_as_dict": globals(),
|
|
3430
|
+
"ctor_param_names": ctor_param_names}
|
|
3431
|
+
|
|
3432
|
+
_validation_and_conversion_funcs_ = \
|
|
3433
|
+
fancytypes.return_validation_and_conversion_funcs(**kwargs)
|
|
3434
|
+
_pre_serialization_funcs_ = \
|
|
3435
|
+
fancytypes.return_pre_serialization_funcs(**kwargs)
|
|
3436
|
+
_de_pre_serialization_funcs_ = \
|
|
3437
|
+
fancytypes.return_de_pre_serialization_funcs(**kwargs)
|
|
3438
|
+
|
|
3439
|
+
del ctor_param_names, kwargs
|
|
3440
|
+
|
|
3441
|
+
|
|
3442
|
+
|
|
3443
|
+
def __init__(self,
|
|
3444
|
+
new_signal_space_sizes=\
|
|
3445
|
+
_default_new_signal_space_sizes,
|
|
3446
|
+
new_signal_space_scales=\
|
|
3447
|
+
_default_new_signal_space_scales,
|
|
3448
|
+
new_signal_space_offsets=\
|
|
3449
|
+
_default_new_signal_space_offsets,
|
|
3450
|
+
spline_degrees=\
|
|
3451
|
+
_default_spline_degrees,
|
|
3452
|
+
interpolate_polar_cmpnts=\
|
|
3453
|
+
_default_interpolate_polar_cmpnts,
|
|
3454
|
+
title=\
|
|
3455
|
+
_default_title,
|
|
3456
|
+
skip_validation_and_conversion=\
|
|
3457
|
+
_default_skip_validation_and_conversion):
|
|
3458
|
+
ctor_params = {key: val
|
|
3459
|
+
for key, val in locals().items()
|
|
3460
|
+
if (key not in ("self", "__class__"))}
|
|
3461
|
+
kwargs = ctor_params
|
|
3462
|
+
kwargs["skip_cls_tests"] = True
|
|
3463
|
+
fancytypes.PreSerializableAndUpdatable.__init__(self, **kwargs)
|
|
3464
|
+
|
|
3465
|
+
return None
|
|
3466
|
+
|
|
3467
|
+
|
|
3468
|
+
|
|
3469
|
+
@classmethod
|
|
3470
|
+
def get_validation_and_conversion_funcs(cls):
|
|
3471
|
+
validation_and_conversion_funcs = \
|
|
3472
|
+
cls._validation_and_conversion_funcs_.copy()
|
|
3473
|
+
|
|
3474
|
+
return validation_and_conversion_funcs
|
|
3475
|
+
|
|
3476
|
+
|
|
3477
|
+
|
|
3478
|
+
@classmethod
|
|
3479
|
+
def get_pre_serialization_funcs(cls):
|
|
3480
|
+
pre_serialization_funcs = \
|
|
3481
|
+
cls._pre_serialization_funcs_.copy()
|
|
3482
|
+
|
|
3483
|
+
return pre_serialization_funcs
|
|
3484
|
+
|
|
3485
|
+
|
|
3486
|
+
|
|
3487
|
+
@classmethod
|
|
3488
|
+
def get_de_pre_serialization_funcs(cls):
|
|
3489
|
+
de_pre_serialization_funcs = \
|
|
3490
|
+
cls._de_pre_serialization_funcs_.copy()
|
|
3491
|
+
|
|
3492
|
+
return de_pre_serialization_funcs
|
|
3493
|
+
|
|
3494
|
+
|
|
3495
|
+
|
|
3496
|
+
def resample(input_signal, optional_params=_default_optional_params):
|
|
3497
|
+
r"""Resample a given input 2D ``hyperspy`` signal via interpolation.
|
|
3498
|
+
|
|
3499
|
+
This Python function copies a given input 2D ``hyperspy`` signal and
|
|
3500
|
+
resamples the copy along the axes in signal space by interpolating the
|
|
3501
|
+
original input signal using bivariate spines. Effectively,
|
|
3502
|
+
:func:`empix.resample` resamples the input signal.
|
|
3503
|
+
|
|
3504
|
+
Parameters
|
|
3505
|
+
----------
|
|
3506
|
+
input_signal : :class:`hyperspy._signals.signal2d.Signal2D` | :class:`hyperspy._signals.complex_signal2d.ComplexSignal2D`
|
|
3507
|
+
The input ``hyperspy`` signal.
|
|
3508
|
+
optional_params : :class:`empix.OptionalResamplingParams` | `None`, optional
|
|
3509
|
+
The set of optional parameters. See the documentation for the class
|
|
3510
|
+
:class:`empix.OptionalResamplingParams` for details. If
|
|
3511
|
+
``optional_params`` is set to ``None``, rather than an instance of
|
|
3512
|
+
:class:`empix.OptionalResamplingParams`, then the default values of the
|
|
3513
|
+
optional parameters are chosen.
|
|
3514
|
+
|
|
3515
|
+
Returns
|
|
3516
|
+
-------
|
|
3517
|
+
output_signal : :class:`hyperspy._signals.signal2d.Signal2D` | :class:`hyperspy._signals.complex_signal2d.ComplexSignal2D`
|
|
3518
|
+
The output ``hyperspy`` signal that results from the resampling. Note
|
|
3519
|
+
that the metadata of the input signal is copied over to the output
|
|
3520
|
+
signal, with the title being overwritten.
|
|
3521
|
+
|
|
3522
|
+
"""
|
|
3523
|
+
params = locals()
|
|
3524
|
+
params["action_to_apply_to_input_signal"] = inspect.stack()[0][3]
|
|
3525
|
+
for param_name in params:
|
|
3526
|
+
func_name = "_check_and_convert_" + param_name
|
|
3527
|
+
func_alias = globals()[func_name]
|
|
3528
|
+
params[param_name] = func_alias(params)
|
|
3529
|
+
|
|
3530
|
+
func_name = "_" + inspect.stack()[0][3]
|
|
3531
|
+
func_alias = globals()[func_name]
|
|
3532
|
+
kwargs = params
|
|
3533
|
+
del kwargs["action_to_apply_to_input_signal"]
|
|
3534
|
+
output_signal = func_alias(**kwargs)
|
|
3535
|
+
|
|
3536
|
+
return output_signal
|
|
3537
|
+
|
|
3538
|
+
|
|
3539
|
+
|
|
3540
|
+
def _resample(input_signal, optional_params):
|
|
3541
|
+
optional_params_core_attrs = optional_params.get_core_attrs(deep_copy=False)
|
|
3542
|
+
title = optional_params_core_attrs["title"]
|
|
3543
|
+
|
|
3544
|
+
func_alias = _calc_input_signal_datasubset_resampling_params
|
|
3545
|
+
input_signal_datasubset_resampling_params = func_alias(input_signal,
|
|
3546
|
+
optional_params)
|
|
3547
|
+
|
|
3548
|
+
navigation_dims = input_signal.data.shape[:-2]
|
|
3549
|
+
num_patterns = int(np.prod(navigation_dims))
|
|
3550
|
+
|
|
3551
|
+
for pattern_idx in range(0, num_patterns):
|
|
3552
|
+
navigation_indices = np.unravel_index(pattern_idx, navigation_dims)
|
|
3553
|
+
input_signal_datasubset = input_signal.data[navigation_indices]
|
|
3554
|
+
|
|
3555
|
+
kwargs = {"input_signal_datasubset": \
|
|
3556
|
+
input_signal_datasubset,
|
|
3557
|
+
"input_signal_datasubset_resampling_params": \
|
|
3558
|
+
input_signal_datasubset_resampling_params}
|
|
3559
|
+
output_signal_datasubset = _resample_input_signal_datasubset(**kwargs)
|
|
3560
|
+
|
|
3561
|
+
if pattern_idx == 0:
|
|
3562
|
+
output_signal_data_shape = (navigation_dims
|
|
3563
|
+
+ output_signal_datasubset.shape)
|
|
3564
|
+
output_signal_data = np.zeros(output_signal_data_shape,
|
|
3565
|
+
dtype=input_signal.data.dtype)
|
|
3566
|
+
|
|
3567
|
+
output_signal_data[navigation_indices] = output_signal_datasubset
|
|
3568
|
+
|
|
3569
|
+
kwargs = {"data": output_signal_data,
|
|
3570
|
+
"metadata": input_signal.metadata.as_dictionary()}
|
|
3571
|
+
if np.isrealobj(output_signal_data):
|
|
3572
|
+
output_signal = hyperspy.signals.Signal2D(**kwargs)
|
|
3573
|
+
else:
|
|
3574
|
+
output_signal = hyperspy.signals.ComplexSignal2D(**kwargs)
|
|
3575
|
+
output_signal.metadata.set_item("General.title", title)
|
|
3576
|
+
|
|
3577
|
+
kwargs = {"input_signal": input_signal,
|
|
3578
|
+
"optional_params": optional_params,
|
|
3579
|
+
"bin_coords": None,
|
|
3580
|
+
"output_signal": output_signal}
|
|
3581
|
+
_update_output_signal_axes(**kwargs)
|
|
3582
|
+
|
|
3583
|
+
return output_signal
|
|
3584
|
+
|
|
3585
|
+
|
|
3586
|
+
|
|
3587
|
+
def _calc_input_signal_datasubset_resampling_params(input_signal,
|
|
3588
|
+
optional_params):
|
|
3589
|
+
old_sizes = [input_signal.axes_manager[idx].size for idx in (-2, -1)]
|
|
3590
|
+
old_scales = [input_signal.axes_manager[idx].scale for idx in (-2, -1)]
|
|
3591
|
+
old_offsets = [input_signal.axes_manager[idx].offset for idx in (-2, -1)]
|
|
3592
|
+
|
|
3593
|
+
optional_params_core_attrs = optional_params.get_core_attrs(deep_copy=False)
|
|
3594
|
+
new_sizes = optional_params_core_attrs["new_signal_space_sizes"]
|
|
3595
|
+
new_scales = optional_params_core_attrs["new_signal_space_scales"]
|
|
3596
|
+
new_offsets = optional_params_core_attrs["new_signal_space_offsets"]
|
|
3597
|
+
|
|
3598
|
+
h_old = np.sign(old_scales[0]) * (old_offsets[0]
|
|
3599
|
+
+ old_scales[0]*np.arange(old_sizes[0]))
|
|
3600
|
+
v_old = np.sign(old_scales[1]) * (old_offsets[1]
|
|
3601
|
+
+ old_scales[1]*np.arange(old_sizes[1]))
|
|
3602
|
+
|
|
3603
|
+
h_new = np.sign(old_scales[0]) * (new_offsets[0]
|
|
3604
|
+
+ new_scales[0]*np.arange(new_sizes[0]))
|
|
3605
|
+
v_new = np.sign(old_scales[1]) * (new_offsets[1]
|
|
3606
|
+
+ new_scales[1]*np.arange(new_sizes[1]))
|
|
3607
|
+
|
|
3608
|
+
s_h_new = int(np.sign(h_new[1]-h_new[0]))
|
|
3609
|
+
s_v_new = int(np.sign(v_new[1]-v_new[0]))
|
|
3610
|
+
|
|
3611
|
+
h_new = np.sort(h_new)
|
|
3612
|
+
v_new = np.sort(v_new)
|
|
3613
|
+
|
|
3614
|
+
spline_degrees = \
|
|
3615
|
+
optional_params_core_attrs["spline_degrees"]
|
|
3616
|
+
interpolate_polar_cmpnts = \
|
|
3617
|
+
optional_params_core_attrs["interpolate_polar_cmpnts"]
|
|
3618
|
+
|
|
3619
|
+
input_signal_datasubset_resampling_params = \
|
|
3620
|
+
{"h_old": h_old,
|
|
3621
|
+
"v_old": v_old,
|
|
3622
|
+
"h_new": h_new,
|
|
3623
|
+
"v_new": v_new,
|
|
3624
|
+
"s_h_new": int(np.sign(h_new[1]-h_new[0])),
|
|
3625
|
+
"s_v_new": int(np.sign(v_new[1]-v_new[0])),
|
|
3626
|
+
"spline_degrees": spline_degrees,
|
|
3627
|
+
"interpolate_polar_cmpnts": interpolate_polar_cmpnts}
|
|
3628
|
+
|
|
3629
|
+
return input_signal_datasubset_resampling_params
|
|
3630
|
+
|
|
3631
|
+
|
|
3632
|
+
|
|
3633
|
+
def _resample_input_signal_datasubset(
|
|
3634
|
+
input_signal_datasubset,
|
|
3635
|
+
input_signal_datasubset_resampling_params):
|
|
3636
|
+
kwargs = \
|
|
3637
|
+
{"x": input_signal_datasubset_resampling_params["v_old"],
|
|
3638
|
+
"y": input_signal_datasubset_resampling_params["h_old"],
|
|
3639
|
+
"z": None,
|
|
3640
|
+
"bbox": [None, None, None, None],
|
|
3641
|
+
"kx": input_signal_datasubset_resampling_params["spline_degrees"][1],
|
|
3642
|
+
"ky": input_signal_datasubset_resampling_params["spline_degrees"][0],
|
|
3643
|
+
"s": 0}
|
|
3644
|
+
|
|
3645
|
+
v_new = \
|
|
3646
|
+
input_signal_datasubset_resampling_params["v_new"]
|
|
3647
|
+
h_new = \
|
|
3648
|
+
input_signal_datasubset_resampling_params["h_new"]
|
|
3649
|
+
polar_cmpnts_are_to_be_interpolated = \
|
|
3650
|
+
input_signal_datasubset_resampling_params["interpolate_polar_cmpnts"]
|
|
3651
|
+
|
|
3652
|
+
if np.isrealobj(input_signal_datasubset):
|
|
3653
|
+
kwargs["z"] = input_signal_datasubset
|
|
3654
|
+
method_alias = scipy.interpolate.RectBivariateSpline(**kwargs)
|
|
3655
|
+
resampled_input_signal_datasubset = method_alias(v_new, h_new)
|
|
3656
|
+
else:
|
|
3657
|
+
cmpnts = tuple()
|
|
3658
|
+
np_funcs = ((np.abs, np.angle)
|
|
3659
|
+
if polar_cmpnts_are_to_be_interpolated
|
|
3660
|
+
else (np.real, np.imag))
|
|
3661
|
+
|
|
3662
|
+
for np_func in np_funcs:
|
|
3663
|
+
kwargs["z"] = np_func(input_signal_datasubset)
|
|
3664
|
+
method_alias = scipy.interpolate.RectBivariateSpline(**kwargs)
|
|
3665
|
+
cmpnts += (method_alias(v_new, h_new),)
|
|
3666
|
+
|
|
3667
|
+
resampled_input_signal_datasubset = (cmpnts[0] * np.exp(1j*cmpnts[1])
|
|
3668
|
+
if (np_funcs[0] == np.abs)
|
|
3669
|
+
else cmpnts[0] + 1j*cmpnts[1])
|
|
3670
|
+
|
|
3671
|
+
s_h_new = input_signal_datasubset_resampling_params["s_h_new"]
|
|
3672
|
+
s_v_new = input_signal_datasubset_resampling_params["s_v_new"]
|
|
3673
|
+
|
|
3674
|
+
resampled_input_signal_datasubset[:, :] = \
|
|
3675
|
+
resampled_input_signal_datasubset[::s_v_new, ::s_h_new]
|
|
3676
|
+
|
|
3677
|
+
return resampled_input_signal_datasubset
|
|
3678
|
+
|
|
3679
|
+
|
|
3680
|
+
|
|
3681
|
+
###########################
|
|
3682
|
+
## Define error messages ##
|
|
3683
|
+
###########################
|
|
3684
|
+
|
|
3685
|
+
_check_and_convert_center_err_msg_1 = \
|
|
3686
|
+
("The object ``center`` must be `NoneType` or a pair of real numbers.")
|
|
3687
|
+
_check_and_convert_center_err_msg_2 = \
|
|
3688
|
+
("The object ``center`` must specify a point within the boundaries of the "
|
|
3689
|
+
"input ``hyperspy`` signal.")
|
|
3690
|
+
|
|
3691
|
+
_check_and_convert_radial_range_err_msg_1 = \
|
|
3692
|
+
("The object ``radial_range`` must be `NoneType` or a pair of non-negative "
|
|
3693
|
+
"real numbers satisfying ``radial_range[0]<radial_range[1]``.")
|
|
3694
|
+
|
|
3695
|
+
_check_and_convert_num_bins_err_msg_1 = \
|
|
3696
|
+
("The object ``num_bins`` must be `NoneType` or a positive `int`.")
|
|
3697
|
+
|
|
3698
|
+
_check_and_convert_limits_err_msg_1 = \
|
|
3699
|
+
("The object ``limits`` must be `NoneType` or a pair of distinct real "
|
|
3700
|
+
"numbers.")
|
|
3701
|
+
|
|
3702
|
+
_check_and_convert_window_dims_err_msg_1 = \
|
|
3703
|
+
("The object ``window_dims`` must be `NoneType` or a pair of positive "
|
|
3704
|
+
"integers.")
|
|
3705
|
+
|
|
3706
|
+
_crop_err_msg_1 = \
|
|
3707
|
+
("The object ``optional_params`` specifies a crop that yields an output "
|
|
3708
|
+
"``hyperspy`` signal with zero elements.")
|
|
3709
|
+
|
|
3710
|
+
_check_and_convert_downsample_mode_err_msg_1 = \
|
|
3711
|
+
("The object ``downsample_mode`` must be either ``'sum'``, ``'mean'``, "
|
|
3712
|
+
"``'median'``, ``'amin'``, or ``'amax'``.")
|
|
3713
|
+
|
|
3714
|
+
_downsample_err_msg_1 = \
|
|
3715
|
+
("The object ``optional_params`` must be `NoneType` or an instance of the "
|
|
3716
|
+
"class `OptionalDownsamplingParams`.")
|
|
3717
|
+
|
|
3718
|
+
_check_and_convert_new_signal_space_sizes_err_msg_1 = \
|
|
3719
|
+
("The object ``new_signal_space_sizes`` must be `NoneType` or a pair of "
|
|
3720
|
+
"positive integers.")
|
|
3721
|
+
|
|
3722
|
+
_check_and_convert_new_signal_space_scales_err_msg_1 = \
|
|
3723
|
+
("The object ``new_signal_space_scales`` must be `NoneType` or a pair of "
|
|
3724
|
+
"non-zero real numbers.")
|
|
3725
|
+
|
|
3726
|
+
_check_and_convert_new_signal_space_offsets_err_msg_1 = \
|
|
3727
|
+
("The object ``new_signal_space_offsets`` must be `NoneType` or a pair of "
|
|
3728
|
+
"real numbers.")
|
|
3729
|
+
|
|
3730
|
+
_check_and_convert_spline_degrees_err_msg_1 = \
|
|
3731
|
+
("The object ``spline_degrees`` must be a pair of positive integers where "
|
|
3732
|
+
"each integer is less than or equal to ``5``.")
|