From a6bfaa042a59dcfbcdc6c269f2dc4e001df83af3 Mon Sep 17 00:00:00 2001 From: aladinor Date: Tue, 29 Oct 2024 08:16:44 -0500 Subject: [PATCH 01/51] refactoring dtree node apend using _attach_sweep_groups function --- xradar/io/backends/cfradial1.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/xradar/io/backends/cfradial1.py b/xradar/io/backends/cfradial1.py index c77d2613..1feabd17 100644 --- a/xradar/io/backends/cfradial1.py +++ b/xradar/io/backends/cfradial1.py @@ -49,7 +49,7 @@ required_global_attrs, required_root_vars, ) -from .common import _maybe_decode +from .common import _maybe_decode, _attach_sweep_groups def _get_required_root_dataset(ds, optional=True): @@ -361,17 +361,18 @@ def open_cfradial1_datatree(filename_or_obj, **kwargs): if calib: dtree["/radar_calibration"] = calib - sweep_child = list( - _get_sweep_groups( - ds, - sweep=sweep, - first_dim=first_dim, - optional=optional, - site_coords=site_coords, - ).values() + dtree = _attach_sweep_groups( + dtree, + list( + _get_sweep_groups( + ds, + sweep=sweep, + first_dim=first_dim, + optional=optional, + site_coords=site_coords, + ).values() + ), ) - for i, sw in enumerate(sweep_child): - dtree[f"sweep_{i}"] = sw return DataTree.from_dict(dtree) From e71d04635d66290542a265a0dd9ece01249021fc Mon Sep 17 00:00:00 2001 From: aladinor Date: Tue, 29 Oct 2024 08:21:08 -0500 Subject: [PATCH 02/51] refactoring iris datatree to use from_dict constructor --- xradar/io/backends/iris.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/xradar/io/backends/iris.py b/xradar/io/backends/iris.py index 0953e2e4..aa14ff0e 100644 --- a/xradar/io/backends/iris.py +++ b/xradar/io/backends/iris.py @@ -43,6 +43,7 @@ import numpy as np import xarray as xr +from xarray import DataTree from xarray.backends.common import AbstractDataStore, BackendArray, BackendEntrypoint from xarray.backends.file_manager import CachingFileManager from xarray.backends.store import StoreBackendEntrypoint @@ -62,6 +63,7 @@ optional_root_attrs, optional_root_vars, radar_parameters_subgroup, + georeferencing_correction_subgroup, required_global_attrs, required_root_vars, sweep_vars_mapping, @@ -3948,6 +3950,10 @@ def get_attrs(self): attributes.update( {"elevation_lower_limit": ll, "elevation_upper_limit": ul} ) + attributes['source'] = "Sigmet" + attributes['scan_name'] = self.root.product_hdr["product_configuration"]["task_name"] + attributes['instrument_name'] = self.root.ingest_header["ingest_configuration"]["site_name"].strip() + attributes['comment'] = self.root.ingest_header["task_configuration"]["task_end_info"]['task_description'] return FrozenDict(attributes) @@ -4138,7 +4144,7 @@ def open_iris_datatree(filename_or_obj, **kwargs): """ # handle kwargs, extract first_dim backend_kwargs = kwargs.pop("backend_kwargs", {}) - # first_dim = backend_kwargs.pop("first_dim", None) + optional = kwargs.pop("optional", True) sweep = kwargs.pop("sweep", None) sweeps = [] kwargs["backend_kwargs"] = backend_kwargs @@ -4159,13 +4165,19 @@ def open_iris_datatree(filename_or_obj, **kwargs): xr.open_dataset(filename_or_obj, group=swp, engine="iris", **kwargs) for swp in sweeps ] - # get the datatree root - root = _get_required_root_dataset(ls_ds) - # create datatree root node with required data - dtree = xr.DataTree(dataset=root, name="root") - # get radar_parameters group - subgroup = _get_subgroup(ls_ds, radar_parameters_subgroup) - # attach radar_parameter group - dtree["radar_parameters"] = xr.DataTree(subgroup) - # return Datatree attaching the sweep child nodes - return _attach_sweep_groups(dtree, ls_ds) + + dtree: dict = { + "/": _get_required_root_dataset(ls_ds, optional=optional), + "/radar_parameters": _get_subgroup(ls_ds, radar_parameters_subgroup), + "/georeferencing_correction": _get_subgroup( + ls_ds, georeferencing_correction_subgroup + ), + } + + # radar_calibration (connected with calib-dimension) + # calib = _get_radar_calibration(ds) + # if calib: + # dtree["/radar_calibration"] = calib + + dtree = _attach_sweep_groups(dtree, ls_ds) + return DataTree.from_dict(dtree) From 20d29a35e021f5ca00643f03134a81b2f9e5810b Mon Sep 17 00:00:00 2001 From: aladinor Date: Tue, 29 Oct 2024 08:24:43 -0500 Subject: [PATCH 03/51] running pre-commit hook --- xradar/io/backends/cfradial1.py | 2 +- xradar/io/backends/iris.py | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/xradar/io/backends/cfradial1.py b/xradar/io/backends/cfradial1.py index 1feabd17..228ebf0a 100644 --- a/xradar/io/backends/cfradial1.py +++ b/xradar/io/backends/cfradial1.py @@ -49,7 +49,7 @@ required_global_attrs, required_root_vars, ) -from .common import _maybe_decode, _attach_sweep_groups +from .common import _attach_sweep_groups, _maybe_decode def _get_required_root_dataset(ds, optional=True): diff --git a/xradar/io/backends/iris.py b/xradar/io/backends/iris.py index aa14ff0e..a2fddb70 100644 --- a/xradar/io/backends/iris.py +++ b/xradar/io/backends/iris.py @@ -53,6 +53,7 @@ from ... import util from ...model import ( + georeferencing_correction_subgroup, get_altitude_attrs, get_azimuth_attrs, get_elevation_attrs, @@ -63,7 +64,6 @@ optional_root_attrs, optional_root_vars, radar_parameters_subgroup, - georeferencing_correction_subgroup, required_global_attrs, required_root_vars, sweep_vars_mapping, @@ -3950,10 +3950,16 @@ def get_attrs(self): attributes.update( {"elevation_lower_limit": ll, "elevation_upper_limit": ul} ) - attributes['source'] = "Sigmet" - attributes['scan_name'] = self.root.product_hdr["product_configuration"]["task_name"] - attributes['instrument_name'] = self.root.ingest_header["ingest_configuration"]["site_name"].strip() - attributes['comment'] = self.root.ingest_header["task_configuration"]["task_end_info"]['task_description'] + attributes["source"] = "Sigmet" + attributes["scan_name"] = self.root.product_hdr["product_configuration"][ + "task_name" + ] + attributes["instrument_name"] = self.root.ingest_header["ingest_configuration"][ + "site_name" + ].strip() + attributes["comment"] = self.root.ingest_header["task_configuration"][ + "task_end_info" + ]["task_description"] return FrozenDict(attributes) From e4c8c9c241bbd1286453ff0b475b6e253ec287ba Mon Sep 17 00:00:00 2001 From: aladinor Date: Tue, 29 Oct 2024 10:58:50 -0500 Subject: [PATCH 04/51] refactoring calibration subgroup --- xradar/io/backends/cfradial1.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/xradar/io/backends/cfradial1.py b/xradar/io/backends/cfradial1.py index 228ebf0a..77478e9c 100644 --- a/xradar/io/backends/cfradial1.py +++ b/xradar/io/backends/cfradial1.py @@ -33,7 +33,7 @@ __doc__ = __doc__.format("\n ".join(__all__)) import numpy as np -from xarray import DataTree, merge, open_dataset +from xarray import Dataset, DataTree, merge, open_dataset from xarray.backends import NetCDF4DataStore from xarray.backends.common import BackendEntrypoint from xarray.backends.store import StoreBackendEntrypoint @@ -89,7 +89,6 @@ def _get_required_root_dataset(ds, optional=True): root.sweep_group_name.encoding["dtype"] = root.sweep_group_name.dtype # remove cf standard name root.sweep_group_name.attrs = [] - return root @@ -297,6 +296,8 @@ def _get_radar_calibration(ds): subgroup = subgroup.rename_vars(calib_vars) subgroup.attrs = {} return subgroup + else: + return Dataset() def open_cfradial1_datatree(filename_or_obj, **kwargs): @@ -354,13 +355,10 @@ def open_cfradial1_datatree(filename_or_obj, **kwargs): "/georeferencing_correction": _get_subgroup( ds, georeferencing_correction_subgroup ), + "/radar_calibration": _get_radar_calibration(ds), } # radar_calibration (connected with calib-dimension) - calib = _get_radar_calibration(ds) - if calib: - dtree["/radar_calibration"] = calib - dtree = _attach_sweep_groups( dtree, list( From 8d694db423e3a435b7baacca94896e6291cc786e Mon Sep 17 00:00:00 2001 From: aladinor Date: Tue, 29 Oct 2024 11:10:51 -0500 Subject: [PATCH 05/51] dropping common attributes present in all sweeps --- xradar/io/backends/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xradar/io/backends/common.py b/xradar/io/backends/common.py index a480c15d..cdca9fda 100644 --- a/xradar/io/backends/common.py +++ b/xradar/io/backends/common.py @@ -56,7 +56,7 @@ def _fix_angle(da): def _attach_sweep_groups(dtree, sweeps): """Attach sweep groups to DataTree.""" for i, sw in enumerate(sweeps): - dtree[f"sweep_{i}"] = xr.DataTree(sw) + dtree[f"sweep_{i}"] = xr.DataTree(sw.drop_attrs()) return dtree From 06d37a7144c08c1a712ea7f330c3c9d5feae3268 Mon Sep 17 00:00:00 2001 From: aladinor Date: Tue, 29 Oct 2024 11:49:45 -0500 Subject: [PATCH 06/51] refactoring to use from_dict datatree constructor --- xradar/io/backends/iris.py | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/xradar/io/backends/iris.py b/xradar/io/backends/iris.py index a2fddb70..9529f580 100644 --- a/xradar/io/backends/iris.py +++ b/xradar/io/backends/iris.py @@ -43,7 +43,7 @@ import numpy as np import xarray as xr -from xarray import DataTree +from xarray import Dataset, DataTree from xarray.backends.common import AbstractDataStore, BackendArray, BackendEntrypoint from xarray.backends.file_manager import CachingFileManager from xarray.backends.store import StoreBackendEntrypoint @@ -68,7 +68,7 @@ required_root_vars, sweep_vars_mapping, ) -from .common import _assign_root, _attach_sweep_groups +from .common import _attach_sweep_groups #: mapping from IRIS names to CfRadial2/ODIM iris_mapping = { @@ -3974,15 +3974,12 @@ def _get_iris_group_names(filename): def _get_required_root_dataset(ls_ds, optional=True): """Extract Root Dataset.""" # keep only defined mandatory and defined optional variables per default + # by checking in all nodes data_var = {x for xs in [sweep.variables.keys() for sweep in ls_ds] for x in xs} remove_root = set(data_var) ^ set(required_root_vars) if optional: remove_root ^= set(optional_root_vars) - remove_root ^= { - "fixed_angle", - "sweep_group_name", - "sweep_fixed_angle", - } + remove_root ^= {"sweep_number", "fixed_angle"} remove_root &= data_var root = [sweep.drop_vars(remove_root) for sweep in ls_ds] root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} @@ -4004,13 +4001,11 @@ def _get_required_root_dataset(ls_ds, optional=True): remove_attrs ^= set(optional_root_attrs) for k in remove_attrs: root.attrs.pop(k, None) - # creating a copy of the dataset list for using the _assing_root function. - # and get the variabes/attributes for the root dataset - ls = ls_ds.copy() - ls.insert(0, xr.Dataset()) - dtree = xr.DataTree(dataset=_assign_root(ls), name="root") - root = root.assign(dtree.variables) - root.attrs = dtree.attrs + # renaming variables in the root group + root = root.rename_vars({"sweep_number": "sweep_group_name"}) + root["sweep_group_name"].values = np.array( + [f"sweep_{i}" for i in root["sweep_group_name"].values] + ) return root @@ -4030,6 +4025,12 @@ def _get_subgroup(ls_ds: list[xr.Dataset], subdict): return subgroup +def _get_radar_calibration(ls_ds: list[xr.Dataset]) -> xr.Dataset: + """Get radar calibration root metadata group.""" + # radar_calibration is connected with calib-dimension + return Dataset() + + class IrisBackendEntrypoint(BackendEntrypoint): """Xarray BackendEntrypoint for IRIS/Sigmet data.""" @@ -4178,12 +4179,8 @@ def open_iris_datatree(filename_or_obj, **kwargs): "/georeferencing_correction": _get_subgroup( ls_ds, georeferencing_correction_subgroup ), + "/radar_calibration": _get_radar_calibration(ls_ds), } - - # radar_calibration (connected with calib-dimension) - # calib = _get_radar_calibration(ds) - # if calib: - # dtree["/radar_calibration"] = calib - + # attach all sweeps dtree = _attach_sweep_groups(dtree, ls_ds) return DataTree.from_dict(dtree) From e45c673715cf07d738373565ad28ff57e2c5db12 Mon Sep 17 00:00:00 2001 From: aladinor Date: Tue, 29 Oct 2024 14:09:28 -0500 Subject: [PATCH 07/51] refactoring loop by iterating only over sweeps --- tests/io/test_io.py | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/tests/io/test_io.py b/tests/io/test_io.py index da887076..69d6628b 100644 --- a/tests/io/test_io.py +++ b/tests/io/test_io.py @@ -641,28 +641,27 @@ def test_open_iris_datatree(iris0_file): azimuths = [360] * 10 ranges = [664] * 10 i = 0 - for grp in dtree.groups: - if grp.startswith("/sweep_"): - ds = dtree[grp].ds - assert dict(ds.sizes) == {"azimuth": azimuths[i], "range": ranges[i]} - assert set(ds.data_vars) & ( - sweep_dataset_vars | non_standard_sweep_dataset_vars - ) == set(moments) - assert set(ds.data_vars) & (required_sweep_metadata_vars) == set( - required_sweep_metadata_vars ^ {"azimuth", "elevation"} - ) - assert set(ds.coords) == { - "azimuth", - "elevation", - "time", - "latitude", - "longitude", - "altitude", - "range", - } - assert np.round(ds.elevation.mean().values.item(), 1) == elevations[i] - assert ds.sweep_number == i - i += 1 + for grp in dtree.match("sweep_*"): + ds = dtree[grp].ds + assert dict(ds.sizes) == {"azimuth": azimuths[i], "range": ranges[i]} + assert set(ds.data_vars) & ( + sweep_dataset_vars | non_standard_sweep_dataset_vars + ) == set(moments) + assert set(ds.data_vars) & (required_sweep_metadata_vars) == set( + required_sweep_metadata_vars ^ {"azimuth", "elevation"} + ) + assert set(ds.coords) == { + "azimuth", + "elevation", + "time", + "latitude", + "longitude", + "altitude", + "range", + } + assert np.round(ds.elevation.mean().values.item(), 1) == elevations[i] + assert ds.sweep_number == i + i += 1 def test_open_iris0_dataset(iris0_file): From 435d281d8174895de45f46239c6cf37661e3b0e7 Mon Sep 17 00:00:00 2001 From: aladinor Date: Tue, 29 Oct 2024 14:09:58 -0500 Subject: [PATCH 08/51] fixing failing test with attributes --- xradar/io/backends/iris.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/xradar/io/backends/iris.py b/xradar/io/backends/iris.py index 9529f580..c9a9243d 100644 --- a/xradar/io/backends/iris.py +++ b/xradar/io/backends/iris.py @@ -61,14 +61,12 @@ get_longitude_attrs, get_range_attrs, moment_attrs, - optional_root_attrs, optional_root_vars, radar_parameters_subgroup, - required_global_attrs, required_root_vars, sweep_vars_mapping, ) -from .common import _attach_sweep_groups +from .common import _assign_root, _attach_sweep_groups #: mapping from IRIS names to CfRadial2/ODIM iris_mapping = { @@ -3992,16 +3990,12 @@ def _get_required_root_dataset(ls_ds, optional=True): root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} ds_vars = [sweep[root_vars] for sweep in ls_ds] - - root = xr.concat(ds_vars, dim="sweep").reset_coords() - # keep only defined mandatory and defined optional attributes per default - attrs = root.attrs.keys() - remove_attrs = set(attrs) ^ set(required_global_attrs) - if optional: - remove_attrs ^= set(optional_root_attrs) - for k in remove_attrs: - root.attrs.pop(k, None) + vars = xr.concat(ds_vars, dim="sweep").reset_coords() # renaming variables in the root group + ls = ls_ds.copy() + ls.insert(0, xr.Dataset()) + root = _assign_root(ls) + root = xr.merge([root, vars]) root = root.rename_vars({"sweep_number": "sweep_group_name"}) root["sweep_group_name"].values = np.array( [f"sweep_{i}" for i in root["sweep_group_name"].values] From ca659c59c27f0f98598db5f82533ce6767b6201d Mon Sep 17 00:00:00 2001 From: aladinor Date: Tue, 29 Oct 2024 14:42:48 -0500 Subject: [PATCH 09/51] addding comments --- xradar/io/backends/iris.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/xradar/io/backends/iris.py b/xradar/io/backends/iris.py index c9a9243d..7d3c0482 100644 --- a/xradar/io/backends/iris.py +++ b/xradar/io/backends/iris.py @@ -3991,11 +3991,16 @@ def _get_required_root_dataset(ls_ds, optional=True): root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} ds_vars = [sweep[root_vars] for sweep in ls_ds] vars = xr.concat(ds_vars, dim="sweep").reset_coords() - # renaming variables in the root group + + # Creating the root group using _assing_root funciont ls = ls_ds.copy() ls.insert(0, xr.Dataset()) root = _assign_root(ls) + + # merging both the created and the variables within each dataset root = xr.merge([root, vars]) + + # Renaming variable root = root.rename_vars({"sweep_number": "sweep_group_name"}) root["sweep_group_name"].values = np.array( [f"sweep_{i}" for i in root["sweep_group_name"].values] @@ -4166,7 +4171,6 @@ def open_iris_datatree(filename_or_obj, **kwargs): xr.open_dataset(filename_or_obj, group=swp, engine="iris", **kwargs) for swp in sweeps ] - dtree: dict = { "/": _get_required_root_dataset(ls_ds, optional=optional), "/radar_parameters": _get_subgroup(ls_ds, radar_parameters_subgroup), @@ -4175,6 +4179,5 @@ def open_iris_datatree(filename_or_obj, **kwargs): ), "/radar_calibration": _get_radar_calibration(ls_ds), } - # attach all sweeps dtree = _attach_sweep_groups(dtree, ls_ds) return DataTree.from_dict(dtree) From 9cbbca9f82e3e48b5b2bd1da4fc01c5d2a3727d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20M=C3=BChlbauer?= Date: Wed, 30 Oct 2024 07:58:32 +0100 Subject: [PATCH 10/51] Update xradar/io/backends/iris.py --- xradar/io/backends/iris.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xradar/io/backends/iris.py b/xradar/io/backends/iris.py index 7d3c0482..18fc4a17 100644 --- a/xradar/io/backends/iris.py +++ b/xradar/io/backends/iris.py @@ -4005,6 +4005,8 @@ def _get_required_root_dataset(ls_ds, optional=True): root["sweep_group_name"].values = np.array( [f"sweep_{i}" for i in root["sweep_group_name"].values] ) + # override/fix dtype in encoding + root["sweep_group_name"].encoding["dtype"] = root["sweep_group_name"].dtype return root From 62e9c623b39f4e66237dcbf20deed62a9681d5c7 Mon Sep 17 00:00:00 2001 From: Kai Muehlbauer Date: Wed, 30 Oct 2024 08:02:43 +0100 Subject: [PATCH 11/51] fix lint --- xradar/io/backends/iris.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xradar/io/backends/iris.py b/xradar/io/backends/iris.py index 18fc4a17..fe1979c9 100644 --- a/xradar/io/backends/iris.py +++ b/xradar/io/backends/iris.py @@ -4005,7 +4005,7 @@ def _get_required_root_dataset(ls_ds, optional=True): root["sweep_group_name"].values = np.array( [f"sweep_{i}" for i in root["sweep_group_name"].values] ) - # override/fix dtype in encoding + # override/fix dtype in encoding root["sweep_group_name"].encoding["dtype"] = root["sweep_group_name"].dtype return root From e400dc56b9beb45fb82d0c73e3c1d063ebcab991 Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 30 Oct 2024 07:57:52 -0500 Subject: [PATCH 12/51] fixing typo --- xradar/io/backends/iris.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xradar/io/backends/iris.py b/xradar/io/backends/iris.py index 7d3c0482..54735578 100644 --- a/xradar/io/backends/iris.py +++ b/xradar/io/backends/iris.py @@ -3992,7 +3992,7 @@ def _get_required_root_dataset(ls_ds, optional=True): ds_vars = [sweep[root_vars] for sweep in ls_ds] vars = xr.concat(ds_vars, dim="sweep").reset_coords() - # Creating the root group using _assing_root funciont + # Creating the root group using _assign_root function ls = ls_ds.copy() ls.insert(0, xr.Dataset()) root = _assign_root(ls) From 274c2241cc6ab3ce9e517fc87ac081014a7a9303 Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 30 Oct 2024 07:58:45 -0500 Subject: [PATCH 13/51] refactoring datamet backend to use from_dict datatree construnctor --- xradar/io/backends/datamet.py | 93 +++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 9 deletions(-) diff --git a/xradar/io/backends/datamet.py b/xradar/io/backends/datamet.py index e5cdd59b..1956b717 100644 --- a/xradar/io/backends/datamet.py +++ b/xradar/io/backends/datamet.py @@ -31,6 +31,7 @@ import numpy as np import xarray as xr +from xarray import Dataset, DataTree from xarray.backends.common import AbstractDataStore, BackendArray, BackendEntrypoint from xarray.backends.file_manager import CachingFileManager from xarray.backends.store import StoreBackendEntrypoint @@ -40,6 +41,7 @@ from ... import util from ...model import ( + georeferencing_correction_subgroup, get_altitude_attrs, get_azimuth_attrs, get_elevation_attrs, @@ -48,6 +50,9 @@ get_range_attrs, get_time_attrs, moment_attrs, + optional_root_vars, + radar_parameters_subgroup, + required_root_vars, sweep_vars_mapping, ) from .common import _assign_root, _attach_sweep_groups @@ -361,7 +366,12 @@ def get_variables(self): ) def get_attrs(self): - return FrozenDict() + attributes = { + "scan_name": self.root.scan_metadata["scan_type"], + "instrument_name": self.root.scan_metadata["origin"], + "source": "Datamet", + } + return FrozenDict(attributes) class DataMetBackendEntrypoint(BackendEntrypoint): @@ -441,6 +451,67 @@ def open_dataset( return ds +def _get_required_root_dataset(ls_ds, optional=True): + """Extract Root Dataset.""" + # keep only defined mandatory and defined optional variables per default + # by checking in all nodes + data_var = {x for xs in [sweep.variables.keys() for sweep in ls_ds] for x in xs} + remove_root = set(data_var) ^ set(required_root_vars) + if optional: + remove_root ^= set(optional_root_vars) + remove_root ^= {"sweep_number", "fixed_angle"} + remove_root &= data_var + root = [sweep.drop_vars(remove_root) for sweep in ls_ds] + root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} + # rename variables + # todo: find a more easy method not iterating over all variables + for k in root_vars: + rename = optional_root_vars.get(k, None) + if rename: + root = [sweep.rename_vars({k: rename}) for sweep in root] + + root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} + ds_vars = [sweep[root_vars] for sweep in ls_ds] + vars = xr.concat(ds_vars, dim="sweep").reset_coords() + + # Creating the root group using _assign_root function + ls = ls_ds.copy() + ls.insert(0, xr.Dataset()) + root = _assign_root(ls) + + # merging both the created and the variables within each dataset + root = xr.merge([root, vars]) + + # Renaming variable + root = root.rename_vars({"sweep_number": "sweep_group_name"}) + root["sweep_group_name"].values = np.array( + [f"sweep_{i}" for i in root["sweep_group_name"].values] + ) + return root + + +def _get_subgroup(ls_ds: list[xr.Dataset], subdict): + """Get iris-sigmet root metadata group. + Variables are fetched from the provided Dataset according to the subdict dictionary. + """ + meta_vars = subdict + data_vars = {x for xs in [ds.variables.keys() for ds in ls_ds] for x in xs} + extract_vars = set(data_vars) & set(meta_vars) + subgroup = xr.concat([ds[extract_vars] for ds in ls_ds], "sweep") + for k in subgroup.data_vars: + rename = meta_vars[k] + if rename: + subgroup = subgroup.rename_vars({k: rename}) + subgroup.attrs = {} + return subgroup + + +def _get_radar_calibration(ls_ds: list[xr.Dataset]) -> xr.Dataset: + """Get radar calibration root metadata group.""" + # radar_calibration is connected with calib-dimension + return Dataset() + + def open_datamet_datatree(filename_or_obj, **kwargs): """Open DataMet dataset as :py:class:`xarray.DataTree`. @@ -476,7 +547,7 @@ def open_datamet_datatree(filename_or_obj, **kwargs): """ # handle kwargs, extract first_dim backend_kwargs = kwargs.pop("backend_kwargs", {}) - # first_dim = backend_kwargs.pop("first_dim", None) + optional = kwargs.pop("optional", True) kwargs["backend_kwargs"] = backend_kwargs sweep = kwargs.pop("sweep", None) @@ -499,16 +570,20 @@ def open_datamet_datatree(filename_or_obj, **kwargs): f"sweep_{i}" for i in range(0, dmet.scan_metadata["elevation_number"]) ] - ds = [ + ls_ds: list[xr.Dataset] = [ xr.open_dataset( filename_or_obj, group=swp, engine=DataMetBackendEntrypoint, **kwargs ) for swp in sweeps ] - ds.insert(0, xr.Dataset()) - - # create datatree root node with required data - dtree = xr.DataTree(dataset=_assign_root(ds), name="root") - # return datatree with attached sweep child nodes - return _attach_sweep_groups(dtree, ds[1:]) + dtree: dict = { + "/": _get_required_root_dataset(ls_ds, optional=optional), + "/radar_parameters": _get_subgroup(ls_ds, radar_parameters_subgroup), + "/georeferencing_correction": _get_subgroup( + ls_ds, georeferencing_correction_subgroup + ), + "/radar_calibration": _get_radar_calibration(ls_ds), + } + dtree = _attach_sweep_groups(dtree, ls_ds) + return DataTree.from_dict(dtree) From 9a66b1816f9af8b24181cccc5b38a437252459fa Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 30 Oct 2024 08:32:48 -0500 Subject: [PATCH 14/51] fixing issue with group test since datamet now has root, georefernce, radar_parameter, and calib groups --- tests/io/test_io.py | 47 ++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/tests/io/test_io.py b/tests/io/test_io.py index 69d6628b..5e058902 100644 --- a/tests/io/test_io.py +++ b/tests/io/test_io.py @@ -878,36 +878,35 @@ def test_open_datamet_datatree(datamet_file): azimuths = [360] * 11 ranges = [493, 493, 493, 664, 832, 832, 1000, 1000, 1332, 1332, 1332] i = 0 - for grp in dtree.groups: - if grp.startswith("/sweep_"): - ds = dtree[grp].ds - assert dict(ds.sizes) == {"azimuth": azimuths[i], "range": ranges[i]} - assert set(ds.data_vars) & ( - sweep_dataset_vars | non_standard_sweep_dataset_vars - ) == set(moments) - assert set(ds.data_vars) & (required_sweep_metadata_vars) == set( - required_sweep_metadata_vars ^ {"azimuth", "elevation"} - ) - assert set(ds.coords) == { - "azimuth", - "elevation", - "time", - "latitude", - "longitude", - "altitude", - "range", - } - assert np.round(ds.elevation.mean().values.item(), 1) == elevations[i] - assert ds.sweep_number == i - i += 1 + for grp in dtree.match("sweep_*"): + ds = dtree[grp].ds + assert dict(ds.sizes) == {"azimuth": azimuths[i], "range": ranges[i]} + assert set(ds.data_vars) & ( + sweep_dataset_vars | non_standard_sweep_dataset_vars + ) == set(moments) + assert set(ds.data_vars) & (required_sweep_metadata_vars) == set( + required_sweep_metadata_vars ^ {"azimuth", "elevation"} + ) + assert set(ds.coords) == { + "azimuth", + "elevation", + "time", + "latitude", + "longitude", + "altitude", + "range", + } + assert np.round(ds.elevation.mean().values.item(), 1) == elevations[i] + assert ds.sweep_number == i + i += 1 # Try to reed single sweep dtree = open_datamet_datatree(datamet_file, sweep=1) - assert len(dtree.groups) == 2 + assert len(dtree.groups) == 5 # Try to read list of sweeps dtree = open_datamet_datatree(datamet_file, sweep=[1, 2]) - assert len(dtree.groups) == 3 + assert len(dtree.groups) == 6 @pytest.mark.parametrize("first_dim", ["time", "auto"]) From 15f0c17c968ed95119880daa799a1b0d6e751950 Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 30 Oct 2024 10:53:50 -0500 Subject: [PATCH 15/51] adding some calibration variables from Furuno datasets --- xradar/model.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/xradar/model.py b/xradar/model.py index c925afc8..405d3185 100644 --- a/xradar/model.py +++ b/xradar/model.py @@ -258,6 +258,8 @@ ("noise_vc", None), ("noise_hx", None), ("noise_vx", None), + ("noise_power_short_pulse_h", None), + ("noise_power_short_pulse_v", None), ("receiver_gain_hc", None), ("receiver_gain_vc", None), ("receiver_gain_hx", None), @@ -276,6 +278,8 @@ ("sun_power_vx", None), ("noise_source_power_h", None), ("noise_source_power_v", None), + ("noise_level_pulse_modulation_h", None), # Furuno + ("noise_level_frequency_modulation_h", None), # Furuno ("power_measure_loss_h", None), ("power_measure_loss_v", None), ("coupler_forward_loss_h", None), @@ -286,6 +290,12 @@ ("system_phidp", None), ("test_power_h", None), ("test_power_v", None), + ("tx_pulse_blind_length", None), # Furuno + ("tx_pulse_specification", None), # Furuno + ("tx_power_h", None), + ("tx_power_v", None), + ("threshold_power_short_pulse", None), + ("threshold_power_long_pulse", None), ("receiver_slope_hc", None), ("receiver_slope_vc", None), ("receiver_slope_hx", None), @@ -300,6 +310,8 @@ ("dynamic_range_db_hx", None), ("dynamic_range_db_vx", None), ("dbz_correction", None), + ("half_power_beam_wigth_h", None), + ("half_power_beam_wigth_v", None), ] ) From a185598f1e482ce566998212baebf59a3625b911 Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 30 Oct 2024 10:54:24 -0500 Subject: [PATCH 16/51] refactoring furuno backeds to use from_dict contructor --- xradar/io/backends/furuno.py | 107 +++++++++++++++++++++++++++++++---- 1 file changed, 95 insertions(+), 12 deletions(-) diff --git a/xradar/io/backends/furuno.py b/xradar/io/backends/furuno.py index 518afd42..a3efcf8f 100644 --- a/xradar/io/backends/furuno.py +++ b/xradar/io/backends/furuno.py @@ -47,6 +47,7 @@ import lat_lon_parser import numpy as np import xarray as xr +from xarray import Dataset, DataTree from xarray.backends.common import AbstractDataStore, BackendArray, BackendEntrypoint from xarray.backends.file_manager import CachingFileManager from xarray.backends.store import StoreBackendEntrypoint @@ -56,6 +57,7 @@ from ... import util from ...model import ( + georeferencing_correction_subgroup, get_altitude_attrs, get_azimuth_attrs, get_elevation_attrs, @@ -64,6 +66,10 @@ get_range_attrs, get_time_attrs, moment_attrs, + optional_root_vars, + radar_calibration_subgroup, + radar_parameters_subgroup, + required_root_vars, sweep_vars_mapping, ) from .common import ( @@ -686,8 +692,13 @@ def get_variables(self): ) def get_attrs(self): - # attributes = {"fixed_angle": float(self.ds.fixed_angle)} - return FrozenDict() + attributes = {"source": "Furuno", "version": self.root.header["format_version"]} + return FrozenDict(attributes) + + def get_calibration_parameters(self): + vars = [var for var in self.root.header if var in radar_calibration_subgroup] + calibration = {var: self.root.header[var] for var in vars} + return FrozenDict(calibration) class FurunoBackendEntrypoint(BackendEntrypoint): @@ -767,10 +778,78 @@ def open_dataset( "altitude": ds.altitude, } ) - + ds.attrs = store.get_calibration_parameters() return ds +def _get_required_root_dataset(ls_ds, optional=True): + """Extract Root Dataset.""" + # keep only defined mandatory and defined optional variables per default + # by checking in all nodes + data_var = {x for xs in [sweep.variables.keys() for sweep in ls_ds] for x in xs} + remove_root = set(data_var) ^ set(required_root_vars) + if optional: + remove_root ^= set(optional_root_vars) + remove_root ^= {"sweep_number", "fixed_angle"} + remove_root &= data_var + root = [sweep.drop_vars(remove_root) for sweep in ls_ds] + root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} + # rename variables + # todo: find a more easy method not iterating over all variables + for k in root_vars: + rename = optional_root_vars.get(k, None) + if rename: + root = [sweep.rename_vars({k: rename}) for sweep in root] + + root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} + ds_vars = [sweep[root_vars] for sweep in ls_ds] + vars = xr.concat(ds_vars, dim="sweep").reset_coords() + + # Creating the root group using _assign_root function + ls = ls_ds.copy() + ls.insert(0, xr.Dataset()) + root = _assign_root(ls) + + # merging both the created and the variables within each dataset + root = xr.merge([root, vars]) + + # Renaming variable + root = root.rename_vars({"sweep_number": "sweep_group_name"}) + root["sweep_group_name"].values = np.array( + [f"sweep_{i}" for i in root["sweep_group_name"].values] + ) + return root + + +def _get_subgroup(ls_ds: list[xr.Dataset], subdict): + """Get iris-sigmet root metadata group. + Variables are fetched from the provided Dataset according to the subdict dictionary. + """ + meta_vars = subdict + data_vars = {x for xs in [ds.variables.keys() for ds in ls_ds] for x in xs} + extract_vars = set(data_vars) & set(meta_vars) + subgroup = xr.concat([ds[extract_vars] for ds in ls_ds], "sweep") + for k in subgroup.data_vars: + rename = meta_vars[k] + if rename: + subgroup = subgroup.rename_vars({k: rename}) + subgroup.attrs = {} + return subgroup + + +def _get_radar_calibration(ls_ds: list[xr.Dataset], subdict: dict) -> xr.Dataset: + """Get radar calibration root metadata group.""" + + meta_vars = subdict + data_vars = {x for xs in [ds.attrs for ds in ls_ds] for x in xs} + extract_vars = set(data_vars) & set(meta_vars) + if extract_vars: + var_dict = {var: ls_ds[0].attrs[var] for var in extract_vars} + return xr.Dataset({key: xr.DataArray(value) for key, value in var_dict.items()}) + else: + return Dataset() + + def open_furuno_datatree(filename_or_obj, **kwargs): """Open FURUNO dataset as :py:class:`xarray.DataTree`. @@ -806,14 +885,18 @@ def open_furuno_datatree(filename_or_obj, **kwargs): """ # handle kwargs, extract first_dim backend_kwargs = kwargs.pop("backend_kwargs", {}) - # first_dim = backend_kwargs.pop("first_dim", None) + optional = backend_kwargs.pop("Optional", True) kwargs["backend_kwargs"] = backend_kwargs - ds = [xr.open_dataset(filename_or_obj, engine="furuno", **kwargs)] - - ds.insert(0, xr.Dataset()) - - # create datatree root node with required data - dtree = xr.DataTree(dataset=_assign_root(ds), name="root") - # return datatree with attached sweep child nodes - return _attach_sweep_groups(dtree, ds[1:]) + ls_ds = [xr.open_dataset(filename_or_obj, engine="furuno", **kwargs)] + + dtree: dict = { + "/": _get_required_root_dataset(ls_ds, optional=optional), + "/radar_parameters": _get_subgroup(ls_ds, radar_parameters_subgroup), + "/georeferencing_correction": _get_subgroup( + ls_ds, georeferencing_correction_subgroup + ), + "/radar_calibration": _get_radar_calibration(ls_ds, radar_calibration_subgroup), + } + dtree = _attach_sweep_groups(dtree, ls_ds) + return DataTree.from_dict(dtree) From d1275c229fcbab3a46ac6792507375d403227785 Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 30 Oct 2024 14:57:00 -0500 Subject: [PATCH 17/51] getting ride of unncessary attributes in the root dataset --- xradar/io/backends/furuno.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/xradar/io/backends/furuno.py b/xradar/io/backends/furuno.py index a3efcf8f..0554f49d 100644 --- a/xradar/io/backends/furuno.py +++ b/xradar/io/backends/furuno.py @@ -66,9 +66,11 @@ get_range_attrs, get_time_attrs, moment_attrs, + optional_root_attrs, optional_root_vars, radar_calibration_subgroup, radar_parameters_subgroup, + required_global_attrs, required_root_vars, sweep_vars_mapping, ) @@ -813,6 +815,12 @@ def _get_required_root_dataset(ls_ds, optional=True): # merging both the created and the variables within each dataset root = xr.merge([root, vars]) + attrs = root.attrs.keys() + remove_attrs = set(attrs) ^ set(required_global_attrs) + if optional: + remove_attrs ^= set(optional_root_attrs) + for k in remove_attrs: + root.attrs.pop(k, None) # Renaming variable root = root.rename_vars({"sweep_number": "sweep_group_name"}) root["sweep_group_name"].values = np.array( From 393f586b0dcae2b9a56a7ea824f1f633f5035795 Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 30 Oct 2024 15:30:30 -0500 Subject: [PATCH 18/51] updating global attrributes --- xradar/io/backends/furuno.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xradar/io/backends/furuno.py b/xradar/io/backends/furuno.py index 0554f49d..fd816a35 100644 --- a/xradar/io/backends/furuno.py +++ b/xradar/io/backends/furuno.py @@ -780,7 +780,7 @@ def open_dataset( "altitude": ds.altitude, } ) - ds.attrs = store.get_calibration_parameters() + ds.attrs.update(store.get_calibration_parameters()) return ds From 811f6aee6b69c603f9f1f028f06718195d08eb7e Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 30 Oct 2024 15:32:52 -0500 Subject: [PATCH 19/51] adding gamic radar calibration parameters --- xradar/model.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/xradar/model.py b/xradar/model.py index 405d3185..087864f4 100644 --- a/xradar/model.py +++ b/xradar/model.py @@ -241,6 +241,8 @@ ("pulse_width", None), ("antenna_gain_h", None), ("antenna_gain_v", None), + ("ant_gain_h", None), # gamic + ("ant_gain_v", None), # gamic ("xmit_power_h", None), ("xmit_power_v", None), ("two_way_waveguide_loss_h", None), @@ -260,6 +262,10 @@ ("noise_vx", None), ("noise_power_short_pulse_h", None), ("noise_power_short_pulse_v", None), + ("noise_power_h", None), # Gamic + ("noise_power_v", None), + ("rx_loss_h", None), + ("rx_loss_v", None), ("receiver_gain_hc", None), ("receiver_gain_vc", None), ("receiver_gain_hx", None), From 4d4db75fa1e45faa0de0043932adfbec4b4099f1 Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 30 Oct 2024 15:33:33 -0500 Subject: [PATCH 20/51] refactoring gamic backend to use from_dict datatree constructor --- xradar/io/backends/gamic.py | 128 +++++++++++++++++++++++++++++++++--- 1 file changed, 118 insertions(+), 10 deletions(-) diff --git a/xradar/io/backends/gamic.py b/xradar/io/backends/gamic.py index 642ca322..4a44460d 100644 --- a/xradar/io/backends/gamic.py +++ b/xradar/io/backends/gamic.py @@ -39,6 +39,7 @@ import h5netcdf import numpy as np import xarray as xr +from xarray import Dataset, DataTree from xarray.backends.common import ( AbstractDataStore, BackendEntrypoint, @@ -53,10 +54,17 @@ from ... import util from ...model import ( + georeferencing_correction_subgroup, get_azimuth_attrs, get_elevation_attrs, get_time_attrs, moment_attrs, + optional_root_attrs, + optional_root_vars, + radar_calibration_subgroup, + radar_parameters_subgroup, + required_global_attrs, + required_root_vars, sweep_vars_mapping, ) from .common import _assign_root, _attach_sweep_groups, _fix_angle, _get_h5group_names @@ -334,7 +342,29 @@ def get_variables(self): ) def get_attrs(self): - return FrozenDict() + _attributes = { + attrs: self.root.grp.attrs[attrs] + for attrs in (dict(self.root.grp.attrs)) + if attrs in required_global_attrs | optional_root_attrs + } + _attributes.update( + { + attrs: self.root.what.attrs[attrs] + for attrs in (dict(self.root.what)) + if attrs in required_global_attrs | optional_root_attrs + } + ) + _attributes["source"] = "gamic" + return FrozenDict(_attributes) + + def get_calibration_parameters(self): + calib_vars = [ + var + for var in dict(self.root.how).keys() + if var in radar_calibration_subgroup + ] + calibration = {var: self.root.how[var] for var in calib_vars} + return FrozenDict(calibration) class GamicBackendEntrypoint(BackendEntrypoint): @@ -445,10 +475,84 @@ def open_dataset( "altitude": ds.altitude, } ) - + ds.attrs.update(store.get_calibration_parameters()) return ds +def _get_required_root_dataset(ls_ds, optional=True): + """Extract Root Dataset.""" + # keep only defined mandatory and defined optional variables per default + # by checking in all nodes + data_var = {x for xs in [sweep.variables.keys() for sweep in ls_ds] for x in xs} + remove_root = set(data_var) ^ set(required_root_vars) + if optional: + remove_root ^= set(optional_root_vars) + remove_root ^= {"sweep_number", "fixed_angle"} + remove_root &= data_var + root = [sweep.drop_vars(remove_root) for sweep in ls_ds] + root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} + # rename variables + # todo: find a more easy method not iterating over all variables + for k in root_vars: + rename = optional_root_vars.get(k, None) + if rename: + root = [sweep.rename_vars({k: rename}) for sweep in root] + + root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} + ds_vars = [sweep[root_vars] for sweep in ls_ds] + vars = xr.concat(ds_vars, dim="sweep").reset_coords() + + # Creating the root group using _assign_root function + ls = ls_ds.copy() + ls.insert(0, xr.Dataset()) + root = _assign_root(ls) + + # merging both the created and the variables within each dataset + root = xr.merge([root, vars]) + attrs = root.attrs.keys() + remove_attrs = set(attrs) ^ set(required_global_attrs) + if optional: + remove_attrs ^= set(optional_root_attrs) + for k in remove_attrs: + root.attrs.pop(k, None) + # Renaming variable + root = root.rename_vars({"sweep_number": "sweep_group_name"}) + root["sweep_group_name"].values = np.array( + [f"sweep_{i}" for i in root["sweep_group_name"].values] + ) + # override/fix dtype in encoding + root["sweep_group_name"].encoding["dtype"] = root["sweep_group_name"].dtype + return root + + +def _get_subgroup(ls_ds: list[xr.Dataset], subdict): + """Get iris-sigmet root metadata group. + Variables are fetched from the provided Dataset according to the subdict dictionary. + """ + meta_vars = subdict + data_vars = {x for xs in [ds.variables.keys() for ds in ls_ds] for x in xs} + extract_vars = set(data_vars) & set(meta_vars) + subgroup = xr.concat([ds[extract_vars] for ds in ls_ds], "sweep") + for k in subgroup.data_vars: + rename = meta_vars[k] + if rename: + subgroup = subgroup.rename_vars({k: rename}) + subgroup.attrs = {} + return subgroup + + +def _get_radar_calibration(ls_ds: list[xr.Dataset], subdict: dict) -> xr.Dataset: + """Get radar calibration root metadata group.""" + meta_vars = subdict + data_vars = {x for xs in [ds.attrs for ds in ls_ds] for x in xs} + extract_vars = set(data_vars) & set(meta_vars) + if extract_vars: + var_dict = {var: ls_ds[0].attrs[var] for var in extract_vars} + return xr.Dataset({key: xr.DataArray(value) for key, value in var_dict.items()}) + else: + return Dataset() + + def open_gamic_datatree(filename_or_obj, **kwargs): """Open GAMIC HDF5 dataset as :py:class:`xarray.DataTree`. @@ -484,7 +588,7 @@ def open_gamic_datatree(filename_or_obj, **kwargs): """ # handle kwargs, extract first_dim backend_kwargs = kwargs.pop("backend_kwargs", {}) - # first_dim = backend_kwargs.pop("first_dim", None) + optional = backend_kwargs.pop("Optional", True) sweep = kwargs.pop("sweep", None) sweeps = [] kwargs["backend_kwargs"] = backend_kwargs @@ -501,14 +605,18 @@ def open_gamic_datatree(filename_or_obj, **kwargs): else: sweeps = _get_h5group_names(filename_or_obj, "gamic") - ds = [ + ls_ds: list[xr.Dataset] = [ xr.open_dataset(filename_or_obj, group=swp, engine="gamic", **kwargs) for swp in sweeps ] - ds.insert(0, xr.open_dataset(filename_or_obj, group="/")) - - # create datatree root node with required data - dtree = xr.DataTree(dataset=_assign_root(ds), name="root") - # return datatree with attached sweep child nodes - return _attach_sweep_groups(dtree, ds[1:]) + dtree: dict = { + "/": _get_required_root_dataset(ls_ds, optional=optional), + "/radar_parameters": _get_subgroup(ls_ds, radar_parameters_subgroup), + "/georeferencing_correction": _get_subgroup( + ls_ds, georeferencing_correction_subgroup + ), + "/radar_calibration": _get_radar_calibration(ls_ds, radar_calibration_subgroup), + } + dtree = _attach_sweep_groups(dtree, ls_ds) + return DataTree.from_dict(dtree) From c7b2ea63a5aca02e1d30685496bf56a49842faa8 Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 30 Oct 2024 15:56:28 -0500 Subject: [PATCH 21/51] refactoring code. moving _get_required_root_dataset, _get_subgroup, to common.py file --- xradar/io/backends/common.py | 68 +++++++++++++++++++++++++++++++++ xradar/io/backends/datamet.py | 59 +--------------------------- xradar/io/backends/furuno.py | 68 +-------------------------------- xradar/io/backends/gamic.py | 72 ++++------------------------------- 4 files changed, 78 insertions(+), 189 deletions(-) diff --git a/xradar/io/backends/common.py b/xradar/io/backends/common.py index cdca9fda..5c4ce4dd 100644 --- a/xradar/io/backends/common.py +++ b/xradar/io/backends/common.py @@ -20,6 +20,13 @@ import numpy as np import xarray as xr +from ...model import ( + optional_root_attrs, + optional_root_vars, + required_global_attrs, + required_root_vars, +) + def _maybe_decode(attr): try: @@ -219,6 +226,67 @@ def _unpack_dictionary(buffer, dictionary, rawdata=False): return data +def _get_required_root_dataset(ls_ds, optional=True): + """Extract Root Dataset.""" + # keep only defined mandatory and defined optional variables per default + # by checking in all nodes + data_var = {x for xs in [sweep.variables.keys() for sweep in ls_ds] for x in xs} + remove_root = set(data_var) ^ set(required_root_vars) + if optional: + remove_root ^= set(optional_root_vars) + remove_root ^= {"sweep_number", "fixed_angle"} + remove_root &= data_var + root = [sweep.drop_vars(remove_root) for sweep in ls_ds] + root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} + # rename variables + # todo: find a more easy method not iterating over all variables + for k in root_vars: + rename = optional_root_vars.get(k, None) + if rename: + root = [sweep.rename_vars({k: rename}) for sweep in root] + + root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} + ds_vars = [sweep[root_vars] for sweep in ls_ds] + vars = xr.concat(ds_vars, dim="sweep").reset_coords() + + # Creating the root group using _assign_root function + ls = ls_ds.copy() + ls.insert(0, xr.Dataset()) + root = _assign_root(ls) + + # merging both the created and the variables within each dataset + root = xr.merge([root, vars]) + + attrs = root.attrs.keys() + remove_attrs = set(attrs) ^ set(required_global_attrs) + if optional: + remove_attrs ^= set(optional_root_attrs) + for k in remove_attrs: + root.attrs.pop(k, None) + # Renaming variable + root = root.rename_vars({"sweep_number": "sweep_group_name"}) + root["sweep_group_name"].values = np.array( + [f"sweep_{i}" for i in root["sweep_group_name"].values] + ) + return root + + +def _get_subgroup(ls_ds: list[xr.Dataset], subdict): + """Get iris-sigmet root metadata group. + Variables are fetched from the provided Dataset according to the subdict dictionary. + """ + meta_vars = subdict + data_vars = {x for xs in [ds.variables.keys() for ds in ls_ds] for x in xs} + extract_vars = set(data_vars) & set(meta_vars) + subgroup = xr.concat([ds[extract_vars] for ds in ls_ds], "sweep") + for k in subgroup.data_vars: + rename = meta_vars[k] + if rename: + subgroup = subgroup.rename_vars({k: rename}) + subgroup.attrs = {} + return subgroup + + # IRIS Data Types and corresponding python struct format characters # 4.2 Scalar Definitions, Page 23 # https://docs.python.org/3/library/struct.html#format-characters diff --git a/xradar/io/backends/datamet.py b/xradar/io/backends/datamet.py index 1956b717..935cbe78 100644 --- a/xradar/io/backends/datamet.py +++ b/xradar/io/backends/datamet.py @@ -50,12 +50,10 @@ get_range_attrs, get_time_attrs, moment_attrs, - optional_root_vars, radar_parameters_subgroup, - required_root_vars, sweep_vars_mapping, ) -from .common import _assign_root, _attach_sweep_groups +from .common import _attach_sweep_groups, _get_required_root_dataset, _get_subgroup #: mapping from DataMet names to CfRadial2/ODIM datamet_mapping = { @@ -451,61 +449,6 @@ def open_dataset( return ds -def _get_required_root_dataset(ls_ds, optional=True): - """Extract Root Dataset.""" - # keep only defined mandatory and defined optional variables per default - # by checking in all nodes - data_var = {x for xs in [sweep.variables.keys() for sweep in ls_ds] for x in xs} - remove_root = set(data_var) ^ set(required_root_vars) - if optional: - remove_root ^= set(optional_root_vars) - remove_root ^= {"sweep_number", "fixed_angle"} - remove_root &= data_var - root = [sweep.drop_vars(remove_root) for sweep in ls_ds] - root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} - # rename variables - # todo: find a more easy method not iterating over all variables - for k in root_vars: - rename = optional_root_vars.get(k, None) - if rename: - root = [sweep.rename_vars({k: rename}) for sweep in root] - - root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} - ds_vars = [sweep[root_vars] for sweep in ls_ds] - vars = xr.concat(ds_vars, dim="sweep").reset_coords() - - # Creating the root group using _assign_root function - ls = ls_ds.copy() - ls.insert(0, xr.Dataset()) - root = _assign_root(ls) - - # merging both the created and the variables within each dataset - root = xr.merge([root, vars]) - - # Renaming variable - root = root.rename_vars({"sweep_number": "sweep_group_name"}) - root["sweep_group_name"].values = np.array( - [f"sweep_{i}" for i in root["sweep_group_name"].values] - ) - return root - - -def _get_subgroup(ls_ds: list[xr.Dataset], subdict): - """Get iris-sigmet root metadata group. - Variables are fetched from the provided Dataset according to the subdict dictionary. - """ - meta_vars = subdict - data_vars = {x for xs in [ds.variables.keys() for ds in ls_ds] for x in xs} - extract_vars = set(data_vars) & set(meta_vars) - subgroup = xr.concat([ds[extract_vars] for ds in ls_ds], "sweep") - for k in subgroup.data_vars: - rename = meta_vars[k] - if rename: - subgroup = subgroup.rename_vars({k: rename}) - subgroup.attrs = {} - return subgroup - - def _get_radar_calibration(ls_ds: list[xr.Dataset]) -> xr.Dataset: """Get radar calibration root metadata group.""" # radar_calibration is connected with calib-dimension diff --git a/xradar/io/backends/furuno.py b/xradar/io/backends/furuno.py index fd816a35..a932549e 100644 --- a/xradar/io/backends/furuno.py +++ b/xradar/io/backends/furuno.py @@ -66,12 +66,8 @@ get_range_attrs, get_time_attrs, moment_attrs, - optional_root_attrs, - optional_root_vars, radar_calibration_subgroup, radar_parameters_subgroup, - required_global_attrs, - required_root_vars, sweep_vars_mapping, ) from .common import ( @@ -80,10 +76,11 @@ UINT1, UINT2, UINT4, - _assign_root, _attach_sweep_groups, _calculate_angle_res, _get_fmt_string, + _get_required_root_dataset, + _get_subgroup, _unpack_dictionary, ) @@ -784,67 +781,6 @@ def open_dataset( return ds -def _get_required_root_dataset(ls_ds, optional=True): - """Extract Root Dataset.""" - # keep only defined mandatory and defined optional variables per default - # by checking in all nodes - data_var = {x for xs in [sweep.variables.keys() for sweep in ls_ds] for x in xs} - remove_root = set(data_var) ^ set(required_root_vars) - if optional: - remove_root ^= set(optional_root_vars) - remove_root ^= {"sweep_number", "fixed_angle"} - remove_root &= data_var - root = [sweep.drop_vars(remove_root) for sweep in ls_ds] - root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} - # rename variables - # todo: find a more easy method not iterating over all variables - for k in root_vars: - rename = optional_root_vars.get(k, None) - if rename: - root = [sweep.rename_vars({k: rename}) for sweep in root] - - root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} - ds_vars = [sweep[root_vars] for sweep in ls_ds] - vars = xr.concat(ds_vars, dim="sweep").reset_coords() - - # Creating the root group using _assign_root function - ls = ls_ds.copy() - ls.insert(0, xr.Dataset()) - root = _assign_root(ls) - - # merging both the created and the variables within each dataset - root = xr.merge([root, vars]) - - attrs = root.attrs.keys() - remove_attrs = set(attrs) ^ set(required_global_attrs) - if optional: - remove_attrs ^= set(optional_root_attrs) - for k in remove_attrs: - root.attrs.pop(k, None) - # Renaming variable - root = root.rename_vars({"sweep_number": "sweep_group_name"}) - root["sweep_group_name"].values = np.array( - [f"sweep_{i}" for i in root["sweep_group_name"].values] - ) - return root - - -def _get_subgroup(ls_ds: list[xr.Dataset], subdict): - """Get iris-sigmet root metadata group. - Variables are fetched from the provided Dataset according to the subdict dictionary. - """ - meta_vars = subdict - data_vars = {x for xs in [ds.variables.keys() for ds in ls_ds] for x in xs} - extract_vars = set(data_vars) & set(meta_vars) - subgroup = xr.concat([ds[extract_vars] for ds in ls_ds], "sweep") - for k in subgroup.data_vars: - rename = meta_vars[k] - if rename: - subgroup = subgroup.rename_vars({k: rename}) - subgroup.attrs = {} - return subgroup - - def _get_radar_calibration(ls_ds: list[xr.Dataset], subdict: dict) -> xr.Dataset: """Get radar calibration root metadata group.""" diff --git a/xradar/io/backends/gamic.py b/xradar/io/backends/gamic.py index 4a44460d..ffacaad6 100644 --- a/xradar/io/backends/gamic.py +++ b/xradar/io/backends/gamic.py @@ -60,14 +60,18 @@ get_time_attrs, moment_attrs, optional_root_attrs, - optional_root_vars, radar_calibration_subgroup, radar_parameters_subgroup, required_global_attrs, - required_root_vars, sweep_vars_mapping, ) -from .common import _assign_root, _attach_sweep_groups, _fix_angle, _get_h5group_names +from .common import ( + _attach_sweep_groups, + _fix_angle, + _get_h5group_names, + _get_required_root_dataset, + _get_subgroup, +) from .odim import H5NetCDFArrayWrapper, _get_h5netcdf_encoding, _H5NetCDFMetadata HDF5_LOCK = SerializableLock() @@ -479,68 +483,6 @@ def open_dataset( return ds -def _get_required_root_dataset(ls_ds, optional=True): - """Extract Root Dataset.""" - # keep only defined mandatory and defined optional variables per default - # by checking in all nodes - data_var = {x for xs in [sweep.variables.keys() for sweep in ls_ds] for x in xs} - remove_root = set(data_var) ^ set(required_root_vars) - if optional: - remove_root ^= set(optional_root_vars) - remove_root ^= {"sweep_number", "fixed_angle"} - remove_root &= data_var - root = [sweep.drop_vars(remove_root) for sweep in ls_ds] - root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} - # rename variables - # todo: find a more easy method not iterating over all variables - for k in root_vars: - rename = optional_root_vars.get(k, None) - if rename: - root = [sweep.rename_vars({k: rename}) for sweep in root] - - root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} - ds_vars = [sweep[root_vars] for sweep in ls_ds] - vars = xr.concat(ds_vars, dim="sweep").reset_coords() - - # Creating the root group using _assign_root function - ls = ls_ds.copy() - ls.insert(0, xr.Dataset()) - root = _assign_root(ls) - - # merging both the created and the variables within each dataset - root = xr.merge([root, vars]) - attrs = root.attrs.keys() - remove_attrs = set(attrs) ^ set(required_global_attrs) - if optional: - remove_attrs ^= set(optional_root_attrs) - for k in remove_attrs: - root.attrs.pop(k, None) - # Renaming variable - root = root.rename_vars({"sweep_number": "sweep_group_name"}) - root["sweep_group_name"].values = np.array( - [f"sweep_{i}" for i in root["sweep_group_name"].values] - ) - # override/fix dtype in encoding - root["sweep_group_name"].encoding["dtype"] = root["sweep_group_name"].dtype - return root - - -def _get_subgroup(ls_ds: list[xr.Dataset], subdict): - """Get iris-sigmet root metadata group. - Variables are fetched from the provided Dataset according to the subdict dictionary. - """ - meta_vars = subdict - data_vars = {x for xs in [ds.variables.keys() for ds in ls_ds] for x in xs} - extract_vars = set(data_vars) & set(meta_vars) - subgroup = xr.concat([ds[extract_vars] for ds in ls_ds], "sweep") - for k in subgroup.data_vars: - rename = meta_vars[k] - if rename: - subgroup = subgroup.rename_vars({k: rename}) - subgroup.attrs = {} - return subgroup - - def _get_radar_calibration(ls_ds: list[xr.Dataset], subdict: dict) -> xr.Dataset: """Get radar calibration root metadata group.""" meta_vars = subdict From 3d9065ce76086dd5b81e3d0a175e41e15f1302b2 Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 30 Oct 2024 15:57:12 -0500 Subject: [PATCH 22/51] using dtree.match to iterate over sweep nodes --- tests/io/test_io.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/io/test_io.py b/tests/io/test_io.py index 5e058902..de994aa2 100644 --- a/tests/io/test_io.py +++ b/tests/io/test_io.py @@ -258,7 +258,7 @@ def test_open_gamic_datatree_sweep(gamic_file, sweep): lswp = len([sweep]) else: lswp = len(sweep) - assert len(dtree.groups[1:]) == lswp + assert len(dtree.match("sweep_*")) == lswp def test_open_gamic_datatree(gamic_file): @@ -319,7 +319,7 @@ def test_open_gamic_datatree(gamic_file): 1000, 1000, ] - for i, grp in enumerate(dtree.groups[1:]): + for i, grp in enumerate(dtree.match("sweep_*")): ds = dtree[grp].ds assert dict(ds.sizes) == {"azimuth": azimuths[i], "range": ranges[i]} assert set(ds.data_vars) & ( From 978d15e8c505eadce71b1cdd0754580024604791 Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 30 Oct 2024 16:03:19 -0500 Subject: [PATCH 23/51] refactoring _get_subgroups and _get_required_root funcition --- xradar/io/backends/iris.py | 61 +------------------------------------- 1 file changed, 1 insertion(+), 60 deletions(-) diff --git a/xradar/io/backends/iris.py b/xradar/io/backends/iris.py index ea42bead..996063bc 100644 --- a/xradar/io/backends/iris.py +++ b/xradar/io/backends/iris.py @@ -61,12 +61,10 @@ get_longitude_attrs, get_range_attrs, moment_attrs, - optional_root_vars, radar_parameters_subgroup, - required_root_vars, sweep_vars_mapping, ) -from .common import _assign_root, _attach_sweep_groups +from .common import _attach_sweep_groups, _get_required_root_dataset, _get_subgroup #: mapping from IRIS names to CfRadial2/ODIM iris_mapping = { @@ -3969,63 +3967,6 @@ def _get_iris_group_names(filename): return keys -def _get_required_root_dataset(ls_ds, optional=True): - """Extract Root Dataset.""" - # keep only defined mandatory and defined optional variables per default - # by checking in all nodes - data_var = {x for xs in [sweep.variables.keys() for sweep in ls_ds] for x in xs} - remove_root = set(data_var) ^ set(required_root_vars) - if optional: - remove_root ^= set(optional_root_vars) - remove_root ^= {"sweep_number", "fixed_angle"} - remove_root &= data_var - root = [sweep.drop_vars(remove_root) for sweep in ls_ds] - root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} - # rename variables - # todo: find a more easy method not iterating over all variables - for k in root_vars: - rename = optional_root_vars.get(k, None) - if rename: - root = [sweep.rename_vars({k: rename}) for sweep in root] - - root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} - ds_vars = [sweep[root_vars] for sweep in ls_ds] - vars = xr.concat(ds_vars, dim="sweep").reset_coords() - - # Creating the root group using _assign_root function - ls = ls_ds.copy() - ls.insert(0, xr.Dataset()) - root = _assign_root(ls) - - # merging both the created and the variables within each dataset - root = xr.merge([root, vars]) - - # Renaming variable - root = root.rename_vars({"sweep_number": "sweep_group_name"}) - root["sweep_group_name"].values = np.array( - [f"sweep_{i}" for i in root["sweep_group_name"].values] - ) - # override/fix dtype in encoding - root["sweep_group_name"].encoding["dtype"] = root["sweep_group_name"].dtype - return root - - -def _get_subgroup(ls_ds: list[xr.Dataset], subdict): - """Get iris-sigmet root metadata group. - Variables are fetched from the provided Dataset according to the subdict dictionary. - """ - meta_vars = subdict - data_vars = {x for xs in [ds.variables.keys() for ds in ls_ds] for x in xs} - extract_vars = set(data_vars) & set(meta_vars) - subgroup = xr.concat([ds[extract_vars] for ds in ls_ds], "sweep") - for k in subgroup.data_vars: - rename = meta_vars[k] - if rename: - subgroup = subgroup.rename_vars({k: rename}) - subgroup.attrs = {} - return subgroup - - def _get_radar_calibration(ls_ds: list[xr.Dataset]) -> xr.Dataset: """Get radar calibration root metadata group.""" # radar_calibration is connected with calib-dimension From d6fed5dd33696bb8e19803ac6dd37add55176483 Mon Sep 17 00:00:00 2001 From: aladinor Date: Wed, 30 Oct 2024 16:12:40 -0500 Subject: [PATCH 24/51] fixing typo --- xradar/io/backends/furuno.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xradar/io/backends/furuno.py b/xradar/io/backends/furuno.py index a932549e..9f73c28f 100644 --- a/xradar/io/backends/furuno.py +++ b/xradar/io/backends/furuno.py @@ -829,7 +829,7 @@ def open_furuno_datatree(filename_or_obj, **kwargs): """ # handle kwargs, extract first_dim backend_kwargs = kwargs.pop("backend_kwargs", {}) - optional = backend_kwargs.pop("Optional", True) + optional = backend_kwargs.pop("optional", True) kwargs["backend_kwargs"] = backend_kwargs ls_ds = [xr.open_dataset(filename_or_obj, engine="furuno", **kwargs)] From 63fb2019cafd228562c53c653e455db07d97e32a Mon Sep 17 00:00:00 2001 From: aladinor Date: Thu, 31 Oct 2024 09:41:41 -0500 Subject: [PATCH 25/51] adding _get_radar_calibration fuction --- xradar/io/backends/common.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/xradar/io/backends/common.py b/xradar/io/backends/common.py index 5c4ce4dd..8ed3609d 100644 --- a/xradar/io/backends/common.py +++ b/xradar/io/backends/common.py @@ -245,9 +245,8 @@ def _get_required_root_dataset(ls_ds, optional=True): if rename: root = [sweep.rename_vars({k: rename}) for sweep in root] - root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} ds_vars = [sweep[root_vars] for sweep in ls_ds] - vars = xr.concat(ds_vars, dim="sweep").reset_coords() + _vars = xr.concat(ds_vars, dim="sweep").reset_coords() # Creating the root group using _assign_root function ls = ls_ds.copy() @@ -255,7 +254,7 @@ def _get_required_root_dataset(ls_ds, optional=True): root = _assign_root(ls) # merging both the created and the variables within each dataset - root = xr.merge([root, vars]) + root = xr.merge([root, _vars]) attrs = root.attrs.keys() remove_attrs = set(attrs) ^ set(required_global_attrs) @@ -264,9 +263,11 @@ def _get_required_root_dataset(ls_ds, optional=True): for k in remove_attrs: root.attrs.pop(k, None) # Renaming variable - root = root.rename_vars({"sweep_number": "sweep_group_name"}) + if "sweep_group_name" not in data_var: + root = root.rename_vars({"sweep_number": "sweep_group_name"}) + root["sweep_group_name"].values = np.array( - [f"sweep_{i}" for i in root["sweep_group_name"].values] + [f"sweep_{i}" for i in range(len(root["sweep_group_name"].values))] ) return root @@ -287,6 +288,18 @@ def _get_subgroup(ls_ds: list[xr.Dataset], subdict): return subgroup +def _get_radar_calibration(ls_ds: list[xr.Dataset], subdict: dict) -> xr.Dataset: + """Get radar calibration root metadata group.""" + meta_vars = subdict + data_vars = {x for xs in [ds.attrs for ds in ls_ds] for x in xs} + extract_vars = set(data_vars) & set(meta_vars) + if extract_vars: + var_dict = {var: ls_ds[0].attrs[var] for var in extract_vars} + return xr.Dataset({key: xr.DataArray(value) for key, value in var_dict.items()}) + else: + return xr.Dataset() + + # IRIS Data Types and corresponding python struct format characters # 4.2 Scalar Definitions, Page 23 # https://docs.python.org/3/library/struct.html#format-characters From ca0b8cc19df84d7ffe515a3358b9ef9f720b0137 Mon Sep 17 00:00:00 2001 From: aladinor Date: Thu, 31 Oct 2024 09:43:04 -0500 Subject: [PATCH 26/51] refactoring and moving _get_requiered_root_Dataset, _get_subgroup, and _get_calibration function to commo.py file --- xradar/io/backends/iris.py | 61 +------------------------------------- 1 file changed, 1 insertion(+), 60 deletions(-) diff --git a/xradar/io/backends/iris.py b/xradar/io/backends/iris.py index ea42bead..996063bc 100644 --- a/xradar/io/backends/iris.py +++ b/xradar/io/backends/iris.py @@ -61,12 +61,10 @@ get_longitude_attrs, get_range_attrs, moment_attrs, - optional_root_vars, radar_parameters_subgroup, - required_root_vars, sweep_vars_mapping, ) -from .common import _assign_root, _attach_sweep_groups +from .common import _attach_sweep_groups, _get_required_root_dataset, _get_subgroup #: mapping from IRIS names to CfRadial2/ODIM iris_mapping = { @@ -3969,63 +3967,6 @@ def _get_iris_group_names(filename): return keys -def _get_required_root_dataset(ls_ds, optional=True): - """Extract Root Dataset.""" - # keep only defined mandatory and defined optional variables per default - # by checking in all nodes - data_var = {x for xs in [sweep.variables.keys() for sweep in ls_ds] for x in xs} - remove_root = set(data_var) ^ set(required_root_vars) - if optional: - remove_root ^= set(optional_root_vars) - remove_root ^= {"sweep_number", "fixed_angle"} - remove_root &= data_var - root = [sweep.drop_vars(remove_root) for sweep in ls_ds] - root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} - # rename variables - # todo: find a more easy method not iterating over all variables - for k in root_vars: - rename = optional_root_vars.get(k, None) - if rename: - root = [sweep.rename_vars({k: rename}) for sweep in root] - - root_vars = {x for xs in [sweep.variables.keys() for sweep in root] for x in xs} - ds_vars = [sweep[root_vars] for sweep in ls_ds] - vars = xr.concat(ds_vars, dim="sweep").reset_coords() - - # Creating the root group using _assign_root function - ls = ls_ds.copy() - ls.insert(0, xr.Dataset()) - root = _assign_root(ls) - - # merging both the created and the variables within each dataset - root = xr.merge([root, vars]) - - # Renaming variable - root = root.rename_vars({"sweep_number": "sweep_group_name"}) - root["sweep_group_name"].values = np.array( - [f"sweep_{i}" for i in root["sweep_group_name"].values] - ) - # override/fix dtype in encoding - root["sweep_group_name"].encoding["dtype"] = root["sweep_group_name"].dtype - return root - - -def _get_subgroup(ls_ds: list[xr.Dataset], subdict): - """Get iris-sigmet root metadata group. - Variables are fetched from the provided Dataset according to the subdict dictionary. - """ - meta_vars = subdict - data_vars = {x for xs in [ds.variables.keys() for ds in ls_ds] for x in xs} - extract_vars = set(data_vars) & set(meta_vars) - subgroup = xr.concat([ds[extract_vars] for ds in ls_ds], "sweep") - for k in subgroup.data_vars: - rename = meta_vars[k] - if rename: - subgroup = subgroup.rename_vars({k: rename}) - subgroup.attrs = {} - return subgroup - - def _get_radar_calibration(ls_ds: list[xr.Dataset]) -> xr.Dataset: """Get radar calibration root metadata group.""" # radar_calibration is connected with calib-dimension From 2fffb9bde7a6f5335c0de8c72f2ecf4b3041e3c5 Mon Sep 17 00:00:00 2001 From: aladinor Date: Thu, 31 Oct 2024 09:43:40 -0500 Subject: [PATCH 27/51] refactoring and moving _get_calibration function to commo.py file --- xradar/io/backends/furuno.py | 18 +++--------------- xradar/io/backends/gamic.py | 15 ++------------- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/xradar/io/backends/furuno.py b/xradar/io/backends/furuno.py index a932549e..ab47687f 100644 --- a/xradar/io/backends/furuno.py +++ b/xradar/io/backends/furuno.py @@ -47,7 +47,7 @@ import lat_lon_parser import numpy as np import xarray as xr -from xarray import Dataset, DataTree +from xarray import DataTree from xarray.backends.common import AbstractDataStore, BackendArray, BackendEntrypoint from xarray.backends.file_manager import CachingFileManager from xarray.backends.store import StoreBackendEntrypoint @@ -79,6 +79,7 @@ _attach_sweep_groups, _calculate_angle_res, _get_fmt_string, + _get_radar_calibration, _get_required_root_dataset, _get_subgroup, _unpack_dictionary, @@ -781,19 +782,6 @@ def open_dataset( return ds -def _get_radar_calibration(ls_ds: list[xr.Dataset], subdict: dict) -> xr.Dataset: - """Get radar calibration root metadata group.""" - - meta_vars = subdict - data_vars = {x for xs in [ds.attrs for ds in ls_ds] for x in xs} - extract_vars = set(data_vars) & set(meta_vars) - if extract_vars: - var_dict = {var: ls_ds[0].attrs[var] for var in extract_vars} - return xr.Dataset({key: xr.DataArray(value) for key, value in var_dict.items()}) - else: - return Dataset() - - def open_furuno_datatree(filename_or_obj, **kwargs): """Open FURUNO dataset as :py:class:`xarray.DataTree`. @@ -829,7 +817,7 @@ def open_furuno_datatree(filename_or_obj, **kwargs): """ # handle kwargs, extract first_dim backend_kwargs = kwargs.pop("backend_kwargs", {}) - optional = backend_kwargs.pop("Optional", True) + optional = backend_kwargs.pop("optional", True) kwargs["backend_kwargs"] = backend_kwargs ls_ds = [xr.open_dataset(filename_or_obj, engine="furuno", **kwargs)] diff --git a/xradar/io/backends/gamic.py b/xradar/io/backends/gamic.py index ffacaad6..f48a06bc 100644 --- a/xradar/io/backends/gamic.py +++ b/xradar/io/backends/gamic.py @@ -39,7 +39,7 @@ import h5netcdf import numpy as np import xarray as xr -from xarray import Dataset, DataTree +from xarray import DataTree from xarray.backends.common import ( AbstractDataStore, BackendEntrypoint, @@ -69,6 +69,7 @@ _attach_sweep_groups, _fix_angle, _get_h5group_names, + _get_radar_calibration, _get_required_root_dataset, _get_subgroup, ) @@ -483,18 +484,6 @@ def open_dataset( return ds -def _get_radar_calibration(ls_ds: list[xr.Dataset], subdict: dict) -> xr.Dataset: - """Get radar calibration root metadata group.""" - meta_vars = subdict - data_vars = {x for xs in [ds.attrs for ds in ls_ds] for x in xs} - extract_vars = set(data_vars) & set(meta_vars) - if extract_vars: - var_dict = {var: ls_ds[0].attrs[var] for var in extract_vars} - return xr.Dataset({key: xr.DataArray(value) for key, value in var_dict.items()}) - else: - return Dataset() - - def open_gamic_datatree(filename_or_obj, **kwargs): """Open GAMIC HDF5 dataset as :py:class:`xarray.DataTree`. From dcdb5424c727d806150de4a0765239a85b2148ff Mon Sep 17 00:00:00 2001 From: aladinor Date: Thu, 31 Oct 2024 09:44:23 -0500 Subject: [PATCH 28/51] refactoring hpl backends to use from_dict contructor and adding sweeps --- xradar/io/backends/hpl.py | 43 +++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/xradar/io/backends/hpl.py b/xradar/io/backends/hpl.py index 79849d64..4197a00c 100644 --- a/xradar/io/backends/hpl.py +++ b/xradar/io/backends/hpl.py @@ -37,6 +37,7 @@ import numpy as np import pandas as pd import xarray as xr +from xarray import DataTree from xarray.backends.common import AbstractDataStore, BackendArray, BackendEntrypoint from xarray.backends.file_manager import CachingFileManager from xarray.backends.store import StoreBackendEntrypoint @@ -44,14 +45,22 @@ from xarray.core.utils import FrozenDict from ...model import ( + georeferencing_correction_subgroup, get_altitude_attrs, get_azimuth_attrs, get_elevation_attrs, get_latitude_attrs, get_longitude_attrs, get_time_attrs, + radar_calibration_subgroup, + radar_parameters_subgroup, +) +from .common import ( + _attach_sweep_groups, + _get_radar_calibration, + _get_required_root_dataset, + _get_subgroup, ) -from .common import _assign_root, _attach_sweep_groups variable_attr_dict = {} variable_attr_dict["intensity"] = { @@ -571,6 +580,11 @@ def open_dataset( return ds +def _get_h5group_names(filename_or_obj): + store = HplStore.open(filename_or_obj) + return [f"sweep_{i}" for i in store.root.data["sweep_number"]] + + def open_hpl_datatree(filename_or_obj, **kwargs): """Open Halo Photonics processed Doppler lidar dataset as :py:class:`xarray.DataTree`. @@ -606,7 +620,7 @@ def open_hpl_datatree(filename_or_obj, **kwargs): """ # handle kwargs, extract first_dim backend_kwargs = kwargs.pop("backend_kwargs", {}) - # first_dim = backend_kwargs.pop("first_dim", None) + optional = backend_kwargs.pop("optional", None) sweep = kwargs.pop("sweep", None) sweeps = [] kwargs["backend_kwargs"] = backend_kwargs @@ -621,19 +635,22 @@ def open_hpl_datatree(filename_or_obj, **kwargs): else: sweeps.extend(sweep) else: - sweeps = ["sweep_0"] + sweeps = _get_h5group_names(filename_or_obj) - ds = [ + ls_ds: list[xr.Dataset] = [ xr.open_dataset(filename_or_obj, group=swp, engine="hpl", **kwargs) for swp in sweeps ] - ds.insert(0, xr.Dataset()) # open_dataset(filename_or_obj, group="/")) - - # create datatree root node with required data - root = _assign_root(ds) - root["fixed_angle"] = ("sweep", [x["sweep_fixed_angle"].values for x in ds[1:]]) - root["sweep_group_name"] = ("sweep", [x["sweep_group_name"].values for x in ds[1:]]) - dtree = xr.DataTree(dataset=root, name="root") - # return datatree with attached sweep child nodes - return _attach_sweep_groups(dtree, ds[1:]) + dtree: dict = { + "/": _get_required_root_dataset(ls_ds, optional=optional).rename( + {"sweep_fixed_angle": "fixed_angle"} + ), + "/radar_parameters": _get_subgroup(ls_ds, radar_parameters_subgroup), + "/georeferencing_correction": _get_subgroup( + ls_ds, georeferencing_correction_subgroup + ), + "/radar_calibration": _get_radar_calibration(ls_ds, radar_calibration_subgroup), + } + dtree = _attach_sweep_groups(dtree, ls_ds) + return DataTree.from_dict(dtree) From c20157686b97c7bccd2c046160fe19a3c875ef1a Mon Sep 17 00:00:00 2001 From: aladinor Date: Thu, 31 Oct 2024 09:48:25 -0500 Subject: [PATCH 29/51] using radar_calibration group from common.py in Datamet backend --- xradar/io/backends/datamet.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/xradar/io/backends/datamet.py b/xradar/io/backends/datamet.py index 935cbe78..a64ffadd 100644 --- a/xradar/io/backends/datamet.py +++ b/xradar/io/backends/datamet.py @@ -31,7 +31,7 @@ import numpy as np import xarray as xr -from xarray import Dataset, DataTree +from xarray import DataTree from xarray.backends.common import AbstractDataStore, BackendArray, BackendEntrypoint from xarray.backends.file_manager import CachingFileManager from xarray.backends.store import StoreBackendEntrypoint @@ -50,10 +50,16 @@ get_range_attrs, get_time_attrs, moment_attrs, + radar_calibration_subgroup, radar_parameters_subgroup, sweep_vars_mapping, ) -from .common import _attach_sweep_groups, _get_required_root_dataset, _get_subgroup +from .common import ( + _attach_sweep_groups, + _get_radar_calibration, + _get_required_root_dataset, + _get_subgroup, +) #: mapping from DataMet names to CfRadial2/ODIM datamet_mapping = { @@ -449,12 +455,6 @@ def open_dataset( return ds -def _get_radar_calibration(ls_ds: list[xr.Dataset]) -> xr.Dataset: - """Get radar calibration root metadata group.""" - # radar_calibration is connected with calib-dimension - return Dataset() - - def open_datamet_datatree(filename_or_obj, **kwargs): """Open DataMet dataset as :py:class:`xarray.DataTree`. @@ -526,7 +526,7 @@ def open_datamet_datatree(filename_or_obj, **kwargs): "/georeferencing_correction": _get_subgroup( ls_ds, georeferencing_correction_subgroup ), - "/radar_calibration": _get_radar_calibration(ls_ds), + "/radar_calibration": _get_radar_calibration(ls_ds, radar_calibration_subgroup), } dtree = _attach_sweep_groups(dtree, ls_ds) return DataTree.from_dict(dtree) From f731eb8ef33863d8bae461205fa6d7c56782cd95 Mon Sep 17 00:00:00 2001 From: aladinor Date: Thu, 31 Oct 2024 10:48:08 -0500 Subject: [PATCH 30/51] using radar calibration subgrup fucntion from common.py in iris backend --- xradar/io/backends/iris.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/xradar/io/backends/iris.py b/xradar/io/backends/iris.py index 996063bc..731f8283 100644 --- a/xradar/io/backends/iris.py +++ b/xradar/io/backends/iris.py @@ -43,7 +43,7 @@ import numpy as np import xarray as xr -from xarray import Dataset, DataTree +from xarray import DataTree from xarray.backends.common import AbstractDataStore, BackendArray, BackendEntrypoint from xarray.backends.file_manager import CachingFileManager from xarray.backends.store import StoreBackendEntrypoint @@ -61,10 +61,16 @@ get_longitude_attrs, get_range_attrs, moment_attrs, + radar_calibration_subgroup, radar_parameters_subgroup, sweep_vars_mapping, ) -from .common import _attach_sweep_groups, _get_required_root_dataset, _get_subgroup +from .common import ( + _attach_sweep_groups, + _get_radar_calibration, + _get_required_root_dataset, + _get_subgroup, +) #: mapping from IRIS names to CfRadial2/ODIM iris_mapping = { @@ -3967,12 +3973,6 @@ def _get_iris_group_names(filename): return keys -def _get_radar_calibration(ls_ds: list[xr.Dataset]) -> xr.Dataset: - """Get radar calibration root metadata group.""" - # radar_calibration is connected with calib-dimension - return Dataset() - - class IrisBackendEntrypoint(BackendEntrypoint): """Xarray BackendEntrypoint for IRIS/Sigmet data.""" @@ -4120,7 +4120,7 @@ def open_iris_datatree(filename_or_obj, **kwargs): "/georeferencing_correction": _get_subgroup( ls_ds, georeferencing_correction_subgroup ), - "/radar_calibration": _get_radar_calibration(ls_ds), + "/radar_calibration": _get_radar_calibration(ls_ds, radar_calibration_subgroup), } dtree = _attach_sweep_groups(dtree, ls_ds) return DataTree.from_dict(dtree) From 0b0eab07c8119c0593817a685443346eb7fe3cae Mon Sep 17 00:00:00 2001 From: aladinor Date: Thu, 31 Oct 2024 10:52:45 -0500 Subject: [PATCH 31/51] allowing metek to read lines from gzip file when using context manager --- xradar/io/backends/metek.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xradar/io/backends/metek.py b/xradar/io/backends/metek.py index c819c2a3..01dfcd3e 100644 --- a/xradar/io/backends/metek.py +++ b/xradar/io/backends/metek.py @@ -232,6 +232,8 @@ def open(self, filename_or_obj): temp_number = np.zeros((self.n_gates, 64)) spec_var = "" for file_line in self._fp: + if isinstance(file_line, bytes): + file_line = file_line.decode("utf-8") if file_line[:3] == "MRR": if num_times > 0: self._data[spec_var].append(temp_spectra) From f21c7d5f79ecbbb7935343af29da69b949419492 Mon Sep 17 00:00:00 2001 From: aladinor Date: Thu, 31 Oct 2024 12:44:26 -0500 Subject: [PATCH 32/51] refactoring _get_required_root function to fit with metek backend --- xradar/io/backends/common.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/xradar/io/backends/common.py b/xradar/io/backends/common.py index 8ed3609d..25e15f16 100644 --- a/xradar/io/backends/common.py +++ b/xradar/io/backends/common.py @@ -254,7 +254,7 @@ def _get_required_root_dataset(ls_ds, optional=True): root = _assign_root(ls) # merging both the created and the variables within each dataset - root = xr.merge([root, _vars]) + root = xr.merge([root, _vars], compat="override") attrs = root.attrs.keys() remove_attrs = set(attrs) ^ set(required_global_attrs) @@ -263,12 +263,12 @@ def _get_required_root_dataset(ls_ds, optional=True): for k in remove_attrs: root.attrs.pop(k, None) # Renaming variable - if "sweep_group_name" not in data_var: + if "sweep_number" in data_var and "sweep_group_name" not in data_var: root = root.rename_vars({"sweep_number": "sweep_group_name"}) - - root["sweep_group_name"].values = np.array( - [f"sweep_{i}" for i in range(len(root["sweep_group_name"].values))] - ) + elif "sweep_group_name" in data_var: + root["sweep_group_name"].values = np.array( + [f"sweep_{i}" for i in range(len(root["sweep_group_name"].values))] + ) return root @@ -279,7 +279,7 @@ def _get_subgroup(ls_ds: list[xr.Dataset], subdict): meta_vars = subdict data_vars = {x for xs in [ds.variables.keys() for ds in ls_ds] for x in xs} extract_vars = set(data_vars) & set(meta_vars) - subgroup = xr.concat([ds[extract_vars] for ds in ls_ds], "sweep") + subgroup = xr.merge([ds[extract_vars] for ds in ls_ds]) for k in subgroup.data_vars: rename = meta_vars[k] if rename: From 723386c577bf22a8a767d002492846bbf8dd9df8 Mon Sep 17 00:00:00 2001 From: aladinor Date: Thu, 31 Oct 2024 12:46:06 -0500 Subject: [PATCH 33/51] refactoring metek backend to use from_dict constructor --- xradar/io/backends/metek.py | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/xradar/io/backends/metek.py b/xradar/io/backends/metek.py index 01dfcd3e..0451c59f 100644 --- a/xradar/io/backends/metek.py +++ b/xradar/io/backends/metek.py @@ -21,6 +21,7 @@ import numpy as np import xarray as xr +from xarray import DataTree from xarray.backends.common import AbstractDataStore, BackendArray, BackendEntrypoint from xarray.backends.file_manager import CachingFileManager from xarray.backends.store import StoreBackendEntrypoint @@ -28,12 +29,21 @@ from xarray.core.utils import FrozenDict from ...model import ( + georeferencing_correction_subgroup, get_altitude_attrs, get_azimuth_attrs, get_elevation_attrs, get_latitude_attrs, get_longitude_attrs, get_time_attrs, + radar_calibration_subgroup, + radar_parameters_subgroup, +) +from .common import ( + _attach_sweep_groups, + _get_radar_calibration, + _get_required_root_dataset, + _get_subgroup, ) __all__ = [ @@ -649,7 +659,7 @@ def open_metek_datatree(filename_or_obj, **kwargs): """ # handle kwargs, extract first_dim backend_kwargs = kwargs.pop("backend_kwargs", {}) - # first_dim = backend_kwargs.pop("first_dim", None) + optional = backend_kwargs.pop("optional", True) sweep = kwargs.pop("sweep", None) sweeps = [] kwargs["backend_kwargs"] = backend_kwargs @@ -666,14 +676,17 @@ def open_metek_datatree(filename_or_obj, **kwargs): else: sweeps = ["sweep_0"] - dtree = {"/": xr.Dataset()} - dtree.update( - { - swp: xr.open_dataset( - filename_or_obj, group=swp, engine="metek", **kwargs - ).copy() - for swp in sweeps - } - ) - - return xr.DataTree.from_dict(dtree) + ls_ds: list[xr.Dataset] = [ + xr.open_dataset(filename_or_obj, group=swp, engine="metek", **kwargs) + for swp in sweeps + ].copy() + dtree: dict = { + "/": _get_required_root_dataset(ls_ds, optional=optional), + "/radar_parameters": _get_subgroup(ls_ds, radar_parameters_subgroup), + "/georeferencing_correction": _get_subgroup( + ls_ds, georeferencing_correction_subgroup + ), + "/radar_calibration": _get_radar_calibration(ls_ds, radar_calibration_subgroup), + } + dtree = _attach_sweep_groups(dtree, ls_ds) + return DataTree.from_dict(dtree) From 119a7cd9e22d1f8894810ec25e5cb8e83b6df2ae Mon Sep 17 00:00:00 2001 From: aladinor Date: Thu, 31 Oct 2024 14:02:20 -0500 Subject: [PATCH 34/51] fixing nexrad test that support the radar_calibration, georeference, and parameter group --- tests/io/test_io.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/io/test_io.py b/tests/io/test_io.py index de994aa2..db506bbc 100644 --- a/tests/io/test_io.py +++ b/tests/io/test_io.py @@ -991,6 +991,7 @@ def test_cfradial_n_points_file(cfradial1n_file): assert ds.sweep_mode == "azimuth_surveillance" +@pytest.mark.run(order=1) @pytest.mark.parametrize("sweep", ["sweep_0", 0, [0, 1], ["sweep_0", "sweep_1"]]) @pytest.mark.parametrize( "nexradlevel2_files", ["nexradlevel2_gzfile", "nexradlevel2_bzfile"], indirect=True @@ -1001,7 +1002,7 @@ def test_open_nexradlevel2_datatree_sweep(nexradlevel2_files, sweep): lswp = len([sweep]) else: lswp = len(sweep) - assert len(dtree.groups[1:]) == lswp + assert len(dtree.match("sweep*")) == lswp @pytest.mark.parametrize( @@ -1078,8 +1079,8 @@ def test_open_nexradlevel2_datatree(nexradlevel2_files): 308, 232, ] - assert len(dtree.groups[1:]) == 11 - for i, grp in enumerate(dtree.groups[1:]): + assert len(dtree.groups[1:]) == 14 + for i, grp in enumerate(dtree.match("sweep_*")): print(i) ds = dtree[grp].ds assert dict(ds.sizes) == {"azimuth": azimuths[i], "range": ranges[i]} @@ -1099,4 +1100,4 @@ def test_open_nexradlevel2_datatree(nexradlevel2_files): "range", } assert np.round(ds.elevation.mean().values.item(), 1) == elevations[i] - assert ds.sweep_number.values == int(grp[7:]) + assert ds.sweep_number.values == int(grp[6:]) From fab8a3fc0bc98b79f7312b4143f230fe85945829 Mon Sep 17 00:00:00 2001 From: aladinor Date: Thu, 31 Oct 2024 14:03:00 -0500 Subject: [PATCH 35/51] refactoring nexrad backend to use from_dict constructor --- xradar/io/backends/nexrad_level2.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/xradar/io/backends/nexrad_level2.py b/xradar/io/backends/nexrad_level2.py index 394b6e83..c07d0246 100644 --- a/xradar/io/backends/nexrad_level2.py +++ b/xradar/io/backends/nexrad_level2.py @@ -41,6 +41,7 @@ import numpy as np import xarray as xr +from xarray import DataTree from xarray.backends.common import AbstractDataStore, BackendArray, BackendEntrypoint from xarray.backends.file_manager import CachingFileManager from xarray.backends.store import StoreBackendEntrypoint @@ -52,8 +53,11 @@ from xradar.io.backends.common import ( _assign_root, _attach_sweep_groups, + _get_radar_calibration, + _get_subgroup, ) from xradar.model import ( + georeferencing_correction_subgroup, get_altitude_attrs, get_azimuth_attrs, get_elevation_attrs, @@ -62,6 +66,8 @@ get_range_attrs, get_time_attrs, moment_attrs, + radar_calibration_subgroup, + radar_parameters_subgroup, sweep_vars_mapping, ) @@ -1537,18 +1543,22 @@ def open_nexradlevel2_datatree(filename_or_obj, **kwargs): engine = NexradLevel2BackendEntrypoint # todo: only open once! Needs new xarray built in datatree! - ds = [] + ls_ds: list[xr.Dataset] = [] for swp in sweeps: try: dsx = xr.open_dataset(filename_or_obj, group=swp, engine=engine, **kwargs) except IndexError: break else: - ds.append(dsx) - - ds.insert(0, xr.Dataset()) - - # create datatree root node with required data - dtree = xr.DataTree(dataset=_assign_root(ds), name="root") - # return datatree with attached sweep child nodes - return _attach_sweep_groups(dtree, ds[1:]) + ls_ds.append(dsx) + ls_ds.insert(0, xr.Dataset()) + dtree: dict = { + "/": _assign_root(ls_ds), + "/radar_parameters": _get_subgroup(ls_ds, radar_parameters_subgroup), + "/georeferencing_correction": _get_subgroup( + ls_ds, georeferencing_correction_subgroup + ), + "/radar_calibration": _get_radar_calibration(ls_ds, radar_calibration_subgroup), + } + dtree = _attach_sweep_groups(dtree, ls_ds[1:]) + return DataTree.from_dict(dtree) From 52ffd9783ca7e70c633c32c038809d701fb22aee Mon Sep 17 00:00:00 2001 From: aladinor Date: Thu, 31 Oct 2024 14:44:56 -0500 Subject: [PATCH 36/51] reformating odim test to include calibration, georeference, and radar parameter groups in odim backend --- tests/io/test_io.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/io/test_io.py b/tests/io/test_io.py index db506bbc..6fe6f3a5 100644 --- a/tests/io/test_io.py +++ b/tests/io/test_io.py @@ -109,7 +109,7 @@ def test_open_odim_datatree_sweep(odim_file, sweep): lswp = len([sweep]) else: lswp = len(sweep) - assert len(dtree.groups[1:]) == lswp + assert len(dtree.match("sweep_*")) == lswp def test_open_odim_datatree(odim_file): @@ -164,7 +164,7 @@ def test_open_odim_datatree(odim_file): 200, 200, ] - for i, grp in enumerate(dtree.groups[1:]): + for i, grp in enumerate(dtree.match("sweep_*")): ds = dtree[grp].ds assert dict(ds.sizes) == {"azimuth": azimuths[i], "range": ranges[i]} assert set(ds.data_vars) & ( @@ -183,7 +183,7 @@ def test_open_odim_datatree(odim_file): "range", } assert np.round(ds.elevation.mean().values.item(), 1) == elevations[i] - assert ds.sweep_number.values == int(grp[7:]) + assert ds.sweep_number.values == int(grp[6:]) @pytest.mark.parametrize("first_dim", ["auto", "time"]) From 4e2d96e8c532a84356ee725e643fa3427df76bdc Mon Sep 17 00:00:00 2001 From: aladinor Date: Thu, 31 Oct 2024 14:45:40 -0500 Subject: [PATCH 37/51] refactoring odim backend to use from_dict constructor --- xradar/io/backends/odim.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/xradar/io/backends/odim.py b/xradar/io/backends/odim.py index 6d85a2c0..69528f57 100644 --- a/xradar/io/backends/odim.py +++ b/xradar/io/backends/odim.py @@ -39,6 +39,7 @@ import h5netcdf import numpy as np import xarray as xr +from xarray import DataTree from xarray.backends.common import ( AbstractDataStore, BackendArray, @@ -54,6 +55,7 @@ from ... import util from ...model import ( + georeferencing_correction_subgroup, get_altitude_attrs, get_azimuth_attrs, get_elevation_attrs, @@ -62,13 +64,17 @@ get_range_attrs, get_time_attrs, moment_attrs, + radar_calibration_subgroup, + radar_parameters_subgroup, sweep_vars_mapping, ) from .common import ( - _assign_root, _attach_sweep_groups, _fix_angle, _get_h5group_names, + _get_radar_calibration, + _get_required_root_dataset, + _get_subgroup, _maybe_decode, ) @@ -742,7 +748,8 @@ def get_variables(self): ) def get_attrs(self): - return FrozenDict() + attributes = {"Conventions": "ODIM_H5/V2_2"} + return FrozenDict(attributes) class OdimBackendEntrypoint(BackendEntrypoint): @@ -890,7 +897,7 @@ def open_odim_datatree(filename_or_obj, **kwargs): """ # handle kwargs, extract first_dim backend_kwargs = kwargs.pop("backend_kwargs", {}) - # first_dim = backend_kwargs.pop("first_dim", None) + optional = backend_kwargs.pop("optional", True) sweep = kwargs.pop("sweep", None) sweeps = [] kwargs["backend_kwargs"] = backend_kwargs @@ -907,15 +914,18 @@ def open_odim_datatree(filename_or_obj, **kwargs): else: sweeps = _get_h5group_names(filename_or_obj, "odim") - ds = [ + ls_ds: list[xr.Dataset] = [ xr.open_dataset(filename_or_obj, group=swp, engine="odim", **kwargs) for swp in sweeps ] - # todo: apply CfRadial2 group structure below - ds.insert(0, xr.open_dataset(filename_or_obj, group="/")) - - # create datatree root node with required data - dtree = xr.DataTree(dataset=_assign_root(ds), name="root") - # return datatree with attached sweep child nodes - return _attach_sweep_groups(dtree, ds[1:]) + dtree: dict = { + "/": _get_required_root_dataset(ls_ds, optional=optional), + "/radar_parameters": _get_subgroup(ls_ds, radar_parameters_subgroup), + "/georeferencing_correction": _get_subgroup( + ls_ds, georeferencing_correction_subgroup + ), + "/radar_calibration": _get_radar_calibration(ls_ds, radar_calibration_subgroup), + } + dtree = _attach_sweep_groups(dtree, ls_ds) + return DataTree.from_dict(dtree) From 71470c79818afdf074a5814c646e06233fd5e199 Mon Sep 17 00:00:00 2001 From: aladinor Date: Thu, 31 Oct 2024 14:46:11 -0500 Subject: [PATCH 38/51] refactoring odim export to iterate only over sweeps --- xradar/io/export/odim.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xradar/io/export/odim.py b/xradar/io/export/odim.py index 0a7203dc..dd9811d9 100644 --- a/xradar/io/export/odim.py +++ b/xradar/io/export/odim.py @@ -185,7 +185,7 @@ def to_odim( h5_how = h5.create_group("how") _write_odim(how, h5_how) - grps = dtree.groups[1:] + grps = list(dtree.match("sweep_*")) # groups[1:] # what group, object, version, date, time, source, mandatory # p. 10 f @@ -216,7 +216,7 @@ def to_odim( # datasets ds_list = [f"dataset{i + 1}" for i in range(len(grps))] for idx in range(len(ds_list)): - ds = dtree[grps[idx]].ds + ds = dtree[grps[idx]].to_dataset() dim0 = "elevation" if ds.sweep_mode == "rhi" else "azimuth" # datasetN group From 68544918c277f6d156f6af75dc525d94b1fcac64 Mon Sep 17 00:00:00 2001 From: aladinor Date: Thu, 31 Oct 2024 14:55:19 -0500 Subject: [PATCH 39/51] refactoring rainbow test to only iterate over sweeps --- tests/io/test_io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/io/test_io.py b/tests/io/test_io.py index 6fe6f3a5..23977a92 100644 --- a/tests/io/test_io.py +++ b/tests/io/test_io.py @@ -545,7 +545,7 @@ def test_open_rainbow_datatree(rainbow_file): ] azimuths = [361] * 14 ranges = [400] * 14 - for i, grp in enumerate(dtree.groups[1:]): + for i, grp in enumerate(dtree.match("sweep_*")): ds = dtree[grp].ds assert dict(ds.sizes) == {"azimuth": azimuths[i], "range": ranges[i]} assert set(ds.data_vars) & ( From 9aec3e4dafbad4b6892dea6d824ac04e44a16d8e Mon Sep 17 00:00:00 2001 From: aladinor Date: Thu, 31 Oct 2024 14:55:48 -0500 Subject: [PATCH 40/51] refactoring rainbow backend to use from_dict constructor --- xradar/io/backends/rainbow.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/xradar/io/backends/rainbow.py b/xradar/io/backends/rainbow.py index 1a50bed0..12bd69f0 100644 --- a/xradar/io/backends/rainbow.py +++ b/xradar/io/backends/rainbow.py @@ -39,6 +39,7 @@ import numpy as np import xarray as xr import xmltodict +from xarray import DataTree from xarray.backends.common import AbstractDataStore, BackendArray, BackendEntrypoint from xarray.backends.file_manager import CachingFileManager from xarray.backends.store import StoreBackendEntrypoint @@ -48,6 +49,7 @@ from ... import util from ...model import ( + georeferencing_correction_subgroup, get_altitude_attrs, get_azimuth_attrs, get_elevation_attrs, @@ -56,10 +58,16 @@ get_range_attrs, get_time_attrs, moment_attrs, + radar_calibration_subgroup, + radar_parameters_subgroup, sweep_vars_mapping, ) -from .common import _attach_sweep_groups -from .odim import _assign_root +from .common import ( + _attach_sweep_groups, + _get_radar_calibration, + _get_required_root_dataset, + _get_subgroup, +) #: mapping of rainbow moment names to CfRadial2/ODIM names rainbow_mapping = { @@ -904,7 +912,7 @@ def open_rainbow_datatree(filename_or_obj, **kwargs): """ # handle kwargs, extract first_dim backend_kwargs = kwargs.pop("backend_kwargs", {}) - # first_dim = backend_kwargs.pop("first_dim", None) + optional = backend_kwargs.pop("optional", True) sweep = kwargs.pop("sweep", None) sweeps = [] kwargs["backend_kwargs"] = backend_kwargs @@ -921,14 +929,18 @@ def open_rainbow_datatree(filename_or_obj, **kwargs): else: sweeps = _get_rainbow_group_names(filename_or_obj) - ds = [ + ls_ds: list[xr.Dataset] = [ xr.open_dataset(filename_or_obj, group=swp, engine="rainbow", **kwargs) for swp in sweeps ] - ds.insert(0, xr.Dataset()) # open_dataset(filename_or_obj, group="/")) - - # create datatree root node with required data - dtree = xr.DataTree(dataset=_assign_root(ds), name="root") - # return datatree with attached sweep child nodes - return _attach_sweep_groups(dtree, ds[1:]) + dtree: dict = { + "/": _get_required_root_dataset(ls_ds, optional=optional), + "/radar_parameters": _get_subgroup(ls_ds, radar_parameters_subgroup), + "/georeferencing_correction": _get_subgroup( + ls_ds, georeferencing_correction_subgroup + ), + "/radar_calibration": _get_radar_calibration(ls_ds, radar_calibration_subgroup), + } + dtree = _attach_sweep_groups(dtree, ls_ds) + return DataTree.from_dict(dtree) From d408218b401e18077468069021146c62ac17736b Mon Sep 17 00:00:00 2001 From: aladinor Date: Thu, 31 Oct 2024 14:59:26 -0500 Subject: [PATCH 41/51] fixin typo --- xradar/io/export/odim.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xradar/io/export/odim.py b/xradar/io/export/odim.py index dd9811d9..d0084200 100644 --- a/xradar/io/export/odim.py +++ b/xradar/io/export/odim.py @@ -185,7 +185,7 @@ def to_odim( h5_how = h5.create_group("how") _write_odim(how, h5_how) - grps = list(dtree.match("sweep_*")) # groups[1:] + grps = list(dtree.match("sweep_*")) # what group, object, version, date, time, source, mandatory # p. 10 f From 66c46e7829836e052a440a1f891fbcf4b8e4105e Mon Sep 17 00:00:00 2001 From: aladinor Date: Fri, 1 Nov 2024 21:25:55 -0500 Subject: [PATCH 42/51] adding test for _get_requered_root_group, _get_subgroup, and _get_radar_calibration functions --- tests/test_util.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/test_util.py b/tests/test_util.py index d33e470d..ed0a80e6 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -11,6 +11,11 @@ import xradar as xd from xradar import io, model, util +from xradar.io.backends.common import ( + _get_radar_calibration, + _get_required_root_dataset, + _get_subgroup, +) @pytest.fixture( @@ -424,3 +429,57 @@ def dummy_function(ds, refl="none"): sweep_0.dummy_field.values ) # Convert NaNs to zero for comparison assert np.all(np.isclose(non_nan_values, 0)) + + +def test_get_required_root_dataset(): + + filename = DATASETS.fetch("cor-main131125105503.RAW2049") + sweeps = [f"sweep_{i}" for i in range(10)] + ls_ds = [xr.open_dataset(filename, engine="iris", group=sweep) for sweep in sweeps] + root = _get_required_root_dataset(ls_ds, optional=True) + elevations = [ + 0.5, + 1.0, + 2.0, + 3.0, + 5.0, + 7.0, + 10.0, + 15.0, + 20.0, + 30.0, + ] + assert len(root.variables) == 10 + assert root.variables["time_coverage_start"] == "2013-11-25T10:55:04Z" + assert root.variables["time_coverage_end"] == "2013-11-25T10:59:24Z" + np.testing.assert_equal( + root.variables["sweep_fixed_angle"].values, np.array(elevations) + ) + assert len(list(root.attrs.keys())) == 10 + assert root.attrs["instrument_name"] == "Corozal, Radar" + assert root.attrs["scan_name"] == "SURV_HV_300 " + assert root.attrs["comment"] == "AEROCIVIL OPERATIONAL DUAL POLE SCAN" + + +def test_get_radar_calibration(): + filename = DATASETS.fetch("DWD-Vol-2_99999_20180601054047_00.h5") + sweeps = [f"sweep_{i}" for i in range(10)] + ls_ds = [xr.open_dataset(filename, engine="gamic", group=sweep) for sweep in sweeps] + subgroup = _get_radar_calibration(ls_ds, model.radar_calibration_subgroup) + assert len(subgroup.variables) == 6 + assert subgroup["noise_power_h"] == "-3.8298" + assert subgroup["rx_loss_h"] == "3" + assert subgroup["ant_gain_v"] == "43" + assert subgroup["ant_gain_h"] == "43" + + +def test_get_subgroup(): + filename = DATASETS.fetch("71_20181220_060628.pvol.h5") + sweeps = [f"sweep_{i}" for i in range(10)] + ls_ds = [xr.open_dataset(filename, engine="odim", group=sweep) for sweep in sweeps] + subgroup = _get_subgroup(ls_ds, model.radar_parameters_subgroup) + assert len(subgroup.variables) == 3 + assert list(subgroup.variables) == ["longitude", "latitude", "altitude"] + np.testing.assert_almost_equal(subgroup.longitude.values.item(), 151.20899963378906) + np.testing.assert_almost_equal(subgroup.latitude.values.item(), -33.700801849365234) + assert isinstance(subgroup.altitude.values.item(), float) From 8abc2e7ea30fdb54e66a5c2da0f4e32244a95f0b Mon Sep 17 00:00:00 2001 From: Alfonso Ladino Date: Sat, 2 Nov 2024 09:16:15 -0500 Subject: [PATCH 43/51] Update xradar/model.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kai Mühlbauer --- xradar/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xradar/model.py b/xradar/model.py index 087864f4..f2d0ffcc 100644 --- a/xradar/model.py +++ b/xradar/model.py @@ -241,7 +241,7 @@ ("pulse_width", None), ("antenna_gain_h", None), ("antenna_gain_v", None), - ("ant_gain_h", None), # gamic + ("ant_gain_h", "antenna_gain_h"), # gamic ("ant_gain_v", None), # gamic ("xmit_power_h", None), ("xmit_power_v", None), From 9fd48e03a5ec2ea380b1575983a3cc7e9a4f434a Mon Sep 17 00:00:00 2001 From: Alfonso Ladino Date: Sat, 2 Nov 2024 10:03:01 -0500 Subject: [PATCH 44/51] Update model.py --- xradar/model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xradar/model.py b/xradar/model.py index f2d0ffcc..e0cfbdbf 100644 --- a/xradar/model.py +++ b/xradar/model.py @@ -242,7 +242,7 @@ ("antenna_gain_h", None), ("antenna_gain_v", None), ("ant_gain_h", "antenna_gain_h"), # gamic - ("ant_gain_v", None), # gamic + ("ant_gain_v", "antenna_gain_v"), # gamic ("xmit_power_h", None), ("xmit_power_v", None), ("two_way_waveguide_loss_h", None), @@ -316,8 +316,8 @@ ("dynamic_range_db_hx", None), ("dynamic_range_db_vx", None), ("dbz_correction", None), - ("half_power_beam_wigth_h", None), - ("half_power_beam_wigth_v", None), + ("half_power_beam_width_h", None), + ("half_power_beam_width_v", None), ] ) From e6be32bc57d8ff9da13c16142add7670fb713a85 Mon Sep 17 00:00:00 2001 From: Alfonso Ladino Date: Sat, 2 Nov 2024 10:13:23 -0500 Subject: [PATCH 45/51] Update model.py --- xradar/model.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xradar/model.py b/xradar/model.py index e0cfbdbf..329b0eeb 100644 --- a/xradar/model.py +++ b/xradar/model.py @@ -260,10 +260,6 @@ ("noise_vc", None), ("noise_hx", None), ("noise_vx", None), - ("noise_power_short_pulse_h", None), - ("noise_power_short_pulse_v", None), - ("noise_power_h", None), # Gamic - ("noise_power_v", None), ("rx_loss_h", None), ("rx_loss_v", None), ("receiver_gain_hc", None), @@ -284,6 +280,10 @@ ("sun_power_vx", None), ("noise_source_power_h", None), ("noise_source_power_v", None), + ("noise_power_short_pulse_h", "noise_source_power_h"), + ("noise_power_short_pulse_v", "noise_source_power_v"), + ("noise_power_h", "noise_source_power_h"), # Gamic + ("noise_power_v", "noise_source_power_v"), ("noise_level_pulse_modulation_h", None), # Furuno ("noise_level_frequency_modulation_h", None), # Furuno ("power_measure_loss_h", None), From 971ea59701807f484fdcad4debfc17acde4478a2 Mon Sep 17 00:00:00 2001 From: Alfonso Ladino Date: Sat, 2 Nov 2024 10:32:11 -0500 Subject: [PATCH 46/51] Update model.py --- xradar/model.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/xradar/model.py b/xradar/model.py index 329b0eeb..485cbc55 100644 --- a/xradar/model.py +++ b/xradar/model.py @@ -245,6 +245,12 @@ ("ant_gain_v", "antenna_gain_v"), # gamic ("xmit_power_h", None), ("xmit_power_v", None), + ("tx_pulse_blind_length", None), # Furuno + ("tx_pulse_specification", None), # Furuno + ("tx_power_h", "xmit_power_h"), + ("tx_power_v", "xmit_power_v", + ("threshold_power_short_pulse", None), + ("threshold_power_long_pulse", None), ("two_way_waveguide_loss_h", None), ("two_way_waveguide_loss_v", None), ("two_way_radome_loss_h", None), @@ -252,6 +258,8 @@ ("receiver_mismatch_loss", None), ("receiver_mismatch_loss_h", None), ("receiver_mismatch_loss_v", None), + ("rx_loss_h", "receiver_mismatch_loss_h"), + ("rx_loss_v", "receiver_mismatch_loss_h"), ("radar_constant_h", None), ("radar_constant_v", None), ("probert_jones_correction", None), @@ -260,8 +268,6 @@ ("noise_vc", None), ("noise_hx", None), ("noise_vx", None), - ("rx_loss_h", None), - ("rx_loss_v", None), ("receiver_gain_hc", None), ("receiver_gain_vc", None), ("receiver_gain_hx", None), @@ -284,8 +290,6 @@ ("noise_power_short_pulse_v", "noise_source_power_v"), ("noise_power_h", "noise_source_power_h"), # Gamic ("noise_power_v", "noise_source_power_v"), - ("noise_level_pulse_modulation_h", None), # Furuno - ("noise_level_frequency_modulation_h", None), # Furuno ("power_measure_loss_h", None), ("power_measure_loss_v", None), ("coupler_forward_loss_h", None), @@ -296,12 +300,6 @@ ("system_phidp", None), ("test_power_h", None), ("test_power_v", None), - ("tx_pulse_blind_length", None), # Furuno - ("tx_pulse_specification", None), # Furuno - ("tx_power_h", None), - ("tx_power_v", None), - ("threshold_power_short_pulse", None), - ("threshold_power_long_pulse", None), ("receiver_slope_hc", None), ("receiver_slope_vc", None), ("receiver_slope_hx", None), From da470d25777741d73a144bd15e2dc482629007b3 Mon Sep 17 00:00:00 2001 From: Alfonso Ladino Date: Sat, 2 Nov 2024 10:34:38 -0500 Subject: [PATCH 47/51] Update model.py --- xradar/model.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/xradar/model.py b/xradar/model.py index 485cbc55..bab10dfe 100644 --- a/xradar/model.py +++ b/xradar/model.py @@ -245,12 +245,8 @@ ("ant_gain_v", "antenna_gain_v"), # gamic ("xmit_power_h", None), ("xmit_power_v", None), - ("tx_pulse_blind_length", None), # Furuno - ("tx_pulse_specification", None), # Furuno ("tx_power_h", "xmit_power_h"), ("tx_power_v", "xmit_power_v", - ("threshold_power_short_pulse", None), - ("threshold_power_long_pulse", None), ("two_way_waveguide_loss_h", None), ("two_way_waveguide_loss_v", None), ("two_way_radome_loss_h", None), From 3cf21cd909c61439edd38a2365dd60a4fc4a158d Mon Sep 17 00:00:00 2001 From: Alfonso Ladino Date: Sat, 2 Nov 2024 10:35:13 -0500 Subject: [PATCH 48/51] Update xradar/model.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kai Mühlbauer --- xradar/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xradar/model.py b/xradar/model.py index bab10dfe..2f03bec3 100644 --- a/xradar/model.py +++ b/xradar/model.py @@ -255,7 +255,7 @@ ("receiver_mismatch_loss_h", None), ("receiver_mismatch_loss_v", None), ("rx_loss_h", "receiver_mismatch_loss_h"), - ("rx_loss_v", "receiver_mismatch_loss_h"), + ("rx_loss_v", "receiver_mismatch_loss_v"), ("radar_constant_h", None), ("radar_constant_v", None), ("probert_jones_correction", None), From 064662a94cf122a5fb8f9eb629e71c889e1c78dd Mon Sep 17 00:00:00 2001 From: Alfonso Ladino Date: Sat, 2 Nov 2024 10:36:42 -0500 Subject: [PATCH 49/51] Update model.py --- xradar/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xradar/model.py b/xradar/model.py index 2f03bec3..c5ab5b4e 100644 --- a/xradar/model.py +++ b/xradar/model.py @@ -228,6 +228,8 @@ ("radar_antenna_gain_v", None), ("radar_beam_width_h", None), ("radar_beam_width_v", None), + ("half_power_beam_width_h", "radar_beam_width_h"), + ("half_power_beam_width_v", "radar_beam_width_v"), ("radar_receiver_bandwidth", None), # cfradial2.1 ("radar_rx_bandwidth", "radar_receiver_bandwidth"), # cfradial1.X ] @@ -310,8 +312,6 @@ ("dynamic_range_db_hx", None), ("dynamic_range_db_vx", None), ("dbz_correction", None), - ("half_power_beam_width_h", None), - ("half_power_beam_width_v", None), ] ) From 468f50249490fb570bf39bb53b3bdd77b686f0bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20M=C3=BChlbauer?= Date: Sat, 2 Nov 2024 16:39:03 +0100 Subject: [PATCH 50/51] Update xradar/model.py --- xradar/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xradar/model.py b/xradar/model.py index c5ab5b4e..47a0c2fb 100644 --- a/xradar/model.py +++ b/xradar/model.py @@ -248,7 +248,7 @@ ("xmit_power_h", None), ("xmit_power_v", None), ("tx_power_h", "xmit_power_h"), - ("tx_power_v", "xmit_power_v", + ("tx_power_v", "xmit_power_v"), ("two_way_waveguide_loss_h", None), ("two_way_waveguide_loss_v", None), ("two_way_radome_loss_h", None), From 9c626c4a4ec256dfb9698822424c19aef22e432b Mon Sep 17 00:00:00 2001 From: Alfonso Ladino Date: Sat, 2 Nov 2024 10:49:00 -0500 Subject: [PATCH 51/51] Update history.md --- docs/history.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/history.md b/docs/history.md index c3f4f8c0..cc60f714 100644 --- a/docs/history.md +++ b/docs/history.md @@ -3,6 +3,7 @@ ## 0.8.0 (2024-10-28) This is the first version which uses datatree directly from xarray. Thus, xarray is pinned to version >= 2024.10.0. +* ENH: Refactoring all xradar backends to use `from_dict` datatree constructor. Test for `_get_required_root`, `_get_subgroup`, and `_get_radar_calibration` were also added ({pull}`221`) by [@aladinor](https://github.com/aladinor) * ENH: Added pytests to the missing functions in the `test_xradar` and `test_iris` in order to increase codecov in ({pull}`228`) by [@syedhamidali](https://github.com/syedhamidali). * ENH: Updated Readme ({pull}`226`) by [@syedhamidali](https://github.com/syedhamidali). * ADD: Added new module `transform` for transforming CF1 data to CF2 and vice versa ({pull}`224`) by [@syedhamidali](https://github.com/syedhamidali).