diff --git a/sample_photos/agric-field3.jpeg b/sample_photos/agric-field3.jpeg deleted file mode 100644 index c44d794..0000000 Binary files a/sample_photos/agric-field3.jpeg and /dev/null differ diff --git a/sample_photos/farm-equipment5.jpg b/sample_photos/farm-equipment5.jpg deleted file mode 100644 index 6883fab..0000000 Binary files a/sample_photos/farm-equipment5.jpg and /dev/null differ diff --git a/sample_photos/farmer1.png b/sample_photos/farmer1.png deleted file mode 100644 index d79c60b..0000000 Binary files a/sample_photos/farmer1.png and /dev/null differ diff --git a/sample_photos/farmer10.jpg b/sample_photos/farmer10.jpg deleted file mode 100644 index 047a34e..0000000 Binary files a/sample_photos/farmer10.jpg and /dev/null differ diff --git a/sample_photos/farmer4.jpeg b/sample_photos/farmer4.jpeg deleted file mode 100644 index 5d0bb50..0000000 Binary files a/sample_photos/farmer4.jpeg and /dev/null differ diff --git a/sample_photos/farmer9.png b/sample_photos/farmer9.png deleted file mode 100644 index d29ed7f..0000000 Binary files a/sample_photos/farmer9.png and /dev/null differ diff --git a/sample_photos/harvest2.jpg b/sample_photos/harvest2.jpg deleted file mode 100644 index c3b449d..0000000 Binary files a/sample_photos/harvest2.jpg and /dev/null differ diff --git a/src/api/main.py b/src/api/main.py index e6e0582..c5fbd45 100644 --- a/src/api/main.py +++ b/src/api/main.py @@ -44,7 +44,27 @@ app.add_middleware( ) # Mount static files for serving images -app.mount("/static", StaticFiles(directory="data"), name="static") +app.mount("/static", StaticFiles(directory="../../data"), name="static") + +# Create uploads directory for temporary image storage +uploads_dir = "uploads" +os.makedirs(uploads_dir, exist_ok=True) +app.mount("/uploads", StaticFiles(directory=uploads_dir), name="uploads") + +def cleanup_old_uploads(): + """Clean up uploaded files older than 1 hour""" + try: + import time + current_time = time.time() + for filename in os.listdir(uploads_dir): + file_path = os.path.join(uploads_dir, filename) + if os.path.isfile(file_path): + # Remove files older than 1 hour (3600 seconds) + if current_time - os.path.getctime(file_path) > 3600: + os.remove(file_path) + print(f"Cleaned up old upload: {filename}") + except Exception as e: + print(f"Error during cleanup: {e}") # Global components (initialized on startup) image_processor = None @@ -126,6 +146,7 @@ async def root(): .loading { display: none; text-align: center; margin: 20px 0; } .status { padding: 10px; border-radius: 5px; margin: 10px 0; } .status.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; } + .status.warning { background: #fff3cd; color: #856404; border: 1px solid #ffeaa7; } .status.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .demo-section { margin: 30px 0; padding: 20px; background: #e8f5e8; border-radius: 8px; } .api-docs { margin: 20px 0; } @@ -230,16 +251,34 @@ async def root(): let html = `

📊 Processing Results

