disdrodb 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- disdrodb/__init__.py +64 -34
- disdrodb/_config.py +5 -4
- disdrodb/_version.py +16 -3
- disdrodb/accessor/__init__.py +20 -0
- disdrodb/accessor/methods.py +125 -0
- disdrodb/api/checks.py +139 -9
- disdrodb/api/configs.py +4 -2
- disdrodb/api/info.py +10 -10
- disdrodb/api/io.py +237 -18
- disdrodb/api/path.py +81 -75
- disdrodb/api/search.py +6 -6
- disdrodb/cli/disdrodb_create_summary_station.py +91 -0
- disdrodb/cli/disdrodb_run_l0.py +1 -1
- disdrodb/cli/disdrodb_run_l0_station.py +1 -1
- disdrodb/cli/disdrodb_run_l0b.py +1 -1
- disdrodb/cli/disdrodb_run_l0b_station.py +1 -1
- disdrodb/cli/disdrodb_run_l0c.py +1 -1
- disdrodb/cli/disdrodb_run_l0c_station.py +1 -1
- disdrodb/cli/disdrodb_run_l2e_station.py +1 -1
- disdrodb/configs.py +149 -4
- disdrodb/constants.py +61 -0
- disdrodb/data_transfer/download_data.py +5 -5
- disdrodb/etc/configs/attributes.yaml +339 -0
- disdrodb/etc/configs/encodings.yaml +473 -0
- disdrodb/etc/products/L1/global.yaml +13 -0
- disdrodb/etc/products/L2E/10MIN.yaml +12 -0
- disdrodb/etc/products/L2E/1MIN.yaml +1 -0
- disdrodb/etc/products/L2E/global.yaml +22 -0
- disdrodb/etc/products/L2M/10MIN.yaml +12 -0
- disdrodb/etc/products/L2M/GAMMA_ML.yaml +8 -0
- disdrodb/etc/products/L2M/NGAMMA_GS_LOG_ND_MAE.yaml +6 -0
- disdrodb/etc/products/L2M/NGAMMA_GS_ND_MAE.yaml +6 -0
- disdrodb/etc/products/L2M/NGAMMA_GS_Z_MAE.yaml +6 -0
- disdrodb/etc/products/L2M/global.yaml +26 -0
- disdrodb/l0/__init__.py +13 -0
- disdrodb/l0/configs/LPM/l0b_cf_attrs.yml +4 -4
- disdrodb/l0/configs/PARSIVEL/l0b_cf_attrs.yml +1 -1
- disdrodb/l0/configs/PARSIVEL/l0b_encodings.yml +3 -3
- disdrodb/l0/configs/PARSIVEL/raw_data_format.yml +1 -1
- disdrodb/l0/configs/PARSIVEL2/l0b_cf_attrs.yml +5 -5
- disdrodb/l0/configs/PARSIVEL2/l0b_encodings.yml +3 -3
- disdrodb/l0/configs/PARSIVEL2/raw_data_format.yml +1 -1
- disdrodb/l0/configs/PWS100/l0b_cf_attrs.yml +4 -4
- disdrodb/l0/configs/PWS100/raw_data_format.yml +1 -1
- disdrodb/l0/l0a_processing.py +30 -30
- disdrodb/l0/l0b_nc_processing.py +108 -2
- disdrodb/l0/l0b_processing.py +4 -4
- disdrodb/l0/l0c_processing.py +5 -13
- disdrodb/l0/readers/LPM/NETHERLANDS/DELFT_LPM_NC.py +66 -0
- disdrodb/l0/readers/LPM/SLOVENIA/{CRNI_VRH.py → UL.py} +3 -0
- disdrodb/l0/readers/LPM/SWITZERLAND/INNERERIZ_LPM.py +195 -0
- disdrodb/l0/readers/PARSIVEL/GPM/PIERS.py +0 -2
- disdrodb/l0/readers/PARSIVEL/JAPAN/JMA.py +4 -1
- disdrodb/l0/readers/PARSIVEL/NCAR/PECAN_MOBILE.py +1 -1
- disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2009.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/BELGIUM/ILVO.py +168 -0
- disdrodb/l0/readers/PARSIVEL2/DENMARK/DTU.py +165 -0
- disdrodb/l0/readers/PARSIVEL2/FINLAND/FMI_PARSIVEL2.py +69 -0
- disdrodb/l0/readers/PARSIVEL2/FRANCE/ENPC_PARSIVEL2.py +255 -134
- disdrodb/l0/readers/PARSIVEL2/FRANCE/OSUG.py +525 -0
- disdrodb/l0/readers/PARSIVEL2/FRANCE/SIRTA_PARSIVEL2.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/GPM/GCPEX.py +9 -7
- disdrodb/l0/readers/PARSIVEL2/KIT/BURKINA_FASO.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/KIT/TEAMX.py +123 -0
- disdrodb/l0/readers/PARSIVEL2/NASA/APU.py +120 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/FARM_PARSIVEL2.py +1 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/PECAN_FP3.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_MIPS.py +126 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_PIPS.py +165 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_P2.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_PIPS.py +20 -12
- disdrodb/l0/readers/PARSIVEL2/NETHERLANDS/DELFT_NC.py +2 -0
- disdrodb/l0/readers/PARSIVEL2/SPAIN/CENER.py +144 -0
- disdrodb/l0/readers/PARSIVEL2/SPAIN/CR1000DL.py +201 -0
- disdrodb/l0/readers/PARSIVEL2/SPAIN/LIAISE.py +137 -0
- disdrodb/l0/readers/PARSIVEL2/{NETHERLANDS/DELFT.py → USA/C3WE.py} +65 -85
- disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100.py +105 -99
- disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100_SIRTA.py +151 -0
- disdrodb/l0/routines.py +105 -14
- disdrodb/l1/__init__.py +5 -0
- disdrodb/l1/filters.py +34 -20
- disdrodb/l1/processing.py +45 -44
- disdrodb/l1/resampling.py +77 -66
- disdrodb/l1/routines.py +35 -43
- disdrodb/l1_env/routines.py +18 -3
- disdrodb/l2/__init__.py +7 -0
- disdrodb/l2/empirical_dsd.py +58 -10
- disdrodb/l2/event.py +27 -120
- disdrodb/l2/processing.py +267 -116
- disdrodb/l2/routines.py +618 -254
- disdrodb/metadata/standards.py +3 -1
- disdrodb/psd/fitting.py +463 -144
- disdrodb/psd/models.py +8 -5
- disdrodb/routines.py +3 -3
- disdrodb/scattering/__init__.py +16 -4
- disdrodb/scattering/axis_ratio.py +56 -36
- disdrodb/scattering/permittivity.py +486 -0
- disdrodb/scattering/routines.py +701 -159
- disdrodb/summary/__init__.py +17 -0
- disdrodb/summary/routines.py +4120 -0
- disdrodb/utils/attrs.py +68 -125
- disdrodb/utils/compression.py +30 -1
- disdrodb/utils/dask.py +59 -8
- disdrodb/utils/dataframe.py +61 -7
- disdrodb/utils/directories.py +35 -15
- disdrodb/utils/encoding.py +33 -19
- disdrodb/utils/logger.py +13 -6
- disdrodb/utils/manipulations.py +71 -0
- disdrodb/utils/subsetting.py +214 -0
- disdrodb/utils/time.py +165 -19
- disdrodb/utils/writer.py +20 -7
- disdrodb/utils/xarray.py +2 -4
- disdrodb/viz/__init__.py +13 -0
- disdrodb/viz/plots.py +327 -0
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/METADATA +3 -2
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/RECORD +121 -88
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/entry_points.txt +1 -0
- disdrodb/l1/encoding_attrs.py +0 -642
- disdrodb/l2/processing_options.py +0 -213
- /disdrodb/l0/readers/PARSIVEL/SLOVENIA/{UL_FGG.py → UL.py} +0 -0
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/WHEEL +0 -0
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/top_level.txt +0 -0
disdrodb/utils/encoding.py
CHANGED
|
@@ -17,20 +17,33 @@
|
|
|
17
17
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
18
18
|
# -----------------------------------------------------------------------------.
|
|
19
19
|
"""DISDRODB netCDF4 encoding utilities."""
|
|
20
|
+
import os
|
|
21
|
+
|
|
20
22
|
import xarray as xr
|
|
21
23
|
|
|
24
|
+
from disdrodb.utils.yaml import read_yaml
|
|
25
|
+
|
|
22
26
|
EPOCH = "seconds since 1970-01-01 00:00:00"
|
|
23
27
|
|
|
24
28
|
|
|
25
|
-
def
|
|
29
|
+
def get_encodings_dict():
|
|
30
|
+
"""Get encoding dictionary for DISDRODB product variables and coordinates."""
|
|
31
|
+
import disdrodb
|
|
32
|
+
|
|
33
|
+
configs_path = os.path.join(disdrodb.__root_path__, "disdrodb", "etc", "configs")
|
|
34
|
+
encodings_dict = read_yaml(os.path.join(configs_path, "encodings.yaml"))
|
|
35
|
+
return encodings_dict
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def set_encodings(ds: xr.Dataset, encodings_dict: dict) -> xr.Dataset:
|
|
26
39
|
"""Apply the encodings to the xarray Dataset.
|
|
27
40
|
|
|
28
41
|
Parameters
|
|
29
42
|
----------
|
|
30
43
|
ds : xarray.Dataset
|
|
31
44
|
Input xarray dataset.
|
|
32
|
-
|
|
33
|
-
Dictionary with
|
|
45
|
+
encodings_dict : dict
|
|
46
|
+
Dictionary with encodings specifications.
|
|
34
47
|
|
|
35
48
|
Returns
|
|
36
49
|
-------
|
|
@@ -38,21 +51,22 @@ def set_encodings(ds: xr.Dataset, encoding_dict: dict) -> xr.Dataset:
|
|
|
38
51
|
Output xarray dataset.
|
|
39
52
|
"""
|
|
40
53
|
# Subset encoding dictionary
|
|
41
|
-
# - Here below
|
|
42
|
-
|
|
54
|
+
# - Here below encodings_dict contains only keys (variables) within the dataset
|
|
55
|
+
encodings_dict = {var: encodings_dict[var] for var in ds.data_vars if var in encodings_dict}
|
|
43
56
|
|
|
44
57
|
# Ensure chunksize smaller than the array shape
|
|
45
|
-
|
|
58
|
+
encodings_dict = sanitize_encodings_dict(encodings_dict, ds)
|
|
46
59
|
|
|
47
60
|
# Rechunk variables for fast writing !
|
|
48
61
|
# - This pop the chunksize argument from the encoding dict !
|
|
49
|
-
ds = rechunk_dataset(ds,
|
|
62
|
+
ds = rechunk_dataset(ds, encodings_dict)
|
|
50
63
|
|
|
51
64
|
# Set time encoding
|
|
52
|
-
|
|
65
|
+
if "time" in ds:
|
|
66
|
+
ds["time"].encoding.update(get_time_encoding())
|
|
53
67
|
|
|
54
68
|
# Set the variable encodings
|
|
55
|
-
for var, encoding in
|
|
69
|
+
for var, encoding in encodings_dict.items():
|
|
56
70
|
ds[var].encoding.update(encoding)
|
|
57
71
|
|
|
58
72
|
# Ensure no deprecated "missing_value" attribute
|
|
@@ -63,12 +77,12 @@ def set_encodings(ds: xr.Dataset, encoding_dict: dict) -> xr.Dataset:
|
|
|
63
77
|
return ds
|
|
64
78
|
|
|
65
79
|
|
|
66
|
-
def sanitize_encodings_dict(
|
|
80
|
+
def sanitize_encodings_dict(encodings_dict: dict, ds: xr.Dataset) -> dict:
|
|
67
81
|
"""Ensure chunk size to be smaller than the array shape.
|
|
68
82
|
|
|
69
83
|
Parameters
|
|
70
84
|
----------
|
|
71
|
-
|
|
85
|
+
encodings_dict : dict
|
|
72
86
|
Dictionary containing the variable encodings.
|
|
73
87
|
ds : xarray.Dataset
|
|
74
88
|
Input dataset.
|
|
@@ -79,23 +93,23 @@ def sanitize_encodings_dict(encoding_dict: dict, ds: xr.Dataset) -> dict:
|
|
|
79
93
|
Encoding dictionary.
|
|
80
94
|
"""
|
|
81
95
|
for var in ds.data_vars:
|
|
82
|
-
if var in
|
|
96
|
+
if var in encodings_dict:
|
|
83
97
|
shape = ds[var].shape
|
|
84
|
-
chunks =
|
|
98
|
+
chunks = encodings_dict[var].get("chunksizes", None)
|
|
85
99
|
if chunks is not None:
|
|
86
100
|
chunks = [shape[i] if chunks[i] > shape[i] else chunks[i] for i in range(len(chunks))]
|
|
87
|
-
|
|
88
|
-
return
|
|
101
|
+
encodings_dict[var]["chunksizes"] = chunks
|
|
102
|
+
return encodings_dict
|
|
89
103
|
|
|
90
104
|
|
|
91
|
-
def rechunk_dataset(ds: xr.Dataset,
|
|
105
|
+
def rechunk_dataset(ds: xr.Dataset, encodings_dict: dict) -> xr.Dataset:
|
|
92
106
|
"""Coerce the dataset arrays to have the chunk size specified in the encoding dictionary.
|
|
93
107
|
|
|
94
108
|
Parameters
|
|
95
109
|
----------
|
|
96
110
|
ds : xarray.Dataset
|
|
97
111
|
Input xarray dataset
|
|
98
|
-
|
|
112
|
+
encodings_dict : dict
|
|
99
113
|
Dictionary containing the encoding to write the xarray dataset as a netCDF.
|
|
100
114
|
|
|
101
115
|
Returns
|
|
@@ -104,8 +118,8 @@ def rechunk_dataset(ds: xr.Dataset, encoding_dict: dict) -> xr.Dataset:
|
|
|
104
118
|
Output xarray dataset
|
|
105
119
|
"""
|
|
106
120
|
for var in ds.data_vars:
|
|
107
|
-
if var in
|
|
108
|
-
chunks =
|
|
121
|
+
if var in encodings_dict:
|
|
122
|
+
chunks = encodings_dict[var].pop("chunksizes", None)
|
|
109
123
|
if chunks is not None:
|
|
110
124
|
dims = list(ds[var].dims)
|
|
111
125
|
chunks_dict = dict(zip(dims, chunks))
|
disdrodb/utils/logger.py
CHANGED
|
@@ -164,9 +164,16 @@ def _define_station_summary_log_file(list_logs, summary_filepath):
|
|
|
164
164
|
|
|
165
165
|
|
|
166
166
|
def _define_station_problem_log_file(list_logs, problem_filepath):
|
|
167
|
-
#
|
|
168
|
-
list_keywords = ["ERROR"]
|
|
169
|
-
|
|
167
|
+
# Copy the log of files with errors
|
|
168
|
+
list_keywords = ["ERROR"]
|
|
169
|
+
# Exclude lines with the following patterns
|
|
170
|
+
list_patterns = [
|
|
171
|
+
# Caused by no data with L2E and L2M filtering
|
|
172
|
+
"No timesteps with rain rate",
|
|
173
|
+
"No timesteps with N",
|
|
174
|
+
"No timesteps with Nbins",
|
|
175
|
+
]
|
|
176
|
+
# Compile patterns to search, escaping any special regex characters
|
|
170
177
|
re_keyword = re.compile("|".join(list_keywords))
|
|
171
178
|
# Compile patterns to ignore, escaping any special regex characters
|
|
172
179
|
re_patterns = re.compile("|".join(map(re.escape, list_patterns))) if list_patterns else None
|
|
@@ -221,7 +228,7 @@ def create_product_logs(
|
|
|
221
228
|
|
|
222
229
|
The logs directory structure is the follow:
|
|
223
230
|
/logs
|
|
224
|
-
- /files/<
|
|
231
|
+
- /files/<product_name>/<station> (same structure as data ... a log for each processed file)
|
|
225
232
|
- /summary
|
|
226
233
|
--> SUMMARY.<PRODUCT_ACRONYM>.<CAMPAIGN_NAME>.<STATION_NAME>.log
|
|
227
234
|
- /problems
|
|
@@ -269,7 +276,7 @@ def create_product_logs(
|
|
|
269
276
|
# Product options
|
|
270
277
|
**product_kwargs,
|
|
271
278
|
)
|
|
272
|
-
list_logs = list_files(logs_dir,
|
|
279
|
+
list_logs = list_files(logs_dir, recursive=True)
|
|
273
280
|
|
|
274
281
|
# --------------------------------------------------------.
|
|
275
282
|
# LogCaptureHandler of pytest does not have baseFilename attribute, so it returns None
|
|
@@ -332,5 +339,5 @@ def create_product_logs(
|
|
|
332
339
|
|
|
333
340
|
# --------------------------------------------------------.
|
|
334
341
|
# Remove /problem directory if empty !
|
|
335
|
-
if len(
|
|
342
|
+
if len(list_files(logs_problem_dir, glob_pattern="*.log")) == 0:
|
|
336
343
|
os.rmdir(logs_problem_dir)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# -----------------------------------------------------------------------------.
|
|
4
|
+
# Copyright (c) 2021-2023 DISDRODB developers
|
|
5
|
+
#
|
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
# (at your option) any later version.
|
|
10
|
+
#
|
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
# GNU General Public License for more details.
|
|
15
|
+
#
|
|
16
|
+
# You should have received a copy of the GNU General Public License
|
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
18
|
+
# -----------------------------------------------------------------------------.
|
|
19
|
+
"""Include functions helping for DISDRODB product manipulations."""
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
from disdrodb.utils.xarray import unstack_datarray_dimension
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_diameter_bin_edges(ds):
|
|
27
|
+
"""Retrieve diameter bin edges."""
|
|
28
|
+
bin_edges = np.append(ds["diameter_bin_lower"].compute().data, ds["diameter_bin_upper"].compute().data[-1])
|
|
29
|
+
return bin_edges
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def convert_from_decibel(x):
|
|
33
|
+
"""Convert dB to unit."""
|
|
34
|
+
return np.power(10.0, 0.1 * x) # x/10
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def convert_to_decibel(x):
|
|
38
|
+
"""Convert unit to dB."""
|
|
39
|
+
return 10 * np.log10(x)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def unstack_radar_variables(ds):
|
|
43
|
+
"""Unstack radar variables."""
|
|
44
|
+
from disdrodb.scattering import RADAR_VARIABLES
|
|
45
|
+
|
|
46
|
+
for var in RADAR_VARIABLES:
|
|
47
|
+
if var in ds:
|
|
48
|
+
ds_unstack = unstack_datarray_dimension(ds[var], dim="frequency", prefix="", suffix="_")
|
|
49
|
+
ds.update(ds_unstack)
|
|
50
|
+
ds = ds.drop_vars(var)
|
|
51
|
+
if "frequency" in ds.dims:
|
|
52
|
+
ds = ds.drop_dims("frequency")
|
|
53
|
+
return ds
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def resample_drop_number_concentration(da, diameter_bin_edges, method="linear"):
|
|
57
|
+
"""Resample drop number concentration N(D) DataArray to high resolution diameter bins."""
|
|
58
|
+
diameters_bin_center = diameter_bin_edges[:-1] + np.diff(diameter_bin_edges) / 2
|
|
59
|
+
|
|
60
|
+
da = da.interp(coords={"diameter_bin_center": diameters_bin_center}, method=method)
|
|
61
|
+
diameter_bin_width = np.diff(diameter_bin_edges)
|
|
62
|
+
diameter_bin_lower = diameter_bin_edges[:-1]
|
|
63
|
+
diameter_bin_upper = diameter_bin_edges[1:]
|
|
64
|
+
da = da.assign_coords(
|
|
65
|
+
{
|
|
66
|
+
"diameter_bin_width": ("diameter_bin_center", diameter_bin_width),
|
|
67
|
+
"diameter_bin_lower": ("diameter_bin_center", diameter_bin_lower),
|
|
68
|
+
"diameter_bin_upper": ("diameter_bin_center", diameter_bin_upper),
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
return da
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# -----------------------------------------------------------------------------.
|
|
2
|
+
# Copyright (c) 2021-2023 DISDRODB developers
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
# -----------------------------------------------------------------------------.
|
|
18
|
+
"""This module contains functions for subsetting and aligning DISDRODB products."""
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
from xarray.core.utils import either_dict_or_kwargs
|
|
22
|
+
|
|
23
|
+
from disdrodb.constants import DIAMETER_DIMENSION, VELOCITY_DIMENSION
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def is_1d_non_dimensional_coord(xr_obj, coord):
|
|
27
|
+
"""Checks if a coordinate is a 1d, non-dimensional coordinate."""
|
|
28
|
+
if coord not in xr_obj.coords:
|
|
29
|
+
return False
|
|
30
|
+
if xr_obj[coord].ndim != 1:
|
|
31
|
+
return False
|
|
32
|
+
is_1d_dim_coord = xr_obj[coord].dims[0] == coord
|
|
33
|
+
return not is_1d_dim_coord
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _get_dim_of_1d_non_dimensional_coord(xr_obj, coord):
|
|
37
|
+
"""Get the dimension of a 1D non-dimension coordinate."""
|
|
38
|
+
if not is_1d_non_dimensional_coord(xr_obj, coord):
|
|
39
|
+
raise ValueError(f"'{coord}' is not a dimension or a 1D non-dimensional coordinate.")
|
|
40
|
+
dim = xr_obj[coord].dims[0]
|
|
41
|
+
return dim
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _get_dim_isel_on_non_dim_coord_from_isel(xr_obj, coord, isel_indices):
|
|
45
|
+
"""Get dimension and isel_indices related to a 1D non-dimension coordinate.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
xr_obj : (xr.Dataset, xr.DataArray)
|
|
50
|
+
A xarray object.
|
|
51
|
+
coord : str
|
|
52
|
+
Name of the coordinate wishing to subset with .sel
|
|
53
|
+
isel_indices : (str, int, float, list, np.array)
|
|
54
|
+
Coordinate indices wishing to be selected.
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
dim : str
|
|
59
|
+
Dimension related to the 1D non-dimension coordinate.
|
|
60
|
+
isel_indices : (int, list, slice)
|
|
61
|
+
Indices for index-based selection.
|
|
62
|
+
"""
|
|
63
|
+
dim = _get_dim_of_1d_non_dimensional_coord(xr_obj, coord)
|
|
64
|
+
return dim, isel_indices
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _get_dim_isel_indices_from_isel_indices(xr_obj, key, indices, method="dummy"): # noqa
|
|
68
|
+
"""Return the dimension and isel_indices related to the dimension position indices of a coordinate."""
|
|
69
|
+
# Non-dimensional coordinate case
|
|
70
|
+
if key not in xr_obj.dims:
|
|
71
|
+
key, indices = _get_dim_isel_on_non_dim_coord_from_isel(xr_obj, coord=key, isel_indices=indices)
|
|
72
|
+
return key, indices
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _get_isel_indices_from_sel_indices(xr_obj, coord, sel_indices, method):
|
|
76
|
+
"""Get isel_indices corresponding to sel_indices."""
|
|
77
|
+
da_coord = xr_obj[coord]
|
|
78
|
+
dim = da_coord.dims[0]
|
|
79
|
+
da_coord = da_coord.assign_coords({"isel_indices": (dim, np.arange(0, da_coord.size))})
|
|
80
|
+
da_subset = da_coord.swap_dims({dim: coord}).sel({coord: sel_indices}, method=method)
|
|
81
|
+
isel_indices = da_subset["isel_indices"].data
|
|
82
|
+
return isel_indices
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _get_dim_isel_on_non_dim_coord_from_sel(xr_obj, coord, sel_indices, method):
|
|
86
|
+
"""
|
|
87
|
+
Return the dimension and isel_indices related to a 1D non-dimension coordinate.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
xr_obj : (xr.Dataset, xr.DataArray)
|
|
92
|
+
A xarray object.
|
|
93
|
+
coord : str
|
|
94
|
+
Name of the coordinate wishing to subset with .sel
|
|
95
|
+
sel_indices : (str, int, float, list, np.array)
|
|
96
|
+
Coordinate values wishing to be selected.
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
dim : str
|
|
101
|
+
Dimension related to the 1D non-dimension coordinate.
|
|
102
|
+
isel_indices : np.ndarray
|
|
103
|
+
Indices for index-based selection.
|
|
104
|
+
"""
|
|
105
|
+
dim = _get_dim_of_1d_non_dimensional_coord(xr_obj, coord)
|
|
106
|
+
isel_indices = _get_isel_indices_from_sel_indices(xr_obj, coord=coord, sel_indices=sel_indices, method=method)
|
|
107
|
+
return dim, isel_indices
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _get_dim_isel_indices_from_sel_indices(xr_obj, key, indices, method):
|
|
111
|
+
"""Return the dimension and isel_indices related to values of a coordinate."""
|
|
112
|
+
# Dimension case
|
|
113
|
+
if key in xr_obj.dims:
|
|
114
|
+
if key not in xr_obj.coords:
|
|
115
|
+
raise ValueError(f"Can not subset with disdrodb.sel the dimension '{key}' if it is not also a coordinate.")
|
|
116
|
+
isel_indices = _get_isel_indices_from_sel_indices(xr_obj, coord=key, sel_indices=indices, method=method)
|
|
117
|
+
# Non-dimensional coordinate case
|
|
118
|
+
else:
|
|
119
|
+
key, isel_indices = _get_dim_isel_on_non_dim_coord_from_sel(
|
|
120
|
+
xr_obj,
|
|
121
|
+
coord=key,
|
|
122
|
+
sel_indices=indices,
|
|
123
|
+
method=method,
|
|
124
|
+
)
|
|
125
|
+
return key, isel_indices
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _get_dim_isel_indices_function(func):
|
|
129
|
+
func_dict = {
|
|
130
|
+
"sel": _get_dim_isel_indices_from_sel_indices,
|
|
131
|
+
"isel": _get_dim_isel_indices_from_isel_indices,
|
|
132
|
+
}
|
|
133
|
+
return func_dict[func]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _subset(xr_obj, indexers=None, func="isel", drop=False, method=None, **indexers_kwargs):
|
|
137
|
+
"""Perform selection with isel or isel."""
|
|
138
|
+
# Retrieve indexers
|
|
139
|
+
indexers = either_dict_or_kwargs(indexers, indexers_kwargs, func)
|
|
140
|
+
# Get function returning isel_indices
|
|
141
|
+
get_dim_isel_indices = _get_dim_isel_indices_function(func)
|
|
142
|
+
# Define isel_dict
|
|
143
|
+
isel_dict = {}
|
|
144
|
+
for key, indices in indexers.items():
|
|
145
|
+
key, isel_indices = get_dim_isel_indices(xr_obj, key=key, indices=indices, method=method)
|
|
146
|
+
if key in isel_dict:
|
|
147
|
+
raise ValueError(f"Multiple indexers point to the '{key}' dimension.")
|
|
148
|
+
isel_dict[key] = isel_indices
|
|
149
|
+
|
|
150
|
+
# Subset and update area
|
|
151
|
+
xr_obj = xr_obj.isel(isel_dict, drop=drop)
|
|
152
|
+
return xr_obj
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def isel(xr_obj, indexers=None, drop=False, **indexers_kwargs):
|
|
156
|
+
"""Perform index-based dimension selection."""
|
|
157
|
+
return _subset(xr_obj, indexers=indexers, func="isel", drop=drop, **indexers_kwargs)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def sel(xr_obj, indexers=None, drop=False, method=None, **indexers_kwargs):
|
|
161
|
+
"""Perform value-based coordinate selection.
|
|
162
|
+
|
|
163
|
+
Slices are treated as inclusive of both the start and stop values, unlike normal Python indexing.
|
|
164
|
+
The disdrodb `sel` method is empowered to:
|
|
165
|
+
|
|
166
|
+
- slice by disdrodb-id strings !
|
|
167
|
+
- slice by any xarray coordinate value !
|
|
168
|
+
|
|
169
|
+
You can use string shortcuts for datetime coordinates (e.g., '2000-01' to select all values in January 2000).
|
|
170
|
+
"""
|
|
171
|
+
return _subset(xr_obj, indexers=indexers, func="sel", drop=drop, method=method, **indexers_kwargs)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def align(*args):
|
|
175
|
+
"""Align DISDRODB products over time, velocity and diameter dimensions."""
|
|
176
|
+
list_xr_obj = args
|
|
177
|
+
|
|
178
|
+
# Check input
|
|
179
|
+
if len(list_xr_obj) <= 1:
|
|
180
|
+
raise ValueError("At least two xarray object are required for alignment.")
|
|
181
|
+
|
|
182
|
+
# Define dimensions used for alignment
|
|
183
|
+
dims_to_align = ["time", DIAMETER_DIMENSION, VELOCITY_DIMENSION]
|
|
184
|
+
|
|
185
|
+
# Check which dimensions and coordinates are available across all datasets
|
|
186
|
+
coords = [coord for coord in dims_to_align if all(coord in xr_obj.coords for xr_obj in list_xr_obj)]
|
|
187
|
+
if not coords:
|
|
188
|
+
raise ValueError("No common coordinates found among the input datasets for alignment.")
|
|
189
|
+
|
|
190
|
+
# Start with the input datasets
|
|
191
|
+
list_aligned = list(list_xr_obj)
|
|
192
|
+
|
|
193
|
+
# Loop over the dimensions which are available
|
|
194
|
+
for coord in coords:
|
|
195
|
+
# Retrieve list of coordinate values
|
|
196
|
+
list_coord_values = [xr_obj[coord].data for xr_obj in list_aligned]
|
|
197
|
+
|
|
198
|
+
# Retrieve intersection of coordinates values
|
|
199
|
+
# - np.atleast_1d ensure that the dimension is not dropped if only 1 value
|
|
200
|
+
# - np.intersect1d returns the sorted array of common unique elements
|
|
201
|
+
common_values = list_coord_values[0]
|
|
202
|
+
for coord_values in list_coord_values[1:]:
|
|
203
|
+
common_values = np.intersect1d(common_values, coord_values)
|
|
204
|
+
sel_indices = np.atleast_1d(common_values)
|
|
205
|
+
|
|
206
|
+
# Check there are common coordinate values
|
|
207
|
+
if len(sel_indices) == 0:
|
|
208
|
+
raise ValueError(f"No common {coord} values across input objects.")
|
|
209
|
+
|
|
210
|
+
# Subset dataset
|
|
211
|
+
new_list_aligned = [sel(xr_obj, {coord: sel_indices}) for xr_obj in list_aligned]
|
|
212
|
+
list_aligned = new_list_aligned
|
|
213
|
+
|
|
214
|
+
return list_aligned
|