Detectors
In practice, photodetectors allow the conversion of electromagnetic radiation into electric signals. BMO provides the Detector 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 and Field distributions sections.
In general, detector-like elements are supposed to fall under the BeamletOptics.AbstractDetector type, which defines a interface for detector implementations.
Resetting detectors
By default, the data stored in a Detector is not reset between calls of solve_system!. This is intentional, since all beams of a beam group and several sources solved one after another are meant to superpose on the same detectors. To start from empty detectors, e.g. when a component is changed in a loop, solve with initialize = true or call initialize!(system), which resets all detectors of a System including those in nested ObjectGroups. A single detector is reset with empty!. Hits are pushed under a lock, so beam groups can safely be solved multithreaded.
Detector type
A Detector can be easily spawned by initializing e.g. pd = Detector(5mm) which will create a 5x5 mm² detection screen.
BeamletOptics.Detector Method
Spawns a quadratic Detector surface that is aligned with the neg. y-axis. The detector edge length can be configured via edge_length. Additionally, continued tracing can be configured via the stop flag where
falseindicates continued tracingtruestops the incoming beams as with any hard target
BeamletOptics.Detector Type
Detector <: AbstractDetectorRepresents a flat rectangular or quadratic, infinitesimally thin surface in R³. The detector surface is a detection screen that captures incoming ray or beamlet data. The active surface is discretized in the local R² x-y-coordinate system. If configured, beams or beamlets can continue tracing after hitting the detector.
Hits
Hits are represented via the AbstractDetectorHit interface. An empty detector is able to detect any kind of incoming hit, but as soon as the initial hit type has been determined, all following hits must share the same type, i.e. no cross-interaction between hit types is allowed.
Functions
The following functions allow a posteriori evaluation of hit contributions via e.g. f(detector). Refer to the respective function documentation.
Additional information
In general, the detection surface is represented by a flat Mesh that has been rotated such that the surface normals point towards the negative y-axis for the initial positioning. This allows for the definition of a left-handed (x, z) surface coordinate system, where incoming beams intersect against the detector surface normal.
Reset behavior
A Detector accumulates hits across calls of solve_system!, on purpose: all beams of a beam group and several sources solved one after another superpose on it. To discard the results of previous solves, e.g. after changing a component in a loop, call initialize!(system) for all detectors of a system (nested groups included), empty!(detector) for a single one, or solve with initialize = true. Otherwise, the current result is added onto the previous result.
Thread safety
Beam groups are solved multithreaded. Pushing hits is guarded by the lock of the detector, so all beams can hit the same detector concurrently. Each task of the solve of a beam group collects its hits and stores them at its end, see with_hit_buffers. The order of the stored hits is not deterministic.
Reading hits
A hit stores the values of the ray or beamlet at the moment of the solve, in world coordinates. Solving again or moving a source or another object does not change the stored hits. Moving the detector itself empties it, since its hits would no longer lie on its surface. The hits of consecutive solves are kept side by side, and it is up to the user when to evaluate them: spot_diagram shows all of them, whereas electric_field, intensity and optical_power superpose all of them coherently, as for beams that exist at the same time.
Fields
shape: geometry of the active surface, must represent 2D-fieldinxanyydimensions, normal vector direction must adhere to definition abovehits: a union ofNothingand all implementedAbstractDetectorHits, resettable viaempty!(note that only one type is allowed at any time)stop: a boolean value that allows for continued tracing after "passing through" the detectorlock: locks theDetectorfor multithreading-safepush!ing to the hits vector
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.
Reading out a detector
A detector stores hits. The following rules define what a readout computes.
A hit holds the values of the ray or beamlet at the moment of the solve. Solving again, or moving a source or any other component, does not change hits that are already stored.
Any kinematic call on the detector (e.g.
translate3d!orrotate3d!) empties it, also when it is moved as part of anObjectGroup, since its hits would no longer lie on its surface.A detector can hold the hits of solves that follow each other in time side by side. It is up to the user when to evaluate them.
spot_diagramshows all hits, which is harmless.electric_field,intensityandoptical_powersuperpose all hits coherently. This is right for beams that exist at the same time, e.g. a beam group or an interferometer. For solves that follow each other in time, the user must know what this computes, or evaluate after each solve and reset the detector (initialize = true,initialize!orempty!).
# Reset and evaluate after every solve
for y in ys
translate_to3d!(mirror, [0, y, 0])
solve_system!(system, source; initialize = true)
push!(powers, optical_power(pd))
end
# Solve several times, evaluate once: the spots of all solves
for _ in 1:10
zrotate3d!(source, deg2rad(1))
solve_system!(system, source)
end
spots = spot_diagram(pd)Spot diagrams
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.
BeamletOptics.spot_diagram Function
spot_diagram(d::Detector)Returns an array of 2D points in local detector coordinates that represent the points of intersection for incoming beams.
Beams
For Beams, the point of intersection on the screen surface is stored.
Beamlets
For GaussianBeamlets, the projected 1/e² waist is returned. The number of points and radius can be adjusted via the num_spots and crop_factor keyword arguments.
The following image shows the spot diagram of a Sonnar lens. Two CollimatedSources are traced through the system at different angles. A Detector is positioned at the focal plane to capture the resulting spot diagrams, which are visualized using the method explained above.

