Spaces:
Sleeping
Sleeping
Commit ·
f29f921
1
Parent(s): 216e4eb
Save full-res before image so the before/after slider works from history View
Browse files- app/main.py +11 -2
- app/models.py +1 -0
- static/js/app.js +9 -27
- templates/index.html +1 -1
app/main.py
CHANGED
|
@@ -35,6 +35,7 @@ try:
|
|
| 35 |
for col, col_type in [
|
| 36 |
("zone", "VARCHAR(128) DEFAULT ''"),
|
| 37 |
("village", "VARCHAR(128) DEFAULT ''"),
|
|
|
|
| 38 |
("before_thumb_path", "VARCHAR(512) DEFAULT ''"),
|
| 39 |
("after_thumb_path", "VARCHAR(512) DEFAULT ''"),
|
| 40 |
]:
|
|
@@ -236,7 +237,12 @@ async def detect(
|
|
| 236 |
Image.fromarray(result_image).save(overlay_path)
|
| 237 |
relative_overlay = f"overlays/{overlay_filename}"
|
| 238 |
|
| 239 |
-
# Save before
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
before_thumb_file = OVERLAYS_DIR / f"{base_name}_before_thumb.png"
|
| 241 |
after_thumb_file = OVERLAYS_DIR / f"{base_name}_after_thumb.png"
|
| 242 |
before_thumb_pil = before_pil.copy()
|
|
@@ -280,6 +286,7 @@ async def detect(
|
|
| 280 |
change_percentage=change_pct,
|
| 281 |
regions_count=len(change_regions),
|
| 282 |
overlay_path=relative_overlay,
|
|
|
|
| 283 |
before_thumb_path=relative_before_thumb,
|
| 284 |
after_thumb_path=relative_after_thumb,
|
| 285 |
regions_json=json.dumps(regions_serializable),
|
|
@@ -323,6 +330,7 @@ async def detect(
|
|
| 323 |
"regions": regions_serializable,
|
| 324 |
"overlayBase64Png": overlay_b64,
|
| 325 |
"overlayUrl": f"/api/overlay/{relative_overlay}",
|
|
|
|
| 326 |
"beforeThumbUrl": f"/api/overlay/{relative_before_thumb}",
|
| 327 |
"afterThumbUrl": f"/api/overlay/{relative_after_thumb}",
|
| 328 |
"notificationSent": notification_sent,
|
|
@@ -400,6 +408,7 @@ def get_run(
|
|
| 400 |
},
|
| 401 |
"regions": regions,
|
| 402 |
"overlayUrl": f"/api/overlay/{run.overlay_path}" if run.overlay_path else None,
|
|
|
|
| 403 |
"beforeThumbUrl": f"/api/overlay/{run.before_thumb_path}" if (getattr(run, "before_thumb_path", None) or "").strip() else None,
|
| 404 |
"afterThumbUrl": f"/api/overlay/{run.after_thumb_path}" if (getattr(run, "after_thumb_path", None) or "").strip() else None,
|
| 405 |
"createdAt": run.created_at.isoformat(),
|
|
@@ -419,7 +428,7 @@ def delete_run(
|
|
| 419 |
if not run:
|
| 420 |
raise HTTPException(status_code=404, detail="Run not found")
|
| 421 |
# Delete overlay and thumbnail files if they exist
|
| 422 |
-
for path_attr in ("overlay_path", "before_thumb_path", "after_thumb_path"):
|
| 423 |
path_val = getattr(run, path_attr, None)
|
| 424 |
if path_val:
|
| 425 |
f = OVERLAYS_DIR.parent / path_val
|
|
|
|
| 35 |
for col, col_type in [
|
| 36 |
("zone", "VARCHAR(128) DEFAULT ''"),
|
| 37 |
("village", "VARCHAR(128) DEFAULT ''"),
|
| 38 |
+
("before_full_path", "VARCHAR(512) DEFAULT ''"),
|
| 39 |
("before_thumb_path", "VARCHAR(512) DEFAULT ''"),
|
| 40 |
("after_thumb_path", "VARCHAR(512) DEFAULT ''"),
|
| 41 |
]:
|
|
|
|
| 237 |
Image.fromarray(result_image).save(overlay_path)
|
| 238 |
relative_overlay = f"overlays/{overlay_filename}"
|
| 239 |
|
| 240 |
+
# Save full-resolution before image (used by the before/after slider from history)
|
| 241 |
+
before_full_file = OVERLAYS_DIR / f"{base_name}_before.png"
|
| 242 |
+
before_pil.save(before_full_file)
|
| 243 |
+
relative_before_full = f"overlays/{base_name}_before.png"
|
| 244 |
+
|
| 245 |
+
# Save small thumbnails for the history table rows
|
| 246 |
before_thumb_file = OVERLAYS_DIR / f"{base_name}_before_thumb.png"
|
| 247 |
after_thumb_file = OVERLAYS_DIR / f"{base_name}_after_thumb.png"
|
| 248 |
before_thumb_pil = before_pil.copy()
|
|
|
|
| 286 |
change_percentage=change_pct,
|
| 287 |
regions_count=len(change_regions),
|
| 288 |
overlay_path=relative_overlay,
|
| 289 |
+
before_full_path=relative_before_full,
|
| 290 |
before_thumb_path=relative_before_thumb,
|
| 291 |
after_thumb_path=relative_after_thumb,
|
| 292 |
regions_json=json.dumps(regions_serializable),
|
|
|
|
| 330 |
"regions": regions_serializable,
|
| 331 |
"overlayBase64Png": overlay_b64,
|
| 332 |
"overlayUrl": f"/api/overlay/{relative_overlay}",
|
| 333 |
+
"beforeFullUrl": f"/api/overlay/{relative_before_full}",
|
| 334 |
"beforeThumbUrl": f"/api/overlay/{relative_before_thumb}",
|
| 335 |
"afterThumbUrl": f"/api/overlay/{relative_after_thumb}",
|
| 336 |
"notificationSent": notification_sent,
|
|
|
|
| 408 |
},
|
| 409 |
"regions": regions,
|
| 410 |
"overlayUrl": f"/api/overlay/{run.overlay_path}" if run.overlay_path else None,
|
| 411 |
+
"beforeFullUrl": f"/api/overlay/{run.before_full_path}" if (getattr(run, "before_full_path", None) or "").strip() else None,
|
| 412 |
"beforeThumbUrl": f"/api/overlay/{run.before_thumb_path}" if (getattr(run, "before_thumb_path", None) or "").strip() else None,
|
| 413 |
"afterThumbUrl": f"/api/overlay/{run.after_thumb_path}" if (getattr(run, "after_thumb_path", None) or "").strip() else None,
|
| 414 |
"createdAt": run.created_at.isoformat(),
|
|
|
|
| 428 |
if not run:
|
| 429 |
raise HTTPException(status_code=404, detail="Run not found")
|
| 430 |
# Delete overlay and thumbnail files if they exist
|
| 431 |
+
for path_attr in ("overlay_path", "before_full_path", "before_thumb_path", "after_thumb_path"):
|
| 432 |
path_val = getattr(run, path_attr, None)
|
| 433 |
if path_val:
|
| 434 |
f = OVERLAYS_DIR.parent / path_val
|
app/models.py
CHANGED
|
@@ -29,6 +29,7 @@ class DetectionRun(Base):
|
|
| 29 |
change_percentage = Column(Float, nullable=False)
|
| 30 |
regions_count = Column(Integer, default=0)
|
| 31 |
overlay_path = Column(String(512), default="")
|
|
|
|
| 32 |
before_thumb_path = Column(String(512), default="")
|
| 33 |
after_thumb_path = Column(String(512), default="")
|
| 34 |
zone = Column(String(128), default="")
|
|
|
|
| 29 |
change_percentage = Column(Float, nullable=False)
|
| 30 |
regions_count = Column(Integer, default=0)
|
| 31 |
overlay_path = Column(String(512), default="")
|
| 32 |
+
before_full_path = Column(String(512), default="")
|
| 33 |
before_thumb_path = Column(String(512), default="")
|
| 34 |
after_thumb_path = Column(String(512), default="")
|
| 35 |
zone = Column(String(128), default="")
|
static/js/app.js
CHANGED
|
@@ -398,48 +398,30 @@ function showResult(data) {
|
|
| 398 |
const beforeImg = document.getElementById('compare-before-img');
|
| 399 |
const afterImg = document.getElementById('compare-after-img');
|
| 400 |
|
| 401 |
-
// "Before" side = original before image, "After" side = result overlay
|
| 402 |
-
const sliderSection = document.querySelector('.zoom-slider-section');
|
| 403 |
-
let hasBefore = false;
|
| 404 |
-
|
| 405 |
if (data.overlayBase64Png) {
|
| 406 |
-
// Fresh detection:
|
| 407 |
afterImg.src = 'data:image/png;base64,' + data.overlayBase64Png;
|
| 408 |
const beforeFile = document.getElementById('file-before').files?.[0];
|
| 409 |
if (beforeFile) {
|
| 410 |
-
hasBefore = true;
|
| 411 |
readFileAsDataURL(beforeFile).then((url) => { beforeImg.src = url; });
|
| 412 |
-
} else
|
| 413 |
-
|
| 414 |
-
beforeImg.src = data.beforeThumbUrl;
|
| 415 |
}
|
| 416 |
} else {
|
| 417 |
-
// Loading from history: use
|
| 418 |
afterImg.src = data.overlayUrl || '';
|
| 419 |
-
|
| 420 |
-
hasBefore = true;
|
| 421 |
-
beforeImg.src = data.beforeThumbUrl;
|
| 422 |
-
}
|
| 423 |
}
|
| 424 |
|
| 425 |
-
//
|
| 426 |
-
if (!hasBefore) {
|
| 427 |
-
beforeImg.src = afterImg.src;
|
| 428 |
-
}
|
| 429 |
-
|
| 430 |
-
// Wait for images to load before positioning the slider, then reset
|
| 431 |
let loaded = 0;
|
| 432 |
const onReady = () => {
|
| 433 |
-
|
| 434 |
-
if (loaded >= 2) {
|
| 435 |
-
resetCompareSlider();
|
| 436 |
-
resetZoom();
|
| 437 |
-
}
|
| 438 |
};
|
| 439 |
afterImg.onload = onReady;
|
| 440 |
beforeImg.onload = onReady;
|
| 441 |
-
|
| 442 |
-
setTimeout(() => { resetCompareSlider(); resetZoom(); }, 400);
|
| 443 |
|
| 444 |
tbody.innerHTML = '';
|
| 445 |
const regions = (data.regions || []).slice(0, 50);
|
|
|
|
| 398 |
const beforeImg = document.getElementById('compare-before-img');
|
| 399 |
const afterImg = document.getElementById('compare-after-img');
|
| 400 |
|
| 401 |
+
// "Before" side = original before image (full-res), "After" side = result overlay
|
|
|
|
|
|
|
|
|
|
| 402 |
if (data.overlayBase64Png) {
|
| 403 |
+
// Fresh detection: overlay is base64, before from file picker or saved full-res
|
| 404 |
afterImg.src = 'data:image/png;base64,' + data.overlayBase64Png;
|
| 405 |
const beforeFile = document.getElementById('file-before').files?.[0];
|
| 406 |
if (beforeFile) {
|
|
|
|
| 407 |
readFileAsDataURL(beforeFile).then((url) => { beforeImg.src = url; });
|
| 408 |
+
} else {
|
| 409 |
+
beforeImg.src = data.beforeFullUrl || data.beforeThumbUrl || '';
|
|
|
|
| 410 |
}
|
| 411 |
} else {
|
| 412 |
+
// Loading from history: use full-res before image for slider, overlay for changes
|
| 413 |
afterImg.src = data.overlayUrl || '';
|
| 414 |
+
beforeImg.src = data.beforeFullUrl || data.beforeThumbUrl || '';
|
|
|
|
|
|
|
|
|
|
| 415 |
}
|
| 416 |
|
| 417 |
+
// Wait for both images to fully load before positioning the slider handle
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
let loaded = 0;
|
| 419 |
const onReady = () => {
|
| 420 |
+
if (++loaded >= 2) { resetCompareSlider(); resetZoom(); }
|
|
|
|
|
|
|
|
|
|
|
|
|
| 421 |
};
|
| 422 |
afterImg.onload = onReady;
|
| 423 |
beforeImg.onload = onReady;
|
| 424 |
+
setTimeout(() => { resetCompareSlider(); resetZoom(); }, 500);
|
|
|
|
| 425 |
|
| 426 |
tbody.innerHTML = '';
|
| 427 |
const regions = (data.regions || []).slice(0, 50);
|
templates/index.html
CHANGED
|
@@ -345,6 +345,6 @@
|
|
| 345 |
</div>
|
| 346 |
</div>
|
| 347 |
|
| 348 |
-
<script src="/static/js/app.js?v=
|
| 349 |
</body>
|
| 350 |
</html>
|
|
|
|
| 345 |
</div>
|
| 346 |
</div>
|
| 347 |
|
| 348 |
+
<script src="/static/js/app.js?v=19"></script>
|
| 349 |
</body>
|
| 350 |
</html>
|