Python: taking the GLCM of a non-rectangular region - python

I have been using the SLIC implementation of skimage to segment images in superpixels. I would like to use GLCMs to extract additional features from these superpixels for a classification problem. These superpixels are not rectangular. In MATLAB you can set pixels to NaN and they will be ignored by the algorithm (link). I could use this to make bounding boxes around the superpixels and then just setting the unused pixels to NaN.
The greycomatrix function in skimage does not work entirely the same as the MATLAB implementation however. When setting pixels to NaN the function fails on an assert to check if all values are larger than 0.
Is there a Python implementation available which is able to work with nonrectangular ROIs?

Although mahotas is also an excellent computer vision library, there's no need to stop using skimage to do this.
What is necessary, as #Tonechas has pointed out, is to set those NaN values to an integer, since np.nan has type float and the greycomatrix function requires an array of integers.
The easiest option would be setting those NaN's to zero but, if you already have zero values in your pixels and don't want to mix them, you can choose any other constant. After that, all you have to do is filter that chosen value (once again, generally zero) out of the GLCM.
To understand what this means, let's see what skimage tells us about the output of the greycomatrix function:
4-D ndarray
[...] The value P[i,j,d,theta] is the number of times that grey-level j occurs at a distance d and at an angle theta from grey-level i. If normed is False, the output is of type uint32, otherwise it is float64. The dimensions are: levels x levels x number of distances x number of angles.
In other words, the first two dimensions of the array define a matrix that tells us how many times two different values are a certain distant apart. Note that the GLCM does not keep the shape of the input array. Those rows and columns are telling us how the values relate.
Knowing this, it's easy to filter out the values outside our ROI (imagine we've set those NaN's to zero):
glcm = greycomatrix(img, [1], [0]) # Calculate the GLCM "one pixel to the right"
filt_glcm = glcm[1:, 1:, :, :] # Filter out the first row and column
Now you could easily calculate the Haralick properties of your filtered GLCM. For example:
greycoprops(filt_glcm, prop='contrast')

The issue is you have to pass an integer array to greycomatrix, but np.nan has type float (take a look at this thread for details). As a result you cannot encode the pixels outside the ROI as NaN.
An approximate workaround for dealing with non-rectangular ROI's would be setting the pixels outside the ROI to 0 and using the function haralick from mahotas library. This function returns 13 Haralick features extracted from four different GLCM's, corresponding to the four 2-D orientations and the particular value of the distance parameter.
From the documentation:
ignore_zeros can be used to have the function ignore any zero-valued
pixels (as background).
In summary, you need to mask those pixels that fall outside the ROI, and set ignore_zeros to True in the call to haralick.
DEMO
To begin with, let us generate some mock data:
In [213]: import numpy as np
In [214]: shape = (3, 4)
In [215]: levels = 8
In [216]: np.random.seed(2017)
In [217]: x = np.random.randint(0, levels, size=shape)
In [218]: x
Out[218]:
array([[3, 1, 6, 5],
[2, 0, 2, 2],
[3, 7, 7, 7]])
Then we have to remove all the zeros from the image as in this approach the zero intensity level is reserved for the pixels outside the ROI. It is worth to point out that merging intensities 0 and 1 into a single intensity 1 introduces inaccuracies in the results.
In [219]: x[x == 0] = 1
In [220]: x
Out[220]:
array([[3, 1, 6, 5],
[2, 1, 2, 2],
[3, 7, 7, 7]])
Next step consists in defining a mask for the pixels outside the ROI (in this toy example, the four corners of the image) and setting those pixels to 0.
In [221]: non_roi = np.zeros(shape=shape, dtype=np.bool)
In [222]: non_roi[np.ix_([0, -1], [0, -1])] = True
In [223]: non_roi
Out[223]:
array([[ True, False, False, True],
[False, False, False, False],
[ True, False, False, True]], dtype=bool)
In [224]: x[non_roi] = 0
In [225]: x
Out[225]:
array([[0, 1, 6, 0],
[2, 1, 2, 2],
[0, 7, 7, 0]])
We can now perform feature extraction from the GLCM's of a non-rectangular ROI:
In [226]: import mahotas.features.texture as mht
In [227]: features = mht.haralick(x, ignore_zeros=True)
In [228]: features.size
Out[228]: 52
In [229]: features.ravel()
Out[229]: array([ 0.18 , 5.4 , 0.5254833 , ..., 0.81127812,
-0.68810414, 0.96300727])
It may be useful to check how the co-occurrence matrices look. For example, the "pixel to the right" GLCM would be:
In [230]: mht.cooccurence(x, 0)
Out[230]:
array([[0, 1, 0, ..., 0, 1, 2],
[1, 0, 2, ..., 0, 1, 0],
[0, 2, 2, ..., 0, 0, 0],
...,
[0, 0, 0, ..., 0, 0, 0],
[1, 1, 0, ..., 0, 0, 0],
[2, 0, 0, ..., 0, 0, 2]])

