Pipeline complet Radiacode 103 - identification automatique d'isotopes
- VegaModel CNN-FCNN 34.5M params, 82 isotopes, val acc 99.89% - Generation 50k spectres synthetiques 1D (12-24h durees) - Entrainement 100 epochs sur RTX 5060 Ti (CUDA 12.8, Blackwell) - Detection continue avec soustraction du background - Capture background 24h avec gestion deconnexion - Docker Compose : conteneur train (GPU) + detect (CPU/USB) - Modele entraite inclus (vega_best.pt, 395 Mo) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
141
train/vega_ml/training/vega/isotope_index.py
Normal file
141
train/vega_ml/training/vega/isotope_index.py
Normal file
@ -0,0 +1,141 @@
|
||||
"""
|
||||
Isotope Index - Mapping between isotope names and model output indices.
|
||||
|
||||
This module provides a consistent mapping between isotope names and their
|
||||
corresponding indices in the model's output tensors. This is critical for
|
||||
training and inference to ensure consistent label encoding.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
# Add project root to path for imports
|
||||
PROJECT_ROOT = Path(__file__).parent.parent.parent
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from synthetic_spectra.ground_truth.isotope_data import ISOTOPE_DATABASE, get_isotope_names
|
||||
|
||||
|
||||
class IsotopeIndex:
|
||||
"""
|
||||
Manages the mapping between isotope names and model indices.
|
||||
|
||||
The index is deterministic - isotopes are sorted alphabetically to ensure
|
||||
consistent ordering across training and inference.
|
||||
"""
|
||||
|
||||
def __init__(self, isotope_names: Optional[List[str]] = None):
|
||||
"""
|
||||
Initialize the isotope index.
|
||||
|
||||
Args:
|
||||
isotope_names: Optional list of isotope names. If None, uses all
|
||||
isotopes from the database.
|
||||
"""
|
||||
if isotope_names is None:
|
||||
isotope_names = get_isotope_names()
|
||||
|
||||
# Sort alphabetically for deterministic ordering
|
||||
self._isotope_names = sorted(isotope_names)
|
||||
|
||||
# Build bidirectional mappings
|
||||
self._name_to_idx: Dict[str, int] = {
|
||||
name: idx for idx, name in enumerate(self._isotope_names)
|
||||
}
|
||||
self._idx_to_name: Dict[int, str] = {
|
||||
idx: name for idx, name in enumerate(self._isotope_names)
|
||||
}
|
||||
|
||||
@property
|
||||
def num_isotopes(self) -> int:
|
||||
"""Total number of isotopes in the index."""
|
||||
return len(self._isotope_names)
|
||||
|
||||
@property
|
||||
def isotope_names(self) -> List[str]:
|
||||
"""List of all isotope names in index order."""
|
||||
return self._isotope_names.copy()
|
||||
|
||||
def name_to_index(self, name: str) -> int:
|
||||
"""
|
||||
Get the index for an isotope name.
|
||||
|
||||
Args:
|
||||
name: Isotope name (e.g., "Cs-137")
|
||||
|
||||
Returns:
|
||||
Integer index for the isotope
|
||||
|
||||
Raises:
|
||||
KeyError: If isotope name not in index
|
||||
"""
|
||||
if name not in self._name_to_idx:
|
||||
raise KeyError(f"Isotope '{name}' not found in index. "
|
||||
f"Available isotopes: {self._isotope_names[:5]}...")
|
||||
return self._name_to_idx[name]
|
||||
|
||||
def index_to_name(self, idx: int) -> str:
|
||||
"""
|
||||
Get the isotope name for an index.
|
||||
|
||||
Args:
|
||||
idx: Integer index
|
||||
|
||||
Returns:
|
||||
Isotope name string
|
||||
|
||||
Raises:
|
||||
KeyError: If index out of range
|
||||
"""
|
||||
if idx not in self._idx_to_name:
|
||||
raise KeyError(f"Index {idx} out of range. Valid range: 0-{self.num_isotopes-1}")
|
||||
return self._idx_to_name[idx]
|
||||
|
||||
def names_to_indices(self, names: List[str]) -> List[int]:
|
||||
"""Convert list of names to list of indices."""
|
||||
return [self.name_to_index(name) for name in names]
|
||||
|
||||
def indices_to_names(self, indices: List[int]) -> List[str]:
|
||||
"""Convert list of indices to list of names."""
|
||||
return [self.index_to_name(idx) for idx in indices]
|
||||
|
||||
def save(self, path: Path):
|
||||
"""Save the isotope index to a file."""
|
||||
with open(path, 'w') as f:
|
||||
for name in self._isotope_names:
|
||||
f.write(f"{name}\n")
|
||||
|
||||
@classmethod
|
||||
def load(cls, path: Path) -> 'IsotopeIndex':
|
||||
"""Load an isotope index from a file."""
|
||||
with open(path, 'r') as f:
|
||||
isotope_names = [line.strip() for line in f if line.strip()]
|
||||
return cls(isotope_names)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"IsotopeIndex(num_isotopes={self.num_isotopes})"
|
||||
|
||||
def __len__(self) -> int:
|
||||
return self.num_isotopes
|
||||
|
||||
|
||||
# Global default isotope index using all isotopes from database
|
||||
DEFAULT_ISOTOPE_INDEX = IsotopeIndex()
|
||||
|
||||
|
||||
def get_default_isotope_index() -> IsotopeIndex:
|
||||
"""Get the default isotope index with all database isotopes."""
|
||||
return DEFAULT_ISOTOPE_INDEX
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Print isotope index information
|
||||
index = get_default_isotope_index()
|
||||
print(f"Isotope Index: {index}")
|
||||
print(f"\nFirst 10 isotopes:")
|
||||
for i in range(min(10, index.num_isotopes)):
|
||||
print(f" {i:3d}: {index.index_to_name(i)}")
|
||||
print(f"\nLast 10 isotopes:")
|
||||
for i in range(max(0, index.num_isotopes - 10), index.num_isotopes):
|
||||
print(f" {i:3d}: {index.index_to_name(i)}")
|
||||
Reference in New Issue
Block a user