Others have suggested logical_and, but you have objected that it involves too much copying. But first let's set up an interative case that does this
In [353]: arr=np.zeros((10,10))
In [354]: arr[3:7,3:7]=1
In [355]: tups=[(slice(5),slice(5)),
(slice(0,5),slice(3,8)),
(slice(4,9),slice(1,6))]
In [356]: for i,tup in enumerate(tups):
mask1=np.logical_and(mask,arr[tup]==0)
arr[tup][mask1]=i+1
.....:
In [357]: arr
Out[357]:
array([[ 1., 1., 1., 1., 1., 2., 2., 2., 0., 0.],
[ 1., 1., 1., 1., 1., 2., 2., 2., 0., 0.],
[ 1., 1., 1., 1., 1., 2., 2., 2., 0., 0.],
[ 1., 1., 1., 1., 1., 1., 1., 2., 0., 0.],
[ 1., 1., 1., 1., 1., 1., 1., 2., 0., 0.],
[ 0., 3., 3., 1., 1., 1., 1., 0., 0., 0.],
[ 0., 3., 3., 1., 1., 1., 1., 0., 0., 0.],
[ 0., 3., 3., 3., 3., 3., 0., 0., 0., 0.],
[ 0., 3., 3., 3., 3., 3., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])
arr[tup]==0 is another mask. It's the only way you can tell numpy that you are interested in changing only the 0s. It does not automatically treat 0s differently from 1s or 3s. I don't see any way around using logical_and to create a new mask at each step.
Application of a boolean mask does involve flat indexing - that is, the result is a 1d array (whether on the right or left hand side)
Look at the result of applying the masks from that last iteration
In [360]: arr[tup][mask]
Out[360]:
array([ 1., 1., 1., 1., 1., 3., 3., 1., 1., 1., 3., 3., 1.,
1., 1., 3., 3., 3., 3., 3., 3., 3., 3., 3., 3.])
In [361]: arr[tup][mask1]
Out[361]: array([ 3., 3., 3., 3., 3., 3., 3., 3., 3., 3., 3., 3., 3., 3.])
Here's an alternative using np.where:
for i,tup in enumerate(tups):
arr[tup]=np.where(arr[tup]==0,i+1,arr[tup])
That's more concise, but involves writing the whole arr[tup] slice each time.
In [374]: %%timeit arr=np.zeros((10,10),int);arr[3:7,3:7]=1
.....: for i,tup in enumerate(tups):
arr[tup]=np.where(arr[tup]==0,i+1,arr[tup])
.....:
1000 loops, best of 3: 134 us per loop
In [375]: %%timeit arr=np.zeros((10,10),int);arr[3:7,3:7]=1
.....: for i,tup in enumerate(tups):
mask1=np.logical_and(mask,arr[tup]==0)
arr[tup][mask1]=i+1p
.....:
10000 loops, best of 3: 64.9 us per loop
Warning, when using arr[tup][mask]=..., arr[tup] must be a view, such as produced by slicing. Other indexing produces a copy, which blocks changes to the original array.
mask = np.logical_and(mask, arr == 0)