Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
@@ -107,59 +107,99 @@ class ImageProcessor:
|
|
107 |
"""Handles image preprocessing for better OCR results"""
|
108 |
|
109 |
@staticmethod
|
110 |
-
def
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
115 |
try:
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
# Load image
|
120 |
-
image = Image.open(image_path)
|
121 |
-
|
122 |
-
# Convert to RGB if needed
|
123 |
-
if image.mode != 'RGB':
|
124 |
-
image = image.convert('RGB')
|
125 |
-
|
126 |
-
# Enhance contrast
|
127 |
-
enhancer = ImageEnhance.Contrast(image)
|
128 |
-
image = enhancer.enhance(1.5)
|
129 |
-
|
130 |
-
# Enhance sharpness
|
131 |
-
enhancer = ImageEnhance.Sharpness(image)
|
132 |
-
image = enhancer.enhance(2.0)
|
133 |
-
|
134 |
-
# Convert to grayscale
|
135 |
-
image = image.convert('L')
|
136 |
-
|
137 |
-
# Apply Gaussian blur to reduce noise
|
138 |
-
image = image.filter(ImageFilter.GaussianBlur(radius=0.5))
|
139 |
-
|
140 |
-
# Convert to numpy array for OpenCV processing
|
141 |
-
if 'cv2' in globals() and cv2 is not None:
|
142 |
-
img_array = np.array(image)
|
143 |
-
|
144 |
-
# Apply threshold to get binary image
|
145 |
-
_, binary = cv2.threshold(img_array, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
146 |
-
|
147 |
-
# Morphological operations to clean up the image
|
148 |
-
kernel = np.ones((1,1), np.uint8)
|
149 |
-
binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
|
150 |
-
|
151 |
-
# Convert back to PIL Image
|
152 |
-
image = Image.fromarray(binary)
|
153 |
-
|
154 |
-
# Save processed image
|
155 |
-
processed_path = image_path.replace('.', '_processed.')
|
156 |
-
image.save(processed_path)
|
157 |
-
|
158 |
-
return processed_path, "Enhanced contrast, sharpness, applied thresholding"
|
159 |
-
|
160 |
except Exception as e:
|
161 |
-
print(f"
|
162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
163 |
|
164 |
@staticmethod
|
165 |
def extract_text_regions(image_path):
|
|
|
107 |
"""Handles image preprocessing for better OCR results"""
|
108 |
|
109 |
@staticmethod
|
110 |
+
def process_receipt_image(image_file, phone):
|
111 |
+
"""
|
112 |
+
Complete receipt processing pipeline that handles Gradio file objects
|
113 |
+
Returns: (success, status_message, extracted_data, image_preview)
|
114 |
+
"""
|
115 |
+
try:
|
116 |
+
if not phone:
|
117 |
+
return False, "β Please sign in first", {}, None
|
118 |
+
|
119 |
+
if not image_file:
|
120 |
+
return False, "β No image uploaded", {}, None
|
121 |
+
|
122 |
+
# Debug input type
|
123 |
+
print(f"\nπ Input type: {type(image_file)}")
|
124 |
+
|
125 |
+
# Handle different input types
|
126 |
+
if isinstance(image_file, str):
|
127 |
+
# Case 1: Direct file path (local testing)
|
128 |
+
image_path = image_file
|
129 |
+
elif hasattr(image_file, 'name'):
|
130 |
+
# Case 2: Gradio NamedString object (Hugging Face Spaces)
|
131 |
+
image_path = image_file.name
|
132 |
+
else:
|
133 |
+
return False, "β Unsupported file input type", {}, None
|
134 |
+
|
135 |
+
# Create receipts directory if needed
|
136 |
+
os.makedirs(RECEIPTS_DIR, exist_ok=True)
|
137 |
+
|
138 |
+
# Generate unique filename
|
139 |
+
timestamp = int(time.time())
|
140 |
+
filename = f"receipt_{phone}_{timestamp}{os.path.splitext(image_path)[1]}"
|
141 |
+
save_path = os.path.join(RECEIPTS_DIR, filename)
|
142 |
+
|
143 |
+
# Copy the uploaded file (works for both Gradio and direct paths)
|
144 |
+
with open(image_path, 'rb') as src, open(save_path, 'wb') as dst:
|
145 |
+
dst.write(src.read())
|
146 |
+
|
147 |
+
print(f"π Saved receipt to: {save_path}")
|
148 |
+
|
149 |
+
# Preprocess image
|
150 |
+
processed_path, preprocessing_info = ImageProcessor.preprocess_receipt_image(save_path)
|
151 |
+
print(f"πΌοΈ Preprocessing: {preprocessing_info}")
|
152 |
+
|
153 |
+
# Extract text using OCR
|
154 |
+
raw_text, confidence, extracted_data = ocr_service.extract_text_from_receipt(processed_path)
|
155 |
+
print(f"π OCR Confidence: {confidence:.1%}")
|
156 |
+
|
157 |
+
# Auto-categorize
|
158 |
+
if extracted_data.get('merchant'):
|
159 |
+
suggested_category = db.auto_categorize_receipt(
|
160 |
+
phone,
|
161 |
+
extracted_data['merchant'],
|
162 |
+
extracted_data.get('total_amount', 0)
|
163 |
+
)
|
164 |
+
extracted_data['suggested_category'] = suggested_category
|
165 |
+
print(f"π·οΈ Suggested category: {suggested_category}")
|
166 |
+
|
167 |
+
# Prepare receipt data for database
|
168 |
+
receipt_data = {
|
169 |
+
'image_path': save_path,
|
170 |
+
'processed_image_path': processed_path,
|
171 |
+
'merchant': extracted_data.get('merchant', ''),
|
172 |
+
'amount': extracted_data.get('total_amount', 0.0),
|
173 |
+
'date': extracted_data.get('date', ''),
|
174 |
+
'category': extracted_data.get('suggested_category', 'Miscellaneous'),
|
175 |
+
'confidence': confidence,
|
176 |
+
'raw_text': raw_text,
|
177 |
+
'extracted_data': extracted_data,
|
178 |
+
'is_validated': False
|
179 |
+
}
|
180 |
+
|
181 |
+
# Save to database
|
182 |
+
receipt_id = db.save_receipt(phone, receipt_data)
|
183 |
+
extracted_data['receipt_id'] = receipt_id
|
184 |
+
print(f"πΎ Saved to DB with ID: {receipt_id}")
|
185 |
+
|
186 |
+
# Create image preview
|
187 |
try:
|
188 |
+
image_preview = Image.open(save_path)
|
189 |
+
image_preview.thumbnail((400, 600)) # Resize for display
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
190 |
except Exception as e:
|
191 |
+
print(f"β οΈ Preview generation failed: {e}")
|
192 |
+
image_preview = None
|
193 |
+
|
194 |
+
status_msg = f"β
Receipt processed! Confidence: {confidence:.1%}"
|
195 |
+
if confidence < 0.7:
|
196 |
+
status_msg += " β οΈ Low confidence - please verify"
|
197 |
+
|
198 |
+
return True, status_msg, extracted_data, image_preview
|
199 |
+
|
200 |
+
except Exception as e:
|
201 |
+
print(f"β Processing error: {traceback.format_exc()}")
|
202 |
+
return False, f"β Processing failed: {str(e)}", {}, None
|
203 |
|
204 |
@staticmethod
|
205 |
def extract_text_regions(image_path):
|