@@ -136,6 +136,56 @@ def get_voxel_info(self, row_number):
136136 raise ValueError ('invalid row number {}' .format (row_number ))
137137 return row_dict
138138
139+ def get_voxel_coordinates (self , row_numbers = None ):
140+ """
141+ param row_numbers: rows of influence matrix for which voxel coordinates should be computed
142+ return: xyz_mm. Center Coordinates of voxel in x,y and z direction
143+ """
144+ if row_numbers is None :
145+ row_numbers = np .arange (0 , self .A .shape [0 ])
146+ row_numbers = np .array (row_numbers ) # Ensure input is a NumPy array
147+
148+ # Extract the dose voxel map (shape: Z, Y, X)
149+ dose_map = self .opt_voxels_dict ['ct_to_dose_voxel_map' ][0 ]
150+
151+ # Find all locations where dose_map matches any row_number
152+ mask = np .isin (dose_map , row_numbers )
153+ z_inds , y_inds , x_inds = np .where (mask ) # Get all matching coordinates
154+ matched_row_numbers = dose_map [mask ] # Get corresponding row numbers
155+
156+ # Sort indices by row number for efficient processing
157+ sort_idx = np .argsort (matched_row_numbers )
158+ matched_row_numbers = matched_row_numbers [sort_idx ]
159+ z_inds , y_inds , x_inds = z_inds [sort_idx ], y_inds [sort_idx ], x_inds [sort_idx ]
160+
161+ # Find unique row numbers and their positions in sorted array
162+ unique_rows , row_start_idx = np .unique (matched_row_numbers , return_index = True )
163+
164+ # Compute min/max indices for each row using grouped operations
165+ min_z = np .minimum .reduceat (z_inds , row_start_idx )
166+ max_z = np .maximum .reduceat (z_inds , row_start_idx )
167+ min_y = np .minimum .reduceat (y_inds , row_start_idx )
168+ max_y = np .maximum .reduceat (y_inds , row_start_idx )
169+ min_x = np .minimum .reduceat (x_inds , row_start_idx )
170+ max_x = np .maximum .reduceat (x_inds , row_start_idx )
171+
172+ # Compute center of patch in voxel coordinates
173+ center_z = (min_z + max_z ) / 2
174+ center_y = (min_y + max_y ) / 2
175+ center_x = (min_x + max_x ) / 2
176+
177+ # Convert to physical coordinates
178+ ct_res = self .opt_voxels_dict ['ct_voxel_resolution_xyz_mm' ]
179+ ct_orig = self .opt_voxels_dict ['ct_origin_xyz_mm' ]
180+
181+ xyz_mm = np .column_stack ([
182+ ct_orig [0 ] + center_x * ct_res [0 ],
183+ ct_orig [1 ] + center_y * ct_res [1 ],
184+ ct_orig [2 ] + center_z * ct_res [2 ]
185+ ]) # Shape (N, 3)
186+
187+ return xyz_mm # NumPy array containing [x, y, z] coordinates for each row_number
188+
139189 def get_beamlet_info (self , col_number : int ) -> dict :
140190 """
141191
0 commit comments