Trained on segment l_2 with l_2_inklabels3.png (15,286 tiles).
Ablation 3/5 — 15,286 training tiles.
This is one of six sibling models released together — five label
ablations on segment l_2 (ink1–ink5, increasing label coverage)
and one cross-segment baseline (ink0). The full family is listed
at the bottom of this card.
Preview
l_2 (training segment) prediction with the training label overlaid in
magenta, and l_5 (held-out segment) prediction. All panels are
downsampled 16× and rotated 180° to match the publication-figure
convention. The full-resolution last.ckpt outputs are at 43008 × ~30000
voxels.
training label
l_2 prediction
l_5 prediction
label
l_2 pred
l_5 pred
Architecture in one paragraph
A 3-D volumetric input (B, 1, 62, 256, 256) is encoded by a
ResNet3D-50 backbone (Hara, Kataoka & Satoh, 2018; initialised from
the Kinetics-700 release r3d50_KM_200ep.pth with conv1 weights
summed across RGB → 1 grayscale channel). Each of the four backbone
stages is collapsed along the z (depth) axis with torch.max,
producing a 2-D feature pyramid {(256,64,64), (512,32,32), (1024,16,16), (2048,8,8)}. A small 2-D U-Net decoder upsamples
coarse-to-fine with concatenated skip connections; a 1×1 conv head
produces a single sigmoid logit channel at quarter resolution
(B, 1, 64, 64). Training uses 0.5·Dice + 0.5·SoftBCE against the
label down-interpolated to 64×64.
Quick start
python
1import torch
2from transformers import AutoModel
34model = AutoModel.from_pretrained(5"YoussefMoNader/PHerc.1667-iteration-3",6 trust_remote_code=True,7).eval().cuda()89# Input: float32, shape (B, 1, D=62, H=256, W=256).10# Intensity should already be in roughly [0, 1] (the training pipeline11# clipped raw uint8 layers to [0, 200] then applied Normalize(mean=0, std=1)12# which keeps the magnitude small).13x = torch.randn(1,1,62,256,256, device="cuda")1415with torch.no_grad():16 out = model(x)1718print(out.logits.shape)# torch.Size([1, 1, 64, 64])19prob = torch.sigmoid(out.logits)# ink probability per pixel
Full-segment inference (tiling)
The model only sees 256×256 windows. For a full scroll segment you
need to slide the window across the (padded) layer stack and average
overlapping predictions:
python
1import numpy as np, cv2, torch
2import torch.nn.functional as F
3from transformers import AutoModel
45model = AutoModel.from_pretrained(6"YoussefMoNader/PHerc.1667-iteration-3", trust_remote_code=True,7).eval().cuda()89WINDOW, STRIDE =256,128# 128 = 2x oversample; 64 for 8x oversample10D =62# number of z-layers1112# image: (H, W, D) uint8 stack of the 62 layers, padded to multiples of 256.13# fmask: (H, W) uint8 fragment mask (0 = outside, 255 = inside).14H, W, _ = image.shape
15mask_pred = np.zeros((H, W), dtype=np.float32)16mask_count = np.zeros((H, W), dtype=np.float32)1718with torch.no_grad():19for y inrange(0, H - WINDOW +1, STRIDE):20for x inrange(0, W - WINDOW +1, STRIDE):21if np.any(fmask[y:y+WINDOW, x:x+WINDOW]==0):22continue23 tile = image[y:y+WINDOW, x:x+WINDOW]# (256,256,62)24 t = torch.from_numpy(tile).permute(2,0,1)# (62,256,256)25 t = t.unsqueeze(0).unsqueeze(0).float().cuda()# (1,1,62,256,256)26 logits = model(t).logits # (1,1,64,64)27 prob = torch.sigmoid(logits)28 prob = F.interpolate(prob, scale_factor=4,29 mode="bilinear").squeeze().cpu().numpy()30 mask_pred[y:y+WINDOW, x:x+WINDOW]+= prob
31 mask_count[y:y+WINDOW, x:x+WINDOW]+=1.03233pred = np.divide(mask_pred, mask_count,34 out=np.zeros_like(mask_pred),35 where=mask_count !=0)36cv2.imwrite("prediction.png", np.clip(pred *255,0,255).astype(np.uint8))
Training summary
Backbone
ResNet3D-50 (3-D conv, BN, ReLU residual blocks)
Encoder init
r3d50_KM_200ep.pth (Kinetics-700), conv1 summed across RGB
architecture + provenance metadata; loaded by AutoConfig
configuration_inkdetection.py
2 KB
InkDetectionConfig(PretrainedConfig)
modeling_inkdetection.py
9 KB
self-contained InkDetectionModel(PreTrainedModel)
model.safetensors
319 MB
converted weights (338 tensors)
last.ckpt
963 MB
original PyTorch-Lightning checkpoint (incl. optimizer + LR-scheduler state) — load with torch.load(...)["state_dict"]
preview_l_2.png
~700 KB
low-res preview of the l_2 prediction (1/16 scale, 180° rotated)
preview_l_5.png
~2 MB
low-res preview of the l_5 (held-out) prediction
preview_label.png
~50 KB
the training label, same scale + rotation
The HuggingFace weights are bit-perfect identical to the original
PyTorch-Lightning checkpoint (verified max abs diff = 0.0e+00 on
identical inputs). Use model.safetensors for AutoModel.from_pretrained;
use last.ckpt only if you want to resume training from the saved
optimizer / scheduler state.
All six share the architecture, hyperparameters, and a fixed step
budget of 12,396 optimizer steps; the only thing that varies between
rows is the supervising label (or, for ink0, the training segments).
Citation
If you use this model in published work, please cite the Vesuvius
Challenge and the underlying ResNet3D paper:
bibtex
1@inproceedings{hara2018can,
2 title = {Can spatiotemporal 3D CNNs retrace the history of 2D CNNs and ImageNet?},
3 author = {Hara, Kensho and Kataoka, Hirokatsu and Satoh, Yutaka},
4 booktitle = {CVPR}, year = {2018},
5}