Extract mask from 3D RGB image using a 1D Boolean array - python

I have a 3D image which is a numpy array of shape (1314, 489, 3) and looks as follows:
Now I want to calculate the mean RGB color value of the mask (the cob without the black background). Calculating the RGB value for the whole image is easy:
print(np.mean(colormaskcutted, axis=(0, 1)))
>>[186.18434633 88.89164511 46.32022921]
But now I want this mean RGB color value only for the cob. I have a 1D boolean mask
array for the mask with this shape where one value corresponds to all of the 3 color channel values: (1314, 489)
I tried slicing the image array for the mask, as follows:
print(np.mean(colormaskcutted[boolean[:,:,0]], axis=(0, 1)))
>>124.57794089613752
But this returned only one value instead of 3 values for the RGB color.
How can I filter the 3D numpy image for a 1D boolean mask so that the mean RGB color calculation can be performed?

If your question is limited to computing the mean, you don't necessarily need to subset the image. You can simply do, e.g.
np.sum(colormaskcutted*boolean[:,:,None], axis = (0,1))/np.sum(boolean)
P.S. I've played around with indexing, you can amend your original approach as follows:
np.mean(colormaskcutted[boolean,:], axis = 0)
P.P.S. Can't resist some benchmarking. So, the summation approach takes 15.9s (1000 iterations, dimensions like in the example, old computer); the advanced indexing approach is slightly longer, at 17.7s. However, the summation can be optimized further. Using count_nonzero as per Mad Physicist suggestion marginally improves the time to 15.3s. We can also use tensordot to skip creating a temporary array:
np.tensordot(colormaskcutted, boolean, axes = [[0,1], [0,1]])/np.count_nonzero(msk)
This cuts the time to 4.5s.

Related

Mask a three dimensional array to perform segmentation

I'm working with Python. I want to know if there is a python way to mask a 3d array XYZ (volumetric image) to perform a segmentation analysis such as skeletonization.
I'm handling a 600x600x300 array so reducing the number of candidates to be analyzed is key to performance. I tried np.array[mask] but the array dimension changes to 1. The where method such as this How to Correctly mask 3D Array with numpy performs the change to one value at the time, but skeletonization needs to analyze the neighbors to be performed.
Edit: This is something simple but it might help you to get the idea. It's to create a 3d AOI inside a volume.
# create array with random numbers
Array = np.random.random([10, 10,10])
# create a boolean mask of zeros
maskArr=np.zeros_like(Array, dtype=bool)
# set a few values in the mask to true
maskArr[1:8,1:5,1:3] = 1
# Try to analise the data with mask
process= morphology.skeletonize(Array[maskArr])
this is the error due to the 1-d array:
ValueError: skeletonize requires a 2D or 3D image as input, got 1D.

Generate pixel density map (heatmap) from image with numpy array manipulation

The specific problem I try to solve is:
I have a binary image binary map that I want to generate a heatmap (density map) for, my idea is to get the 2D array of this image, let say it is 12x12
a = np.random.randint(20, size=(12, 12));
index and process it with a fixed-size submatrix (let say 3x3), so for every submatrix, a pixel percentage value will be calculated (nonzero pixels/total pixel).
submatrix = a[0:3, 0:3]
pixel_density = np.count_nonzero(submatrix) / submatrix.size
At the end, all the percentage values will made up a new 2D array (a smaller, 4x4 density array) that represent the density estimation of the original image. Lower resolution is fine because the data it will be compared to has a lower resolution as well.
I am not sure how to do that through numpy, especially for the indexing part. Also if there is a better way for generating heatmap like that, please let me know as well.
Thank you!
Maybe a 2-D convolution? Basically this will sweep through the a matrix with the b matrix, which is just 1s below. So it will do the summation you were looking for. This link has a visual demo of convolution near the bottom.
import numpy as np
from scipy import signal
a = np.random.randint(2, size=(12, 12))
b = np.ones((4,4))
signal.convolve2d(a,b, 'valid') / b.sum()

Numpy : how to use np.where in a multidimensional array with a given test condition?

