Skip to content

Commit

Permalink
Fix vmin/vmax (#199)
Browse files Browse the repository at this point in the history
* Fix vmin/vmax

* Fix typing

* Remove broken pine gulch example

* Fix typing

* Remove debug statements
  • Loading branch information
banesullivan authored Feb 10, 2024
1 parent 2d747d1 commit 3b46d9e
Show file tree
Hide file tree
Showing 16 changed files with 95 additions and 62 deletions.
2 changes: 1 addition & 1 deletion doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@
attribution: 'Map tiles by <a href="https://carto.com">Carto</a>, under CC BY 3.0. Data by <a href="https://www.openstreetmap.org/">OpenStreetMap</a>, under ODbL.'
}).addTo(map);
L.tileLayer('https://tileserver.banesullivan.com/api/tiles/{z}/{x}/{y}.png?filename=https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2018-02-16/pine-gulch-fire20/1030010076004E00.tif', {
L.tileLayer('https://tileserver.banesullivan.com/api/tiles/{z}/{x}/{y}.png', {
attribution: 'Raster file served by <a href="https://github.com/banesullivan/localtileserver" target="_blank">localtileserver</a>.',
subdomains: '',
crossOrigin: false,
Expand Down
2 changes: 1 addition & 1 deletion doc/source/user-guide/remote-cog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ we can view tiles of the remote file very efficiently in a Jupyter notebook.
from localtileserver import TileClient
import folium, ipyleaflet

url = 'https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2018-02-16/pine-gulch-fire20/1030010076004E00.tif'
url = 'https://github.com/giswqs/data/raw/main/raster/landsat7.tif'

# First, create a tile server from the URL raster file
client = TileClient(url)
Expand Down
2 changes: 1 addition & 1 deletion doc/source/user-guide/validate_cog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ You can use the script by:
from localtileserver import validate_cog

# Path to raster (URL or local path)
url = 'https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2018-02-16/pine-gulch-fire20/1030010076004E00.tif'
url = 'https://github.com/giswqs/data/raw/main/raster/landsat7.tif'

# If invalid, returns False
validate_cog(url)
Expand Down
16 changes: 8 additions & 8 deletions localtileserver/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ def tile(
y: int,
indexes: Optional[List[int]] = None,
colormap: Optional[str] = None,
vmin: Optional[float] = None,
vmax: Optional[float] = None,
vmin: Optional[Union[float, List[float]]] = None,
vmax: Optional[Union[float, List[float]]] = None,
nodata: Optional[Union[int, float]] = None,
output_path: pathlib.Path = None,
encoding: str = "PNG",
Expand Down Expand Up @@ -220,8 +220,8 @@ def thumbnail(
self,
indexes: Optional[List[int]] = None,
colormap: Optional[str] = None,
vmin: Optional[float] = None,
vmax: Optional[float] = None,
vmin: Optional[Union[float, List[float]]] = None,
vmax: Optional[Union[float, List[float]]] = None,
nodata: Optional[Union[int, float]] = None,
output_path: pathlib.Path = None,
encoding: str = "PNG",
Expand Down Expand Up @@ -417,8 +417,8 @@ def get_tile_url(
self,
indexes: Optional[List[int]] = None,
colormap: Optional[str] = None,
vmin: Optional[float] = None,
vmax: Optional[float] = None,
vmin: Optional[Union[float, List[float]]] = None,
vmax: Optional[Union[float, List[float]]] = None,
nodata: Optional[Union[int, float]] = None,
client: bool = False,
):
Expand Down Expand Up @@ -452,11 +452,11 @@ def get_tile_url(
if vmin is not None:
if isinstance(vmin, Iterable) and not isinstance(indexes, Iterable):
raise ValueError("`indexes` must be explicitly set if `vmin` is an iterable.")
params["min"] = vmin
params["vmin"] = vmin
if vmax is not None:
if isinstance(vmax, Iterable) and not isinstance(indexes, Iterable):
raise ValueError("`indexes` must be explicitly set if `vmax` is an iterable.")
params["max"] = vmax
params["vmax"] = vmax
if nodata is not None:
if isinstance(nodata, Iterable) and not isinstance(indexes, Iterable):
raise ValueError("`indexes` must be explicitly set if `nodata` is an iterable.")
Expand Down
7 changes: 0 additions & 7 deletions localtileserver/examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
get_data_path,
get_elevation_us_url,
get_oam2_url,
get_pine_gulch_url,
get_sf_bay_url,
)
from localtileserver.tiler.data import DIRECTORY
Expand Down Expand Up @@ -54,12 +53,6 @@ def get_bahamas(*args, **kwargs):
return TileClient(path, *args, **kwargs)


@wraps(_get_example_client)
def get_pine_gulch(*args, **kwargs):
path = get_pine_gulch_url()
return TileClient(path, *args, **kwargs)


@wraps(_get_example_client)
def get_landsat(*args, **kwargs):
path = get_data_path("landsat.tif")
Expand Down
1 change: 0 additions & 1 deletion localtileserver/tiler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
get_data_path,
get_elevation_us_url,
get_oam2_url,
get_pine_gulch_url,
get_sf_bay_url,
str_to_bool,
)
Expand Down
4 changes: 0 additions & 4 deletions localtileserver/tiler/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ def get_data_path(name):
return DIRECTORY / name


def get_pine_gulch_url():
return "https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2018-02-16/pine-gulch-fire20/1030010076004E00.tif"


def get_sf_bay_url():
# Non-COG: https://data.kitware.com/#item/60747d792fa25629b9a79538
# COG: https://data.kitware.com/#item/626854a04acac99f42126a72
Expand Down
79 changes: 57 additions & 22 deletions localtileserver/tiler/handler.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Methods for working with images."""
import pathlib
from typing import List, Optional, Union
from typing import Dict, List, Optional, Tuple, Union

import numpy as np
import rasterio
Expand Down Expand Up @@ -114,32 +114,65 @@ def _handle_nodata(tile_source: Reader, nodata: Optional[Union[int, float]] = No
return nodata


def _handle_vmin_vmax(
indexes: List[int],
vmin: Optional[Union[float, List[float]]] = None,
vmax: Optional[Union[float, List[float]]] = None,
) -> Tuple[Dict[int, float], Dict[int, float]]:
# TODO: move these string checks to the rest api
if isinstance(vmin, (str, int)):
vmin = float(vmin)
if isinstance(vmax, (str, int)):
vmax = float(vmax)
if isinstance(vmin, list):
vmin = [float(v) for v in vmin]
if isinstance(vmax, list):
vmax = [float(v) for v in vmax]
if isinstance(vmin, float) or vmin is None:
vmin = [vmin] * len(indexes)
if isinstance(vmax, float) or vmax is None:
vmax = [vmax] * len(indexes)
# vmin/vmax must be list of values at this point
if len(vmin) != len(indexes):
raise ValueError("vmin must be same length as indexes")
if len(vmax) != len(indexes):
raise ValueError("vmax must be same length as indexes")
# Now map to the band indexes
return dict(zip(indexes, vmin)), dict(zip(indexes, vmax))


def _render_image(
tile_source: Reader,
img: ImageData,
indexes: Optional[List[int]] = None,
indexes: List[int],
vmin: Dict[int, Optional[float]],
vmax: Dict[int, Optional[float]],
colormap: Optional[str] = None,
vmin: Optional[float] = None,
vmax: Optional[float] = None,
img_format: str = "PNG",
):
if isinstance(vmin, str):
vmin = float(vmin)
if isinstance(vmax, str):
vmax = float(vmax)
colormap = cmap.get(colormap) if colormap else None
if (
not colormap
and len(indexes) == 1
and tile_source.dataset.colorinterp[indexes[0] - 1] == ColorInterp.palette
):
# NOTE: vmin/vmax are not used for palette images
colormap = tile_source.dataset.colormap(indexes[0])
elif img.data.dtype != np.dtype("uint8") or vmin is not None or vmax is not None:
stats = tile_source.statistics()
in_range = [
(s.min if vmin is None else vmin, s.max if vmax is None else vmax)
for s in stats.values()
]
# TODO: change these to any checks for none in vmin/vmax
elif (
img.data.dtype != np.dtype("uint8")
or any(v is not None for v in vmin)
or any(v is not None for v in vmax)
):
stats = tile_source.statistics(indexes=indexes)
in_range = []
for i in indexes:
in_range.append(
(
stats[f"b{i}"].min if vmin[i] is None else vmin[i],
stats[f"b{i}"].max if vmax[i] is None else vmax[i],
)
)
img.rescale(
in_range=in_range,
out_range=[(0, 255)],
Expand All @@ -157,24 +190,25 @@ def get_tile(
y: int,
indexes: Optional[List[int]] = None,
colormap: Optional[str] = None,
vmin: Optional[float] = None,
vmax: Optional[float] = None,
vmin: Optional[Union[float, List[float]]] = None,
vmax: Optional[Union[float, List[float]]] = None,
nodata: Optional[Union[int, float]] = None,
img_format: str = "PNG",
):
if colormap is not None and indexes is None:
indexes = [1]
indexes = _handle_band_indexes(tile_source, indexes)
nodata = _handle_nodata(tile_source, nodata)
vmin, vmax = _handle_vmin_vmax(indexes, vmin, vmax)
img = tile_source.tile(x, y, z, indexes=indexes, nodata=nodata)
return _render_image(
tile_source,
img,
indexes=indexes,
colormap=colormap,
img_format=img_format,
vmin=vmin,
vmax=vmax,
colormap=colormap,
img_format=img_format,
)


Expand All @@ -191,8 +225,8 @@ def get_preview(
tile_source: Reader,
indexes: Optional[List[int]] = None,
colormap: Optional[str] = None,
vmin: Optional[float] = None,
vmax: Optional[float] = None,
vmin: Optional[Union[float, List[float]]] = None,
vmax: Optional[Union[float, List[float]]] = None,
nodata: Optional[Union[int, float]] = None,
img_format: str = "PNG",
max_size: int = 512,
Expand All @@ -201,13 +235,14 @@ def get_preview(
indexes = [1]
indexes = _handle_band_indexes(tile_source, indexes)
nodata = _handle_nodata(tile_source, nodata)
vmin, vmax = _handle_vmin_vmax(indexes, vmin, vmax)
img = tile_source.preview(max_size=max_size, indexes=indexes, nodata=nodata)
return _render_image(
tile_source,
img,
indexes=indexes,
colormap=colormap,
img_format=img_format,
vmin=vmin,
vmax=vmax,
colormap=colormap,
img_format=img_format,
)
4 changes: 1 addition & 3 deletions localtileserver/tiler/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from rasterio import CRS

from localtileserver.tiler.data import clean_url, get_data_path, get_pine_gulch_url
from localtileserver.tiler.data import clean_url, get_data_path


class ImageBytes(bytes):
Expand Down Expand Up @@ -84,8 +84,6 @@ def get_clean_filename(filename: str):
filename = get_data_path("aws_elevation_tiles_prod.xml")
elif filename == "bahamas":
filename = get_data_path("bahamas_rgb.tif")
elif filename == "pine_gulch":
filename = get_pine_gulch_url()

if str(filename).startswith("/vsi"):
return filename
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
<option value="{{ filename_oam2 }}">OpenArialMap Sample</option>
<!-- &indexes=4&vmin=&vmax=130&colormap=cmo.tarn -->
<option value="{{ filename_sf_bay }}">San Francisco Bay</option>
<option value="{{ filename_pine_gulch }}">Pine Gulch Fire</option>
<option value="{{ filename_dem }}">Elevation</option>
<!-- &indexes=1&vmin=-5000&vmax=5000&colormap=cmo.topo -->
<option value="{{ filename_bluemarble }}">Blue Marble</option>
Expand Down
1 change: 0 additions & 1 deletion localtileserver/web/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ def sample_data_context():
context["filename_dem"] = data.get_data_path("aws_elevation_tiles_prod.xml")
context["filename_bluemarble"] = data.get_data_path("frmt_wms_bluemarble_s3_tms.xml")
context["filename_virtualearth"] = data.get_data_path("frmt_wms_virtualearth.xml")
context["filename_pine_gulch"] = data.get_pine_gulch_url()
context["filename_sf_bay"] = data.get_sf_bay_url()
context["filename_landsat_salt_lake"] = data.get_data_path("landsat.tif")
context["filename_oam2"] = data.get_oam2_url()
Expand Down
8 changes: 4 additions & 4 deletions localtileserver/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ def get_leaflet_tile_layer(
debug: bool = False,
indexes: Optional[List[int]] = None,
colormap: Optional[str] = None,
vmin: Optional[float] = None,
vmax: Optional[float] = None,
vmin: Optional[Union[float, List[float]]] = None,
vmax: Optional[Union[float, List[float]]] = None,
nodata: Optional[Union[int, float]] = None,
attribution: str = None,
**kwargs,
Expand Down Expand Up @@ -128,8 +128,8 @@ def get_folium_tile_layer(
debug: bool = False,
indexes: Optional[List[int]] = None,
colormap: Optional[str] = None,
vmin: Optional[float] = None,
vmax: Optional[float] = None,
vmin: Optional[Union[float, List[float]]] = None,
vmax: Optional[Union[float, List[float]]] = None,
nodata: Optional[Union[int, float]] = None,
attr: str = None,
**kwargs,
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def blue_marble(port="default", debug=True):

@pytest.fixture
def remote_file_url():
return "https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2018-02-16/pine-gulch-fire20/1030010076004E00.tif"
return "https://github.com/giswqs/data/raw/main/raster/landsat7.tif"


@pytest.fixture
Expand Down
4 changes: 2 additions & 2 deletions tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ def test_home_page(flask_client):


def test_cesium_split_view(flask_client):
filenameA = "https%3A%2F%2Fopendata.digitalglobe.com%2Fmarshall-fire21%2Fpre%2F13%2F031131113123%2F2021-12-21%2F1050010028D5F600-visual.tif"
filenameB = "https%3A%2F%2Fopendata.digitalglobe.com%2Fmarshall-fire21%2Fpost-event%2F2021-12-30%2F10200100BCB1A500%2F10200100BCB1A500.tif"
filenameA = "https://github.com/giswqs/data/raw/main/raster/landsat7.tif"
filenameB = filenameA
r = flask_client.get(f"/split/?filenameA={filenameA}&filenameB={filenameB}")
assert r.status_code == 200
r = flask_client.get(f"/split/?filenameA={filenameA}")
Expand Down
19 changes: 19 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,25 @@ def test_multiband_vmin_vmax(bahamas):
).format(z=8, x=72, y=110)


def test_vmin_vmax(bahamas):
url = bahamas.get_tile_url(
indexes=[1, 2, 3],
vmin=[100, 200, 250],
).format(z=8, x=72, y=110)
assert get_content(url) # just make sure it doesn't fail
url = bahamas.get_tile_url(
indexes=[1, 2, 3],
vmax=[20, 50, 70],
).format(z=8, x=72, y=110)
assert get_content(url) # just make sure it doesn't fail
url = bahamas.get_tile_url(
indexes=[1, 2, 3],
vmin=[20, 50, 70],
vmax=[100, 200, 250],
).format(z=8, x=72, y=110)
assert get_content(url) # just make sure it doesn't fail


def test_launch_non_default_server(bahamas_file):
default = TileClient(bahamas_file)
diff = TileClient(bahamas_file, port=0)
Expand Down
5 changes: 0 additions & 5 deletions tests/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@ def test_get_bahamas():
assert client.metadata


def test_get_pine_gulch():
client = examples.get_pine_gulch()
assert client.metadata


def test_get_landsat():
client = examples.get_landsat()
assert client.metadata
Expand Down

0 comments on commit 3b46d9e

Please sign in to comment.