"""Unit tests for CompactMask and its private RLE helpers."""

from contextlib import ExitStack as DoesNotRaise

import numpy as np
import pytest

from supervision import _cv2 as cv2
from supervision.detection.compact_mask import (
    CompactMask,
    _rle_area,
)
from supervision.detection.utils.converters import (
    _mask_to_rle_counts,
    _rle_counts_to_mask,
    mask_to_rle,
    mask_to_xyxy,
)
from supervision.detection.utils.masks import (
    calculate_masks_centroids,
    contains_holes,
    contains_multiple_segments,
    move_masks,
)


def _make_cm(masks: np.ndarray, image_shape: tuple[int, int]) -> CompactMask:
    """Build a CompactMask whose crops equal the full bounding-box extents."""
    num_masks = len(masks)
    img_h, img_w = image_shape
    xyxy = np.tile(np.array([0, 0, img_w, img_h], dtype=np.float32), (num_masks, 1))
    return CompactMask.from_dense(masks, xyxy, image_shape=image_shape)


class TestRleHelpers:
    """Tests for _mask_to_rle_counts, _rle_counts_to_mask, and _rle_area.

    Verifies that the private RLE encoding round-trips correctly for a range
    of mask shapes (all-False, all-True, diagonal, L-shape, checkerboard,
    single-pixel, and empty), and that _rle_area matches np.sum on the
    original boolean array.
    """

    @pytest.mark.parametrize(
        ("mask_2d", "description"),
        [
            pytest.param(np.zeros((5, 5), dtype=bool), "all-False", id="all-false"),
            pytest.param(np.ones((5, 5), dtype=bool), "all-True", id="all-true"),
            pytest.param(np.eye(4, dtype=bool), "diagonal", id="diagonal"),
            pytest.param(
                np.array([[True, True, False], [True, False, False]], dtype=bool),
                "L-shape",
                id="l-shape",
            ),
            pytest.param(
                np.indices((4, 4)).sum(axis=0) % 2 == 0,
                "checkerboard",
                id="checkerboard",
            ),
            pytest.param(
                np.zeros((1, 1), dtype=bool),
                "single-pixel-False",
                id="single-pixel-false",
            ),
            pytest.param(
                np.ones((1, 1), dtype=bool), "single-pixel-True", id="single-pixel-true"
            ),
            pytest.param(np.zeros((0, 0), dtype=bool), "empty", id="empty"),
        ],
    )
    def test_encode_decode_round_trip(
        self, mask_2d: np.ndarray, description: str
    ) -> None:
        """_mask_to_rle_counts -> _rle_counts_to_mask round-trip is lossless."""
        if mask_2d.size == 0:
            rle = _mask_to_rle_counts(mask_2d)
            assert _rle_area(rle) == 0
            return

        rle = _mask_to_rle_counts(mask_2d)
        assert rle.dtype == np.int32, "RLE must be int32"
        reconstructed = _rle_counts_to_mask(rle, mask_2d.shape[0], mask_2d.shape[1])
        np.testing.assert_array_equal(
            reconstructed, mask_2d, err_msg=f"Round-trip failed for: {description}"
        )

    @pytest.mark.parametrize(
        "mask_2d",
        [
            pytest.param(np.zeros((6, 6), dtype=bool), id="all-false"),
            pytest.param(np.ones((6, 6), dtype=bool), id="all-true"),
            pytest.param(np.eye(6, dtype=bool), id="diagonal"),
            pytest.param(
                np.array([[True, False, True], [False, True, False]], dtype=bool),
                id="mixed",
            ),
        ],
    )
    def test_area_matches_numpy_sum(self, mask_2d: np.ndarray) -> None:
        """_rle_area must equal np.sum on the original boolean array."""
        rle = _mask_to_rle_counts(mask_2d)
        assert _rle_area(rle) == int(np.sum(mask_2d))

    @pytest.mark.parametrize(
        ("mask_2d", "expected_rle"),
        [
            # 2x3; F-order flat: [F,T,T,F,T,F] -> 1F,2T,1F,1T,1F
            pytest.param(
                np.array([[False, True, True], [True, False, False]]),
                [1, 2, 1, 1, 1],
                id="2x3-mixed",
            ),
            # 3x3 all-False -> single run of 9
            pytest.param(np.zeros((3, 3), dtype=bool), [9], id="3x3-all-false"),
            # 3x1 all-True; F-order scan starts True -> leading zero prepended
            pytest.param(np.ones((3, 1), dtype=bool), [0, 3], id="3x1-all-true"),
            # 2x2; F-order flat: [F,T,F,T] -> alternating single-pixel runs
            pytest.param(
                np.array([[False, False], [True, True]]),
                [1, 1, 1, 1],
                id="2x2-alternating",
            ),
        ],
    )
    def test_encode_matches_coco_f_order(
        self, mask_2d: np.ndarray, expected_rle: list[int]
    ) -> None:
        """_mask_to_rle_counts produces COCO-compatible F-order RLE for known masks."""
        assert _mask_to_rle_counts(mask_2d).tolist() == expected_rle

    @pytest.mark.parametrize(
        "mask_2d",
        [
            pytest.param(
                np.array([[False, True, True], [True, False, False]]), id="l-shape"
            ),
            pytest.param(np.zeros((4, 4), dtype=bool), id="all-false"),
            pytest.param(np.array([[False, False], [True, True]]), id="alternating"),
            pytest.param(np.ones((3, 1), dtype=bool), id="all-true"),
        ],
    )
    def test_encode_agrees_with_mask_to_rle(self, mask_2d: np.ndarray) -> None:
        """_mask_to_rle_counts output matches the public mask_to_rle encoder."""
        assert _mask_to_rle_counts(mask_2d).tolist() == mask_to_rle(mask_2d)


class TestFromDenseToDense:
    """Tests for CompactMask.from_dense and to_dense.

    Verifies that the from_dense → to_dense round-trip is lossless when the
    bounding boxes span the full image (no True pixels fall outside the crop).
    Covers N=0 (empty), N=1 (single mask), and N=5 (several random masks).
    """

    @pytest.mark.parametrize(
        ("num_masks", "image_shape"),
        [
            pytest.param(0, (50, 50), id="zero-masks"),
            pytest.param(1, (50, 50), id="single-mask"),
            pytest.param(5, (50, 50), id="five-masks"),
        ],
    )
    def test_round_trip(self, num_masks: int, image_shape: tuple[int, int]) -> None:
        """from_dense -> to_dense round-trips losslessly for N=0, 1, and 5 masks."""
        rng = np.random.default_rng(42)
        img_h, img_w = image_shape
        masks = rng.integers(0, 2, size=(num_masks, img_h, img_w)).astype(bool)
        cm = _make_cm(masks, image_shape)
        np.testing.assert_array_equal(cm.to_dense(), masks)

    def test_round_trip_with_mask_to_xyxy(self) -> None:
        """Round-trip must be lossless with inclusive xyxy from mask_to_xyxy."""
        img_h, img_w = 12, 14
        masks = np.zeros((1, img_h, img_w), dtype=bool)
        masks[0, 3:7, 4:9] = True  # non-full-image object

        xyxy = mask_to_xyxy(masks).astype(np.float32)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))

        np.testing.assert_array_equal(cm.to_dense(), masks)


