phaserEM 0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. phaser/__init__.py +0 -0
  2. phaser/__main__.py +5 -0
  3. phaser/engines/common/__init__.py +0 -0
  4. phaser/engines/common/noise_models.py +113 -0
  5. phaser/engines/common/output.py +189 -0
  6. phaser/engines/common/position_correction.py +62 -0
  7. phaser/engines/common/regularizers.py +403 -0
  8. phaser/engines/common/simulation.py +270 -0
  9. phaser/engines/conventional/__init__.py +0 -0
  10. phaser/engines/conventional/run.py +142 -0
  11. phaser/engines/conventional/solvers.py +476 -0
  12. phaser/engines/gradient/run.py +451 -0
  13. phaser/engines/gradient/solvers.py +139 -0
  14. phaser/execute.py +371 -0
  15. phaser/hooks/__init__.py +158 -0
  16. phaser/hooks/hook.py +159 -0
  17. phaser/hooks/io/empad.py +88 -0
  18. phaser/hooks/object.py +25 -0
  19. phaser/hooks/preprocessing.py +133 -0
  20. phaser/hooks/probe.py +24 -0
  21. phaser/hooks/regularization.py +97 -0
  22. phaser/hooks/scan.py +27 -0
  23. phaser/hooks/schedule.py +75 -0
  24. phaser/hooks/solver.py +169 -0
  25. phaser/io/__init__.py +0 -0
  26. phaser/io/empad.py +212 -0
  27. phaser/main.py +92 -0
  28. phaser/plan.py +184 -0
  29. phaser/py.typed +0 -0
  30. phaser/state.py +249 -0
  31. phaser/types.py +305 -0
  32. phaser/utils/__init__.py +0 -0
  33. phaser/utils/_cuda_kernels.py +213 -0
  34. phaser/utils/_jax_kernels.py +98 -0
  35. phaser/utils/analysis.py +263 -0
  36. phaser/utils/image.py +201 -0
  37. phaser/utils/io.py +402 -0
  38. phaser/utils/misc.py +295 -0
  39. phaser/utils/num.py +800 -0
  40. phaser/utils/object.py +578 -0
  41. phaser/utils/optics.py +377 -0
  42. phaser/utils/physics.py +88 -0
  43. phaser/utils/plotting.py +699 -0
  44. phaser/utils/scan.py +60 -0
  45. phaser/web/__init__.py +0 -0
  46. phaser/web/dist/03510a839ccb97b0da9f.module.wasm +0 -0
  47. phaser/web/dist/9573273f862f4f5d9644.module.wasm +0 -0
  48. phaser/web/dist/bundle-dashboard.js +712 -0
  49. phaser/web/dist/bundle-manager.js +210 -0
  50. phaser/web/dist/bundle-vendors-node_modules_wasm-array_wasm_array_js.js +106 -0
  51. phaser/web/dist/style.css +152 -0
  52. phaser/web/notebook.py +269 -0
  53. phaser/web/routes.py +211 -0
  54. phaser/web/server.py +540 -0
  55. phaser/web/slurm.py +180 -0
  56. phaser/web/templates/base.html +13 -0
  57. phaser/web/templates/dashboard.html +14 -0
  58. phaser/web/templates/manager.html +19 -0
  59. phaser/web/types.py +255 -0
  60. phaser/web/util.py +116 -0
  61. phaser/web/worker.py +200 -0
  62. phaserem-0.1.dist-info/METADATA +121 -0
  63. phaserem-0.1.dist-info/RECORD +67 -0
  64. phaserem-0.1.dist-info/WHEEL +5 -0
  65. phaserem-0.1.dist-info/entry_points.txt +2 -0
  66. phaserem-0.1.dist-info/licenses/LICENSE.txt +373 -0
  67. phaserem-0.1.dist-info/top_level.txt +1 -0
