Skip to content

Commit 7167d6c

Browse files
committed
feat: Properties and RelTypes
1 parent 40a44dc commit 7167d6c

File tree

1 file changed

+162
-18
lines changed

1 file changed

+162
-18
lines changed

pystac/extensions/processing.py

Lines changed: 162 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from __future__ import annotations
77

8+
from datetime import datetime
89
from typing import (
910
Any,
1011
Generic,
@@ -42,15 +43,70 @@
4243

4344
class ProcessingLevel(StringEnum):
4445
RAW = "RAW"
46+
"""Data in their original packets, as received from the instrument."""
4547
L0 = "L0"
48+
"""Reconstructed unprocessed instrument data at full space time resolution with all
49+
available supplemental information to be used in subsequent processing
50+
(e.g., ephemeris, health and safety) appended."""
4651
L1 = "L1"
52+
"""Unpacked, reformatted level 0 data, with all supplemental information to be used
53+
in subsequent processing appended. Optional radiometric and geometric correction
54+
applied to produce parameters in physical units. Data generally presented as full
55+
time/space resolution."""
4756
L1A = "L1A"
57+
"""Unpacked, reformatted level 0 data, with all supplemental information to be used
58+
in subsequent processing appended. Optional radiometric and geometric correction
59+
applied to produce parameters in physical units. Data generally presented as full
60+
time/space resolution."""
4861
L1B = "L1B"
62+
"""Unpacked, reformatted level 0 data, with all supplemental information to be used
63+
in subsequent processing appended. Optional radiometric and geometric correction
64+
applied to produce parameters in physical units. Data generally presented as full
65+
time/space resolution."""
4966
L1C = "L1C"
67+
"""Unpacked, reformatted level 0 data, with all supplemental information to be used
68+
in subsequent processing appended. Optional radiometric and geometric correction
69+
applied to produce parameters in physical units. Data generally presented as full
70+
time/space resolution."""
5071
L2 = "L2"
72+
"""Retrieved environmental variables (e.g., ocean wave height, soil-moisture,
73+
ice concentration) at the same resolution and location as the level 1 source
74+
data."""
5175
L2A = "L2A"
76+
"""Retrieved environmental variables (e.g., ocean wave height, soil-moisture,
77+
ice concentration) at the same resolution and location as the level 1 source
78+
data."""
5279
L3 = "L3"
80+
"""Data or retrieved environmental variables which have been spatially and/or
81+
temporally re-sampled (i.e., derived from level 1 or 2 products). Such
82+
re-sampling may include averaging and compositing."""
5383
L4 = "L4"
84+
"""Model output or results from analyses of lower level data (i.e., variables that
85+
are not directly measured by the instruments, but are derived from these
86+
measurements)"""
87+
88+
89+
class ProcessingRelType(StringEnum):
90+
"""A list of rel types defined in the Processing Extension.
91+
92+
See the :stac-ext:`Processing Extension Relation types
93+
<processing#relation-types>` documentation
94+
for details."""
95+
96+
EXPRESSION = "processing-expression"
97+
"""A processing chain (or script) that describes how the data has been processed."""
98+
99+
EXECUTION = "processing-execution"
100+
"""URL to any resource representing the processing execution
101+
(e.g. OGC Process API)."""
102+
103+
SOFTWARE = "processing-software"
104+
"""URL to any resource that identifies the software and versions used for processing
105+
the data, e.g. a Pipfile.lock (Python) or package-lock.json (NodeJS)."""
106+
107+
VALIDATION = "processing-validation"
108+
"""URL to any kind of validation that has been applied after processing, e.g. a
109+
validation report or a script used for validation."""
54110

55111

56112
class ProcessingExtension(
@@ -74,74 +130,162 @@ class ProcessingExtension(
74130
75131
"""
76132

133+
name: Literal["processing"] = "processing"
134+
77135
def __init__(self: Self, item: pystac.Item) -> None:
78136
self.item = item
79137
self.properties = item.properties
80138

81139
def __repr__(self: Self) -> str:
82140
return f"<ProcessingExtension Item id={self.item.id}>"
83141

84-
def apply(self: Self, level: str | None = None) -> None:
142+
def apply(
143+
self: Self,
144+
level: ProcessingLevel | None = None,
145+
datetime: datetime | None = None,
146+
expression: str | None = None,
147+
lineage: str | None = None,
148+
facility: str | None = None,
149+
version: str | None = None,
150+
software: dict[str, str] | None = None,
151+
) -> None:
152+
"""Applies the processing extension properties to the extended Item.
153+
154+
Args:
155+
level: The processing level of the product. This should be the short name,
156+
as one of the available options under the `ProcessingLevel` enum.
157+
datetime: The datetime when the product was processed. Might be already
158+
specified in the common STAC metadata.
159+
expression: The expression used to obtain the processed product, like
160+
`gdal-calc` or `rio-calc`.
161+
lineage: Free text information about the how observations were processed or
162+
models that were used to create the resource being described.
163+
facility: The name of the facility that produced the data, like ESA.
164+
version: The version of the primary processing software or processing chain
165+
that produced the data, like the processing baseline for the Sentinel
166+
missions.
167+
software: A dictionary describing one or more applications or libraries that
168+
were involved during the production of the data for provenance purposes.
169+
"""
85170
self.level = level
171+
self.datetime = datetime
172+
self.expression = expression
173+
self.lineage = lineage
174+
self.facility = facility
175+
self.version = version
176+
self.software = software
86177

87178
@property
88179
def level(self: Self) -> ProcessingLevel | None:
89-
"""Get or sets the processing level of the object."""
180+
"""Get or sets the processing level as the name commonly used to refer to the
181+
processing level to make it easier to search for product level across
182+
collections or items. This property is expected to be a `ProcessingLevel`"""
90183
return map_opt(
91184
lambda x: ProcessingLevel(x), self._get_property(LEVEL_PROP, str)
92185
)
93186

94187
@level.setter
95188
def level(self: Self, v: ProcessingLevel | None) -> None:
96-
self._set_property(LEVEL_PROP, map_opt(lambda x: x.value, v), pop_if_none=True)
189+
self._set_property(LEVEL_PROP, map_opt(lambda x: x.value, v))
97190

98191
@property
99-
def datetime(self: Self) -> str | None:
100-
return self._get_property(DATETIME_PROP, str)
192+
def datetime(self: Self) -> datetime | None:
193+
"""Gets or set the processing date and time of the corresponding data formatted
194+
according to RFC 3339, section 5.6, in UTC. The time of the processing can be
195+
specified as a global field in processing:datetime, but it can also be specified
196+
directly and individually via the created properties of the target asset as
197+
specified in the STAC Common metadata. See more at
198+
https://github.com/stac-extensions/processing?tab=readme-ov-file#processing-date-time"""
199+
return map_opt(str_to_datetime, self._get_property(DATETIME_PROP, str))
101200

102201
@datetime.setter
103-
def datetime(self: Self, v: str | None) -> None:
104-
self._set_property(DATETIME_PROP, v, pop_if_none=True)
202+
def datetime(self: Self, v: datetime | None) -> None:
203+
self._set_property(DATETIME_PROP, map_opt(datetime_to_str, v))
105204

106205
@property
107-
def expression(self: Self) -> str | None:
108-
return self._get_property(EXPRESSION_PROP, str)
206+
def expression(self: Self) -> dict[str, str | Any] | None:
207+
"""Gets or sets an expression or processing chain that describes how the data
208+
has been processed. Alternatively, you can also link to a processing chain with
209+
the relation type processing-expression.
210+
.. code-block:: python
211+
>>> proc_ext.expression = "(b4-b1)/(b4+b1)"
212+
"""
213+
return self._get_property(EXPRESSION_PROP, dict[str, str | Any])
109214

110215
@expression.setter
111-
def expression(self: Self, v: str | None) -> None:
112-
self._set_property(EXPRESSION_PROP, v, pop_if_none=True)
216+
def expression(self: Self, v: str | Any | None) -> None:
217+
if isinstance(v.expression, str):
218+
exp_format = "string"
219+
elif isinstance(v.expression, object):
220+
exp_format = "object"
221+
else:
222+
raise ValueError(
223+
"The provided expression is not a valid type (string or object)"
224+
)
225+
226+
expression = {
227+
"format": exp_format,
228+
"expression": v,
229+
}
230+
231+
self._set_property(EXPRESSION_PROP, expression)
113232

114233
@property
115234
def lineage(self: Self) -> str | None:
235+
"""Gets or sets the lineage provided as free text information about how
236+
observations were processed or models that were used to create the resource
237+
being described NASA ISO. For example, GRD Post Processing for "GRD" product of
238+
Sentinel-1 satellites. CommonMark 0.29 syntax MAY be used for rich text
239+
representation."""
116240
return self._get_property(LINEAGE_PROP, str)
117241

118242
@lineage.setter
119243
def lineage(self: Self, v: str | None) -> None:
120-
self._set_property(LINEAGE_PROP, v, pop_if_none=True)
244+
self._set_property(LINEAGE_PROP, v)
121245

122246
@property
123247
def facility(self: Self) -> str | None:
248+
"""Gets or sets the name of the facility that produced the data. For example,
249+
Copernicus S1 Core Ground Segment - DPA for product of Sentinel-1 satellites."""
124250
return self._get_property(FACILITY_PROP, str)
125251

126252
@facility.setter
127253
def facility(self: Self, v: str | None) -> None:
128-
self._set_property(FACILITY_PROP, v, pop_if_none=True)
254+
self._set_property(FACILITY_PROP, v)
129255

130256
@property
131257
def version(self: Self) -> str | None:
258+
"""Gets or sets The version of the primary processing software or processing
259+
chain that produced the data. For example, this could be the processing baseline
260+
for the Sentinel missions."""
132261
return self._get_property(VERSION_PROP, str)
133262

134263
@version.setter
135264
def version(self: Self, v: str | None) -> None:
136-
self._set_property(VERSION_PROP, v, pop_if_none=True)
265+
self._set_property(VERSION_PROP, v)
137266

138267
@property
139-
def software(self: Self) -> str | None:
140-
return self._get_property(SOFTWARE_PROP, str)
268+
def software(self: Self) -> dict[str, str] | None:
269+
"""Gets or sets the processing software as a dictionary with name/version for
270+
key/value describing one or more applications or libraries that were involved
271+
during the production of the data for provenance purposes.
272+
273+
They are mostly informative and important to be complete for reproducibility
274+
purposes. Thus, the values in the object can not just be version numbers, but
275+
also be e.g. tag names, commit hashes or similar. For example, you could expose
276+
a simplified version of the Pipfile.lock (Python) or package-lock.json (NodeJS).
277+
If you need more information, you could also link to such files via the relation
278+
type processing-software.
279+
.. code-block:: python
280+
>>> proc_ext.software = {
281+
"Sentinel-1 IPF": "002.71"
282+
}
283+
"""
284+
return self._get_property(SOFTWARE_PROP, dict[str, str])
141285

142286
@software.setter
143-
def software(self: Self, v: str | None) -> None:
144-
self._set_property(SOFTWARE_PROP, v, pop_if_none=True)
287+
def software(self: Self, v: dict[str, str] | None) -> None:
288+
self._set_property(SOFTWARE_PROP, v)
145289

146290
@classmethod
147291
def get_schema_uri(cls) -> str:

0 commit comments

Comments
 (0)