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>
166 lines
7.0 KiB
JavaScript
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;
|
|
} |