class TestFromCocoRle:
    """Tests for CompactMask.from_coco_rle."""

    def test_empty_collection_has_dense_empty_shape(self) -> None:
        """Empty COCO RLE input should return an empty CompactMask."""
        compact = CompactMask.from_coco_rle(
            rles=[],
            xyxy=np.empty((0, 4), dtype=np.float32),
            image_shape=(3, 5),
        )

        assert len(compact) == 0
        assert compact.shape == (0, 3, 5)
        assert compact.area.shape == (0,)
        np.testing.assert_array_equal(
            compact.to_dense(), np.zeros((0, 3, 5), dtype=bool)
        )

    @pytest.mark.parametrize(
        ("masks", "xyxy"),
        [
            pytest.param(
                np.array(
                    [
                        [
                            [False, True, False, False, False],
                            [False, True, True, False, False],
                            [False, False, False, True, False],
                        ]
                    ],
                    dtype=bool,
                ),
                np.array([[1, 0, 3, 2]], dtype=np.float32),
                id="non-square-crop",
            ),
            pytest.param(
                np.ones((1, 4, 5), dtype=bool),
                np.array([[-2, 1, 8, 3]], dtype=np.float32),
                id="clipped-box",
            ),
            pytest.param(
                np.zeros((1, 4, 5), dtype=bool),
                np.array([[0, 0, 4, 3]], dtype=np.float32),
                id="all-false",
            ),
            pytest.param(
                np.ones((1, 4, 5), dtype=bool),
                np.array([[0, 0, 4, 3]], dtype=np.float32),
                id="all-true-full-image",
            ),
            pytest.param(
                np.ones((1, 4, 5), dtype=bool),
                np.array([[1, 1, 3, 2]], dtype=np.float32),
                id="all-true-crop",
            ),
            pytest.param(
                np.ones((1, 4, 5), dtype=bool),
                np.array([[3, 2, 1, 2]], dtype=np.float32),
                id="invalid-box",
            ),
            pytest.param(
                np.array([[[True]]], dtype=bool),
                np.array([[0, 0, 0, 0]], dtype=np.float32),
                id="single-pixel-image",
            ),
        ],
    )
    def test_matches_dense_reference(self, masks: np.ndarray, xyxy: np.ndarray) -> None:
        """COCO RLE construction should match dense decode plus from_dense."""
        image_shape = masks.shape[1:]
        rles = [
            {
                "size": list(image_shape),
                "counts": mask_to_rle(mask, compressed=True),
            }
            for mask in masks
        ]

        compact = CompactMask.from_coco_rle(
            rles=rles, xyxy=xyxy, image_shape=image_shape
        )

        reference = CompactMask.from_dense(masks, xyxy, image_shape=image_shape)
        np.testing.assert_array_equal(compact.to_dense(), reference.to_dense())
        np.testing.assert_array_equal(compact.area, reference.area)
        np.testing.assert_array_equal(compact.bbox_xyxy, reference.bbox_xyxy)

    def test_matches_dense_reference_for_multiple_masks(self) -> None:
        """COCO RLE construction handles N>1 batches."""
        masks = np.zeros((2, 5, 6), dtype=bool)
        masks[0, 1:3, 1:4] = True
        masks[1, 3:5, 4:6] = True
        xyxy = np.array([[1, 1, 3, 2], [4, 3, 5, 4]], dtype=np.float32)
        image_shape = masks.shape[1:]
        rles = [
            {"size": list(image_shape), "counts": mask_to_rle(mask)} for mask in masks
        ]

        compact = CompactMask.from_coco_rle(
            rles=rles, xyxy=xyxy, image_shape=image_shape
        )

        reference = CompactMask.from_dense(masks, xyxy, image_shape=image_shape)
        np.testing.assert_array_equal(compact.to_dense(), reference.to_dense())

    def test_out_of_frame_box_returns_empty_crop(self) -> None:
        """Boxes with no image intersection do not collapse onto edge pixels."""
        mask = np.zeros((4, 5), dtype=bool)
        mask[2, 4] = True
        rles = [{"size": [4, 5], "counts": mask_to_rle(mask)}]
        xyxy = np.array([[5, 2, 6, 2]], dtype=np.float32)

        compact = CompactMask.from_coco_rle(rles=rles, xyxy=xyxy, image_shape=(4, 5))

        assert compact.area.tolist() == [0]
        np.testing.assert_array_equal(compact.to_dense(), np.zeros((1, 4, 5), bool))

    def test_rejects_rle_size_mismatch(self) -> None:
        """COCO RLE size should match the explicit image shape."""
        rles = [{"size": [2, 2], "counts": [4]}]
        xyxy = np.array([[0, 0, 1, 1]], dtype=np.float32)

        with pytest.raises(ValueError, match="RLE size"):
            CompactMask.from_coco_rle(rles=rles, xyxy=xyxy, image_shape=(3, 2))

    @pytest.mark.parametrize(
        ("rles", "xyxy_arr", "image_shape", "err_match"),
        [
            pytest.param(
                [{"size": [0, 4], "counts": [0]}],
                np.array([[0, 0, 3, 3]], dtype=np.float32),
                (0, 4),
                "positive",
                id="zero-height",
            ),
            pytest.param(
                [{"size": [4, 0], "counts": [0]}],
                np.array([[0, 0, 3, 3]], dtype=np.float32),
                (4, 0),
                "positive",
                id="zero-width",
            ),
            pytest.param(
                [{"size": [4, 4], "counts": [16]}],
                np.array([[0, 0, 3, 3, 0]], dtype=np.float32),
                (4, 4),
                "shape",
                id="xyxy-shape-mismatch",
            ),
            pytest.param(
                [42],
                np.array([[0, 0, 3, 3]], dtype=np.float32),
                (4, 4),
                "mapping",
                id="non-mapping-rle-item",
            ),
            pytest.param(
                [{"size": [4, 4]}],
                np.array([[0, 0, 3, 3]], dtype=np.float32),
                (4, 4),
                "'size' and 'counts'",
                id="missing-counts-key",
            ),
            pytest.param(
                [{"size": [4, 4], "counts": [1, 2, 3]}],
                np.array([[0, 0, 3, 3]], dtype=np.float32),
                (4, 4),
                "sum",
                id="counts-sum-mismatch",
            ),
            pytest.param(
                [],
                np.empty((0, 4), dtype=np.float32),
                (32769, 4),
                "maximum",
                id="max-image-dimension-exceeded",
            ),
        ],
    )
    def test_raises_on_invalid_input(
        self,
        rles: list,
        xyxy_arr: np.ndarray,
        image_shape: tuple,
        err_match: str,
    ) -> None:
        """from_coco_rle raises ValueError for each documented invalid-input path."""
        with pytest.raises(ValueError, match=err_match):
            CompactMask.from_coco_rle(rles=rles, xyxy=xyxy_arr, image_shape=image_shape)

    def test_transcodes_without_dense_decode_helpers(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """COCO RLE construction should avoid full-mask dense decode helpers."""
        rles = [{"size": [4, 4], "counts": "52203"}]
        xyxy = np.array([[0, 0, 3, 3]], dtype=np.float32)

        def fail_dense_helper(*args: object, **kwargs: object) -> None:
            raise AssertionError("dense helper should not be called")

        monkeypatch.setattr(
            "supervision.detection.compact_mask._mask_to_rle_counts",
            fail_dense_helper,
        )
        monkeypatch.setattr(
            "supervision.detection.compact_mask._rle_counts_to_mask",
            fail_dense_helper,
        )

        compact = CompactMask.from_coco_rle(rles=rles, xyxy=xyxy, image_shape=(4, 4))

        assert compact.shape == (1, 4, 4)

    def test_large_image_column_split_path(self) -> None:
        """from_coco_rle hits column-split path on large images (H*W > 307200)."""
        H, W = 720, 1280
        assert H * W > 640 * 480, (
            "test must use image above _SMALL_IMAGE_DENSE_THRESHOLD"
        )
        rng = np.random.default_rng(42)
        mask = rng.integers(0, 2, (H, W), dtype=np.uint8).astype(bool)
        xyxy = np.array([[0, 0, W - 1, H - 1]], dtype=np.float32)
        rle = {"size": [H, W], "counts": mask_to_rle(mask, compressed=True)}

        compact = CompactMask.from_coco_rle(rles=[rle], xyxy=xyxy, image_shape=(H, W))
        reference = CompactMask.from_dense(mask[np.newaxis], xyxy, image_shape=(H, W))

        np.testing.assert_array_equal(compact.to_dense(), reference.to_dense())

    def test_bytes_counts_match_string_counts(self) -> None:
        """from_coco_rle accepts bytes-encoded compressed counts."""
        # Both encodings of "52203" should produce identical crops.
        rle_str = {"size": [4, 4], "counts": "52203"}
        rle_bytes = {"size": [4, 4], "counts": b"52203"}
        xyxy = np.array([[0, 0, 3, 3]], dtype=np.float32)

        cm_str = CompactMask.from_coco_rle(
            rles=[rle_str], xyxy=xyxy, image_shape=(4, 4)
        )
        cm_bytes = CompactMask.from_coco_rle(
            rles=[rle_bytes], xyxy=xyxy, image_shape=(4, 4)
        )

        np.testing.assert_array_equal(cm_str.to_dense(), cm_bytes.to_dense())


class TestCocoRleCountsToArray:
    """Tests for _coco_rle_counts_to_array input-format decoding."""

    @pytest.mark.parametrize(
        "counts",
        [
            pytest.param("52203", id="str-input"),
            pytest.param(b"52203", id="bytes-input"),
        ],
    )
    def test_str_and_bytes_decode_identically(self, counts: object) -> None:
        """str and bytes inputs decode to the same run-length array."""
        from supervision.detection.compact_mask import _coco_rle_counts_to_array

        result = _coco_rle_counts_to_array(counts)
        assert result.dtype == np.int32
        assert result.sum() == 16  # total pixels in a 4x4 image

    @pytest.mark.parametrize(
        ("counts", "err_match"),
        [
            pytest.param([2**31], "Invalid", id="int32-overflow"),
            pytest.param([[4, 8], [3, 5]], "one-dimensional", id="two-dimensional"),
            pytest.param([4, -1, 8], "non-negative", id="negative-count"),
            pytest.param(None, "Invalid", id="none"),
            pytest.param("", "empty", id="empty-string"),
        ],
    )
    def test_invalid_counts_raise_value_error(
        self, counts: object, err_match: str
    ) -> None:
        """Invalid COCO RLE counts raise ValueError."""
        from supervision.detection.compact_mask import _coco_rle_counts_to_array

        with pytest.raises(ValueError, match=err_match):
            _coco_rle_counts_to_array(counts)


class TestRleTrimColRuns:
    """Tests for _rle_trim_col_runs row-crop behavior."""

    @pytest.mark.parametrize(
        ("col_runs", "height", "y1", "y2"),
        [
            pytest.param([0, 2, 3], 5, 0, 2, id="starts-at-row-zero"),
            pytest.param([2, 1, 2], 5, 2, 2, id="single-row-crop"),
            pytest.param([1, 3, 2], 6, 2, 4, id="straddles-both-bounds"),
            pytest.param([3, 2, 1], 6, 4, 5, id="starts-inside-true-run"),
            pytest.param([0, 6], 6, 1, 4, id="all-true-col-interior-crop"),
        ],
    )
    def test_matches_decode_slice_encode(
        self, col_runs: list[int], height: int, y1: int, y2: int
    ) -> None:
        """Trimmed column runs match dense slice then encode."""
        from supervision.detection.compact_mask import _rle_trim_col_runs

        column = _rle_counts_to_mask(np.array(col_runs, dtype=np.int32), height, 1)
        expected = _mask_to_rle_counts(column[y1 : y2 + 1, :]).tolist()

        result = _rle_trim_col_runs(col_runs, y1, y2)

        assert result == expected
        assert sum(result) == y2 - y1 + 1

    def test_returns_all_false_when_no_runs_reach_crop(self) -> None:
        """Truncated input before y1 returns an all-False crop."""
        from supervision.detection.compact_mask import _rle_trim_col_runs

        result = _rle_trim_col_runs([2], y1=3, y2=4)

        assert result == [2]


class TestGetItem:
    """Tests for CompactMask.__getitem__.

    Covers four indexing modes:
    - Integer index → dense (H, W) np.ndarray with correct shape and dtype.
    - List of indices → new CompactMask with the selected detections.
    - Slice → new CompactMask with the sliced detections.
    - Boolean ndarray → new CompactMask filtered by the boolean selector.
    """

    def test_int_returns_2d_dense(self) -> None:
        """Integer index returns a dense (H, W) array matching the source mask."""
        img_h, img_w = 30, 40
        rng = np.random.default_rng(0)
        masks = rng.integers(0, 2, size=(3, img_h, img_w)).astype(bool)
        cm = _make_cm(masks, (img_h, img_w))

        result = cm[1]
        assert isinstance(result, np.ndarray)
        assert result.shape == (img_h, img_w)
        assert result.dtype == bool
        np.testing.assert_array_equal(result, masks[1])

    def test_list_returns_compact_mask(self) -> None:
        """List of indices returns a new CompactMask with the selected detections."""
        img_h, img_w = 20, 20
        masks = np.zeros((4, img_h, img_w), dtype=bool)
        for mask_idx in range(4):
            masks[
                mask_idx,
                mask_idx * 2 : mask_idx * 2 + 2,
                mask_idx * 2 : mask_idx * 2 + 2,
            ] = True
        cm = _make_cm(masks, (img_h, img_w))

        subset = cm[[0, 2]]
        assert isinstance(subset, CompactMask)
        assert len(subset) == 2
        np.testing.assert_array_equal(subset[0], masks[0])
        np.testing.assert_array_equal(subset[1], masks[2])

    def test_slice_returns_compact_mask(self) -> None:
        """Slice indexing returns a new CompactMask with the sliced detections."""
        img_h, img_w = 20, 20
        masks = np.zeros((5, img_h, img_w), dtype=bool)
        cm = _make_cm(masks, (img_h, img_w))

        subset = cm[1:4]
        assert isinstance(subset, CompactMask)
        assert len(subset) == 3

    def test_bool_ndarray(self) -> None:
        """Boolean ndarray selector filters detections like NumPy boolean masking."""
        img_h, img_w = 15, 15
        rng = np.random.default_rng(7)
        masks = rng.integers(0, 2, size=(4, img_h, img_w)).astype(bool)
        cm = _make_cm(masks, (img_h, img_w))

        selector = np.array([True, False, True, False])
        subset = cm[selector]
        assert isinstance(subset, CompactMask)
        assert len(subset) == 2
        np.testing.assert_array_equal(subset[0], masks[0])
        np.testing.assert_array_equal(subset[1], masks[2])

    def test_bool_list(self) -> None:
        """Python list[bool] should behave like boolean masking."""
        img_h, img_w = 15, 15
        rng = np.random.default_rng(8)
        masks = rng.integers(0, 2, size=(4, img_h, img_w)).astype(bool)
        cm = _make_cm(masks, (img_h, img_w))

        subset = cm[[True, False, True, False]]
        assert isinstance(subset, CompactMask)
        assert len(subset) == 2
        np.testing.assert_array_equal(subset[0], masks[0])
        np.testing.assert_array_equal(subset[1], masks[2])


class TestProperties:
    """Tests for len, shape, dtype, and area properties.

    Verifies that the shape tuple follows the (N, H, W) dense convention,
    dtype is always bool, and area returns per-mask True-pixel counts that
    match np.sum on the corresponding dense masks.
    """

    def test_len(self) -> None:
        """len() returns the number of masks in the collection."""
        masks = np.zeros((3, 10, 10), dtype=bool)
        cm = _make_cm(masks, (10, 10))
        assert len(cm) == 3

    def test_shape(self) -> None:
        """shape follows the (N, H, W) dense array convention."""
        masks = np.zeros((3, 10, 10), dtype=bool)
        cm = _make_cm(masks, (10, 10))
        assert cm.shape == (3, 10, 10)

    def test_shape_empty(self) -> None:
        """shape reports N=0 for an empty CompactMask while keeping (H, W)."""
        cm = CompactMask(
            [],
            np.empty((0, 2), dtype=np.int32),
            np.empty((0, 2), dtype=np.int32),
            (480, 640),
        )
        assert cm.shape == (0, 480, 640)

    def test_dtype(self) -> None:
        """dtype is always bool regardless of the input mask dtype."""
        cm = _make_cm(np.zeros((1, 5, 5), dtype=bool), (5, 5))
        assert cm.dtype == np.dtype(bool)

    def test_area_matches_dense(self) -> None:
        """area returns per-mask True-pixel counts matching np.sum on dense masks."""
        img_h, img_w = 20, 20
        rng = np.random.default_rng(3)
        masks = rng.integers(0, 2, size=(4, img_h, img_w)).astype(bool)
        cm = _make_cm(masks, (img_h, img_w))

        expected = np.array([mask.sum() for mask in masks])
        np.testing.assert_array_equal(cm.area, expected)

    def test_area_empty(self) -> None:
        """area is an empty (0,) array for an empty CompactMask."""
        cm = CompactMask(
            [],
            np.empty((0, 2), dtype=np.int32),
            np.empty((0, 2), dtype=np.int32),
            (10, 10),
        )
        assert cm.area.shape == (0,)


class TestCrop:
    """Tests for CompactMask.crop.

    Verifies that crop(index) returns an array shaped (crop_h, crop_w)
    containing only the pixels within the bounding box, without allocating
    the full (H, W) image.
    """

    def test_returns_crop_shape(self) -> None:
        img_h, img_w = 50, 60
        masks = np.zeros((1, img_h, img_w), dtype=bool)
        masks[0, 10:30, 5:25] = True  # 20 x 20 region
        xyxy = np.array([[5, 10, 24, 29]], dtype=np.float32)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))

        crop = cm.crop(0)
        assert crop.shape == (20, 20)
        assert crop.all()  # the entire crop should be True


