Aniket2006 commited on
Commit
a404eb8
·
1 Parent(s): cb7fde2

Add Gaussian smoothing and field boundary overlay

Browse files

Features:
- gaussian_sigma: Control smoothing strength (default 1.5, 0 = no smoothing)
- show_field_boundary: Toggle field boundary overlay (default true)
- Light dashed rectangle with corner markers
- Added scipy dependency for gaussian_filter

Files changed (2) hide show
  1. app.py +55 -6
  2. requirements.txt +1 -0
app.py CHANGED
@@ -12,6 +12,7 @@ import base64
12
  import logging
13
  from datetime import datetime, timedelta
14
  from typing import Optional
 
15
 
16
  import numpy as np
17
  import matplotlib
@@ -69,6 +70,8 @@ class HeatmapRequest(BaseModel):
69
  center_lon: float
70
  field_size_hectares: float = 10.0
71
  index_type: str = "NDVI" # Any index from vegetation_indices.py
 
 
72
 
73
 
74
  class HeatmapResponse(BaseModel):
@@ -145,8 +148,13 @@ def parse_timestamp(ts_str: str) -> datetime:
145
  return datetime.fromisoformat(ts_str)
146
 
147
 
148
- def generate_heatmap_image(data: np.ndarray, index_type: str) -> tuple:
149
- """Generate a heatmap image from index data."""
 
 
 
 
 
150
 
151
  # Handle NaN values
152
  valid_mask = ~np.isnan(data)
@@ -161,6 +169,10 @@ def generate_heatmap_image(data: np.ndarray, index_type: str) -> tuple:
161
  data_normalized = np.clip((data - min_val) / (max_val - min_val + 1e-8), 0, 1)
162
  data_normalized = np.nan_to_num(data_normalized, nan=0.5)
163
 
 
 
 
 
164
  # Create figure
165
  fig, ax = plt.subplots(figsize=(8, 8), dpi=100)
166
 
@@ -172,6 +184,34 @@ def generate_heatmap_image(data: np.ndarray, index_type: str) -> tuple:
172
 
173
  im = ax.imshow(data_normalized, cmap=cmap, interpolation='bilinear')
174
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  # Add colorbar
176
  cbar = plt.colorbar(im, ax=ax, shrink=0.8, pad=0.02)
177
  cbar.set_label(f'{index_type} Value', fontsize=10)
@@ -299,8 +339,13 @@ async def generate_heatmap(request: HeatmapRequest):
299
 
300
  logger.info(f"Calculated {request.index_type}: min={np.nanmin(index_data):.4f}, max={np.nanmax(index_data):.4f}")
301
 
302
- # Generate heatmap
303
- img_base64, min_val, max_val, mean_val = generate_heatmap_image(index_data, request.index_type)
 
 
 
 
 
304
 
