Project Structure
An annotated tour of the repository, how configuration layers resolve into a single run, and the conventions to follow when adding an experiment or a new approach.
1. Annotated tree
vesuvius/
├── configs/ shared, approach-independent configuration
│ ├── globals.yaml seed, output directory, defaults
│ ├── data/base.yaml dataset defaults
│ └── wandb/wandb.yaml experiment-tracking project and defaults
│
├── src/
│ ├── approach/ one directory per modelling approach
│ │ └── unetbasic/ the active custom approach
│ │ ├── train.py training entry point and orchestration
│ │ ├── inference.py standalone sliding-window inference
│ │ ├── data/dataset.py transforms and the dataset
│ │ ├── models/unet.py network definitions
│ │ ├── metrics/ competition metrics + post-processing
│ │ ├── utils/ losses, loss-weight schedule, logging
│ │ └── configs/experiments/ per-experiment YAML (e.g. exp_v0.yaml)
│ └── utils/ shared helpers (site metadata, logging)
│
├── scripts/ standalone tools and the nnU-Net pipeline
│ ├── nnunetbaseline/ full nnU-Net v2 driver
│ ├── run_experiment.py experiment launcher
│ ├── queue_experiments.py sequential experiment queue
│ └── syn-data-generator*.py synthetic training-data generation
│
├── exploration/ local working area (not published)
│ └── scoring/ post-processing strategies and scoring harness
│
├── kaggle_datasets/inference/ code published as a Kaggle inference dataset
├── tests/ unit tests for the dataset, augmentation and losses
├── docs/ plans file + this documentation portal
├── Makefile the three canonical command lines
├── pyproject.toml / uv.lock dependencies and the lockfile
└── README.md the front door
2. The shape of the tree
Three boundaries in this layout do real work, and understanding them prevents most confusion:
| Boundary | Meaning |
|---|---|
configs/ versus src/approach/*/configs/
|
configs/ holds settings shared by every approach. Each approach holds only what
is specific to one experiment. If a value belongs to all approaches, it goes in
configs/; if it distinguishes two runs, it goes under the approach.
|
src/ versus scripts/
|
src/ is importable package code, entered through its own modules.
scripts/ holds runnable tools that orchestrate package code from the outside,
which is why the nnU-Net driver lives there rather than inside an approach.
|
exploration/ versus everything else
|
exploration/ is where experiments that are not yet settled live. Code there
operates on stored predictions and artifacts rather than participating in the training loop,
so it can change freely without threatening reproducibility.
|
3. Configuration layering
Configuration is composed from a shared base plus one experiment file. Precedence is strict and predictable, so an experiment only needs to state what it changes:
configs/globals.yaml seed, output_dir, defaults
configs/data/base.yaml data section defaults
configs/wandb/wandb.yaml tracking project / entity / mode
↓ composed with
src/approach/<approach>/configs/experiments/<experiment>.yaml
= one resolved configuration per run
| File | Contains |
|---|---|
configs/globals.yaml |
The random seed, the output directory root, the approach/experiment placeholders, and the Hydra run directory setting that keeps outputs in one predictable place instead of timestamped directories. |
configs/data/base.yaml |
Dataset-level defaults: file naming, label semantics and spacing expectations. |
configs/wandb/wandb.yaml |
Tracking project name, entity and mode (online, offline or disabled). |
.../configs/experiments/<experiment>.yaml |
Everything that distinguishes one run from another: split, patch size, augmentation set, training hyperparameters, model size, loss hyperparameters and the loss-weight schedule. |
The experiment file is where essentially all tuning happens, and it separates two things that are easy to conflate:
- Loss hyperparameters — the internal parameters of the loss classes, such as the Tversky asymmetry or the expected sheet gap. These define how a component measures what it measures.
- Loss weights — how much each component contributes to the total. These are set through the loss-weight schedule, which is what makes a weight able to change over the course of a run.
Keeping them separate is what allows a curriculum: the measurement stays fixed while its influence ramps in.
4. Conventions
| Convention | Rationale |
|---|---|
One directory per approach under src/approach/ |
Approaches are independent. Adding one means adding a directory, not modifying an existing one, so experiments cannot interfere with each other. |
| Experiment variation lives in YAML, not in code branches | A run is reproducible from its configuration, and two runs can be diffed as files. |
PYTHONPATH includes src/ |
Approach packages are imported as approach.<name>.<module>. |
| Case identifiers are filename stems, everywhere | The same identifier threads through data, predictions and reports, which is what makes a reported metric traceable back to its input. |
| Derived label tensors are produced by transforms, not stored on disk | Distance fields are computed during loading at patch size, so the derived arrays stay proportional to the patch rather than the volume. |
| Outputs never live inside the repository | Checkpoints, predictions and tracking logs go to the configured fast-storage root, keeping the working tree clean and diffable. |
| Post-processing operates on stored predictions | It keeps the search over strategies cheap and makes every comparison run against identical predictions. |
5. Adding an experiment
The intended workflow, using the existing approach:
-
Copy an existing experiment YAML under
src/approach/unetbasic/configs/experiments/to a new name. - Change only what the experiment is about. Anything left out is inherited, which keeps the experiment readable and its diff meaningful.
- If the experiment introduces a new loss combination, express it as weights in the loss-weight schedule rather than as a new loss class.
- Launch it by name through the same entry point. A new name is a new output directory, so runs cannot overwrite one another.
- Compare against both the previous experiment and the nnU-Net baseline before drawing a conclusion.
6. Adding an approach
A new approach is a new sibling directory under src/approach/ containing the same
shape as the existing one — a training entry point, a dataset module, a models module, a
utils/ package and its own configs/experiments/. Reuse the shared
configs/ layer for anything not specific to the approach.
Before writing a second approach from scratch, check whether the change you want is compositional.
Most modelling ideas in this domain — a different loss weighting, a new auxiliary head, a
different stride pattern — are expressible within the existing approach as a configuration
change or a new class in the existing models/ module. A new approach is justified when
the data contract changes, not merely the objective.
7. Working with notebooks
Exploratory notebooks are a legitimate part of the workflow rather than scratch space, but they are working files and are not published with the repository: they embed machine-specific paths and large executed outputs, so they stay in the local checkout. Two habits keep them useful while they do:
- Notebooks consume artifacts, not training runs. Analysis reads stored predictions and score reports, so it can be re-run cheaply and does not occupy a GPU.
-
Promote rather than fork. When analysis stabilises into something that should
run every time, move it into a module — a strategy in the scoring lab or a tool in
scripts/— rather than leaving it as the only copy inside a notebook.
8. Tests
tests/ contains unit tests covering the dataset contract, the augmentation
configuration and the loss implementations — the areas where a silent behavioural change is
hardest to spot from training metrics alone. They are the natural place to add coverage for a new
transform or a new loss component, since both are pure functions of tensors and can be checked
directly. Run them with make test.