| from __future__ import annotations |
|
|
| from pathlib import Path |
|
|
| import numpy as np |
| import pytest |
| from PIL import Image |
|
|
|
|
| def _make_sfw_fixture(path: Path, size: tuple[int, int] = (512, 640)) -> Path: |
| """Solid + soft blob — deterministic, non-explicit test image.""" |
| img = Image.new("RGB", size, (210, 190, 230)) |
| for x in range(180, 330): |
| for y in range(140, 360): |
| img.putpixel((x, y), (240, 210, 200)) |
| path.parent.mkdir(parents=True, exist_ok=True) |
| img.save(path) |
| return path |
|
|
|
|
| def test_wd_preprocess_matches_imgutils(tmp_path: Path) -> None: |
| from imgutils.tagging.wd14 import _prepare_image_for_tagging |
|
|
| from app.inference_engine import preprocess_wd14 |
|
|
| image_path = _make_sfw_fixture(tmp_path / "wd.png") |
| expected = _prepare_image_for_tagging(str(image_path), 448) |
| actual = preprocess_wd14(image_path, 448) |
| assert actual.shape == expected.shape |
| np.testing.assert_allclose(actual, expected, rtol=0, atol=1e-5) |
|
|
|
|
| def test_ml_preprocess_matches_imgutils(tmp_path: Path) -> None: |
| from imgutils.data import load_image |
| from imgutils.tagging.mldanbooru import _resize_align, _to_tensor |
|
|
| from app.inference_engine import preprocess_mldanbooru |
|
|
| image_path = _make_sfw_fixture(tmp_path / "ml.png", size=(640, 480)) |
| pil = load_image(str(image_path), mode="RGB") |
| expected = _to_tensor(_resize_align(pil, 448, True))[None, ...] |
| actual = preprocess_mldanbooru(image_path, size=448, keep_ratio=True) |
| assert actual.shape == expected.shape |
| np.testing.assert_allclose(actual, expected, rtol=0, atol=1e-5) |
|
|
|
|
| def test_force_cpu_providers(monkeypatch: pytest.MonkeyPatch) -> None: |
| from app import inference_engine as eng |
|
|
| monkeypatch.setenv("FORCE_CPU_INFERENCE", "true") |
| providers = eng.ort_providers() |
| assert providers == ["CPUExecutionProvider"] |
|
|
| monkeypatch.setenv("FORCE_CPU_INFERENCE", "false") |
| providers = eng.ort_providers() |
| assert providers[0] in {"CUDAExecutionProvider", "CPUExecutionProvider"} |
| assert "CPUExecutionProvider" in providers |
|
|
|
|
| @pytest.mark.live_onnx |
| def test_engine_scores_close_to_imgutils_wd(tmp_path: Path) -> None: |
| from imgutils.tagging import get_wd14_tags |
|
|
| from app.inference_engine import InferenceEngine |
| from app.services import _normalize_score_tags, _parse_wd14_raw |
|
|
| image_path = _make_sfw_fixture(tmp_path / "live_wd.png") |
| raw = get_wd14_tags( |
| str(image_path), |
| model_name="SwinV2_v3", |
| general_threshold=0.35, |
| no_underline=False, |
| drop_overlap=False, |
| fmt="general", |
| ) |
| expected = _parse_wd14_raw(raw) |
|
|
| engine = InferenceEngine() |
| engine.warm("wd_swinv2_v3") |
| actual = engine.score_one( |
| image_path, |
| tagger_model="wd_swinv2_v3", |
| wd_general_threshold=0.35, |
| ) |
|
|
| assert set(actual) == set(expected) |
| for tag, score in expected.items(): |
| |
| assert abs(actual[tag] - score) < 1e-3, tag |
|
|
|
|
| @pytest.mark.live_onnx |
| def test_engine_scores_close_to_imgutils_ml(tmp_path: Path) -> None: |
| from imgutils.tagging import get_mldanbooru_tags |
|
|
| from app.inference_engine import InferenceEngine |
| from app.services import _normalize_score_tags, _parse_mldanbooru_raw |
|
|
| image_path = _make_sfw_fixture(tmp_path / "live_ml.png") |
| raw = get_mldanbooru_tags( |
| str(image_path), |
| threshold=0.0, |
| size=448, |
| keep_ratio=True, |
| drop_overlap=False, |
| use_real_name=False, |
| ) |
| expected = _normalize_score_tags(_parse_mldanbooru_raw(raw)) |
|
|
| engine = InferenceEngine() |
| engine.warm("ml_danbooru") |
| actual = engine.score_one( |
| image_path, |
| tagger_model="ml_danbooru", |
| wd_general_threshold=0.35, |
| ) |
|
|
| assert set(actual) == set(expected) |
| for tag, score in expected.items(): |
| assert abs(actual[tag] - score) < 1e-3, tag |
|
|
|
|
| @pytest.mark.live_onnx |
| def test_wd_batch_matches_single(tmp_path: Path) -> None: |
| from app.inference_engine import InferenceEngine |
|
|
| paths = [ |
| _make_sfw_fixture(tmp_path / "a.png", (448, 448)), |
| _make_sfw_fixture(tmp_path / "b.png", (512, 384)), |
| _make_sfw_fixture(tmp_path / "c.png", (600, 600)), |
| ] |
| engine = InferenceEngine() |
| engine.warm("wd_swinv2_v3") |
| singles = [ |
| engine.score_one(p, tagger_model="wd_swinv2_v3", wd_general_threshold=0.35) |
| for p in paths |
| ] |
| batched = engine.score_many( |
| paths, |
| tagger_model="wd_swinv2_v3", |
| wd_general_threshold=0.35, |
| batch_size=3, |
| ) |
| assert len(batched) == 3 |
| for single, batch in zip(singles, batched): |
| assert set(single) == set(batch) |
| for tag, score in single.items(): |
| |
| assert abs(batch[tag] - score) < 1e-3, tag |
|
|
|
|
| def test_engine_clear_drops_cached_sessions() -> None: |
| from app.inference_engine import InferenceEngine |
|
|
| engine = InferenceEngine() |
| engine._sessions["wd_swinv2_v3"] = object() |
| engine._sessions["ml_danbooru"] = object() |
| assert engine.loaded_models() == ["ml_danbooru", "wd_swinv2_v3"] |
| assert engine.clear() == ["ml_danbooru", "wd_swinv2_v3"] |
| assert engine.loaded_models() == [] |
|
|