I have an image and I depicted it as a matrix image which is composed of 0 and 1 (or True and False).
There are many 1 data(blue color) forming triangle grid, but some points are missing. What I want to do is
finding that missed red points. Is there effective and beautiful way to find red points in python?
I hope many python experts help this problem.
-
3can you also add reproducible input dataAkshay Sehgal– Akshay Sehgal2020-12-14 21:22:10 +00:00Commented Dec 14, 2020 at 21:22
-
also, is the same pattern existing in both axis? meaning, are 1s in axis=0 same distance than the 1s in axis=1?. In this example there are 3 zeros between each 1 in both directions.. Can there be a situation where in one direction there are 3 zeros between 1s and in another direction there is just 1 zero?Akshay Sehgal– Akshay Sehgal2020-12-14 21:30:32 +00:00Commented Dec 14, 2020 at 21:30
-
I have updated my solution with a detailed explanation. Once you post your input image, I will run the below method and add the results as part of my answer. For now, I have only used a dummy example, assuming that the repeating pattern you have here is non-symmetric (not a symmetric square matrix)Akshay Sehgal– Akshay Sehgal2020-12-14 23:18:40 +00:00Commented Dec 14, 2020 at 23:18
-
Added another example run on the solution fyr..Akshay Sehgal– Akshay Sehgal2020-12-14 23:49:00 +00:00Commented Dec 14, 2020 at 23:49
2 Answers
Interesting problem. Here is a way you can solve this. I have mentioned the complete code first with examples and then an explanation below that. This solution has the following advantages -
- Works on non-square 2D images
- No need for knowing the repeating pattern beforehand
- Can handle square and non-square repeating patterns of any shape
- Memory efficient and completely (almost) vectorized
NOTE: This approach only works if at least 1 complete, pure repeating pattern exists in the image.
import numpy as np
import matplotlib.pyplot as plt
def find_gaps(x):
#Identifying the size and shape of the repeating pattern
xpattern = int(x.shape[0]//np.max(x.sum(axis=0))+2)
ypattern = int(x.shape[1]//np.max(x.sum(axis=1))+2)
pattern_shape = (xpattern, ypattern)
#Calculating number of rolling windows that exist with that pattern
num_xpatterns = x.shape[0]//pattern_shape[0]+1
num_ypatterns = x.shape[1]//pattern_shape[1]+1
#Calculating the stride and shape that needs to be taken with stride_tricks
shp = (num_xpatterns, num_ypatterns, xpattern, ypattern) #(2, 2, 3, 5)
strd = (x.strides[0]*(xpattern-1), x.strides[1]*(ypattern-1), x.strides[0], x.strides[1]) #(144, 32, 72, 8)
#Generating rolling windows/convolutions over the image to separate the patterns.
convolve_pattern = np.lib.stride_tricks.as_strided(x, shape=shp, strides=strd)
#Assuming at least 1 untouched pattern exists, finding that pure pattern
pattern_sums = convolve_pattern.sum(axis=(-1,-2))
idx = np.unravel_index(np.argmax(pattern_sums), pattern_sums.shape)
truth_pattern = convolve_pattern[idx]
#Identifying the gaps by subtracting the convolved image with the truth pattern
gaps = convolve_pattern - truth_pattern[None, None, :, :]
#Setting the gaps as -1 directly into the location of memory of the original image
for i in np.argwhere(gaps==-1):
convolve_pattern[tuple(i)]=-1
return(x)
Here is an example with an image with non-square repeating patterns (3,5) and 2 gaps. Here value of -1 represents the gaps or missing 1s but that can be changed to what you want in the function -
#Image with non-square repeating patterns
img = np.array([[1., 0., 0., 0., 1., 0., 0., 0., 0.], #One gap added here
[0., 0., 1., 0., 0., 0., 1., 0., 0.],
[1., 0., 0., 0., 1., 0., 0., 0., 1.],
[0., 0., 1., 0., 0., 0., 0., 0., 0.], #One gap added here
[1., 0., 0., 0., 1., 0., 0., 0., 1.]])
plt.imshow(img)
plt.imshow(find_gaps(img))
Here is another example where I have added 4 gaps in a larger image with square repeating patterns -
img = np.array([[1., 0., 0., 0., 1., 0., 0., 0., 1., 0., 0., 0., 1., 0., 0., 0., 1.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 1., 0., 0., 0., 1., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0.], #One gap added here
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[1., 0., 0., 0., 1., 0., 0., 0., 1., 0., 0., 0., 1., 0., 0., 0., 1.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 1., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0.], #One gap added here
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[1., 0., 0., 0., 1., 0., 0., 0., 1., 0., 0., 0., 1., 0., 0., 0., 1.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 1., 0., 0., 0., 1., 0., 0., 0., 1., 0., 0., 0., 1., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[1., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0.]]) #Two gaps added here
plt.imshow(img)
plt.imshow(find_gaps(img))
Detailed Explanation
Let's assume an image with a non-square (different over axis0 and axis1) repeating pattern. Here the non-square pattern clearly seems to be of the shape (3,5)
x = np.array([[1., 0., 0., 0., 1., 0., 0., 0., 0.], #<- gap in last entry
[0., 0., 1., 0., 0., 0., 1., 0., 0.],
[1., 0., 0., 0., 1., 0., 0., 0., 1.],
[0., 0., 1., 0., 0., 0., 0., 0., 0.], #<- gap in 3rd last entry
[1., 0., 0., 0., 1., 0., 0., 0., 1.]])
The first step would be to find the shape of this untouched/pure pattern that should be repeating in the image. For this, you can calculate the max value of sum over both axes and divide the original shape with that (then add +2) to get (3,5)
xpattern = int(x.shape[0]//np.max(x.sum(axis=0))+2)
ypattern = int(x.shape[1]//np.max(x.sum(axis=1))+2)
pattern_shape = (xpattern, ypattern)
(3,5)
Now that you have the shape of the pattern, you want to perform rolling window operations to separate this shape in the original image. Here, you can see that the image comprises of this pattern repeating 4 times, 2 times over x-axis, and 2 times over y-axis. To calculate how many times the pattern would repeat in the image you can do this -
num_xpatterns = x.shape[0]//pattern_shape[0]+1
num_ypatterns = x.shape[1]//pattern_shape[1]+1
print(num_xpatterns, num_ypatterns)
(2,2)
Now, you have everything to let you build rolling windows using strides to separate the image into blocks of pattern size. The output shape of this array that contains the (3,5) shaped pattern blocks would be (2,2,3,5),i.e, 2 patterns repeat over x-axis and 2 patterns repeat over y-axis.
You can also calculate the strides that need to be taken to achieve this output array by multiplying the number of bytes in each direction with the pattern shape in each direction.
shp = (num_xpatterns, num_ypatterns, xpattern, ypattern) #(2, 2, 3, 5)
strd = (x.strides[0]*(xpattern-1), x.strides[1]*(ypattern-1), x.strides[0], x.strides[1]) #(144, 32, 72, 8)
convolve_pattern = np.lib.stride_tricks.as_strided(x, shape=shp, strides=strd)
convolve_pattern
array([[[[1., 0., 0., 0., 1.],
[0., 0., 1., 0., 0.],
[1., 0., 0., 0., 1.]],
[[1., 0., 0., 0., 0.],
[0., 0., 1., 0., 0.],
[1., 0., 0., 0., 1.]]],
[[[1., 0., 0., 0., 1.],
[0., 0., 1., 0., 0.],
[1., 0., 0., 0., 1.]],
[[1., 0., 0., 0., 1.],
[0., 0., 0., 0., 0.],
[1., 0., 0., 0., 1.]]]])
You have now separated the image into blocks of potential pure and impure patterns of the shape (3,5). Now, all you have to do is find a pure pattern, and compare it to every other pattern in this array of pattern blocks.
This can be done by the simple fact that the pure pattern, will have the largest sum among all other pattern blocks.
pattern_sums = convolve_pattern.sum(axis=(-1,-2))
idx = np.unravel_index(np.argmax(pattern_sums), pattern_sums.shape)
pure_pattern = convolve_pattern[idx]
pure_pattern
array([[1., 0., 0., 0., 1.],
[0., 0., 1., 0., 0.],
[1., 0., 0., 0., 1.]])
Subtracting this pure pattern block with every other pattern block using broadcasting, you reveal the positions of the gaps in each pattern block with a -1
gaps = convolve_pattern - pure_pattern[None, None, :, :]
gaps
array([[[[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.]],
[[ 0., 0., 0., 0., -1.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.]]],
[[[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.]],
[[ 0., 0., 0., 0., 0.],
[ 0., 0., -1., 0., 0.],
[ 0., 0., 0., 0., 0.]]]])
Finally, (and here comes the power of using the numpy view from the stride_tricks), you can identify the location of the gaps (-1) and simply set them as -1 (or what-ever you like) in the numpy view, thus modifying the original image as well, since the assignment has been made directly to the memory of the original image.
for i in np.argwhere(gaps==-1):
convolve_pattern[tuple(i)]=-1
print(x)
array([[ 1., 0., 0., 0., 1., 0., 0., 0., -1.], #<- Found gap 1!
[ 0., 0., 1., 0., 0., 0., 1., 0., 0.],
[ 1., 0., 0., 0., 1., 0., 0., 0., 1.],
[ 0., 0., 1., 0., 0., 0., -1., 0., 0.], #<- Found gap 2!
[ 1., 0., 0., 0., 1., 0., 0., 0., 1.]])
This is how you can identify gaps in an image with non-square repeating patterns.
4 Comments
With numpy things get very simple-
import numpy as np
rows, columns = 13, 17
m = np.zeros((rows, columns))
m[::4, ::4] = 1
m[2::4, 2::4] = 1
print(m)
[[1. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 1.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 1.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 1.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 1.]]
Now to find the indices where the 1 is missing-
np.argwhere(m != your_sample_matrix)
4 Comments
There are many 1 data(blue color) forming triangle grid, but some points are missing. What I want to do is finding that missed red points. But i do agree the question lacks clarity and specificity, (as i mentioned in my comments aboove as well)



