Spaces:
Sleeping
Sleeping
Muhammad Ahad Hassan Khan commited on
Commit ·
336fa4a
1
Parent(s): 11b39b6
ndvi_pred only
Browse files- app.py +41 -0
- dockerfile +25 -0
- ndvi_predictor.py +76 -0
- requirements.txt +9 -0
app.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
+
from fastapi import FastAPI, File, UploadFile
|
| 3 |
+
from fastapi.responses import StreamingResponse, JSONResponse
|
| 4 |
+
from ndvi_predictor import load_model, normalize_rgb, predict_ndvi, create_visualization
|
| 5 |
+
from PIL import Image
|
| 6 |
+
import numpy as np
|
| 7 |
+
import io
|
| 8 |
+
import os
|
| 9 |
+
|
| 10 |
+
app = FastAPI()
|
| 11 |
+
model = load_model("ndvi_model.keras")
|
| 12 |
+
|
| 13 |
+
@app.post("/predict/")
|
| 14 |
+
async def predict_ndvi_api(file: UploadFile = File(...)):
|
| 15 |
+
img = Image.open(await file.read()).convert("RGB")
|
| 16 |
+
rgb_np = np.array(img)
|
| 17 |
+
rgb_norm = normalize_rgb(rgb_np)
|
| 18 |
+
ndvi = predict_ndvi(model, rgb_norm)
|
| 19 |
+
|
| 20 |
+
# Generate visual output
|
| 21 |
+
vis_image = create_visualization(rgb_np, ndvi)
|
| 22 |
+
vis_filename = "ndvi_visualization.png"
|
| 23 |
+
|
| 24 |
+
# Generate GeoTIFF in memory
|
| 25 |
+
geotiff_buffer = io.BytesIO()
|
| 26 |
+
import rasterio
|
| 27 |
+
profile = {
|
| 28 |
+
'driver': 'GTiff',
|
| 29 |
+
'height': ndvi.shape[0],
|
| 30 |
+
'width': ndvi.shape[1],
|
| 31 |
+
'count': 1,
|
| 32 |
+
'dtype': 'float32'
|
| 33 |
+
}
|
| 34 |
+
with rasterio.open(geotiff_buffer, 'w', **profile) as dst:
|
| 35 |
+
dst.write(ndvi, 1)
|
| 36 |
+
geotiff_buffer.seek(0)
|
| 37 |
+
|
| 38 |
+
return {
|
| 39 |
+
"ndvi_tiff": StreamingResponse(geotiff_buffer, media_type="image/tiff", headers={"Content-Disposition": "attachment; filename=predicted_ndvi.tif"}),
|
| 40 |
+
"visualization": StreamingResponse(vis_image, media_type="image/png", headers={"Content-Disposition": "attachment; filename=ndvi_visualization.png"})
|
| 41 |
+
}
|
dockerfile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10
|
| 2 |
+
|
| 3 |
+
# Avoid interactive prompts
|
| 4 |
+
ENV DEBIAN_FRONTEND=noninteractive
|
| 5 |
+
|
| 6 |
+
# Install system dependencies
|
| 7 |
+
RUN apt-get update && apt-get install -y \
|
| 8 |
+
libgl1-mesa-glx \
|
| 9 |
+
libglib2.0-0 \
|
| 10 |
+
gdal-bin \
|
| 11 |
+
python3-gdal \
|
| 12 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 13 |
+
|
| 14 |
+
# Set work directory
|
| 15 |
+
WORKDIR /code
|
| 16 |
+
|
| 17 |
+
# Install Python packages
|
| 18 |
+
COPY requirements.txt .
|
| 19 |
+
RUN pip install --upgrade pip && pip install -r requirements.txt
|
| 20 |
+
|
| 21 |
+
# Copy all files
|
| 22 |
+
COPY . .
|
| 23 |
+
|
| 24 |
+
# Launch FastAPI app with Uvicorn
|
| 25 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
ndvi_predictor.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ndvi_predictor.py
|
| 2 |
+
import os
|
| 3 |
+
os.environ["TF_ENABLE_ONEDNN_OPTS"] = "0"
|
| 4 |
+
os.environ["SM_FRAMEWORK"] = "tf.keras"
|
| 5 |
+
|
| 6 |
+
import segmentation_models as sm
|
| 7 |
+
import tensorflow as tf
|
| 8 |
+
import numpy as np
|
| 9 |
+
import rasterio
|
| 10 |
+
import matplotlib.pyplot as plt
|
| 11 |
+
from PIL import Image
|
| 12 |
+
import io
|
| 13 |
+
|
| 14 |
+
def load_model(model_path):
|
| 15 |
+
return tf.keras.models.load_model(model_path, compile=False)
|
| 16 |
+
|
| 17 |
+
def normalize_rgb(rgb):
|
| 18 |
+
rgb_norm = rgb.copy().astype(np.float32)
|
| 19 |
+
for b in range(3):
|
| 20 |
+
band = rgb_norm[:, :, b]
|
| 21 |
+
min_val, max_val = np.percentile(band, [1, 99])
|
| 22 |
+
if min_val < max_val:
|
| 23 |
+
rgb_norm[:, :, b] = np.clip((band - min_val) / (max_val - min_val), 0, 1)
|
| 24 |
+
return rgb_norm
|
| 25 |
+
|
| 26 |
+
def predict_ndvi(model, rgb_np):
|
| 27 |
+
height, width = rgb_np.shape[:2]
|
| 28 |
+
tile_size = 512
|
| 29 |
+
stride = int(tile_size * 0.7)
|
| 30 |
+
|
| 31 |
+
ndvi_pred = np.zeros((height, width), dtype=np.float32)
|
| 32 |
+
weight_map = np.zeros((height, width), dtype=np.float32)
|
| 33 |
+
|
| 34 |
+
if height < tile_size or width < tile_size:
|
| 35 |
+
pad_height = max(0, tile_size - height)
|
| 36 |
+
pad_width = max(0, tile_size - width)
|
| 37 |
+
rgb_padded = np.pad(rgb_np, ((0, pad_height), (0, pad_width), (0, 0)), mode='reflect')
|
| 38 |
+
height_padded, width_padded = rgb_padded.shape[0], rgb_padded.shape[1]
|
| 39 |
+
else:
|
| 40 |
+
rgb_padded = rgb_np
|
| 41 |
+
height_padded, width_padded = height, width
|
| 42 |
+
|
| 43 |
+
for i in range(0, height_padded - tile_size + 1, stride):
|
| 44 |
+
for j in range(0, width_padded - tile_size + 1, stride):
|
| 45 |
+
tile = rgb_padded[i:i+tile_size, j:j+tile_size, :]
|
| 46 |
+
y, x = np.mgrid[0:tile_size, 0:tile_size]
|
| 47 |
+
weights = np.minimum(np.minimum(x, tile_size - x - 1), np.minimum(y, tile_size - y - 1))
|
| 48 |
+
weights = np.clip(weights, 0, 50) / 50
|
| 49 |
+
tile_pred = model.predict(np.expand_dims(tile, axis=0), verbose=0)[0, :, :, 0]
|
| 50 |
+
valid_height = min(tile_size, height - i)
|
| 51 |
+
valid_width = min(tile_size, width - j)
|
| 52 |
+
ndvi_pred[i:i+valid_height, j:j+valid_width] += tile_pred[:valid_height, :valid_width] * weights[:valid_height, :valid_width]
|
| 53 |
+
weight_map[i:i+valid_height, j:j+valid_width] += weights[:valid_height, :valid_width]
|
| 54 |
+
|
| 55 |
+
mask = weight_map > 0
|
| 56 |
+
ndvi_pred[mask] = ndvi_pred[mask] / weight_map[mask]
|
| 57 |
+
return ndvi_pred
|
| 58 |
+
|
| 59 |
+
def create_visualization(rgb, ndvi):
|
| 60 |
+
fig, axes = plt.subplots(1, 2, figsize=(12, 6))
|
| 61 |
+
rgb_disp = np.clip(rgb / 255 if rgb.max() > 1 else rgb, 0, 1)
|
| 62 |
+
axes[0].imshow(rgb_disp)
|
| 63 |
+
axes[0].set_title("RGB Input")
|
| 64 |
+
axes[0].axis("off")
|
| 65 |
+
|
| 66 |
+
im = axes[1].imshow(ndvi, cmap='RdYlGn', vmin=-1, vmax=1)
|
| 67 |
+
axes[1].set_title("Predicted NDVI")
|
| 68 |
+
axes[1].axis("off")
|
| 69 |
+
fig.colorbar(im, ax=axes[1])
|
| 70 |
+
|
| 71 |
+
buf = io.BytesIO()
|
| 72 |
+
plt.tight_layout()
|
| 73 |
+
plt.savefig(buf, format="png")
|
| 74 |
+
plt.close(fig)
|
| 75 |
+
buf.seek(0)
|
| 76 |
+
return buf
|
requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
tensorflow
|
| 2 |
+
segmentation-models
|
| 3 |
+
rasterio
|
| 4 |
+
numpy
|
| 5 |
+
matplotlib
|
| 6 |
+
Pillow
|
| 7 |
+
tqdm
|
| 8 |
+
fastapi
|
| 9 |
+
uvicorn
|