Fix: Complete image upload and display system with error handling
|
Before Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 230 KiB |
@@ -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 = `
|
||||
<h3>📊 Processing Results</h3>
|
||||
<div class="status success">
|
||||
✅ Processed ${data.successful}/${data.total_images} images successfully<br>
|
||||
⏱️ Total time: ${data.total_processing_time.toFixed(1)}s<br>
|
||||
🎯 Average quality: ${data.average_quality.toFixed(1)}/100
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (data.successful === 0 && data.failed > 0) {
|
||||
html += `
|
||||
<div class="status error">
|
||||
❌ Failed to process ${data.failed} image(s)<br>
|
||||
💡 <strong>Tips:</strong><br>
|
||||
• Make sure you're uploading valid image files (JPG, PNG, GIF, etc.)<br>
|
||||
• Try converting your image to JPG format<br>
|
||||
• Check that the file isn't corrupted<br>
|
||||
• Supported formats: JPEG, PNG, GIF, BMP, TIFF
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
html += `
|
||||
<div class="status ${data.failed > 0 ? 'warning' : 'success'}">
|
||||
✅ Processed ${data.successful}/${data.total_images} images successfully<br>
|
||||
${data.failed > 0 ? `⚠️ ${data.failed} image(s) failed to process<br>` : ''}
|
||||
⏱️ Total time: ${(data.total_processing_time || 0).toFixed(1)}s<br>
|
||||
🎯 Average quality: ${(data.average_quality || 0).toFixed(1)}/100
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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():
|
||||
<div class="result-card">
|
||||
<div class="image-preview">
|
||||
<img src="${imageUrl}" alt="${result.filename}"
|
||||
onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
|
||||
<div style="display:none; width:200px; height:150px; background:#f0f0f0;
|
||||
border-radius:8px; display:flex; align-items:center; justify-content:center;
|
||||
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';"
|
||||
onload="this.nextElementSibling.style.display='none';">
|
||||
<div class="image-placeholder" style="display:none; width:200px; height:150px; background:#f0f0f0;
|
||||
border-radius:8px; align-items:center; justify-content:center;
|
||||
color:#666; font-size:14px;">📸 Image not available</div>
|
||||
</div>
|
||||
<div class="result-content">
|
||||
@@ -261,9 +301,9 @@ async def root():
|
||||
${result.keywords.map(k => `<span class="keyword">${k}</span>`).join('')}
|
||||
</div>
|
||||
<p><strong>Quality Score:</strong>
|
||||
<span class="quality-score ${qualityClass}">${result.quality_score}/100</span>
|
||||
<span class="quality-score ${qualityClass}">${qualityScore}/100</span>
|
||||
</p>
|
||||
<p><strong>Processing Time:</strong> ${result.processing_time.toFixed(1)}s</p>
|
||||
<p><strong>Processing Time:</strong> ${(result.processing_time || 0).toFixed(1)}s</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -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__":
|
||||
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 53 KiB |