Initial commit
This commit is contained in:
+65
@@ -0,0 +1,65 @@
|
||||
"""Shared spectrum I/O and relative spectral-response correction.
|
||||
|
||||
A "spectrum CSV" throughout this project is two columns with a one-line header:
|
||||
`wavelength_nm,value`. Measured spectra, dark frames and responsivity.csv all
|
||||
share that shape, so one loader serves them all.
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
_H, _C, _K = 6.62607015e-34, 2.99792458e8, 1.380649e-23
|
||||
|
||||
|
||||
def planck(wl_nm, temperature):
|
||||
"""Blackbody spectral radiance vs wavelength, arbitrary scale.
|
||||
|
||||
A greybody with flat emissivity. Used both as a calibration reference (a
|
||||
tungsten lamp near this shape) and as the fit model for a hot emitter. A
|
||||
real filament's emissivity falls slowly toward the NIR, a gentle tilt we
|
||||
accept for relative work.
|
||||
"""
|
||||
lam = wl_nm * 1e-9
|
||||
return 1.0 / (lam ** 5 * np.expm1(_H * _C / (lam * _K * temperature)))
|
||||
|
||||
|
||||
def load_csv(path):
|
||||
"""Load a two-column spectrum CSV -> (wavelength_nm, value) arrays."""
|
||||
wl, value = np.loadtxt(path, delimiter=",", skiprows=1, unpack=True)
|
||||
return wl, value
|
||||
|
||||
|
||||
def add_calibration_args(parser):
|
||||
"""Add the shared --response / --dark options to an argparse parser."""
|
||||
parser.add_argument("-r", "--response", metavar="CSV", default=None,
|
||||
help="responsivity.csv from calibrate_response.py; "
|
||||
"output becomes relative irradiance")
|
||||
parser.add_argument("--dark", metavar="CSV", default=None,
|
||||
help="dark_model.csv from capture_dark.py; the dark is "
|
||||
"reconstructed for this measurement's integration time")
|
||||
|
||||
|
||||
def dark_at(dark_path, integration_us):
|
||||
"""Reconstruct the dark at an integration time from a dark_model.csv.
|
||||
|
||||
Returns a (wavelength_nm, counts) tuple, or None if `dark_path` is falsy.
|
||||
The model is per-pixel `offset + rate * t_us` (see capture_dark.py).
|
||||
"""
|
||||
if not dark_path:
|
||||
return None
|
||||
wl, offset, rate = np.loadtxt(dark_path, delimiter=",", skiprows=1, unpack=True)
|
||||
return wl, offset + rate * integration_us
|
||||
|
||||
|
||||
def apply_response(wl, counts, cal, dark=None):
|
||||
"""Response-correct `counts` on grid `wl` into relative-irradiance units.
|
||||
|
||||
corrected = (counts - dark) / R, with dark and R interpolated onto `wl`.
|
||||
`cal` and `dark` are (wl, value) tuples; `dark` may be None. The dark here
|
||||
MUST match the one used to build the calibration. Non-positive or non-finite
|
||||
R (i.e. outside the calibrated band) yields NaN.
|
||||
"""
|
||||
signal = counts.astype(float)
|
||||
if dark is not None:
|
||||
signal = signal - np.interp(wl, dark[0], dark[1])
|
||||
r = np.interp(wl, cal[0], cal[1])
|
||||
r = np.where(np.isfinite(r) & (r > 0), r, np.nan)
|
||||
return signal / r
|
||||
Reference in New Issue
Block a user