Team Ai
Apppublic

DevelopmentT/background-remover

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
test_ocr_service.py177 linesDownload Raw Back to tests
1"""2Unit tests for services/ocr_service.py3 4Tests cover:5  - Singleton pattern (get_instance returns same object)6  - Direct __init__ raises RuntimeError7  - read_all returns list[OCRResult]8  - read_lines is a generator9  - Low-confidence filtering10  - Empty image (no detections)11  - OCRResult dataclass integrity12 13NOTE: These tests require EasyOCR to be installed.14      If EasyOCR is not available, tests are skipped.15"""16 17import numpy as np18import pytest19 20try:21    import easyocr  # noqa: F40122    HAS_EASYOCR = True23except ImportError:24    HAS_EASYOCR = False25 26pytestmark = pytest.mark.skipif(not HAS_EASYOCR, reason="EasyOCR not installed")27 28import cv229 30from services.ocr_service import OCRResult, OCRService31 32 33# ---------------------------------------------------------------------------34# Fixtures35# ---------------------------------------------------------------------------36 37def _make_text_image(text: str = "Paracetamol 500mg", width: int = 400, height: int = 100) -> np.ndarray:38    """Create a clean black-text-on-white image for OCR."""39    img = np.full((height, width, 3), 255, dtype=np.uint8)40    cv2.putText(img, text, (10, height // 2 + 10), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 0), 2)41    # Convert to grayscale as OCR expects preprocessed input42    return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)43 44 45@pytest.fixture(scope="module")46def ocr_service() -> OCRService:47    """Module-scoped fixture so the model loads only once."""48    return OCRService.get_instance()49 50 51@pytest.fixture52def text_image() -> np.ndarray:53    return _make_text_image()54 55 56@pytest.fixture57def empty_image() -> np.ndarray:58    """Blank white image with no text."""59    return np.full((100, 400), 255, dtype=np.uint8)60 61 62# ---------------------------------------------------------------------------63# Tests64# ---------------------------------------------------------------------------65 66class TestSingleton:67    """Verify singleton pattern."""68 69    def test_same_instance(self):70        a = OCRService.get_instance()71        b = OCRService.get_instance()72        assert a is b73 74    def test_direct_init_raises(self):75        with pytest.raises(RuntimeError, match="Use OCRService.get_instance"):76            OCRService()77 78 79class TestOCRResult:80    """Verify OCRResult dataclass."""81 82    def test_fields(self):83        r = OCRResult(text="Hello", confidence=0.99, bbox=[[0, 0], [10, 0], [10, 10], [0, 10]])84        assert r.text == "Hello"85        assert r.confidence == 0.9986        assert len(r.bbox) == 487 88    def test_immutable(self):89        r = OCRResult(text="Hello", confidence=0.99, bbox=[[0, 0], [10, 0], [10, 10], [0, 10]])90        with pytest.raises(AttributeError):91            r.text = "World"  # frozen=True92 93 94class TestReadAll:95    """Test read_all method."""96 97    def test_returns_list(self, ocr_service: OCRService, text_image: np.ndarray):98        results = ocr_service.read_all(text_image)99        assert isinstance(results, list)100 101    def test_detects_text(self, ocr_service: OCRService, text_image: np.ndarray):102        results = ocr_service.read_all(text_image)103        # Should detect at least something on a clean text image104        assert len(results) >= 1105        assert all(isinstance(r, OCRResult) for r in results)106 107    def test_text_content(self, ocr_service: OCRService, text_image: np.ndarray):108        results = ocr_service.read_all(text_image)109        all_text = " ".join(r.text for r in results).lower()110        # The OCR should detect at least part of "paracetamol"111        assert "paracetamol" in all_text or "500" in all_text or len(results) > 0112 113    def test_confidence_range(self, ocr_service: OCRService, text_image: np.ndarray):114        results = ocr_service.read_all(text_image)115        for r in results:116            assert 0.0 <= r.confidence <= 1.0117 118    def test_bbox_format(self, ocr_service: OCRService, text_image: np.ndarray):119        results = ocr_service.read_all(text_image)120        for r in results:121            assert len(r.bbox) == 4122            for point in r.bbox:123                assert len(point) == 2124                assert isinstance(point[0], int)125                assert isinstance(point[1], int)126 127 128class TestReadLines:129    """Test read_lines generator."""130 131    def test_is_generator(self, ocr_service: OCRService, text_image: np.ndarray):132        gen = ocr_service.read_lines(text_image)133        import types134        assert isinstance(gen, types.GeneratorType)135 136    def test_yields_results(self, ocr_service: OCRService, text_image: np.ndarray):137        results = list(ocr_service.read_lines(text_image))138        assert len(results) >= 1139 140 141class TestLowConfidenceFilter:142    """Test that low-confidence results are filtered."""143 144    def test_high_threshold_filters(self, ocr_service: OCRService, text_image: np.ndarray):145        # With normal threshold146        normal = ocr_service.read_all(text_image, min_confidence=0.15)147        # With very high threshold148        strict = ocr_service.read_all(text_image, min_confidence=0.99)149        # Strict should have fewer or equal results150        assert len(strict) <= len(normal)151 152    def test_zero_threshold_includes_all(self, ocr_service: OCRService, text_image: np.ndarray):153        results = ocr_service.read_all(text_image, min_confidence=0.0)154        assert isinstance(results, list)155 156 157class TestEdgeCases:158    """Edge case coverage for OCR."""159 160    def test_empty_image_no_crash(self, ocr_service: OCRService, empty_image: np.ndarray):161        results = ocr_service.read_all(empty_image)162        assert isinstance(results, list)163        # Blank image should have 0 detections (or very few false positives)164        assert len(results) <= 2165 166    def test_small_image(self, ocr_service: OCRService):167        """Very small image should not crash."""168        small = np.full((10, 10), 128, dtype=np.uint8)169        results = ocr_service.read_all(small)170        assert isinstance(results, list)171 172    def test_noisy_image(self, ocr_service: OCRService):173        """Random noise image should not crash."""174        noise = np.random.randint(0, 256, (100, 400), dtype=np.uint8)175        results = ocr_service.read_all(noise)176        assert isinstance(results, list)177