File: api.py

package info (click to toggle)
xarray-safe-rcm 2024.11.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 212 kB
  • sloc: python: 1,082; sh: 23; makefile: 4
file content (166 lines) | stat: -rw-r--r-- 5,323 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import os
import posixpath
from fnmatch import fnmatchcase

import fsspec
import xarray as xr
from fsspec.implementations.dirfs import DirFileSystem
from tlz.dicttoolz import valmap
from tlz.functoolz import compose_left, curry, juxt

from safe_rcm.calibrations import read_noise_levels
from safe_rcm.manifest import read_manifest
from safe_rcm.product.reader import read_product
from safe_rcm.product.transformers import extract_dataset
from safe_rcm.product.utils import starcall
from safe_rcm.xml import read_xml

try:
    ExceptionGroup
except NameError:
    from exceptiongroup import ExceptionGroup


@curry
def execute(tree, f, path):
    node = tree[path]

    return f(node)


def ignored_file(path, ignores):
    ignored = [
        fnmatchcase(path, ignore) or fnmatchcase(posixpath.basename(path), ignore)
        for ignore in ignores
    ]
    return any(ignored)


def open_rcm(
    url,
    *,
    backend_kwargs=None,
    manifest_ignores=[
        "*.pdf",
        "*.html",
        "*.xslt",
        "*.png",
        "*.kml",
        "*.txt",
        "preview/*",
    ],
    **dataset_kwargs,
):
    """read SAFE files of the radarsat constellation mission (RCM)

    Parameters
    ----------
    url : str
    backend_kwargs : mapping
    manifest_ignores : list of str, default: ["*.pdf", "*.html", "*.xslt", "*.png", \
                                              "*.kml", "*.txt", "preview/*"]
        Globs that match files from the manifest that are allowed to be missing.
    **dataset_kwargs
        Keyword arguments forwarded to `xr.open_dataset`, used to open
        the contained data files.
    """
    if not isinstance(url, (str, os.PathLike)):
        raise ValueError(f"cannot deal with object of type {type(url)}: {url}")

    if backend_kwargs is None:
        backend_kwargs = {}

    url = os.fspath(url)

    storage_options = backend_kwargs.get("storage_options", {})
    mapper = fsspec.get_mapper(url, **storage_options)
    relative_fs = DirFileSystem(path=url, fs=mapper.fs)

    try:
        declared_files = read_manifest(mapper, "manifest.safe")
    except (FileNotFoundError, KeyError):
        raise ValueError(
            "cannot find the `manifest.safe` file. Are you sure this is a SAFE dataset?"
        )

    missing_files = [
        path
        for path in declared_files
        if not ignored_file(path, manifest_ignores) and not relative_fs.exists(path)
    ]
    if missing_files:
        raise ExceptionGroup(
            "not all files declared in the manifest are available",
            [ValueError(f"{p} does not exist") for p in missing_files],
        )

    tree = read_product(mapper, "metadata/product.xml")

    calibration_root = "metadata/calibration"
    lookup_table_structure = {
        "/incidenceAngles": {
            "path": "/imageReferenceAttributes",
            "f": compose_left(
                lambda obj: obj.attrs["incidenceAngleFileName"],
                curry(posixpath.join, calibration_root),
                curry(read_xml, mapper),
                curry(extract_dataset, dims="coefficients"),
            ),
        },
        "/lookupTables": {
            "path": "/imageReferenceAttributes/lookupTableFileName",
            "f": compose_left(
                lambda obj: obj.stack(stacked=["sarCalibrationType", "pole"]),
                lambda obj: obj.reset_index("stacked"),
                juxt(
                    compose_left(
                        lambda obj: obj.to_series().to_dict(),
                        curry(valmap, curry(posixpath.join, calibration_root)),
                        curry(valmap, curry(read_xml)(mapper)),
                        curry(valmap, curry(extract_dataset, dims="coefficients")),
                        curry(valmap, lambda ds: ds["gains"].assign_attrs(ds.attrs)),
                        lambda d: xr.concat(list(d.values()), dim="stacked"),
                    ),
                    lambda obj: obj.coords,
                ),
                curry(starcall, lambda arr, coords: arr.assign_coords(coords)),
                lambda arr: arr.set_index({"stacked": ["sarCalibrationType", "pole"]}),
                lambda arr: arr.unstack("stacked"),
                lambda arr: arr.rename("lookup_tables"),
                lambda arr: arr.to_dataset(),
            ),
        },
        "/noiseLevels": {
            "path": "/imageReferenceAttributes/noiseLevelFileName",
            "f": curry(read_noise_levels, mapper, calibration_root),
        },
    }
    calibration = valmap(
        lambda x: execute(**x)(tree),
        lookup_table_structure,
    )

    imagery_paths = tree["/sceneAttributes/ipdf"].to_series().to_dict()
    resolved = valmap(
        compose_left(
            curry(posixpath.join, "metadata"),
            posixpath.normpath,
        ),
        imagery_paths,
    )
    imagery_dss = valmap(
        compose_left(
            curry(relative_fs.open),
            curry(xr.open_dataset, engine="rasterio", **dataset_kwargs),
        ),
        resolved,
    )
    dss = [ds.assign_coords(pole=coord) for coord, ds in imagery_dss.items()]
    imagery = xr.concat(dss, dim="pole")

    return tree.assign(
        {
            "lookupTables": xr.DataTree.from_dict(calibration),
            "imagery": xr.DataTree(imagery),
        }
    )