WOUNDSEG/TSGCNET

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.

2network streams
24features / triangle
4loss functions compared

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.

1

Raw mesh

A 3D scan — roughly 3,900 vertices forming ~7,800 triangles — with the wound colour-coded yellow in the training data.

2

Per-face features

24 numbers per triangle: corner positions, centre point, and two kinds of surface-orientation vectors.

3

TSGCNet

Two parallel streams — one over coordinates, one over surface normals — fused into a per-triangle prediction.

4

Wound probability

For every triangle: how likely is it to be part of the wound, versus normal skin?

5

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.

TSGCNet two-stream architecture diagram: coordinate stream and normal stream, each with KNN graph, shared MLP, and graph attention/max-pooling layers, fused into a final per-triangle class prediction.
Figure adapted from Nguyen et al. (2024), Fig. 2 (CC BY 4.0). Verified layer-by-layer against 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 functionVal. accuracyVal. mIoU
CrossEntropy0.94300.4715
WeightedCrossEntropy0.92200.4788
DiceLoss0.94300.4715
FocalLoss0.91430.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.

Read the code, run it yourself, or just skim the README.

github.com/Dharsh62/3D-Facial-Wound-Segmentation →