class TestArrayProtocol:
    """Tests for the __array__ protocol.

    Verifies that np.asarray(cm) materialises the full (N, H, W) dense array
    and that optional dtype casting (e.g. to uint8) is correctly applied.
    """

    def test_array_protocol(self) -> None:
        img_h, img_w = 10, 10
        rng = np.random.default_rng(9)
        masks = rng.integers(0, 2, size=(2, img_h, img_w)).astype(bool)
        cm = _make_cm(masks, (img_h, img_w))

        arr = np.asarray(cm)
        assert arr.shape == (2, img_h, img_w)
        np.testing.assert_array_equal(arr, masks)

    def test_dtype_cast(self) -> None:
        masks = np.ones((1, 5, 5), dtype=bool)
        cm = _make_cm(masks, (5, 5))
        arr = np.asarray(cm, dtype=np.uint8)
        assert arr.dtype == np.uint8
        assert arr.sum() == 25


class TestMerge:
    """Tests for CompactMask.merge.

    Verifies that multiple CompactMask instances with the same image_shape
    can be concatenated into a single CompactMask, that merging with an empty
    instance works, that an empty input list raises ValueError, and that
    mismatched image shapes raise ValueError.
    """

    def test_merge(self) -> None:
        img_h, img_w = 20, 20
        masks1 = np.zeros((2, img_h, img_w), dtype=bool)
        masks2 = np.zeros((3, img_h, img_w), dtype=bool)
        cm1 = _make_cm(masks1, (img_h, img_w))
        cm2 = _make_cm(masks2, (img_h, img_w))

        merged = CompactMask.merge([cm1, cm2])
        assert len(merged) == 5
        assert merged.shape == (5, img_h, img_w)
        np.testing.assert_array_equal(
            merged.to_dense(), np.concatenate([masks1, masks2], axis=0)
        )

    def test_merge_with_empty(self) -> None:
        img_h, img_w = 10, 10
        empty_cm = CompactMask(
            [],
            np.empty((0, 2), dtype=np.int32),
            np.empty((0, 2), dtype=np.int32),
            (img_h, img_w),
        )
        masks = np.zeros((2, img_h, img_w), dtype=bool)
        cm = _make_cm(masks, (img_h, img_w))

        merged = CompactMask.merge([empty_cm, cm])
        assert len(merged) == 2

    def test_merge_empty_list_raises(self) -> None:
        with pytest.raises(ValueError, match="empty list"):
            CompactMask.merge([])

    def test_merge_mismatched_image_shape_raises(self) -> None:
        cm1 = CompactMask(
            [],
            np.empty((0, 2), dtype=np.int32),
            np.empty((0, 2), dtype=np.int32),
            (10, 10),
        )
        cm2 = CompactMask(
            [],
            np.empty((0, 2), dtype=np.int32),
            np.empty((0, 2), dtype=np.int32),
            (20, 20),
        )
        with pytest.raises(ValueError, match="image shapes"):
            CompactMask.merge([cm1, cm2])


