Spaces:
Sleeping
Sleeping
Commit ·
eda868e
1
Parent(s): e335b6f
Reject non-leaf uploads in predictions
Browse files
api.py
CHANGED
|
@@ -134,8 +134,31 @@ def load_model():
|
|
| 134 |
def preprocess_image(image_bytes: bytes) -> np.ndarray:
|
| 135 |
image = Image.open(BytesIO(image_bytes)).convert("RGB")
|
| 136 |
image = image.resize((224, 224))
|
| 137 |
-
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
|
| 141 |
MODEL = load_model()
|
|
@@ -154,7 +177,7 @@ async def predict(file: UploadFile = File(...)) -> dict:
|
|
| 154 |
|
| 155 |
try:
|
| 156 |
image_bytes = await file.read()
|
| 157 |
-
input_tensor = preprocess_image(image_bytes)
|
| 158 |
preds = MODEL.predict(input_tensor)
|
| 159 |
probs = np.asarray(preds[0], dtype=np.float32)
|
| 160 |
|
|
@@ -176,6 +199,19 @@ async def predict(file: UploadFile = File(...)) -> dict:
|
|
| 176 |
"confidence": float(probs[idx]),
|
| 177 |
})
|
| 178 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
return {
|
| 180 |
"prediction": {
|
| 181 |
"class_index": result_idx,
|
|
@@ -183,6 +219,7 @@ async def predict(file: UploadFile = File(...)) -> dict:
|
|
| 183 |
"confidence": confidence,
|
| 184 |
},
|
| 185 |
"top3": top3,
|
|
|
|
| 186 |
}
|
| 187 |
except HTTPException:
|
| 188 |
raise
|
|
|
|
| 134 |
def preprocess_image(image_bytes: bytes) -> np.ndarray:
|
| 135 |
image = Image.open(BytesIO(image_bytes)).convert("RGB")
|
| 136 |
image = image.resize((224, 224))
|
| 137 |
+
rgb_uint8 = np.array(image, dtype=np.uint8)
|
| 138 |
+
img_array = rgb_uint8.astype(np.float32) / 255.0
|
| 139 |
+
return np.expand_dims(img_array, axis=0), rgb_uint8
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def should_reject_as_no_leaf(rgb_uint8: np.ndarray, probs: np.ndarray) -> tuple[bool, str]:
|
| 143 |
+
confidence = float(np.max(probs))
|
| 144 |
+
sorted_probs = np.sort(probs)
|
| 145 |
+
second_best = float(sorted_probs[-2]) if sorted_probs.size > 1 else 0.0
|
| 146 |
+
margin = confidence - second_best
|
| 147 |
+
|
| 148 |
+
r = rgb_uint8[:, :, 0].astype(np.float32)
|
| 149 |
+
g = rgb_uint8[:, :, 1].astype(np.float32)
|
| 150 |
+
b = rgb_uint8[:, :, 2].astype(np.float32)
|
| 151 |
+
|
| 152 |
+
green_mask = (g > 40) & (g > 1.05 * r) & (g > 1.05 * b)
|
| 153 |
+
green_ratio = float(np.mean(green_mask))
|
| 154 |
+
|
| 155 |
+
if confidence < 0.55:
|
| 156 |
+
return True, "low_confidence"
|
| 157 |
+
if margin < 0.12 and confidence < 0.70:
|
| 158 |
+
return True, "ambiguous_prediction"
|
| 159 |
+
if green_ratio < 0.05 and confidence < 0.80:
|
| 160 |
+
return True, "non_leaf_visual_pattern"
|
| 161 |
+
return False, "accepted"
|
| 162 |
|
| 163 |
|
| 164 |
MODEL = load_model()
|
|
|
|
| 177 |
|
| 178 |
try:
|
| 179 |
image_bytes = await file.read()
|
| 180 |
+
input_tensor, rgb_uint8 = preprocess_image(image_bytes)
|
| 181 |
preds = MODEL.predict(input_tensor)
|
| 182 |
probs = np.asarray(preds[0], dtype=np.float32)
|
| 183 |
|
|
|
|
| 199 |
"confidence": float(probs[idx]),
|
| 200 |
})
|
| 201 |
|
| 202 |
+
reject, reject_reason = should_reject_as_no_leaf(rgb_uint8, probs)
|
| 203 |
+
if reject:
|
| 204 |
+
return {
|
| 205 |
+
"prediction": {
|
| 206 |
+
"class_index": -1,
|
| 207 |
+
"class_name": "No leaf detected",
|
| 208 |
+
"confidence": float(1.0 - confidence),
|
| 209 |
+
},
|
| 210 |
+
"top3": top3,
|
| 211 |
+
"rejected": True,
|
| 212 |
+
"reject_reason": reject_reason,
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
return {
|
| 216 |
"prediction": {
|
| 217 |
"class_index": result_idx,
|
|
|
|
| 219 |
"confidence": confidence,
|
| 220 |
},
|
| 221 |
"top3": top3,
|
| 222 |
+
"rejected": False,
|
| 223 |
}
|
| 224 |
except HTTPException:
|
| 225 |
raise
|