Spaces:
Sleeping
Sleeping
Commit ·
2ca56f0
1
Parent(s): 1125b60
Add bbox coordinates and colorbar for geo-aligned overlay
Browse files
app.py
CHANGED
|
@@ -150,6 +150,10 @@ class HeatmapResponse(BaseModel):
|
|
| 150 |
timestamp: str
|
| 151 |
image_date: Optional[str] = None
|
| 152 |
image_size: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
# Patch analysis (for pixel-wise)
|
| 154 |
num_patches: Optional[int] = None
|
| 155 |
health_summary: Optional[dict] = None
|
|
@@ -175,6 +179,37 @@ def get_stress_colormap():
|
|
| 175 |
colors = [(0.2, 0.7, 0.2), (0.8, 0.8, 0.2), (0.9, 0.5, 0.1), (0.8, 0.2, 0.2)]
|
| 176 |
return LinearSegmentedColormap.from_list('stress', colors, N=256)
|
| 177 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
# ============================================================================
|
| 179 |
# EVALSCRIPT
|
| 180 |
# ============================================================================
|
|
@@ -423,6 +458,14 @@ async def generate_heatmap(request: HeatmapRequest):
|
|
| 423 |
request.center_lon + lon_off, request.center_lat + lat_off
|
| 424 |
), crs=CRS.WGS84)
|
| 425 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
size = bbox_to_dimensions(bbox, resolution=10)
|
| 427 |
log_detail("Image Size", f"{size[0]}×{size[1]} pixels")
|
| 428 |
|
|
@@ -476,6 +519,11 @@ async def generate_heatmap(request: HeatmapRequest):
|
|
| 476 |
|
| 477 |
log_section(f"SUCCESS [{req_id}]")
|
| 478 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 479 |
return HeatmapResponse(
|
| 480 |
success=True,
|
| 481 |
metric=request.metric,
|
|
@@ -488,6 +536,8 @@ async def generate_heatmap(request: HeatmapRequest):
|
|
| 488 |
timestamp=datetime.now().isoformat(),
|
| 489 |
image_date=end_date.strftime('%Y-%m-%d'),
|
| 490 |
image_size=f"{size[0]}x{size[1]}",
|
|
|
|
|
|
|
| 491 |
num_patches=len(patches_list),
|
| 492 |
health_summary=health_summary
|
| 493 |
)
|
|
@@ -557,6 +607,11 @@ async def generate_heatmap(request: HeatmapRequest):
|
|
| 557 |
|
| 558 |
log_section(f"SUCCESS [{req_id}]")
|
| 559 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 560 |
return HeatmapResponse(
|
| 561 |
success=True,
|
| 562 |
metric=request.metric,
|
|
@@ -569,6 +624,8 @@ async def generate_heatmap(request: HeatmapRequest):
|
|
| 569 |
timestamp=datetime.now().isoformat(),
|
| 570 |
image_date=end_date.strftime('%Y-%m-%d'),
|
| 571 |
image_size=f"{size[0]}x{size[1]}",
|
|
|
|
|
|
|
| 572 |
level=llm_result.get('level', 'Unknown'),
|
| 573 |
analysis=llm_result.get('analysis', ''),
|
| 574 |
stress_score=float(stress_results['stress_scores'].mean()),
|
|
|
|
| 150 |
timestamp: str
|
| 151 |
image_date: Optional[str] = None
|
| 152 |
image_size: Optional[str] = None
|
| 153 |
+
# Bounding box for geo-alignment [sw_lon, sw_lat, ne_lon, ne_lat]
|
| 154 |
+
bbox: Optional[List[float]] = None
|
| 155 |
+
# Separate colorbar image (horizontal) for UI display
|
| 156 |
+
colorbar_base64: Optional[str] = None
|
| 157 |
# Patch analysis (for pixel-wise)
|
| 158 |
num_patches: Optional[int] = None
|
| 159 |
health_summary: Optional[dict] = None
|
|
|
|
| 179 |
colors = [(0.2, 0.7, 0.2), (0.8, 0.8, 0.2), (0.9, 0.5, 0.1), (0.8, 0.2, 0.2)]
|
| 180 |
return LinearSegmentedColormap.from_list('stress', colors, N=256)
|
| 181 |
|
| 182 |
+
def generate_colorbar_image(min_val: float, max_val: float, index_type: str, is_stress: bool = False) -> str:
|
| 183 |
+
"""Generate a separate horizontal colorbar image for UI display."""
|
| 184 |
+
if is_stress:
|
| 185 |
+
cmap = get_stress_colormap()
|
| 186 |
+
label = 'Stress Level'
|
| 187 |
+
elif index_type in ['NDWI', 'SMI']:
|
| 188 |
+
cmap = get_water_colormap()
|
| 189 |
+
label = index_type
|
| 190 |
+
else:
|
| 191 |
+
cmap = get_vegetation_colormap()
|
| 192 |
+
label = index_type
|
| 193 |
+
|
| 194 |
+
fig, ax = plt.subplots(figsize=(6, 0.5), dpi=100)
|
| 195 |
+
|
| 196 |
+
# Create gradient
|
| 197 |
+
gradient = np.linspace(0, 1, 256).reshape(1, -1)
|
| 198 |
+
ax.imshow(gradient, aspect='auto', cmap=cmap)
|
| 199 |
+
|
| 200 |
+
# Labels
|
| 201 |
+
ax.set_xticks([0, 127, 255])
|
| 202 |
+
ax.set_xticklabels([f'{min_val:.2f}', f'{(min_val+max_val)/2:.2f}', f'{max_val:.2f}'], fontsize=8)
|
| 203 |
+
ax.set_yticks([])
|
| 204 |
+
ax.set_xlabel(label, fontsize=9)
|
| 205 |
+
|
| 206 |
+
buf = io.BytesIO()
|
| 207 |
+
plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0.1, facecolor='white')
|
| 208 |
+
plt.close(fig)
|
| 209 |
+
buf.seek(0)
|
| 210 |
+
|
| 211 |
+
return base64.b64encode(buf.getvalue()).decode('utf-8')
|
| 212 |
+
|
| 213 |
# ============================================================================
|
| 214 |
# EVALSCRIPT
|
| 215 |
# ============================================================================
|
|
|
|
| 458 |
request.center_lon + lon_off, request.center_lat + lat_off
|
| 459 |
), crs=CRS.WGS84)
|
| 460 |
|
| 461 |
+
# Store bbox coordinates for response [sw_lon, sw_lat, ne_lon, ne_lat]
|
| 462 |
+
bbox_coords = [
|
| 463 |
+
request.center_lon - lon_off, # SW lon
|
| 464 |
+
request.center_lat - lat_off, # SW lat
|
| 465 |
+
request.center_lon + lon_off, # NE lon
|
| 466 |
+
request.center_lat + lat_off # NE lat
|
| 467 |
+
]
|
| 468 |
+
|
| 469 |
size = bbox_to_dimensions(bbox, resolution=10)
|
| 470 |
log_detail("Image Size", f"{size[0]}×{size[1]} pixels")
|
| 471 |
|
|
|
|
| 519 |
|
| 520 |
log_section(f"SUCCESS [{req_id}]")
|
| 521 |
|
| 522 |
+
# Generate colorbar if in overlay mode
|
| 523 |
+
colorbar_b64 = None
|
| 524 |
+
if request.overlay_mode:
|
| 525 |
+
colorbar_b64 = generate_colorbar_image(min_v, max_v, index_type, is_stress=False)
|
| 526 |
+
|
| 527 |
return HeatmapResponse(
|
| 528 |
success=True,
|
| 529 |
metric=request.metric,
|
|
|
|
| 536 |
timestamp=datetime.now().isoformat(),
|
| 537 |
image_date=end_date.strftime('%Y-%m-%d'),
|
| 538 |
image_size=f"{size[0]}x{size[1]}",
|
| 539 |
+
bbox=bbox_coords,
|
| 540 |
+
colorbar_base64=colorbar_b64,
|
| 541 |
num_patches=len(patches_list),
|
| 542 |
health_summary=health_summary
|
| 543 |
)
|
|
|
|
| 607 |
|
| 608 |
log_section(f"SUCCESS [{req_id}]")
|
| 609 |
|
| 610 |
+
# Generate colorbar if in overlay mode
|
| 611 |
+
colorbar_b64 = None
|
| 612 |
+
if request.overlay_mode:
|
| 613 |
+
colorbar_b64 = generate_colorbar_image(min_v, max_v, "Stress", is_stress=True)
|
| 614 |
+
|
| 615 |
return HeatmapResponse(
|
| 616 |
success=True,
|
| 617 |
metric=request.metric,
|
|
|
|
| 624 |
timestamp=datetime.now().isoformat(),
|
| 625 |
image_date=end_date.strftime('%Y-%m-%d'),
|
| 626 |
image_size=f"{size[0]}x{size[1]}",
|
| 627 |
+
bbox=bbox_coords,
|
| 628 |
+
colorbar_base64=colorbar_b64,
|
| 629 |
level=llm_result.get('level', 'Unknown'),
|
| 630 |
analysis=llm_result.get('analysis', ''),
|
| 631 |
stress_score=float(stress_results['stress_scores'].mean()),
|