diff --git a/README.md b/README.md index 78093ce..b7f6c66 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ infrastructure, splits, logging, versioning) stays yours. **1. Raw events.** Any PyTorch `Dataset` yielding `raw[i] -> {"event_no": int, "pulses": [P, F] float32, "sensor_key": [P] int}` -(stated canonically in `spine/data/datamodule.py`). Pulses stay raw: the -sampler builds its cutoffs and dt targets in detector units, and -standardization happens later at collate. Columns follow the task's +(stated canonically in `spine/data/datamodule.py`). Pulses stay raw: pretext +tasks make their sampling decisions and build their targets in detector +units, and standardization happens later at collate. Columns follow the task's `FeatureLayout`, by default `(x, y, z, t, charge)`; pass a different layout instead of reordering your data. `sensor_key` is a unique integer identity per sensor carried in the data itself; single PMT detectors can use any @@ -46,22 +46,24 @@ stable per sensor id. Reference readers: `SQLiteDataset` / `LMDBDataset` adapted via `spine_graphnet.readers.GraphNetRawDataset` (see `examples/graphnet_demo.py`). -**2. A geometry asset.** An `.npz` with per sensor arrays: `xyz [S, 3]` in -the same units as the pulse coordinates, `knn_idx [S, K]` neighbours sorted -by distance (the sampler walks it to the nearest dark sensor, so K must be -large enough that one is always found; the full sorted list is safest), and -one unique integer key array whose name you pass to -`load_geometry(path, sensor_key=...)`. The reader's `sensor_key` values must -resolve through exactly these keys; coordinate matching is deliberately not -supported. `build_geometry_asset` in `examples/graphnet_demo.py` shows a -build from a pulse file. +**2. A geometry asset.** An `.npz` with per sensor arrays: always +`xyz [S, 3]` in the same units as the pulse coordinates, and one unique +integer key array whose name you pass to `load_geometry(path, sensor_key=...)`. +A task can require more; CURTAIN for example needs `knn_idx [S, K]`, +neighbours sorted by distance, to anchor its negatives at the nearest dark +sensor (K large enough that one is always found; the full sorted list is +safest). The reader's `sensor_key` values must resolve through exactly these +keys; coordinate matching is deliberately not supported. +`build_geometry_asset` in `examples/graphnet_demo.py` shows a build from a +pulse file. **3. Selections.** SPINE owns no split: `fit` takes separate train and val -Datasets and you keep them disjoint. Every selected event must be guaranteed -splittable: pre filter with `spine.pretext.curtain.sampler.can_always_split` -using the same `min_visible`/`min_future` you give the task, and float32 -times. `make_sample` raises on events that slip through rather than skipping -them silently. +Datasets and you keep them disjoint. Every selected event must satisfy the +task's sampling requirements; tasks raise on events that fall short instead +of skipping them silently, so pre filter your selection with the task's own +predicate. For CURTAIN that is +`spine.pretext.curtain.sampler.can_always_split`, called with the same +`min_visible`/`min_future` you give the task and float32 times. **4. Feature scaling.** A `FeatureScaler` subclass (`scale_pulses` and `scale_positions`, both applying the same xyz factors) for your detector, or