From ce8667d36ab621b60d21905e10e2ed3150811b89 Mon Sep 17 00:00:00 2001 From: Philip Cook Date: Mon, 7 Oct 2024 18:44:34 -0400 Subject: [PATCH] BUG: label_image_centroids only worked with sequential labels 1..N BUG: make_points_image function not imported ENH: New tests for label stats and make_points_image STYLE: Clean up whitespace and unescaped chars in comments --- ants/label/__init__.py | 3 +- ants/label/label_image_centroids.py | 26 +++++----- ants/label/make_points_image.py | 26 +++++----- ants/registration/affine_initializer.py | 11 ++--- ants/utils/mni2tal.py | 2 +- tests/test_utils.py | 66 +++++++++++++++++++------ 6 files changed, 86 insertions(+), 48 deletions(-) diff --git a/ants/label/__init__.py b/ants/label/__init__.py index a5081a19..b5105351 100644 --- a/ants/label/__init__.py +++ b/ants/label/__init__.py @@ -5,4 +5,5 @@ from .label_overlap_measures import label_overlap_measures from .label_stats import label_stats from .labels_to_matrix import labels_to_matrix -from .multi_label_morphology import multi_label_morphology \ No newline at end of file +from .make_points_image import make_points_image +from .multi_label_morphology import multi_label_morphology diff --git a/ants/label/label_image_centroids.py b/ants/label/label_image_centroids.py index ac2ca296..c096c14b 100644 --- a/ants/label/label_image_centroids.py +++ b/ants/label/label_image_centroids.py @@ -18,14 +18,14 @@ def label_image_centroids(image, physical=False, convex=True, verbose=False): --------- image : ANTsImage image of integer labels - + physical : boolean whether you want physical space coordinates or not - + convex : boolean if True, return centroid if False return point with min average distance to other points with same label - + Returns ------- dictionary w/ following key-value pairs: @@ -58,14 +58,14 @@ def label_image_centroids(image, physical=False, convex=True, verbose=False): zc = np.zeros(n_labels) if convex: - for i in mylabels: - idx = (labels == i).flatten() - xc[i-1] = np.mean(xcoords[idx]) - yc[i-1] = np.mean(ycoords[idx]) - zc[i-1] = np.mean(zcoords[idx]) + for lab_idx, label_intensity in enumerate(mylabels): + idx = (labels == label_intensity).flatten() + xc[lab_idx] = np.mean(xcoords[idx]) + yc[lab_idx] = np.mean(ycoords[idx]) + zc[lab_idx] = np.mean(zcoords[idx]) else: - for i in mylabels: - idx = (labels == i).flatten() + for lab_idx, label_intensity in enumerate(mylabels): + idx = (labels == label_intensity).flatten() xci = xcoords[idx] yci = ycoords[idx] zci = zcoords[idx] @@ -75,9 +75,9 @@ def label_image_centroids(image, physical=False, convex=True, verbose=False): dist[j] = np.mean(np.sqrt((xci[j] - xci)**2 + (yci[j] - yci)**2 + (zci[j] - zci)**2)) mid = np.where(dist==np.min(dist)) - xc[i-1] = xci[mid] - yc[i-1] = yci[mid] - zc[i-1] = zci[mid] + xc[lab_idx] = xci[mid] + yc[lab_idx] = yci[mid] + zc[lab_idx] = zci[mid] centroids = np.vstack([xc,yc,zc]).T diff --git a/ants/label/make_points_image.py b/ants/label/make_points_image.py index 8533cb00..362f2e21 100644 --- a/ants/label/make_points_image.py +++ b/ants/label/make_points_image.py @@ -7,7 +7,7 @@ import ants -def make_points_image(pts, mask, radius=5): +def make_points_image(pts, target, radius=5): """ Create label image from physical space points @@ -21,10 +21,10 @@ def make_points_image(pts, mask, radius=5): Arguments --------- pts : numpy.ndarray - input powers points + input points - mask : ANTsImage - mask defining target space + target : ANTsImage + Image defining target space radius : integer radius for the points @@ -37,25 +37,25 @@ def make_points_image(pts, mask, radius=5): ------- >>> import ants >>> import pandas as pd - >>> mni = ants.image_read(ants.get_data('mni')).get_mask() + >>> mni = ants.image_read(ants.get_data('mni')) >>> powers_pts = pd.read_csv(ants.get_data('powers_mni_itk')) >>> powers_labels = ants.make_points_image(powers_pts.iloc[:,:3].values, mni, radius=3) """ - powers_lblimg = mask * 0 + lblimg = target * 0 npts = len(pts) - dim = mask.dimension + dim = target.dimension if pts.shape[1] != dim: raise ValueError('points dimensionality should match that of images') for r in range(npts): pt = pts[r,:] - idx = ants.transform_physical_point_to_index(mask, pt.tolist() ).astype(int) + idx = ants.transform_physical_point_to_index(target, pt.tolist() ).astype(int) in_image=True - for kk in range(mask.dimension): - in_image = in_image and idx[kk] >= 0 and idx[kk] < mask.shape[kk] + for kk in range(target.dimension): + in_image = in_image and idx[kk] >= 0 and idx[kk] < target.shape[kk] if ( in_image == True ): if (dim == 3): - powers_lblimg[idx[0],idx[1],idx[2]] = r + 1 + lblimg[idx[0],idx[1],idx[2]] = r + 1 elif (dim == 2): - powers_lblimg[idx[0],idx[1]] = r + 1 - return ants.morphology( powers_lblimg, 'dilate', radius, 'grayscale' ) + lblimg[idx[0],idx[1]] = r + 1 + return ants.morphology( lblimg, 'dilate', radius, 'grayscale' ) diff --git a/ants/registration/affine_initializer.py b/ants/registration/affine_initializer.py index e7b6558d..7c9a36ee 100644 --- a/ants/registration/affine_initializer.py +++ b/ants/registration/affine_initializer.py @@ -8,21 +8,21 @@ def affine_initializer(fixed_image, moving_image, search_factor=20, - radian_fraction=0.1, use_principal_axis=False, + radian_fraction=0.1, use_principal_axis=False, local_search_iterations=10, mask=None, txfn=None ): """ A multi-start optimizer for affine registration Searches over the sphere to find a good initialization for further registration refinement, if needed. This is a wrapper for the ANTs function antsAffineInitializer. - + ANTsR function: `affineInitializer` Arguments --------- fixed_image : ANTsImage the fixed reference image - moving_image : ANTsImage + moving_image : ANTsImage the moving image to be mapped to the fixed space search_factor : scalar degree of increments on the sphere to search @@ -41,7 +41,7 @@ def affine_initializer(fixed_image, moving_image, search_factor=20, ------- ndarray transformation matrix - + Example ------- >>> import ants @@ -66,6 +66,5 @@ def affine_initializer(fixed_image, moving_image, search_factor=20, if retval != 0: warnings.warn('ERROR: Non-zero exit status!') - - return txfn + return txfn \ No newline at end of file diff --git a/ants/utils/mni2tal.py b/ants/utils/mni2tal.py index 98452795..99dbfb74 100644 --- a/ants/utils/mni2tal.py +++ b/ants/utils/mni2tal.py @@ -25,7 +25,7 @@ def mni2tal(xin): References ---------- - http://bioimagesuite.yale.edu/mni2tal/501_95733_More\%20Accurate\%20Talairach\%20Coordinates\%20SLIDES.pdf + http://bioimagesuite.yale.edu/mni2tal/501_95733_More\\%20Accurate\\%20Talairach\\%20Coordinates\\%20SLIDES.pdf http://imaging.mrc-cbu.cam.ac.uk/imaging/MniTalairach """ if (not isinstance(xin, (tuple,list))) or (len(xin) != 3): diff --git a/tests/test_utils.py b/tests/test_utils.py index 7a86e27c..1509c5c6 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -209,13 +209,13 @@ def test_crop_image_example(self): # label image not float cropped = ants.crop_image(fi, fi.clone("unsigned int"), 100) - + # channel image fi = ants.image_read( ants.get_ants_data('r16') ) cropped = ants.crop_image(fi) fi2 = ants.merge_channels([fi,fi]) cropped2 = ants.crop_image(fi2) - + self.assertEqual(cropped.shape, cropped2.shape) def test_crop_indices_example(self): @@ -583,11 +583,27 @@ def setUp(self): def tearDown(self): pass - def test_label_clusters_example(self): + def test_label_image_centroids(self): image = ants.from_numpy( np.asarray([[[0, 2], [1, 3]], [[4, 6], [5, 7]]]).astype("float32") ) labels = ants.label_image_centroids(image) + self.assertEqual(len(labels['labels']), 7) + + # Test non-sequential labels + image = ants.from_numpy( + np.asarray([[[0, 2], [2, 2]], [[2, 0], [5, 0]]]).astype("float32") + ) + + labels = ants.label_image_centroids(image) + self.assertTrue(len(labels['labels']) == 2) + self.assertTrue(labels['labels'][1] == 5) + self.assertTrue(np.allclose(labels['vertices'][0], [0.5 , 0.5 , 0.25], atol=1e-5)) + # With convex = False, the centroid position should change + labels = ants.label_image_centroids(image, convex=False) + self.assertTrue(np.allclose(labels['vertices'][0], [1.0, 1.0, 0.0], atol=1e-5)) + # single point unchanged + self.assertTrue(np.allclose(labels['vertices'][1], [0.0, 1.0, 1.0], atol=1e-5)) class TestModule_label_overlap_measures(unittest.TestCase): @@ -633,6 +649,28 @@ def test_labels_to_matrix_example(self): labmat = ants.labels_to_matrix(labs, mask) +class TestModule_make_points_image(unittest.TestCase): + def setUp(self): + pass + + def tearDown(self): + pass + + def test_make_points_image_example(self): + image = ants.image_read(ants.get_ants_data("r16")) + points = np.array([[102, 76],[134, 129]]) + points_image = ants.make_points_image(points, image, radius=5) + stats = ants.label_stats(image, points_image) + self.assertTrue(np.allclose(stats['Volume'].to_numpy()[1:3], 97.0, atol=1e-5)) + self.assertTrue(np.allclose(stats['x'].to_numpy()[1:3], points[:,0], atol=1e-5)) + self.assertTrue(np.allclose(stats['y'].to_numpy()[1:3], points[:,1], atol=1e-5)) + + points = np.array([[102, 76, 50],[134, 129, 50]]) + # Shouldn't allow 3D points on a 2D image + with self.assertRaises(Exception): + points_image = ants.make_points_image(image, points, radius=3) + + class TestModule_mask_image(unittest.TestCase): def setUp(self): pass @@ -846,7 +884,7 @@ def setUp(self): pass def tearDown(self): pass - + def test_bspline_field(self): points = np.array([[-50, -50]]) deltas = np.array([[10, 10]]) @@ -874,17 +912,17 @@ def test_ilr(self): result = ants.ilr( df, vlist, myform) myform = " mat2 ~ covar + mat1 " result = ants.ilr( df, vlist, myform) - + def test_quantile(self): img = ants.image_read(ants.get_data('r16')) ants.quantile(img, 0.5) ants.quantile(img, (0.5, 0.75)) - + def test_bandpass(self): brainSignal = np.random.randn( 400, 1000 ) tr = 1 filtered = ants.bandpass_filter_matrix( brainSignal, tr = tr ) - + def test_compcorr(self): cc = ants.compcor( ants.image_read(ants.get_ants_data("ch2")) ) @@ -892,11 +930,11 @@ def test_histogram_match(self): src_img = ants.image_read(ants.get_data('r16')) ref_img = ants.image_read(ants.get_data('r64')) src_ref = ants.histogram_match_image(src_img, ref_img) - + src_img = ants.image_read(ants.get_data('r16')) ref_img = ants.image_read(ants.get_data('r64')) src_ref = ants.histogram_match_image2(src_img, ref_img) - + def test_averaging(self): x0=[ ants.get_data('r16'), ants.get_data('r27'), ants.get_data('r62'), ants.get_data('r64') ] x1=[] @@ -906,7 +944,7 @@ def test_averaging(self): avg1=ants.average_images(x1) avg2=ants.average_images(x1,mask=0) avg3=ants.average_images(x1,mask=1,normalize=True) - + def test_n3_2(self): image = ants.image_read( ants.get_ants_data('r16') ) image_n3 = ants.n3_bias_field_correction2(image) @@ -929,7 +967,7 @@ def test_thin_plate_spline(self): displacement_origins=points, displacements=deltas, origin=[0.0, 0.0], spacing=[1.0, 1.0], size=[100, 100], direction=np.array([[-1, 0], [0, -1]])) - + def test_multi_label_morph(self): img = ants.image_read(ants.get_data('r16')) labels = ants.get_mask(img,1,150) + ants.get_mask(img,151,225) * 2 @@ -937,21 +975,21 @@ def test_multi_label_morph(self): # should see original label regions preserved in dilated version # label N should have mean N and 0 variance print(ants.label_stats(labels_dilated, labels)) - + def test_hausdorff_distance(self): r16 = ants.image_read( ants.get_ants_data('r16') ) r64 = ants.image_read( ants.get_ants_data('r64') ) s16 = ants.kmeans_segmentation( r16, 3 )['segmentation'] s64 = ants.kmeans_segmentation( r64, 3 )['segmentation'] stats = ants.hausdorff_distance(s16, s64) - + def test_channels_first(self): import ants image = ants.image_read(ants.get_ants_data('r16')) image2 = ants.image_read(ants.get_ants_data('r16')) img3 = ants.merge_channels([image,image2]) img4 = ants.merge_channels([image,image2], channels_first=True) - + self.assertTrue(np.allclose(img3.numpy()[:,:,0], img4.numpy()[0,:,:])) self.assertTrue(np.allclose(img3.numpy()[:,:,1], img4.numpy()[1,:,:]))