Files
pyrometry/fit_sequence.py
T
2026-09-10 15:11:55 +01:00

89 lines
3.4 KiB
Python

#!/usr/bin/env python
"""Fit a temperature to each spectrum in a sequence and plot T versus time.
For a cooling or heating object: grab a burst of frames, then fit each and watch
the temperature track. Reuses the Planck fit from fit_temperature.py.
Inputs are spectrum CSVs in time order. With --response (and --dark plus
--integration) each is response-corrected first, exactly as grab_spectrum.py
--response would; otherwise they are assumed already corrected. Time comes from
the file modification times (first file = 0), or pass --interval for even spacing.
Note the printed +/- is the formal fit error and understates the truth; the
real scatter is frame-to-frame noise and the gray-body and reference systematics.
Usage: python fit_sequence.py f1.csv f2.csv ... [--response R.csv --dark D.csv
--integration US] [--band LO HI] [--interval S]
[--out T.csv] [--png fig.png]
"""
import argparse
import os
import numpy as np
from response import add_calibration_args, apply_response, dark_at, load_csv
from fit_temperature import fit_planck
p = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument("spectra", nargs="+", help="spectrum CSVs, in time order")
p.add_argument("--integration", type=int, metavar="US",
help="integration time (us) the spectra were captured at; required with --dark")
p.add_argument("--band", nargs=2, type=float, default=[420.0, 800.0],
metavar=("LO", "HI"), help="fit band in nm (default: 420 800)")
p.add_argument("--interval", type=float, default=None,
help="seconds between frames (default: use file modification times)")
p.add_argument("--out", default=None, help="write a time,temperature_K,dT_K CSV")
p.add_argument("--png", default="temperature_sequence.png")
add_calibration_args(p)
args = p.parse_args()
cal = load_csv(args.response) if args.response else None
dark = None
if args.dark:
if args.integration is None:
p.error("--integration US is required with --dark")
dark = dark_at(args.dark, args.integration)
if args.interval is not None:
times = np.arange(len(args.spectra)) * args.interval
else:
t0 = os.path.getmtime(args.spectra[0])
times = np.array([os.path.getmtime(f) - t0 for f in args.spectra])
rows = []
for t, f in zip(times, args.spectra):
wl, y = load_csv(f)
if cal is not None:
y = apply_response(wl, y, cal, dark)
result = fit_planck(wl, y, tuple(args.band))
if result is None:
print(f"{f}: no fit (too few usable points in band)")
continue
temperature, dT, _ = result
rows.append((t, temperature, dT))
print(f"{f}: t = {t:7.2f} s T = {temperature:.2f} +/- {dT:.2f} K")
if not rows:
p.error("no spectra could be fit; check --band and inputs")
rows = np.array(rows)
if args.out:
np.savetxt(args.out, rows, delimiter=",",
header="time_s,temperature_K,dT_K", comments="")
print(f"wrote {args.out}")
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(9, 5))
ax.errorbar(rows[:, 0], rows[:, 1], yerr=rows[:, 2], fmt="o-", capsize=3)
ax.set_xlabel("time (s)")
ax.set_ylabel("temperature (K)")
ax.set_title(f"Temperature sequence, fit {args.band[0]:.0f}-{args.band[1]:.0f} nm")
ax.grid(alpha=0.3)
fig.tight_layout()
fig.savefig(args.png, dpi=110)
print(f"wrote {args.png}")