Skip to content

Commit

Permalink
add create_colormap_dependency function (#710)
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentsarago authored Oct 12, 2023
1 parent 19b3787 commit d60bc24
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 79 deletions.
2 changes: 1 addition & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Next Release

* Allow a default `color_formula` parameter to be set via a dependency (author @samn, https://github.com/developmentseed/titiler/pull/707)

* add `titiler.core.dependencies.create_colormap_dependency` to create ColorMapParams dependency from `rio_tiler.colormap.ColorMaps` object

## 0.15.0 (2023-09-28)

Expand Down
27 changes: 27 additions & 0 deletions docs/src/advanced/customization.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@

`TiTiler` is designed to help user customize input/output for each endpoint. This section goes over some simple customization examples.

### Custom Colormap

Add user defined colormap to the default colormaps provided by rio-tiler

```python
from fastapi import FastAPI

from rio_tiler.colormap import cmap as default_cmap

from titiler.core.dependencies import create_colormap_dependency
from titiler.core.factory import TilerFactory


app = FastAPI(title="My simple app with custom TMS")

cmap_values = {
"cmap1": {6: (4, 5, 6, 255)},
}
# add custom colormap `cmap1` to the default colormaps
cmap = default_cmap.register(cmap_values)
ColorMapParams = create_colormap_dependency(cmap)


cog = TilerFactory(colormap_dependency=ColorMapParams)
app.include_router(cog.router)
```

### Custom DatasetPathParams for `reader_dependency`

One common customization could be to create your own `path_dependency`. This dependency is used on all endpoint and pass inputs to the *Readers* (MosaicBackend, COGReader, STACReader...).
Expand Down
38 changes: 19 additions & 19 deletions docs/src/examples/code/tiler_with_custom_colormap.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,34 +33,34 @@ app/dependencies.py
"""

import json
from enum import Enum
from typing import Dict, Optional

from typing import Dict, Optional, Literal
from typing_extensions import Annotated

import numpy
import matplotlib
from rio_tiler.colormap import cmap, parse_color
from rio_tiler.colormap import parse_color
from rio_tiler.colormap import cmap as default_cmap
from fastapi import HTTPException, Query


ColorMapName = Enum( # type: ignore
"ColorMapName", [(a, a) for a in sorted(cmap.list())]
)

class ColorMapType(str, Enum):
"""Colormap types."""

explicit = "explicit"
linear = "linear"


def ColorMapParams(
colormap_name: ColorMapName = Query(None, description="Colormap name"),
colormap: str = Query(None, description="JSON encoded custom Colormap"),
colormap_type: ColorMapType = Query(ColorMapType.explicit, description="User input colormap type."),
colormap_name: Annotated[ # type: ignore
Literal[tuple(default_cmap.list())],
Query(description="Colormap name"),
] = None,
colormap: Annotated[
str,
Query(description="JSON encoded custom Colormap"),
] = None,
colormap_type: Annotated[
Literal["explicit", "linear"],
Query(description="User input colormap type."),
] = "explicit",
) -> Optional[Dict]:
"""Colormap Dependency."""
if colormap_name:
return cmap.get(colormap_name.value)
return default_cmap.get(colormap_name)

if colormap:
try:
Expand All @@ -73,7 +73,7 @@ def ColorMapParams(
status_code=400, detail="Could not parse the colormap value."
)

if colormap_type == ColorMapType.linear:
if colormap_type == "linear":
# input colormap has to start from 0 to 255 ?
cm = matplotlib.colors.LinearSegmentedColormap.from_list(
'custom',
Expand Down
24 changes: 4 additions & 20 deletions src/titiler/core/tests/test_CustomCmap.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
"""Test TiTiler Custom Colormap Params."""

from enum import Enum
from io import BytesIO
from typing import Dict, Optional

import numpy
from fastapi import FastAPI, Query
from fastapi import FastAPI
from rio_tiler.colormap import ColorMaps
from starlette.testclient import TestClient
from typing_extensions import Annotated

from titiler.core.dependencies import create_colormap_dependency
from titiler.core.factory import TilerFactory

from .conftest import DATA_DIR
Expand All @@ -18,22 +16,8 @@
"cmap1": {6: (4, 5, 6, 255)},
}
cmap = ColorMaps(data=cmap_values)
ColorMapName = Enum( # type: ignore
"ColorMapName", [(a, a) for a in sorted(cmap.list())]
)


def ColorMapParams(
colormap_name: Annotated[
ColorMapName,
Query(description="Colormap name"),
] = None,
) -> Optional[Dict]:
"""Colormap Dependency."""
if colormap_name:
return cmap.get(colormap_name.value)

return None

ColorMapParams = create_colormap_dependency(cmap)


def test_CustomCmap():
Expand Down
13 changes: 9 additions & 4 deletions src/titiler/core/titiler/core/algorithm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import attr
from fastapi import HTTPException, Query
from pydantic import ValidationError
from typing_extensions import Annotated

from titiler.core.algorithm.base import AlgorithmMetadata, BaseAlgorithm # noqa
from titiler.core.algorithm.dem import Contours, HillShade, TerrainRGB, Terrarium
Expand Down Expand Up @@ -55,10 +56,14 @@ def dependency(self):
"""FastAPI PostProcess dependency."""

def post_process(
algorithm: Literal[tuple(self.data.keys())] = Query(
None, description="Algorithm name"
),
algorithm_params: str = Query(None, description="Algorithm parameter"),
algorithm: Annotated[
Literal[tuple(self.data.keys())],
Query(description="Algorithm name"),
] = None,
algorithm_params: Annotated[
Optional[str],
Query(description="Algorithm parameter"),
] = None,
) -> Optional[BaseAlgorithm]:
"""Data Post-Processing options."""
kwargs = json.loads(algorithm_params) if algorithm_params else {}
Expand Down
76 changes: 41 additions & 35 deletions src/titiler/core/titiler/core/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,59 @@

import json
from dataclasses import dataclass
from enum import Enum
from typing import Dict, List, Optional, Sequence, Tuple, Union
from typing import Callable, Dict, List, Literal, Optional, Sequence, Tuple, Union

import numpy
from fastapi import HTTPException, Query
from rasterio.crs import CRS
from rio_tiler.colormap import cmap, parse_color
from rio_tiler.colormap import ColorMaps
from rio_tiler.colormap import cmap as default_cmap
from rio_tiler.colormap import parse_color
from rio_tiler.errors import MissingAssets, MissingBands
from rio_tiler.types import ColorMapType, RIOResampling
from rio_tiler.types import RIOResampling
from typing_extensions import Annotated

ColorMapName = Enum( # type: ignore
"ColorMapName", [(a, a) for a in sorted(cmap.list())]
)

def create_colormap_dependency(cmap: ColorMaps) -> Callable:
"""Create Colormap Dependency."""

def ColorMapParams(
colormap_name: Annotated[
Optional[ColorMapName],
Query(description="Colormap name"),
] = None,
colormap: Annotated[
Optional[str], Query(description="JSON encoded custom Colormap")
] = None,
) -> Optional[ColorMapType]:
"""Colormap Dependency."""
if colormap_name:
return cmap.get(colormap_name.value)

if colormap:
try:
c = json.loads(
colormap,
object_hook=lambda x: {int(k): parse_color(v) for k, v in x.items()},
)
def deps(
colormap_name: Annotated[ # type: ignore
Literal[tuple(cmap.list())],
Query(description="Colormap name"),
] = None,
colormap: Annotated[
Optional[str], Query(description="JSON encoded custom Colormap")
] = None,
):
if colormap_name:
return cmap.get(colormap_name)

# Make sure to match colormap type
if isinstance(c, Sequence):
c = [(tuple(inter), parse_color(v)) for (inter, v) in c]
if colormap:
try:
c = json.loads(
colormap,
object_hook=lambda x: {
int(k): parse_color(v) for k, v in x.items()
},
)

return c
except json.JSONDecodeError as e:
raise HTTPException(
status_code=400, detail="Could not parse the colormap value."
) from e
# Make sure to match colormap type
if isinstance(c, Sequence):
c = [(tuple(inter), parse_color(v)) for (inter, v) in c]

return None
return c
except json.JSONDecodeError as e:
raise HTTPException(
status_code=400, detail="Could not parse the colormap value."
) from e

return None

return deps


ColorMapParams = create_colormap_dependency(default_cmap)


def DatasetPathParams(url: Annotated[str, Query(description="Dataset URL")]) -> str:
Expand Down

0 comments on commit d60bc24

Please sign in to comment.