Aprendizagem Profunda | Mestrado em InteligĂȘncia Artificial | Universidade do Minho | 2025/26
Classificação multi-classe de imagens fluoroscĂłpicas de CPRE em quatro categorias â Biliary_Leaks, Lithiasis, Normal e Stricture â com replicação fiel do baseline da referĂȘncia (Monica) em pipeline MONAI puro, ablation sem o equipamento confounder (PCR Eleva), mĂĄscaras SAM3 como 4Âș canal (atenuam o viĂ©s do instrumento), um ensemble com explicabilidade Grad-CAM, modelos especialistas com routing por equipamento, VLMs em few-shot e um Conditional VAE para sĂntese de dados.
| Abordagem | Melhor Modelo | F1 Macro (test) |
|---|---|---|
| CNN â Monica baseline (replica, com PCR) | DenseNet121 | 0.620 ± 0.077 |
| CNN â Baseline (sem PCR em train/val) | ResNet50 | ablação â ver compare.md |
| CNN â SAM3 4-canais + ensemble (soft voting) | 4 modelos (3Ă SAM3 4ch + RGB) | 0.763 |
| VLM â Few-Shot | BiomedCLIP / Qwen2.5-VL | â |
| CVAE encoder + Classificador clĂĄssico | CVAE-512 (latent 4096) + XGBoost / SVM / RF / MLP / LR | â |
| Concept Bottleneck | BiomedCLIP + CBM | â |
Baseline do paper (MIQR-CC, todos os equipamentos): F1 macro = 0.738
Todos os resultados CNN da replica sĂŁo mean ± std sobre 3 seeds {1, 24, 42} com o test set sagrado (idĂȘntico em todas as seeds, inclui PCR). O ensemble SAM3 (4 modelos, F1 0.763) e o seu ganho sobre a baseline (eliminação do viĂ©s do endoscĂłpio) estĂŁo documentados em notebooks/ensemble.ipynb.
đ RelatĂłrio: Ver PDF do RelatĂłrio
Replicação bit-a-bit do pipeline do paper de referĂȘncia (MONAI puro quando preset: monica estĂĄ activo). 5 modelos:
| Modelo | Backbone (lib) | Imagem | Config |
|---|---|---|---|
| ResNet50 | torchvision V1 (tv_resnet50) | 512 | configs/resnet50_monica.yaml |
| DenseNet121 | MONAI (monai_densenet121) | 512 | configs/densenet121_monica.yaml |
| EfficientNet-B7 | torchvision V1 (tv_efficientnet_b7) | 512 | configs/efficientnet_b7_monica.yaml |
| MobileNetV2 | torchvision V1 (tv_mobilenet_v2) | 512 | configs/mobilenetv2_monica.yaml |
| DeiT-III Small | timm (deit3_small_patch16_384) | 384 | configs/deit3_small_monica.yaml |
Pipeline activo quando augmentation.preset: monica:
monai.losses.FocalLoss(to_onehot_y=True) (Îł=2)monai.transforms â RandRotate(range_x=15) (em radianos = 360° aleatĂłrio), RandZoom(0.9-1.1), RandAdjustContrast, RandGaussianNoise(std=0.01), NormalizeIntensity() (z-score per-image), Lambda(repeat_to_3ch)monai.data.DataLoader, batch=4, num_workers=0, pin_memory=False, use_amp=Falseset_determinism(seed=0) antes de set_seed(42), cudnn.deterministic=TrueEstratĂ©gia para isolar o efeito do equipamento PCR Eleva:
| Pasta | Train/Val | Test | Uso |
|---|---|---|---|
database/splits/monica/seed{N} | inclui PCR | inclui PCR (sagrado) | Monica baseline â replica o paper exactamente |
database/splits/seed{N} | sem PCR | inclui PCR (sagrado) | Baseline limpa â quantifica o efeito confounder |
seed42 em monica/ reproduz exactamente o monica/main original. O test set Ă© idĂȘntico em todas as seeds e em ambas as variantes (sagrado, com PCR).
Beta-VAE condicional por classe para geração de imagens sintéticas (Biliary_Leaks é a classe mais rara):
CzĂhĂw com latent_channels=16 e lat_size=16 â latent_dim = 4096 (2^12) a 512Ă512Usa o encoder do CVAE jĂĄ treinado como feature extractor (mu 4096-d, label-agnostic via mĂ©dia sobre as 4 classes possĂveis) e classifica com um de vĂĄrios algoritmos clĂĄssicos: xgboost (default), svm (RBF + grid search), random_forest, mlp, logistic.
--classifier {xgboost, svm, random_forest, mlp, logistic} â todos partilham a mesma extração de features--feature_mode {mean, concat, single} â controla como agregar mu(x|y) sobre as classes (default mean â sem vazamento da label verdadeira)class_weight="balanced" (SVM/RF/LR) ou sample_weight inverso-frequĂȘncia (XGB); veredito sempre por f1_macrorandom_state--pca_dim 256) antes do classificador se as 4096 features puserem o modelo sobre-parametrizadoSegmentação zero-shot com SAM3 (facebook/sam3) por prompts clĂnicos (ducto, guia, patologia, endoscĂłpio) para focar o ROI e atenuar o viĂ©s do instrumento (o modelo aprende o endoscĂłpio em vez da anatomia). A mĂĄscara entra como 4Âș canal (RGB + mĂĄscara soft); a 1ÂȘ conv Ă© adaptada preservando os pesos prĂ©-treinados (canal extra inicializado pela mĂ©dia RGB).
scripts/generate_sam3_masks.py (mask.png combinada; --multi gera mask_{duct,wire,pathology,device}.png) â database/masks/src/data/dataset_sam3.py (CPRESAMM3Dataset, transforms sincronizados imagem+mĂĄscara), src/models/builder_sam3.py, entrypoints train_sam3.py / evaluate_sam3.pyconfigs/resnet50_sam3*.yamlSoft-voting de vĂĄrias ResNet50+SAM3 4-canais (melhor membro: resnet50_sam3_4ch_opt, peso â 0.46). RelatĂłrio: notebooks/ensemble.ipynb.
| Modelo / Ensemble | F1-macro (test) | Viés do instrumento |
|---|---|---|
| Baseline EfficientNet-B7 | 73.81% | muito alto (shortcut no endoscĂłpio) |
| Ensemble 3 modelos (soft voting) | 75.18% | mĂnimo |
| Ensemble 4 modelos (soft voting) | 76.30% | eliminado |
Código em scripts/ensemble/ (composição, pesos, avaliação, stacking e geração do relatório + Grad-CAMs).
notebooks/pcr.ipynb separa por equipamento e junta no fim: Fase 1 especialista Ziehm Vision (nĂŁo-PCR), Fase 2 especialista PCR (PCR Eleva, sĂł ~36 imgs â test de 11 intocĂĄvel), Fase 3 sistema com routing por equipamento (PCR â especialista PCR, resto â Ziehm).
Dataset MIQR-CC â imagens fluoroscĂłpicas de CPRE, anotadas por classe diagnĂłstica.
Biliary_Leaks, Lithiasis, Normal, Stricture (Benign + Malignant Stricture agregadas â ver notebooks/domain.ipynb){1, 24, 42}. Test set sagrado: extraĂdo com TEST_SEED=42 fixo e idĂȘntico em todas as seeds + variantesprepare_dataset.py:
database/splits/monica/seed{N} â inclui PCR em train/val (replica o paper)database/splits/seed{N} â train/val sem PCR; test mantĂ©m PCR (sagrado)monica/seed42 reproduz exactamente o monica/main original (mesmo split que o paper)AP2/
âââ configs/ # ConfiguraçÔes YAML
â âââ *_monica.yaml # Monica baseline â 5 modelos (resnet50/densenet121/efficientnet_b7/mobilenetv2/deit3_small)
â âââ resnet50_sam3*.yaml # SAM3 4-canais (base / _reg / _frozen)
â âââ grid/ # ~288 configs do grid search (3 modelos Ă combos Ă seeds)
â âââ biomedclip_fewshot.yaml # VLM â BiomedCLIP few-shot
â âââ qwen2_5_vl_7b_fewshot.yaml # VLM â Qwen2.5-VL few-shot
â âââ vila_m3_8b_fewshot.yaml # VLM â VILA-M3 few-shot
â âââ cvae.yaml # CVAE â geração de imagens sintĂ©ticas
â
âââ scripts/
â âââ prepare_dataset.py # Splits por paciente, 2 variantes (--also_no_pcr_out)
â âââ multiseed.py / aggregate_seeds.py / audit_splits.py / compare_datasets.py
â âââ gridsearch.py # Grid search retomĂĄvel (preset MONAI)
â âââ generate_sam3_masks.py # MĂĄscaras SAM3 (combinada; --multi = por-categoria) â database/masks/
â âââ mirror_masks_to_splits.py # Espelha mĂĄscaras para a estrutura dos splits
â âââ train_cvae.py / generate_cvae.py / cvae_separability.py / cvae_classifier.py / cvae_grid*.py
â âââ run_vlm_fewshot.py / run_biomedclip_fewshot.py / aggregate_vlm_results.py
â âââ roi_experiment.py / concept_bottleneck.py / augment_experiment.py / metadata_only.py
â âââ ensemble/ # Ensemble SAM3 + relatĂłrio Grad-CAM (soft voting, save, eval, stacking)
â
âââ src/
â âââ data/
â â âââ dataset.py / transforms.py / loaders.py # pipeline cv2 + albumentations (default)
â â âââ monai_pipeline.py # pipeline MONAI fiel (preset=monica)
â â âââ dataset_sam3.py / loaders_sam3.py # CPRESAMM3Dataset 4-canais (RGB + mĂĄscara SAM3)
â âââ models/
â â âââ builder.py # backbones monai/torchvision/timm/SimpleCNN
â â âââ builder_sam3.py # variantes 4-canais (1ÂȘ conv adaptada)
â â âââ cvae.py # Conditional VAE-GAN
â âââ interpretability/gradcam.py # Grad-CAM (CNN + ViT via reshape_transform)
â âââ training/ # Trainer, losses (focal + monai_focal), mĂ©tricas, EMA
â âââ vlm/ # Wrappers BiomedCLIP, Qwen2.5-VL, VILA-M3
â âââ utils/ # Seed (com set_determinism MONAI), config
â
âââ notebooks/
â âââ domain.ipynb # Contexto clĂnico das 4 classes
â âââ exploration.ipynb # EDA + metadata-only + prĂ©-proc/augmentation visuais
â âââ baseline.ipynb # Replica Monica (5 modelos sequenciais) + figuras
â âââ augmentation.ipynb # Efeito da augmentation na ResNet50 (none/light/monica/heavy)
â âââ masks.ipynb # Impacto das mĂĄscaras SAM3 (4Âș canal) na ResNet50
â âââ age.ipynb # FusĂŁo da idade (late fusion) na ResNet50
â âââ pcr.ipynb # Especialistas (Ziehm/PCR) + sistema com routing
â âââ segmentation.ipynb # SAM3 â prompts, mĂĄscaras, tensor 4 canais
â âââ ensemble.ipynb # Ensemble SAM3 (soft-voting) + Grad-CAM
â âââ conclusion.ipynb # ConclusĂŁo geral (vs baseline MĂłnica + Grad-CAM)
â âââ figures/ # Figuras exportadas pelos notebooks
â
âââ monica/ # Notebooks originais da referĂȘncia â read-only (gitignored)
â
âââ database/ # NĂŁo versionado (gitignored)
â âââ MIQR-CC-Dataset/ # dataset original (metadata.csv + raw/ + processed/)
â âââ splits/ # monica/seed{N} (com PCR) + seed{N} (sem PCR), test sagrado
â âââ masks/ # mĂĄscaras SAM3 por split/classe/imagem
â
âââ outputs/ # NĂŁo versionado (gitignored)
â âââ baseline/ # replica Monica (5 modelos Ă 3 seeds)
â âââ ablation/ (pcr, clahe, metadata) · checkpoints/ · reports/ · gridsearch/ · ensemble/
â âââ audit.md · compare.md
â
âââ docs/enunciado.pdf
âââ train.py / evaluate.py # Entrypoints (CNN baseline)
âââ train_sam3.py / evaluate_sam3.py # Entrypoints (pipeline SAM3 4-canais)
âââ env.yaml
âââ README.md
\ para quebra de linha em vez de uma linha sĂł.git clone https://github.com/Luismpso/AP2.git
cd AP2
conda env create -f env.yaml
conda activate dl
CPU-only: trocar o URL no
env.yamlparahttps://download.pytorch.org/whl/cpu. MONAI Ă© dependĂȘncia obrigatĂłria (jĂĄ emenv.yaml).
Colocar o dataset em database/MIQR-CC-Dataset/ (deve conter metadata.csv + raw/ + processed/).
python scripts/prepare_dataset.py --dataset_dir database/MIQR-CC-Dataset --out_dir database/splits/monica --also_no_pcr_out database/splits --seeds 1 24 42 --no_exclude --overwrite
Resultado:
database/splits/monica/seed{1,24,42}/ â Monica-style (com PCR em train/val + test)database/splits/seed{1,24,42}/ â train/val sem PCR, test idĂȘntico (sagrado)python scripts/audit_splits.py --monica
Gera outputs/audit.md com tabelas: leakage por split, contagens por classe e equipamento, variabilidade do test set entre seeds (deve ser 100% â test sagrado).
python scripts/multiseed.py --configs configs/densenet121_monica.yaml configs/resnet50_monica.yaml configs/efficientnet_b7_monica.yaml configs/mobilenetv2_monica.yaml configs/deit3_small_monica.yaml --seeds 1 24 42 --splits_root database/splits/monica --out_dir outputs/baseline --evaluate_after
5 modelos Ă 3 seeds = 15 runs (treino + evaluate). Ă resumĂvel: se cair a meio, voltar a correr o mesmo comando salta os runs jĂĄ feitos. Resultados ficam em outputs/baseline/{checkpoints,reports,figures}/.
Para quantificar o impacto do equipamento PCR Eleva (presente no test sagrado mas removido aqui de train/val), corremos sĂł ResNet50 Ă 3 seeds:
python scripts/multiseed.py --configs configs/resnet50_monica.yaml --seeds 1 24 42 --splits_root database/splits --out_dir outputs/ablation/pcr --evaluate_after
3 runs (~30 min total). NĂŁo Ă© necessĂĄrio replicar todos os 5 modelos â basta um para a ablação ser informativa no relatĂłrio.
python scripts/aggregate_seeds.py --reports_dir outputs/baseline/reports outputs/ablation/pcr/reports --labels baseline ablation_pcr --out outputs/compare.md
Output: outputs/compare.md (tabela markdown com val + test, mean±std) e outputs/compare.md.csv. Compara os 5 modelos da replica com a ResNet50 sem PCR â mostra o "delta" do confounder.
python scripts/run_biomedclip_fewshot.py --config configs/biomedclip_fewshot.yaml
python scripts/run_vlm_fewshot.py --config configs/qwen2_5_vl_7b_fewshot.yaml
python train_cvae.py --config configs/cvae.yaml --data_root database/splits/seed1
python scripts/generate_cvae.py --checkpoint outputs/checkpoints/cvae_512/best.pt --n_per_class 200 --out_dir database/synthetic/cvae_512
python scripts/cvae_separability.py --checkpoint outputs/checkpoints/cvae_512/best.pt --data_root database/splits/seed1
# XGBoost (default)
python scripts/cvae_classifier.py --checkpoint outputs/checkpoints/cvae_512/best.pt --data_root database/splits/seed1 --seeds 42 1 24
# trocar para SVM / RF / MLP / Logistic
python scripts/cvae_classifier.py --checkpoint outputs/checkpoints/cvae_512/best.pt --data_root database/splits/seed1 --classifier svm
python scripts/cvae_classifier.py --checkpoint outputs/checkpoints/cvae_512/best.pt --data_root database/splits/seed1 --classifier random_forest
python scripts/cvae_classifier.py --checkpoint outputs/checkpoints/cvae_512/best.pt --data_root database/splits/seed1 --classifier mlp
# rĂĄpido (single conditioning + PCA)
python scripts/cvae_classifier.py --checkpoint outputs/checkpoints/cvae_512/best.pt --data_root database/splits/seed1 --feature_mode single --pca_dim 256
Para decidir se vale a pena fazer fusion CNN + metadata, treina um classificador clĂĄssico sĂł com idade + sexo e vĂȘ o F1 no test sagrado:
# XGBoost (default)
python scripts/metadata_only.py
# alternativa linear
python scripts/metadata_only.py --classifier logistic
# diagnĂłstico do confounder (adiciona equipment_model â nĂŁo reportar como resultado clĂnico)
python scripts/metadata_only.py --include_equipment
Interpretação:
Resumo guardado em outputs/ablation/metadata/summary.json.
Por seed: outputs/<dir>/reports/<run_name>/
metrics.json â campos legacy (test) + bloco splits: {val, test}confusion_matrix_val.png / confusion_matrix_test.pngpredictions_val.csv / predictions_test.csveval.log â termina com VAL | acc=... f1m=... / TEST | ...Agregado: outputs/compare.md + .csv
notebooks/figures/none/light/monica/heavy): F1, curvas, confusĂŁo, Grad-CAMnomask/combined/no_endoscope/separated)Dois entrypoints na raiz (classes â nomes de pasta exatos:
Biliary_Leaks, Lithiasis, Normal, Stricture):
| Script | Dados | SaĂda |
|---|---|---|
evaluate.py / evaluate_sam3.py | rotulados (<root>/test/<Classe>/*.png) | F1, accuracy, matriz, predictions.csv |
predict.py | imagens soltas (sem rótulos) | CSV de prediçÔes |
Os checkpoints estĂŁo em models/ (incluĂdos no zip â ver models/README.md).
densenet121 (rĂ©plica fiel da MĂłnica, ~0.62 F1) â sĂł precisa das imagens:
# rotulado (F1/matriz):
python evaluate.py --config configs/densenet121_monica.yaml \
--checkpoint models/densenet121_monica_seed42.pt --data_root <DADOS> --skip_val
# imagens soltas (só prediçÔes):
python predict.py --config configs/densenet121_monica.yaml \
--checkpoint models/densenet121_monica_seed42.pt --images_dir <PASTA> --out predicoes.csv
4 modelos em soft-voting (reportado 76.30%; reproduzido localmente 75.23%). Precisa
de GPU + SAM3 (facebook/sam3) para gerar as mĂĄscaras das imagens novas:
# 1. mĂĄscaras por-categoria dos dados novos
python scripts/generate_sam3_masks.py --multi --data_root <DADOS> --out_root <MASCARAS>
# 2. avaliar os 4 membros (--tta no baseline 3ch; evaluate_sam3 jĂĄ tem TTA on)
python evaluate_sam3.py --config configs/resnet50_sam3_4ch_opt.yaml --checkpoint models/resnet50_sam3_4ch_opt.pt --data_root <DADOS> --masks_root <MASCARAS>
python evaluate_sam3.py --config configs/resnet50_sam3_4ch_opt_v3.yaml --checkpoint models/resnet50_sam3_4ch_opt_v3.pt --data_root <DADOS> --masks_root <MASCARAS>
python evaluate_sam3.py --config configs/resnet50_radimagenet_4ch.yaml --checkpoint models/multitask_resnet50_imagenet_4ch.pt --data_root <DADOS> --masks_root <MASCARAS>
python evaluate.py --tta --config configs/resnet50_baseline_opt.yaml --checkpoint models/resnet50_baseline_opt.pt --data_root <DADOS> --skip_val
# 3. combinar (pesos 4-M: V1 .458 / V3 .241 / Multitask .194 / Baseline .107 â ver notebooks/ensemble.ipynb;
# scripts/ensemble/ensemble_save.py faz a versĂŁo 3-M)
Reproduzir o nosso test (seed42):
database/masks_multi/seed42segue no zip â corre o passo 2 sem--data_root/--masks_root(usa os defaults dos configs). Sem GPU/SAM3, usa o Caminho A (corre sĂł com imagens).
| Nome | NÂș | |
|---|---|---|
| LuĂs Miguel Pereira Silva | PG60390 | pg60390@alunos.uminho.pt |
| Pedro Miguel S. A. Urbano dos Reis | PG59908 | pg59908@alunos.uminho.pt |
| Guilherme Lobo Pinto | PG60225 | pg60225@alunos.uminho.pt |
| Pedro Alexandre Silva Gomes | PG60289 | pg60289@alunos.uminho.pt |
Este trabalho é de cariz estritamente académico. Universidade do Minho, Escola de Engenharia, Departamento de Informåtica.
Jupyter Notebook
98.7%
Python
1.3%
Aprendizagem Profunda | Mestrado em InteligĂȘncia Artificial | Universidade do Minho | 2025/26
Classificação multi-classe de imagens fluoroscĂłpicas de CPRE em quatro categorias â Biliary_Leaks, Lithiasis, Normal e Stricture â com replicação fiel do baseline da referĂȘncia (Monica) em pipeline MONAI puro, ablation sem o equipamento confounder (PCR Eleva), mĂĄscaras SAM3 como 4Âș canal (atenuam o viĂ©s do instrumento), um ensemble com explicabilidade Grad-CAM, modelos especialistas com routing por equipamento, VLMs em few-shot e um Conditional VAE para sĂntese de dados.
| Abordagem | Melhor Modelo | F1 Macro (test) |
|---|---|---|
| CNN â Monica baseline (replica, com PCR) | DenseNet121 | 0.620 ± 0.077 |
| CNN â Baseline (sem PCR em train/val) | ResNet50 | ablação â ver compare.md |
| CNN â SAM3 4-canais + ensemble (soft voting) | 4 modelos (3Ă SAM3 4ch + RGB) | 0.763 |
| VLM â Few-Shot | BiomedCLIP / Qwen2.5-VL | â |
| CVAE encoder + Classificador clĂĄssico | CVAE-512 (latent 4096) + XGBoost / SVM / RF / MLP / LR | â |
| Concept Bottleneck | BiomedCLIP + CBM | â |
Baseline do paper (MIQR-CC, todos os equipamentos): F1 macro = 0.738
Todos os resultados CNN da replica sĂŁo mean ± std sobre 3 seeds {1, 24, 42} com o test set sagrado (idĂȘntico em todas as seeds, inclui PCR). O ensemble SAM3 (4 modelos, F1 0.763) e o seu ganho sobre a baseline (eliminação do viĂ©s do endoscĂłpio) estĂŁo documentados em notebooks/ensemble.ipynb.
đ RelatĂłrio: Ver PDF do RelatĂłrio
Replicação bit-a-bit do pipeline do paper de referĂȘncia (MONAI puro quando preset: monica estĂĄ activo). 5 modelos:
| Modelo | Backbone (lib) | Imagem | Config |
|---|---|---|---|
| ResNet50 | torchvision V1 (tv_resnet50) | 512 | configs/resnet50_monica.yaml |
| DenseNet121 | MONAI (monai_densenet121) | 512 | configs/densenet121_monica.yaml |
| EfficientNet-B7 | torchvision V1 (tv_efficientnet_b7) | 512 | configs/efficientnet_b7_monica.yaml |
| MobileNetV2 | torchvision V1 (tv_mobilenet_v2) | 512 | configs/mobilenetv2_monica.yaml |
| DeiT-III Small | timm (deit3_small_patch16_384) | 384 | configs/deit3_small_monica.yaml |
Pipeline activo quando augmentation.preset: monica:
monai.losses.FocalLoss(to_onehot_y=True) (Îł=2)monai.transforms â RandRotate(range_x=15) (em radianos = 360° aleatĂłrio), RandZoom(0.9-1.1), RandAdjustContrast, RandGaussianNoise(std=0.01), NormalizeIntensity() (z-score per-image), Lambda(repeat_to_3ch)monai.data.DataLoader, batch=4, num_workers=0, pin_memory=False, use_amp=Falseset_determinism(seed=0) antes de set_seed(42), cudnn.deterministic=TrueEstratĂ©gia para isolar o efeito do equipamento PCR Eleva:
| Pasta | Train/Val | Test | Uso |
|---|---|---|---|
database/splits/monica/seed{N} | inclui PCR | inclui PCR (sagrado) | Monica baseline â replica o paper exactamente |
database/splits/seed{N} | sem PCR | inclui PCR (sagrado) | Baseline limpa â quantifica o efeito confounder |
seed42 em monica/ reproduz exactamente o monica/main original. O test set Ă© idĂȘntico em todas as seeds e em ambas as variantes (sagrado, com PCR).
Beta-VAE condicional por classe para geração de imagens sintéticas (Biliary_Leaks é a classe mais rara):
CzĂhĂw com latent_channels=16 e lat_size=16 â latent_dim = 4096 (2^12) a 512Ă512Usa o encoder do CVAE jĂĄ treinado como feature extractor (mu 4096-d, label-agnostic via mĂ©dia sobre as 4 classes possĂveis) e classifica com um de vĂĄrios algoritmos clĂĄssicos: xgboost (default), svm (RBF + grid search), random_forest, mlp, logistic.
--classifier {xgboost, svm, random_forest, mlp, logistic} â todos partilham a mesma extração de features--feature_mode {mean, concat, single} â controla como agregar mu(x|y) sobre as classes (default mean â sem vazamento da label verdadeira)class_weight="balanced" (SVM/RF/LR) ou sample_weight inverso-frequĂȘncia (XGB); veredito sempre por f1_macrorandom_state--pca_dim 256) antes do classificador se as 4096 features puserem o modelo sobre-parametrizadoSegmentação zero-shot com SAM3 (facebook/sam3) por prompts clĂnicos (ducto, guia, patologia, endoscĂłpio) para focar o ROI e atenuar o viĂ©s do instrumento (o modelo aprende o endoscĂłpio em vez da anatomia). A mĂĄscara entra como 4Âș canal (RGB + mĂĄscara soft); a 1ÂȘ conv Ă© adaptada preservando os pesos prĂ©-treinados (canal extra inicializado pela mĂ©dia RGB).
scripts/generate_sam3_masks.py (mask.png combinada; --multi gera mask_{duct,wire,pathology,device}.png) â database/masks/src/data/dataset_sam3.py (CPRESAMM3Dataset, transforms sincronizados imagem+mĂĄscara), src/models/builder_sam3.py, entrypoints train_sam3.py / evaluate_sam3.pyconfigs/resnet50_sam3*.yamlSoft-voting de vĂĄrias ResNet50+SAM3 4-canais (melhor membro: resnet50_sam3_4ch_opt, peso â 0.46). RelatĂłrio: notebooks/ensemble.ipynb.
| Modelo / Ensemble | F1-macro (test) | Viés do instrumento |
|---|---|---|
| Baseline EfficientNet-B7 | 73.81% | muito alto (shortcut no endoscĂłpio) |
| Ensemble 3 modelos (soft voting) | 75.18% | mĂnimo |
| Ensemble 4 modelos (soft voting) | 76.30% | eliminado |
Código em scripts/ensemble/ (composição, pesos, avaliação, stacking e geração do relatório + Grad-CAMs).
notebooks/pcr.ipynb separa por equipamento e junta no fim: Fase 1 especialista Ziehm Vision (nĂŁo-PCR), Fase 2 especialista PCR (PCR Eleva, sĂł ~36 imgs â test de 11 intocĂĄvel), Fase 3 sistema com routing por equipamento (PCR â especialista PCR, resto â Ziehm).
Dataset MIQR-CC â imagens fluoroscĂłpicas de CPRE, anotadas por classe diagnĂłstica.
Biliary_Leaks, Lithiasis, Normal, Stricture (Benign + Malignant Stricture agregadas â ver notebooks/domain.ipynb){1, 24, 42}. Test set sagrado: extraĂdo com TEST_SEED=42 fixo e idĂȘntico em todas as seeds + variantesprepare_dataset.py:
database/splits/monica/seed{N} â inclui PCR em train/val (replica o paper)database/splits/seed{N} â train/val sem PCR; test mantĂ©m PCR (sagrado)monica/seed42 reproduz exactamente o monica/main original (mesmo split que o paper)AP2/
âââ configs/ # ConfiguraçÔes YAML
â âââ *_monica.yaml # Monica baseline â 5 modelos (resnet50/densenet121/efficientnet_b7/mobilenetv2/deit3_small)
â âââ resnet50_sam3*.yaml # SAM3 4-canais (base / _reg / _frozen)
â âââ grid/ # ~288 configs do grid search (3 modelos Ă combos Ă seeds)
â âââ biomedclip_fewshot.yaml # VLM â BiomedCLIP few-shot
â âââ qwen2_5_vl_7b_fewshot.yaml # VLM â Qwen2.5-VL few-shot
â âââ vila_m3_8b_fewshot.yaml # VLM â VILA-M3 few-shot
â âââ cvae.yaml # CVAE â geração de imagens sintĂ©ticas
â
âââ scripts/
â âââ prepare_dataset.py # Splits por paciente, 2 variantes (--also_no_pcr_out)
â âââ multiseed.py / aggregate_seeds.py / audit_splits.py / compare_datasets.py
â âââ gridsearch.py # Grid search retomĂĄvel (preset MONAI)
â âââ generate_sam3_masks.py # MĂĄscaras SAM3 (combinada; --multi = por-categoria) â database/masks/
â âââ mirror_masks_to_splits.py # Espelha mĂĄscaras para a estrutura dos splits
â âââ train_cvae.py / generate_cvae.py / cvae_separability.py / cvae_classifier.py / cvae_grid*.py
â âââ run_vlm_fewshot.py / run_biomedclip_fewshot.py / aggregate_vlm_results.py
â âââ roi_experiment.py / concept_bottleneck.py / augment_experiment.py / metadata_only.py
â âââ ensemble/ # Ensemble SAM3 + relatĂłrio Grad-CAM (soft voting, save, eval, stacking)
â
âââ src/
â âââ data/
â â âââ dataset.py / transforms.py / loaders.py # pipeline cv2 + albumentations (default)
â â âââ monai_pipeline.py # pipeline MONAI fiel (preset=monica)
â â âââ dataset_sam3.py / loaders_sam3.py # CPRESAMM3Dataset 4-canais (RGB + mĂĄscara SAM3)
â âââ models/
â â âââ builder.py # backbones monai/torchvision/timm/SimpleCNN
â â âââ builder_sam3.py # variantes 4-canais (1ÂȘ conv adaptada)
â â âââ cvae.py # Conditional VAE-GAN
â âââ interpretability/gradcam.py # Grad-CAM (CNN + ViT via reshape_transform)
â âââ training/ # Trainer, losses (focal + monai_focal), mĂ©tricas, EMA
â âââ vlm/ # Wrappers BiomedCLIP, Qwen2.5-VL, VILA-M3
â âââ utils/ # Seed (com set_determinism MONAI), config
â
âââ notebooks/
â âââ domain.ipynb # Contexto clĂnico das 4 classes
â âââ exploration.ipynb # EDA + metadata-only + prĂ©-proc/augmentation visuais
â âââ baseline.ipynb # Replica Monica (5 modelos sequenciais) + figuras
â âââ augmentation.ipynb # Efeito da augmentation na ResNet50 (none/light/monica/heavy)
â âââ masks.ipynb # Impacto das mĂĄscaras SAM3 (4Âș canal) na ResNet50
â âââ age.ipynb # FusĂŁo da idade (late fusion) na ResNet50
â âââ pcr.ipynb # Especialistas (Ziehm/PCR) + sistema com routing
â âââ segmentation.ipynb # SAM3 â prompts, mĂĄscaras, tensor 4 canais
â âââ ensemble.ipynb # Ensemble SAM3 (soft-voting) + Grad-CAM
â âââ conclusion.ipynb # ConclusĂŁo geral (vs baseline MĂłnica + Grad-CAM)
â âââ figures/ # Figuras exportadas pelos notebooks
â
âââ monica/ # Notebooks originais da referĂȘncia â read-only (gitignored)
â
âââ database/ # NĂŁo versionado (gitignored)
â âââ MIQR-CC-Dataset/ # dataset original (metadata.csv + raw/ + processed/)
â âââ splits/ # monica/seed{N} (com PCR) + seed{N} (sem PCR), test sagrado
â âââ masks/ # mĂĄscaras SAM3 por split/classe/imagem
â
âââ outputs/ # NĂŁo versionado (gitignored)
â âââ baseline/ # replica Monica (5 modelos Ă 3 seeds)
â âââ ablation/ (pcr, clahe, metadata) · checkpoints/ · reports/ · gridsearch/ · ensemble/
â âââ audit.md · compare.md
â
âââ docs/enunciado.pdf
âââ train.py / evaluate.py # Entrypoints (CNN baseline)
âââ train_sam3.py / evaluate_sam3.py # Entrypoints (pipeline SAM3 4-canais)
âââ env.yaml
âââ README.md
\ para quebra de linha em vez de uma linha sĂł.git clone https://github.com/Luismpso/AP2.git
cd AP2
conda env create -f env.yaml
conda activate dl
CPU-only: trocar o URL no
env.yamlparahttps://download.pytorch.org/whl/cpu. MONAI Ă© dependĂȘncia obrigatĂłria (jĂĄ emenv.yaml).
Colocar o dataset em database/MIQR-CC-Dataset/ (deve conter metadata.csv + raw/ + processed/).
python scripts/prepare_dataset.py --dataset_dir database/MIQR-CC-Dataset --out_dir database/splits/monica --also_no_pcr_out database/splits --seeds 1 24 42 --no_exclude --overwrite
Resultado:
database/splits/monica/seed{1,24,42}/ â Monica-style (com PCR em train/val + test)database/splits/seed{1,24,42}/ â train/val sem PCR, test idĂȘntico (sagrado)python scripts/audit_splits.py --monica
Gera outputs/audit.md com tabelas: leakage por split, contagens por classe e equipamento, variabilidade do test set entre seeds (deve ser 100% â test sagrado).
python scripts/multiseed.py --configs configs/densenet121_monica.yaml configs/resnet50_monica.yaml configs/efficientnet_b7_monica.yaml configs/mobilenetv2_monica.yaml configs/deit3_small_monica.yaml --seeds 1 24 42 --splits_root database/splits/monica --out_dir outputs/baseline --evaluate_after
5 modelos Ă 3 seeds = 15 runs (treino + evaluate). Ă resumĂvel: se cair a meio, voltar a correr o mesmo comando salta os runs jĂĄ feitos. Resultados ficam em outputs/baseline/{checkpoints,reports,figures}/.
Para quantificar o impacto do equipamento PCR Eleva (presente no test sagrado mas removido aqui de train/val), corremos sĂł ResNet50 Ă 3 seeds:
python scripts/multiseed.py --configs configs/resnet50_monica.yaml --seeds 1 24 42 --splits_root database/splits --out_dir outputs/ablation/pcr --evaluate_after
3 runs (~30 min total). NĂŁo Ă© necessĂĄrio replicar todos os 5 modelos â basta um para a ablação ser informativa no relatĂłrio.
python scripts/aggregate_seeds.py --reports_dir outputs/baseline/reports outputs/ablation/pcr/reports --labels baseline ablation_pcr --out outputs/compare.md
Output: outputs/compare.md (tabela markdown com val + test, mean±std) e outputs/compare.md.csv. Compara os 5 modelos da replica com a ResNet50 sem PCR â mostra o "delta" do confounder.
python scripts/run_biomedclip_fewshot.py --config configs/biomedclip_fewshot.yaml
python scripts/run_vlm_fewshot.py --config configs/qwen2_5_vl_7b_fewshot.yaml
python train_cvae.py --config configs/cvae.yaml --data_root database/splits/seed1
python scripts/generate_cvae.py --checkpoint outputs/checkpoints/cvae_512/best.pt --n_per_class 200 --out_dir database/synthetic/cvae_512
python scripts/cvae_separability.py --checkpoint outputs/checkpoints/cvae_512/best.pt --data_root database/splits/seed1
# XGBoost (default)
python scripts/cvae_classifier.py --checkpoint outputs/checkpoints/cvae_512/best.pt --data_root database/splits/seed1 --seeds 42 1 24
# trocar para SVM / RF / MLP / Logistic
python scripts/cvae_classifier.py --checkpoint outputs/checkpoints/cvae_512/best.pt --data_root database/splits/seed1 --classifier svm
python scripts/cvae_classifier.py --checkpoint outputs/checkpoints/cvae_512/best.pt --data_root database/splits/seed1 --classifier random_forest
python scripts/cvae_classifier.py --checkpoint outputs/checkpoints/cvae_512/best.pt --data_root database/splits/seed1 --classifier mlp
# rĂĄpido (single conditioning + PCA)
python scripts/cvae_classifier.py --checkpoint outputs/checkpoints/cvae_512/best.pt --data_root database/splits/seed1 --feature_mode single --pca_dim 256
Para decidir se vale a pena fazer fusion CNN + metadata, treina um classificador clĂĄssico sĂł com idade + sexo e vĂȘ o F1 no test sagrado:
# XGBoost (default)
python scripts/metadata_only.py
# alternativa linear
python scripts/metadata_only.py --classifier logistic
# diagnĂłstico do confounder (adiciona equipment_model â nĂŁo reportar como resultado clĂnico)
python scripts/metadata_only.py --include_equipment
Interpretação:
Resumo guardado em outputs/ablation/metadata/summary.json.
Por seed: outputs/<dir>/reports/<run_name>/
metrics.json â campos legacy (test) + bloco splits: {val, test}confusion_matrix_val.png / confusion_matrix_test.pngpredictions_val.csv / predictions_test.csveval.log â termina com VAL | acc=... f1m=... / TEST | ...Agregado: outputs/compare.md + .csv
notebooks/figures/none/light/monica/heavy): F1, curvas, confusĂŁo, Grad-CAMnomask/combined/no_endoscope/separated)Dois entrypoints na raiz (classes â nomes de pasta exatos:
Biliary_Leaks, Lithiasis, Normal, Stricture):
| Script | Dados | SaĂda |
|---|---|---|
evaluate.py / evaluate_sam3.py | rotulados (<root>/test/<Classe>/*.png) | F1, accuracy, matriz, predictions.csv |
predict.py | imagens soltas (sem rótulos) | CSV de prediçÔes |
Os checkpoints estĂŁo em models/ (incluĂdos no zip â ver models/README.md).
densenet121 (rĂ©plica fiel da MĂłnica, ~0.62 F1) â sĂł precisa das imagens:
# rotulado (F1/matriz):
python evaluate.py --config configs/densenet121_monica.yaml \
--checkpoint models/densenet121_monica_seed42.pt --data_root <DADOS> --skip_val
# imagens soltas (só prediçÔes):
python predict.py --config configs/densenet121_monica.yaml \
--checkpoint models/densenet121_monica_seed42.pt --images_dir <PASTA> --out predicoes.csv
4 modelos em soft-voting (reportado 76.30%; reproduzido localmente 75.23%). Precisa
de GPU + SAM3 (facebook/sam3) para gerar as mĂĄscaras das imagens novas:
# 1. mĂĄscaras por-categoria dos dados novos
python scripts/generate_sam3_masks.py --multi --data_root <DADOS> --out_root <MASCARAS>
# 2. avaliar os 4 membros (--tta no baseline 3ch; evaluate_sam3 jĂĄ tem TTA on)
python evaluate_sam3.py --config configs/resnet50_sam3_4ch_opt.yaml --checkpoint models/resnet50_sam3_4ch_opt.pt --data_root <DADOS> --masks_root <MASCARAS>
python evaluate_sam3.py --config configs/resnet50_sam3_4ch_opt_v3.yaml --checkpoint models/resnet50_sam3_4ch_opt_v3.pt --data_root <DADOS> --masks_root <MASCARAS>
python evaluate_sam3.py --config configs/resnet50_radimagenet_4ch.yaml --checkpoint models/multitask_resnet50_imagenet_4ch.pt --data_root <DADOS> --masks_root <MASCARAS>
python evaluate.py --tta --config configs/resnet50_baseline_opt.yaml --checkpoint models/resnet50_baseline_opt.pt --data_root <DADOS> --skip_val
# 3. combinar (pesos 4-M: V1 .458 / V3 .241 / Multitask .194 / Baseline .107 â ver notebooks/ensemble.ipynb;
# scripts/ensemble/ensemble_save.py faz a versĂŁo 3-M)
Reproduzir o nosso test (seed42):
database/masks_multi/seed42segue no zip â corre o passo 2 sem--data_root/--masks_root(usa os defaults dos configs). Sem GPU/SAM3, usa o Caminho A (corre sĂł com imagens).
| Nome | NÂș | |
|---|---|---|
| LuĂs Miguel Pereira Silva | PG60390 | pg60390@alunos.uminho.pt |
| Pedro Miguel S. A. Urbano dos Reis | PG59908 | pg59908@alunos.uminho.pt |
| Guilherme Lobo Pinto | PG60225 | pg60225@alunos.uminho.pt |
| Pedro Alexandre Silva Gomes | PG60289 | pg60289@alunos.uminho.pt |
Este trabalho é de cariz estritamente académico. Universidade do Minho, Escola de Engenharia, Departamento de Informåtica.
Jupyter Notebook
98.7%
Python
1.3%