Source code for pyRadPlan.machines.photons._svd_kernel

import warnings
from typing import Any
from typing_extensions import Self
import numpy as np
from pydantic import (
    Field,
    computed_field,
    field_validator,
    model_validator,
    ValidatorFunctionWrapHandler,
    ValidationInfo,
    ValidationError,
)
from numpydantic import NDArray, Shape
from pyRadPlan.core import PyRadPlanBaseModel


[docs] class PhotonSVDKernel(PyRadPlanBaseModel): """Kernel data for photon beams. Attributes ---------- energy : float The maximum energy of the photon beam in MeV (corresponds to the LINAC voltage in MV). """ # some basic kernel information energy: float = Field(ge=0.0, description="The energy of the photon beam in MeV.") m: float = Field(description="attuneation factor") penumbra: float = Field( default=5.0, description="The penumbra of an open field as FWHM at Isocenter", alias="penumbarFWHMatIso", ) # Tabulated kernel data kernel_betas: NDArray[Shape["1-*"], np.float64] = Field( description="weighting factors for kernel components", alias="betas" ) kernel_ssds: NDArray[Shape["1-*"], np.float64] = Field(description="Kernel SSD points") kernel_pos: NDArray[Shape["1-*"], np.float64] = Field(description="Kernel position") kernel_data: NDArray[Shape["1-*,1-*, 1-*"], np.float64] = Field( description="Spatial grid of kernels" ) primary_fluence: NDArray[Shape["1-*,2"], np.float64] = Field(description="Primary fluence") @computed_field(return_type=int) @property def num_kernel_components(self): return self.kernel_data.shape[1]
[docs] def get_kernels_at_ssd(self, ssd: float) -> NDArray[Shape["1-*, 1-*"], np.float64]: """Get the kernel components at a specific SSD value. Parameters ---------- ssd : float The SSD value to search for. Returns ------- NDArray[Shape["1-*, 1-*"], np.float64] The kernels at the specified SSD value. """ # For performance reasons we slice kernel_data at the closest SSD value if ssd < self.kernel_ssds.min() or ssd > self.kernel_ssds.max(): warnings.warn(f"SSD value {ssd} is not in the kernel SSD range.") ix = np.argmin(np.abs(self.kernel_ssds - ssd)) return self.kernel_data[ix]
[docs] def kernel_interpolator(self): pass
[docs] @field_validator("kernel_betas", "kernel_ssds", "kernel_pos", "primary_fluence", mode="wrap") @classmethod def validate_kernel_data_shapes( cls, v: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo ) -> NDArray: try: return handler(v, info) except ValidationError: try: v = np.asarray(v) return handler(v, info) except Exception as exc: raise ValueError("Input not an array") from exc
[docs] @model_validator(mode="after") def validate_kernel_data(self) -> Self: if self.kernel_data.shape[1] != self.kernel_betas.shape[0]: raise ValueError("Kernel data and kernel betas do not match in length") if self.kernel_data.shape[0] != self.kernel_ssds.shape[0]: raise ValueError("Kernel data and kernel ssds do not match in length") if self.kernel_data.shape[2] != self.kernel_pos.shape[0]: raise ValueError("Kernel data and kernel positions do not match in length") return self