From 6836c85d9f921761cba313627428d41d5fc54120 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 14:44:13 +0000 Subject: [PATCH 1/3] Initial plan From a0a7bde00f637d40d33fec7bb1f781ead22f3c49 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 14:47:05 +0000 Subject: [PATCH 2/3] Fix marker ID normalization and add CI test diagnostics artifacts Co-authored-by: qian-chu <97355086+qian-chu@users.noreply.github.com> --- .github/workflows/main.yml | 20 ++++++++++++++++++-- pyneon/video/marker.py | 10 ++++++++-- tests/test_video_marker.py | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 tests/test_video_marker.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 34491aa..1744bed 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,13 +64,29 @@ jobs: - name: Run tests if: matrix.os != 'windows-latest' - run: pytest tests -p no:cacheprovider -p no:faulthandler -p no:unraisableexception + run: | + mkdir -p "${{ github.workspace }}/test-reports" + pytest -vv --tb=long \ + --junitxml="${{ github.workspace }}/test-reports/junit-${{ matrix.os }}-py${{ matrix.python-version }}.xml" \ + tests -p no:cacheprovider -p no:faulthandler -p no:unraisableexception - name: Run tests (Windows, disable MSMF) if: matrix.os == 'windows-latest' env: OPENCV_VIDEOIO_PRIORITY_MSMF: "0" - run: pytest tests -p no:cacheprovider -p no:faulthandler -p no:unraisableexception + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path "${{ github.workspace }}/test-reports" | Out-Null + pytest -vv --tb=long ` + --junitxml="${{ github.workspace }}/test-reports/junit-${{ matrix.os }}-py${{ matrix.python-version }}.xml" ` + tests -p no:cacheprovider -p no:faulthandler -p no:unraisableexception + + - name: Upload test report + if: always() + uses: actions/upload-artifact@v4 + with: + name: pytest-junit-${{ matrix.os }}-py${{ matrix.python-version }} + path: ${{ github.workspace }}/test-reports/junit-${{ matrix.os }}-py${{ matrix.python-version }}.xml build-docs: runs-on: ubuntu-latest diff --git a/pyneon/video/marker.py b/pyneon/video/marker.py index ace7c10..079e7ec 100644 --- a/pyneon/video/marker.py +++ b/pyneon/video/marker.py @@ -78,6 +78,11 @@ def generate_marker( return img +def _normalize_marker_id(marker_id: int | np.ndarray) -> int: + """Normalize marker IDs returned by OpenCV across shape variants.""" + return int(np.asarray(marker_id).reshape(-1)[0]) + + @fill_doc def detect_markers( video: "Video", @@ -133,6 +138,7 @@ def _process_frame(frame_idx: int, gray_frame: np.ndarray) -> list[dict]: for corners, marker_id in zip(all_corners, all_ids): corners = corners.reshape((4, 2)) + marker_id_int = _normalize_marker_id(marker_id) if fam_type == "april": # For AprilTags, corners start with bottom right # For ArUco, corners start with top left @@ -147,8 +153,8 @@ def _process_frame(frame_idx: int, gray_frame: np.ndarray) -> list[dict]: "timestamp [ns]": video.ts[frame_idx], "frame index": frame_idx, "marker family": fam_name, - "marker id": int(marker_id[0]), - "marker name": f"{fam_name}_{marker_id[0]}", + "marker id": marker_id_int, + "marker name": f"{fam_name}_{marker_id_int}", "top left x [px]": corners[0, 0], "top left y [px]": corners[0, 1], "top right x [px]": corners[1, 0], diff --git a/tests/test_video_marker.py b/tests/test_video_marker.py new file mode 100644 index 0000000..57be293 --- /dev/null +++ b/tests/test_video_marker.py @@ -0,0 +1,36 @@ +import numpy as np +import pytest + +from pyneon.video import marker + + +class _DummyVideo: + def __init__(self): + self.ts = np.array([123], dtype=np.int64) + + def reset(self): + return None + + def read_frame_at(self, frame_index: int): + return np.zeros((2, 2, 3), dtype=np.uint8) + + +@pytest.mark.parametrize("raw_marker_id", [7, np.array(7), np.array([7])]) +def test_detect_markers_normalizes_marker_id_shapes(monkeypatch, raw_marker_id): + class _DummyDetector: + def detectMarkers(self, gray_frame): + corners = [ + np.array( + [[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]], + dtype=np.float32, + ) + ] + return corners, [raw_marker_id], None + + monkeypatch.setattr(marker, "marker_family_to_dict", lambda _: ("aruco", object())) + monkeypatch.setattr(marker.cv2.aruco, "ArucoDetector", lambda *_: _DummyDetector()) + + detections = marker.detect_markers(_DummyVideo(), marker_family="5x5_50") + + assert int(detections.data.iloc[0]["marker id"]) == 7 + assert detections.data.iloc[0]["marker name"] == "5x5_50_7" From 2b4f84b503f5caec1ce054fc5fadc412cc228488 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 14:49:08 +0000 Subject: [PATCH 3/3] Add marker ID regression tests and CI JUnit artifact upload Co-authored-by: qian-chu <97355086+qian-chu@users.noreply.github.com> --- tests/test_video_marker.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/test_video_marker.py b/tests/test_video_marker.py index 57be293..19b575c 100644 --- a/tests/test_video_marker.py +++ b/tests/test_video_marker.py @@ -15,6 +15,13 @@ def read_frame_at(self, frame_index: int): return np.zeros((2, 2, 3), dtype=np.uint8) +@pytest.mark.parametrize("raw_marker_id", [7, np.array(7), np.array([7])]) +def test_normalize_marker_id_shapes(raw_marker_id): + normalized_marker_id = marker._normalize_marker_id(raw_marker_id) + assert isinstance(normalized_marker_id, int) + assert normalized_marker_id == 7 + + @pytest.mark.parametrize("raw_marker_id", [7, np.array(7), np.array([7])]) def test_detect_markers_normalizes_marker_id_shapes(monkeypatch, raw_marker_id): class _DummyDetector: @@ -32,5 +39,5 @@ def detectMarkers(self, gray_frame): detections = marker.detect_markers(_DummyVideo(), marker_family="5x5_50") - assert int(detections.data.iloc[0]["marker id"]) == 7 + assert detections.data.iloc[0]["marker id"] == "7" assert detections.data.iloc[0]["marker name"] == "5x5_50_7"