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.OpticalSpectroscopyModule

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.

source

Data Types

OpticalSpectroscopy.AbstractSpectroscopyDataType
AbstractSpectroscopyData

Abstract 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 2D
  • zdata(d) — Matrix data for 2D types, nothing for 1D
  • xlabel(d) — X-axis label string
  • ylabel(d) — Y-axis or signal label string
  • is_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: zdata is (n_time, n_wavelength) = (length(ydata), length(xdata)) — plot with heatmap(xdata, ydata, zdata').
  • PLMap: zdata is (nx, ny) = (length(xdata), length(ydata)) — plot with heatmap(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
end
source
OpticalSpectroscopy.SpectrumType
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). See axis_label.
  • :xlabel / :ylabel — literal label strings; override the tokens.
  • :time_delay (+ :time_delay_unit) — fixed delay of a slice from a TimeResolvedMatrix; :gate_start / :gate_end (+ :gate_unit) — the time window of a gated/integrated slice.
  • :filename / :source — source file for source_file.
  • :cavity_length — default cavity length for fit_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 axis
  • y::Vector{Float64}: Signal
  • metadata::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]
source
OpticalSpectroscopy.KineticTraceType
KineticTrace <: AbstractSpectroscopyData

Single-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 axis
  • signal::Vector{Float64}: Signal at each time point
  • wavelength::Float64: Probe/emission wavelength, NaN if unknown
  • metadata::Dict{Symbol,Any}: Additional info
source
OpticalSpectroscopy.TimeResolvedMatrixType
TimeResolvedMatrix <: AbstractSpectroscopyData

Two-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 axis
  • wavelength::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.

source
OpticalSpectroscopy.SweepDataType
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 sweep
  • Y::Matrix{Float64}: quadrature component per sweep
  • DC::Matrix{Float64}: probe DC level per sweep (0.0 if 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)
source
OpticalSpectroscopy.TASpectrumFitType
TASpectrumFit

Result 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 of TAPeak
  • result[i] — i-th peak
  • result[:esa] — first peak with label :esa
  • anharmonicity(result) — GSB center minus ESA center (NaN if not applicable)

Fields

  • peaks::Vector{TAPeak} — Fitted peak parameters
  • offset, rsquared, residuals — Fit metadata; residuals are data − fit (package-wide convention)
source
OpticalSpectroscopy.TAPeakType
TAPeak

Information 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 position
  • width::Float64 — Width parameter (sigma for gaussian/voigt, fwhm for lorentzian)
  • amplitude::Float64 — Peak amplitude (always positive; sign determined by label)
source
OpticalSpectroscopy.fit_ta_spectrumFunction
fit_ta_spectrum(spec::Spectrum; kwargs...) -> TASpectrumFit

Fit 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 a Symbol (: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 contribution
source
OpticalSpectroscopy.anharmonicityFunction
anharmonicity(fit::TASpectrumFit) -> Float64

Compute the anharmonicity (GSB center - ESA center) from a TA spectrum fit. Returns NaN if there is not exactly one ESA and one GSB peak.

source

Time-Resolved Analysis

Slice extraction, binning, cosmic ray removal, and lifetime fitting for streak-camera and broadband TA data.

OpticalSpectroscopy.kinetic_traceFunction
kinetic_trace(m::TimeResolvedMatrix; wavelength, band=0.0) -> KineticTrace

Extract 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.

source
OpticalSpectroscopy.spectral_sliceFunction
spectral_slice(m::TimeResolvedMatrix; time, window=0.0) -> Spectrum

Extract 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.

source
OpticalSpectroscopy.integrate_timeFunction
integrate_time(m::TimeResolvedMatrix; t_range=nothing) -> Spectrum

Time-integrated spectrum: sum over the time axis (preserves total counts), optionally restricted to t_range = (t_lo, t_hi).

source
OpticalSpectroscopy.bin_matrixFunction
bin_matrix(m::TimeResolvedMatrix; time=1, wavelength=1) -> TimeResolvedMatrix

Block-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).

source
OpticalSpectroscopy.fit_lifetime_spectrumFunction
fit_lifetime_spectrum(m::TimeResolvedMatrix; n_exp=1, nbins=32,
                      t_range=nothing, min_signal=0.0) -> LifetimeSpectrumResult

