|
@@ -60,12 +60,12 @@ async def set_confidence(threshold: float = Body(..., embed=True)):
|
|
|
else:
|
|
else:
|
|
|
return {"status": "error", "message": "Threshold must be between 0.0 and 1.0"}
|
|
return {"status": "error", "message": "Threshold must be between 0.0 and 1.0"}
|
|
|
|
|
|
|
|
-@app.post("/detect")
|
|
|
|
|
-async def detect_ripeness(file: UploadFile = File(...)):
|
|
|
|
|
- """Simple YOLO detection only. No archival or vectorization."""
|
|
|
|
|
|
|
+
|
|
|
|
|
+@app.post("/analyze")
|
|
|
|
|
+async def analyze_only(file: UploadFile = File(...)):
|
|
|
|
|
+ """Local YOLO detection only. Guaranteed to work without Billing."""
|
|
|
image_bytes = await file.read()
|
|
image_bytes = await file.read()
|
|
|
img = Image.open(io.BytesIO(image_bytes))
|
|
img = Image.open(io.BytesIO(image_bytes))
|
|
|
-
|
|
|
|
|
results = yolo_model(img, conf=current_conf)
|
|
results = yolo_model(img, conf=current_conf)
|
|
|
|
|
|
|
|
detections = []
|
|
detections = []
|
|
@@ -78,56 +78,46 @@ async def detect_ripeness(file: UploadFile = File(...)):
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
- "status": "success",
|
|
|
|
|
|
|
+ "status": "success",
|
|
|
"current_threshold": current_conf,
|
|
"current_threshold": current_conf,
|
|
|
- "data": detections
|
|
|
|
|
|
|
+ "detections": detections
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-@app.post("/analyze")
|
|
|
|
|
-async def analyze_ripeness(file: UploadFile = File(...)):
|
|
|
|
|
- """Full analysis: Detection + Vertex Vectorization + MongoDB Archival."""
|
|
|
|
|
- # 1. Save file temporarily for YOLO and Vertex
|
|
|
|
|
|
|
+@app.post("/vectorize_and_store")
|
|
|
|
|
+async def vectorize_and_store(file: UploadFile = File(...), detection_data: str = Form(...)):
|
|
|
|
|
+ """Cloud-dependent. Requires active billing."""
|
|
|
|
|
+ import json
|
|
|
|
|
+ try:
|
|
|
|
|
+ primary_detection = json.loads(detection_data)
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ return {"status": "error", "message": "Invalid detection_data format"}
|
|
|
|
|
+
|
|
|
unique_id = uuid.uuid4().hex
|
|
unique_id = uuid.uuid4().hex
|
|
|
- temp_path = f"temp_{unique_id}_{file.filename}"
|
|
|
|
|
|
|
+ temp_path = f"temp_vec_{unique_id}_{file.filename}"
|
|
|
|
|
+
|
|
|
|
|
+ # Reset file pointer since it might have been read (though here it's a new request)
|
|
|
|
|
+ # Actually, in a new request, we read it for the first time.
|
|
|
with open(temp_path, "wb") as buffer:
|
|
with open(temp_path, "wb") as buffer:
|
|
|
shutil.copyfileobj(file.file, buffer)
|
|
shutil.copyfileobj(file.file, buffer)
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
|
- # 2. Run YOLO detection
|
|
|
|
|
- img = Image.open(temp_path)
|
|
|
|
|
- results = yolo_model(img, conf=current_conf)
|
|
|
|
|
-
|
|
|
|
|
- detections = []
|
|
|
|
|
- for r in results:
|
|
|
|
|
- for box in r.boxes:
|
|
|
|
|
- detections.append({
|
|
|
|
|
- "class": yolo_model.names[int(box.cls)],
|
|
|
|
|
- "confidence": round(float(box.conf), 2),
|
|
|
|
|
- "box": box.xyxy.tolist()[0]
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- # 3. If detections found, analyze the first one (primary) for deeper insights
|
|
|
|
|
- if detections:
|
|
|
|
|
- primary_detection = detections[0]
|
|
|
|
|
- record_id = analyze_use_case.execute(temp_path, primary_detection)
|
|
|
|
|
-
|
|
|
|
|
- return {
|
|
|
|
|
- "status": "success",
|
|
|
|
|
- "record_id": record_id,
|
|
|
|
|
- "detections": detections,
|
|
|
|
|
- "message": "FFB analyzed, vectorized, and archived successfully"
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return {"status": "no_detection", "message": "No palm oil FFB detected"}
|
|
|
|
|
-
|
|
|
|
|
|
|
+ record_id = analyze_use_case.execute(temp_path, primary_detection)
|
|
|
|
|
+ return {
|
|
|
|
|
+ "status": "success",
|
|
|
|
|
+ "record_id": record_id,
|
|
|
|
|
+ "message": "FFB vectorized and archived successfully"
|
|
|
|
|
+ }
|
|
|
|
|
+ except RuntimeError as e:
|
|
|
|
|
+ return {"status": "error", "message": str(e)}
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ return {"status": "error", "message": f"An unexpected error occurred: {str(e)}"}
|
|
|
finally:
|
|
finally:
|
|
|
- # Clean up temp file
|
|
|
|
|
if os.path.exists(temp_path):
|
|
if os.path.exists(temp_path):
|
|
|
os.remove(temp_path)
|
|
os.remove(temp_path)
|
|
|
|
|
|
|
|
-@app.post("/analyze_batch")
|
|
|
|
|
-async def analyze_batch(files: List[UploadFile] = File(...)):
|
|
|
|
|
- """Handles multiple images: Detect -> Vectorize -> Store."""
|
|
|
|
|
|
|
+@app.post("/process_batch")
|
|
|
|
|
+async def process_batch(files: List[UploadFile] = File(...)):
|
|
|
|
|
+ """Handles multiple images: Detect -> Vectorize -> Store. Graceful handling of cloud errors."""
|
|
|
batch_results = []
|
|
batch_results = []
|
|
|
temp_files = []
|
|
temp_files = []
|
|
|
|
|
|
|
@@ -135,9 +125,9 @@ async def analyze_batch(files: List[UploadFile] = File(...)):
|
|
|
for file in files:
|
|
for file in files:
|
|
|
# 1. Save Temp
|
|
# 1. Save Temp
|
|
|
unique_id = uuid.uuid4().hex
|
|
unique_id = uuid.uuid4().hex
|
|
|
- path = f"temp_{unique_id}_{file.filename}"
|
|
|
|
|
- with open(path, "wb") as f:
|
|
|
|
|
- shutil.copyfileobj(file.file, f)
|
|
|
|
|
|
|
+ path = f"temp_batch_{unique_id}_{file.filename}"
|
|
|
|
|
+ with open(path, "wb") as f_out:
|
|
|
|
|
+ shutil.copyfileobj(file.file, f_out)
|
|
|
temp_files.append(path)
|
|
temp_files.append(path)
|
|
|
|
|
|
|
|
# 2. YOLO Detect
|
|
# 2. YOLO Detect
|
|
@@ -156,14 +146,27 @@ async def analyze_batch(files: List[UploadFile] = File(...)):
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- # 4. Process Batch Use Case
|
|
|
|
|
- record_ids = analyze_batch_use_case.execute(batch_results)
|
|
|
|
|
- return {
|
|
|
|
|
- "status": "success",
|
|
|
|
|
- "processed_count": len(record_ids),
|
|
|
|
|
- "record_ids": record_ids,
|
|
|
|
|
- "message": f"Successfully processed {len(record_ids)} bunches"
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if not batch_results:
|
|
|
|
|
+ return {"status": "no_detection", "message": "No bunches detected in batch"}
|
|
|
|
|
+
|
|
|
|
|
+ # 4. Process Batch Use Case with error handling for cloud services
|
|
|
|
|
+ try:
|
|
|
|
|
+ record_ids = analyze_batch_use_case.execute(batch_results)
|
|
|
|
|
+ return {
|
|
|
|
|
+ "status": "success",
|
|
|
|
|
+ "processed_count": len(record_ids),
|
|
|
|
|
+ "record_ids": record_ids,
|
|
|
|
|
+ "message": f"Successfully processed {len(record_ids)} bunches"
|
|
|
|
|
+ }
|
|
|
|
|
+ except RuntimeError as e:
|
|
|
|
|
+ return {
|
|
|
|
|
+ "status": "partial_success",
|
|
|
|
|
+ "message": f"Detections completed, but cloud archival failed: {str(e)}",
|
|
|
|
|
+ "detections_count": len(batch_results)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ return {"status": "error", "message": f"Batch processing failed: {str(e)}"}
|
|
|
|
|
|
|
|
finally:
|
|
finally:
|
|
|
# 5. Clean up all temp files
|
|
# 5. Clean up all temp files
|
|
@@ -179,19 +182,22 @@ async def search_hybrid(
|
|
|
"""Hybrid Search: Supports Visual Similarity and Natural Language Search."""
|
|
"""Hybrid Search: Supports Visual Similarity and Natural Language Search."""
|
|
|
temp_path = None
|
|
temp_path = None
|
|
|
try:
|
|
try:
|
|
|
- if file:
|
|
|
|
|
- unique_id = uuid.uuid4().hex
|
|
|
|
|
- temp_path = f"temp_search_{unique_id}_{file.filename}"
|
|
|
|
|
- with open(temp_path, "wb") as buffer:
|
|
|
|
|
- shutil.copyfileobj(file.file, buffer)
|
|
|
|
|
-
|
|
|
|
|
- results = search_use_case.execute(image_path=temp_path, limit=limit)
|
|
|
|
|
- elif text_query:
|
|
|
|
|
- results = search_use_case.execute(text_query=text_query, limit=limit)
|
|
|
|
|
- else:
|
|
|
|
|
- return {"status": "error", "message": "No search input provided"}
|
|
|
|
|
-
|
|
|
|
|
- return {"status": "success", "results": results}
|
|
|
|
|
|
|
+ try:
|
|
|
|
|
+ if file:
|
|
|
|
|
+ unique_id = uuid.uuid4().hex
|
|
|
|
|
+ temp_path = f"temp_search_{unique_id}_{file.filename}"
|
|
|
|
|
+ with open(temp_path, "wb") as buffer:
|
|
|
|
|
+ shutil.copyfileobj(file.file, buffer)
|
|
|
|
|
+
|
|
|
|
|
+ results = search_use_case.execute(image_path=temp_path, limit=limit)
|
|
|
|
|
+ elif text_query:
|
|
|
|
|
+ results = search_use_case.execute(text_query=text_query, limit=limit)
|
|
|
|
|
+ else:
|
|
|
|
|
+ return {"status": "error", "message": "No search input provided"}
|
|
|
|
|
+
|
|
|
|
|
+ return {"status": "success", "results": results}
|
|
|
|
|
+ except RuntimeError as e:
|
|
|
|
|
+ return {"status": "error", "message": f"Search unavailable: {str(e)}"}
|
|
|
|
|
|
|
|
finally:
|
|
finally:
|
|
|
if temp_path and os.path.exists(temp_path):
|
|
if temp_path and os.path.exists(temp_path):
|