Skip to content

Commit 3cf2420

Browse files
ClePoldkuegler
authored andcommitted
bugfixes for hires images, README updates, error handling
1 parent b8751a5 commit 3cf2420

File tree

6 files changed

+49
-28
lines changed

6 files changed

+49
-28
lines changed

CorpusCallosum/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ The pipeline produces the following outputs in the specified output directory:
136136
- `surf/callosum.thickness.w`: FreeSurfer overlay file containing thickness values
137137
- `surf/callosum_mesh.vtk`: VTK format mesh file for 3D visualization
138138

139-
### Template Files (when --save_template is used)
139+
**Template Files (when --save_template is used):**
140140

141141
- `contours.txt`: Corpus callosum contour coordinates for visualization
142142
- `thickness_values.txt`: Thickness measurements at each contour point

CorpusCallosum/cc_visualization.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def main(
128128
contour_idx=len(cc_mesh.contours) // 2, save_path=str(output_dir / "midslice_2d.png")
129129
)
130130

131-
cc_mesh.to_fs_coordinates()
131+
cc_mesh.to_fs_coordinates(vox_size=[resolution, resolution, resolution])
132132
cc_mesh.write_vtk(str(output_dir / "cc_mesh.vtk"))
133133
cc_mesh.write_fssurf(str(output_dir / "cc_mesh.fssurf"))
134134
cc_mesh.write_overlay(str(output_dir / "cc_mesh_overlay.curv"))

