Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e490436
add Detector.jl
Sep 15, 2025
56f5fbc
flesh out hit types
Sep 15, 2025
4678f0a
add spot_diagram for testing
Sep 15, 2025
e82ffb2
change wave_number to wavenumber
Sep 16, 2025
3320cb5
more "ideas"
Sep 16, 2025
380d5c5
use getter fcts
Sep 16, 2025
9feccec
gaussian beamlet intensity test fct
Sep 17, 2025
1855131
switch gb eval loop order
Sep 22, 2025
0fef229
dispatch calc_local_pos and calc_local_lims based on detector hit type
Sep 22, 2025
b415624
fix issue #36
Sep 23, 2025
2f7b526
rename some hit getters
Sep 23, 2025
b2b98a0
prepare calc local lims and pos for gb integration
Sep 23, 2025
8bfaca6
add ellipse helper fct
Sep 23, 2025
e294978
add calc_local_pos and "spot diagram" for GaussianBeamlets
Sep 23, 2025
0142c3d
calc_local_lims for GaussianBeamlet
Sep 23, 2025
aa945da
add some docs
Sep 24, 2025
5d39f02
add ellipse tests
Sep 24, 2025
c111996
add Detector type docs
Sep 24, 2025
31a7d5e
up vn to 0.11 due to breaking detector changes
Sep 24, 2025
902ed3f
replace Photodetector with new API
Sep 24, 2025
a59d1f4
start with splitting up runtests.jl
Sep 25, 2025
b01a3da
fix spot diagram x-axis flip
Sep 25, 2025
23f3d33
more runtests splitting
Sep 25, 2025
ba9e586
even more runtests splitting
Sep 25, 2025
08802a0
replace Spotdetector and PSFDetector with new API
Sep 25, 2025
fb579a3
remove double Interference tests
Sep 25, 2025
a473f1c
finalize runtests splitting
Sep 26, 2025
e8f31b6
update countlines
Sep 26, 2025
4292d30
add hit docstrings
Sep 26, 2025
f66d38a
rm old detector files
Sep 26, 2025
1bfdbd0
rm stale refs in abstract pd docstring
Sep 26, 2025
134ad98
fix psf orientation bug
Sep 26, 2025
208b570
update docs p1
Sep 26, 2025
f6d7b3e
Make normal3d deterministic
TacHawkes Sep 27, 2025
af5e3e7
return multithreading to gb intensity calc.
Sep 29, 2025
f5f7756
add reentrant lock to detector push!
Sep 29, 2025
4fb6cf8
add spot diagram docstring and error msg
Sep 29, 2025
87da72e
update MI tutorial
Sep 29, 2025
9370027
update detectors.md
Sep 29, 2025
443fbb1
final changes
Sep 29, 2025
f1a4fa0
Update detector docs
TacHawkes Sep 29, 2025
b05f3d6
fix type bug
Oct 1, 2025
68a39b7
use Union{all implemented hit types} instead of <:AbstractDetectorHit…
Oct 1, 2025
2f62433
Try to improve inference
TacHawkes Oct 2, 2025
96e39e1
add center calc. algorithm types for type stability
Oct 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "BeamletOptics"
uuid = "c387492b-ffec-4e51-9a46-bd230226031c"
authors = ["Hugo Uittenbosch <hugo.uittenbosch@dlr.de>, Oliver Kliebisch <oliver.kliebisch@dlr.de> and Thomas Dekorsy <thomas.dekorsy@dlr.de>"]
version = "0.10.2"
version = "0.11.0"

[deps]
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
Expand Down
2 changes: 1 addition & 1 deletion docs/src/api/conventions.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Conventions

In order to ensure implicit and explicit compliance with large parts of the API, the following conventions need to be used. Below you can find some essential conventions that need to be followed. For specific components or parts of the code base, refer to the documentation if certain guidelines need to be followed.
In order to ensure implicit and explicit compliance with large parts of the API, the following conventions need to be used unless specified otherwise. Below you can find some essential conventions. For specific components or parts of the code base, refer to the respective documentation if certain guidelines need to be followed.

!!! warning
Failure to comply with the following conventions can lead to spurious effects and silent bugs when using the API of this package!
Expand Down
2 changes: 1 addition & 1 deletion docs/src/api/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ BeamletOptics.retrace_system!

