How to convert slice object to boolean index?

Viewed 251

TL;DR:

Knowing the value of NUM_ROWS, rmin and rmax, how do I construct a bool array my_idx such that np.arange(NUM_ROWS)[my_idx] == np.arange(NUM_ROWS)[rmin:rmax]? Can the construction operation be broadcasted, if rmin and rmax are arrays ad I'm interested in all slices [slice(from, to) for from, to in zip(rmin, rmax)]?


Long version with details

I have an array of polygons in a 2D image and I want to find rows and columns of the image that don't contain a polygon. In order to do this fast, I'm trying to vectorize the code as much as possible.

I calculate the extreme points of each polygon on both dimension and obtain for each polygon the min_row, min_col, max_row and max_col values. Let's consider just the rows (as for the columns it 's the same algorithm) and assume that, for example, these are the values I obtain for two polygons:

NUM_ROWS = 10
# Two intervals: slice(1,5) and slice(7,8)
row_mins = np.array([1, 7], dtype=np.int32)
row_maxs = np.array([5, 8], dtype=np.int32)

I want now to merge the intervals in a way equivalent to:

row_mask = np.zeros(NUM_ROWS)
for rmin, rmax in zip(row_mins, row_maxs):
  row_mask[rmin:rmax] = 1

however, it should avoid the for loop and repeated setting of values in row_mask. I thought of doing this by turning each range into a bool array and using np.logical_or.reduce(), but I can't find a way to generate the bool array equivalent to the [rmin:rmax] index. Is there a way to convert a slice object to a bool index?

2 Answers

EDIT: Found the right way to do it.

I stand corrected. There IS a way to unpack a list of slices inside np.r_ and its as simple as using a tuple(). That means, once you have your slices mapped to the rmin and rmax arrays, you can simply convert them into an array with np.r_ and use that to update the values of the mask to 1.

import numpy as np

NUM_ROWS = 15

## 3 slices (1:5), (7:10), (12:14)
row_mins = np.array([1, 7, 12])
row_maxs = np.array([5, 10, 14])

mask = np.zeros(NUM_ROWS) #Zeros
slices = list(map(slice,row_mins,row_maxs)) #List of slices
mask[np.r_[tuple(slices)]]=1     #get ranges from list of slices and then update mask
mask
array([0., 1., 1., 1., 1., 0., 0., 1., 1., 1., 0., 0., 1., 1., 0.])

Old method I recommended -

If you want to make a mask with multiple slices, then you can do this without a for loop (vectorized), by using np.hstack with np.arange to get all the indexes and then set them to 1.

import numpy as np

NUM_ROWS = 15

## 3 slices (1:5), (7:10), (12:14)
row_mins = np.array([1, 7, 12])
row_maxs = np.array([5, 10, 14])

mask = np.zeros(NUM_ROWS)  #Zeros
idx = np.hstack(list(map(np.arange,row_mins,row_maxs)))  #Indexes to choose
mask[idx]=1  #Set to 1
mask
array([0., 1., 1., 1., 1., 0., 0., 1., 1., 1., 0., 0., 1., 1., 0.])

EDIT: Another way -

You could use np.eye() -

s = slice(1,4)

mask = np.eye(10)[s].sum(0)
print(mask)
[0. 1. 1. 1. 0. 0. 0. 0. 0. 0.]

Over a list of slices -

masks = [np.eye(NUM_ROWS)[slice(i,j)].sum(0) for i,j in zip(row_mins, row_maxs)]
final = np.logical_or.reduce(masks)
final
array([False,  True,  True,  True,  True, False, False,  True,  True,
        True, False, False,  True,  True, False])

Hope this helps:

arr = np.arange(NUM_ROWS)
bool_indices = (arr >= rmin) & (arr < rmax)

As you are looking for the intersection between the two, a logical and between them should create that array.

Using the rest of your solution:

arrs = [(b >= rmin) & (b<rmax) for rmin,rmax in zip(row_mins,row_maxs)]
mask = np.logical_or.reduce(arrs)

Related