305
  return HeatmapResponse(
306
  success=True,
@@ -325,7 +370,9 @@ async def generate_heatmap_image_direct(
325
  center_lat: float,
326
  center_lon: float,
327
  field_size_hectares: float = 10.0,
328
- index_type: str = "NDVI"
 
 
329
  ):
330
  """Generate and return heatmap as PNG image directly."""
331
 
@@ -333,7 +380,9 @@ async def generate_heatmap_image_direct(
333
  center_lat=center_lat,
334
  center_lon=center_lon,
335
  field_size_hectares=field_size_hectares,
336
- index_type=index_type
 
 
337
  )
338
 
339
  response = await generate_heatmap(request)
 
12
  import logging
13
  from datetime import datetime, timedelta
14
  from typing import Optional
15
+ from scipy.ndimage import gaussian_filter
16
 
17
  import numpy as np
18
  import matplotlib
 
70
  center_lon: float
71
  field_size_hectares: float = 10.0
72
  index_type: str = "NDVI" # Any index from vegetation_indices.py
73
+ gaussian_sigma: float = 1.5 # Gaussian smoothing strength (0 = no smoothing)
74
+ show_field_boundary: bool = True # Whether to show field boundary
75
 
76
 
77
  class HeatmapResponse(BaseModel):
 
148
  return datetime.fromisoformat(ts_str)
149
 
150
 
151
+ def generate_heatmap_image(
152
+ data: np.ndarray,
153
+ index_type: str,
154
+ gaussian_sigma: float = 1.5,
155
+ show_field_boundary: bool = True
156
+ ) -> tuple:
157
+ """Generate a heatmap image from index data with optional smoothing and field boundary."""
158
 
159
  # Handle NaN values
160
  valid_mask = ~np.isnan(data)
 
169
  data_normalized = np.clip((data - min_val) / (max_val - min_val + 1e-8), 0, 1)
170
  data_normalized = np.nan_to_num(data_normalized, nan=0.5)
171
 
172
+ # Apply Gaussian smoothing if sigma > 0
173
+ if gaussian_sigma > 0:
174
+ data_normalized = gaussian_filter(data_normalized, sigma=gaussian_sigma)
175
+
176
  # Create figure
177
  fig, ax = plt.subplots(figsize=(8, 8), dpi=100)
178
 
 
184
 
185
  im = ax.imshow(data_normalized, cmap=cmap, interpolation='bilinear')
186
 
187
+ # Draw field boundary if enabled
188
+ if show_field_boundary:
189
+ h, w = data_normalized.shape
190
+ # Draw a light rectangular boundary with some padding
191
+ padding = 0.05 # 5% padding from edges
192
+ rect = plt.Rectangle(
193
+ (w * padding, h * padding),
194
+ w * (1 - 2 * padding),
195
+ h * (1 - 2 * padding),
196
+ fill=False,
197
+ edgecolor='white',
198
+ linewidth=2,
199
+ linestyle='--',
200
+ alpha=0.6
201
+ )
202
+ ax.add_patch(rect)
203
+
204
+ # Add corner markers
205
+ corner_size = min(h, w) * 0.05
206
+ corners = [
207
+ (w * padding, h * padding), # Top-left
208
+ (w * (1 - padding), h * padding), # Top-right
209
+ (w * padding, h * (1 - padding)), # Bottom-left
210
+ (w * (1 - padding), h * (1 - padding)) # Bottom-right
211
+ ]
212
+ for cx, cy in corners:
213
+ ax.plot(cx, cy, 'o', color='white', markersize=6, alpha=0.7)
214
+
215
  # Add colorbar
216
  cbar = plt.colorbar(im, ax=ax, shrink=0.8, pad=0.02)
217
  cbar.set_label(f'{index_type} Value', fontsize=10)
 
339
 
340
  logger.info(f"Calculated {request.index_type}: min={np.nanmin(index_data):.4f}, max={np.nanmax(index_data):.4f}")
341
 
342
+ # Generate heatmap with Gaussian smoothing and optional field boundary
343
+ img_base64, min_val, max_val, mean_val = generate_heatmap_image(
344
+ index_data,
345
+ request.index_type,
346
+ gaussian_sigma=request.gaussian_sigma,
347
+ show_field_boundary=request.show_field_boundary
348
+ )
349
 
350
  return HeatmapResponse(
351
  success=True,
 
370
  center_lat: float,
371
  center_lon: float,
372
  field_size_hectares: float = 10.0,
373
+ index_type: str = "NDVI",
374
+ gaussian_sigma: float = 1.5,
375
+ show_field_boundary: bool = True
376
  ):
377
  """Generate and return heatmap as PNG image directly."""
378
 
 
380
  center_lat=center_lat,
381
  center_lon=center_lon,
382
  field_size_hectares=field_size_hectares,
383
+ index_type=index_type,
384
+ gaussian_sigma=gaussian_sigma,
385
+ show_field_boundary=show_field_boundary
386
  )
387
 
388
  response = await generate_heatmap(request)
requirements.txt CHANGED
@@ -2,6 +2,7 @@ fastapi==0.104.1
2
  uvicorn[standard]==0.24.0
3
  pydantic==2.5.2
4
  numpy>=1.24.0
 
5
  matplotlib>=3.7.0
6
  Pillow>=9.0.0
7
  sentinelhub>=3.9.0
 
2
  uvicorn[standard]==0.24.0
3
  pydantic==2.5.2
4
  numpy>=1.24.0
5
+ scipy>=1.10.0
6
  matplotlib>=3.7.0
7
  Pillow>=9.0.0
8
  sentinelhub>=3.9.0