diff --git a/TUTORIEL.md b/TUTORIEL.md new file mode 100644 index 0000000..bd41aac --- /dev/null +++ b/TUTORIEL.md @@ -0,0 +1,416 @@ +# Tutoriel : Radiacode 103 — Du debut a la detection d'isotopes + +Ce tutoriel explique chaque etape du pipeline, pourquoi les durees sont ce qu'elles sont, et comment interpreter les resultats. + +--- + +## 1. Pre-requis materiels + +| Element | Detail | +|---------|--------| +| Radiacode 103 | Spectrometre gamma CsI(Tl), 1024 canaux, USB | +| Machine GPU | NVIDIA avec drivers + nvidia-container-toolkit | +| Docker | Version 20.10+ avec Docker Compose V2 (`docker compose`) | +| Cable USB | Pour connecter le Radiacode a la machine | + +Verifier que le Radiacode est bien detecte : + +```bash +lsusb | grep -i radiacode +# Doit afficher : ID 0483:f123 RadiaCode RadiaCode-103 +``` + +Verifier le GPU : + +```bash +nvidia-smi +# Doit afficher votre carte NVIDIA +``` + +--- + +## 2. Builder les images Docker (~5 min) + +```bash +docker compose build +``` + +Deux images sont construites : +- `radiacode_103-train` : PyTorch 2.7.0 + CUDA 12.8 (pour l'entrainement sur GPU) +- `radiacode_103-detect` : Python 3.11 + radiacode + PyTorch CPU (pour la detection) + +**Pourquoi PyTorch 2.7.0 ?** Les cartes RTX 50-series (Blackwell, compute capability sm_120) ne sont pas supportees par PyTorch < 2.7. Si vous avez une carte plus ancienne (RTX 30/40-series), PyTorch 2.4.0 suffit. + +--- + +## 3. Entrainer le modele (~45 min sur RTX 5060 Ti) + +```bash +docker compose run --rm train +``` + +Cette commande lance deux phases automatiquement : + +### Phase 1 : Generation des spectres synthetiques (~10 min) + +Le generateur cree 50 000 spectres 1D simulant la physique reelle : + +| Type | Nombre | Description | +|------|--------|-------------| +| Mono-isotope | 20 000 | Un seul isotope + background | +| Bi-isotope | 15 000 | Deux isotopes + background | +| Multi-isotope | 10 000 | 3-5 isotopes + background | +| Background seul | 5 000 | Background environnemental uniquement | + +**Pourquoi des durees de 12-24h ?** + +Les spectres synthetiques simulent des acquisitions de 12 a 24 heures (43200-86400 secondes). C'est volontaire : + +1. **Bruit Poissonnien** : La statistique de comptage suit une loi de Poisson. Pour un canal avec N comptages, l'ecart-type est sqrt(N). Le rapport signal/bruit s'ameliore en sqrt(N), donc en sqrt(duree). + - A 6.5 CPS/canal moyen et 1024 canaux, apres 1h on a ~23 comptages/canal (SNR ~5) + - Apres 24h on a ~560 comptages/canal (SNR ~23) + - Apres 1 semaine ~3900 comptages/canal (SNR ~63) + +2. **Conditions reelles** : Le detecteur sera utilise en continu pendant des jours. Le modele doit voir des spectres avec le meme niveau de bruit que les mesures reelles. + +3. **Sensibilite** : Les isotopes a faible activite (1-5 Bq) ne sont detectables qu'apres plusieurs heures d'accumulation. Le modele doit apprendre cette relation. + +**Ce que le generateur simule physiquement :** + +- **Pics photoelectriques** : Gaussiennes dont la largeur (FWHM) depend de l'energie. Pour le CsI(Tl) du Radiacode 103, FWHM = 8.4% a 662 keV, avec la relation FWHM(E) = FWHM_662 * sqrt(E/662). +- **Continuum Compton** : Distribution de Klein-Nishina simplifiee sous chaque pic, simulant la diffusion des photons gamma. +- **Bruit Poissonnien** : Chaque canal est echantillonne depuis une loi Poisson(lambda), ce qui reproduit exactement les fluctuations statistiques reelles. +- **Background environnemental** : Continuum exponentiel + pics de K-40 (1460 keV), Pb-214 (295, 352 keV), Bi-214 (609, 1120, 1764 keV), Ac-228 (911 keV), Pb-212 (239 keV), Tl-208 (583, 2614 keV), avec des activites aleatoires realistes. +- **Efficacite du detecteur** : Modele phenomenologique tenant compte de l'absorption basse energie et de la penetration haute energie du scintillateur CsI(Tl) de 1 cm3. + +### Phase 2 : Entrainement du VegaModel (~35 min sur RTX 5060 Ti) + +Le modele VegaModel est un CNN-FCNN multi-tache : + +``` +Entree : spectre 1D (1023 canaux, 20-3000 keV) + │ + ├── Bloc CNN 1 : Conv1d(1, 64, 7) → BN → ReLU → MaxPool + ├── Bloc CNN 2 : Conv1d(64, 128, 5) → BN → ReLU → MaxPool + ├── Bloc CNN 3 : Conv1d(128, 256, 3) → BN → ReLU → MaxPool + │ + ├── Tete classification : FC(256→512→256→82) → Sigmoid + │ 82 isotopes, probabilite de presence [0, 1] + │ + └── Tete regression : FC(256→512→256→82) + Activite estimee en Bq pour chaque isotope +``` + +- **34 493 156 parametres** au total +- **Fonction de perte** : BCE (classification) + MSE ponderee (regression sur isotopes presents uniquement) +- **Mixed precision (AMP)** : Acceleration sur GPU via float16 +- **Early stopping** : Patience de 10 epochs sans amelioration + +**Resultats typiques :** + +| Metrique | Valeur | +|----------|--------| +| Val accuracy | 99.89% | +| AUC macro | 0.995 | +| Precision | 99.8% | +| Recall | 95.6% | +| Exact match | 93.5% | +| Best val loss | 0.0051 | + +**Fichiers generes dans `models/` :** + +| Fichier | Taille | Description | +|---------|--------|-------------| +| `vega_best.pt` | 395 Mo | Meilleur modele (checkpoint avec val loss minimale) | +| `vega_final.pt` | 395 Mo | Modele final (epoch 100) | +| `vega_isotope_index.txt` | 546 o | Liste des 82 isotopes detectables | +| `vega_history.json` | 75 Ko | Metriques d'entrainement par epoch | + +> **Note** : Seul `vega_best.pt` et `vega_isotope_index.txt` sont necessaires pour la detection. Les checkpoints par epoch (`vega_epoch_*.pt`) peuvent etre supprimes pour gagner ~39 Go. + +--- + +## 4. Capturer le bruit de fond (24h) + +Cette etape est **critique** et ne doit pas etre ignoree. Placez le detecteur dans un endroit sans source radioactive connue. + +```bash +docker compose run --rm -d --name radiacode-bg \ + -e TARGET_DURATION=86400 -e SAMPLE_INTERVAL=60 \ + detect python capture_background.py +``` + +### Pourquoi 24h ? + +Le background environnemental n'est pas constant. Il varie selon : + +1. **Le radon** : Concentration dans l'air qui varie avec la ventilation, la meteo, l'heure du jour (cycle diurne) +2. **Le thorium** : Present dans les materiaux de construction (beton, brique), relativement stable mais varie lentement +3. **Le potassium-40** : Partout dans la nature (aliments, corps humain), assez stable +4. **Les rayons cosmiques** : Varient avec l'altitude et l'activite solaire + +Un echantillonnage de 24h capture un cycle complet de ces variations, donnant un background de reference representative. + +**Pourquoi pas moins ?** + +| Duree | CPS/canal | SNR/canal | Pics visibles | +|-------|-----------|-----------|---------------| +| 1h | ~23 | ~5 | Aucun pic clair | +| 4h | ~94 | ~10 | K-40 commence a emerger | +| 12h | ~280 | ~17 | K-40, Bi-214 visibles | +| **24h** | **~560** | **~24** | **Tous les pics de background nets** | +| 48h | ~1120 | ~33 | Pics tres nets, variations capturees | + +A 24h, les principaux pics du background sont clairement identifiables : +- **K-40 a 1460 keV** : Toujours present (~5-15 Bq dans l'environnement) +- **Bi-214 a 609 keV** : Fille du radon +- **Pb-214 a 295 et 352 keV** : Fille du radon +- **Tl-208 a 2614 keV** : Fille du thorium + +### Suivre la progression + +```bash +# Voir les logs +docker logs -f radiacode-bg + +# Lire le snapshot JSON +cat data/background_snapshot.json | python3 -m json.tool | head -20 +``` + +Le snapshot montre en temps reel les pics detectes, le CPS moyen et la duree ecoulee. + +### Le script gere les deconnexions + +Si le Radiacode est debranche pendant la capture, le script se reconnecte automatiquement et reprend l'accumulation. Les donnees deja collectees ne sont pas perdues. + +### A la fin (24h plus tard) + +Le fichier `data/background_24h.npy` est genere avec : +```python +{ + "counts": np.array, # 1024 canaux, comptages cumules + "duration": float, # live time en secondes + "timestamp": float # timestamp Unix +} +``` + +--- + +## 5. Lancer la detection continue + +```bash +docker compose up detect +``` + +### Comment ca marche + +Toutes les 60 secondes, le moniteur : + +``` +┌──────────────────────────────────────────────────────────────┐ +│ 1. Echantillonnage │ +│ Radiacode.spectrum() → 1024 canaux + duree │ +│ cumulated_counts += counts │ +│ cumulated_live_time += duration.total_seconds() │ +│ Radiacode.spectrum_reset() │ +│ │ +│ 2. A 00h00 chaque jour : Rapport │ +│ Si live_time > 1h : │ +│ rate = cumulated_counts / cumulated_live_time │ +│ bg_rate = bg_counts / bg_live_time │ +│ net_rate = clip(rate - bg_rate, 0, None) │ +│ normalized = net_rate / net_rate.max() │ +│ logits, activities = model(normalized) │ +│ probs = sigmoid(logits) │ +│ Pour chaque isotope avec prob > 0.5 : │ +│ rapport[name, prob%, activite_bq] │ +│ Sauvegarder dans logs/report_YYYY-MM-DD.json │ +│ Reset cumulateurs │ +│ │ +│ 3. Si detecteur debranche : │ +│ Attendre 60s, retenter la connexion │ +│ Les donnees cumulees sont conservees │ +└──────────────────────────────────────────────────────────────┘ +``` + +### Pourquoi soustraire le background ? + +Le modele est entraite avec du background synthetique, mais le background reel varie selon l'emplacement. La soustraction reelle ameliore la detection : + +- **Sans soustraction** : Le modele voit K-40 a 1460 keV et peut le signaler comme isotope detecte, meme si c'est juste du background +- **Avec soustraction** : Le pic de K-40 du background est elimine, seul un signal supplementaire est analyse +- **Resultat** : Moins de faux positifs, meilleure sensibilite pour les isotopes faibles + +### Debranchement et rebranchement + +Le moniteur gere les deconnexions USB proprement : +- Si le Radiacode est debranche, `try_connect()` echoue et retourne `None` +- Le moniteur attend 60 secondes et retente +- Les compteurs cumules ne sont pas reinitialises +- Quand le detecteur est rebranche, l'accumulation reprend + +Cela permet de prendre le detecteur avec soi pendant la journee sans perdre les donnees de la nuit. + +--- + +## 6. Interpreter les rapports + +Chaque jour a `REPORT_HOUR` (00h00 par defaut), un rapport JSON est genere dans `logs/` : + +```json +{ + "date": "2026-05-20T00:00:00", + "live_time_hours": 18.5, + "total_counts": 434500, + "cps_mean": 6.52, + "background_subtracted": true, + "isotopes_detected": [ + { + "isotope": "Cs-137", + "probability": 0.97, + "activity_bq": 12.3 + }, + { + "isotope": "K-40", + "probability": 0.85, + "activity_bq": 8.1 + } + ] +} +``` + +### Champs du rapport + +| Champ | Description | +|-------|-------------| +| `live_time_hours` | Temps effectif d'acquisition (sans les trous de deconnexion) | +| `total_counts` | Nombre total de coups cumules | +| `cps_mean` | Comptage par seconde moyen | +| `background_subtracted` | `true` si le background a ete soustrait | +| `isotopes_detected` | Liste des isotopes avec probabilite > seuil (0.5 par defaut) | + +### Interpretation des resultats + +- **Probabilite > 95%** : Detection certaine, l'isotope est present +- **Probabilite 70-95%** : Detection probable, a confirmer avec plus de donnees +- **Probabilite 50-70%** : Detection incertaine, probablement du bruit ou une confusion spectrale +- **Probabilite < 50%** : Non detecte (en dessous du seuil) + +**Isotopes de background frequents** (normaux, pas une alerte) : +- K-40 : Toujours present dans l'environnement (~5-15 Bq) +- Bi-214 / Pb-214 : Filles du radon, varient selon la ventilation +- Ac-228 / Pb-212 / Tl-208 : Chaine du thorium, presents dans les materiaux de construction + +**Isotopes d'interet** (a surveiller) : +- Cs-137 : Marqueur de contamination (accident nucleaire, sources industrielles) +- Co-60 : Source medicale/industrielle +- I-131 : Marqueur medical (scintigraphie thyroidienne) +- Am-241 : Source de calibration, detecteurs de fumee +- Ir-192 : Source industrielle (radiographie gamma) + +--- + +## 7. Tester avec une source connue + +Si vous avez une source radioactive connue (par ex. Cs-137) : + +1. Placez la source pres du detecteur +2. Lancez le moniteur +3. Attendez au moins 1 heure d'acquisition (MIN_LIVE_TIME=3600) +4. Forcez un rapport manuel : + +```bash +docker compose exec detect python -c " +from radiacode_monitor import RadiacodeMonitor +m = RadiacodeMonitor() +m.generate_report() +" +``` + +Pour un Cs-137 (662 keV, ~10 uSv/h a 10 cm), le resultat attendu est : +- Cs-137 detecte avec probabilite > 90% +- Activite estimee approximative (la calibration exacte necessite un etalonnage) + +--- + +## 8. Nettoyer les checkpoints (~39 Go a recuperer) + +Apres l'entrainement, seuls `vega_best.pt` et `vega_isotope_index.txt` sont necessaires : + +```bash +# Supprimer les checkpoints intermediaires +rm models/vega_epoch_*.pt models/vega_final.pt + +# Verifier +ls -lh models/ +# vega_best.pt vega_history.json vega_isotope_index.txt +``` + +--- + +## 9. Transfer vers Raspberry Pi 4 (production) + +Quand le pipeline fonctionne sur la machine GPU : + +1. Copier les fichiers du modele : + ```bash + scp models/vega_best.pt models/vega_isotope_index.txt pi@raspberrypi:~/radiacode_103/models/ + ``` + +2. Capturer le background sur le Pi (emplacement final) : + ```bash + docker compose run --rm -d --name radiacode-bg \ + -e TARGET_DURATION=86400 -e SAMPLE_INTERVAL=60 \ + detect python capture_background.py + ``` + +3. Adapter `docker-compose.yml` sur le Pi : + - Retirer `deploy.resources.reservations.devices` (pas de GPU) + - Mettre `VEGA_DEVICE=cpu` + - Le detect/Dockerfile fonctionne sur ARM64 (PyTorch a des wheels ARM) + +4. Lancer : + ```bash + docker compose up detect + ``` + +L'inference CPU sur Pi 4 prend environ 0.5-1 seconde par spectre, ce qui est suffisant pour un rapport quotidien. + +--- + +## 10. Variables d'environnement de reference + +### Entrainement (`docker-compose.yml` - service `train`) + +| Variable | Defaut | Description | +|----------|--------|-------------| +| `NUM_SAMPLES` | `50000` | Nombre de spectres synthetiques | +| `EPOCHS` | `100` | Epochs max d'entrainement | +| `BATCH_SIZE` | `64` | Taille de batch | +| `LEARNING_RATE` | `0.001` | Taux d'apprentissage initial | +| `DETECTOR` | `radiacode_103` | Config du detecteur | +| `MIN_DURATION` | `43200` | Duree min des spectres (12h en secondes) | +| `MAX_DURATION` | `86400` | Duree max des spectres (24h en secondes) | +| `NVIDIA_VISIBLE_DEVICES` | `1` | GPU a utiliser (0 ou 1) | +| `CUDA_VISIBLE_DEVICES` | `1` | GPU visible par CUDA | + +### Detection (`docker-compose.yml` - service `detect`) + +| Variable | Defaut | Description | +|----------|--------|-------------| +| `MODEL_PATH` | `/models/vega_best.pt` | Chemin du modele | +| `ISOTOPE_INDEX_PATH` | `/models/vega_isotope_index.txt` | Index des isotopes | +| `BACKGROUND_PATH` | `/data/background_24h.npy` | Fichier background | +| `VEGA_DEVICE` | `cpu` | Device PyTorch | +| `THRESHOLD` | `0.5` | Seuil de probabilite | +| `SAMPLE_INTERVAL` | `60` | Intervalle d'echantillonnage (s) | +| `REPORT_HOUR` | `0` | Heure du rapport quotidien | +| `MIN_LIVE_TIME` | `3600` | Live time min pour rapport (s) | + +### Capture de background + +| Variable | Defaut | Description | +|----------|--------|-------------| +| `TARGET_DURATION` | `86400` | Duree de capture (24h en secondes) | +| `SAMPLE_INTERVAL` | `60` | Intervalle entre echantillons (s) | +| `BACKGROUND_PATH` | `/data/background_24h.npy` | Fichier de sortie | \ No newline at end of file