class TestEquality:
    """Tests for CompactMask.__eq__.

    Verifies element-wise equality between two CompactMask instances and
    between a CompactMask and an equivalent dense (N, H, W) boolean array.
    """

    def test_eq_identical(self) -> None:
        masks = np.zeros((2, 10, 10), dtype=bool)
        masks[0, 2:5, 2:5] = True
        cm1 = _make_cm(masks, (10, 10))
        cm2 = _make_cm(masks, (10, 10))
        assert cm1 == cm2

    def test_eq_different(self) -> None:
        masks_a = np.zeros((2, 10, 10), dtype=bool)
        masks_a[0, 2:5, 2:5] = True
        masks_b = np.zeros((2, 10, 10), dtype=bool)
        masks_b[1, 6:9, 6:9] = True
        cm1 = _make_cm(masks_a, (10, 10))
        cm2 = _make_cm(masks_b, (10, 10))
        assert not (cm1 == cm2)

    def test_eq_with_dense_array(self) -> None:
        masks = np.zeros((1, 8, 8), dtype=bool)
        masks[0, 1:4, 1:4] = True
        cm = _make_cm(masks, (8, 8))
        assert cm == masks


class TestEdgeCases:
    """Tests for boundary conditions and unusual inputs.

    Covers: zero-area bounding box (x1 == x2), masks that reach the image
    edge, xyxy values beyond image dimensions (clamped silently), empty
    CompactMask (N=0), sum axis compatibility with area, and with_offset for
    use by InferenceSlicer.
    """

    def test_zero_area_mask_clipped_to_1x1(self) -> None:
        """An invalid bounding box should not crash from_dense."""
        masks = np.zeros((1, 10, 10), dtype=bool)
        xyxy = np.array([[6, 5, 5, 8]], dtype=np.float32)
        with DoesNotRaise():
            cm = CompactMask.from_dense(masks, xyxy, image_shape=(10, 10))
        assert len(cm) == 1

    def test_mask_at_image_boundary(self) -> None:
        img_h, img_w = 20, 20
        masks = np.zeros((1, img_h, img_w), dtype=bool)
        masks[0, 15:20, 15:20] = True
        xyxy = np.array([[15, 15, 19, 19]], dtype=np.float32)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))
        np.testing.assert_array_equal(cm.to_dense(), masks)

    def test_xyxy_beyond_image_clipped(self) -> None:
        """xyxy values beyond the image boundary should be clipped silently."""
        img_h, img_w = 10, 10
        masks = np.zeros((1, img_h, img_w), dtype=bool)
        masks[0, 5:10, 5:10] = True
        xyxy = np.array([[5, 5, 999, 999]], dtype=np.float32)
        with DoesNotRaise():
            cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))
        np.testing.assert_array_equal(cm.to_dense(), masks)

    def test_empty_compact_mask_to_dense(self) -> None:
        cm = CompactMask(
            [],
            np.empty((0, 2), dtype=np.int32),
            np.empty((0, 2), dtype=np.int32),
            (50, 60),
        )
        dense = cm.to_dense()
        assert dense.shape == (0, 50, 60)
        assert dense.dtype == bool

    def test_sum_axis_1_2_equals_area(self) -> None:
        rng = np.random.default_rng(11)
        masks = rng.integers(0, 2, size=(4, 15, 15)).astype(bool)
        cm = _make_cm(masks, (15, 15))
        np.testing.assert_array_equal(cm.sum(axis=(1, 2)), cm.area)

    def test_with_offset(self) -> None:
        img_h, img_w = 20, 20
        masks = np.zeros((1, img_h, img_w), dtype=bool)
        masks[0, 5:10, 5:10] = True
        xyxy = np.array([[5, 5, 9, 9]], dtype=np.float32)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))

        cm2 = cm.with_offset(100, 200, new_image_shape=(400, 400))
        assert cm2.offsets[0].tolist() == [105, 205]
        assert cm2._image_shape == (400, 400)
        np.testing.assert_array_equal(cm2.crop(0), cm.crop(0))

    def test_with_offset_clips_partial_overlap_like_move_masks(self) -> None:
        """with_offset must clip partial out-of-frame translations like move_masks."""
        img_h, img_w = 10, 10
        masks = np.zeros((1, img_h, img_w), dtype=bool)
        masks[0, 2:6, 3:8] = True
        xyxy = np.array([[3, 2, 7, 5]], dtype=np.float32)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))

        dx, dy = -4, 3
        cm_shifted = cm.with_offset(dx=dx, dy=dy, new_image_shape=(img_h, img_w))
        expected = move_masks(
            masks=masks,
            offset=np.array([dx, dy], dtype=np.int32),
            resolution_wh=(img_w, img_h),
        )

        np.testing.assert_array_equal(cm_shifted.to_dense(), expected)

    def test_with_offset_clips_full_outside_like_move_masks(self) -> None:
        """Masks shifted fully outside should remain valid and decode to all-False."""
        img_h, img_w = 10, 10
        masks = np.zeros((1, img_h, img_w), dtype=bool)
        masks[0, 2:6, 2:6] = True
        xyxy = np.array([[2, 2, 5, 5]], dtype=np.float32)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))

        dx, dy = 100, 100
        cm_shifted = cm.with_offset(dx=dx, dy=dy, new_image_shape=(img_h, img_w))
        expected = move_masks(
            masks=masks,
            offset=np.array([dx, dy], dtype=np.int32),
            resolution_wh=(img_w, img_h),
        )

        np.testing.assert_array_equal(cm_shifted.to_dense(), expected)

    def test_repack_tightens_loose_bbox(self) -> None:
        """repack() shrinks the crop to the minimal True-pixel rectangle."""
        img_h, img_w = 20, 20
        masks = np.zeros((1, img_h, img_w), dtype=bool)
        masks[0, 5:10, 6:12] = True  # True block at (5,6)-(9,11)

        # Deliberately loose bbox covers full image.
        xyxy = np.array([[0, 0, img_w - 1, img_h - 1]], dtype=np.float32)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))

        # Before repack: crop is the full 20x20 image.
        assert cm._crop_shapes[0].tolist() == [20, 20]

        repacked = cm.repack()

        # After repack: crop is exactly the True block.
        assert repacked.offsets[0].tolist() == [6, 5]  # (x1, y1)
        assert repacked._crop_shapes[0].tolist() == [5, 6]  # (h, w)
        # Pixel content must be identical to the original.
        np.testing.assert_array_equal(repacked.to_dense(), masks)

    def test_repack_preserves_all_false_mask(self) -> None:
        """repack() normalises an all-False mask to a 1x1 crop."""
        img_h, img_w = 10, 10
        masks = np.zeros((2, img_h, img_w), dtype=bool)
        masks[1, 3:6, 3:6] = True  # only mask 1 is non-empty

        xyxy = np.array([[0, 0, 9, 9], [0, 0, 9, 9]], dtype=np.float32)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))
        repacked = cm.repack()

        assert repacked._crop_shapes[0].tolist() == [1, 1]  # normalised
        assert repacked._crop_shapes[1].tolist() == [3, 3]  # tight True block
        np.testing.assert_array_equal(repacked.to_dense(), masks)

    def test_repack_empty_collection(self) -> None:
        """repack() on an empty CompactMask returns another empty CompactMask."""
        cm = CompactMask(
            [],
            np.empty((0, 2), dtype=np.int32),
            np.empty((0, 2), dtype=np.int32),
            (10, 10),
        )
        repacked = cm.repack()
        assert len(repacked) == 0
        assert repacked._image_shape == (10, 10)

    def test_repack_already_tight(self) -> None:
        """repack() is a no-op when bboxes are already tight."""
        img_h, img_w = 15, 15
        masks = np.zeros((1, img_h, img_w), dtype=bool)
        masks[0, 4:9, 3:8] = True

        # Tight bbox.
        xyxy = np.array([[3, 4, 7, 8]], dtype=np.float32)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))
        repacked = cm.repack()

        np.testing.assert_array_equal(repacked.offsets, cm.offsets)
        np.testing.assert_array_equal(repacked._crop_shapes, cm._crop_shapes)
        np.testing.assert_array_equal(repacked.to_dense(), masks)


