Files
radiacode/web/static/js/isotope_lines.js
Jacquin Antoine 0847a3fc80 Fix: CsI(Tl) non-linear response correction + detector calibration overhaul
Root cause of Am-241 misidentification: the Radiacode 103's CsI(Tl) crystal
shifts low-energy peaks upward (59.5 keV → 71.6 keV for Am-241) due to
non-proportional scintillation response. The model was trained on theoretical
peak positions and couldn't match the shifted real peaks.

Changes:
- Add inverse CsI(Tl) non-linear correction to inference pipeline
  (radiacode_monitor.py, web/config.py, test_detection.py)
  E_apparent = E_true * (1 + 0.37 * exp(-E_true/100))
  Corrects channel mapping so peaks appear at theoretical energies
- Fix energy calibration: DetectorConfig now uses E = 0.33 + 2.97*ch
  with 1023 channels, matching the real detector (was energy_min=20,
  skip_first_channel=True, different channel width)
- Add K-escape peaks for CsI(Tl) iodine X-ray escape (E - 28.5 keV)
- Add asymmetric peak shapes for low-energy tails (< 200 keV)
- Add log1p normalization in dataset and inference (replaces max-norm)
- Add background-subtracted training mode (subtract_background flag)
- Add low-signal augmentation (0.01-5 Bq activities, 30-300s durations)
- Update docker-compose.yml: batch_size=32, duration=30-300s,
  CSI_NONLINEAR_ALPHA/BETA env vars for detect and web
- Web dashboard: apply CsI correction to displayed spectra
- Various UI fixes (Chart.js width, zoom/pan, isotope lines)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 17:35:22 +02:00

166 lines
7.0 KiB
JavaScript

