@@ -53,70 +53,164 @@ function _prepare_shifted_angular_spectrum(field::AbstractArray{CT}, z, λ, L,
53
53
shift = txy .* z
54
54
55
55
ya = similar (field_new, real (eltype (field)), (size (field_new, 1 ), 1 ))
56
- ya .= (fftpos (L_new[1 ], size (field_new, 1 ), CenterFT)) .+ shift[1 ]
56
+ Zygote . @ignore ya .= (fftpos (L_new[1 ], size (field_new, 1 ), CenterFT)) .+ shift[1 ]
57
57
xa = similar (field_new, real (eltype (field)), (1 , size (field_new, 2 )))
58
- xa .= (fftpos (L_new[2 ], size (field_new, 2 ), CenterFT))' .+ shift[2 ]
58
+ Zygote . @ignore xa .= (fftpos (L_new[2 ], size (field_new, 2 ), CenterFT))' .+ shift[2 ]
59
59
60
60
ramp_before = ifftshift (exp .(1im .* 2 .* T (π) ./ λ .* (sxy[2 ] .* x .+ sxy[1 ] .* y)), (1 ,2 ))
61
61
ramp_after = ifftshift (exp .(1im .* 2 .* T (π) ./ λ .* (sxy[2 ] .* xa .+ sxy[1 ] .* ya)), (1 ,2 ))
62
62
return (;field_new, H, W, fftdims, ramp_before, ramp_after)
63
63
end
64
64
65
65
66
+ function shifted_angular_spectrum (field:: AbstractArray{CT, 2} , z, λ, L, α;
67
+ padding= true , pad_factor= 2 ,
68
+ bandlimit= true ,
69
+ bandlimit_border= (0.8 , 1.0 )) where {CT<: Complex }
70
+
71
+ @assert size (field, 1 ) == size (field, 2 ) " input field needs to be quadradically shaped and not $(size (field, 1 )) , $(size (field, 2 )) "
72
+
73
+ (; field_new, H, W, fftdims, ramp_before, ramp_after) = _prepare_shifted_angular_spectrum (field, z, λ, L, real (CT).(α); padding,
74
+ pad_factor, bandlimit, bandlimit_border)
75
+
76
+ # propagate field
77
+ field_new_is = ifftshift (field_new, fftdims) ./ (ramp_before)
78
+ field_out = fftshift (ramp_after .* ifft (fft (field_new_is, fftdims) .* H .* W, fftdims), fftdims)
79
+ field_out_cropped = padding ? crop_center (field_out, size (field)) : field_out
80
+ shift = z .* tan .(α) ./ L
81
+ # return final field and some other variables
82
+ return field_out_cropped, (; L, shift)
83
+ end
84
+
85
+
86
+ # highly optimized version with pre-planning
87
+ struct ShiftedAngularSpectrum{A, T, T2, P, R}
88
+ HW:: A
89
+ buffer:: A
90
+ buffer2:: A
91
+ L:: T
92
+ shift:: T2
93
+ p:: P
94
+ padding:: Bool
95
+ pad_factor:: Int
96
+ ramp_before:: R
97
+ ramp_after:: R
98
+ end
99
+
100
+
66
101
"""
67
- shifted_angular_spectrum (field, z, λ, L, α; kwargs...)
102
+ ShiftedAngularSpectrum (field, z, λ, L, α; kwargs...)
68
103
69
- Returns the electrical field with physical length `L` and wavelength `λ` propagated with the shifted angular spectrum
104
+ Returns a method to propagate the electrical field with physical length `L` and wavelength `λ` with the shifted angular spectrum
70
105
method of plane waves (AS) by the propagation distance `z`.
71
- `α` is the shift angle with respect to the optical axis.
106
+ `α` should be a tuple containing the offset angles with respect to the optical axis.
72
107
73
- This method is efficient but to avoid recalculating some arrays (such as the phase kernel), see [`ShiftedAngularSpectrum`](@ref).
74
108
75
109
# Arguments
76
110
* `field`: Input field
77
111
* `z`: propagation distance. Can be a single number or a vector of `z`s (Or `CuVector`). In this case the returning array has one dimension more.
78
112
* `λ`: wavelength of field
79
113
* `L`: field size (can be a scalar or a tuple) indicating field size
80
- * `α` is the shift angle with respect to the optical axis.
114
+ * `α` is the tuple of shift angles with respect to the optical axis.
81
115
82
116
83
117
# Keyword Arguments
84
- * `padding=true`: applies padding to avoid convolution wraparound
85
118
* `pad_factor=2`: padding of 2. Larger numbers are not recommended since they don't provide better results.
86
119
* `bandlimit=true`: applies the bandlimit to avoid circular wraparound due to undersampling
87
120
of the complex propagation kernel [1]
88
- * `bandlimit_border=(0.8, 1) `: applies a smooth bandlimit cut-off instead of hard-edge.
89
-
121
+ * `extract_ramp=true `: divides the field by phase ramp `exp.(1im * 2π / λ * (sin(α[2]) .* x .+ sin(α[1]) .* y))` and multiplies after
122
+ propagation the ramp (with new real space coordinates) back to the field
90
123
91
124
# Examples
92
125
```jldoctest
93
126
julia> field = zeros(ComplexF32, (4,4)); field[3,3] = 1
94
- ```
95
127
128
+ julia> AS, _ = ShiftedAngularSpectrum(field, 100e-6, 633e-9, 100e-6, (deg2rad(10), 0));
129
+
130
+ julia> AS(field)
131
+ (ComplexF32[1.5269792f-5 + 1.7594219f-5im -3.996831f-5 - 7.624799f-5im -0.0047351345f0 + 0.002100923f0im -3.996831f-5 - 7.624799f-5im; -8.294997f-5 - 1.8230454f-5im 0.00028230582f0 + 8.1745195f-5im 0.0051693693f0 - 0.016958509f0im 0.00028230582f0 + 8.1745195f-5im; 0.0029884572f0 + 0.0040671355f0im -0.009686601f0 - 0.014245203f0im -0.82990384f0 + 0.5566719f0im -0.009686601f0 - 0.014245203f0im; -1.4191573f-7 + 9.41665f-5im 2.111472f-5 - 0.00031620878f0im -0.017670793f0 - 0.0014212304f0im 2.111472f-5 - 0.00031620878f0im], (L = 0.0001, shift = (0.17632698070846498, 0.0)))
132
+ ```
96
133
97
134
# References
98
135
* Matsushima, Kyoji. "Shifted angular spectrum method for off-axis numerical propagation." Optics Express 18.17 (2010): 18453-18463.
99
136
"""
100
- function shifted_angular_spectrum (field:: AbstractArray{CT, 2} , z, λ, L, α;
137
+ function ShiftedAngularSpectrum (field:: AbstractArray{CT, N} , z:: Number , λ, L, α;
138
+ extract_ramp= true ,
101
139
padding= true , pad_factor= 2 ,
102
140
bandlimit= true ,
103
- bandlimit_border= (0.8 , 1.0 )) where {CT<: Complex }
104
-
105
- @assert size (field, 1 ) == size (field, 2 ) " input field needs to be quadradically shaped and not $(size (field, 1 )) , $(size (field, 2 )) "
106
-
107
- (; field_new, H, W, fftdims, ramp_before, ramp_after) =
141
+ bandlimit_border= (0.8 , 1 )) where {CT, N}
142
+
143
+ (; field_new, H, W, fftdims, ramp_before, ramp_after) =
108
144
_prepare_shifted_angular_spectrum (field, z, λ, L, real (CT).(α); padding,
109
145
pad_factor, bandlimit, bandlimit_border)
146
+
147
+
148
+ buffer2 = similar (field, complex (eltype (field_new)), (size (field_new, 1 ), size (field_new, 2 )))
149
+ buffer = copy (buffer2)
150
+
151
+ p = plan_fft! (buffer, (1 , 2 ))
152
+ H .= H .* W
153
+ HW = H
154
+ shift = z .* tan .(α) ./ L
155
+ if ! extract_ramp
156
+ ramp_before = nothing
157
+ ramp_after = nothing
158
+ end
110
159
111
- # propagate field
112
- field_new_is = ifftshift (field_new, fftdims) ./ (ramp_before)
113
- # ramp_after = 1
114
- field_out = fftshift (ramp_after .* ifft (fft (field_new_is, fftdims) .* H .* W, fftdims), fftdims)
115
- field_out_cropped = padding ? crop_center (field_out, size (field)) : field_out
116
- shift = z .* tan .(α) ./ L
117
- # return final field and some other variables
118
- return field_out_cropped, (; L, shift)
160
+
161
+ return ShiftedAngularSpectrum {typeof(H), typeof(L), typeof(shift), typeof(p), typeof(ramp_before)} (HW,
162
+ buffer, buffer2, L, shift, p, padding, pad_factor, ramp_before, ramp_after), (;L, shift)
163
+ end
164
+
165
+
166
+
167
+ """
168
+ (shifted_as::ShiftedAngularSpectrum)(field)
169
+
170
+ Uses the struct to efficiently store some pre-calculated objects.
171
+ Propagate the field.
172
+ """
173
+ function (as:: ShiftedAngularSpectrum{A, T, T2, P, R} )(field) where {A,T,T2,P,R}
174
+ fill! (as. buffer2, 0 )
175
+ field_new = set_center! (as. buffer2, field, broadcast= true )
176
+ ifftshift! (as. buffer, field_new, (1 , 2 ))
177
+ if ! (R === Nothing)
178
+ as. buffer ./= as. ramp_before
179
+ end
180
+ field_imd = as. p * as. buffer
181
+ field_imd .*= as. HW
182
+ field_imd = inv (as. p) * field_imd
183
+ if ! (R === Nothing)
184
+ field_imd .*= as. ramp_after
185
+ end
186
+ field_out = fftshift! (as. buffer2, field_imd, (1 , 2 ))
187
+ field_out_cropped = as. padding ? crop_center (field_out, size (field), return_view= true ) : field_out
188
+ return field_out_cropped, (; as. L, as. shift)
119
189
end
120
190
121
191
122
- scalable_angular_spectrum (field:: AbstractArray , z, λ, L; kwargs... ) = throw (ArgumentError (" Provided field needs to have a complex elementype" ))
192
+ function ChainRulesCore. rrule (as:: ShiftedAngularSpectrum{A, T, T2, P, R} , field) where {A,T,T2,P,R}
193
+ field_and_tuple = as (field)
194
+ function as_pullback (ȳ)
195
+ f̄ = NoTangent ()
196
+ y2 = ȳ. backing[1 ]
197
+
198
+ fill! (as. buffer2, 0 )
199
+ field_new = as. padding ? set_center! (as. buffer2, y2, broadcast= true ) : y2
200
+ ifftshift! (as. buffer, field_new, (1 , 2 ))
201
+ if ! (R === Nothing)
202
+ as. buffer .*= conj .(as. ramp_after)
203
+ end
204
+ field_imd = as. p * as. buffer
205
+ field_imd .*= conj .(as. HW)
206
+
207
+ field_imd = inv (as. p) * field_imd
208
+ if ! (R === Nothing)
209
+ field_imd ./= conj .(as. ramp_before)
210
+ end
211
+ field_out = fftshift! (as. buffer2, field_imd, (1 , 2 ))
212
+ field_out_cropped = as. padding ? crop_center (field_out, size (field), return_view= true ) : field_out
213
+ return f̄, field_out_cropped
214
+ end
215
+ return field_and_tuple, as_pullback
216
+ end
0 commit comments