Fix: Complete image upload and display system with error handling

This commit is contained in:
Aherobo Ovie Victor
2025-07-16 22:34:21 +01:00
parent e4de02e70f
commit 8f52fac445
29 changed files with 130 additions and 45 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

+117 -32
View File
@@ -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,11 +349,28 @@ 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()
@@ -325,7 +382,7 @@ async def analyze_single_image(file: UploadFile = File(...)):
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:
@@ -347,6 +404,9 @@ async def analyze_batch_images(files: List[UploadFile] = File(...)):
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,10 +415,28 @@ 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)
@@ -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__":
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB