Numpy array filled in diamond shape

Viewed 1453

I'm looking for a way to fill a numpy matrix in a diamond shape.

I know you can fill one in a circle shape like this:

radius = 1
y,x = np.ogrid[-radius: radius + 1, -radius: radius + 1]
matrix = x**2 + y**2 <= radius**2

Which give me this correct output:

[[False  True False]
 [ True  True  True]
 [False  True False]]

But obviously a circle is not a diamond and as the radius increases, the shape stops looking like a diamond.

4 Answers

Diamond mask with given matrix-length

Here's one leveraging broadcasting -

def diamond(n):
    a = np.arange(n)
    b = np.minimum(a,a[::-1])
    return (b[:,None]+b)>=(n-1)//2

Sample runs -

In [73]: diamond(3)
Out[73]: 
array([[False,  True, False],
       [ True,  True,  True],
       [False,  True, False]])

In [74]: diamond(4)
Out[74]: 
array([[False,  True,  True, False],
       [ True,  True,  True,  True],
       [ True,  True,  True,  True],
       [False,  True,  True, False]])

In [75]: diamond(5)
Out[75]: 
array([[False, False,  True, False, False],
       [False,  True,  True,  True, False],
       [ True,  True,  True,  True,  True],
       [False,  True,  True,  True, False],
       [False, False,  True, False, False]])

Diamond mask with given radius

For given radius, simplifies further -

def diamond2(r):
    b = np.r_[:r,r:-1:-1]
    return (b[:,None]+b)>=r

And that could be further simplified to a one-liner -

def diamond2(r):
    return np.add.outer(*[np.r_[:r,r:-1:-1]]*2)>=r

Sample runs -

In [19]: diamond2(1)
Out[19]: 
array([[False,  True, False],
       [ True,  True,  True],
       [False,  True, False]])

In [20]: diamond2(2)
Out[20]: 
array([[False, False,  True, False, False],
       [False,  True,  True,  True, False],
       [ True,  True,  True,  True,  True],
       [False,  True,  True,  True, False],
       [False, False,  True, False, False]])

In [21]: diamond2(3)
Out[21]: 
array([[False, False, False,  True, False, False, False],
       [False, False,  True,  True,  True, False, False],
       [False,  True,  True,  True,  True,  True, False],
       [ True,  True,  True,  True,  True,  True,  True],
       [False,  True,  True,  True,  True,  True, False],
       [False, False,  True,  True,  True, False, False],
       [False, False, False,  True, False, False, False]])

Right; you need to use two pairs of parallel lines, rather than a circle. Let n be your side length (quantity of rows).

half = n//2
matrix =  half <= x+y <= 3 * half and    # lower-left to upper-right borders
         -half <= x-y <= half            # upper-left to lower-right borders

The second line can be shortened with abs(x-y) <= half

I'll assume your matrix has an odd length. This is a different technique than what you are using, but you might still find it interesting. It generates triangle matrices that are then used to mask away the corners of a matrix of ones:

import numpy as np 

radius = 3 
length = 2*3 + 1 
allset = np.ones((length, length)) 
zeros_bottom_left  = np.triu(np.ones((length,length)),-((length-1)/2)) 
zeros_bottom_right = np.rot90(zeros_bottom_left, 1) 
zeros_top_right    = np.rot90(zeros_bottom_left, 2) 
zeros_top_left     = np.rot90(zeros_bottom_left, 3)  

diamond = np.ones((length, length)) * zeros_bottom_left * zeros_bottom_right *\
                                      zeros_top_left * zeros_top_right 
diamond.astype(bool)  

Here is a fast strides based method:

import numpy as np
from numpy.lib.stride_tricks import as_strided

# my (pp) approach
def diamond_fill(A,center=None,radius=None,fill=True,background=False):
    if hasattr(A,"__iter__"):
        if np.ndim(A) == 2:
            m,n = A.shape
        else:
            m,n = A
            A = np.full((m,n),background)
    else:
        m,n = A,A
        A = np.full((m,n),background)
    if center is None:
        cm,cn = (m-1)//2,(n-1)//2
    else:
        if hasattr(center,"__iter__"):
            cm,cn = center
        else:
            cm,cn = center,center
        cm %= m
        cn %= n
    if radius is None:
        radius = min(cm,cn,m-cm-1,n-cn-1)
    else:
        assert radius <= min(cm,cn,m-cm-1,n-cn-1)
    s,t = A.strides
    diamond = as_strided(A[cm:,cn-radius:],(2,radius+1,radius+1),(t,t-s,t+s))
    diamond[0] = fill
    diamond[1,:-1,:-1] = fill
    return A

# @Divakar
def diamond2(r):
    return np.add.outer(*[np.r_[:r,r:-1:-1]]*2)>=r

# @peer
def peer(radius):
    length = 2*radius + 1 
    zeros_bottom_left  = np.triu(np.ones((length,length)),-((length-1)/2)) 
    zeros_bottom_right = np.rot90(zeros_bottom_left, 1) 
    zeros_top_right    = np.rot90(zeros_bottom_left, 2) 
    zeros_top_left     = np.rot90(zeros_bottom_left, 3)  

    diamond = np.ones((length, length)) * zeros_bottom_left * zeros_bottom_right *\
                                          zeros_top_left * zeros_top_right 
    return diamond

# example    
print(diamond_fill(10,(4,5),4,'X','-'))

# benchmarks
from timeit import timeit

for R in (5,10,100,1000):
    assert np.all(diamond_fill(2*R+1)==diamond2(R))
    assert np.all(diamond_fill(2*R+1)==peer(R))
    print("pp     ",timeit(lambda:diamond_fill(2*R+1),number=100)*10,"ms")
    print("divakar",timeit(lambda:diamond2(R),number=100)*10,"ms")
    print("peer   ",timeit(lambda:peer(R),number=100)*10,"ms")

Sample run:

[['-' '-' '-' '-' '-' 'X' '-' '-' '-' '-']
 ['-' '-' '-' '-' 'X' 'X' 'X' '-' '-' '-']
 ['-' '-' '-' 'X' 'X' 'X' 'X' 'X' '-' '-']
 ['-' '-' 'X' 'X' 'X' 'X' 'X' 'X' 'X' '-']
 ['-' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X' 'X']
 ['-' '-' 'X' 'X' 'X' 'X' 'X' 'X' 'X' '-']
 ['-' '-' '-' 'X' 'X' 'X' 'X' 'X' '-' '-']
 ['-' '-' '-' '-' 'X' 'X' 'X' '-' '-' '-']
 ['-' '-' '-' '-' '-' 'X' '-' '-' '-' '-']
 ['-' '-' '-' '-' '-' '-' '-' '-' '-' '-']]
radius 5
pp      0.014590059872716665 ms
divakar 0.02198140005930327 ms
peer    0.0753312800952699 ms
radius 10
pp      0.015058760036481544 ms
divakar 0.023163620062405244 ms
peer    0.07851567002944648 ms
radius 100
pp      0.02892196993343532 ms
divakar 0.09013354996568523 ms
peer    0.5498108400206547 ms
radius 1000
pp      4.311415600386681 ms
divakar 11.687388699647272 ms
peer    68.75276724967989 ms
Related