Filter low variance YOLO negative tiles
This commit is contained in:
@@ -5,6 +5,8 @@ from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
@@ -39,6 +41,8 @@ def test_operator_yolo_tile_dataset_export_script_contract() -> None:
|
||||
assert "stride" in script
|
||||
assert "negative_keep_ratio" in script
|
||||
assert "min_label_visible_ratio" in script
|
||||
assert "drop_low_variance_negatives" in script
|
||||
assert "skipped_low_variance_negative_tile_count" in script
|
||||
assert "positive_tile_count" in script
|
||||
assert "negative_tile_count" in script
|
||||
assert "skipped_negative_tile_count" in script
|
||||
@@ -67,6 +71,8 @@ def test_operator_yolo_tile_dataset_export_help_does_not_require_gis_dependencie
|
||||
assert "--negative-keep-ratio" in result.stdout
|
||||
assert "--min-label-visible-ratio" in result.stdout
|
||||
assert "--background-negative-repeat" in result.stdout
|
||||
assert "--drop-low-variance-negatives" in result.stdout
|
||||
assert "--blank-range-threshold" in result.stdout
|
||||
|
||||
|
||||
def test_iter_tile_windows_covers_edges_without_duplicates() -> None:
|
||||
@@ -171,3 +177,86 @@ def test_background_category_is_derived_for_legacy_operator_manifests() -> None:
|
||||
assert module.background_category_for_sample(pure_empty_sample) == "pure_empty_negative"
|
||||
assert module.background_category_for_sample(sparse_context_sample) == "sparse_building_context"
|
||||
assert module.background_category_for_sample(reference_sample) == "reference_aoi"
|
||||
|
||||
|
||||
def test_export_can_skip_low_variance_negative_tiles(tmp_path: Path, monkeypatch) -> None:
|
||||
module = load_tile_exporter()
|
||||
raster_path = tmp_path / "sample.tif"
|
||||
reference_path = tmp_path / "reference.geojson"
|
||||
raster_path.write_bytes(b"fake-raster")
|
||||
reference_path.write_text('{"type": "FeatureCollection", "features": []}', encoding="utf-8")
|
||||
|
||||
class FakeDataset:
|
||||
width = 256
|
||||
height = 128
|
||||
crs = "EPSG:31370"
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc, traceback):
|
||||
return False
|
||||
|
||||
class FakeRasterio:
|
||||
@staticmethod
|
||||
def open(path):
|
||||
assert Path(path) == raster_path
|
||||
return FakeDataset()
|
||||
|
||||
class FakeImageObject:
|
||||
def __init__(self, array):
|
||||
self.array = array
|
||||
|
||||
def save(self, path):
|
||||
Path(path).write_bytes(b"png")
|
||||
|
||||
class FakeImage:
|
||||
@staticmethod
|
||||
def fromarray(array):
|
||||
return FakeImageObject(array)
|
||||
|
||||
def fake_image_array_from_raster_window(dataset, tile_window):
|
||||
if tile_window.col_off == 0:
|
||||
return np.full((128, 128, 3), 255, dtype=np.uint8)
|
||||
image = np.zeros((128, 128, 3), dtype=np.uint8)
|
||||
image[:, 64:, :] = 80
|
||||
return image
|
||||
|
||||
monkeypatch.setattr(module, "rasterio", FakeRasterio)
|
||||
monkeypatch.setattr(module, "Image", FakeImage)
|
||||
monkeypatch.setattr(module, "load_reference_pixel_boxes", lambda reference_path, dataset, min_label_px: [])
|
||||
monkeypatch.setattr(module, "image_array_from_raster_window", fake_image_array_from_raster_window)
|
||||
|
||||
records = module.export_sample_tiles(
|
||||
sample={
|
||||
"sample_slug": "blank_negative",
|
||||
"sample_role": "background_candidate",
|
||||
"background_category": "pure_empty_negative",
|
||||
"raster_path": str(raster_path),
|
||||
"reference_path": str(reference_path),
|
||||
},
|
||||
manifest_path=tmp_path / "operator_samples_manifest.json",
|
||||
output_dir=tmp_path / "dataset",
|
||||
val_slugs=set(),
|
||||
tile_size=128,
|
||||
stride=128,
|
||||
negative_keep_ratio=1.0,
|
||||
min_label_px=4,
|
||||
min_label_visible_ratio=0.0,
|
||||
background_negative_repeat=1,
|
||||
drop_low_variance_negatives=True,
|
||||
blank_range_threshold=3,
|
||||
)
|
||||
|
||||
skipped = [record for record in records if not record["kept"]]
|
||||
kept = [record for record in records if record["kept"]]
|
||||
|
||||
assert len(skipped) == 1
|
||||
assert skipped[0]["skip_reason"] == "low_visual_variance_negative"
|
||||
assert skipped[0]["low_visual_variance"] is True
|
||||
assert skipped[0]["is_negative"] is True
|
||||
assert skipped[0]["tile_index"] == 0
|
||||
assert len(kept) == 1
|
||||
assert kept[0]["tile_index"] == 1
|
||||
assert kept[0]["low_visual_variance"] is False
|
||||
assert Path(kept[0]["image_path"]).exists()
|
||||
|
||||
Reference in New Issue
Block a user