fakecbed 0.3.6__py3-none-any.whl → 0.5.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.
fakecbed/discretized.py CHANGED
@@ -48,6 +48,9 @@ import distoptica
48
48
  # For inpainting images.
49
49
  import skimage.restoration
50
50
 
51
+ # For cropping hyperspy signals.
52
+ import empix
53
+
51
54
 
52
55
 
53
56
  # For creating undistorted geometric shapes.
@@ -63,7 +66,8 @@ import fakecbed.tds
63
66
  ##################################
64
67
 
65
68
  # List of public objects in module.
66
- __all__ = ["CBEDPattern"]
69
+ __all__ = ["CBEDPattern",
70
+ "CroppedCBEDPattern"]
67
71
 
68
72
 
69
73
 
@@ -613,8 +617,7 @@ _default_mask_frame = \
613
617
 
614
618
 
615
619
  class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
616
- r"""The parameters of a discretized fake convergent beam electron
617
- diffraction (CBED) pattern.
620
+ r"""A discretized fake convergent beam electron diffraction (CBED) pattern.
618
621
 
619
622
  A series of parameters need to be specified in order to create an image of a
620
623
  fake CBED pattern, with the most important parameters being: the set of
@@ -640,7 +643,7 @@ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
640
643
  model, represented by an instance of the class
641
644
  :class:`distoptica.DistortionModel`, they also specify a coordinate