-
- ✅ Processed ${data.successful}/${data.total_images} images successfully
- ⏱️ Total time: ${data.total_processing_time.toFixed(1)}s
- 🎯 Average quality: ${data.average_quality.toFixed(1)}/100 -
`; + + if (data.successful === 0 && data.failed > 0) { + html += ` +
+ ❌ Failed to process ${data.failed} image(s)
+ 💡 Tips:
+ • Make sure you're uploading valid image files (JPG, PNG, GIF, etc.)
+ • Try converting your image to JPG format
+ • Check that the file isn't corrupted
+ • Supported formats: JPEG, PNG, GIF, BMP, TIFF +
+ `; + } else { + html += ` +
+ ✅ Processed ${data.successful}/${data.total_images} images successfully
+ ${data.failed > 0 ? `⚠️ ${data.failed} image(s) failed to process
` : ''} + ⏱️ Total time: ${(data.total_processing_time || 0).toFixed(1)}s
+ 🎯 Average quality: ${(data.average_quality || 0).toFixed(1)}/100 +
+ `; + } data.results.forEach((result, index) => { - const qualityClass = result.quality_score >= 70 ? 'quality-high' : - result.quality_score >= 50 ? 'quality-medium' : 'quality-low'; + const qualityScore = result.quality_score || 0; + const qualityClass = qualityScore >= 70 ? 'quality-high' : + qualityScore >= 50 ? 'quality-medium' : 'quality-low'; // Create image URL for sample images or uploaded images const imageUrl = result.image_url || `/static/working_images/${result.filename}`; @@ -248,9 +287,10 @@ async def root():
${result.filename} -
+
@@ -261,9 +301,9 @@ async def root(): ${result.keywords.map(k => `${k}`).join('')}

Quality Score: - ${result.quality_score}/100 + ${qualityScore}/100

-

Processing Time: ${result.processing_time.toFixed(1)}s

+

Processing Time: ${(result.processing_time || 0).toFixed(1)}s

`; @@ -309,23 +349,40 @@ async def analyze_single_image(file: UploadFile = File(...)): try: # Read and validate image contents = await file.read() - image = Image.open(io.BytesIO(contents)) - - # Save temporarily for processing - temp_path = f"temp_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{file.filename}" - image.save(temp_path) - + + # Validate file is an image + if not file.content_type or not file.content_type.startswith('image/'): + raise ValueError(f"File {file.filename} is not a valid image") + + # Create BytesIO object and open image + image_bytes = io.BytesIO(contents) + image = Image.open(image_bytes) + + # Convert to RGB if necessary (handles RGBA, P mode, etc.) + if image.mode not in ('RGB', 'L'): + image = image.convert('RGB') + + # Save temporarily for processing and display + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S_%f') + safe_filename = f"{timestamp}_{file.filename.replace(' ', '_')}" + temp_path = f"temp_{safe_filename}" + upload_path = f"uploads/{safe_filename}" + + # Save both temp file for processing and upload file for display + image.save(temp_path, format='JPEG') + image.save(upload_path, format='JPEG') + start_time = datetime.now() - + # Generate keywords ai_results = keyword_generator.generate_keywords(temp_path) - + # Validate quality quality_result = validator.validate_keywords(ai_results['keywords']) - + processing_time = (datetime.now() - start_time).total_seconds() - - # Clean up + + # Clean up temp file (keep upload file for display) os.remove(temp_path) return KeywordResponse( @@ -335,7 +392,7 @@ async def analyze_single_image(file: UploadFile = File(...)): quality_score=quality_result['score'], processing_time=processing_time, caption=ai_results['caption'], - image_url=None # For uploaded files, we don't serve them back + image_url=f"/uploads/{safe_filename}" ) except Exception as e: @@ -346,7 +403,10 @@ async def analyze_batch_images(files: List[UploadFile] = File(...)): """Analyze multiple agricultural images""" if not keyword_generator: raise HTTPException(status_code=500, detail="AI system not initialized") - + + # Clean up old uploads periodically + cleanup_old_uploads() + results = [] failed = 0 start_time = datetime.now() @@ -355,16 +415,34 @@ async def analyze_batch_images(files: List[UploadFile] = File(...)): try: # Process each file contents = await file.read() - image = Image.open(io.BytesIO(contents)) - - temp_path = f"temp_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{file.filename}" - image.save(temp_path) - + + # Validate file is an image + if not file.content_type or not file.content_type.startswith('image/'): + raise ValueError(f"File {file.filename} is not a valid image") + + # Create BytesIO object and open image + image_bytes = io.BytesIO(contents) + image = Image.open(image_bytes) + + # Convert to RGB if necessary (handles RGBA, P mode, etc.) + if image.mode not in ('RGB', 'L'): + image = image.convert('RGB') + + # Save temporarily for processing and display + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S_%f') + safe_filename = f"{timestamp}_{file.filename.replace(' ', '_')}" + temp_path = f"temp_{safe_filename}" + upload_path = f"uploads/{safe_filename}" + + # Save both temp file for processing and upload file for display + image.save(temp_path, format='JPEG') + image.save(upload_path, format='JPEG') + file_start = datetime.now() ai_results = keyword_generator.generate_keywords(temp_path) quality_result = validator.validate_keywords(ai_results['keywords']) file_time = (datetime.now() - file_start).total_seconds() - + results.append(KeywordResponse( filename=file.filename, keywords=ai_results['keywords'], @@ -372,25 +450,32 @@ async def analyze_batch_images(files: List[UploadFile] = File(...)): quality_score=quality_result['score'], processing_time=file_time, caption=ai_results['caption'], - image_url=None # For uploaded files, we don't serve them back + image_url=f"/uploads/{safe_filename}" )) - + + # Clean up temp file (keep upload file for display) os.remove(temp_path) except Exception as e: failed += 1 - print(f"Error processing {file.filename}: {e}") + error_msg = f"Error processing {file.filename}: {str(e)}" + print(error_msg) + # Add error details to help debugging + if "cannot identify image file" in str(e): + print(f" - File type: {file.content_type}") + print(f" - File size: {len(contents) if 'contents' in locals() else 'unknown'} bytes") + # You could also add failed files to results with error info if needed total_time = (datetime.now() - start_time).total_seconds() - avg_quality = sum(r.quality_score for r in results) / len(results) if results else 0 - + avg_quality = sum(r.quality_score for r in results) / len(results) if results else 0.0 + return BatchResponse( total_images=len(files), successful=len(results), failed=failed, results=results, - average_quality=avg_quality, - total_processing_time=total_time + average_quality=float(avg_quality), + total_processing_time=float(total_time) ) @app.get("/demo", response_model=BatchResponse) @@ -400,7 +485,7 @@ async def run_demo(): raise HTTPException(status_code=500, detail="AI system not initialized") # Use existing sample images - sample_dir = "data/working_images" + sample_dir = "../../data/working_images" if not os.path.exists(sample_dir): raise HTTPException(status_code=404, detail="Sample images not found") @@ -419,7 +504,7 @@ async def run_demo(): file_time = (datetime.now() - file_start).total_seconds() # Create image URL for serving - relative_path = os.path.relpath(img_path, "data") + relative_path = os.path.relpath(img_path, "../../data") image_url = f"/static/{relative_path}" results.append(KeywordResponse( @@ -436,15 +521,15 @@ async def run_demo(): print(f"Error processing {img_path}: {e}") total_time = (datetime.now() - start_time).total_seconds() - avg_quality = sum(r.quality_score for r in results) / len(results) if results else 0 - + avg_quality = sum(r.quality_score for r in results) / len(results) if results else 0.0 + return BatchResponse( total_images=len(image_files), successful=len(results), failed=len(image_files) - len(results), results=results, - average_quality=avg_quality, - total_processing_time=total_time + average_quality=float(avg_quality), + total_processing_time=float(total_time) ) if __name__ == "__main__": diff --git a/src/api/uploads/20250716_221242_945308_farmer-chicken.jpeg b/src/api/uploads/20250716_221242_945308_farmer-chicken.jpeg new file mode 100644 index 0000000..266ee4d Binary files /dev/null and b/src/api/uploads/20250716_221242_945308_farmer-chicken.jpeg differ diff --git a/src/api/uploads/20250716_221301_815457_farmer-cow.jpeg b/src/api/uploads/20250716_221301_815457_farmer-cow.jpeg new file mode 100644 index 0000000..69cff7b Binary files /dev/null and b/src/api/uploads/20250716_221301_815457_farmer-cow.jpeg differ diff --git a/src/api/uploads/20250716_221536_699393_farmer-cow.jpeg b/src/api/uploads/20250716_221536_699393_farmer-cow.jpeg new file mode 100644 index 0000000..69cff7b Binary files /dev/null and b/src/api/uploads/20250716_221536_699393_farmer-cow.jpeg differ diff --git a/src/api/uploads/20250716_221615_954924_farmer10.jpg b/src/api/uploads/20250716_221615_954924_farmer10.jpg new file mode 100644 index 0000000..9515689 Binary files /dev/null and b/src/api/uploads/20250716_221615_954924_farmer10.jpg differ diff --git a/src/api/uploads/20250716_221832_426206_farmer10.jpg b/src/api/uploads/20250716_221832_426206_farmer10.jpg new file mode 100644 index 0000000..9515689 Binary files /dev/null and b/src/api/uploads/20250716_221832_426206_farmer10.jpg differ diff --git a/src/api/uploads/20250716_221955_087223_child-cow.webp b/src/api/uploads/20250716_221955_087223_child-cow.webp new file mode 100644 index 0000000..6b0c8f6 Binary files /dev/null and b/src/api/uploads/20250716_221955_087223_child-cow.webp differ diff --git a/src/api/uploads/20250716_222021_923243_child-cow.webp b/src/api/uploads/20250716_222021_923243_child-cow.webp new file mode 100644 index 0000000..6b0c8f6 Binary files /dev/null and b/src/api/uploads/20250716_222021_923243_child-cow.webp differ diff --git a/src/api/uploads/20250716_222025_937828_farmer-chicken.jpeg b/src/api/uploads/20250716_222025_937828_farmer-chicken.jpeg new file mode 100644 index 0000000..f23b521 Binary files /dev/null and b/src/api/uploads/20250716_222025_937828_farmer-chicken.jpeg differ diff --git a/src/api/uploads/20250716_222029_080442_farmer-cow.jpeg b/src/api/uploads/20250716_222029_080442_farmer-cow.jpeg new file mode 100644 index 0000000..69cff7b Binary files /dev/null and b/src/api/uploads/20250716_222029_080442_farmer-cow.jpeg differ diff --git a/src/api/uploads/20250716_222109_329568_tool.jpg b/src/api/uploads/20250716_222109_329568_tool.jpg new file mode 100644 index 0000000..e3ea786 Binary files /dev/null and b/src/api/uploads/20250716_222109_329568_tool.jpg differ diff --git a/src/api/uploads/20250716_222221_057888_farmer.jpg b/src/api/uploads/20250716_222221_057888_farmer.jpg new file mode 100644 index 0000000..94766da Binary files /dev/null and b/src/api/uploads/20250716_222221_057888_farmer.jpg differ diff --git a/src/api/uploads/20250716_222700_994055_farmers-cows.jpg b/src/api/uploads/20250716_222700_994055_farmers-cows.jpg new file mode 100644 index 0000000..1c52396 Binary files /dev/null and b/src/api/uploads/20250716_222700_994055_farmers-cows.jpg differ diff --git a/src/api/uploads/20250716_222739_016220_child-cow.webp b/src/api/uploads/20250716_222739_016220_child-cow.webp new file mode 100644 index 0000000..6b0c8f6 Binary files /dev/null and b/src/api/uploads/20250716_222739_016220_child-cow.webp differ diff --git a/src/api/uploads/20250716_222743_411158_cows.jpg b/src/api/uploads/20250716_222743_411158_cows.jpg new file mode 100644 index 0000000..768f6f0 Binary files /dev/null and b/src/api/uploads/20250716_222743_411158_cows.jpg differ diff --git a/src/api/uploads/20250716_222746_984457_farmer-chicken.jpeg b/src/api/uploads/20250716_222746_984457_farmer-chicken.jpeg new file mode 100644 index 0000000..f23b521 Binary files /dev/null and b/src/api/uploads/20250716_222746_984457_farmer-chicken.jpeg differ diff --git a/src/api/uploads/20250716_222749_581312_farmer-cow.jpeg b/src/api/uploads/20250716_222749_581312_farmer-cow.jpeg new file mode 100644 index 0000000..69cff7b Binary files /dev/null and b/src/api/uploads/20250716_222749_581312_farmer-cow.jpeg differ diff --git a/src/api/uploads/20250716_222751_807142_farmer.jpg b/src/api/uploads/20250716_222751_807142_farmer.jpg new file mode 100644 index 0000000..94766da Binary files /dev/null and b/src/api/uploads/20250716_222751_807142_farmer.jpg differ diff --git a/src/api/uploads/20250716_222754_484189_farmers-cows.jpg b/src/api/uploads/20250716_222754_484189_farmers-cows.jpg new file mode 100644 index 0000000..1c52396 Binary files /dev/null and b/src/api/uploads/20250716_222754_484189_farmers-cows.jpg differ diff --git a/src/api/uploads/20250716_222919_181805_farm-child.jpg b/src/api/uploads/20250716_222919_181805_farm-child.jpg new file mode 100644 index 0000000..1a9dec1 Binary files /dev/null and b/src/api/uploads/20250716_222919_181805_farm-child.jpg differ diff --git a/src/api/uploads/20250716_223050_427310_farm-phone.jpg b/src/api/uploads/20250716_223050_427310_farm-phone.jpg new file mode 100644 index 0000000..38e80ea Binary files /dev/null and b/src/api/uploads/20250716_223050_427310_farm-phone.jpg differ diff --git a/src/api/uploads/20250716_223214_996431_farm-woman.jpg b/src/api/uploads/20250716_223214_996431_farm-woman.jpg new file mode 100644 index 0000000..31fc3b1 Binary files /dev/null and b/src/api/uploads/20250716_223214_996431_farm-woman.jpg differ