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

measure.regionprops_table with properties=['centroid_weighted'] fails on images with >1 color channel. #6860

Closed
aslyon opened this issue Mar 29, 2023 · 3 comments · Fixed by #6861
Labels

Comments

@aslyon
Copy link

aslyon commented Mar 29, 2023

Description:

Calling skimage.measure.regionprops_table with properties=['centroid_weighted'] on an image with >1 color channel raises ValueError: setting an array element with a sequence. I expected to get a dictionary with keys centroid_weighted-n-c where n are the indices of the spatial dimensions and c are the indices of the color channels.

Way to reproduce:

import numpy as np
import skimage

# Create 3D label image with a sphere
label_img = np.zeros((9, 9, 9), dtype='uint8')
label_img[:, :, :] = skimage.morphology.ball(radius=4)
n_pixels = label_img.sum()

# Create random number generator
rng = np.random.default_rng(seed=123)

# Create intensity image of sphere filled with random values
intensity_img = np.zeros((9, 9, 9, 3))
intensity_img[:, :, :, 0][label_img == 1] = rng.uniform(low=10, high=20, size=n_pixels)
intensity_img[:, :, :, 1][label_img == 1] = rng.uniform(low=10, high=20, size=n_pixels)
intensity_img[:, :, :, 2][label_img == 1] = rng.uniform(low=10, high=20, size=n_pixels)

# Measure weighted centroid for 1 color channel - this works
skimage.measure.regionprops_table(label_img, intensity_img[:, :, :, 0:1], properties=['centroid_weighted'])
# output: {'centroid_weighted-0': array([4.0115177]), 'centroid_weighted-1': array([3.98670859]), 'centroid_weighted-2': array([3.99605087])}

# Measure weighted centroid for 2 color channels - this DOESN'T work
skimage.measure.regionprops_table(label_img, intensity_img[:, :, :, 0:2], properties=['centroid_weighted'])

# Measure weighted centroid for 3 color channels - this DOESN'T work
skimage.measure.regionprops_table(label_img, intensity_img[:, :, :, 0:3], properties=['centroid_weighted'])

The ValueError with traceback is below. The error is the same whether 2 or 3 color intensity image is used.

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
TypeError: only size-1 arrays can be converted to Python scalars

The above exception was the direct cause of the following exception:

ValueError                                Traceback (most recent call last)
Cell In[86], line 26
     16 intensity_img[:, :, :, 2][label_img == 1] = rng.uniform(low=10, high=20, size=n_pixels)
     18 # Measure weighted centroid for 1 color channel - this works
     19 # skimage.measure.regionprops_table(label_img, intensity_img[:, :, :, 0:1], properties=['centroid_weighted'])
     20 # output: {'centroid_weighted-0': array([4.0115177]), 'centroid_weighted-1': array([3.98670859]), 'centroid_weighted-2': array([3.99605087])}
   (...)
     24 
     25 # Measure weighted centroid for 3 color channels - this DOESN'T work
---> 26 skimage.measure.regionprops_table(label_img, intensity_img[:, :, :, 0:3], properties=['centroid_weighted'])

File /Applications/miniconda3/envs/idpimg/lib/python3.10/site-packages/skimage/measure/_regionprops.py:1045, in regionprops_table(label_image, intensity_image, properties, cache, separator, extra_properties, spacing)
   1041     out_d = _props_to_dict(regions, properties=properties,
   1042                            separator=separator)
   1043     return {k: v[:0] for k, v in out_d.items()}
-> 1045 return _props_to_dict(
   1046     regions, properties=properties, separator=separator
   1047 )

File /Applications/miniconda3/envs/idpimg/lib/python3.10/site-packages/skimage/measure/_regionprops.py:877, in _props_to_dict(regions, properties, separator)
    875     rp = regions[k][prop]
    876     for i, loc in enumerate(locs):
--> 877         column_data[k, i] = rp[loc]
    879 # add the columns to the output dictionary
    880 for i, modified_prop in enumerate(modified_props):

ValueError: setting an array element with a sequence.

Version information:

3.10.8 (main, Nov 11 2022, 08:11:25) [Clang 12.0.0 ]
macOS-10.16-x86_64-i386-64bit
scikit-image version: 0.20.0
numpy version: 1.22.3
@jni
Copy link
Member

jni commented Mar 29, 2023

Thanks for the report, @aslyon! The issue is here:

if isinstance(rp, np.ndarray):
shape = rp.shape
else:
shape = (len(rp),)

It turns out that the output of weighted_moments is an array-like, a tuple of arrays, rather than an array, so instead of inferring a 2D array here, we infer a linear set of numbers. This just happens to work when the arrays contain one element, but fails when there's more, as you have observed. Here's how I modified your example to show these outputs:

import numpy as np
import skimage

# Create 3D label image with a sphere
label_img = np.zeros((9, 9, 9), dtype='uint8')
label_img[:, :, :] = skimage.morphology.ball(radius=4)
n_pixels = label_img.sum()

# Create random number generator
rng = np.random.default_rng(seed=123)

# Create intensity image of sphere filled with random values
intensity_img = np.zeros((9, 9, 9, 3))
intensity_img[:, :, :, 0][label_img == 1] = rng.uniform(low=10, high=20, size=n_pixels)
intensity_img[:, :, :, 1][label_img == 1] = rng.uniform(low=10, high=20, size=n_pixels)
intensity_img[:, :, :, 2][label_img == 1] = rng.uniform(low=10, high=20, size=n_pixels)

# Measure props for no channel axis:
props0 = skimage.measure.regionprops(label_img, intensity_img[..., 0])[0]
print(f'no channel:\n {props0.centroid_weighted}')

# Measure props for single channel
props1 = skimage.measure.regionprops(label_img, intensity_img[..., 0:1])[0]
print(f'one channel:\n {props1.centroid_weighted}')

# Measure props for multiple channels
propsm = skimage.measure.regionprops(label_img, intensity_img)[0]
print(f'multichannel:\n {propsm.centroid_weighted}')

which gives:

no channel:
 (4.011517704412646, 3.9867085865741485, 3.996050865015718)
one channel:
 (array([4.0115177]), array([3.98670859]), array([3.99605087]))
multichannel:
 (array([4.0115177 , 4.03724327, 3.98084178]), array([3.98670859, 4.01261718, 3.96234087]), array([3.99605087, 4.0387654 , 3.99636875]))

So, it looks like we need a better way of identifying the type and shape of the output of each property when turning it into a table. My first instinct is to convert any non-scalar, non-object property to an array. I might give that a go.

@jni
Copy link
Member

jni commented Mar 29, 2023

Attempted fix in #6861. Please check locally with your data @aslyon!

@aslyon
Copy link
Author

aslyon commented Mar 29, 2023

Working correctly now, thanks so much for the rapid response @jni !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants