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