PHerc. Paris 4 β 4-class fiber/ink segmentation, self-distilled (step 29000)
Segments background / vertical fiber / horizontal-angular fiber / ink β 4
classes, in 3D, directly in micro-CT of PHerc. Paris 4 β trained with no
fixed ground truth via self-distillation from two frozen teacher UNets. This
is villa's scripts/fiber_5class
pipeline (PR #985), and this
checkpoint is confirmed (via direct inspection of its embedded training config,
which matches its W&B run exactly) to be the real, finished model that
pipeline produced.
This model's own training pipeline does not use DINO at all β it is pure two-teacher self-distillation (see below). DINO only appears earlier/elsewhere in this broader fiber-modeling effort, in a separate checkpoint that may feed this run's fiber teacher input β see Related models.
Training-time debug visualization from this run at step 29899 (image slice, teacher probability maps, watershed instances, student prediction). Logged to W&B, not an independent evaluation.
Model details
| Architecture | vesuvius NetworkFromConfig 3D UNet (shared_encoder/shared_decoder/task_heads) β verified directly from the checkpoint: 544 encoder tensors, 60 decoder tensors, single head |
| Output | 4 channels, softmax, head named task_heads.labels β verified shape (4, 32, 1, 1, 1) |
| Classes | 0 background Β· 1 vertical fiber Β· 2 horizontal/angular fiber Β· 3 ink |
| Input | 1-channel CT, 256Β³ patches |
| This checkpoint | step 29000 of a 30000-step schedule Β· W&B run p4_4class_ddp8_20260526 (36pykwky, project paris4-full-features, state finished) |
| Weights | model (raw) and ema (EMA β recommended for inference, decay 0.9995) |
| Optimisation | SGD + Nesterov (lr 0.005, momentum 0.99, weight_decay 3e-5), cosine LR, 2000-step warmup, bf16, CE + multiclass soft Dice (0.1 label smoothing each, Dice over foreground classes only), batch size 2 Γ 8 GPUs (ddp8) |
| Trained on | PHerc. Paris 4, 2.4 Β΅m scan (s3://vesuvius-challenge-open-data/PHercParis4/volumes/20260411134726-2.400um-0.2m-78keV-masked.zarr/) |
We verified this checkpoint directly (torch.load(..., weights_only=False)):
its embedded config.wandb_run_name is p4_4class_ddp8_20260526, its
config.out_dir is /ephemeral/fiber_5class_ckpts/p4_4class_ddp8_20260526 β
both matching the W&B run's own recorded config exactly, and step=29000
matches the file's provenance exactly. save_every=1000 with
num_iterations=30000 means step 29000 is mathematically the last
checkpoint this run could have saved (the loop exits at step 30000 before
another save triggers) β this is the final checkpoint of a finished run, not
an arbitrary snapshot.
This run was reached after two earlier attempts under the same name failed
(5ga68dxv) or crashed (q8jmzbiv), and after an even earlier, broader
experiment line tagged 5class/fiber-ink-papyrus (out_channels=5, adding a
"papyrus" class) was simplified down to the 4-class scheme published here.
Training procedure: two-teacher self-distillation (verified against source)
Read directly from label_generator.py (FiveClassLabelGenerator) and
train.py in PR #985 β this is a from-source description, not an inference
from config field names:
fiber_prob = sigmoid(fiber_teacher(image)),ink_prob = sigmoid(ink_teacher(image))β two independent frozen teacher UNets, FG channel only.fiber_mask = fiber_prob > fiber_thr(0.5).- GPU watershed-from-minima (
cuws) on the distance transform offiber_mask(ws_image_mode="distance",ws_h_merge=14000) β per-instance fiber segmentation. - Per-instance PCA on each instance's ZYX voxel coordinates:
|principal_axis Β· αΊ| > pca_cos_threshold(0.819 = cos 35Β°) β class 1 (vertical), else class 2 (horizontal/angular). Instances belowws_min_voxels(400) default to class 2 rather than being dropped. - Ink overrides fiber:
label[ink_prob > ink_thr] = 3(ink_thr=0.1) β applied after the fiber/orientation assignment, so ink always wins where the ink teacher is confident. - Dark-voxel guard (final step):
label[raw < dark_voxel_thr] = 0(dark_voxel_thr=90) β forces very dark/air voxels to background regardless of any earlier assignment.
Loss = cross-entropy (label smoothing 0.1) + multiclass soft Dice (smoothing 0.1, foreground classes only). A fresh pseudo-label is generated from the two frozen teachers every step β there is no fixed/static label set at any point in training.
The two teacher checkpoints for this run were configured as
/ephemeral/fiber_5class_inputs/fiber_teacher.pth and ink_teacher.pth β
generic on-disk names that don't self-identify their origin. Per the identical
scripts/fiber_5class/train.py module docstring, the fiber teacher is
documented as "ihoo3tpl ckpt", i.e. very likely
scrollprize/fiber_dinoguided_2class_step010000
(not proven byte-identical β it was copied/renamed on the training box). The
ink teacher is a separate checkpoint we never had; it no longer exists on the
original training instance and was not found anywhere else we checked, so we
are treating it as unrecoverable and are not able to publish it.
Metrics
Final logged values at step 29999 (run marked finished; checkpoint published here is step 29000, the last one actually saved):
| metric | value |
|---|---|
loss (ce + dice) |
1.0055 |
loss_ce |
0.4513 |
loss_dice |
0.5542 |
metrics/dice_0_bg |
0.961 |
metrics/dice_1_vert_fiber |
0.673 |
metrics/dice_2_horiz_fiber |
0.705 |
metrics/dice_3_ink |
0.791 |
metrics/dice_fg_mean |
0.723 |
pseudo/frac_bg / frac_vert / frac_horiz / frac_ink |
0.845 / 0.026 / 0.080 / 0.049 |
pseudo/n_instances_mean |
27.5 |
pseudo/n_vert_mean |
9 |
Important: the per-class Dice above is student-vs-its-own-pseudo-label
self-consistency, recomputed each val_every steps by re-forwarding the
student on a clean (non-augmented) training crop and comparing to that crop's
pseudo-label β confirmed directly from train.py's logging code. It is not
accuracy against independent, human-verified ground truth (none exists for
this pipeline). Treat these numbers as a training-health signal, not a
benchmark score.
Categorical mask visualization (pseudo-label vs. student prediction) from the same step, with the fixed class palette used throughout this pipeline.
Relationship to other fiber-effort models β please read before conflating pipelines
This is the only one of the four related repos published so far whose own training loop is DINO-free. The others are separate, earlier, or upstream components of the same broader effort:
scrollprize/fiber_dinoguided_2class_step010000β 2-class (background/fiber) DINO-embedding-guided self-training checkpoint, very likely (not proven byte-identical) thefiber_teacherinput consumed by this run. Trains completely differently (Otsu + DINO-similarity dynamic pseudo-labels, no watershed, no PCA, no ink).scrollprize/dinovol_v2_ps8_supcon3class_step362500andscrollprize/fiber_selftrain_teacher_epoch30β further upstream still (inputs to producing this run's likely fiber-teacher checkpoint, not direct inputs to this run itself).scrollprize/fiber_hz_vtβ an independent, supervised, real-annotation-trained 2-class horizontal/vertical model (villa PR #825). Different pipeline, different training data (WebKnossos skeleton traces vs. self-distillation), not directly comparable.
Files
| File | Size | Role |
|---|---|---|
p4_4class_ddp8_20260526_step029000.pth |
~2.1 GB | model (raw) + ema.model_state (recommended for inference) + optimizer + embedded config. |
images/p4_4class_36pykwky_debug_figure_step29899.png |
β | Training-time debug figure (illustrative only), from this run's W&B logs. |
images/p4_4class_36pykwky_debug_mask_step29899.png |
β | Training-time categorical mask visualization (illustrative only), from this run's W&B logs. |
Usage
import torch
from huggingface_hub import hf_hub_download
path = hf_hub_download(
"scrollprize/fiber_ink_4class_selfdistill",
"p4_4class_ddp8_20260526_step029000.pth",
)
ckpt = torch.load(path, map_location="cpu", weights_only=False)
state = ckpt["ema"]["model_state"] # recommended over ckpt["model"]
# Build with vesuvius' NetworkFromConfig (target "labels", out_channels=4,
# in_channels=1, patch_size 256^3) then load_state_dict(state).
The vesuvius package and the full training pipeline are in
https://github.com/ScrollPrize/villa (scripts/fiber_5class/).
Links
- Code: https://github.com/ScrollPrize/villa β PR #985 (
scripts/fiber_5class, this model's exact training code) Β· PR #825 (related cross-frame affine infra, separate pipeline) - W&B run: https://wandb.ai/vesuvius-challenge/paris4-full-features/runs/36pykwky
- Data: https://scrollprize.org/data_browser
- Vesuvius Challenge: https://scrollprize.org
Caveats
- Metrics are self-consistency (student vs. its own pseudo-label), not held-out validation against independent ground truth β there is none in this pipeline.
- The ink teacher checkpoint used to train this model is not published here and is believed unrecoverable (no longer present on the original training instance; not found elsewhere in our search).
- The fiber teacher checkpoint used to train this model is very likely
scrollprize/fiber_dinoguided_2class_step010000based on matching documentation in the training source, but this was not proven byte-identical (it was renamed to a generic filename on the training box before this run consumed it). - Trained on a single scroll (PHerc. Paris 4); generalization to other scrolls is untested by us.
License
MIT.

