|
6 | 6 |
|
7 | 7 | from highdicom.content import PlanePositionSequence |
8 | 8 | from highdicom.enum import CoordinateSystemNames |
9 | | -from highdicom.spatial import map_pixel_into_coordinate_system |
| 9 | +from highdicom.spatial import ( |
| 10 | + map_pixel_into_coordinate_system, |
| 11 | + PixelToReferenceTransformer, |
| 12 | +) |
10 | 13 |
|
11 | 14 |
|
12 | 15 | def tile_pixel_matrix( |
@@ -203,30 +206,94 @@ def compute_plane_position_slide_per_frame( |
203 | 206 | float(pixel_measures.PixelSpacing[0]), |
204 | 207 | float(pixel_measures.PixelSpacing[1]), |
205 | 208 | ) |
206 | | - slice_thickness = getattr( |
207 | | - pixel_measures, |
208 | | - 'SliceThickness', |
209 | | - 1.0 |
210 | | - ) |
211 | | - spacing_between_slices = getattr( |
212 | | - pixel_measures, |
213 | | - 'SpacingBetweenSlices', |
214 | | - 1.0 |
| 209 | + spacing_between_slices = float( |
| 210 | + getattr( |
| 211 | + pixel_measures, |
| 212 | + 'SpacingBetweenSlices', |
| 213 | + 1.0 |
| 214 | + ) |
215 | 215 | ) |
| 216 | + x_offset = image_origin.XOffsetInSlideCoordinateSystem |
| 217 | + y_offset = image_origin.YOffsetInSlideCoordinateSystem |
| 218 | + |
| 219 | + transformer_lut = {} |
| 220 | + for slice_index in range(1, num_focal_planes + 1): |
| 221 | + # These checks are needed for mypy to determine the correct type |
| 222 | + z_offset = float(slice_index - 1) * spacing_between_slices |
| 223 | + transformer_lut[slice_index] = PixelToReferenceTransformer( |
| 224 | + image_position=(x_offset, y_offset, z_offset), |
| 225 | + image_orientation=image_orientation, |
| 226 | + pixel_spacing=pixel_spacing |
| 227 | + ) |
| 228 | + |
| 229 | + def _compute_plane_position_tiled_full_efficiently( |
| 230 | + row_index: int, |
| 231 | + column_index: int, |
| 232 | + rows: int, |
| 233 | + columns: int, |
| 234 | + transformer: PixelToReferenceTransformer |
| 235 | + ) -> PlanePositionSequence: |
| 236 | + """More efficient implementation of `compute_plane_position_tiled_full`. |
| 237 | +
|
| 238 | + Function re-uses an existing `transformer` instance instead of creating |
| 239 | + one for every function call. This can hurt performance if the number |
| 240 | + of frames in an image is large. |
| 241 | +
|
| 242 | + Parameters |
| 243 | + ---------- |
| 244 | + row_index: int |
| 245 | + One-based Row index value for a given frame (tile) along the column |
| 246 | + direction of the tiled Total Pixel Matrix, which is defined by |
| 247 | + the second triplet in `image_orientation` (values should be in the |
| 248 | + range [1, *n*], where *n* is the number of tiles per column) |
| 249 | + column_index: int |
| 250 | + One-based Column index value for a given frame (tile) along the row |
| 251 | + direction of the tiled Total Pixel Matrix, which is defined by |
| 252 | + the first triplet in `image_orientation` (values should be in the |
| 253 | + range [1, *n*], where *n* is the number of tiles per row) |
| 254 | + rows: int |
| 255 | + Number of rows per Frame (tile) |
| 256 | + columns: int |
| 257 | + Number of columns per Frame (tile) |
| 258 | + transformer: highdicom.spatial.PixelToReferenceTransformer |
| 259 | + Callable transformer instance to map pixel indices into reference |
| 260 | + slide coordinates |
| 261 | +
|
| 262 | + Returns |
| 263 | + ------- |
| 264 | + highdicom.PlanePositionSequence |
| 265 | + Positon of the plane in the slide coordinate system |
| 266 | +
|
| 267 | + """ |
| 268 | + row_offset_frame = ((row_index - 1) * rows) |
| 269 | + column_offset_frame = ((column_index - 1) * columns) |
| 270 | + |
| 271 | + # We should only be dealing with planar rotations. |
| 272 | + transformed_coordinates = transformer( |
| 273 | + np.array([(column_offset_frame, row_offset_frame)], dtype=int) |
| 274 | + ) |
| 275 | + x = transformed_coordinates[0, 0] |
| 276 | + y = transformed_coordinates[0, 1] |
| 277 | + z = transformed_coordinates[0, 2] |
| 278 | + |
| 279 | + return PlanePositionSequence( |
| 280 | + coordinate_system=CoordinateSystemNames.SLIDE, |
| 281 | + image_position=(x, y, z), |
| 282 | + # Position of plane (tile) in Total Pixel Matrix: |
| 283 | + # First tile has position (1, 1) |
| 284 | + pixel_matrix_position=( |
| 285 | + column_offset_frame + 1, |
| 286 | + row_offset_frame + 1, |
| 287 | + ) |
| 288 | + ) |
216 | 289 |
|
217 | 290 | return [ |
218 | | - compute_plane_position_tiled_full( |
| 291 | + _compute_plane_position_tiled_full_efficiently( |
219 | 292 | row_index=r, |
220 | 293 | column_index=c, |
221 | | - x_offset=image_origin.XOffsetInSlideCoordinateSystem, |
222 | | - y_offset=image_origin.YOffsetInSlideCoordinateSystem, |
223 | 294 | rows=dataset.Rows, |
224 | 295 | columns=dataset.Columns, |
225 | | - image_orientation=image_orientation, |
226 | | - pixel_spacing=pixel_spacing, |
227 | | - slice_thickness=slice_thickness, |
228 | | - spacing_between_slices=spacing_between_slices, |
229 | | - slice_index=s, |
| 296 | + transformer=transformer_lut[s], |
230 | 297 | ) |
231 | 298 | for _, s, r, c in itertools.product( |
232 | 299 | range(num_optical_paths), |
|
0 commit comments