Found 2026-08-03 while fixing the OpenCV 4/5 `ids` shape compat issue in the same method (see #44). `ImageFiducials.py`'s `FiducialCollection.estimatePose` does:
```python
cornerss = [
corners.T.squeeze()
for corners, id in zip(cornerss, ids)
if id in self._ids
]
ids = [id for corners, id in zip(cornerss, ids) if id in self._ids]
```
The second line's `zip(cornerss, ids)` pairs the already-filtered `cornerss` (reassigned on the line above) against the original, unfiltered `ids`. If any detected marker's ID isn't in `self._ids` (exactly the scenario the docstring calls out -- "filter the markers, useful if there are several ArUco boards in the scene"), the two lists have different lengths and `zip` silently pairs mismatched corners/ids from that point on. Only harmless when every detected marker happens to belong to this board (no actual filtering occurs), which is presumably why it hasn't been caught yet.
Fix
Compute the filtered `cornerss`/`ids` together in a single pass (e.g. one list comprehension producing tuples, then unzip), so both lists are always built from the same original, unfiltered pairing. Needs a test with multiple boards/extraneous marker IDs in view to catch a regression -- there's currently no coverage for the multi-board filtering case at all.
Found 2026-08-03 while fixing the OpenCV 4/5 `ids` shape compat issue in the same method (see #44). `ImageFiducials.py`'s `FiducialCollection.estimatePose` does:
```python
cornerss = [
corners.T.squeeze()
for corners, id in zip(cornerss, ids)
if id in self._ids
]
ids = [id for corners, id in zip(cornerss, ids) if id in self._ids]
```
The second line's `zip(cornerss, ids)` pairs the already-filtered `cornerss` (reassigned on the line above) against the original, unfiltered `ids`. If any detected marker's ID isn't in `self._ids` (exactly the scenario the docstring calls out -- "filter the markers, useful if there are several ArUco boards in the scene"), the two lists have different lengths and `zip` silently pairs mismatched corners/ids from that point on. Only harmless when every detected marker happens to belong to this board (no actual filtering occurs), which is presumably why it hasn't been caught yet.
Fix
Compute the filtered `cornerss`/`ids` together in a single pass (e.g. one list comprehension producing tuples, then unzip), so both lists are always built from the same original, unfiltered pairing. Needs a test with multiple boards/extraneous marker IDs in view to catch a regression -- there's currently no coverage for the multi-board filtering case at all.