glodformat 0.0.1__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.
glodformat/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ from glodformat.moon import validate_moon_observation
2
+ from glodformat.srf import validate_srf, validate_srf_for_obs
glodformat/_version.py ADDED
@@ -0,0 +1,11 @@
1
+ """
2
+ glodformat Version Handler Module
3
+
4
+ Handles versioning for the `glodformat` package.
5
+ """
6
+ from importlib.metadata import version, PackageNotFoundError
7
+
8
+ try:
9
+ __version__ = version("glodformat")
10
+ except PackageNotFoundError:
11
+ __version__ = "0.0.0"
glodformat/moon.py ADDED
@@ -0,0 +1,149 @@
1
+ """
2
+ This module contains the functionality that validates moon observations files.
3
+
4
+ It exports the following functions:
5
+ * validate_moon_observation - Validates that a xarray dataset follows the Moon Observation schema.
6
+ """
7
+ from typing import Union
8
+ import re
9
+ from pathlib import Path
10
+
11
+ import numpy as np
12
+ import xarray as xr
13
+ import pandera.xarray as pa
14
+
15
+ from glodformat.netcdfcommon import (
16
+ validate_schema,
17
+ get_valid_metric_units,
18
+ check_stringy,
19
+ check_floating,
20
+ _as_ctx,
21
+ )
22
+
23
+ _check_shape_1 = pa.Check(
24
+ lambda da: da.size == 1, error="Coordinate 'date' must have exactly one element."
25
+ )
26
+
27
+ _UNITS_REGEX = "^(?:" + "|".join(re.escape(v) for v in get_valid_metric_units()) + ")$"
28
+
29
+ _LUNAR_OBS_SCHEMA = pa.DatasetSchema(
30
+ data_vars={
31
+ "channel_name": pa.DataArraySchema(checks=check_stringy, dims=["chan"]),
32
+ "sat_pos": pa.DataArraySchema(
33
+ checks=check_floating,
34
+ shape=(3,),
35
+ dims=["sat_xyz"],
36
+ attrs={"units": _UNITS_REGEX},
37
+ ),
38
+ "sat_pos_ref": pa.DataArraySchema(checks=check_stringy, dims=[]),
39
+ "irr_obs": pa.DataArraySchema(
40
+ checks=check_floating, dims=["chan"], nullable=True
41
+ ),
42
+ },
43
+ coords={"date": pa.Coordinate(np.datetime64, checks=_check_shape_1)},
44
+ attrs={"data_source": lambda _: True},
45
+ )
46
+
47
+ _LUNAR_OBS_OTHER_SCHEMAS = [
48
+ pa.DatasetSchema(
49
+ data_vars={
50
+ "channel_name": pa.DataArraySchema(checks=check_stringy, dims=["chan"]),
51
+ "sat_pos": pa.DataArraySchema(
52
+ checks=check_floating,
53
+ shape=(3,),
54
+ dims=["sat_xyz"],
55
+ attrs={"units": _UNITS_REGEX},
56
+ ),
57
+ "sat_pos_ref": pa.DataArraySchema(checks=check_stringy, dims=[]),
58
+ "irr_obs": pa.DataArraySchema(
59
+ checks=check_floating, dims=["chan"], nullable=True
60
+ ),
61
+ },
62
+ coords={"date": pa.Coordinate(checks=[check_floating, _check_shape_1])},
63
+ attrs={"data_source": lambda _: True},
64
+ ),
65
+ pa.DatasetSchema(
66
+ data_vars={
67
+ "channel_name": pa.DataArraySchema(checks=check_stringy, dims=["chan"]),
68
+ "sat_pos": pa.DataArraySchema(
69
+ checks=check_floating,
70
+ shape=(3,),
71
+ dims=["sat_xyz"],
72
+ attrs={"units": _UNITS_REGEX},
73
+ ),
74
+ "sat_pos_ref": pa.DataArraySchema(checks=check_stringy, dims=[]),
75
+ "irr_obs": pa.DataArraySchema(
76
+ checks=check_floating, dims=["chan"], nullable=True
77
+ ),
78
+ "date": pa.DataArraySchema(np.datetime64, shape=(1,)),
79
+ },
80
+ attrs={"data_source": lambda _: True},
81
+ ),
82
+ pa.DatasetSchema(
83
+ data_vars={
84
+ "channel_name": pa.DataArraySchema(checks=check_stringy, dims=["chan"]),
85
+ "sat_pos": pa.DataArraySchema(
86
+ checks=check_floating,
87
+ shape=(3,),
88
+ dims=["sat_xyz"],
89
+ attrs={"units": _UNITS_REGEX},
90
+ ),
91
+ "sat_pos_ref": pa.DataArraySchema(checks=check_stringy, dims=[]),
92
+ "irr_obs": pa.DataArraySchema(
93
+ checks=check_floating, dims=["chan"], nullable=True
94
+ ),
95
+ "date": pa.DataArraySchema(checks=check_floating, shape=(1,)),
96
+ },
97
+ attrs={"data_source": lambda _: True},
98
+ ),
99
+ ]
100
+
101
+
102
+ def _validate_schema_moon_observation(ds: xr.Dataset, strict: bool = False):
103
+ """Validates that a xarray dataset follows the Basic Moon Observation schema.
104
+
105
+ The dataset structure is:
106
+
107
+ coordinates: 'date': datetime
108
+ dims without coordinates: 'chan' and 'sat_xyz'
109
+ data_vars: 'channel_name': str, 'sat_pos': float, 'sat_pos_ref': str, 'irr_obs': float
110
+
111
+ Parameters
112
+ ----------
113
+ ds: xr.Dataset
114
+ Dataset to validate
115
+ strict: bool
116
+ Flag to specify if the check must be exact to the desired schema, or if
117
+ it can pass if it would work in most cases.
118
+ """
119
+ schema = _LUNAR_OBS_SCHEMA
120
+ if strict:
121
+ validate_schema(schema, ds)
122
+ else:
123
+ validate_schema(schema, ds, _LUNAR_OBS_OTHER_SCHEMAS)
124
+
125
+
126
+ def validate_moon_observation(
127
+ data: Union[xr.Dataset, str, Path], strict: bool = False
128
+ ) -> None:
129
+ """Validates that a dataset follows the Moon Observation schema.
130
+
131
+ Parameters
132
+ ----------
133
+ data : xr.Dataset or str or pathlib.Path
134
+ Dataset to validate, or path to a NetCDF file that will be opened
135
+ and validated. If a path is given, the file is opened in read-only
136
+ mode and closed before returning.
137
+ strict : bool
138
+ Flag to specify if the check must be exact to the desired schema, or
139
+ if it can pass if it would work in most cases.
140
+
141
+ Raises
142
+ ------
143
+ TypeError
144
+ If ``data`` is neither an ``xr.Dataset``, ``str`` nor ``Path``.
145
+ ValueError
146
+ If the dataset does not conform to any of the accepted SRF schemas.
147
+ """
148
+ with _as_ctx(data) as ds:
149
+ _validate_schema_moon_observation(ds, strict=strict)
@@ -0,0 +1,190 @@
1
+ """Common functionalities for reading netCDF files with `xarray`."""
2
+ from typing import Iterable, Mapping, Union, Dict, List
3
+ from pathlib import Path
4
+ from contextlib import nullcontext
5
+
6
+ import numpy as np
7
+ import xarray as xr
8
+ import pandera.xarray as pa
9
+
10
+
11
+ def _is_stringy(da) -> bool:
12
+ dtype = da.dtype
13
+ if dtype.kind in ("U", "S"):
14
+ return True
15
+ if hasattr(np, "dtypes") and hasattr(np.dtypes, "StringDType"):
16
+ if isinstance(dtype, np.dtypes.StringDType):
17
+ return True
18
+ if dtype.kind == "O":
19
+ return all(isinstance(x, str) for x in da.values.flat)
20
+ return False
21
+
22
+
23
+ check_stringy = pa.Check(_is_stringy, error="Data array must be of type string/bytes")
24
+
25
+
26
+ def _is_floating(da) -> bool:
27
+ return da.dtype.kind == "f"
28
+
29
+
30
+ check_floating = pa.Check(
31
+ _is_floating,
32
+ error="Data array must have a floating-point dtype (e.g. float32, float64).",
33
+ )
34
+
35
+
36
+ def _gen_unit_variations(basename: str) -> List[str]:
37
+ """Generate common variations of a metric unit (American & British spellings)."""
38
+ return [
39
+ f"{basename}metre", # "metre" (British English)
40
+ f"{basename}meter", # "meter" (American English)
41
+ f"{basename}metres", # "metres" (plural British)
42
+ f"{basename}meters", # "meters" (plural American)
43
+ ]
44
+
45
+
46
+ def _get_metric_factors() -> Dict[str, int]:
47
+ # Build the metric factors dictionary efficiently
48
+ metric_factors = {
49
+ **dict.fromkeys(("nm", *_gen_unit_variations("nano")), -9),
50
+ **dict.fromkeys(("um", "μm", *_gen_unit_variations("micro")), -6),
51
+ **dict.fromkeys(("mm", *_gen_unit_variations("milli")), -3),
52
+ **dict.fromkeys(("cm", *_gen_unit_variations("centi")), -2),
53
+ **dict.fromkeys(("m", *_gen_unit_variations("")), 0),
54
+ **dict.fromkeys(("km", *_gen_unit_variations("kilo")), 3),
55
+ }
56
+ return metric_factors
57
+
58
+
59
+ def get_valid_metric_units() -> List[str]:
60
+ metric_factors = _get_metric_factors()
61
+ return list(metric_factors.keys())
62
+
63
+
64
+ def get_length_conversion_factor(from_unit: str, to_unit: str) -> float:
65
+ """
66
+ Get the conversion factor between two metric length units.
67
+
68
+ This function calculates the factor needed to convert a length measurement
69
+ from one metric unit to another. It supports various unit spellings, including
70
+ American and British English variations.
71
+
72
+ Parameters
73
+ ----------
74
+ from_unit : str
75
+ The original unit of measurement. Supported units include metres (m),
76
+ centimetres (cm), millimeters (mm), micrometres (μm / um), and nanometres (nm).
77
+ to_unit : str
78
+ The target unit of measurement. Must be one of the supported values listed above.
79
+
80
+ Returns
81
+ -------
82
+ float
83
+ The multiplication factor to convert from `from_unit` to `to_unit`.
84
+
85
+ Raises
86
+ ------
87
+ ValueError
88
+ If either `from_unit` or `to_unit` is not a recognized metric length unit.
89
+ """
90
+ metric_factors = _get_metric_factors()
91
+ if from_unit not in metric_factors:
92
+ raise ValueError(
93
+ f"Unsupported unit: {from_unit} or {to_unit}. Supported units: {list(metric_factors.keys())}"
94
+ )
95
+ factor = metric_factors[from_unit] - metric_factors[to_unit]
96
+ factor = 10**factor
97
+ return factor
98
+
99
+
100
+ def xr_open_dataset(
101
+ filepath: str,
102
+ mask_fillvalue: Union[bool, Mapping[str, bool]] = True,
103
+ mask_limits: Union[bool, Mapping[str, bool]] = True,
104
+ ) -> xr.Dataset:
105
+ """Open a netCDF dataset as an xarray Dataset
106
+
107
+ Read the netCDF dataset masking the appropiate values, checking
108
+ against the variable attributes `_FillValue`, `valid_min` and `valid_max`.
109
+
110
+ Parameters
111
+ ----------
112
+ filepath: str
113
+ Path where the netCDF file is located
114
+ mask_fill_value: bool | Mapping[str, bool]
115
+ If True, tell `xarray` replace array values equal to `_FillValue` with `NA` and scale values.
116
+ Pass a mapping, e.g. {"my_variable": False}, to toggle this feature per-variable individually.
117
+ If a mapping is passed and a variable is missing, it's understood that the variable is mapped to True by default.
118
+ mask_limits: bool | Mapping[str, bool]
119
+ If True, replace array values lower than `valid_min` or higher than `valid_max` with `NA`, if present.
120
+ Pass a mapping, e.g. {"my_variable": False}, to toggle this feature per-variable individually.
121
+ If a mapping is passed and a variable is missing, it's understood that the variable is mapped to the default value.
122
+ The default value is the one defined for the "__default__" mapping, or True if not specified.
123
+
124
+ Returns
125
+ -------
126
+ ds: xr.Dataset
127
+ Dataset with the information of the netCDF file.
128
+ """
129
+ ds = xr.open_dataset(filepath, engine="netcdf4", mask_and_scale=mask_fillvalue)
130
+ if mask_limits is not False:
131
+ default = True
132
+ if not isinstance(mask_limits, bool):
133
+ default = mask_limits.get("__default__", True)
134
+ for vname, var in ds.data_vars.items():
135
+ mask_limits_var = False
136
+ if isinstance(mask_limits, bool):
137
+ mask_limits_var = mask_limits
138
+ else:
139
+ mask_limits_var = mask_limits.get(vname, default)
140
+ if mask_limits_var:
141
+ values = var.values
142
+ mask = np.ones(values.shape, dtype=bool)
143
+ if hasattr(var, "valid_min"):
144
+ mask &= values >= var.valid_min
145
+ if hasattr(var, "valid_max"):
146
+ mask &= values <= var.valid_max
147
+ var.values = np.where(mask, values, np.nan)
148
+ return ds
149
+
150
+
151
+ def validate_schema(
152
+ dss: pa.DatasetSchema,
153
+ ds: xr.Dataset,
154
+ other_schemas: Iterable[pa.DatasetSchema] = None,
155
+ ):
156
+ """Validates the dataset `ds` against the schema `dss`.
157
+
158
+ Parameters
159
+ ----------
160
+ dss: pandera.xarray.DatasetSchema
161
+ Schema that the dataset is going to be validated against.
162
+ ds: xarray.Dataset
163
+ Dataset to validate.
164
+ other_schemas: Iterable[pandera.xarray.DatasetSchema]
165
+ If the dataset can have multiple schemas, it will be validated
166
+ against those before the canonical one. Once it succeeds in a validation,
167
+ it's understood as validated and finishes the validation.
168
+
169
+ Raises
170
+ ------
171
+ SchemaErrors
172
+ Error set indicating that the dataset is not following the schema.
173
+ """
174
+ if other_schemas is not None:
175
+ for odss in other_schemas:
176
+ try:
177
+ odss.validate(ds)
178
+ return
179
+ except pa.errors.SchemaError:
180
+ pass
181
+ dss.validate(ds, lazy=True)
182
+
183
+
184
+ def _as_ctx(data: Union[xr.Dataset, str, Path]):
185
+ """Returns a context manager that produces an xr.Dataset."""
186
+ if isinstance(data, (str, Path)):
187
+ return xr.open_dataset(data)
188
+ if isinstance(data, xr.Dataset):
189
+ return nullcontext(data)
190
+ raise TypeError(f"expected xr.Dataset, str or Path, got {type(data).__name__}")
glodformat/srf.py ADDED
@@ -0,0 +1,205 @@
1
+ """
2
+ This module contains the functionality that validates Spectral Response Function files
3
+ from GLOD format files.
4
+
5
+ It exports the following functions:
6
+ * validate_srf: Validates that a xarray dataset follows the SRF schema.
7
+ """
8
+ from typing import Union
9
+ from pathlib import Path
10
+ import re
11
+
12
+ import numpy as np
13
+ import xarray as xr
14
+ import pandera.xarray as pa
15
+
16
+ from glodformat.netcdfcommon import (
17
+ validate_schema,
18
+ get_valid_metric_units,
19
+ check_floating,
20
+ check_stringy,
21
+ _as_ctx,
22
+ )
23
+ from glodformat import moon
24
+
25
+
26
+ _UNITS_REGEX = "^(?:" + "|".join(re.escape(v) for v in get_valid_metric_units()) + ")$"
27
+
28
+ _SRF_SCHEMA = pa.DatasetSchema(
29
+ data_vars={ # TODO check if None works in pandera
30
+ "srf": pa.DataArraySchema(
31
+ checks=check_floating, dims=[None, "channel"], nullable=True
32
+ ),
33
+ "wavelength": pa.DataArraySchema(
34
+ checks=check_floating,
35
+ dims=[None, "channel"],
36
+ attrs={"units": _UNITS_REGEX},
37
+ nullable=True,
38
+ ),
39
+ "channel_id": pa.DataArraySchema(checks=check_stringy), # TODO check if works
40
+ },
41
+ coords={"channel": pa.Coordinate(checks=check_floating)},
42
+ )
43
+
44
+ _SRF_OTHER_SCHEMAS = [
45
+ pa.DatasetSchema(
46
+ data_vars={
47
+ "srf": pa.DataArraySchema(
48
+ checks=check_floating, dims=[None, "channel"], nullable=True
49
+ ),
50
+ "wavelength": pa.DataArraySchema(
51
+ checks=check_floating,
52
+ dims=[None, "channel"],
53
+ attrs={"units": _UNITS_REGEX},
54
+ nullable=True,
55
+ ),
56
+ },
57
+ coords={
58
+ "channel": pa.Coordinate(checks=check_floating),
59
+ "channel_id": pa.Coordinate(checks=check_stringy),
60
+ },
61
+ ),
62
+ pa.DatasetSchema(
63
+ data_vars={
64
+ "srf": pa.DataArraySchema(
65
+ checks=check_floating, dims=[None, None], nullable=True
66
+ ),
67
+ "wavelength": pa.DataArraySchema(
68
+ checks=check_floating,
69
+ dims=[None, None],
70
+ attrs={"units": _UNITS_REGEX},
71
+ nullable=True,
72
+ ),
73
+ "channel": pa.DataArraySchema(checks=check_floating),
74
+ "channel_id": pa.DataArraySchema(checks=check_stringy),
75
+ },
76
+ ),
77
+ pa.DatasetSchema(
78
+ data_vars={
79
+ "srf": pa.DataArraySchema(
80
+ checks=check_floating, dims=[None, "wavelength"], nullable=True
81
+ ),
82
+ "channel_id": pa.DataArraySchema(checks=check_stringy),
83
+ },
84
+ coords={
85
+ "channel": pa.Coordinate(checks=check_floating),
86
+ "wavelength": pa.Coordinate(checks=check_floating, nullable=True),
87
+ },
88
+ checks=[
89
+ pa.Check(
90
+ lambda ds: re.match(
91
+ _UNITS_REGEX, ds.coords["wavelength"].attrs.get("units", "")
92
+ )
93
+ )
94
+ ],
95
+ ),
96
+ ]
97
+
98
+
99
+ def _validate_schema_srf(ds: xr.Dataset, strict: bool = False):
100
+ """Validates that a xarray dataset follows the SRF schema.
101
+
102
+ The dataset structure is:
103
+
104
+ coordinates: 'channel': float
105
+ dims without coordinates: Any name, for example 'sample'.
106
+ data_vars: 'srf': float, 'wavelength': float, 'channel_id': str
107
+
108
+ Parameters
109
+ ----------
110
+ ds: xr.Dataset
111
+ Dataset to validate
112
+ strict: bool
113
+ Flag to specify if the check must be exact to the desired schema, or if
114
+ it can pass if it would work in most cases.
115
+ """
116
+ schema = _SRF_SCHEMA
117
+ if strict:
118
+ validate_schema(schema, ds)
119
+ else:
120
+ validate_schema(schema, ds, _SRF_OTHER_SCHEMAS)
121
+
122
+
123
+ def validate_srf(data: Union[xr.Dataset, str, Path], strict: bool = False) -> None:
124
+ """Validates that a dataset follows the SRF schema.
125
+
126
+ Parameters
127
+ ----------
128
+ data : xr.Dataset or str or pathlib.Path
129
+ Dataset to validate, or path to a NetCDF file that will be opened
130
+ and validated. If a path is given, the file is opened in read-only
131
+ mode and closed before returning.
132
+ strict : bool
133
+ Flag to specify if the check must be exact to the desired schema, or
134
+ if it can pass if it would work in most cases.
135
+
136
+ Raises
137
+ ------
138
+ TypeError
139
+ If ``data`` is neither an ``xr.Dataset``, ``str`` nor ``Path``.
140
+ ValueError
141
+ If the dataset does not conform to any of the accepted SRF schemas.
142
+ """
143
+ with _as_ctx(data) as ds:
144
+ _validate_schema_srf(ds, strict=strict)
145
+
146
+
147
+ def _validate_srf_for_obs(ds_srf: xr.Dataset, ds_obs: xr.Dataset) -> None:
148
+ def _channels(da: xr.DataArray) -> set[str]:
149
+ values = da.values
150
+ if values.dtype.kind == "S": # bytes -> str
151
+ values = np.char.decode(values, "utf-8")
152
+ return {str(v).rstrip("\x00") for v in values}
153
+
154
+ obs_ch_names = _channels(ds_obs["channel_name"])
155
+ srf_channel_ids = _channels(ds_srf["channel_id"])
156
+
157
+ missing = obs_ch_names - srf_channel_ids
158
+ if not missing:
159
+ return
160
+
161
+ raise pa.errors.SchemaError(
162
+ schema=ds_srf,
163
+ data=ds_obs,
164
+ message=(
165
+ "SRF is missing channel_ids required by the observation data: "
166
+ f"{sorted(missing)}"
167
+ ),
168
+ check="srf_contains_obs_channels",
169
+ reason_code="DATA",
170
+ )
171
+
172
+
173
+ def validate_srf_for_obs(
174
+ srf_data: Union[xr.Dataset, str, Path],
175
+ obs_data: Union[xr.Dataset, str, Path],
176
+ strict: bool = False,
177
+ ) -> None:
178
+ """Validates that a SRF dataset correctly represents the channels of an observation dataset.
179
+
180
+ Parameters
181
+ ----------
182
+ srf_data : xr.Dataset or str or pathlib.Path
183
+ SRF Dataset to validate, or path to a NetCDF file that will be opened
184
+ and validated. If a path is given, the file is opened in read-only
185
+ mode and closed before returning.
186
+ obs_data : xr.Dataset or str or pathlib.Path
187
+ Observation Dataset to validate, or path to a NetCDF file that will be opened
188
+ and validated. If a path is given, the file is opened in read-only
189
+ mode and closed before returning.
190
+ strict : bool
191
+ Prior to checking the channels, both datasets are validated independently.
192
+ This flag specifies if the check must be exact to the desired schema,
193
+ or if it can pass if it would work in most cases.
194
+
195
+ Raises
196
+ ------
197
+ TypeError
198
+ If ``data`` is neither an ``xr.Dataset``, ``str`` nor ``Path``.
199
+ ValueError
200
+ If the dataset does not conform to any of the accepted SRF schemas.
201
+ """
202
+ validate_srf(srf_data, strict)
203
+ moon.validate_moon_observation(obs_data, strict)
204
+ with _as_ctx(srf_data) as ds_srf, _as_ctx(obs_data) as ds_obs:
205
+ _validate_srf_for_obs(ds_srf, ds_obs)
File without changes
@@ -0,0 +1,18 @@
1
+ """Tests for moon module"""
2
+ import unittest
3
+ from importlib.resources import files, as_file
4
+
5
+ from glodformat.moon import validate_moon_observation
6
+
7
+
8
+ class TestMoon(unittest.TestCase):
9
+ def test_val_moon(self):
10
+ data_file = files("glodformat.tests").joinpath(
11
+ "data/W_XX-EUMETSAT-Darmstadt,VISNIR+SUBSET+MOON,MSG3+SEVIRI_C_EUMG_20140318140112_01.nc"
12
+ )
13
+ with as_file(data_file) as path:
14
+ validate_moon_observation(path, strict=True)
15
+
16
+
17
+ if __name__ == "__main__":
18
+ unittest.main()
@@ -0,0 +1,27 @@
1
+ import unittest
2
+ from importlib.resources import files, as_file
3
+
4
+ from glodformat.srf import validate_srf, validate_srf_for_obs
5
+
6
+
7
+ class TestSRF(unittest.TestCase):
8
+ def test_val_srf(self):
9
+ data_file = files("glodformat.tests").joinpath(
10
+ "data/W_XX-EUMETSAT-Darmstadt_VIS+IR+SRF_MSG3+SEVIRI_C_EUMG.nc"
11
+ )
12
+ with as_file(data_file) as path:
13
+ validate_srf(path, strict=True)
14
+
15
+ def test_val_srf_for_obs(self):
16
+ srf_file = files("glodformat.tests").joinpath(
17
+ "data/W_XX-EUMETSAT-Darmstadt_VIS+IR+SRF_MSG3+SEVIRI_C_EUMG.nc"
18
+ )
19
+ obs_file = files("glodformat.tests").joinpath(
20
+ "data/W_XX-EUMETSAT-Darmstadt,VISNIR+SUBSET+MOON,MSG3+SEVIRI_C_EUMG_20140318140112_01.nc"
21
+ )
22
+ with as_file(srf_file) as srf_path, as_file(obs_file) as obs_path:
23
+ validate_srf_for_obs(srf_path, obs_path, strict=True)
24
+
25
+
26
+ if __name__ == "__main__":
27
+ unittest.main()
@@ -0,0 +1,213 @@
1
+ Metadata-Version: 2.4
2
+ Name: glodformat
3
+ Version: 0.0.1
4
+ Summary: Reader and diagnostics toolkit for GLOD-based netCDF files.
5
+ Author-email: Javier Gatón Herguedas <gaton@goa.uva.es>
6
+ Maintainer-email: Javier Gatón Herguedas <gaton@goa.uva.es>
7
+ License-Expression: LGPL-3.0-only
8
+ Project-URL: Homepage, https://lime.uva.es
9
+ Project-URL: Repository, https://github.com/LIME-ESA/glodformat.git
10
+ Project-URL: Issues, https://github.com/LIME-ESA/glodformat/issues
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Natural Language :: English
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
24
+ Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: netcdf4~=1.7
29
+ Requires-Dist: PyYAML~=6.0
30
+ Requires-Dist: pandera~=0.36.0
31
+ Requires-Dist: xarray>=2024.10.0
32
+ Provides-Extra: dev
33
+ Requires-Dist: pre-commit; extra == "dev"
34
+ Requires-Dist: pytest; extra == "dev"
35
+ Provides-Extra: docs
36
+ Requires-Dist: sphinx~=7.0; extra == "docs"
37
+ Requires-Dist: sphinx_rtd_theme; extra == "docs"
38
+ Requires-Dist: myst_parser; extra == "docs"
39
+ Requires-Dist: sphinxcontrib-openapi; extra == "docs"
40
+ Requires-Dist: linkify-it-py; extra == "docs"
41
+ Dynamic: license-file
42
+
43
+ [![Version 0.0.0](https://img.shields.io/badge/version-0.0.0-informational?style=for-the-badge)](https://github.com/LIME-ESA/glodformat/tags)
44
+ [![License: LGPL 3.0][license-shield]][license-url]
45
+ [![Issues][issues-shield]][issues-url]
46
+ [![Stargazers][stars-shield]][stars-url]
47
+
48
+ <!-- PROJECT LOGO -->
49
+ <br />
50
+ <div align="center">
51
+ <a href="https://lime.uva.es">
52
+ <img src="https://raw.githubusercontent.com/LIME-ESA/glodformat/main/docs/images/glodformat_logo.png" alt="glodformat logo" height="80" style="height: 80px !important;">
53
+ </a>
54
+
55
+ <h3 align="center">glodformat</h3>
56
+
57
+ <p align="center">
58
+ Reader and diagnostics toolkit for GLOD-based netCDF files.
59
+ <br />
60
+ <!--<a href="https://lime-esa.github.io/glodformat/"><strong>Explore the docs »</strong></a>-->
61
+ <br />
62
+ </p>
63
+ </div>
64
+
65
+
66
+ <!-- TABLE OF CONTENTS -->
67
+ <details>
68
+ <summary>Table of Contents</summary>
69
+ <ol>
70
+ <li>
71
+ <a href="#about-the-project">About The Project</a>
72
+ <ul>
73
+ <li><a href="#collaborators">Collaborators</a></li>
74
+ </ul>
75
+ </li>
76
+ <li>
77
+ <a href="#getting-started">Getting Started</a>
78
+ <ul>
79
+ <li><a href="#prerequisites">Prerequisites</a></li>
80
+ <li><a href="#installation">Installation</a></li>
81
+ </ul>
82
+ </li>
83
+ <li><a href="#usage">Usage</a></li>
84
+ <li><a href="#roadmap">Roadmap</a></li>
85
+ <li>
86
+ <a href="#development-guide">Development Guide</a>
87
+ <ul>
88
+ <li><a href="#versioning">Versioning</a></li>
89
+ <li><a href="#setting-up-the-environment">Setting Up the Environment</a></li>
90
+ <li><a href="#testing">Testing</a></li>
91
+ </ul>
92
+ </li>
93
+ <li><a href="#license">License</a></li>
94
+ <li><a href="#authors">Authors</a></li>
95
+ </ol>
96
+ </details>
97
+
98
+
99
+ ## About the project
100
+
101
+ `glodformat` is a Python package that helps validate the format of netCDF files derived
102
+ from GLOD and other specifications.
103
+
104
+ This library was developed for the LIME Toolbox, under the LIME projects development efforts.
105
+ More information about LIME can be found on [lime.uva.es](https://lime.uva.es).
106
+
107
+ ### Collaborators
108
+
109
+ The LIME project is managed, financed and supported by the European Space
110
+ Agency ([ESA](https://esa.int)).
111
+
112
+ The project is a collaboration between multiple institutions, including:
113
+ - [NPL](https://npl.co.uk) (National Physics Laboratory, United Kingdom)
114
+ - [UVa](https://uva.es) (Universidad de Valladolid, Spain)
115
+ - [VITO](https://vito.be) (Vlaamse Instelling voor Technologisch Onderzoek, Belgium)
116
+
117
+ ## Getting started
118
+
119
+ ### Prerequisites
120
+
121
+ - Python >= 3.10
122
+
123
+ ### Installation
124
+
125
+ The package is available at PyPI and can be installed with:
126
+ ```sh
127
+ pip install glodformat
128
+ ```
129
+
130
+ Another option is installing the python package and its dependencies
131
+ directly from the cloned repository in editable mode by using:
132
+ ```sh
133
+ pip install -e .
134
+ ```
135
+
136
+ ## Usage
137
+
138
+ ```python
139
+ import glodformat
140
+
141
+ # Functions accept both paths or loaded xarray datasets
142
+ glodformat.validate_srf("response.nc")
143
+ glodformat.validate_moon_observation("moon_obs.nc")
144
+ glodformat.validate_srf_for_obs("response.nc", "moon_obs.nc")
145
+ ```
146
+
147
+ ## Roadmap
148
+
149
+ - [ ] Fully support checking if an observation file is correctly represented by a SRF file
150
+ - [ ] Add support for LIME Toolbox related formats
151
+
152
+ ## Development Guide
153
+
154
+ ### Versioning
155
+
156
+ Version changes and descriptions are stored in the <a href="./CHANGELOG.md">CHANGELOG</a>.
157
+ This file is updated each time a new version is released.
158
+
159
+ ### Setting Up the Environment
160
+
161
+ To prepare your environment for development, testing or deployment, follow these steps:
162
+
163
+ 1. **Install Pre-commit Hooks**
164
+ Install the `pre-commit` hooks to automatically check code styling:
165
+ ```sh
166
+ pre-commit install
167
+ ```
168
+
169
+ - When you commit changes, `black` will check your code for styling errors.
170
+ - If errors are found, they will be corrected, and the commit will be aborted to allow you to review the changes.
171
+ - If you're satisfied, reattempt the commit.
172
+
173
+ 2. **Install Python Dependencies**
174
+
175
+ Install the python package dependencies listed in the `requirements.txt` file, preferably in a python virtual enironment:
176
+ ```sh
177
+ pip install -r requirements.txt
178
+ ```
179
+
180
+ ### Testing
181
+
182
+ Run the following commands to ensure the code works as expected:
183
+
184
+ 1. **Unit Tests**
185
+ To perform unit tests:
186
+ ```sh
187
+ python3 -m unittest
188
+ ```
189
+
190
+
191
+ ## License
192
+
193
+ Distributed under the LGPL-v3 License. See [LGPL v3](./LICENSE) for more information.
194
+
195
+
196
+ ## Acknowledgements
197
+
198
+ * [Javier Gatón Herguedas](mailto:gaton@goa.uva.es) - [GOA-UVa](https://goa.uva.es)
199
+ * [Pieter De Vis](mailto:pieter.de.vis@npl.co.uk) - [NPL](https://npl.co.uk)
200
+ * [Stefan Adriaensen](mailto:stefan.adriaensen@vito.be) - [VITO](https://vito.be)
201
+ * [Jacob Fahy](mailto:jacob.fahy@npl.co.uk) - [NPL](https://npl.co.uk)
202
+ * [Ramiro González Catón](mailto:ramiro@goa.uva.es) - [GOA-UVa](https://goa.uva.es)
203
+ * [Carlos Toledano](mailto:toledano@goa.uva.es) - [GOA-UVa](https://goa.uva.es)
204
+ * [África Barreto](mailto:abarretov@aemet.es) - [AEMET](https://aemet.es)
205
+ * [Agnieszka Bialek](mailto:agnieszka.bialek@npl.co.uk) - [NPL](https://npl.co.uk)
206
+ * [Marc Bouvet](mailto:marc.bouvet@esa.int) - [ESA](https://esa.int)
207
+
208
+ [stars-shield]: https://img.shields.io/github/stars/LIME-ESA/glodformat.svg?style=for-the-badge
209
+ [stars-url]: https://github.com/LIME-ESA/glodformat/stargazers
210
+ [issues-shield]: https://img.shields.io/github/issues/LIME-ESA/glodformat.svg?style=for-the-badge
211
+ [issues-url]: https://github.com/LIME-ESA/glodformat/issues
212
+ [license-shield]: https://img.shields.io/github/license/LIME-ESA/glodformat.svg?style=for-the-badge
213
+ [license-url]: https://github.com/LIME-ESA/glodformat/blob/main/LICENSE
@@ -0,0 +1,15 @@
1
+ glodformat/__init__.py,sha256=Kbx2Pm0dxZOfhZSoAMG9uO_60o887YnICmrlPjNLwTg,116
2
+ glodformat/_version.py,sha256=6Fr5RRjm7dcNtKqECuaZKcxVk8ULxysaxd4Wt1wFjMo,254
3
+ glodformat/moon.py,sha256=RnwrX-5-ODF_K_7j4hRBcagLJfXb_F7eRSZZUP2vTwo,5043
4
+ glodformat/netcdfcommon.py,sha256=lqDJtof8XaifNOTrc5ETuvFEYRhntEjFsf8_ncrhm18,6924
5
+ glodformat/srf.py,sha256=b1il5tgpBbXdCK7vMt59vEWoTDPyLePMAwKkl9Eecl0,6695
6
+ glodformat/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ glodformat/tests/test_moon.py,sha256=GHh8omMqNQ7FZsXUb8zfdoPiyO7-C6-LAZM5nBWwu-Y,527
8
+ glodformat/tests/test_srf.py,sha256=lfyKfuP4Oc1hOQfLTn4ANrwtTs-HKuTE3IvltK-dMos,952
9
+ "glodformat/tests/data/W_XX-EUMETSAT-Darmstadt,VISNIR+SUBSET+MOON,MSG3+SEVIRI_C_EUMG_20140318140112_01.nc",sha256=9RwunNtejAwKOGE_yb6iR7HNDp6ypXX25m5gtfmXvT8,252693
10
+ glodformat/tests/data/W_XX-EUMETSAT-Darmstadt_VIS+IR+SRF_MSG3+SEVIRI_C_EUMG.nc,sha256=K8NonT30emxud-er42PdWCcsfr-22tcQjWbjYU5InTo,62918
11
+ glodformat-0.0.1.dist-info/licenses/LICENSE,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
12
+ glodformat-0.0.1.dist-info/METADATA,sha256=xLNPYMpP-mcEwA5Iq4jCJYOSqOQ3IsenMon1a0hh4jM,7367
13
+ glodformat-0.0.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
14
+ glodformat-0.0.1.dist-info/top_level.txt,sha256=Eznz5y9S10OcJviz7wFkEo_blPEprE7WNiOrmu-rSic,11
15
+ glodformat-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,165 @@
1
+ GNU LESSER GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+
9
+ This version of the GNU Lesser General Public License incorporates
10
+ the terms and conditions of version 3 of the GNU General Public
11
+ License, supplemented by the additional permissions listed below.
12
+
13
+ 0. Additional Definitions.
14
+
15
+ As used herein, "this License" refers to version 3 of the GNU Lesser
16
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
17
+ General Public License.
18
+
19
+ "The Library" refers to a covered work governed by this License,
20
+ other than an Application or a Combined Work as defined below.
21
+
22
+ An "Application" is any work that makes use of an interface provided
23
+ by the Library, but which is not otherwise based on the Library.
24
+ Defining a subclass of a class defined by the Library is deemed a mode
25
+ of using an interface provided by the Library.
26
+
27
+ A "Combined Work" is a work produced by combining or linking an
28
+ Application with the Library. The particular version of the Library
29
+ with which the Combined Work was made is also called the "Linked
30
+ Version".
31
+
32
+ The "Minimal Corresponding Source" for a Combined Work means the
33
+ Corresponding Source for the Combined Work, excluding any source code
34
+ for portions of the Combined Work that, considered in isolation, are
35
+ based on the Application, and not on the Linked Version.
36
+
37
+ The "Corresponding Application Code" for a Combined Work means the
38
+ object code and/or source code for the Application, including any data
39
+ and utility programs needed for reproducing the Combined Work from the
40
+ Application, but excluding the System Libraries of the Combined Work.
41
+
42
+ 1. Exception to Section 3 of the GNU GPL.
43
+
44
+ You may convey a covered work under sections 3 and 4 of this License
45
+ without being bound by section 3 of the GNU GPL.
46
+
47
+ 2. Conveying Modified Versions.
48
+
49
+ If you modify a copy of the Library, and, in your modifications, a
50
+ facility refers to a function or data to be supplied by an Application
51
+ that uses the facility (other than as an argument passed when the
52
+ facility is invoked), then you may convey a copy of the modified
53
+ version:
54
+
55
+ a) under this License, provided that you make a good faith effort to
56
+ ensure that, in the event an Application does not supply the
57
+ function or data, the facility still operates, and performs
58
+ whatever part of its purpose remains meaningful, or
59
+
60
+ b) under the GNU GPL, with none of the additional permissions of
61
+ this License applicable to that copy.
62
+
63
+ 3. Object Code Incorporating Material from Library Header Files.
64
+
65
+ The object code form of an Application may incorporate material from
66
+ a header file that is part of the Library. You may convey such object
67
+ code under terms of your choice, provided that, if the incorporated
68
+ material is not limited to numerical parameters, data structure
69
+ layouts and accessors, or small macros, inline functions and templates
70
+ (ten or fewer lines in length), you do both of the following:
71
+
72
+ a) Give prominent notice with each copy of the object code that the
73
+ Library is used in it and that the Library and its use are
74
+ covered by this License.
75
+
76
+ b) Accompany the object code with a copy of the GNU GPL and this license
77
+ document.
78
+
79
+ 4. Combined Works.
80
+
81
+ You may convey a Combined Work under terms of your choice that,
82
+ taken together, effectively do not restrict modification of the
83
+ portions of the Library contained in the Combined Work and reverse
84
+ engineering for debugging such modifications, if you also do each of
85
+ the following:
86
+
87
+ a) Give prominent notice with each copy of the Combined Work that
88
+ the Library is used in it and that the Library and its use are
89
+ covered by this License.
90
+
91
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
92
+ document.
93
+
94
+ c) For a Combined Work that displays copyright notices during
95
+ execution, include the copyright notice for the Library among
96
+ these notices, as well as a reference directing the user to the
97
+ copies of the GNU GPL and this license document.
98
+
99
+ d) Do one of the following:
100
+
101
+ 0) Convey the Minimal Corresponding Source under the terms of this
102
+ License, and the Corresponding Application Code in a form
103
+ suitable for, and under terms that permit, the user to
104
+ recombine or relink the Application with a modified version of
105
+ the Linked Version to produce a modified Combined Work, in the
106
+ manner specified by section 6 of the GNU GPL for conveying
107
+ Corresponding Source.
108
+
109
+ 1) Use a suitable shared library mechanism for linking with the
110
+ Library. A suitable mechanism is one that (a) uses at run time
111
+ a copy of the Library already present on the user's computer
112
+ system, and (b) will operate properly with a modified version
113
+ of the Library that is interface-compatible with the Linked
114
+ Version.
115
+
116
+ e) Provide Installation Information, but only if you would otherwise
117
+ be required to provide such information under section 6 of the
118
+ GNU GPL, and only to the extent that such information is
119
+ necessary to install and execute a modified version of the
120
+ Combined Work produced by recombining or relinking the
121
+ Application with a modified version of the Linked Version. (If
122
+ you use option 4d0, the Installation Information must accompany
123
+ the Minimal Corresponding Source and Corresponding Application
124
+ Code. If you use option 4d1, you must provide the Installation
125
+ Information in the manner specified by section 6 of the GNU GPL
126
+ for conveying Corresponding Source.)
127
+
128
+ 5. Combined Libraries.
129
+
130
+ You may place library facilities that are a work based on the
131
+ Library side by side in a single library together with other library
132
+ facilities that are not Applications and are not covered by this
133
+ License, and convey such a combined library under terms of your
134
+ choice, if you do both of the following:
135
+
136
+ a) Accompany the combined library with a copy of the same work based
137
+ on the Library, uncombined with any other library facilities,
138
+ conveyed under the terms of this License.
139
+
140
+ b) Give prominent notice with the combined library that part of it
141
+ is a work based on the Library, and explaining where to find the
142
+ accompanying uncombined form of the same work.
143
+
144
+ 6. Revised Versions of the GNU Lesser General Public License.
145
+
146
+ The Free Software Foundation may publish revised and/or new versions
147
+ of the GNU Lesser General Public License from time to time. Such new
148
+ versions will be similar in spirit to the present version, but may
149
+ differ in detail to address new problems or concerns.
150
+
151
+ Each version is given a distinguishing version number. If the
152
+ Library as you received it specifies that a certain numbered version
153
+ of the GNU Lesser General Public License "or any later version"
154
+ applies to it, you have the option of following the terms and
155
+ conditions either of that published version or of any later version
156
+ published by the Free Software Foundation. If the Library as you
157
+ received it does not specify a version number of the GNU Lesser
158
+ General Public License, you may choose any version of the GNU Lesser
159
+ General Public License ever published by the Free Software Foundation.
160
+
161
+ If the Library as you received it specifies that a proxy can decide
162
+ whether future versions of the GNU Lesser General Public License shall
163
+ apply, that proxy's public statement of acceptance of any version is
164
+ permanent authorization for you to choose that version for the
165
+ Library.
@@ -0,0 +1 @@
1
+ glodformat