Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add **render_params.kwargs ito pass custom render params #259

Merged
merged 3 commits into from
Mar 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* adapt for cogeo-mosaic `3.0.0rc2` and add `backend_options` attribute in MosaicTilerFactory (https://github.com/developmentseed/titiler/pull/247)
* update FastAPI requirements
* update minimal python version to 3.6
* add `**render_params.kwargs` to pass custom render params in `image.render` method (https://github.com/developmentseed/titiler/pull/259)

**breaking change**

Expand Down
38 changes: 28 additions & 10 deletions docs/concepts/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,55 @@ In titiler `Factories`, we use the dependencies to define the inputs for each en

Example:
```python
# Custom Dependency
@dataclass
class ImageParams:
"""Common Image parameters."""
"""Common Preview/Crop parameters."""

max_size: Optional[int] = Query(
1024, description="Maximum image size to read onto."
)
height: Optional[int] = Query(None, description="Force output image height.")
width: Optional[int] = Query(None, description="Force output image width.")

kwargs: Dict = field(init=False, default_factory=dict)

def __post_init__(self):
"""Post Init."""
super().__post_init__()

if self.width and self.height:
self.max_size = None

if self.width is not None:
self.kwargs["width"] = self.width

if self.height is not None:
self.kwargs["height"] = self.height

@router.get(r"/preview.png")
if self.max_size is not None:
self.kwargs["max_size"] = self.max_size


# Simple preview endpoint
@router.get("/preview.png")
def preview(
url: str = Query(..., description="data set URL"), params: ImageParams = Depends(),
url: str = Query(..., description="data set URL"),
params: ImageParams = Depends(),
):

with COGReader(url) as cog:
data, mask = cog.preview(
max_size=params.max_size,
width=params.width,
height=params.height,
)
img = cog.preview(**params.kwargs)
...
```

!!! important

In the example above, we create a custom `ImageParams` dependency which will then be injected to the `preview` endpoint to add **max_size**, **height** and **width** querystring parameters. As for most of `titiler` dependencies, the `ImageParams` class will host the input querystrings to a `kwargs` dictionary, which will then be used to pass the options to the `cog.preview()` methods.

Note: when calling the `cog.preview()` method, we use the `**` operator to pass the dictionary key/value pairs as keyword arguments.


### TiTiler Dependencies

The `factories` allow users to set multiple default dependencies. Here is the list of common dependencies and their default values:

* **path_dependency**: Set dataset path (url).
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,4 @@ def parse_img(content: bytes) -> Dict[Any, Any]:
"""Read tile image and return metadata."""
with MemoryFile(content) as mem:
with mem.open() as dst:
return dst.meta
return dst.profile
63 changes: 63 additions & 0 deletions tests/test_CustomRender.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# """Test TiTiler Custom Render Params."""

from dataclasses import dataclass
from typing import Optional, Union

import numpy

from titiler.dependencies import RenderParams
from titiler.endpoints import factory

from .conftest import DATA_DIR, parse_img

from fastapi import FastAPI, Query

from starlette.testclient import TestClient


@dataclass
class CustomRenderParams(RenderParams):

output_nodata: Optional[Union[str, int, float]] = Query(
None, title="Tiff Ouptut Nodata value",
)
output_compression: Optional[str] = Query(
None, title="Tiff compression schema",
)

def __post_init__(self):
super().__post_init__()
if self.output_nodata is not None:
self.kwargs["nodata"] = (
numpy.nan if self.output_nodata == "nan" else float(self.output_nodata)
)
if self.output_compression is not None:
self.kwargs["compress"] = self.output_compression

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @neda-dtu
this is an example of custom RenderParams that add output_nodata and output_compression to the available query options (you could also hardcode some options).


def test_CustomRender():
"""Test Custom Render Params dependency."""
app = FastAPI()
cog = factory.TilerFactory(render_dependency=CustomRenderParams)
app.include_router(cog.router)
client = TestClient(app)

response = client.get(f"/tiles/8/87/48.tif?url={DATA_DIR}/cog.tif")
assert response.status_code == 200
assert response.headers["content-type"] == "image/tiff; application=geotiff"
meta = parse_img(response.content)
assert meta["driver"] == "GTiff"
assert meta["nodata"] is None
assert meta["count"] == 2
assert not meta.get("compress")

response = client.get(
f"/tiles/8/87/48.tif?url={DATA_DIR}/cog.tif&return_mask=false&output_nodata=0&output_compression=deflate"
)
assert response.status_code == 200
assert response.headers["content-type"] == "image/tiff; application=geotiff"
meta = parse_img(response.content)
assert meta["driver"] == "GTiff"
assert meta["nodata"] == 0
assert meta["count"] == 1
assert meta["compress"] == "deflate"
2 changes: 1 addition & 1 deletion titiler/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def TMSParams(
class DefaultDependency:
"""Dependency Base Class"""

kwargs: dict = field(init=False, default_factory=dict)
kwargs: Dict = field(init=False, default_factory=dict)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix



@dataclass
Expand Down
11 changes: 7 additions & 4 deletions titiler/endpoints/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,9 +320,8 @@ def tile(
**dataset_params.kwargs,
**kwargs,
)
colormap = render_params.colormap or getattr(
src_dst, "colormap", None
)
dst_colormap = getattr(src_dst, "colormap", None)

timings.append(("dataread", round(t.elapsed * 1000, 2)))

if not format:
Expand All @@ -339,8 +338,9 @@ def tile(
content = image.render(
add_mask=render_params.return_mask,
img_format=format.driver,
colormap=colormap,
colormap=render_params.colormap or dst_colormap,
**format.profile,
**render_params.kwargs,
)
timings.append(("format", round(t.elapsed * 1000, 2)))

Expand Down Expand Up @@ -608,6 +608,7 @@ def preview(
img_format=format.driver,
colormap=colormap,
**format.profile,
**render_params.kwargs,
)
timings.append(("format", round(t.elapsed * 1000, 2)))

Expand Down Expand Up @@ -676,6 +677,7 @@ def part(
img_format=format.driver,
colormap=colormap,
**format.profile,
**render_params.kwargs,
)
timings.append(("format", round(t.elapsed * 1000, 2)))

Expand Down Expand Up @@ -1132,6 +1134,7 @@ def tile(
img_format=format.driver,
colormap=render_params.colormap,
**format.profile,
**render_params.kwargs,
)
timings.append(("format", round(t.elapsed * 1000, 2)))

Expand Down