Field distributions
Alternatively, the detector data can also be used to reconstruct electric field distributions of incoming beams on its surface using coherent addition. Three readouts build on each other: electric_field is the coherent sum of the fields of all hits, intensity the flux of the Poynting vector of that field through the detector, and optical_power the integral of the intensity. Their definitions follow.
BeamletOptics.electric_field Method
electric_field(pd::Detector; kwargs...)Samples the electric field of the hits of the Detector pd on a regular n×n grid in its local (x, z) plane, in a window around the hits. The field at the local point (x, z) is the coherent sum of the fields Eⱼ of all hits j,
with the position p_d of the detector and its local axes x̂, ẑ, which form a left-handed system with the detector normal. Eⱼ is defined per hit type in electric_field(::BeamletOptics.AbstractDetectorHit, ::AbstractVector). The field is not projected onto the detector: it is the field that is there, for any angle of incidence. The power per detector area follows from intensity, which takes the angle of incidence of each hit into account. To sample on a grid of one's own, use electric_field(::Detector, ::AbstractVector, ::AbstractVector).
Coherent sum
All hits are summed as one monochromatic, mutually coherent field, also hits of different wavelengths or of sources that are mutually incoherent. For those, evaluate the intensity of each wavelength or source on its own and add the intensities.
Resetting detectors
Be sure to call empty!(pd) (or initialize!(system) for all detectors) before each new measurement if reusing the same detector.
Keyword Arguments
The following generic kwargs can be used for all hit types:
n::Int=100Number of sample points per axis.crop_factor::RealScales the width of the sampling window returned bycalc_local_lims; values >1 expand, <1 shrink. The default is1for ray hits (the box around the hit points) and3for beamlet hits (three beam radii around each hit, which holds the power of a Gaussian beam except for a part of10⁻⁸).x_min, x_max, z_min, z_maxManually override the sampling bounds in the local x or z directions. If left asInf, the bounds fromcalc_local_limsare used.x0_shift::Real=0, z0_shift::Real=0Applies a constant offset to the entire x or z coordinate arrays, useful for recentring or testing alignment.progress::Bool=trueShows a progress bar once the calculation has run forget_progress_threshold()seconds (default 5 s). It is only drawn ifstderris a terminal.
Ray specific keyword arguments
center::AbstractCenterAlgorithm=Centroid()How the sampling window is centred.Centroid()uses the projection‑weighted centroid,MinMax()uses the geometric mid‑point of the bounding box.
Scaling
The returned values for ray hits correspond to the E-field of the point spread function. The values are raw/unscaled and not equal to a Strehl ratio: a Ray has the amplitude 1, a PolarizedRay its field vector E0, which carries the Fresnel/Jones amplitude factors but not the ray-tube area or the pupil sampling density.
Returns
A tuple (xs, zs, E) where
xs::LinRange{T}andzs::LinRange{T}are the sampled coordinates in the detector's local x and z axes,Eis the field at(xs[i], zs[j]): aMatrix{Complex{T}}forRays andGaussianBeamlets, aMatrix{Point3{Complex{T}}}of 3D vectors in global coordinates forPolarizedRays andAstigmaticGaussianBeamlets, which carry a polarization.
Unpolarized light
An unpolarized PSF is not obtained from a single coherent trace. Instead, trace twice with orthogonal input polarizations (e.g. E0 = [1,0,0] and E0 = [0,0,1]), evaluate intensity(pd, xs, zs) on the same grid after each trace and add the results.
BeamletOptics.electric_field Method
electric_field(pd::Detector, xs::AbstractVector, zs::AbstractVector; progress = true)Samples the electric field of the hits of the Detector pd on the grid xs × zs of its local coordinates in [m]: E[i, j] is the field at (xs[i], zs[j]). The type of E and the meaning of its values are those of electric_field(pd; kwargs...), which picks the grid automatically.
BeamletOptics.electric_field Method
electric_field(hit::AbstractDetectorHit, p::AbstractVector)Electric field Eⱼ(p) of a single detector hit at the global point p [m], not projected onto the detector: a complex scalar for RayHits and GaussianBeamletHits, a complex 3D vector in global coordinates for PolarizedRayHits and AstigmaticGaussianBeamletHits. It is a phasor with the time dependence exp(-iωt), so that a plane wave in a medium of index n carries the intensity n|E|²/(2Z₀). With the vacuum wavenumber k₀ = 2π/λ, the direction s of the hit, the refractive index n in front of the detector and the optical path length L from the source to the hit point p_hit:
RayHit: a plane wave of amplitude1,PolarizedRayHit: the same plane wave times the field vectorE₀of the ray,GaussianBeamletHit: the TEM₀₀ field of the traced segment, at the distancelalong its chief ray and the distancerfrom it,with the field
E₀and waistw₀of the beamlet, the beam radiusw, the radius of curvatureRand the Gouy phaseψof the segment (seegauss_parameters), the indexnof the segment and the optical pathL(l)along the chief ray.AstigmaticGaussianBeamletHit: the field vector of the chief ray of the segment,E_pol, times the general astigmatic Gaussian beam of its parabasal rays, , with the Gouy factorg, which also holds the change of the amplitude with the beam area, and the complex transverse phaseW, whose imaginary part is the Gaussian envelope, see Astigmatic polarized beamlets.
Beamlet hits give the physical field in [V/m]. Ray hits are raw and unscaled: |E|² does not carry the power of the ray, see the note on scaling in electric_field(pd; kwargs...).
A new hit type implements this method, direction(hit) and refractive_index(hit); the readouts of a Detector on a grid (electric_field, intensity, optical_power) then work for it.
BeamletOptics.intensity Method
Samples the irradiance on the Detector pd, the power per detector area in [W/m²], on the grid of electric_field(pd; kwargs...), with the same keyword arguments. Returns a tuple (xs, zs, I). To sample on a grid of one's own, use intensity(::Detector, ::AbstractVector, ::AbstractVector).
I is the flux of the coherent sum of all hits through the detector: the time-averaged Poynting vector along the detector normal n̂, which points along the incoming beams. Hits with a polarization (PolarizedRays, AstigmaticGaussianBeamlets) give
with the field Eⱼ of hit j (see electric_field(hit, p)), its direction sⱼ, the refractive index nⱼ in front of the detector, the vacuum impedance Z₀ and σⱼ = sign(sⱼ ⋅ n̂), i.e. a hit on the back side of the detector counts like one on its front side. Hits without a polarization (Rays, GaussianBeamlets) have no vector H and give the scalar form of the same flux,
A single hit under the angle θ to the normal gives I = n|E|² cos θ / (2Z₀), the power of the beam per detector area, so that optical_power is the power of the beam for any angle of incidence. No projection factor is applied to the field. For two scalar hits, the cross term is Re(E₁* E₂) (n₁ cos θ₁ + n₂ cos θ₂) / (2Z₀), i.e. hits under different angles interfere with the mean of their n cos θ.
I is in [W/m²] for beamlet hits. For ray hits, whose fields are unscaled, it has the same form but no absolute unit.
BeamletOptics.intensity Method
Samples the irradiance on the Detector pd in [W/m²] on the grid xs × zs of its local coordinates in [m]: I[i, j] is the irradiance at (xs[i], zs[j]), see intensity(pd; kwargs...).
BeamletOptics.optical_power Method
optical_power(pd::Detector; kwargs...)Calculates the total optical power on pd in [W] as the integral of its intensity over the sampling window in the local detector coordinates,
evaluated with the trapezoidal rule on the n×n grid of electric_field(pd; kwargs...), with the same keyword arguments. Since I is the flux through the detector, P is the power of the beams for any angle of incidence.
The integral runs over the sampling window, not over the active area of the detector: the window is not cut at the edges of the detector. The default window of beamlet hits holds the power of each beamlet except for a part of 10⁻⁸, see crop_factor. For ray hits, P has no absolute unit, see intensity.
Gaussian beamlet interference
One of the use cases of the Detector is to analyse interference patterns. Below a rendered example of a detector model (FDS010) can be seen. The detector active area is marked in blue (1x1 mm²).

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 GaussianBeamlets. In addition, the beam waists have been visualized.
Interferometer tutorial
Refer to the Michelson interferometer section for a detailed tutorial on how to use the Detector.

