8

When testing if a numpy array c is member of a list of numpy arrays CNTS:

import numpy as np

c = np.array([[[ 75, 763]],
              [[ 57, 763]],
              [[ 57, 749]],
              [[ 75, 749]]])

CNTS = [np.array([[[  78, 1202]],
                  [[  63, 1202]],
                  [[  63, 1187]],
                  [[  78, 1187]]]),
        np.array([[[ 75, 763]],
                  [[ 57, 763]],
                  [[ 57, 749]],
                  [[ 75, 749]]]),
        np.array([[[ 72, 742]],
                  [[ 58, 742]],
                  [[ 57, 741]],
                  [[ 57, 727]],
                  [[ 58, 726]],
                  [[ 72, 726]]]),
        np.array([[[ 66, 194]],
                  [[ 51, 194]],
                  [[ 51, 179]],
                  [[ 66, 179]]])]

print(c in CNTS)

I get:

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

However, the answer is rather clear: c is exactly CNTS[1], so c in CNTS should return True!

How to correctly test if a numpy array is member of a list of numpy arrays?

The same problem happens when removing:

CNTS.remove(c)

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Application: test if an opencv contour (numpy array) is member of a list of contours, see for example Remove an opencv contour from a list of contours.

8
  • @DavidG the real problem (for the application) is not only test membership, but also remove a numpy array from a list of numpy arrays, I added more details to the question. It's not really a duplicate. Commented Oct 30, 2018 at 13:29
  • I think you can use bool(sum((map(lambda x: np.array_equal(x,c), CNTS)))) Commented Oct 30, 2018 at 13:30
  • 1
    @RudolfMorkovskyi. You could just use any, which would also do short-circuiting Commented Oct 30, 2018 at 13:31
  • Are all the arrays going to be the same size? Commented Oct 30, 2018 at 13:38
  • @MadPhysicist No, those arrays are contours, i.e. list of points, it can be a rectangle (4 points), pentagon (5 points), etc. Commented Oct 30, 2018 at 13:39

3 Answers 3

6

You are getting the error because in essentially invokes bool(c == x) on every element x of CNTS. It's the __bool__ conversion that is raising the error:

>>> c == CNTS[1]
array([[[ True,  True]],
       [[ True,  True]],
       [[ True,  True]],
       [[ True,  True]]])

>>> bool(_)
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

The same applies for removal, since it tests for equality with each element.

Containment

The solution is to use np.array_equal or apply the all method to each comparison:

any(np.array_equal(c, x) for x in CNTS)

OR

any((c == x).all() for x in CNTS)

Removal

To perform the removal, you are more interested in the index of the element than its existence. The fastest way I can think of is to iterate over the indices, using the elements of CNTS as comparison keys:

index = next((i for i, x in enumerate(CNTS) if (c == x).all()), -1)

This option short circuits quite nicely, and returns -1 as the default index rather than raising a StopIteration. You can remove the argument -1 to next if you prefer the error. If you prefer, you can replace (c == x).all() with np.array_equal(c, x).

Now you can remove as usual:

del CNTS[index]
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you @MadPhysicist. As I mentioned in the edited question, the goal is to test membership to finally remove it (if present in the list of numpy arrays). How would you do the latter?
@Basj. Updated to include removal
Weird indeed (I upvoted all the 3 answers) @MadPhysicist!
3

This solution could work for this case:

def arrayisin(array, list_of_arrays):
    for a in list_of_arrays:
        if np.array_equal(array, a):
            return True
    return False

This function iterates over a list of arrays and tests the equality against some other array. So the usage would be:

>>> arrayisin(c, CNTS)
True

To remove the array from the list, you can get the index of the array and then use list.pop. In the function get_index, we enumerate the list of arrays, meaning we zip the indices of the list and the contents of the list. If there is a match, we return the index of the match.

def get_index(array, list_of_arrays):
    for j, a in enumerate(list_of_arrays):
        if np.array_equal(array, a):
            return j
    return None

idx = get_index(c, CNTS)  # 1
CNTS.pop(idx)

Please see the python data structures tutorial for the documentation of list.pop https://docs.python.org/3/tutorial/datastructures.html

1 Comment

Thank you @Jakub. As I mentioned in the question, the goal is to test membership to finally remove it (if present in the list of numpy arrays). How would you do that?
1

Use del to delete the index of the list you want to remove.

del CNTS[int(np.where(list(np.array_equal(row, c) for row in CNTS))[0])]

CNTS

[array([[[  78, 1202]],

        [[  63, 1202]],

        [[  63, 1187]],

        [[  78, 1187]]]), array([[[ 72, 742]],

        [[ 58, 742]],

        [[ 57, 741]],

        [[ 57, 727]],

        [[ 58, 726]],

        [[ 72, 726]]]), array([[[ 66, 194]],

        [[ 51, 194]],

        [[ 51, 179]],

        [[ 66, 179]]])]

6 Comments

This does not short circuit, and creates an extra numpy array, just to call np.where...
Thanks @MadPhysicist, answer modified
I tried running your updated code and it does not work as advertised. np.where will wrap any input in an array. Generators do not get expanded, so it gives you index 0 no matter what (since a generator object is truthy).
I would recommend going back to your original answer with list. While not optimal, it worked perfectly.
int(np.where(list(np.array_equal(row, c) for row in CNTS))[0]) -> 1, int(np.where((np.array_equal(row, c) for row in CNTS))[0]) -> 0. I'm using numpy 1.14.2. Perhaps in version 15, generators are expanded?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.