Related

Python Fancy Indexing Assignments: cannot assign 3 input values to the 6 output values where the mask is true

I am trying to make a zeroed array with the same shape of a source array. Then modify every value in the second array that corresponds to a specific value in the first array.
This would be simple enough if I was just replacing one value. Here is a toy example:
import numpy as np
arr1 = np.array([[1,2,3],[3,4,5],[1,2,3]])
arr2 = np.array([[0,0,0],[0,0,0],[0,0,0]])
arr2[arr1==1] = -1
This will work as expected and arr2 would be:
[[-1,0,0],
[ 0,0,0],
[-1,0,0]]
But I would like to replace an entire row. Something like this replacing the last line of the sample code above:
arr2[arr1==[3,4,5]] = [-1,-1,-1]
When I do this, it also works as expected and arr2 would be:
[[ 0, 0, 0],
[-1,-1,-1],
[ 0, 0, 0]]
But when I tried to replace the last line of sample code with something like:
arr2[arr1==[1,2,3]] = [-1,-1,-1]
I expected to get something like the last output, but with the 0th and 2nd rows being changed. But instead I got the following error.
ValueError: NumPy boolean array indexing assignment cannot assign 3 input values to the 6
output values where the mask is true
I assume this is because, unlike the other example, it was going to have to replace more than one row. Though this seems odd to me, since it worked fine replacing more than one value in the simple single value example.
I'm just wondering if anyone can explain this behavior to me, because it is a little confusing. I am not that experienced with the inner workings of numpy operations. Also, if anyone has an any recommendations to do what I am trying to accomplish in an efficient manner.
In my real world implementation, I am working with a very large three dimensional array (an image with 3 color channels) and I want to make an new array that stores a specific value into these three color channels if the source image has a specific three color values in that corresponding pixel (and remain [0,0,0] if it doesn't match our pixel_rgb_of_interest). I could go through in linear time and just check every single pixel, but this could get kind of slow if there are a lot of images, and was just wondering if there was a better way.
Thank you!
This would be a good application for numpy.where
>>> import numpy as np
>>> arr1 = np.array([[1,2,3],[3,4,5],[1,2,3]])
>>> arr2 = np.array([[0,0,0],[0,0,0],[0,0,0]])
>>> np.where(arr1 == [1,2,3], [-1,-1,-1], arr1)
array([[-1, -1, -1],
[ 3, 4, 5],
[-1, -1, -1]])
This basically works as "wherever the condition is true, use the x argument, then use the y argument the rest of the time"
Lets add an "index" array:
In [56]: arr1 = np.array([[1,2,3],[3,4,5],[1,2,3]])
...: arr2 = np.array([[0,0,0],[0,0,0],[0,0,0]])
...: arr3 = np.arange(9).reshape(3,3)
The test against 1 value:
In [57]: arr1==1
Out[57]:
array([[ True, False, False],
[False, False, False],
[ True, False, False]])
that has 2 true values:
In [58]: arr3[arr1==1]
Out[58]: array([0, 6])
We could assign one value as you do, or 2.
Test with a list, which is converted to array first:
In [59]: arr1==[3,4,5]
Out[59]:
array([[False, False, False],
[ True, True, True],
[False, False, False]])
That has 3 True:
In [60]: arr3[arr1==[3,4,5]]
Out[60]: array([3, 4, 5])
so it works to assign a list of 3 values as you do. Or a scalar.
In [61]: arr1==[1,2,3]
Out[61]:
array([[ True, True, True],
[False, False, False],
[ True, True, True]])
Here the test has 6 True.
In [62]: arr3[arr1==[1,2,3]]
Out[62]: array([0, 1, 2, 6, 7, 8])
So we can assign 6 values or a scalar. But you tried to assign 3 values.
Or we could apply all to find the rows that match [1,2,3]:
In [63]: np.all(arr1==[1,2,3], axis=1)
Out[63]: array([ True, False, True])
In [64]: arr3[np.all(arr1==[1,2,3], axis=1)]
Out[64]:
array([[0, 1, 2],
[6, 7, 8]])
To this we could assign a (2,3) array, a scalar, a (3,) array, or a (2,1) (as per broadcasting rules):
In [65]: arr2[np.all(arr1==[1,2,3], axis=1)]=np.array([100,200])[:,None]
In [66]: arr2
Out[66]:
array([[100, 100, 100],
[ 0, 0, 0],
[200, 200, 200]])

Pytorch: accessing a subtensor using lists of indices

I have a pair of tensors S and T of dimensions (s1,...,sm) and (t1,...,tn) with si < ti. I want to specify a list of indices in each dimensions of T to "embed" S in T. If I1 is a list of s1 indices in (0,1,...,t1) and likewise for I2 up to In, I would like to do something like
T.select(I1,...,In)=S
that will have the effect that now T has entries equal to the entries of S over the indices (I1,...,In).
for example
`S=
[[1,1],
[1,1]]
T=
[[0,0,0],
[0,0,0],
[0,0,0]]
T.select([0,2],[0,2])=S
T=
[[1,0,1],
[0,0,0],
[1,0,1]]`
If you're flexible with using NumPy only for the indices part, then here's one approach by constructing an open mesh using numpy.ix_() and using this mesh to fill-in the values from the tensor S. If this is not acceptable, then you can use torch.meshgrid()
Below is an illustration of both approaches with descriptions interspersed in comments.
# input tensors to work with
In [174]: T
Out[174]:
tensor([[0, 0, 0],
[0, 0, 0],
[0, 0, 0]])
# I'm using unique tensor just for clarity; But any tensor should work.
In [175]: S
Out[175]:
tensor([[10, 11],
[12, 13]])
# indices where we want the values from `S` to be filled in, along both dimensions
In [176]: idxs = [[0,2], [0,2]]
Now we will leverage np.ix_() or torch.meshgrid() to generate an open mesh by passing in the indices:
# mesh using `np.ix_`
In [177]: mesh = np.ix_(*idxs)
# as an alternative, we can use `torch.meshgrid()`
In [191]: mesh = torch.meshgrid([torch.tensor(lst) for lst in idxs])
# replace the values from tensor `S` using basic indexing
In [178]: T[mesh] = S
# sanity check!
In [179]: T
Out[179]:
tensor([[10, 0, 11],
[ 0, 0, 0],
[12, 0, 13]])

Slicing to change pixel color on an numpy.array image

On a previous question I asked how to change colors on an image uploaded on a numpy.array using opencv (BGR format). The goal was to convert any red channel value < 255 to 255.
How to optimize changing the value of 3d numpy.array if meet a condition
The best answer was:
img[img[:, :, 2] < 255] = 255
The point is that I am not understanding what is going on (This is a different question since the previous one was perfectly answered). I understand this part:
img[:, :, 2] < 255
Is slicing y, x pixels on the red channel and being compared to 255 value.
However, I do not understand why this is embedded in another array:
img[...] = 255
This has to be read something like: if (img[:, :, 2] < 255) then make this pixel-red channel=255 but I don't know how to read the code line to make it sound like that.
Can anyone explain me this code line very clear so I can modify for other purposes? How should I read it while writing it?
This is called logical indexing.
Let's break this into two parts:
c=img[:, :, 2] < 255 # construct a boolean array
All the indices where c is equal to 1 are used to index elements of img.
img[c, 2] = 255 # set img to 255 at indices in c equal to 1
As an example, assume you have this array A=[10, 20, 34, 90, 100] and you want to change all the elements in A that greater than 11 to 2.
To do this with logical indexing, you would first construct a boolean array like this:
b=A>11 # [0, 1, 1, 1, 1]
Then, use b to index elements of A
A[b]=2
The resulting array would look like this:
[10, 2, 2, 2, 2]
First of all, I don't think the expression does what you want: convert any red channel value < 255 to 255., it will instead convert the pixel of all three channels to 255 if the corresponding red channel has value less than 255, what you need if you only want to change the red channel only is:
img[img[:, :, 2] < 255, 2] = 255
Example:
img = np.arange(12).reshape(2,2,3)
img
# array([[[ 0, 1, 2],
# [ 3, 4, 5]],
# [[ 6, 7, 8],
# [ 9, 10, 11]]])
img[img[:,:,2] < 5,2] = 5
img
#array([[[ 0, 1, 5], # the red channel of this pixel is modified to 5
# [ 3, 4, 5]],
#
# [[ 6, 7, 8],
# [ 9, 10, 11]]])
This uses numpy boolean indexing. Simply speaking, the assignment does what your if statement is trying to do, replace values where the boolean mask(generated by img[:, :, 2] < 255) is true, and keep values where the mask is false.

numpy sum antidiagonals of array

Given a numpy ndarray, I would like to take the first two axes, and replace them with a new axis, which is the sum of their antidiagonals.
In particular, suppose I have variables x,y,z,..., and the entries of my array represent the probability
array[i,j,k,...] = P(x=i, y=j, z=k, ...)
I would like to obtain
new_array[l,k,...] = P(x+y=l, z=k, ...) = sum_i P(x=i, y=l-i, z=k, ...)
i.e., new_array[l,k,...] is the sum of all array[i,j,k,...] such that i+j=l.
What is the most efficient and/or cleanest way to do this in numpy?
EDIT to add:
On recommendation of #hpaulj, here is the obvious iterative solution:
array = numpy.arange(30).reshape((2,3,5))
array = array / float(array.sum()) # make it a probability
new_array = numpy.zeros([array.shape[0] + array.shape[1] - 1] + list(array.shape[2:]))
for i in range(array.shape[0]):
for j in range(array.shape[1]):
new_array[i+j,...] += array[i,j,...]
new_array.sum() # == 1
There is a trace function that gives the sum of a diagonal. You can specify the offset and 2 axes (0 and 1 are the defaults). And to get the antidiagonal, you just need to flip one dimension. np.flipud does that, though it's just [::-1,...] indexing.
Putting those together,
np.array([np.trace(np.flipud(array),offset=k) for k in range(-1,3)])
matches your new_array.
It still loops over the possible values of l (4 in this case). trace itself is compiled.
In this small case, it's actually slower than your double loop (2x3 steps). Even if I move the flipud out of the inner loop, it is still slower. I don't know how this scales for larger arrays.
Part of the problem with vectorizing this even further is that fact that each diagonal has a different length.
In [331]: %%timeit
array1 = array[::-1]
np.array([np.trace(array1,offset=k) for k in range(-1,3)])
.....:
10000 loops, best of 3: 87.4 µs per loop
In [332]: %%timeit
new_array = np.zeros([array.shape[0] + array.shape[1] - 1] + list(array.shape[2:]))
for i in range(2):
for j in range(3):
new_array[i+j] += array[i,j]
.....:
10000 loops, best of 3: 43.5 µs per loop
scipy.sparse has a dia format, which stores the values of nonzero diagonals. It stores a padded array of values, along with the offsets.
array([[12, 0, 0, 0],
[ 8, 13, 0, 0],
[ 4, 9, 14, 0],
[ 0, 5, 10, 15],
[ 0, 1, 6, 11],
[ 0, 0, 2, 7],
[ 0, 0, 0, 3]])
array([-3, -2, -1, 0, 1, 2, 3])
While that's a way of getting around the issue of variable diagonal lengths, I don't think it helps in this case where you just need their sums.

Finding the row with the highest average in a numpy array

Given the following array:
complete_matrix = numpy.array([
[0, 1, 2, 4],
[1, 0, 3, 5],
[2, 3, 0, 6],
[4, 5, 6, 0]])
I would like to identify the row with the highest average, excluding the diagonal zeros.
So, in this case, I would be able to identify complete_matrix[:,3] as being the row with the highest average.
Note that the presence of the zeros doesn't affect which row has the highest mean because all rows have the same number of elements. Therefore, we just take the mean of each row, and then ask for the index of the largest element.
#Take the mean along the 1st index, ie collapse into a Nx1 array of means
means = np.mean(complete_matrix, 1)
#Now just get the index of the largest mean
idx = np.argmax(means)
idx is now the index of the row with the highest mean!
You don't need to worry about the 0s, they shouldn't effect how the averages compare since there will presumably be one in each row. Hence, you can do something like this to get the index of the row with the highest average:
>>> import numpy as np
>>> complete_matrix = np.array([
... [0, 1, 2, 4],
... [1, 0, 3, 5],
... [2, 3, 0, 6],
... [4, 5, 6, 0]])
>>> np.argmax(np.mean(complete_matrix, axis=1))
3
Reference:
numpy.mean
numpy.argmax
As pointed out by a lot of people, presence of zeros isn't an issue as long as you have the same number of zeros in each column. Just in case your intention was to ignore all the zeros, preventing them from participating in the average computation, you could use weights to suppress the contribution of the zeros. The following solution assigns 0 weight to zero entries, 1 otherwise:
numpy.argmax(numpy.average(complete_matrix,axis=0, weights=complete_matrix!=0))
You can always create a weight matrix where the weight is 0 for diagonal entries, and 1 otherwise.
You will see that this answer actually would fit better to your other question that was marked as duplicated to this one (and don't know why because it is not the same question...)
The presence of zeros can indeed affect the columns' or rows' average, for instance:
a = np.array([[ 0, 1, 0.9, 1],
[0.9, 0, 1, 1],
[ 1, 1, 0, 0.5]])
Without eliminating the diagonals, it would tell that the column 3 has the highest average, but eliminating the diagonals the highest average belongs to column 1 and now column 3 has the least average of all columns!
You can correct the calculated mean using the lcm (least common multiple) of the number of lines with and without the diagonals, by guaranteeing that where a diagonal element does not exist the correction is not applied:
correction = column_sum/lcm(len(column), len(column)-1)
new_mean = mean + correction
I copied the algorithm for lcm from this answer and proposed a solution for your case:
import numpy as np
def gcd(a, b):
"""Return greatest common divisor using Euclid's Algorithm."""
while b:
a, b = b, a % b
return a
def lcm(a, b):
"""Return lowest common multiple."""
return a * b // gcd(a, b)
def mymean(a):
if len(a.diagonal()) < a.shape[1]:
tmp = np.hstack((a.diagonal()*0+1,0))
else:
tmp = a.diagonal()*0+1
return np.mean(a, axis=0) + np.sum(a,axis=0)*tmp/lcm(a.shape[0],a.shape[0]-1)
Testing with the a given above:
mymean(a)
#array([ 0.95 , 1. , 0.95 , 0.83333333])
With another example:
b = np.array([[ 0, 1, 0.9, 0],
[0.9, 0, 1, 1],
[ 1, 1, 0, 0.5],
[0.9, 0.2, 1, 0],
[ 1, 1, 0.7, 0.5]])
mymean(b)
#array([ 0.95, 0.8 , 0.9 , 0.5 ])
With the corrected average you just use np.argmax() to get the column index with the highest average. Similarly, np.argmin() to get the index of the column with the least average:
np.argmin(mymean(a))

Categories

Resources