# -*- coding: utf-8 -*-
# Copyright 2024 Matthew Fitzpatrick.
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
"""For creating discretized fake CBED patterns.
"""
#####################################
## Load libraries/packages/modules ##
#####################################
# For accessing attributes of functions.
import inspect
# For randomly selecting items in dictionaries.
import random
# For performing deep copies.
import copy
# For general array handling.
import numpy as np
import torch
# For validating and converting objects.
import czekitout.check
import czekitout.convert
# For defining classes that support enforced validation, updatability,
# pre-serialization, and de-serialization.
import fancytypes
# For creating hyperspy signals and axes.
import hyperspy.signals
import hyperspy.axes
# For creating distortion models.
import distoptica
# For inpainting images.
import skimage.restoration
# For creating undistorted geometric shapes.
import fakecbed.shapes
# For creating undistorted thermal diffuse models.
import fakecbed.tds
##################################
## Define classes and functions ##
##################################
# List of public objects in module.
__all__ = ["CBEDPattern"]
def _check_and_convert_undistorted_tds_model(params):
current_func_name = inspect.stack()[0][3]
char_idx = 19
obj_name = current_func_name[char_idx:]
obj = params[obj_name]
accepted_types = (fakecbed.tds.Model, type(None))
if isinstance(obj, accepted_types[-1]):
undistorted_tds_model = accepted_types[0]()
else:
kwargs = {"obj": obj,
"obj_name": obj_name,
"accepted_types": accepted_types}
czekitout.check.if_instance_of_any_accepted_types(**kwargs)
undistorted_tds_model = copy.deepcopy(obj)
return undistorted_tds_model
def _pre_serialize_undistorted_tds_model(undistorted_tds_model):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize.pre_serialize()
return serializable_rep
def _de_pre_serialize_undistorted_tds_model(serializable_rep):
undistorted_tds_model = \
fakecbed.tds.Model.de_pre_serialize(serializable_rep)
return undistorted_tds_model
def _check_and_convert_undistorted_disks(params):
current_func_name = inspect.stack()[0][3]
char_idx = 19
obj_name = current_func_name[char_idx:]
obj = params[obj_name]
try:
for undistorted_disk in obj:
accepted_types = (fakecbed.shapes.NonuniformBoundedShape,)
kwargs = {"obj": undistorted_disk,
"obj_name": "undistorted_disk",
"accepted_types": accepted_types}
czekitout.check.if_instance_of_any_accepted_types(**kwargs)
accepted_types = (fakecbed.shapes.Circle, fakecbed.shapes.Ellipse)
undistorted_disk_core_attrs = \
undistorted_disk.get_core_attrs(deep_copy=False)
undistorted_disk_support = \
undistorted_disk_core_attrs["support"]
kwargs = {"obj": undistorted_disk_support,
"obj_name": "undistorted_disk_support",
"accepted_types": accepted_types}
czekitout.check.if_instance_of_any_accepted_types(**kwargs)
except:
err_msg = globals()[current_func_name+"_err_msg_1"]
raise TypeError(err_msg)
undistorted_disks = copy.deepcopy(obj)
return undistorted_disks
def _pre_serialize_undistorted_disks(undistorted_disks):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = tuple()
for elem in obj_to_pre_serialize:
serializable_rep += (elem.pre_serialize(),)
return serializable_rep
def _de_pre_serialize_undistorted_disks(serializable_rep):
undistorted_disks = \
tuple()
for pre_serialized_undistorted_disk in serializable_rep:
cls_alias = \
fakecbed.shapes.NonuniformBoundedShape
undistorted_disk = \
cls_alias.de_pre_serialize(pre_serialized_undistorted_disk)
undistorted_disks += \
(undistorted_disk,)
return undistorted_disks
def _check_and_convert_undistorted_misc_shapes(params):
current_func_name = inspect.stack()[0][3]
char_idx = 19
obj_name = current_func_name[char_idx:]
obj = params[obj_name]
accepted_types = (fakecbed.shapes.Circle,
fakecbed.shapes.Ellipse,
fakecbed.shapes.Peak,
fakecbed.shapes.Band,
fakecbed.shapes.PlaneWave,
fakecbed.shapes.Arc,
fakecbed.shapes.GenericBlob,
fakecbed.shapes.Orbital,
fakecbed.shapes.Lune,
fakecbed.shapes.NonuniformBoundedShape)
try:
for undistorted_misc_shape in obj:
kwargs = {"obj": undistorted_misc_shape,
"obj_name": "undistorted_misc_shape",
"accepted_types": accepted_types}
czekitout.check.if_instance_of_any_accepted_types(**kwargs)
except:
err_msg = globals()[current_func_name+"_err_msg_1"]
raise TypeError(err_msg)
undistorted_misc_shapes = copy.deepcopy(obj)
return undistorted_misc_shapes
def _pre_serialize_undistorted_misc_shapes(undistorted_misc_shapes):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = tuple()
for elem in obj_to_pre_serialize:
serializable_rep += (elem.pre_serialize(),)
return serializable_rep
def _de_pre_serialize_undistorted_misc_shapes(serializable_rep):
undistorted_misc_shapes = tuple()
for pre_serialized_undistorted_misc_shape in serializable_rep:
if "radius" in pre_serialized_undistorted_misc_shape:
cls_alias = fakecbed.shapes.Circle
elif "eccentricity" in pre_serialized_undistorted_misc_shape:
cls_alias = fakecbed.shapes.Ellipse
elif "functional_form" in pre_serialized_undistorted_misc_shape:
cls_alias = fakecbed.shapes.Peak
elif "end_pt_1" in pre_serialized_undistorted_misc_shape:
cls_alias = fakecbed.shapes.Band
elif "propagation_direction" in pre_serialized_undistorted_misc_shape:
cls_alias = fakecbed.shapes.PlaneWave
elif "subtending_angle" in pre_serialized_undistorted_misc_shape:
cls_alias = fakecbed.shapes.Arc
elif "radial_amplitudes" in pre_serialized_undistorted_misc_shape:
cls_alias = fakecbed.shapes.GenericBlob
elif "magnetic_quantum_number" in pre_serialized_undistorted_misc_shape:
cls_alias = fakecbed.shapes.Orbital
elif "bg_ellipse" in pre_serialized_undistorted_misc_shape:
cls_alias = fakecbed.shapes.Lune
else:
cls_alias = fakecbed.shapes.NonuniformBoundedShape
undistorted_misc_shape = \
cls_alias.de_pre_serialize(pre_serialized_undistorted_misc_shape)
undistorted_misc_shapes += \
(undistorted_misc_shape,)
return undistorted_misc_shapes
def _check_and_convert_undistorted_outer_illumination_shape(params):
current_func_name = inspect.stack()[0][3]
char_idx = 19
obj_name = current_func_name[char_idx:]
obj = params[obj_name]
accepted_types = (fakecbed.shapes.Circle,
fakecbed.shapes.Ellipse,
fakecbed.shapes.GenericBlob,
type(None))
if isinstance(obj, accepted_types[-1]):
kwargs = {"radius": np.inf}
undistorted_outer_illumination_shape = accepted_types[0](**kwargs)
else:
kwargs = {"obj": obj,
"obj_name": obj_name,
"accepted_types": accepted_types}
czekitout.check.if_instance_of_any_accepted_types(**kwargs)
undistorted_outer_illumination_shape = copy.deepcopy(obj)
return undistorted_outer_illumination_shape
def _pre_serialize_undistorted_outer_illumination_shape(
undistorted_outer_illumination_shape):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize.pre_serialize()
return serializable_rep
def _de_pre_serialize_undistorted_outer_illumination_shape(serializable_rep):
if "radius" in serializable_rep:
undistorted_outer_illumination_shape = \
fakecbed.shapes.Circle.de_pre_serialize(serializable_rep)
elif "eccentricity" in serializable_rep:
undistorted_outer_illumination_shape = \
fakecbed.shapes.Ellipse.de_pre_serialize(serializable_rep)
else:
undistorted_outer_illumination_shape = \
fakecbed.shapes.GenericBlob.de_pre_serialize(serializable_rep)
return undistorted_outer_illumination_shape
def _check_and_convert_gaussian_filter_std_dev(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
func_alias = czekitout.convert.to_nonnegative_float
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
gaussian_filter_std_dev = func_alias(**kwargs)
return gaussian_filter_std_dev
def _pre_serialize_gaussian_filter_std_dev(
gaussian_filter_std_dev):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_gaussian_filter_std_dev(serializable_rep):
gaussian_filter_std_dev = serializable_rep
return gaussian_filter_std_dev
def _check_and_convert_distortion_model(params):
current_func_name = inspect.stack()[0][3]
char_idx = 19
obj_name = current_func_name[char_idx:]
obj = params[obj_name]
num_pixels_across_pattern = \
_check_and_convert_num_pixels_across_pattern(params)
accepted_types = (distoptica.DistortionModel, type(None))
if isinstance(obj, accepted_types[-1]):
sampling_grid_dims_in_pixels = 2*(num_pixels_across_pattern,)
kwargs = {"sampling_grid_dims_in_pixels": sampling_grid_dims_in_pixels}
distortion_model = accepted_types[0](**kwargs)
else:
kwargs = {"obj": obj,
"obj_name": obj_name,
"accepted_types": accepted_types}
czekitout.check.if_instance_of_any_accepted_types(**kwargs)
distortion_model = copy.deepcopy(obj)
distortion_model_core_attrs = \
distortion_model.get_core_attrs(deep_copy=False)
sampling_grid_dims_in_pixels = \
distortion_model_core_attrs["sampling_grid_dims_in_pixels"]
if ((sampling_grid_dims_in_pixels[0]%num_pixels_across_pattern != 0)
or (sampling_grid_dims_in_pixels[1]%num_pixels_across_pattern != 0)):
err_msg = globals()[current_func_name+"_err_msg_1"]
raise ValueError(err_msg)
return distortion_model
def _pre_serialize_distortion_model(distortion_model):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize.pre_serialize()
return serializable_rep
def _de_pre_serialize_distortion_model(serializable_rep):
distortion_model = \
distoptica.DistortionModel.de_pre_serialize(serializable_rep)
return distortion_model
def _check_and_convert_num_pixels_across_pattern(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
num_pixels_across_pattern = czekitout.convert.to_positive_int(**kwargs)
return num_pixels_across_pattern
def _pre_serialize_num_pixels_across_pattern(num_pixels_across_pattern):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_num_pixels_across_pattern(serializable_rep):
num_pixels_across_pattern = serializable_rep
return num_pixels_across_pattern
def _check_and_convert_apply_shot_noise(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
apply_shot_noise = czekitout.convert.to_bool(**kwargs)
return apply_shot_noise
def _pre_serialize_apply_shot_noise(apply_shot_noise):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_apply_shot_noise(serializable_rep):
apply_shot_noise = serializable_rep
return apply_shot_noise
def _check_and_convert_detector_partition_width_in_pixels(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
func_alias = czekitout.convert.to_nonnegative_int
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
detector_partition_width_in_pixels = func_alias(**kwargs)
return detector_partition_width_in_pixels
def _pre_serialize_detector_partition_width_in_pixels(
detector_partition_width_in_pixels):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_detector_partition_width_in_pixels(serializable_rep):
detector_partition_width_in_pixels = serializable_rep
return detector_partition_width_in_pixels
def _check_and_convert_cold_pixels(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
cold_pixels = czekitout.convert.to_pairs_of_ints(**kwargs)
num_pixels_across_pattern = \
_check_and_convert_num_pixels_across_pattern(params)
coords_of_cold_pixels = cold_pixels
for coords_of_cold_pixel in coords_of_cold_pixels:
row, col = coords_of_cold_pixel
if ((row < -num_pixels_across_pattern)
or (num_pixels_across_pattern <= row)
or (col < -num_pixels_across_pattern)
or (num_pixels_across_pattern <= col)):
err_msg = globals()[current_func_name+"_err_msg_1"]
raise TypeError(err_msg)
return cold_pixels
def _pre_serialize_cold_pixels(cold_pixels):
serializable_rep = cold_pixels
return serializable_rep
def _de_pre_serialize_cold_pixels(serializable_rep):
cold_pixels = serializable_rep
return cold_pixels
def _check_and_convert_deep_copy(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
deep_copy = czekitout.convert.to_bool(**kwargs)
return deep_copy
def _check_and_convert_overriding_image(params):
current_func_name = inspect.stack()[0][3]
char_idx = 19
obj_name = current_func_name[char_idx:]
obj = params[obj_name]
func_alias = fakecbed.shapes._check_and_convert_real_torch_matrix
params["real_torch_matrix"] = obj
params["name_of_alias_of_real_torch_matrix"] = obj_name
overriding_image = func_alias(params)
del params["real_torch_matrix"]
del params["name_of_alias_of_real_torch_matrix"]
num_pixels_across_pattern = params["num_pixels_across_pattern"]
expected_image_dims_in_pixels = 2*(num_pixels_across_pattern,)
if overriding_image.shape != expected_image_dims_in_pixels:
unformatted_err_msg = globals()[current_func_name+"_err_msg_1"]
args = expected_image_dims_in_pixels
err_msg = unformatted_err_msg.format(*args)
raise ValueError(err_msg)
return overriding_image
def _check_and_convert_skip_validation_and_conversion(params):
func_alias = \
fakecbed.shapes._check_and_convert_skip_validation_and_conversion
skip_validation_and_conversion = \
func_alias(params)
return skip_validation_and_conversion
_default_undistorted_outer_illumination_shape = \
None
_default_undistorted_tds_model = \
None
_default_undistorted_disks = \
tuple()
_default_undistorted_misc_shapes = \
tuple()
_default_gaussian_filter_std_dev = \
0
_default_distortion_model = \
None
_default_num_pixels_across_pattern = \
512
_default_apply_shot_noise = \
False
_default_detector_partition_width_in_pixels = \
0
_default_cold_pixels = \
tuple()
_default_skip_validation_and_conversion = \
fakecbed.shapes._default_skip_validation_and_conversion
_default_deep_copy = \
True
[docs]class CBEDPattern(fancytypes.PreSerializableAndUpdatable):
r"""The parameters of a discretized fake convergent beam electron
diffraction (CBED) pattern.
A series of parameters need to be specified in order to create an image of a
fake CBED pattern, with the most important parameters being: the set of
intensity patterns of undistorted shapes that determine the undistorted
noiseless non-blurred uncorrupted (UNNBU) fake CBED pattern; and a
distortion model which transforms the UNNBU fake CBED pattern into a
distorted noiseless non-blurred uncorrupted (DNNBU) fake CBED pattern. The
remaining parameters determine whether additional images effects are
applied, like e.g. shot noise or blur effects. Note that in the case of the
aforementioned shapes, we expand the notion of intensity patterns to mean a
2D real-valued function, i.e. it can be negative. To be clear, we do not
apply this generalized notion of intensity patterns to the fake CBED
patterns: in such cases intensity patterns mean 2D real-valued functions
that are strictly nonnegative.
Let :math:`u_{x}` and :math:`u_{y}` be the fractional horizontal and
vertical coordinates, respectively, of a point in an undistorted image,
where :math:`\left(u_{x},u_{y}\right)=\left(0,0\right)` is the bottom left
corner of the image. Secondly, let :math:`q_{x}` and :math:`q_{y}` be the
fractional horizontal and vertical coordinates, respectively, of a point in
a distorted image, where :math:`\left(q_{x},q_{y}\right)=\left(0,0\right)`
is the bottom left corner of the image. When users specify the distortion
model, represented by an instance of the class
:class:`distoptica.DistortionModel`, they also specify a coordinate
transformation, :math:`\left(T_{⌑;x}\left(u_{x},u_{y}\right),
T_{⌑;x}\left(u_{x},u_{y}\right)\right)`, which maps a given coordinate pair
:math:`\left(u_{x},u_{y}\right)` to a corresponding coordinate pair
:math:`\left(q_{x},q_{y}\right)`, and implicitly a right-inverse to said
coordinate transformation,
:math:`\left(T_{\square;x}\left(q_{x},q_{y}\right),
T_{\square;y}\left(q_{x},q_{y}\right)\right)`, that maps a coordinate pair
:math:`\left(q_{x},q_{y}\right)` to a corresponding coordinate pair
:math:`\left(u_{x},u_{y}\right)`, when such a relationship exists for
:math:`\left(q_{x},q_{y}\right)`.
The calculation of the image of the target fake CBED pattern involves
calculating various intermediate images which are subsequently combined to
yield the target image. These intermediate images share the same horizontal
and vertical dimensions in units of pixels, which may differ from those of
the image of the target fake CBED pattern. Let :math:`N_{\mathcal{I};x}` and
:math:`N_{\mathcal{I};y}` be the number of pixels in the image of the target
fake CBED pattern from left to right and top to bottom respectively, and let
:math:`N_{\mathring{\mathcal{I}};x}` and
:math:`N_{\mathring{\mathcal{I}};y}` be the number of pixels in each of the
aforementioned intermediate images from left to right and top to bottom
respectively. In :mod:`fakecbed`, we assume that
.. math ::
N_{\mathcal{I};x}=N_{\mathcal{I};y},
:label: N_I_x_eq_N_I_y__1
.. math ::
N_{\mathring{\mathcal{I}};x}\ge N_{\mathcal{I};x},
:label: N_ring_I_x_ge_N_I_x__1
and
.. math ::
N_{\mathring{\mathcal{I}};y}\ge N_{\mathcal{I};y}.
:label: N_ring_I_y_ge_N_I_y__1
The integer :math:`N_{\mathcal{I};x}` is specified by the parameter
``num_pixels_across_pattern``. The integers
:math:`N_{\mathring{\mathcal{I}};x}` and
:math:`N_{\mathring{\mathcal{I}};y}` are specified indirectly by the
parameter ``distortion_model``. The parameter ``distortion_model`` specifies
the distortion model, which as mentioned above is represented by an instance
of the class :class:`distoptica.DistortionModel`. One of the parameters of
said distortion model is the integer pair
``sampling_grid_dims_in_pixels``. In the current context,
``sampling_grid_dims_in_pixels[0]`` and ``sampling_grid_dims_in_pixels[1]``
are equal to :math:`N_{\mathring{\mathcal{I}};x}` and
:math:`N_{\mathring{\mathcal{I}};y}` respectively.
As mentioned above, a set of intensity patterns need to be specified in
order to create the target fake CBED pattern. The first of these is the
intensity pattern of an undistorted thermal diffuse scattering (TDS) model,
:math:`\mathcal{I}_{\text{TDS}}\left(u_{x},u_{y}\right)`, which is specified
by the parameter ``undistorted_tds_model``. The second of these intensity
patterns is that of the undistorted outer illumination shape,
:math:`\mathcal{I}_{\text{OI}}\left(u_{x},u_{y}\right)`, which is specified
by the parameter ``undistorted_outer_illumination_shape``.
:math:`\mathcal{I}_{\text{OI}}\left(u_{x},u_{y}\right)` is defined such that
for every coordinate pair :math:`\left(u_{x},u_{y}\right)`, if
:math:`\mathcal{I}_{\text{OI}}\left(u_{x},u_{y}\right)=0` then the value of
the UNNBU fake CBED pattern is also equal to 0. A separate subset of the
intensity patterns that need to be specified are :math:`N_{\text{D}}`
intensity patterns of undistorted nonuniform circles and/or ellipses,
:math:`\left\{ \mathcal{I}_{k;\text{D}}\left(u_{x},u_{y}\right)\right\}
_{k=0}^{N_{\text{D}}-1}`, which is specified by the parameter
``undistorted_disks``. Each intensity pattern
:math:`\mathcal{I}_{k;\text{D}}\left(u_{x},u_{y}\right)` is suppose to
depict one of the CBED disks in the fake CBED pattern in the absence of the
intensity background. Moreover, each intensity pattern
:math:`\mathcal{I}_{k;\text{D}}\left(u_{x},u_{y}\right)` has a corresponding
supporting intensity pattern
:math:`\mathcal{I}_{k;\text{DS}}\left(u_{x},u_{y}\right)`, which is defined
such that for every coordinate pair :math:`\left(u_{x},u_{y}\right)`, if
:math:`\mathcal{I}_{k;\text{DS}}\left(u_{x},u_{y}\right)=0` then
:math:`\mathcal{I}_{k;\text{D}}\left(u_{x},u_{y}\right)=0`. The remaining
intensity patterns that need to be specified, of which there are
:math:`N_{\text{M}}`, are intensity patterns of undistorted miscellaneous
shapes, :math:`\left\{
\mathcal{I}_{k;\text{M}}\left(u_{x},u_{y}\right)\right\}
_{k=0}^{N_{\text{M}}-1}`, which is specified by the parameter
``undistorted_misc_shapes``. These patterns, along with that of the TDS
model, contribute to the intensity background.
To add blur effects, users can specify a nonzero standard deviation
:math:`\sigma_{\text{blur}}` of the Gaussian filter used to yield such blur
effects on the target fake CBED pattern. The value of
:math:`\sigma_{\text{blur}}` is specified by the parameter
``gaussian_filter_std_dev``.
To add shot noise effects to the image of the target fake CBED pattern, the
parameter ``apply_shot_noise`` needs to be set to ``True``.
For some pixelated electron detectors, the pixels of a number
:math:`N_{\text{DPW}}` of contiguous rows and an equal number of contiguous
columns will not measure or readout incident electron counts. Instead, the
final intensity values measured are inpainted according to the final
intensity values of the other pixels in the detector. The intersection of
the aforementioned contiguous block of rows and the aforementioned
contiguous block of columns is located within one pixel of the center of the
detector. The integer :math:`N_{\text{DPW}}`, which we call the detector
partition width in units of pixels, is specified by the parameter
``detector_partition_width_in_pixels``.
Cold pixels, which are individual zero-valued pixels in the image of the
target fake CBED pattern, are specified by the parameter
``cold_pixels``. Let :math:`N_{\text{CP}}` be the number of cold pixels in
the image of the target fake CBED pattern. Furthermore, let :math:`\left\{
n_{k;\text{CP}}\right\} _{k=0}^{N_{\text{CP}}-1}` and :math:`\left\{
m_{k;\text{CP}}\right\} _{k=0}^{N_{\text{CP}}-1}` be integer sequences
respectively, where :math:`n_{k;\text{CP}}` and :math:`m_{k;\text{CP}}` are
the row and column indices respectively of the :math:`k^{\text{th}}` cold
pixel. For every nonnegative integer ``k`` less than :math:`N_{\text{CP}}`,
``cold_pixels[k][0]`` and ````cold_pixels[k][1]`` are
:math:`n_{k;\text{CP}}` and :math:`m_{k;\text{CP}}` respectively, with the
integer :math:`k` being equal to the value of ``k``.
Below we describe in more detail how various attributes of the current class
are effectively calculated. Before doing so, we need to introduce a few more
quantities:
.. math ::
j\in\left\{ j^{\prime}\right\}_{j^{\prime}=0}^{
N_{\mathcal{\mathring{I}};x}-1},
:label: j_range__1
.. math ::
i\in\left\{ i^{\prime}\right\} _{i^{\prime}=0}^{
N_{\mathcal{\mathring{I}};y}-1}
:label: i_range__1
.. math ::
q_{\mathcal{\mathring{I}};x;j}=\left(j+\frac{1}{2}\right)
\Delta q_{\mathcal{\mathring{I}};x},
:label: q_I_circ_x_j__1
.. math ::
q_{\mathcal{\mathring{I}};y;i}=1-\left(i+\frac{1}{2}\right)
\Delta q_{\mathcal{\mathring{I}};y},
:label: q_I_circ_y_i__1
.. math ::
\Delta q_{\mathcal{\mathring{I}};x}=
\frac{1}{N_{\mathcal{\mathring{I}};x}},
:label: Delta_q_I_circ_x__1
.. math ::
\Delta q_{\mathcal{\mathring{I}};y}=
\frac{1}{N_{\mathcal{\mathring{I}};y}}.
:label: Delta_q_I_circ_y__1
.. math ::
m\in\left\{ m^{\prime}\right\} _{m^{\prime}=0}^{N_{\mathcal{I};x}-1},
:label: m_range__1
.. math ::
n\in\left\{ n^{\prime}\right\} _{n^{\prime}=0}^{N_{\mathcal{I};y}-1},
:label: n_range__1
and
.. math ::
\mathbf{J}_{\square}\left(q_{x},q_{y}\right)=
\begin{pmatrix}\frac{\partial T_{\square;x}}{\partial q_{x}}
& \frac{\partial T_{\square;x}}{\partial q_{y}}\\
\frac{\partial T_{\square;y}}{\partial q_{x}}
& \frac{\partial T_{\square;y}}{\partial q_{y}}
\end{pmatrix},
:label: J_sq__1
where the derivatives in Eq. :eq:`J_sq__1` are calculated numerically using
the second-order accurate central differences method. The aforementioned
attributes of the current class are effectively calculated by executing the
following steps:
1. Calculate
.. math ::
\mathring{\mathcal{I}}_{\text{OI};⌑;i,j}\leftarrow
\mathcal{I}_{\text{OI}}\left(
T_{\square;x}\left(q_{\mathring{\mathcal{I}};x;j},
q_{\mathring{\mathcal{I}};y;i}\right),
T_{\square;y}\left(q_{\mathring{\mathcal{I}};x;j},
q_{\mathring{\mathcal{I}};y;i}\right)\right),
:label: HD_I_OI__1
.. math ::
\mathring{\mathcal{I}}_{\text{OI};⌑;i,j}\leftarrow\begin{cases}
\text{True}, & \text{if }\mathring{\mathcal{I}}_{\text{OI};⌑;i,j}
\neq0,\\
\text{False}, & \text{otherwise},
\end{cases}
:label: HD_I_OI__2
and then apply max pooling to
:math:`\mathring{\mathcal{I}}_{\text{OI};⌑;i,j}` with a kernel of dimensions
:math:`\left(N_{\mathring{\mathcal{I}};y}/N_{\mathcal{I};y},
N_{\mathring{\mathcal{I}};x}/N_{\mathcal{I};x}\right)`
and store the result in :math:`\mathcal{I}_{\text{OI};⌑;n,m}`.
2. Calculate
.. math ::
\mathcal{I}_{\text{DOM};⌑;n,m}\leftarrow0.
:label: LD_I_DOM__1
3. Calculate
.. math ::
\mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}&
\leftarrow\mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}\\&
\quad\quad\mathop{+}\mathcal{I}_{k;\text{D}}\left(
T_{\square;x}\left(q_{\mathring{\mathcal{I}};x;j},
q_{\mathring{\mathcal{I}};y;i}\right),T_{\square;y}\left(
q_{\mathring{\mathcal{I}};x;j},
q_{\mathring{\mathcal{I}};y;i}\right)\right),
:label: HD_I_CBED__1
4. For :math:`0\le k<N_{\text{D}}`, calculate
.. math ::
\mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}&
\leftarrow\mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}\\&
\quad\quad\mathop{+}\mathcal{I}_{k;\text{D}}\left(T_{\square;x}\left(
q_{\mathring{\mathcal{I}};x;j},q_{\mathring{\mathcal{I}};y;i}\right),
T_{\square;y}\left(q_{\mathring{\mathcal{I}};x;j},
q_{\mathring{\mathcal{I}};y;i}\right)\right),
:label: HD_I_CBED__2
.. math ::
\mathring{\mathcal{I}}_{k;\text{DS};⌑;i,j}\leftarrow
\mathcal{I}_{k;\text{DS}}\left(T_{\square;x}\left(
q_{\mathring{\mathcal{I}};x;j},q_{\mathring{\mathcal{I}};y;i}\right),
T_{\square;y}\left(q_{\mathring{\mathcal{I}};x;j},
q_{\mathring{\mathcal{I}};y;i}\right)\right),
:label: HD_I_k_DS__1
.. math ::
\mathring{\mathcal{I}}_{k;\text{DS};⌑;i,j}\leftarrow\begin{cases}
\text{True}, & \text{if }\mathring{\mathcal{I}}_{k;\text{DS};⌑;i,j}
\neq0,\\
\text{False}, & \text{otherwise},
\end{cases}
:label: HD_I_k_DS__2
then apply max pooling to :math:`\mathring{\mathcal{I}}_{k;\text{DS};⌑;i,j}`
with a kernel of dimensions
:math:`\left(N_{\mathring{\mathcal{I}};y}/N_{\mathcal{I};y},
N_{\mathring{\mathcal{I}};x}/N_{\mathcal{I};x}\right)` and store the result
in :math:`\mathcal{I}_{k;\text{DS};⌑;n,m}`, and calculate
.. math ::
\mathcal{I}_{\text{DOM};⌑;n,m}\leftarrow
\mathcal{I}_{\text{DOM};⌑;n,m}+\mathcal{I}_{k;\text{DS};⌑;n,m}.
:label: LD_I_DOM__2
5. Calculate
.. math ::
\mathcal{I}_{\text{DOM};⌑;n,m}\leftarrow
\mathcal{I}_{\text{OI};⌑;n,m}\mathcal{I}_{\text{DOM};⌑;n,m}.
:label: LD_I_DOM__3
6. For :math:`0\le k<N_{\text{M}}`, calculate
.. math ::
\mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}&
\leftarrow\mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}\\&
\quad\quad\mathop{+}\mathcal{I}_{k;\text{M}}\left(T_{\square;x}\left(
q_{\mathring{\mathcal{I}};x;j},q_{\mathring{\mathcal{I}};y;i}\right),
T_{\square;y}\left(q_{\mathring{\mathcal{I}};x;j},
q_{\mathring{\mathcal{I}};y;i}\right)\right).
:label: HD_I_CBED__3
7. Calculate
.. math ::
\mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}\leftarrow
\text{det}\left(\mathbf{J}_{\square}\left(
q_{\mathring{\mathcal{I}};x;j},
q_{\mathring{\mathcal{I}};y;i}\right)\right)
\mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}.
:label: HD_I_CBED__4
8. Apply average pooling to
:math:`\mathring{\mathcal{I}}_{\text{CBED};⌑;i,j}` with a kernel of
dimensions :math:`\left(N_{\mathring{\mathcal{I}};y}/N_{\mathcal{I};y},
N_{\mathring{\mathcal{I}};x}/N_{\mathcal{I};x}\right)`, and store the result
in :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
9. Apply a Gaussian filter to :math:`\mathcal{I}_{\text{CBED};⌑;n,m}` that
is identical in outcome to that implemented by the function
:func:`scipy.ndimage.gaussian_filter`, with ``sigma`` set to
``gaussian_filter_std_dev`` and ``truncate`` set to ``4``, and store the
result in :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
10. Calculate
.. math ::
k_{\text{I};1}\leftarrow
\left\lfloor \frac{N_{\mathcal{I};x}-1}{2}\right\rfloor
-\left\lfloor \frac{N_{\text{DPW}}}{2}\right\rfloor ,
:label: k_I_1__1
and
.. math ::
k_{\text{I};2}\leftarrow k_{\text{I};1}+N_{\text{DPW}}-1.
:label: k_I_2__1
11. If ``apply_shot_noise`` is set to ``True``, then apply shot/Poisson
noise to :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`, and store the result in
:math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
12. If :math:`N_{\text{DPW}}>0`, then inpaint the pixels in the rows indexed
from :math:`k_{\text{I};1}` to :math:`k_{\text{I};2}` and the columns
indexed from :math:`k_{\text{I};1}` to :math:`k_{\text{I};2}` of the image
:math:`\mathcal{I}_{\text{CBED};⌑;n,m}` using the function
:func:`skimage.restoration.inpaint_biharmonic`, and store the result in
:math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
13. Calculate
.. math ::
\mathcal{I}_{\text{CBED};⌑;n,m}\leftarrow
\mathcal{I}_{\text{OI};⌑;n,m}\mathcal{I}_{\text{CBED};⌑;n,m}.
:label: LD_I_CBED__1
14. Update pixels of :math:`\mathcal{I}_{\text{CBED};⌑;n,m}` at pixel
locations specified by ``cold_pixels`` to the value of zero.
15. Apply min-max normalization of :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`,
and store result in :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
16. Calculate
.. math ::
\mathcal{I}_{\text{CS};⌑;n,m}\leftarrow1-\mathcal{I}_{\text{OI};⌑;n,m}.
:label: LD_I_CS__1
17. Convolve a :math:`3 \times 3` filter of ones over a symmetrically unity-padded
:math:`\mathcal{I}_{\text{CS};⌑;n,m}` to yield an output matrix with the
same dimensions of :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`, and store said
output matrix in :math:`\mathcal{I}_{\text{CS};⌑;n,m}`.
18. For :math:`0\le k<N_{\text{D}}`, calculate
.. math ::
\mathcal{I}_{k;\text{DCM};⌑;n,m}\leftarrow
\mathcal{I}_{\text{CS};⌑;n,m}\mathcal{I}_{k;\text{DS};⌑;n,m},
:label: LD_I_DCM__1
.. math ::
\Omega_{k;\text{DCR};⌑}\leftarrow\begin{cases}
\text{True}, & \text{if }\sum_{n,m}\mathcal{I}_{k;\text{DCM};⌑;n,m}
\neq0,\\
\text{False}, & \text{otherwise},
\end{cases}
:label: Omega_k_DCR__1
and
.. math ::
\Omega_{k;\text{DAR};⌑}\leftarrow\begin{cases}
\text{True}, & \text{if }\sum_{n,m}\mathcal{I}_{k;\text{DS};⌑;n,m}=0,\\
\text{False}, & \text{otherwise}.
\end{cases}
:label: Omega_k_DAR__1
We refer to :math:`\mathcal{I}_{\text{CBED};⌑;n,m}` as the image of the
target fake CBED pattern, :math:`\mathcal{I}_{\text{OI};⌑;n,m}` as the image
of the illumination support, :math:`\mathcal{I}_{\text{DOM};⌑;n,m}` as the
image of the disk overlap map, :math:`\mathcal{I}_{k;\text{DS};⌑;n,m}` as
the image of the support of the :math:`k^{\text{th}}` CBED disk,
:math:`\left\{ \Omega_{k;\text{DCR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}` as
the disk clipping registry, and :math:`\left\{
\Omega_{k;\text{DAR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}` as the disk absence
registry.
Parameters
----------
undistorted_tds_model : :class:`fakecbed.tds.Model` | `None`, optional
The intensity pattern of the undistorted TDS model,
:math:`\mathcal{I}_{\text{TDS}}\left(u_{x},u_{y}\right)`. If
``undistorted_tds_model`` is set to ``None``, then the parameter will be
reassigned to the value ``fakecbed.tds.Model()``.
undistorted_disks : `array_like` (:class:`fakecbed.shapes.NonuniformBoundedShape`, ndim=1), optional
The intensity patterns of the undistorted fake CBED disks,
:math:`\left\{ \mathcal{I}_{k;\text{D}}\left(u_{x},u_{y}\right)\right\}
_{k=0}^{N_{\text{D}}-1}`. For every nonnegative integer ``k`` less than
:math:`N_{\text{D}}`, ``undistorted_disks[k]`` is
:math:`\mathcal{I}_{k;\text{D}}\left(u_{x},u_{y}\right)`, with the
integer :math:`k` being equal to the value of ``k``
undistorted_misc_shapes : `array_like` (`any_shape`, ndim=1), optional
The intensity patterns of the undistorted miscellaneous shapes,
:math:`\left\{ \mathcal{I}_{k;\text{M}}\left(u_{x},u_{y}\right)\right\}
_{k=0}^{N_{\text{M}}-1}`. Note that `any_shape` means any public class
defined in the module :mod:`fakecbed.shapes` that is a subclass of
:class:`fakecbed.shapes.BaseShape`.
undistorted_outer_illumination_shape : :class:`fakecbed.shapes.Circle` | :class:`fakecbed.shapes.Ellipse` | :class:`fakecbed.shapes.GenericBlob` | `None`, optional
The intensity pattern of the undistorted outer illumination shape,
:math:`\mathcal{I}_{\text{OI}}\left(u_{x},u_{y}\right)`. If
``undistorted_outer_illumination_shape`` is set to ``None``, then
:math:`\mathcal{I}_{\text{OI}}\left(u_{x},u_{y}\right)` will equal unity
for all :math:`u_{x}` and :math:`u_{y}`.
gaussian_filter_std_dev : `float`, optional
The standard deviation :math:`\sigma_{\text{blur}}` of the Gaussian
filter used to yield such blur effects on the target fake CBED
pattern. Must be nonnegative.
num_pixels_across_pattern : `int`, optional
The number of pixels across the image of the fake CBED pattern,
:math:`N_{\mathcal{I};x}`. Must be positive.
distortion_model : :class:`distoptica.DistortionModel` | `None`, optional
The distortion model. If ``distortion_model`` is set to ``None``, then
the parameter will be reassigned to the value
``distoptica.DistortionModel(sampling_grid_dims_in_pixels=(N_x, N_x))``,
Where ``N_x`` is equal to ``num_pixels_across_pattern``.
apply_shot_noise : `bool`, optional
If ``apply_shot_noise`` is set to ``True``, then shot noise is applied
to the image of the fake CBED pattern. Otherwise, no shot noise is
applied.
detector_partition_width_in_pixels : `int`, optional
The detector partition width in units of pixels,
:math:`N_{\text{DPW}}`. Must be nonnegative.
cold_pixels : `array_like` (`int`, ndim=2), optional
The pixel coordinates of the cold pixels.
"""
ctor_param_names = ("undistorted_tds_model",
"undistorted_disks",
"undistorted_misc_shapes",
"undistorted_outer_illumination_shape",
"gaussian_filter_std_dev",
"num_pixels_across_pattern",
"distortion_model",
"apply_shot_noise",
"detector_partition_width_in_pixels",
"cold_pixels")
kwargs = {"namespace_as_dict": globals(),
"ctor_param_names": ctor_param_names}
_validation_and_conversion_funcs_ = \
fancytypes.return_validation_and_conversion_funcs(**kwargs)
_pre_serialization_funcs_ = \
fancytypes.return_pre_serialization_funcs(**kwargs)
_de_pre_serialization_funcs_ = \
fancytypes.return_de_pre_serialization_funcs(**kwargs)
del ctor_param_names, kwargs
def __init__(self,
undistorted_tds_model=\
_default_undistorted_tds_model,
undistorted_disks=\
_default_undistorted_disks,
undistorted_misc_shapes=\
_default_undistorted_misc_shapes,
undistorted_outer_illumination_shape=\
_default_undistorted_outer_illumination_shape,
gaussian_filter_std_dev=\
_default_gaussian_filter_std_dev,
num_pixels_across_pattern=\
_default_num_pixels_across_pattern,
distortion_model=\
_default_distortion_model,
apply_shot_noise=\
_default_apply_shot_noise,
detector_partition_width_in_pixels=\
_default_detector_partition_width_in_pixels,
cold_pixels=\
_default_cold_pixels,
skip_validation_and_conversion=\
_default_skip_validation_and_conversion):
ctor_params = {key: val
for key, val in locals().items()
if (key not in ("self", "__class__"))}
kwargs = ctor_params
kwargs["skip_cls_tests"] = True
fancytypes.PreSerializableAndUpdatable.__init__(self, **kwargs)
self.execute_post_core_attrs_update_actions()
return None
[docs] @classmethod
def get_validation_and_conversion_funcs(cls):
validation_and_conversion_funcs = \
cls._validation_and_conversion_funcs_.copy()
return validation_and_conversion_funcs
[docs] @classmethod
def get_pre_serialization_funcs(cls):
pre_serialization_funcs = \
cls._pre_serialization_funcs_.copy()
return pre_serialization_funcs
[docs] @classmethod
def get_de_pre_serialization_funcs(cls):
de_pre_serialization_funcs = \
cls._de_pre_serialization_funcs_.copy()
return de_pre_serialization_funcs
def execute_post_core_attrs_update_actions(self):
self_core_attrs = self.get_core_attrs(deep_copy=False)
for self_core_attr_name in self_core_attrs:
attr_name = "_"+self_core_attr_name
attr = self_core_attrs[self_core_attr_name]
setattr(self, attr_name, attr)
self._num_disks = len(self._undistorted_disks)
self._device = self._distortion_model.device
self._illumination_support = None
self._image = None
self._image_has_been_overridden = False
self._signal = None
self._disk_clipping_registry = None
self._disk_supports = None
self._disk_absence_registry = None
self._disk_overlap_map = None
return None
[docs] def update(self, new_core_attr_subset_candidate):
super().update(new_core_attr_subset_candidate)
self.execute_post_core_attrs_update_actions()
return None
@property
def num_disks(self):
r"""`int`: The total number of CBED disks defined, :math:`N_{\text{D}}`.
See the summary documentation of the class
:class:`fakecbed.discretized.CBEDPattern` for additional context.
Let ``core_attrs`` denote the attribute
:attr:`~fancytypes.Checkable.core_attrs`. ``num_disks`` is equal to
``len(core_attrs["undistorted_disks"])``.
Note that ``num_disks`` should be considered **read-only**.
"""
result = self._num_disks
return result
@property
def device(self):
r"""`torch.device`: The device on which computationally intensive
PyTorch operations are performed and attributes of the type
:class:`torch.Tensor` are stored.
Note that ``device`` should be considered **read-only**.
"""
result = copy.deepcopy(self._device)
return result
[docs] def override_image_then_reapply_mask(
self,
overriding_image,
skip_validation_and_conversion=\
_default_skip_validation_and_conversion):
r"""Override the target fake CBED pattern image and reapply masking.
See the summary documentation of the class
:class:`fakecbed.discretized.CBEDPattern` for additional context.
Let ``image``, ``illumination_support``, and ``core_attrs`` denote the
attributes :attr:`fakecbed.discretized.CBEDPattern.image`,
:attr:`fakecbed.discretized.CBEDPattern.illumination_support`, and
:attr:`~fancytypes.Checkable.core_attrs`. ``overriding_image`` is the
overriding image.
Upon calling the method ``override_image_then_reapply_mask``, the
attribute ``image`` is updated effectively by:
.. code-block:: python
coords_of_cold_pixels = core_attrs["cold_pixels"]
image = (overriding_image * illumination_support).clip(min=0)
for coords_of_cold_pixel in coords_of_cold_pixels:
image[coords_of_cold_pixel] = 0
and then finally min-max normalization is applied to ``image``.
Parameters
----------
overriding_image : `array_like` (`float`, shape=image.shape)
The overriding image.
skip_validation_and_conversion : `bool`, optional
If ``skip_validation_and_conversion`` is set to ``False``, then
validations and conversions are performed on the above parameters.
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
no validations and conversions are performed on the above
parameters. This option is desired primarily when the user wants to
avoid potentially expensive validation and/or conversion operations.
"""
params = locals()
func_alias = _check_and_convert_skip_validation_and_conversion
skip_validation_and_conversion = func_alias(params)
if (skip_validation_and_conversion == False):
params = {"overriding_image": \
overriding_image,
"num_pixels_across_pattern": \
self._num_pixels_across_pattern,
"device": \
self._device}
overriding_image = _check_and_convert_overriding_image(params)
self._override_image_then_reapply_mask(overriding_image)
return None
def _override_image_then_reapply_mask(self, overriding_image):
if self._illumination_support is None:
u_x, u_y = self._calc_u_x_and_u_y()
method_name = "_calc_illumination_support"
method_alias = getattr(self, method_name)
self._illumination_support = method_alias(u_x, u_y)
illumination_support = self._illumination_support
coords_of_cold_pixels = self._cold_pixels
image = overriding_image*illumination_support
for coords_of_cold_pixel in coords_of_cold_pixels:
image[coords_of_cold_pixel] = 0
kwargs = {"input_matrix": image}
image = self._normalize_matrix(**kwargs)
self._image = image
self._image_has_been_overridden = True
if self._signal is not None:
self._signal.data[0] = image.numpy(force=True)
return None
def _calc_u_x_and_u_y(self):
distortion_model = self._distortion_model
method_alias = distortion_model.get_sampling_grid
sampling_grid = method_alias(deep_copy=False)
try:
method_alias = \
distortion_model.get_flow_field_of_coord_transform_right_inverse
flow_field_of_coord_transform_right_inverse = \
method_alias(deep_copy=False)
except:
err_msg = _cbed_pattern_err_msg_1
raise RuntimeError(err_msg)
u_x = sampling_grid[0] + flow_field_of_coord_transform_right_inverse[0]
u_y = sampling_grid[1] + flow_field_of_coord_transform_right_inverse[1]
return u_x, u_y
def _calc_illumination_support(self, u_x, u_y):
shape = self._undistorted_outer_illumination_shape
pooler_kernel_size = self._calc_pooler_kernel_size()
pooler = torch.nn.MaxPool2d(kernel_size=pooler_kernel_size)
illumination_support = (shape._eval(u_x, u_y) != 0)
illumination_support = torch.unsqueeze(illumination_support, dim=0)
illumination_support = torch.unsqueeze(illumination_support, dim=0)
illumination_support = illumination_support.to(dtype=u_x.dtype)
illumination_support = pooler(illumination_support)[0, 0]
illumination_support = illumination_support.to(dtype=torch.bool)
return illumination_support
def _calc_pooler_kernel_size(self):
distortion_model = self._distortion_model
num_pixels_across_pattern = self._num_pixels_across_pattern
distortion_model_core_attrs = \
distortion_model.get_core_attrs(deep_copy=False)
sampling_grid_dims_in_pixels = \
distortion_model_core_attrs["sampling_grid_dims_in_pixels"]
pooler_kernel_size = (sampling_grid_dims_in_pixels[1]
// num_pixels_across_pattern,
sampling_grid_dims_in_pixels[0]
// num_pixels_across_pattern)
return pooler_kernel_size
def _normalize_matrix(self, input_matrix):
if input_matrix.max()-input_matrix.min() > 0:
normalization_weight = 1 / (input_matrix.max()-input_matrix.min())
normalization_bias = -normalization_weight*input_matrix.min()
output_matrix = (input_matrix*normalization_weight
+ normalization_bias).clip(min=0, max=1)
else:
output_matrix = torch.zeros_like(input_matrix)
return output_matrix
[docs] def get_signal(self, deep_copy=_default_deep_copy):
r"""Return the hyperspy signal representation of the fake CBED pattern.
Parameters
----------
deep_copy : `bool`, optional
Let ``signal`` denote the attribute
:attr:`fakecbed.discretized.CBEDPattern.signal`.
If ``deep_copy`` is set to ``True``, then a deep copy of ``signal``
is returned. Otherwise, a reference to ``signal`` is returned.
Returns
-------
signal : :class:`hyperspy._signals.signal2d.Signal2D`
The attribute :attr:`fakecbed.discretized.CBEDPattern.signal`.
"""
params = {"deep_copy": deep_copy}
deep_copy = _check_and_convert_deep_copy(params)
if self._signal is None:
u_x, u_y = self._calc_u_x_and_u_y()
method_name = "_calc_signal_and_cache_select_intermediates"
method_alias = getattr(self, method_name)
self._signal = method_alias(u_x, u_y)
signal = (copy.deepcopy(self._signal)
if (deep_copy == True)
else self._signal)
return signal
def _calc_signal_and_cache_select_intermediates(self, u_x, u_y):
method_name = "_calc_signal_metadata_and_cache_select_intermediates"
method_alias = getattr(self, method_name)
signal_metadata = method_alias(u_x, u_y)
if self._image is None:
method_name = "_calc_image_and_cache_select_intermediates"
method_alias = getattr(self, method_name)
self._image = method_alias(u_x, u_y)
image = self._image.numpy(force=True)
if self._disk_overlap_map is None:
method_name = ("_calc_disk_overlap_map"
"_and_cache_select_intermediates")
method_alias = getattr(self, method_name)
self._disk_overlap_map = method_alias(u_x, u_y)
disk_overlap_map = self._disk_overlap_map.numpy(force=True)
illumination_support = self._illumination_support.cpu().detach().clone()
illumination_support = illumination_support.numpy(force=True)
disk_supports = self._disk_supports.numpy(force=True)
num_disks = self._num_disks
signal_data_shape = (num_disks+3,) + image.shape
signal_data = np.zeros(signal_data_shape, dtype=image.dtype)
signal_data[0] = image
signal_data[1] = illumination_support
signal_data[2] = disk_overlap_map
signal_data[3:] = disk_supports
signal = hyperspy.signals.Signal2D(data=signal_data,
metadata=signal_metadata)
self._update_signal_axes(signal)
return signal
def _calc_signal_metadata_and_cache_select_intermediates(self, u_x, u_y):
distortion_model = self._distortion_model
title = ("Fake Undistorted CBED Intensity Pattern"
if distortion_model.is_trivial
else "Fake Distorted CBED Intensity Pattern")
pre_serialized_core_attrs = self.pre_serialize()
if self._disk_clipping_registry is None:
method_name = ("_calc_disk_clipping_registry"
"_and_cache_select_intermediates")
method_alias = getattr(self, method_name)
self._disk_clipping_registry = method_alias(u_x, u_y)
disk_clipping_registry = self._disk_clipping_registry.cpu()
disk_clipping_registry = disk_clipping_registry.detach().clone()
disk_clipping_registry = tuple(disk_clipping_registry.tolist())
if self._disk_absence_registry is None:
method_name = ("_calc_disk_absence_registry"
"_and_cache_select_intermediates")
method_alias = getattr(self, method_name)
self._disk_absence_registry = method_alias(u_x, u_y)
disk_absence_registry = self._disk_absence_registry.cpu()
disk_absence_registry = disk_absence_registry.detach().clone()
disk_absence_registry = tuple(disk_absence_registry.tolist())
fakecbed_metadata = {"num_disks": \
self._num_disks,
"disk_clipping_registry": \
disk_clipping_registry,
"disk_absence_registry": \
disk_absence_registry,
"pre_serialized_core_attrs": \
pre_serialized_core_attrs,
"cbed_pattern_image_has_been_overridden": \
self._image_has_been_overridden}
signal_metadata = {"General": {"title": title},
"Signal": {"pixel value units": "dimensionless"},
"FakeCBED": fakecbed_metadata}
return signal_metadata
def _calc_disk_clipping_registry_and_cache_select_intermediates(self,
u_x,
u_y):
undistorted_disks = self._undistorted_disks
num_disks = len(undistorted_disks)
if num_disks > 0:
if self._illumination_support is None:
method_name = "_calc_illumination_support"
method_alias = getattr(self, method_name)
self._illumination_support = method_alias(u_x, u_y)
illumination_support = self._illumination_support
if self._disk_supports is None:
method_name = ("_calc_disk_supports"
"_and_cache_select_intermediates")
method_alias = getattr(self, method_name)
self._disk_supports = method_alias(u_x, u_y)
disk_supports = self._disk_supports
clip_support = ~illumination_support
for _ in range(2):
clip_support = torch.unsqueeze(clip_support, dim=0)
clip_support = clip_support.to(dtype=torch.float)
conv_weights = torch.ones((1, 1, 5, 5),
device=illumination_support.device)
kwargs = {"input": clip_support,
"weight": conv_weights,
"padding": "same"}
clip_support = (torch.nn.functional.conv2d(**kwargs) != 0)
clip_support = clip_support.to(dtype=torch.bool)
clip_support[0, 0, :2, :] = True
clip_support[0, 0, -2:, :] = True
clip_support[0, 0, :, :2] = True
clip_support[0, 0, :, -2:] = True
clip_support = clip_support[0, 0]
disk_clipping_map = disk_supports*clip_support[None, :, :]
disk_clipping_registry = ((disk_clipping_map.sum(dim=(1, 2)) != 0)
+ (disk_supports.sum(dim=(1, 2)) == 0))
else:
disk_clipping_registry = torch.zeros((num_disks,),
device=u_x.device,
dtype=torch.bool)
return disk_clipping_registry
def _calc_disk_supports_and_cache_select_intermediates(self, u_x, u_y):
undistorted_disks = self._undistorted_disks
num_disks = len(undistorted_disks)
if num_disks > 0:
if self._illumination_support is None:
method_name = "_calc_illumination_support"
method_alias = getattr(self, method_name)
self._illumination_support = method_alias(u_x, u_y)
illumination_support = self._illumination_support
pooler_kernel_size = self._calc_pooler_kernel_size()
pooler = torch.nn.MaxPool2d(kernel_size=pooler_kernel_size)
disk_supports_shape = (num_disks,)+u_x.shape
disk_supports = torch.zeros(disk_supports_shape,
device=u_x.device)
for disk_idx, undistorted_disk in enumerate(undistorted_disks):
method_name = "_eval_without_intra_support_shapes"
method_alias = getattr(undistorted_disk, method_name)
disk_supports[disk_idx] = (method_alias(u_x, u_y) != 0)
disk_supports = torch.unsqueeze(disk_supports, dim=0)
disk_supports = disk_supports.to(dtype=u_x.dtype)
disk_supports = pooler(disk_supports)[0]
disk_supports = disk_supports.to(dtype=torch.bool)
disk_supports[:, :, :] *= illumination_support[None, :, :]
else:
num_pixels_across_pattern = self._num_pixels_across_pattern
disk_supports_shape = (num_disks,
num_pixels_across_pattern,
num_pixels_across_pattern)
disk_supports = torch.zeros(disk_supports_shape,
device=u_x.device,
dtype=torch.bool)
return disk_supports
def _calc_disk_absence_registry_and_cache_select_intermediates(self,
u_x,
u_y):
undistorted_disks = self._undistorted_disks
num_disks = len(undistorted_disks)
if num_disks > 0:
if self._disk_supports is None:
method_name = ("_calc_disk_supports"
"_and_cache_select_intermediates")
method_alias = getattr(self, method_name)
self._disk_supports = method_alias(u_x, u_y)
disk_supports = self._disk_supports
disk_absence_registry = (disk_supports.sum(dim=(1, 2)) == 0)
else:
disk_absence_registry = torch.zeros((num_disks,),
device=u_x.device,
dtype=torch.bool)
return disk_absence_registry
def _calc_image_and_cache_select_intermediates(self, u_x, u_y):
method_name = ("_calc_maskless_and_noiseless_image"
"_and_cache_select_intermediates")
method_alias = getattr(self, method_name)
maskless_and_noiseless_image = method_alias(u_x, u_y)
if self._illumination_support is None:
method_name = "_calc_illumination_support"
method_alias = getattr(self, method_name)
self._illumination_support = method_alias(u_x, u_y)
illumination_support = self._illumination_support
noiseless_image = maskless_and_noiseless_image*illumination_support
apply_shot_noise = self._apply_shot_noise
image = (torch.poisson(noiseless_image)
if (apply_shot_noise == True)
else noiseless_image)
coords_of_cold_pixels = self._cold_pixels
for coords_of_cold_pixel in coords_of_cold_pixels:
image[coords_of_cold_pixel] = 0
image = self._apply_detector_partition_inpainting(input_image=image)
image = self._normalize_matrix(input_matrix=image)
image = torch.clip(image, min=0)
return image
def _calc_maskless_and_noiseless_image_and_cache_select_intermediates(self,
u_x,
u_y):
jacobian_weights = self._calc_jacobian_weights(u_x, u_y)
bg = self._calc_bg(u_x, u_y, jacobian_weights)
if self._disk_supports is None:
method_name = ("_calc_disk_supports"
"_and_cache_select_intermediates")
method_alias = getattr(self, method_name)
self._disk_supports = method_alias(u_x, u_y)
disk_supports = self._disk_supports
intra_disk_shapes = self._calc_intra_disk_shapes(u_x,
u_y,
jacobian_weights)
gaussian_filter_std_dev = self._gaussian_filter_std_dev
maskless_and_noiseless_image = (bg
+ (disk_supports
* intra_disk_shapes).sum(dim=0))
kwargs = {"input_matrix": maskless_and_noiseless_image,
"truncate": 4}
maskless_and_noiseless_image = self._apply_2d_guassian_filter(**kwargs)
maskless_and_noiseless_image = torch.clip(maskless_and_noiseless_image,
min=0)
return maskless_and_noiseless_image
def _calc_jacobian_weights(self, u_x, u_y):
distortion_model = self._distortion_model
sampling_grid = distortion_model.get_sampling_grid(deep_copy=False)
spacing = (sampling_grid[1][:, 0], sampling_grid[0][0, :])
kwargs = {"input": u_x,
"spacing": spacing,
"dim": None,
"edge_order": 2}
d_u_x_over_d_q_y, d_u_x_over_d_q_x = torch.gradient(**kwargs)
kwargs["input"] = u_y
d_u_y_over_d_q_y, d_u_y_over_d_q_x = torch.gradient(**kwargs)
jacobian_weights = torch.abs(d_u_x_over_d_q_x*d_u_y_over_d_q_y
- d_u_x_over_d_q_y*d_u_y_over_d_q_x)
return jacobian_weights
def _calc_bg(self, u_x, u_y, jacobian_weights):
undistorted_tds_model = self._undistorted_tds_model
undistorted_misc_shapes = self._undistorted_misc_shapes
pooler_kernel_size = self._calc_pooler_kernel_size()
pooler = torch.nn.AvgPool2d(kernel_size=pooler_kernel_size)
bg = undistorted_tds_model._eval(u_x, u_y)
for undistorted_misc_shape in undistorted_misc_shapes:
bg[:, :] += undistorted_misc_shape._eval(u_x, u_y)[:, :]
bg[:, :] *= jacobian_weights[:, :]
bg = torch.unsqueeze(bg, dim=0)
bg = torch.unsqueeze(bg, dim=0)
bg = pooler(bg)[0, 0]
return bg
def _calc_intra_disk_shapes(self, u_x, u_y, jacobian_weights):
undistorted_disks = self._undistorted_disks
num_disks = len(undistorted_disks)
if num_disks > 0:
pooler_kernel_size = self._calc_pooler_kernel_size()
pooler = torch.nn.AvgPool2d(kernel_size=pooler_kernel_size)
intra_disk_shapes_shape = (num_disks,)+u_x.shape
intra_disk_shapes = torch.zeros(intra_disk_shapes_shape,
device=u_x.device)
for disk_idx, undistorted_disk in enumerate(undistorted_disks):
method_alias = undistorted_disk._eval_without_support
intra_disk_shapes[disk_idx] = method_alias(u_x, u_y)
intra_disk_shapes[:, :, :] *= jacobian_weights[None, :, :]
intra_disk_shapes = torch.unsqueeze(intra_disk_shapes, dim=0)
intra_disk_shapes = pooler(intra_disk_shapes)[0]
else:
num_pixels_across_pattern = self._num_pixels_across_pattern
intra_disk_shapes_shape = (num_disks,
num_pixels_across_pattern,
num_pixels_across_pattern)
intra_disk_shapes = torch.zeros(intra_disk_shapes_shape,
device=u_x.device)
return intra_disk_shapes
def _apply_2d_guassian_filter(self, input_matrix, truncate):
intermediate_tensor = input_matrix
for axis_idx in range(2):
kwargs = {"input_matrix": intermediate_tensor,
"truncate": truncate,
"axis_idx": axis_idx}
intermediate_tensor = self._apply_1d_guassian_filter(**kwargs)
output_matrix = intermediate_tensor
return output_matrix
def _apply_1d_guassian_filter(self, input_matrix, truncate, axis_idx):
intermediate_tensor = torch.unsqueeze(input_matrix, dim=0)
intermediate_tensor = torch.unsqueeze(intermediate_tensor, dim=0)
sigma = self._gaussian_filter_std_dev
if sigma > 0:
radius = int(truncate*sigma + 0.5)
coords = torch.arange(-radius, radius+1, device=input_matrix.device)
weights = torch.exp(-(coords/sigma)*(coords/sigma)/2)
weights /= torch.sum(weights)
weights = torch.unsqueeze(weights, dim=axis_idx)
weights = torch.unsqueeze(weights, dim=0)
weights = torch.unsqueeze(weights, dim=0)
kwargs = {"input": intermediate_tensor,
"weight": weights,
"padding": "same"}
output_matrix = torch.nn.functional.conv2d(**kwargs)[0, 0]
else:
output_matrix = input_matrix
return output_matrix
def _apply_detector_partition_inpainting(self, input_image):
N_DPW = self._detector_partition_width_in_pixels
k_I_1 = ((input_image.shape[1]-1)//2) - (N_DPW//2)
k_I_2 = k_I_1 + N_DPW - 1
inpainting_mask = np.zeros(input_image.shape, dtype=bool)
inpainting_mask[k_I_1:k_I_2+1, :] = True
inpainting_mask[:, k_I_1:k_I_2+1] = True
kwargs = {"image": input_image.numpy(force=True),
"mask": inpainting_mask}
output_image = skimage.restoration.inpaint_biharmonic(**kwargs)
output_image = torch.from_numpy(output_image)
output_image = output_image.to(device=input_image.device,
dtype=input_image.dtype)
return output_image
def _calc_disk_overlap_map_and_cache_select_intermediates(self, u_x, u_y):
undistorted_disks = self._undistorted_disks
num_disks = len(undistorted_disks)
if num_disks > 0:
if self._illumination_support is None:
method_name = "_calc_illumination_support"
method_alias = getattr(self, method_name)
self._illumination_support = method_alias(u_x, u_y)
illumination_support = self._illumination_support
if self._disk_supports is None:
method_name = ("_calc_disk_supports"
"_and_cache_select_intermediates")
method_alias = getattr(self, method_name)
self._disk_supports = method_alias(u_x, u_y)
disk_supports = self._disk_supports
disk_overlap_map = (illumination_support
* torch.sum(disk_supports, dim=0))
else:
num_pixels_across_pattern = self._num_pixels_across_pattern
disk_overlap_map_shape = 2*(num_pixels_across_pattern,)
disk_overlap_map = torch.zeros(disk_overlap_map_shape,
device=u_x.device,
dtype=torch.int)
return disk_overlap_map
def _update_signal_axes(self, signal):
num_pixels_across_pattern = signal.axes_manager.signal_shape[0]
sizes = (signal.axes_manager.navigation_shape
+ signal.axes_manager.signal_shape)
scales = (1,
1/num_pixels_across_pattern,
-1/num_pixels_across_pattern)
offsets = (0,
0.5/num_pixels_across_pattern,
1-(1-0.5)/num_pixels_across_pattern)
axes_labels = (r"fake CBED pattern attribute",
r"fractional horizontal coordinate",
r"fractional vertical coordinate")
units = ("dimensionless",)*3
num_axes = len(units)
for axis_idx in range(num_axes):
axis = hyperspy.axes.UniformDataAxis(size=sizes[axis_idx],
scale=scales[axis_idx],
offset=offsets[axis_idx],
units=units[axis_idx],
name=axes_labels[axis_idx])
signal.axes_manager[axis_idx].update_from(axis)
signal.axes_manager[axis_idx].name = axis.name
return None
@property
def signal(self):
r"""`hyperspy._signals.signal2d.Signal2D`: The hyperspy signal
representation of the fake CBED pattern.
See the summary documentation of the class
:class:`fakecbed.discretized.CBEDPattern` for additional context.
Let ``image``, ``illumination_support``, ``disk_overlap_map``,
``disk_supports``, ``disk_clipping_registry``,
``image_has_been_overridden``, ``num_disks``, ``disk_absence_registry``,
and ``core_attrs`` denote the attributes
:attr:`fakecbed.discretized.CBEDPattern.image`,
:attr:`fakecbed.discretized.CBEDPattern.illumination_support`,
:attr:`fakecbed.discretized.CBEDPattern.disk_overlap_map`,
:attr:`fakecbed.discretized.CBEDPattern.disk_supports`,
:attr:`fakecbed.discretized.CBEDPattern.disk_clipping_registry`,
:attr:`fakecbed.discretized.CBEDPattern.disk_absence_registry`,
:attr:`fakecbed.discretized.CBEDPattern.image_has_been_overridden`,
:attr:`fakecbed.discretized.CBEDPattern.num_disks`, and
:attr:`~fancytypes.Checkable.core_attrs`. Furthermore, let
``pre_serialize`` denote the method
:meth:`~fancytypes.PreSerializable.pre_serialize`.
The signal data, ``signal.data``, is a NumPy array having a shape equal
to ``(num_disks+3,)+2*(core_attrs["num_pixels_across_pattern"],)``. The
elements of ``signal.data`` are set effectively by:
.. code-block:: python
signal.data[0] = image.numpy(force=True)
signal.data[1] = illumination_support.numpy(force=True)
signal.data[2] = disk_overlap_map.numpy(force=True)
signal.data[3:] = disk_supports.numpy(force=True)
The signal metadata, ``signal.metadata``, stores serializable forms of
several instance attributes, in addition to other items of
metadata. ``signal.metadata.as_dictionary()`` yields a dictionary
``signal_metadata`` that is calculated effectively by:
.. code-block:: python
distortion_model = core_attrs["distortion_model"]
title = ("Fake Undistorted CBED Intensity Pattern"
if distortion_model.is_trivial
else "Fake Distorted CBED Intensity Pattern")
pre_serialized_core_attrs = pre_serialize()
fakecbed_metadata = {"num_disks": \
num_disks,
"disk_clipping_registry": \
disk_clipping_registry.numpy(force=True),
"disk_absence_registry": \
disk_absence_registry.numpy(force=True),
"pre_serialized_core_attrs": \
pre_serialized_core_attrs,
"cbed_pattern_image_has_been_overridden": \
image_has_been_overridden}
signal_metadata = {"General": {"title": title},
"Signal": {"pixel value units": "dimensionless"},
"FakeCBED": fakecbed_metadata}
Note that ``signal`` should be considered **read-only**.
"""
result = self.get_signal(deep_copy=True)
return result
[docs] def get_image(self, deep_copy=_default_deep_copy):
r"""Return the image of the target fake CBED pattern,
:math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
Parameters
----------
deep_copy : `bool`, optional
Let ``image`` denote the attribute
:attr:`fakecbed.discretized.CBEDPattern.image`.
If ``deep_copy`` is set to ``True``, then a deep copy of ``image``
is returned. Otherwise, a reference to ``image`` is returned.
Returns
-------
image : `torch.Tensor` (`float`, ndim=2)
The attribute :attr:`fakecbed.discretized.CBEDPattern.image`.
"""
params = {"deep_copy": deep_copy}
deep_copy = _check_and_convert_deep_copy(params)
if self._image is None:
u_x, u_y = self._calc_u_x_and_u_y()
method_name = "_calc_image_and_cache_select_intermediates"
method_alias = getattr(self, method_name)
self._image = method_alias(u_x, u_y)
image = (self._image.detach().clone()
if (deep_copy == True)
else self._image)
return image
@property
def image(self):
r"""`torch.Tensor`: The image of the target fake CBED pattern,
:math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
See the summary documentation of the class
:class:`fakecbed.discretized.CBEDPattern` for additional context, in
particular a description of the calculation of
:math:`\mathcal{I}_{\text{CBED};⌑;n,m}`.
Let ``core_attrs`` denote the attribute
:attr:`~fancytypes.Checkable.core_attrs`.
``image`` is a PyTorch tensor having a shape equal to
``2*(core_attrs["num_pixels_across_pattern"],)``.
For every pair of nonnegative integers ``(n, m)`` that does not raise an
``IndexError`` exception upon calling ``image[n, m]``, ``image[n, m]``
is equal to :math:`\mathcal{I}_{\text{CBED};⌑;n,m}`, with the integers
:math:`n` and :math:`m` being equal to the values of ``n`` and ``m``
respectively.
Note that ``image`` should be considered **read-only**.
"""
result = self.get_image(deep_copy=True)
return result
[docs] def get_illumination_support(self, deep_copy=_default_deep_copy):
r"""Return the image of the illumination support,
:math:`\mathcal{I}_{\text{OI};⌑;n,m}`.
Parameters
----------
deep_copy : `bool`, optional
Let ``illumination_support`` denote the attribute
:attr:`fakecbed.discretized.CBEDPattern.illumination_support`.
If ``deep_copy`` is set to ``True``, then a deep copy of
``illumination_support`` is returned. Otherwise, a reference to
``illumination_support`` is returned.
Returns
-------
illumination_support : `torch.Tensor` (`bool`, ndim=2)
The attribute
:attr:`fakecbed.discretized.CBEDPattern.illumination_support`.
"""
params = {"deep_copy": deep_copy}
deep_copy = _check_and_convert_deep_copy(params)
if self._illumination_support is None:
u_x, u_y = self._calc_u_x_and_u_y()
method_name = "_calc_illumination_support"
method_alias = getattr(self, method_name)
self._illumination_support = method_alias(u_x, u_y)
illumination_support = (self._illumination_support.detach().clone()
if (deep_copy == True)
else self._illumination_support)
return illumination_support
@property
def illumination_support(self):
r"""`torch.Tensor`: The image of the illumination support,
:math:`\mathcal{I}_{\text{OI};⌑;n,m}`.
See the summary documentation of the class
:class:`fakecbed.discretized.CBEDPattern` for additional context, in
particular a description of the calculation of
:math:`\mathcal{I}_{\text{OI};⌑;n,m}`.
Note that :math:`\mathcal{I}_{\text{CBED};⌑;n,m}` is the image of the
target fake CBED pattern, which is stored in the attribute
:attr:`fakecbed.discretized.CBEDPattern.image`
Let ``core_attrs`` denote the attribute
:attr:`~fancytypes.Checkable.core_attrs`.
``illumination_support`` is a PyTorch tensor having a shape equal to
``2*(core_attrs["num_pixels_across_pattern"],)``.
For every pair of nonnegative integers ``(n, m)`` that does not raise an
``IndexError`` exception upon calling ``illumination_support[n, m]``,
``illumination_support[n, m]`` is equal to
:math:`\mathcal{I}_{\text{OI};⌑;n,m}`, with the integers :math:`n` and
:math:`m` being equal to the values of ``n`` and ``m``
respectively. Furthermore, for each such pair of integers ``(n, m)``, if
``illumination_support[n, m]`` equals zero, then the corresponding pixel
of the image of the target fake CBED pattern is also zero.
Note that ``illumination_support`` should be considered **read-only**.
"""
result = self.get_illumination_support(deep_copy=True)
return result
[docs] def get_disk_supports(self, deep_copy=_default_deep_copy):
r"""Return the image stack of the disk supports,
:math:`\left\{\mathcal{I}_{k;
\text{DS};⌑;n,m}\right\}_{k=0}^{N_{\text{D}}-1}`.
Parameters
----------
deep_copy : `bool`, optional
Let ``disk_supports`` denote the attribute
:attr:`fakecbed.discretized.CBEDPattern.disk_supports`.
If ``deep_copy`` is set to ``True``, then a deep copy of
``disk_supports`` is returned. Otherwise, a reference to
``disk_supports`` is returned.
Returns
-------
disk_supports : `torch.Tensor` (`bool`, ndim=3)
The attribute
:attr:`fakecbed.discretized.CBEDPattern.disk_supports`.
"""
params = {"deep_copy": deep_copy}
deep_copy = _check_and_convert_deep_copy(params)
if self._disk_supports is None:
u_x, u_y = self._calc_u_x_and_u_y()
method_name = "_calc_disk_supports_and_cache_select_intermediates"
method_alias = getattr(self, method_name)
self._disk_supports = method_alias(u_x, u_y)
disk_supports = (self._disk_supports.detach().clone()
if (deep_copy == True)
else self._disk_supports)
return disk_supports
@property
def disk_supports(self):
r"""`torch.Tensor`: The image stack of the disk supports,
:math:`\left\{\mathcal{I}_{k;
\text{DS};⌑;n,m}\right\}_{k=0}^{N_{\text{D}}-1}`.
See the summary documentation of the class
:class:`fakecbed.discretized.CBEDPattern` for additional context, in
particular a description of the calculation of
:math:`\left\{\mathcal{I}_{k;
\text{DS};⌑;n,m}\right\}_{k=0}^{N_{\text{D}}-1}`.
Let ``core_attrs`` and ``num_disks`` denote the attributes
:attr:`~fancytypes.Checkable.core_attrs` and
:attr:`fakecbed.discretized.CBEDPattern.num_disks` respectively.
``disk_supports`` is a PyTorch tensor having a shape equal to
``(num_disks,) + 2*(core_attrs["num_pixels_across_pattern"],)``.
For every pair of nonnegative integers ``(k, n, m)`` that does not raise
an ``IndexError`` exception upon calling ``disk_supports[k, n, m]``,
``disk_supports[k, n, m]`` is equal to
:math:`\mathcal{I}_{k;\text{DS};⌑;n,m}`, with the integers :math:`k`,
:math:`n`, and :math:`m` being equal to the values of ``k``, ``n``, and
``m`` respectively. Furthermore, for each such triplet of integers ``(k,
n, m)``, if ``disk_supports[k, n, m]`` equals zero, then the
:math:`k^{\text{th}}` distorted CBED disk is not supported at the pixel
of the image of the target fake CBED pattern specified by ``(n, m)``.
Note that ``disk_supports`` should be considered **read-only**.
"""
result = self.get_disk_supports(deep_copy=True)
return result
[docs] def get_disk_overlap_map(self, deep_copy=_default_deep_copy):
r"""Return the image of the disk overlap map,
:math:`\mathcal{I}_{\text{DOM};⌑;n,m}`.
Parameters
----------
deep_copy : `bool`, optional
Let ``disk_overlap_map`` denote the attribute
:attr:`fakecbed.discretized.CBEDPattern.disk_overlap_map`.
If ``deep_copy`` is set to ``True``, then a deep copy of
``disk_overlap_map`` is returned. Otherwise, a reference to
``disk_overlap_map`` is returned.
Returns
-------
disk_overlap_map : `torch.Tensor` (`int`, ndim=2)
The attribute
:attr:`fakecbed.discretized.CBEDPattern.disk_overlap_map`.
"""
params = {"deep_copy": deep_copy}
deep_copy = _check_and_convert_deep_copy(params)
if self._disk_overlap_map is None:
u_x, u_y = self._calc_u_x_and_u_y()
method_name = ("_calc_disk_overlap_map"
"_and_cache_select_intermediates")
method_alias = getattr(self, method_name)
self._disk_overlap_map = method_alias(u_x, u_y)
disk_overlap_map = (self._disk_overlap_map.detach().clone()
if (deep_copy == True)
else self._disk_overlap_map)
return disk_overlap_map
@property
def disk_overlap_map(self):
r"""`torch.Tensor`: The image of the disk overlap map,
:math:`\mathcal{I}_{\text{DOM};⌑;n,m}`.
See the summary documentation of the class
:class:`fakecbed.discretized.CBEDPattern` for additional context, in
particular a description of the calculation of
:math:`\mathcal{I}_{\text{DOM};⌑;n,m}`.
Note that :math:`\mathcal{I}_{\text{CBED};⌑;n,m}` is the image of the
target fake CBED pattern, which is stored in the attribute
:attr:`fakecbed.discretized.CBEDPattern.image`
Let ``core_attrs`` denote the attribute
:attr:`~fancytypes.Checkable.core_attrs`.
``disk_overlap_map`` is a PyTorch tensor having a shape equal to
``2*(core_attrs["num_pixels_across_pattern"],)``.
For every pair of nonnegative integers ``(n, m)`` that does not raise an
``IndexError`` exception upon calling ``disk_overlap_map[n, m]``,
``disk_overlap_map[n, m]`` is equal to
:math:`\mathcal{I}_{\text{DOM};⌑;n,m}`, with the integers :math:`n` and
:math:`m` being equal to the values of ``n`` and ``m`` respectively. In
other words, for each such pair of integers ``(n, m)``,
``disk_overlap_map[n, m]`` is equal to the number of imaged CBED disks
that overlap at the corresponding pixel of the image of the target fake
CBED pattern.
Note that ``disk_overlap_map`` should be considered **read-only**.
"""
result = self.get_disk_overlap_map(deep_copy=True)
return result
[docs] def get_disk_clipping_registry(self, deep_copy=_default_deep_copy):
r"""Return the disk clipping registry,
:math:`\left\{\Omega_{k;\text{DCR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
Parameters
----------
deep_copy : `bool`, optional
Let ``disk_clipping_registry`` denote the attribute
:attr:`fakecbed.discretized.CBEDPattern.disk_clipping_registry`.
If ``deep_copy`` is set to ``True``, then a deep copy of
``disk_clipping_registry`` is returned. Otherwise, a reference to
``disk_clipping_registry`` is returned.
Returns
-------
disk_clipping_registry : `torch.Tensor` (`bool`, ndim=1)
The attribute
:attr:`fakecbed.discretized.CBEDPattern.disk_clipping_registry`.
"""
params = {"deep_copy": deep_copy}
deep_copy = _check_and_convert_deep_copy(params)
if self._disk_clipping_registry is None:
u_x, u_y = self._calc_u_x_and_u_y()
method_name = ("_calc_disk_clipping_registry"
"_and_cache_select_intermediates")
method_alias = getattr(self, method_name)
self._disk_clipping_registry = method_alias(u_x, u_y)
disk_clipping_registry = (self._disk_clipping_registry.detach().clone()
if (deep_copy == True)
else self._disk_clipping_registry)
return disk_clipping_registry
@property
def disk_clipping_registry(self):
r"""`torch.Tensor`: The disk clipping registry,
:math:`\left\{\Omega_{k;\text{DCR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
See the summary documentation of the class
:class:`fakecbed.discretized.CBEDPattern` for additional context, in
particular a description of the calculation of
:math:`\left\{\Omega_{k;\text{DCR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
Note that :math:`N_{\text{D}}` is equal to the value of the attribute
:attr:`fakecbed.discretized.CBEDPattern.num_disks`,
:math:`\mathcal{I}_{\text{OI};⌑;n,m}` is the image of the illumination
support, and :math:`\mathcal{I}_{k;\text{DS};⌑;n,m}` is the image of the
support of the :math:`k^{\text{th}}` distorted CBED disk.
``disk_clipping_registry`` is a one-dimensional PyTorch tensor of length
equal to :math:`N_{\text{D}}`. For every nonnegative integer ``k`` less
than :math:`N_{\text{D}}`, ``disk_clipping_registry[k]`` is
:math:`\Omega_{k;\text{DCR};⌑}`, with the integer :math:`k` being equal
to the value of ``k``. If ``disk_clipping_registry[k]`` is equal to
``False``, then every nonzero pixel of the image of the support of the
:math:`k^{\text{th}}` distorted CBED disk is at least two pixels away
from (i.e. at least next-nearest neighbours to) every zero-valued pixel
of the image of the illumination support and is at least one pixel away
from every pixel bordering the image of the illumination support, and
that the image of the support of the :math:`k^{\text{th}}` distorted
CBED disk has at least one nonzero pixel. Otherwise, if
``disk_clipping_registry[k]`` is equal to ``True``, then the opposite of
the above scenario is true.
Note that ``disk_clipping_registry`` should be considered **read-only**.
"""
result = self.get_disk_clipping_registry(deep_copy=True)
return result
[docs] def get_disk_absence_registry(self, deep_copy=_default_deep_copy):
r"""Return the disk clipping registry,
:math:`\left\{\Omega_{k;\text{DAR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
Parameters
----------
deep_copy : `bool`, optional
Let ``disk_absence_registry`` denote the attribute
:attr:`fakecbed.discretized.CBEDPattern.disk_absence_registry`.
If ``deep_copy`` is set to ``True``, then a deep copy of
``disk_absence_registry`` is returned. Otherwise, a reference to
``disk_absence_registry`` is returned.
Returns
-------
disk_absence_registry : `torch.Tensor` (`bool`, ndim=1)
The attribute
:attr:`fakecbed.discretized.CBEDPattern.disk_absence_registry`.
"""
params = {"deep_copy": deep_copy}
deep_copy = _check_and_convert_deep_copy(params)
if self._disk_absence_registry is None:
u_x, u_y = self._calc_u_x_and_u_y()
method_name = ("_calc_disk_absence_registry"
"_and_cache_select_intermediates")
method_alias = getattr(self, method_name)
self._disk_absence_registry = method_alias(u_x, u_y)
disk_absence_registry = (self._disk_absence_registry.detach().clone()
if (deep_copy == True)
else self._disk_absence_registry)
return disk_absence_registry
@property
def disk_absence_registry(self):
r"""`torch.Tensor`: The disk clipping registry,
:math:`\left\{\Omega_{k;\text{DAR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
See the summary documentation of the class
:class:`fakecbed.discretized.CBEDPattern` for additional context, in
particular a description of the calculation of
:math:`\left\{\Omega_{k;\text{DCR};⌑}\right\}_{k=0}^{N_{\text{D}}-1}`.
Note that :math:`N_{\text{D}}` is equal to the value of the attribute
:attr:`fakecbed.discretized.CBEDPattern.num_disks`,
:math:`\mathcal{I}_{\text{OI};⌑;n,m}` is the image of the illumination
support, and :math:`\mathcal{I}_{k;\text{DS};⌑;n,m}` is the image of the
support of the :math:`k^{\text{th}}` distorted CBED disk.
``disk_absence_registry`` is a one-dimensional PyTorch tensor of length
equal to :math:`N_{\text{D}}`. For every nonnegative integer ``k`` less
than :math:`N_{\text{D}}`, ``disk_absence_registry[k]`` is
:math:`\Omega_{k;\text{DAR};⌑}`, with the integer :math:`k` being equal
to the value of ``k``. If ``disk_absence_registry[k]`` is equal to
``False``, then the image of the support of the :math:`k^{\text{th}}`
distorted CBED disk has at least one nonzero pixel. Otherwise, if
``disk_absence_registry[k]`` is equal to ``True``, then the opposite of
the above scenario is true.
Note that ``disk_absence_registry`` should be considered **read-only**.
"""
result = self.get_disk_absence_registry(deep_copy=True)
return result
@property
def image_has_been_overridden(self):
r"""`bool`: Equals ``True`` if the image of the target fake CBED pattern
has been overridden.
Let ``override_image_then_reapply_mask``, and ``update`` denote the
methods
:meth:`fakecbed.discretized.CBEDPattern.override_image_then_reapply_mask`,
and :meth:`~fancytypes.Updatable.update`
respectively. ``image_has_been_overridden`` equals ``True`` if the
method ``override_image_then_reapply_mask`` has been called without
raising an exception after either instance update via the method
``update``, or instance construction. Otherwise,
``image_has_been_overridden`` equals ``False``.
Note that ``image_has_been_overridden`` should be considered
**read-only**.
"""
result = self._image_has_been_overridden
return result
###########################
## Define error messages ##
###########################
_check_and_convert_undistorted_disks_err_msg_1 = \
("The object ``undistorted_disks`` must be a sequence of "
"`fakecbed.shapes.NonuniformBoundedShape` objects, where for each element "
"``elem`` in ``undistorted_disks``, "
"``isinstance(elem.core_attrs['support'], "
"(fakecbed.shapes.Circle, fakecbed.shapes.Ellipse))`` evaluates to "
"``True``.")
_check_and_convert_undistorted_misc_shapes_err_msg_1 = \
("The object ``undistorted_misc_shapes`` must be a sequence of objects of "
"any of the following types: ("
"`fakecbed.shapes.Circle`, "
"`fakecbed.shapes.Ellipse`, "
"`fakecbed.shapes.Peak`, "
"`fakecbed.shapes.Band`, "
"`fakecbed.shapes.PlaneWave`, "
"`fakecbed.shapes.Arc`, "
"`fakecbed.shapes.GenericBlob`, "
"`fakecbed.shapes.Orbital`, "
"`fakecbed.shapes.Lune`, "
"`fakecbed.shapes.NonuniformBoundedShape`).")
_check_and_convert_distortion_model_err_msg_1 = \
("The dimensions, in units of pixels, of the distortion model sampling "
"grid, specified by the object ``distortion_model``, must be divisible "
"by the object ``num_pixels_across_pattern``.")
_check_and_convert_cold_pixels_err_msg_1 = \
("The object ``cold_pixels`` must be a sequence of integer pairs, where "
"each integer pair specifies valid pixel coordinates (i.e. row and column "
"indices) of a pixel in the discretized fake CBED pattern.")
_check_and_convert_overriding_image_err_msg_1 = \
("The object ``overriding_image`` must have dimensions, in units of "
"pixels, equal to those of the original CBED pattern intensity image "
"being overridden, which in this case are ``({}, {})``.")
_cbed_pattern_err_msg_1 = \
("Failed to generate discretized fake CBED pattern. See traceback for "
"details.")