Skip to content

Commit

Permalink
Merge pull request #183 from wri/merge-dist-to-prod
Browse files Browse the repository at this point in the history
Merge dist to prod
  • Loading branch information
danscales authored Dec 12, 2024
2 parents f9ab770 + 96c7862 commit 343bffb
Show file tree
Hide file tree
Showing 21 changed files with 808 additions and 150 deletions.
8 changes: 4 additions & 4 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
from .routes import preview

from .routes.titiler import routes as titiler_routes
from .routes.titiler.gfw_integrated_alerts import router as integrated_alerts_router
from .routes.titiler.umd_glad_dist_alerts import router as dist_alerts_router

gunicorn_logger = logging.getLogger("gunicorn.error")
logger.handlers = gunicorn_logger.handlers
Expand All @@ -55,6 +57,8 @@
burned_areas_tiles.router,
dynamic_vector_tiles.router,
vector_tiles.router,
integrated_alerts_router,
dist_alerts_router,
umd_tree_cover_loss_raster_tiles.router,
umd_glad_landsat_alerts_raster_tiles.router,
umd_glad_sentinel2_alerts_raster_tiles.router,
Expand All @@ -79,10 +83,6 @@
app.include_router(
titiler_routes.mosaic.router, prefix="/cog/mosaic", tags=["Mosaic Tiles"]
)
app.include_router(
titiler_routes.custom.router, prefix="/cog/custom", tags=["Custom Tiles"]
)


#####################
## Middleware
Expand Down
7 changes: 0 additions & 7 deletions app/models/enumerators/alerts_confidence.py

This file was deleted.

17 changes: 17 additions & 0 deletions app/models/enumerators/titiler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from enum import Enum


class AlertConfidence(str, Enum):
low = "low"
high = "high"


class IntegratedAlertConfidence(str, Enum):
low = "low"
high = "high"
highest = "highest"


class RenderType(str, Enum):
true_color = "true_color"
encoded = "encoded"
5 changes: 4 additions & 1 deletion app/routes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@


def to_bbox(x: int, y: int, z: int) -> Bounds:
'''x and y are the tile column and row, z is the zoom level'''
logger.debug(f"Coordinates (X, Y, Z): {x},{y},{z}")
left, bottom, right, top = mercantile.xy_bounds(x, y, z)
logger.debug(f"Bounds (Left, Bottom, Right, Top): {left},{bottom},{right},{top}")
Expand Down Expand Up @@ -232,7 +233,9 @@ def validate_dates(start_date: str, end_date: str, force_date_range) -> None:

def validate_bbox(left: float, bottom: float, right: float, top: float) -> None:
"""Tile should be within WebMercator extent."""

# Extent of whole-world (single) tile at zoom 0, in meters.
min_left, min_bottom, max_right, max_top = mercantile.xy_bounds(0, 0, 0)

if left < min_left or bottom < min_bottom or right > max_right or top > max_top:
if round(left, 0) < round(min_left, 0) or round(bottom, 0) < round(min_bottom, 0) or round(right, 0) > round(max_right, 0) or round(top, 0) > round(max_top, 0):
raise HTTPException(status_code=400, detail="Tile index is out of bounds")
101 changes: 0 additions & 101 deletions app/routes/titiler/algorithms.py

This file was deleted.

Empty file.
175 changes: 175 additions & 0 deletions app/routes/titiler/algorithms/alerts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
from collections import OrderedDict, namedtuple
from typing import Optional

import numpy as np
from fastapi.logger import logger
from rio_tiler.models import ImageData
from titiler.core.algorithm import BaseAlgorithm

from app.models.enumerators.titiler import IntegratedAlertConfidence, RenderType

Colors: namedtuple = namedtuple("Colors", ["red", "green", "blue"])
AlertConfig: namedtuple = namedtuple("AlertConfig", ["confidence", "colors"])


class Alerts(BaseAlgorithm):
"""Decode Deforestation Alerts."""

title: str = "Deforestation Alerts"
description: str = "Decode and visualize alerts"

conf_colors: OrderedDict = OrderedDict(
{
IntegratedAlertConfidence.low: AlertConfig(
confidence=2, colors=Colors(237, 164, 194)
),
IntegratedAlertConfidence.high: AlertConfig(
confidence=3, colors=Colors(220, 102, 153)
),
IntegratedAlertConfidence.highest: AlertConfig(
confidence=4, colors=Colors(201, 42, 109)
),
}
)

