Files
radiacode/web/app/routers/spectrum.py
Jacquin Antoine 75d271c696 Background réaliste CsI(Tl) + hybridation mesuré/synthétique + dashboard continuum
- Remplace le continuum exponentiel par un modèle réaliste CsI(Tl) dans
  l'entraînement (bosse asymétrique ~110 keV + queue Compton)
- Ajoute l'injection de background mesuré (70% mesuré / 30% synthétique)
  via --measured_background et MEASURED_BACKGROUND_PATH
- Ajoute l'endpoint /api/background/continuum et le toggle "Continuum CsI"
  sur le dashboard background
- Exclut le canal 1023 (overflow bin) de l'affichage web (NUM_CHANNELS=1023)
- Corrige le lissage Gaussien du background (normalisation locale aux bords)
- Met à jour README.md, CLAUDE.md, TUTORIEL.md, TOTO.md, vega_ml/README.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 18:14:00 +02:00

76 lines
2.7 KiB
Python

import json
from fastapi import APIRouter, HTTPException
from app.config import STATE_PATH, BACKGROUND_PATH, energy_axis, NUM_CHANNELS
import numpy as np
router = APIRouter()
@router.get("/current")
async def get_current_spectrum():
"""Current accumulated spectrum with energy axis."""
if not STATE_PATH.exists():
raise HTTPException(status_code=503, detail="Monitor not started yet")
try:
with open(STATE_PATH) as f:
state = json.load(f)
except (json.JSONDecodeError, OSError):
raise HTTPException(status_code=503, detail="Monitor state file corrupt")
return {
"timestamp": state.get("timestamp", ""),
"connected": state.get("connected", False),
"cumulated_live_time_s": state.get("cumulated_live_time_s", 0),
"cumulated_live_time_h": state.get("cumulated_live_time_h", 0),
"cps": state.get("cps", 0),
"total_counts": state.get("total_counts", 0),
"background_subtracted": state.get("background_subtracted", False),
"isotopes_detected": state.get("isotopes_detected", []),
"channels": list(range(NUM_CHANNELS)),
"energy_kev": energy_axis(),
"counts": state.get("counts", [0] * 1024)[:NUM_CHANNELS],
}
@router.get("/difference")
async def get_difference_spectrum():
"""Background-subtracted spectrum (net signal)."""
if not STATE_PATH.exists():
raise HTTPException(status_code=503, detail="Monitor not started yet")
try:
with open(STATE_PATH) as f:
state = json.load(f)
except (json.JSONDecodeError, OSError):
raise HTTPException(status_code=503, detail="Monitor state file corrupt")
counts = np.array(state.get("counts", [0] * 1024), dtype=np.float64)[:NUM_CHANNELS]
live_time = state.get("cumulated_live_time_s", 0)
if live_time <= 0:
raise HTTPException(status_code=503, detail="No live time data yet")
rate = counts / live_time
if BACKGROUND_PATH.exists():
bg_data = np.load(str(BACKGROUND_PATH), allow_pickle=True).item()
bg_counts = bg_data["counts"].astype(np.float64)[:NUM_CHANNELS]
bg_live_time = float(bg_data["duration"])
bg_rate = bg_counts / bg_live_time
net_rate = np.clip(rate - bg_rate, 0, None)
net_counts = net_rate * live_time
bg_available = True
else:
net_counts = counts
bg_available = False
return {
"timestamp": state.get("timestamp", ""),
"cumulated_live_time_s": live_time,
"background_subtracted": bg_available,
"channels": list(range(NUM_CHANNELS)),
"energy_kev": energy_axis(),
"counts": [round(float(c), 1) for c in net_counts],
"raw_counts": state.get("counts", [])[:NUM_CHANNELS],
}