## CPU and GPU support

Parallizing the execution of a [`solve_system!`](@ref) call on the CPU is straight-forward for systems that do not feature objects which can be mutated during runtime, e.g. detectors like the [`Photodetector`](@ref). For each beam or ray the solution is independent and the solver can run on multiple threads. Special consideration needs to be taken when implementing mutable elements as mentioned above, since multiple threads might be able to access the underlying memory, leading to race conditions. Specifically, this means ensuring atomic write and read access.
Parallizing the execution of a [`solve_system!`](@ref) call on the CPU is straight-forward for systems that do not feature objects which can be mutated during runtime, e.g. detectors like the [`Detector`](@ref). For each beam or ray the solution is independent and the solver can run on multiple threads. Special consideration needs to be taken when implementing mutable elements as mentioned above, since multiple threads might be able to access the underlying memory, leading to race conditions. Specifically, this means ensuring e.g. atomic write and read access.

With respect to GPU acceleration, this is not the case. Currently, all available implementations of [`solve_system!`](@ref) are highly branching algorithms which can not be implemented in a parallized way easily. This will most likley require a specific new subtype of the [`BeamletOptics.AbstractSystem`](@ref) with determinable sequential properties. This is not a development goal as of the writing of this section.

Expand Down
11 changes: 8 additions & 3 deletions docs/src/assets/detector_assets/photodetector_showcase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ using GLMakie, BeamletOptics
GLMakie.activate!(; ssao=true)

##
pd = Photodetector(1e-3, 1000)
pd = Detector(1e-3)
pd_body = MeshDummy(joinpath(@__DIR__, "FDS010.stl"))
zrotate3d!(pd_body, π)
translate3d!(pd, [0,-3e-3,0])
Expand All @@ -18,13 +18,18 @@ g2 = GaussianBeamlet([0,-20e-3,0], [0,1,0], 532e-9, 5e-4)
solve_system!(system, g1)
solve_system!(system, g2)

x, y, I = intensity(pd, n=1000)
spots = spot_diagram(pd)

## render fringes
fringes_fig = Figure()
heat = Axis(fringes_fig[1, 1], xlabel="x [mm]", ylabel="y [mm]", aspect=1)
hm = heatmap!(heat, pd.x*1e3, pd.y*1e3, intensity(pd), colormap=:viridis)
hm = heatmap!(heat, x*1e3, y*1e3, I, colormap=:viridis)
cb = Colorbar(fringes_fig[1, 2], hm, label="Intensity [W/m²]")

save("fringes_showcase.png", fringes_fig; px_per_unit=4)
scatter!(heat, spots*1e3; color=:red, markersize=4)

save("fringes_showcase.png", fringes_fig; px_per_unit=8)

## render system
detector_fig = Figure(size=(600, 280))
Expand Down
4 changes: 2 additions & 2 deletions docs/src/assets/detector_assets/psfdetector_showcase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ n = 1.5
cs = UniformDiscSource([0, -10mm, 0], [0, 1, 0], 15e-3, λ)
lens = SphericalLens(R1, R2, l, d, x -> n)

psfd = PSFDetector(10e-3)
psfd = Detector(10e-3)
translate3d!(psfd, [0, 200mm + 0.13mm, 0])

