Here is a program that processes 2 grayscale images. Both represent the position of objects, in the form of multiple masks per image. Each mask is a shape with one color delimiting it, with 0 (or black) for the background. Each object uses the color available in order when created, so if there are n object, the color from 1 to n are used.
The first image is the result of an AI prediction, while the second is estimated by a user. I want to match objetcs between the 2 images, since numbering is not done in the same order in both. I thus matched the objects, in one of three categories :
- no match
- exactly one match
- more than one match
This is done by first extracting a boolean image of the position for each mask, storing them in 2 lists, and summing (equivalent to and in bool) each combinaition to see if they overlap. I don't have multiple matches in this exemple, but there are in other cases. For exactly 1-1 match, I do the numbering again for img_2, by matching it with what is stored in one.
The code is working as intended, but it is a bit slow when the image size gets bigger, and especially when the number of objects gets higher. The matching part is the main culprit, since its complexity is O(n²). It takes about 2min for images with 250 objects (is it possible to share those? I can't create them in the code as I did here), and it is most likely the upper bound of what I'll encounter.
My question is : is it possible to reduce the complexity of the matching part of my code ?
Any feedback on the rest of code is also welcomed, although not my main concern.
import numpy as np
import matplotlib.pyplot as plt
### Creating both sample images
img_1 = np.array([[0]*27+[1]*22+[0]*10,
[0]*27+[1]*22+[0]*10,
[0]*27+[1]*22+[0]*10,
[0]*28+[1]*20+[0]*11,
[0]*28+[1]*20+[0]*11,
[0]*29+[1]*18+[0]*12,
[0]*29+[1]*18+[0]*12,
[0]*30+[1]*16+[0]*13,
[0]*12+[3]*2+[0]*18+[1]*12+[0]*15,
[0]*11+[3]*4+[0]*19+[1]*8+[0]*17,
[0]*10+[3]*6+[0]*43,
[0]*10+[3]*6+[0]*43,
[0]*10+[3]*6+[0]*43,
[0]*11+[3]*4+[0]*44,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*12+[2]*2+[0]*45,
[0]*10+[2]*6+[0]*43,
[0]*8+[2]*10+[0]*41,
[0]*7+[2]*12+[0]*40,
[0]*5+[2]*16+[0]*38,
[0]*4+[2]*18+[0]*37,
[0]*3+[2]*20+[0]*36,
[0]*3+[2]*20+[0]*36,
[0]*3+[2]*20+[0]*36,
[0]*3+[2]*20+[0]*36,
[0]*3+[2]*20+[0]*36,
[0]*3+[2]*20+[0]*36,
[0]*3+[2]*20+[0]*36,
[0]*3+[2]*20+[0]*36,
[0]*3+[2]*20+[0]*36,
[0]*3+[2]*20+[0]*36,
[0]*3+[2]*20+[0]*36,
[0]*3+[2]*20+[0]*36,
[0]*4+[2]*18+[0]*37,
[0]*5+[2]*16+[0]*38,
[0]*7+[2]*12+[0]*40,
[0]*8+[2]*10+[0]*41,
[0]*10+[2]*6+[0]*43,
[0]*11+[2]*4+[0]*44,
[0]*59,
[0]*59],dtype=np.int32)
img_2 = np.array([[0]*24+[3]*23+[0]*12,
[0]*23+[3]*25+[0]*11,
[0]*23+[3]*25+[0]*11,
[0]*21+[2]*2+[3]*25+[0]*11,
[0]*20+[2]*3+[3]*25+[0]*11,
[0]*19+[2]*4+[3]*25+[0]*11,
[0]*18+[2]*5+[3]*25+[0]*11,
[0]*18+[2]*6+[3]*23+[0]*12,
[0]*18+[2]*6+[3]*23+[0]*12,
[0]*18+[2]*7+[3]*21+[0]*13,
[0]*19+[2]*7+[3]*19+[0]*14,
[0]*20+[2]*7+[3]*17+[0]*15,
[0]*21+[2]*5+[0]*3+[3]*13+[0]*17,
[0]*32+[3]*7+[0]*20,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*59,
[0]*13+[1]*3+[0]*43,
[0]*11+[1]*6+[0]*42,
[0]*9+[1]*10+[0]*40,
[0]*8+[1]*12+[0]*39,
[0]*6+[1]*16+[0]*37,
[0]*4+[1]*20+[0]*35,
[0]*3+[1]*22+[0]*34,
[0]*2+[1]*24+[0]*33,
[0]*2+[1]*24+[0]*33,
[0]*2+[1]*24+[0]*33,
[0]*2+[1]*24+[0]*33,
[0]*2+[1]*24+[0]*33,
[0]*2+[1]*24+[0]*33,
[0]*2+[1]*24+[0]*33,
[0]*2+[1]*24+[0]*33,
[0]*2+[1]*24+[0]*33,
[0]*2+[1]*24+[0]*33,
[0]*2+[1]*24+[0]*33,
[0]*2+[1]*24+[0]*33,
[0]*2+[1]*24+[0]*33,
[0]*2+[1]*24+[0]*33,
[0]*3+[1]*22+[0]*34,
[0]*4+[1]*20+[0]*35,
[0]*6+[1]*16+[0]*37,
[0]*7+[1]*14+[0]*38,
[0]*9+[1]*10+[0]*40,
[0]*11+[1]*6+[0]*42,
[0]*12+[1]*4+[0]*43,
[0]*59,
[0]*59,
[0]*59],dtype=np.int32)
### Side to side images
print("Number of items predicted in img_1 :",len(np.unique(img_1)))
print("Number of items in ground-truth, img_2 :",len(np.unique(img_2)))
plt.figure(0)
plt.subplot(1,2,1), plt.imshow(img_1)
plt.axis("off")
plt.subplot(1,2,2), plt.imshow(img_2)
plt.axis("off")
### Creating one boolean image for each mask
masks_1 = []
masks_2 = []
leng = max(len(np.unique(img_1)), len(np.unique(img_2)))
for k in range(1,leng):
mask_1 = img_1==k
masks_1.append(mask_1)
mask_2 = img_2==k
masks_2.append(mask_2)
### Each possible combination, from img_1 compared to img_2, and the opposite
zero=[] # No match from img_1 to img_2
one={} # Exactly one match from img_1 to img_2
more={} # More than one match from img_1 to img_2
zero_2=[] # Same as zero but from img_2 to img_1
one_2={} # Same as one but from img_2 to img_1
more_2={} # Same as more but from img_2 to img_1
for i in range(leng-1):
number_match = 0
number_match_2 = 0
index_match = []
index_match_2 = []
### Checks if there are pixels in common in both images, and the total number
for j in range(leng-1):
if not (sum(sum(masks_1[i]*masks_2[j]))==0):
number_match += 1
index_match.append(j)
if not (sum(sum(masks_2[i]*masks_1[j]))==0):
number_match_2 += 1
index_match_2.append(j)
### Sorts each index depending on the number of match
if number_match==0:
zero.append(i)
elif number_match>1:
more[i] = index_match
elif number_match==1:
one[i] = index_match[0]
if number_match_2==0:
zero_2.append(i)
elif number_match_2>1:
more_2[i] = index_match_2
elif number_match_2==1:
one_2[i] = index_match_2[0]
### Remove pairs if it's not a 1-1 match
temp = []
for m in one.keys():
if one_2.get(one[m]) is None:
if more_2.get(one[m]) is None:
zero_2.append(one[m])
zero.append(m)
temp.append(m)
else:
more.update({m:one[m]})
temp.append(m)
for k in range(len(temp)):
del one[temp[k]]
temp = []
for n in one_2.keys():
if one.get(one_2[n]) is None:
if more.get(one_2[n]) is None:
zero.append(one_2[n])
zero_2.append(n)
temp.append(n)
else:
more_2.update({n:one_2[n]})
temp.append(n)
for k in range(len(temp)):
del one_2[temp[k]]
### Second pair of image, with all 1-1 match
xSize, ySize = np.shape(img_1)
img_1 = np.zeros((xSize,ySize),dtype=np.int32)
img_2 = np.zeros((xSize,ySize),dtype=np.int32)
if bool(one):
lis = list(one.keys())
for m in range(len(lis)):
img_1 = img_1+(masks_1[lis[m]])*(lis[m]+1)
if bool(one_2):
lis2 = list(one_2.keys())
for n in range(len(lis2)):
img_2 = img_2+(masks_2[lis2[n]])*(one_2[lis2[n]]+1)
plt.figure(1)
plt.subplot(1,2,1), plt.imshow(img_1)
plt.axis("off")
plt.subplot(1,2,2), plt.imshow(img_2)
plt.axis("off")

np.unique(img_1)produces[0, 1, 2, 3], but in your mask loop you start at 1, so 0 will never get a mask. Is this intentional? \$\endgroup\$