phaser/__init__.py ADDED
File without changes
phaser/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env python3
2
+
3
+ if __name__ == '__main__':
4
+ from phaser.main import cli
5
+ cli()
File without changes
@@ -0,0 +1,113 @@
1
+
2
+ import typing as t
3
+
4
+ import numpy
5
+ from numpy.typing import NDArray
6
+
7
+ from phaser.hooks.solver import NoiseModel
8
+ from phaser.plan import AmplitudeNoisePlan, AnscombeNoisePlan, PoissonNoisePlan
9
+ from phaser.utils.num import get_array_module, Float
10
+ from phaser.state import ReconsState
11
+
12
+
13
+ class AmplitudeNoiseModel(NoiseModel[None]):
14
+ @classmethod
15
+ def name(cls) -> str:
16
+ return "amplitude"
17
+
18
+ def __init__(self, args: None, props: AmplitudeNoisePlan):
19
+ self.offset: float = props.offset
20
+ self.gaussian_variance: float = props.gaussian_variance
21
+
22
+ self.var: float = 1 + self.gaussian_variance
23
+
24
+ self.eps: float = props.eps
25
+
26
+ def init_state(self, sim: ReconsState) -> None:
27
+ return None
28
+
29
+ def calc_loss(
30
+ self,
31
+ model_wave: NDArray[numpy.complexfloating],
32
+ model_intensity: NDArray[numpy.floating],
33
+ exp_patterns: NDArray[numpy.floating],
34
+ mask: NDArray[numpy.floating],
35
+ state: None
36
+ ) -> t.Tuple[Float, None]:
37
+ xp = get_array_module(model_wave, model_intensity, exp_patterns, mask)
38
+ patterns = xp.maximum(exp_patterns, 0.0)
39
+
40
+ return ((
41
+ 2. * xp.sum(mask * (
42
+ xp.sqrt(patterns + self.offset) - xp.sqrt(model_intensity + self.offset) - self.eps
43
+ )**2) / self.var).astype(exp_patterns.dtype),
44
+ state
45
+ )
46
+
47
+ def calc_wave_update(
48
+ self,
49
+ model_wave: NDArray[numpy.complexfloating],
50
+ model_intensity: NDArray[numpy.floating],
51
+ exp_patterns: NDArray[numpy.floating],
52
+ mask: NDArray[numpy.floating],
53
+ state: None
54
+ ) -> t.Tuple[NDArray[numpy.complexfloating], None]:
55
+ xp = get_array_module(model_wave, model_intensity, exp_patterns, mask)
56
+ patterns = xp.maximum(exp_patterns, 0.0)
57
+
58
+ update = xp.sqrt(patterns + self.offset) / (xp.sqrt(model_intensity + self.offset) + self.eps) - 1.0
59
+ update *= mask # / self.var
60
+ #print(f"min update: {xp.min(update).get()}")
61
+ #print(f"max update: {xp.max(update).get()}")
62
+
63
+ return (update * model_wave, state)
64
+
65
+
66
+ class AnscombeNoiseModel(AmplitudeNoiseModel):
67
+ @classmethod
68
+ def name(cls) -> str:
69
+ return "anscombe"
70
+
71
+ def __init__(self, args: None, props: AnscombeNoisePlan):
72
+ super().__init__(args, props)
73
+
74
+
75
+ class PoissonNoiseModel(NoiseModel[None]):
76
+ @classmethod
77
+ def name(cls) -> str:
78
+ return "poisson"
79
+
80
+ def __init__(self, args: None, props: PoissonNoisePlan):
81
+ self.eps: float = props.eps
82
+
83
+ def init_state(self, sim: ReconsState) -> None:
84
+ return None
85
+
86
+ def calc_loss(
87
+ self,
88
+ model_wave: NDArray[numpy.complexfloating],
89
+ model_intensity: NDArray[numpy.floating],
90
+ exp_patterns: NDArray[numpy.floating],
91
+ mask: NDArray[numpy.floating],
92
+ state: None
93
+ ) -> t.Tuple[Float, None]:
94
+ xp = get_array_module(model_wave, model_intensity, exp_patterns, mask)
95
+ patterns = xp.maximum(exp_patterns, 0.0)
96
+ #intensity - patterns * xp.log(intensity + self.offset)
97
+
98
+ loss = xp.sum(mask * (
99
+ #model_intensity - patterns * xp.log(model_intensity + self.eps)
100
+ model_intensity + patterns * (xp.log(patterns + self.eps) - xp.log(model_intensity + self.eps) - 1.0)
101
+ #patterns - (model_intensity + self.offset) * xp.log(patterns)
102
+ )).astype(exp_patterns.dtype)
103
+ return (loss, state)
104
+
105
+ def calc_wave_update(
106
+ self,
107
+ model_wave: NDArray[numpy.complexfloating],
108
+ model_intensity: NDArray[numpy.floating],
109
+ exp_patterns: NDArray[numpy.floating],
110
+ mask: NDArray[numpy.floating],
111
+ state: None
112
+ ) -> t.Tuple[NDArray[numpy.complexfloating], None]:
113
+ raise NotImplementedError()
@@ -0,0 +1,189 @@
1
+ from functools import partial
2
+ from pathlib import Path
3
+ import typing as t
4
+
5
+ import numpy
6
+ from numpy.typing import NDArray
7
+ import tifffile
8
+
9
+ from phaser.utils.num import to_numpy, abs2, fft2, get_array_module
10
+ from phaser.utils.image import remove_linear_ramp, colorize_complex, scale_to_integral_type
11
+ from phaser.utils.io import tiff_write_opts, tiff_write_opts_recip
12
+ from phaser.state import ReconsState
13
+ from phaser.plan import SaveOptions
14
+
15
+
16
+ def output_images(state: ReconsState, out_dir: Path, options: SaveOptions):
17
+ for ty in options.images:
18
+ if ty not in _SAVE_FUNCS:
19
+ raise ValueError(f"Unknown image type '{ty}'")
20
+
21
+ try:
22
+ out_name = options.img_fmt.format(
23
+ type=ty, iter=state.iter,
24
+ )
25
+ out_path = out_dir / out_name
26
+ except KeyError as e:
27
+ raise ValueError(f"Invalid format string in 'img_fmt' (unknown key {e})") from None
28
+ except Exception as e:
29
+ raise ValueError("Invalid format string in 'img_fmt'") from e
30
+
31
+ _SAVE_FUNCS[ty](state, out_path, options)
32
+
33
+
34
+ def output_state(state: ReconsState, out_dir: Path, options: SaveOptions):
35
+ try:
36
+ out_name = options.hdf5_fmt.format(
37
+ iter=state.iter
38
+ )
39
+ except KeyError as e:
40
+ raise ValueError(f"Invalid format string in 'hdf5_fmt' (unknown key {e})") from None
41
+ except Exception as e:
42
+ raise ValueError("Invalid format string in 'hdf5_fmt'") from e
43
+
44
+ state.write_hdf5(out_dir / out_name)
45
+
46
+
47
+ def _save_probe(state: ReconsState, out_path: Path, options: SaveOptions):
48
+ probe = to_numpy(state.probe.data)
49
+ write_opts = tiff_write_opts(state.probe.sampling, n_slices=probe.shape[0])
50
+
51
+ if options.img_dtype == 'float':
52
+ # save complex image
53
+ with tifffile.TiffWriter(out_path, ome=True) as w:
54
+ write_opts['metadata']['axes'] = 'CYX'
55
+ w.write(probe, **write_opts)
56
+ return
57
+
58
+ img = scale_to_integral_type(
59
+ colorize_complex(probe), options.img_dtype
60
+ )
61
+ with tifffile.TiffWriter(out_path, ome=True) as w:
62
+ write_opts['metadata']['axes'] = 'CYXS'
63
+ w.write(img, photometric='rgb', **write_opts)
64
+
65
+
66
+ def _save_probe_mag(state: ReconsState, out_path: Path, options: SaveOptions):
67
+ probe_mag = abs2(state.probe.data)
68
+ write_opts = tiff_write_opts(state.probe.sampling, n_slices=probe_mag.shape[0])
69
+
70
+ if options.img_dtype != 'float':
71
+ probe_mag = scale_to_integral_type(to_numpy(probe_mag), options.img_dtype, min_range=0.2)
72
+
73
+ with tifffile.TiffWriter(out_path, ome=True) as w:
74
+ write_opts['metadata']['axes'] = 'CYX'
75
+ w.write(to_numpy(probe_mag), **write_opts)
76
+
77
+
78
+ def _save_probe_recip(state: ReconsState, out_path: Path, options: SaveOptions):
79
+ xp = get_array_module(state.probe.data)
80
+ probe = to_numpy(xp.fft.fftshift(fft2(state.probe.data), axes=(-1, -2)))
81
+ write_opts = tiff_write_opts_recip(state.probe.sampling, n_slices=probe.shape[0])
82
+
83
+ if options.img_dtype == 'float':
84
+ # save complex image
85
+ with tifffile.TiffWriter(out_path, ome=True) as w:
86
+ write_opts['metadata']['axes'] = 'CYX'
87
+ w.write(probe, **write_opts)
88
+ return
89
+
90
+ img = scale_to_integral_type(
91
+ colorize_complex(probe), options.img_dtype
92
+ )
93
+ with tifffile.TiffWriter(out_path, ome=True) as w:
94
+ write_opts['metadata']['axes'] = 'CYXS'
95
+ w.write(img, photometric='rgb', **write_opts)
96
+
97
+
98
+ def _save_probe_recip_mag(state: ReconsState, out_path: Path, options: SaveOptions):
99
+ xp = get_array_module(state.probe.data)
100
+ probe_mag = to_numpy(abs2(xp.fft.fftshift(fft2(state.probe.data), axes=(-1, -2))))
101
+ write_opts = tiff_write_opts_recip(state.probe.sampling, n_slices=probe_mag.shape[0])
102
+
103
+ if options.img_dtype != 'float':
104
+ probe_mag = scale_to_integral_type(probe_mag, options.img_dtype, min_range=0.2)
105
+
106
+ with tifffile.TiffWriter(out_path, ome=True) as w:
107
+ write_opts['metadata']['axes'] = 'CYX'
108
+ w.write(probe_mag, **write_opts)
109
+
110
+
111
+ def _save_object_phase(state: ReconsState, out_path: Path, options: SaveOptions, stack: bool = False):
112
+ crop = options.crop_roi
113
+
114
+ xp = get_array_module(state.object.data)
115
+ obj_phase = xp.angle(state.object.data)
116
+
117
+ obj_sampling = state.object.sampling
118
+ write_opts = tiff_write_opts(
119
+ obj_sampling,
120
+ corner=obj_sampling.region_min if crop else None,
121
+ zs=state.object.zs() if stack else None,
122
+ )
123
+
124
+ if crop:
125
+ obj_phase = obj_phase[..., *state.object.sampling.get_region_crop()]
126
+ mask = xp.ones(obj_phase.shape[-2:], dtype=numpy.bool_)
127
+ else:
128
+ # include whole image, but only scale based on ROI
129
+ mask = state.object.sampling.get_region_mask(xp=xp)
130
+
131
+ if options.unwrap_phase:
132
+ obj_phase = xp.unwrap(xp.unwrap(obj_phase, axis=-1), axis=-2)
133
+
134
+ if not stack:
135
+ obj_phase = xp.sum(obj_phase, axis=0)
136
+
137
+ mask = to_numpy(mask)
138
+ obj_phase = remove_linear_ramp(to_numpy(obj_phase), mask)
139
+
140
+ if options.img_dtype != 'float':
141
+ obj_phase = scale_to_integral_type(obj_phase, options.img_dtype, mask)
142
+
143
+ with tifffile.TiffWriter(out_path, ome=True) as w:
144
+ write_opts['metadata']['axes'] = 'ZYX' if stack else 'YX'
145
+ w.write(obj_phase, **write_opts)
146
+
147
+
148
+ def _save_object_mag(state: ReconsState, out_path: Path, options: SaveOptions, stack: bool = False):
149
+ crop = options.crop_roi
150
+
151
+ obj_sampling = state.object.sampling
152
+ write_opts = tiff_write_opts(
153
+ obj_sampling,
154
+ corner=obj_sampling.region_min if crop else None,
155
+ zs=state.object.zs() if stack else None,
156
+ )
157
+
158
+ xp = get_array_module(state.object.data)
159
+ obj_mag = abs2(state.object.data)
160
+ if crop:
161
+ obj_mag = obj_mag[..., *state.object.sampling.get_region_crop()]
162
+ mask = numpy.ones(obj_mag.shape[-2:], dtype=numpy.bool_)
163
+ else:
164
+ # include whole image, but only scale based on ROI
165
+ mask = state.object.sampling.get_region_mask(xp=numpy)
166
+
167
+ if not stack:
168
+ obj_mag = xp.prod(obj_mag, axis=0)
169
+
170
+ obj_mag = to_numpy(obj_mag)
171
+ if options.img_dtype != 'float':
172
+ obj_mag = scale_to_integral_type(obj_mag, options.img_dtype, mask, min_range=0.2)
173
+
174
+ with tifffile.TiffWriter(out_path, ome=True) as w:
175
+ write_opts['metadata']['axes'] = 'ZYX' if stack else 'YX'
176
+ w.write(obj_mag, **write_opts)
177
+
178
+
179
+
180
+ _SAVE_FUNCS: t.Dict[str, t.Callable[[ReconsState, Path, SaveOptions], t.Any]] = {
181
+ 'probe': _save_probe,
182
+ 'probe_mag': _save_probe_mag,
183
+ 'probe_recip': _save_probe_recip,
184
+ 'probe_recip_mag': _save_probe_recip_mag,
185
+ 'object_phase_stack': partial(_save_object_phase, stack=True),
186
+ 'object_phase_sum': partial(_save_object_phase, stack=False),
187
+ 'object_mag_stack': partial(_save_object_mag, stack=True),
188
+ 'object_mag_sum': partial(_save_object_mag, stack=False),
189
+ }
@@ -0,0 +1,62 @@
1
+ import typing as t
2
+
3
+ import numpy
4
+ from numpy.typing import NDArray
5
+
6
+ from phaser.utils.num import get_array_module
7
+ from phaser.hooks.solver import (
8
+ PositionSolver, SteepestDescentPositionSolverProps, MomentumPositionSolverProps
9
+ )
10
+ from phaser.state import ReconsState
11
+
12
+
13
+ class SteepestDescentPositionSolver(PositionSolver[None]):
14
+ def __init__(self, args: None, props: SteepestDescentPositionSolverProps):
15
+ self.step_size = props.step_size
16
+ self.max_step_size = props.step_size
17
+
18
+ def init_state(self, sim: ReconsState) -> None:
19
+ return None
20
+
21
+ def perform_update(
22
+ self,
23
+ positions: NDArray[numpy.floating],
24
+ gradients: NDArray[numpy.floating],
25
+ state: None
26
+ ) -> t.Tuple[NDArray[numpy.floating], None]:
27
+ xp = get_array_module(positions, gradients)
28
+ update = self.step_size * gradients
29
+
30
+ if self.max_step_size is not None:
31
+ update_mag = xp.linalg.norm(update, axis=-1, keepdims=True)
32
+ update *= xp.minimum(update_mag, self.max_step_size) / update_mag
33
+
34
+ return (update, state)
35
+
36
+
37
+ class MomentumPositionSolver(PositionSolver[NDArray[numpy.floating]]):
38
+ def __init__(self, args: None, props: MomentumPositionSolverProps):
39
+ self.step_size = props.step_size
40
+ self.max_step_size = props.max_step_size
41
+ self.momentum = props.momentum
42
+
43
+ def init_state(self, sim: ReconsState) -> NDArray[numpy.floating]:
44
+ xp = get_array_module(sim.scan)
45
+ return xp.zeros_like(sim.scan)
46
+
47
+ def perform_update(
48
+ self,
49
+ positions: NDArray[numpy.floating],
50
+ gradients: NDArray[numpy.floating],
51
+ state: NDArray[numpy.floating]
52
+ ) -> t.Tuple[NDArray[numpy.floating], NDArray[numpy.floating]]:
53
+ xp = get_array_module(positions, gradients, state)
54
+
55
+ update = self.step_size * gradients + self.momentum * state
56
+
57
+ if self.max_step_size is not None:
58
+ update_mag = xp.linalg.norm(update, axis=-1, keepdims=True)
59
+ update *= xp.minimum(update_mag, self.max_step_size) / update_mag
60
+
61
+ # state is just previous update step
62
+ return (update, update)