structured-optics 0.6__tar.gz → 0.6.2__tar.gz

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 (30) hide show
  1. {structured_optics-0.6/structured_optics.egg-info → structured_optics-0.6.2}/PKG-INFO +8 -4
  2. {structured_optics-0.6 → structured_optics-0.6.2}/README.md +7 -3
  3. {structured_optics-0.6 → structured_optics-0.6.2}/pyproject.toml +4 -1
  4. {structured_optics-0.6 → structured_optics-0.6.2}/structured_optics/__init__.py +1 -1
  5. structured_optics-0.6.2/structured_optics/bluestein.py +229 -0
  6. {structured_optics-0.6 → structured_optics-0.6.2}/structured_optics/diagnostics.py +0 -17
  7. structured_optics-0.6.2/structured_optics/hologram.py +133 -0
  8. structured_optics-0.6.2/structured_optics/masks.py +410 -0
  9. structured_optics-0.6.2/structured_optics/modes.py +751 -0
  10. structured_optics-0.6.2/structured_optics/polarization.py +261 -0
  11. {structured_optics-0.6 → structured_optics-0.6.2}/structured_optics/prop_methods.py +117 -10
  12. structured_optics-0.6.2/structured_optics/slmdisplay.py +72 -0
  13. {structured_optics-0.6 → structured_optics-0.6.2}/structured_optics/struct_opt.py +92 -227
  14. structured_optics-0.6.2/structured_optics/tools.py +301 -0
  15. structured_optics-0.6.2/structured_optics/utils.py +883 -0
  16. {structured_optics-0.6 → structured_optics-0.6.2/structured_optics.egg-info}/PKG-INFO +8 -4
  17. {structured_optics-0.6 → structured_optics-0.6.2}/structured_optics.egg-info/SOURCES.txt +1 -1
  18. structured_optics-0.6/structured_optics/algebra_utils.py +0 -76
  19. structured_optics-0.6/structured_optics/bluestein.py +0 -118
  20. structured_optics-0.6/structured_optics/hologram.py +0 -63
  21. structured_optics-0.6/structured_optics/masks.py +0 -206
  22. structured_optics-0.6/structured_optics/modes.py +0 -309
  23. structured_optics-0.6/structured_optics/polarization.py +0 -76
  24. structured_optics-0.6/structured_optics/slmdisplay.py +0 -160
  25. structured_optics-0.6/structured_optics/utils.py +0 -471
  26. {structured_optics-0.6 → structured_optics-0.6.2}/LICENSE +0 -0
  27. {structured_optics-0.6 → structured_optics-0.6.2}/setup.cfg +0 -0
  28. {structured_optics-0.6 → structured_optics-0.6.2}/structured_optics.egg-info/dependency_links.txt +0 -0
  29. {structured_optics-0.6 → structured_optics-0.6.2}/structured_optics.egg-info/requires.txt +0 -0
  30. {structured_optics-0.6 → structured_optics-0.6.2}/structured_optics.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structured_optics
3
- Version: 0.6
3
+ Version: 0.6.2
4
4
  Summary: Package for Structured Light simulation
5
5
  Author: Altilano C. Barbosa
6
6
  License-Expression: BSD-3-Clause
@@ -59,18 +59,22 @@ beam.propagate(z=0.1, method='fres_c')
59
59
  # Look at the intensity profile
60
60
  import matplotlib.pyplot as plt
61
61
  plt.pcolormesh(beam.x, beam.y, beam.int_profile())
62
+ plt.colorbar()
62
63
  plt.show()