Point spread function estimation
If a Detector is placed in the focal plane of an imaging system, the coherent addition of the ray-attached plane waves yields an estimate of its point spread function (PSF). For the Sonnar example from the Spot diagrams section the following distribution was calculated for the on-axis beam bundle.
Experimental feature
The PSF estimation does not use pupils (yet) but merely superimposes the ray-attached plane waves. It gives qualitatively sound results, but requires good sampling of the problem to be quantitatively meaningful. No Strehl ratio is calculated.

For PolarizedRays the field is added as 3D vectors, so electric_field returns a matrix of complex vectors and each component of the focal field can be analyzed separately. This becomes relevant at high NA, where the field vectors tilt towards the optical axis. The same holds for AstigmaticGaussianBeamlets.
PSF examples
The Point spread functions example page covers the Airy disc, an aberrated asphere and the vectorial focus of a parabolic mirror at NA 0.88, including the caveats on collimated sources and ray amplitudes.
OpticsBase interoperability
If OpticsBase.jl is loaded alongside BMO (using OpticsBase), the BeamletOpticsOpticsBaseExt package extension adds a PlaneField constructor for a Detector. It samples the physical field of the beamlet hits as tangential electric and magnetic field on the detector plane, OpticsBase's single exchange format for coupling to other Maxwell solvers (angular spectrum propagators, BPM, FDTD, ...). As for intensity(::Detector), the Poynting flux of an oblique beam through the plane is the full beam power.
OpticsBase.PlaneField Method
OpticsBase.PlaneField(detector::BeamletOptics.Detector;
size::NTuple{2,Int}, spacing::NTuple{2,Real},
origin = BeamletOptics.position(detector), axes = <default detector frame, see below>)Samples the coherent field of the beamlets hitting a BeamletOptics (BMO) Detector into an OpticsBase.PlaneField. Call it after solve_system!. The hits keep the state of their solve: solving again or moving other objects does not change them, moving the detector empties it. Requires using OpticsBase (package extension).
Arguments
detector: a BMODetectorwhose hits are allGaussianBeamletHitor allAstigmaticGaussianBeamletHit(i.e. Gaussian beamlet sources; pure-ray hits have no scaled field in BMO and throw anArgumentError, as does an empty detector). All hits must share the vacuum wavelength and the refractive index of the medium in front of the detector.size(keyword, required): number of samples(nx, ny)along the plane'su,vaxes.spacing(keyword, required): sample spacings(Δu, Δv)in [m], as inOpticsBase.PlaneField.origin(keyword): center of the plane in global coordinates in [m]. Default: the detector position.axes(keyword):3×3matrix with columnsu,v,n(seeOpticsBase.PlaneField). Default:n = −orientation(detector)[:, 2](BMO's detector normal points against the beam;npoints downstream, i.e. the direction the light was travelling in when it hit the detector),u = −orientation(detector)[:, 1](BMO's local detector x axis) andv = n × u(BMO's local −z axis), matching the default port of BMO's detector hit readers.
Conventions
Eis the physical field: the coherent sum of the beamlet fields at the plane's sample points, projected ontou,v. Likeelectric_field(detector), it carries no projection factor; for oblique beamspower(f)is the full beam power crossing the plane, asoptical_power(detector).His the coherent sum, over hits, of(n_medium/Z₀) dᵢ × Eᵢ, withdᵢthe propagation direction of the chief/stigmatic ray of hitiandEᵢthat hit's own 3D field contribution at the sample point (not the paraxial(n/Z₀) n × Ebuilt from the total field, which would be exact only for normal incidence); this matches the surface-equivalence-theorem content ofPlaneFieldeven for beams that hit the plane off-normal.R = Inf(no reference sphere removed: BMO's beamlet field already carries the full spatial phase without needing one).Eᵢneeds a 3D direction for each hit's otherwise-scalar contribution:AstigmaticGaussianBeamletHits use their chief ray's own polarization (beamlet_hit_polarization). StigmaticGaussianBeamletHits carry no polarization in BMO (the model is scalar); by convention, matchingOpticsBase's own rule for a scalar field (PlaneField(E::AbstractMatrix, ...)puts a scalar field entirely intoEu), such a hit's contribution is polarized along the plane'suaxis, made transverse to the hit direction (alongvifuis parallel to it). At normal incidence this is exactlyu.n(medium refractive index) is that of the hits;λis their shared wavelength.Time convention
exp(−iωt), as in BMO andOpticsBase.
Throws an ArgumentError if the detector has no hits, if the hits are not beamlet hits, or if the hits do not share wavelength and refractive index.
The return paths, WavefrontBeamletDecomposition and GaussianModeDecomposition for a PlaneField, are documented in OpticsBase fields as beam groups. Without a detector, a single traced beamlet segment is sampled with PlaneField(beamlet, id; ...), which components that hand light to another solver use in their interact3d method, see Re-emitting components.