From b8295c70a4916f851ab0b6f83dae76a3fb23da4c Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Tue, 20 Jan 2026 10:07:27 -0500 Subject: [PATCH 1/6] Test_streamlines_in_mask ok --- src/scilpy/segment/streamlines.py | 9 +++- src/scilpy/segment/tests/test_bundleseg.py | 7 +++ src/scilpy/segment/tests/test_streamlines.py | 45 +++++++++++++++++++ .../segment/tests/test_tractogram_from_roi.py | 1 + .../tests/test_intersection_finder.py | 8 ++++ 5 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/scilpy/segment/tests/test_bundleseg.py create mode 100644 src/scilpy/segment/tests/test_streamlines.py create mode 100644 src/scilpy/tractograms/tests/test_intersection_finder.py diff --git a/src/scilpy/segment/streamlines.py b/src/scilpy/segment/streamlines.py index 5dd461e65..9aec7d8e4 100644 --- a/src/scilpy/segment/streamlines.py +++ b/src/scilpy/segment/streamlines.py @@ -17,16 +17,22 @@ def streamlines_in_mask(sft, target_mask, all_in=False): """ + Finds the streamlines that are either touching a mask (if all_in=False) or + entirely contained in the mask (if all_in=True). + Parameters ---------- sft : StatefulTractogram StatefulTractogram containing the streamlines to segment. target_mask : numpy.ndarray Binary mask in which the streamlines should pass. + all_in: bool + If true, finds + Returns ------- ids : list - Ids of the streamlines passing through the mask. + Ids of the streamlines passing the test. """ sft.to_vox() sft.to_corner() @@ -64,6 +70,7 @@ def filter_grid_roi_both(sft, mask_1, mask_2): Binary mask in which the streamlines should start or end. mask_2: numpy.ndarray Binary mask in which the streamlines should start or end. + Returns ------- new_sft: StatefulTractogram diff --git a/src/scilpy/segment/tests/test_bundleseg.py b/src/scilpy/segment/tests/test_bundleseg.py new file mode 100644 index 000000000..f3dbc0a79 --- /dev/null +++ b/src/scilpy/segment/tests/test_bundleseg.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- + +def test_recognize(): + # todo: + # bundleseg = BundleSeg(...) + # bundleseg.recognize. + pass diff --git a/src/scilpy/segment/tests/test_streamlines.py b/src/scilpy/segment/tests/test_streamlines.py new file mode 100644 index 000000000..704bd8ed2 --- /dev/null +++ b/src/scilpy/segment/tests/test_streamlines.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +from dipy.io.stateful_tractogram import StatefulTractogram, Space, Origin +import nibabel as nib +import numpy as np +from scilpy.segment.streamlines import streamlines_in_mask + + +def test_streamlines_in_mask(): + # Binary mask: ROI on x=1 and x=2 + mask = np.zeros((10, 10, 10)) + mask[1:3, :, :] = 1 + fake_reference = nib.Nifti1Image( + np.zeros((10, 10, 10, 1)), affine=np.eye(4)) + + # line 1 entirely in mask. y and z values are random. + line1 = [[1.3, 5, 6], + [1.5, 5, 7], + [2.5, 7, 4], + [2.9, 9, 9]] + + # line 2: partially in mask. + line2 = [[1.3, 5, 6], + [5.5, 5, 7], + [2.5, 7, 4], + [2.9, 9, 9]] + + # line3: never in mask + line3 = [[4.3, 5, 6], + [5.5, 5, 7], + [7.5, 7, 4], + [9.9, 9, 9]] + + sft = StatefulTractogram([line1, line2, line3], fake_reference, + space=Space.VOXMM, origin=Origin('corner')) + + # Test option 'all' + ids = streamlines_in_mask(sft, mask, all_in=True) + assert len(ids) == 1 + assert ids[0] == 0 + + # Test option 'any' + ids = streamlines_in_mask(sft, mask, all_in=False) + assert len(ids) == 2 + assert ids[0] == 0 + assert ids[1] == 1 diff --git a/src/scilpy/segment/tests/test_tractogram_from_roi.py b/src/scilpy/segment/tests/test_tractogram_from_roi.py index 2d451bedd..d05c5f14f 100644 --- a/src/scilpy/segment/tests/test_tractogram_from_roi.py +++ b/src/scilpy/segment/tests/test_tractogram_from_roi.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import os import tempfile import nibabel as nib diff --git a/src/scilpy/tractograms/tests/test_intersection_finder.py b/src/scilpy/tractograms/tests/test_intersection_finder.py new file mode 100644 index 000000000..c39e5aad1 --- /dev/null +++ b/src/scilpy/tractograms/tests/test_intersection_finder.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + + +def test_find_intersection(): + # Todo: + # finder = IntersectionFinder(...) + # finder.find_intersection() + pass From 291aaa14c9a29ddfecb6acb82ef3ca8337786b87 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Tue, 20 Jan 2026 10:12:27 -0500 Subject: [PATCH 2/6] Test filter_grid_roi... ok --- src/scilpy/segment/streamlines.py | 2 +- src/scilpy/segment/tests/test_streamlines.py | 45 +++++++++++++++++++- src/scilpy/segment/tractogram_from_roi.py | 6 +-- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/scilpy/segment/streamlines.py b/src/scilpy/segment/streamlines.py index 9aec7d8e4..40c7c440a 100644 --- a/src/scilpy/segment/streamlines.py +++ b/src/scilpy/segment/streamlines.py @@ -58,7 +58,7 @@ def streamlines_in_mask(sft, target_mask, all_in=False): return np.where(streamlines_case == [0, 1][True])[0].tolist() -def filter_grid_roi_both(sft, mask_1, mask_2): +def filter_grid_roi_both_ends(sft, mask_1, mask_2): """ Filters streamlines with one end in a mask and the other in another mask. diff --git a/src/scilpy/segment/tests/test_streamlines.py b/src/scilpy/segment/tests/test_streamlines.py index 704bd8ed2..30c79bd49 100644 --- a/src/scilpy/segment/tests/test_streamlines.py +++ b/src/scilpy/segment/tests/test_streamlines.py @@ -2,7 +2,8 @@ from dipy.io.stateful_tractogram import StatefulTractogram, Space, Origin import nibabel as nib import numpy as np -from scilpy.segment.streamlines import streamlines_in_mask +from scilpy.segment.streamlines import streamlines_in_mask, \ + filter_grid_roi_both_ends def test_streamlines_in_mask(): @@ -43,3 +44,45 @@ def test_streamlines_in_mask(): assert len(ids) == 2 assert ids[0] == 0 assert ids[1] == 1 + + +def test_filter_grid_roi_both_ends(): + # Binary mask: ROI on x=1 and x=2 + mask = np.zeros((10, 10, 10)) + mask[1:3, :, :] = 1 + fake_reference = nib.Nifti1Image( + np.zeros((10, 10, 10, 1)), affine=np.eye(4)) + + # line 1 entirely in mask. y and z values are random. + line1 = [[1.3, 5, 6], + [1.5, 5, 7], + [2.5, 7, 4], + [2.9, 9, 9]] + + # line 2: both ends in mask but not in the middle + line2 = [[1.3, 5, 6], + [5.5, 5, 7], + [2.5, 7, 4], + [2.9, 9, 9]] + + # line 3: Only one end in mask + line3 = [[1.3, 5, 6], + [5.5, 5, 7], + [6.5, 7, 4], + [7.9, 9, 9]] + + # line 4: Both ends out of mask + line4 = [[1.3, 5, 6], + [5.5, 5, 7], + [6.5, 7, 4], + [7.9, 9, 9]] + + sft = StatefulTractogram([line1, line2, line3, line4], fake_reference, + space=Space.VOXMM, origin=Origin('corner')) + + new_sft, ids = filter_grid_roi_both_ends(sft, mask_1=mask, mask_2=mask) + assert len(ids) == 2 + assert len(new_sft) == 2 + assert ids[0] == 0 + assert ids[1] == 1 + \ No newline at end of file diff --git a/src/scilpy/segment/tractogram_from_roi.py b/src/scilpy/segment/tractogram_from_roi.py index 1ef2f560b..a166803e1 100644 --- a/src/scilpy/segment/tractogram_from_roi.py +++ b/src/scilpy/segment/tractogram_from_roi.py @@ -15,7 +15,7 @@ split_mask_blobs_kmeans from scilpy.io.image import get_data_as_mask from scilpy.io.streamlines import load_tractogram_with_reference -from scilpy.segment.streamlines import filter_grid_roi, filter_grid_roi_both +from scilpy.segment.streamlines import filter_grid_roi, filter_grid_roi_both_ends from scilpy.tractograms.streamline_operations import \ remove_loops_and_sharp_turns from scilpy.tractanalysis.streamlines_metrics import compute_tract_counts_map @@ -363,7 +363,7 @@ def _extract_vb_one_bundle( mask_1 = binary_dilation(mask_1, iterations=dilate_endpoints) mask_2 = binary_dilation(mask_2, iterations=dilate_endpoints) - _, vs_ids = filter_grid_roi_both(sft, mask_1, mask_2) + _, vs_ids = filter_grid_roi_both_ends(sft, mask_1, mask_2) else: vs_ids = np.array([]) @@ -514,7 +514,7 @@ def _extract_ib_one_bundle(sft, mask_1_filename, mask_2_filename, mask_1 = binary_dilation(mask_1, iterations=dilate_endpoints) mask_2 = binary_dilation(mask_2, iterations=dilate_endpoints) - _, fc_ids = filter_grid_roi_both(sft, mask_1, mask_2) + _, fc_ids = filter_grid_roi_both_ends(sft, mask_1, mask_2) else: fc_ids = [] From dc77a7331570e3cb84ec74828c9dca4a5c2d7f79 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Tue, 20 Jan 2026 12:57:02 -0500 Subject: [PATCH 3/6] Test_filter_grid_roi ok --- src/scilpy/segment/streamlines.py | 37 +++-- src/scilpy/segment/tests/test_streamlines.py | 158 +++++++++++-------- 2 files changed, 111 insertions(+), 84 deletions(-) diff --git a/src/scilpy/segment/streamlines.py b/src/scilpy/segment/streamlines.py index 40c7c440a..919a87bbc 100644 --- a/src/scilpy/segment/streamlines.py +++ b/src/scilpy/segment/streamlines.py @@ -59,8 +59,10 @@ def streamlines_in_mask(sft, target_mask, all_in=False): def filter_grid_roi_both_ends(sft, mask_1, mask_2): - """ Filters streamlines with one end in a mask and the other in - another mask. + """ + Filters streamlines with one end in a mask and the other in another mask. + See also filter_grid_roi, but here we may give two different masks for the + endpoints. Parameters ---------- @@ -115,6 +117,9 @@ def filter_grid_roi_both_ends(sft, mask_1, mask_2): def filter_grid_roi(sft, mask, filter_type, is_exclude, filter_distance=0, return_sft=False, return_rejected_sft=False): """ + Filters streamlines based on a given criteria (any, all, either_end, + both_ends). + Parameters ---------- sft : StatefulTractogram @@ -206,8 +211,8 @@ def filter_grid_roi(sft, mask, filter_type, is_exclude, filter_distance=0, return line_based_indices -def pre_filtering_for_geometrical_shape(sft, size, center, filter_type, - is_in_vox): +def _pre_filtering_for_geometrical_shape(sft, size, center, filter_type, + is_in_vox): """ Parameters ---------- @@ -261,6 +266,9 @@ def pre_filtering_for_geometrical_shape(sft, size, center, filter_type, def filter_ellipsoid(sft, ellipsoid_radius, ellipsoid_center, filter_type, is_exclude, is_in_vox=False): """ + Finds streamlines that respect some criteria in a ROI, where the ROI is + a bounding box of ellipsoid type. + Parameters ---------- sft : StatefulTractogram @@ -287,9 +295,9 @@ def filter_ellipsoid(sft, ellipsoid_radius, ellipsoid_center, return np.array([]), sft pre_filtered_indices, pre_filtered_sft = \ - pre_filtering_for_geometrical_shape(sft, ellipsoid_radius, - ellipsoid_center, filter_type, - is_in_vox) + _pre_filtering_for_geometrical_shape(sft, ellipsoid_radius, + ellipsoid_center, filter_type, + is_in_vox) pre_filtered_sft.to_rasmm() pre_filtered_sft.to_center() pre_filtered_streamlines = pre_filtered_sft.streamlines @@ -367,9 +375,11 @@ def filter_ellipsoid(sft, ellipsoid_radius, ellipsoid_center, return line_based_indices, new_sft -def filter_cuboid(sft, cuboid_radius, cuboid_center, - filter_type, is_exclude): +def filter_cuboid(sft, cuboid_radius, cuboid_center, filter_type, is_exclude): """ + Finds streamlines that respect some criteria in a ROI, where the ROI is + a bounding box of cuboid type. + Parameters ---------- sft : StatefulTractogram @@ -382,8 +392,7 @@ def filter_cuboid(sft, cuboid_radius, cuboid_center, One of the 4 following choices: 'any', 'all', 'either_end', 'both_ends'. is_exclude: bool Value to indicate if the ROI is an AND (false) or a NOT (true). - is_in_vox: bool - Value to indicate if the ROI is in voxel space. + Returns ------- ids : list @@ -395,9 +404,9 @@ def filter_cuboid(sft, cuboid_radius, cuboid_center, return np.array([]), sft pre_filtered_indices, pre_filtered_sft = \ - pre_filtering_for_geometrical_shape(sft, cuboid_radius, - cuboid_center, filter_type, - is_in_vox=False) + _pre_filtering_for_geometrical_shape(sft, cuboid_radius, + cuboid_center, filter_type, + is_in_vox=False) pre_filtered_sft.to_rasmm() pre_filtered_sft.to_center() pre_filtered_streamlines = pre_filtered_sft.streamlines diff --git a/src/scilpy/segment/tests/test_streamlines.py b/src/scilpy/segment/tests/test_streamlines.py index 30c79bd49..9424c5bc7 100644 --- a/src/scilpy/segment/tests/test_streamlines.py +++ b/src/scilpy/segment/tests/test_streamlines.py @@ -3,86 +3,104 @@ import nibabel as nib import numpy as np from scilpy.segment.streamlines import streamlines_in_mask, \ - filter_grid_roi_both_ends + filter_grid_roi_both_ends, filter_grid_roi + +# Preparing SFT for all tests here. +# Binary mask: ROI on x=1 and x=2 +mask = np.zeros((10, 10, 10)) +mask[1:3, :, :] = 1 + +# line 0 entirely in mask. y and z values are random. +line0 = [[1.3, 5, 6], + [1.5, 5, 7], + [2.5, 7, 4], + [2.9, 9, 9]] + +# line 1: partially in mask (both ends in mask but not in the middle) +line1 = [[1.3, 5, 6], + [5.5, 5, 7], + [2.5, 7, 4], + [2.9, 9, 9]] + +# line 2: Only one end in mask +line2 = [[1.3, 5, 6], + [5.5, 5, 7], + [6.5, 7, 4], + [7.9, 9, 9]] + +# line 3: Both ends out of mask but touches in the middle +line3 = [[5.3, 5, 6], + [1.5, 5, 7], + [2.5, 7, 4], + [7.9, 9, 9]] + +# line 4: never in mask +line4 = [[4.3, 5, 6], + [5.5, 5, 7], + [7.5, 7, 4], + [9.9, 9, 9]] + +# line 5: passes through, but no real point inside. +line5 = [[0.3, 5, 6], + [5.5, 5, 7], + [7.5, 7, 4], + [9.9, 9, 9]] + +fake_reference = nib.Nifti1Image( + np.zeros((10, 10, 10, 1)), affine=np.eye(4)) +sft = StatefulTractogram([line0, line1, line2, line3, line4, line5], + fake_reference, space=Space.VOXMM, + origin=Origin('corner')) def test_streamlines_in_mask(): - # Binary mask: ROI on x=1 and x=2 - mask = np.zeros((10, 10, 10)) - mask[1:3, :, :] = 1 - fake_reference = nib.Nifti1Image( - np.zeros((10, 10, 10, 1)), affine=np.eye(4)) - - # line 1 entirely in mask. y and z values are random. - line1 = [[1.3, 5, 6], - [1.5, 5, 7], - [2.5, 7, 4], - [2.9, 9, 9]] - - # line 2: partially in mask. - line2 = [[1.3, 5, 6], - [5.5, 5, 7], - [2.5, 7, 4], - [2.9, 9, 9]] - - # line3: never in mask - line3 = [[4.3, 5, 6], - [5.5, 5, 7], - [7.5, 7, 4], - [9.9, 9, 9]] - - sft = StatefulTractogram([line1, line2, line3], fake_reference, - space=Space.VOXMM, origin=Origin('corner')) - # Test option 'all' ids = streamlines_in_mask(sft, mask, all_in=True) - assert len(ids) == 1 - assert ids[0] == 0 + assert np.array_equal(ids, [0]) # Test option 'any' ids = streamlines_in_mask(sft, mask, all_in=False) - assert len(ids) == 2 - assert ids[0] == 0 - assert ids[1] == 1 + assert np.array_equal(ids, [0, 1, 2, 3, 5]) def test_filter_grid_roi_both_ends(): - # Binary mask: ROI on x=1 and x=2 - mask = np.zeros((10, 10, 10)) - mask[1:3, :, :] = 1 - fake_reference = nib.Nifti1Image( - np.zeros((10, 10, 10, 1)), affine=np.eye(4)) - - # line 1 entirely in mask. y and z values are random. - line1 = [[1.3, 5, 6], - [1.5, 5, 7], - [2.5, 7, 4], - [2.9, 9, 9]] - - # line 2: both ends in mask but not in the middle - line2 = [[1.3, 5, 6], - [5.5, 5, 7], - [2.5, 7, 4], - [2.9, 9, 9]] - - # line 3: Only one end in mask - line3 = [[1.3, 5, 6], - [5.5, 5, 7], - [6.5, 7, 4], - [7.9, 9, 9]] - - # line 4: Both ends out of mask - line4 = [[1.3, 5, 6], - [5.5, 5, 7], - [6.5, 7, 4], - [7.9, 9, 9]] - - sft = StatefulTractogram([line1, line2, line3, line4], fake_reference, - space=Space.VOXMM, origin=Origin('corner')) - + # Pretending to have two masks + # Test option 'both ends' new_sft, ids = filter_grid_roi_both_ends(sft, mask_1=mask, mask_2=mask) - assert len(ids) == 2 + assert np.array_equal(ids, [0, 1]) assert len(new_sft) == 2 - assert ids[0] == 0 - assert ids[1] == 1 - \ No newline at end of file + + +def test_filter_grid_roi(): + # Note. Distance not tested yet. (toDo) + # Testing the returned SFT only the first time. + # Parameter is "is_exclude", so: + include=False + exclude=True + + # Test 'any' + ids, new_sft, rejected_sft = filter_grid_roi( + sft, mask, 'any', include, + return_sft=True, return_rejected_sft=True) + assert np.array_equal(ids, [0, 1, 2, 3, 5]) + assert len(new_sft) == 5 + ids = filter_grid_roi(sft, mask, 'any', exclude) + assert np.array_equal(ids, [4]) + + # Test 'all' + ids = filter_grid_roi(sft, mask, 'all', include) + assert np.array_equal(ids, [0]) + ids = filter_grid_roi(sft, mask, 'all', exclude) + assert np.array_equal(ids, [1, 2, 3, 4, 5]) + + # Test 'either_end' + ids = filter_grid_roi(sft, mask, 'either_end', include) + assert np.array_equal(ids, [0, 1, 2]) + ids = filter_grid_roi(sft, mask, 'either_end', exclude) + assert np.array_equal(ids, [3, 4, 5]) + + # Test 'both_ends' + ids = filter_grid_roi(sft, mask, 'both_ends', include) + assert np.array_equal(ids, [0, 1]) + ids = filter_grid_roi(sft, mask, 'both_ends', exclude) + assert np.array_equal(ids, [2, 3, 4, 5]) \ No newline at end of file From 0e46b34f0d97e9930932d8cf1fb971ec14e2d3f8 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Tue, 20 Jan 2026 15:07:53 -0500 Subject: [PATCH 4/6] filter_ellipsoid and filter_cuboid ok --- src/scilpy/segment/streamlines.py | 10 +- src/scilpy/segment/tests/test_streamlines.py | 129 ++++++++++++------- 2 files changed, 83 insertions(+), 56 deletions(-) diff --git a/src/scilpy/segment/streamlines.py b/src/scilpy/segment/streamlines.py index 919a87bbc..dc14a84bc 100644 --- a/src/scilpy/segment/streamlines.py +++ b/src/scilpy/segment/streamlines.py @@ -259,8 +259,7 @@ def _pre_filtering_for_geometrical_shape(sft, size, center, filter_type, pre_mask[min_x:max_x, min_y:max_y, min_z:max_z] = 1 - return filter_grid_roi(sft, pre_mask, filter_type, is_exclude=False, - return_sft=True) + return filter_grid_roi(sft, pre_mask, filter_type, is_exclude=False) def filter_ellipsoid(sft, ellipsoid_radius, ellipsoid_center, @@ -276,7 +275,7 @@ def filter_ellipsoid(sft, ellipsoid_radius, ellipsoid_center, ellipsoid_radius : numpy.ndarray (3) Size in mm, x/y/z of the ellipsoid. ellipsoid_center: numpy.ndarray (3) - Center x/y/z of the ellipsoid. + Center x/y/z of the ellipsoid, in RASMM space, center origin. filter_type: str One of the 4 following choices, 'any', 'all', 'either_end', 'both_ends'. is_exclude: bool @@ -300,7 +299,6 @@ def filter_ellipsoid(sft, ellipsoid_radius, ellipsoid_center, is_in_vox) pre_filtered_sft.to_rasmm() pre_filtered_sft.to_center() - pre_filtered_streamlines = pre_filtered_sft.streamlines transfo, _, res, _ = sft.space_attributes if is_in_vox: @@ -318,7 +316,7 @@ def filter_ellipsoid(sft, ellipsoid_radius, ellipsoid_center, ellipsoid_radius = np.asarray(ellipsoid_radius, dtype=float) ellipsoid_center = np.asarray(ellipsoid_center, dtype=float) - for i, line in enumerate(pre_filtered_streamlines): + for i, line in enumerate(pre_filtered_sft.streamlines): if filter_type in ['any', 'all']: # Resample to 1/10 of the voxel size nb_points = max(int(length(line) / np.average(res) * 10), 2) @@ -387,7 +385,7 @@ def filter_cuboid(sft, cuboid_radius, cuboid_center, filter_type, is_exclude): cuboid_radius : numpy.ndarray (3) Size in mm, x/y/z of the cuboid. cuboid_center: numpy.ndarray (3) - Center x/y/z of the cuboid. + Center x/y/z of the cuboid, in RASMM space, center origin. filter_type: str One of the 4 following choices: 'any', 'all', 'either_end', 'both_ends'. is_exclude: bool diff --git a/src/scilpy/segment/tests/test_streamlines.py b/src/scilpy/segment/tests/test_streamlines.py index 9424c5bc7..93bcd93df 100644 --- a/src/scilpy/segment/tests/test_streamlines.py +++ b/src/scilpy/segment/tests/test_streamlines.py @@ -3,48 +3,59 @@ import nibabel as nib import numpy as np from scilpy.segment.streamlines import streamlines_in_mask, \ - filter_grid_roi_both_ends, filter_grid_roi + filter_grid_roi_both_ends, filter_grid_roi, filter_ellipsoid, filter_cuboid # Preparing SFT for all tests here. -# Binary mask: ROI on x=1 and x=2 +# 3 ways to crete ROI, all ~voxels 1 and 2 in x, y, z +# - mask +# - bdo_ellipsoid +# - bdo_cuboid + +# Binary mask mask = np.zeros((10, 10, 10)) -mask[1:3, :, :] = 1 - -# line 0 entirely in mask. y and z values are random. -line0 = [[1.3, 5, 6], - [1.5, 5, 7], - [2.5, 7, 4], - [2.9, 9, 9]] - -# line 1: partially in mask (both ends in mask but not in the middle) -line1 = [[1.3, 5, 6], - [5.5, 5, 7], - [2.5, 7, 4], - [2.9, 9, 9]] - -# line 2: Only one end in mask -line2 = [[1.3, 5, 6], - [5.5, 5, 7], - [6.5, 7, 4], - [7.9, 9, 9]] - -# line 3: Both ends out of mask but touches in the middle -line3 = [[5.3, 5, 6], - [1.5, 5, 7], - [2.5, 7, 4], - [7.9, 9, 9]] - -# line 4: never in mask -line4 = [[4.3, 5, 6], - [5.5, 5, 7], - [7.5, 7, 4], - [9.9, 9, 9]] - -# line 5: passes through, but no real point inside. -line5 = [[0.3, 5, 6], - [5.5, 5, 7], - [7.5, 7, 4], - [9.9, 9, 9]] +mask[1:3, 1:3, 1:3] = 1 + +# bdo. +# Note. Everything else here is in vox, corner, except bdo_center +bdo_center_rasmm_centerorigin = np.asarray([1.5, 1.5, 1.5]) +bdo_radius_mm = 1.5 + +# Preparing streamlines fitting criteria. +# line 0 'all': entirely in mask. +line0 = [[1.5, 1.5, 1.5], + [1.6, 1.6, 1.6], + [1.7, 1.7, 1.7], + [2.5, 2.5, 2.5]] + +# line 1 'both_ends': both ends in mask but not in the middle. +line1 = [[1.5, 1.5, 1.5], + [9, 9, 9], + [1.7, 1.7, 1.7], + [2.5, 2.5, 2.5]] + +# line 2 'any': Only one end in mask +line2 = [[1.5, 1.5, 1.5], + [1.6, 1.6, 1.6], + [1.7, 1.7, 1.7], + [9, 9, 9]] + +# line 3 'any': Both ends out of mask but touches in the middle +line3 = [[9, 9, 9], + [1.6, 1.6, 1.6], + [1.7, 1.7, 1.7], + [9, 9, 9]] + +# line 4 (exclude): never in mask +line4 = [[9, 9, 9.0], + [8, 8, 8.0], + [7, 7, 7.0], + [9, 9, 9.0]] + +# line 5 'any': passes through, but no real point inside. +line5 = [[0.1, 0.1, 0.1], + [8, 8, 8.0], + [7, 7, 7.0], + [9, 9, 9.0]] fake_reference = nib.Nifti1Image( np.zeros((10, 10, 10, 1)), affine=np.eye(4)) @@ -73,34 +84,52 @@ def test_filter_grid_roi_both_ends(): def test_filter_grid_roi(): # Note. Distance not tested yet. (toDo) - # Testing the returned SFT only the first time. + roi_options = (mask,) + _test_all_criteria(filter_grid_roi, roi_options) + + +def test_filter_ellipsoid(): + roi_options = (bdo_radius_mm, bdo_center_rasmm_centerorigin) + _test_all_criteria(filter_ellipsoid, roi_options) + + +def test_filter_cuboid(): + roi_options = (bdo_radius_mm, bdo_center_rasmm_centerorigin) + _test_all_criteria(filter_cuboid, roi_options) + + +def _test_all_criteria(fct, roi_options): + """ + The three filtering methods (filter_grid_roi, filter_ellipsoid, + filter_cuboid) test the same criteria, but with a different way to treat + the ROI. + """ # Parameter is "is_exclude", so: include=False exclude=True # Test 'any' - ids, new_sft, rejected_sft = filter_grid_roi( - sft, mask, 'any', include, - return_sft=True, return_rejected_sft=True) + # Testing the returned sft only this once + ids, new_sft = fct(sft, *roi_options, 'any', include) assert np.array_equal(ids, [0, 1, 2, 3, 5]) assert len(new_sft) == 5 - ids = filter_grid_roi(sft, mask, 'any', exclude) + ids, _ = fct(sft, *roi_options, 'any', exclude) assert np.array_equal(ids, [4]) # Test 'all' - ids = filter_grid_roi(sft, mask, 'all', include) + ids, _ = fct(sft, *roi_options, 'all', include) assert np.array_equal(ids, [0]) - ids = filter_grid_roi(sft, mask, 'all', exclude) + ids, _ = fct(sft, *roi_options, 'all', exclude) assert np.array_equal(ids, [1, 2, 3, 4, 5]) # Test 'either_end' - ids = filter_grid_roi(sft, mask, 'either_end', include) + ids, _ = fct(sft, *roi_options, 'either_end', include) assert np.array_equal(ids, [0, 1, 2]) - ids = filter_grid_roi(sft, mask, 'either_end', exclude) + ids, _ = fct(sft, *roi_options, 'either_end', exclude) assert np.array_equal(ids, [3, 4, 5]) # Test 'both_ends' - ids = filter_grid_roi(sft, mask, 'both_ends', include) + ids, _ = fct(sft, *roi_options, 'both_ends', include) assert np.array_equal(ids, [0, 1]) - ids = filter_grid_roi(sft, mask, 'both_ends', exclude) + ids, _ = fct(sft, *roi_options, 'both_ends', exclude) assert np.array_equal(ids, [2, 3, 4, 5]) \ No newline at end of file From cfb3cf0497e1cb945c9d5520d034c7c07babbe6b Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Tue, 20 Jan 2026 15:10:11 -0500 Subject: [PATCH 5/6] Finish docstring --- src/scilpy/segment/streamlines.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/scilpy/segment/streamlines.py b/src/scilpy/segment/streamlines.py index dc14a84bc..2bb8a9c7c 100644 --- a/src/scilpy/segment/streamlines.py +++ b/src/scilpy/segment/streamlines.py @@ -27,7 +27,8 @@ def streamlines_in_mask(sft, target_mask, all_in=False): target_mask : numpy.ndarray Binary mask in which the streamlines should pass. all_in: bool - If true, finds + If true, finds streamlines satisfying the 'all' criteria. Else, finds + streamlines satisfying the 'any' criteria. Returns ------- From c2415a2838ae6929520dd4ff0b5d74bc29b97444 Mon Sep 17 00:00:00 2001 From: EmmaRenauld Date: Wed, 21 Jan 2026 09:34:25 -0500 Subject: [PATCH 6/6] Last fixes --- src/scilpy/segment/streamlines.py | 3 +- src/scilpy/segment/tests/test_streamlines.py | 35 +++++++++++--------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/scilpy/segment/streamlines.py b/src/scilpy/segment/streamlines.py index 2bb8a9c7c..de92dd13e 100644 --- a/src/scilpy/segment/streamlines.py +++ b/src/scilpy/segment/streamlines.py @@ -260,7 +260,8 @@ def _pre_filtering_for_geometrical_shape(sft, size, center, filter_type, pre_mask[min_x:max_x, min_y:max_y, min_z:max_z] = 1 - return filter_grid_roi(sft, pre_mask, filter_type, is_exclude=False) + return filter_grid_roi(sft, pre_mask, filter_type, is_exclude=False, + return_sft=True) def filter_ellipsoid(sft, ellipsoid_radius, ellipsoid_center, diff --git a/src/scilpy/segment/tests/test_streamlines.py b/src/scilpy/segment/tests/test_streamlines.py index 93bcd93df..82654bea7 100644 --- a/src/scilpy/segment/tests/test_streamlines.py +++ b/src/scilpy/segment/tests/test_streamlines.py @@ -85,51 +85,54 @@ def test_filter_grid_roi_both_ends(): def test_filter_grid_roi(): # Note. Distance not tested yet. (toDo) roi_options = (mask,) - _test_all_criteria(filter_grid_roi, roi_options) + _test_all_criteria(filter_grid_roi, roi_options, fct_returns_sft=False) def test_filter_ellipsoid(): roi_options = (bdo_radius_mm, bdo_center_rasmm_centerorigin) - _test_all_criteria(filter_ellipsoid, roi_options) + _test_all_criteria(filter_ellipsoid, roi_options, fct_returns_sft=True) def test_filter_cuboid(): roi_options = (bdo_radius_mm, bdo_center_rasmm_centerorigin) - _test_all_criteria(filter_cuboid, roi_options) + _test_all_criteria(filter_cuboid, roi_options, fct_returns_sft=True) -def _test_all_criteria(fct, roi_options): +def _test_all_criteria(fct, roi_args, fct_returns_sft): """ The three filtering methods (filter_grid_roi, filter_ellipsoid, filter_cuboid) test the same criteria, but with a different way to treat - the ROI. + the ROI. Here are the tests, the roi_args should be different based on fct. """ - # Parameter is "is_exclude", so: + # Parameter is "is_exclude" for all three methods. So: include=False exclude=True + def get_ids(output): + if fct_returns_sft: + return output[0] + return output + # Test 'any' - # Testing the returned sft only this once - ids, new_sft = fct(sft, *roi_options, 'any', include) + ids = get_ids(fct(sft, *roi_args, 'any', include)) assert np.array_equal(ids, [0, 1, 2, 3, 5]) - assert len(new_sft) == 5 - ids, _ = fct(sft, *roi_options, 'any', exclude) + ids = get_ids(fct(sft, *roi_args, 'any', exclude)) assert np.array_equal(ids, [4]) # Test 'all' - ids, _ = fct(sft, *roi_options, 'all', include) + ids = get_ids(fct(sft, *roi_args, 'all', include)) assert np.array_equal(ids, [0]) - ids, _ = fct(sft, *roi_options, 'all', exclude) + ids = get_ids(fct(sft, *roi_args, 'all', exclude)) assert np.array_equal(ids, [1, 2, 3, 4, 5]) # Test 'either_end' - ids, _ = fct(sft, *roi_options, 'either_end', include) + ids = get_ids(fct(sft, *roi_args, 'either_end', include)) assert np.array_equal(ids, [0, 1, 2]) - ids, _ = fct(sft, *roi_options, 'either_end', exclude) + ids = get_ids(fct(sft, *roi_args, 'either_end', exclude)) assert np.array_equal(ids, [3, 4, 5]) # Test 'both_ends' - ids, _ = fct(sft, *roi_options, 'both_ends', include) + ids = get_ids(fct(sft, *roi_args, 'both_ends', include)) assert np.array_equal(ids, [0, 1]) - ids, _ = fct(sft, *roi_options, 'both_ends', exclude) + ids = get_ids(fct(sft, *roi_args, 'both_ends', exclude)) assert np.array_equal(ids, [2, 3, 4, 5]) \ No newline at end of file