Source code for pyRadPlan.io._patient_loader

from typing import Optional
import os
import warnings
import logging
import sys

if sys.version_info < (3, 10):
    import importlib_resources as resources  # Backport for older versions
else:
    from importlib import resources  # Standard from Python 3.9+

from pyRadPlan.ct import validate_ct, CT
from pyRadPlan.cst import validate_cst, StructureSet
from pyRadPlan.stf import validate_stf
from pyRadPlan.plan import validate_pln
from pyRadPlan.dij import validate_dij
from . import matfile

logger = logging.getLogger(__name__)


[docs] def load_tg119() -> tuple[CT, StructureSet]: """ Load the included TG119 phantom. This is a helper function to load the TG119 phantom included in the package data using importlib.resources. Returns ------- tuple[CT, StructureSet] The CT and StructureSet objects. """ phantom_data_str = resources.files("pyRadPlan.data.phantoms").joinpath("TG119.mat") return load_patient(phantom_data_str)
[docs] def load_patient( filename: os.PathLike, extra_plan_data: Optional[dict] = None, extra_data: Optional[dict] = None, ) -> tuple[CT, StructureSet]: """ Load a patient from a file. Chooses loader from the extension. Parameters ---------- filename : os.PathLike Path to the file. additional_plan_data : Optional[dict] Additional data structures known to pyRadPlan that were present in the mat file """ # Sanitize path and check if file exists path = os.path.normpath(filename) if not os.path.exists(path): raise FileNotFoundError(f"Patient file not found: {path}") # Get the file extension ext = os.path.splitext(path)[1].lower() # Load the patient if ext == ".mat": mdict = matfile.load(path) patient_dict = validate_matrad_patient(mdict) else: raise ValueError(f"Unsupported file extension: {ext}") # we require ct ct = patient_dict.pop("ct", None) if ct is None: raise ValueError("ct is missing from the patient file.") # we issue a warning when getting the cst cst = patient_dict.pop("cst", None) if cst is None: warnings.warn("cst/StructureSet is missing from the patient file.") extra_plan_data.update(patient_dict) if isinstance(extra_plan_data, dict) else None extra_data.update(mdict) if isinstance(extra_data, dict) else None return ct, cst
[docs] def validate_matrad_patient(mdict: dict[str], remove_matrad_structures: bool = True) -> dict[str]: """ Load a matRad-like patient from a mat file. Assumes that the file uses matRad's data structures and tries to validates them. Parameters ---------- mdict : dict[str] Dictionary imported from a .mat file. Modified if remove_matrad_structures is True. remove_matrad_structures : bool Pop the input data recognized/named as matRad structures from the dictionary. Returns ------- dict[str] A dictionary with the validated data. """ patient_dict = {} ct = mdict.pop("ct", None) cst = mdict.pop("cst", None) if ct is not None: patient_dict["ct"] = validate_ct(ct) if cst is not None: patient_dict["cst"] = validate_cst(cst, patient_dict["ct"]) for key, validator in [("pln", validate_pln), ("stf", validate_stf), ("dij", validate_dij)]: value = mdict.get(key, None) if value is not None: try: patient_dict[key] = validator(value) if remove_matrad_structures: mdict.pop(key) except ValueError: logger.warning(f"{key} present but could not be validated.") result = mdict.get("resultGUI", None) if result is not None: # TODO: validation as soon as result structure is implemented patient_dict["result"] = result if remove_matrad_structures: mdict.pop("resultGUI") return patient_dict