CorpusCallosum/fastsurfer_cc.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -532,8 +532,8 @@ def main(
532532
# Validate subdivision fractions
533533
for i in subdivisions:
534534
if i < 0 or i > 1:
535-
print("Error: Subdivision fractions must be between 0 and 1, but got: ", i)
536-
exit(1)
535+
logger.error(f"Error: Subdivision fractions must be between 0 and 1, but got: {i}")
536+
raise ValueError(f"Subdivision fractions must be between 0 and 1, but got: {i}")
537537

538538
#### setup variables
539539
IO_processes = []
@@ -551,9 +551,9 @@ def main(
551551
"center around the mid-sagittal plane)"
552552
)
553553

554-
if not is_conform(orig):
554+
if not is_conform(orig, vox_size='min', img_size=None):
555555
logger.error("Error: MRI is not conformed, please run conform.py or mri_convert to conform the image.")
556-
exit(1)
556+
raise ValueError("MRI is not conformed, please run conform.py or mri_convert to conform the image.")
557557

558558
# load models
559559
device = torch.device("cuda" if torch.cuda.is_available() and not cpu else "cpu")
@@ -651,7 +651,8 @@ def main(
651651
cc_html_path=cc_html_path,
652652
vtk_file_path=vtk_file_path,
653653
thickness_image_path=thickness_image_path,
654-
vox_size=orig.header.get_zooms()[0],
654+
vox_size=orig.header.get_zooms(),
655+
image_size=orig.shape,
655656
verbose=verbose,
656657
save_template=save_template,
657658
)

CorpusCallosum/segmentation/segmentation_postprocessing.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,11 @@ def get_cc_volume_contour(desired_width_mm: int, cc_contours: list[np.ndarray],
100100

101101
# Calculate cross-sectional areas for each contour
102102
areas = []
103+
103104
for contour in cc_contours:
105+
contour = contour.copy()
106+
assert voxel_size[1] == voxel_size[2], "volume must be isotropic"
107+
contour *= voxel_size[1]
104108
# Calculate area using the shoelace formula for polygon area
105109
if contour.shape[1] < 3:
106110
areas.append(0.0)
@@ -117,15 +121,25 @@ def get_cc_volume_contour(desired_width_mm: int, cc_contours: list[np.ndarray],
117121

118122
# Calculate spacing between slices (left-right direction)
119123
lr_spacing = voxel_size[0] # x-direction voxel size
124+
125+
measurement_points = np.arange(-voxel_size[0]*(areas.shape[0]//2),
126+
voxel_size[0]*((areas.shape[0]+1)//2), lr_spacing)
120127

121-
# interpolate areas at 0 and 5
122-
areas_interpolated = np.interp(x=[0, 5], xp=np.arange(lr_spacing/2, 5, lr_spacing), fp=areas)
128+
# interpolate areas at 0.25 and 5
129+
areas_interpolated = np.interp(x=[-2.5, 2.5],
130+
xp=measurement_points,
131+
fp=areas)
123132

124133

134+
# remove measurement points that are outside of the desired range
135+
# not sure if this can happen, but let's be safe
136+
outside_range = (measurement_points < -2.5) | (measurement_points > 2.5)
137+
measurement_points = [-2.5] + measurement_points[~outside_range].tolist() + [2.5]
138+
areas = [areas_interpolated[0]] + areas[~outside_range].tolist() + [areas_interpolated[1]]
139+
125140

126-
measurements = [0,0.5,1.5,2.5,3.5,4.5,5]
127-
# can also use cumulative trapezoidal rule
128-
return integrate.simpson([areas_interpolated[0]] + areas.tolist() + [areas_interpolated[1]], x=measurements)
141+
# can also use trapezoidal rule
142+
return integrate.simpson(areas, x=measurement_points)
129143

130144

131145
def get_largest_cc(seg_arr: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
@@ -192,7 +206,7 @@ def clean_cc_segmentation(seg_arr: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
192206

193207
if 250 not in unique_labels:
194208
clean_seg[seg_arr == 250] = 250
195-
mask [seg_arr == 250] = True
209+
mask[seg_arr == 250] = True
196210
if 192 not in unique_labels:
197211
clean_seg[seg_arr == 192] = 192
198212
mask[seg_arr == 192] = True

CorpusCallosum/shape/cc_mesh.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,14 +1317,19 @@ def load_thickness_values(self, input_path: str, original_thickness_vertices_pat
13171317
try:
13181318
new_thickness_values[slice_idx][vertex_indices] = loaded_thickness_values[slice_idx][
13191319
~np.isnan(loaded_thickness_values[slice_idx])]
1320-
except IndexError:
1321-
print(
1320+
except IndexError as err:
1321+
logger.error(
13221322
f"Tried to load "
13231323
f"{loaded_thickness_values[slice_idx][~np.isnan(loaded_thickness_values[slice_idx])]} "
13241324
f"values, but template has {new_thickness_values[slice_idx][vertex_indices]} values, "
13251325
"supply a correct template to visualize the thickness values"
13261326
)
1327-
sys.exit(1)
1327+
raise ValueError(
1328+
f"Tried to load "
1329+
f"{loaded_thickness_values[slice_idx][~np.isnan(loaded_thickness_values[slice_idx])]} "
1330+
f"values, but template has {new_thickness_values[slice_idx][vertex_indices]} values, "
1331+
"supply a correct template to visualize the thickness values"
1332+
) from err
13281333
self.thickness_values = new_thickness_values
13291334

13301335
@staticmethod
@@ -1334,15 +1339,17 @@ def __make_parent_folder(filename: str):
13341339
output_folder = Path(filename).parent
13351340
output_folder.mkdir(parents=False, exist_ok=True)
13361341

1337-
def to_fs_coordinates(self):
1342+
def to_fs_coordinates(self, vox_size: tuple[int, int, int], image_size: tuple[int, int, int]):
13381343
"""Convert mesh coordinates to FreeSurfer coordinate system.
13391344
13401345
Transforms the mesh vertices from the original coordinate system to the
13411346
FreeSurfer coordinate system by reordering axes and applying appropriate offsets.
13421347
"""
1343-
self.v = self.v[:, [2, 0, 1]]
1344-
self.v[:, 1] -= 128
1345-
self.v[:, 2] += 128
1348+
self.v = self.v[:, [2, 0, 1]] # LIA to ALI?
1349+
self.v *= (vox_size[0] **2) ## ???
1350+
self.v[:, 1] -= image_size[1] * vox_size[1] // 2 # move 0 to center of image
1351+
self.v[:, 2] += image_size[2] * vox_size[2] // 2
1352+
13461353

13471354
def write_fssurf(self, filename):
13481355
"""Write the mesh to a FreeSurfer surface file.

CorpusCallosum/shape/cc_postprocessing.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ def process_slice(segmentation, slice_idx, ac_coords, pc_coords, affine, num_thi
134134
'angular', or 'eigenvector')
135135
contour_smoothing (float):
136136
Gaussian sigma for contour smoothing
137-
138137
Returns:
139138
slice_data (dict | None):
140139
Dictionary containing measurements if successful, including:
@@ -246,7 +245,7 @@ def process_slice(segmentation, slice_idx, ac_coords, pc_coords, affine, num_thi
246245
def process_slices(segmentation, slice_selection, temp_seg_affine, midslices, ac_coords, pc_coords,
247246
num_thickness_points, subdivisions, subdivision_method, contour_smoothing,
248247
debug_image_path=None, one_debug_image=False,
249-
thickness_image_path=None, vox_size=None,
248+
thickness_image_path=None, vox_size=None, image_size=None,
250249
save_template=None, surf_file_path=None, overlay_file_path=None, cc_html_path=None,
251250
vtk_file_path=None, verbose=False):
252251
"""Process corpus callosum slices based on selection mode.
@@ -281,7 +280,7 @@ def process_slices(segmentation, slice_selection, temp_seg_affine, midslices, ac
281280
if slice_selection == "middle":
282281
cc_mesh = CC_Mesh(num_slices=1)
283282
cc_mesh.set_acpc_coords(ac_coords, pc_coords)
284-
cc_mesh.set_resolution(vox_size) # contour is always scaled to 1 mm
283+
cc_mesh.set_resolution(vox_size[0]) # contour is always scaled to 1 mm
285284

286285
# Process only the middle slice
287286
slice_idx = segmentation.shape[0] // 2
@@ -309,12 +308,12 @@ def process_slices(segmentation, slice_selection, temp_seg_affine, midslices, ac
309308
if verbose:
310309
logger.info(f"Saving segmentation qc image to {debug_image_path}")
311310
IO_processes.append(create_visualization(subdivision_method, result, midslices,
312-
debug_image_path, ac_coords, pc_coords, vox_size))
311+
debug_image_path, ac_coords, pc_coords, vox_size[0]))
313312
else:
314313
num_slices = segmentation.shape[0]
315314
cc_mesh = CC_Mesh(num_slices=num_slices)
316315
cc_mesh.set_acpc_coords(ac_coords, pc_coords)
317-
cc_mesh.set_resolution(vox_size) # contour is always scaled to 1 mm
316+
cc_mesh.set_resolution(vox_size[0]) # contour is always scaled to 1 mm
318317

319318
# Process multiple slices or specific slice
320319
if slice_selection == "all":
@@ -367,7 +366,7 @@ def process_slices(segmentation, slice_selection, temp_seg_affine, midslices, ac
367366
IO_processes.append(create_visualization(subdivision_method, result,
368367
midslices[current_slice_in_volume:current_slice_in_volume+1],
369368
debug_output_path_slice, ac_coords, pc_coords,
370-
vox_size, f' (Slice {slice_idx})'))
369+
vox_size[0], f' (Slice {slice_idx})'))
371370

372371
if save_template is not None:
373372
# Convert to Path object and ensure directory exists
@@ -394,7 +393,7 @@ def process_slices(segmentation, slice_selection, temp_seg_affine, midslices, ac
394393
#cc_mesh.write_vtk(str(output_dir / 'cc_mesh.vtk'))
395394

396395

397-
cc_mesh.to_fs_coordinates()
396+
cc_mesh.to_fs_coordinates(vox_size=vox_size, image_size=image_size)
398397

399398
if overlay_file_path is not None:
400399
if verbose:
@@ -417,7 +416,7 @@ def process_slices(segmentation, slice_selection, temp_seg_affine, midslices, ac
417416

418417
if not slice_results:
419418
logger.error("Error: No valid slices were found for postprocessing")
420-
exit(1)
419+
raise ValueError("No valid slices were found for postprocessing")
421420

422421
return slice_results, IO_processes
423422

0 commit comments

Comments
 (0)