From f013f33b8bc597ee50368c2de01d53dbee041aae Mon Sep 17 00:00:00 2001 From: Dani Arribas-Bel Date: Sat, 29 Jan 2022 16:09:58 +0000 Subject: [PATCH 1/5] Swap inline code for method to pick up nodata --- libpysal/weights/raster.py | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/libpysal/weights/raster.py b/libpysal/weights/raster.py index 90ea91388..07cef54b4 100644 --- a/libpysal/weights/raster.py +++ b/libpysal/weights/raster.py @@ -128,6 +128,37 @@ def da2W( w.index = wsp.index return w +def get_nodata(da): + """ + Identify nodata value in a `DataArray` + + NOTE: follows guidance from https://corteva.github.io/rioxarray/stable/getting_started/nodata_management.html + ... + + Arguments + --------- + da : xarray.DataArray + Input 2D or 3D DataArray with shape=(z, y, x) + + Returns + ------- + nodata : int/float + Value used for nodata pixels. If no value is available, `None` is + returned + """ + try: + return da.rio.nodata + except: + candidates = [ + '_FillValue', 'missing_value', 'fill_value', 'nodata' + ] + for i in candidates: + if i in da.attrs: + if type(i) == tuple: + return da.attrs[i][0] + else: + return da.attrs[i] + return None def da2WSP( da, @@ -226,11 +257,12 @@ def da2WSP( ser = da.to_series() dtype = np.int32 if (shape[0] * shape[1]) < 46340 ** 2 else np.int64 - if "nodatavals" in da.attrs and da.attrs["nodatavals"]: - mask = (ser != da.attrs["nodatavals"][0]).to_numpy() + nodata = get_nodata(da) + if nodata is not None: + mask = (ser != nodata).to_numpy() ids = np.where(mask)[0] id_map = _idmap(ids, mask, dtype) - ser = ser[ser != da.attrs["nodatavals"][0]] + ser = ser[ser != nodata] else: ids = np.arange(len(ser), dtype=dtype) id_map = ids.copy() From 13eddc2becec4f97e936f2313060d55ad72ed2de Mon Sep 17 00:00:00 2001 From: Dani Arribas-Bel Date: Sat, 29 Jan 2022 16:42:44 +0000 Subject: [PATCH 2/5] New method for getting nodata from da.attrs --- libpysal/weights/raster.py | 50 ++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/libpysal/weights/raster.py b/libpysal/weights/raster.py index 07cef54b4..e831b94a2 100644 --- a/libpysal/weights/raster.py +++ b/libpysal/weights/raster.py @@ -149,17 +149,40 @@ def get_nodata(da): try: return da.rio.nodata except: - candidates = [ - '_FillValue', 'missing_value', 'fill_value', 'nodata' - ] - for i in candidates: - if i in da.attrs: - if type(i) == tuple: - return da.attrs[i][0] - else: - return da.attrs[i] + nodata = nodata_from_attrs(da.attrs) + if nodata is not None: + return nodata return None +def nodata_from_attrs(attrs): + """ + Identify nodata value in a `DataArray.attrs` + + NOTE: follows guidance from https://corteva.github.io/rioxarray/stable/getting_started/nodata_management.html + ... + + Arguments + --------- + attrs : dict + `DataArray.attrs` dictionary + + Returns + ------- + nodata : int/float + Value used for nodata pixels. If no value is available, `None` is + returned + """ + candidates = [ + '_FillValue', 'missing_value', 'fill_value', 'nodata' + ] + for i in candidates: + if i in attrs: + if type(i) == tuple: + return attrs[i][0] + else: + return attrs[i] + return None + def da2WSP( da, criterion="queen", @@ -280,7 +303,7 @@ def da2WSP( include_nodata = False # Fallback method to build sparse matrix sw = lat2SW(*shape, criterion) - if "nodatavals" in da.attrs and da.attrs["nodatavals"]: + if nodata is not None: sw = sw[mask] sw = sw[:, mask] @@ -572,12 +595,13 @@ def _index2da(data, index, attrs, coords): dims = idx.names indexer = tuple(idx.codes) shape = tuple(lev.size for lev in idx.levels) + nodata = nodata_from_attrs(attrs) if coords is None: missing = np.prod(shape) > idx.shape[0] if missing: - if "nodatavals" in attrs: - fill_value = attrs["nodatavals"][0] + if nodata is not None: + fill_value = nodata else: min_data = np.min(data) fill_value = min_data - 1 if min_data < 0 else -1 @@ -590,7 +614,7 @@ def _index2da(data, index, attrs, coords): for dim, lev in zip(dims, idx.levels): coords[dim] = lev.to_numpy() else: - fill = attrs["nodatavals"][0] if "nodatavals" in attrs else 0 + fill = nodata if nodata is not None else 0 data_complete = np.full(shape, fill, data.dtype) data_complete[indexer] = data From ceaa75e3ffd90c24419c7353a9f57f0c2da269d7 Mon Sep 17 00:00:00 2001 From: Dani Arribas-Bel Date: Sat, 29 Jan 2022 17:02:55 +0000 Subject: [PATCH 3/5] Add nodatavals to look for nodata values. raster tests pass locally --- libpysal/weights/raster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libpysal/weights/raster.py b/libpysal/weights/raster.py index e831b94a2..fa9b583f1 100644 --- a/libpysal/weights/raster.py +++ b/libpysal/weights/raster.py @@ -173,7 +173,7 @@ def nodata_from_attrs(attrs): returned """ candidates = [ - '_FillValue', 'missing_value', 'fill_value', 'nodata' + '_FillValue', 'missing_value', 'fill_value', 'nodata', 'nodatavals' ] for i in candidates: if i in attrs: From 0062f2a13f1744b0e0c9fa2b2da88c2413a45fdc Mon Sep 17 00:00:00 2001 From: Dani Arribas-Bel Date: Sun, 30 Jan 2022 16:07:36 +0000 Subject: [PATCH 4/5] Update libpysal/weights/raster.py Co-authored-by: Martin Fleischmann --- libpysal/weights/raster.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libpysal/weights/raster.py b/libpysal/weights/raster.py index fa9b583f1..cf34a3b43 100644 --- a/libpysal/weights/raster.py +++ b/libpysal/weights/raster.py @@ -149,10 +149,7 @@ def get_nodata(da): try: return da.rio.nodata except: - nodata = nodata_from_attrs(da.attrs) - if nodata is not None: - return nodata - return None + return nodata_from_attrs(da.attrs) def nodata_from_attrs(attrs): """ From ea8e4af9597aba64df8d357dc8f8d3808ad5a6dd Mon Sep 17 00:00:00 2001 From: Dani Arribas-Bel Date: Fri, 4 Feb 2022 17:15:58 +0000 Subject: [PATCH 5/5] Replace try/except for hasattr --- libpysal/weights/raster.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libpysal/weights/raster.py b/libpysal/weights/raster.py index cf34a3b43..0e2165fd0 100644 --- a/libpysal/weights/raster.py +++ b/libpysal/weights/raster.py @@ -146,9 +146,9 @@ def get_nodata(da): Value used for nodata pixels. If no value is available, `None` is returned """ - try: + if hasattr(da, 'rio'): return da.rio.nodata - except: + else: return nodata_from_attrs(da.attrs) def nodata_from_attrs(attrs):