File size: 8,145 Bytes
c02c6ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
"""
OCR (Optical Character Recognition) module for extracting text from images.
Uses EasyOCR for reliable text detection and recognition.
"""

import logging
from typing import List, Dict, Optional
import easyocr

logger = logging.getLogger(__name__)


class OCREngine:
    """Handle OCR for food labels and expiry dates."""
    
    def __init__(self, languages: List[str] = None, use_gpu: bool = False):
        """
        Initialize OCR engine.
        
        Args:
            languages: List of language codes (e.g., ['en', 'es'])
            use_gpu: Whether to use GPU for OCR
        """
        self.languages = languages or ['en']
        self.use_gpu = use_gpu
        self.reader = None
        self._init_reader()
    
    def _init_reader(self):
        """Initialize EasyOCR reader."""
        try:
            logger.info(f"Initializing OCR reader for languages: {self.languages}")
            self.reader = easyocr.Reader(
                self.languages,
                gpu=self.use_gpu,
                model_storage_directory=None
            )
            logger.info("OCR reader initialized successfully")
        except Exception as e:
            logger.error(f"Failed to initialize OCR reader: {e}")
            raise RuntimeError(f"OCR initialization failed: {e}")
    
    def extract_text(self, image_path: str, confidence_threshold: float = 0.3) -> Dict:
        """
        Extract text from image file.
        
        Args:
            image_path: Path to image file
            confidence_threshold: Minimum confidence for text detection
            
        Returns:
            Dictionary with extracted text and metadata
        """
        if self.reader is None:
            raise RuntimeError("OCR reader not initialized")
        
        logger.info(f"Extracting text from {image_path}")
        
        try:
            results = self.reader.readtext(image_path)
            
            extracted_texts = []
            for (bbox, text, confidence) in results:
                if confidence >= confidence_threshold:
                    extracted_texts.append({
                        "text": text.strip(),
                        "confidence": float(confidence),
                        "bbox": {
                            "x": float(bbox[0][0]),
                            "y": float(bbox[0][1]),
                            "width": float(bbox[2][0] - bbox[0][0]),
                            "height": float(bbox[2][1] - bbox[0][1])
                        }
                    })
            
            logger.info(f"Extracted {len(extracted_texts)} text regions")
            
            return {
                "status": "success",
                "texts": extracted_texts,
                "full_text": " ".join([t["text"] for t in extracted_texts]),
                "num_texts": len(extracted_texts)
            }
        
        except Exception as e:
            logger.error(f"OCR extraction failed: {e}")
            return {
                "status": "error",
                "error": str(e),
                "texts": [],
                "full_text": ""
            }
    
    def extract_text_from_bytes(
        self,
        image_bytes: bytes,
        confidence_threshold: float = 0.3
    ) -> Dict:
        """
        Extract text from image bytes.
        
        Args:
            image_bytes: Image data as bytes
            confidence_threshold: Minimum confidence for text detection
            
        Returns:
            Dictionary with extracted text and metadata
        """
        if self.reader is None:
            raise RuntimeError("OCR reader not initialized")
        
        import cv2
        import numpy as np
        
        logger.info("Extracting text from image bytes")
        
        try:
            # Convert bytes to image
            nparr = np.frombuffer(image_bytes, np.uint8)
            image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
            
            if image is None:
                raise ValueError("Failed to decode image")
            
            # Convert BGR to RGB for EasyOCR
            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
            
            results = self.reader.readtext(image)
            
            extracted_texts = []
            for (bbox, text, confidence) in results:
                if confidence >= confidence_threshold:
                    extracted_texts.append({
                        "text": text.strip(),
                        "confidence": float(confidence),
                        "bbox": {
                            "x": float(bbox[0][0]),
                            "y": float(bbox[0][1]),
                            "width": float(bbox[2][0] - bbox[0][0]),
                            "height": float(bbox[2][1] - bbox[0][1])
                        }
                    })
            
            logger.info(f"Extracted {len(extracted_texts)} text regions from bytes")
            
            return {
                "status": "success",
                "texts": extracted_texts,
                "full_text": " ".join([t["text"] for t in extracted_texts]),
                "num_texts": len(extracted_texts)
            }
        
        except Exception as e:
            logger.error(f"OCR extraction from bytes failed: {e}")
            return {
                "status": "error",
                "error": str(e),
                "texts": [],
                "full_text": ""
            }
    
    def parse_ingredients_from_text(self, text: str) -> List[str]:
        """
        Parse ingredient names from extracted text.
        Uses simple heuristics to identify likely ingredient names.
        
        Args:
            text: Extracted text from OCR
            
        Returns:
            List of potential ingredient names
        """
        # Common ingredient keywords
        ingredient_keywords = {
            "contains", "ingredients", "product", "made from",
            "of", "and", "with", "including", "mix"
        }
        
        # Split text and filter potential ingredients
        words = text.lower().split()
        potential_ingredients = []
        
        for word in words:
            # Remove common non-ingredient words and punctuation
            cleaned = word.strip('.,;:!?()[]{}"\'-').strip()
            
            if (len(cleaned) > 2 and
                cleaned not in ingredient_keywords and
                not cleaned.isdigit() and
                '%' not in cleaned):
                potential_ingredients.append(cleaned)
        
        # Remove duplicates and sort
        unique_ingredients = list(set(potential_ingredients))
        
        logger.info(f"Parsed {len(unique_ingredients)} potential ingredients from text")
        return sorted(unique_ingredients)
    
    def detect_expiry_date(self, text: str) -> Optional[str]:
        """
        Attempt to detect expiry date from extracted text.
        Looks for common date patterns.
        
        Args:
            text: Extracted text from OCR
            
        Returns:
            Detected expiry date string or None
        """
        import re
        
        # Common expiry date patterns
        patterns = [
            r'\b(?:exp|expiry|best before|use by)[:\s]*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b',
            r'\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b',  # General date pattern
            r'\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\.?\s+\d{4}\b'
        ]
        
        text_lower = text.lower()
        
        for pattern in patterns:
            matches = re.finditer(pattern, text_lower, re.IGNORECASE)
            for match in matches:
                return match.group(0) if match.lastindex is None else match.group(1)
        
        return None


# Global OCR instance
_ocr_instance = None


def get_ocr_engine(languages: List[str] = None, use_gpu: bool = False) -> OCREngine:
    """Get or create singleton OCR engine."""
    global _ocr_instance
    
    if _ocr_instance is None:
        _ocr_instance = OCREngine(languages=languages, use_gpu=use_gpu)
    
    return _ocr_instance