Front page: model card of reg_upernet_convnext_t_bmlines
Browse files
README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
library_name: pytorch
|
| 3 |
+
tags:
|
| 4 |
+
- medical-imaging
|
| 5 |
+
- ophthalmology
|
| 6 |
+
- oct
|
| 7 |
+
- choroid
|
| 8 |
+
- image-registration
|
| 9 |
+
- motion-correction
|
| 10 |
+
- unsupervised
|
| 11 |
+
- video
|
| 12 |
+
- pytorch_model_hub_mixin
|
| 13 |
+
- model_hub_mixin
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
# OCT B-scan video registration (`reg_upernet_convnext_t_bmlines`)
|
| 17 |
+
|
| 18 |
+
A learned, **fully unsupervised** registration model for OCT B-scan videos, where
|
| 19 |
+
one B-scan location is imaged repeatedly over time. It predicts the motion of each
|
| 20 |
+
frame against a reference frame:
|
| 21 |
+
|
| 22 |
+
- **`dx`:** one lateral shift per frame;
|
| 23 |
+
- **`dy(x)`:** one axial shift per A-scan column.
|
| 24 |
+
|
| 25 |
+
It is the second stage of the OcularRigidity pipeline (choroid segmentation, then
|
| 26 |
+
registration, then pulsation analysis). The registered videos are used to measure
|
| 27 |
+
the pulsatile choroid thickness change.
|
| 28 |
+
|
| 29 |
+
The model has no image encoder of its own. It works on the features of the
|
| 30 |
+
**frozen encoder of the choroid segmentation model**
|
| 31 |
+
[`ClementP/ChoroidSegmentationModule@version-3.0.0`](https://huggingface.co/ClementP/ChoroidSegmentationModule/tree/version-3.0.0)
|
| 32 |
+
(UPerNet ConvNeXt-T). The two models must be used together, at these revisions.
|
| 33 |
+
|
| 34 |
+
| | |
|
| 35 |
+
|---|---|
|
| 36 |
+
| Parameters | 4.6 M (plus the frozen 37 M segmentation model) |
|
| 37 |
+
| Input | Encoder feature pyramids (channels [96, 192, 384, 768]) of a fixed and a moving frame |
|
| 38 |
+
| Frame format | uint8, 1536 rows × 1024 columns, PLEX Elite pixel size (1.95 µm axial × 5.9 µm lateral) |
|
| 39 |
+
| Output | `dx` (B,) and `dy` (B, W), in pixels of the input frame |
|
| 40 |
+
| Checkpoint | epoch 10 of 10, step 167,360 (best validation objective) |
|
| 41 |
+
|
| 42 |
+
## Motion convention
|
| 43 |
+
|
| 44 |
+
The warped frame is
|
| 45 |
+
|
| 46 |
+
out(x, y) = moving(x - dx, y + dy(x))
|
| 47 |
+
|
| 48 |
+
so `dy(x)` is the axial displacement of the moving frame's tissue relative to
|
| 49 |
+
the reference (positive means the tissue sits lower in the moving frame), and
|
| 50 |
+
`dx` is the lateral shift.
|
| 51 |
+
|
| 52 |
+
## Usage
|
| 53 |
+
|
| 54 |
+
Through the OcularRigidity package. The fused pass runs the shared encoder once
|
| 55 |
+
per frame for both segmentation and registration:
|
| 56 |
+
|
| 57 |
+
```python
|
| 58 |
+
from ocularrigidity.registration.config import RegistrationConfig
|
| 59 |
+
from ocularrigidity.registration.fused import segment_and_register
|
| 60 |
+
from ocularrigidity.segmentation.trainer.pl_module import ChoroidSegmentationModule
|
| 61 |
+
from ocularrigidity.registration.deep_learning.models.regressor import RegistrationRegressor
|
| 62 |
+
|
| 63 |
+
seg = ChoroidSegmentationModule.from_pretrained("ClementP/ChoroidSegmentationModule", revision="version-3.0.0").eval().cuda()
|
| 64 |
+
reg = RegistrationRegressor.from_pretrained("ClementP/OCTVideoRegistration", revision="reg_upernet_convnext_t_bmlines").eval().cuda()
|
| 65 |
+
|
| 66 |
+
# frames: (T, H, W) uint8 video at the PLEX pixel size (Spectralis resampled to the
|
| 67 |
+
# PLEX grid and padded to 1536x1024)
|
| 68 |
+
res = segment_and_register(frames, seg, reg, RegistrationConfig(method="learned"))
|
| 69 |
+
res.registered_frames # (T, H, W) uint8
|
| 70 |
+
res.transform["dx"] # (T,)
|
| 71 |
+
res.transform["dy"] # (T, W)
|
| 72 |
+
res.registered_masks # (T, H, W) bool, registered choroid masks
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
**Calling the model directly:** `reg(fixed_feats, moving_feats)` takes the
|
| 76 |
+
encoder outputs `seg.model.encoder(x)[2:]`, with `x = (frame / 255 - 0.5) / 0.5`
|
| 77 |
+
of shape (B, 1, H, W).
|
| 78 |
+
|
| 79 |
+
**Reference frame:** by default it is picked automatically (the frame closest to
|
| 80 |
+
the median choroid area, then the motion medoid). Pass `ref_idx=` to fix it.
|
| 81 |
+
|
| 82 |
+
## Training data
|
| 83 |
+
|
| 84 |
+
All the data is unlabelled. Each video contributes 40 frames drawn at random
|
| 85 |
+
without replacement, with a reproducible seed per video. For PLEX the first 20
|
| 86 |
+
and last 10 frames are excluded, the same trim as the pipeline; Spectralis series
|
| 87 |
+
are used whole:
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
| Device | Scan | Videos | Patients |
|
| 91 |
+
|---|---|---|---|
|
| 92 |
+
| Zeiss PLEX Elite 9000 | Macula | 400 | 338 |
|
| 93 |
+
| Zeiss PLEX Elite 9000 | Optic nerve head | 400 | 362 |
|
| 94 |
+
| Heidelberg Spectralis | Macula | 400 | 400 |
|
| 95 |
+
|
| 96 |
+
- **Total:** 1,200 videos and 48,000 frames.
|
| 97 |
+
- **Spectralis frames:** resampled to the PLEX grid.
|
| 98 |
+
- **Validation:** 5% of the videos, split by patient, with 40 pairs per video.
|
| 99 |
+
|
| 100 |
+
## Training procedure
|
| 101 |
+
|
| 102 |
+
There are no ground-truth displacements and no classical registration targets.
|
| 103 |
+
|
| 104 |
+
**Training samples**
|
| 105 |
+
- **Pairs:** within-video pairs, 120 per video per epoch.
|
| 106 |
+
- **Triplets:** 40 per video, for cycle consistency.
|
| 107 |
+
- **Synthetic shifts:** 40 per video, up to ±8 px, for shift equivariance.
|
| 108 |
+
- **Crops:** random 768 × 512 crops.
|
| 109 |
+
|
| 110 |
+
**Objective** (weights in brackets):
|
| 111 |
+
|
| 112 |
+
| Term | Weight | What it does |
|
| 113 |
+
|---|---|---|
|
| 114 |
+
| Feature | 1.0 | Similarity of frozen encoder features after warping |
|
| 115 |
+
| Photometric | 1.0 | Multi-scale local normalised cross-correlation |
|
| 116 |
+
| Lateral | 1.0 | Lateral alignment term |
|
| 117 |
+
| Bruch's membrane (BM) | 0.1 | Distance between the fixed BM line and the analytically warped moving BM line |
|
| 118 |
+
| Smoothness | 0.05 | Penalises jumps in `dy(x)` between columns |
|
| 119 |
+
| Curvature | 0.02 | Penalises curvature in `dy(x)` |
|
| 120 |
+
| Coverage | 0.5 | Keeps the warped choroid inside the frame |
|
| 121 |
+
| Cycle | 0.3 | A to B to C must equal A to C on triplets |
|
| 122 |
+
| Shift equivariance | 1.0 | A known synthetic shift must be recovered |
|
| 123 |
+
|
| 124 |
+
The BM lines come from the segmentation model, run once on every full,
|
| 125 |
+
unaugmented frame and stored with the dataset. They are cropped with the same
|
| 126 |
+
window as the frames.
|
| 127 |
+
|
| 128 |
+
**Architecture:** a cascade of one stage per encoder level, coarse to fine. Each
|
| 129 |
+
stage builds a local correlation volume (±6 px axially,
|
| 130 |
+
±10 steps of 8.0 px laterally) and refines the
|
| 131 |
+
global `dx` and the per-column `dy`. The `dy` is a bulk shift plus a
|
| 132 |
+
zero-mean residual. Training also uses a coarse-to-fine progress schedule.
|
| 133 |
+
|
| 134 |
+
**Optimisation**
|
| 135 |
+
- **Optimiser:** AdamW, learning rate 0.0005, weight decay 0.0001.
|
| 136 |
+
- **Batches:** batch size 4 per GPU, 10 epochs.
|
| 137 |
+
- **Hardware:** 2 GPUs (PyTorch Lightning DDP), bf16 encoder.
|
| 138 |
+
|
| 139 |
+
## Evaluation
|
| 140 |
+
|
| 141 |
+
Unsupervised validation objective on held-out patients, at the released checkpoint:
|
| 142 |
+
|
| 143 |
+
| Metric | Value |
|
| 144 |
+
|---|---|
|
| 145 |
+
| Total objective | 2.327 |
|
| 146 |
+
| BM line error after warping | 3.59 px (about 7 µm) |
|
| 147 |
+
| Cycle inconsistency | 0.13 px |
|
| 148 |
+
| Synthetic-shift error | 0.07 px |
|
| 149 |
+
|
| 150 |
+
These are self-consistency measures, not errors against ground-truth motion,
|
| 151 |
+
which doesn't exist for these videos.
|
| 152 |
+
|
| 153 |
+
## Limitations
|
| 154 |
+
|
| 155 |
+
- **Motion model:** only a global lateral shift and a per-column axial shift. No
|
| 156 |
+
rotation, no lateral warp within the frame, no out-of-plane motion: frames
|
| 157 |
+
where the eye moved out of the B-scan plane can't be corrected, only flagged.
|
| 158 |
+
- **Segmentation dependency:** the model is tied to the segmentation encoder it
|
| 159 |
+
was trained with. A different segmentation revision needs retraining.
|
| 160 |
+
- **Training domain:** PLEX Elite (macula and optic nerve head) and Spectralis
|
| 161 |
+
(macula) only.
|
| 162 |
+
- **Research use only.** This is not a medical device.
|
| 163 |
+
|
| 164 |
+
## Citation
|
| 165 |
+
|
| 166 |
+
OcularRigidity (reference to come).
|