class TestCalculateMasksCentroidsCompact:
    """Verify calculate_masks_centroids gives identical results for CompactMask.

    The function has a dedicated CompactMask branch that computes centroids
    per-crop.  Results must match the dense path to within integer rounding.
    """

    def test_centroids_compact_matches_dense(self) -> None:
        """Centroid coordinates must be numerically identical for dense and compact."""
        rng = np.random.default_rng(42)
        img_h, img_w = 30, 30
        masks = rng.integers(0, 2, size=(5, img_h, img_w)).astype(bool)
        # Ensure each mask has at least one True pixel.
        for mask_idx in range(5):
            masks[mask_idx, mask_idx * 5, mask_idx * 5] = True

        cm = _make_cm(masks, (img_h, img_w))

        centroids_dense = calculate_masks_centroids(masks)
        centroids_compact = calculate_masks_centroids(cm)

        np.testing.assert_array_equal(centroids_compact, centroids_dense)

    def test_centroids_empty_mask(self) -> None:
        """All-zero masks should return centroid (0, 0) — same as dense."""
        img_h, img_w = 10, 10
        masks = np.zeros((3, img_h, img_w), dtype=bool)
        cm = _make_cm(masks, (img_h, img_w))

        centroids_dense = calculate_masks_centroids(masks)
        centroids_compact = calculate_masks_centroids(cm)

        np.testing.assert_array_equal(centroids_compact, centroids_dense)

    def test_centroids_empty_mask_with_tight_bbox(self) -> None:
        """All-zero tight crops must still return centroid (0, 0)."""
        img_h, img_w = 10, 10
        masks = np.zeros((1, img_h, img_w), dtype=bool)
        xyxy = np.array([[3, 4, 7, 8]], dtype=np.float32)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))

        centroids_dense = calculate_masks_centroids(masks)
        centroids_compact = calculate_masks_centroids(cm)

        np.testing.assert_array_equal(centroids_compact, centroids_dense)

    def test_centroids_zero_masks_returns_empty(self) -> None:
        """Empty CompactMask (0 objects) must return shape (0, 2)."""
        empty_cm = CompactMask(
            [],
            np.empty((0, 2), dtype=np.int32),
            np.empty((0, 2), dtype=np.int32),
            (10, 10),
        )
        result = calculate_masks_centroids(empty_cm)
        assert result.shape == (0, 2)


class TestContainsHolesCompact:
    """Verify contains_holes result is unchanged after CompactMask roundtrip.

    contains_holes works on a 2D boolean mask.  Encoding then decoding via
    CompactMask must preserve pixel topology so that the function returns
    the same result as on the original array.
    """

    @pytest.mark.parametrize(
        ("mask_2d", "expected"),
        [
            # simple foreground blob — no holes
            (
                np.array(
                    [[0, 1, 1, 0], [1, 1, 1, 1], [1, 1, 1, 1], [0, 1, 1, 0]],
                    dtype=bool,
                ),
                False,
            ),
            # ring shape — has one hole
            (
                np.array(
                    [[1, 1, 1, 0], [1, 0, 1, 0], [1, 1, 1, 0], [0, 0, 0, 0]],
                    dtype=bool,
                ),
                True,
            ),
            # all-False — no holes
            (np.zeros((6, 6), dtype=bool), False),
            # all-True — no holes
            (np.ones((6, 6), dtype=bool), False),
        ],
    )
    def test_contains_holes_compact_roundtrip(
        self, mask_2d: np.ndarray, expected: bool
    ) -> None:
        """contains_holes must agree after CompactMask encode→decode."""
        img_h, img_w = mask_2d.shape
        masks = mask_2d[np.newaxis]  # (1, H, W)
        cm = _make_cm(masks, (img_h, img_w))

        decoded = cm.to_dense()[0]
        assert contains_holes(decoded) == expected
        assert contains_holes(decoded) == contains_holes(mask_2d)


class TestContainsMultipleSegmentsCompact:
    """Verify contains_multiple_segments result survives CompactMask roundtrip.

    Encoding and decoding must preserve connected-component topology so
    that the multi-segment predicate returns the same value.
    """

    @pytest.mark.parametrize(
        ("mask_2d", "connectivity", "expected"),
        [
            # single contiguous blob — not multi-segment
            (
                np.array(
                    [[0, 1, 1, 0], [1, 1, 1, 1], [1, 1, 1, 1], [0, 1, 1, 0]],
                    dtype=bool,
                ),
                4,
                False,
            ),
            # two separate blobs — multi-segment
            (
                np.array(
                    [[1, 1, 0, 0], [1, 1, 0, 0], [0, 0, 1, 1], [0, 0, 1, 1]],
                    dtype=bool,
                ),
                4,
                True,
            ),
            # diagonal touch — single segment under 8-connectivity
            (
                np.array(
                    [[1, 1, 0, 0], [1, 1, 0, 1], [1, 0, 1, 1], [0, 0, 1, 1]],
                    dtype=bool,
                ),
                8,
                False,
            ),
            # all-False — not multi-segment
            (np.zeros((6, 6), dtype=bool), 4, False),
        ],
    )
    def test_contains_multiple_segments_compact_roundtrip(
        self, mask_2d: np.ndarray, connectivity: int, expected: bool
    ) -> None:
        """contains_multiple_segments must agree after CompactMask encode→decode."""
        img_h, img_w = mask_2d.shape
        masks = mask_2d[np.newaxis]  # (1, H, W)
        cm = _make_cm(masks, (img_h, img_w))

        decoded = cm.to_dense()[0]
        result = contains_multiple_segments(decoded, connectivity=connectivity)
        assert result == expected
        assert result == contains_multiple_segments(mask_2d, connectivity=connectivity)


# ---------------------------------------------------------------------------
# Random scenario helpers
# ---------------------------------------------------------------------------

# Varying (N, image_h, image_w) combinations for random tests.
_RANDOM_CONFIGS = [
    (1, 50, 50),
    (5, 50, 50),
    (5, 200, 300),
    (20, 100, 150),
    (20, 200, 300),
    (50, 50, 50),
    (5, 1080, 1920),
    (1, 1080, 1920),
    (20, 480, 640),
    (50, 100, 100),
]


def _random_masks_and_xyxy(
    rng: np.random.Generator,
    num_masks: int,
    img_h: int,
    img_w: int,
    fill_prob: float = 0.3,
) -> tuple[np.ndarray, np.ndarray]:
    """Generate *num_masks* random boolean masks with matching tight xyxy boxes.

    Each mask is built by filling a random sub-rectangle with Bernoulli noise at
    ``fill_prob``, then computing tight bounding boxes via ``mask_to_xyxy``.
    This guarantees every mask has at least one True pixel (for non-degenerate
    bounding boxes).
    """
    masks = np.zeros((num_masks, img_h, img_w), dtype=bool)
    for mask_idx in range(num_masks):
        y1 = rng.integers(0, img_h)
        y2 = rng.integers(y1, img_h)
        x1 = rng.integers(0, img_w)
        x2 = rng.integers(x1, img_w)
        region = rng.random((y2 - y1 + 1, x2 - x1 + 1)) < fill_prob
        # Ensure at least one True pixel.
        if not region.any():
            region[0, 0] = True
        masks[mask_idx, y1 : y2 + 1, x1 : x2 + 1] = region

    xyxy = mask_to_xyxy(masks).astype(np.float32)
    return masks, xyxy


class TestCompactMaskRoundtripRandom:
    """from_dense -> to_dense pixel equality across 10 random seeds.

    Uses tight bounding boxes so the round-trip must be lossless (all True
    pixels lie strictly within the crop).
    """

    @pytest.mark.parametrize("seed", list(range(10)))
    def test_parity_seed(self, seed: int) -> None:
        rng = np.random.default_rng(seed)
        num_masks, img_h, img_w = _RANDOM_CONFIGS[seed]
        masks, xyxy = _random_masks_and_xyxy(rng, num_masks, img_h, img_w)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))
        np.testing.assert_array_equal(
            cm.to_dense(),
            masks,
            err_msg=(
                f"Round-trip failed for seed={seed}, "
                f"N={num_masks}, shape=({img_h},{img_w})"
            ),
        )

    @pytest.mark.parametrize("seed", list(range(10)))
    def test_shape_and_len(self, seed: int) -> None:
        """len() and .shape must agree with the dense array."""
        rng = np.random.default_rng(seed)
        num_masks, img_h, img_w = _RANDOM_CONFIGS[seed]
        masks, xyxy = _random_masks_and_xyxy(rng, num_masks, img_h, img_w)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))
        assert len(cm) == num_masks
        assert cm.shape == (num_masks, img_h, img_w)

    @pytest.mark.parametrize("seed", list(range(10)))
    def test_individual_mask_access(self, seed: int) -> None:
        """cm[i] must equal masks[i] for every index."""
        rng = np.random.default_rng(seed)
        num_masks, img_h, img_w = _RANDOM_CONFIGS[seed]
        masks, xyxy = _random_masks_and_xyxy(rng, num_masks, img_h, img_w)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))
        for mask_idx in range(num_masks):
            np.testing.assert_array_equal(
                cm[mask_idx],
                masks[mask_idx],
                err_msg=f"cm[{mask_idx}] mismatch for seed={seed}",
            )


class TestCompactMaskAreaRandom:
    """area from CompactMask equals dense .sum(axis=(1,2)) across 10 seeds."""

    @pytest.mark.parametrize("seed", list(range(10)))
    def test_parity_seed(self, seed: int) -> None:
        rng = np.random.default_rng(seed)
        num_masks, img_h, img_w = _RANDOM_CONFIGS[seed]
        masks, xyxy = _random_masks_and_xyxy(rng, num_masks, img_h, img_w)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))

        expected_area = masks.sum(axis=(1, 2))
        np.testing.assert_array_equal(
            cm.area,
            expected_area,
            err_msg=(
                f"Area mismatch for seed={seed}, N={num_masks}, shape=({img_h},{img_w})"
            ),
        )

    @pytest.mark.parametrize("seed", list(range(10)))
    def test_sum_axis_matches_area(self, seed: int) -> None:
        """cm.sum(axis=(1,2)) must equal cm.area (the fast path)."""
        rng = np.random.default_rng(seed)
        num_masks, img_h, img_w = _RANDOM_CONFIGS[seed]
        masks, xyxy = _random_masks_and_xyxy(rng, num_masks, img_h, img_w)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))
        np.testing.assert_array_equal(cm.sum(axis=(1, 2)), cm.area)


