Newer
Older
TIASJudgeModel / tests / segmentation / test_pipeline.py
"""画像 1 枚のセグメンテーション(`segmentation/pipeline.py`)のテスト.

仕様(実装サマリーを仕様として扱う):

- 枠で切り出して ResUNet に掛け,枠のサイズに戻したマスクを元の画像サイズのマスクに配置する
  (枠の外は背景)
- 検出できない場合は前景の無いマスク(空マスク)
"""

from __future__ import annotations

import numpy as np

from tias_judge_model.segmentation.detection import Rect, full_rect
from tias_judge_model.segmentation.pipeline import (
    SegmentationParams,
    empty_masks,
    place_masks,
    segment_in_rect,
)
from tias_judge_model.segmentation.resunet import CropMasks, ResUNetRunner

PARAMS = SegmentationParams(foreground_channel=0, min_area=500, mask_threshold=127)


def test_place_masks_puts_crop_at_rect() -> None:
    crop = np.ones((3, 4), bool)
    masks = CropMasks(post=crop, no_post=crop, other_channel=~crop, postprocess_applied=True)
    placed = place_masks(masks, Rect(2, 1, 6, 4), height=6, width=8)
    assert placed.post.shape == (6, 8)
    assert placed.post.sum() == 12
    assert placed.post[1:4, 2:6].all()
    assert not placed.other_channel.any()
    assert placed.postprocess_applied is True


def test_empty_masks_have_no_foreground() -> None:
    m = empty_masks(5, 7)
    for arr in (m.post, m.no_post, m.other_channel):
        assert arr.shape == (5, 7)
        assert not arr.any()
    assert m.postprocess_applied is False


def _scene() -> tuple[np.ndarray, np.ndarray]:
    bgr = np.full((120, 160, 3), (150, 120, 110), np.uint8)  # 青みがかった背景
    yy, xx = np.mgrid[0:120, 0:160]
    mask = ((yy - 60) / 30) ** 2 + ((xx - 90) / 40) ** 2 <= 1
    bgr[mask] = (105, 95, 200)  # 赤(BGR)
    return bgr, mask


def _iou(a: np.ndarray, b: np.ndarray) -> float:
    return (a & b).sum() / (a | b).sum()


def test_segment_whole_image_with_dummy_model(dummy_seg_models: dict) -> None:
    runner = ResUNetRunner(dummy_seg_models["resunet"], 256)
    bgr, mask = _scene()
    m = segment_in_rect(bgr, full_rect(120, 160), runner, PARAMS)
    assert m.post.shape == mask.shape
    assert _iou(m.post, mask) > 0.9


def test_segment_in_sub_rect_keeps_outside_background(dummy_seg_models: dict) -> None:
    runner = ResUNetRunner(dummy_seg_models["resunet"], 256)
    bgr, mask = _scene()
    rect = Rect(40, 20, 140, 100)
    m = segment_in_rect(bgr, rect, runner, PARAMS)
    outside = np.ones_like(mask)
    outside[20:100, 40:140] = False
    assert not m.post[outside].any()
    assert not m.no_post[outside].any()
    assert _iou(m.post, mask) > 0.9