record_start_date: str = "2014-12-31"

start_date: Optional[str] = None
end_date: Optional[str] = None
alert_confidence: Optional[str] = None
render_type: RenderType = RenderType.true_color

# metadata
input_nbands: int = 2
output_nbands: int = 4
output_dtype: str = "uint8"

def __call__(self, img: ImageData) -> ImageData:
"""Process the input image and decode deforestation or land disturbance
alert raster data into RGBA format.
Args:
img (ImageData): Input image data with alert date/confidence and intensity
(zoom-level visibility) layers.
Returns:
ImageData: Processed image with RGBA channels either with true colors ready for
visualization or encoding date and confidence for front-end processing.
"""
date_conf_data = img.data[0]

self.intensity = img.data[1]
self.no_data = img.array.mask[0]
self.data_alert_confidence = date_conf_data // 10000
self.alert_date = date_conf_data % 10000

self.mask = self.create_mask()

if self.render_type == RenderType.true_color:
rgb = self.create_true_color_rgb()
alpha = self.create_true_color_alpha()
else: # encoded
rgb = self.create_encoded_rgb()
alpha = self.create_encoded_alpha()

data = np.vstack([rgb, alpha[np.newaxis, ...]]).astype(self.output_dtype)
data = np.ma.MaskedArray(data, mask=False)

return ImageData(data, assets=img.assets, crs=img.crs, bounds=img.bounds)

def create_mask(self):
"""Generate a mask for pixel visibility based on date and confidence
filters, and no data values.
Returns:
np.ndarray: A mask array pixels with no alert or alerts not meeting filter
condition are masked.
"""

mask = ~self.no_data

if self.alert_confidence:
confidence_mask = (
self.data_alert_confidence
>= self.conf_colors[self.alert_confidence].confidence
)
mask *= confidence_mask

if self.start_date:
start_mask = self.alert_date >= (
np.datetime64(self.start_date) - np.datetime64(self.record_start_date)
)
mask *= start_mask

if self.end_date:
end_mask = self.alert_date <= (
np.datetime64(self.end_date) - np.datetime64(self.record_start_date)
)
mask *= end_mask

return mask

def create_true_color_rgb(self):
"""Map alert confidence levels to RGB values for visualization.
Returns:
np.ndarray: A 3D array with RGB channels.
"""
r, g, b = self._rgb_zeros_array()

for properties in self.conf_colors.values():
confidence = properties.confidence
colors = properties.colors
r[self.data_alert_confidence >= confidence] = colors.red
g[self.data_alert_confidence >= confidence] = colors.green
b[self.data_alert_confidence >= confidence] = colors.blue

return np.stack([r, g, b], axis=0)

def create_encoded_rgb(self):
"""Encode the alert date and confidence into the RGB channels, allowing
interactive date filtering and color control on Flagship.
Returns:
np.ndarray: A 3D array with encoded RGB values.
"""
r, g, b = self._rgb_zeros_array()
r = self.alert_date // 255
g = self.alert_date % 255
b = (self.data_alert_confidence // 3 + 1) * 100 + self.intensity

return np.stack([r, g, b], axis=0)

def create_true_color_alpha(self):
"""Set the transparency (alpha) channel for alert pixels based on date,
confidence filters, and intensity input. The intensity multiplier is
used to control how isolated alerts fade out at low zoom levels,
matching the rendering behavior in Flagship.
Returns:
np.ndarray: Array representing the alpha (transparency) channel, where pixel
visibility is adjusted by intensity.
"""
alpha = np.where(self.mask, self.intensity * 150, 0)
return np.minimum(255, alpha)

def create_encoded_alpha(self):
"""Generate the alpha channel for encoded alerts. The default
implementation sets pixel visibility based on date/confidence filters
and intensity input. Can be overridden for specific alert types.
Returns:
np.ndarray: An array representing the alpha channel.
"""
logger.info(
"""Encoded alpha not provided, returning alpha
from input layer and date/confidence mask."""
)
return self.create_true_color_alpha()

def _rgb_zeros_array(self):
r = np.zeros_like(self.data_alert_confidence, dtype=np.uint8)
g = np.zeros_like(self.data_alert_confidence, dtype=np.uint8)
b = np.zeros_like(self.data_alert_confidence, dtype=np.uint8)

return r, g, b
Loading

0 comments on commit 343bffb

Please sign in to comment.