Skip to content

feat(schema): optical sub-table additions — scintillator/Geant4 (#153)#194

Merged
gerchowl merged 1 commit into
mainfrom
feature/153-optical-fields
May 6, 2026
Merged

feat(schema): optical sub-table additions — scintillator/Geant4 (#153)#194
gerchowl merged 1 commit into
mainfrom
feature/153-optical-fields

Conversation

@gerchowl
Copy link
Copy Markdown
Contributor

@gerchowl gerchowl commented May 6, 2026

Summary

Adds 11 optical fields, mostly scintillator + Geant4 photon transport data that the schema currently has no slot for.

Scalars

Field Unit Use
scattering_length / rayleigh_length cm Geant4 photon transport (currently missing entirely)
afterglow_pct_at_3ms / afterglow_pct_at_100ms % count-rate ceiling
non_proportionality % deviation 10 keV → 1 MeV
intrinsic_resolution_pct_at_662keV % 137Cs photopeak FWHM
temperature_coefficient_light_yield %/K LYSO ≈ -0.4
hygroscopic bool NaI:Tl yes, BGO/LYSO no

Structured (3 — plain dict / list[dict], no new dataclass)

Field Shape Use
decay_components list[{tau_ns, fraction}] multi-component decay (LYSO fast+slow, TOF-PET)
emission_spectrum {wavelengths_nm: [...], intensities: [...]} SiPM PDE matching
refractive_index_dispersion {wavelengths_nm: [...], n: [...]} Sellmeier-style Geant4 ray tracing

Loader bug fix bundled

#149's loader sugar treated every dict-valued property as a {nominal, stddev} uncertainty dict — which would crash on emission_spectrum = {wavelengths_nm=[...], intensities=[...]}. Tightened the dict-shape detection: only route through _parse_value when the dict's keys overlap with {nominal, stddev, min, max}. Verified tests/test_stddev_sugar.py still passes (no regression on the ufloat path).

Test plan

  • pytest tests/test_optical_fields.py — 11 tests pass
  • pytest tests/test_stddev_sugar.py tests/test_temp_curves.py — no regressions on adjacent loader logic
  • Full suite: 570 passed, 11 skipped, zero regressions
  • ruff check clean

Closes #153

Adds 11 optical fields, mostly scintillator + Geant4 photon transport
data that the schema currently has no slot for.

Scalars:
- scattering_length / rayleigh_length (cm) — Geant4 photon transport
- afterglow_pct_at_3ms / afterglow_pct_at_100ms — count-rate ceiling
- non_proportionality (% deviation 10 keV → 1 MeV)
- intrinsic_resolution_pct_at_662keV (137Cs photopeak FWHM)
- temperature_coefficient_light_yield (%/K) — LYSO ~-0.4
- hygroscopic (bool) — NaI:Tl yes, BGO/LYSO no

Structured (3 — pure data shapes, no new dataclass needed):
- decay_components: list[{tau_ns, fraction}] — multi-component decay
  (LYSO has fast ~36 ns + slow ~600 ns; scalar would lose TOF info)
- emission_spectrum: {wavelengths_nm, intensities} — for SiPM PDE
  matching
- refractive_index_dispersion: {wavelengths_nm, n} — Sellmeier-style
  for Geant4 ray tracing

Loader fix (regression of #149's loader sugar): only route dict-valued
properties through `_parse_value` when their keys overlap with
{nominal, stddev, min, max}. Otherwise pass through verbatim — previously
emission_spectrum / refractive_index_dispersion would be parsed as
uncertainty dicts and crash. Tightens the dict-shape detection to the
ufloat keyword set rather than "any dict".

Pint *_qty accessors for scattering_length / rayleigh_length.

Tests: tests/test_optical_fields.py (11 tests). All 570 tests pass.

Closes #153
@gerchowl gerchowl merged commit 113da96 into main May 6, 2026
19 checks passed
@gerchowl gerchowl deleted the feature/153-optical-fields branch May 6, 2026 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Schema: optical sub-table additions (scintillator-heavy)

1 participant