100 lines
3.3 KiB
Python
Executable File
100 lines
3.3 KiB
Python
Executable File
# papila_builders.py
|
|
from typing import List, Dict
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
from classes import ClinicalData # adjust import path if needed
|
|
|
|
# ---- Pachymetry → IOP correction (per PAPILA Table 3) ----
|
|
_PACHY_TABLE: Dict[int, int] = {
|
|
475:+5, 485:+4, 495:+4, 505:+3, 515:+2, 525:+1, 535:+1,
|
|
545: 0, 555:-1, 565:-1, 575:-2, 585:-3, 595:-4, 605:-4, 615:-5,
|
|
}
|
|
_PACHY_KEYS = np.array(sorted(_PACHY_TABLE.keys()))
|
|
|
|
def _nearest_pachy_key(x: float) -> int:
|
|
idx = int(np.argmin(np.abs(_PACHY_KEYS - float(x))))
|
|
return int(_PACHY_KEYS[idx])
|
|
|
|
def _pick_iop(row: pd.Series) -> float:
|
|
"""Prefer Pneumatic, else Perkins; may return NaN."""
|
|
raw = row["Pneumatic"] if not pd.isna(row.get("Pneumatic", np.nan)) else row.get("Perkins", np.nan)
|
|
return float(raw) if not pd.isna(raw) else np.nan
|
|
|
|
def _correct_iop(raw_iop: float, pachy: float) -> float:
|
|
"""Return corrected IOP using nearest pachymetry bin; if pachy missing, return raw."""
|
|
if pd.isna(raw_iop):
|
|
return np.nan
|
|
if pd.isna(pachy):
|
|
return float(raw_iop)
|
|
key = _nearest_pachy_key(float(pachy))
|
|
return float(raw_iop) + float(_PACHY_TABLE[key])
|
|
|
|
def _apply_iop_and_drop_md(df: pd.DataFrame) -> pd.DataFrame:
|
|
"""Add IOP_raw/IOP_corr and drop VF_MD if present (in-place safe)."""
|
|
# IOP_raw
|
|
df["IOP_raw"] = df.apply(_pick_iop, axis=1)
|
|
|
|
# IOP_corr
|
|
pachy = df.get("Pachymetry", pd.Series(np.nan, index=df.index))
|
|
df["IOP_corr"] = [
|
|
_correct_iop(r, p) for r, p in zip(df["IOP_raw"].values, pachy.values)
|
|
]
|
|
|
|
# Drop VF_MD if present
|
|
if "VF_MD" in df.columns:
|
|
df.drop(columns=["VF_MD"], inplace=True)
|
|
return df
|
|
|
|
|
|
def build_papila_clinical(
|
|
image_dir: str,
|
|
clinical_dir: str,
|
|
label_col: str,
|
|
cat_cols: List[str],
|
|
n_splits: int = 5,
|
|
random_seed: int = 42,
|
|
) -> ClinicalData:
|
|
"""
|
|
Build ClinicalData exactly like the user's original build_clinical:
|
|
- add_df(OD), set eyeID='OD'
|
|
- add_df(OS), set eyeID='OS'
|
|
- normalize 'Patient ID' on frames
|
|
THEN:
|
|
- compute IOP_raw / IOP_corr on each frame
|
|
- drop VF_MD
|
|
- refresh master df + kfold indices
|
|
"""
|
|
clinical = ClinicalData(
|
|
image_dir=image_dir,
|
|
clinical_dir=clinical_dir,
|
|
label_col=label_col,
|
|
cat_cols=cat_cols,
|
|
n_splits=n_splits,
|
|
random_seed=random_seed,
|
|
)
|
|
|
|
# --- Load exactly like original build_clinical ---
|
|
clinical.add_df(pd.read_excel(f"{clinical_dir}/patient_data_od.xlsx", header=1), id_column="ID")
|
|
clinical.frames[0]["eyeID"] = "OD"
|
|
|
|
clinical.add_df(pd.read_excel(f"{clinical_dir}/patient_data_os.xlsx", header=1), id_column="ID")
|
|
clinical.frames[1]["eyeID"] = "OS"
|
|
|
|
# Normalize 'Patient ID' on the per-eye frames (string → int)
|
|
for frame in clinical.frames:
|
|
frame["Patient ID"] = frame["Patient ID"].astype(str).str.extract(r"(\d+)")[0].astype(int)
|
|
|
|
# Build initial master as in original
|
|
clinical._refresh_master_df()
|
|
|
|
# --- Post-processing ON THE FRAMES (so everything stays consistent) ---
|
|
for i in range(len(clinical.frames)):
|
|
clinical.frames[i] = _apply_iop_and_drop_md(clinical.frames[i])
|
|
|
|
# Refresh master again so IOP_raw/IOP_corr & MD removal propagate
|
|
clinical._refresh_master_df()
|
|
clinical._build_kfold_indices()
|
|
|
|
return clinical
|