11"""Contains the dij class as a (collection of) influence matrices."""
22
3- from typing import Any , Union , Annotated , cast
3+ from typing import Any , Union , Annotated , Optional , cast
44from pydantic import (
55 Field ,
66 field_validator ,
@@ -41,15 +41,17 @@ class Dij(PyRadPlanBaseModel):
4141 ct_grid : Annotated [Grid , Field (default = None )]
4242
4343 physical_dose : Annotated [NDArray , Field (default = None )]
44- let_dose : Annotated [NDArray , Field (default = None )]
44+ let_dose : Annotated [NDArray , Field (default = None , alias = "mLETDose" )]
4545 alpha_dose : Annotated [NDArray , Field (default = None )]
4646 sqrt_beta_dose : Annotated [NDArray , Field (default = None )]
4747
4848 num_of_beams : Annotated [int , Field (default = None )]
4949
50- bixel_num : Annotated [np .ndarray , Field (default = None )]
51- ray_num : Annotated [np .ndarray , Field (default = None )]
52- beam_num : Annotated [np .ndarray , Field (default = None )]
50+ bixel_num : Annotated [NDArray , Field (default = None )]
51+ ray_num : Annotated [NDArray , Field (default = None )]
52+ beam_num : Annotated [NDArray , Field (default = None )]
53+
54+ rad_depth_cubes : Optional [list [NDArray ]] = Field (default = None )
5355
5456 @computed_field
5557 @property
@@ -109,11 +111,30 @@ def validate_matrices(cls, v: Any, info: ValidationInfo) -> np.ndarray:
109111 if mat .shape [0 ] != info .data ["dose_grid" ].num_voxels :
110112 raise ValueError (f"{ info .field_name } shape inconsistent with ct grid" )
111113
114+ if info .context and "from_matRad" in info .context and info .context ["from_matRad" ]:
115+ if v is not None :
116+ for i in range (v .size ):
117+ shape = (
118+ int (info .data ["dose_grid" ].dimensions [2 ]),
119+ int (info .data ["dose_grid" ].dimensions [0 ]),
120+ int (info .data ["dose_grid" ].dimensions [1 ]),
121+ )
122+ v .flat [i ] = swap_orientation_sparse_matrix (
123+ v .flat [i ],
124+ shape ,
125+ (1 , 2 ), # (65, 100, 100) example
126+ )
127+ if v .flat [i ] is not None and not isinstance (v .flat [i ], sp .csc_matrix ):
128+ v .flat [i ] = sp .csc_matrix (v .flat [i ])
129+ else :
130+ v = np .array ([0 ])
131+ return [[v ]] # TODO
132+
112133 return v
113134
114135 @field_validator ("dose_grid" , "ct_grid" , mode = "before" )
115136 @classmethod
116- def validate_grid (cls , grid : Union [Grid , dict ]) -> Union [Grid , dict ]:
137+ def validate_grid (cls , grid : Union [Grid , dict ], info : ValidationInfo ) -> Union [Grid , dict ]:
117138 """
118139 Validate grid dictionaries.
119140
@@ -123,7 +144,14 @@ def validate_grid(cls, grid: Union[Grid, dict]) -> Union[Grid, dict]:
123144 """
124145 # Check if it is a dictionary and then try to create a Grid object
125146 if isinstance (grid , dict ):
126- grid = Grid .model_validate (grid )
147+ if info .context and "from_matRad" in info .context and info .context ["from_matRad" ]:
148+ grid ["dimensions" ] = np .array (
149+ [grid ["dimensions" ][1 ], grid ["dimensions" ][0 ], grid ["dimensions" ][2 ]]
150+ )
151+ # TODO: might swap offset and resolution
152+ grid = Grid .model_validate (grid )
153+ else :
154+ grid = Grid .model_validate (grid )
127155 return grid
128156
129157 @field_validator ("beam_num" , mode = "before" )
@@ -184,7 +212,6 @@ def grid_serializer(
184212 context = info .context
185213 if context and context .get ("matRad" ) == "mat-file" :
186214 return value .to_matrad (context = context ["matRad" ])
187-
188215 return handler (value , info )
189216
190217 @field_serializer ("physical_dose" , "let_dose" , "alpha_dose" , "sqrt_beta_dose" )
@@ -204,6 +231,18 @@ def physical_dose_serializer(self, value: np.ndarray, info: SerializationInfo) -
204231 )
205232 if value .flat [i ] is not None and not isinstance (value .flat [i ], sp .csc_matrix ):
206233 value .flat [i ] = sp .csc_matrix (value .flat [i ])
234+ # return 0 if value is None. savemat() cant handle 'None'
235+ elif context and context .get ("matRad" ) == "mat-file" and value is None :
236+ value = np .array ([0 ])
237+ return value
238+
239+ @field_serializer ("rad_depth_cubes" )
240+ def rad_depth_cubes_serializer (self , value : np .ndarray , info : SerializationInfo ) -> np .ndarray :
241+ context = info .context
242+ if context and context .get ("matRad" ) == "mat-file" and value is not None :
243+ # TODO: it might be necessary to rotate the cube!
244+ return value
245+ # return 0 if value is None. savemat() cant handle 'None'
207246 elif context and context .get ("matRad" ) == "mat-file" and value is None :
208247 value = np .array ([0 ])
209248 return value
@@ -253,7 +292,7 @@ def get_result_arrays_from_intensity(
253292 if self .physical_dose is None :
254293 raise ValueError ("Physical dose must be calculated for dose-weighted let" )
255294
256- indices = out ["physical_dose" ] > 0.0
295+ indices = out ["physical_dose" ] > 0.05 * np . max ( out [ "physical_dose" ])
257296
258297 let_dose = self .let_dose .flat [scenario_index ] @ intensity
259298 out ["let" ] = np .zeros_like (let_dose )
0 commit comments