diff --git a/CHANGES.md b/CHANGES.md index 70e85fad7..2f427e89d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Release Notes +## Next + +* Allow a default `rescale` parameter to be set via a dependency (author @samn, https://github.com/developmentseed/titiler/pull/619) + ## 0.11.5 (2023-03-22) * fix `TerrainRGB` (change interval from `1.0` to `0.1`) diff --git a/src/titiler/core/tests/test_factories.py b/src/titiler/core/tests/test_factories.py index 40a44d8e9..f62e1760f 100644 --- a/src/titiler/core/tests/test_factories.py +++ b/src/titiler/core/tests/test_factories.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from enum import Enum from io import BytesIO -from typing import Dict, Type +from typing import Dict, Optional, Type from unittest.mock import patch from urllib.parse import urlencode @@ -21,6 +21,7 @@ from starlette.requests import Request from starlette.testclient import TestClient +from titiler.core.dependencies import RescaleType from titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers from titiler.core.factory import ( AlgorithmFactory, @@ -1513,3 +1514,34 @@ def test_AutoFormat_Colormap(): 0, ] # when creating a PNG, GDAL will set masked value to 0 assert img[:, 500, 500].tolist() == [255, 0, 0, 255] + + +def test_rescale_dependency(): + """Ensure that we can set default rescale values via the rescale_dependency""" + + def custom_rescale_params() -> Optional[RescaleType]: + return [(0, 100)] + + cog = TilerFactory() + cog_custom_range = TilerFactory(rescale_dependency=custom_rescale_params) + + app = FastAPI() + app.include_router(cog.router, prefix="/cog") + app.include_router(cog_custom_range.router, prefix="/cog_custom") + + with TestClient(app) as client: + response = client.get( + f"/cog/tiles/8/87/48.npy?url={DATA_DIR}/cog.tif&rescale=0,1000" + ) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/x-binary" + npy_tile = numpy.load(BytesIO(response.content)) + assert npy_tile.shape == (2, 256, 256) # mask + data + + response = client.get( + f"/cog_custom/tiles/8/87/48.npy?url={DATA_DIR}/cog.tif&rescale=0,1000" + ) + assert response.status_code == 200 + assert response.headers["content-type"] == "application/x-binary" + npy_tile_custom = numpy.load(BytesIO(response.content)) + assert npy_tile.shape == (2, 256, 256) # mask + data diff --git a/src/titiler/core/titiler/core/dependencies.py b/src/titiler/core/titiler/core/dependencies.py index 64f822537..2d1a44ebb 100644 --- a/src/titiler/core/titiler/core/dependencies.py +++ b/src/titiler/core/titiler/core/dependencies.py @@ -350,6 +350,9 @@ class ImageRenderingParams(DefaultDependency): ) +RescaleType = List[Tuple[float, ...]] + + def RescalingParams( rescale: Optional[List[str]] = Query( None, @@ -357,7 +360,7 @@ def RescalingParams( description="comma (',') delimited Min,Max range. Can set multiple time for multiple bands.", example=["0,2000", "0,1000", "0,10000"], # band 1 # band 2 # band 3 ) -) -> Optional[List[Tuple[float, ...]]]: +) -> Optional[RescaleType]: """Min/Max data Rescaling""" if rescale: return [tuple(map(float, r.replace(" ", "").split(","))) for r in rescale] diff --git a/src/titiler/core/titiler/core/factory.py b/src/titiler/core/titiler/core/factory.py index d0defb30b..fbc77ea10 100644 --- a/src/titiler/core/titiler/core/factory.py +++ b/src/titiler/core/titiler/core/factory.py @@ -41,6 +41,7 @@ HistogramParams, ImageParams, ImageRenderingParams, + RescaleType, RescalingParams, StatisticsParams, ) @@ -141,6 +142,8 @@ class BaseTilerFactory(metaclass=abc.ABCMeta): render_dependency: Type[DefaultDependency] = ImageRenderingParams colormap_dependency: Callable[..., Optional[ColorMapType]] = ColorMapParams + rescale_dependency: Callable[..., Optional[RescaleType]] = RescalingParams + # Post Processing Dependencies (algorithm) process_dependency: Callable[ ..., Optional[BaseAlgorithm] @@ -512,7 +515,7 @@ def tile( description="Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).", ), post_process=Depends(self.process_dependency), - rescale: Optional[List[Tuple[float, ...]]] = Depends(RescalingParams), + rescale=Depends(self.rescale_dependency), color_formula: Optional[str] = Query( None, title="Color Formula", @@ -604,9 +607,7 @@ def tilejson( description="Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).", ), post_process=Depends(self.process_dependency), # noqa - rescale: Optional[List[Tuple[float, ...]]] = Depends( - RescalingParams - ), # noqa + rescale=Depends(self.rescale_dependency), # noqa color_formula: Optional[str] = Query( # noqa None, title="Color Formula", @@ -687,9 +688,7 @@ def map_viewer( description="Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).", ), post_process=Depends(self.process_dependency), # noqa - rescale: Optional[List[Tuple[float, ...]]] = Depends( - RescalingParams - ), # noqa + rescale=Depends(self.rescale_dependency), # noqa color_formula: Optional[str] = Query( # noqa None, title="Color Formula", @@ -754,9 +753,7 @@ def wmts( description="Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).", ), post_process=Depends(self.process_dependency), # noqa - rescale: Optional[List[Tuple[float, ...]]] = Depends( - RescalingParams - ), # noqa + rescale=Depends(self.rescale_dependency), # noqa color_formula: Optional[str] = Query( # noqa None, title="Color Formula", @@ -886,7 +883,7 @@ def preview( dataset_params=Depends(self.dataset_dependency), img_params=Depends(self.img_dependency), post_process=Depends(self.process_dependency), - rescale: Optional[List[Tuple[float, ...]]] = Depends(RescalingParams), + rescale=Depends(self.rescale_dependency), # noqa color_formula: Optional[str] = Query( None, title="Color Formula", @@ -956,7 +953,7 @@ def part( dataset_params=Depends(self.dataset_dependency), image_params=Depends(self.img_dependency), post_process=Depends(self.process_dependency), - rescale: Optional[List[Tuple[float, ...]]] = Depends(RescalingParams), + rescale=Depends(self.rescale_dependency), color_formula: Optional[str] = Query( None, title="Color Formula", @@ -1021,7 +1018,7 @@ def geojson_crop( dataset_params=Depends(self.dataset_dependency), image_params=Depends(self.img_dependency), post_process=Depends(self.process_dependency), - rescale: Optional[List[Tuple[float, ...]]] = Depends(RescalingParams), + rescale=Depends(self.rescale_dependency), color_formula: Optional[str] = Query( None, title="Color Formula", diff --git a/src/titiler/mosaic/titiler/mosaic/factory.py b/src/titiler/mosaic/titiler/mosaic/factory.py index 0dc4a3952..fee6c8c5f 100644 --- a/src/titiler/mosaic/titiler/mosaic/factory.py +++ b/src/titiler/mosaic/titiler/mosaic/factory.py @@ -21,7 +21,7 @@ from starlette.requests import Request from starlette.responses import HTMLResponse, Response -from titiler.core.dependencies import DefaultDependency, RescalingParams +from titiler.core.dependencies import DefaultDependency from titiler.core.factory import BaseTilerFactory, img_endpoint_params, templates from titiler.core.models.mapbox import TileJSON from titiler.core.resources.enums import ImageType, MediaType, OptionalHeader @@ -254,7 +254,7 @@ def tile( description="Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * tile_buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).", ), post_process=Depends(self.process_dependency), - rescale: Optional[List[Tuple[float, ...]]] = Depends(RescalingParams), + rescale=Depends(self.rescale_dependency), color_formula: Optional[str] = Query( None, title="Color Formula", @@ -371,9 +371,7 @@ def tilejson( description="Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * tile_buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).", ), post_process=Depends(self.process_dependency), # noqa - rescale: Optional[List[Tuple[float, ...]]] = Depends( - RescalingParams - ), # noqa + rescale=Depends(self.rescale_dependency), # noqa color_formula: Optional[str] = Query( # noqa None, title="Color Formula", @@ -463,9 +461,7 @@ def map_viewer( title="Tile buffer.", description="Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * tile_buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).", ), - rescale: Optional[List[Tuple[float, ...]]] = Depends( - RescalingParams - ), # noqa + rescale=Depends(self.rescale_dependency), # noqa color_formula: Optional[str] = Query( # noqa None, title="Color Formula", @@ -532,9 +528,7 @@ def wmts( description="Buffer on each side of the given tile. It must be a multiple of `0.5`. Output **tilesize** will be expanded to `tilesize + 2 * tile_buffer` (e.g 0.5 = 257x257, 1.0 = 258x258).", ), post_process=Depends(self.process_dependency), # noqa - rescale: Optional[List[Tuple[float, ...]]] = Depends( - RescalingParams - ), # noqa + rescale=Depends(self.rescale_dependency), # noqa color_formula: Optional[str] = Query( # noqa None, title="Color Formula",