# S2F experiments: data audit, evaluation, baselines and ablations This folder holds the code for every number reported in the paper beyond the training of the released checkpoints: - the dataset audit and split manifests; - the evaluation of the released models; - the baselines and ablation study. Run every command from the repository root (`Shape2Force/`); a GPU is used automatically when available. ``` experiments/ layout.py dataset layout, per-set metadata (Young's modulus, TFM settings), manifest helpers prepare_dataset.py audit legacy data vs dataset/, copy missing images, write splits/*.csv manifest_data.py data loader driven by a split manifest (same preprocessing as data/cell_dataset.py) ablation_models.py plain U-Net and S2F generator variants losses.py per-image WFM-RME and weighted MSE + soft Dice losses (both used for the released model) configs.py shared training protocol + one entry per variant train.py train one variant evaluate.py evaluate released checkpoints / baselines / trained runs on all manifests run_all.py run the steps above in order splits/ split manifests (tracked in git) runs/ training outputs (git-ignored) results/ audit and evaluation tables ``` ## Data `dataset/` (git-ignored, Zenodo record 10.5281/zenodo.18457879) contains: - `singlecell/train` (236), `singlecell/test` (46); - `spheroid/train` (129), `spheroid/test` (28). The released single-cell model was trained on a larger split (`all_tune`: 280 training / 81 validation pairs, 1, 6 and 10 kPa). The paper also reports independent test sets. `prepare_dataset.py` reconstructs all of this from the original data tree and adds the missing images to `dataset/`: ``` dataset/singlecell_extra/// all_tune images that are not in singlecell/ (1 and 10 kPa) dataset/singlecell_external/// independent test sets (DMSO, Y-27632, TGF-beta, fibronectin, ...) ``` Each manifest in `splits/` lists one sample per row: - the sample folder relative to `dataset/`; - condition, Young's modulus and TFM settings: colour limit of the heatmap (`ythreshold_Pa`, 500 Pa for single cells and 200 Pa for spheroids) and pixel-size setting; - the substrate setting the original code fed to the network (`substrate_legacy`) and the nominal one (`substrate_nominal`); - the MD5 of the bright-field image. ## Steps ```bash # 1. audit only: what is in dataset/ vs the original tree, all_tune composition, leakage check python -m experiments.run_all --legacy-root /cellforce/dataset --steps audit # 2. complete dataset/ and write splits/*.csv (then commit experiments/splits/) python -m experiments.run_all --legacy-root /cellforce/dataset --steps prepare # 3. released checkpoints, substrate sweep, input-free baseline, image-only stage-1 model python -m experiments.run_all --steps eval_released --stage1 /outputs/checkpoints/last/last_checkpoint_final.pth # 4. baselines / ablations (S2F about 7 h, U-Net about 2.5 h each on an RTX A6000; resumable), then their evaluation python -m experiments.run_all --steps train,eval_runs --variants s2f_full,unet,unet_gan,s2f_no_gan,s2f_no_substrate,s2f_no_force ``` A single run: `python -m experiments.train --variant unet --seed 0`. ### GPUs and run time - `--steps train` runs one job per GPU in parallel (default `--gpus 0,1`). Each job sees only its own GPU (`CUDA_VISIBLE_DEVICES`), and the next variant starts as soon as a GPU is free. - A model is never split across GPUs: with batch size 2, that would harm batch-norm/GAN training. - `train.py` refuses to run without CUDA unless `--allow-cpu` is given. - Augmentation runs in `--num-workers` DataLoader processes (default 2 per job; use 0 if RAM is short). - Finished runs (and finished phases) are skipped and interrupted runs resume from the phase's `last_checkpoint.pth`, so the command can simply be re-run. - Run time on an RTX A6000: S2F about 3 min per epoch, i.e. about 7 h per variant (100 + 40 epochs); U-Net about 2.5 h. On two GPUs the 6 core variants take about 15 h (`s2f_no_force` reuses phase 1 of `s2f_full`). - Progress is logged to `experiments/runs/_seed/train.log` (validation pixel r and WFM-RME every epoch) and `phase*/history.json`. For a quick check that everything runs, add `--epochs 1 --max-batches 2` (1 epoch per phase; add `--allow-cpu` without a GPU). ## Training protocol (all variants) Every variant is trained from scratch on `alltune_train` (280 pairs), batch size 2, 1024 × 1024 px, the augmentations of `data/augmentations.py`, Adam (β = 0.5/0.999) with cosine decay after 10 % linear warm-up, in two phases that mirror the staged training of the released model: | phase | epochs | reconstruction loss | other terms | lr | starts from | |---|---|---|---|---|---| | 1 localisation | 100 | 100 × weighted MSE + soft Dice (pos_weight 3, threshold 0.2, Dice 0.5) | 1 × adversarial | 1e-4 | random weights | | 2 magnitude | 40 | 10 × per-image WFM-RME | 1 × adversarial + 100 × MSE of mean intensity | 1e-5 | best phase-1 generator | Each phase uses a new discriminator and optimiser and keeps the checkpoint with the lowest validation loss of that phase on `alltune_val` (81 pairs); the final model is the best phase-2 checkpoint. WFM-RME alone does not learn to localise the forces from random weights (a first protocol that used it from the start gave pixel r ≈ 0), which is also why the released model was first trained with the weighted MSE + Dice loss. Variants change one component each (`configs.py`): | variant | change | |---|---| | `s2f_full` | none (from-scratch counterpart of the released model) | | `unet` | plain U-Net (Ronneberger et al. 2015), no attention modules, no discriminator | | `unet_gan` | plain U-Net + PatchGAN (pix2pix-style) | | `mask_only` | binary cell mask instead of bright field (needs `cell_mask.tif` in each sample folder) | | `s2f_no_gan` | no discriminator (λ_GAN = 0) | | `s2f_no_force` | no stress-consistency term (λ_force = 0 in phase 2; phase 1 is identical to `s2f_full` and reused) | | `s2f_gan_0.1`, `s2f_gan_5` | λ_GAN = 0.1 / 5 | | `s2f_no_substrate` | bright field only (no substrate channels) | | `s2f_nominal_substrate` | substrate channels from the nominal Young's modulus of each image | | `s2f_no_multiscale` | no multi-scale input module | | `s2f_bridge_no_cbam` | bottleneck without CBAM | | `s2f_bridge_plain` | bottleneck without CBAM, Global Context Module and hierarchical attention | | `s2f_no_attention_gates` | skip connections without attention gates | The released checkpoint was obtained by staged fine-tuning from earlier checkpoints, with substrate channels added only in its last two stages (see Methods). `evaluate.py` reports it alongside the from-scratch variants. ## Metrics `evaluate.py` computes every metric per image and reports the mean and SD across images: - MSE, MS-SSIM and pixel-wise Pearson r; - per-image WFM-RME; - mean traction stress in Pa (heatmap value × colour limit); - the fraction of saturated ground-truth pixels; - inference time. Its outputs are `results/eval/per_image.csv`, `summary.csv` and `model_params.csv`.