Fit 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 matrix
  • n_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 to fit_exp_decay
  • min_signal: skip bins whose peak |signal| is below this (default 0.0 = fit all)
source
OpticalSpectroscopy.StretchedDecayFitType
StretchedDecayFit <: AbstractDecayFit

Result of stretched-exponential (Kohlrausch–Williams–Watts) decay fitting:

signal(t) = A·exp(-((t - t₀)/τ)^β) + offset

Fields

  • amplitude::Float64
  • tau::Float64: Characteristic time constant
  • beta::Float64: Stretching exponent (0 < β ≤ 1)
  • t0::Float64: Fit-region time origin
  • offset::Float64
  • signal_type::Symbol: :esa (positive) or :gsb (negative)
  • residuals::Vector{Float64}: data − fit (package-wide convention)
  • rsquared::Float64

See mean_lifetime for ⟨τ⟩ = (τ/β)·Γ(1/β).

source
OpticalSpectroscopy.LifetimeSpectrumResultType
LifetimeSpectrumResult

Result of fit_lifetime_spectrum: per-wavelength-bin decay fits.

Fields

  • wavelength::Vector{Float64}: Bin centers (mean wavelength per bin); NaN for empty bins
  • taus::Matrix{Float64}: Time constants, size (nbins, n_exp); NaN where skipped
  • amplitudes::Matrix{Float64}: Amplitudes, size (nbins, n_exp); NaN where skipped
  • rsquared::Vector{Float64}: R² per bin; NaN where skipped
  • fitted::BitVector: Whether each bin was fitted
  • n_exp::Int: Exponential components per fit
source

Data Interface

Uniform accessors implemented by every AbstractSpectroscopyData subtype.

OpticalSpectroscopy.xlabelFunction
xlabel(d::AbstractSpectroscopyData) -> String

Return a display label for the x axis (e.g. "Time (ps)", "Wavenumber (cm⁻¹)").

source
OpticalSpectroscopy.zlabelFunction
zlabel(d::AbstractSpectroscopyData) -> String

Return a display label for the signal of 2D data (e.g. "ΔA", "PL Intensity").

source

Semantic Accessors

Type-specific accessors with domain names.

OpticalSpectroscopy.signalFunction
signal(t::KineticTrace) -> Vector{Float64}

Return the signal values (ΔA, counts, or other units depending on data source).

source
signal(m::TimeResolvedMatrix) -> Matrix{Float64}

Return the signal matrix (ntime × nwavelength).

source
signal(s::Spectrum) -> Vector{Float64}

Return the signal.

source

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_quantityFunction
normalize_quantity(s::AbstractString) -> Symbol

Map 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.

source
OpticalSpectroscopy.normalize_unitFunction
normalize_unit(s::AbstractString) -> Symbol

Map 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.

source
OpticalSpectroscopy.axis_labelFunction
axis_label(quantity::Symbol, unit::Symbol) -> String

Compose 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.

source
OpticalSpectroscopy.is_canonicalFunction
is_canonical(slot::Symbol, value::Symbol) -> Bool

Whether value is in the controlled vocabulary for slot. Slots: :technique, :quantity (axis or signal), :unit. Unknown slots return false.

source
OpticalSpectroscopy.validate_tokensFunction
validate_tokens(metadata::AbstractDict) -> Bool

Check 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.

source
OpticalSpectroscopy.guess_units!Function
guess_units!(s::Spectrum) -> Spectrum

Opt-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).

source
OpticalSpectroscopy.xdata_unitfulFunction
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.

source
OpticalSpectroscopy.ydata_unitfulFunction
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.

source

Plotting

OpticalSpectroscopy.plot_fitFunction
plot_fit(fit::MultiPeakFitResult; residuals=true, components=false,
         baseline=false, xlabel="x", ylabel="Signal",
         figure=(;), axis=(;)) -> Figure

Plot 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 axis
  • components::Bool=false — Draw each fitted peak separately
  • baseline::Bool=false — Draw the fitted polynomial baseline
  • xlabel, ylabel — Axis labels
  • figure, axis — NamedTuples forwarded to Figure/Axis

Example

using GLMakie, OpticalSpectroscopy
result = fit_peaks(x, y, (2000, 2100); n_peaks=2)
plot_fit(result; components=true, baseline=true)
source

Chirp Correction

