Mask a circular sector in a numpy array
Asked Answered
R

2

14

I have a code that slices a numpy array into a circle. I wish to recover only the values included in a certain range of angles from the circle and mask the array. For example: mask the original array with the (x,y) positions comprised between 0 and 45 degrees of the circle.

Is there a pythonic way for doing so?

Here's my (simplified) original code:

import numpy as np
matrix = np.zeros((500,500))
x = 240
y = 280
radius = 10
mask=np.ogrid[x-radius:x+radius+1,y-radius:y+radius+1]
matrix[mask]

Thanks in advance

Edit: I omitted that radius can vary.

Rosio answered 21/8, 2013 at 8:54 Comment(5)
Your code will mask a square in the array rather than a circle - is it definitely a circle that you want?Shockey
Yes, that is. I see my error and I'm trying to solve it!Rosio
maybe this is a duplicate of https://mcmap.net/q/400155/-how-to-apply-a-disc-shaped-mask-to-a-numpy-array/832621Ataractic
@SaulloCastro in this case it's a circular sector rather than just a circleShockey
@Shockey thank you... I was not sure if it was a duplicate or not!Ataractic
S
32

I would do this by converting from cartesian to polar coordinates and constructing boolean masks for the circle and for the range of angles you want:

import numpy as np

def sector_mask(shape,centre,radius,angle_range):
    """
    Return a boolean mask for a circular sector. The start/stop angles in  
    `angle_range` should be given in clockwise order.
    """

    x,y = np.ogrid[:shape[0],:shape[1]]
    cx,cy = centre
    tmin,tmax = np.deg2rad(angle_range)

    # ensure stop angle > start angle
    if tmax < tmin:
            tmax += 2*np.pi

    # convert cartesian --> polar coordinates
    r2 = (x-cx)*(x-cx) + (y-cy)*(y-cy)
    theta = np.arctan2(x-cx,y-cy) - tmin

    # wrap angles between 0 and 2*pi
    theta %= (2*np.pi)

    # circular mask
    circmask = r2 <= radius*radius

    # angular mask
    anglemask = theta <= (tmax-tmin)

    return circmask*anglemask

For example:

from matplotlib import pyplot as pp
from scipy.misc import lena

matrix = lena()
mask = sector_mask(matrix.shape,(200,100),300,(0,50))
matrix[~mask] = 0
pp.imshow(matrix)
pp.show()

enter image description here

Shockey answered 21/8, 2013 at 8:54 Comment(3)
@Shockey +1. I hope you don't mind, I added the picture into the answer so we can see exactly what is going on.Cloddish
theta = np.arctan2(x-cx,y-cy) must be changed into all positive angles using theta = np.where(theta<0,2*pi+theta,theta) otherwise the code above will not work correctly for angles higher than 180.Lonlona
@Lonlona well spotted, I've edited my function to address this caseShockey
F
1

Same approach for centered circles in square matrices:

def circleMask(mat, r=0):
    if mat.shape[0] != mat.shape[1]:
        raise TypeError('Matrix has to be square')
    if not isinstance(r, int):
        raise TypeError('Radius has to be of type int')

    s = mat.shape[0]
    d = num.abs(num.arange(-s/2 + s%2, s/2 + s%2))
    dm = num.sqrt(d[:, num.newaxis]**2 + d[num.newaxis, :]**2)

    return num.logical_and(dm >= r-.5, dm < r+.5)

looping over this implicit function is costly!

Feline answered 19/1, 2017 at 13:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.