Skip to content

Commit 5c3f084

Browse files
committed
Add fuser support and documentation.
1 parent 8d538e8 commit 5c3f084

1 file changed

Lines changed: 73 additions & 23 deletions

File tree

odc/stac/_stac_load.py

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -81,36 +81,38 @@ def patch_urls(
8181
# pylint: disable=too-many-arguments,too-many-locals,too-many-statements
8282
def load(
8383
items: Iterable[pystac.item.Item],
84-
bands: Optional[Union[str, Sequence[str]]] = None,
84+
bands: str | Sequence[str] | None = None,
8585
*,
86-
groupby: Optional[Groupby] = "time",
87-
resampling: Optional[Union[str, Dict[str, str]]] = None,
86+
groupby: Groupby | None = "time",
87+
resampling: str | dict[str, str] | None = None,
8888
dtype: Band_DType = None,
89-
chunks: Optional[Dict[str, int | Literal["auto"]]] = None,
90-
pool: Union[ThreadPoolExecutor, int, None] = None,
89+
chunks: dict[str, int | Literal["auto"]] | None = None,
90+
pool: ThreadPoolExecutor | int | None = None,
9191
# Geo selection
9292
crs: MaybeCRS = Unset(),
93-
resolution: Optional[SomeResolution] = None,
94-
anchor: Optional[GeoboxAnchor] = None,
95-
geobox: Optional[GeoBox] = None,
96-
bbox: Optional[Tuple[float, float, float, float]] = None,
97-
lon: Optional[Tuple[float, float]] = None,
98-
lat: Optional[Tuple[float, float]] = None,
99-
x: Optional[Tuple[float, float]] = None,
100-
y: Optional[Tuple[float, float]] = None,
101-
like: Optional[Any] = None,
102-
geopolygon: Optional[Any] = None,
103-
intersects: Optional[Any] = None,
93+
resolution: SomeResolution | None = None,
94+
anchor: GeoboxAnchor | None = None,
95+
geobox: GeoBox | None = None,
96+
bbox: tuple[float, float, float, float] | None = None,
97+
lon: tuple[float, float] | None = None,
98+
lat: tuple[float, float] | None = None,
99+
x: tuple[float, float] | None = None,
100+
y: tuple[float, float] | None = None,
101+
like: Any = None,
102+
geopolygon: Any = None,
103+
intersects: Any = None,
104104
# UI
105-
progress: Optional[Any] = None,
105+
progress: Any = None,
106106
fail_on_error: bool = True,
107107
# stac related
108-
stac_cfg: Optional[ConversionConfig] = None,
109-
with_properties: Optional[Sequence[str | Mapping[str, Any]]] = None,
110-
patch_url: Optional[Callable[[str], str]] = None,
108+
stac_cfg: ConversionConfig | None = None,
109+
with_properties: Sequence[str | Mapping[str, Any]] | None = None,
110+
patch_url: Callable[[str], str] | None = None,
111111
preserve_original_order: bool = False,
112112
# custom driver
113-
driver: Optional[ReaderDriverSpec] = None,
113+
driver: ReaderDriverSpec | None = None,
114+
# load behaviour
115+
fuse_func: str | Mapping[str, str | None] | None = None,
114116
**kw,
115117
) -> xr.Dataset:
116118
"""
@@ -266,8 +268,7 @@ def load(
266268
267269
.. rubric:: STAC Related Options
268270
269-
:param stac_cfg:
270-
Controls interpretation of :py:class:`pystac.Item`. Mostly used to specify "missing"
271+
:param stac_cfg: Controls interpretation of :py:class:`pystac.Item`. Mostly used to specify "missing"
271272
metadata like pixel data types.
272273
273274
:param with_properties:
@@ -277,6 +278,54 @@ def load(
277278
:param patch_url:
278279
Optionally transform url of every band before loading
279280
281+
.. rubric:: Load behaviour options
282+
283+
:param driver:
284+
Optional. If provided, use the specified driver to load the data.
285+
286+
:param fuse_func:
287+
Function used to fuse/combine/reduce data with the ``group_by`` parameter.
288+
289+
By default, pixels are only copied where valid (i.e. not nodata) pixels
290+
have not yet been copied from previous items.
291+
292+
If data (especially categorical data) appears wrong or unexpected in areas
293+
where items overlap, then an appropriate fuse_func may help.
294+
295+
The fuse_func can perform specific combining steps and can be specified per band.
296+
297+
.. rubric:: Custom fuser functions
298+
299+
Custom fuse functions should be defined as follows:
300+
301+
.. code-block:: python
302+
303+
def my_fuser(dst: np.ndarray, src: np.ndarray) -> None:
304+
# Create a boolean mask array of pixels from this src array to copy.
305+
mask = pixels_to_copy(src)
306+
307+
# Efficiently copy only masked pixels to dst.
308+
np.copyto(dst, src, where=mask)
309+
310+
For an example of a more sophisticated fuser function, see
311+
https://github.com/GeoscienceAustralia/dea-notebooks/blob/77e9e3a05c104f4a0de91857905acce5853975b6/Tools/dea_tools/datahandling.py#L713
312+
313+
Fuser functions are passed to odc-stac as importable strings (fully qualified Python
314+
names of top-level functions) so that they can be serialised to dask workers.
315+
316+
In the following example, the ``my_fuser`` function is used for ``band0``, the default nodata-only
317+
fuser is used for ``band1`` and the ``other_fuser`` function is used for all other raster bands:
318+
319+
.. code-block:: python
320+
321+
data = odc.stac.load(...,
322+
fuse_func={
323+
"band0": "mymodule.my_fuser",
324+
"band1": None,
325+
"*": "mymodule.other_fuser",
326+
}
327+
)
328+
280329
:return:
281330
:py:class:`xarray.Dataset` with requested bands populated
282331
@@ -372,6 +421,7 @@ def load(
372421
use_overviews=kw.get("use_overviews", True),
373422
nodata=kw.get("nodata", None),
374423
fail_on_error=fail_on_error,
424+
fuse_func=fuse_func,
375425
)
376426
if patch_url is not None:
377427
_parsed = [patch_urls(item, edit=patch_url, bands=bands) for item in _parsed]

0 commit comments

Comments
 (0)