s2-rut-python 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.
docs/conf.py ADDED
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env python
2
+ #
3
+ # s2_rut_python documentation build configuration file, created by
4
+ # cookiecutter
5
+ #
6
+ # This file is execfile()d with the current directory set to its
7
+ # containing dir.
8
+ #
9
+ # Note that not all possible configuration values are present in this
10
+ # autogenerated file.
11
+ #
12
+ # All configuration values have a default; values that are commented out
13
+ # serve to show the default.
14
+
15
+ import s2_rut_python
16
+
17
+ project_title = "s2_rut_python".replace("_", " ").title()
18
+
19
+
20
+ # -- General configuration ---------------------------------------------
21
+
22
+ # If your documentation needs a minimal Sphinx version, state it here.
23
+ #
24
+ # needs_sphinx = '1.0'
25
+
26
+ # Attempt to make links automatially
27
+ default_role = "code"
28
+
29
+ # Add any Sphinx extension module names here, as strings. They can be
30
+ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
31
+ # CFAB added napolean to support google-style docstrings
32
+ extensions = [
33
+ "sphinx.ext.autodoc",
34
+ "sphinx.ext.autosummary",
35
+ "sphinx.ext.intersphinx",
36
+ "sphinx.ext.viewcode",
37
+ "sphinx.ext.napoleon",
38
+ "IPython.sphinxext.ipython_directive",
39
+ "IPython.sphinxext.ipython_console_highlighting",
40
+ "sphinx_design",
41
+ ]
42
+
43
+ # Add any paths that contain templates here, relative to this directory.
44
+ templates_path = ["_templates"]
45
+
46
+ # The suffix(es) of source filenames.
47
+ # You can specify multiple suffix as a list of string:
48
+ #
49
+ # source_suffix = ['.rst', '.md']
50
+ source_suffix = ".rst"
51
+
52
+ # The master toctree document.
53
+ master_doc = "index"
54
+
55
+ # General information about the project.
56
+ project = project_title
57
+ copyright = "Jacob Fahy"
58
+ author = "Jacob Fahy"
59
+
60
+ # The version info for the project you're documenting, acts as replacement
61
+ # for |version| and |release|, also used in various other places throughout
62
+ # the built documents.
63
+ #
64
+ # The short X.Y version.
65
+ version = s2_rut_python.__version__
66
+ # The full version, including alpha/beta/rc tags.
67
+ release = s2_rut_python.__version__
68
+
69
+ # The language for content autogenerated by Sphinx. Refer to documentation
70
+ # for a list of supported languages.
71
+ #
72
+ # This is also used if you do content translation via gettext catalogs.
73
+ # Usually you set "language" from the command line for these cases.
74
+ language = None
75
+
76
+ # List of patterns, relative to source directory, that match files and
77
+ # directories to ignore when looking for source files.
78
+ # This patterns also effect to html_static_path and html_extra_path
79
+ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
80
+
81
+ # The name of the Pygments (syntax highlighting) style to use.
82
+ pygments_style = "sphinx"
83
+
84
+ # If true, `todo` and `todoList` produce output, else they produce nothing.
85
+ todo_include_todos = False
86
+
87
+
88
+ # -- Options for HTML output -------------------------------------------
89
+
90
+ # The theme to use for HTML and HTML Help pages. See the documentation for
91
+ # a list of builtin themes.
92
+ #
93
+ html_theme = "sphinx_book_theme"
94
+
95
+ # Theme options are theme-specific and customize the look and feel of a
96
+ # theme further. For a list of options available for each theme, see the
97
+ # documentation.
98
+ #
99
+ html_theme_options = {
100
+ "announcement": "<strong>Beta Version:</strong> This software is a beta version, results should be used with caution. Please share any feedback you have after using the tool.",
101
+ }
102
+
103
+ # Add any paths that contain custom static files (such as style sheets) here,
104
+ # relative to this directory. They are copied after the builtin static files,
105
+ # so a file named "default.css" will overwrite the builtin "default.css".
106
+ html_static_path = ["_static"]
107
+
108
+ # -- Options for HTMLHelp output ---------------------------------------
109
+
110
+ # Output file base name for HTML help builder.
111
+ htmlhelp_basename = "s2_rut_pythondoc"
112
+
113
+
114
+ # -- Options for LaTeX output ------------------------------------------
115
+
116
+ latex_elements = {
117
+ # The paper size ('letterpaper' or 'a4paper').
118
+ #
119
+ # 'papersize': 'letterpaper',
120
+ # The font size ('10pt', '11pt' or '12pt').
121
+ #
122
+ # 'pointsize': '10pt',
123
+ # Additional stuff for the LaTeX preamble.
124
+ #
125
+ # 'preamble': '',
126
+ # Latex figure (float) alignment
127
+ #
128
+ # 'figure_align': 'htbp',
129
+ }
130
+
131
+ # Grouping the document tree into LaTeX files. List of tuples
132
+ # (source start file, target name, title, author, documentclass
133
+ # [howto, manual, or own class]).
134
+ latex_documents = [
135
+ (
136
+ "content/user/user_guide",
137
+ "user_manual.tex",
138
+ "{}: User Guide".format(project_title),
139
+ "Jacob Fahy",
140
+ "manual",
141
+ ),
142
+ (
143
+ "content/user/atbd",
144
+ "atbd.tex",
145
+ "{}: Algorithm Theoretical Basis Document".format(project_title),
146
+ "Jacob Fahy",
147
+ "manual",
148
+ ),
149
+ ]
@@ -0,0 +1,117 @@
1
+ """example: read a Sentinel-2 L1C SAFE product and run S2-RUT."""
2
+
3
+ import os
4
+ import glob
5
+ import socket
6
+
7
+ import matplotlib.pyplot as plt
8
+ from eoio import read
9
+ from shapely import wkt
10
+
11
+ from s2_rut_python.interface import S2RUTTool
12
+
13
+ if (
14
+ socket.gethostname() == "lyon.npl.co.uk"
15
+ or socket.gethostname() == "leipzig.npl.co.uk"
16
+ or "leiden" in socket.gethostname()
17
+ ):
18
+ DATA_DIRECTORY = "/mnt/t/data/"
19
+ else:
20
+ DATA_DIRECTORY = r"T:\ECO\EOServer\data"
21
+
22
+
23
+ # Define input path from test datasets
24
+ SAFE_PATH = os.path.join(
25
+ DATA_DIRECTORY,
26
+ "unittest_datasets",
27
+ "S2MSIL1C",
28
+ "S2A_MSIL1C_20251128T111431_N0511_R137_T30UXE_20251128T121631.SAFE",
29
+ )
30
+ print("SAFE PATH:", SAFE_PATH)
31
+
32
+ # Define ROI
33
+ roi_string = "POLYGON ((\
34
+ -0.913910 53.786950,\
35
+ -0.382900 53.776490,\
36
+ -0.406240 53.464690,\
37
+ -0.925060 53.474770,\
38
+ -0.913910 53.786950\
39
+ ))"
40
+
41
+ geom = wkt.loads(roi_string)
42
+
43
+ # Read the dataset with eoio, selecting only a few bands and auxiliary variables for efficiency.
44
+ bands = ["B01", "B03", "B09"]
45
+ ds = read(
46
+ SAFE_PATH,
47
+ vars_sel={
48
+ "meas": bands,
49
+ "aux": [
50
+ "solar_zenith_angle",
51
+ "solar_azimuth_angle",
52
+ "viewing_zenith_angle",
53
+ "viewing_azimuth_angle",
54
+ ],
55
+ },
56
+ read_params={
57
+ "use_chunks": True,
58
+ "metadata_level": "all",
59
+ "save_extracted": True,
60
+ "ave_va_det": True,
61
+ },
62
+ subset={"roi": geom, "roi_crs": "EPSG:4326"},
63
+ processors={
64
+ "interpolate": {
65
+ "coords": ["y_5000m", "x_5000m"],
66
+ "data_vars": ["solar_zenith_angle"],
67
+ "target_grid": [["y_60m", "y_10m"], ["x_60m", "x_10m"]],
68
+ "method": "linear",
69
+ "inplace": False,
70
+ }
71
+ },
72
+ )
73
+
74
+ # Run S2-RUT on the dataset, computing both random and systematic uncertainties.
75
+ print("Running S2-RUT")
76
+ rut = S2RUTTool()
77
+ ds_out = rut.run(ds=ds, data_vars=bands, group_unc=True)
78
+
79
+
80
+ # Create a single grid plot: rows = bands, columns = [reflectance, random [%], systematic [%]].
81
+ fig, axes = plt.subplots(
82
+ nrows=len(bands),
83
+ ncols=3,
84
+ figsize=(15, 4 * len(bands)),
85
+ )
86
+
87
+ # Handle single band case (axes is 1D instead of 2D).
88
+ if len(bands) == 1:
89
+ axes = axes.reshape(1, -1)
90
+
91
+ for i, band in enumerate(bands):
92
+ # Column 0: Reflectance
93
+ ds_out[band].plot(ax=axes[i, 0], robust=True)
94
+ axes[i, 0].set_title(f"{band} Reflectance")
95
+ axes[i, 0].set_xlabel("")
96
+ axes[i, 0].set_ylabel("")
97
+
98
+ # Column 1: Random uncertainty [%]
99
+ random_unc_pct = 100.0 * ds_out[f"u_random_{band}"] / ds_out[band]
100
+ random_unc_pct.plot(ax=axes[i, 1], robust=True)
101
+ axes[i, 1].set_title(f"{band} Random Uncertainty [%]")
102
+ axes[i, 1].set_xlabel("")
103
+ axes[i, 1].set_ylabel("")
104
+
105
+ # Column 2: Systematic uncertainty [%]
106
+ sys_unc_pct = 100.0 * ds_out[f"u_systematic_{band}"] / ds_out[band]
107
+ sys_unc_pct.plot(ax=axes[i, 2], robust=True)
108
+ axes[i, 2].set_title(f"{band} Systematic Uncertainty [%]")
109
+ axes[i, 2].set_xlabel("")
110
+ axes[i, 2].set_ylabel("")
111
+
112
+ plt.tight_layout()
113
+ plt.savefig("s2rut_example.png")
114
+
115
+
116
+ if __name__ == "__main__":
117
+ pass
@@ -0,0 +1,202 @@
1
+ import sys
2
+ import os
3
+
4
+ import matplotlib
5
+ from eoio import read
6
+
7
+ import s2_rut_python._vendor # noqa: F401
8
+
9
+ from S2RUT import S2RUT_L1
10
+
11
+ import os
12
+
13
+ this_directory = os.path.dirname(__file__)
14
+
15
+ file = os.path.abspath(
16
+ os.path.join(
17
+ os.path.dirname(this_directory),
18
+ "third-party",
19
+ "Data",
20
+ "S2A_MSIL1C_20210310T084801_N0500_R107_T33KWP_20230525T152740.SAFE",
21
+ )
22
+ )
23
+ # Open product using eoio
24
+ ds = read(
25
+ file,
26
+ vars_sel={
27
+ "meas": ["B01"], # Read only a few bands for efficiency
28
+ "aux": [
29
+ "solar_zenith_angle",
30
+ "solar_azimuth_angle",
31
+ "viewing_zenith_angle",
32
+ "viewing_azimuth_angle",
33
+ ],
34
+ },
35
+ read_params={
36
+ "use_chunks": False,
37
+ "metadata_level": "all",
38
+ "save_extracted": True,
39
+ "ave_va_det": True, # Average over the detector dimension for the angle variables
40
+ },
41
+ processors={
42
+ "interpolate": {
43
+ "coords": ["y_5000m", "x_5000m"],
44
+ "data_vars": ["solar_zenith_angle"],
45
+ "target_grid": ["y_60m", "x_60m"],
46
+ "method": "linear",
47
+ "inplace": True,
48
+ },
49
+ },
50
+ )
51
+
52
+ print(ds.data_vars)
53
+ # print(ds['solar_zenith_angle'])
54
+ print(ds.coords)
55
+
56
+ # Compute uncertainty (absolute value, in reflectance dimension)
57
+ input_contributors = os.path.abspath(
58
+ os.path.join(
59
+ os.path.dirname(this_directory), "third-party", "Data", "unc_contributors.json"
60
+ )
61
+ )
62
+
63
+ metadata = {
64
+ "spacecraft": None,
65
+ "quant": None,
66
+ "A": [],
67
+ "offset": [],
68
+ "alpha": {},
69
+ "beta": {},
70
+ "Esun": [],
71
+ "Usun": None,
72
+ "refined": False,
73
+ } # this dictionary contains the relevant metadata parameters
74
+ # read required metadata from ds.attrs and fill the metadata dictionary
75
+ METADATA_MAP = {
76
+ "spacecraft": "platform",
77
+ "quant": "quantification_level",
78
+ "Usun": "reflectance_conversion_u",
79
+ }
80
+ VAR_MTD_MAP = {
81
+ "Esun": "solar_irradiance",
82
+ "alpha": "noise_model_alpha",
83
+ "beta": "noise_model_beta",
84
+ "A": "physical_gains",
85
+ "offset": "radiometric_offset",
86
+ }
87
+
88
+ for key, path in METADATA_MAP.items():
89
+ if path in ds.attrs:
90
+ metadata[key] = ds.attrs[path]
91
+ elif path in ds.attrs["product_metadata"]:
92
+ metadata[key] = ds.attrs["product_metadata"][path]
93
+ else:
94
+ raise KeyError(
95
+ f"Required metadata parameter '{key}' not found in dataset attributes at path '{path}'."
96
+ )
97
+
98
+ for key, path in VAR_MTD_MAP.items():
99
+ metadata[key] = {
100
+ band: ds[band].attrs["product_metadata"][path]
101
+ for band in ds.data_vars
102
+ if "B" in band
103
+ }
104
+
105
+ print("Metadata extracted for uncertainty calculation:", metadata)
106
+ RUTl1 = S2RUT_L1(input_contributors)
107
+ u_ref, u_cont = RUTl1.unc_calculation_abs(
108
+ ds["B01"].values,
109
+ "B01",
110
+ 0,
111
+ metadata,
112
+ [ds["solar_zenith_angle"].values],
113
+ do_contributor=True,
114
+ )
115
+
116
+
117
+ print("Uncertainty calculated for B01:", u_ref, u_cont)
118
+ print("Shape of u_ref:", u_ref.shape)
119
+ print("u_cont keys:", u_cont.keys())
120
+
121
+
122
+ from s2_rut_python.interface import S2RUTTool
123
+
124
+ rut_processor = S2RUTTool()
125
+ ds = rut_processor.run(
126
+ ds,
127
+ data_vars=["B01"],
128
+ group_unc=False, # set to True to compute group uncertainties (systematic and random)
129
+ )
130
+
131
+ # Check the resulting dataset
132
+ print("Data variables in resulting dataset:", ds.data_vars)
133
+ print(ds.unc)
134
+
135
+ # compare with reference uncertainty
136
+ import matplotlib.pyplot as plt
137
+
138
+ # plt.imshow(ds.B01.values)
139
+ # plt.title("B01 reflectance")
140
+ # plt.colorbar()
141
+ # plt.show()
142
+ # for unc in ds.data_vars:
143
+ # if 'u_' in unc:
144
+ # print(f"Comparing {unc} with reference uncertainty...")
145
+ # if unc.split('_B01')[0] in u_cont:
146
+ # plt.imshow(ds[unc].values - u_cont[unc.split('_B01')[0]])
147
+ # plt.title(f"Difference between {unc} and reference uncertainty")
148
+ # plt.colorbar()
149
+ # plt.show()
150
+
151
+ ds = read(
152
+ file,
153
+ vars_sel={
154
+ "meas": ["B01"], # Read only a few bands for efficiency
155
+ "aux": [
156
+ "solar_zenith_angle",
157
+ "solar_azimuth_angle",
158
+ "viewing_zenith_angle",
159
+ "viewing_azimuth_angle",
160
+ ],
161
+ },
162
+ read_params={
163
+ "use_chunks": False,
164
+ "metadata_level": "all",
165
+ "save_extracted": True,
166
+ "ave_va_det": True, # Average over the detector dimension for the angle variables
167
+ },
168
+ processors={
169
+ "interpolate": {
170
+ "coords": ["y_5000m", "x_5000m"],
171
+ "data_vars": ["solar_zenith_angle"],
172
+ "target_grid": ["y_60m", "x_60m"],
173
+ "method": "linear",
174
+ "inplace": True,
175
+ },
176
+ "s2_rut": {
177
+ "data_vars": ["B01"],
178
+ "group_unc": True, # set to True to compute group uncertainties (systematic and random)
179
+ },
180
+ },
181
+ )
182
+
183
+ print("Data variables in resulting dataset after s2_rut processor:", ds.data_vars)
184
+ print(ds.unc)
185
+
186
+ for unc in ds.data_vars:
187
+ if "u_" in unc:
188
+ print(f"Comparing {unc} with reference uncertainty...")
189
+ if unc.split("_B01")[0] in u_cont:
190
+ plt.imshow(ds[unc].values - u_cont[unc.split("_B01")[0]])
191
+ plt.title(f"Difference between {unc} and reference uncertainty")
192
+ plt.colorbar()
193
+ plt.show()
194
+ else:
195
+ import numpy as np
196
+
197
+ (100 * ds[unc] / ds["B01"]).plot(
198
+ robust=True
199
+ ) # , vmin=np.nanpercentile(100*ds[unc].values / ds['B01'].values, 1), vmax=np.nanpercentile(100*ds[unc].values / ds['B01'].values, 99)) # relative uncertainty in percentage
200
+ plt.title(f"{unc} (no reference uncertainty available)")
201
+ # plt.colorbar()
202
+ plt.show()
@@ -0,0 +1,9 @@
1
+ """s2_rut_python - Pure Python port of SNAP's Sentinel-2 radiometric uncertainty tool"""
2
+
3
+ __author__ = "Sam Hunt <sam.hunt@npl.co.uk>"
4
+ __all__: list = []
5
+
6
+ from ._version import get_versions
7
+
8
+ __version__ = get_versions()["version"]
9
+ del get_versions
@@ -0,0 +1,19 @@
1
+ """
2
+ Internal helper to make vendored third-party S2-RUT code importable.
3
+
4
+ This module adjusts sys.path to include the vendored subtree.
5
+ Do not modify without updating the subtree integration.
6
+ """
7
+
8
+ import os
9
+ import sys
10
+
11
+ _BASE_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
12
+ _VENDOR_DIR = os.path.join(
13
+ _BASE_DIR,
14
+ "third-party",
15
+ "Source",
16
+ )
17
+
18
+ if _VENDOR_DIR not in sys.path:
19
+ sys.path.insert(0, _VENDOR_DIR)
@@ -0,0 +1,21 @@
1
+
2
+ # This file was generated by 'versioneer.py' (0.29) from
3
+ # revision-control system data, or from the parent directory name of an
4
+ # unpacked source archive. Distribution tarballs contain a pre-generated copy
5
+ # of this file.
6
+
7
+ import json
8
+
9
+ version_json = '''
10
+ {
11
+ "date": "2026-04-11T21:23:36+0100",
12
+ "dirty": false,
13
+ "error": null,
14
+ "full-revisionid": "53f4b6263d29cba0b31d24f7520fab6bd921c56a",
15
+ "version": "0.0.1"
16
+ }
17
+ ''' # END VERSION_JSON
18
+
19
+
20
+ def get_versions():
21
+ return json.loads(version_json)