Skip to content

Commit 7b385d7

Browse files
authored
Point Spread Functions (#10)
* Implement plane wave interaction with Photodetector * Fix review points * add point source * add CollimatedSource * up vn to 0.8.5 * fix spot detector docs * export reset_detector! * add pd interact3d comments * PSFDetector (warning: WIP) * reset_photodetector! -> reset! and optimize PSF code for performance * Fix syntax bugs * spotdetector reset! adjust * psf intensity bug fix * opl docs fix * rename reset! to empty! (dispatch base) * up vn to 0.9 for breaking name change * stricter refraction3d testset for (very) small angles * add auto-limits (WIP) to PSFDetector * Get rid of most allocations * Use Auto-Diff for SDF gradients to fix issue #11 * Remove temporary issue fix * add testset for jesus bug * add BeamGroup testset * rename Groups.jl to ObjectGroups.jl * beam group docs * fix typo * fix docstring links * rm create_spot_diagram * Add primitive means of Strehl normalization * Improve strehl ratio and centering * Slay issue #11 again (happy Easter! It should have stayed dead) and fix specialization issues * Remove photodetector PSF implementation * Remove abs, match the sdf function behaviour * Fix axis labeling and introduce a shift option for the user * Adds the possibility to set a custom grid limit * Remove Strehl, some docs * beam group docs * extended beam group tests * typo * minor changes 1 * minor changes 2 * import position * export psfd (again) * not a software * fix some merge issues with regards to PR9 * Add a testset for PSFs and the UniformDiscSource * Update UniformDiscSource doc strings * Finish docs of UniformDiscSource * Add docs * rm BeamletOptics namespace when unnecessary * rm solved field from psf detector (no purpose) * typos * UDSource docs update * test psfd empty! * fix num_rays kwarg in collimated sc
1 parent 581fd22 commit 7b385d7

File tree

16 files changed

+589
-53
lines changed

16 files changed

+589
-53
lines changed

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "BeamletOptics"
22
uuid = "c387492b-ffec-4e51-9a46-bd230226031c"
33
authors = ["Hugo Uittenbosch <hugo.uittenbosch@dlr.de>, Oliver Kliebisch <oliver.kliebisch@dlr.de> and Thomas Dekorsy <thomas.dekorsy@dlr.de>"]
4-
version = "0.10.0"
4+
version = "0.10.1"
55

66
[deps]
77
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
@@ -44,4 +44,4 @@ Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
4444
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
4545

4646
[targets]
47-
test = ["Aqua", "Test"]
47+
test = ["Aqua", "Test"]

docs/src/assets/beam_renders/collimated_sc.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,23 @@ cs_view = [
2525

2626
set_view(cs_ax, cs_view)
2727
save("collimated_beam_source.png", cs_fig; px_per_unit=8, update = false)
28+
29+
## uniform disc source
30+
cs = UniformDiscSource([0,0,0], [0,1,0], 15e-3; num_rays=100)
31+
32+
cs_fig = Figure(; size=(600,200))
33+
display(cs_fig)
34+
cs_ax = LScene(cs_fig[1,1])
35+
hide_axis(cs_ax)
36+
37+
render!(cs_ax, cs, show_pos=true, flen=0.05, color=:red, render_every=1)
38+
39+
cs_view = [
40+
0.26513 0.964213 7.68482e-16 -0.0180273
41+
0.0488896 -0.0134432 0.998714 0.00108822
42+
0.962972 -0.264789 -0.0507042 -0.025119
43+
0.0 0.0 0.0 1.0
44+
]
45+
46+
set_view(cs_ax, cs_view)
47+
save("collimated_uniform_beam_source.png", cs_fig; px_per_unit=8, update = false)

docs/src/assets/mi_assets/michelson_plots.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,13 @@ heat2 = Axis(fringes_fig[1, 2], xlabel="x [mm]", ylabel="y [mm]", title="After r
127127
hidedecorations!(heat1)
128128
hidedecorations!(heat2)
129129

130-
hm = heatmap!(heat1, pd.x*1e3, pd.y*1e3, BeamletOptics.intensity(pd), colormap=:viridis)
130+
hm = heatmap!(heat1, pd.x*1e3, pd.y*1e3, intensity(pd), colormap=:viridis)
131131

132132
zrotate3d!(m1, 1e-3)
133133
empty!(pd)
134134
solve_system!(system, beam)
135135

136-
hm = heatmap!(heat2, pd.x*1e3, pd.y*1e3, BeamletOptics.intensity(pd), colormap=:viridis)
136+
hm = heatmap!(heat2, pd.x*1e3, pd.y*1e3, intensity(pd), colormap=:viridis)
137137

138138
save("mi_fringes.png", fringes_fig, px_per_unit=4)
139139

docs/src/assets/photodetector_showcase.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ solve_system!(system, g2)
2121
## render fringes
2222
fringes_fig = Figure()
2323
heat = Axis(fringes_fig[1, 1], xlabel="x [mm]", ylabel="y [mm]", aspect=1)
24-
hm = heatmap!(heat, pd.x*1e3, pd.y*1e3, BeamletOptics.intensity(pd), colormap=:viridis)
24+
hm = heatmap!(heat, pd.x*1e3, pd.y*1e3, intensity(pd), colormap=:viridis)
2525
cb = Colorbar(fringes_fig[1, 2], hm, label="Intensity [W/m²]")
2626

2727
## render system
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using GLMakie, BeamletOptics
2+
3+
const mm = 1e-3
4+
5+
l = 1e-3
6+
R1 = 100e-3
7+
R2 = Inf
8+
d = 25.4e-3
9+
n = 1.5
10+
λ = 1e-6
11+
12+
cs = UniformDiscSource([0, -10mm, 0], [0, 1, 0], 15e-3, λ)
13+
lens = SphericalLens(R1, R2, l, d, x -> n)
14+
15+
psfd = PSFDetector(10e-3)
16+
translate3d!(psfd, [0, 200mm + 0.13mm, 0])
17+
18+
sys = System([lens, psfd])
19+
20+
solve_system!(sys, cs)
21+
22+
x, z, I_num = intensity(psfd; n=500, crop_factor=10)
23+
24+
fringes_fig = Figure()
25+
srf = Axis3(fringes_fig[1, 1], xlabel="x [µm]", ylabel="z [µm]", elevation=0.9, azimuth=2.372)
26+
hm = surface!(srf, x*1e6, z*1e6, I_num, colormap=:viridis)
27+
28+
hidezdecorations!(srf)
29+
30+
##
31+
k = -0.675
32+
d = 75.0e-3
33+
l = 15e-3
34+
radius = 76.68e-3
35+
A = [0*(1e3)^1, 2.7709219e-8*(1e3)^3, 6.418186e-13*(1e3)^5, -1.5724014e-17*(1e3)^7, -2.7768768e-21*(1e3)^9, -2.590162e-25*(1e3)^11]
36+
AL75150 = Lens(
37+
EvenAsphericalSurface(radius, d, k, A),
38+
l,
39+
n -> 1.5006520430
40+
)
41+
42+
xrotate3d!(AL75150, deg2rad(-0.5))
43+
44+
pd = PSFDetector(15e-3)
45+
46+
translate3d!(pd, [0, 158.1779e-3, 0.0])
47+
system = System([AL75150, pd])
48+
49+
ps = UniformDiscSource([0, -0.1, 0], [0,1,0], 0.8*d, 1550e-9)
50+
51+
solve_system!(system, ps)
52+
53+
asph_fig = Figure(size=(800, 500))
54+
55+
xs, ys, _intensity = intensity(pd, n=801, x_min=-25e-6, x_max=25e-6, z_min=-25e-6, z_max=25e-6, z0_shift=-50e-6)
56+
57+
ax = Axis(asph_fig[1,1], xlabel="x [µm]", ylabel="z [µm]", aspect=1)
58+
59+
hm = heatmap!(xs*1e6, ys*1e6, _intensity)

docs/src/assets/spotdetector_showcase.jl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ cs = CollimatedSource([0,-50mm,0], [0,1,0], aperture, 1e-6; num_rings, num_rays)
3131

3232
t1 = @timed solve_system!(system, cs)
3333

34-
for i = 1:50:length(BeamletOptics.beams(cs))
35-
render!(system_ax, BeamletOptics.beams(cs)[i], color=:blue, show_pos=true)
36-
end
34+
render!(system_ax, cs, color=:blue, show_pos=true, render_every=50)
3735

3836
## render diagram
3937
spot_fig = Figure(size=(600,400))

docs/src/basics/beams.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ CollimatedSource(::AbstractArray{<:Real}, ::AbstractArray{<:Real}, ::Real, ::Rea
4646

4747
![Collimated group of beams](collimated_beam_source.png)
4848

49+
A special constructor called [`UniformDiscSource`](@ref) is available, which offers an equal-area
50+
sampling (Fibonnaci-pattern) sampling and is thus favorable in situations where the weighting of the
51+
individual beams becomes important, e.g. for calculating a point spread function using [`PSFDetector`](@ref).
52+
53+
```@docs; canonical=false
54+
UniformDiscSource
55+
```
56+
57+
![Collimated uniform group of beams](collimated_uniform_beam_source.png)
58+
4959
### Point beam source
5060

5161
The `PointSource` type is used to model emission from a spatially localized source that radiates [`Beam`](@ref)s in a range of directions. This is commonly used to simulate conical emission patterns, such as light emerging from a fiber tip or a light source for a lens objective with a known focal distance. You can specify the origin and a propagation direction, which are then used to construct the monochromatic `PointSource`.

docs/src/components/detectors.md

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ save("pd_showcase.png", detector_fig, px_per_unit=4)
2626
nothing
2727
```
2828

29-
The `interact3d` model of the [`Photodetector`](@ref) can store complex electric field (E-field) values from intersecting [`GaussianBeamlet`](@ref)s, enabling the reconstruction of spatial intensity distribution across its active surface. This data can be used to calculate e.g. beam interference patterns via the [`BeamletOptics.intensity`](@ref) function. The [`BeamletOptics.optical_power`](@ref) method can be used in order to obtain the total optical power at the detector. Below a rendered example of a detector model ([FDS010](https://www.thorlabs.com/thorproduct.cfm?partnumber=FDS010)) can be seen. The detector active area is marked in blue (1x1 mm²).
29+
The `interact3d` model of the [`Photodetector`](@ref) can store complex electric field (E-field) values from intersecting [`GaussianBeamlet`](@ref)s, enabling the reconstruction of spatial intensity distribution across its active surface. This data can be used to calculate e.g. beam interference patterns via the [`intensity`](@ref) function. The [`BeamletOptics.optical_power`](@ref) method can be used in order to obtain the total optical power at the detector. Below a rendered example of a detector model ([FDS010](https://www.thorlabs.com/thorproduct.cfm?partnumber=FDS010)) can be seen. The detector active area is marked in blue (1x1 mm²).
3030

3131
![Photodetector showcase](pd_showcase.png)
3232

@@ -62,4 +62,116 @@ Below an optical system consisting of a collection of collimated [`Beam`](@ref)s
6262

6363
The beam bundle used to generate the spot diagram was created via the [`CollimatedSource`](@ref) constructor. The resulting spot diagram of the lens shown above is visualized below.
6464

65-
![Spot diagram showcase](spot_diagram_showcase.png)
65+
![Spot diagram showcase](spot_diagram_showcase.png)
66+
67+
## Point-spread-function detector type
68+
69+
!!! warning "Experimental feature"
70+
The point spread function estimation is a highly experimental feature. It does not use
71+
pupils (yet) but merely uses superposition of the ray-attached plane-waves. While this
72+
gives qualitatively sound results, it requires good sampling of the problem to obtain
73+
quantitatively good results. Currently no Strehl-ratio is calculated due to that.
74+
75+
The package offers a simple method to estimate the point spread function of a system. It is
76+
currently limited and requires careful assessment by the user, if the results are to be trusted.
77+
78+
To analyze the PSF of a imaging system a [`PSFDetector`](@ref) is added to the system at the plane
79+
and orientation where the PSF is requested. This is the same approach as for the other detector types.
80+
81+
```@docs; canonical=false
82+
PSFDetector(::Real)
83+
```
84+
85+
The intensity map together with the coordinate system of the detector can be retrieved after solving the system by calling the [`intensity`](@ref) function.
86+
87+
```@docs; canonical=false
88+
intensity(::PSFDetector)
89+
```
90+
91+
!!! warning
92+
When dealing with a collimated source as the input to your optical system, where you want to calculate the PSF, **DO NOT** use the [`CollimatedSource`](@ref) beam group directly but instead use the [`UniformDiscSource`](@ref) constructor. This function returns a `CollimatedSource` with an equal-area sampling, which correctly weights the outer beams in relation to the inner beams. Otherwise the results might be wrong.
93+
94+
95+
```@eval
96+
asset_dir = joinpath(@__DIR__, "..", "assets")
97+
98+
Base.include(@__MODULE__, joinpath(asset_dir, "psfdetector_showcase.jl"))
99+
100+
save("psf_airy_showcase.png", fringes_fig, px_per_unit=4)
101+
save("psf_tilted_showcase.png", asph_fig, px_per_unit=4)
102+
103+
nothing
104+
```
105+
106+
### Airy-Disc Example
107+
108+
This is a classic example where a collimated circular beam is imaged onto a point by a singlet lens.
109+
Due to the finite size of the aperture stop (in this case given by the 15 mm size of the beam), the diffraction
110+
limited intensity pattern is given by the Airy-disc:
111+
112+
```math
113+
I(r)=I_0\!\left[\frac{2J_1\!\bigl(\pi D r/(\lambda f)\bigr)}{\pi D r/(\lambda f)}\right]^2
114+
```
115+
116+
With ``r`` the radius from the origin, ``I_0`` the maximum intensity, ``J_1`` the Bessel function of the first kind of order one, ``D`` the aperture width, ``\lambda`` the wavelength and the focal length ``f``.
117+
118+
```julia
119+
# example parameters
120+
l = 1e-3
121+
R1 = 100e-3
122+
R2 = Inf
123+
d = 25.4e-3
124+
n = 1.5
125+
λ = 1e-6
126+
127+
# generate uniform source, lens and PSF detector
128+
cs = UniformDiscSource([0, -10mm, 0], [0, 1, 0], 15e-3, λ)
129+
lens = SphericalLens(R1, R2, l, d, x -> n)
130+
psfd = PSFDetector(10e-3)
131+
132+
# shift detector into focus
133+
translate3d!(psfd, [0, 200mm + 0.13mm, 0])
134+
135+
# build system
136+
sys = System([lens, psfd])
137+
138+
solve_system!(sys, cs)
139+
140+
# retrieve intensity
141+
x, z, I_num = intensity(psfd; n=500, crop_factor=5)
142+
```
143+
144+
Visualizing the result yields the expected Airy-disk pattern.
145+
146+
![Airy disc PSF](psf_airy_showcase.png)
147+
148+
### Coma and Astigmatism Example
149+
150+
In this example, an aspheric lens images the collimated source onto a point but is tilted around the x-axis by 0.5 degrees.
151+
This results in aberrations distorting the stigmatic imaging and leading to coma and astigmatism.
152+
153+
```julia
154+
k = -0.675
155+
d = 75.0e-3
156+
l = 15e-3
157+
radius = 76.68e-3
158+
A = [0*(1e3)^1, 2.7709219e-8*(1e3)^3, 6.418186e-13*(1e3)^5, -1.5724014e-17*(1e3)^7, -2.7768768e-21*(1e3)^9, -2.590162e-25*(1e3)^11]
159+
AL75150 = Lens(
160+
EvenAsphericalSurface(radius, d, k, A),
161+
l,
162+
n -> 1.5006520430
163+
)
164+
165+
xrotate3d!(AL75150, deg2rad(-0.5))
166+
167+
pd = PSFDetector(15e-3)
168+
169+
translate3d!(pd, [0, 158.1779e-3, 0.0])
170+
system = System([AL75150, pd])
171+
172+
ps = UniformDiscSource([0, -0.1, 0], [0,1,0], 0.8*d, 1550e-9)
173+
174+
solve_system!(system, ps)
175+
```
176+
177+
![Tilted asphere PSF](psf_tilted_showcase.png)

docs/src/tutorials/michelson.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,14 +216,14 @@ fringes_fig = Figure()
216216
heat1 = Axis(fringes_fig[1, 1], aspect=1)
217217
heat2 = Axis(fringes_fig[1, 2], aspect=1)
218218

219-
hm = heatmap!(heat1, pd.x, pd.y, BeamletOptics.intensity(pd), colormap=:viridis)
219+
hm = heatmap!(heat1, pd.x, pd.y, intensity(pd), colormap=:viridis)
220220

221221
# rotate m1, reset pd field data, resolve system
222222
zrotate3d!(m1, 1e-3)
223223
empty!(pd)
224224
solve_system!(system, beam)
225225

226-
hm = heatmap!(heat2, pd.x, pd.y, BeamletOptics.intensity(pd), colormap=:viridis)
226+
hm = heatmap!(heat2, pd.x, pd.y, intensity(pd), colormap=:viridis)
227227
```
228228

229229
By experimenting with different mirror angles, arm lengths, or beamsplitter properties, you can observe how interference fringes evolve and gain insights into the stability and sensitivity of the interferometric setup. This can be important to optimize alignment and achieve high contrast fringes.

src/BeamGroups.jl

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ The following inputs and arguments can be used to configure the [`PointSource`](
4545
- `num_rings`: number of concentric beam rings, default is 10
4646
- `num_rays`: total number of rays in the source, default is 100x num_rings
4747
48-
!!! warning
48+
!!! warning
4949
The orthogonal basis vectors for the beam generation are generated randomly.
5050
"""
5151
function PointSource(
@@ -138,15 +138,15 @@ The following inputs and arguments can be used to configure the [`CollimatedSour
138138
139139
- `pos`: center beam starting position
140140
- `dir`: center beam starting direction
141-
- `diameter`: outer beam bundle diameter in [m]
141+
- `diameter`: outer beam bundle diameter in [m]
142142
- `λ = 1e-6`: wavelength in [m], default val. is 1000 nm
143143
144144
## Keyword Arguments
145145
146146
- `num_rings`: number of concentric beam rings, default is 10
147147
- `num_rays`: total number of rays in the source, default is 100x num_rings
148148
149-
!!! warning
149+
!!! warning
150150
The orthogonal basis vectors for the beam generation are generated randomly.
151151
"""
152152
function CollimatedSource(
@@ -192,4 +192,54 @@ function CollimatedSource(
192192
end
193193
end
194194
return CollimatedSource(beams, T(diameter))
195-
end
195+
end
196+
197+
"""
198+
UniformDiscSource(pos, dir, diameter, λ; num_rays=1_000)
199+
200+
Generates a ray fan with *equal area per ray* across a circular pupil
201+
using the deterministic sunflower (Fibonacci) pattern.
202+
203+
!!! note
204+
This is merely a [`CollimatedSource`](@ref) constructor which uses Fibonacci sampling
205+
instead of a linear grid.
206+
207+
# Arguments
208+
209+
The following inputs and arguments can be used to configure the underlying [`CollimatedSource`](@ref):
210+
211+
## Inputs
212+
213+
- `pos`: center beam starting position
214+
- `dir`: center beam starting direction
215+
- `diameter`: outer beam bundle diameter in [m]
216+
- `λ = 1e-6`: wavelength in [m]
217+
218+
## Keyword Arguments
219+
220+
- `num_rays=1000`: total number of rays in the source
221+
"""
222+
function UniformDiscSource(
223+
pos::AbstractArray{P},
224+
dir::AbstractArray{D1},
225+
diameter::D2,
226+
λ::L=1e-6;
227+
# kwargs
228+
num_rays::Int=1_000
229+
) where {P<:Real, D1<:Real, D2<:Real, L<:Real}
230+
T = promote_type(P, D1, D2, L)
231+
R = diameter / 2
232+
φ0 = 2π / (1 + 5) # golden angle
233+
beams = Vector{Beam{T,Ray{T}}}(undef, num_rays)
234+
# orthogonal basis in the pupil plane
235+
e1 = normal3d(dir)
236+
e2 = normalize(cross(dir, e1))
237+
for k in 0:num_rays-1
238+
ρ = ((k + 0.5)/num_rays) # equal-area radius
239+
φ = k * φ0
240+
r = R * ρ
241+
x = r * cos(φ) * e1 + r * sin(φ) * e2
242+
beams[k+1] = Beam(pos + x, dir, λ)
243+
end
244+
return CollimatedSource(beams, T(diameter))
245+
end

0 commit comments

Comments
 (0)