项目文件夹

文件
wehub-resource-sync 9194ef5abd
Docs/Test Workflow / Test docs build (push) Failing after 0s
Check links & references / links-check (push) Failing after 1s
Pytest/Test Workflow / Import Test and Pytest Run (ubuntu-latest, 3.10) (push) Failing after 0s
Pytest/Test Workflow / Import Test and Pytest Run (ubuntu-latest, 3.11) (push) Failing after 0s
PR Conflict Labeler / main (push) Failing after 2s
Pytest/Test Workflow / Import Test and Pytest Run (ubuntu-latest, 3.12) (push) Failing after 2s
Pytest/Test Workflow / Import Test and Pytest Run (ubuntu-latest, 3.13) (push) Failing after 0s
Pytest/Test Workflow / Build this Package (push) Failing after 5s
Pytest/Test Workflow / Import Test and Pytest Run (macos-latest, 3.10) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (macos-latest, 3.11) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (macos-latest, 3.12) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (macos-latest, 3.13) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (windows-latest, 3.10) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (windows-latest, 3.11) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (windows-latest, 3.12) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (windows-latest, 3.13) (push) Has been cancelled
Pytest/Test Workflow / testing-guardian (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:06:10 +08:00

370 行
12 KiB
Python

"""Tests for windowed GeoTIFF reads in InferenceSlicer."""
import threading
import time
from collections.abc import Callable
import numpy as np
import pytest
from supervision.detection.core import Detections
from supervision.detection.tools.inference_slicer import InferenceSlicer
class _FakeCRS:
"""Minimal rasterio-style CRS stub exposing only `is_projected`."""
def __init__(self, is_projected: bool):
self.is_projected = is_projected
def __repr__(self) -> str:
kind = "projected" if self.is_projected else "geographic"
return f"_FakeCRS({kind})"
class _FakeRasterDataset:
"""Lightweight rasterio-style dataset supporting windowed reads.
Mimics the duck-typed interface that ``InferenceSlicer`` relies on without
requiring ``rasterio`` to be installed.
"""
def __init__(self, image_hwc: np.ndarray, crs: object | None = None):
self._image = image_hwc # numpy (H, W, C)
self.height, self.width = image_hwc.shape[:2]
self.crs = crs # None or object with .is_projected
def read(self, window: tuple[tuple[int, int], tuple[int, int]]) -> np.ndarray:
(row_start, row_stop), (col_start, col_stop) = window
crop = self._image[row_start:row_stop, col_start:col_stop, :]
return np.transpose(crop, (2, 0, 1)) # (C, H, W) like rasterio
class _ConcurrencyCheckDataset:
"""Dataset that tracks peak concurrent reads to verify read serialization."""
def __init__(self, image_hwc: np.ndarray):
self._image = image_hwc
self.height, self.width = image_hwc.shape[:2]
self.crs = None
self._lock = threading.Lock()
self._active = 0
self.peak_concurrent = 0
def read(self, window: tuple[tuple[int, int], tuple[int, int]]) -> np.ndarray:
with self._lock:
self._active += 1
self.peak_concurrent = max(self.peak_concurrent, self._active)
time.sleep(0.002) # amplify race window so concurrent reads are detectable
(row_start, row_stop), (col_start, col_stop) = window
crop = self._image[row_start:row_stop, col_start:col_stop, :]
result = np.transpose(crop, (2, 0, 1))
with self._lock:
self._active -= 1
return result
def _sortable(detections: Detections) -> np.ndarray:
"""Sort detection boxes so two runs can be compared order-independently."""
return np.array(sorted(detections.xyxy.tolist()), dtype=float)
@pytest.fixture
def make_raster_dataset() -> Callable[..., _FakeRasterDataset]:
"""Factory: create a _FakeRasterDataset from a numpy image array."""
def factory(image_hwc: np.ndarray, crs: object | None = None) -> _FakeRasterDataset:
return _FakeRasterDataset(image_hwc, crs=crs)
return factory
@pytest.fixture
def fixed_detection_callback() -> Callable[[np.ndarray], Detections]:
"""Return a constant single-box detection for every tile."""
def callback(_: np.ndarray) -> Detections:
return Detections(
xyxy=np.array([[0, 0, 10, 10]], dtype=float),
confidence=np.array([0.9]),
class_id=np.array([0]),
)
return callback
@pytest.fixture
def make_recording_callback() -> Callable[[list[np.ndarray]], Callable]:
"""Factory: given a sink list, return a callback that records each tile."""
def factory(sink: list[np.ndarray]) -> Callable[[np.ndarray], Detections]:
def callback(tile: np.ndarray) -> Detections:
sink.append(tile.copy())
return Detections.empty()
return callback
return factory
class TestInferenceSlicerGeoTIFF:
def test_windowed_raster_matches_in_memory_array(
self,
make_raster_dataset: Callable,
fixed_detection_callback: Callable,
) -> None:
"""Raster and array paths produce identical merged detections."""
# Arrange
rng = np.random.default_rng(42)
image = rng.integers(0, 255, size=(256, 256, 3), dtype=np.uint8)
dataset = make_raster_dataset(image, crs=_FakeCRS(is_projected=True))
slicer = InferenceSlicer(
callback=fixed_detection_callback,
slice_wh=128,
overlap_wh=0,
)
# Act
detections_array = slicer(image)
detections_raster = slicer(dataset)
# Assert
assert np.array_equal(_sortable(detections_array), _sortable(detections_raster))
@pytest.mark.parametrize(
("seed", "image_shape", "slice_wh", "overlap_wh"),
[
pytest.param(7, (128, 192, 3), 64, 0, id="no-overlap"),
pytest.param(99, (200, 220, 3), 96, 32, id="with-overlap"),
],
)
def test_raster_tiles_match_array_tiles(
self,
make_raster_dataset: Callable,
make_recording_callback: Callable,
seed: int,
image_shape: tuple[int, int, int],
slice_wh: int,
overlap_wh: int,
) -> None:
"""Windowed raster read returns identical pixel tiles as array path."""
# Arrange
rng = np.random.default_rng(seed)
image = rng.integers(0, 255, size=image_shape, dtype=np.uint8)
dataset = make_raster_dataset(image)
array_tiles: list[np.ndarray] = []
raster_tiles: list[np.ndarray] = []
slicer_array = InferenceSlicer(
callback=make_recording_callback(array_tiles),
slice_wh=slice_wh,
overlap_wh=overlap_wh,
)
slicer_raster = InferenceSlicer(
callback=make_recording_callback(raster_tiles),
slice_wh=slice_wh,
overlap_wh=overlap_wh,
)
# Act
slicer_array(image)
slicer_raster(dataset)
# Assert
assert len(array_tiles) == len(raster_tiles) > 0
for array_tile, raster_tile in zip(array_tiles, raster_tiles):
assert np.array_equal(array_tile, raster_tile)
def test_windowed_raster_preserves_band_dtype(
self, make_raster_dataset: Callable
) -> None:
"""Tiles read from a dataset keep the source dtype (e.g. uint16)."""
# Arrange
rng = np.random.default_rng(5)
image = rng.integers(0, 4000, size=(128, 128, 3), dtype=np.uint16)
dataset = make_raster_dataset(image)
seen: list[np.ndarray] = []
def callback(tile: np.ndarray) -> Detections:
seen.append(tile)
return Detections.empty()
slicer = InferenceSlicer(callback=callback, slice_wh=64, overlap_wh=0)
# Act
slicer(dataset)
# Assert
assert seen
assert all(tile.dtype == np.uint16 for tile in seen)
@pytest.mark.parametrize(
"crs",
[
pytest.param(None, id="no-crs"),
pytest.param(_FakeCRS(is_projected=True), id="projected-crs"),
],
)
def test_crs_allows_slicing(
self,
make_raster_dataset: Callable,
fixed_detection_callback: Callable,
crs: object | None,
) -> None:
"""None CRS and projected CRS both allow slicing without error."""
image = np.zeros((128, 128, 3), dtype=np.uint8)
dataset = make_raster_dataset(image, crs=crs)
slicer = InferenceSlicer(
callback=fixed_detection_callback,
slice_wh=64,
overlap_wh=0,
)
detections = slicer(dataset)
assert len(detections) == 4
def test_geographic_crs_raises(
self,
make_raster_dataset: Callable,
fixed_detection_callback: Callable,
) -> None:
"""Dataset with a geographic (non-projected) CRS raises ValueError."""
image = np.zeros((128, 128, 3), dtype=np.uint8)
dataset = make_raster_dataset(image, crs=_FakeCRS(is_projected=False))
slicer = InferenceSlicer(
callback=fixed_detection_callback,
slice_wh=64,
overlap_wh=0,
)
with pytest.raises(ValueError, match="projected coordinate reference"):
slicer(dataset)
def test_single_band_raster_produces_hwc1_tiles(
self, make_raster_dataset: Callable
) -> None:
"""Single-band raster tiles arrive at the callback as (H, W, 1) arrays."""
image = np.zeros((128, 128, 1), dtype=np.uint8)
dataset = make_raster_dataset(image)
seen: list[np.ndarray] = []
def callback(tile: np.ndarray) -> Detections:
seen.append(tile)
return Detections.empty()
slicer = InferenceSlicer(callback=callback, slice_wh=64, overlap_wh=0)
slicer(dataset)
assert seen
assert all(tile.ndim == 3 and tile.shape[2] == 1 for tile in seen)
def test_raster_smaller_than_slice_produces_single_tile(
self, make_raster_dataset: Callable
) -> None:
"""Raster smaller than slice_wh is processed as exactly one tile."""
image = np.zeros((48, 64, 3), dtype=np.uint8)
dataset = make_raster_dataset(image)
tile_count: list[int] = [0]
def callback(tile: np.ndarray) -> Detections:
tile_count[0] += 1
return Detections.empty()
slicer = InferenceSlicer(callback=callback, slice_wh=128, overlap_wh=0)
slicer(dataset)
assert tile_count[0] == 1
def test_compact_masks_with_windowed_raster(
self, make_raster_dataset: Callable
) -> None:
"""compact_masks=True correctly moves and compresses masks from raster tiles."""
rng = np.random.default_rng(17)
image = rng.integers(0, 255, size=(128, 128, 3), dtype=np.uint8)
dataset = make_raster_dataset(image)
def masked_callback(tile: np.ndarray) -> Detections:
h, w = tile.shape[:2]
mask = np.zeros((1, h, w), dtype=bool)
mask[0, : h // 2, : w // 2] = True
return Detections(
xyxy=np.array([[0, 0, w // 2, h // 2]], dtype=float),
confidence=np.array([0.9]),
class_id=np.array([0]),
mask=mask,
)
slicer = InferenceSlicer(
callback=masked_callback,
slice_wh=64,
overlap_wh=0,
compact_masks=True,
)
detections = slicer(dataset)
assert len(detections) > 0
def test_thread_workers_with_raster_serializes_reads(
self, fixed_detection_callback: Callable
) -> None:
"""Raster reads are serialized even when thread_workers > 1."""
rng = np.random.default_rng(3)
image = rng.integers(0, 255, size=(256, 256, 3), dtype=np.uint8)
dataset = _ConcurrencyCheckDataset(image)
slicer = InferenceSlicer(
callback=fixed_detection_callback,
slice_wh=64,
overlap_wh=0,
thread_workers=4,
)
slicer(dataset)
assert dataset.peak_concurrent == 1
def test_real_rasterio_memoryfile_integration(
self, fixed_detection_callback: Callable
) -> None:
"""Real rasterio MemoryFile produces same detections as the array path."""
pytest.importorskip("rasterio")
from rasterio.io import MemoryFile
# Arrange
rng = np.random.default_rng(123)
image = rng.integers(0, 255, size=(128, 128, 3), dtype=np.uint8)
bands = np.transpose(image, (2, 0, 1)) # (C, H, W)
slicer = InferenceSlicer(
callback=fixed_detection_callback,
slice_wh=64,
overlap_wh=0,
)
detections_array = slicer(image)
profile = {
"driver": "GTiff",
"height": image.shape[0],
"width": image.shape[1],
"count": image.shape[2],
"dtype": image.dtype,
}
# Act
with MemoryFile() as memfile:
with memfile.open(**profile) as dst:
dst.write(bands)
with memfile.open() as dataset:
detections_raster = slicer(dataset)
# Assert
assert np.array_equal(_sortable(detections_array), _sortable(detections_raster))