sys = System([lens, psfd])
Expand Down Expand Up @@ -46,7 +46,7 @@ AL75150 = Lens(

xrotate3d!(AL75150, deg2rad(-0.5))

pd = PSFDetector(15e-3)
pd = Detector(15e-3)

translate3d!(pd, [0, 158.1779e-3, 0.0])
system = System([AL75150, pd])
Expand Down
6 changes: 4 additions & 2 deletions docs/src/assets/detector_assets/spotdetector_showcase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const mm = 1e-3
##
lens = ThinLens(50mm, 50mm, BeamletOptics.inch, 1.5)

sd = Spotdetector(10e-3)
sd = Detector(10e-3)
translate3d!(sd, [0, 50mm, 0])

system = System([lens, sd])
Expand All @@ -33,14 +33,16 @@ cs = CollimatedSource([0,-50mm,0], [0,1,0], aperture, 1e-6; num_rings, num_rays)

t1 = @timed solve_system!(system, cs)

spots = spot_diagram(sd)

render!(system_ax, cs, color=:blue, show_pos=false, render_every=50)

save("spot_diagram_system.png", system_fig, px_per_unit=4)

## render diagram
spot_fig = Figure(size=(600,400))
spot_ax = Axis(spot_fig[1,1], aspect=1, xlabel="x [mm]", ylabel="y [mm]")
sc = scatter!(spot_ax, sd.data, markersize=3, color=:blue)
sc = scatter!(spot_ax, spots, markersize=3, color=:blue)

extime = trunc(t1.time*1e3, digits=2)

Expand Down
31 changes: 27 additions & 4 deletions docs/src/assets/mi_assets/michelson_showcase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ arm_2 = ObjectGroup([m2, arm_holder_2])

# PD
pd_holder = MeshDummy(joinpath(asset_dir, "PD Assembly.stl"))
pd = Photodetector(5e-3, 200)
pd_size = 5mm
pd = Detector(pd_size)
translate3d!(pd, [0, -12cm, 0])

pd_assembly = ObjectGroup([pd, pd_holder])
Expand Down Expand Up @@ -235,20 +236,42 @@ set_view(ax, pd_view)
save("mi_pd.png", fig; px_per_unit=8, update = false)

## fringe plot
x, y, I = intensity(
pd,
n=200,
x_min=-pd_size/2,
x_max= pd_size/2,
z_min=-pd_size/2,
z_max= pd_size/2,
)
spots = spot_diagram(pd)

fringes_fig = Figure(size=(600, 270))
heat1 = Axis(fringes_fig[1, 1], xlabel="x [mm]", ylabel="y [mm]", title="Before rotation", aspect=1)
heat2 = Axis(fringes_fig[1, 2], xlabel="x [mm]", ylabel="y [mm]", title="After rotation", aspect=1, yaxisposition=:right)

hidedecorations!(heat1)
hidedecorations!(heat2)

hm = heatmap!(heat1, pd.x*1e3, pd.y*1e3, intensity(pd), colormap=:viridis)
hm = heatmap!(heat1, x*1e3, y*1e3, I, colormap=:viridis)
scatter!(heat1, spots*1e3; color=:red, markersize=2)

zrotate3d!(m1, 1e-3)
empty!(pd)
solve_system!(system, beam)

hm = heatmap!(heat2, pd.x*1e3, pd.y*1e3, intensity(pd), colormap=:viridis)
x, y, I = intensity(
pd,
n=200,
x_min=-pd_size/2,
x_max= pd_size/2,
z_min=-pd_size/2,
z_max= pd_size/2,
)
spots = spot_diagram(pd)

hm = heatmap!(heat2, x*1e3, y*1e3, I, colormap=:viridis)
scatter!(heat2, spots*1e3; color=:red, markersize=2)

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

Expand All @@ -265,7 +288,7 @@ P = zeros(n+1)
for i in eachindex(P)
empty!(pd)
solve_system!(system, beam)
P[i] = BeamletOptics.optical_power(pd)
P[i] = optical_power(pd)
# translate by Δy
translate3d!(m2, [0, Δy, 0])
end
Expand Down
2 changes: 1 addition & 1 deletion docs/src/basics/beams.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ CollimatedSource(::AbstractArray{<:Real}, ::AbstractArray{<:Real}, ::Real, ::Rea

A special constructor called [`UniformDiscSource`](@ref) is available, which offers an equal-area
sampling (Fibonnaci-pattern) sampling and is thus favorable in situations where the weighting of the
individual beams becomes important, e.g. for calculating a point spread function using [`PSFDetector`](@ref).
individual beams becomes important, e.g. for calculating a point spread function using the [`intensity`](@ref) function.

```@docs; canonical=false
UniformDiscSource
Expand Down
100 changes: 55 additions & 45 deletions docs/src/basics/components/detectors.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,75 @@ include(joinpath(@__DIR__, "..", "..", "assets", "cond_save.jl"))

detector_showcase_dir = joinpath(@__DIR__, "..", "..", "assets", "detector_assets")

conditional_include(joinpath(detector_showcase_dir, "photodetector_showcase.jl"))
conditional_include(joinpath(detector_showcase_dir, "spotdetector_showcase.jl"))
conditional_include(joinpath(detector_showcase_dir, "psfdetector_showcase.jl"))
conditional_include(joinpath(detector_showcase_dir, "photodetector_showcase.jl"), use_placeholder=false)
conditional_include(joinpath(detector_showcase_dir, "psfdetector_showcase.jl"), use_placeholder=false)
```

# Detectors

Detectors provide a way to evaluate beam data during optical simulations. They are designed to accumulate e.g. field data, enabling analysis of intensity distributions, interference patterns, and other beam properties.
In practice, photodetectors allow the conversion of electromagnetic radiation into electric signals. BMO provides the [`Detector`](@ref) as an element to capture and evaluate optical data during and after running a simulation, respectively. The `Detector` is designed to accumulate e.g. field or ray data, enabling analysis of intensity distributions, interference patterns, and other beam properties. Currently, post-processing capabilities are limited to the functionality as described in the [Spot diagrams](@ref) and [Field distributions](@ref) sections.

Detectors are supposed to fall under the [`BeamletOptics.AbstractDetector`](@ref) type, which defines a interface for detector implementations.
In general, detector-like elements are supposed to fall under the [`BeamletOptics.AbstractDetector`](@ref) type, which defines a interface for detector implementations.

!!! warning "Resetting detectors"
In general, the data stored in a detector is not automatically reset between calls of [`solve_system!`](@ref). This task is placed within the responsibility of the user. A detector reset can be performed with the [`empty!`](@ref) function.
In general, the data stored in a `Detector` is not automatically reset between calls of [`solve_system!`](@ref). This task is placed within the responsibility of the user. A detector reset can be performed with the [`empty!`](@ref) function.

## Photodetector type
## Detector type

A concrete implementation to "measure" intensity distributions generated by a [`GaussianBeamlet`](@ref) is provided in the form of the [`Photodetector`](@ref):
A `Detector` can be easily spawned by initializing e.g. `pd = Detector(5mm)` which will create a 5x5 mm² detection screen.

```@docs; canonical=false
Photodetector(::Real, ::Int)
Detector(::Real, ::Bool)
Detector
```

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²).
After solving a system containing a `Detector`, the methods listed below can be used in order to analyze the stored data. If no data is obtained during the tracing procedure, an error message will be stored.

![Photodetector showcase](pd_showcase.png)
## Spot diagrams

One of the use cases of the [`Photodetector`](@ref) is to analyse interference patterns. The figure below demonstrates an example intensity distribution captured by the detector pictured above, showing radial fringes due to a mismatch of the radii of curvature of the interfering [`GaussianBeamlet`](@ref)s.
The `spot_diagram` method provides a straight forward way to generate spot diagrams, which are commonly used to perform initial assessments of the optical performance of an imaging setup.

!!! tip "Interferometer tutorial"
Refer to the [Michelson interferometer](@ref) section for a detailed tutorial on how to use the [`Photodetector`](@ref).
```@docs; canonical=false
spot_diagram
```

![Interference fringes showcase](fringes_showcase.png)
Below an optical system consisting of a collection of collimated [`Beam`](@ref)s passing through a [`ThinLens`](@ref) is shown. A [`Detector`](@ref) is positioned at the approximate focal plane to capture the resulting spot diagram.

![Thin lens setup](spot_diagram_system.png)

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.

![Spot diagram showcase](spot_diagram_showcase.png)

## Spotdetector type
## Field distributions

A straight forward detector that stores the [`BeamletOptics.Intersection`](@ref) position of an incoming [`Beam`](@ref). The [`Spotdetector`](@ref) can be used to generate spot diagrams, which are commonly used to perform initial assessments of the optical performance of an imaging setup.
Alternatively, the detector data can also be used to reconstruct electric field distributions of incoming beams on its surface using coherent addition. Depending on the beam type, either plane wave or Gaussian beam models are used. As a user, this data can be accessed using the [`electric_field`](@ref) interface.

```@docs; canonical=false
Spotdetector(::AbstractFloat)
electric_field(::Detector)
```

Below an optical system consisting of a collection of collimated [`Beam`](@ref)s passing through a [`ThinLens`](@ref) is shown. A [`Spotdetector`](@ref) is positioned at the approximate focal plane to capture the resulting spot diagram.
For convienience, the [`intensity`](@ref) function returns flux values directly. The [`optical_power`](@ref) method can be used in order to obtain the total optical power on the detector surface.

![Thin lens setup](spot_diagram_system.png)
```@docs; canonical=false
intensity(::Detector)
```

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.
### Gaussian beamlet interference

![Spot diagram showcase](spot_diagram_showcase.png)
One of the use cases of the [`Detector`](@ref) is to analyse interference patterns. 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²).

