512ebd13b2
- Introduced `protocol.py` for shared data models used in server/client communication, including request and response schemas for registration, job submission, and status updates. - Implemented `server.py` to manage a SQLite job queue and client registry, handling job polling, status updates, and job completion. - Created a cheat sheet for server usage, detailing commands for starting the server, submitting jobs, and monitoring clients. - Added several experiment configuration files for various training setups, including geometry vector injections and baseline ensembles.
145 lines
5.3 KiB
Python
145 lines
5.3 KiB
Python
"""Result dataclasses and serialisation helpers for V3 fold outputs."""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
|
|
import numpy as np
|
|
|
|
|
|
def _nan() -> float:
|
|
return float("nan")
|
|
|
|
|
|
def _f(v) -> Optional[float]:
|
|
"""Round a scalar to 6 dp, return None for nan/None."""
|
|
if v is None or (isinstance(v, float) and np.isnan(v)):
|
|
return None
|
|
return round(float(v), 6)
|
|
|
|
|
|
def _sv(vec) -> Optional[str]:
|
|
"""Serialise a vector to a pipe-separated string, or None."""
|
|
if vec is None:
|
|
return None
|
|
return "|".join(f"{float(v):.4f}" for v in vec)
|
|
|
|
|
|
@dataclass
|
|
class FoldResult:
|
|
mode: str
|
|
fold: int
|
|
# Epoch where each model hit its peak val AUC
|
|
best_epoch_single: int
|
|
best_epoch_bilat: int
|
|
# Classic (eye-level eval of SingleEyeHT)
|
|
classic_val_auc: float
|
|
classic_val_acc: float
|
|
classic_val_kappa: float
|
|
classic_val_mcc: float
|
|
classic_val_f1: float
|
|
classic_val_recall: Optional[str]
|
|
classic_val_ece: float
|
|
classic_val_threshold: float
|
|
classic_val_bias: Optional[str]
|
|
classic_val_n: int
|
|
# Ensemble (patient-level eval of SingleEyeHT)
|
|
ensemble_val_auc: float
|
|
ensemble_val_acc: float
|
|
ensemble_val_kappa: float
|
|
ensemble_val_mcc: float
|
|
ensemble_val_f1: float
|
|
ensemble_val_recall: Optional[str]
|
|
ensemble_val_ece: float
|
|
ensemble_val_threshold: float
|
|
ensemble_val_bias: Optional[str]
|
|
ensemble_val_n: int
|
|
# Bilateral (BilateralHT patient-level)
|
|
bilat_val_auc: float
|
|
bilat_val_acc: float
|
|
bilat_val_kappa: float
|
|
bilat_val_mcc: float
|
|
bilat_val_f1: float
|
|
bilat_val_recall: Optional[str]
|
|
bilat_val_ece: float
|
|
bilat_val_threshold: float
|
|
bilat_val_bias: Optional[str]
|
|
bilat_val_n: int
|
|
# Test metrics (evaluated once after training on final-epoch model)
|
|
ensemble_test_auc: float = float("nan")
|
|
ensemble_test_acc: float = float("nan")
|
|
ensemble_test_kappa: float = float("nan")
|
|
ensemble_test_f1: float = float("nan")
|
|
ensemble_test_ece: float = float("nan")
|
|
classic_test_auc: float = float("nan")
|
|
classic_test_acc: float = float("nan")
|
|
classic_test_kappa: float = float("nan")
|
|
classic_test_f1: float = float("nan")
|
|
classic_test_ece: float = float("nan")
|
|
bilat_test_auc: float = float("nan")
|
|
bilat_test_acc: float = float("nan")
|
|
bilat_test_kappa: float = float("nan")
|
|
bilat_test_f1: float = float("nan")
|
|
bilat_test_ece: float = float("nan")
|
|
test_n: int = 0
|
|
# Training sample counts
|
|
single_train_n: int = 0
|
|
bilat_train_n: int = 0
|
|
# Fused head (optional)
|
|
fused_val_auc: float = float("nan")
|
|
fused_val_acc: float = float("nan")
|
|
fused_val_kappa: float = float("nan")
|
|
fused_val_mcc: float = float("nan")
|
|
fused_val_f1: float = float("nan")
|
|
fused_val_recall: Optional[str] = None
|
|
fused_val_ece: float = float("nan")
|
|
fused_val_threshold: float = float("nan")
|
|
fused_val_bias: Optional[str] = None
|
|
fused_val_n: int = 0
|
|
fused_test_auc: float = float("nan")
|
|
fused_test_acc: float = float("nan")
|
|
fused_test_kappa: float = float("nan")
|
|
fused_test_f1: float = float("nan")
|
|
fused_test_ece: float = float("nan")
|
|
fused_test_n: int = 0
|
|
|
|
|
|
@dataclass
|
|
class FoldArtifacts:
|
|
# Val split
|
|
y_true_classic: Optional[np.ndarray]
|
|
probs_classic: Optional[np.ndarray]
|
|
y_true_ensemble: Optional[np.ndarray]
|
|
probs_ensemble: Optional[np.ndarray]
|
|
y_true_bilat: Optional[np.ndarray]
|
|
probs_bilat: Optional[np.ndarray]
|
|
y_true_fused: Optional[np.ndarray] = None
|
|
probs_fused: Optional[np.ndarray] = None
|
|
probs_ensemble_img: Optional[np.ndarray] = None
|
|
probs_ensemble_md: Optional[np.ndarray] = None
|
|
probs_classic_img: Optional[np.ndarray] = None
|
|
probs_classic_md: Optional[np.ndarray] = None
|
|
# per-eye (pre-averaged) for ensemble mode
|
|
y_true_ensemble_pereye: Optional[np.ndarray] = None
|
|
probs_ensemble_pereye: Optional[np.ndarray] = None
|
|
probs_ensemble_img_pereye: Optional[np.ndarray] = None
|
|
probs_ensemble_md_pereye: Optional[np.ndarray] = None
|
|
# raw logits — patient-level
|
|
logits_ensemble: Optional[np.ndarray] = None
|
|
logits_ensemble_img: Optional[np.ndarray] = None
|
|
logits_ensemble_md: Optional[np.ndarray] = None
|
|
logits_classic: Optional[np.ndarray] = None
|
|
logits_classic_img: Optional[np.ndarray] = None
|
|
logits_classic_md: Optional[np.ndarray] = None
|
|
# raw logits — per-eye
|
|
logits_ensemble_pereye: Optional[np.ndarray] = None
|
|
logits_ensemble_img_pereye: Optional[np.ndarray] = None
|
|
logits_ensemble_md_pereye: Optional[np.ndarray] = None
|
|
# Test split equivalents
|
|
y_true_test: Optional[np.ndarray] = None
|
|
probs_test: Optional[np.ndarray] = None
|
|
probs_test_img: Optional[np.ndarray] = None
|
|
probs_test_md: Optional[np.ndarray] = None
|
|
y_true_fused_test: Optional[np.ndarray] = None
|
|
probs_fused_test: Optional[np.ndarray] = None
|