Edit : I reduce to a minimal problem, since my first question was probably too messy
when I use np.where on a condition on a scalar cell things work fine:
new_array = np.where(old_array==6, rempl_array, old_array)
but if I want my condition to work on a full dimension of the array:
new_array = np.where((old_array == [1, 2, 3]).all(axis=-1), rempl_array, old_array)
I does not any more, for dimension mismatch
But I can't figure out how to transform the 2D boolean (old_array == [1, 2, 3]).all(axis=-1) in a suitable 3D boolean for where
Here was the initial post :
I have a 3D array, that I have created from a picture (so dimensions hold for height, width and RGB value). I want to change colors according to a given condition.
submap = np.any([(carr == [pr["red"], pr["green"], pr["blue"]]).all(axis=-1) for pr in list_areas], axis=0)
The condition works fine, retruning a 2D array with True for pixels where the condition is met, and False otherwise.
However, when I try to build a new 3D array where I change colors according to that condition:
new_carr = np.where(submap, new_color, carr)
I get a shape mismatch error :
ValueError: operands could not be broadcast together with shapes (2048,5632) (3,) (2048,5632,3)
The problem seems not to be only the fact that my new_color has shape (3,), since the problem still holds when I replace it with an array of shape (2048,5632,3), but the fact that my condition is 2D while my initial array is 3D. But how could this condition not be 2D by definition, and how could I make this work?
Thanks for your help
Starting with this posterised image of Paddington:
I think you want to use np.where() as follows to make all red areas into magenta and all other areas into yellow:
#!/usr/bin/env python3
from PIL import Image
import numpy as np
# Load PIL Image and ensure RGB rather than palette based, then make into Numpy array
pi = Image.open('paddington.png').convert('RGB')
na = np.array(pi)
# Now make 2 images same size, one magenta, one yellow
magenta = np.zeros_like(na) + [255,0,255]
yellow = np.zeros_like(na) + [255,255,0]
# Anywhere paddington is red, make him magenta. Anywhere else, make him yellow.
result = np.where((na==[255,0,0]).all(axis=-1)[...,None], magenta, yellow)
# Save result
Image.fromarray(result.astype(np.uint8)).save('result.png')
Of course, it was not necessary to make a full size image of magenta and yellow, I just did that to match your original code. You could have used a single pixel and saved memory, making him green and blue like this:
result = np.where((na==[255,0,0]).all(axis=-1)[...,None], [0,255,0], [0,0,255])
Actually, I have solved my problem in a very ugly way
submap = np.array([[[b, b, b] for b in x] for x in submap.tolist()])
But boy that seems inefficient. There should be a way to do that with arrays only.

Taking mean of numpy ndarray with masked elements

I have a MxN array of values taken from an experiment. Some of these values are invalid and are set to 0 to indicate such. I can construct a mask of valid/invalid values using
mask = (mat1 == 0) & (mat2 == 0)
which produces an MxN array of bool. It should be noted that the masked locations do not neatly follow columns or rows of the matrix - so simply cropping the matrix is not an option.
Now, I want to take the mean along one axis of my array (E.G end up with a 1xN array) while excluding those invalid values in the mean calculation. Intuitively I thought
np.mean(mat1[mask],axis=1)
should do it, but the mat1[mask] operation produces a 1D array which appears to just be the elements where mask is true - which doesn't help when I only want a mean across one dimension of the array.
Is there a 'python-esque' or numpy way to do this? I suppose I could use the mask to set masked elements to NaN and use np.nanmean - but that still feels kind of clunky. Is there a way to do this 'cleanly'?
I think the best way to do this would be something along the lines of:
masked = np.ma.masked_where(mat1 == 0 && mat2 == 0, array_to_mask)
Then take the mean with
masked.mean(axis=1)
One similarly clunky but efficient way is to multiply your array with the mask, setting the masked values to zero. Then of course you'll have to divide by the number of non-masked values manually. Hence clunkiness. But this will work with integer-valued arrays, something that can't be said about the nan case. It also seems to be fastest for both small and larger arrays (including the masked array solution in another answer):
import numpy as np
def nanny(mat, mask):
mat = mat.astype(float).copy() # don't mutate the original
mat[~mask] = np.nan # mask values
return np.nanmean(mat, axis=0) # compute mean
def manual(mat, mask):
# zero masked values, divide by number of nonzeros
return (mat*mask).sum(axis=0)/mask.sum(axis=0)
# set up dummy data for testing
N,M = 400,400
mat1 = np.random.randint(0,N,(N,M))
mask = np.random.randint(0,2,(N,M)).astype(bool)
print(np.array_equal(nanny(mat1, mask), manual(mat1, mask))) # True

Mean value for dimension in numpy array

My numpy array (name: data) has following size: (10L,3L,256L,256L).
It has 10 images with each 3 color channels (RGB) and each an image size of 256x256 pixel.
I want to compute the mean pixel value for each color channel of all 10 images. If I use the numpy function np.mean(data), I receive the mean for all pixel values. Using np.mean(data, axis=1) returns a numpy array with size (10L, 256L, 256L).
If I understand your question correctly you want an array containing the mean value of each channel for each of the three images. (i.e. an array of shape (10,3) ) (Let me know in the comments if this is incorrect and I can edit this answer)
If you are using a version of numpy greater than 1.7 you can pass multiple axes to np.mean as a tuple
mean_values = data.mean(axis=(2,3))
Otherwise you will have to flatten the array first to get it into the correct shape.
mean_values = data.reshape((data.shape[0], data.shape[1], data.shape[2]*data.shape[3])).mean(axis=2)

Categories