@@ -81,36 +81,38 @@ def patch_urls(
8181# pylint: disable=too-many-arguments,too-many-locals,too-many-statements
8282def 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