hotopy 0.21__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.
hotopy/__init__.py ADDED
@@ -0,0 +1,42 @@
1
+ """
2
+ =============================================
3
+ HoToPy: A Holo- and Tomography Python Toolbox
4
+ =============================================
5
+
6
+ To use any submodule an explicit import is required either via
7
+ - ``import hotopy.holo`` or
8
+ - ``from hotopy import holo`` or
9
+ - ``from hotopy.holo import *``
10
+
11
+ or the used functions are imported explicitly ``from hotopy.holo import CTF``.
12
+
13
+ Submodules
14
+ ----------
15
+
16
+ .. autosummary::
17
+ :toctree: generated/
18
+
19
+ holo
20
+ tomo
21
+ image
22
+ datasets
23
+
24
+
25
+ Optimization
26
+ ------------
27
+
28
+ .. autosummary::
29
+ :toctree: generated/
30
+
31
+ optimize
32
+
33
+
34
+ Utilities and helpers
35
+ ----------------------
36
+
37
+ .. autosummary::
38
+ :toctree: generated/
39
+
40
+ utils
41
+
42
+ """
@@ -0,0 +1,76 @@
1
+ """
2
+ =========================================
3
+ Example datasets (:mod:`hotopy.datasets`)
4
+ =========================================
5
+
6
+ Experimental holographic datasets
7
+ ---------------------------------
8
+ .. autosummary::
9
+ :toctree: generated/
10
+
11
+ beads
12
+ radiodurans
13
+ macrophage
14
+ world_holograms
15
+ spider
16
+
17
+ Example:
18
+
19
+ >>> from hotopy.datasets import beads
20
+ >>> data = beads()
21
+ >>> # list content
22
+ >>> print(list(data.keys())) # ['holograms', 'fresnelNumbers']
23
+
24
+ Some dataset also have a ``'support'`` field, which can be used for constrained phase retrieval.
25
+
26
+ >>> holos, fresnel_nums = data["holograms"], data["fresnelNumbers"]
27
+ >>> print(holos.shape, fresnel_nums.shape)
28
+
29
+ This datasets can be used for phase retrieval, e.g. with CTF or Tikhonov.
30
+
31
+ >>> from hotopy.holo import Tikhonov
32
+ >>> imshape = holos.shape[-2:]
33
+ >>> alpha = [0, 5e-2]
34
+ >>> betadelta = 0.01 # 1% effective absorption
35
+ >>> device = "cpu" # if CUDA cards are available set ``device="cuda"``.
36
+ >>> tik = Tikhonov(imshape, fresnel_nums, betadelta=betadelta, alpha=alpha, device=device)
37
+ >>> rec_tik = tik(holos).cpu().numpy()
38
+
39
+
40
+ Simulation phantoms
41
+ -------------------
42
+ .. autosummary::
43
+ :toctree: generated/
44
+
45
+ dicty
46
+ dicty_multi
47
+ world
48
+
49
+ .. author: Jens Lucht, 2023-2024
50
+ """
51
+
52
+ from ._holograms import (
53
+ beads,
54
+ radiodurans,
55
+ macrophage,
56
+ world_holograms,
57
+ spider,
58
+ logo_holograms,
59
+ catparticle,
60
+ )
61
+ from ._phantoms import dicty, dicty_multi, world, balls, checkerboard
62
+
63
+ __all__ = [
64
+ "beads",
65
+ "radiodurans",
66
+ "macrophage",
67
+ "world_holograms",
68
+ "logo_holograms",
69
+ "catparticle",
70
+ "spider",
71
+ "dicty",
72
+ "dicty_multi",
73
+ "world",
74
+ "balls",
75
+ "checkerboard",
76
+ ]
@@ -0,0 +1,37 @@
1
+ import numpy as np
2
+ import pooch
3
+ import importlib.resources # requires python>=3.7
4
+
5
+
6
+ class _DataFetcher:
7
+ def __init__(self):
8
+ self.storage = pooch.create(
9
+ path=pooch.os_cache("hotopy/datasets"),
10
+ base_url="https://gitlab.gwdg.de/irp/",
11
+ registry=None,
12
+ env="HOTOPY_DATA_DIR",
13
+ )
14
+
15
+ # find registry
16
+ self._registry_fp = registry_fp = importlib.resources.files(__package__).joinpath(
17
+ "registry.txt"
18
+ )
19
+ self.storage.load_registry(registry_fp)
20
+
21
+ def __call__(self, *args, **kwargs):
22
+ defaults = {"processor": _process_npz}
23
+ kwargs = {**defaults, **kwargs}
24
+ return self.storage.fetch(*args, **kwargs)
25
+
26
+
27
+ def _process_npz(fname, *args):
28
+ return np.load(fname)
29
+
30
+
31
+ def _decompress_npz(fname, *args):
32
+ decompressor = pooch.processors.Decompress()
33
+ fd_inflated = decompressor(fname, *args)
34
+ return np.load(fd_inflated)
35
+
36
+
37
+ fetcher = _DataFetcher()
@@ -0,0 +1,54 @@
1
+ """
2
+ .. autosummary::
3
+ :toctree: generated/
4
+
5
+ .. author: Jens Lucht, 2024
6
+ """
7
+
8
+ from ._fetcher import fetcher
9
+ from ._fetcher import _decompress_npz
10
+
11
+
12
+ def _simple_holograms(name):
13
+ return fetcher(f"holograms_{name!s}.npz")
14
+
15
+
16
+ def radiodurans():
17
+ return _simple_holograms("radiodurans")
18
+
19
+
20
+ def beads():
21
+ return _simple_holograms("beads")
22
+
23
+
24
+ def macrophage():
25
+ return _simple_holograms("macrophage")
26
+
27
+
28
+ def world_holograms():
29
+ return _simple_holograms("world")
30
+
31
+
32
+ def spider():
33
+ """
34
+ Single-distance dataset for direct contrast aka TIE regime phase retrieval.
35
+ """
36
+ return _simple_holograms("spider")
37
+
38
+
39
+ def logo_holograms():
40
+ """
41
+ deep-holographic dataset
42
+ """
43
+ return _simple_holograms("logo")
44
+
45
+
46
+ def catparticle():
47
+ """
48
+ Normalized holographic projections (holograms) at two defocus positions of a
49
+ catalytic nano-particle in the deep-holographic regime.
50
+ This dataset is at a single tomographic angle.
51
+
52
+ For the full tomographic dataset see https://doi.org/10.25625/CQ1EKY
53
+ """
54
+ return fetcher("holograms_catparticle.npz.bz2", processor=_decompress_npz)
@@ -0,0 +1,120 @@
1
+ """
2
+ .. autosummary::
3
+ :toctree: generated/
4
+
5
+
6
+ .. author: Jens Lucht, 2023
7
+ """
8
+
9
+ from ._fetcher import fetcher
10
+ from ..image import dissect_levels, ball
11
+ import numpy as np
12
+ from functools import reduce
13
+
14
+
15
+ def _simple_phantom(name):
16
+ cnt = fetcher(f"phantom_{name!s}.npz")
17
+ return cnt["phantom"]
18
+
19
+
20
+ def dicty():
21
+ return _simple_phantom("dicty")
22
+
23
+
24
+ # not an actual dataset, but simple wrapper to get a multi-component/material phantom
25
+ def dicty_multi(*lims):
26
+ """
27
+ Multi-component/material ``dicty`` phantom.
28
+
29
+ See dissect_levels, dicty
30
+ """
31
+ im = dicty()
32
+ return dissect_levels(im, *lims)
33
+
34
+
35
+ def world():
36
+ return _simple_phantom("world").astype(float)
37
+
38
+
39
+ def world_uint():
40
+ # dataset in original datatype
41
+ return _simple_phantom("world")
42
+
43
+
44
+ def _get_default_cen_rad_dens(shape=(120, 128, 128)):
45
+ ref_length_radii = min(shape)
46
+ height = shape[0]
47
+ dist_center = 0.7 * min(shape[1:]) / 2
48
+
49
+ centers, radii, densities = [], [], []
50
+ for z, phi in zip(
51
+ ((0.3 * height,) * 3 + (0.7 * height,)),
52
+ 2 * np.pi * np.array((0, 0.25, 0.5, 0.25)),
53
+ strict=True,
54
+ ):
55
+ radii.append(ref_length_radii / 15)
56
+ centers.append((z, dist_center, phi)) # z, r, phi
57
+ densities.append(1)
58
+
59
+ for i, phi in enumerate(2 * np.pi * np.arange(0, 1, 1 / 12)[:-1]):
60
+ radii.append(ref_length_radii * np.sqrt(i + 1) / 60)
61
+ centers.append((0.5 * height, dist_center, phi)) # z, r, phi
62
+ densities.append(2 / np.sqrt(i + 1))
63
+
64
+ centers = np.array(centers)
65
+ # centers: r, phi -> x, y
66
+ r, phi = centers[:, 1:].T
67
+ centers[:, 1:] = np.stack(
68
+ (shape[1] / 2 + r * np.cos(phi), shape[2] / 2 + r * np.sin(phi)), axis=-1
69
+ )
70
+ return centers, radii, densities
71
+
72
+
73
+ def balls(shape=(120, 128, 128), centers=None, radii=None, densities=1, dtype=np.float32):
74
+ """generate a volume containing balls of uniform density.
75
+ The default values yield an example volume for tomography.
76
+ """
77
+ # preprocess parameters
78
+ if centers is None:
79
+ if len(shape) == 3:
80
+ centers, radii, densities = _get_default_cen_rad_dens(shape)
81
+ else:
82
+ rng = np.random.default_rng(seed=0)
83
+ centers = shape * rng.random((10, len(shape)))
84
+ else:
85
+ centers = np.asarray(centers)
86
+ if radii is None:
87
+ radii = min((min(shape) / 2, sum(shape) / len(shape) / 10))
88
+ radii = np.broadcast_to(radii, len(centers))
89
+ densities = np.broadcast_to(densities, len(centers))
90
+
91
+ # generate phantom
92
+ phantom = np.zeros(shape, dtype=dtype)
93
+ for r, cen, d in zip(radii, centers, densities, strict=True):
94
+ r_px = int(r)
95
+ cen_px = cen.astype(int)
96
+ slc = tuple( # roi in phantom
97
+ slice(
98
+ max(0, c_px - r_px),
99
+ c_px + r_px + 2,
100
+ )
101
+ for c_px in cen_px
102
+ )
103
+ slc_ball = tuple( # crop ball at borders of phantom
104
+ slice(
105
+ max(0, r_px - c_px),
106
+ r_px + s - c_px,
107
+ )
108
+ for s, c_px in zip(shape, cen_px, strict=True)
109
+ )
110
+ phantom[slc] += d * ball(len(shape) * (2 * r_px + 2,), r, center=r_px + cen % 1)[slc_ball]
111
+ return phantom
112
+
113
+
114
+ def checkerboard(shape, boxsize=1):
115
+ ndim = len(shape)
116
+ boxsize = np.broadcast_to(boxsize, ndim)
117
+ coordinates = np.indices(shape)
118
+ stripes = (coordinates / (2 * np.expand_dims(boxsize, tuple(range(1, ndim + 1))))) % 1
119
+ stripes = (coordinates / boxsize[(slice(None),) + ndim * (None,)]) % 2 >= 1
120
+ return reduce(np.logical_xor, stripes)
@@ -0,0 +1,9 @@
1
+ phantom_dicty.npz d80a8ce6b4faef56057e79bee18b2303780f340c462132b398e3b61ad045738d https://gitlab.gwdg.de/irp/datasets/dataset-phantom-dicty/-/raw/master/phantom_dicty.npz
2
+ phantom_world.npz 41ad9834121af5e94200927a0013cb4d9807a384790f1b157b76f87bac569374 https://gitlab.gwdg.de/irp/datasets/dataset-phantom-world/-/raw/master/phantom_world.npz
3
+ holograms_radiodurans.npz 30d1b234124a6f4dd1d0c86bf24429c7ba509cf0e4b2cbdd71c8300f03e76873 https://gitlab.gwdg.de/irp/datasets/dataset-holograms-radiodurans/-/raw/master/holograms_radiodurans.npz
4
+ holograms_beads.npz 27b6648c526f9272e6d492aa7f1de3a35172351fb0ecb84abc68e6711b6b9495 https://gitlab.gwdg.de/irp/datasets/dataset-holograms-beads/-/raw/master/holograms_beads.npz
5
+ holograms_macrophage.npz 2f50a1a06b09cc8cf2246742daec4412fd638f005da5e84892fe3f33a9195c95 https://gitlab.gwdg.de/irp/datasets/dataset-holograms-macrophage/-/raw/master/holograms_macrophage.npz
6
+ holograms_world.npz e9856299ad9d392b5253b3cdfe10edcb08f2e4aca3d67c48d645f74d11a4c78e https://gitlab.gwdg.de/irp/datasets/dataset-holograms-world/-/raw/master/holograms_world.npz
7
+ holograms_spider.npz ea226167db6668b564698d7ce1c2f5c4c61da0ff3b9568ab6e08be1d71ce0dc1 https://gitlab.gwdg.de/irp/datasets/dataset-holograms-spider/-/raw/master/holograms_spider.npz
8
+ holograms_logo.npz 45ae0903c4e79d3b1d56ab880bc290382bfec66577401ab9d01002aace3aad75 https://gitlab.gwdg.de/irp/datasets/dataset-holograms-logo/-/raw/main/holograms_logo.npz
9
+ holograms_catparticle.npz.bz2 d058890d4985706124286cfa608d2a2b879814a1b2683b9272ed8b822e88515f https://gitlab.gwdg.de/irp/datasets/dataset-holograms-catparticle/-/raw/master/holograms_catparticle.npz.bz2
@@ -0,0 +1,124 @@
1
+ """
2
+ =========================================================
3
+ Holographic phase retrieval methods (:mod:`hotopy.holo`)
4
+ =========================================================
5
+
6
+ .. currentmodule:: hotopy.holo
7
+
8
+
9
+ Submodules
10
+ ==========
11
+
12
+ .. autosummary::
13
+ :toctree: generated/
14
+
15
+ propagation
16
+
17
+
18
+ (Deep) Holographic regime phase retrieval
19
+ -----------------------------------------
20
+
21
+ For Fresnel numbers << 1 and monochromatic sources, e.g. at synchrotrons.
22
+
23
+ .. autosummary::
24
+ :toctree: generated/
25
+
26
+ CTF
27
+ Tikhonov
28
+ TikhonovTV
29
+ AP
30
+ ICT
31
+
32
+
33
+ Transport of Intensity Equation based methods (TIE)
34
+ ---------------------------------------------------
35
+
36
+ Transport of Intensity (TIE) used for laboratory X-ray sources.
37
+
38
+ .. autosummary::
39
+ :toctree: generated/
40
+
41
+ BronnikovAidedCorrection
42
+ ModifiedBronnikov
43
+ Paganin
44
+ GeneralizedPaganin
45
+
46
+
47
+ Propagation methods
48
+ -------------------
49
+
50
+ Convenience imports from ``propagation`` submodule.
51
+
52
+ .. autosummary::
53
+ :toctree: generated/
54
+
55
+ simulate_hologram
56
+
57
+ Helpers
58
+ -------
59
+
60
+ .. autosummary::
61
+ :toctree: generated/
62
+
63
+ Constraints
64
+ rescale_defocus_series
65
+ rescale_defocus_fresnel_numbers
66
+ twolevel_regularization
67
+ ctf_erf_filter
68
+ erf_filter
69
+ find_fresnel_number
70
+ pca_decompose_flats
71
+ pca_synthesize_flat
72
+ """
73
+
74
+ from .propagation import (
75
+ FresnelTFPropagator,
76
+ simulate_hologram,
77
+ expand_fresnel_numbers,
78
+ )
79
+ from .constraints import Constraints, WaveConstraints
80
+ from ._tieregime import BronnikovAidedCorrection, ModifiedBronnikov, Paganin, GeneralizedPaganin
81
+ from ._ctf import CTF
82
+ from ._tikhonov import Tikhonov, TikhonovTV, nonlinearity_low_freq_correction
83
+ from ._ap import AP
84
+ from ._pbi import ICT
85
+ from .regularization import erf_filter, ctf_erf_filter, twolevel_regularization
86
+ from ._util import (
87
+ check_fresnel_number,
88
+ rescale_defocus_series,
89
+ rescale_defocus_fresnel_numbers,
90
+ find_fresnel_number,
91
+ difference_fresnel_numbers,
92
+ flatfield_inpainting_correction,
93
+ )
94
+ from ._pca import pca_decompose_flats, pca_synthesize_flat, pca_decompose_arpack
95
+
96
+ __all__ = [
97
+ "FresnelTFPropagator",
98
+ "simulate_hologram",
99
+ "expand_fresnel_numbers",
100
+ "BronnikovAidedCorrection",
101
+ "ModifiedBronnikov",
102
+ "Paganin",
103
+ "GeneralizedPaganin",
104
+ "CTF",
105
+ "Tikhonov",
106
+ "TikhonovTV",
107
+ "ICT",
108
+ "AP",
109
+ "Constraints",
110
+ "WaveConstraints",
111
+ "nonlinearity_low_freq_correction",
112
+ "erf_filter",
113
+ "ctf_erf_filter",
114
+ "twolevel_regularization",
115
+ "check_fresnel_number",
116
+ "rescale_defocus_series",
117
+ "rescale_defocus_fresnel_numbers",
118
+ "find_fresnel_number",
119
+ "difference_fresnel_numbers",
120
+ "flatfield_inpainting_correction",
121
+ "pca_decompose_flats",
122
+ "pca_synthesize_flat",
123
+ "pca_decompose_arpack",
124
+ ]
hotopy/holo/_ap.py ADDED
@@ -0,0 +1,103 @@
1
+ """
2
+ Author: Jens Lucht
3
+ """
4
+
5
+ from numpy import ndarray, newaxis
6
+ from torch import ones, as_tensor
7
+
8
+ from ..optimize import AlternatingProjections
9
+ from .propagation import FresnelTFPropagator
10
+ from .constraints import AmplitudeProjector, ConstraintOperator, WaveConstraints
11
+
12
+
13
+ class AP:
14
+ """Alternating projections for holographic phase retrieval."""
15
+
16
+ algorithm = AlternatingProjections
17
+
18
+ def __init__(
19
+ self,
20
+ shape,
21
+ fresnel_nums,
22
+ ndim=2,
23
+ dtype=None,
24
+ device=None,
25
+ ):
26
+ self.dtype = dtype
27
+ self.device = device
28
+ self.ndim = ndim
29
+
30
+ self.propagator = FresnelTFPropagator(
31
+ shape,
32
+ fresnel_nums,
33
+ dtype=dtype,
34
+ device=device,
35
+ keep_type=False,
36
+ )
37
+
38
+ def __call__(
39
+ self,
40
+ holograms,
41
+ constraints=None,
42
+ initial_guess=None,
43
+ max_iter: int = 100,
44
+ keep_type=False,
45
+ ):
46
+ """Reconstruct holograms.
47
+
48
+ Parameters
49
+ ----------
50
+ holograms :
51
+ Intensities measured at detector.
52
+ constraints :
53
+ Constraints projectors for object/sample. Default Amplitude = 1 constrain for pure-phase object.
54
+ max_iter : int, Optional
55
+ Maximal number of iterations.
56
+ """
57
+ holograms_t = type(holograms)
58
+ holograms = as_tensor(holograms, device=self.device)
59
+ shape = holograms.shape[-self.ndim :]
60
+
61
+ # if single image is entered expand to stack of one image
62
+ single_image = holograms.ndim == self.ndim
63
+ if single_image:
64
+ holograms = holograms[newaxis]
65
+
66
+ projector_holos = AmplitudeProjector(holograms.sqrt())
67
+ projector_object = (
68
+ constraints if constraints is not None else WaveConstraints(betadelta=0.0)
69
+ )
70
+
71
+ # ensure correct device placement
72
+ if isinstance(projector_object, ConstraintOperator):
73
+ projector_object.to_device(self.device)
74
+
75
+ # initialize with plane wave (ones) or initial guess if given
76
+ if initial_guess is None:
77
+ x = ones(shape, device=self.device, dtype=holograms.dtype)
78
+ else:
79
+ x = as_tensor(initial_guess, device=self.device)
80
+
81
+ # AP
82
+ ap = self.algorithm(
83
+ self.propagator,
84
+ (projector_object, projector_holos),
85
+ x,
86
+ max_iter=max_iter,
87
+ )
88
+
89
+ # iterate util stopping condition is met
90
+ while not ap.done():
91
+ ap.step()
92
+
93
+ # recast into numpy if requested
94
+ if keep_type and holograms_t is ndarray:
95
+ out = ap.x.cpu().numpy()
96
+ else:
97
+ out = ap.x
98
+
99
+ # remove stack axis if single image
100
+ if single_image:
101
+ out = out[0]
102
+
103
+ return out