class TestCompactMaskFilterRandom:
    """Boolean filter on CompactMask matches dense fancy indexing across 10 seeds."""

    @pytest.mark.parametrize("seed", list(range(10)))
    def test_parity_seed(self, seed: int) -> None:
        rng = np.random.default_rng(seed)
        num_masks, img_h, img_w = _RANDOM_CONFIGS[seed]
        masks, xyxy = _random_masks_and_xyxy(rng, num_masks, img_h, img_w)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))

        selector = rng.random(num_masks) > 0.5
        # Guarantee at least one True in the selector so we test non-empty subsets.
        if not selector.any():
            selector[0] = True

        subset_cm = cm[selector]
        subset_dense = masks[selector]

        assert isinstance(subset_cm, CompactMask)
        assert len(subset_cm) == int(selector.sum())
        np.testing.assert_array_equal(
            subset_cm.to_dense(),
            subset_dense,
            err_msg=f"Boolean filter mismatch for seed={seed}",
        )

    @pytest.mark.parametrize("seed", list(range(10)))
    def test_list_index(self, seed: int) -> None:
        """Integer list indexing must match dense fancy indexing."""
        rng = np.random.default_rng(seed)
        num_masks, img_h, img_w = _RANDOM_CONFIGS[seed]
        masks, xyxy = _random_masks_and_xyxy(rng, num_masks, img_h, img_w)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))

        num_selected = min(num_masks, max(1, rng.integers(1, num_masks + 1)))
        indices = sorted(
            rng.choice(num_masks, size=num_selected, replace=False).tolist()
        )

        subset_cm = cm[indices]
        subset_dense = masks[indices]
        np.testing.assert_array_equal(
            subset_cm.to_dense(),
            subset_dense,
            err_msg=f"List index mismatch for seed={seed}, indices={indices}",
        )


class TestCompactMaskWithOffsetRandom:
    """with_offset roundtrip matches move_masks across 10 random seeds."""

    @pytest.mark.parametrize("seed", list(range(10)))
    def test_parity_seed(self, seed: int) -> None:
        rng = np.random.default_rng(seed)
        # Use smaller images to keep move_masks fast.
        num_masks = rng.integers(1, 10)
        img_h, img_w = int(rng.integers(30, 80)), int(rng.integers(30, 80))
        masks, xyxy = _random_masks_and_xyxy(rng, num_masks, img_h, img_w)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))

        # Random offset that may push some masks partially or fully off-frame.
        dx = int(rng.integers(-img_w, img_w))
        dy = int(rng.integers(-img_h, img_h))

        cm_shifted = cm.with_offset(dx=dx, dy=dy, new_image_shape=(img_h, img_w))
        expected = move_masks(
            masks=masks,
            offset=np.array([dx, dy], dtype=np.int32),
            resolution_wh=(img_w, img_h),
        )

        np.testing.assert_array_equal(
            cm_shifted.to_dense(),
            expected,
            err_msg=(
                f"with_offset mismatch for seed={seed}, "
                f"dx={dx}, dy={dy}, shape=({img_h},{img_w})"
            ),
        )

    @pytest.mark.parametrize("seed", list(range(10)))
    def test_offset_into_larger_canvas(self, seed: int) -> None:
        """Offset into a larger destination image must preserve pixels."""
        rng = np.random.default_rng(seed + 100)
        num_masks = rng.integers(1, 8)
        img_h, img_w = int(rng.integers(20, 50)), int(rng.integers(20, 50))
        masks, xyxy = _random_masks_and_xyxy(rng, num_masks, img_h, img_w)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))

        new_h, new_w = img_h * 2, img_w * 2
        dx = int(rng.integers(0, img_w))
        dy = int(rng.integers(0, img_h))

        cm_shifted = cm.with_offset(dx=dx, dy=dy, new_image_shape=(new_h, new_w))
        dense_shifted = cm_shifted.to_dense()

        assert dense_shifted.shape == (num_masks, new_h, new_w)
        # Manually place each original mask into the larger canvas.
        expected = np.zeros((num_masks, new_h, new_w), dtype=bool)
        for mask_idx in range(num_masks):
            expected[mask_idx, dy : dy + img_h, dx : dx + img_w] |= masks[mask_idx]

        np.testing.assert_array_equal(
            dense_shifted,
            expected,
            err_msg=f"Larger canvas offset mismatch for seed={seed}",
        )


class TestRleSplitCols:
    """Tests for _rle_split_cols: splitting F-order RLE into per-column lists."""

    def test_all_true_2x2(self) -> None:
        """All-True 2x2 splits into two columns each [0, 2]."""
        from supervision.detection.compact_mask import _rle_split_cols

        mask = np.ones((2, 2), dtype=bool)
        rle = _mask_to_rle_counts(mask)
        result = _rle_split_cols(rle, 2, 2)
        assert result == [[0, 2], [0, 2]]

    def test_all_false_3x3(self) -> None:
        """All-False 3x3 splits into three columns each [3]."""
        from supervision.detection.compact_mask import _rle_split_cols

        mask = np.zeros((3, 3), dtype=bool)
        rle = _mask_to_rle_counts(mask)
        result = _rle_split_cols(rle, 3, 3)
        assert result == [[3], [3], [3]]

    def test_mixed_2x2(self) -> None:
        """Mixed mask splits correctly per column."""
        from supervision.detection.compact_mask import _rle_split_cols

        mask = np.array([[True, False], [True, True]], dtype=bool)
        rle = _mask_to_rle_counts(mask)
        result = _rle_split_cols(rle, 2, 2)
        assert result == [[0, 2], [1, 1]]

    @pytest.mark.parametrize("seed", list(range(20)))
    def test_round_trip_random(self, seed: int) -> None:
        """Split then rejoin must reconstruct original mask for random inputs."""
        from supervision.detection.compact_mask import (
            _rle_join_cols,
            _rle_split_cols,
        )

        rng = np.random.default_rng(seed + 8000)
        crop_h = int(rng.integers(1, 30))
        crop_w = int(rng.integers(1, 30))
        mask = rng.random((crop_h, crop_w)) < 0.4
        rle = _mask_to_rle_counts(mask)
        per_col = _rle_split_cols(rle, crop_h, crop_w)

        assert len(per_col) == crop_w
        for c in range(crop_w):
            assert sum(per_col[c]) == crop_h, f"col {c} sum mismatch"

        # Rejoin and verify pixel equality.
        rejoined = _rle_join_cols(per_col, crop_h * crop_w)
        decoded = _rle_counts_to_mask(rejoined, crop_h, crop_w)
        np.testing.assert_array_equal(
            decoded,
            mask,
            err_msg=f"Split→join round-trip failed for seed={seed}",
        )

    def test_join_true_true_junction_no_zero_run(self) -> None:
        """_rle_join_cols merges True/True boundary; no zero-length False run inserted.

        When column A ends True and column B starts True (leading False count = 0),
        the junction must produce a single merged True run, not a zero-length False
        run between two True runs.  A zero-length run would inflate len(rle) and
        misroute density-based dispatch in _resize_crop.
        """
        from supervision.detection.compact_mask import _rle_join_cols

        # col A: [0, 3] → T=3 (height=3, all True)
        # col B: [0, 3] → T=3 (height=3, all True)
        # Merged: should be [0, 6], NOT [0, 3, 0, 3].
        cols = [[0, 3], [0, 3]]
        result = _rle_join_cols(cols, 6).tolist()
        assert result == [0, 6], (
            f"Expected [0, 6] (merged True runs), got {result}; "
            "zero-length False run would inflate density metric"
        )
        assert 0 not in result[1:], "Zero-length run found after junction merge"


