3636 transform_to_acpc_standard ,
3737)
3838from CorpusCallosum .shape .thickness import cc_thickness
39- from CorpusCallosum .utils .types import (
40- CCMeasuresDict ,
41- ContourThickness ,
42- Points2dType ,
43- Polygon2dType ,
44- SliceSelection ,
45- SubdivisionMethod ,
46- )
39+ from CorpusCallosum .utils .types import CCMeasuresDict , ContourThickness , Points2dType , SliceSelection , SubdivisionMethod
4740from CorpusCallosum .utils .visualization import plot_contours
4841from FastSurferCNN .utils import AffineMatrix4x4 , Image3d , Mask2d , Shape2d , Shape3d , Vector2d
4942from FastSurferCNN .utils .common import SubjectDirectory , update_docstring
@@ -100,7 +93,6 @@ def recon_cc_surf_measures_multi(
10093 subdivision_method : SubdivisionMethod ,
10194 contour_smoothing : int ,
10295 subject_dir : SubjectDirectory ,
103- vox_size : tuple [float , float , float ],
10496) -> tuple [list [CCMeasuresDict ], list [concurrent .futures .Future ]]:
10597 """Surface reconstruction and metrics computation of corpus callosum slices based on selection mode.
10698
@@ -134,8 +126,6 @@ def recon_cc_surf_measures_multi(
134126 Gaussian sigma for contour smoothing.
135127 subject_dir : SubjectDirectory
136128 The SubjectDirectory object managing file names in the subject directory.
137- vox_size : 3-tuple of floats
138- LIA-oriented voxel size in millimeters (x, y, z).
139129
140130 Returns
141131 -------
@@ -162,7 +152,6 @@ def recon_cc_surf_measures_multi(
162152 subdivisions = subdivisions ,
163153 subdivision_method = subdivision_method ,
164154 contour_smoothing = contour_smoothing ,
165- vox_size = vox_size ,
166155 )
167156
168157 # Process multiple slices or specific slice
@@ -224,14 +213,16 @@ def _gen_slice2slab_vox2vox(_slice_idx: int) -> AffineMatrix4x4:
224213 io_futures .append (
225214 run (
226215 plot_contours ,
227- transformed = midslices [current_slice_in_volume :current_slice_in_volume + 1 ],
216+ # select the data of the current slice
217+ slice_or_slab = midslices [[current_slice_in_volume ]],
218+ # the following need to be in voxel coordinates...
228219 split_contours = cc_measures ["split_contours" ],
229220 midline_equidistant = cc_measures ["midline_equidistant" ],
230221 levelpaths = cc_measures ["levelpaths" ],
231222 output_path = qc_imgs ,
232- ac_coords = ac_coords_vox ,
233- pc_coords = pc_coords_vox ,
234- vox_size = vox_size ,
223+ ac_coords_vox = ac_coords_vox ,
224+ pc_coords_vox = pc_coords_vox ,
225+ vox2ras = this_slice_vox2ras ,
235226 title = f"CC Subsegmentation by { subdivision_method } (Slice { slice_idx + 1 } )" ,
236227 )
237228 )
@@ -244,8 +235,6 @@ def _gen_slice2slab_vox2vox(_slice_idx: int) -> AffineMatrix4x4:
244235 f"thickness_measurement_points.txt) to { template_dir } " )
245236 run = run
246237 for j in range (len (cc_contours )):
247- # FIXME: check, if this is fixed (thickness values not nan == 200)
248- # this does not seem to be thread-safe, do not parallelize!
249238 io_futures .append (run (cc_contours [j ].save_contour , template_dir / f"contour_{ j } .txt" ))
250239 io_futures .append (run (cc_contours [j ].save_thickness_values , template_dir / f"thickness_values_{ j } .txt" ))
251240
@@ -267,17 +256,14 @@ def _gen_slice2slab_vox2vox(_slice_idx: int) -> AffineMatrix4x4:
267256 logger .info (f"Saving overlay file to { overlay_file_path } " )
268257 io_futures .append (run (cc_mesh .write_morph_data , overlay_file_path ))
269258
270- if any (wants_output (f"cc_{ n } " ) for n in ("thickness_image" , "cc_surf " )):
259+ if any (wants_output (f"cc_{ n } " ) for n in ("thickness_image" , "surf " )):
271260 import nibabel as nib
272261 up_data : Image3d [np .uint8 ] = np .empty (upright_header ["dims" ][:3 ], dtype = upright_header .get_data_dtype ())
273262 upright_img = nib .MGHImage (up_data , fsavg_vox2ras , upright_header )
274263 # the mesh is generated in upright coordinates, so we need to also transform to orig coordinates
275- # FIXME: this is currently not in RAS coordinates!
276-
277264 # Mesh is fsavg_midplane (RAS); we need to transform to voxel coordinates
278265 # fsavg ras is also on the midslice, so this is fine and we multiply in the IA and SP offsets
279266 cc_mesh = cc_mesh .to_vox_coordinates (mesh_ras2vox = np .linalg .inv (fsavg_vox2ras @ orig2fsavg_vox2vox ))
280- #FIXME: to_fs_coordinate needs to transform from upright to
281267 if wants_output ("cc_thickness_image" ):
282268 # this will also write overlay and surface
283269 thickness_image_path = output_path ("cc_thickness_image" )
@@ -318,7 +304,6 @@ def recon_cc_surf_measure(
318304 subdivisions : list [float ],
319305 subdivision_method : SubdivisionMethod ,
320306 contour_smoothing : int ,
321- vox_size : tuple [float , float , float ],
322307) -> tuple [CCMeasuresDict , ContourThickness , tuple [int , int ]]:
323308 """Reconstruct surfaces and compute measures for a single slice for the corpus callosum.
324309
@@ -342,8 +327,6 @@ def recon_cc_surf_measure(
342327 Method for contour subdivision ('shape', 'vertical', 'angular', or 'eigenvector').
343328 contour_smoothing : int
344329 Gaussian sigma for contour smoothing.
345- vox_size : triplet of floats
346- LIA-oriented voxel size in millimeters.
347330
348331 Returns
349332 -------
@@ -478,7 +461,7 @@ def recon_cc_surf_measure(
478461
479462
480463def test_right_of_line (
481- coords : Polygon2dType ,
464+ coords : Points2dType ,
482465 line_start : Vector2d ,
483466 line_end : Vector2d ,
484467) -> np .ndarray [tuple [int ], np .dtype [np .bool_ ]]:
@@ -487,27 +470,28 @@ def test_right_of_line(
487470 Parameters
488471 ----------
489472 coords : np.ndarray
490- Array of coordinates of shape (2 , N).
473+ Array of coordinates of shape (... , N).
491474 line_start : array-like
492- [x, y] coordinates of line start point.
475+ [x, y] coordinates of line start point (N,) .
493476 line_end : array-like
494- [x, y] coordinates of line end point.
477+ [x, y] coordinates of line end point (N,) .
495478
496479 Returns
497480 -------
498481 np.ndarray
499- Boolean array where True means point is to the left of the line.
482+ Boolean array where True means point is to the left of the line of shape coords.shape[:-1] .
500483 """
501484 # Vector from line_start to line_end
502- line_vec = np .array (line_end ) - np .array (line_start )
485+ line_start_arr = np .expand_dims (line_start , axis = np .arange (line_start .ndim , coords .ndim ).tolist ())
486+ line_vec = np .expand_dims (line_end , axis = np .arange (line_end .ndim , coords .ndim ).tolist ()) - line_start_arr
503487
504488 # Vectors from line_start to all points (vectorized)
505- point_vec = coords - np .expand_dims ( line_start , axis = list ( range ( 1 , coords . ndim )))
489+ point_vec = np .moveaxis ( coords , - 1 , 0 ) - line_start_arr
506490
507491 # Cross product (vectorized): positive means point is to the left of the line
508492 cross_products = line_vec [0 ] * point_vec [1 ] - line_vec [1 ] * point_vec [0 ]
509493
510- return cross_products > 0
494+ return np . greater ( cross_products , 0 )
511495
512496
513497def get_unique_contour_points (split_contours : ContourList ) -> list [Points2dType ]:
@@ -535,41 +519,24 @@ def get_unique_contour_points(split_contours: ContourList) -> list[Points2dType]
535519 3. Collects points unique to each subsegment.
536520 """
537521 # For each contour point, check if it appears in other contours
538- unique_contour_points : list [Points2dType ] = []
539-
540- for i , contour in enumerate (split_contours ):
541- # Get points for this contour
542- contour_points : Points2dType = np .vstack ((contour [0 ], - contour [1 ])).T # Shape: (N,2)
543-
544- # Check each point against all other contours
545- unique_points = []
546- for point in contour_points :
547- is_unique = True
548-
549- # Compare against other contours
550- for j , other_contour in enumerate (split_contours ):
551- if i == j :
552- continue
553-
554- other_points = np .vstack ((other_contour [0 ], - other_contour [1 ])).T
555-
556- # Check if point exists in other contour (with small tolerance)
557- if np .any (np .all (np .abs (other_points - point ) < 1e-6 , axis = 1 )):
558- is_unique = False
559- break
560-
561- if is_unique :
562- unique_points .append (point )
563-
564- unique_contour_points .append (np .array (unique_points ))
522+ # initialize with values for first_contour, which are by definition just "the contour" (empty)
523+ unique_contour_points : list [Points2dType ] = [np .zeros ((0 , 2 ))]
524+ first_contour = split_contours [0 ]
525+ # Check each point against all other contours
526+ for contour in split_contours [1 :]:
527+ # 0: coord-axis, 1: contour-axis, 2: first_contour_axis
528+ contour_comparison = np .isclose (first_contour [:, None ], contour [:, :, None ], atol = 1e-6 )
529+ # mask of contour points, that are also in first_contour (axis 1 after all)
530+ contour_points_in_first_contour_mask = np .any (np .all (contour_comparison , axis = 0 ), axis = 1 )
531+ unique_contour_points .append (contour [:, ~ contour_points_in_first_contour_mask ].T )
565532
566533 return unique_contour_points
567534
568535
569536def make_subdivision_mask (
570537 slice_shape : Shape2d ,
571538 split_contours : ContourList ,
572- vox_size : tuple [ float , float ] ,
539+ vox2ras : AffineMatrix4x4 ,
573540) -> np .ndarray [Shape2d , np .dtype [np .int_ ]]:
574541 """Create a mask for subdividing the corpus callosum based on split contours.
575542
@@ -580,8 +547,8 @@ def make_subdivision_mask(
580547 split_contours : ContourList
581548 List of contours defining the subdivisions.
582549 Each contour is a tuple of x and y coordinates.
583- vox_size : pair of floats
584- The voxel sizes of the image grid in AS orientation .
550+ vox2ras : AffineMatrix4x4
551+ The vox2ras transformation matrix for the requested shape .
585552
586553 Returns
587554 -------
@@ -599,6 +566,7 @@ def make_subdivision_mask(
599566 - Tests which points lie to the right of the line.
600567 - Updates labels for those points.
601568 """
569+ from nibabel .affines import apply_affine
602570
603571 # unique_contour_points are the points where sub-division lines were inserted
604572 unique_contour_points : list [Points2dType ] = get_unique_contour_points (split_contours ) # shape (N, 2)
@@ -610,29 +578,27 @@ def make_subdivision_mask(
610578
611579 # Create coordinate grids for all points in the slice
612580 rows , cols = slice_shape
613- coords = np .array (np .mgrid [0 :rows , 0 :cols ])[[1 , 0 ]]
581+ coords_vox = np .stack (np .mgrid [0 :1 , 0 :rows , 0 :cols ], axis = - 1 )
582+ coords_ras = apply_affine (vox2ras , coords_vox )
583+
584+ cc_labels_posterior_to_anterior = SUBSEGMENT_LABELS
614585
615- cc_subsegment_lut_anterior_to_posterior = SUBSEGMENT_LABELS .copy ()
616- cc_subsegment_lut_anterior_to_posterior .reverse ()
617-
618586 # Initialize with first segment label
619- subdivision_mask = np .full (slice_shape , cc_subsegment_lut_anterior_to_posterior [0 ], dtype = np .int32 )
620-
587+ subdivision_mask = np .full (slice_shape , cc_labels_posterior_to_anterior [0 ], dtype = np .int32 )
588+
621589 # Process each subdivision line, subdivision_segments has for each division line the two points that are on the
622590 # contour and divide the subsegments
623- for segment_idx , segment_points in enumerate ( subdivision_segments ):
591+ for label , segment_points in zip ( cc_labels_posterior_to_anterior [ 1 :], reversed ( subdivision_segments ), strict = True ):
624592 # line_start and line_end are the intersection points of the CC subsegmentation boundary and the contour line
593+ line_start , line_end = segment_points
594+
625595 # --> find all voxels posterior to the line in question
626- line_start : Vector2d = segment_points [0 ] / vox_size
627- line_end : Vector2d = segment_points [- 1 ] / vox_size
628-
629596 # Vectorized test: find all points to the right of line (line_start->line_end)
630597 # right_of_line == posterior to line
631- points_right_of_line = test_right_of_line (coords , line_start , line_end )
598+ points_right_of_line = test_right_of_line (coords_ras [ 0 , ..., 1 :] , line_start , line_end )
632599
633600 # All points to the right of this line belong to the next segment or beyond
634- subdivision_mask [points_right_of_line ] = cc_subsegment_lut_anterior_to_posterior [segment_idx + 1 ]
635-
601+ subdivision_mask [points_right_of_line ] = label
636602 return subdivision_mask
637603
638604
0 commit comments