2

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.

enter image description here

4
  • 3
    can you also add reproducible input data Commented 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? Commented 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) Commented Dec 14, 2020 at 23:18
  • Added another example run on the solution fyr.. Commented Dec 14, 2020 at 23:49

2 Answers 2

3

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 -

  1. Works on non-square 2D images
  2. No need for knowing the repeating pattern beforehand
  3. Can handle square and non-square repeating patterns of any shape
  4. 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)

enter image description here

plt.imshow(find_gaps(img))

enter image description here

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)

enter image description here

plt.imshow(find_gaps(img))

enter image description here


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.


Sign up to request clarification or add additional context in comments.

4 Comments

wow~ this is amazing solution. I don't know what to say. I never thought in that manner. I hope you enjoy coffee :)
Thank you, glad to help! this was a fun problem to solve. Do note, there are some constraints still on where this can work. If you have specific rules for the patterns and images do update your question so I can help with those edge cases as well.
Also worth noting that this works for patterns with non touching pixels only. For example, it does not work for a pattern made of circles made of multiple pixels, due to the calculation of the pattern size
@Liris, that is true!, The updated solution for the same problem above is posted here - akshaysehgal.substack.com/p/stack-puzzle-finding-patterns-in
3

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

Wouldn't you have to know exactly what pattern is repeating in the image for this? What if you have a bunch of images with different patterns or a non-square repeating pattern?
@AkshaySehgal That is a possibility, but here the solution I have provided is based just on the details what OP has provided. Most of the times the problems/questions posted on SO are very specific in nature. Also, the question here doesn't ask for a way to detect pattern but rather to detect abnormalities/missing values in a pattern that is already known.
agreed, but the assumption in your answer is that the pattern is already known and you are finding abnormalities in it right? I doubt that is what OP needs since he mentioned 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)
@AkshaySehgal Yes you are right, I have proceeded based on those assumptions. Though the solution I have provided is very specific, I think it addresses the requirements completely. Let's see what the OP has to say about.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.