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():

-
@@ -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