Project Structure

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:

BoundaryMeaning
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
FileContains
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

ConventionRationale
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:

  1. Copy an existing experiment YAML under src/approach/unetbasic/configs/experiments/ to a new name.
  2. Change only what the experiment is about. Anything left out is inherited, which keeps the experiment readable and its diff meaningful.
  3. If the experiment introduces a new loss combination, express it as weights in the loss-weight schedule rather than as a new loss class.
  4. Launch it by name through the same entry point. A new name is a new output directory, so runs cannot overwrite one another.
  5. 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.