## Point-spread-function detector type
![Photodetector showcase](pd_showcase.png)

The figure below demonstrates an example intensity distribution captured by the detector pictured above, showing radial fringes due to a mismatch of the radii of curvature of the interfering [`GaussianBeamlet`](@ref)s. In addition, the beam waists have been visualized.

!!! tip "Interferometer tutorial"
Refer to the [Michelson interferometer](@ref) section for a detailed tutorial on how to use the [`Detector`](@ref).

![Interference fringes showcase](fringes_showcase.png)

### Point spread function calculation

!!! warning "Experimental feature"
The point spread function estimation is a highly experimental feature. It does not use
Expand All @@ -63,24 +82,15 @@ The beam bundle used to generate the spot diagram was created via the [`Collimat
The package offers a simple method to estimate the point spread function of a system. It is
currently limited and requires careful assessment by the user, if the results are to be trusted.

To analyze the PSF of a imaging system a [`PSFDetector`](@ref) is added to the system at the plane
and orientation where the PSF is requested. This is the same approach as for the other detector types.

```@docs; canonical=false
PSFDetector(::Real)
```

The intensity map together with the coordinate system of the detector can be retrieved after solving the system by calling the [`intensity`](@ref) function.

```@docs; canonical=false
intensity(::PSFDetector)
```
To analyze the PSF of a imaging system a [`Detector`](@ref) is added to the system at the plane
and orientation where the PSF is requested. This is the same approach as for the other detector types.
The intensity map together with the coordinate system of the detector can be retrieved after solving
the system by calling the [`intensity`](@ref) function.

!!! warning
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.


### Airy-Disc Example
#### Airy-Disc Example

This is a classic example where a collimated circular beam is imaged onto a point by a singlet lens.
Due to the finite size of the aperture stop (in this case given by the 15 mm size of the beam), the diffraction
Expand All @@ -101,28 +111,28 @@ d = 25.4e-3
n = 1.5
λ = 1e-6

# generate uniform source, lens and PSF detector
# generate uniform source, lens and detector
cs = UniformDiscSource([0, -10mm, 0], [0, 1, 0], 15e-3, λ)
lens = SphericalLens(R1, R2, l, d, x -> n)
psfd = PSFDetector(10e-3)
detector = Detector(10e-3)

# shift detector into focus
translate3d!(psfd, [0, 200mm + 0.13mm, 0])
translate3d!(detector, [0, 200mm + 0.13mm, 0])

# build system
sys = System([lens, psfd])
sys = System([lens, detector])

solve_system!(sys, cs)

# retrieve intensity
x, z, I_num = intensity(psfd; n=500, crop_factor=5)
x, z, I_num = intensity(detector; n=500, crop_factor=10)
```

Visualizing the result yields the expected Airy-disk pattern.

![Airy disc PSF](psf_airy_showcase.png)

### Coma and Astigmatism Example
#### Coma and Astigmatism Example

In this example, an aspheric lens images the collimated source onto a point but is tilted around the x-axis by 0.5 degrees.
This results in aberrations distorting the stigmatic imaging and leading to coma and astigmatism.
Expand All @@ -141,10 +151,10 @@ AL75150 = Lens(

xrotate3d!(AL75150, deg2rad(-0.5))

pd = PSFDetector(15e-3)
detector = Detector(15e-3)

translate3d!(pd, [0, 158.1779e-3, 0.0])
system = System([AL75150, pd])
translate3d!(detector, [0, 158.1779e-3, 0.0])
system = System([AL75150, detector])

ps = UniformDiscSource([0, -0.1, 0], [0,1,0], 0.8*d, 1550e-9)

Expand Down
Loading