class TestCompactMaskResize:
    """Tests for CompactMask.resize method.

    Verifies scaling behaviour, coordinate arithmetic, identity resize,
    empty collections, invalid dimensions, and dense parity with cv2.
    """

    @pytest.mark.parametrize(
        ("src_shape", "mask_slice", "target_shape", "description"),
        [
            (
                (10, 10),
                (slice(2, 5), slice(2, 5)),
                (100, 100),
                "10x upscale 10x10 to 100x100",
            ),
            (
                (480, 640),
                (slice(100, 200), slice(150, 300)),
                (240, 320),
                "HD halve 480x640 to 240x320",
            ),
            (
                (100, 200),
                (slice(20, 40), slice(50, 100)),
                (50, 400),
                "asymmetric: shrink H, grow W",
            ),
        ],
    )
    def test_scale_shape_and_offsets(
        self,
        src_shape: tuple[int, int],
        mask_slice: tuple[slice, slice],
        target_shape: tuple[int, int],
        description: str,
    ) -> None:
        """Resize scales shape and offsets proportionally."""
        img_h, img_w = src_shape
        masks = np.zeros((1, img_h, img_w), dtype=bool)
        masks[0, mask_slice[0], mask_slice[1]] = True
        xyxy = mask_to_xyxy(masks)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=src_shape)

        resized = cm.resize(target_shape)

        assert resized.shape == (1, target_shape[0], target_shape[1]), description

        sx = target_shape[1] / src_shape[1]
        sy = target_shape[0] / src_shape[0]
        orig_offset_x = int(cm.offsets[0, 0])
        orig_offset_y = int(cm.offsets[0, 1])
        expected_x = round(orig_offset_x * sx)
        expected_y = round(orig_offset_y * sy)
        assert abs(int(resized.offsets[0, 0]) - expected_x) <= 1, description
        assert abs(int(resized.offsets[0, 1]) - expected_y) <= 1, description

    def test_identity_preserves_rle(self) -> None:
        """Resize to same shape returns identical RLE, offsets, and crop shapes."""
        masks = np.zeros((1, 80, 80), dtype=bool)
        masks[0, 10:30, 15:45] = True
        xyxy = mask_to_xyxy(masks)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(80, 80))

        resized = cm.resize((80, 80))

        assert resized.shape == cm.shape
        np.testing.assert_array_equal(resized.offsets, cm.offsets)
        np.testing.assert_array_equal(resized._crop_shapes, cm._crop_shapes)
        for orig_rle, new_rle in zip(cm._rles, resized._rles):
            np.testing.assert_array_equal(orig_rle, new_rle)

    def test_empty_n0(self) -> None:
        """Resize of an empty CompactMask returns empty with new image_shape."""
        masks = np.zeros((0, 50, 50), dtype=bool)
        xyxy = np.empty((0, 4), dtype=np.float32)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(50, 50))

        resized = cm.resize((100, 200))

        assert len(resized) == 0
        assert resized.shape == (0, 100, 200)

    @pytest.mark.parametrize(
        "bad_shape",
        [
            (0, 50),
            (-1, 50),
            (50, 0),
            (50, -1),
        ],
    )
    def test_invalid_dimensions_raises(self, bad_shape: tuple[int, int]) -> None:
        """Resize with non-positive dimensions raises ValueError."""
        masks = np.zeros((1, 50, 50), dtype=bool)
        masks[0, 10:20, 10:20] = True
        xyxy = mask_to_xyxy(masks)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(50, 50))

        with pytest.raises(ValueError, match="positive"):
            cm.resize(bad_shape)

    def test_multi_mask_each_scales_independently(self) -> None:
        """N=4 masks at different positions all scale correctly after resize."""
        img_h, img_w = 100, 100
        target_h, target_w = 50, 50
        masks = np.zeros((4, img_h, img_w), dtype=bool)
        masks[0, 10:20, 10:20] = True
        masks[1, 30:50, 30:50] = True
        masks[2, 60:80, 60:80] = True
        masks[3, 5:10, 80:90] = True
        xyxy = mask_to_xyxy(masks)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))

        resized = cm.resize((target_h, target_w))

        assert resized.shape == (4, target_h, target_w)
        sx = target_w / img_w
        sy = target_h / img_h
        for i in range(4):
            expected_x = round(int(cm.offsets[i, 0]) * sx)
            expected_y = round(int(cm.offsets[i, 1]) * sy)
            assert abs(int(resized.offsets[i, 0]) - expected_x) <= 1, f"mask {i} x"
            assert abs(int(resized.offsets[i, 1]) - expected_y) <= 1, f"mask {i} y"

    def test_zero_extent_extreme_downscale(self) -> None:
        """Extreme downscale that collapses a 1px bbox returns valid 1x1 crop."""
        masks = np.zeros((1, 1000, 1000), dtype=bool)
        masks[0, 500, 500] = True
        xyxy = mask_to_xyxy(masks)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(1000, 1000))

        resized = cm.resize((2, 2))

        assert resized.shape == (1, 2, 2)
        assert int(resized._crop_shapes[0, 0]) >= 1
        assert int(resized._crop_shapes[0, 1]) >= 1
        dense = resized.to_dense()
        assert dense.shape == (1, 2, 2)

    @pytest.mark.parametrize("seed", list(range(10)))
    def test_dense_parity_roundtrip(self, seed: int) -> None:
        """Resized CompactMask matches OpenCV-resized dense masks within 1px."""
        rng = np.random.default_rng(seed + 500)
        img_h, img_w = 80, 120
        target_h, target_w = 40, 60
        num_masks = int(rng.integers(1, 5))
        masks, xyxy = _random_masks_and_xyxy(rng, num_masks, img_h, img_w)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))

        resized = cm.resize((target_h, target_w))
        resized_dense = resized.to_dense()

        for i in range(num_masks):
            expected = cv2.resize(
                masks[i].astype(np.uint8),
                (target_w, target_h),
                interpolation=cv2.INTER_NEAREST,
            ).astype(bool)
            actual = resized_dense[i]
            diff = np.abs(actual.astype(int) - expected.astype(int)).max()
            assert int(diff) <= 1, (
                f"Dense parity mismatch for seed={seed}, mask={i}: "
                f"max pixel diff={diff}"
            )


