obsarray 1.0.2__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.
@@ -0,0 +1,133 @@
1
+ """
2
+ Handling for multiple ds templates
3
+ """
4
+
5
+ from typing import Optional, Dict, List
6
+ import xarray
7
+ from obsarray import create_ds
8
+
9
+
10
+ __author__ = "Sam Hunt <sam.hunt@npl.co.uk>"
11
+
12
+
13
+ class DSTemplater:
14
+ """
15
+ Class to generate ``xarray.Dataset``'s from a set of templates.
16
+
17
+ :param templates: template dictionary for each product format
18
+ :param metadata_defs: metadata for each product format
19
+
20
+ Each dictionary has as keys ``"template_name"`` strings that define the names of the different available dataset
21
+ templates, e.g. "Level-0".
22
+
23
+ For the ``templates`` dictionary the corresponding entries should be a `variable definition dictionary <https://dsbuilder.readthedocs.io/en/latest/content/quickstart.html#defining-a-template-dataset>`_.
24
+
25
+ For ``metadata_defs`` the corresponding entries should be a dictionary of per product metadata.
26
+ """
27
+
28
+ def __init__(
29
+ self, templates: Optional[Dict] = None, metadata_defs: Optional[Dict] = None
30
+ ):
31
+ self.templates = templates if templates is not None else {}
32
+ self.metadata_defs = metadata_defs if metadata_defs is not None else {}
33
+
34
+ def create(self, template_name: str, size: Dict[str, int]) -> xarray.Dataset:
35
+ """
36
+ Returns template dataset
37
+
38
+ :param template_name: name of template to create (value returned by ``self.return_template_names()``)
39
+ :param size: entry per dataset dimension with value of size
40
+
41
+ :returns: Empty dataset
42
+ """
43
+
44
+ # Find template
45
+ if template_name in self.return_template_names():
46
+ template = self.templates[template_name]
47
+ else:
48
+ raise NameError(
49
+ "No template: "
50
+ + template_name
51
+ + " - must be one of "
52
+ + str(self.return_template_names())
53
+ )
54
+
55
+ # Find metadata def
56
+ metadata = (
57
+ self.metadata_defs[template_name]
58
+ if template_name in self.metadata_defs.keys()
59
+ else None
60
+ )
61
+
62
+ return create_ds(template, size, metadata=metadata)
63
+
64
+ def return_template_names(self) -> list:
65
+ """
66
+ Returns available ds template names
67
+
68
+ :returns: template names
69
+ """
70
+
71
+ return list(self.templates.keys())
72
+
73
+ def return_template_var_names(self, template_name: str) -> List[str]:
74
+ """
75
+ Returns variables for specified template
76
+
77
+ :param template_name: template name (value returned by ``self.return_template_names()``)
78
+
79
+ :returns: variable names for specified template
80
+ """
81
+
82
+ return list(self.templates[template_name].keys())
83
+
84
+ def return_ds_format_variable_dict(
85
+ self, template_name: str, variable_name: str
86
+ ) -> Dict:
87
+ """
88
+ Returns variable definition info for specified template variable
89
+
90
+ :param template_name: template name (value returned by ``self.return_template_names()``)
91
+ :param variable_name: variable name
92
+
93
+ :returns: variable definition info
94
+ """
95
+
96
+ return self.templates[template_name][variable_name]
97
+
98
+ def return_template_dim_names(self, template_name: str) -> List[str]:
99
+ """
100
+ Returns dims required for specified template
101
+
102
+ :param template_name: template name (value returned by ``self.return_template_names()``)
103
+
104
+ :returns: template dimensions
105
+ """
106
+
107
+ template = self.templates[template_name]
108
+
109
+ template_dims = set()
110
+
111
+ for var_name in template.keys():
112
+ template_dims.update(template[var_name]["dim"])
113
+
114
+ return list(template_dims)
115
+
116
+ def create_size_dict(self, ds_format: str) -> Dict[str, None]:
117
+ """
118
+ Returns empty size dictionary for specified template
119
+
120
+ :param template_name: template name (value returned by ``self.return_template_names()``)
121
+
122
+ :return: empty sizes dictionary
123
+ """
124
+
125
+ dim_sizes_dict = dict()
126
+ for dim in self.return_template_dim_names(ds_format):
127
+ dim_sizes_dict[dim] = None
128
+
129
+ return dim_sizes_dict
130
+
131
+
132
+ if __name__ == "__main__":
133
+ pass
@@ -0,0 +1,113 @@
1
+ """
2
+ DSWriter class
3
+ """
4
+
5
+ import os
6
+ from obsarray.templater.template_util import DatasetUtil
7
+
8
+ __author__ = "Sam Hunt <sam.hunt@npl.co.uk>"
9
+
10
+
11
+ class DSWriter:
12
+ """
13
+ Class to write datasets to output files
14
+ """
15
+
16
+ @staticmethod
17
+ def write(ds, path, overwrite=False, fmt="netCDF4", compression_level=None):
18
+ """
19
+ Write xarray dataset to file
20
+
21
+ :type ds: xarray.Dataset
22
+ :param ds: dataset
23
+
24
+ :type path: str
25
+ :param path: file path
26
+
27
+ :type overwrite: bool
28
+ :param overwrite: set to true to overwrite existing file
29
+
30
+ :type fmt: str
31
+ :param fmt: format to write to, may be 'netCDF4' (default) or 'csv'
32
+
33
+ :type compression_level: int
34
+ :param compression_level: the file compression level if 'netCDF4' fmt, 0 - 9 (default is 5)
35
+ """
36
+
37
+ if os.path.isfile(path):
38
+ if overwrite is True:
39
+ os.remove(path)
40
+ else:
41
+ raise IOError("The file already exists: " + path)
42
+
43
+ if (fmt.lower() == "netcdf4") or (fmt.lower() == "netcdf"):
44
+ DSWriter._write_netcdf(ds, path, compression_level=compression_level)
45
+
46
+ elif fmt == "csv":
47
+ DSWriter._write_csv(ds, path)
48
+
49
+ else:
50
+ raise NameError("Invalid fmt: " + fmt)
51
+
52
+ @staticmethod
53
+ def _write_netcdf(ds, path, compression_level=None):
54
+ """
55
+ Write xarray dataset to file to netcdf
56
+
57
+ :type ds: xarray.Dataset
58
+ :param ds: dataset
59
+
60
+ :type path: str
61
+ :param path: file path
62
+
63
+ :type compression_level: int
64
+ :param compression_level: the file compression level if 'netCDF4' fmt, 0 - 9 (default is 5)
65
+ """
66
+
67
+ if compression_level is None:
68
+ compression_level = 5
69
+
70
+ comp = dict(zlib=True, complevel=compression_level)
71
+
72
+ encoding = dict()
73
+ for var_name in ds.data_vars:
74
+ var_encoding = dict(comp)
75
+ var_encoding.update(ds[var_name].encoding)
76
+ if "dtype" in var_encoding.keys():
77
+ var_encoding.update(
78
+ {
79
+ "_FillValue": DatasetUtil.get_default_fill_value(
80
+ var_encoding["dtype"]
81
+ )
82
+ }
83
+ )
84
+ if "_FillValue" in ds[var_name].attrs:
85
+ ds[var_name].attrs.pop("_FillValue")
86
+ encoding.update({var_name: var_encoding})
87
+
88
+ ds.to_netcdf(path, format="netCDF4", engine="netcdf4", encoding=encoding)
89
+
90
+ @staticmethod
91
+ def _write_csv(ds, path):
92
+ """
93
+ Write xarray dataset to file to csv
94
+
95
+ :type ds: xarray.Dataset
96
+ :param ds: dataset
97
+
98
+ :type path: str
99
+ :param path: file path
100
+ """
101
+
102
+ # write data variables (via pandas dataframe)
103
+ ds.to_dataframe().to_csv(path)
104
+
105
+ # write metadata
106
+ metadata_path = os.path.splitext(path)[0] + "_meta.txt"
107
+ with open(metadata_path, "w") as f:
108
+ for meta_name in ds.attrs.keys():
109
+ f.write(meta_name + ": " + ds.attrs[meta_name] + "\n")
110
+
111
+
112
+ if __name__ == "__main__":
113
+ pass
@@ -0,0 +1,257 @@
1
+ """
2
+ Utilities for creating template xarray datasets
3
+ """
4
+
5
+ from typing import Optional, Dict, List
6
+ from obsarray.templater.dataset_util import DatasetUtil
7
+ import xarray
8
+
9
+
10
+ __author__ = "Sam Hunt <sam.hunt@npl.co.uk>"
11
+
12
+
13
+ def create_ds(
14
+ template: Dict[str, Dict],
15
+ size: Dict[str, int],
16
+ metadata: Optional[Dict] = None,
17
+ append_ds: Optional[xarray.Dataset] = None,
18
+ propagate_ds: Optional[xarray.Dataset] = None,
19
+ ) -> xarray.Dataset:
20
+ """
21
+ Returns template dataset
22
+
23
+ :param template: dictionary defining ds variable structure, as defined below.
24
+ :param size: dictionary of dataset dimensions, entry per dataset dimension with value of size as int
25
+ :param metadata: dictionary of dataset metadata
26
+ :param append_ds: base dataset to append with template variables
27
+ :param propagate_ds: template dataset is populated with data from propagate_ds for their variables with
28
+ common names and dimensions. Useful for transferring common data between datasets at different processing levels
29
+ (e.g. times, etc.).
30
+ :returns: template dataset
31
+
32
+ For the ``template`` dictionary each key/value pair defines one variable, where the key is the variable name and the value is a dictionary with the following entries:
33
+
34
+ * ``"dtype"`` (*np.typecodes*/*str*) - variable data type, either a numpy data type or special value ``"flag"`` for
35
+ flag variable
36
+ * ``"dim"`` (*list*) - list of variable dimension names
37
+ * ``"attributes"`` (*dict*) - (optional) variable attributes
38
+ * ``"encoding"`` (*dict*) - (optional) variable encoding.
39
+
40
+ For more information on the required form of these entries, see the :ref:`variables definition section <variables dict>`
41
+ of the user guide.
42
+ """
43
+
44
+ # Create dataset
45
+ ds = append_ds if append_ds is not None else xarray.Dataset()
46
+
47
+ # Add variables
48
+ ds = TemplateUtil.add_variables(ds, template, size)
49
+
50
+ # Add metadata
51
+ if metadata is not None:
52
+ ds = TemplateUtil.add_metadata(ds, metadata)
53
+
54
+ # Propagate variable data
55
+ if propagate_ds is not None:
56
+ TemplateUtil.propagate_values(ds, propagate_ds)
57
+
58
+ return ds
59
+
60
+
61
+ def create_var(var_name: str, var_attrs: Dict, size: Dict[str, int]) -> xarray.Variable:
62
+ """
63
+ Returns template variable
64
+
65
+ :param var_name: variable name
66
+ :param var_attrs: variable definition dictionary (as an entry to a template dictionary)
67
+ :param size: dictionary of dataset dimensions, entry per dataset dimension with value of size as int
68
+ :return:
69
+ """
70
+
71
+ return TemplateUtil._create_var(var_name, var_attrs, size)
72
+
73
+
74
+ class TemplateUtil:
75
+ """
76
+ Class to create template xarray datasets
77
+ """
78
+
79
+ @staticmethod
80
+ def add_variables(
81
+ ds: xarray.Dataset, template: Dict[str, Dict], size: Dict[str, int]
82
+ ) -> xarray.Dataset:
83
+ """
84
+ Adds defined variables dataset
85
+
86
+ :param ds: dataset
87
+ :param template: dictionary defining variables, see the :ref:`variables definition section <variables dict>` of the user guide for more information.
88
+ :param size: dictionary of dataset dimensions, entry per dataset dimension with value of size as int
89
+
90
+ :returns: dataset with defined variables
91
+ """
92
+
93
+ for var_name in template.keys():
94
+
95
+ var = TemplateUtil._create_var(var_name, template[var_name], size)
96
+
97
+ ds[var_name] = var
98
+
99
+ return ds
100
+
101
+ @staticmethod
102
+ def _create_var(
103
+ var_name: str, var_attrs: dict, size: Dict[str, int]
104
+ ) -> xarray.Variable:
105
+
106
+ du = DatasetUtil()
107
+
108
+ # Check variable definition
109
+ TemplateUtil._check_variable_definition(var_name, var_attrs)
110
+
111
+ # Unpack variable attributes
112
+ dtype = var_attrs["dtype"]
113
+ dim_names = var_attrs["dim"]
114
+ attributes = var_attrs["attributes"] if "attributes" in var_attrs else None
115
+
116
+ err_corr = None
117
+ if attributes is not None:
118
+ if "err_corr" in attributes:
119
+ err_corr = attributes.pop("err_corr")
120
+
121
+ # Determine variable shape from dims
122
+ try:
123
+ dim_sizes = TemplateUtil._return_variable_shape(dim_names, size)
124
+ except KeyError:
125
+ raise KeyError(
126
+ "Dim Name Error - Variable "
127
+ + var_name
128
+ + " defined with dim not in dim_sizes_dict"
129
+ )
130
+
131
+ # Create variable and add to dataset
132
+ if isinstance(dtype, str):
133
+ if dtype == "flag":
134
+ flag_meanings = attributes.pop("flag_meanings")
135
+ variable = du.create_flags_variable(
136
+ dim_sizes,
137
+ meanings=flag_meanings,
138
+ dim_names=dim_names,
139
+ attributes=attributes,
140
+ )
141
+
142
+ else:
143
+ raise ValueError("unknown dtype - " + dtype)
144
+
145
+ else:
146
+ if err_corr is None:
147
+ variable = du.create_variable(
148
+ dim_sizes, dim_names=dim_names, dtype=dtype, attributes=attributes
149
+ )
150
+
151
+ else:
152
+ variable = du.create_unc_variable(
153
+ dim_sizes,
154
+ dim_names=dim_names,
155
+ dtype=dtype,
156
+ attributes=attributes,
157
+ err_corr=err_corr,
158
+ )
159
+
160
+ if "encoding" in var_attrs:
161
+ du.add_encoding(variable, **var_attrs["encoding"])
162
+ variable.attrs.pop("_FillValue")
163
+
164
+ return variable
165
+
166
+ @staticmethod
167
+ def _check_variable_definition(variable_name: str, variable_attrs: Dict):
168
+ """
169
+ Checks validity of variable definition, raising errors as appropriate
170
+
171
+ :param variable_name: variable name
172
+ :param variable_attrs: variable defining dictionary
173
+ """
174
+
175
+ # Variable name must be type str
176
+ if type(variable_name) != str:
177
+ raise TypeError(
178
+ "Invalid variable name: " + str(variable_name) + " (must be string)"
179
+ )
180
+
181
+ # todo - add more tests to check validity of variable definition
182
+
183
+ @staticmethod
184
+ def _return_variable_shape(dim_names: List[str], size: Dict[str, int]) -> List[int]:
185
+ """
186
+ Returns dimension sizes of specified dimensions
187
+
188
+ :param dim_names: dimension names
189
+ :param size: dictionary of dataset dimensions, entry per dataset dimension with value of size as int
190
+
191
+ :returns: dimension sizes
192
+ """
193
+
194
+ return [size[dim_name] for dim_name in dim_names]
195
+
196
+ @staticmethod
197
+ def add_metadata(ds: xarray.Dataset, metadata: Dict) -> xarray.Dataset:
198
+ """
199
+ Adds metadata to dataset
200
+
201
+ :param ds: dataset
202
+ :param metadata: dictionary of dataset metadata
203
+
204
+ :returns: dataset with updated metadata
205
+ """
206
+
207
+ ds.attrs.update(metadata)
208
+
209
+ return ds
210
+
211
+ @staticmethod
212
+ def propagate_values(target_ds, source_ds, exclude=None):
213
+ """
214
+ Populates target_ds in-place with data from source_ds for their variables with common names and dimensions.
215
+ Useful for transferring common data between datasets at different processing levels (e.g. times, etc.).
216
+
217
+ N.B. propagates data only, not variables as a whole with attributes etc.
218
+
219
+ :type target_ds: xarray.Dataset
220
+ :param target_ds: ds to populate (perhaps data at new processing level)
221
+
222
+ :type source_ds: xarray.Dataset
223
+ :param source_ds: ds to take data from (perhaps data at previous processing level)
224
+ """
225
+
226
+ # Find variable names common to target_ds and source_ds, excluding specified exclude variables
227
+ common_variable_names = list(set(target_ds).intersection(source_ds))
228
+ # common_variable_names = list(set(target_ds.variables).intersection(source_ds.variables))
229
+ # print(common_variable_names)
230
+
231
+ if exclude is not None:
232
+ common_variable_names = [
233
+ name for name in common_variable_names if name not in exclude
234
+ ]
235
+
236
+ # Remove any common variables that have different dimensions in target_ds and source_ds
237
+ common_variable_names = [
238
+ name
239
+ for name in common_variable_names
240
+ if target_ds[name].dims == source_ds[name].dims
241
+ ]
242
+
243
+ # Propagate data
244
+ for common_variable_name in common_variable_names:
245
+ if (
246
+ target_ds[common_variable_name].shape
247
+ == source_ds[common_variable_name].shape
248
+ ):
249
+ target_ds[common_variable_name].values = source_ds[
250
+ common_variable_name
251
+ ].values
252
+
253
+ # to do - add method to propagate common unpopulated metadata
254
+
255
+
256
+ if __name__ == "__main__":
257
+ pass
File without changes