Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: label_image_centroids only worked with sequential labels 1..N #714

Merged
merged 1 commit into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion ants/label/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
from .make_points_image import make_points_image
from .multi_label_morphology import multi_label_morphology
26 changes: 13 additions & 13 deletions ants/label/label_image_centroids.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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]
Expand All @@ -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

Expand Down
26 changes: 13 additions & 13 deletions ants/label/make_points_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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' )
11 changes: 5 additions & 6 deletions ants/registration/affine_initializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -41,7 +41,7 @@ def affine_initializer(fixed_image, moving_image, search_factor=20,
-------
ndarray
transformation matrix

Example
-------
>>> import ants
Expand All @@ -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
2 changes: 1 addition & 1 deletion ants/utils/mni2tal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
66 changes: 52 additions & 14 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]])
Expand Down Expand Up @@ -874,29 +912,29 @@ 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")) )

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=[]
Expand All @@ -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)
Expand All @@ -929,29 +967,29 @@ 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
labels_dilated = ants.multi_label_morphology(labels, 'MD', 2)
# 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,:,:]))

Expand Down