fakecbed 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.
@@ -0,0 +1,2438 @@
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
+ """For creating discretized fake CBED patterns.
15
+
16
+ """
17
+
18
+
19
+
20
+ #####################################
21
+ ## Load libraries/packages/modules ##
22
+ #####################################
23
+
24
+ # For accessing attributes of functions.
25
+ import inspect
26
+
27
+ # For randomly selecting items in dictionaries.
28
+ import random
29
+
30
+ # For performing deep copies.
31
+ import copy
32
+
33
+
34
+
35
+ # For general array handling.
36
+ import numpy as np
37
+ import torch
38
+
39
+ # For validating and converting objects.
40
+ import czekitout.check
41
+ import czekitout.convert
42
+
43
+ # For defining classes that support enforced validation, updatability,
44
+ # pre-serialization, and de-serialization.
45
+ import fancytypes
46
+
47
+ # For creating hyperspy signals and axes.
48
+ import hyperspy.signals
49
+ import hyperspy.axes
50
+
51
+ # For creating distortion models.
52
+ import distoptica
53
+
54
+ # For inpainting images.
55
+ import skimage.restoration
56
+
57
+
58
+
59
+ # For creating undistorted geometric shapes.
60
+ import fakecbed.shapes
61
+
62
+ # For creating undistorted thermal diffuse models.
63
+ import fakecbed.tds
64
+
65
+
66
+
67
+ ##################################
68
+ ## Define classes and functions ##
69
+ ##################################
70
+
71
+ # List of public objects in module.
72
+ __all__ = ["CBEDPattern"]
73
+
74
+
75
+
76
+ def _check_and_convert_undistorted_tds_model(params):
77
+ current_func_name = inspect.stack()[0][3]
78
+ char_idx = 19
79
+ obj_name = current_func_name[char_idx:]
80
+ obj = params[obj_name]
81
+
82
+ accepted_types = (fakecbed.tds.Model, type(None))
83
+
84
+ if isinstance(obj, accepted_types[-1]):
85
+ undistorted_tds_model = accepted_types[0]()
86
+ else:
87
+ kwargs = {"obj": obj,
88
+ "obj_name": obj_name,
89
+ "accepted_types": accepted_types}
90
+ czekitout.check.if_instance_of_any_accepted_types(**kwargs)
91
+ undistorted_tds_model = copy.deepcopy(obj)
92
+
93
+ return undistorted_tds_model
94
+
95
+
96
+
97
+ def _pre_serialize_undistorted_tds_model(undistorted_tds_model):
98
+ obj_to_pre_serialize = random.choice(list(locals().values()))
99
+ serializable_rep = obj_to_pre_serialize.pre_serialize()
100
+
101
+ return serializable_rep
102
+
103
+
104
+
105
+ def _de_pre_serialize_undistorted_tds_model(serializable_rep):
106
+ undistorted_tds_model = \
107
+ fakecbed.tds.Model.de_pre_serialize(serializable_rep)
108
+
109
+ return undistorted_tds_model
110
+
111
+
112
+
113
+ def _check_and_convert_undistorted_disks(params):
114
+ current_func_name = inspect.stack()[0][3]
115
+ char_idx = 19
116
+ obj_name = current_func_name[char_idx:]
117
+ obj = params[obj_name]
118
+
119
+ try:
120
+ for undistorted_disk in obj:
121
+ accepted_types = (fakecbed.shapes.NonuniformBoundedShape,)
122
+
123
+ kwargs = {"obj": undistorted_disk,
124
+ "obj_name": "undistorted_disk",
125
+ "accepted_types": accepted_types}
126
+ czekitout.check.if_instance_of_any_accepted_types(**kwargs)
127
+
128
+ accepted_types = (fakecbed.shapes.Circle, fakecbed.shapes.Ellipse)
129
+
130
+ undistorted_disk_core_attrs = \
131
+ undistorted_disk.get_core_attrs(deep_copy=False)
132
+ undistorted_disk_support = \
133
+ undistorted_disk_core_attrs["support"]
134
+
135
+ kwargs = {"obj": undistorted_disk_support,
136
+ "obj_name": "undistorted_disk_support",
137
+ "accepted_types": accepted_types}
138
+ czekitout.check.if_instance_of_any_accepted_types(**kwargs)
139
+ except:
140
+ err_msg = globals()[current_func_name+"_err_msg_1"]
141
+ raise TypeError(err_msg)
142
+
143
+ undistorted_disks = copy.deepcopy(obj)
144
+
145
+ return undistorted_disks
146
+
147
+
148
+
149
+ def _pre_serialize_undistorted_disks(undistorted_disks):
150
+ obj_to_pre_serialize = random.choice(list(locals().values()))
151
+ serializable_rep = tuple()
152
+ for elem in obj_to_pre_serialize:
153
+ serializable_rep += (elem.pre_serialize(),)
154
+
155
+ return serializable_rep
156
+
157
+
158
+
159
+ def _de_pre_serialize_undistorted_disks(serializable_rep):
160
+ undistorted_disks = \
161
+ tuple()
162
+ for pre_serialized_undistorted_disk in serializable_rep:
163
+ cls_alias = \
164
+ fakecbed.shapes.NonuniformBoundedShape
165
+ undistorted_disk = \
166
+ cls_alias.de_pre_serialize(pre_serialized_undistorted_disk)
167
+ undistorted_disks += \
168
+ (undistorted_disk,)
169
+
170
+ return undistorted_disks
171
+
172
+
173
+
174
+ def _check_and_convert_undistorted_misc_shapes(params):
175
+ current_func_name = inspect.stack()[0][3]
176
+ char_idx = 19
177
+ obj_name = current_func_name[char_idx:]
178
+ obj = params[obj_name]
179
+
180
+ accepted_types = (fakecbed.shapes.Circle,
181
+ fakecbed.shapes.Ellipse,
182
+ fakecbed.shapes.Peak,
183
+ fakecbed.shapes.Band,
184
+ fakecbed.shapes.PlaneWave,
185
+ fakecbed.shapes.Arc,
186
+ fakecbed.shapes.GenericBlob,
187
+ fakecbed.shapes.Orbital,
188
+ fakecbed.shapes.Lune,
189
+ fakecbed.shapes.NonuniformBoundedShape)
190
+
191
+ try:
192
+ for undistorted_misc_shape in obj:
193
+ kwargs = {"obj": undistorted_misc_shape,
194
+ "obj_name": "undistorted_misc_shape",
195
+ "accepted_types": accepted_types}
196
+ czekitout.check.if_instance_of_any_accepted_types(**kwargs)
197
+ except:
198
+ err_msg = globals()[current_func_name+"_err_msg_1"]
199
+ raise TypeError(err_msg)
200
+
201
+ undistorted_misc_shapes = copy.deepcopy(obj)
202
+
203
+ return undistorted_misc_shapes
204
+
205
+
206
+
207
+ def _pre_serialize_undistorted_misc_shapes(undistorted_misc_shapes):
208
+ obj_to_pre_serialize = random.choice(list(locals().values()))
209
+ serializable_rep = tuple()
210
+ for elem in obj_to_pre_serialize:
211
+ serializable_rep += (elem.pre_serialize(),)
212
+
213
+ return serializable_rep
214
+
215
+
216
+
217
+ def _de_pre_serialize_undistorted_misc_shapes(serializable_rep):
218
+ undistorted_misc_shapes = tuple()
219
+
220
+ for pre_serialized_undistorted_misc_shape in serializable_rep:
221
+ if "radius" in pre_serialized_undistorted_misc_shape:
222
+ cls_alias = fakecbed.shapes.Circle
223
+ elif "eccentricity" in pre_serialized_undistorted_misc_shape:
224
+ cls_alias = fakecbed.shapes.Ellipse
225
+ elif "functional_form" in pre_serialized_undistorted_misc_shape:
226
+ cls_alias = fakecbed.shapes.Peak
227
+ elif "end_pt_1" in pre_serialized_undistorted_misc_shape:
228
+ cls_alias = fakecbed.shapes.Band
229
+ elif "propagation_direction" in pre_serialized_undistorted_misc_shape:
230
+ cls_alias = fakecbed.shapes.PlaneWave
231
+ elif "subtending_angle" in pre_serialized_undistorted_misc_shape:
232
+ cls_alias = fakecbed.shapes.Arc
233
+ elif "radial_amplitudes" in pre_serialized_undistorted_misc_shape:
234
+ cls_alias = fakecbed.shapes.GenericBlob
235
+ elif "magnetic_quantum_number" in pre_serialized_undistorted_misc_shape:
236
+ cls_alias = fakecbed.shapes.Orbital
237
+ elif "bg_ellipse" in pre_serialized_undistorted_misc_shape:
238
+ cls_alias = fakecbed.shapes.Lune
239
+ else:
240
+ cls_alias = fakecbed.shapes.NonuniformBoundedShape
241
+
242
+ undistorted_misc_shape = \
243
+ cls_alias.de_pre_serialize(pre_serialized_undistorted_misc_shape)
244
+ undistorted_misc_shapes += \
245
+ (undistorted_misc_shape,)
246
+
247
+ return undistorted_misc_shapes
248
+
249
+
250
+
251
+ def _check_and_convert_undistorted_outer_illumination_shape(params):
252
+ current_func_name = inspect.stack()[0][3]
253
+ char_idx = 19
254
+ obj_name = current_func_name[char_idx:]
255
+ obj = params[obj_name]
256
+
257
+ accepted_types = (fakecbed.shapes.Circle,
258
+ fakecbed.shapes.Ellipse,
259
+ fakecbed.shapes.GenericBlob,
260
+ type(None))
261
+
262
+ if isinstance(obj, accepted_types[-1]):
263
+ kwargs = {"radius": np.inf}
264
+ undistorted_outer_illumination_shape = accepted_types[0](**kwargs)
265
+ else:
266
+ kwargs = {"obj": obj,
267
+ "obj_name": obj_name,
268
+ "accepted_types": accepted_types}
269
+ czekitout.check.if_instance_of_any_accepted_types(**kwargs)
270
+ undistorted_outer_illumination_shape = copy.deepcopy(obj)
271
+
272
+ return undistorted_outer_illumination_shape
273
+
274
+
275
+
276
+ def _pre_serialize_undistorted_outer_illumination_shape(
277
+ undistorted_outer_illumination_shape):
278
+ obj_to_pre_serialize = random.choice(list(locals().values()))
279
+ serializable_rep = obj_to_pre_serialize.pre_serialize()
280
+
281
+ return serializable_rep
282
+
283
+
284
+
285
+ def _de_pre_serialize_undistorted_outer_illumination_shape(serializable_rep):
286
+ if "radius" in serializable_rep:
287
+ undistorted_outer_illumination_shape = \
288
+ fakecbed.shapes.Circle.de_pre_serialize(serializable_rep)
289
+ elif "eccentricity" in serializable_rep:
290
+ undistorted_outer_illumination_shape = \
291
+ fakecbed.shapes.Ellipse.de_pre_serialize(serializable_rep)
292
+ else:
293
+ undistorted_outer_illumination_shape = \
294
+ fakecbed.shapes.GenericBlob.de_pre_serialize(serializable_rep)
295
+
296
+ return undistorted_outer_illumination_shape
297
+
298
+
299
+
300
+ def _check_and_convert_gaussian_filter_std_dev(params):
301
+ current_func_name = inspect.stack()[0][3]
302
+ obj_name = current_func_name[19:]
303
+ func_alias = czekitout.convert.to_nonnegative_float
304
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
305
+ gaussian_filter_std_dev = func_alias(**kwargs)
306
+
307
+ return gaussian_filter_std_dev
308
+
309
+
310
+
311
+ def _pre_serialize_gaussian_filter_std_dev(
312
+ gaussian_filter_std_dev):
313
+ obj_to_pre_serialize = random.choice(list(locals().values()))
314
+ serializable_rep = obj_to_pre_serialize
315
+
316
+ return serializable_rep
317
+
318
+
319
+
320
+ def _de_pre_serialize_gaussian_filter_std_dev(serializable_rep):
321
+ gaussian_filter_std_dev = serializable_rep
322
+
323
+ return gaussian_filter_std_dev
324
+
325
+
326
+
327
+ def _check_and_convert_distortion_model(params):
328
+ current_func_name = inspect.stack()[0][3]
329
+ char_idx = 19
330
+ obj_name = current_func_name[char_idx:]
331
+ obj = params[obj_name]
332
+
333
+ num_pixels_across_pattern = \
334
+ _check_and_convert_num_pixels_across_pattern(params)
335
+
336
+ accepted_types = (distoptica.DistortionModel, type(None))
337
+
338
+ if isinstance(obj, accepted_types[-1]):
339
+ sampling_grid_dims_in_pixels = 2*(num_pixels_across_pattern,)
340
+ kwargs = {"sampling_grid_dims_in_pixels": sampling_grid_dims_in_pixels}
341
+ distortion_model = accepted_types[0](**kwargs)
342
+ else:
343
+ kwargs = {"obj": obj,
344
+ "obj_name": obj_name,
345
+ "accepted_types": accepted_types}
346
+ czekitout.check.if_instance_of_any_accepted_types(**kwargs)
347
+ distortion_model = copy.deepcopy(obj)
348
+
349
+ distortion_model_core_attrs = \
350
+ distortion_model.get_core_attrs(deep_copy=False)
351
+ sampling_grid_dims_in_pixels = \
352
+ distortion_model_core_attrs["sampling_grid_dims_in_pixels"]
353
+
354
+ if ((sampling_grid_dims_in_pixels[0]%num_pixels_across_pattern != 0)
355
+ or (sampling_grid_dims_in_pixels[1]%num_pixels_across_pattern != 0)):
356
+ err_msg = globals()[current_func_name+"_err_msg_1"]
357
+ raise ValueError(err_msg)
358
+
359
+ return distortion_model
360
+
361
+
362
+
363
+ def _pre_serialize_distortion_model(distortion_model):
364
+ obj_to_pre_serialize = random.choice(list(locals().values()))
365
+ serializable_rep = obj_to_pre_serialize.pre_serialize()
366
+
367
+ return serializable_rep
368
+
369
+
370
+
371
+ def _de_pre_serialize_distortion_model(serializable_rep):
372
+ distortion_model = \
373
+ distoptica.DistortionModel.de_pre_serialize(serializable_rep)
374
+
375
+ return distortion_model
376
+
377
+
378
+
379
+ def _check_and_convert_num_pixels_across_pattern(params):
380
+ current_func_name = inspect.stack()[0][3]
381
+ obj_name = current_func_name[19:]
382
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
383
+ num_pixels_across_pattern = czekitout.convert.to_positive_int(**kwargs)
384
+
385
+ return num_pixels_across_pattern
386
+
387
+
388
+
389
+ def _pre_serialize_num_pixels_across_pattern(num_pixels_across_pattern):
390
+ obj_to_pre_serialize = random.choice(list(locals().values()))
391
+ serializable_rep = obj_to_pre_serialize
392
+
393
+ return serializable_rep
394
+
395
+
396
+
397
+ def _de_pre_serialize_num_pixels_across_pattern(serializable_rep):
398
+ num_pixels_across_pattern = serializable_rep
399
+
400
+ return num_pixels_across_pattern
401
+
402
+
403
+
404
+ def _check_and_convert_apply_shot_noise(params):
405
+ current_func_name = inspect.stack()[0][3]
406
+ obj_name = current_func_name[19:]
407
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
408
+ apply_shot_noise = czekitout.convert.to_bool(**kwargs)
409
+
410
+ return apply_shot_noise
411
+
412
+
413
+
414
+ def _pre_serialize_apply_shot_noise(apply_shot_noise):
415
+ obj_to_pre_serialize = random.choice(list(locals().values()))
416
+ serializable_rep = obj_to_pre_serialize
417
+
418
+ return serializable_rep
419
+
420
+
421
+
422
+ def _de_pre_serialize_apply_shot_noise(serializable_rep):
423
+ apply_shot_noise = serializable_rep
424
+
425
+ return apply_shot_noise
426
+
427
+
428
+
429
+ def _check_and_convert_detector_partition_width_in_pixels(params):
430
+ current_func_name = inspect.stack()[0][3]
431
+ obj_name = current_func_name[19:]
432
+ func_alias = czekitout.convert.to_nonnegative_int
433
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
434
+ detector_partition_width_in_pixels = func_alias(**kwargs)
435
+
436
+ return detector_partition_width_in_pixels
437
+
438
+
439
+
440
+ def _pre_serialize_detector_partition_width_in_pixels(
441
+ detector_partition_width_in_pixels):
442
+ obj_to_pre_serialize = random.choice(list(locals().values()))
443
+ serializable_rep = obj_to_pre_serialize
444
+
445
+ return serializable_rep
446
+
447
+
448
+
449
+ def _de_pre_serialize_detector_partition_width_in_pixels(serializable_rep):
450
+ detector_partition_width_in_pixels = serializable_rep
451
+
452
+ return detector_partition_width_in_pixels
453
+
454
+
455
+
456
+ def _check_and_convert_cold_pixels(params):
457
+ current_func_name = inspect.stack()[0][3]
458
+ obj_name = current_func_name[19:]
459
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
460
+ cold_pixels = czekitout.convert.to_pairs_of_ints(**kwargs)
461
+
462
+ num_pixels_across_pattern = \
463
+ _check_and_convert_num_pixels_across_pattern(params)
464
+
465
+ coords_of_cold_pixels = cold_pixels
466
+ for coords_of_cold_pixel in coords_of_cold_pixels:
467
+ row, col = coords_of_cold_pixel
468
+ if ((row < -num_pixels_across_pattern)
469
+ or (num_pixels_across_pattern <= row)
470
+ or (col < -num_pixels_across_pattern)
471
+ or (num_pixels_across_pattern <= col)):
472
+ err_msg = globals()[current_func_name+"_err_msg_1"]
473
+ raise TypeError(err_msg)
474
+
475
+ return cold_pixels
476
+
477
+
478
+
479
+ def _pre_serialize_cold_pixels(cold_pixels):
480
+ serializable_rep = cold_pixels
481
+
482
+ return serializable_rep
483
+
484
+
485
+
486
+ def _de_pre_serialize_cold_pixels(serializable_rep):
487
+ cold_pixels = serializable_rep
488
+
489
+ return cold_pixels
490
+
491
+
492
+
493
+ def _check_and_convert_deep_copy(params):
494
+ current_func_name = inspect.stack()[0][3]
495
+ obj_name = current_func_name[19:]
496
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
497
+ deep_copy = czekitout.convert.to_bool(**kwargs)
498
+
499
+ return deep_copy
500
+
501
+
502
+
503
+ def _check_and_convert_overriding_image(params):
504
+ current_func_name = inspect.stack()[0][3]
505
+ char_idx = 19
506
+ obj_name = current_func_name[char_idx:]
507
+ obj = params[obj_name]
508
+
509
+ func_alias = fakecbed.shapes._check_and_convert_real_torch_matrix
510
+ params["real_torch_matrix"] = obj
511
+ params["name_of_alias_of_real_torch_matrix"] = obj_name
512
+ overriding_image = func_alias(params)
513
+
514
+ del params["real_torch_matrix"]
515
+ del params["name_of_alias_of_real_torch_matrix"]
516
+
517
+ num_pixels_across_pattern = params["num_pixels_across_pattern"]
518
+ expected_image_dims_in_pixels = 2*(num_pixels_across_pattern,)
519
+
520
+ if overriding_image.shape != expected_image_dims_in_pixels:
521
+ unformatted_err_msg = globals()[current_func_name+"_err_msg_1"]
522
+ args = expected_image_dims_in_pixels
523
+ err_msg = unformatted_err_msg.format(*args)
524
+ raise ValueError(err_msg)
525
+
526
+ return overriding_image
527
+
528
+
529
+
530
+ def _check_and_convert_skip_validation_and_conversion(params):
531
+ func_alias = \
532
+ fakecbed.shapes._check_and_convert_skip_validation_and_conversion
533
+ skip_validation_and_conversion = \
534
+ func_alias(params)
535
+
536
+ return skip_validation_and_conversion
537
+
538
+
539
+
540
+ _default_undistorted_outer_illumination_shape = \
541
+ None
542
+ _default_undistorted_tds_model = \
543
+ None
544
+ _default_undistorted_disks = \
545
+ tuple()
546
+ _default_undistorted_misc_shapes = \
547
+ tuple()
548
+ _default_gaussian_filter_std_dev = \
549
+ 0
550
+ _default_distortion_model = \
551
+ None
552
+ _default_num_pixels_across_pattern = \
553
+ 512
554
+ _default_apply_shot_noise = \
555
+ False
556
+ _default_detector_partition_width_in_pixels = \
557
+ 0
558
+ _default_cold_pixels = \
559
+ tuple()
560
+ _default_skip_validation_and_conversion = \
561
+ fakecbed.shapes._default_skip_validation_and_conversion
562
+ _default_deep_copy = \
563
+ True
564
+
565
+
566
+
567
+ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
568
+ r"""The parameters of a discretized fake convergent beam electron
569
+ diffraction (CBED) pattern.
570
+
571
+ A series of parameters need to be specified in order to create an image of a
572
+ fake CBED pattern, with the most important parameters being: the set of
573
+ intensity patterns of undistorted shapes that determine the undistorted
574
+ noiseless non-blurred uncorrupted (UNNBU) fake CBED pattern; and a
575
+ distortion model which transforms the UNNBU fake CBED pattern into a
576
+ distorted noiseless non-blurred uncorrupted (DNNBU) fake CBED pattern. The
577
+ remaining parameters determine whether additional images effects are
578
+ applied, like e.g. shot noise or blur effects. Note that in the case of the
579
+ aforementioned shapes, we expand the notion of intensity patterns to mean a
580
+ 2D real-valued function, i.e. it can be negative. To be clear, we do not
581
+ apply this generalized notion of intensity patterns to the fake CBED
582
+ patterns: in such cases intensity patterns mean 2D real-valued functions
583
+ that are strictly nonnegative.
584
+
585
+ Let :math:`u_{x}` and :math:`u_{y}` be the fractional horizontal and
586
+ vertical coordinates, respectively, of a point in an undistorted image,
587
+ where :math:`\left(u_{x},u_{y}\right)=\left(0,0\right)` is the bottom left
588
+ corner of the image. Secondly, let :math:`q_{x}` and :math:`q_{y}` be the
589
+ fractional horizontal and vertical coordinates, respectively, of a point in
590
+ a distorted image, where :math:`\left(q_{x},q_{y}\right)=\left(0,0\right)`
591
+ is the bottom left corner of the image. When users specify the distortion
592
+ model, represented by an instance of the class
593
+ :class:`distoptica.DistortionModel`, they also specify a coordinate
594
+ transformation, :math:`\left(T_{⌑;x}\left(u_{x},u_{y}\right),
595
+ T_{⌑;x}\left(u_{x},u_{y}\right)\right)`, which maps a given coordinate pair
596
+ :math:`\left(u_{x},u_{y}\right)` to a corresponding coordinate pair
597
+ :math:`\left(q_{x},q_{y}\right)`, and implicitly a right-inverse to said
598
+ coordinate transformation,
599
+ :math:`\left(T_{\square;x}\left(q_{x},q_{y}\right),
600
+ T_{\square;y}\left(q_{x},q_{y}\right)\right)`, that maps a coordinate pair
601
+ :math:`\left(q_{x},q_{y}\right)` to a corresponding coordinate pair
602
+ :math:`\left(u_{x},u_{y}\right)`, when such a relationship exists for
603
+ :math:`\left(q_{x},q_{y}\right)`.
604
+
605
+ The calculation of the image of the target fake CBED pattern involves
606
+ calculating various intermediate images which are subsequently combined to
607
+ yield the target image. These intermediate images share the same horizontal
608
+ and vertical dimensions in units of pixels, which may differ from those of
609
+ the image of the target fake CBED pattern. Let :math:`N_{\mathcal{I};x}` and
610
+ :math:`N_{\mathcal{I};y}` be the number of pixels in the image of the target
611
+ fake CBED pattern from left to right and top to bottom respectively, and let
612
+ :math:`N_{\mathring{\mathcal{I}};x}` and
613
+ :math:`N_{\mathring{\mathcal{I}};y}` be the number of pixels in each of the
614
+ aforementioned intermediate images from left to right and top to bottom
615
+ respectively. In :mod:`fakecbed`, we assume that
616
+
617
+ .. math ::
618
+ N_{\mathcal{I};x}=N_{\mathcal{I};y},
619
+ :label: N_I_x_eq_N_I_y__1
620
+
621
+ .. math ::
622
+ N_{\mathring{\mathcal{I}};x}\ge N_{\mathcal{I};x},
623
+ :label: N_ring_I_x_ge_N_I_x__1
624
+
625
+ and
626
+
627
+ .. math ::
628
+ N_{\mathring{\mathcal{I}};y}\ge N_{\mathcal{I};y}.
629
+ :label: N_ring_I_y_ge_N_I_y__1
630
+
631
+ The integer :math:`N_{\mathcal{I};x}` is specified by the parameter
632
+ ``num_pixels_across_pattern``. The integers
633
+ :math:`N_{\mathring{\mathcal{I}};x}` and
634
+ :math:`N_{\mathring{\mathcal{I}};y}` are specified indirectly by the
635
+ parameter ``distortion_model``. The parameter ``distortion_model`` specifies
636
+ the distortion model, which as mentioned above is represented by an instance
637
+ of the class :class:`distoptica.DistortionModel`. One of the parameters of
638
+ said distortion model is the integer pair
639
+ ``sampling_grid_dims_in_pixels``. In the current context,
640
+ ``sampling_grid_dims_in_pixels[0]`` and ``sampling_grid_dims_in_pixels[1]``
641
+ are equal to :math:`N_{\mathring{\mathcal{I}};x}` and
642
+ :math:`N_{\mathring{\mathcal{I}};y}` respectively.
643
+
644
+ As mentioned above, a set of intensity patterns need to be specified in
645
+ order to create the target fake CBED pattern. The first of these is the
646
+ intensity pattern of an undistorted thermal diffuse scattering (TDS) model,
647
+ :math:`\mathcal{I}_{\text{TDS}}\left(u_{x},u_{y}\right)`, which is specified
648
+ by the parameter ``undistorted_tds_model``. The second of these intensity
649
+ patterns is that of the undistorted outer illumination shape,
650
+ :math:`\mathcal{I}_{\text{OI}}\left(u_{x},u_{y}\right)`, which is specified
651
+ by the parameter ``undistorted_outer_illumination_shape``.
652
+ :math:`\mathcal{I}_{\text{OI}}\left(u_{x},u_{y}\right)` is defined such that
653
+ for every coordinate pair :math:`\left(u_{x},u_{y}\right)`, if
654
+ :math:`\mathcal{I}_{\text{OI}}\left(u_{x},u_{y}\right)=0` then the value of
655
+ the UNNBU fake CBED pattern is also equal to 0. A separate subset of the
656
+ intensity patterns that need to be specified are :math:`N_{\text{D}}`
657
+ intensity patterns of undistorted nonuniform circles and/or ellipses,
658
+ :math:`\left\{ \mathcal{I}_{k;\text{D}}\left(u_{x},u_{y}\right)\right\}
659
+ _{k=0}^{N_{\text{D}}-1}`, which is specified by the parameter
660
+ ``undistorted_disks``. Each intensity pattern
661
+ :math:`\mathcal{I}_{k;\text{D}}\left(u_{x},u_{y}\right)` is suppose to
662
+ depict one of the CBED disks in the fake CBED pattern in the absence of the
663
+ intensity background. Moreover, each intensity pattern
664
+ :math:`\mathcal{I}_{k;\text{D}}\left(u_{x},u_{y}\right)` has a corresponding
665
+ supporting intensity pattern
666
+ :math:`\mathcal{I}_{k;\text{DS}}\left(u_{x},u_{y}\right)`, which is defined
667
+ such that for every coordinate pair :math:`\left(u_{x},u_{y}\right)`, if
668
+ :math:`\mathcal{I}_{k;\text{DS}}\left(u_{x},u_{y}\right)=0` then
669
+ :math:`\mathcal{I}_{k;\text{D}}\left(u_{x},u_{y}\right)=0`. The remaining
670
+ intensity patterns that need to be specified, of which there are
671
+ :math:`N_{\text{M}}`, are intensity patterns of undistorted miscellaneous
672
+ shapes, :math:`\left\{
673
+ \mathcal{I}_{k;\text{M}}\left(u_{x},u_{y}\right)\right\}
674
+ _{k=0}^{N_{\text{M}}-1}`, which is specified by the parameter
675
+ ``undistorted_misc_shapes``. These patterns, along with that of the TDS
676
+ model, contribute to the intensity background.
677
+
678
+ To add blur effects, users can specify a nonzero standard deviation
679
+ :math:`\sigma_{\text{blur}}` of the Gaussian filter used to yield such blur
680
+ effects on the target fake CBED pattern. The value of
681
+ :math:`\sigma_{\text{blur}}` is specified by the parameter
682
+ ``gaussian_filter_std_dev``.
683
+
684
+ To add shot noise effects to the image of the target fake CBED pattern, the
685
+ parameter ``apply_shot_noise`` needs to be set to ``True``.
686
+
687
+ For some pixelated electron detectors, the pixels of a number
688
+ :math:`N_{\text{DPW}}` of contiguous rows and an equal number of contiguous
689
+ columns will not measure or readout incident electron counts. Instead, the
690
+ final intensity values measured are inpainted according to the final
691
+ intensity values of the other pixels in the detector. The intersection of
692
+ the aforementioned contiguous block of rows and the aforementioned
693
+ contiguous block of columns is located within one pixel of the center of the
694
+ detector. The integer :math:`N_{\text{DPW}}`, which we call the detector
695
+ partition width in units of pixels, is specified by the parameter
696
+ ``detector_partition_width_in_pixels``.
697
+
698
+ Cold pixels, which are individual zero-valued pixels in the image of the
699
+ target fake CBED pattern, are specified by the parameter
700
+ ``cold_pixels``. Let :math:`N_{\text{CP}}` be the number of cold pixels in
701
+ the image of the target fake CBED pattern. Furthermore, let :math:`\left\{
702
+ n_{k;\text{CP}}\right\} _{k=0}^{N_{\text{CP}}-1}` and :math:`\left\{
703
+ m_{k;\text{CP}}\right\} _{k=0}^{N_{\text{CP}}-1}` be integer sequences
704
+ respectively, where :math:`n_{k;\text{CP}}` and :math:`m_{k;\text{CP}}` are
705
+ the row and column indices respectively of the :math:`k^{\text{th}}` cold
706
+ pixel. For every nonnegative integer ``k`` less than :math:`N_{\text{CP}}`,
707
+ ``cold_pixels[k][0]`` and ````cold_pixels[k][1]`` are
708
+ :math:`n_{k;\text{CP}}` and :math:`m_{k;\text{CP}}` respectively, with the
709
+ integer :math:`k` being equal to the value of ``k``.
710
+
711
+ Below we describe in more detail how various attributes of the current class
712
+ are effectively calculated. Before doing so, we need to introduce a few more
713
+ quantities:
714
+
715
+ .. math ::
716
+ j\in\left\{ j^{\prime}\right\}_{j^{\prime}=0}^{
717
+ N_{\mathcal{\mathring{I}};x}-1},
718
+ :label: j_range__1
719
+
720
+ .. math ::
721
+ i\in\left\{ i^{\prime}\right\} _{i^{\prime}=0}^{
722
+ N_{\mathcal{\mathring{I}};y}-1}
723
+ :label: i_range__1
724
+
725
+ .. math ::
726
+ q_{\mathcal{\mathring{I}};x;j}=\left(j+\frac{1}{2}\right)
727
+ \Delta q_{\mathcal{\mathring{I}};x},
728
+ :label: q_I_circ_x_j__1
729
+
730
+ .. math ::
731
+ q_{\mathcal{\mathring{I}};y;i}=1-\left(i+\frac{1}{2}\right)
732
+ \Delta q_{\mathcal{\mathring{I}};y},
733
+ :label: q_I_circ_y_i__1
734
+
735
+ .. math ::
736
+ \Delta q_{\mathcal{\mathring{I}};x}=
737
+ \frac{1}{N_{\mathcal{\mathring{I}};x}},
738
+ :label: Delta_q_I_circ_x__1
739
+
740
+ .. math ::
741
+ \Delta q_{\mathcal{\mathring{I}};y}=
742
+ \frac{1}{N_{\mathcal{\mathring{I}};y}}.
743
+ :label: Delta_q_I_circ_y__1
744
+
745
+ .. math ::
746
+ m\in\left\{ m^{\prime}\right\} _{m^{\prime}=0}^{N_{\mathcal{I};x}-1},
747
+ :label: m_range__1
748
+
749
+ .. math ::
750
+ n\in\left\{ n^{\prime}\right\} _{n^{\prime}=0}^{N_{\mathcal{I};y}-1},
751
+ :label: n_range__1
752
+
753
+ and
754
+
755
+ .. math ::
756
+ \mathbf{J}_{\square}\left(q_{x},q_{y}\right)=
757
+ \begin{pmatrix}\frac{\partial T_{\square;x}}{\partial q_{x}}
758
+ & \frac{\partial T_{\square;x}}{\partial q_{y}}\\
759
+ \frac{\partial T_{\square;y}}{\partial q_{x}}
760
+ & \frac{\partial T_{\square;y}}{\partial q_{y}}
761
+ \end{pmatrix},
762
+ :label: J_sq__1
763
+
764
+ where the derivatives in Eq. :eq:`J_sq__1` are calculated numerically using
765
+ the second-order accurate central differences method. The aforementioned
766
+ attributes of the current class are effectively calculated by executing the
767
+ following steps:
768
+
769
+ 1. Calculate
770
+
771
+ .. math ::
772
+ \mathring{\mathcal{I}}_{\text{OI};⌑;i,j}\leftarrow
773
+ \mathcal{I}_{\text{OI}}\left(
774
+ T_{\square;x}\left(q_{\mathring{\mathcal{I}};x;j},
775
+ q_{\mathring{\mathcal{I}};y;i}\right),
776
+ T_{\square;y}\left(q_{\mathring{\mathcal{I}};x;j},
777
+ q_{\mathring{\mathcal{I}};y;i}\right)\right),
778
+ :label: HD_I_OI__1
779
+
780
+ .. math ::
781
+ \mathring{\mathcal{I}}_{\text{OI};⌑;i,j}\leftarrow\begin{cases}
782
+ \text{True}, & \text{if }\mathring{\mathcal{I}}_{\text{OI};⌑;i,j}
783
+ \neq0,\\
784
+ \text{False}, & \text{otherwise},
785
+ \end{cases}
786
+ :label: HD_I_OI__2
787
+
788
+ and then apply max pooling to
789
+ :math:`\mathring{\mathcal{I}}_{\text{OI};⌑;i,j}` with a kernel of dimensions
790
+ :math:`\left(N_{\mathring{\mathcal{I}};y}/N_{\mathcal{I};y},
791
+ N_{\mathring{\mathcal{I}};x}/N_{\mathcal{I};x}\right)`
792
+ and store the result in :math:`\mathcal{I}_{\text{OI};⌑;n,m}`.
793
+
794
+ 2. Calculate
795
+
796
+ .. math ::
797
+ \mathcal{I}_{\text{DOM};⌑;n,m}\leftarrow0.
798
+ :label: LD_I_DOM__1
799
+
800
+ 3. Calculate
801
+
802
+ .. math ::
803
+ \mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}&
804
+ \leftarrow\mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}\\&
805
+ \quad\quad\mathop{+}\mathcal{I}_{k;\text{D}}\left(
806
+ T_{\square;x}\left(q_{\mathring{\mathcal{I}};x;j},
807
+ q_{\mathring{\mathcal{I}};y;i}\right),T_{\square;y}\left(
808
+ q_{\mathring{\mathcal{I}};x;j},
809
+ q_{\mathring{\mathcal{I}};y;i}\right)\right),
810
+ :label: HD_I_CBED__1
811
+
812
+ 4. For :math:`0\le k<N_{\text{D}}`, calculate
813
+
814
+ .. math ::
815
+ \mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}&
816
+ \leftarrow\mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}\\&
817
+ \quad\quad\mathop{+}\mathcal{I}_{k;\text{D}}\left(T_{\square;x}\left(
818
+ q_{\mathring{\mathcal{I}};x;j},q_{\mathring{\mathcal{I}};y;i}\right),
819
+ T_{\square;y}\left(q_{\mathring{\mathcal{I}};x;j},
820
+ q_{\mathring{\mathcal{I}};y;i}\right)\right),
821
+ :label: HD_I_CBED__2
822
+
823
+ .. math ::
824
+ \mathring{\mathcal{I}}_{k;\text{DS};⌑;i,j}\leftarrow
825
+ \mathcal{I}_{k;\text{DS}}\left(T_{\square;x}\left(
826
+ q_{\mathring{\mathcal{I}};x;j},q_{\mathring{\mathcal{I}};y;i}\right),
827
+ T_{\square;y}\left(q_{\mathring{\mathcal{I}};x;j},
828
+ q_{\mathring{\mathcal{I}};y;i}\right)\right),
829
+ :label: HD_I_k_DS__1
830
+
831
+ .. math ::
832
+ \mathring{\mathcal{I}}_{k;\text{DS};⌑;i,j}\leftarrow\begin{cases}
833
+ \text{True}, & \text{if }\mathring{\mathcal{I}}_{k;\text{DS};⌑;i,j}
834
+ \neq0,\\
835
+ \text{False}, & \text{otherwise},
836
+ \end{cases}
837
+ :label: HD_I_k_DS__2
838
+
839
+ then apply max pooling to :math:`\mathring{\mathcal{I}}_{k;\text{DS};⌑;i,j}`
840
+ with a kernel of dimensions
841
+ :math:`\left(N_{\mathring{\mathcal{I}};y}/N_{\mathcal{I};y},
842
+ N_{\mathring{\mathcal{I}};x}/N_{\mathcal{I};x}\right)` and store the result
843
+ in :math:`\mathcal{I}_{k;\text{DS};⌑;n,m}`, and calculate
844
+
845
+ .. math ::
846
+ \mathcal{I}_{\text{DOM};⌑;n,m}\leftarrow
847
+ \mathcal{I}_{\text{DOM};⌑;n,m}+\mathcal{I}_{k;\text{DS};⌑;n,m}.
848
+ :label: LD_I_DOM__2
849
+
850
+ 5. Calculate
851
+
852
+ .. math ::
853
+ \mathcal{I}_{\text{DOM};⌑;n,m}\leftarrow
854
+ \mathcal{I}_{\text{OI};⌑;n,m}\mathcal{I}_{\text{DOM};⌑;n,m}.
855
+ :label: LD_I_DOM__3
856
+
857
+ 6. For :math:`0\le k<N_{\text{M}}`, calculate
858
+
859
+ .. math ::
860
+ \mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}&
861
+ \leftarrow\mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}\\&
862
+ \quad\quad\mathop{+}\mathcal{I}_{k;\text{M}}\left(T_{\square;x}\left(
863
+ q_{\mathring{\mathcal{I}};x;j},q_{\mathring{\mathcal{I}};y;i}\right),
864
+ T_{\square;y}\left(q_{\mathring{\mathcal{I}};x;j},
865
+ q_{\mathring{\mathcal{I}};y;i}\right)\right).
866
+ :label: HD_I_CBED__3
867
+
868
+ 7. Calculate
869
+
870
+ .. math ::
871
+ \mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}\leftarrow
872
+ \text{det}\left(\mathbf{J}_{\square}\left(
873
+ q_{\mathring{\mathcal{I}};x;j},
874
+ q_{\mathring{\mathcal{I}};y;i}\right)\right)
875
+ \mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}.
876
+ :label: HD_I_CBED__4
877
+
878
+ 8. Apply average pooling to
879
+ :math:`\mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}` with a kernel of
880
+ dimensions :math:`\left(N_{\mathring{\mathcal{I}};y}/N_{\mathcal{I};y},
881
+ N_{\mathring{\mathcal{I}};x}/N_{\mathcal{I};x}\right)`, and store the result
882
+ in :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
883
+
884
+ 9. Apply a Gaussian filter to :math:`\mathcal{I}_{\text{CBED};⌑;n,m}` that
885
+ is identical in outcome to that implemented by the function
886
+ :func:`scipy.ndimage.gaussian_filter`, with ``sigma`` set to
887
+ ``gaussian_filter_std_dev`` and ``truncate`` set to ``4``, and store the
888
+ result in :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
889
+
890
+ 10. Calculate
891
+
892
+ .. math ::
893
+ k_{\text{I};1}\leftarrow
894
+ \left\lfloor \frac{N_{\mathcal{I};x}-1}{2}\right\rfloor
895
+ -\left\lfloor \frac{N_{\text{DPW}}}{2}\right\rfloor ,
896
+ :label: k_I_1__1
897
+
898
+ and
899
+
900
+ .. math ::
901
+ k_{\text{I};2}\leftarrow k_{\text{I};1}+N_{\text{DPW}}-1.
902
+ :label: k_I_2__1
903
+
904
+ 11. If ``apply_shot_noise`` is set to ``True``, then apply shot/Poisson
905
+ noise to :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`, and store the result in
906
+ :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
907
+
908
+ 12. If :math:`N_{\text{DPW}}>0`, then inpaint the pixels in the rows indexed
909
+ from :math:`k_{\text{I};1}` to :math:`k_{\text{I};2}` and the columns
910
+ indexed from :math:`k_{\text{I};1}` to :math:`k_{\text{I};2}` of the image
911
+ :math:`\mathcal{I}_{\text{CBED};⌑;n,m}` using the function
912
+ :func:`skimage.restoration.inpaint_biharmonic`, and store the result in
913
+ :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
914
+
915
+ 13. Calculate
916
+
917
+ .. math ::
918
+ \mathcal{I}_{\text{CBED};⌑;n,m}\leftarrow
919
+ \mathcal{I}_{\text{OI};⌑;n,m}\mathcal{I}_{\text{CBED};⌑;n,m}.
920
+ :label: LD_I_CBED__1
921
+
922
+ 14. Update pixels of :math:`\mathcal{I}_{\text{CBED};⌑;n,m}` at pixel
923
+ locations specified by ``cold_pixels`` to the value of zero.
924
+
925
+ 15. Apply min-max normalization of :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`,
926
+ and store result in :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
927
+
928
+ 16. Calculate
929
+
930
+ .. math ::
931
+ \mathcal{I}_{\text{CS};⌑;n,m}\leftarrow1-\mathcal{I}_{\text{OI};⌑;n,m}.
932
+ :label: LD_I_CS__1
933
+
934
+ 17. Convolve a :math:`3 \times 3` filter of ones over a symmetrically unity-padded
935
+ :math:`\mathcal{I}_{\text{CS};⌑;n,m}` to yield an output matrix with the
936
+ same dimensions of :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`, and store said
937
+ output matrix in :math:`\mathcal{I}_{\text{CS};⌑;n,m}`.
938
+
939
+ 18. For :math:`0\le k<N_{\text{D}}`, calculate
940
+
941
+ .. math ::
942
+ \mathcal{I}_{k;\text{DCM};⌑;n,m}\leftarrow
943
+ \mathcal{I}_{\text{CS};⌑;n,m}\mathcal{I}_{k;\text{DS};⌑;n,m},
944
+ :label: LD_I_DCM__1
945
+
946
+ .. math ::
947
+ \Omega_{k;\text{DCR};⌑}\leftarrow\begin{cases}
948
+ \text{True}, & \text{if }\sum_{n,m}\mathcal{I}_{k;\text{DCM};⌑;n,m}
949
+ \neq0,\\
950
+ \text{False}, & \text{otherwise},
951
+ \end{cases}
952
+ :label: Omega_k_DCR__1
953
+
954
+ and
955
+
956
+ .. math ::
957
+ \Omega_{k;\text{DAR};⌑}\leftarrow\begin{cases}
958
+ \text{True}, & \text{if }\sum_{n,m}\mathcal{I}_{k;\text{DS};⌑;n,m}=0,\\
959
+ \text{False}, & \text{otherwise}.
960
+ \end{cases}
961
+ :label: Omega_k_DAR__1
962
+
963
+ We refer to :math:`\mathcal{I}_{\text{CBED};⌑;n,m}` as the image of the
964
+ target fake CBED pattern, :math:`\mathcal{I}_{\text{OI};⌑;n,m}` as the image
965
+ of the illumination support, :math:`\mathcal{I}_{\text{DOM};⌑;n,m}` as the
966
+ image of the disk overlap map, :math:`\mathcal{I}_{k;\text{DS};⌑;n,m}` as
967
+ the image of the support of the :math:`k^{\text{th}}` CBED disk,
968
+ :math:`\left\{ \Omega_{k;\text{DCR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}` as
969
+ the disk clipping registry, and :math:`\left\{
970
+ \Omega_{k;\text{DAR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}` as the disk absence
971
+ registry.
972
+
973
+ Parameters
974
+ ----------
975
+ undistorted_tds_model : :class:`fakecbed.tds.Model` | `None`, optional
976
+ The intensity pattern of the undistorted TDS model,
977
+ :math:`\mathcal{I}_{\text{TDS}}\left(u_{x},u_{y}\right)`. If
978
+ ``undistorted_tds_model`` is set to ``None``, then the parameter will be
979
+ reassigned to the value ``fakecbed.tds.Model()``.
980
+ undistorted_disks : `array_like` (:class:`fakecbed.shapes.NonuniformBoundedShape`, ndim=1), optional
981
+ The intensity patterns of the undistorted fake CBED disks,
982
+ :math:`\left\{ \mathcal{I}_{k;\text{D}}\left(u_{x},u_{y}\right)\right\}
983
+ _{k=0}^{N_{\text{D}}-1}`. For every nonnegative integer ``k`` less than
984
+ :math:`N_{\text{D}}`, ``undistorted_disks[k]`` is
985
+ :math:`\mathcal{I}_{k;\text{D}}\left(u_{x},u_{y}\right)`, with the
986
+ integer :math:`k` being equal to the value of ``k``
987
+ undistorted_misc_shapes : `array_like` (`any_shape`, ndim=1), optional
988
+ The intensity patterns of the undistorted miscellaneous shapes,
989
+ :math:`\left\{ \mathcal{I}_{k;\text{M}}\left(u_{x},u_{y}\right)\right\}
990
+ _{k=0}^{N_{\text{M}}-1}`. Note that `any_shape` means any public class
991
+ defined in the module :mod:`fakecbed.shapes` that is a subclass of
992
+ :class:`fakecbed.shapes.BaseShape`.
993
+ undistorted_outer_illumination_shape : :class:`fakecbed.shapes.Circle` | :class:`fakecbed.shapes.Ellipse` | :class:`fakecbed.shapes.GenericBlob` | `None`, optional
994
+ The intensity pattern of the undistorted outer illumination shape,
995
+ :math:`\mathcal{I}_{\text{OI}}\left(u_{x},u_{y}\right)`. If
996
+ ``undistorted_outer_illumination_shape`` is set to ``None``, then
997
+ :math:`\mathcal{I}_{\text{OI}}\left(u_{x},u_{y}\right)` will equal unity
998
+ for all :math:`u_{x}` and :math:`u_{y}`.
999
+ gaussian_filter_std_dev : `float`, optional
1000
+ The standard deviation :math:`\sigma_{\text{blur}}` of the Gaussian
1001
+ filter used to yield such blur effects on the target fake CBED
1002
+ pattern. Must be nonnegative.
1003
+ num_pixels_across_pattern : `int`, optional
1004
+ The number of pixels across the image of the fake CBED pattern,
1005
+ :math:`N_{\mathcal{I};x}`. Must be positive.
1006
+ distortion_model : :class:`distoptica.DistortionModel` | `None`, optional
1007
+ The distortion model. If ``distortion_model`` is set to ``None``, then
1008
+ the parameter will be reassigned to the value
1009
+ ``distoptica.DistortionModel(sampling_grid_dims_in_pixels=(N_x, N_x))``,
1010
+ Where ``N_x`` is equal to ``num_pixels_across_pattern``.
1011
+ apply_shot_noise : `bool`, optional
1012
+ If ``apply_shot_noise`` is set to ``True``, then shot noise is applied
1013
+ to the image of the fake CBED pattern. Otherwise, no shot noise is
1014
+ applied.
1015
+ detector_partition_width_in_pixels : `int`, optional
1016
+ The detector partition width in units of pixels,
1017
+ :math:`N_{\text{DPW}}`. Must be nonnegative.
1018
+ cold_pixels : `array_like` (`int`, ndim=2), optional
1019
+ The pixel coordinates of the cold pixels.
1020
+
1021
+ """
1022
+ ctor_param_names = ("undistorted_tds_model",
1023
+ "undistorted_disks",
1024
+ "undistorted_misc_shapes",
1025
+ "undistorted_outer_illumination_shape",
1026
+ "gaussian_filter_std_dev",
1027
+ "num_pixels_across_pattern",
1028
+ "distortion_model",
1029
+ "apply_shot_noise",
1030
+ "detector_partition_width_in_pixels",
1031
+ "cold_pixels")
1032
+ kwargs = {"namespace_as_dict": globals(),
1033
+ "ctor_param_names": ctor_param_names}
1034
+
1035
+ _validation_and_conversion_funcs_ = \
1036
+ fancytypes.return_validation_and_conversion_funcs(**kwargs)
1037
+ _pre_serialization_funcs_ = \
1038
+ fancytypes.return_pre_serialization_funcs(**kwargs)
1039
+ _de_pre_serialization_funcs_ = \
1040
+ fancytypes.return_de_pre_serialization_funcs(**kwargs)
1041
+
1042
+ del ctor_param_names, kwargs
1043
+
1044
+
1045
+
1046
+ def __init__(self,
1047
+ undistorted_tds_model=\
1048
+ _default_undistorted_tds_model,
1049
+ undistorted_disks=\
1050
+ _default_undistorted_disks,
1051
+ undistorted_misc_shapes=\
1052
+ _default_undistorted_misc_shapes,
1053
+ undistorted_outer_illumination_shape=\
1054
+ _default_undistorted_outer_illumination_shape,
1055
+ gaussian_filter_std_dev=\
1056
+ _default_gaussian_filter_std_dev,
1057
+ num_pixels_across_pattern=\
1058
+ _default_num_pixels_across_pattern,
1059
+ distortion_model=\
1060
+ _default_distortion_model,
1061
+ apply_shot_noise=\
1062
+ _default_apply_shot_noise,
1063
+ detector_partition_width_in_pixels=\
1064
+ _default_detector_partition_width_in_pixels,
1065
+ cold_pixels=\
1066
+ _default_cold_pixels,
1067
+ skip_validation_and_conversion=\
1068
+ _default_skip_validation_and_conversion):
1069
+ ctor_params = {key: val
1070
+ for key, val in locals().items()
1071
+ if (key not in ("self", "__class__"))}
1072
+ kwargs = ctor_params
1073
+ kwargs["skip_cls_tests"] = True
1074
+ fancytypes.PreSerializableAndUpdatable.__init__(self, **kwargs)
1075
+
1076
+ self.execute_post_core_attrs_update_actions()
1077
+
1078
+ return None
1079
+
1080
+
1081
+
1082
+ @classmethod
1083
+ def get_validation_and_conversion_funcs(cls):
1084
+ validation_and_conversion_funcs = \
1085
+ cls._validation_and_conversion_funcs_.copy()
1086
+
1087
+ return validation_and_conversion_funcs
1088
+
1089
+
1090
+
1091
+ @classmethod
1092
+ def get_pre_serialization_funcs(cls):
1093
+ pre_serialization_funcs = \
1094
+ cls._pre_serialization_funcs_.copy()
1095
+
1096
+ return pre_serialization_funcs
1097
+
1098
+
1099
+
1100
+ @classmethod
1101
+ def get_de_pre_serialization_funcs(cls):
1102
+ de_pre_serialization_funcs = \
1103
+ cls._de_pre_serialization_funcs_.copy()
1104
+
1105
+ return de_pre_serialization_funcs
1106
+
1107
+
1108
+
1109
+ def execute_post_core_attrs_update_actions(self):
1110
+ self_core_attrs = self.get_core_attrs(deep_copy=False)
1111
+ for self_core_attr_name in self_core_attrs:
1112
+ attr_name = "_"+self_core_attr_name
1113
+ attr = self_core_attrs[self_core_attr_name]
1114
+ setattr(self, attr_name, attr)
1115
+
1116
+ self._num_disks = len(self._undistorted_disks)
1117
+ self._device = self._distortion_model.device
1118
+
1119
+ self._illumination_support = None
1120
+ self._image = None
1121
+ self._image_has_been_overridden = False
1122
+ self._signal = None
1123
+ self._disk_clipping_registry = None
1124
+ self._disk_supports = None
1125
+ self._disk_absence_registry = None
1126
+ self._disk_overlap_map = None
1127
+
1128
+ return None
1129
+
1130
+
1131
+
1132
+ def update(self, new_core_attr_subset_candidate):
1133
+ super().update(new_core_attr_subset_candidate)
1134
+ self.execute_post_core_attrs_update_actions()
1135
+
1136
+ return None
1137
+
1138
+
1139
+
1140
+ @property
1141
+ def num_disks(self):
1142
+ r"""`int`: The total number of CBED disks defined, :math:`N_{\text{D}}`.
1143
+
1144
+ See the summary documentation of the class
1145
+ :class:`fakecbed.discretized.CBEDPattern` for additional context.
1146
+
1147
+ Let ``core_attrs`` denote the attribute
1148
+ :attr:`~fancytypes.Checkable.core_attrs`. ``num_disks`` is equal to
1149
+ ``len(core_attrs["undistorted_disks"])``.
1150
+
1151
+ Note that ``num_disks`` should be considered **read-only**.
1152
+
1153
+ """
1154
+ result = self._num_disks
1155
+
1156
+ return result
1157
+
1158
+
1159
+
1160
+ @property
1161
+ def device(self):
1162
+ r"""`torch.device`: The device on which computationally intensive
1163
+ PyTorch operations are performed and attributes of the type
1164
+ :class:`torch.Tensor` are stored.
1165
+
1166
+ Note that ``device`` should be considered **read-only**.
1167
+
1168
+ """
1169
+ result = copy.deepcopy(self._device)
1170
+
1171
+ return result
1172
+
1173
+
1174
+
1175
+ def override_image_then_reapply_mask(
1176
+ self,
1177
+ overriding_image,
1178
+ skip_validation_and_conversion=\
1179
+ _default_skip_validation_and_conversion):
1180
+ r"""Override the target fake CBED pattern image and reapply masking.
1181
+
1182
+ See the summary documentation of the class
1183
+ :class:`fakecbed.discretized.CBEDPattern` for additional context.
1184
+
1185
+ Let ``image``, ``illumination_support``, and ``core_attrs`` denote the
1186
+ attributes :attr:`fakecbed.discretized.CBEDPattern.image`,
1187
+ :attr:`fakecbed.discretized.CBEDPattern.illumination_support`, and
1188
+ :attr:`~fancytypes.Checkable.core_attrs`. ``overriding_image`` is the
1189
+ overriding image.
1190
+
1191
+ Upon calling the method ``override_image_then_reapply_mask``, the
1192
+ attribute ``image`` is updated effectively by:
1193
+
1194
+ .. code-block:: python
1195
+
1196
+ coords_of_cold_pixels = core_attrs["cold_pixels"]
1197
+
1198
+ image = (overriding_image * illumination_support).clip(min=0)
1199
+ for coords_of_cold_pixel in coords_of_cold_pixels:
1200
+ image[coords_of_cold_pixel] = 0
1201
+
1202
+ and then finally min-max normalization is applied to ``image``.
1203
+
1204
+ Parameters
1205
+ ----------
1206
+ overriding_image : `array_like` (`float`, shape=image.shape)
1207
+ The overriding image.
1208
+ skip_validation_and_conversion : `bool`, optional
1209
+ If ``skip_validation_and_conversion`` is set to ``False``, then
1210
+ validations and conversions are performed on the above parameters.
1211
+
1212
+ Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
1213
+ no validations and conversions are performed on the above
1214
+ parameters. This option is desired primarily when the user wants to
1215
+ avoid potentially expensive validation and/or conversion operations.
1216
+
1217
+ """
1218
+ params = locals()
1219
+
1220
+ func_alias = _check_and_convert_skip_validation_and_conversion
1221
+ skip_validation_and_conversion = func_alias(params)
1222
+
1223
+ if (skip_validation_and_conversion == False):
1224
+ params = {"overriding_image": \
1225
+ overriding_image,
1226
+ "num_pixels_across_pattern": \
1227
+ self._num_pixels_across_pattern,
1228
+ "device": \
1229
+ self._device}
1230
+ overriding_image = _check_and_convert_overriding_image(params)
1231
+
1232
+ self._override_image_then_reapply_mask(overriding_image)
1233
+
1234
+ return None
1235
+
1236
+
1237
+
1238
+ def _override_image_then_reapply_mask(self, overriding_image):
1239
+ if self._illumination_support is None:
1240
+ u_x, u_y = self._calc_u_x_and_u_y()
1241
+ method_name = "_calc_illumination_support"
1242
+ method_alias = getattr(self, method_name)
1243
+ self._illumination_support = method_alias(u_x, u_y)
1244
+ illumination_support = self._illumination_support
1245
+
1246
+ coords_of_cold_pixels = self._cold_pixels
1247
+
1248
+ image = overriding_image*illumination_support
1249
+ for coords_of_cold_pixel in coords_of_cold_pixels:
1250
+ image[coords_of_cold_pixel] = 0
1251
+
1252
+ kwargs = {"input_matrix": image}
1253
+ image = self._normalize_matrix(**kwargs)
1254
+
1255
+ self._image = image
1256
+ self._image_has_been_overridden = True
1257
+
1258
+ if self._signal is not None:
1259
+ self._signal.data[0] = image.numpy(force=True)
1260
+
1261
+ return None
1262
+
1263
+
1264
+
1265
+ def _calc_u_x_and_u_y(self):
1266
+ distortion_model = self._distortion_model
1267
+
1268
+ method_alias = distortion_model.get_sampling_grid
1269
+ sampling_grid = method_alias(deep_copy=False)
1270
+
1271
+ try:
1272
+ method_alias = \
1273
+ distortion_model.get_flow_field_of_coord_transform_right_inverse
1274
+ flow_field_of_coord_transform_right_inverse = \
1275
+ method_alias(deep_copy=False)
1276
+ except:
1277
+ err_msg = _cbed_pattern_err_msg_1
1278
+ raise RuntimeError(err_msg)
1279
+
1280
+ u_x = sampling_grid[0] + flow_field_of_coord_transform_right_inverse[0]
1281
+ u_y = sampling_grid[1] + flow_field_of_coord_transform_right_inverse[1]
1282
+
1283
+ return u_x, u_y
1284
+
1285
+
1286
+
1287
+ def _calc_illumination_support(self, u_x, u_y):
1288
+ shape = self._undistorted_outer_illumination_shape
1289
+
1290
+ pooler_kernel_size = self._calc_pooler_kernel_size()
1291
+ pooler = torch.nn.MaxPool2d(kernel_size=pooler_kernel_size)
1292
+
1293
+ illumination_support = (shape._eval(u_x, u_y) != 0)
1294
+ illumination_support = torch.unsqueeze(illumination_support, dim=0)
1295
+ illumination_support = torch.unsqueeze(illumination_support, dim=0)
1296
+ illumination_support = illumination_support.to(dtype=u_x.dtype)
1297
+ illumination_support = pooler(illumination_support)[0, 0]
1298
+ illumination_support = illumination_support.to(dtype=torch.bool)
1299
+
1300
+ return illumination_support
1301
+
1302
+
1303
+
1304
+ def _calc_pooler_kernel_size(self):
1305
+ distortion_model = self._distortion_model
1306
+ num_pixels_across_pattern = self._num_pixels_across_pattern
1307
+
1308
+ distortion_model_core_attrs = \
1309
+ distortion_model.get_core_attrs(deep_copy=False)
1310
+ sampling_grid_dims_in_pixels = \
1311
+ distortion_model_core_attrs["sampling_grid_dims_in_pixels"]
1312
+
1313
+ pooler_kernel_size = (sampling_grid_dims_in_pixels[1]
1314
+ // num_pixels_across_pattern,
1315
+ sampling_grid_dims_in_pixels[0]
1316
+ // num_pixels_across_pattern)
1317
+
1318
+ return pooler_kernel_size
1319
+
1320
+
1321
+
1322
+ def _normalize_matrix(self, input_matrix):
1323
+ if input_matrix.max()-input_matrix.min() > 0:
1324
+ normalization_weight = 1 / (input_matrix.max()-input_matrix.min())
1325
+ normalization_bias = -normalization_weight*input_matrix.min()
1326
+ output_matrix = (input_matrix*normalization_weight
1327
+ + normalization_bias).clip(min=0, max=1)
1328
+ else:
1329
+ output_matrix = torch.zeros_like(input_matrix)
1330
+
1331
+ return output_matrix
1332
+
1333
+
1334
+
1335
+ def get_signal(self, deep_copy=_default_deep_copy):
1336
+ r"""Return the hyperspy signal representation of the fake CBED pattern.
1337
+
1338
+ Parameters
1339
+ ----------
1340
+ deep_copy : `bool`, optional
1341
+ Let ``signal`` denote the attribute
1342
+ :attr:`fakecbed.discretized.CBEDPattern.signal`.
1343
+
1344
+ If ``deep_copy`` is set to ``True``, then a deep copy of ``signal``
1345
+ is returned. Otherwise, a reference to ``signal`` is returned.
1346
+
1347
+ Returns
1348
+ -------
1349
+ signal : :class:`hyperspy._signals.signal2d.Signal2D`
1350
+ The attribute :attr:`fakecbed.discretized.CBEDPattern.signal`.
1351
+
1352
+ """
1353
+ params = {"deep_copy": deep_copy}
1354
+ deep_copy = _check_and_convert_deep_copy(params)
1355
+
1356
+ if self._signal is None:
1357
+ u_x, u_y = self._calc_u_x_and_u_y()
1358
+ method_name = "_calc_signal_and_cache_select_intermediates"
1359
+ method_alias = getattr(self, method_name)
1360
+ self._signal = method_alias(u_x, u_y)
1361
+
1362
+ signal = (copy.deepcopy(self._signal)
1363
+ if (deep_copy == True)
1364
+ else self._signal)
1365
+
1366
+ return signal
1367
+
1368
+
1369
+
1370
+ def _calc_signal_and_cache_select_intermediates(self, u_x, u_y):
1371
+ method_name = "_calc_signal_metadata_and_cache_select_intermediates"
1372
+ method_alias = getattr(self, method_name)
1373
+ signal_metadata = method_alias(u_x, u_y)
1374
+
1375
+ if self._image is None:
1376
+ method_name = "_calc_image_and_cache_select_intermediates"
1377
+ method_alias = getattr(self, method_name)
1378
+ self._image = method_alias(u_x, u_y)
1379
+ image = self._image.numpy(force=True)
1380
+
1381
+ if self._disk_overlap_map is None:
1382
+ method_name = ("_calc_disk_overlap_map"
1383
+ "_and_cache_select_intermediates")
1384
+ method_alias = getattr(self, method_name)
1385
+ self._disk_overlap_map = method_alias(u_x, u_y)
1386
+ disk_overlap_map = self._disk_overlap_map.numpy(force=True)
1387
+
1388
+ illumination_support = self._illumination_support.cpu().detach().clone()
1389
+ illumination_support = illumination_support.numpy(force=True)
1390
+
1391
+ disk_supports = self._disk_supports.numpy(force=True)
1392
+
1393
+ num_disks = self._num_disks
1394
+
1395
+ signal_data_shape = (num_disks+3,) + image.shape
1396
+
1397
+ signal_data = np.zeros(signal_data_shape, dtype=image.dtype)
1398
+ signal_data[0] = image
1399
+ signal_data[1] = illumination_support
1400
+ signal_data[2] = disk_overlap_map
1401
+ signal_data[3:] = disk_supports
1402
+
1403
+ signal = hyperspy.signals.Signal2D(data=signal_data,
1404
+ metadata=signal_metadata)
1405
+ self._update_signal_axes(signal)
1406
+
1407
+ return signal
1408
+
1409
+
1410
+
1411
+ def _calc_signal_metadata_and_cache_select_intermediates(self, u_x, u_y):
1412
+ distortion_model = self._distortion_model
1413
+
1414
+ title = ("Fake Undistorted CBED Intensity Pattern"
1415
+ if distortion_model.is_trivial
1416
+ else "Fake Distorted CBED Intensity Pattern")
1417
+
1418
+ pre_serialized_core_attrs = self.pre_serialize()
1419
+
1420
+ if self._disk_clipping_registry is None:
1421
+ method_name = ("_calc_disk_clipping_registry"
1422
+ "_and_cache_select_intermediates")
1423
+ method_alias = getattr(self, method_name)
1424
+ self._disk_clipping_registry = method_alias(u_x, u_y)
1425
+ disk_clipping_registry = self._disk_clipping_registry.cpu()
1426
+ disk_clipping_registry = disk_clipping_registry.detach().clone()
1427
+ disk_clipping_registry = tuple(disk_clipping_registry.tolist())
1428
+
1429
+ if self._disk_absence_registry is None:
1430
+ method_name = ("_calc_disk_absence_registry"
1431
+ "_and_cache_select_intermediates")
1432
+ method_alias = getattr(self, method_name)
1433
+ self._disk_absence_registry = method_alias(u_x, u_y)
1434
+ disk_absence_registry = self._disk_absence_registry.cpu()
1435
+ disk_absence_registry = disk_absence_registry.detach().clone()
1436
+ disk_absence_registry = tuple(disk_absence_registry.tolist())
1437
+
1438
+ fakecbed_metadata = {"num_disks": \
1439
+ self._num_disks,
1440
+ "disk_clipping_registry": \
1441
+ disk_clipping_registry,
1442
+ "disk_absence_registry": \
1443
+ disk_absence_registry,
1444
+ "pre_serialized_core_attrs": \
1445
+ pre_serialized_core_attrs,
1446
+ "cbed_pattern_image_has_been_overridden": \
1447
+ self._image_has_been_overridden}
1448
+
1449
+ signal_metadata = {"General": {"title": title},
1450
+ "Signal": {"pixel value units": "dimensionless"},
1451
+ "FakeCBED": fakecbed_metadata}
1452
+
1453
+ return signal_metadata
1454
+
1455
+
1456
+
1457
+ def _calc_disk_clipping_registry_and_cache_select_intermediates(self,
1458
+ u_x,
1459
+ u_y):
1460
+ undistorted_disks = self._undistorted_disks
1461
+ num_disks = len(undistorted_disks)
1462
+
1463
+ if num_disks > 0:
1464
+ if self._illumination_support is None:
1465
+ method_name = "_calc_illumination_support"
1466
+ method_alias = getattr(self, method_name)
1467
+ self._illumination_support = method_alias(u_x, u_y)
1468
+ illumination_support = self._illumination_support
1469
+
1470
+ if self._disk_supports is None:
1471
+ method_name = ("_calc_disk_supports"
1472
+ "_and_cache_select_intermediates")
1473
+ method_alias = getattr(self, method_name)
1474
+ self._disk_supports = method_alias(u_x, u_y)
1475
+ disk_supports = self._disk_supports
1476
+
1477
+ clip_support = ~illumination_support
1478
+ for _ in range(2):
1479
+ clip_support = torch.unsqueeze(clip_support, dim=0)
1480
+ clip_support = clip_support.to(dtype=torch.float)
1481
+
1482
+ conv_weights = torch.ones((1, 1, 5, 5),
1483
+ device=illumination_support.device)
1484
+
1485
+ kwargs = {"input": clip_support,
1486
+ "weight": conv_weights,
1487
+ "padding": "same"}
1488
+ clip_support = (torch.nn.functional.conv2d(**kwargs) != 0)
1489
+ clip_support = clip_support.to(dtype=torch.bool)
1490
+
1491
+ clip_support[0, 0, :2, :] = True
1492
+ clip_support[0, 0, -2:, :] = True
1493
+ clip_support[0, 0, :, :2] = True
1494
+ clip_support[0, 0, :, -2:] = True
1495
+ clip_support = clip_support[0, 0]
1496
+
1497
+ disk_clipping_map = disk_supports*clip_support[None, :, :]
1498
+
1499
+ disk_clipping_registry = ((disk_clipping_map.sum(dim=(1, 2)) != 0)
1500
+ + (disk_supports.sum(dim=(1, 2)) == 0))
1501
+ else:
1502
+ disk_clipping_registry = torch.zeros((num_disks,),
1503
+ device=u_x.device,
1504
+ dtype=torch.bool)
1505
+
1506
+ return disk_clipping_registry
1507
+
1508
+
1509
+
1510
+ def _calc_disk_supports_and_cache_select_intermediates(self, u_x, u_y):
1511
+ undistorted_disks = self._undistorted_disks
1512
+ num_disks = len(undistorted_disks)
1513
+
1514
+ if num_disks > 0:
1515
+ if self._illumination_support is None:
1516
+ method_name = "_calc_illumination_support"
1517
+ method_alias = getattr(self, method_name)
1518
+ self._illumination_support = method_alias(u_x, u_y)
1519
+ illumination_support = self._illumination_support
1520
+
1521
+ pooler_kernel_size = self._calc_pooler_kernel_size()
1522
+ pooler = torch.nn.MaxPool2d(kernel_size=pooler_kernel_size)
1523
+
1524
+ disk_supports_shape = (num_disks,)+u_x.shape
1525
+
1526
+ disk_supports = torch.zeros(disk_supports_shape,
1527
+ device=u_x.device)
1528
+ for disk_idx, undistorted_disk in enumerate(undistorted_disks):
1529
+ method_name = "_eval_without_intra_support_shapes"
1530
+ method_alias = getattr(undistorted_disk, method_name)
1531
+ disk_supports[disk_idx] = (method_alias(u_x, u_y) != 0)
1532
+ disk_supports = torch.unsqueeze(disk_supports, dim=0)
1533
+ disk_supports = disk_supports.to(dtype=u_x.dtype)
1534
+ disk_supports = pooler(disk_supports)[0]
1535
+ disk_supports = disk_supports.to(dtype=torch.bool)
1536
+ disk_supports[:, :, :] *= illumination_support[None, :, :]
1537
+ else:
1538
+ num_pixels_across_pattern = self._num_pixels_across_pattern
1539
+
1540
+ disk_supports_shape = (num_disks,
1541
+ num_pixels_across_pattern,
1542
+ num_pixels_across_pattern)
1543
+
1544
+ disk_supports = torch.zeros(disk_supports_shape,
1545
+ device=u_x.device,
1546
+ dtype=torch.bool)
1547
+
1548
+ return disk_supports
1549
+
1550
+
1551
+
1552
+ def _calc_disk_absence_registry_and_cache_select_intermediates(self,
1553
+ u_x,
1554
+ u_y):
1555
+ undistorted_disks = self._undistorted_disks
1556
+ num_disks = len(undistorted_disks)
1557
+
1558
+ if num_disks > 0:
1559
+ if self._disk_supports is None:
1560
+ method_name = ("_calc_disk_supports"
1561
+ "_and_cache_select_intermediates")
1562
+ method_alias = getattr(self, method_name)
1563
+ self._disk_supports = method_alias(u_x, u_y)
1564
+ disk_supports = self._disk_supports
1565
+
1566
+ disk_absence_registry = (disk_supports.sum(dim=(1, 2)) == 0)
1567
+ else:
1568
+ disk_absence_registry = torch.zeros((num_disks,),
1569
+ device=u_x.device,
1570
+ dtype=torch.bool)
1571
+
1572
+ return disk_absence_registry
1573
+
1574
+
1575
+
1576
+ def _calc_image_and_cache_select_intermediates(self, u_x, u_y):
1577
+ method_name = ("_calc_maskless_and_noiseless_image"
1578
+ "_and_cache_select_intermediates")
1579
+ method_alias = getattr(self, method_name)
1580
+ maskless_and_noiseless_image = method_alias(u_x, u_y)
1581
+
1582
+ if self._illumination_support is None:
1583
+ method_name = "_calc_illumination_support"
1584
+ method_alias = getattr(self, method_name)
1585
+ self._illumination_support = method_alias(u_x, u_y)
1586
+ illumination_support = self._illumination_support
1587
+
1588
+ noiseless_image = maskless_and_noiseless_image*illumination_support
1589
+
1590
+ apply_shot_noise = self._apply_shot_noise
1591
+
1592
+ image = (torch.poisson(noiseless_image)
1593
+ if (apply_shot_noise == True)
1594
+ else noiseless_image)
1595
+
1596
+ coords_of_cold_pixels = self._cold_pixels
1597
+
1598
+ for coords_of_cold_pixel in coords_of_cold_pixels:
1599
+ image[coords_of_cold_pixel] = 0
1600
+
1601
+ image = self._apply_detector_partition_inpainting(input_image=image)
1602
+
1603
+ image = self._normalize_matrix(input_matrix=image)
1604
+
1605
+ image = torch.clip(image, min=0)
1606
+
1607
+ return image
1608
+
1609
+
1610
+
1611
+ def _calc_maskless_and_noiseless_image_and_cache_select_intermediates(self,
1612
+ u_x,
1613
+ u_y):
1614
+ jacobian_weights = self._calc_jacobian_weights(u_x, u_y)
1615
+
1616
+ bg = self._calc_bg(u_x, u_y, jacobian_weights)
1617
+
1618
+ if self._disk_supports is None:
1619
+ method_name = ("_calc_disk_supports"
1620
+ "_and_cache_select_intermediates")
1621
+ method_alias = getattr(self, method_name)
1622
+ self._disk_supports = method_alias(u_x, u_y)
1623
+ disk_supports = self._disk_supports
1624
+
1625
+ intra_disk_shapes = self._calc_intra_disk_shapes(u_x,
1626
+ u_y,
1627
+ jacobian_weights)
1628
+
1629
+ gaussian_filter_std_dev = self._gaussian_filter_std_dev
1630
+
1631
+ maskless_and_noiseless_image = (bg
1632
+ + (disk_supports
1633
+ * intra_disk_shapes).sum(dim=0))
1634
+
1635
+ kwargs = {"input_matrix": maskless_and_noiseless_image,
1636
+ "truncate": 4}
1637
+ maskless_and_noiseless_image = self._apply_2d_guassian_filter(**kwargs)
1638
+
1639
+ maskless_and_noiseless_image = torch.clip(maskless_and_noiseless_image,
1640
+ min=0)
1641
+
1642
+ return maskless_and_noiseless_image
1643
+
1644
+
1645
+
1646
+ def _calc_jacobian_weights(self, u_x, u_y):
1647
+ distortion_model = self._distortion_model
1648
+
1649
+ sampling_grid = distortion_model.get_sampling_grid(deep_copy=False)
1650
+
1651
+ spacing = (sampling_grid[1][:, 0], sampling_grid[0][0, :])
1652
+
1653
+ kwargs = {"input": u_x,
1654
+ "spacing": spacing,
1655
+ "dim": None,
1656
+ "edge_order": 2}
1657
+ d_u_x_over_d_q_y, d_u_x_over_d_q_x = torch.gradient(**kwargs)
1658
+
1659
+ kwargs["input"] = u_y
1660
+ d_u_y_over_d_q_y, d_u_y_over_d_q_x = torch.gradient(**kwargs)
1661
+
1662
+ jacobian_weights = torch.abs(d_u_x_over_d_q_x*d_u_y_over_d_q_y
1663
+ - d_u_x_over_d_q_y*d_u_y_over_d_q_x)
1664
+
1665
+ return jacobian_weights
1666
+
1667
+
1668
+
1669
+ def _calc_bg(self, u_x, u_y, jacobian_weights):
1670
+ undistorted_tds_model = self._undistorted_tds_model
1671
+ undistorted_misc_shapes = self._undistorted_misc_shapes
1672
+
1673
+ pooler_kernel_size = self._calc_pooler_kernel_size()
1674
+ pooler = torch.nn.AvgPool2d(kernel_size=pooler_kernel_size)
1675
+
1676
+ bg = undistorted_tds_model._eval(u_x, u_y)
1677
+ for undistorted_misc_shape in undistorted_misc_shapes:
1678
+ bg[:, :] += undistorted_misc_shape._eval(u_x, u_y)[:, :]
1679
+ bg[:, :] *= jacobian_weights[:, :]
1680
+ bg = torch.unsqueeze(bg, dim=0)
1681
+ bg = torch.unsqueeze(bg, dim=0)
1682
+ bg = pooler(bg)[0, 0]
1683
+
1684
+ return bg
1685
+
1686
+
1687
+
1688
+ def _calc_intra_disk_shapes(self, u_x, u_y, jacobian_weights):
1689
+ undistorted_disks = self._undistorted_disks
1690
+ num_disks = len(undistorted_disks)
1691
+
1692
+ if num_disks > 0:
1693
+ pooler_kernel_size = self._calc_pooler_kernel_size()
1694
+ pooler = torch.nn.AvgPool2d(kernel_size=pooler_kernel_size)
1695
+
1696
+ intra_disk_shapes_shape = (num_disks,)+u_x.shape
1697
+
1698
+ intra_disk_shapes = torch.zeros(intra_disk_shapes_shape,
1699
+ device=u_x.device)
1700
+ for disk_idx, undistorted_disk in enumerate(undistorted_disks):
1701
+ method_alias = undistorted_disk._eval_without_support
1702
+ intra_disk_shapes[disk_idx] = method_alias(u_x, u_y)
1703
+ intra_disk_shapes[:, :, :] *= jacobian_weights[None, :, :]
1704
+ intra_disk_shapes = torch.unsqueeze(intra_disk_shapes, dim=0)
1705
+ intra_disk_shapes = pooler(intra_disk_shapes)[0]
1706
+ else:
1707
+ num_pixels_across_pattern = self._num_pixels_across_pattern
1708
+
1709
+ intra_disk_shapes_shape = (num_disks,
1710
+ num_pixels_across_pattern,
1711
+ num_pixels_across_pattern)
1712
+
1713
+ intra_disk_shapes = torch.zeros(intra_disk_shapes_shape,
1714
+ device=u_x.device)
1715
+
1716
+ return intra_disk_shapes
1717
+
1718
+
1719
+
1720
+ def _apply_2d_guassian_filter(self, input_matrix, truncate):
1721
+ intermediate_tensor = input_matrix
1722
+ for axis_idx in range(2):
1723
+ kwargs = {"input_matrix": intermediate_tensor,
1724
+ "truncate": truncate,
1725
+ "axis_idx": axis_idx}
1726
+ intermediate_tensor = self._apply_1d_guassian_filter(**kwargs)
1727
+ output_matrix = intermediate_tensor
1728
+
1729
+ return output_matrix
1730
+
1731
+
1732
+
1733
+ def _apply_1d_guassian_filter(self, input_matrix, truncate, axis_idx):
1734
+ intermediate_tensor = torch.unsqueeze(input_matrix, dim=0)
1735
+ intermediate_tensor = torch.unsqueeze(intermediate_tensor, dim=0)
1736
+
1737
+ sigma = self._gaussian_filter_std_dev
1738
+
1739
+ if sigma > 0:
1740
+ radius = int(truncate*sigma + 0.5)
1741
+ coords = torch.arange(-radius, radius+1, device=input_matrix.device)
1742
+
1743
+ weights = torch.exp(-(coords/sigma)*(coords/sigma)/2)
1744
+ weights /= torch.sum(weights)
1745
+ weights = torch.unsqueeze(weights, dim=axis_idx)
1746
+ weights = torch.unsqueeze(weights, dim=0)
1747
+ weights = torch.unsqueeze(weights, dim=0)
1748
+
1749
+ kwargs = {"input": intermediate_tensor,
1750
+ "weight": weights,
1751
+ "padding": "same"}
1752
+ output_matrix = torch.nn.functional.conv2d(**kwargs)[0, 0]
1753
+ else:
1754
+ output_matrix = input_matrix
1755
+
1756
+ return output_matrix
1757
+
1758
+
1759
+
1760
+ def _apply_detector_partition_inpainting(self, input_image):
1761
+ N_DPW = self._detector_partition_width_in_pixels
1762
+
1763
+ k_I_1 = ((input_image.shape[1]-1)//2) - (N_DPW//2)
1764
+ k_I_2 = k_I_1 + N_DPW - 1
1765
+
1766
+ inpainting_mask = np.zeros(input_image.shape, dtype=bool)
1767
+ inpainting_mask[k_I_1:k_I_2+1, :] = True
1768
+ inpainting_mask[:, k_I_1:k_I_2+1] = True
1769
+
1770
+ kwargs = {"image": input_image.numpy(force=True),
1771
+ "mask": inpainting_mask}
1772
+ output_image = skimage.restoration.inpaint_biharmonic(**kwargs)
1773
+ output_image = torch.from_numpy(output_image)
1774
+ output_image = output_image.to(device=input_image.device,
1775
+ dtype=input_image.dtype)
1776
+
1777
+ return output_image
1778
+
1779
+
1780
+
1781
+ def _calc_disk_overlap_map_and_cache_select_intermediates(self, u_x, u_y):
1782
+ undistorted_disks = self._undistorted_disks
1783
+ num_disks = len(undistorted_disks)
1784
+
1785
+ if num_disks > 0:
1786
+ if self._illumination_support is None:
1787
+ method_name = "_calc_illumination_support"
1788
+ method_alias = getattr(self, method_name)
1789
+ self._illumination_support = method_alias(u_x, u_y)
1790
+ illumination_support = self._illumination_support
1791
+
1792
+ if self._disk_supports is None:
1793
+ method_name = ("_calc_disk_supports"
1794
+ "_and_cache_select_intermediates")
1795
+ method_alias = getattr(self, method_name)
1796
+ self._disk_supports = method_alias(u_x, u_y)
1797
+ disk_supports = self._disk_supports
1798
+
1799
+ disk_overlap_map = (illumination_support
1800
+ * torch.sum(disk_supports, dim=0))
1801
+ else:
1802
+ num_pixels_across_pattern = self._num_pixels_across_pattern
1803
+
1804
+ disk_overlap_map_shape = 2*(num_pixels_across_pattern,)
1805
+
1806
+ disk_overlap_map = torch.zeros(disk_overlap_map_shape,
1807
+ device=u_x.device,
1808
+ dtype=torch.int)
1809
+
1810
+ return disk_overlap_map
1811
+
1812
+
1813
+
1814
+ def _update_signal_axes(self, signal):
1815
+ num_pixels_across_pattern = signal.axes_manager.signal_shape[0]
1816
+
1817
+ sizes = (signal.axes_manager.navigation_shape
1818
+ + signal.axes_manager.signal_shape)
1819
+ scales = (1,
1820
+ 1/num_pixels_across_pattern,
1821
+ -1/num_pixels_across_pattern)
1822
+ offsets = (0,
1823
+ 0.5/num_pixels_across_pattern,
1824
+ 1-(1-0.5)/num_pixels_across_pattern)
1825
+ axes_labels = (r"fake CBED pattern attribute",
1826
+ r"fractional horizontal coordinate",
1827
+ r"fractional vertical coordinate")
1828
+ units = ("dimensionless",)*3
1829
+
1830
+ num_axes = len(units)
1831
+
1832
+ for axis_idx in range(num_axes):
1833
+ axis = hyperspy.axes.UniformDataAxis(size=sizes[axis_idx],
1834
+ scale=scales[axis_idx],
1835
+ offset=offsets[axis_idx],
1836
+ units=units[axis_idx],
1837
+ name=axes_labels[axis_idx])
1838
+ signal.axes_manager[axis_idx].update_from(axis)
1839
+ signal.axes_manager[axis_idx].name = axis.name
1840
+
1841
+ return None
1842
+
1843
+
1844
+
1845
+ @property
1846
+ def signal(self):
1847
+ r"""`hyperspy._signals.signal2d.Signal2D`: The hyperspy signal
1848
+ representation of the fake CBED pattern.
1849
+
1850
+ See the summary documentation of the class
1851
+ :class:`fakecbed.discretized.CBEDPattern` for additional context.
1852
+
1853
+ Let ``image``, ``illumination_support``, ``disk_overlap_map``,
1854
+ ``disk_supports``, ``disk_clipping_registry``,
1855
+ ``image_has_been_overridden``, ``num_disks``, ``disk_absence_registry``,
1856
+ and ``core_attrs`` denote the attributes
1857
+ :attr:`fakecbed.discretized.CBEDPattern.image`,
1858
+ :attr:`fakecbed.discretized.CBEDPattern.illumination_support`,
1859
+ :attr:`fakecbed.discretized.CBEDPattern.disk_overlap_map`,
1860
+ :attr:`fakecbed.discretized.CBEDPattern.disk_supports`,
1861
+ :attr:`fakecbed.discretized.CBEDPattern.disk_clipping_registry`,
1862
+ :attr:`fakecbed.discretized.CBEDPattern.disk_absence_registry`,
1863
+ :attr:`fakecbed.discretized.CBEDPattern.image_has_been_overridden`,
1864
+ :attr:`fakecbed.discretized.CBEDPattern.num_disks`, and
1865
+ :attr:`~fancytypes.Checkable.core_attrs`. Furthermore, let
1866
+ ``pre_serialize`` denote the method
1867
+ :meth:`~fancytypes.PreSerializable.pre_serialize`.
1868
+
1869
+ The signal data, ``signal.data``, is a NumPy array having a shape equal
1870
+ to ``(num_disks+3,)+2*(core_attrs["num_pixels_across_pattern"],)``. The
1871
+ elements of ``signal.data`` are set effectively by:
1872
+
1873
+ .. code-block:: python
1874
+
1875
+ signal.data[0] = image.numpy(force=True)
1876
+ signal.data[1] = illumination_support.numpy(force=True)
1877
+ signal.data[2] = disk_overlap_map.numpy(force=True)
1878
+ signal.data[3:] = disk_supports.numpy(force=True)
1879
+
1880
+ The signal metadata, ``signal.metadata``, stores serializable forms of
1881
+ several instance attributes, in addition to other items of
1882
+ metadata. ``signal.metadata.as_dictionary()`` yields a dictionary
1883
+ ``signal_metadata`` that is calculated effectively by:
1884
+
1885
+ .. code-block:: python
1886
+
1887
+ distortion_model = core_attrs["distortion_model"]
1888
+
1889
+ title = ("Fake Undistorted CBED Intensity Pattern"
1890
+ if distortion_model.is_trivial
1891
+ else "Fake Distorted CBED Intensity Pattern")
1892
+
1893
+ pre_serialized_core_attrs = pre_serialize()
1894
+
1895
+ fakecbed_metadata = {"num_disks": \
1896
+ num_disks,
1897
+ "disk_clipping_registry": \
1898
+ disk_clipping_registry.numpy(force=True),
1899
+ "disk_absence_registry": \
1900
+ disk_absence_registry.numpy(force=True),
1901
+ "pre_serialized_core_attrs": \
1902
+ pre_serialized_core_attrs,
1903
+ "cbed_pattern_image_has_been_overridden": \
1904
+ image_has_been_overridden}
1905
+
1906
+ signal_metadata = {"General": {"title": title},
1907
+ "Signal": {"pixel value units": "dimensionless"},
1908
+ "FakeCBED": fakecbed_metadata}
1909
+
1910
+ Note that ``signal`` should be considered **read-only**.
1911
+
1912
+ """
1913
+ result = self.get_signal(deep_copy=True)
1914
+
1915
+ return result
1916
+
1917
+
1918
+
1919
+ def get_image(self, deep_copy=_default_deep_copy):
1920
+ r"""Return the image of the target fake CBED pattern,
1921
+ :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
1922
+
1923
+ Parameters
1924
+ ----------
1925
+ deep_copy : `bool`, optional
1926
+ Let ``image`` denote the attribute
1927
+ :attr:`fakecbed.discretized.CBEDPattern.image`.
1928
+
1929
+ If ``deep_copy`` is set to ``True``, then a deep copy of ``image``
1930
+ is returned. Otherwise, a reference to ``image`` is returned.
1931
+
1932
+ Returns
1933
+ -------
1934
+ image : `torch.Tensor` (`float`, ndim=2)
1935
+ The attribute :attr:`fakecbed.discretized.CBEDPattern.image`.
1936
+
1937
+ """
1938
+ params = {"deep_copy": deep_copy}
1939
+ deep_copy = _check_and_convert_deep_copy(params)
1940
+
1941
+ if self._image is None:
1942
+ u_x, u_y = self._calc_u_x_and_u_y()
1943
+ method_name = "_calc_image_and_cache_select_intermediates"
1944
+ method_alias = getattr(self, method_name)
1945
+ self._image = method_alias(u_x, u_y)
1946
+
1947
+ image = (self._image.detach().clone()
1948
+ if (deep_copy == True)
1949
+ else self._image)
1950
+
1951
+ return image
1952
+
1953
+
1954
+
1955
+ @property
1956
+ def image(self):
1957
+ r"""`torch.Tensor`: The image of the target fake CBED pattern,
1958
+ :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
1959
+
1960
+ See the summary documentation of the class
1961
+ :class:`fakecbed.discretized.CBEDPattern` for additional context, in
1962
+ particular a description of the calculation of
1963
+ :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
1964
+
1965
+ Let ``core_attrs`` denote the attribute
1966
+ :attr:`~fancytypes.Checkable.core_attrs`.
1967
+
1968
+ ``image`` is a PyTorch tensor having a shape equal to
1969
+ ``2*(core_attrs["num_pixels_across_pattern"],)``.
1970
+
1971
+ For every pair of nonnegative integers ``(n, m)`` that does not raise an
1972
+ ``IndexError`` exception upon calling ``image[n, m]``, ``image[n, m]``
1973
+ is equal to :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`, with the integers
1974
+ :math:`n` and :math:`m` being equal to the values of ``n`` and ``m``
1975
+ respectively.
1976
+
1977
+ Note that ``image`` should be considered **read-only**.
1978
+
1979
+ """
1980
+ result = self.get_image(deep_copy=True)
1981
+
1982
+ return result
1983
+
1984
+
1985
+
1986
+ def get_illumination_support(self, deep_copy=_default_deep_copy):
1987
+ r"""Return the image of the illumination support,
1988
+ :math:`\mathcal{I}_{\text{OI};⌑;n,m}`.
1989
+
1990
+ Parameters
1991
+ ----------
1992
+ deep_copy : `bool`, optional
1993
+ Let ``illumination_support`` denote the attribute
1994
+ :attr:`fakecbed.discretized.CBEDPattern.illumination_support`.
1995
+
1996
+ If ``deep_copy`` is set to ``True``, then a deep copy of
1997
+ ``illumination_support`` is returned. Otherwise, a reference to
1998
+ ``illumination_support`` is returned.
1999
+
2000
+ Returns
2001
+ -------
2002
+ illumination_support : `torch.Tensor` (`bool`, ndim=2)
2003
+ The attribute
2004
+ :attr:`fakecbed.discretized.CBEDPattern.illumination_support`.
2005
+
2006
+ """
2007
+ params = {"deep_copy": deep_copy}
2008
+ deep_copy = _check_and_convert_deep_copy(params)
2009
+
2010
+ if self._illumination_support is None:
2011
+ u_x, u_y = self._calc_u_x_and_u_y()
2012
+ method_name = "_calc_illumination_support"
2013
+ method_alias = getattr(self, method_name)
2014
+ self._illumination_support = method_alias(u_x, u_y)
2015
+
2016
+ illumination_support = (self._illumination_support.detach().clone()
2017
+ if (deep_copy == True)
2018
+ else self._illumination_support)
2019
+
2020
+ return illumination_support
2021
+
2022
+
2023
+
2024
+ @property
2025
+ def illumination_support(self):
2026
+ r"""`torch.Tensor`: The image of the illumination support,
2027
+ :math:`\mathcal{I}_{\text{OI};⌑;n,m}`.
2028
+
2029
+ See the summary documentation of the class
2030
+ :class:`fakecbed.discretized.CBEDPattern` for additional context, in
2031
+ particular a description of the calculation of
2032
+ :math:`\mathcal{I}_{\text{OI};⌑;n,m}`.
2033
+
2034
+ Note that :math:`\mathcal{I}_{\text{CBED};⌑;n,m}` is the image of the
2035
+ target fake CBED pattern, which is stored in the attribute
2036
+ :attr:`fakecbed.discretized.CBEDPattern.image`
2037
+
2038
+ Let ``core_attrs`` denote the attribute
2039
+ :attr:`~fancytypes.Checkable.core_attrs`.
2040
+
2041
+ ``illumination_support`` is a PyTorch tensor having a shape equal to
2042
+ ``2*(core_attrs["num_pixels_across_pattern"],)``.
2043
+
2044
+ For every pair of nonnegative integers ``(n, m)`` that does not raise an
2045
+ ``IndexError`` exception upon calling ``illumination_support[n, m]``,
2046
+ ``illumination_support[n, m]`` is equal to
2047
+ :math:`\mathcal{I}_{\text{OI};⌑;n,m}`, with the integers :math:`n` and
2048
+ :math:`m` being equal to the values of ``n`` and ``m``
2049
+ respectively. Furthermore, for each such pair of integers ``(n, m)``, if
2050
+ ``illumination_support[n, m]`` equals zero, then the corresponding pixel
2051
+ of the image of the target fake CBED pattern is also zero.
2052
+
2053
+ Note that ``illumination_support`` should be considered **read-only**.
2054
+
2055
+ """
2056
+ result = self.get_illumination_support(deep_copy=True)
2057
+
2058
+ return result
2059
+
2060
+
2061
+
2062
+ def get_disk_supports(self, deep_copy=_default_deep_copy):
2063
+ r"""Return the image stack of the disk supports,
2064
+ :math:`\left\{\mathcal{I}_{k;
2065
+ \text{DS};⌑;n,m}\right\}_{k=0}^{N_{\text{D}}-1}`.
2066
+
2067
+ Parameters
2068
+ ----------
2069
+ deep_copy : `bool`, optional
2070
+ Let ``disk_supports`` denote the attribute
2071
+ :attr:`fakecbed.discretized.CBEDPattern.disk_supports`.
2072
+
2073
+ If ``deep_copy`` is set to ``True``, then a deep copy of
2074
+ ``disk_supports`` is returned. Otherwise, a reference to
2075
+ ``disk_supports`` is returned.
2076
+
2077
+ Returns
2078
+ -------
2079
+ disk_supports : `torch.Tensor` (`bool`, ndim=3)
2080
+ The attribute
2081
+ :attr:`fakecbed.discretized.CBEDPattern.disk_supports`.
2082
+
2083
+ """
2084
+ params = {"deep_copy": deep_copy}
2085
+ deep_copy = _check_and_convert_deep_copy(params)
2086
+
2087
+ if self._disk_supports is None:
2088
+ u_x, u_y = self._calc_u_x_and_u_y()
2089
+ method_name = "_calc_disk_supports_and_cache_select_intermediates"
2090
+ method_alias = getattr(self, method_name)
2091
+ self._disk_supports = method_alias(u_x, u_y)
2092
+
2093
+ disk_supports = (self._disk_supports.detach().clone()
2094
+ if (deep_copy == True)
2095
+ else self._disk_supports)
2096
+
2097
+ return disk_supports
2098
+
2099
+
2100
+
2101
+ @property
2102
+ def disk_supports(self):
2103
+ r"""`torch.Tensor`: The image stack of the disk supports,
2104
+ :math:`\left\{\mathcal{I}_{k;
2105
+ \text{DS};⌑;n,m}\right\}_{k=0}^{N_{\text{D}}-1}`.
2106
+
2107
+ See the summary documentation of the class
2108
+ :class:`fakecbed.discretized.CBEDPattern` for additional context, in
2109
+ particular a description of the calculation of
2110
+ :math:`\left\{\mathcal{I}_{k;
2111
+ \text{DS};⌑;n,m}\right\}_{k=0}^{N_{\text{D}}-1}`.
2112
+
2113
+ Let ``core_attrs`` and ``num_disks`` denote the attributes
2114
+ :attr:`~fancytypes.Checkable.core_attrs` and
2115
+ :attr:`fakecbed.discretized.CBEDPattern.num_disks` respectively.
2116
+
2117
+ ``disk_supports`` is a PyTorch tensor having a shape equal to
2118
+ ``(num_disks,) + 2*(core_attrs["num_pixels_across_pattern"],)``.
2119
+
2120
+ For every pair of nonnegative integers ``(k, n, m)`` that does not raise
2121
+ an ``IndexError`` exception upon calling ``disk_supports[k, n, m]``,
2122
+ ``disk_supports[k, n, m]`` is equal to
2123
+ :math:`\mathcal{I}_{k;\text{DS};⌑;n,m}`, with the integers :math:`k`,
2124
+ :math:`n`, and :math:`m` being equal to the values of ``k``, ``n``, and
2125
+ ``m`` respectively. Furthermore, for each such triplet of integers ``(k,
2126
+ n, m)``, if ``disk_supports[k, n, m]`` equals zero, then the
2127
+ :math:`k^{\text{th}}` distorted CBED disk is not supported at the pixel
2128
+ of the image of the target fake CBED pattern specified by ``(n, m)``.
2129
+
2130
+ Note that ``disk_supports`` should be considered **read-only**.
2131
+
2132
+ """
2133
+ result = self.get_disk_supports(deep_copy=True)
2134
+
2135
+ return result
2136
+
2137
+
2138
+
2139
+ def get_disk_overlap_map(self, deep_copy=_default_deep_copy):
2140
+ r"""Return the image of the disk overlap map,
2141
+ :math:`\mathcal{I}_{\text{DOM};⌑;n,m}`.
2142
+
2143
+ Parameters
2144
+ ----------
2145
+ deep_copy : `bool`, optional
2146
+ Let ``disk_overlap_map`` denote the attribute
2147
+ :attr:`fakecbed.discretized.CBEDPattern.disk_overlap_map`.
2148
+
2149
+ If ``deep_copy`` is set to ``True``, then a deep copy of
2150
+ ``disk_overlap_map`` is returned. Otherwise, a reference to
2151
+ ``disk_overlap_map`` is returned.
2152
+
2153
+ Returns
2154
+ -------
2155
+ disk_overlap_map : `torch.Tensor` (`int`, ndim=2)
2156
+ The attribute
2157
+ :attr:`fakecbed.discretized.CBEDPattern.disk_overlap_map`.
2158
+
2159
+ """
2160
+ params = {"deep_copy": deep_copy}
2161
+ deep_copy = _check_and_convert_deep_copy(params)
2162
+
2163
+ if self._disk_overlap_map is None:
2164
+ u_x, u_y = self._calc_u_x_and_u_y()
2165
+ method_name = ("_calc_disk_overlap_map"
2166
+ "_and_cache_select_intermediates")
2167
+ method_alias = getattr(self, method_name)
2168
+ self._disk_overlap_map = method_alias(u_x, u_y)
2169
+
2170
+ disk_overlap_map = (self._disk_overlap_map.detach().clone()
2171
+ if (deep_copy == True)
2172
+ else self._disk_overlap_map)
2173
+
2174
+ return disk_overlap_map
2175
+
2176
+
2177
+
2178
+ @property
2179
+ def disk_overlap_map(self):
2180
+ r"""`torch.Tensor`: The image of the disk overlap map,
2181
+ :math:`\mathcal{I}_{\text{DOM};⌑;n,m}`.
2182
+
2183
+ See the summary documentation of the class
2184
+ :class:`fakecbed.discretized.CBEDPattern` for additional context, in
2185
+ particular a description of the calculation of
2186
+ :math:`\mathcal{I}_{\text{DOM};⌑;n,m}`.
2187
+
2188
+ Note that :math:`\mathcal{I}_{\text{CBED};⌑;n,m}` is the image of the
2189
+ target fake CBED pattern, which is stored in the attribute
2190
+ :attr:`fakecbed.discretized.CBEDPattern.image`
2191
+
2192
+ Let ``core_attrs`` denote the attribute
2193
+ :attr:`~fancytypes.Checkable.core_attrs`.
2194
+
2195
+ ``disk_overlap_map`` is a PyTorch tensor having a shape equal to
2196
+ ``2*(core_attrs["num_pixels_across_pattern"],)``.
2197
+
2198
+ For every pair of nonnegative integers ``(n, m)`` that does not raise an
2199
+ ``IndexError`` exception upon calling ``disk_overlap_map[n, m]``,
2200
+ ``disk_overlap_map[n, m]`` is equal to
2201
+ :math:`\mathcal{I}_{\text{DOM};⌑;n,m}`, with the integers :math:`n` and
2202
+ :math:`m` being equal to the values of ``n`` and ``m`` respectively. In
2203
+ other words, for each such pair of integers ``(n, m)``,
2204
+ ``disk_overlap_map[n, m]`` is equal to the number of imaged CBED disks
2205
+ that overlap at the corresponding pixel of the image of the target fake
2206
+ CBED pattern.
2207
+
2208
+ Note that ``disk_overlap_map`` should be considered **read-only**.
2209
+
2210
+ """
2211
+ result = self.get_disk_overlap_map(deep_copy=True)
2212
+
2213
+ return result
2214
+
2215
+
2216
+
2217
+ def get_disk_clipping_registry(self, deep_copy=_default_deep_copy):
2218
+ r"""Return the disk clipping registry,
2219
+ :math:`\left\{\Omega_{k;\text{DCR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
2220
+
2221
+ Parameters
2222
+ ----------
2223
+ deep_copy : `bool`, optional
2224
+ Let ``disk_clipping_registry`` denote the attribute
2225
+ :attr:`fakecbed.discretized.CBEDPattern.disk_clipping_registry`.
2226
+
2227
+ If ``deep_copy`` is set to ``True``, then a deep copy of
2228
+ ``disk_clipping_registry`` is returned. Otherwise, a reference to
2229
+ ``disk_clipping_registry`` is returned.
2230
+
2231
+ Returns
2232
+ -------
2233
+ disk_clipping_registry : `torch.Tensor` (`bool`, ndim=1)
2234
+ The attribute
2235
+ :attr:`fakecbed.discretized.CBEDPattern.disk_clipping_registry`.
2236
+
2237
+ """
2238
+ params = {"deep_copy": deep_copy}
2239
+ deep_copy = _check_and_convert_deep_copy(params)
2240
+
2241
+ if self._disk_clipping_registry is None:
2242
+ u_x, u_y = self._calc_u_x_and_u_y()
2243
+ method_name = ("_calc_disk_clipping_registry"
2244
+ "_and_cache_select_intermediates")
2245
+ method_alias = getattr(self, method_name)
2246
+ self._disk_clipping_registry = method_alias(u_x, u_y)
2247
+
2248
+ disk_clipping_registry = (self._disk_clipping_registry.detach().clone()
2249
+ if (deep_copy == True)
2250
+ else self._disk_clipping_registry)
2251
+
2252
+ return disk_clipping_registry
2253
+
2254
+
2255
+
2256
+ @property
2257
+ def disk_clipping_registry(self):
2258
+ r"""`torch.Tensor`: The disk clipping registry,
2259
+ :math:`\left\{\Omega_{k;\text{DCR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
2260
+
2261
+ See the summary documentation of the class
2262
+ :class:`fakecbed.discretized.CBEDPattern` for additional context, in
2263
+ particular a description of the calculation of
2264
+ :math:`\left\{\Omega_{k;\text{DCR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
2265
+
2266
+ Note that :math:`N_{\text{D}}` is equal to the value of the attribute
2267
+ :attr:`fakecbed.discretized.CBEDPattern.num_disks`,
2268
+ :math:`\mathcal{I}_{\text{OI};⌑;n,m}` is the image of the illumination
2269
+ support, and :math:`\mathcal{I}_{k;\text{DS};⌑;n,m}` is the image of the
2270
+ support of the :math:`k^{\text{th}}` distorted CBED disk.
2271
+
2272
+ ``disk_clipping_registry`` is a one-dimensional PyTorch tensor of length
2273
+ equal to :math:`N_{\text{D}}`. For every nonnegative integer ``k`` less
2274
+ than :math:`N_{\text{D}}`, ``disk_clipping_registry[k]`` is
2275
+ :math:`\Omega_{k;\text{DCR};⌑}`, with the integer :math:`k` being equal
2276
+ to the value of ``k``. If ``disk_clipping_registry[k]`` is equal to
2277
+ ``False``, then every nonzero pixel of the image of the support of the
2278
+ :math:`k^{\text{th}}` distorted CBED disk is at least two pixels away
2279
+ from (i.e. at least next-nearest neighbours to) every zero-valued pixel
2280
+ of the image of the illumination support and is at least one pixel away
2281
+ from every pixel bordering the image of the illumination support, and
2282
+ that the image of the support of the :math:`k^{\text{th}}` distorted
2283
+ CBED disk has at least one nonzero pixel. Otherwise, if
2284
+ ``disk_clipping_registry[k]`` is equal to ``True``, then the opposite of
2285
+ the above scenario is true.
2286
+
2287
+ Note that ``disk_clipping_registry`` should be considered **read-only**.
2288
+
2289
+ """
2290
+ result = self.get_disk_clipping_registry(deep_copy=True)
2291
+
2292
+ return result
2293
+
2294
+
2295
+
2296
+ def get_disk_absence_registry(self, deep_copy=_default_deep_copy):
2297
+ r"""Return the disk clipping registry,
2298
+ :math:`\left\{\Omega_{k;\text{DAR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
2299
+
2300
+ Parameters
2301
+ ----------
2302
+ deep_copy : `bool`, optional
2303
+ Let ``disk_absence_registry`` denote the attribute
2304
+ :attr:`fakecbed.discretized.CBEDPattern.disk_absence_registry`.
2305
+
2306
+ If ``deep_copy`` is set to ``True``, then a deep copy of
2307
+ ``disk_absence_registry`` is returned. Otherwise, a reference to
2308
+ ``disk_absence_registry`` is returned.
2309
+
2310
+ Returns
2311
+ -------
2312
+ disk_absence_registry : `torch.Tensor` (`bool`, ndim=1)
2313
+ The attribute
2314
+ :attr:`fakecbed.discretized.CBEDPattern.disk_absence_registry`.
2315
+
2316
+ """
2317
+ params = {"deep_copy": deep_copy}
2318
+ deep_copy = _check_and_convert_deep_copy(params)
2319
+
2320
+ if self._disk_absence_registry is None:
2321
+ u_x, u_y = self._calc_u_x_and_u_y()
2322
+ method_name = ("_calc_disk_absence_registry"
2323
+ "_and_cache_select_intermediates")
2324
+ method_alias = getattr(self, method_name)
2325
+ self._disk_absence_registry = method_alias(u_x, u_y)
2326
+
2327
+ disk_absence_registry = (self._disk_absence_registry.detach().clone()
2328
+ if (deep_copy == True)
2329
+ else self._disk_absence_registry)
2330
+
2331
+ return disk_absence_registry
2332
+
2333
+
2334
+
2335
+ @property
2336
+ def disk_absence_registry(self):
2337
+ r"""`torch.Tensor`: The disk clipping registry,
2338
+ :math:`\left\{\Omega_{k;\text{DAR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
2339
+
2340
+ See the summary documentation of the class
2341
+ :class:`fakecbed.discretized.CBEDPattern` for additional context, in
2342
+ particular a description of the calculation of
2343
+ :math:`\left\{\Omega_{k;\text{DCR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
2344
+
2345
+ Note that :math:`N_{\text{D}}` is equal to the value of the attribute
2346
+ :attr:`fakecbed.discretized.CBEDPattern.num_disks`,
2347
+ :math:`\mathcal{I}_{\text{OI};⌑;n,m}` is the image of the illumination
2348
+ support, and :math:`\mathcal{I}_{k;\text{DS};⌑;n,m}` is the image of the
2349
+ support of the :math:`k^{\text{th}}` distorted CBED disk.
2350
+
2351
+ ``disk_absence_registry`` is a one-dimensional PyTorch tensor of length
2352
+ equal to :math:`N_{\text{D}}`. For every nonnegative integer ``k`` less
2353
+ than :math:`N_{\text{D}}`, ``disk_absence_registry[k]`` is
2354
+ :math:`\Omega_{k;\text{DAR};⌑}`, with the integer :math:`k` being equal
2355
+ to the value of ``k``. If ``disk_absence_registry[k]`` is equal to
2356
+ ``False``, then the image of the support of the :math:`k^{\text{th}}`
2357
+ distorted CBED disk has at least one nonzero pixel. Otherwise, if
2358
+ ``disk_absence_registry[k]`` is equal to ``True``, then the opposite of
2359
+ the above scenario is true.
2360
+
2361
+ Note that ``disk_absence_registry`` should be considered **read-only**.
2362
+
2363
+ """
2364
+ result = self.get_disk_absence_registry(deep_copy=True)
2365
+
2366
+ return result
2367
+
2368
+
2369
+
2370
+ @property
2371
+ def image_has_been_overridden(self):
2372
+ r"""`bool`: Equals ``True`` if the image of the target fake CBED pattern
2373
+ has been overridden.
2374
+
2375
+ Let ``override_image_then_reapply_mask``, and ``update`` denote the
2376
+ methods
2377
+ :meth:`fakecbed.discretized.CBEDPattern.override_image_then_reapply_mask`,
2378
+ and :meth:`~fancytypes.Updatable.update`
2379
+ respectively. ``image_has_been_overridden`` equals ``True`` if the
2380
+ method ``override_image_then_reapply_mask`` has been called without
2381
+ raising an exception after either instance update via the method
2382
+ ``update``, or instance construction. Otherwise,
2383
+ ``image_has_been_overridden`` equals ``False``.
2384
+
2385
+ Note that ``image_has_been_overridden`` should be considered
2386
+ **read-only**.
2387
+
2388
+ """
2389
+ result = self._image_has_been_overridden
2390
+
2391
+ return result
2392
+
2393
+
2394
+
2395
+ ###########################
2396
+ ## Define error messages ##
2397
+ ###########################
2398
+
2399
+ _check_and_convert_undistorted_disks_err_msg_1 = \
2400
+ ("The object ``undistorted_disks`` must be a sequence of "
2401
+ "`fakecbed.shapes.NonuniformBoundedShape` objects, where for each element "
2402
+ "``elem`` in ``undistorted_disks``, "
2403
+ "``isinstance(elem.core_attrs['support'], "
2404
+ "(fakecbed.shapes.Circle, fakecbed.shapes.Ellipse))`` evaluates to "
2405
+ "``True``.")
2406
+
2407
+ _check_and_convert_undistorted_misc_shapes_err_msg_1 = \
2408
+ ("The object ``undistorted_misc_shapes`` must be a sequence of objects of "
2409
+ "any of the following types: ("
2410
+ "`fakecbed.shapes.Circle`, "
2411
+ "`fakecbed.shapes.Ellipse`, "
2412
+ "`fakecbed.shapes.Peak`, "
2413
+ "`fakecbed.shapes.Band`, "
2414
+ "`fakecbed.shapes.PlaneWave`, "
2415
+ "`fakecbed.shapes.Arc`, "
2416
+ "`fakecbed.shapes.GenericBlob`, "
2417
+ "`fakecbed.shapes.Orbital`, "
2418
+ "`fakecbed.shapes.Lune`, "
2419
+ "`fakecbed.shapes.NonuniformBoundedShape`).")
2420
+
2421
+ _check_and_convert_distortion_model_err_msg_1 = \
2422
+ ("The dimensions, in units of pixels, of the distortion model sampling "
2423
+ "grid, specified by the object ``distortion_model``, must be divisible "
2424
+ "by the object ``num_pixels_across_pattern``.")
2425
+
2426
+ _check_and_convert_cold_pixels_err_msg_1 = \
2427
+ ("The object ``cold_pixels`` must be a sequence of integer pairs, where "
2428
+ "each integer pair specifies valid pixel coordinates (i.e. row and column "
2429
+ "indices) of a pixel in the discretized fake CBED pattern.")
2430
+
2431
+ _check_and_convert_overriding_image_err_msg_1 = \
2432
+ ("The object ``overriding_image`` must have dimensions, in units of "
2433
+ "pixels, equal to those of the original CBED pattern intensity image "
2434
+ "being overridden, which in this case are ``({}, {})``.")
2435
+
2436
+ _cbed_pattern_err_msg_1 = \
2437
+ ("Failed to generate discretized fake CBED pattern. See traceback for "
2438
+ "details.")