OpticalSpectroscopy.ChirpCalibrationType
ChirpCalibration

Stores 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 used
  • reference_λ: Reference wavelength where chirp = 0 (nm)
  • r_squared: How well the polynomial fits the points that were detected
  • metadata: Detection parameters for reproducibility
R² does not measure detection accuracy

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.

source
OpticalSpectroscopy.calibrate_chirpFunction
calibrate_chirp(oke::TimeResolvedMatrix; kwargs...) -> ChirpCalibration

Measure 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 in metadata[: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:gaussian or :peak (see above)
  • order::Int = 3 — chirp polynomial order
  • reference = :center — wavelength (nm) where the polynomial is zero; :center uses the midpoint of the surviving wavelength coverage
  • wl_range = nothing(λmin, λmax): restrict which columns are considered
  • t_range = nothing(tmin, tmax): restrict the per-column fit window
  • min_amplitude::Real = 0.05 — amplitude mask threshold as a fraction of the strongest column's peak-to-peak amplitude; 0 disables the mask
  • source = nothing — provenance string stored as metadata[:source_file]; defaults to the OKE matrix's own metadata[: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)
source
OpticalSpectroscopy.detect_chirpFunction
detect_chirp(matrix::TimeResolvedMatrix; kwargs...) -> ChirpCalibration

Detect chirp (GVD) in a broadband TA matrix via cross-correlation (:xcorr) or threshold crossing (:threshold). Returns a ChirpCalibration with polynomial fit.

source
OpticalSpectroscopy.correct_chirpFunction
correct_chirp(matrix::TimeResolvedMatrix, cal::ChirpCalibration) -> TimeResolvedMatrix

Apply 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.

source
OpticalSpectroscopy.polynomialFunction
polynomial(cal::ChirpCalibration) -> Function

Return a callable polynomial t_shift = poly(λ) from the calibration. Coefficients are in ascending order: c[1] + c[2]*λ + c[3]*λ² + ...

source

SVD Filtering

OpticalSpectroscopy.svd_filterFunction
svd_filter(matrix::TimeResolvedMatrix; n_components::Int=5) -> TimeResolvedMatrix

Denoise 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. Use singular_values to 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)
source
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
source
OpticalSpectroscopy.singular_valuesFunction
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)
source
singular_values(data::AbstractMatrix) -> Vector{Float64}

Return the singular values of a raw data matrix.

source
OpticalSpectroscopy.estimate_n_componentsFunction
estimate_n_components(sv; ratio=10.0) -> Int

Estimate 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.

source

Exponential Decay Fitting

OpticalSpectroscopy.fit_exp_decayFunction
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: KineticTrace
  • n_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 region
  • model: :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: For n_exp = 1 a number; for n_exp > 1 a vector of length n_exp whose entries are numbers or nothing (nothing = keep the automatic guess for that component). tau0 values must be positive.
  • offset0: Constant-offset guess.
  • beta0: Stretching-exponent guess in (0, 1] (model=:stretched only).

Returns

  • n_exp=1: ExpDecayFit
  • n_exp>1: MultiexpDecayFit
  • model=:stretched: StretchedDecayFit
source
OpticalSpectroscopy.fit_globalFunction
fit_global(traces::Vector{KineticTrace}; n_exp=1, irf_width=0.15, labels=nothing) -> GlobalFitResult

Fit 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 components
  • irf_width::Float64=0.15: Initial guess for IRF σ in ps
  • labels=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)
source
fit_global(matrix::TimeResolvedMatrix; n_exp=1, irf_width=0.15, λ=nothing,
           max_wavelengths=200) -> GlobalFitResult

Global 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 components
  • irf_width::Float64=0.15: Initial guess for IRF σ in ps
  • λ=nothing: Specific wavelengths to fit. If nothing, fits all wavelengths.
  • max_wavelengths::Int=200: Refuse to fit more wavelengths than this. Every wavelength adds n_exp + 1 free 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 raise max_wavelengths explicitly 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))
source
OpticalSpectroscopy.ExpDecayFitType
ExpDecayFit <: AbstractDecayFit

Result 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.

source
OpticalSpectroscopy.MultiexpDecayFitType
MultiexpDecayFit <: AbstractDecayFit

Result of multi-exponential decay fitting (n >= 1 components).