63
64
  ```
64
65
 
65
66
  Expected output:
66
- ([examples/image_examples/LG_20 prop.png](https://github.com/Altilano/structured-optics/blob/main/examples/image_examples/LG_20%20prop.png))
67
+ <p align="center">
68
+ <img src="https://github.com/Altilano/structured-optics/blob/main/examples/image_examples/LG_20%20prop.png" width="400" alt="LG20">
69
+ </p>
70
+
67
71
 
68
72
  ## Documentation
69
73
 
70
74
  Full API documentation, including all mode types, propagation methods, and
71
75
  optical elements, is available at:
72
76
 
73
- Soon...
77
+ (https://altilano.github.io/structured-optics/)
74
78
 
75
79
  ## Requirements
76
80
 
@@ -96,7 +100,7 @@ This project is licensed under the BSD-3-Clause License. See `LICENSE` for detai
96
100
 
97
101
  - Documentation.
98
102
  - Benchmark and optimization.
99
- - Support for high NA focalization. (Non paraxial propagation)
103
+ - Support for high NA focalization. (Non paraxial propagation, no scalar field)
100
104
  - Cuda and ROCm speed up.
101
105
  - Support for propagation inside linear medium.
102
106
 
@@ -45,18 +45,22 @@ beam.propagate(z=0.1, method='fres_c')
45
45
  # Look at the intensity profile
46
46
  import matplotlib.pyplot as plt
47
47
  plt.pcolormesh(beam.x, beam.y, beam.int_profile())
48
+ plt.colorbar()
48
49
  plt.show()
49
50
  ```
50
51
 
51
52
  Expected output:
52
- ([examples/image_examples/LG_20 prop.png](https://github.com/Altilano/structured-optics/blob/main/examples/image_examples/LG_20%20prop.png))
53
+ <p align="center">
54
+ <img src="https://github.com/Altilano/structured-optics/blob/main/examples/image_examples/LG_20%20prop.png" width="400" alt="LG20">
55
+ </p>
56
+
53
57
 
54
58
  ## Documentation
55
59
 
56
60
  Full API documentation, including all mode types, propagation methods, and
57
61
  optical elements, is available at:
58
62
 
59
- Soon...
63
+ (https://altilano.github.io/structured-optics/)
60
64
 
61
65
  ## Requirements
62
66
 
@@ -82,7 +86,7 @@ This project is licensed under the BSD-3-Clause License. See `LICENSE` for detai
82
86
 
83
87
  - Documentation.
84
88
  - Benchmark and optimization.
85
- - Support for high NA focalization. (Non paraxial propagation)
89
+ - Support for high NA focalization. (Non paraxial propagation, no scalar field)
86
90
  - Cuda and ROCm speed up.
87
91
  - Support for propagation inside linear medium.
88
92
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "structured_optics"
7
- version = "0.6"
7
+ version = "0.6.2"
8
8
  description = "Package for Structured Light simulation"
9
9
  readme = "README.md"
10
10
  license = "BSD-3-Clause"
@@ -18,3 +18,6 @@ dependencies = [
18
18
  "scipy",
19
19
  "screeninfo"
20
20
  ]
21
+
22
+ [tool.setuptools]
23
+ packages = ["structured_optics"]
@@ -2,4 +2,4 @@ from .struct_opt import *
2
2
  from .modes import *
3
3
  from .masks import *
4
4
  from .polarization import *
5
- from .utils import overlap, int_overlap
5
+ from .tools import *
@@ -0,0 +1,229 @@
1
+ import numpy as np
2
+
3
+
4
+ def fft_bluestein(f, dx, dk, D_out=None, x0=0.0, k0=0.0, inverse=False):
5
+ """
6
+ Generalized 1D Fourier transform using Bluestein's algorithm
7
+ (chirp-z transform).
8
+
9
+ Unlike a standard FFT, this evaluates the continuous Fourier
10
+ integral on arbitrary, independently chosen input/output sample
11
+ spacings (`dx`, `dk`) and offsets (`x0`, `k0`), and can change the
12
+ number of samples from `D` (input) to `D_out` (output). It works by
13
+ rewriting the transform as a linear convolution (via the standard
14
+ Bluestein/chirp-z trick) and evaluating that convolution with zero-
15
+ padded FFTs, so it costs O((D + D_out) log(D + D_out)) rather than
16
+ the O(D * D_out) of a direct sum.
17
+
18
+ Forward:
19
+ F(k) = integral of f(x) * exp(-i k x) dx
20
+
21
+ Inverse:
22
+ f(x) = 1/(2*pi) * integral of F(k) * exp(+i k x) dk
23
+
24
+ Parameters
25
+ ----------
26
+ f : array_like
27
+ Input samples. The transform is taken along the last axis, so
28
+ `f` may have leading batch dimensions (shape `(..., D)`).
29
+ dx : float
30
+ Input sample spacing. Must be positive. In inverse mode, this
31
+ is the desired *output* spacing (of `x`).
32
+ dk : float
33
+ Output sample spacing. Must be nonzero. In inverse mode, this
34
+ is the *input* spacing (of `k`/`F`).
35
+ D_out : int, optional
36
+ Number of output samples. Defaults to `D` (the input length,
37
+ i.e. `f.shape[-1]`), giving a same-length transform.
38
+ x0 : float, optional
39
+ Origin of the `x` grid (forward: input origin; inverse: output
40
+ origin). Defaults to 0.0.
41
+ k0 : float, optional
42
+ Origin of the `k` grid (forward: output origin; inverse: input
43
+ origin). Defaults to 0.0.
44
+ inverse : bool, optional
45
+ If False (default), compute the forward transform
46
+ `f(x) -> F(k)`. If True, compute the inverse transform
47
+ `F(k) -> f(x)` (with `f` here holding the input spectrum
48
+ samples).
49
+
50
+ Returns
51
+ -------
52
+ result : np.ndarray
53
+ Forward mode: the transformed array `F`, of shape
54
+ `(..., D_out)`.
55
+ Inverse mode: the reconstructed array `f`, of shape
56
+ `(..., D_out)`.
57
+ coords : np.ndarray
58
+ Forward mode: the output `k` grid, `k0 + m*dk` for
59
+ `m = 0, ..., D_out-1`.
60
+ Inverse mode: the output `x` grid, `x0 + m*dx` for
61
+ `m = 0, ..., D_out-1`.
62
+
63
+ Raises
64
+ ------
65
+ ValueError
66
+ If the input is empty, `D_out` is not positive, `dx` is not
67
+ positive, or `dk` is zero.
68
+
69
+ Reference
70
+ ---------
71
+ [1] Hu, Yanlei, et al. "Efficient full-path optical calculation of scalar and vector diffraction using the Bluestein method."
72
+ Light: Science & Applications 9.1 (2020): 119.
73
+ """
74
+
75
+
76
+ f = np.asarray(f)
77
+ D = f.shape[-1]
78
+
79
+ if D_out is None:
80
+ D_out = D
81
+ if D < 1:
82
+ raise ValueError("Input cannot be empty.")
83
+ if D_out < 1:
84
+ raise ValueError("D_out must be positive.")
85
+ if dx <= 0:
86
+ raise ValueError("dx must be positive.")
87
+ if dk == 0:
88
+ raise ValueError("dk cannot be zero.")
89
+
90
+ n = np.arange(D)
91
+ m = np.arange(D_out)
92
+ a = dx * dk
93
+
94
+ # FORWARD
95
+ if not inverse:
96
+ # Chirps
97
+ chirp_n = np.exp(-0.5j * a * n**2)
98
+ chirp_m = np.exp(-0.5j * a * m**2)
99
+
100
+ # Coordinate-origin phases
101
+ input_phase = np.exp(-1j * k0 * dx * n)
102
+ output_phase = np.exp(-1j * x0 * dk * m)
103
+ global_phase = np.exp(-1j * k0 * x0)
104
+
105
+ # Input chirp
106
+ a_input = (f * input_phase * chirp_n)
107
+
108
+ # Convolution kernel
109
+ q = np.arange(-(D - 1),D_out)
110
+ kernel = np.exp(+0.5j * a * q**2)
111
+
112
+ # Linear convolution length
113
+ L = D + D_out - 1
114
+
115
+ # FFT padding
116
+ P = 1 << (L - 1).bit_length()
117
+
118
+ A = np.fft.fft(a_input, P, axis=-1)
119
+ B = np.fft.fft(kernel, P)
120
+ B = B.reshape((1,) * (f.ndim - 1) + (P,))
121
+ convolution = np.fft.ifft(A * B, axis=-1)
122
+
123
+ # Extract m = 0,...,D_out-1
124
+ convolution = np.take(convolution, np.arange(D - 1, D - 1 + D_out), axis=-1)
125
+
126
+ # Final result
127
+ F = (dx * global_phase * output_phase * chirp_m * convolution)
128
+ k = k0 + m * dk
129
+
130
+ return F, k
131
+
132
+
133
+ # INVERSE
134
+ else:
135
+ # Positive exponential
136
+ chirp_n = np.exp(+0.5j * a * n**2)
137
+ chirp_m = np.exp(+0.5j * a * m**2)
138
+ input_phase = np.exp(+1j * x0 * dk * n)
139
+ output_phase = np.exp(+1j * k0 * dx * m)
140
+ global_phase = np.exp(+1j * k0 * x0)
141
+ a_input = (f * input_phase * chirp_n)
142
+
143
+ # Correct convolution kernel
144
+ q = np.arange(-(D - 1), D_out)
145
+ kernel = np.exp(-0.5j * a * q**2)
146
+ L = D + D_out - 1
147
+ P = 1 << (L - 1).bit_length()
148
+ A = np.fft.fft(a_input, P, axis=-1)
149
+ B = np.fft.fft(kernel, P)
150
+ B = B.reshape((1,) * (f.ndim - 1) + (P,))
151
+ convolution = np.fft.ifft(A * B, axis=-1)
152
+ convolution = np.take(convolution, np.arange(D - 1, D - 1 + D_out), axis=-1)
153
+ result = (dk/(2*np.pi) * global_phase * output_phase * chirp_m * convolution)
154
+ x = x0 + m * dx
155
+ return result, x
156
+
157
+ def fft2_bluestein(field, dx, dy, dkx, dky, Dx_out=None, Dy_out=None, x0=0.0, y0=0.0, kx0=0.0, ky0=0.0):
158
+ """
159
+ Generalized 2D (forward) Fourier transform using Bluestein's
160
+ algorithm, applied separably along each axis.
161
+
162
+ Performs `fft_bluestein` along the last axis (x), then along the
163
+ remaining axis (y), i.e. a separable 2D chirp-z transform with
164
+ independently chosen sample spacing, sample count, and origin for
165
+ each axis.
166
+
167
+ Parameters
168
+ ----------
169
+ field : array_like
170
+ 2D input array of shape `(Dy, Dx)`.
171
+ dx : float
172
+ Input sample spacing along x.
173
+ dy : float
174
+ Input sample spacing along y.
175
+ dkx : float
176
+ Output sample spacing along kx. Must be nonzero.
177
+ dky : float
178
+ Output sample spacing along ky. Must be nonzero.
179
+ Dx_out : int, optional
180
+ Number of output samples along x. Defaults to `Dx`
181
+ (`field.shape[1]`).
182
+ Dy_out : int, optional
183
+ Number of output samples along y. Defaults to `Dy`
184
+ (`field.shape[0]`).
185
+ x0 : float, optional
186
+ Origin of the input x grid. Defaults to 0.0.
187
+ y0 : float, optional
188
+ Origin of the input y grid. Defaults to 0.0.
189
+ kx0 : float, optional
190
+ Origin of the output kx grid. Defaults to 0.0.
191
+ ky0 : float, optional
192
+ Origin of the output ky grid. Defaults to 0.0.
193
+
194
+ Returns
195
+ -------
196
+ F : np.ndarray
197
+ Transformed array, of shape `(Dy_out, Dx_out)`.
198
+ kx : np.ndarray
199
+ Output kx grid, `kx0 + m*dkx` for `m = 0, ..., Dx_out-1`.
200
+ ky : np.ndarray
201
+ Output ky grid, `ky0 + m*dky` for `m = 0, ..., Dy_out-1`.
202
+
203
+ Raises
204
+ ------
205
+ ValueError
206
+ If `field` is not 2-dimensional, or if any of the underlying
207
+ `fft_bluestein` calls raise (e.g. non-positive spacing, zero
208
+ `dk`).
209
+ """
210
+
211
+ field = np.asarray(field)
212
+ if field.ndim != 2:
213
+ raise ValueError("bluestein_fft2 expects a 2D array.")
214
+
215
+ Dy, Dx = field.shape
216
+ if Dx_out is None:
217
+ Dx_out = Dx
218
+ if Dy_out is None:
219
+ Dy_out = Dy
220
+
221
+ # x transform
222
+ F, kx = fft_bluestein(field, dx=dx, dk=dkx, D_out=Dx_out, x0=x0, k0=kx0)
223
+
224
+ # y transform
225
+ F = np.swapaxes(F, -1, -2)
226
+ F, ky = fft_bluestein(F, dx=dy, dk=dky, D_out=Dy_out, x0=y0, k0=ky0)
227
+ F = np.swapaxes(F, -1, -2)
228
+ return F, kx, ky
229
+
@@ -1,5 +1,4 @@
1
1
  import numpy as np
2
-
3
2
  from .utils import get_section
4
3
 
5
4
 
@@ -260,21 +259,5 @@ class BeamDiagnostics:
260
259
  return self
261
260
 
262
261
 
263
- """
264
- Parameters
265
- ----------
266
- center : tuple of float, optional
267
- (x, y) center of the crop window. Defaults to the beam's own centroid.
268
- std : float, optional
269
- Standard deviation used to size the crop window. Defaults to the
270
- beam's own computed std.
271
- window : float, optional
272
- Multiplier on `std` defining the half-width of the crop region.
273
262
 
274
- Returns
275
- -------
276
- Beam
277
- A new, spatially cropped Beam.
278
- """
279
- return get_crop(self, center, std, window)
280
263
 
@@ -0,0 +1,133 @@
1
+ import numpy as np
2
+ from .utils import inv_J0, inv_J1, inv_sinc
3
+
4
+ __all__ =["slm_hologram", "dmd_hologram"]
5
+
6
+
7
+
8
+ def slm_hologram(beam, x_grating:int, y_grating:int, method:str = 'bessel1', input_beam:object= None,
9
+ eps:float = np.finfo(float).eps, max_range:int = 255)-> np.ndarray:
10
+ """
11
+ Parameters
12
+ ----------
13
+ x_grating : int
14
+ Carrier grating spatial frequency along x (grid pixels).
15
+ y_grating : int
16
+ Carrier grating spatial frequency along y (grid pixels).
17
+ method : str, optional
18
+ Amplitude/phase encoding scheme used to build the hologram.
19
+ input_beam : Beam, optional
20
+ Reference illumination beam; defaults to self if None.
21
+ eps : float, optional
22
+ Small value added to avoid division by zero during encoding.
23
+ max_range : int, optional
24
+ Output gray-level range (e.g. 255 for an 8-bit SLM lookup table).
25
+
26
+ Returns
27
+ -------
28
+ ndarray
29
+ Encoded hologram pattern, shape (Dy, Dx), ready for SLM display.
30
+
31
+ Reference
32
+ ---------
33
+ [1] T. Clark, R. Offer, S. Franke-Arnold, A. Arnold, and N. Radwell,
34
+ "Comparison of beam generation techniques using a phase only spatial light modulator,"
35
+ Opt. Express 24, 6249-6264 (2016).
36
+ """
37
+
38
+ dx = (beam.x[0,1] - beam.x[0,0])
39
+ dy = (beam.y[1,0] - beam.y[0,0])
40
+ if input_beam is None:
41
+ input_field = np.exp(-((beam.x**2 + beam.y**2)/(2*np.max(beam.x))**2))
42
+ else:
43
+ input_field = input_beam.Ex
44
+ desired = beam.Ex/np.max(np.abs(beam.Ex))
45
+ lamb_x = dx*x_grating
46
+ lamb_y = dy*y_grating
47
+ Ain = np.abs(input_field)
48
+ Ain = Ain / Ain.max()
49
+ Ades = np.abs(desired)
50
+ s = np.min(Ain / (Ades + eps))
51
+ Ades = s * Ades
52
+ A_rel = Ades / Ain
53
+ phi_g = np.mod(2*np.pi*(beam.x/lamb_x + beam.y/lamb_y), 2*np.pi)
54
+ phi_relg = np.angle(desired) - np.angle(input_field) + phi_g
55
+ if method == 'simple':
56
+ H = A_rel*phi_relg
57
+ elif method == 'g_holo':
58
+ edg = desired*np.exp(1j*phi_g)
59
+ N = np.min(np.abs(input_field)/(np.abs(desired) + 1e-9))
60
+ H = np.angle(N*edg + input_field)
61
+ elif method == 'davis':
62
+ H = (1-inv_sinc(A_rel)/np.pi)*phi_relg
63
+ elif method == 'bolduc':
64
+ M = 1+inv_sinc(A_rel)/np.pi
65
+ H = M*(phi_relg - np.pi*M)
66
+ elif method == 'bessel0':
67
+ H = phi_relg + inv_J0(A_rel)*np.sin(phi_relg)
68
+ elif method == 'bessel1':
69
+ H = inv_J1(A_rel)*np.sin(phi_relg)
70
+ else:
71
+ raise ValueError('Error! method must be simple, g_holo, davis, bolduc, bessel0 or bessel1.')
72
+ H = max_range * (H - H.min()) / (H.max() - H.min())
73
+ return H.astype(np.uint8)
74
+
75
+
76
+ def dmd_hologram(beam, cx:float, cy:float, sign:int=1, input:object=None)-> np.ndarray:
77
+ """
78
+ Generate a binary-amplitude DMD hologram.
79
+
80
+ Parameters
81
+ ----------
82
+ beam : Beam
83
+ Desired output beam. Its Ex component defines the target amplitude and phase.
84
+ cx, cy : float
85
+ Carrier frequencies expressed in DMD pixel units.
86
+ sign : int
87
+ Controls the binary hologram orientation. Usually +1 or -1.
88
+ input : Beam
89
+ Optional Incident beam illuminating the DMD. If None, the incident beam is assumed
90
+ to have uniform amplitude and zero phase, reproducing the original behavior.
91
+
92
+ Returns
93
+ -------
94
+ H : ndarray
95
+ Binary DMD hologram.
96
+
97
+ Reference
98
+ ---------
99
+ [1] Scholes, Stirling, et al. "Structured light with digital micromirror devices: a guide to best practice."
100
+ Optical Engineering 59.4 (2020): 041202-041202."""
101
+
102
+ # Target field
103
+ target = beam.Ex
104
+ # Target amplitude and phase
105
+ target_amp = np.abs(target)
106
+ target_phase = np.angle(target)
107
+
108
+ if input is None:
109
+ U = target_amp / np.amax(target_amp)
110
+ phi = target_phase
111
+ else:
112
+ input_field = input.Ex
113
+ input_amp = np.abs(input_field)
114
+ input_phase = np.angle(input_field)
115
+
116
+ eps = np.finfo(float).eps
117
+ U = target_amp / np.maximum(input_amp, eps)
118
+ phi = target_phase - input_phase
119
+ max_U = np.amax(U)
120
+ if max_U > 0:
121
+ U = U / max_U
122
+
123
+ A = np.arcsin(U)
124
+ fx = cx * beam.Dx / (2 * beam.nix)
125
+ fy = cy * beam.Dy / (2 * beam.niy)
126
+ G = fx * beam.x + fy * beam.y
127
+
128
+ H = 0.5 + sign * 0.5 * np.sign( np.cos(phi + 2 * np.pi * G) - np.cos(A) )
129
+ return H
130
+
131
+
132
+
133
+