Academic reproduction · Nguyen et al., 2024
Finding the wound
on a 3D face.
A two-stream graph convolutional network reads a 3D facial scan — thousands of connected triangles, not pixels — and predicts, triangle by triangle, which ones are wound and which are normal skin. Reproduced end-to-end and re-engineered to train on an ordinary laptop CPU, no GPU required.
Why this is hard
Reconstructing a facial wound normally needs a "before" scan to compare against — one that almost never exists. This approach skips that entirely: it recognizes what a wound looks like geometrically — an interrupted curve, an odd little crater in an otherwise smooth cheek — the same way you'd spot a scratch on a table without ever having seen the table unscratched.
The pipeline
Five stages, one mesh at a time.
Raw mesh
A 3D scan — roughly 3,900 vertices forming ~7,800 triangles — with the wound colour-coded yellow in the training data.
Per-face features
24 numbers per triangle: corner positions, centre point, and two kinds of surface-orientation vectors.
TSGCNet
Two parallel streams — one over coordinates, one over surface normals — fused into a per-triangle prediction.
Wound probability
For every triangle: how likely is it to be part of the wound, versus normal skin?
Filling extraction
Combined with a separately-reconstructed "healed" face to determine the fillable 3D shape — printable as a surgical guide.
Model architecture
Two streams, one verdict.
The C-stream reasons over where each triangle sits in space; the N-stream reasons over which way it's facing. Neither alone is enough — a wound is defined by an odd location and an unusual surface shape at once.
model.py — including the 64+128+256=448 concatenated feature width shown above.Engineering, not just modeling
What made this take real work.
imbalance
A wound is a sliver of a face
Wounds cover a small fraction of each mesh, so a model that predicts "normal skin" everywhere still scores 94%+ accuracy. Compared four imbalance-aware loss functions properly — each trained from scratch — so the comparison actually means something.
hardware
No GPU, no problem
Rebuilt the model's memory footprint for CPU training: a narrower network, a smaller graph neighbourhood, and gradient accumulation standing in for a larger batch size.
resilience
Training that survives a reboot
Per-epoch checkpointing and CSV logging mean a forced OS restart mid-run costs minutes, not hours — the next run just picks up where it left off.
geometry
Mesh surgery, from scratch
Sub-mesh extraction, mesh merging, and watertightness cleanup for the printable filling output — implemented and unit-tested in plain NumPy.
Results
Four losses, one honest comparison.
80 train / 20 test meshes, CPU-only laptop, lite model configuration.
| Loss function | Val. accuracy | Val. mIoU |
|---|---|---|
| CrossEntropy | 0.9430 | 0.4715 |
| WeightedCrossEntropy | 0.9220 | 0.4788 |
| DiceLoss | 0.9430 | 0.4715 |
| FocalLoss | 0.9143 | 0.5429 |
For scale: the paper's own 100/20-mesh ablation (full GPU model, 50 epochs) reports 97.69% accuracy — the fair target at this data scale, not its headline 0.9999993% figure, which used a ~32,000-mesh dataset on a workstation GPU.