Computer Vision course project for the AI & Robotics master's programme.
Project topic: anatomy-aware periodontal radiograph analysis on the DenPAR dataset.
This repository implements a complete pipeline for dental panoramic radiographs: dataset inspection, preprocessing, anatomical target construction, RBL-derived image-level severity labelling, U-Net baselines, anatomical autoencoder pretraining, and two anatomically constrained models inspired by ACNN-style priors.
The core idea is to use anatomical annotations not only as direct segmentation targets, but also as a learned prior over plausible dental anatomy. We first train a multi-task U-Net to predict anatomical maps and an image-level severity class. We then train an anatomical autoencoder on ground-truth anatomical maps only. The frozen autoencoder is used in two ways:
- ACNN-Soft: the autoencoder encoder defines a latent-space regularization loss.
- ACNN-Strong: the autoencoder encoder-decoder acts as a hard projection module for the predicted anatomical maps.
Important clinical note
The classification target used in this repository is an RBL-derived image-level severity class, computed from estimated radiographic bone loss (RBL). It is a project-specific image-level target and should not be interpreted as a full clinical periodontal staging label.
- Livio Marzio della Penna
- Claudia Julia Istoc
This project uses DenPAR, a dental panoramic radiograph dataset with anatomical annotations and periodontal information.
- Dataset paper: https://www.nature.com/articles/s41597-025-05906-9
- Dataset download page: https://zenodo.org/records/16645076
The dataset is not included in this repository. Download it manually and extract it in the repository root, preserving the expected DenPAR folder structure used by the notebooks.
The repository is organized as a notebook-based experimental pipeline plus a reusable PyTorch codebase.
ComputerVision2026/
├── README.md
│
├── Dataset/ # downloaded DenPAR dataset (not included)
│ ├── Testing/
│ ├── Training/
│ ├── Validation/
│ └── Characteristics of radiographs included.xlsx
│
├── notebooks/
│ ├── 01_dataset_exploration.ipynb
│ ├── 02_preprocessing.ipynb
│ ├── 02A_pretrain_sanity_check.ipynb
│ │
│ ├── 03_train_unet_baseline.ipynb
│ ├── 03A_train_unet_baseline_v2.ipynb
│ ├── 03B_train_unet_baseline_v3.ipynb
│ │
│ ├── 04_train_autoencoder.ipynb
│ ├── 05_soft_constraint.ipynb
│ ├── 07_hard_constraint.ipynb
│ │
│ └── 06_simple_hard_constraint.ipynb # archived / latent-fusion prototype
│
├── src/
│ ├── data/
│ │ └── data.py
│ ├── models/
│ │ ├── blocks.py
│ │ ├── unet.py
│ │ ├── autoencoder.py
│ │ └── hard.py
│ └── training/
│ ├── losses.py
│ └── metrics.py
│
├── Processed_Data/ # generated by 02_preprocessing.ipynb
│ └── processed_512x512/
│ ├── processed_labels_final_clean.csv
│ ├── preprocessing_config.json
│ ├── rbl_detail_records.csv
│ ├── rbl_label_diagnostics.csv
│ ├── images/
│ └── anatomical_maps/
│
├── runs/ # generated by training notebooks
│ ├── unet_baseline_v1/
│ ├── unet_baseline_v2/
│ ├── unet_baseline_v3/
│ ├── anatomical_autoencoder_v2/
│ ├── acnn_soft_v1/
│ └── acnn_hard_v1/
│
└── References/ # Material references and additional readings and project notes
The notebooks assume that the src package is importable from the project root.
The project was developed with Python 3.11 and PyTorch. A CUDA-enabled GPU is strongly recommended for the training notebooks.
conda create -n cv26 python=3.11 -y
conda activate cv26
python -m pip install --upgrade pip
pip install ipykernel jupyterlab
python -m ipykernel install --user --name cv26 --display-name "Python (CV 2026)"
# CUDA 12.1 PyTorch build. Change this command if your CUDA/PyTorch setup differs.
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
pip install numpy pandas matplotlib pillow opencv-contrib-python scikit-image scikit-learn tqdm openpyxl pyyaml pycocotools shapely richThe recommended execution order is:
1. 01_dataset_exploration.ipynb
2. 02_preprocessing.ipynb
3. 02A_pretrain_sanity_check.ipynb
4. 03_train_unet_baseline.ipynb # simple baseline, BCE segmentation
5. 03A_train_unet_baseline_v2.ipynb # main U-Net baseline, channel-aware loss
6. 03B_train_unet_baseline_v3.ipynb # sparse-channel ablation
7. 04_train_autoencoder.ipynb # anatomical autoencoder prior
8. 05_soft_constraint.ipynb # ACNN-Soft
9. 07_hard_constraint.ipynb # ACNN-Strong
The main experimental comparison is:
U-Net baseline with channel-aware loss
vs
ACNN-Soft from scratch
vs
ACNN-Strong from scratch
The older notebook 06_simple_hard_constraint.ipynb is kept as development history / ablation prototype. It is not the primary hard-constraint implementation.
notebooks/01_dataset_exploration.ipynb
This notebook inspects the raw DenPAR data before any model training. It is used to understand:
- the original image format;
- the available anatomical annotations;
- the train/validation/test split fields;
- possible missing annotations;
- the structure of keypoint, polygon, and line annotations;
- the feasibility of deriving an image-level RBL class.
The output of this notebook is mostly diagnostic. It does not define the final model inputs; that happens in 02_preprocessing.ipynb.
notebooks/02_preprocessing.ipynb
The preprocessing notebook builds a cleaned 512×512 working dataset:
Processed_Data/processed_512x512/processed_labels_final_clean.csv
Processed_Data/processed_512x512/images/
Processed_Data/processed_512x512/anatomical_maps/
The generated CSV is the central metadata file used by all downstream notebooks.
Each radiograph is converted to grayscale, resized to 512×512, normalized to [0, 1], and saved as a processed image. The corresponding anatomical annotations are transformed consistently to the resized coordinate system.
Main constants:
TARGET_HEIGHT = 512
TARGET_WIDTH = 512
CEJ_SIGMA = 4.0
APEX_SIGMA = 4.0
BONE_LINE_THICKNESS = 3For each image, preprocessing creates an anatomical map tensor:
anatomical_map: [4, 512, 512]
The channel convention is fixed across the whole project:
| Channel | Name | Target type | Meaning |
|---|---|---|---|
| 0 | tooth_mask |
binary mask | tooth region mask |
| 1 | cej_heatmap |
continuous heatmap | cemento-enamel junction points |
| 2 | apex_heatmap |
continuous heatmap | root apex points |
| 3 | bone_line_mask |
binary/thin mask | alveolar bone-line annotation |
CEJ and apex annotations are point-like. Instead of predicting single pixels, the project converts them into Gaussian heatmaps. For a set of annotated points
with [0, 1].
This makes the target smoother and more learnable than a one-pixel target, while still preserving the sparse anatomical localization task.
The bone-line annotations are rasterized as thin line masks with thickness 3. This gives a binary target for the alveolar bone line while preserving its thin structure.
The project derives a classification label from estimated radiographic bone loss (RBL). The label is image-level, not tooth-level.
For each usable tooth, a CEJ point and an apex point define a local anatomical axis:
where:
-
$c$ is the CEJ point; -
$a$ is the apex point; -
$v$ is the CEJ-to-apex axis.
For each bone-line point
The scalar [0, 1]:
In this convention:
-
$t=0$ means the projected bone point lies at the CEJ; -
$t=1$ means the projected bone point lies at the apex.
The RBL estimate for a tooth is the selected projected value along the axis:
The image-level RBL value is the maximum tooth-level RBL estimate in the image:
The image-level class is then defined as:
| Class | Rule | Interpretation used in this project |
|---|---|---|
| 0 | max_rbl < 0.15 |
low RBL |
| 1 | 0.15 <= max_rbl <= 0.33 |
moderate RBL |
| 2 | max_rbl > 0.33 |
high RBL |
The thresholds correspond to 15% and 33% of the CEJ-apex axis.
Again, this is an RBL-derived image-level severity class. It is not a complete clinical diagnosis.
The preprocessing notebook stores diagnostic flags for downstream filtering. Important fields include:
| Column | Meaning |
|---|---|
max_rbl |
maximum estimated image-level RBL |
rbl_class |
derived class 0/1/2 |
has_valid_label |
whether a class could be computed |
has_reliable_label |
whether the label passed uncertainty checks |
flag_invalid_label |
invalid or missing RBL-derived label |
flag_saturated_rbl |
extremely high/saturated RBL estimate |
flag_large_max_gap |
diagnostic flag based on RBL distribution within the image |
flag_uncertain_rbl_label |
final uncertainty flag for classification |
exclude_for_leakage |
whether the image is excluded due to duplicate/leakage detection |
use_for_segmentation |
usable for anatomical segmentation |
use_for_classification |
usable for image-level classification |
use_for_multitask_training |
usable for joint segmentation/classification training |
To reduce train/validation/test leakage, the preprocessing includes a SIFT-based near-duplicate check. The strict duplicate rule uses:
SIFT_MIN_GOOD_MATCHES = 150
SIFT_MIN_INLIERS = 140
SIFT_MIN_INLIER_RATIO = 0.90
SIFT_MIN_COVERAGE_RATIO = 0.08A pair of images is automatically considered strong duplicate/leakage evidence only when all strict criteria are satisfied. Confirmed near-duplicates are excluded through the exclude_for_leakage flag.
src/data/data.py
DenPARDataset
make_datasets
make_dataloaders
compute_class_weights
summarize_datasetsDenPARDataset reads processed_labels_final_clean.csv, loads the processed image and anatomical map, and returns a dictionary:
{
"image": Tensor[1, H, W],
"anat_map": Tensor[4, H, W],
"label": Tensor[],
"image_id": str,
"split": str,
"index": int,
... optional metadata ...
}Supported task modes:
| Mode | Used by | Filtering logic |
|---|---|---|
autoencoder |
04_train_autoencoder.ipynb |
anatomical maps only; segmentation-usable samples |
segmentation |
optional segmentation-only experiments | segmentation-usable samples |
classification |
optional classification-only experiments | valid/reliable classification labels |
multitask |
U-Net, ACNN-Soft, ACNN-Strong | samples usable for both segmentation and classification |
all |
diagnostics | minimal filtering |
The main training notebooks use task_mode="multitask", while the anatomical autoencoder uses task_mode="autoencoder".
src/models/blocks.py
The project uses a small set of reusable convolutional blocks:
| Component | Purpose |
|---|---|
ConvBlock |
two convolutional layers with normalization and activation |
UpBlock |
U-Net upsampling block with skip connection |
UpBlockAE |
autoencoder upsampling block without skip connection |
count_trainable_parameters |
utility for reporting model size |
The U-Net decoder uses skip connections; the anatomical autoencoder decoder does not.
src/models/unet.py
notebooks/03_train_unet_baseline.ipynb
notebooks/03A_train_unet_baseline_v2.ipynb
notebooks/03B_train_unet_baseline_v3.ipynb
The main baseline is a multi-task U-Net. Given an input radiograph:
it predicts:
for anatomical segmentation logits, and:
for image-level class logits.
The model returns:
outputs = model(images)
seg_logits = outputs["seg_logits"] # [B, 4, 512, 512]
cls_logits = outputs["cls_logits"] # [B, 3]Important implementation detail: seg_logits are raw logits, not probabilities. The loss uses BCEWithLogitsLoss; metrics and visualizations apply torch.sigmoid explicitly.
The standard multi-task objective is:
where:
in the main experiments.
The classification loss is standard cross-entropy:
where
The final U-Net baseline uses a channel-aware segmentation loss implemented in:
src/training/losses.py
The four anatomical channels have very different target densities. The tooth mask is dense, while CEJ, apex, and bone-line targets are sparse. A single uniform BCE loss is therefore not ideal.
The project uses:
| Channel | Loss terms |
|---|---|
| tooth mask | weighted BCE + Dice |
| CEJ heatmap | weighted BCE + MSE |
| apex heatmap | weighted BCE + MSE |
| bone-line mask | weighted BCE + Dice |
For a binary/continuous target
For heatmap channels, the MSE term is computed on probabilities:
The per-channel losses are:
The final segmentation loss is a normalized weighted average:
All segmentation loss computations explicitly cast logits and targets to float32. This avoids FP16 overflow when AMP is enabled and losses/metrics sum over dense 512×512 maps.
This is the first simple baseline. It trains the same multi-task U-Net architecture with simpler segmentation supervision. It is kept as a reference and development baseline.
This is the main U-Net baseline used for comparison against ACNN variants. It uses the channel-aware loss with a balanced configuration:
seg_channel_weights = {
"tooth_mask": 1.0,
"cej_heatmap": 2.0,
"apex_heatmap": 2.0,
"bone_line_mask": 2.0,
}
seg_pos_weights = {
"tooth_mask": 1.0,
"cej_heatmap": 10.0,
"apex_heatmap": 10.0,
"bone_line_mask": 8.0,
}
tooth_dice_weight = 1.0
bone_dice_weight = 0.5
heatmap_mse_weight = 2.0This configuration is used again in ACNN-Soft and ACNN-Strong to make the comparison fair: the anatomical constraint is the main changed variable, not the base segmentation loss.
This notebook uses the same U-Net architecture and the same general channel-aware loss structure, but changes the weights to test a sharper sparse-channel trade-off:
seg_channel_weights = {
"tooth_mask": 1.0,
"cej_heatmap": 1.5,
"apex_heatmap": 2.5,
"bone_line_mask": 1.5,
}
seg_pos_weights = {
"tooth_mask": 1.0,
"cej_heatmap": 6.0,
"apex_heatmap": 12.0,
"bone_line_mask": 5.0,
}
tooth_dice_weight = 1.0
bone_dice_weight = 0.25
heatmap_mse_weight = 4.0This is not a new architecture. It is a loss-weight ablation and has richer checkpointing/model-selection logic.
src/models/autoencoder.py
notebooks/04_train_autoencoder.ipynb
The anatomical autoencoder learns a compact latent representation of plausible anatomical maps. It is trained only on ground-truth anatomical maps, not on radiographs.
This makes it an anatomical prior that can later be frozen and reused in ACNN-Soft and ACNN-Strong.
The autoencoder maps anatomical maps to a latent vector and reconstructs them:
The implementation returns logits:
outputs = ae(anat_maps)
z = outputs["latent"]
reconstruction_logits = outputs["reconstruction"]The final autoencoder configuration uses:
in_channels = 4
out_channels = 4
base_channels = 32
latent_dim = 256
up_mode = "bilinear"The autoencoder is trained with the same channel-aware segmentation loss used for the U-Net v2 baseline:
This makes the reconstruction objective aware of dense and sparse anatomical channels.
The notebook saves multiple checkpoints, including:
best_val_loss.pt
best_val_sparse_soft_dice.pt
last_autoencoder.pt
The main prior used by ACNN models is selected from the best validation loss checkpoint. The sparse checkpoint is useful for diagnostics.
notebooks/05_soft_constraint.ipynb
ACNN-Soft keeps the U-Net segmentation output as the final output, but adds a soft anatomical regularization term in the latent space of the frozen anatomical autoencoder.
The frozen autoencoder encoder maps both predicted anatomical maps and ground-truth anatomical maps to latent codes. The model is penalized if these latent codes are far apart.
Given an image
where
The frozen anatomical encoder computes:
For the ground-truth map
The autoencoder parameters are frozen. Gradients flow through
The full objective is:
where the latent weight is warmed up over the first epochs:
Main configuration:
lambda_latent = 1e-4
latent_warmup_epochs = 10
latent_batch_size = 4latent_batch_size computes the latent loss only on a subset of each batch. This reduces memory and time while preserving the anatomical regularization signal.
In ACNN-Soft, the final segmentation prediction is still the U-Net output:
final segmentation = raw U-Net segmentation logits
The autoencoder is used only as an extra training loss. It does not modify the output map at inference time.
src/models/hard.py
notebooks/07_hard_constraint.ipynb
ACNN-Strong uses the frozen anatomical autoencoder as an explicit projection module. The U-Net first predicts raw anatomical logits. These logits are converted to probabilities and passed through the frozen anatomical autoencoder encoder-decoder. The projected output becomes the final segmentation prediction.
In short:
ACNN-Soft:
penalize predictions that are far from the anatomical latent manifold
ACNN-Strong:
force the final segmentation output through the anatomical autoencoder projection
The model computes:
Then the frozen anatomical autoencoder projects the raw probability map:
The final segmentation output is:
The model returns both projected and raw logits:
outputs = model(images)
projected_seg_logits = outputs["seg_logits"]
raw_seg_logits = outputs["raw_seg_logits"]
cls_logits = outputs["cls_logits"]
latent = outputs["latent"]The strong model also uses the anatomical latent code for classification. The classification head receives:
- the U-Net bottleneck feature map;
- the anatomical latent vector extracted from the raw predicted maps.
After global average pooling of the bottleneck, the two vectors are normalized, concatenated, and classified:
This makes the classifier anatomy-aware, while the segmentation output is anatomically projected.
The main segmentation loss is computed on the projected logits:
A smaller auxiliary loss is also applied to the raw U-Net logits:
The auxiliary raw loss prevents the U-Net from learning arbitrary raw outputs that are only corrected by the autoencoder.
The final strong segmentation loss is:
with:
raw_aux_seg_weight = 0.25The full ACNN-Strong objective is:
The autoencoder projection is executed in float32 even when AMP is enabled:
raw_seg_probs = torch.sigmoid(raw_seg_logits.float())
with torch.amp.autocast(device_type=raw_seg_probs.device.type, enabled=False):
latent = anatomical_autoencoder.encode(raw_seg_probs)
projected_seg_logits = anatomical_autoencoder.decode(latent)This is intentional. During early training, random U-Net predictions can be far outside the distribution of ground-truth anatomical maps. Running the frozen autoencoder projection in FP16 can produce numerical instability or NaNs. Keeping only this projection in FP32 preserves stability while allowing the rest of the model to benefit from AMP.
src/training/metrics.py
The notebooks report:
- accuracy;
- balanced accuracy;
- macro precision;
- macro recall;
- macro F1;
- weighted F1.
Macro F1 is especially important because the RBL-derived classes may be imbalanced.
Soft Dice is computed on sigmoid probabilities without thresholding:
The mean soft Dice is:
The sparse soft Dice excludes the dense tooth channel:
This metric is useful because the tooth mask is much easier and denser than CEJ, apex, and bone-line targets.
For thresholded metrics, logits are converted to probabilities and then binarized:
Evaluation thresholds:
seg_eval_thresholds = {
"tooth_mask": 0.5,
"cej_heatmap": 0.3,
"apex_heatmap": 0.3,
"bone_line_mask": 0.5,
}The notebooks report per-channel:
- hard Dice;
- IoU;
- precision;
- recall;
- F1;
- predicted positive fraction;
- target positive fraction.
The predicted/target positive fractions are useful diagnostics for sparse channels. They show whether the model is predicting everything positive, everything negative, or a plausible amount of foreground.
For the main model-selection checkpoint, the notebooks use a balanced score:
This score is used only for checkpoint selection. It is not a training objective.
The usual checkpoint set is:
last_*.pt
best_val_loss.pt
best_val_cls_macro_f1.pt
best_val_mean_soft_dice.pt
best_val_sparse_soft_dice.pt
best_val_combined_score.pt
The main checkpoint for final test evaluation is usually:
best_val_combined_score.pt
because the task is multi-objective: both image-level classification and anatomical segmentation matter.
Each training notebook creates a run folder under runs/:
runs/<run_name>/
├── config.json
├── history.csv
├── run_summary.json
├── test_metrics.csv
├── test_classification_predictions.csv
├── test_confusion_matrix.csv
├── checkpoints/
└── plots/
Typical files:
| File | Meaning |
|---|---|
config.json |
exact configuration used for the run |
history.csv |
epoch-level training/validation metrics |
run_summary.json |
selected checkpoint and final test summary |
test_metrics.csv |
final test metrics |
test_classification_predictions.csv |
image-level predictions and probabilities |
test_confusion_matrix.csv |
classification confusion matrix |
checkpoints/*.pt |
saved PyTorch checkpoints |
plots/ |
diagnostic and qualitative plots |
Explores the raw DenPAR dataset and annotations. Use this to verify that paths, annotation formats, and splits are understood before preprocessing.
Builds the final processed dataset:
- resizes radiographs to 512×512;
- creates four-channel anatomical maps;
- estimates RBL values through CEJ-apex axis projection;
- derives image-level RBL classes;
- computes reliability flags;
- runs SIFT leakage checks;
- writes the final cleaned CSV.
Checks that the processed dataset can be loaded correctly:
- image tensors have shape
[1, 512, 512]; - anatomical maps have shape
[4, 512, 512]; - class labels are available where expected;
- train/validation/test splits are usable;
- visualization of processed images and targets is sensible.
Initial U-Net baseline. Useful as a simple reference run and development notebook.
Main U-Net baseline. Uses the balanced channel-aware loss configuration. This is the primary conventional baseline for comparison against ACNN-Soft and ACNN-Strong.
Sparse-channel ablation. Same U-Net architecture, different sparse-channel loss weights and richer checkpointing logic.
Trains the anatomical autoencoder on ground-truth anatomical maps. The resulting frozen autoencoder defines the anatomical prior used by ACNN-Soft and ACNN-Strong.
Trains ACNN-Soft from scratch:
- U-Net predicts segmentation and class logits;
- frozen AE encoder extracts predicted and ground-truth latent codes;
- latent MSE is added as a soft anatomical regularizer;
- final segmentation remains the raw U-Net output.
Trains ACNN-Strong from scratch:
- U-Net predicts raw segmentation logits;
- raw predictions are projected through the frozen AE encoder-decoder;
- projected logits are used as final segmentation output;
- raw logits receive auxiliary segmentation supervision;
- classification uses both U-Net bottleneck features and anatomical latent features.
Prototype / ablation for latent fusion. This is not the final hard projection model.
All model segmentation outputs are logits. The code applies sigmoid only when needed:
- inside BCE-compatible losses when required;
- inside Dice/MSE terms;
- inside metrics;
- before feeding predicted maps to the anatomical autoencoder.
This avoids accidental double-sigmoid or mismatched loss inputs.
CEJ and apex are sparse point-like annotations. Training with one-pixel binary maps would make the positive class extremely rare and unstable. Gaussian heatmaps provide a smoother target and make localization learnable.
The tooth channel can dominate mean metrics because it is dense and easier. For this reason, the notebooks also track sparse-channel performance separately:
CEJ + apex + bone-line
This prevents the model from looking strong only because it segments teeth well.
The autoencoder is trained before the ACNN models. During ACNN training it is frozen because it represents a fixed anatomical prior. If it were updated jointly, the prior itself could move to accommodate bad predictions, weakening the constraint.
Without raw auxiliary supervision, the U-Net could learn raw maps that are not meaningful by themselves, relying entirely on the autoencoder decoder to produce reasonable outputs. The raw auxiliary term keeps the intermediate prediction grounded:
AMP is useful for speed and memory, but dense Dice-style reductions over large 512×512 maps can overflow or become unstable in FP16. The loss and metric implementations cast logits and targets to float32. ACNN-Strong also runs the frozen AE projection in float32 to avoid NaNs during early training.
To reproduce the full project from scratch:
- Create the environment.
- Download and extract DenPAR into the repository root.
- Run
01_dataset_exploration.ipynbfor inspection. - Run
02_preprocessing.ipynbto generate the processed dataset. - Run
02A_pretrain_sanity_check.ipynbto verify processed data. - Train the U-Net baseline with
03A_train_unet_baseline_v2.ipynb. - Optionally train the sparse-channel ablation with
03B_train_unet_baseline_v3.ipynb. - Train the anatomical autoencoder with
04_train_autoencoder.ipynb. - Train ACNN-Soft with
05_soft_constraint.ipynb. - Train ACNN-Strong with
07_hard_constraint.ipynb. - Use the saved
run_summary.json,test_metrics.csv, prediction CSVs, and qualitative plots for the report.
| Model | Main idea | Segmentation output | Anatomical prior usage |
|---|---|---|---|
| U-Net baseline | standard multi-task model | raw U-Net logits | none |
| ACNN-Soft | latent-space regularization | raw U-Net logits | frozen AE encoder in loss |
| ACNN-Strong | hard anatomical projection | AE-projected logits | frozen AE encoder-decoder in forward path |
Formally, the three main models can be summarized as:
U-Net baseline:
learn anatomy directly from radiograph supervision
ACNN-Soft:
learn anatomy from radiographs while being softly pulled toward the anatomical latent manifold
ACNN-Strong:
force the final anatomical output to pass through the learned anatomical manifold
To avoid ambiguity:
- RBL-derived severity class means the project-specific image-level class computed from estimated RBL.
- Anatomical maps means the four-channel target tensor: tooth, CEJ, apex, bone-line.
- Sparse channels means CEJ, apex, and bone-line.
- ACNN-Soft means latent-space regularization through the frozen AE encoder.
- ACNN-Strong means hard projection through the frozen AE encoder-decoder.
- Projected segmentation means the ACNN-Strong output after autoencoder projection.
- Raw segmentation means the intermediate U-Net output before autoencoder projection.