Fields

  • taus::Vector{Float64}: Time constants (sorted fast->slow)
  • amplitudes::Vector{Float64}: Corresponding amplitudes
  • t0, sigma, offset, signal_type, residuals, rsquared

residuals follow the package-wide convention data − fit.

Derived properties

  • n_exp(fit): Number of exponential components
  • weights(fit): Relative amplitude weights (normalized to 100%)
source
OpticalSpectroscopy.GlobalFitResultType
GlobalFitResult

Result 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 width
  • t0::Float64: Shared time zero
  • amplitudes::Matrix{Float64}: Per-trace amplitudes (ntraces × nexp)
  • offsets::Vector{Float64}: Per-trace offsets
  • labels::Vector{String}: Trace labels
  • wavelengths::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 components
  • das(fit): Decay-associated spectra (requires TimeResolvedMatrix input)
source
OpticalSpectroscopy.dasFunction
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).

source

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).

Spectroscopy Utilities

OpticalSpectroscopy.calc_fwhmFunction
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).

source
OpticalSpectroscopy.transmittance_to_absorbanceFunction
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.

source
transmittance_to_absorbance(s::Spectrum; percent=nothing) -> Spectrum

Convert 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.

source
OpticalSpectroscopy.absorbance_to_transmittanceFunction
absorbance_to_transmittance(A; percent=false)

Convert absorbance to transmittance: T = 10^(-A).

source
absorbance_to_transmittance(s::Spectrum; percent=false) -> Spectrum

Convert 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.

source
OpticalSpectroscopy.npointsFunction
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).

source

Spectral Arithmetic

OpticalSpectroscopy.add_spectraFunction
add_spectra(a, b; interpolate=false)

Add two spectra element-wise. Returns (x=a.x, y=a.y + b.y).

source
add_spectra(a::Spectrum, b::Spectrum; interpolate=false) -> Spectrum

Add two spectra. Returns a new Spectrum carrying a's shallow-copied metadata.

source
OpticalSpectroscopy.divide_spectraFunction
divide_spectra(a, b; interpolate=false)

Divide spectrum a by spectrum b element-wise. Returns (x=a.x, y=a.y / b.y).

source
divide_spectra(a::Spectrum, b::Spectrum; interpolate=false) -> Spectrum

Divide 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).

source
OpticalSpectroscopy.multiply_spectrumFunction
multiply_spectrum(spec, factor::Real)

Scale a spectrum by a constant factor. Returns (x=spec.x, y=spec.y * factor).

source
multiply_spectrum(s::Spectrum, factor::Real) -> Spectrum

Scale a spectrum by a constant. Returns a new Spectrum with shallow-copied metadata.

source
OpticalSpectroscopy.average_spectraFunction
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.

source
average_spectra(specs::Spectrum...; interpolate=false) -> Spectrum

Point-wise average. Returns a new Spectrum carrying the first spectrum's shallow-copied metadata.

source
OpticalSpectroscopy.interpolate_spectrumFunction
interpolate_spectrum(x, y, new_x)

Resample a spectrum onto a new x-grid using linear interpolation.

source
interpolate_spectrum(s::Spectrum, new_x) -> Spectrum

Resample 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.

source

Transforms

OpticalSpectroscopy.kramers_kronigFunction
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.

source
OpticalSpectroscopy.kubelka_munkFunction
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).

source
kubelka_munk(s::Spectrum; percent=nothing) -> Spectrum

Kubelka-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.

source
OpticalSpectroscopy.tauc_plotFunction
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 eV
  • absorption: 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 if nothing)
source
OpticalSpectroscopy.snvFunction
snv(y::AbstractVector)

Standard Normal Variate: (y - mean(y)) / std(y).

Removes multiplicative scatter effects in diffuse reflectance spectra.

source
snv(s::Spectrum) -> Spectrum

Standard normal variate transform of a Spectrum. Returns a new Spectrum with shallow-copied metadata.

source
OpticalSpectroscopy.beer_lambertFunction
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)
source
OpticalSpectroscopy.urbach_tailFunction
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 absorbance
  • fit_range: Energy range (min, max) for the exponential fit region. If nothing, auto-selects the sub-gap region.
source
OpticalSpectroscopy.thickness_from_fringesFunction
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.

source
OpticalSpectroscopy.transmittance_to_reflectanceFunction
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.

source

Unit Conversions