// Raies gamma principales pour les isotopes les plus courants
// Format : { isotope, energy_keV, intensity } (intensity = % de désintégration)
const ISOTOPE_LINES = [
// Chaîne Uranium-238 (présent naturellement)
{ isotope: "Bi-214", energy_keV: 609.3, intensity: 45.5 },
{ isotope: "Bi-214", energy_keV: 1120.3, intensity: 15.0 },
{ isotope: "Bi-214", energy_keV: 1764.5, intensity: 15.4 },
{ isotope: "Pb-214", energy_keV: 295.2, intensity: 19.2 },
{ isotope: "Pb-214", energy_keV: 351.9, intensity: 37.6 },
{ isotope: "Ra-226", energy_keV: 186.2, intensity: 3.6 },
// Chaîne Thorium-232 (présent naturellement)
{ isotope: "Ac-228", energy_keV: 911.2, intensity: 27.7 },
{ isotope: "Ac-228", energy_keV: 338.3, intensity: 11.3 },
{ isotope: "Tl-208", energy_keV: 583.2, intensity: 84.5 },
{ isotope: "Tl-208", energy_keV: 2614.5, intensity: 99.0 },
{ isotope: "Pb-212", energy_keV: 238.6, intensity: 43.6 },
// Potassium-40 (naturel, ubiquitaire)
{ isotope: "K-40", energy_keV: 1460.8, intensity: 10.7 },
// Isotopes artificiels courants
{ isotope: "Cs-137", energy_keV: 661.7, intensity: 85.1 },
{ isotope: "Cs-134", energy_keV: 604.7, intensity: 97.6 },
{ isotope: "Cs-134", energy_keV: 795.8, intensity: 85.5 },
{ isotope: "Co-60", energy_keV: 1173.2, intensity: 99.9 },
{ isotope: "Co-60", energy_keV: 1332.5, intensity: 100.0 },
{ isotope: "Co-58", energy_keV: 810.8, intensity: 99.4 },
{ isotope: "I-131", energy_keV: 364.5, intensity: 81.7 },
{ isotope: "I-131", energy_keV: 637.0, intensity: 7.3 },
{ isotope: "I-131", energy_keV: 284.3, intensity: 6.1 },
{ isotope: "Ba-133", energy_keV: 356.0, intensity: 62.0 },
{ isotope: "Ir-192", energy_keV: 316.5, intensity: 82.8 },
{ isotope: "Ir-192", energy_keV: 468.1, intensity: 47.8 },
{ isotope: "Ir-192", energy_keV: 604.4, intensity: 8.2 },
{ isotope: "Am-241", energy_keV: 59.5, intensity: 35.9 },
// Autres courants
{ isotope: "Na-22", energy_keV: 511.0, intensity: 90.0 },
{ isotope: "Na-22", energy_keV: 1274.5, intensity: 99.9 },
{ isotope: "Eu-152", energy_keV: 121.8, intensity: 28.6 },
{ isotope: "Eu-152", energy_keV: 344.3, intensity: 26.6 },
{ isotope: "Eu-152", energy_keV: 1408.0, intensity: 20.9 },
{ isotope: "Mn-54", energy_keV: 834.8, intensity: 99.9 },
{ isotope: "Zn-65", energy_keV: 1115.5, intensity: 50.0 },
{ isotope: "Zr-95/Nb-95", energy_keV: 765.8, intensity: 99.8 },
{ isotope: "Ru-106/Rh-106", energy_keV: 512.0, intensity: 20.5 },
];
// Filtrer les lignes dans la plage visible du détecteur (30-3050 keV pour Radiacode 103)
const VISIBLE_LINES = ISOTOPE_LINES.filter(l => l.energy_keV >= 30 && l.energy_keV <= 3000);
// Global crosshair plugin — vertical dashed line on hover for all charts
const CrosshairPlugin = {
id: 'crosshair',
afterDraw(chart) {
const tooltip = chart.tooltip;
if (!tooltip || !tooltip._active || tooltip._active.length === 0) return;
const x = tooltip._active[0].element.x;
const { top, bottom } = chart.chartArea;
const ctx = chart.ctx;
ctx.save();
ctx.beginPath();
ctx.moveTo(x, top);
ctx.lineTo(x, bottom);
ctx.lineWidth = 1;
ctx.strokeStyle = 'rgba(255,255,255,0.25)';
ctx.setLineDash([4, 3]);
ctx.stroke();
ctx.restore();
},
};
Chart.register(CrosshairPlugin);
// Auto-scale Y axis to visible X range
const AutoScaleYPlugin = {
id: 'autoScaleY',
beforeUpdate(chart) {
const xScale = chart.scales?.x;
const yScale = chart.scales?.y;
if (!xScale || !yScale || xScale.type !== 'linear') return;
const xMin = xScale.min;
const xMax = xScale.max;
if (xMin == null || xMax == null) return;
let yMin = Infinity;
let yMax = -Infinity;
let count = 0;
chart.data.datasets.forEach(ds => {
for (const pt of ds.data) {
const x = Array.isArray(pt) ? pt[0] : pt.x;
const y = Array.isArray(pt) ? pt[1] : pt.y;
if (x === undefined || y === undefined) continue;
if (x >= xMin && x <= xMax) {
if (y > 0 && y < yMin) yMin = y;
if (y > yMax) yMax = y;
count++;
}
}
});
if (count === 0 || yMin === Infinity) return;
const isLog = yScale.type === 'logarithmic';
if (isLog) {
chart.options.scales.y.min = Math.max(0.5, yMin * 0.7);
chart.options.scales.y.max = yMax * 1.5;
} else {
const padding = (yMax - yMin) * 0.05 || 1;
chart.options.scales.y.min = Math.max(0, yMin - padding);
chart.options.scales.y.max = yMax + padding;
}
}
};
Chart.register(AutoScaleYPlugin);
// Couleurs par catégorie d'isotope
function isotopeLineColor(isotope) {
if (["K-40", "Bi-214", "Pb-214", "Ra-226"].includes(isotope)) return "rgba(255,152,0,0.5)"; // Uranium chain - orange
if (["Ac-228", "Tl-208", "Pb-212"].includes(isotope)) return "rgba(156,39,176,0.5)"; // Thorium chain - purple
if (isotope === "K-40") return "rgba(255,193,7,0.5)"; // K-40 - yellow
if (isotope.startsWith("Cs") || isotope.startsWith("I-131")) return "rgba(244,67,54,0.5)"; // Fission products - red
if (isotope.startsWith("Co")) return "rgba(76,175,80,0.5)"; // Activation - green
return "rgba(100,181,246,0.35)"; // Others - light blue
}
function isotopeLabelColor(isotope) {
if (["K-40", "Bi-214", "Pb-214", "Ra-226"].includes(isotope)) return "#ff9800";
if (["Ac-228", "Tl-208", "Pb-212"].includes(isotope)) return "#9c27b0";
if (isotope === "K-40") return "#ffc107";
if (isotope.startsWith("Cs") || isotope.startsWith("I-131")) return "#f44336";
if (isotope.startsWith("Co")) return "#4caf50";
return "#64b5f6";
}
// Créer les annotations Chart.js pour les raies isotopiques
function buildIsotopeAnnotations(showDetectedOnly, detectedIsotopes) {
const annotations = {};
const detected = new Set(detectedIsotopes || []);
VISIBLE_LINES.forEach((line, i) => {
if (showDetectedOnly && !detected.has(line.isotope)) return;
// Regrouper les labels pour éviter les chevauchements
annotations[`line_${i}`] = {
type: 'line',
xMin: line.energy_keV,
xMax: line.energy_keV,
borderColor: isotopeLineColor(line.isotope),
borderWidth: 1,
label: {
display: true,
content: `${line.isotope} ${line.energy_keV}`,
position: 'start',
backgroundColor: 'rgba(26,26,46,0.85)',
color: isotopeLabelColor(line.isotope),
font: { size: 9 },
padding: 2,
rotation: -90,
}
};
});
return annotations;
}