642
645
  transformation, :math:`\left(T_{⌑;x}\left(u_{x},u_{y}\right),
643
- T_{⌑;x}\left(u_{x},u_{y}\right)\right)`, which maps a given coordinate pair
646
+ T_{⌑;y}\left(u_{x},u_{y}\right)\right)`, which maps a given coordinate pair
644
647
  :math:`\left(u_{x},u_{y}\right)` to a corresponding coordinate pair
645
648
  :math:`\left(q_{x},q_{y}\right)`, and implicitly a right-inverse to said
646
649
  coordinate transformation,
@@ -1005,8 +1008,9 @@ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
1005
1008
 
1006
1009
  .. math ::
1007
1010
  \Omega_{k;\text{DCR};⌑}\leftarrow\begin{cases}
1008
- \text{True}, & \text{if }\sum_{n,m}\mathcal{I}_{k;\text{DCM};⌑;n,m}
1009
- \neq0,\\
1011
+ \text{True}, & \text{if }
1012
+ \left(\sum_{n,m}\mathcal{I}_{k;\text{DCM};⌑;n,m}\neq0\right)\bigparallel
1013
+ \left(\sum_{n,m}\mathcal{I}_{k;\text{DS};⌑;n,m}=0\right),\\
1010
1014
  \text{False}, & \text{otherwise},
1011
1015
  \end{cases}
1012
1016
  :label: Omega_k_DCR__1
@@ -1087,6 +1091,32 @@ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
1087
1091
  right, bottom, and top sides of the mask frame respectively. If all
1088
1092
  elements of ``mask_frame`` are zero, then no pixels in the image of the
1089
1093
  target fake CBED pattern are masked by the mask frame.
1094
+ skip_validation_and_conversion : `bool`, optional
1095
+ Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
1096
+ attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
1097
+ and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
1098
+ being `dict` objects.
1099
+
1100
+ Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
1101
+ representation of the constructor parameters excluding the parameter
1102
+ ``skip_validation_and_conversion``, where each `dict` key ``key`` is a
1103
+ different constructor parameter name, excluding the name
1104
+ ``"skip_validation_and_conversion"``, and
1105
+ ``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
1106
+ constructor parameter with the name given by ``key``.
1107
+
1108
+ If ``skip_validation_and_conversion`` is set to ``False``, then for each
1109
+ key ``key`` in ``params_to_be_mapped_to_core_attrs``,
1110
+ ``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
1111
+ (params_to_be_mapped_to_core_attrs)``.
1112
+
1113
+ Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
1114
+ then ``core_attrs`` is set to
1115
+ ``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
1116
+ primarily when the user wants to avoid potentially expensive deep copies
1117
+ and/or conversions of the `dict` values of
1118
+ ``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
1119
+ copies or conversions are made in this case.
1090
1120
 
1091
1121
  """
1092
1122
  ctor_param_names = ("undistorted_tds_model",
@@ -1221,7 +1251,7 @@ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
1221
1251
  def num_disks(self):
1222
1252
  r"""`int`: The total number of CBED disks defined, :math:`N_{\text{D}}`.
1223
1253
 
1224
- See the summary documentation of the class
1254
+ See the summary documentation for the class
1225
1255
  :class:`fakecbed.discretized.CBEDPattern` for additional context.
1226
1256
 
1227
1257
  Let ``core_attrs`` denote the attribute
@@ -1259,7 +1289,7 @@ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
1259
1289
  _default_skip_validation_and_conversion):
1260
1290
  r"""Override the target fake CBED pattern image and reapply masking.
1261
1291
 
1262
- See the summary documentation of the class
1292
+ See the summary documentation for the class
1263
1293
  :class:`fakecbed.discretized.CBEDPattern` for additional context.
1264
1294
 
1265
1295
  Let ``image``, ``illumination_support``, and ``core_attrs`` denote the
@@ -1655,20 +1685,17 @@ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
1655
1685
  clip_support = torch.unsqueeze(clip_support, dim=0)
1656
1686
  clip_support = clip_support.to(dtype=torch.float)
1657
1687
 
1658
- conv_weights = torch.ones((1, 1, 5, 5),
1688
+ conv_weights = torch.ones((1, 1, 3, 3),
1659
1689
  device=illumination_support.device)
1660
1690
 
1661
1691
  kwargs = {"input": clip_support,
1662
1692
  "weight": conv_weights,
1663
1693
  "padding": "same"}
1664
- clip_support = (torch.nn.functional.conv2d(**kwargs) != 0)
1665
-
1666
- clip_support = clip_support.to(dtype=torch.bool)
1667
- clip_support[0, 0, :T+2, :] = True
1668
- clip_support[0, 0, max(N_I_y-B-2, 0):, :] = True
1669
- clip_support[0, 0, :, :L+2] = True
1670
- clip_support[0, 0, :, max(N_I_x-R-2, 0):] = True
1671
- clip_support = clip_support[0, 0]
1694
+ clip_support = (torch.nn.functional.conv2d(**kwargs) != 0)[0, 0]
1695
+ clip_support[:T+1, :] = True
1696
+ clip_support[max(N_I_y-B-1, 0):, :] = True
1697
+ clip_support[:, :L+1] = True
1698
+ clip_support[:, max(N_I_x-R-1, 0):] = True
1672
1699
 
1673
1700
  return clip_support
1674
1701
 
@@ -1738,8 +1765,6 @@ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
1738
1765
 
1739
1766
  image = self._normalize_matrix(input_matrix=image)
1740
1767
 
1741
- image = torch.clip(image, min=0)
1742
-
1743
1768
  return image
1744
1769
 
1745
1770
 
@@ -1993,7 +2018,7 @@ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
1993
2018
  r"""`hyperspy._signals.signal2d.Signal2D`: The hyperspy signal
1994
2019
  representation of the fake CBED pattern.
1995
2020
 
1996
- See the summary documentation of the class
2021
+ See the summary documentation for the class
1997
2022
  :class:`fakecbed.discretized.CBEDPattern` for additional context.
1998
2023
 
1999
2024
  Let ``image``, ``illumination_support``, ``disk_overlap_map``,
@@ -2111,7 +2136,7 @@ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
2111
2136
  r"""`torch.Tensor`: The image of the target fake CBED pattern,
2112
2137
  :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
2113
2138
 
2114
- See the summary documentation of the class
2139
+ See the summary documentation for the class
2115
2140
  :class:`fakecbed.discretized.CBEDPattern` for additional context, in
2116
2141
  particular a description of the calculation of
2117
2142
  :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
@@ -2180,7 +2205,7 @@ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
2180
2205
  r"""`torch.Tensor`: The image of the illumination support,
2181
2206
  :math:`\mathcal{I}_{\text{OI};⌑;n,m}`.
2182
2207
 
2183
- See the summary documentation of the class
2208
+ See the summary documentation for the class
2184
2209
  :class:`fakecbed.discretized.CBEDPattern` for additional context, in
2185
2210
  particular a description of the calculation of
2186
2211
  :math:`\mathcal{I}_{\text{OI};⌑;n,m}`.
@@ -2258,7 +2283,7 @@ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
2258
2283
  :math:`\left\{\mathcal{I}_{k;
2259
2284
  \text{DS};⌑;n,m}\right\}_{k=0}^{N_{\text{D}}-1}`.
2260
2285
 
2261
- See the summary documentation of the class
2286
+ See the summary documentation for the class
2262
2287
  :class:`fakecbed.discretized.CBEDPattern` for additional context, in
2263
2288
  particular a description of the calculation of
2264
2289
  :math:`\left\{\mathcal{I}_{k;
@@ -2334,7 +2359,7 @@ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
2334
2359
  r"""`torch.Tensor`: The image of the disk overlap map,
2335
2360
  :math:`\mathcal{I}_{\text{DOM};⌑;n,m}`.
2336
2361
 
2337
- See the summary documentation of the class
2362
+ See the summary documentation for the class
2338
2363
  :class:`fakecbed.discretized.CBEDPattern` for additional context, in
2339
2364
  particular a description of the calculation of
2340
2365
  :math:`\mathcal{I}_{\text{DOM};⌑;n,m}`.
@@ -2412,7 +2437,7 @@ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
2412
2437
  r"""`torch.Tensor`: The disk clipping registry,
2413
2438
  :math:`\left\{\Omega_{k;\text{DCR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
2414
2439
 
2415
- See the summary documentation of the class
2440
+ See the summary documentation for the class
2416
2441
  :class:`fakecbed.discretized.CBEDPattern` for additional context, in
2417
2442
  particular a description of the calculation of
2418
2443
  :math:`\left\{\Omega_{k;\text{DCR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
@@ -2428,13 +2453,14 @@ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
2428
2453
  than :math:`N_{\text{D}}`, ``disk_clipping_registry[k]`` is
2429
2454
  :math:`\Omega_{k;\text{DCR};⌑}`, with the integer :math:`k` being equal
2430
2455
  to the value of ``k``. If ``disk_clipping_registry[k]`` is equal to
2431
- ``False``, then every nonzero pixel of the image of the support of the
2432
- :math:`k^{\text{th}}` distorted CBED disk is at least two pixels away
2433
- from (i.e. at least next-nearest neighbours to) every zero-valued pixel
2434
- of the image of the illumination support and is at least one pixel away
2435
- from every pixel bordering the image of the illumination support, and
2436
- that the image of the support of the :math:`k^{\text{th}}` distorted
2437
- CBED disk has at least one nonzero pixel. Otherwise, if
2456
+ ``False``, then the image of the support of the :math:`k^{\text{th}}`
2457
+ distorted CBED disk has at least one nonzero pixel, and that the
2458
+ position of every nonzero pixel of said image is at least two pixels
2459
+ away from (i.e. at least pixel-wise next-nearest neighbours to) the
2460
+ position of every zero-valued pixel of the image of the illumination
2461
+ support, at least two pixels away from the position of every pixel
2462
+ inside the mask frame, and at least one pixel away from every pixel
2463
+ bordering the image of the illumination support. Otherwise, if
2438
2464
  ``disk_clipping_registry[k]`` is equal to ``True``, then the opposite of
2439
2465
  the above scenario is true.
2440
2466
 
@@ -2448,7 +2474,7 @@ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
2448
2474
 
2449
2475
 
2450
2476
  def get_disk_absence_registry(self, deep_copy=_default_deep_copy):
2451
- r"""Return the disk clipping registry,
2477
+ r"""Return the disk absence registry,
2452
2478
  :math:`\left\{\Omega_{k;\text{DAR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
2453
2479
 
2454
2480
  Parameters
@@ -2488,19 +2514,18 @@ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
2488
2514
 
2489
2515
  @property
2490
2516
  def disk_absence_registry(self):
2491
- r"""`torch.Tensor`: The disk clipping registry,
2517
+ r"""`torch.Tensor`: The disk absence registry,
2492
2518
  :math:`\left\{\Omega_{k;\text{DAR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
2493
2519
 
2494
- See the summary documentation of the class
2520
+ See the summary documentation for the class
2495
2521
  :class:`fakecbed.discretized.CBEDPattern` for additional context, in
2496
2522
  particular a description of the calculation of
2497
- :math:`\left\{\Omega_{k;\text{DCR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
2523
+ :math:`\left\{\Omega_{k;\text{DAR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
2498
2524
 
2499
2525
  Note that :math:`N_{\text{D}}` is equal to the value of the attribute
2500
- :attr:`fakecbed.discretized.CBEDPattern.num_disks`,
2501
- :math:`\mathcal{I}_{\text{OI};⌑;n,m}` is the image of the illumination
2502
- support, and :math:`\mathcal{I}_{k;\text{DS};⌑;n,m}` is the image of the
2503
- support of the :math:`k^{\text{th}}` distorted CBED disk.
2526
+ :attr:`fakecbed.discretized.CBEDPattern.num_disks`, and
2527
+ :math:`\mathcal{I}_{k;\text{DS};⌑;n,m}` is the image of the support of
2528
+ the :math:`k^{\text{th}}` distorted CBED disk.
2504
2529
 
2505
2530
  ``disk_absence_registry`` is a one-dimensional PyTorch tensor of length
2506
2531
  equal to :math:`N_{\text{D}}`. For every nonnegative integer ``k`` less
@@ -2546,51 +2571,2746 @@ class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
2546
2571
 
2547
2572
 
2548
2573
 
2549
- ###########################
2550
- ## Define error messages ##
2551
- ###########################
2574
+ def _check_and_convert_cbed_pattern(params):
2575
+ obj_name = "cbed_pattern"
2576
+ obj = params[obj_name]
2552
2577
 
2553
- _check_and_convert_undistorted_disks_err_msg_1 = \
2554
- ("The object ``undistorted_disks`` must be a sequence of "
2555
- "`fakecbed.shapes.NonuniformBoundedShape` objects, where for each element "
2556
- "``elem`` in ``undistorted_disks``, "
2557
- "``isinstance(elem.core_attrs['support'], "
2558
- "(fakecbed.shapes.Circle, fakecbed.shapes.Ellipse))`` evaluates to "
2559
- "``True``.")
2578
+ accepted_types = (fakecbed.discretized.CBEDPattern, type(None))
2560
2579
 
2561
- _check_and_convert_undistorted_misc_shapes_err_msg_1 = \
2562
- ("The object ``undistorted_misc_shapes`` must be a sequence of objects of "
2563
- "any of the following types: ("
2564
- "`fakecbed.shapes.Circle`, "
2565
- "`fakecbed.shapes.Ellipse`, "
2566
- "`fakecbed.shapes.Peak`, "
2567
- "`fakecbed.shapes.Band`, "
2568
- "`fakecbed.shapes.PlaneWave`, "
2569
- "`fakecbed.shapes.Arc`, "
2570
- "`fakecbed.shapes.GenericBlob`, "
2571
- "`fakecbed.shapes.Orbital`, "
2572
- "`fakecbed.shapes.Lune`, "
2573
- "`fakecbed.shapes.NonuniformBoundedShape`).")
2580
+ if isinstance(obj, accepted_types[-1]):
2581
+ cbed_pattern = accepted_types[0]()
2582
+ else:
2583
+ kwargs = {"obj": obj,
2584
+ "obj_name": obj_name,
2585
+ "accepted_types": accepted_types}
2586
+ czekitout.check.if_instance_of_any_accepted_types(**kwargs)
2587
+ cbed_pattern = copy.deepcopy(obj)
2574
2588
 
2575
- _check_and_convert_distortion_model_err_msg_1 = \
2576
- ("The dimensions, in units of pixels, of the distortion model sampling "
2577
- "grid, specified by the object ``distortion_model``, must be divisible "
2578
- "by the object ``num_pixels_across_pattern``.")
2589
+ return cbed_pattern
2579
2590
 
2580
- _check_and_convert_rng_seed_err_msg_1 = \
2581
- ("The object ``rng_seed`` must be either a nonnegative integer or of the "
2582
- "type `NoneType`.")
2583
2591
 
2584
- _check_and_convert_cold_pixels_err_msg_1 = \
2585
- ("The object ``cold_pixels`` must be a sequence of integer pairs, where "
2586
- "each integer pair specifies valid pixel coordinates (i.e. row and column "
2587
- "indices) of a pixel in the discretized fake CBED pattern.")
2588
2592
 
2589
- _check_and_convert_overriding_image_err_msg_1 = \
2590
- ("The object ``overriding_image`` must have dimensions, in units of "
2591
- "pixels, equal to those of the original CBED pattern intensity image "
2592
- "being overridden, which in this case are ``({}, {})``.")
2593
+ def _pre_serialize_cbed_pattern(cbed_pattern):
2594
+ obj_to_pre_serialize = cbed_pattern
2595
+ serializable_rep = obj_to_pre_serialize.pre_serialize()
2596
+
2597
+ return serializable_rep
2593
2598
 
2594
- _cbed_pattern_err_msg_1 = \
2595
- ("Failed to generate discretized fake CBED pattern. See traceback for "
2596
- "details.")
2599
+
2600
+
2601
+ def _de_pre_serialize_cbed_pattern(serializable_rep):
2602
+ cbed_pattern = \
2603
+ fakecbed.discretized.CBEDPattern.de_pre_serialize(serializable_rep)
2604
+
2605
+ return cbed_pattern
2606
+
2607
+
2608
+
2609
+ def _check_and_convert_cropping_window_center(params):
2610
+ obj_name = "cropping_window_center"
2611
+ obj = params[obj_name]
2612
+
2613
+ current_func_name = "_check_and_convert_cropping_window_center"
2614
+
2615
+ if obj is not None:
2616
+ try:
2617
+ func_alias = czekitout.convert.to_pair_of_floats
2618
+ kwargs = {"obj": obj, "obj_name": obj_name}
2619
+ cropping_window_center = func_alias(**kwargs)
2620
+ except:
2621
+ err_msg = globals()[current_func_name+"_err_msg_1"]
2622
+ raise TypeError(err_msg)
2623
+ else:
2624
+ cls_alias = fakecbed.discretized.CBEDPattern
2625
+ cbed_pattern = (params["cbed_pattern"]
2626
+ if isinstance(params["cbed_pattern"], cls_alias)
2627
+ else _check_and_convert_cbed_pattern(params))
2628
+ params["cbed_pattern"] = cbed_pattern
2629
+
2630
+ cbed_pattern_core_attrs = cbed_pattern.get_core_attrs(deep_copy=False)
2631
+ N_W_h = cbed_pattern_core_attrs["num_pixels_across_pattern"]
2632
+ N_W_v = N_W_h
2633
+
2634
+ h_scale = 1/N_W_h
2635
+ v_scale = -1/N_W_v
2636
+
2637
+ h_offset = 0.5/N_W_h
2638
+ v_offset = 1 - (1-0.5)/N_W_v
2639
+
2640
+ cropping_window_center = (h_offset + h_scale*((N_W_h-1)//2),
2641
+ v_offset + v_scale*((N_W_v-1)//2))
2642
+
2643
+ return cropping_window_center
2644
+
2645
+
2646
+
2647
+ def _pre_serialize_cropping_window_center(cropping_window_center):
2648
+ obj_to_pre_serialize = cropping_window_center
2649
+ serializable_rep = obj_to_pre_serialize
2650
+
2651
+ return serializable_rep
2652
+
2653
+
2654
+
2655
+ def _de_pre_serialize_cropping_window_center(serializable_rep):
2656
+ cropping_window_center = serializable_rep
2657
+
2658
+ return cropping_window_center
2659
+
2660
+
2661
+
2662
+ def _check_and_convert_cropping_window_dims_in_pixels(params):
2663
+ obj_name = "cropping_window_dims_in_pixels"
2664
+ obj = params[obj_name]
2665
+
2666
+ current_func_name = "_check_and_convert_cropping_window_dims_in_pixels"
2667
+
2668
+ if obj is not None:
2669
+ try:
2670
+ kwargs = {"obj": obj, "obj_name": obj_name}
2671
+ func_alias = czekitout.convert.to_pair_of_positive_ints
2672
+ cropping_window_dims_in_pixels = func_alias(**kwargs)
2673
+ except:
2674
+ err_msg = globals()[current_func_name+"_err_msg_1"]
2675
+ raise TypeError(err_msg)
2676
+ else:
2677
+ cls_alias = fakecbed.discretized.CBEDPattern
2678
+ cbed_pattern = (params["cbed_pattern"]
2679
+ if isinstance(params["cbed_pattern"], cls_alias)
2680
+ else _check_and_convert_cbed_pattern(params))
2681
+ params["cbed_pattern"] = cbed_pattern
2682
+
2683
+ cbed_pattern_core_attrs = cbed_pattern.get_core_attrs(deep_copy=False)
2684
+ N_W_h = cbed_pattern_core_attrs["num_pixels_across_pattern"]
2685
+ N_W_v = N_W_h
2686
+
2687
+ cropping_window_dims_in_pixels = (N_W_h, N_W_v)
2688
+
2689
+ return cropping_window_dims_in_pixels
2690
+
2691
+
2692
+
2693
+ def _pre_serialize_cropping_window_dims_in_pixels(
2694
+ cropping_window_dims_in_pixels):
2695
+ obj_to_pre_serialize = cropping_window_dims_in_pixels
2696
+ serializable_rep = obj_to_pre_serialize
2697
+
2698
+ return serializable_rep
2699
+
2700
+
2701
+
2702
+ def _de_pre_serialize_cropping_window_dims_in_pixels(serializable_rep):
2703
+ cropping_window_dims_in_pixels = serializable_rep
2704
+
2705
+ return cropping_window_dims_in_pixels
2706
+
2707
+
2708
+
2709
+ def _check_and_convert_principal_disk_idx(params):
2710
+ obj_name = "principal_disk_idx"
2711
+ func_alias = czekitout.convert.to_nonnegative_int
2712
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
2713
+ principal_disk_idx = func_alias(**kwargs)
2714
+
2715
+ return principal_disk_idx
2716
+
2717
+
2718
+
2719
+ def _pre_serialize_principal_disk_idx(principal_disk_idx):
2720
+ obj_to_pre_serialize = principal_disk_idx
2721
+ serializable_rep = obj_to_pre_serialize
2722
+
2723
+ return serializable_rep
2724
+
2725
+
2726
+
2727
+ def _de_pre_serialize_principal_disk_idx(serializable_rep):
2728
+ principal_disk_idx = serializable_rep
2729
+
2730
+ return principal_disk_idx
2731
+
2732
+
2733
+
2734
+ def _check_and_convert_disk_boundary_sample_size(params):
2735
+ obj_name = "disk_boundary_sample_size"
2736
+ func_alias = czekitout.convert.to_positive_int
2737
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
2738
+ disk_boundary_sample_size = func_alias(**kwargs)
2739
+
2740
+ return disk_boundary_sample_size
2741
+
2742
+
2743
+
2744
+ def _pre_serialize_disk_boundary_sample_size(disk_boundary_sample_size):
2745
+ obj_to_pre_serialize = disk_boundary_sample_size
2746
+ serializable_rep = obj_to_pre_serialize
2747
+
2748
+ return serializable_rep
2749
+
2750
+
2751
+
2752
+ def _de_pre_serialize_disk_boundary_sample_size(serializable_rep):
2753
+ disk_boundary_sample_size = serializable_rep
2754
+
2755
+ return disk_boundary_sample_size
2756
+
2757
+
2758
+
2759
+ _default_cbed_pattern = None
2760
+ _default_cropping_window_center = None
2761
+ _default_cropping_window_dims_in_pixels = None
2762
+ _default_principal_disk_idx = 0
2763
+ _default_disk_boundary_sample_size = 512
2764
+
2765
+
2766
+
2767
+ class CroppedCBEDPattern(fancytypes.PreSerializableAndUpdatable):
2768
+ r"""A discretized cropped fake convergent beam electron diffraction (CBED)
2769
+ pattern.
2770
+
2771
+ A series of parameters need to be specified in order to create an image of a
2772
+ cropped fake CBED pattern, which are: the set of parameters of a discretized
2773
+ fake CBED pattern; the spatial dimensions and the target center of the
2774
+ cropping window to be applied to said pattern; an index specifying the CBED
2775
+ disk, should it exist, for which to analyze in further detail than any other
2776
+ CBED disk in the pattern; the number of points to sample from the boundary
2777
+ of the unclipped support of the CBED disk specified by the aforementioned
2778
+ index, again should the disk exist; and the mask frame to be applied after
2779
+ cropping the fake CBED pattern.
2780
+
2781
+ Parameters
2782
+ ----------
2783
+ cbed_pattern : :class:`fakecbed.discretized.CBEDPattern` | `None`, optional
2784
+ The discretized fake CBED pattern for which to crop. If ``cbed_pattern``
2785
+ is set to ``None``, then the parameter will be reassigned to the value
2786
+ ``fakecbed.discretized.CBEDPattern()``.
2787
+ cropping_window_center : `array_like` (`float`, shape=(2,)) | `None`, optional
2788
+ If ``cropping_window_center`` is set to ``None``, then the center of the
2789
+ cropping window is set to the fractional coordinates of the discretized
2790
+ fake CBED pattern corresponding to the pixel that is ``(h_dim+1)//2-1``
2791
+ pixels to the right of the upper left corner pixel, and
2792
+ ``(v_dim+1)//2-1`` pixels below the same corner, where ``h_dim`` and
2793
+ ``v_dim`` are the horizontal and vertical dimensions of the discretized
2794
+ fake CBED pattern in units of pixels. Otherwise, if
2795
+ ``cropping_window_center`` is set to a pair of floating-point numbers,
2796
+ then ``cropping_window_center[0]`` and ``cropping_window_center[1]``
2797
+ specify the fractional horizontal and vertical coordinates,
2798
+ respectively, of the center of the cropping window prior to the subpixel
2799
+ shift to the nearest pixel. Note that the crop is applied after the
2800
+ subpixel shift to the nearest pixel. Moreover, note that the fractional
2801
+ coordinate pair :math:`\left(0, 0\right)` corresponds to the bottom left
2802
+ corner of the fake CBED pattern.
2803
+
2804
+ We define the center of the cropping window to be ``(N_W_h+1)//2 - 1``
2805
+ pixels to the right of the upper left corner of the cropping window, and
2806
+ ``(N_W_v+1)//2 - 1`` pixels below the same corner, where ``N_W_h`` and
2807
+ ``N_W_v`` are the horizontal and vertical dimensions of the cropping
2808
+ window in units of pixels.
2809
+ cropping_window_dims_in_pixels : `array_like` (`int`, shape=(2,)) | `None`, optional
2810
+ If ``cropping_window_dims_in_pixels`` is set to ``None``, then the
2811
+ dimensions of the cropping window are set to the dimensions of
2812
+ discretized fake CBED pattern. Otherwise, if
2813
+ ``cropping_window_dims_in_pixels`` is set to a pair of positive
2814
+ integers, then ``cropping_window_dims_in_pixels[0]`` and
2815
+ ``cropping_window_dims_in_pixels[1]`` specify the horizontal and
2816
+ vertical dimensions of the cropping window in units of pixels.
2817
+ principal_disk_idx : `int`, optional
2818
+ A nonnegative integer specifying the CBED disk, should it exist, for
2819
+ which to analyze in further detail than any other CBED disk in the
2820
+ pattern. Assuming that ``cbed_pattern`` has already been reassigned if
2821
+ necessary, and that ``principal_disk_idx <
2822
+ len(cbed_pattern.core_attrs["undistorted_disks"])``, then
2823
+ ``principal_disk_idx`` specifies the CBED disk constructed from the
2824
+ undistorted intensity pattern stored in
2825
+ ``cbed_pattern.core_attrs["undistorted_disks"][prinicpal_disk_idx]``. If
2826
+ ``principal_disk_idx >=
2827
+ len(cbed_pattern.core_attrs["undistorted_disks"])``, then
2828
+ ``principal_disk_idx`` specifies a nonexistent CBED disk. See the
2829
+ summary documentation for the class
2830
+ :class:`fakecbed.discretized.CBEDPattern`, in particular the description
2831
+ of the construction parameter ``undistorted_disks`` therein, for
2832
+ additional context.
2833
+ disk_boundary_sample_size : `int`, optional
2834
+ The number of points to sample from the boundary of the unclipped
2835
+ support of the CBED disk specified by ``principal_disk_idx``, should the
2836
+ disk exist, upon calling the method
2837
+ :class:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_boundary_pts_in_cropped_image_fractional_coords`.
2838
+ Must be a positive integer.
2839
+ mask_frame : `array_like` (`int`, shape=(4,)), optional
2840
+ ``mask_frame`` specifies the mask frame to be applied after cropping the
2841
+ fake CBED pattern. ``mask_frame[0]``, ``mask_frame[1]``,
2842
+ ``mask_frame[2]``, and ``mask_frame[3]`` are the widths, in units of
2843
+ pixels, of the left, right, bottom, and top sides of the mask frame
2844
+ respectively.
2845
+ skip_validation_and_conversion : `bool`, optional
2846
+ Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
2847
+ attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
2848
+ and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
2849
+ being `dict` objects.
2850
+
2851
+ Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
2852
+ representation of the constructor parameters excluding the parameter
2853
+ ``skip_validation_and_conversion``, where each `dict` key ``key`` is a
2854
+ different constructor parameter name, excluding the name
2855
+ ``"skip_validation_and_conversion"``, and
2856
+ ``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
2857
+ constructor parameter with the name given by ``key``.
2858
+
2859
+ If ``skip_validation_and_conversion`` is set to ``False``, then for each
2860
+ key ``key`` in ``params_to_be_mapped_to_core_attrs``,
2861
+ ``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
2862
+ (params_to_be_mapped_to_core_attrs)``.
2863
+
2864
+ Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
2865
+ then ``core_attrs`` is set to
2866
+ ``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
2867
+ primarily when the user wants to avoid potentially expensive deep copies
2868
+ and/or conversions of the `dict` values of
2869
+ ``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
2870
+ copies or conversions are made in this case.
2871
+
2872
+ """
2873
+ ctor_param_names = ("cbed_pattern",
2874
+ "cropping_window_center",
2875
+ "cropping_window_dims_in_pixels",
2876
+ "principal_disk_idx",
2877
+ "disk_boundary_sample_size",
2878
+ "mask_frame")
2879
+ kwargs = {"namespace_as_dict": globals(),
2880
+ "ctor_param_names": ctor_param_names}
2881
+
2882
+ _validation_and_conversion_funcs_ = \
2883
+ fancytypes.return_validation_and_conversion_funcs(**kwargs)
2884
+ _pre_serialization_funcs_ = \
2885
+ fancytypes.return_pre_serialization_funcs(**kwargs)
2886
+ _de_pre_serialization_funcs_ = \
2887
+ fancytypes.return_de_pre_serialization_funcs(**kwargs)
2888
+
2889
+ del ctor_param_names, kwargs
2890
+
2891
+
2892
+
2893
+ def __init__(self,
2894
+ cbed_pattern=\
2895
+ _default_cbed_pattern,
2896
+ cropping_window_center=\
2897
+ _default_cropping_window_center,
2898
+ cropping_window_dims_in_pixels=\
2899
+ _default_cropping_window_dims_in_pixels,
2900
+ principal_disk_idx=\
2901
+ _default_principal_disk_idx,
2902
+ disk_boundary_sample_size=\
2903
+ _default_disk_boundary_sample_size,
2904
+ mask_frame=\
2905
+ _default_mask_frame,
2906
+ skip_validation_and_conversion=\
2907
+ _default_skip_validation_and_conversion):
2908
+ ctor_params = {key: val
2909
+ for key, val in locals().items()
2910
+ if (key not in ("self", "__class__"))}
2911
+ kwargs = ctor_params
2912
+ kwargs["skip_cls_tests"] = True
2913
+ fancytypes.PreSerializableAndUpdatable.__init__(self, **kwargs)
2914
+
2915
+ self.execute_post_core_attrs_update_actions()
2916
+
2917
+ return None
2918
+
2919
+
2920
+
2921
+ @classmethod
2922
+ def get_validation_and_conversion_funcs(cls):
2923
+ validation_and_conversion_funcs = \
2924
+ cls._validation_and_conversion_funcs_.copy()
2925
+
2926
+ return validation_and_conversion_funcs
2927
+
2928
+
2929
+
2930
+ @classmethod
2931
+ def get_pre_serialization_funcs(cls):
2932
+ pre_serialization_funcs = \
2933
+ cls._pre_serialization_funcs_.copy()
2934
+
2935
+ return pre_serialization_funcs
2936
+
2937
+
2938
+
2939
+ @classmethod
2940
+ def get_de_pre_serialization_funcs(cls):
2941
+ de_pre_serialization_funcs = \
2942
+ cls._de_pre_serialization_funcs_.copy()
2943
+
2944
+ return de_pre_serialization_funcs
2945
+
2946
+
2947
+
2948
+ def execute_post_core_attrs_update_actions(self):
2949
+ self_core_attrs = self.get_core_attrs(deep_copy=False)
2950
+ for self_core_attr_name in self_core_attrs:
2951
+ attr_name = "_"+self_core_attr_name
2952
+ attr = self_core_attrs[self_core_attr_name]
2953
+ setattr(self, attr_name, attr)
2954
+
2955
+ self._num_disks = \
2956
+ self._cbed_pattern._num_disks
2957
+ self._device = \
2958
+ self._cbed_pattern._device
2959
+ self._image_has_been_overridden = \
2960
+ self._cbed_pattern._image_has_been_overridden
2961
+
2962
+ cropped_blank_signal = self._generate_cropped_blank_signal()
2963
+
2964
+ self._cropped_blank_signal_offsets = \
2965
+ (cropped_blank_signal.axes_manager[0].offset,
2966
+ cropped_blank_signal.axes_manager[1].offset,
2967
+ cropped_blank_signal.axes_manager[2].offset)
2968
+
2969
+ attr_name_subset = ("_illumination_support",
2970
+ "_image",
2971
+ "_signal",
2972
+ "_disk_clipping_registry",
2973
+ "_disk_supports",
2974
+ "_disk_absence_registry",
2975
+ "_principal_disk_is_clipped",
2976
+ "_principal_disk_is_absent",
2977
+ "_disk_overlap_map",
2978
+ "_principal_disk_is_overlapping",
2979
+ "_principal_disk_analysis_results",
2980
+ "_principal_disk_bounding_box"
2981
+ "_in_uncropped_image_fractional_coords",
2982
+ "_principal_disk_bounding_box"
2983
+ "_in_cropped_image_fractional_coords",
2984
+ "_principal_disk_boundary_pts"
2985
+ "_in_uncropped_image_fractional_coords",
2986
+ "_principal_disk_boundary_pts"
2987
+ "_in_cropped_image_fractional_coords")
2988
+
2989
+ for attr_name in attr_name_subset:
2990
+ setattr(self, attr_name, None)
2991
+
2992
+ return None
2993
+
2994
+
2995
+
2996
+ def _generate_cropped_blank_signal(self):
2997
+ uncropped_blank_signal = self._generate_uncropped_blank_signal()
2998
+
2999
+ optional_params = self._generate_optional_cropping_params()
3000
+
3001
+ kwargs = {"input_signal": uncropped_blank_signal,
3002
+ "optional_params": optional_params}
3003
+ cropped_blank_signal = empix.crop(**kwargs)
3004
+
3005
+ return cropped_blank_signal
3006
+
3007
+
3008
+
3009
+ def _generate_uncropped_blank_signal(self):
3010
+ cbed_pattern_core_attrs = \
3011
+ self._cbed_pattern.get_core_attrs(deep_copy=False)
3012
+
3013
+ num_disks = self._num_disks
3014
+ N_x = cbed_pattern_core_attrs["num_pixels_across_pattern"]
3015
+
3016
+ uncropped_blank_signal_data = np.zeros((3+num_disks, N_x, N_x),
3017
+ dtype=float)
3018
+ kwargs = {"data": uncropped_blank_signal_data}
3019
+ uncropped_blank_signal = hyperspy.signals.Signal2D(**kwargs)
3020
+
3021
+ kwargs = {"signal": uncropped_blank_signal}
3022
+ self._cbed_pattern._update_signal_axes(**kwargs)
3023
+
3024
+ return uncropped_blank_signal
3025
+
3026
+
3027
+
3028
+ def _generate_optional_cropping_params(self):
3029
+ kwargs = {"center": self._cropping_window_center,
3030
+ "window_dims": self._cropping_window_dims_in_pixels,
3031
+ "pad_mode": "zeros",
3032
+ "apply_symmetric_mask": False,
3033
+ "title": "",
3034
+ "skip_validation_and_conversion": True}
3035
+ optional_cropping_params = empix.OptionalCroppingParams(**kwargs)
3036
+
3037
+ return optional_cropping_params
3038
+
3039
+
3040
+
3041
+ def update(self,
3042
+ new_core_attr_subset_candidate,
3043
+ skip_validation_and_conversion=\
3044
+ _default_skip_validation_and_conversion):
3045
+ super().update(new_core_attr_subset_candidate,
3046
+ skip_validation_and_conversion)
3047
+ self.execute_post_core_attrs_update_actions()
3048
+
3049
+ return None
3050
+
3051
+
3052
+
3053
+ @property
3054
+ def num_disks(self):
3055
+ r"""`int`: The total number of CBED disks defined, :math:`N_{\text{D}}`.
3056
+
3057
+ See the summary documentation for the class
3058
+ :class:`fakecbed.discretized.CBEDPattern` for additional context.
3059
+
3060
+ Let ``core_attrs`` denote the attribute
3061
+ :attr:`~fancytypes.Checkable.core_attrs`. ``num_disks`` is equal to
3062
+ ``core_attrs["cbed_pattern"].num_disks``.
3063
+
3064
+ Note that ``num_disks`` should be considered **read-only**.
3065
+
3066
+ """
3067
+ result = self._num_disks
3068
+
3069
+ return result
3070
+
3071
+
3072
+
3073
+ @property
3074
+ def device(self):
3075
+ r"""`torch.device`: The device on which computationally intensive
3076
+ PyTorch operations are performed and attributes of the type
3077
+ :class:`torch.Tensor` are stored.
3078
+
3079
+ Note that ``device`` should be considered **read-only**.
3080
+
3081
+ """
3082
+ result = copy.deepcopy(self._device)
3083
+
3084
+ return result
3085
+
3086
+
3087
+
3088
+ def get_principal_disk_boundary_pts_in_uncropped_image_fractional_coords(
3089
+ self, deep_copy=_default_deep_copy):
3090
+ r"""Return the sample of points on the boundary of the "principal" CBED
3091
+ disk, should it exist, in fractional coordinates of the uncropped CBED
3092
+ pattern.
3093
+
3094
+ Let ``core_attrs`` denote the attribute
3095
+ :attr:`~fancytypes.Checkable.core_attrs`.
3096
+
3097
+ Parameters
3098
+ ----------
3099
+ deep_copy : `bool`, optional
3100
+ Let
3101
+ ``principal_disk_boundary_pts_in_uncropped_image_fractional_coords``
3102
+ denote the attribute
3103
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_boundary_pts_in_uncropped_image_fractional_coords`.
3104
+
3105
+ If ``deep_copy`` is set to ``True``, then a deep copy of
3106
+ ``principal_disk_boundary_pts_in_uncropped_image_fractional_coords``
3107
+ is returned. Otherwise, a reference to
3108
+ ``principal_disk_boundary_pts_in_uncropped_image_fractional_coords``
3109
+ is returned.
3110
+
3111
+ Returns
3112
+ -------
3113
+ principal_disk_boundary_pts_in_uncropped_image_fractional_coords : `torch.Tensor` (`float`, shape=(core_attrs["disk_boundary_sample_size"], 2)) | `None`
3114
+ The attribute
3115
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_boundary_pts_in_uncropped_image_fractional_coords`.
3116
+
3117
+ """
3118
+ params = {"deep_copy": deep_copy}
3119
+ deep_copy = _check_and_convert_deep_copy(params)
3120
+
3121
+ attr_name = ("_principal_disk_boundary_pts"
3122
+ "_in_uncropped_image_fractional_coords")
3123
+ attr = getattr(self, attr_name)
3124
+
3125
+ if attr is None:
3126
+ method_name = ("_calc_principal_disk_analysis_results"
3127
+ "_and_cache_select_intermediates")
3128
+ method_alias = getattr(self, method_name)
3129
+ self._principal_disk_analysis_results = method_alias()
3130
+
3131
+ attr = getattr(self, attr_name)
3132
+
3133
+ principal_disk_boundary_pts_in_uncropped_image_fractional_coords = \
3134
+ (attr
3135
+ if ((deep_copy == False) or (attr is None))
3136
+ else attr.detach().clone())
3137
+
3138
+ return principal_disk_boundary_pts_in_uncropped_image_fractional_coords
3139
+
3140
+
3141
+
3142
+ def _calc_principal_disk_analysis_results_and_cache_select_intermediates(
3143
+ self):
3144
+ principal_disk_analysis_results = tuple()
3145
+
3146
+ method_name_set = \
3147
+ ("_calc_disk_boundary_pts_in_uncropped_image_fractional_coords",
3148
+ "_calc_disk_bounding_box_in_uncropped_image_fractional_coords",
3149
+ "_calc_disk_boundary_pts_in_cropped_image_fractional_coords",
3150
+ "_calc_disk_bounding_box_in_cropped_image_fractional_coords")
3151
+
3152
+ for method_alias_idx, method_name in enumerate(method_name_set):
3153
+ method_alias = getattr(self, method_name)
3154
+
3155
+ if method_alias_idx in (0,):
3156
+ kwargs = \
3157
+ {"disk_idx": \
3158
+ self._principal_disk_idx}
3159
+ elif method_alias_idx in (1, 2):
3160
+ kwargs = \
3161
+ {"disk_boundary_pts_in_uncropped_image_fractional_coords": \
3162
+ principal_disk_analysis_results[0]}
3163
+ else:
3164
+ kwargs = \
3165
+ {"disk_boundary_pts_in_cropped_image_fractional_coords": \
3166
+ principal_disk_analysis_results[2]}
3167
+
3168
+ principal_disk_analysis_results += (method_alias(**kwargs),)
3169
+
3170
+ attr_name = "_principal" + method_name[5:]
3171
+ setattr(self, attr_name, principal_disk_analysis_results[-1])
3172
+
3173
+ return principal_disk_analysis_results
3174
+
3175
+
3176
+
3177
+ def _calc_disk_boundary_pts_in_uncropped_image_fractional_coords(self,
3178
+ disk_idx):
3179
+ cbed_pattern_core_attrs = \
3180
+ self._cbed_pattern.get_core_attrs(deep_copy=False)
3181
+
3182
+ undistorted_disks = cbed_pattern_core_attrs["undistorted_disks"]
3183
+
3184
+ if disk_idx < len(undistorted_disks):
3185
+ undistorted_disk = undistorted_disks[disk_idx]
3186
+
3187
+ num_pixels_across_uncropped_pattern = \
3188
+ cbed_pattern_core_attrs["num_pixels_across_pattern"]
3189
+
3190
+ method_name = \
3191
+ "_calc_undistorted_disk_boundary_starting_angular_coord"
3192
+ method_alias = \
3193
+ getattr(self, method_name)
3194
+ kwargs = \
3195
+ {"undistorted_disk": \
3196
+ undistorted_disk,
3197
+ "num_pixels_across_uncropped_pattern": \
3198
+ num_pixels_across_uncropped_pattern}
3199
+ undistorted_disk_boundary_starting_angular_coord = \
3200
+ method_alias(**kwargs)
3201
+
3202
+ kwargs = {"steps": self._disk_boundary_sample_size}
3203
+ kwargs["start"] = undistorted_disk_boundary_starting_angular_coord
3204
+ kwargs["end"] = (kwargs["start"]
3205
+ + (kwargs["steps"]-1)*(2*np.pi/kwargs["steps"]))
3206
+ undistorted_disk_boundary_angular_coords = torch.linspace(**kwargs)
3207
+
3208
+ method_name = ("_calc_disk_boundary_pts_and_diffs"
3209
+ "_in_uncropped_image_fractional_coords")
3210
+ method_alias = getattr(self, method_name)
3211
+ kwargs = {"undistorted_disk": \
3212
+ undistorted_disk,
3213
+ "undistorted_disk_boundary_angular_coords": \
3214
+ undistorted_disk_boundary_angular_coords}
3215
+ pts_and_diffs = method_alias(**kwargs)
3216
+
3217
+ disk_boundary_pts_in_uncropped_image_fractional_coords = \
3218
+ pts_and_diffs[0]
3219
+ else:
3220
+ disk_boundary_pts_in_uncropped_image_fractional_coords = \
3221
+ None
3222
+
3223
+ return disk_boundary_pts_in_uncropped_image_fractional_coords
3224
+
3225
+
3226
+
3227
+ def _calc_undistorted_disk_boundary_starting_angular_coord(
3228
+ self, undistorted_disk, num_pixels_across_uncropped_pattern):
3229
+ max_num_iterations = 30
3230
+ iteration_idx = 0
3231
+ num_pts = self._disk_boundary_sample_size
3232
+ search_algorithm_has_not_finished = True
3233
+
3234
+ kwargs = {"steps": num_pts}
3235
+ kwargs["start"] = 0
3236
+ kwargs["end"] = (kwargs["start"]
3237
+ + (kwargs["steps"]-1)*(2*np.pi/kwargs["steps"]))
3238
+
3239
+ while search_algorithm_has_not_finished:
3240
+ angular_coords = torch.linspace(**kwargs)
3241
+
3242
+ method_name = ("_calc_disk_boundary_pts_and_diffs"
3243
+ "_in_uncropped_image_fractional_coords")
3244
+ method_alias = getattr(self, method_name)
3245
+ kwargs = {"undistorted_disk": \
3246
+ undistorted_disk,
3247
+ "undistorted_disk_boundary_angular_coords": \
3248
+ angular_coords}
3249
+ pts, diffs = method_alias(**kwargs)
3250
+
3251
+ angular_coord_idx = torch.argmax(pts[:, 0])
3252
+ diff_subset = diffs if (iteration_idx==0) else diffs[:-1]
3253
+ N_x = num_pixels_across_uncropped_pattern
3254
+
3255
+ if iteration_idx == 0:
3256
+ min_angle = angular_coords[(angular_coord_idx-16)%num_pts]
3257
+ max_angle = angular_coords[(angular_coord_idx+16)%num_pts]
3258
+ else:
3259
+ min_angle = angular_coords[max(angular_coord_idx-16, 0)]
3260
+ max_angle = angular_coords[min(angular_coord_idx+16, num_pts-1)]
3261
+
3262
+ kwargs = {"steps": num_pts}
3263
+ kwargs["start"] = min_angle
3264
+ kwargs["end"] = max_angle + (min_angle > max_angle)*(2*np.pi)
3265
+
3266
+ search_algorithm_has_not_finished = \
3267
+ ((100*N_x*torch.amax(diff_subset) > 1)
3268
+ and (iteration_idx < max_num_iterations-1))
3269
+
3270
+ iteration_idx += 1
3271
+
3272
+ undistorted_disk_boundary_starting_angular_coord = \
3273
+ angular_coords[angular_coord_idx].item() % (2*np.pi)
3274
+
3275
+ return undistorted_disk_boundary_starting_angular_coord
3276
+
3277
+
3278
+
3279
+ def _calc_disk_boundary_pts_and_diffs_in_uncropped_image_fractional_coords(
3280
+ self, undistorted_disk, undistorted_disk_boundary_angular_coords):
3281
+ method_name = \
3282
+ "_calc_shape_boundary_pts_in_uncropped_image_fractional_coords"
3283
+ method_alias = \
3284
+ getattr(self, method_name)
3285
+ kwargs = \
3286
+ {"shape": \
3287
+ undistorted_disk,
3288
+ "shape_boundary_angular_coords": \
3289
+ undistorted_disk_boundary_angular_coords}
3290
+ shape_boundary_pts_in_uncropped_image_fractional_coords = \
3291
+ method_alias(**kwargs)
3292
+
3293
+ u_x = shape_boundary_pts_in_uncropped_image_fractional_coords[:, 0:1]
3294
+ u_y = shape_boundary_pts_in_uncropped_image_fractional_coords[:, 1:2]
3295
+
3296
+ cbed_pattern_core_attrs = \
3297
+ self._cbed_pattern.get_core_attrs(deep_copy=False)
3298
+ distortion_model = \
3299
+ cbed_pattern_core_attrs["distortion_model"]
3300
+
3301
+ distortion_model_core_attrs = \
3302
+ distortion_model.get_core_attrs(deep_copy=False)
3303
+ coord_transform_params = \
3304
+ distortion_model_core_attrs["coord_transform_params"]
3305
+
3306
+ kwargs = {"u_x": u_x,
3307
+ "u_y": u_y,
3308
+ "coord_transform_params": coord_transform_params,
3309
+ "device": self._device,
3310
+ "skip_validation_and_conversion": True}
3311
+ q_x, q_y = distoptica.apply_coord_transform(**kwargs)
3312
+
3313
+ num_pts = self._disk_boundary_sample_size
3314
+ pts = torch.zeros((num_pts, 2), device=self._device)
3315
+ pts[:, 0] = q_x[:, 0]
3316
+ pts[:, 1] = q_y[:, 0]
3317
+
3318
+ diffs = torch.zeros_like(undistorted_disk_boundary_angular_coords)
3319
+ for angular_coord_idx in range(num_pts):
3320
+ pt_1 = pts[(angular_coord_idx)%num_pts]
3321
+ pt_2 = pts[(angular_coord_idx+1)%num_pts]
3322
+ diffs[angular_coord_idx] = torch.linalg.norm(pt_2-pt_1)
3323
+
3324
+ disk_boundary_pts_and_diffs_in_uncropped_image_fractional_coords = \
3325
+ (pts, diffs)
3326
+
3327
+ return disk_boundary_pts_and_diffs_in_uncropped_image_fractional_coords
3328
+
3329
+
3330
+
3331
+ def _calc_shape_boundary_pts_in_uncropped_image_fractional_coords(
3332
+ self, shape, shape_boundary_angular_coords):
3333
+ shape_core_attrs = shape.get_core_attrs(deep_copy=False)
3334
+ shape_support = shape_core_attrs["support"]
3335
+
3336
+ shape_support_core_attrs = shape_support.get_core_attrs(deep_copy=False)
3337
+ u_x_c, u_y_c = shape_support_core_attrs["center"]
3338
+ a = (shape_support_core_attrs["semi_major_axis"]
3339
+ if ("semi_major_axis" in shape_support_core_attrs)
3340
+ else shape_support_core_attrs["radius"])
3341
+ e = shape_support_core_attrs.get("eccentricity", 0)
3342
+ theta = shape_support_core_attrs.get("rotation_angle", 0)
3343
+
3344
+ b = a*np.sqrt(1-e*e).item()
3345
+ phi = shape_boundary_angular_coords
3346
+
3347
+ u_x = (u_x_c + a*torch.cos(phi+theta))[:, None]
3348
+ u_y = (u_y_c + b*torch.sin(phi+theta))[:, None]
3349
+
3350
+ num_pts = shape_boundary_angular_coords.numel()
3351
+ pts = torch.zeros((num_pts, 2), device=self._device)
3352
+ pts[:, 0] = u_x[:, 0]
3353
+ pts[:, 1] = u_y[:, 0]
3354
+
3355
+ shape_boundary_pts_in_uncropped_image_fractional_coords = pts
3356
+
3357
+ return shape_boundary_pts_in_uncropped_image_fractional_coords
3358
+
3359
+
3360
+
3361
+ def _calc_disk_bounding_box_in_uncropped_image_fractional_coords(
3362
+ self, disk_boundary_pts_in_uncropped_image_fractional_coords):
3363
+ if disk_boundary_pts_in_uncropped_image_fractional_coords is not None:
3364
+ pts = disk_boundary_pts_in_uncropped_image_fractional_coords
3365
+
3366
+ disk_bounding_box_in_uncropped_image_fractional_coords = \
3367
+ (torch.amin(pts[:, 0]).item(),
3368
+ torch.amax(pts[:, 0]).item(),
3369
+ torch.amin(pts[:, 1]).item(),
3370
+ torch.amax(pts[:, 1]).item())
3371
+ else:
3372
+ disk_bounding_box_in_uncropped_image_fractional_coords = None
3373
+
3374
+ return disk_bounding_box_in_uncropped_image_fractional_coords
3375
+
3376
+
3377
+
3378
+ def _calc_disk_boundary_pts_in_cropped_image_fractional_coords(
3379
+ self, disk_boundary_pts_in_uncropped_image_fractional_coords):
3380
+ if disk_boundary_pts_in_uncropped_image_fractional_coords is not None:
3381
+ cbed_pattern_core_attrs = \
3382
+ self._cbed_pattern.get_core_attrs(deep_copy=False)
3383
+
3384
+ N_x = cbed_pattern_core_attrs["num_pixels_across_pattern"]
3385
+ N_y = N_x
3386
+
3387
+ N_W_h, N_W_v = self._cropping_window_dims_in_pixels
3388
+
3389
+ kwargs = \
3390
+ {"input": \
3391
+ disk_boundary_pts_in_uncropped_image_fractional_coords}
3392
+ disk_boundary_pts_in_cropped_image_fractional_coords = \
3393
+ torch.zeros_like(**kwargs)
3394
+
3395
+ disk_boundary_pts_in_cropped_image_fractional_coords[:, 0] = \
3396
+ ((disk_boundary_pts_in_uncropped_image_fractional_coords[:, 0]
3397
+ - self._cropped_blank_signal_offsets[1]) * (N_x/N_W_h)
3398
+ + (0.5/N_W_h))
3399
+ disk_boundary_pts_in_cropped_image_fractional_coords[:, 1] = \
3400
+ ((disk_boundary_pts_in_uncropped_image_fractional_coords[:, 1]
3401
+ - self._cropped_blank_signal_offsets[2]) * (N_y/N_W_v)
3402
+ + (1-(1-0.5)/N_W_v))
3403
+ else:
3404
+ disk_boundary_pts_in_cropped_image_fractional_coords = None
3405
+
3406
+ return disk_boundary_pts_in_cropped_image_fractional_coords
3407
+
3408
+
3409
+
3410
+ def _calc_disk_bounding_box_in_cropped_image_fractional_coords(
3411
+ self, disk_boundary_pts_in_cropped_image_fractional_coords):
3412
+ if disk_boundary_pts_in_cropped_image_fractional_coords is not None:
3413
+ pts = disk_boundary_pts_in_cropped_image_fractional_coords
3414
+
3415
+ disk_bounding_box_in_cropped_image_fractional_coords = \
3416
+ (torch.amin(pts[:, 0]).item(),
3417
+ torch.amax(pts[:, 0]).item(),
3418
+ torch.amin(pts[:, 1]).item(),
3419
+ torch.amax(pts[:, 1]).item())
3420
+ else:
3421
+ disk_bounding_box_in_cropped_image_fractional_coords = None
3422
+
3423
+ return disk_bounding_box_in_cropped_image_fractional_coords
3424
+
3425
+
3426
+
3427
+ @property
3428
+ def principal_disk_boundary_pts_in_uncropped_image_fractional_coords(self):
3429
+ r"""`torch.Tensor` | `None`: The sample of points on the boundary of the
3430
+ unclipped support of the "principal" CBED disk, should it exist, in
3431
+ fractional coordinates of the uncropped CBED pattern.
3432
+
3433
+ See the summary documentation for the classes
3434
+ :class:`fakecbed.discretized.CBEDPattern` and
3435
+ :class:`fakecbed.discretized.CroppedCBEDPattern` for additional context.
3436
+
3437
+ Let ``core_attrs`` denote the attribute
3438
+ :attr:`~fancytypes.Checkable.core_attrs`. Furthermore, let
3439
+ ``cbed_pattern``, ``disk_boundary_sample_size``, and
3440
+ ``principal_disk_idx`` denote ``core_attrs["cbed_pattern"]``,
3441
+ ``core_attrs["disk_boundary_sample_size"]``, and
3442
+ ``core_attrs["principal_disk_idx"]`` respectively.
3443
+
3444
+ The "principal" CBED disk refers to the CBED disk, should it exist, that
3445
+ is specified by the nonnegative integer ``principal_disk_idx``. If
3446
+ ``principal_disk_idx <
3447
+ len(cbed_pattern.core_attrs["undistorted_disks"])``, then
3448
+ ``principal_disk_idx`` specifies the CBED disk constructed from the
3449
+ undistorted intensity pattern stored in
3450
+ ``cbed_pattern.core_attrs["undistorted_disks"][prinicpal_disk_idx]``. If
3451
+ ``principal_disk_idx >=
3452
+ len(cbed_pattern.core_attrs["undistorted_disks"])``, then
3453
+ ``principal_disk_idx`` specifies a nonexistent CBED disk.
3454
+
3455
+ If ``principal_disk_idx`` specifies a nonexistent CBED disk, then
3456
+ ``principal_disk_boundary_pts_in_uncropped_image_fractional_coords`` is
3457
+ set to ``None``.
3458
+
3459
+ The remainder of the documentation for the current attribute describes
3460
+ how said attribute is calculated, assuming that the principal CBED disk
3461
+ exists. In :mod:`fakecbed`, the undistorted shape of the support of
3462
+ every CBED disk is assumed to be an ellipse, which incidentally can be a
3463
+ circle if the eccentricity is zero. The definitions of the center, the
3464
+ semi-major axis, the eccentricity, and the rotation angle of a ellipse
3465
+ that are adopted in :mod:`fakecbed` are given implictly in the
3466
+ documentation for the class :class:`fakecbed.shapes.Ellipse`. Let
3467
+ :math:`\left(u_{x;c;\text{PDS}},u_{y;c;\text{PDS}}\right)`,
3468
+ :math:`a_{\text{PDS}}`, :math:`e_{\text{PDS}}`, and
3469
+ :math:`\theta_{\text{PDS}}` be the center, the semi-major axis, the
3470
+ eccentricity, and the rotation angle of the principal CBED disk support.
3471
+
3472
+ Let :math:`u_{x}` and :math:`u_{y}` be the fractional horizontal and
3473
+ vertical coordinates, respectively, of a point in an undistorted image,
3474
+ where :math:`\left(u_{x},u_{y}\right)=\left(0,0\right)` is the bottom
3475
+ left corner of the image. Similarly, let :math:`q_{x}` and :math:`q_{y}`
3476
+ be the fractional horizontal and vertical coordinates, respectively, of
3477
+ a point in a distorted image, where
3478
+ :math:`\left(q_{x},q_{y}\right)=\left(0,0\right)` is the bottom left
3479
+ corner of the image. The core attribute ``cbed_pattern`` specifies a
3480
+ distortion model, which can be accessed via
3481
+ ``cbed_pattern.core_attrs["distortion_model"]``. The distortion model
3482
+ specifies a coordinate transformation,
3483
+ :math:`\left(T_{⌑;x}\left(u_{x},u_{y}\right),
3484
+ T_{⌑;x}\left(u_{x},u_{y}\right)\right)`, which maps a given coordinate
3485
+ pair :math:`\left(u_{x},u_{y}\right)` to a corresponding coordinate pair
3486
+ :math:`\left(q_{x},q_{y}\right)`.
3487
+
3488
+ Next, let :math:`N_{\mathcal{I};x} and N_{\mathcal{I};y}` be the number
3489
+ of pixels in the image of the uncropped fake CBED pattern, where we
3490
+ assume that
3491
+
3492
+ .. math ::
3493
+ N_{\mathcal{I};x}=N_{\mathcal{I};y},
3494
+ :label: N_I_x_eq_N_I_y__2
3495
+
3496
+ Furthermore, let :math:`N_{\text{max-iter}}=30, and let N_{\text{SS}}`
3497
+ be ``disk_boundary_sample_size``.
3498
+
3499
+ Next, let :math:`\partial S_{\text{PDS};x;l}` and :math:`\partial
3500
+ S_{\text{PDS};y;l}` denote
3501
+ ``principal_disk_boundary_pts_in_uncropped_image_fractional_coords[l,
3502
+ 0]`` and
3503
+ ``principal_disk_boundary_pts_in_uncropped_image_fractional_coords[l,
3504
+ 1]`` respectively, where
3505
+ ````principal_disk_boundary_pts_in_uncropped_image_fractional_coords``
3506
+ is the current attribute, and ``l`` is equal to the value of
3507
+ :math:`l`. The current attribute is calculated effectively by executing
3508
+ the following steps:
3509
+
3510
+ 1. Calculate
3511
+
3512
+ .. math ::
3513
+ X_{1} \leftarrow
3514
+ \left\{ l^{\prime}\right\}_{l^{\prime}=0}^{N_{\text{SS}}-1}.
3515
+ :label: X_1__1
3516
+
3517
+ 2. Calculate
3518
+
3519
+ .. math ::
3520
+ X_{2} \leftarrow
3521
+ \left\{ l^{\prime}\right\}_{l^{\prime}=0}^{N_{\text{SS}}-2}
3522
+ :label: X_2__1
3523
+
3524
+ 3. Calculate
3525
+
3526
+ .. math ::
3527
+ m \leftarrow 0.
3528
+ :label: m__1
3529
+
3530
+ 4. Calculate
3531
+
3532
+ .. math ::
3533
+ N_{\text{iter}} \leftarrow 0.
3534
+ :label: N_iter__1
3535
+
3536
+ 5. Calculate
3537
+
3538
+ .. math ::
3539
+ \phi_{l}\leftarrow\frac{2\pi l}{N_{\text{SS}}}
3540
+ \text{ for }l\in X_{1}.
3541
+ :label: phi_l__1
3542
+
3543
+ 6. Calculate
3544
+
3545
+ .. math ::
3546
+ j\leftarrow\min\left(\left\{ N_{\text{iter}}+1,1\right\} \right).
3547
+ :label: j__1
3548
+
3549
+ 7. Calculate
3550
+
3551
+ .. math ::
3552
+ u_{x;l}\leftarrow u_{x;c;\text{PDS}}
3553
+ +a_{\text{PDS}}\cos\left(\phi_{l}+\theta_{\text{PDS}}\right)
3554
+ \text{ for }l\in X_{1},
3555
+ :label: u_x_l__1
3556
+
3557
+ and
3558
+
3559
+ .. math ::
3560
+ u_{y;l}\leftarrow u_{y;c;\text{PDS}}
3561
+ +a_{\text{PDS}}\sqrt{1-e_{\text{PDS}}^{2}}
3562
+ \sin\left(\phi_{l}+\theta_{\text{PDS}}\right)\text{ for }l\in X_{1}.
3563
+ :label: u_y_l__1
3564
+
3565
+ 8. Calculate
3566
+
3567
+ .. math ::
3568
+ q_{x;l}\leftarrow T_{⌑;x}\left(u_{x;l},u_{y;l}\right)
3569
+ \text{ for }l\in X_{1},
3570
+ :label: q_x_l__1
3571
+
3572
+ and
3573
+
3574
+ .. math ::
3575
+ q_{y;l}\leftarrow T_{⌑;y}\left(u_{x;l},u_{y;l}\right)
3576
+ \text{ for }l\in X_{1}.
3577
+ :label: q_y_l__1
3578
+
3579
+ 9. If :math:`m=0`, then go to step 10, otherwise go to step 21.
3580
+
3581
+ 10. Calculate
3582
+
3583
+ .. math ::
3584
+ n\leftarrow\text{argmax}_{l\in X_{1}}\left(u_{x;l}\right).
3585
+ :label: n__1
3586
+
3587
+ 11. Calculate
3588
+
3589
+ .. math ::
3590
+ \Delta_{q;l}\leftarrow\sqrt{\sum_{\alpha\in\left\{ x,y\right\} }
3591
+ \left[q_{\alpha;l\mod N_{\text{SS}}}
3592
+ -q_{\alpha;\left\{ l+1\right\} \mod N_{\text{SS}}}\right]^{2}}
3593
+ \text{ for }l\in X_{j}.
3594
+ :label: Delta_q_l__1
3595
+
3596
+ 12. If :math:`N_{\text{iter}}=0` then go to step 13, otherwise go to
3597
+ step 15.
3598
+
3599
+ 13. Calculate
3600
+
3601
+ .. math ::
3602
+ \phi_{\vdash}\leftarrow\phi_{\left(n-16\right)\mod N_{\text{SS}}},
3603
+ :label: phi_vdash__1
3604
+
3605
+ and
3606
+
3607
+ .. math ::
3608
+ \phi_{\dashv}\leftarrow\phi_{\left(n+16\right)\mod N_{\text{SS}}}.
3609
+ :label: phi_dashv__1
3610
+
3611
+ 14. Go to step 16.
3612
+
3613
+ 15. Calculate
3614
+
3615
+ .. math ::
3616
+ \phi_{\vdash}\leftarrow
3617
+ \phi_{\max\left(\left\{n-16,0\right\}\right)},
3618
+ :label: phi_vdash__2
3619
+
3620
+ and
3621
+
3622
+ .. math ::
3623
+ \phi_{\dashv}\leftarrow
3624
+ \phi_{\min\left(\left\{n+16,N_{\text{SS}}-1\right\}\right)}.
3625
+ :label: phi_dashv__2
3626
+
3627
+ 16. If :math:`\phi_{\vdash}>\phi_{\dashv}`, then calculate
3628
+
3629
+ .. math ::
3630
+ \phi_{\dashv}\leftarrow\phi_{\dashv}+2\pi.
3631
+ :label: phi_dashv__3
3632
+
3633
+ 17. Calculate
3634
+
3635
+ .. math ::
3636
+ \phi_{l}\leftarrow
3637
+ \phi_{\vdash}+l\frac{\phi_{\dashv}-\phi_{\vdash}}{N_{\text{SS}}-1}
3638
+ \text{ for }l\in X_{1}.
3639
+ :label: phi_l__2
3640
+
3641
+ 18. If :math:`100
3642
+ N_{\mathcal{I};x}\max\left(
3643
+ \left\{\Delta_{q;l}\right\}_{l\in S_{m}}\right) \le 1` or
3644
+ :math:`N_{\text{iter}}=N_{\text{max-iter}}`, then calculate
3645
+
3646
+
3647
+ .. math ::
3648
+ m \leftarrow 1,
3649
+ :label: m__2
3650
+
3651
+ .. math ::
3652
+ \phi_{\vdash}\leftarrow\phi_{n}\mod\left\{ 2\pi\right\},
3653
+ :label: phi_vdash__3
3654
+
3655
+ and
3656
+
3657
+ .. math ::
3658
+ \phi_{\dashv}\leftarrow\phi_{\vdash}+2\pi.
3659
+ :label: phi_dashv__4
3660
+
3661
+
3662
+ 19. Calculate
3663
+
3664
+ .. math ::
3665
+ N_{\text{iter}}\leftarrow N_{\text{iter}}+1.
3666
+ :label: N_iter__2
3667
+
3668
+ 20. Go to step 6.
3669
+
3670
+ 21. Calculate
3671
+
3672
+ .. math ::
3673
+ \partial S_{\text{PDS};x;l}\leftarrow q_{x;l}\text{ for }l\in X_{1},
3674
+ :label: partial_S_PDS_x_l__1
3675
+
3676
+ and
3677
+
3678
+ .. math ::
3679
+ \partial S_{\text{PDS};y;l}\leftarrow q_{y;l}\text{ for }l\in X_{1}.
3680
+ :label: partial_S_PDS_y_l__1
3681
+
3682
+ A consequence of the above algorithm is that :math:`\left(\partial
3683
+ S_{\text{PDS};x;0},\partial S_{\text{PDS};y;0}\right)` is the right-most
3684
+ point of the entire sample of points :math:`\left\{ \left(\partial
3685
+ S_{\text{PDS};x;l},\partial S_{\text{PDS};y;l}\right)\right\} _{l\in
3686
+ S_{1}}` on the boundary of the principal CBED disk.
3687
+
3688
+ Note that
3689
+ ``principal_disk_boundary_pts_in_uncropped_image_fractional_coords``
3690
+ should be considered **read-only**.
3691
+
3692
+ """
3693
+ method_name = ("get_principal_disk_boundary_pts"
3694
+ "_in_uncropped_image_fractional_coords")
3695
+ method_alias = getattr(self, method_name)
3696
+ result = method_alias(deep_copy=True)
3697
+
3698
+ return result
3699
+
3700
+
3701
+
3702
+ def get_principal_disk_bounding_box_in_uncropped_image_fractional_coords(
3703
+ self, deep_copy=_default_deep_copy):
3704
+ r"""Return the bounding box of the "principal" CBED disk, should it
3705
+ exist, in fractional coordinates of the uncropped CBED pattern.
3706
+
3707
+ Let ``core_attrs`` denote the attribute
3708
+ :attr:`~fancytypes.Checkable.core_attrs`.
3709
+
3710
+ Parameters
3711
+ ----------
3712
+ deep_copy : `bool`, optional
3713
+ Let
3714
+ ``principal_disk_bounding_box_in_uncropped_image_fractional_coords``
3715
+ denote the attribute
3716
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_bounding_box_in_uncropped_image_fractional_coords`.
3717
+
3718
+ If ``deep_copy`` is set to ``True``, then a deep copy of
3719
+ ``principal_disk_bounding_box_in_uncropped_image_fractional_coords``
3720
+ is returned. Otherwise, a reference to
3721
+ ``principal_disk_bounding_box_in_uncropped_image_fractional_coords``
3722
+ is returned.
3723
+
3724
+ Returns
3725
+ -------
3726
+ principal_disk_bounding_box_in_uncropped_image_fractional_coords : `torch.Tensor` (`float`, shape=(core_attrs["disk_boundary_sample_size"], 2)) | `None`
3727
+ The attribute
3728
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_bounding_box_in_uncropped_image_fractional_coords`.
3729
+
3730
+ """
3731
+ params = {"deep_copy": deep_copy}
3732
+ deep_copy = _check_and_convert_deep_copy(params)
3733
+
3734
+ attr_name = ("_principal_disk_bounding_box"
3735
+ "_in_uncropped_image_fractional_coords")
3736
+ attr = getattr(self, attr_name)
3737
+
3738
+ if attr is None:
3739
+ method_name = ("_calc_principal_disk_analysis_results"
3740
+ "_and_cache_select_intermediates")
3741
+ method_alias = getattr(self, method_name)
3742
+ self._principal_disk_analysis_results = method_alias()
3743
+
3744
+ attr = getattr(self, attr_name)
3745
+
3746
+ principal_disk_bounding_box_in_uncropped_image_fractional_coords = \
3747
+ copy.deepcopy(attr) if (deep_copy == True) else attr
3748
+
3749
+ return principal_disk_bounding_box_in_uncropped_image_fractional_coords
3750
+
3751
+
3752
+
3753
+ @property
3754
+ def principal_disk_bounding_box_in_uncropped_image_fractional_coords(self):
3755
+ r"""`tuple` | `None`: The bounding box of the unclipped support of the
3756
+ "principal" CBED disk, should it exist, in fractional coordinates of the
3757
+ uncropped CBED pattern.
3758
+
3759
+ See the documentation for the attribute
3760
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_boundary_pts_in_uncropped_image_fractional_coords`
3761
+ for additional context.
3762
+
3763
+ Let ``principal_disk_boundary_pts_in_uncropped_image_fractional_coords``
3764
+ denote the attribute
3765
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_boundary_pts_in_uncropped_image_fractional_coords`.
3766
+
3767
+ ``principal_disk_bounding_box_in_uncropped_image_fractional_coords`` is
3768
+ calculated effectively by:
3769
+
3770
+ .. code-block:: python
3771
+
3772
+ import torch
3773
+
3774
+ pts = \
3775
+ principal_disk_boundary_pts_in_uncropped_image_fractional_coords
3776
+
3777
+ if pts is not None:
3778
+ bounding_box = (torch.amin(pts[:, 0]).item(),
3779
+ torch.amax(pts[:, 0]).item(),
3780
+ torch.amin(pts[:, 1]).item(),
3781
+ torch.amax(pts[:, 1]).item())
3782
+ else:
3783
+ bounding_box = None
3784
+
3785
+ principal_disk_bounding_box_in_uncropped_image_fractional_coords = \
3786
+ bounding_box
3787
+
3788
+ Note that
3789
+ ``principal_disk_bounding_box_in_uncropped_image_fractional_coords``
3790
+ should be considered **read-only**.
3791
+
3792
+ """
3793
+ method_name = ("get_principal_disk_bounding_box"
3794
+ "_in_uncropped_image_fractional_coords")
3795
+ method_alias = getattr(self, method_name)
3796
+ result = method_alias(deep_copy=True)
3797
+
3798
+ return result
3799
+
3800
+
3801
+
3802
+ def get_principal_disk_boundary_pts_in_cropped_image_fractional_coords(
3803
+ self, deep_copy=_default_deep_copy):
3804
+ r"""Return the sample of points on the boundary of the "principal" CBED
3805
+ disk, should it exist, in fractional coordinates of the cropped CBED
3806
+ pattern.
3807
+
3808
+ Let ``core_attrs`` denote the attribute
3809
+ :attr:`~fancytypes.Checkable.core_attrs`.
3810
+
3811
+ Parameters
3812
+ ----------
3813
+ deep_copy : `bool`, optional
3814
+ Let
3815
+ ``principal_disk_boundary_pts_in_cropped_image_fractional_coords``
3816
+ denote the attribute
3817
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_boundary_pts_in_cropped_image_fractional_coords`.
3818
+
3819
+ If ``deep_copy`` is set to ``True``, then a deep copy of
3820
+ ``principal_disk_boundary_pts_in_cropped_image_fractional_coords``
3821
+ is returned. Otherwise, a reference to
3822
+ ``principal_disk_boundary_pts_in_cropped_image_fractional_coords``
3823
+ is returned.
3824
+
3825
+ Returns
3826
+ -------
3827
+ principal_disk_boundary_pts_in_cropped_image_fractional_coords : `torch.Tensor` (`float`, shape=(core_attrs["disk_boundary_sample_size"], 2)) | `None`
3828
+ The attribute
3829
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_boundary_pts_in_cropped_image_fractional_coords`.
3830
+
3831
+ """
3832
+ params = {"deep_copy": deep_copy}
3833
+ deep_copy = _check_and_convert_deep_copy(params)
3834
+
3835
+ attr_name = ("_principal_disk_boundary_pts"
3836
+ "_in_cropped_image_fractional_coords")
3837
+ attr = getattr(self, attr_name)
3838
+
3839
+ if attr is None:
3840
+ method_name = ("_calc_principal_disk_analysis_results"
3841
+ "_and_cache_select_intermediates")
3842
+ method_alias = getattr(self, method_name)
3843
+ self._principal_disk_analysis_results = method_alias()
3844
+
3845
+ attr = getattr(self, attr_name)
3846
+
3847
+ principal_disk_boundary_pts_in_cropped_image_fractional_coords = \
3848
+ (attr
3849
+ if ((deep_copy == False) or (attr is None))
3850
+ else attr.detach().clone())
3851
+
3852
+ return principal_disk_boundary_pts_in_cropped_image_fractional_coords
3853
+
3854
+
3855
+
3856
+ @property
3857
+ def principal_disk_boundary_pts_in_cropped_image_fractional_coords(self):
3858
+ r"""`torch.Tensor` | `None`: The sample of points on the boundary of the
3859
+ unclipped support of the "principal" CBED disk, should it exist, in
3860
+ fractional coordinates of the cropped CBED pattern.
3861
+
3862
+ See the documentation for the attribute
3863
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_boundary_pts_in_uncropped_image_fractional_coords`
3864
+ for additional context.
3865
+
3866
+ Let ``core_attrs``,
3867
+ ``principal_disk_boundary_pts_in_uncropped_image_fractional_coords``,
3868
+ and ``signal``, denote the attributes
3869
+ :attr:`~fancytypes.Checkable.core_attrs`
3870
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_boundary_pts_in_uncropped_image_fractional_coords`,
3871
+ and :attr:`fakecbed.discretized.CroppedCBEDPattern.signal` respectively.
3872
+
3873
+ ``principal_disk_boundary_pts_in_cropped_image_fractional_coords`` is
3874
+ calculated effectively by:
3875
+
3876
+ .. code-block:: python
3877
+
3878
+ import torch
3879
+
3880
+ cbed_pattern = core_attrs["cbed_pattern"]
3881
+ N_x = cbed_pattern.core_attrs["num_pixels_across_pattern"]
3882
+ N_y = N_x
3883
+
3884
+ N_W_h, N_W_v = core_attrs["cropping_window_dims_in_pixels"]
3885
+
3886
+ boundary_pts_in_uncropped_image_fractional_coords = \
3887
+ principal_disk_boundary_pts_in_uncropped_image_fractional_coords
3888
+
3889
+ if boundary_pts_in_uncropped_image_fractional_coords is not None:
3890
+
3891
+ kwargs = \
3892
+ {"input": boundary_pts_in_uncropped_image_fractional_coords}
3893
+ boundary_pts_in_cropped_image_fractional_coords = \
3894
+ torch.zeros_like(**kwargs)
3895
+
3896
+ boundary_pts_in_cropped_image_fractional_coords[:, 0] = \
3897
+ ((boundary_pts_in_uncropped_image_fractional_coords[:, 0]
3898
+ - signal.axes_manager[1].offset) * (N_x/N_W_h)
3899
+ + (0.5/N_W_h))
3900
+ boundary_pts_in_cropped_image_fractional_coords[:, 1] = \
3901
+ ((boundary_pts_in_uncropped_image_fractional_coords[:, 1]
3902
+ - signal.axes_manager[2].offset) * (N_y/N_W_v)
3903
+ + (1-(1-0.5)/N_W_v))
3904
+ else:
3905
+ boundary_pts_in_cropped_image_fractional_coords = \
3906
+ None
3907
+
3908
+ principal_disk_boundary_pts_in_cropped_image_fractional_coords = \
3909
+ boundary_pts_in_cropped_image_fractional_coords
3910
+
3911
+ Note that
3912
+ ``principal_disk_boundary_pts_in_cropped_image_fractional_coords``
3913
+ should be considered **read-only**.
3914
+
3915
+ """
3916
+ method_name = ("get_principal_disk_boundary_pts"
3917
+ "_in_cropped_image_fractional_coords")
3918
+ method_alias = getattr(self, method_name)
3919
+ result = method_alias(deep_copy=True)
3920
+
3921
+ return result
3922
+
3923
+
3924
+
3925
+ def get_principal_disk_bounding_box_in_cropped_image_fractional_coords(
3926
+ self, deep_copy=_default_deep_copy):
3927
+ r"""Return the bounding box of the "principal" CBED disk, should it
3928
+ exist, in fractional coordinates of the cropped CBED pattern.
3929
+
3930
+ Let ``core_attrs`` denote the attribute
3931
+ :attr:`~fancytypes.Checkable.core_attrs`.
3932
+
3933
+ Parameters
3934
+ ----------
3935
+ deep_copy : `bool`, optional
3936
+ Let
3937
+ ``principal_disk_bounding_box_in_cropped_image_fractional_coords``
3938
+ denote the attribute
3939
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_bounding_box_in_cropped_image_fractional_coords`.
3940
+
3941
+ If ``deep_copy`` is set to ``True``, then a deep copy of
3942
+ ``principal_disk_bounding_box_in_cropped_image_fractional_coords``
3943
+ is returned. Otherwise, a reference to
3944
+ ``principal_disk_bounding_box_in_cropped_image_fractional_coords``
3945
+ is returned.
3946
+
3947
+ Returns
3948
+ -------
3949
+ principal_disk_bounding_box_in_cropped_image_fractional_coords : `torch.Tensor` (`float`, shape=(core_attrs["disk_boundary_sample_size"], 2)) | `None`
3950
+ The attribute
3951
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_bounding_box_in_cropped_image_fractional_coords`.
3952
+
3953
+ """
3954
+ params = {"deep_copy": deep_copy}
3955
+ deep_copy = _check_and_convert_deep_copy(params)
3956
+
3957
+ attr_name = ("_principal_disk_bounding_box"
3958
+ "_in_cropped_image_fractional_coords")
3959
+ attr = getattr(self, attr_name)
3960
+
3961
+ if attr is None:
3962
+ method_name = ("_calc_principal_disk_analysis_results"
3963
+ "_and_cache_select_intermediates")
3964
+ method_alias = getattr(self, method_name)
3965
+ self._principal_disk_analysis_results = method_alias()
3966
+
3967
+ attr = getattr(self, attr_name)
3968
+
3969
+ principal_disk_bounding_box_in_cropped_image_fractional_coords = \
3970
+ copy.deepcopy(attr) if (deep_copy == True) else attr
3971
+
3972
+ return principal_disk_bounding_box_in_cropped_image_fractional_coords
3973
+
3974
+
3975
+
3976
+ @property
3977
+ def principal_disk_bounding_box_in_cropped_image_fractional_coords(self):
3978
+ r"""`tuple` | `None`: The bounding box of the unclipped support of the
3979
+ "principal" CBED disk, should it exist, in fractional coordinates of the
3980
+ cropped CBED pattern.
3981
+
3982
+ See the documentation for the attribute
3983
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_boundary_pts_in_cropped_image_fractional_coords`
3984
+ for additional context.
3985
+
3986
+ Let ``principal_disk_boundary_pts_in_cropped_image_fractional_coords``
3987
+ denote the attribute
3988
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_boundary_pts_in_cropped_image_fractional_coords`.
3989
+
3990
+ ``principal_disk_bounding_box_in_cropped_image_fractional_coords`` is
3991
+ calculated effectively by:
3992
+
3993
+ .. code-block:: python
3994
+
3995
+ import torch
3996
+
3997
+ pts = \
3998
+ principal_disk_boundary_pts_in_cropped_image_fractional_coords
3999
+
4000
+ if pts is not None:
4001
+ bounding_box = (torch.amin(pts[:, 0]).item(),
4002
+ torch.amax(pts[:, 0]).item(),
4003
+ torch.amin(pts[:, 1]).item(),
4004
+ torch.amax(pts[:, 1]).item())
4005
+ else:
4006
+ bounding_box = None
4007
+
4008
+ principal_disk_bounding_box_in_cropped_image_fractional_coords = \
4009
+ bounding_box
4010
+
4011
+ Note that
4012
+ ``principal_disk_bounding_box_in_cropped_image_fractional_coords``
4013
+ should be considered **read-only**.
4014
+
4015
+ """
4016
+ method_name = ("get_principal_disk_bounding_box"
4017
+ "_in_cropped_image_fractional_coords")
4018
+ method_alias = getattr(self, method_name)
4019
+ result = method_alias(deep_copy=True)
4020
+
4021
+ return result
4022
+
4023
+
4024
+
4025
+ def get_disk_supports(self, deep_copy=_default_deep_copy):
4026
+ r"""Return the image stack of the disk supports of the cropped fake CBED
4027
+ pattern.
4028
+
4029
+ Parameters
4030
+ ----------
4031
+ deep_copy : `bool`, optional
4032
+ Let ``disk_supports`` denote the attribute
4033
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_supports`.
4034
+
4035
+ If ``deep_copy`` is set to ``True``, then a deep copy of
4036
+ ``disk_supports`` is returned. Otherwise, a reference to
4037
+ ``disk_supports`` is returned.
4038
+
4039
+ Returns
4040
+ -------
4041
+ disk_supports : `torch.Tensor` (`bool`, ndim=3)
4042
+ The attribute
4043
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_supports`.
4044
+
4045
+ """
4046
+ params = {"deep_copy": deep_copy}
4047
+ deep_copy = _check_and_convert_deep_copy(params)
4048
+
4049
+ if self._disk_supports is None:
4050
+ method_name = "_calc_disk_supports"
4051
+ method_alias = getattr(self, method_name)
4052
+ self._disk_supports = method_alias()
4053
+
4054
+ disk_supports = (self._disk_supports.detach().clone()
4055
+ if (deep_copy == True)
4056
+ else self._disk_supports)
4057
+
4058
+ return disk_supports
4059
+
4060
+
4061
+
4062
+ def _calc_disk_supports(self):
4063
+ N_W_h, N_W_v = self._cropping_window_dims_in_pixels
4064
+ L, R, B, T = self._mask_frame # Mask frame of cropped pattern.
4065
+
4066
+ uncropped_cbed_pattern_disk_supports = \
4067
+ self._cbed_pattern.get_disk_supports(deep_copy=False)
4068
+ uncropped_cbed_pattern_disk_supports = \
4069
+ uncropped_cbed_pattern_disk_supports.cpu().detach().clone()
4070
+
4071
+ signal_to_crop = \
4072
+ self._generate_uncropped_blank_signal()
4073
+ signal_to_crop.data[3:] = \
4074
+ uncropped_cbed_pattern_disk_supports.numpy(force=True)[:]
4075
+
4076
+ optional_params = self._generate_optional_cropping_params()
4077
+
4078
+ kwargs = {"input_signal": signal_to_crop,
4079
+ "optional_params": optional_params}
4080
+ cropped_signal = empix.crop(**kwargs)
4081
+
4082
+ disk_supports = cropped_signal.data[3:]
4083
+ disk_supports_dtype = uncropped_cbed_pattern_disk_supports.dtype
4084
+ disk_supports = torch.from_numpy(disk_supports)
4085
+ disk_supports = disk_supports.to(device=self._device,
4086
+ dtype=disk_supports_dtype)
4087
+ disk_supports[:, :T, :] = 0
4088
+ disk_supports[:, max(N_W_v-B, 0):, :] = 0
4089
+ disk_supports[:, :, :L] = 0
4090
+ disk_supports[:, :, max(N_W_h-R, 0):] = 0
4091
+
4092
+ return disk_supports
4093
+
4094
+
4095
+
4096
+ @property
4097
+ def disk_supports(self):
4098
+ r"""`torch.Tensor`: The image stack of the disk supports of the cropped
4099
+ fake CBED pattern.
4100
+
4101
+ See the documentation for the attribute
4102
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.signal` for additional
4103
+ context.
4104
+
4105
+ Let ``signal``, ``device``, and ``core_attrs`` denote the attributes
4106
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.signal`,
4107
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.device`, and
4108
+ :attr:`~fancytypes.Checkable.core_attrs` respectively.
4109
+
4110
+ ``disk_supports`` is calculated effectively by:
4111
+
4112
+ .. code-block:: python
4113
+
4114
+ N_W_h, N_W_v = core_attrs["cropping_window_dims_in_pixels"]
4115
+ L, R, B, T = core_attrs["mask_frame"]
4116
+
4117
+ disk_supports = signal.data[3:]
4118
+ disk_supports = torch.from_numpy(disk_supports)
4119
+ disk_supports = disk_supports.to(device=device, dtype=bool)
4120
+ disk_supports[:, :T, :] = 0
4121
+ disk_supports[:, max(N_W_v-B, 0):, :] = 0
4122
+ disk_supports[:, :, :L] = 0
4123
+ disk_supports[:, :, max(N_W_h-R, 0):] = 0
4124
+
4125
+ Note that ``disk_supports`` should be considered **read-only**.
4126
+
4127
+ """
4128
+ result = self.get_disk_supports(deep_copy=True)
4129
+
4130
+ return result
4131
+
4132
+
4133
+
4134
+ def get_disk_clipping_registry(self, deep_copy=_default_deep_copy):
4135
+ r"""Return the disk clipping registry of the cropped fake CBED pattern.
4136
+
4137
+ Parameters
4138
+ ----------
4139
+ deep_copy : `bool`, optional
4140
+ Let ``disk_clipping_registry`` denote the attribute
4141
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_clipping_registry`.
4142
+
4143
+ If ``deep_copy`` is set to ``True``, then a deep copy of
4144
+ ``disk_clipping_registry`` is returned. Otherwise, a reference to
4145
+ ``disk_clipping_registry`` is returned.
4146
+
4147
+ Returns
4148
+ -------
4149
+ disk_clipping_registry : `torch.Tensor` (`bool`, ndim=1)
4150
+ The attribute
4151
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_clipping_registry`.
4152
+
4153
+ """
4154
+ params = {"deep_copy": deep_copy}
4155
+ deep_copy = _check_and_convert_deep_copy(params)
4156
+
4157
+ if self._disk_clipping_registry is None:
4158
+ method_name = ("_calc_disk_clipping_registry"
4159
+ "_and_cache_select_intermediates")
4160
+ method_alias = getattr(self, method_name)
4161
+ self._disk_clipping_registry = method_alias()
4162
+
4163
+ disk_clipping_registry = (self._disk_clipping_registry.detach().clone()
4164
+ if (deep_copy == True)
4165
+ else self._disk_clipping_registry)
4166
+
4167
+ return disk_clipping_registry
4168
+
4169
+
4170
+
4171
+ def _calc_disk_clipping_registry_and_cache_select_intermediates(self):
4172
+ num_disks = self._num_disks
4173
+
4174
+ if num_disks > 0:
4175
+ if self._disk_supports is None:
4176
+ method_name = "_calc_disk_supports"
4177
+ method_alias = getattr(self, method_name)
4178
+ self._disk_supports = method_alias()
4179
+ disk_supports = self._disk_supports
4180
+
4181
+ clip_support = self._calc_clip_support()
4182
+
4183
+ disk_clipping_map = disk_supports*clip_support[None, :, :]
4184
+
4185
+ disk_clipping_registry = ((disk_clipping_map.sum(dim=(1, 2)) != 0)
4186
+ + (disk_supports.sum(dim=(1, 2)) == 0))
4187
+ else:
4188
+ disk_clipping_registry = torch.zeros((num_disks,),
4189
+ device=self._device,
4190
+ dtype=torch.bool)
4191
+
4192
+ return disk_clipping_registry
4193
+
4194
+
4195
+
4196
+ def _calc_clip_support(self):
4197
+ cbed_pattern = self._cbed_pattern
4198
+
4199
+ cbed_pattern_core_attrs = cbed_pattern.get_core_attrs(deep_copy=False)
4200
+
4201
+ illumination_support = \
4202
+ cbed_pattern.get_illumination_support(deep_copy=False)
4203
+ illumination_support = \
4204
+ illumination_support.cpu().detach().clone()
4205
+
4206
+ L, R, B, T = cbed_pattern._mask_frame
4207
+
4208
+ N_x = cbed_pattern_core_attrs["num_pixels_across_pattern"]
4209
+ N_y = N_x
4210
+
4211
+ signal_to_crop = self._generate_uncropped_blank_signal()
4212
+ signal_to_crop.data[0] = illumination_support.numpy(force=True)
4213
+ signal_to_crop.data[0, :T, :] = 0
4214
+ signal_to_crop.data[0, max(N_y-B, 0):, :] = 0
4215
+ signal_to_crop.data[0, :, :L] = 0
4216
+ signal_to_crop.data[0, :, max(N_x-R, 0):] = 0
4217
+
4218
+ optional_params = self._generate_optional_cropping_params()
4219
+
4220
+ kwargs = {"input_signal": signal_to_crop,
4221
+ "optional_params": optional_params}
4222
+ cropped_signal = empix.crop(**kwargs)
4223
+
4224
+ clip_support = torch.from_numpy(1.0-cropped_signal.data[0])
4225
+ clip_support = clip_support.to(device=self._device, dtype=torch.float)
4226
+ for _ in range(2):
4227
+ clip_support = torch.unsqueeze(clip_support, dim=0)
4228
+
4229
+ conv_weights = torch.ones((1, 1, 3, 3), device=self._device)
4230
+
4231
+ N_W_h, N_W_v = self._cropping_window_dims_in_pixels
4232
+ L, R, B, T = self._mask_frame # Mask frame of cropped pattern.
4233
+
4234
+ kwargs = {"input": clip_support,
4235
+ "weight": conv_weights,
4236
+ "padding": "same"}
4237
+ clip_support = (torch.nn.functional.conv2d(**kwargs) != 0)[0, 0]
4238
+ clip_support[:T+1, :] = True
4239
+ clip_support[max(N_W_v-B-1, 0):, :] = True
4240
+ clip_support[:, :L+1] = True
4241
+ clip_support[:, max(N_W_h-R-1, 0):] = True
4242
+
4243
+ return clip_support
4244
+
4245
+
4246
+
4247
+ @property
4248
+ def disk_clipping_registry(self):
4249
+ r"""`torch.Tensor`: The disk clipping registry of the cropped fake CBED
4250
+ pattern.
4251
+
4252
+ See the summary documentation for the classes
4253
+ :class:`fakecbed.discretized.CBEDPattern`, and
4254
+ :class:`fakecbed.discretized.CroppedCBEDPattern` for additional context.
4255
+
4256
+ Let ``num_disks``, ``disk_supports``, ``illumination_support``, and
4257
+ ``image`` denote the attributes
4258
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.num_disks`,
4259
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_supports`,
4260
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.illumination_support`,
4261
+ and :attr:`fakecbed.discretized.CroppedCBEDPattern.image`
4262
+ respectively. Moreover, let ``k`` be a nonnegative integer less than
4263
+ ``num_disks``.
4264
+
4265
+ ``disk_clipping_registry`` is a one-dimensional PyTorch tensor of length
4266
+ equal to ``num_disks``. If ``disk_clipping_registry[k]`` is equal to
4267
+ ``False``, then the image stored in ``disk_supports[k]`` has at least
4268
+ one nonzero pixel, and that the position of every nonzero pixel of said
4269
+ image is at least two pixels away from (i.e. at least pixel-wise
4270
+ next-nearest neighbours to) the position of every zero-valued pixel of
4271
+ the image stored in ``illumination_support``, at least two pixels away
4272
+ from the position of every pixel inside any of the mask frames that
4273
+ appears in the image stored in ``image``, and at least one pixel away
4274
+ from every pixel bordering the image stored in ``image``. Otherwise, if
4275
+ ``disk_clipping_registry[k]`` is equal to ``True``, then the opposite of
4276
+ the above scenario is true.
4277
+
4278
+ Note that there are potentially two mask frames that may appear in the
4279
+ image stored in ``image``: the mask frame applied to the uncropped fake
4280
+ CBED pattern, and the mask frame that is applied after cropping the fake
4281
+ CBED pattern.
4282
+
4283
+ Note that ``disk_clipping_registry`` should be considered **read-only**.
4284
+
4285
+ """
4286
+ result = self.get_disk_clipping_registry(deep_copy=True)
4287
+
4288
+ return result
4289
+
4290
+
4291
+
4292
+ def get_disk_absence_registry(self, deep_copy=_default_deep_copy):
4293
+ r"""Return the disk absence registry of the cropped fake CBED pattern.
4294
+
4295
+ Parameters
4296
+ ----------
4297
+ deep_copy : `bool`, optional
4298
+ Let ``disk_absence_registry`` denote the attribute
4299
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_absence_registry`.
4300
+
4301
+ If ``deep_copy`` is set to ``True``, then a deep copy of
4302
+ ``disk_absence_registry`` is returned. Otherwise, a reference to
4303
+ ``disk_absence_registry`` is returned.
4304
+
4305
+ Returns
4306
+ -------
4307
+ disk_absence_registry : `torch.Tensor` (`bool`, ndim=1)
4308
+ The attribute
4309
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_absence_registry`.
4310
+
4311
+ """
4312
+ params = {"deep_copy": deep_copy}
4313
+ deep_copy = _check_and_convert_deep_copy(params)
4314
+
4315
+ if self._disk_absence_registry is None:
4316
+ method_name = ("_calc_disk_absence_registry"
4317
+ "_and_cache_select_intermediates")
4318
+ method_alias = getattr(self, method_name)
4319
+ self._disk_absence_registry = method_alias()
4320
+
4321
+ disk_absence_registry = (self._disk_absence_registry.detach().clone()
4322
+ if (deep_copy == True)
4323
+ else self._disk_absence_registry)
4324
+
4325
+ return disk_absence_registry
4326
+
4327
+
4328
+
4329
+ def _calc_disk_absence_registry_and_cache_select_intermediates(self):
4330
+ num_disks = self._num_disks
4331
+
4332
+ if num_disks > 0:
4333
+ if self._disk_supports is None:
4334
+ method_name = "_calc_disk_supports"
4335
+ method_alias = getattr(self, method_name)
4336
+ self._disk_supports = method_alias()
4337
+ disk_supports = self._disk_supports
4338
+
4339
+ disk_absence_registry = (disk_supports.sum(dim=(1, 2)) == 0)
4340
+ else:
4341
+ disk_absence_registry = torch.zeros((num_disks,),
4342
+ device=self._device,
4343
+ dtype=torch.bool)
4344
+
4345
+ return disk_absence_registry
4346
+
4347
+
4348
+
4349
+ @property
4350
+ def disk_absence_registry(self):
4351
+ r"""`torch.Tensor`: The disk absence registry of the cropped fake CBED
4352
+ pattern.
4353
+
4354
+ See the summary documentation for the classes
4355
+ :class:`fakecbed.discretized.CBEDPattern`, and
4356
+ :class:`fakecbed.discretized.CroppedCBEDPattern` for additional context.
4357
+
4358
+ Let ``num_disks``, and ``disk_supports`` denote the attributes
4359
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.num_disks`, and
4360
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_supports`
4361
+ respectively. Moreover, let ``k`` be a nonnegative integer less than
4362
+ ``num_disks``.
4363
+
4364
+ ``disk_absence_registry`` is a one-dimensional PyTorch tensor of length
4365
+ equal to ``num_disks``. If ``disk_absence_registry[k]`` is equal to
4366
+ ``False``, then the image stored in ``disk_supports[k]`` has at least
4367
+ one nonzero pixel. Otherwise, if ``disk_absence_registry[k]`` is equal
4368
+ to ``True``, then the opposite of the above scenario is true.
4369
+
4370
+ Note that ``disk_absence_registry`` should be considered **read-only**.
4371
+
4372
+ """
4373
+ result = self.get_disk_absence_registry(deep_copy=True)
4374
+
4375
+ return result
4376
+
4377
+
4378
+
4379
+ @property
4380
+ def principal_disk_is_clipped(self):
4381
+ r"""`bool`: Equals ``True`` if the "principal" CBED disk either does not
4382
+ exists, or it exists and is clipped in image of the cropped fake CBED
4383
+ pattern.
4384
+
4385
+ See the summary documentation for the class
4386
+ :class:`fakecbed.discretized.CroppedCBEDPattern` for additional context,
4387
+ as well as the documentation for the attribute
4388
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_clipping_registry`.
4389
+
4390
+ Let ``core_attrs``, ``num_disks``, and ``disk_clipping_registry`` denote
4391
+ the attributes :attr:`~fancytypes.Checkable.core_attrs`,
4392
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.num_disks`, and
4393
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_clipping_registry`
4394
+ respectively.
4395
+
4396
+ ``principal_disk_is_clipped`` is calculated effectively by:
4397
+
4398
+ .. code-block:: python
4399
+
4400
+ principal_disk_idx = \
4401
+ core_attrs["principal_disk_idx"]
4402
+ principal_disk_is_clipped = \
4403
+ (disk_clipping_registry[principal_disk_idx].item()
4404
+ if (principal_disk_idx < num_disks)
4405
+ else True)
4406
+
4407
+ Note that ``principal_disk_is_clipped`` should be considered
4408
+ **read-only**.
4409
+
4410
+ """
4411
+ if self._principal_disk_is_clipped is None:
4412
+ method_name = ("_calc_principal_disk_is_clipped"
4413
+ "_and_cache_select_intermediates")
4414
+ method_alias = getattr(self, method_name)
4415
+ self._principal_disk_is_clipped = method_alias()
4416
+
4417
+ result = self._principal_disk_is_clipped
4418
+
4419
+ return result
4420
+
4421
+
4422
+
4423
+ def _calc_principal_disk_is_clipped_and_cache_select_intermediates(self):
4424
+ principal_disk_idx = self._principal_disk_idx
4425
+ num_disks = self._num_disks
4426
+
4427
+ if principal_disk_idx < num_disks:
4428
+ if self._disk_clipping_registry is None:
4429
+ method_name = ("_calc_disk_clipping_registry"
4430
+ "_and_cache_select_intermediates")
4431
+ method_alias = getattr(self, method_name)
4432
+ self._disk_clipping_registry = method_alias()
4433
+ disk_clipping_registry = self._disk_clipping_registry
4434
+
4435
+ disk_idx = principal_disk_idx
4436
+ principal_disk_is_clipped = disk_clipping_registry[disk_idx].item()
4437
+ else:
4438
+ principal_disk_is_clipped = True
4439
+
4440
+ return principal_disk_is_clipped
4441
+
4442
+
4443
+
4444
+ @property
4445
+ def principal_disk_is_absent(self):
4446
+ r"""`bool`: Equals ``True`` if the "principal" CBED disk either does not
4447
+ exists, or it exists and is absent from the image of the cropped fake
4448
+ CBED pattern.
4449
+
4450
+ See the summary documentation for the class
4451
+ :class:`fakecbed.discretized.CroppedCBEDPattern` for additional context,
4452
+ as well as the documentation for the attribute
4453
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_absence_registry`.
4454
+
4455
+ Let ``core_attrs``, ``num_disks``, and ``disk_absence_registry`` denote
4456
+ the attributes :attr:`~fancytypes.Checkable.core_attrs`,
4457
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.num_disks`, and
4458
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_absence_registry`
4459
+ respectively.
4460
+
4461
+ ``principal_disk_is_absent`` is calculated effectively by:
4462
+
4463
+ .. code-block:: python
4464
+
4465
+ principal_disk_idx = \
4466
+ core_attrs["principal_disk_idx"]
4467
+ principal_disk_is_absent = \
4468
+ (disk_absence_registry[principal_disk_idx].item()
4469
+ if (principal_disk_idx < num_disks)
4470
+ else True)
4471
+
4472
+ Note that ``principal_disk_is_absent`` should be considered
4473
+ **read-only**.
4474
+
4475
+ """
4476
+ if self._principal_disk_is_absent is None:
4477
+ method_name = ("_calc_principal_disk_is_absent"
4478
+ "_and_cache_select_intermediates")
4479
+ method_alias = getattr(self, method_name)
4480
+ self._principal_disk_is_absent = method_alias()
4481
+
4482
+ result = self._principal_disk_is_absent
4483
+
4484
+ return result
4485
+
4486
+
4487
+
4488
+ def _calc_principal_disk_is_absent_and_cache_select_intermediates(self):
4489
+ principal_disk_idx = self._principal_disk_idx
4490
+ num_disks = self._num_disks
4491
+
4492
+ if principal_disk_idx < num_disks:
4493
+ if self._disk_absence_registry is None:
4494
+ method_name = ("_calc_disk_absence_registry"
4495
+ "_and_cache_select_intermediates")
4496
+ method_alias = getattr(self, method_name)
4497
+ self._disk_absence_registry = method_alias()
4498
+ disk_absence_registry = self._disk_absence_registry
4499
+
4500
+ disk_idx = principal_disk_idx
4501
+ principal_disk_is_absent = disk_absence_registry[disk_idx].item()
4502
+ else:
4503
+ principal_disk_is_absent = True
4504
+
4505
+ return principal_disk_is_absent
4506
+
4507
+
4508
+
4509
+ def get_disk_overlap_map(self, deep_copy=_default_deep_copy):
4510
+ r"""Return the image of the disk overlap map of the cropped fake CBED
4511
+ pattern.
4512
+
4513
+ Parameters
4514
+ ----------
4515
+ deep_copy : `bool`, optional
4516
+ Let ``disk_overlap_map`` denote the attribute
4517
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_overlap_map`.
4518
+
4519
+ If ``deep_copy`` is set to ``True``, then a deep copy of
4520
+ ``disk_overlap_map`` is returned. Otherwise, a reference to
4521
+ ``disk_overlap_map`` is returned.
4522
+
4523
+ Returns
4524
+ -------
4525
+ disk_overlap_map : `torch.Tensor` (`int`, ndim=2)
4526
+ The attribute
4527
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_overlap_map`.
4528
+
4529
+ """
4530
+ params = {"deep_copy": deep_copy}
4531
+ deep_copy = _check_and_convert_deep_copy(params)
4532
+
4533
+ if self._disk_overlap_map is None:
4534
+ method_name = "_calc_disk_overlap_map"
4535
+ method_alias = getattr(self, method_name)
4536
+ self._disk_overlap_map = method_alias()
4537
+
4538
+ disk_overlap_map = (self._disk_overlap_map.detach().clone()
4539
+ if (deep_copy == True)
4540
+ else self._disk_overlap_map)
4541
+
4542
+ return disk_overlap_map
4543
+
4544
+
4545
+
4546
+ def _calc_disk_overlap_map(self):
4547
+ uncropped_cbed_pattern_disk_overlap_map = \
4548
+ self._cbed_pattern.get_disk_overlap_map(deep_copy=False)
4549
+ uncropped_cbed_pattern_disk_overlap_map = \
4550
+ uncropped_cbed_pattern_disk_overlap_map.cpu().detach().clone()
4551
+
4552
+ signal_to_crop = \
4553
+ self._generate_uncropped_blank_signal()
4554
+ signal_to_crop.data[2] = \
4555
+ uncropped_cbed_pattern_disk_overlap_map.numpy(force=True)
4556
+
4557
+ optional_params = self._generate_optional_cropping_params()
4558
+
4559
+ kwargs = {"input_signal": signal_to_crop,
4560
+ "optional_params": optional_params}
4561
+ cropped_signal = empix.crop(**kwargs)
4562
+
4563
+ N_W_h, N_W_v = self._cropping_window_dims_in_pixels
4564
+ L, R, B, T = self._mask_frame # Mask frame of cropped pattern.
4565
+
4566
+ disk_overlap_map = cropped_signal.data[2]
4567
+ disk_overlap_map_dtype = uncropped_cbed_pattern_disk_overlap_map.dtype
4568
+ disk_overlap_map = torch.from_numpy(disk_overlap_map)
4569
+ disk_overlap_map = disk_overlap_map.to(device=self._device,
4570
+ dtype=disk_overlap_map_dtype)
4571
+ disk_overlap_map[:T, :] = 0
4572
+ disk_overlap_map[max(N_W_v-B, 0):, :] = 0
4573
+ disk_overlap_map[:, :L] = 0
4574
+ disk_overlap_map[:, max(N_W_h-R, 0):] = 0
4575
+
4576
+ return disk_overlap_map
4577
+
4578
+
4579
+
4580
+ @property
4581
+ def disk_overlap_map(self):
4582
+ r"""`torch.Tensor`: The image of the disk overlap map of the cropped
4583
+ fake CBED pattern.
4584
+
4585
+ See the documentation for the attribute
4586
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.signal` for additional
4587
+ context.
4588
+
4589
+ Let ``signal``, ``device``, and ``core_attrs`` denote the attributes
4590
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.signal`,
4591
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.device`, and
4592
+ :attr:`~fancytypes.Checkable.core_attrs` respectively.
4593
+
4594
+ ``disk_overlap_map`` is calculated effectively by:
4595
+
4596
+ .. code-block:: python
4597
+
4598
+ N_W_h, N_W_v = core_attrs["cropping_window_dims_in_pixels"]
4599
+ L, R, B, T = core_attrs["mask_frame"]
4600
+
4601
+ disk_overlap_map = signal.data[2]
4602
+ disk_overlap_map = torch.from_numpy(disk_overlap_map)
4603
+ disk_overlap_map = disk_overlap_map.to(device=device, dtype=int)
4604
+ disk_overlap_map[:T, :] = 0
4605
+ disk_overlap_map[max(N_W_v-B, 0):, :] = 0
4606
+ disk_overlap_map[:, :L] = 0
4607
+ disk_overlap_map[:, max(N_W_h-R, 0):] = 0
4608
+
4609
+ Note that ``disk_overlap_map`` should be considered **read-only**.
4610
+
4611
+ """
4612
+ result = self.get_disk_overlap_map(deep_copy=True)
4613
+
4614
+ return result
4615
+
4616
+
4617
+
4618
+ @property
4619
+ def principal_disk_is_overlapping(self):
4620
+ r"""`bool`: Equals ``True`` if the "principal" CBED disk exists and is
4621
+ overlapping with another CBED disk in the image of the cropped fake CBED
4622
+ pattern.
4623
+
4624
+ See the summary documentation for the class
4625
+ :class:`fakecbed.discretized.CroppedCBEDPattern` for additional context,
4626
+ as well as the documentation for the attribute
4627
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_overlap_map`.
4628
+
4629
+ Let ``core_attrs``, ``num_disks``, ``disk_supports``, and
4630
+ ``disk_overlap_map`` denote the attributes
4631
+ :attr:`~fancytypes.Checkable.core_attrs`,
4632
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.num_disks`,
4633
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_supports`, and
4634
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_overlap_map`
4635
+ respectively.
4636
+
4637
+ ``principal_disk_is_overlapping`` is calculated effectively by:
4638
+
4639
+ .. code-block:: python
4640
+
4641
+ principal_disk_idx = core_attrs["principal_disk_idx"]
4642
+
4643
+ principal_disk_support = (disk_supports[principal_disk_idx]
4644
+ if (principal_disk_idx < num_disks)
4645
+ else torch.zeros_like(disk_overlap_map))
4646
+
4647
+ principal_disk_is_overlapping = \
4648
+ torch.any(disk_overlap_map*principal_disk_support > 1.5).item()
4649
+
4650
+ Note that ``principal_disk_is_overlapping`` should be considered
4651
+ **read-only**.
4652
+
4653
+ """
4654
+ if self._principal_disk_is_overlapping is None:
4655
+ method_name = ("_calc_principal_disk_is_overlapping"
4656
+ "_and_cache_select_intermediates")
4657
+ method_alias = getattr(self, method_name)
4658
+ self._principal_disk_is_overlapping = method_alias()
4659
+
4660
+ result = self._principal_disk_is_overlapping
4661
+
4662
+ return result
4663
+
4664
+
4665
+
4666
+ def _calc_principal_disk_is_overlapping_and_cache_select_intermediates(
4667
+ self):
4668
+ principal_disk_idx = self._principal_disk_idx
4669
+ num_disks = self._num_disks
4670
+
4671
+ if self._disk_supports is None:
4672
+ method_name = "_calc_disk_supports"
4673
+ method_alias = getattr(self, method_name)
4674
+ self._disk_supports = method_alias()
4675
+ disk_supports = self._disk_supports
4676
+
4677
+ if self._disk_overlap_map is None:
4678
+ method_name = "_calc_disk_overlap_map"
4679
+ method_alias = getattr(self, method_name)
4680
+ self._disk_overlap_map = method_alias()
4681
+ disk_overlap_map = self._disk_overlap_map
4682
+
4683
+ principal_disk_support = (disk_supports[principal_disk_idx]
4684
+ if (principal_disk_idx < num_disks)
4685
+ else torch.zeros_like(disk_overlap_map))
4686
+
4687
+ principal_disk_is_overlapping = \
4688
+ torch.any(disk_overlap_map*principal_disk_support > 1.5).item()
4689
+
4690
+ return principal_disk_is_overlapping
4691
+
4692
+
4693
+
4694
+ def get_signal(self, deep_copy=_default_deep_copy):
4695
+ r"""Return the hyperspy signal representation of the cropped fake CBED
4696
+ pattern.
4697
+
4698
+ Parameters
4699
+ ----------
4700
+ deep_copy : `bool`, optional
4701
+ Let ``signal`` denote the attribute
4702
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.signal`.
4703
+
4704
+ If ``deep_copy`` is set to ``True``, then a deep copy of ``signal``
4705
+ is returned. Otherwise, a reference to ``signal`` is returned.
4706
+
4707
+ Returns
4708
+ -------
4709
+ signal : :class:`hyperspy._signals.signal2d.Signal2D`
4710
+ The attribute
4711
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.signal`.
4712
+
4713
+ """
4714
+ params = {"deep_copy": deep_copy}
4715
+ deep_copy = _check_and_convert_deep_copy(params)
4716
+
4717
+ if self._signal is None:
4718
+ method_name = "_calc_signal_and_cache_select_intermediates"
4719
+ method_alias = getattr(self, method_name)
4720
+ self._signal = method_alias()
4721
+
4722
+ signal = (copy.deepcopy(self._signal)
4723
+ if (deep_copy == True)
4724
+ else self._signal)
4725
+
4726
+ return signal
4727
+
4728
+
4729
+
4730
+ def _calc_signal_and_cache_select_intermediates(self):
4731
+ method_name = "_calc_signal_metadata_and_cache_select_intermediates"
4732
+ method_alias = getattr(self, method_name)
4733
+ signal_metadata = method_alias()
4734
+
4735
+ optional_params = self._generate_optional_cropping_params()
4736
+
4737
+ kwargs = {"input_signal": \
4738
+ self._cbed_pattern.get_signal(deep_copy=False),
4739
+ "optional_params": \
4740
+ optional_params}
4741
+ signal_data = empix.crop(**kwargs).data
4742
+
4743
+ N_W_h, N_W_v = self._cropping_window_dims_in_pixels
4744
+ L, R, B, T = self._mask_frame # Mask frame of cropped pattern.
4745
+
4746
+ signal_data[:, :T, :] = 0
4747
+ signal_data[:, max(N_W_v-B, 0):, :] = 0
4748
+ signal_data[:, :, :L] = 0
4749
+ signal_data[:, :, max(N_W_h-R, 0):] = 0
4750
+
4751
+ matrix_to_normalize = torch.from_numpy(signal_data[0])
4752
+ kwargs = {"input_matrix": matrix_to_normalize.to(device=self._device)}
4753
+ normalized_matrix = self._cbed_pattern._normalize_matrix(**kwargs)
4754
+
4755
+ signal_data[0] = \
4756
+ normalized_matrix.cpu().detach().clone().numpy(force=True)
4757
+
4758
+ signal = \
4759
+ hyperspy.signals.Signal2D(data=signal_data,
4760
+ metadata=signal_metadata)
4761
+ signal.axes_manager = \
4762
+ self._generate_cropped_blank_signal().axes_manager
4763
+
4764
+ signal.axes_manager[0].name = \
4765
+ "cropped fake CBED pattern attribute"
4766
+ signal.axes_manager[1].name = \
4767
+ "fractional horizontal coordinate of uncropped fake CBED pattern"
4768
+ signal.axes_manager[2].name = \
4769
+ "fractional vertical coordinate of uncropped fake CBED pattern"
4770
+
4771
+ return signal
4772
+
4773
+
4774
+
4775
+ def _calc_signal_metadata_and_cache_select_intermediates(self):
4776
+ cbed_pattern_signal = self._cbed_pattern.get_signal(deep_copy=False)
4777
+
4778
+ title = "Cropped " + cbed_pattern_signal.metadata.General.title
4779
+
4780
+ pre_serialized_core_attrs = self.pre_serialize()
4781
+
4782
+ attr_name_subset = ("_principal_disk_analysis_results",
4783
+ "_disk_clipping_registry",
4784
+ "_disk_absence_registry",
4785
+ "_principal_disk_is_clipped",
4786
+ "_principal_disk_is_absent",
4787
+ "_principal_disk_is_overlapping")
4788
+ for attr_name in attr_name_subset:
4789
+ attr = getattr(self, attr_name)
4790
+ if attr is None:
4791
+ method_name = ("_calc{}_and_cache_select"
4792
+ "_intermediates").format(attr_name)
4793
+ method_alias = getattr(self, method_name)
4794
+ attr = method_alias()
4795
+ setattr(self, attr_name, attr)
4796
+
4797
+ fakecbed_metadata = \
4798
+ cbed_pattern_signal.metadata.FakeCBED.as_dictionary()
4799
+ fakecbed_metadata["pre_serialized_core_attrs"] = \
4800
+ pre_serialized_core_attrs
4801
+
4802
+ attr_name_subset += ("_principal_disk_bounding_box"
4803
+ "_in_uncropped_image_fractional_coords",
4804
+ "_principal_disk_bounding_box"
4805
+ "_in_cropped_image_fractional_coords",
4806
+ "_principal_disk_boundary_pts"
4807
+ "_in_uncropped_image_fractional_coords",
4808
+ "_principal_disk_boundary_pts"
4809
+ "_in_cropped_image_fractional_coords")
4810
+ for attr_name in attr_name_subset[1:]:
4811
+ key = attr_name[1:]
4812
+ attr = getattr(self, attr_name)
4813
+ metadata_item = self._convert_attr_to_metadata_item(attr)
4814
+ fakecbed_metadata[key] = metadata_item
4815
+
4816
+ signal_metadata = {"General": {"title": title},
4817
+ "Signal": {"pixel value units": "dimensionless"},
4818
+ "FakeCBED": fakecbed_metadata}
4819
+
4820
+ return signal_metadata
4821
+
4822
+
4823
+
4824
+ def _convert_attr_to_metadata_item(self, attr):
4825
+ if isinstance(attr, (tuple, bool)):
4826
+ metadata_item = attr
4827
+ else:
4828
+ if len(attr.shape) == 1:
4829
+ metadata_item = tuple(elem
4830
+ for elem
4831
+ in attr.cpu().detach().clone().tolist())
4832
+ else:
4833
+ metadata_item = tuple(tuple(pt)
4834
+ for pt
4835
+ in attr.cpu().detach().clone().tolist())
4836
+
4837
+ return metadata_item
4838
+
4839
+
4840
+
4841
+ @property
4842
+ def signal(self):
4843
+ r"""`hyperspy._signals.signal2d.Signal2D`: The hyperspy signal
4844
+ representation of the cropped fake CBED pattern.
4845
+
4846
+ See the summary documentation for the classes
4847
+ :class:`fakecbed.discretized.CBEDPattern` and
4848
+ :class:`fakecbed.discretized.CroppedCBEDPattern` for additional context.
4849
+
4850
+ Let ``disk_clipping_registry``, ``disk_absence_registry``,
4851
+ ``principal_disk_is_clipped``, ``principal_disk_is_absent``,
4852
+ ``principal_disk_is_overlapping``,
4853
+ ``principal_disk_bounding_box_in_uncropped_image_fractional_coords``,
4854
+ ``principal_disk_bounding_box_in_cropped_image_fractional_coords``,
4855
+ ``principal_disk_boundary_pts_in_uncropped_image_fractional_coords``,
4856
+ ``principal_disk_boundary_pts_in_cropped_image_fractional_coords``, and
4857
+ ``core_attrs`` denote the attributes
4858
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_clipping_registry`,
4859
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.disk_absence_registry`,
4860
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_is_clipped`,
4861
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_is_absent`,
4862
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_is_overlapping`,
4863
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_bounding_box_in_uncropped_image_fractional_coords`,
4864
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_bounding_box_in_cropped_image_fractional_coords`,
4865
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_boundary_pts_in_uncropped_image_fractional_coords`,
4866
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.principal_disk_boundary_pts_in_cropped_image_fractional_coords`,
4867
+ and :attr:`~fancytypes.Checkable.core_attrs` respectively. Furthermore,
4868
+ let ``pre_serialize`` denote the method
4869
+ :meth:`~fancytypes.PreSerializable.pre_serialize`.
4870
+
4871
+ ``signal`` is calculated effectively by:
4872
+
4873
+ .. code-block:: python
4874
+
4875
+ import empix
4876
+ import numpy as np
4877
+
4878
+ cbed_pattern = core_attrs["cbed_pattern"]
4879
+ N_W_h, N_W_v = core_attrs["cropping_window_dims_in_pixels"]
4880
+ L, R, B, T = core_attrs["mask_frame"]
4881
+
4882
+ cbed_pattern_signal = cbed_pattern.signal
4883
+
4884
+ title = "Cropped " + cbed_pattern_signal.metadata.General.title
4885
+
4886
+ cropping_window_center = \
4887
+ core_attrs["cropping_window_center"]
4888
+ cropping_window_dims_in_pixels = \
4889
+ core_attrs["cropping_window_dims_in_pixels"]
4890
+
4891
+ kwargs = {"center": cropping_window_center,
4892
+ "window_dims": cropping_window_dims_in_pixels,
4893
+ "pad_mode": "zeros",
4894
+ "apply_symmetric_mask": False}
4895
+ optional_params = empix.OptionalCroppingParams(**kwargs)
4896
+
4897
+ kwargs = {"input_signal": cbed_pattern_signal,
4898
+ "optional_params": optional_params}
4899
+ signal = empix.crop(**kwargs)
4900
+
4901
+ signal.data[:, :T, :] = 0
4902
+ signal.data[:, max(N_W_v-B, 0):, :] = 0
4903
+ signal.data[:, :, :L] = 0
4904
+ signal.data[:, :, max(N_W_h-R, 0):] = 0
4905
+
4906
+ # Normalize ``signal.data[0]``.
4907
+ matrix = signal.data[0]
4908
+ if matrix.max()-matrix.min() > 0:
4909
+ normalization_weight = 1 / (matrix.max()-matrix.min())
4910
+ normalization_bias = -normalization_weight*matrix.min()
4911
+ matrix = (matrix*normalization_weight
4912
+ + normalization_bias).clip(min=0, max=1)
4913
+ else:
4914
+ matrix = np.zeros_like(matrix)
4915
+ signal.data[0] = matrix
4916
+
4917
+ kwargs = {"item_path": "General.title", "value": title}
4918
+ signal.metadata.set_item(**kwargs)
4919
+
4920
+ kwargs["item_path"] = "FakeCBED.pre_serialized_core_attrs"
4921
+ kwargs["value"] = pre_serialize()
4922
+ signal.metadata.set_item(**kwargs)
4923
+
4924
+ def _convert_attr_to_metadata_item(attr):
4925
+ if isinstance(attr, (tuple, bool)):
4926
+ metadata_item = \
4927
+ attr
4928
+ else:
4929
+ if len(attr.shape) == 1:
4930
+ metadata_item = \
4931
+ tuple(elem
4932
+ for elem
4933
+ in attr.cpu().detach().clone().tolist())
4934
+ else:
4935
+ metadata_item = \
4936
+ tuple(tuple(pt)
4937
+ for pt
4938
+ in attr.cpu().detach().clone().tolist())
4939
+
4940
+ return metadata_item
4941
+
4942
+ attr = disk_clipping_registry
4943
+ kwargs["item_path"] = "FakeCBED.disk_clipping_registry"
4944
+ kwargs["value"] = _convert_attr_to_metadata_item(attr)
4945
+ signal.metadata.set_item(**kwargs)
4946
+
4947
+ attr = disk_absence_registry
4948
+ kwargs["item_path"] = "FakeCBED.disk_absence_registry"
4949
+ kwargs["value"] = _convert_attr_to_metadata_item(attr)
4950
+ signal.metadata.set_item(**kwargs)
4951
+
4952
+ attr = principal_disk_is_clipped
4953
+ kwargs["item_path"] = "FakeCBED.principal_disk_is_clipped"
4954
+ kwargs["value"] = _convert_attr_to_metadata_item(attr)
4955
+ signal.metadata.set_item(**kwargs)
4956
+
4957
+ attr = principal_disk_is_absent
4958
+ kwargs["item_path"] = "FakeCBED.principal_disk_is_absent"
4959
+ kwargs["value"] = _convert_attr_to_metadata_item(attr)
4960
+ signal.metadata.set_item(**kwargs)
4961
+
4962
+ attr = principal_disk_is_overlapping
4963
+ kwargs["item_path"] = "FakeCBED.principal_disk_is_overlapping"
4964
+ kwargs["value"] = _convert_attr_to_metadata_item(attr)
4965
+ signal.metadata.set_item(**kwargs)
4966
+
4967
+ attr = \
4968
+ principal_disk_bounding_box_in_uncropped_image_fractional_coords
4969
+ kwargs["item_path"] = \
4970
+ ("FakeCBED.principal_disk_bounding_box"
4971
+ "_in_uncropped_image_fractional_coords")
4972
+ kwargs["value"] = \
4973
+ _convert_attr_to_metadata_item(attr)
4974
+ _ = \
4975
+ signal.metadata.set_item(**kwargs)
4976
+
4977
+ attr = \
4978
+ principal_disk_bounding_box_in_cropped_image_fractional_coords
4979
+ kwargs["item_path"] = \
4980
+ ("FakeCBED.principal_disk_bounding_box"
4981
+ "_in_cropped_image_fractional_coords")
4982
+ kwargs["value"] = \
4983
+ _convert_attr_to_metadata_item(attr)
4984
+ _ = \
4985
+ signal.metadata.set_item(**kwargs)
4986
+
4987
+ attr = \
4988
+ principal_disk_boundary_pts_in_uncropped_image_fractional_coords
4989
+ kwargs["item_path"] = \
4990
+ ("FakeCBED.principal_disk_boundary_pts"
4991
+ "_in_uncropped_image_fractional_coords")
4992
+ kwargs["value"] = \
4993
+ _convert_attr_to_metadata_item(attr)
4994
+ _ = \
4995
+ signal.metadata.set_item(**kwargs)
4996
+
4997
+ attr = \
4998
+ principal_disk_boundary_pts_in_cropped_image_fractional_coords
4999
+ kwargs["item_path"] = \
5000
+ ("FakeCBED.principal_disk_boundary_pts"
5001
+ "_in_cropped_image_fractional_coords")
5002
+ kwargs["value"] = \
5003
+ _convert_attr_to_metadata_item(attr)
5004
+ _ = \
5005
+ signal.metadata.set_item(**kwargs)
5006
+
5007
+ signal.axes_manager[0].name = \
5008
+ "cropped fake CBED pattern attribute"
5009
+ signal.axes_manager[1].name = \
5010
+ "fractional horizontal coordinate of uncropped fake CBED pattern"
5011
+ signal.axes_manager[2].name = \
5012
+ "fractional vertical coordinate of uncropped fake CBED pattern"
5013
+
5014
+ Note that ``signal`` should be considered **read-only**.
5015
+
5016
+ """
5017
+ result = self.get_signal(deep_copy=True)
5018
+
5019
+ return result
5020
+
5021
+
5022
+
5023
+ @property
5024
+ def image_has_been_overridden(self):
5025
+ r"""`bool`: Equals ``True`` if the image of the fake cropped CBED
5026
+ pattern has been overridden.
5027
+
5028
+ See the summary documentation for the classes
5029
+ :class:`fakecbed.discretized.CBEDPattern` and
5030
+ :class:`fakecbed.discretized.CroppedCBEDPattern` for additional context,
5031
+ as well as the documentation for the attribute
5032
+ :attr:`fakecbed.discretized.CBEDPattern.image_has_been_overridden`.
5033
+
5034
+ Let ``core_attrs`` denote the attribute
5035
+ :attr:`~fancytypes.Checkable.core_attrs`.
5036
+
5037
+ ``image_has_been_overridden`` is calculated effectively by:
5038
+
5039
+ .. code-block:: python
5040
+
5041
+ cbed_pattern = core_attrs["cbed_pattern"]
5042
+ image_has_been_overridden = cbed_pattern.image_has_been_overridden
5043
+
5044
+ Note that ``image_has_been_overridden`` should be considered
5045
+ **read-only**.
5046
+
5047
+ """
5048
+ result = self._image_has_been_overridden
5049
+
5050
+ return result
5051
+
5052
+
5053
+
5054
+ def get_image(self, deep_copy=_default_deep_copy):
5055
+ r"""Return the image of the cropped fake CBED pattern.
5056
+
5057
+ Parameters
5058
+ ----------
5059
+ deep_copy : `bool`, optional
5060
+ Let ``image`` denote the attribute
5061
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.image`.
5062
+
5063
+ If ``deep_copy`` is set to ``True``, then a deep copy of ``image``
5064
+ is returned. Otherwise, a reference to ``image`` is returned.
5065
+
5066
+ Returns
5067
+ -------
5068
+ image : `torch.Tensor` (`float`, ndim=2)
5069
+ The attribute :attr:`fakecbed.discretized.CroppedCBEDPattern.image`.
5070
+
5071
+ """
5072
+ params = {"deep_copy": deep_copy}
5073
+ deep_copy = _check_and_convert_deep_copy(params)
5074
+
5075
+ if self._image is None:
5076
+ method_name = "_calc_image"
5077
+ method_alias = getattr(self, method_name)
5078
+ self._image = method_alias()
5079
+
5080
+ image = (self._image.detach().clone()
5081
+ if (deep_copy == True)
5082
+ else self._image)
5083
+
5084
+ return image
5085
+
5086
+
5087
+
5088
+ def _calc_image(self):
5089
+ uncropped_cbed_pattern_image = \
5090
+ self._cbed_pattern.get_image(deep_copy=False)
5091
+ uncropped_cbed_pattern_image = \
5092
+ uncropped_cbed_pattern_image.cpu().detach().clone()
5093
+
5094
+ signal_to_crop = \
5095
+ self._generate_uncropped_blank_signal()
5096
+ signal_to_crop.data[0] = \
5097
+ uncropped_cbed_pattern_image.numpy(force=True)
5098
+
5099
+ optional_params = self._generate_optional_cropping_params()
5100
+
5101
+ kwargs = {"input_signal": signal_to_crop,
5102
+ "optional_params": optional_params}
5103
+ cropped_signal = empix.crop(**kwargs)
5104
+
5105
+ image = cropped_signal.data[0]
5106
+ image_dtype = uncropped_cbed_pattern_image.dtype
5107
+ image = torch.from_numpy(image)
5108
+ image = image.to(device=self._device, dtype=image_dtype)
5109
+
5110
+ N_W_h, N_W_v = self._cropping_window_dims_in_pixels
5111
+ L, R, B, T = self._mask_frame # Mask frame of cropped pattern.
5112
+
5113
+ image[:T, :] = 0
5114
+ image[max(N_W_v-B, 0):, :] = 0
5115
+ image[:, :L] = 0
5116
+ image[:, max(N_W_h-R, 0):] = 0
5117
+
5118
+ kwargs = {"input_matrix": image}
5119
+ image = self._cbed_pattern._normalize_matrix(**kwargs)
5120
+
5121
+ return image
5122
+
5123
+
5124
+
5125
+ @property
5126
+ def image(self):
5127
+ r"""`torch.Tensor`: The image of the cropped fake CBED pattern.
5128
+
5129
+ See the documentation for the attribute
5130
+ :attr:`fakecbed.discretized.CBEDPattern.signal` for additional
5131
+ context.
5132
+
5133
+ Let ``signal``, ``device``, and ``core_attrs`` denote the attributes
5134
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.signal`,
5135
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.device`, and
5136
+ :attr:`~fancytypes.Checkable.core_attrs` respectively.
5137
+
5138
+ ``image`` is calculated effectively by:
5139
+
5140
+ .. code-block:: python
5141
+
5142
+ N_W_h, N_W_v = core_attrs["cropping_window_dims_in_pixels"]
5143
+ L, R, B, T = core_attrs["mask_frame"]
5144
+
5145
+ image = signal.data[0]
5146
+ image_dtype = image.dtype
5147
+ image = torch.from_numpy(image)
5148
+ image = image.to(device=device, dtype=image_dtype)
5149
+ image[:T, :] = 0
5150
+ image[max(N_W_v-B, 0):, :] = 0
5151
+ image[:, :L] = 0
5152
+ image[:, max(N_W_h-R, 0):] = 0
5153
+
5154
+ Note that ``image`` should be considered **read-only**.
5155
+
5156
+ """
5157
+ result = self.get_image(deep_copy=True)
5158
+
5159
+ return result
5160
+
5161
+
5162
+
5163
+ def get_illumination_support(self, deep_copy=_default_deep_copy):
5164
+ r"""Return the illumination support of the cropped fake CBED pattern.
5165
+
5166
+ Parameters
5167
+ ----------
5168
+ deep_copy : `bool`, optional
5169
+ Let ``illumination_support`` denote the attribute
5170
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.illumination_support`.
5171
+
5172
+ If ``deep_copy`` is set to ``True``, then a deep copy of
5173
+ ``illumination_support`` is returned. Otherwise, a reference to
5174
+ ``illumination_support`` is returned.
5175
+
5176
+ Returns
5177
+ -------
5178
+ illumination_support : `torch.Tensor` (`float`, ndim=2)
5179
+ The attribute
5180
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.illumination_support`.
5181
+
5182
+ """
5183
+ params = {"deep_copy": deep_copy}
5184
+ deep_copy = _check_and_convert_deep_copy(params)
5185
+
5186
+ if self._illumination_support is None:
5187
+ method_name = "_calc_illumination_support"
5188
+ method_alias = getattr(self, method_name)
5189
+ self._illumination_support = method_alias()
5190
+
5191
+ illumination_support = (self._illumination_support.detach().clone()
5192
+ if (deep_copy == True)
5193
+ else self._illumination_support)
5194
+
5195
+ return illumination_support
5196
+
5197
+
5198
+
5199
+ def _calc_illumination_support(self):
5200
+ uncropped_cbed_pattern_illumination_support = \
5201
+ self._cbed_pattern.get_illumination_support(deep_copy=False)
5202
+ uncropped_cbed_pattern_illumination_support = \
5203
+ uncropped_cbed_pattern_illumination_support.cpu().detach().clone()
5204
+
5205
+ signal_to_crop = \
5206
+ self._generate_uncropped_blank_signal()
5207
+ signal_to_crop.data[1] = \
5208
+ uncropped_cbed_pattern_illumination_support.numpy(force=True)
5209
+
5210
+ optional_params = self._generate_optional_cropping_params()
5211
+
5212
+ kwargs = {"input_signal": signal_to_crop,
5213
+ "optional_params": optional_params}
5214
+ cropped_signal = empix.crop(**kwargs)
5215
+
5216
+ illumination_support = \
5217
+ cropped_signal.data[1]
5218
+ illumination_support_dtype = \
5219
+ uncropped_cbed_pattern_illumination_support.dtype
5220
+ illumination_support = \
5221
+ torch.from_numpy(illumination_support)
5222
+ illumination_support = \
5223
+ illumination_support.to(device=self._device,
5224
+ dtype=illumination_support_dtype)
5225
+
5226
+ return illumination_support
5227
+
5228
+
5229
+
5230
+ @property
5231
+ def illumination_support(self):
5232
+ r"""`torch.Tensor`: The illumination support of the cropped fake CBED
5233
+ pattern.
5234
+
5235
+ See the documentation for the attribute
5236
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.signal` for additional
5237
+ context.
5238
+
5239
+ Let ``signal``, and ``device`` denote the attributes
5240
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.signal`, and
5241
+ :attr:`fakecbed.discretized.CroppedCBEDPattern.device` respectively.
5242
+
5243
+ ``illumination_support`` is calculated effectively by:
5244
+
5245
+ .. code-block:: python
5246
+
5247
+ illumination_support = signal.data[1]
5248
+ illumination_support = torch.from_numpy(illumination_support)
5249
+ illumination_support = illumination_support.to(device=device,
5250
+ dtype=bool)
5251
+
5252
+ Note that ``illumination_support`` should be considered **read-only**.
5253
+
5254
+ """
5255
+ result = self.get_illumination_support(deep_copy=True)
5256
+
5257
+ return result
5258
+
5259
+
5260
+
5261
+ ###########################
5262
+ ## Define error messages ##
5263
+ ###########################
5264
+
5265
+ _check_and_convert_undistorted_disks_err_msg_1 = \
5266
+ ("The object ``undistorted_disks`` must be a sequence of "
5267
+ "`fakecbed.shapes.NonuniformBoundedShape` objects, where for each element "
5268
+ "``elem`` in ``undistorted_disks``, "
5269
+ "``isinstance(elem.core_attrs['support'], "
5270
+ "(fakecbed.shapes.Circle, fakecbed.shapes.Ellipse))`` evaluates to "
5271
+ "``True``.")
5272
+
5273
+ _check_and_convert_undistorted_misc_shapes_err_msg_1 = \
5274
+ ("The object ``undistorted_misc_shapes`` must be a sequence of objects of "
5275
+ "any of the following types: ("
5276
+ "`fakecbed.shapes.Circle`, "
5277
+ "`fakecbed.shapes.Ellipse`, "
5278
+ "`fakecbed.shapes.Peak`, "
5279
+ "`fakecbed.shapes.Band`, "
5280
+ "`fakecbed.shapes.PlaneWave`, "
5281
+ "`fakecbed.shapes.Arc`, "
5282
+ "`fakecbed.shapes.GenericBlob`, "
5283
+ "`fakecbed.shapes.Orbital`, "
5284
+ "`fakecbed.shapes.Lune`, "
5285
+ "`fakecbed.shapes.NonuniformBoundedShape`).")
5286
+
5287
+ _check_and_convert_distortion_model_err_msg_1 = \
5288
+ ("The dimensions, in units of pixels, of the distortion model sampling "
5289
+ "grid, specified by the object ``distortion_model``, must be divisible "
5290
+ "by the object ``num_pixels_across_pattern``.")
5291
+
5292
+ _check_and_convert_rng_seed_err_msg_1 = \
5293
+ ("The object ``rng_seed`` must be either a nonnegative integer or of the "
5294
+ "type `NoneType`.")
5295
+
5296
+ _check_and_convert_cold_pixels_err_msg_1 = \
5297
+ ("The object ``cold_pixels`` must be a sequence of integer pairs, where "
5298
+ "each integer pair specifies valid pixel coordinates (i.e. row and column "
5299
+ "indices) of a pixel in the discretized fake CBED pattern.")
5300
+
5301
+ _check_and_convert_overriding_image_err_msg_1 = \
5302
+ ("The object ``overriding_image`` must have dimensions, in units of "
5303
+ "pixels, equal to those of the original CBED pattern intensity image "
5304
+ "being overridden, which in this case are ``({}, {})``.")
5305
+
5306
+ _cbed_pattern_err_msg_1 = \
5307
+ ("Failed to generate discretized fake CBED pattern. See traceback for "
5308
+ "details.")
5309
+
5310
+ _check_and_convert_cropping_window_center_err_msg_1 = \
5311
+ ("The object ``cropping_window_center`` must be `NoneType` or a pair of "
5312
+ "real numbers.")
5313
+
5314
+ _check_and_convert_cropping_window_dims_in_pixels_err_msg_1 = \
5315
+ ("The object ``cropping_window_dims_in_pixels`` must be `NoneType` or a "
5316
+ "pair of positive integers.")