Additional API Reference
This page collects exported names that aren't covered by the grouped reference pages (peak detection, peak fitting, baseline correction, preprocessing, PL/Raman mapping).
Module
OpticalSpectroscopy.OpticalSpectroscopy — Module
OpticalSpectroscopy.jl — General-purpose spectroscopy analysis toolkit.
Provides data types, fitting routines, baseline correction, peak detection, unit conversions, and utility functions for spectroscopic data analysis.
Data Types
OpticalSpectroscopy.AbstractSpectroscopyData — Type
AbstractSpectroscopyDataAbstract base type for all spectroscopy data types.
Subtypes must implement the following interface:
xdata(d)— Primary x-axis data (Vector{Float64})ydata(d)— Signal data (Vector{Float64}) or secondary axis for 2Dzdata(d)— Matrix data for 2D types,nothingfor 1Dxlabel(d)— X-axis label stringylabel(d)— Y-axis or signal label stringis_matrix(d)— Whether data is 2D (returns Bool)
Optional (have defaults):
source_file(d)— Source filename (default:"")npoints(d)— Number of data points (default:length(xdata(d)), tuple for 2D)title(d)— Display title (default:source_file(d))
This enables uniform handling in data viewers and plotting functions while maintaining semantic field names in each concrete type.
zdata orientation contract
The orientation of zdata differs between the 2D types and is part of the interface contract:
TimeResolvedMatrix:zdatais(n_time, n_wavelength)=(length(ydata), length(xdata))— plot withheatmap(xdata, ydata, zdata').PLMap:zdatais(nx, ny)=(length(xdata), length(ydata))— plot withheatmap(xdata, ydata, zdata)(no transpose).
Check size(zdata(d)) against (length(xdata(d)), length(ydata(d))) when writing generic 2D consumers.
Example
# 1D data works uniformly; for 2D data mind the orientation contract above
function plot_data(data::AbstractSpectroscopyData)
if data isa TimeResolvedMatrix
heatmap(xdata(data), ydata(data), zdata(data)')
elseif data isa PLMap
heatmap(xdata(data), ydata(data), zdata(data))
else
lines(xdata(data), ydata(data))
end
endOpticalSpectroscopy.Spectrum — Type
Spectrum <: AbstractSpectroscopyData
Spectrum(x, y)
Spectrum(x, y, metadata::AbstractDict)
Spectrum(x, y; axis=:wavenumber, metadata...)
Spectrum(M::AbstractMatrix)Generic 1D steady-state spectrum: signal versus a spectral axis.
Covers FTIR, Raman, UV-Vis, photoluminescence, cavity transmission, transient- absorption slices, and any other 1D data. Axis semantics live in metadata as reserved (quantity, unit) tokens, from which labels are derived (never guessed):
:xquantity/:xunit,:yquantity/:yunit— axis tokens (e.g.:wavenumber/:per_cm). Seeaxis_label.:xlabel/:ylabel— literal label strings; override the tokens.:time_delay(+:time_delay_unit) — fixed delay of a slice from aTimeResolvedMatrix;:gate_start/:gate_end(+:gate_unit) — the time window of a gated/integrated slice.:filename/:source— source file forsource_file.:cavity_length— default cavity length forfit_cavity_spectrum.
With no axis metadata, labels fall back to the honest generic floor ("x" / "Signal") — bare row-column data loads with no units guessed.
Metadata keys are stored as Symbols. The positional-dict and keyword constructors both accept String keys (converted via Symbol), so a Dict{String,Any} from the lab layer (JASCO/QPSTools sample metadata) can be passed directly.
Fields
x::Vector{Float64}: Spectral axisy::Vector{Float64}: Signalmetadata::Dict{Symbol,Any}: Additional info
Examples
spec = Spectrum(nu, T)
spec = Spectrum(nu, T; mirror="Au", angle=10, cavity_length=12e-4)
spec.metadata[:mirror]OpticalSpectroscopy.KineticTrace — Type
KineticTrace <: AbstractSpectroscopyDataSingle-wavelength kinetic trace: signal versus time.
Covers transient-absorption kinetics (ΔA) and time-resolved PL decays (counts). Signal/axis semantics live in token metadata (:yquantity/:yunit for the signal; :xunit for the time axis).
Fields
time::Vector{Float64}: Time axissignal::Vector{Float64}: Signal at each time pointwavelength::Float64: Probe/emission wavelength, NaN if unknownmetadata::Dict{Symbol,Any}: Additional info
OpticalSpectroscopy.TimeResolvedMatrix — Type
TimeResolvedMatrix <: AbstractSpectroscopyDataTwo-dimensional time-resolved spectroscopy data (time × wavelength).
Covers transient absorption (ΔA) and streak-camera PL (counts). Spectral axis tokens :xquantity/:xunit; signal tokens :yquantity/:yunit; time-axis unit :time_unit. matrix[t=…] returns a Spectrum; matrix[λ=…] returns a KineticTrace.
Fields
time::Vector{Float64}: Time axiswavelength::Vector{Float64}: Wavelength axis (nm) or wavenumber (cm⁻¹)data::Matrix{Float64}: Signal matrix, size (ntime, nwavelength)metadata::Dict{Symbol,Any}: Additional info
Indexing
matrix[λ=800] # Extract KineticTrace at λ ≈ 800 nm
matrix[t=1.0] # Extract Spectrum at t ≈ 1.0 ps (tagged with :time_delay)matrix[t=...] returns a Spectrum whose axis/signal labels derive from the matrix's tokens (honest "x"/"Signal" floor if absent), tagged with the slice's :time_delay. For a spectrum gated/integrated over a time window, use spectral_slice or integrate_time.
OpticalSpectroscopy.SweepData — Type
SweepData(X, Y, DC)Per-sweep multi-channel lock-in detector data. Each matrix is n_points × n_sweeps. NaN marks an unmeasured point (e.g. from an aborted partial sweep). This is the un-averaged counterpart to KineticTrace — sweep-resolved raw output before collapsing across sweeps.
Fields
X::Matrix{Float64}: in-phase (signal) component per sweepY::Matrix{Float64}: quadrature component per sweepDC::Matrix{Float64}: probe DC level per sweep (0.0if unavailable)
Examples
n_points, n_sweeps = 100, 10
X = zeros(n_points, n_sweeps)
Y = zeros(n_points, n_sweeps)
DC = zeros(n_points, n_sweeps)
sweeps = SweepData(X, Y, DC)OpticalSpectroscopy.TASpectrumFit — Type
TASpectrumFitResult of TA spectrum fitting with N peaks of arbitrary lineshape.
Supports any combination of ESA, GSB, and SE peaks, each with its own lineshape model (Gaussian, Lorentzian, pseudo-Voigt).
Access peaks
result.peaks— Vector ofTAPeakresult[i]— i-th peakresult[:esa]— first peak with label:esaanharmonicity(result)— GSB center minus ESA center (NaN if not applicable)
Fields
peaks::Vector{TAPeak}— Fitted peak parametersoffset,rsquared,residuals— Fit metadata;residualsaredata − fit(package-wide convention)
OpticalSpectroscopy.TAPeak — Type
TAPeakInformation about a single peak in a TA spectrum fit.
Fields
label::Symbol— Peak type (:esa,:gsb,:se,:positive,:negative)model::String— Lineshape model name ("gaussian","lorentzian","pseudo_voigt")center::Float64— Peak center positionwidth::Float64— Width parameter (sigma for gaussian/voigt, fwhm for lorentzian)amplitude::Float64— Peak amplitude (always positive; sign determined by label)
OpticalSpectroscopy.fit_ta_spectrum — Function
fit_ta_spectrum(spec::Spectrum; kwargs...) -> TASpectrumFitFit a transient absorption spectrum with N peaks of arbitrary lineshape.
Uses find_peaks to automatically detect initial peak positions from the data, so multiple well-separated peaks of the same type (e.g., three GSB peaks for W(CO)₆) are initialized correctly.
Keywords
peaks=[:esa, :gsb]— Peak types. Each element is either aSymbol(:esa,:gsb,:se,:positive,:negative) or a(Symbol, Function)tuple specifying label and lineshape model.model=gaussian— Default lineshape for peaks specified as symbols only.region=nothing— Optional(x_min, x_max)fitting region.fit_offset=false— Whether to fit a constant offset.p0=nothing— Manual initial parameter vector. Overrides automatic detection.
Peak signs
:esa,:positive→ +1 (positive ΔA):gsb,:se,:negative→ -1 (negative ΔA)
Examples
# Default: 1 Gaussian ESA + 1 Gaussian GSB
result = fit_ta_spectrum(spec)
# Three GSB peaks (e.g., W(CO)₆ carbonyl stretches)
result = fit_ta_spectrum(spec; peaks=[:esa, :esa, :esa, :gsb, :gsb, :gsb])
# Per-peak lineshapes
result = fit_ta_spectrum(spec; peaks=[(:esa, lorentzian), (:gsb, gaussian)])
# Access results
result[:esa].center # first ESA peak
result[2].center # second peak by index
anharmonicity(result) # GSB - ESA center (only if exactly 1 of each)
predict(result, ν) # full fitted curve
predict_peak(result, 1) # single peak contributionOpticalSpectroscopy.anharmonicity — Function
anharmonicity(fit::TASpectrumFit) -> Float64Compute the anharmonicity (GSB center - ESA center) from a TA spectrum fit. Returns NaN if there is not exactly one ESA and one GSB peak.
Time-Resolved Analysis
Slice extraction, binning, cosmic ray removal, and lifetime fitting for streak-camera and broadband TA data.
OpticalSpectroscopy.kinetic_trace — Function
kinetic_trace(m::TimeResolvedMatrix; wavelength, band=0.0) -> KineticTraceExtract a kinetic trace at wavelength, averaged over wavelength ± band/2.
With band == 0 (default), the single nearest wavelength column is used; if no columns fall inside the band, the nearest column is used as fallback. The trace inherits the matrix metadata, plus :band when band > 0.
OpticalSpectroscopy.spectral_slice — Function
spectral_slice(m::TimeResolvedMatrix; time, window=0.0) -> SpectrumExtract the spectrum at time, averaged over time ± window/2 (gated mean).
With window == 0 (default), the single nearest time row is used; if no rows fall inside the window, the nearest row is used as fallback.
OpticalSpectroscopy.integrate_time — Function
integrate_time(m::TimeResolvedMatrix; t_range=nothing) -> SpectrumTime-integrated spectrum: sum over the time axis (preserves total counts), optionally restricted to t_range = (t_lo, t_hi).
OpticalSpectroscopy.bin_matrix — Function
bin_matrix(m::TimeResolvedMatrix; time=1, wavelength=1) -> TimeResolvedMatrixBlock-average the matrix by integer factors along each axis.
Axis values are averaged the same way as the data. A partial final block is averaged over the elements it contains (no data is dropped). Factors of 1 return an identical copy. Records :bin_time / :bin_wavelength in metadata when the corresponding factor is > 1; calling bin_matrix repeatedly multiplies the stored factors so the metadata always reflects the total binning applied (e.g. binning by 2 twice records :bin_time => 4).
OpticalSpectroscopy.fit_lifetime_spectrum — Function
fit_lifetime_spectrum(m::TimeResolvedMatrix; n_exp=1, nbins=32,
t_range=nothing, min_signal=0.0) -> LifetimeSpectrumResultFit an exponential decay in each of nbins equal-width wavelength bins.
Each bin's columns are averaged into a kinetic trace and fitted with fit_exp_decay. Bins with no wavelength points, peak |signal| below min_signal, or a failed fit are skipped (NaN entries, fitted[i] == false).
Arguments
m: time × wavelength matrixn_exp: exponential components per bin fit (default 1)nbins: number of equal-width wavelength bins (default 32)t_range: optional(t_lo, t_hi)fit window passed tofit_exp_decaymin_signal: skip bins whose peak |signal| is below this (default 0.0 = fit all)
OpticalSpectroscopy.mean_lifetime — Function
mean_lifetime(fit::StretchedDecayFit) -> Float64Mean lifetime of a stretched-exponential decay: ⟨τ⟩ = (τ/β)·Γ(1/β).
OpticalSpectroscopy.StretchedDecayFit — Type
StretchedDecayFit <: AbstractDecayFitResult of stretched-exponential (Kohlrausch–Williams–Watts) decay fitting:
signal(t) = A·exp(-((t - t₀)/τ)^β) + offset
Fields
amplitude::Float64tau::Float64: Characteristic time constantbeta::Float64: Stretching exponent (0 < β ≤ 1)t0::Float64: Fit-region time originoffset::Float64signal_type::Symbol::esa(positive) or:gsb(negative)residuals::Vector{Float64}:data − fit(package-wide convention)rsquared::Float64
See mean_lifetime for ⟨τ⟩ = (τ/β)·Γ(1/β).
OpticalSpectroscopy.LifetimeSpectrumResult — Type
LifetimeSpectrumResultResult of fit_lifetime_spectrum: per-wavelength-bin decay fits.
Fields
wavelength::Vector{Float64}: Bin centers (mean wavelength per bin); NaN for empty binstaus::Matrix{Float64}: Time constants, size (nbins, n_exp); NaN where skippedamplitudes::Matrix{Float64}: Amplitudes, size (nbins, n_exp); NaN where skippedrsquared::Vector{Float64}: R² per bin; NaN where skippedfitted::BitVector: Whether each bin was fittedn_exp::Int: Exponential components per fit
OpticalSpectroscopy.CosmicRayMatrixResult — Type
CosmicRayMatrixResultResult of cosmic ray detection on a TimeResolvedMatrix.
Fields
indices::Vector{CartesianIndex{2}}— flagged (time, wavelength) pixelscount::Int— number of flagged pixelsthreshold::Float64— detection threshold used
Data Interface
Uniform accessors implemented by every AbstractSpectroscopyData subtype.
OpticalSpectroscopy.xdata — Function
xdata(d::AbstractSpectroscopyData) -> Vector{Float64}Return the primary x-axis data (e.g. time for KineticTrace, the spectral axis for a Spectrum slice, wavelength for TimeResolvedMatrix). Every concrete subtype implements this.
OpticalSpectroscopy.ydata — Function
ydata(d::AbstractSpectroscopyData) -> Vector{Float64}Return the signal data for 1D types, or the secondary axis for 2D types (e.g. the time axis of a TimeResolvedMatrix).
OpticalSpectroscopy.zdata — Function
zdata(d::AbstractSpectroscopyData) -> Union{Matrix{Float64}, Nothing}Return the signal matrix for 2D types (e.g. the signal matrix of a TimeResolvedMatrix, the intensity map of a PLMap). Returns nothing for 1D data.
OpticalSpectroscopy.xlabel — Function
xlabel(d::AbstractSpectroscopyData) -> StringReturn a display label for the x axis (e.g. "Time (ps)", "Wavenumber (cm⁻¹)").
OpticalSpectroscopy.ylabel — Function
ylabel(d::AbstractSpectroscopyData) -> StringReturn a display label for the y axis or signal (e.g. "ΔA").
OpticalSpectroscopy.zlabel — Function
zlabel(d::AbstractSpectroscopyData) -> StringReturn a display label for the signal of 2D data (e.g. "ΔA", "PL Intensity").
OpticalSpectroscopy.is_matrix — Function
is_matrix(d::AbstractSpectroscopyData) -> BoolWhether the data is 2D (true for TimeResolvedMatrix and PLMap; false for 1D types). When true, zdata returns a matrix.
Semantic Accessors
Type-specific accessors with domain names.
OpticalSpectroscopy.delay — Function
delay(t::KineticTrace) -> Vector{Float64}Return the time axis.
delay(m::TimeResolvedMatrix) -> Vector{Float64}Return the time axis.
OpticalSpectroscopy.signal — Function
signal(t::KineticTrace) -> Vector{Float64}Return the signal values (ΔA, counts, or other units depending on data source).
signal(m::TimeResolvedMatrix) -> Matrix{Float64}Return the signal matrix (ntime × nwavelength).
signal(s::Spectrum) -> Vector{Float64}Return the signal.
OpticalSpectroscopy.wavenumber — Function
wavenumber(r::CavityFitResult) -> Vector{Float64}Return the wavenumber array used in the fit.
OpticalSpectroscopy.wavelength — Function
wavelength(m::TimeResolvedMatrix) -> Vector{Float64}Return the wavelength axis (nm) or wavenumber axis (cm⁻¹).
Metadata Tokens
The (quantity, unit) Symbol tokens that describe a spectrum's axes: normalizers map free-form strings to canonical Symbols, axis_label resolves a display label, is_canonical/validate_tokens check a metadata dict, guess_units! opts a Spectrum into inferred tokens, and xdata_unitful/ydata_unitful return an axis as a Unitful vector.
OpticalSpectroscopy.normalize_quantity — Function
normalize_quantity(s::AbstractString) -> SymbolMap a free-form instrument quantity string to a canonical quantity token ("ABSORBANCE" → :absorbance, "Raman Shift" → :raman_shift). Unrecognized strings return Symbol of the lowercased, underscored text.
OpticalSpectroscopy.normalize_unit — Function
normalize_unit(s::AbstractString) -> SymbolMap a free-form instrument unit string to a canonical unit token ("NANOMETERS" → :nm, "1/cm" → :per_cm). Unrecognized strings return Symbol of the lowercased, underscored text so nothing is lost.
OpticalSpectroscopy.axis_label — Function
axis_label(quantity::Symbol, unit::Symbol) -> StringCompose a display label from a quantity token and a unit token: axis_label(:wavenumber, :per_cm) == "Wavenumber (cm⁻¹)". A dimensionless or empty unit yields just the quantity name: axis_label(:delta_absorbance, :dimensionless) == "ΔA". Unknown tokens fall back to their humanized String form so nothing is ever lost.
OpticalSpectroscopy.is_canonical — Function
is_canonical(slot::Symbol, value::Symbol) -> BoolWhether value is in the controlled vocabulary for slot. Slots: :technique, :quantity (axis or signal), :unit. Unknown slots return false.
OpticalSpectroscopy.validate_tokens — Function
validate_tokens(metadata::AbstractDict) -> BoolCheck reserved token values against the controlled vocabularies of §3. @warns for each reserved key whose value is outside its vocabulary and returns false; returns true when all present reserved tokens are canonical. Never throws.
OpticalSpectroscopy.guess_units! — Function
guess_units!(s::Spectrum) -> SpectrumOpt-in heuristic: infer the x-axis quantity/unit from the data magnitude and stamp :xquantity/:xunit. Nothing calls this automatically — labels never guess on the user's behalf (the no-guess rule).
OpticalSpectroscopy.xdata_unitful — Function
xdata_unitful(d)Return xdata(d) with its :xunit token attached via Unitful (§6). With no token the unit is Unitful.NoUnits and the plain Float64 vector is returned unchanged. Opt-in; core storage stays unitless.
OpticalSpectroscopy.ydata_unitful — Function
ydata_unitful(d)Return ydata(d) with the unit token of that axis attached via Unitful (§6): the signal :yunit for 1D types (where ydata is the signal), and the time-axis unit :time_unit for TimeResolvedMatrix (where ydata is the time axis, not the signal). With no token the unit is Unitful.NoUnits and the plain Float64 vector is returned unchanged. Opt-in; core storage stays unitless.
Plotting
OpticalSpectroscopy.plot_fit — Function
plot_fit(fit::MultiPeakFitResult; residuals=true, components=false,
baseline=false, xlabel="x", ylabel="Signal",
figure=(;), axis=(;)) -> FigurePlot data, fitted curve, and (optionally) residuals, per-peak components, and the fitted baseline for a multi-peak fit.
Requires a Makie backend: load CairoMakie or GLMakie first to activate the plotting extension. Returns a Makie.Figure.
Keywords
residuals::Bool=true— Add a residuals panel below the main axiscomponents::Bool=false— Draw each fitted peak separatelybaseline::Bool=false— Draw the fitted polynomial baselinexlabel,ylabel— Axis labelsfigure,axis— NamedTuples forwarded toFigure/Axis
Example
using GLMakie, OpticalSpectroscopy
result = fit_peaks(x, y, (2000, 2100); n_peaks=2)
plot_fit(result; components=true, baseline=true)Chirp Correction
OpticalSpectroscopy.ChirpCalibration — Type
ChirpCalibrationStores the result of chirp detection: detected chirp points, polynomial fit, and detection parameters for reproducibility.
Fields
wavelength: Wavelength points where chirp was detected (nm)time_offset: Detected chirp time at each wavelength (ps)poly_coeffs: Polynomial fit coefficients (constant term first, ascending order)poly_order: Polynomial order usedreference_λ: Reference wavelength where chirp = 0 (nm)r_squared: How well the polynomial fits the points that were detectedmetadata: Detection parameters for reproducibility
r_squared says only that a polynomial describes the detected points; it is silent on whether those points are the real chirp. Because R² is normalised by the variance of the points being fitted, a handful of extreme values pull it up — a curve bending to chase them scores well precisely because they are extreme. Judge detection by whether the offsets are resolvable (spread across several time steps, no pile-up at the search bound: see metadata[:n_unmeasurable]), not by R² alone.
OpticalSpectroscopy.calibrate_chirp — Function
calibrate_chirp(oke::TimeResolvedMatrix; kwargs...) -> ChirpCalibrationMeasure a chirp calibration from a dedicated OKE (optical Kerr effect) cross-correlation run.
The electronic Kerr response of a non-resonant medium is effectively instantaneous, so the per-wavelength peak of the pump–probe cross-correlation traces the true t₀(λ) independent of any sample dynamics. This makes an OKE run the standard source of chirp calibrations; use detect_chirp, which infers the curve from a sample matrix's own signal onsets, only when no OKE run is available.
For each wavelength column the peak position is located with method:
:gaussian(default): least-squares fit of a Gaussian with vertical offset,p = [A, t₀, σ, y₀]. Also yields the per-wavelength IRF width σ (a standard deviation, not a FWHM), stored inmetadata[:irf_sigma].:peak: argmax plus parabolic sub-sample interpolation. Fast path for high-SNR runs; provides no IRF widths.
Columns whose peak-to-peak amplitude is below min_amplitude of the strongest column's, and columns whose per-column fit fails, are masked out. The surviving (λ, t₀) points are fitted with an order-order polynomial with MAD outlier rejection (shared with detect_chirp). The stored polynomial is normalized to zero at reference_λ; the absolute overlap time there is kept in metadata[:t0_at_reference].
Keywords
method::Symbol = :gaussian—:gaussianor:peak(see above)order::Int = 3— chirp polynomial orderreference = :center— wavelength (nm) where the polynomial is zero;:centeruses the midpoint of the surviving wavelength coveragewl_range = nothing—(λmin, λmax): restrict which columns are consideredt_range = nothing—(tmin, tmax): restrict the per-column fit windowmin_amplitude::Real = 0.05— amplitude mask threshold as a fraction of the strongest column's peak-to-peak amplitude;0disables the masksource = nothing— provenance string stored asmetadata[:source_file]; defaults to the OKE matrix's ownmetadata[:source]when present
Metadata
The returned calibration's metadata carries provenance and fit bookkeeping: :source => :oke, :source_file, :lambda_range (the kept wavelength coverage — correct_chirp clamps its polynomial evaluation to this window), :t0_at_reference, :irf_sigma (:gaussian only, aligned with cal.wavelength), plus :method, :order, :mad_threshold, point counts, and the wl_range/t_range restrictions when given.
Examples
oke = ... # TimeResolvedMatrix from the OKE run (solvent/glass at sample position)
cal = calibrate_chirp(oke)
corrected = correct_chirp(sample_matrix, cal)OpticalSpectroscopy.detect_chirp — Function
detect_chirp(matrix::TimeResolvedMatrix; kwargs...) -> ChirpCalibrationDetect chirp (GVD) in a broadband TA matrix via cross-correlation (:xcorr) or threshold crossing (:threshold). Returns a ChirpCalibration with polynomial fit.
OpticalSpectroscopy.correct_chirp — Function
correct_chirp(matrix::TimeResolvedMatrix, cal::ChirpCalibration) -> TimeResolvedMatrixApply chirp correction by interpolating each wavelength column onto its t_shift(λ)-shifted time axis from the calibration polynomial. Uses cubic B-splines on a uniform time axis; for a non-uniform axis it falls back to gridded-linear interpolation against the actual sample times (cubic B-splines require evenly-spaced knots) so the correction stays quantitatively correct.
Shifted samples that fall outside a column's measured time range are NaN: no data was recorded there, and extrapolated values would masquerade as signal. Keep the acquisition window generous around t₀ so the correction doesn't push wavelengths of interest off the edge.
When the calibration carries metadata[:lambda_range] — set by calibrate_chirp to its measured wavelength coverage — the shift polynomial is evaluated at clamp(λ, λmin, λmax): outside the measured window the shift is held flat at the endpoint value instead of extrapolating the polynomial into wavelengths it never saw. Calibrations from detect_chirp don't set the key and are evaluated everywhere.
OpticalSpectroscopy.polynomial — Function
polynomial(cal::ChirpCalibration) -> FunctionReturn a callable polynomial t_shift = poly(λ) from the calibration. Coefficients are in ascending order: c[1] + c[2]*λ + c[3]*λ² + ...
OpticalSpectroscopy.save_chirp — Function
save_chirp(path::String, cal::ChirpCalibration)Save a chirp calibration to a JSON file.
OpticalSpectroscopy.load_chirp — Function
load_chirp(path::String) -> ChirpCalibrationLoad a chirp calibration from a JSON file.
SVD Filtering
OpticalSpectroscopy.svd_filter — Function
svd_filter(matrix::TimeResolvedMatrix; n_components::Int=5) -> TimeResolvedMatrixDenoise a TA matrix by keeping only the first n_components singular value components. Higher-order components (dominated by noise) are discarded.
This is a standard preprocessing step for broadband TA data. Typical usage: denoise first, then subtract background, detect chirp, and correct chirp.
Arguments
matrix::TimeResolvedMatrix: Input time × wavelength ΔA matrix
Keywords
n_components::Int=5: Number of singular value components to retain. Usesingular_valuesto inspect the spectrum and choose.
Returns
A new TimeResolvedMatrix with filtered data. Metadata includes :svd_filtered => true and :svd_n_components => n_components.
Examples
sv = singular_values(matrix) # inspect singular value spectrum
filtered = svd_filter(matrix; n_components=3)svd_filter(x::AbstractVector, y::AbstractVector, data::AbstractMatrix;
n_components::Int=5) -> Matrix{Float64}Denoise a raw data matrix by keeping only the first n_components singular value components. Returns the filtered matrix.
Arguments
x: First axis (e.g., time)y: Second axis (e.g., wavelength)data: Matrix of size(length(x), length(y))
Keywords
n_components::Int=5: Number of components to retain
OpticalSpectroscopy.singular_values — Function
singular_values(matrix::TimeResolvedMatrix) -> Vector{Float64}Return the singular values of the TA data matrix. Inspect these to choose n_components for svd_filter — look for a gap between signal and noise components.
Examples
sv = singular_values(matrix)
# Plot sv to find the elbow, then filter:
filtered = svd_filter(matrix; n_components=3)singular_values(data::AbstractMatrix) -> Vector{Float64}Return the singular values of a raw data matrix.
OpticalSpectroscopy.estimate_n_components — Function
estimate_n_components(sv; ratio=10.0) -> IntEstimate the number of significant components from a descending sequence of singular values sv using an elbow heuristic: return the first index i where sv[i] / sv[i+1] > ratio, marking a gap between signal and noise components. Returns length(sv) when no such gap exists. Use with singular_values to choose n_components for svd_filter.
Exponential Decay Fitting
OpticalSpectroscopy.fit_exp_decay — Function
fit_exp_decay(trace::KineticTrace; n_exp=1, irf=false, irf_width=0.15, t_start=0.0, t_range=nothing, model=:exponential,
tau0=nothing, amplitude0=nothing, offset0=nothing, beta0=nothing)Fit an exponential decay model to a KineticTrace.
Arguments
trace: KineticTracen_exp: Number of exponential components (default 1)irf: Include IRF convolution (default false)irf_width: Initial guess for IRF σ in ps (default 0.15)t_start: Start time for fitting when irf=false (default 0.0)t_range: Optional (tmin, tmax) to restrict fit regionmodel::exponential(default) or:stretched— Kohlrausch–Williams–Watts
Initial guesses
Initial parameter guesses are estimated from the data. The keywords below override individual automatic guesses; any guess not supplied keeps its automatic value.
tau0,amplitude0: Forn_exp = 1a number; forn_exp > 1a vector of lengthn_expwhose entries are numbers ornothing(nothing= keep the automatic guess for that component).tau0values must be positive.offset0: Constant-offset guess.beta0: Stretching-exponent guess in (0, 1] (model=:stretchedonly).
Returns
n_exp=1:ExpDecayFitn_exp>1:MultiexpDecayFitmodel=:stretched:StretchedDecayFit
OpticalSpectroscopy.fit_global — Function
fit_global(traces::Vector{KineticTrace}; n_exp=1, irf_width=0.15, labels=nothing) -> GlobalFitResultFit multiple traces simultaneously with shared time constant(s) τ.
Supports single-exponential (n_exp=1) and multi-exponential (n_exp>1) global analysis. All traces share the same time constants, IRF width, and time zero, while amplitudes and offsets are fitted per-trace.
Keywords
n_exp::Int=1: Number of exponential componentsirf_width::Float64=0.15: Initial guess for IRF σ in pslabels=nothing: Optional trace labels (defaults to "Trace 1", "Trace 2", ...)
Returns
GlobalFitResult with shared taus and per-trace amplitudes matrix.
Examples
# Single-exponential global fit
result = fit_global([trace_esa, trace_gsb])
# Multi-exponential with 2 shared time constants
result = fit_global([trace1, trace2, trace3]; n_exp=2)
report(result)fit_global(matrix::TimeResolvedMatrix; n_exp=1, irf_width=0.15, λ=nothing,
max_wavelengths=200) -> GlobalFitResultGlobal analysis of a TimeResolvedMatrix, extracting traces at each wavelength.
Returns a GlobalFitResult with the wavelengths field populated, enabling decay-associated spectra (DAS) via das(result).
Keywords
n_exp::Int=1: Number of exponential componentsirf_width::Float64=0.15: Initial guess for IRF σ in psλ=nothing: Specific wavelengths to fit. Ifnothing, fits all wavelengths.max_wavelengths::Int=200: Refuse to fit more wavelengths than this. Every wavelength addsn_exp + 1free nonlinear parameters plus one bootstrap IRF fit, so an unrestricted fit on CCD-resolution data (e.g. 2048 pixels) builds a multi-gigabyte Jacobian. Pass a subset viaλ(e.g. evenly spaced or SVD-selected wavelengths), or raisemax_wavelengthsexplicitly if you accept the cost.
Examples
result = fit_global(matrix; n_exp=2)
report(result)
# Get decay-associated spectra
spectra = das(result) # n_exp × n_wavelengths matrix
# Broadband CCD data: fit a coarse wavelength subset
result = fit_global(matrix; n_exp=2, λ=range(450, 750, length=50))OpticalSpectroscopy.ExpDecayFit — Type
ExpDecayFit <: AbstractDecayFitResult of exponential decay fitting with instrument response function convolution.
Fields
amplitude,tau,t0,sigma,offset,signal_type,residuals,rsquared
residuals follow the package-wide convention data − fit.
OpticalSpectroscopy.MultiexpDecayFit — Type
MultiexpDecayFit <: AbstractDecayFitResult of multi-exponential decay fitting (n >= 1 components).
Fields
taus::Vector{Float64}: Time constants (sorted fast->slow)amplitudes::Vector{Float64}: Corresponding amplitudest0,sigma,offset,signal_type,residuals,rsquared
residuals follow the package-wide convention data − fit.
Derived properties
n_exp(fit): Number of exponential componentsweights(fit): Relative amplitude weights (normalized to 100%)
OpticalSpectroscopy.GlobalFitResult — Type
GlobalFitResultResult of global fitting multiple traces with shared time constants.
Supports single-exponential (nexp=1) and multi-exponential (nexp>1) global analysis with shared τ values and per-trace amplitudes.
Fields
taus::Vector{Float64}: Shared time constants (sorted fast→slow)sigma::Float64: Shared IRF widtht0::Float64: Shared time zeroamplitudes::Matrix{Float64}: Per-trace amplitudes (ntraces × nexp)offsets::Vector{Float64}: Per-trace offsetslabels::Vector{String}: Trace labelswavelengths::Union{Nothing, Vector{Float64}}: Wavelength axis (from TimeResolvedMatrix input)rsquared::Float64: Global R²rsquared_individual::Vector{Float64}: Per-trace R²residuals::Vector{Vector{Float64}}: Per-trace residuals,data − fit(package-wide convention)
Derived properties
n_exp(fit): Number of exponential componentsdas(fit): Decay-associated spectra (requires TimeResolvedMatrix input)
OpticalSpectroscopy.das — Function
das(fit::GlobalFitResult) -> Matrix{Float64}Return the decay-associated spectra (DAS) as an n_exp × n_wavelengths matrix. Each row is the amplitude spectrum for one time constant.
Requires that the fit was performed on a TimeResolvedMatrix (wavelengths must be available).
Reporting
report(result) prints a formatted summary to the terminal; format_results(result) returns the same content as a Markdown string suitable for composing into lab-notebook entries (e.g. the body of an eLabFTW experiment).
OpticalSpectroscopy.format_results — Function
format_results(result) -> StringReturn a Markdown-formatted string of fit results.
Spectroscopy Utilities
OpticalSpectroscopy.normalize — Function
normalize(x)Normalize array by maximum absolute value. Returns zeros if max is zero.
OpticalSpectroscopy.calc_fwhm — Function
calc_fwhm(x, y; smooth_window=5)
calc_fwhm(spec; smooth_window=5)Calculate full width at half maximum (FWHM) of the dominant positive peak.
The spec form accepts any 1D AbstractSpectroscopyData (uses xdata/ydata).
OpticalSpectroscopy.transmittance_to_absorbance — Function
transmittance_to_absorbance(T; percent=false)Convert transmittance to absorbance: A = -log10(T). Input T is fractional (0 to 1). Use percent=true for percent transmittance.
transmittance_to_absorbance(s::Spectrum; percent=nothing) -> SpectrumConvert a transmittance Spectrum to absorbance, A = -log10(T).
When percent is nothing (default) the transmittance scale is inferred from the :yunit token (:percent → percent, otherwise fractional); pass percent explicitly to override. Throws if :yquantity is present and is not :transmittance (no silent guessing). Nonpositive transmittance (saturated bands) maps to NaN with a warning rather than throwing. Stamps the :yquantity=:absorbance/:yunit=:OD tokens on the result (derived label "Absorbance (OD)") and drops any literal :ylabel, keeping the label derived.
OpticalSpectroscopy.absorbance_to_transmittance — Function
absorbance_to_transmittance(A; percent=false)Convert absorbance to transmittance: T = 10^(-A).
absorbance_to_transmittance(s::Spectrum; percent=false) -> SpectrumConvert an absorbance Spectrum to transmittance, T = 10^(-A).
percent selects the output scale: true gives percent transmittance, false (default) fractional. Throws if :yquantity is present and is not :absorbance (mirrors transmittance_to_absorbance — no silent guessing). Stamps the :yquantity=:transmittance/:yunit (:percent or :fraction) tokens on the result and drops any literal :ylabel, keeping the label derived.
OpticalSpectroscopy.npoints — Function
npoints(d::AbstractSpectroscopyData) -> Int
npoints(d::TimeResolvedMatrix) -> Tuple{Int,Int}Return the number of data points. For 1D data, returns an Int. For 2D data (TimeResolvedMatrix), returns (n_time, n_wavelength).
OpticalSpectroscopy.source_file — Function
source_file(d::AbstractSpectroscopyData) -> StringReturn the source filename for the data.
OpticalSpectroscopy.title — Function
title(d::AbstractSpectroscopyData) -> StringReturn a display title for the data. Defaults to source_file(d).
Spectral Arithmetic
OpticalSpectroscopy.add_spectra — Function
add_spectra(a, b; interpolate=false)Add two spectra element-wise. Returns (x=a.x, y=a.y + b.y).
add_spectra(a::Spectrum, b::Spectrum; interpolate=false) -> SpectrumAdd two spectra. Returns a new Spectrum carrying a's shallow-copied metadata.
OpticalSpectroscopy.divide_spectra — Function
divide_spectra(a, b; interpolate=false)Divide spectrum a by spectrum b element-wise. Returns (x=a.x, y=a.y / b.y).
divide_spectra(a::Spectrum, b::Spectrum; interpolate=false) -> SpectrumDivide a by b element-wise. Returns a new Spectrum carrying a's shallow-copied metadata, retagged with the :ratio signal token (a ratio is no longer in a's y-units).
OpticalSpectroscopy.multiply_spectrum — Function
multiply_spectrum(spec, factor::Real)Scale a spectrum by a constant factor. Returns (x=spec.x, y=spec.y * factor).
multiply_spectrum(s::Spectrum, factor::Real) -> SpectrumScale a spectrum by a constant. Returns a new Spectrum with shallow-copied metadata.
OpticalSpectroscopy.average_spectra — Function
average_spectra(specs...; interpolate=false)Compute the point-wise average of multiple spectra. All spectra are aligned to the x-grid of the first spectrum.
average_spectra(specs::Spectrum...; interpolate=false) -> SpectrumPoint-wise average. Returns a new Spectrum carrying the first spectrum's shallow-copied metadata.
OpticalSpectroscopy.interpolate_spectrum — Function
interpolate_spectrum(x, y, new_x)Resample a spectrum onto a new x-grid using linear interpolation.
interpolate_spectrum(s::Spectrum, new_x) -> SpectrumResample a spectrum onto new_x by linear interpolation. Returns a new Spectrum with shallow-copied metadata. The order of new_x is preserved in the returned Spectrum.
Transforms
OpticalSpectroscopy.kramers_kronig — Function
kramers_kronig(omega, chi; type=:imag_to_real)Kramers-Kronig relation: compute the real part of a response function from its imaginary part (or vice versa).
Uses the Maclaurin formula (alternate-point Cauchy principal value integration; Ohta & Ishida, Appl. Spectrosc. 42, 952 (1988)) for numerical stability without requiring FFT.
Arguments
omega: Frequency/energy/wavenumber axis (should be uniformly spaced)chi: Input spectrum (imaginary or real part)type::imag_to_real(default) or:real_to_imag
Returns the computed conjugate part.
OpticalSpectroscopy.kubelka_munk — Function
kubelka_munk(R)Convert diffuse reflectance R to the Kubelka-Munk function F(R).
F(R) = (1 - R)^2 / (2R)R should be fractional (0 to 1). Use R/100 for percent reflectance. Scalar function; use broadcast for vectors: kubelka_munk.(R_vec).
kubelka_munk(s::Spectrum; percent=nothing) -> SpectrumKubelka-Munk transform of a reflectance Spectrum. F(R) needs fractional R: when percent is nothing (default) the reflectance scale is inferred from the :yunit token (:percent → divide by 100, otherwise fractional); pass percent explicitly to override. Throws if :yquantity is present and is not :reflectance (no silent guessing — mirrors transmittance_to_absorbance). Tags the result with the :kubelka_munk signal-quantity token (derived label "F(R)") and drops any stale reflectance signal tokens or literal :ylabel, keeping the label derived.
OpticalSpectroscopy.tauc_plot — Function
tauc_plot(energy, absorption; gap_type=:direct, fit_range=nothing)Construct a Tauc plot for optical bandgap determination.
Returns (hv, tauc_y, bandgap, fit_x, fit_y).
Arguments
energy: Photon energy in eVabsorption: Absorption coefficient, absorbance, or Kubelka-Munk F(R)gap_type::direct(n=2),:indirect(n=1/2),:direct_forbidden(n=2/3),:indirect_forbidden(n=1/3)fit_range: Energy range(min, max)for linear fit (auto-detected ifnothing)
OpticalSpectroscopy.snv — Function
snv(y::AbstractVector)Standard Normal Variate: (y - mean(y)) / std(y).
Removes multiplicative scatter effects in diffuse reflectance spectra.
snv(s::Spectrum) -> SpectrumStandard normal variate transform of a Spectrum. Returns a new Spectrum with shallow-copied metadata.
OpticalSpectroscopy.beer_lambert — Function
beer_lambert(A, l)
beer_lambert(A, l, c)Compute absorption-related quantities from Beer-Lambert law.
beer_lambert(A, l)returns A/l (absorbance per unit path length)beer_lambert(A, l, c)returns molar extinction coefficient epsilon = A/(l*c)
OpticalSpectroscopy.urbach_tail — Function
urbach_tail(energy, absorption; fit_range=nothing)Fit the Urbach tail to determine the Urbach energy Eu.
Below the bandgap, absorption follows: alpha(E) = alpha_0 * exp((E - E0) / Eu)
Returns (Eu, alpha0, E0, fit_x, fit_y).
Arguments
energy: Photon energy (eV)absorption: Absorption coefficient or absorbancefit_range: Energy range(min, max)for the exponential fit region. Ifnothing, auto-selects the sub-gap region.
OpticalSpectroscopy.thickness_from_fringes — Function
thickness_from_fringes(wavenumber, spectrum; n)Estimate thin film thickness from interference fringe spacing.
d = 1 / (2n * delta_nu)where delta_nu is the fringe spacing in cm^-1 and n is the refractive index.
Returns (thickness, fringe_positions) where thickness is in cm.
OpticalSpectroscopy.reflectance_to_absorbance — Function
reflectance_to_absorbance(R)Convert specular reflectance to pseudo-absorbance: A = -log10(R). Works on scalars and vectors via broadcasting.
OpticalSpectroscopy.absorbance_to_reflectance — Function
absorbance_to_reflectance(A)Convert absorbance to reflectance via transmittance: R = 1 - 10^(-A). Works on scalars and vectors.
OpticalSpectroscopy.transmittance_to_reflectance — Function
transmittance_to_reflectance(T; percent=false)Convert transmittance to reflectance for a non-absorbing, non-scattering sample: R = 1 - T. Input T is fractional (0 to 1); use percent=true for percent transmittance. Works on scalars and vectors.
Unit Conversions
OpticalSpectroscopy.wavenumber_to_wavelength — Function
wavenumber_to_wavelength(ν̃; output_unit=u"nm")Convert wavenumber (cm⁻¹) to wavelength.
OpticalSpectroscopy.wavelength_to_wavenumber — Function
wavelength_to_wavenumber(λ; output_unit=u"cm^-1")Convert wavelength to wavenumber. Input assumed in nm if unitless.
OpticalSpectroscopy.wavenumber_to_energy — Function
wavenumber_to_energy(ν̃; output_unit=u"eV")Convert wavenumber (cm⁻¹) to photon energy.
OpticalSpectroscopy.wavelength_to_energy — Function
wavelength_to_energy(λ; output_unit=u"eV")Convert wavelength to photon energy. Input assumed in nm if unitless.
OpticalSpectroscopy.energy_to_wavelength — Function
energy_to_wavelength(E; output_unit=u"nm")Convert photon energy to wavelength. Input assumed in eV if unitless.
OpticalSpectroscopy.linewidth_to_decay_time — Function
linewidth_to_decay_time(Γ; output_unit=u"ps")Convert spectral linewidth (FWHM) to decay time: τ = ℏ/Γ. Input assumed in meV if unitless.
OpticalSpectroscopy.decay_time_to_linewidth — Function
decay_time_to_linewidth(τ; output_unit=u"meV")Convert excited-state decay time to natural linewidth (FWHM): Γ = ℏ/τ. Input assumed in ps if unitless.