Skip to content

Commit 9c4c0e9

Browse files
add cast, floor and ceil algorithm (#1065)
1 parent 6fcd564 commit 9c4c0e9

File tree

6 files changed

+129
-0
lines changed

6 files changed

+129
-0
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* move `rescale` and `color_formula` QueryParameters dependencies in `ImageRenderingParams` class **breaking change**
1313
* handle image rescaling and color_formula within `titiler.core.utils.render_image` function **breaking change**
1414
* add `render_func: Callable[..., Tuple[bytes, str]] = render_image` attribute in `TilerFactory` class
15+
* add `castToInt`, `Floor`, `Ceil` algorithms
1516

1617
### titiler.application
1718

@@ -32,6 +33,8 @@
3233

3334
* Updated WMTS Capabilities template to avoid inserting extra new lines (author @AndrewAnnex, https://github.com/developmentseed/titiler/pull/1052).
3435
* Updated WMTS endpoint in titiler.mosaic and titiler.core to return layer bounds in coordinate ordering matching CRS order if WGS84 is not used (author @AndrewAnnex, https://github.com/developmentseed/titiler/pull/1052).
36+
* Remove `python3.8` support (author @pratapvardhan, https://github.com/developmentseed/titiler/pull/1058)
37+
* Add `python3.13` support (author @pratapvardhan, https://github.com/developmentseed/titiler/pull/1058)
3538

3639
## 0.19.2 (2024-11-28)
3740

src/titiler/core/tests/test_algorithms.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,39 @@ def test_terrainrgb():
235235
assert out.array.shape == (3, 256, 256)
236236
assert out.array.dtype == "uint8"
237237
assert out.array[0, 0, 0] is numpy.ma.masked
238+
239+
240+
def test_ops():
241+
"""test ops: cast, ceil and floor."""
242+
arr = numpy.ma.MaskedArray(
243+
numpy.random.randint(0, 5000, (1, 256, 256)).astype("float32"),
244+
mask=numpy.zeros((1, 256, 256), dtype="bool"),
245+
)
246+
arr.data[0, 0, 0] = 1.6
247+
arr.mask[0, 1:100, 1:100] = True
248+
249+
img = ImageData(arr)
250+
assert img.array.dtype == numpy.float32
251+
252+
algo = default_algorithms.get("cast")()
253+
out = algo(img)
254+
assert out.array.shape == (1, 256, 256)
255+
assert out.array.dtype == "uint8"
256+
assert out.array[0, 0, 0] == 1
257+
assert out.array[0, 1, 1] is numpy.ma.masked
258+
259+
assert img.array.dtype == numpy.float32
260+
algo = default_algorithms.get("floor")()
261+
out = algo(img)
262+
assert out.array.shape == (1, 256, 256)
263+
assert out.array.dtype == "uint8"
264+
assert out.array[0, 0, 0] == 1
265+
assert out.array[0, 1, 1] is numpy.ma.masked
266+
267+
assert img.array.dtype == numpy.float32
268+
algo = default_algorithms.get("ceil")()
269+
out = algo(img)
270+
assert out.array.shape == (1, 256, 256)
271+
assert out.array.dtype == "uint8"
272+
assert out.array[0, 0, 0] == 2
273+
assert out.array[0, 1, 1] is numpy.ma.masked

src/titiler/core/titiler/core/algorithm/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@
1313
from titiler.core.algorithm.base import BaseAlgorithm
1414
from titiler.core.algorithm.dem import Contours, HillShade, TerrainRGB, Terrarium
1515
from titiler.core.algorithm.index import NormalizedIndex
16+
from titiler.core.algorithm.ops import CastToInt, Ceil, Floor
1617

1718
default_algorithms: Dict[str, Type[BaseAlgorithm]] = {
1819
"hillshade": HillShade,
1920
"contours": Contours,
2021
"normalizedIndex": NormalizedIndex,
2122
"terrarium": Terrarium,
2223
"terrainrgb": TerrainRGB,
24+
"cast": CastToInt,
25+
"ceil": Ceil,
26+
"floor": Floor,
2327
}
2428

2529

src/titiler/core/titiler/core/algorithm/dem.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
from titiler.core.algorithm.base import BaseAlgorithm
1111

12+
__all__ = ["HillShade", "Contours", "Terrarium", "TerrainRGB"]
13+
1214

1315
class HillShade(BaseAlgorithm):
1416
"""Hillshade."""

src/titiler/core/titiler/core/algorithm/index.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
from titiler.core.algorithm.base import BaseAlgorithm
99

10+
__all__ = ["NormalizedIndex"]
11+
1012

1113
class NormalizedIndex(BaseAlgorithm):
1214
"""Normalized Difference Index."""
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"""titiler.core.algorithm Ops."""
2+
3+
from typing import Sequence
4+
5+
import numpy
6+
from rio_tiler.models import ImageData
7+
8+
from titiler.core.algorithm.base import BaseAlgorithm
9+
10+
__all__ = ["CastToInt", "Ceil", "Floor"]
11+
12+
13+
class CastToInt(BaseAlgorithm):
14+
"""Cast data to Integer."""
15+
16+
title: str = "Cast data to Integer"
17+
description: str = "Cast data to Integer."
18+
19+
# metadata
20+
output_dtype: str = "uint8"
21+
output_min: Sequence[int] = [0]
22+
output_max: Sequence[int] = [255]
23+
24+
def __call__(self, img: ImageData) -> ImageData:
25+
"""Cast Data."""
26+
return ImageData(
27+
img.array.astype("uint8"),
28+
assets=img.assets,
29+
crs=img.crs,
30+
bounds=img.bounds,
31+
band_names=img.band_names,
32+
metadata=img.metadata,
33+
cutline_mask=img.cutline_mask,
34+
)
35+
36+
37+
class Ceil(BaseAlgorithm):
38+
"""Round data to the smallest integer."""
39+
40+
title: str = "Round data to the smallest integer"
41+
description: str = "Round data to the smallest integer."
42+
43+
# metadata
44+
output_dtype: str = "uint8"
45+
output_min: Sequence[int] = [0]
46+
output_max: Sequence[int] = [255]
47+
48+
def __call__(self, img: ImageData) -> ImageData:
49+
"""Cast Data."""
50+
return ImageData(
51+
numpy.ceil(img.array).astype("uint8"),
52+
assets=img.assets,
53+
crs=img.crs,
54+
bounds=img.bounds,
55+
band_names=img.band_names,
56+
metadata=img.metadata,
57+
cutline_mask=img.cutline_mask,
58+
)
59+
60+
61+
class Floor(BaseAlgorithm):
62+
"""Round data to the largest integer."""
63+
64+
title: str = "Round data to the largest integer"
65+
description: str = "Round data to the largest integer."
66+
67+
# metadata
68+
output_dtype: str = "uint8"
69+
output_min: Sequence[int] = [0]
70+
output_max: Sequence[int] = [255]
71+
72+
def __call__(self, img: ImageData) -> ImageData:
73+
"""Cast Data."""
74+
return ImageData(
75+
numpy.floor(img.array).astype("uint8"),
76+
assets=img.assets,
77+
crs=img.crs,
78+
bounds=img.bounds,
79+
band_names=img.band_names,
80+
metadata=img.metadata,
81+
cutline_mask=img.cutline_mask,
82+
)

0 commit comments

Comments
 (0)