class TestRleResize:
    """Tests for _rle_resize direct F-order RLE resizing.

    Verifies that _rle_resize produces identical results to the decode ->
    cv2.resize(INTER_NEAREST) -> encode path for identity, upscale, downscale,
    non-square, all-False, all-True, single-pixel, and random masks.
    """

    def test_identity_4x4(self) -> None:
        """Identity resize (same dimensions) preserves the decoded mask."""
        from supervision.detection.compact_mask import _rle_resize

        mask = np.array(
            [
                [False, True, True, False],
                [True, True, False, False],
                [False, False, True, True],
                [True, False, False, True],
            ],
            dtype=bool,
        )
        rle = _mask_to_rle_counts(mask)
        result_rle = _rle_resize(rle, 4, 4, 4, 4)
        result = _rle_counts_to_mask(result_rle, 4, 4)
        np.testing.assert_array_equal(result, mask)

    def test_2x_upscale(self) -> None:
        """2x upscale of a 2x2 mask doubles each pixel."""
        from supervision.detection.compact_mask import _rle_resize

        mask = np.array(
            [
                [True, False],
                [False, True],
            ],
            dtype=bool,
        )
        rle = _mask_to_rle_counts(mask)
        result_rle = _rle_resize(rle, 2, 2, 4, 4)
        result = _rle_counts_to_mask(result_rle, 4, 4)

        expected = cv2.resize(
            mask.astype(np.uint8), (4, 4), interpolation=cv2.INTER_NEAREST
        ).astype(bool)
        np.testing.assert_array_equal(result, expected)

    def test_2x_downscale(self) -> None:
        """2x downscale of a 4x4 block mask halves dimensions."""
        from supervision.detection.compact_mask import _rle_resize

        mask = np.array(
            [
                [True, True, False, False],
                [True, True, False, False],
                [False, False, True, True],
                [False, False, True, True],
            ],
            dtype=bool,
        )
        rle = _mask_to_rle_counts(mask)
        result_rle = _rle_resize(rle, 4, 4, 2, 2)
        result = _rle_counts_to_mask(result_rle, 2, 2)

        expected = cv2.resize(
            mask.astype(np.uint8), (2, 2), interpolation=cv2.INTER_NEAREST
        ).astype(bool)
        np.testing.assert_array_equal(result, expected)

    def test_non_square_scale(self) -> None:
        """Non-square resize: 4x6 to 2x3 with independent axis scaling."""
        from supervision.detection.compact_mask import _rle_resize

        mask = np.zeros((4, 6), dtype=bool)
        mask[0:2, 0:3] = True
        mask[2:4, 3:6] = True
        rle = _mask_to_rle_counts(mask)
        result_rle = _rle_resize(rle, 4, 6, 2, 3)
        result = _rle_counts_to_mask(result_rle, 2, 3)

        expected = cv2.resize(
            mask.astype(np.uint8), (3, 2), interpolation=cv2.INTER_NEAREST
        ).astype(bool)
        np.testing.assert_array_equal(result, expected)

    @pytest.mark.parametrize(
        ("src_shape", "dst_shape"),
        [
            ((3, 3), (6, 6)),
            ((5, 5), (2, 2)),
            ((4, 6), (8, 12)),
            ((10, 10), (3, 3)),
        ],
    )
    def test_all_false(
        self, src_shape: tuple[int, int], dst_shape: tuple[int, int]
    ) -> None:
        """All-False mask resizes to all-False regardless of dimensions."""
        from supervision.detection.compact_mask import _rle_resize

        mask = np.zeros(src_shape, dtype=bool)
        rle = _mask_to_rle_counts(mask)
        result_rle = _rle_resize(rle, *src_shape, *dst_shape)
        result = _rle_counts_to_mask(result_rle, *dst_shape)
        assert not result.any()

    @pytest.mark.parametrize(
        ("src_shape", "dst_shape"),
        [
            ((3, 3), (6, 6)),
            ((5, 5), (2, 2)),
            ((4, 6), (8, 12)),
            ((10, 10), (3, 3)),
        ],
    )
    def test_all_true(
        self, src_shape: tuple[int, int], dst_shape: tuple[int, int]
    ) -> None:
        """All-True mask resizes to all-True regardless of dimensions."""
        from supervision.detection.compact_mask import _rle_resize

        mask = np.ones(src_shape, dtype=bool)
        rle = _mask_to_rle_counts(mask)
        result_rle = _rle_resize(rle, *src_shape, *dst_shape)
        result = _rle_counts_to_mask(result_rle, *dst_shape)
        assert result.all()

    def test_single_pixel_true_upscale(self) -> None:
        """Single True pixel in a 3x3 mask upscaled preserves position."""
        from supervision.detection.compact_mask import _rle_resize

        mask = np.zeros((3, 3), dtype=bool)
        mask[1, 1] = True
        rle = _mask_to_rle_counts(mask)
        result_rle = _rle_resize(rle, 3, 3, 6, 6)
        result = _rle_counts_to_mask(result_rle, 6, 6)

        expected = cv2.resize(
            mask.astype(np.uint8), (6, 6), interpolation=cv2.INTER_NEAREST
        ).astype(bool)
        np.testing.assert_array_equal(result, expected)

    @pytest.mark.parametrize("seed", list(range(45)))
    def test_roundtrip_parity_with_cv2(self, seed: int) -> None:
        """_rle_resize matches cv2.resize(INTER_NEAREST) within 1-pixel tolerance."""
        from supervision.detection.compact_mask import _rle_resize

        rng = np.random.default_rng(seed + 7000)
        crop_h = int(rng.integers(1, 50))
        crop_w = int(rng.integers(1, 50))
        new_crop_h = int(rng.integers(1, 100))
        new_crop_w = int(rng.integers(1, 100))

        mask = rng.random((crop_h, crop_w)) < 0.3
        rle = _mask_to_rle_counts(mask)
        result_rle = _rle_resize(rle, crop_h, crop_w, new_crop_h, new_crop_w)
        result = _rle_counts_to_mask(result_rle, new_crop_h, new_crop_w)

        expected = cv2.resize(
            mask.astype(np.uint8),
            (new_crop_w, new_crop_h),
            interpolation=cv2.INTER_NEAREST,
        ).astype(bool)
        diff = np.abs(result.astype(int) - expected.astype(int)).max()
        assert diff <= 1, (
            f"Parity mismatch >1px for seed={seed}, "
            f"src=({crop_h},{crop_w}), dst=({new_crop_h},{new_crop_w}): "
            f"max diff={diff}"
        )

    @pytest.mark.parametrize(
        ("src_shape", "dst_shape"),
        [
            ((1, 10), (1, 5)),
            ((10, 1), (5, 1)),
            ((1, 20), (1, 40)),
            ((20, 1), (40, 1)),
        ],
    )
    def test_tall_and_wide_crops(
        self, src_shape: tuple[int, int], dst_shape: tuple[int, int]
    ) -> None:
        """Single-row and single-col crops scale correctly with cv2 parity."""
        from supervision.detection.compact_mask import _rle_resize

        rng = np.random.default_rng(src_shape[0] * 31 + dst_shape[1] * 17)
        mask = rng.random(src_shape) < 0.5
        rle = _mask_to_rle_counts(mask)
        result_rle = _rle_resize(rle, *src_shape, *dst_shape)
        result = _rle_counts_to_mask(result_rle, *dst_shape)

        expected = cv2.resize(
            mask.astype(np.uint8),
            (dst_shape[1], dst_shape[0]),
            interpolation=cv2.INTER_NEAREST,
        ).astype(bool)
        np.testing.assert_array_equal(result, expected)

    @pytest.mark.parametrize(
        ("src_shape", "dst_shape"),
        [
            ((7, 11), (5, 13)),
            ((13, 7), (17, 3)),
            ((3, 5), (11, 7)),
            ((11, 13), (7, 17)),
        ],
    )
    def test_prime_sized_crops(
        self, src_shape: tuple[int, int], dst_shape: tuple[int, int]
    ) -> None:
        """Prime-sized crops with non-integer scale ratios match cv2 exactly."""
        from supervision.detection.compact_mask import _rle_resize

        rng = np.random.default_rng(src_shape[0] * 101 + dst_shape[1] * 53)
        mask = rng.random(src_shape) < 0.4
        rle = _mask_to_rle_counts(mask)
        result_rle = _rle_resize(rle, *src_shape, *dst_shape)
        result = _rle_counts_to_mask(result_rle, *dst_shape)

        expected = cv2.resize(
            mask.astype(np.uint8),
            (dst_shape[1], dst_shape[0]),
            interpolation=cv2.INTER_NEAREST,
        ).astype(bool)
        np.testing.assert_array_equal(result, expected)

    @pytest.mark.parametrize(
        ("src_val", "src_shape", "dst_shape"),
        [
            (True, (1, 1), (32, 32)),
            (False, (1, 1), (32, 32)),
        ],
    )
    def test_large_scale_ratio(
        self,
        src_val: bool,
        src_shape: tuple[int, int],
        dst_shape: tuple[int, int],
    ) -> None:
        """1x1 source resized to large shape fills entirely True or False."""
        from supervision.detection.compact_mask import _rle_resize

        mask = np.full(src_shape, src_val, dtype=bool)
        rle = _mask_to_rle_counts(mask)
        result_rle = _rle_resize(rle, *src_shape, *dst_shape)
        result = _rle_counts_to_mask(result_rle, *dst_shape)

        if src_val:
            assert result.all(), "1x1 True -> large shape must be all True"
        else:
            assert not result.any(), "1x1 False -> large shape must be all False"

    def test_resize_dispatch_uses_l3_for_sparse(self) -> None:
        """resize() dispatches to _rle_resize for sparse masks."""
        img_h, img_w = 100, 100
        masks = np.zeros((1, img_h, img_w), dtype=bool)
        masks[0, 50, 50] = True
        xyxy = mask_to_xyxy(masks).astype(np.float32)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))

        resized = cm.resize((200, 200))

        assert resized.shape == (1, 200, 200)
        dense = resized.to_dense()
        assert dense.sum() > 0

    def test_resize_dispatch_uses_cv2_for_dense(self) -> None:
        """_resize_crop falls back to cv2 for dense masks (above _L3_DENSITY_THRESHOLD).

        Checkerboard yields ~1 run per pixel, far above the 0.25 threshold.
        Result must match cv2.resize(INTER_NEAREST) within 1 pixel.
        """
        from supervision.detection.compact_mask import (
            _L3_DENSITY_THRESHOLD,
            _resize_crop,
        )
        from supervision.detection.utils.converters import _mask_to_rle_counts

        h, w = 20, 20
        # Checkerboard: alternates True/False → very dense RLE.
        rows, cols = np.meshgrid(np.arange(h), np.arange(w), indexing="ij")
        mask = ((rows + cols) % 2).astype(bool)
        rle = _mask_to_rle_counts(mask)
        density = len(rle) / max(1, h * w)
        assert density >= _L3_DENSITY_THRESHOLD, (
            f"Test precondition failed: density {density:.3f} < threshold "
            f"{_L3_DENSITY_THRESHOLD}; checkerboard should be dense"
        )

        result_rle = _resize_crop(rle, h, w, h // 2, w // 2)
        result = _rle_counts_to_mask(result_rle, h // 2, w // 2)
        expected = cv2.resize(
            mask.astype(np.uint8), (w // 2, h // 2), interpolation=cv2.INTER_NEAREST
        ).astype(bool)
        diff = np.abs(result.astype(int) - expected.astype(int)).max()
        assert int(diff) <= 1, f"Dense-path cv2 parity failed; max pixel diff={diff}"


class TestResizeParallelPath:
    """Tests for CompactMask.resize() thread-pool code path (N >= 8 masks)."""

    def test_parallel_resize_correctness(self) -> None:
        """resize() with N=10 masks exercises ThreadPoolExecutor; output is correct."""
        img_h, img_w = 80, 80
        n = 10  # above _PARALLEL_THRESHOLD = 8
        masks = np.zeros((n, img_h, img_w), dtype=bool)
        for i in range(n):
            r = 10 + i * 3
            masks[i, r : r + 8, r : r + 8] = True
        xyxy = mask_to_xyxy(masks).astype(np.float32)
        cm = CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))

        target = (40, 40)
        resized = cm.resize(target)

        assert resized.shape == (n, target[0], target[1])
        assert len(resized) == n
        # Each resized mask must be non-empty (the small squares survive downscale).
        for i in range(n):
            assert resized[i].any(), f"Mask {i} is empty after parallel resize"

    def test_parallel_matches_sequential(self) -> None:
        """Thread-pool path produces the same result as the sequential path."""
        img_h, img_w = 60, 60
        n_parallel = 10  # triggers thread pool
        n_sequential = 4  # stays sequential
        rng = np.random.default_rng(0)

        def _make_masks(n: int) -> CompactMask:
            masks = np.zeros((n, img_h, img_w), dtype=bool)
            for i in range(n):
                r, c = int(rng.integers(5, 30)), int(rng.integers(5, 30))
                masks[i, r : r + 10, c : c + 10] = True
            xyxy = mask_to_xyxy(masks).astype(np.float32)
            return CompactMask.from_dense(masks, xyxy, image_shape=(img_h, img_w))

        cm_par = _make_masks(n_parallel)
        cm_seq = _make_masks(n_sequential)

        target = (30, 30)
        resized_par = cm_par.resize(target)
        resized_seq = cm_seq.resize(target)

        # Both return correct shapes.
        assert resized_par.shape == (n_parallel, target[0], target[1])
        assert resized_seq.shape == (n_sequential, target[0], target[1])
