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 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``.")