pystac-ext-label 1.0.1__tar.gz

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,163 @@
1
+ *.pyc
2
+ *.egg-info
3
+ *.eggs
4
+ .DS_Store
5
+ data
6
+ config.json
7
+ stdout*
8
+ /integration*
9
+ .idea
10
+ .vscode
11
+ .actrc
12
+
13
+
14
+ # Sphinx documentation
15
+ .ipynb_checkpoints/
16
+
17
+ docs/tutorials/pystac-example*
18
+ docs/tutorials/spacenet-stac/
19
+ docs/tutorials/spacenet-cog-stac/
20
+ docs/tutorials/data/
21
+ docs/quickstart_stac/
22
+
23
+ # Byte-compiled / optimized / DLL files
24
+ __pycache__/
25
+ *.py[cod]
26
+ *$py.class
27
+
28
+ # C extensions
29
+ *.so
30
+
31
+ # Distribution / packaging
32
+ .Python
33
+ build/
34
+ develop-eggs/
35
+ dist/
36
+ downloads/
37
+ eggs/
38
+ .eggs/
39
+ lib/
40
+ lib64/
41
+ parts/
42
+ sdist/
43
+ var/
44
+ wheels/
45
+ share/python-wheels/
46
+ *.egg-info/
47
+ .installed.cfg
48
+ *.egg
49
+ MANIFEST
50
+
51
+ # PyInstaller
52
+ # Usually these files are written by a python script from a template
53
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
54
+ *.manifest
55
+ *.spec
56
+
57
+ # Installer logs
58
+ pip-log.txt
59
+ pip-delete-this-directory.txt
60
+
61
+ # Unit test / coverage reports
62
+ htmlcov/
63
+ .tox/
64
+ .nox/
65
+ .coverage
66
+ .coverage.*
67
+ .cache
68
+ nosetests.xml
69
+ coverage.xml
70
+ *.cover
71
+ *.py,cover
72
+ .hypothesis/
73
+ .pytest_cache/
74
+ cover/
75
+
76
+ # Translations
77
+ *.mo
78
+ *.pot
79
+
80
+ # Django stuff:
81
+ *.log
82
+ local_settings.py
83
+ db.sqlite3
84
+ db.sqlite3-journal
85
+
86
+ # Flask stuff:
87
+ instance/
88
+ .webassets-cache
89
+
90
+ # Scrapy stuff:
91
+ .scrapy
92
+
93
+ # Sphinx documentation
94
+ docs/_build/
95
+
96
+ # PyBuilder
97
+ .pybuilder/
98
+ target/
99
+
100
+ # Jupyter Notebook
101
+ .ipynb_checkpoints
102
+
103
+ # IPython
104
+ profile_default/
105
+ ipython_config.py
106
+
107
+ # pyenv
108
+ # For a library or package, you might want to ignore these files since the code is
109
+ # intended to run in multiple environments; otherwise, check them in:
110
+ # .python-version
111
+
112
+ # pipenv
113
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
114
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
115
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
116
+ # install all needed dependencies.
117
+ # Pipfile.lock
118
+
119
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
120
+ __pypackages__/
121
+
122
+ # Celery stuff
123
+ celerybeat-schedule
124
+ celerybeat.pid
125
+
126
+ # SageMath parsed files
127
+ *.sage.py
128
+
129
+ # Environments
130
+ .env
131
+ .venv
132
+ env/
133
+ venv/
134
+ ENV/
135
+ env.bak/
136
+ venv.bak/
137
+
138
+ # Spyder project settings
139
+ .spyderproject
140
+ .spyproject
141
+
142
+ # Rope project settings
143
+ .ropeproject
144
+
145
+ # mkdocs documentation
146
+ /site
147
+
148
+ # mypy
149
+ .mypy_cache/
150
+ .dmypy.json
151
+ dmypy.json
152
+
153
+ # Pyre type checker
154
+ .pyre/
155
+
156
+ # pytype static type analyzer
157
+ .pytype/
158
+
159
+ # Cython debug symbols
160
+ cython_debug/
161
+
162
+ # asv environments
163
+ .asv
@@ -0,0 +1,38 @@
1
+ Metadata-Version: 2.4
2
+ Name: pystac-ext-label
3
+ Version: 1.0.1
4
+ Summary: Label extension for PySTAC
5
+ Project-URL: Documentation, https://pystac.readthedocs.io
6
+ Project-URL: Repository, https://github.com/stac-utils/pystac
7
+ Project-URL: Issues, https://github.com/stac-utils/pystac/issues
8
+ Project-URL: Changelog, https://github.com/stac-utils/pystac/blob/main/CHANGELOG.md
9
+ Project-URL: Discussions, https://github.com/radiantearth/stac-spec/discussions/categories/stac-software
10
+ License: Apache-2.0
11
+ Keywords: STAC,catalog,imagery,label,pystac,raster
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Natural Language :: English
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: pystac-core
23
+ Description-Content-Type: text/markdown
24
+
25
+ # pystac-ext-label
26
+
27
+ [PySTAC](https://pypi.org/project/pystac/) extension package for the [Label Extension](https://github.com/stac-extensions/label).
28
+ This extension provides fields for describing labeled training data for machine learning, including label types, classes, tasks, methods, and overviews of label distributions.
29
+
30
+ ## Supported versions
31
+
32
+ - [v1.0.1](https://stac-extensions.github.io/label/v1.0.1/schema.json)
33
+ - [v1.0.0](https://stac-extensions.github.io/label/v1.0.0/schema.json)
34
+
35
+ ## Versioning
36
+
37
+ This package's version corresponds to the version of the extension specification it targets.
38
+ When we release updates to the package code without changing the target extension version, we use [post releases](https://packaging.python.org/en/latest/discussions/versioning/#post-releases), e.g. `1.0.1.post1`.
@@ -0,0 +1,14 @@
1
+ # pystac-ext-label
2
+
3
+ [PySTAC](https://pypi.org/project/pystac/) extension package for the [Label Extension](https://github.com/stac-extensions/label).
4
+ This extension provides fields for describing labeled training data for machine learning, including label types, classes, tasks, methods, and overviews of label distributions.
5
+
6
+ ## Supported versions
7
+
8
+ - [v1.0.1](https://stac-extensions.github.io/label/v1.0.1/schema.json)
9
+ - [v1.0.0](https://stac-extensions.github.io/label/v1.0.0/schema.json)
10
+
11
+ ## Versioning
12
+
13
+ This package's version corresponds to the version of the extension specification it targets.
14
+ When we release updates to the package code without changing the target extension version, we use [post releases](https://packaging.python.org/en/latest/discussions/versioning/#post-releases), e.g. `1.0.1.post1`.
@@ -0,0 +1,36 @@
1
+ [project]
2
+ name = "pystac-ext-label"
3
+ description = "Label extension for PySTAC"
4
+ readme = "README.md"
5
+ version = "1.0.1"
6
+ authors = []
7
+ maintainers = []
8
+ keywords = ["pystac", "imagery", "raster", "catalog", "STAC", "label"]
9
+ license = { text = "Apache-2.0" }
10
+ classifiers = [
11
+ "Development Status :: 5 - Production/Stable",
12
+ "Intended Audience :: Developers",
13
+ "License :: OSI Approved :: Apache Software License",
14
+ "Natural Language :: English",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ ]
21
+ requires-python = ">=3.10"
22
+ dependencies = ["pystac-core"]
23
+
24
+ [project.urls]
25
+ Documentation = "https://pystac.readthedocs.io"
26
+ Repository = "https://github.com/stac-utils/pystac"
27
+ Issues = "https://github.com/stac-utils/pystac/issues"
28
+ Changelog = "https://github.com/stac-utils/pystac/blob/main/CHANGELOG.md"
29
+ Discussions = "https://github.com/radiantearth/stac-spec/discussions/categories/stac-software"
30
+
31
+ [build-system]
32
+ requires = ["hatchling"]
33
+ build-backend = "hatchling.build"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["pystac"]
@@ -0,0 +1,851 @@
1
+ """Implements the :stac-ext:`Label Extension <label>`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import warnings
6
+ from collections.abc import Iterable, Sequence
7
+ from typing import Any, Literal, cast
8
+
9
+ import pystac
10
+ from pystac.extensions.base import ExtensionManagementMixin, SummariesExtension
11
+ from pystac.extensions.hooks import ExtensionHooks
12
+ from pystac.serialization.identify import STACJSONDescription, STACVersionID
13
+ from pystac.utils import StringEnum, get_required, map_opt
14
+
15
+ warnings.warn(
16
+ "The PySTAC Label Extension is deprecated. The extension itself "
17
+ '(https://github.com/stac-extensions/label) is currently unmaintained, in "pilot" '
18
+ "maturity, and has significant issues.",
19
+ DeprecationWarning,
20
+ )
21
+
22
+ SCHEMA_URI = "https://stac-extensions.github.io/label/v1.0.1/schema.json"
23
+ SCHEMA_URIS = [
24
+ "https://stac-extensions.github.io/label/v1.0.0/schema.json",
25
+ SCHEMA_URI,
26
+ ]
27
+ PREFIX = "label:"
28
+
29
+ PROPERTIES_PROP = PREFIX + "properties"
30
+ CLASSES_PROP = PREFIX + "classes"
31
+ DESCRIPTION_PROP = PREFIX + "description"
32
+ TYPE_PROP = PREFIX + "type"
33
+ TASKS_PROP = PREFIX + "tasks"
34
+ METHODS_PROP = PREFIX + "methods"
35
+ OVERVIEWS_PROP = PREFIX + "overviews"
36
+
37
+
38
+ class LabelRelType(StringEnum):
39
+ """A list of rel types defined in the Label Extension.
40
+
41
+ See the :stac-ext:`Label Extension Links <label#links-source-imagery>`
42
+ documentation for details.
43
+ """
44
+
45
+ SOURCE = "source"
46
+ """Used to indicate a link to the source item to which a label item applies."""
47
+
48
+
49
+ class LabelType(StringEnum):
50
+ """Enumerates valid label types ("raster" or "vector")."""
51
+
52
+ VECTOR = "vector"
53
+ RASTER = "raster"
54
+
55
+ ALL = [VECTOR, RASTER]
56
+ """Convenience attribute for checking if values are valid label types"""
57
+
58
+
59
+ class LabelTask(StringEnum):
60
+ """Enumerates recommended values for "label:tasks" field."""
61
+
62
+ REGRESSION = "regression"
63
+ CLASSIFICATION = "classification"
64
+ DETECTION = "detection"
65
+ SEGMENTATION = "segmentation"
66
+
67
+
68
+ class LabelMethod(StringEnum):
69
+ """Enumerates recommended values for "label:methods" field."""
70
+
71
+ AUTOMATED = "automated"
72
+ MANUAL = "manual"
73
+
74
+
75
+ class LabelClasses:
76
+ """Defines the list of possible class names (e.g., tree, building, car, hippo).
77
+
78
+ Use :meth:`LabelClasses.create` to create a new instance from property values.
79
+ """
80
+
81
+ properties: dict[str, Any]
82
+
83
+ def __init__(self, properties: dict[str, Any]):
84
+ self.properties = properties
85
+
86
+ def apply(
87
+ self,
88
+ classes: Sequence[str | int | float],
89
+ name: str | None = None,
90
+ ) -> None:
91
+ """Sets the properties for this instance.
92
+
93
+ Args:
94
+ classes : The different possible class values.
95
+ name : The property key within the asset's each Feature corresponding
96
+ to class labels. If labels are raster-formatted, do not supply;
97
+ required otherwise.
98
+ """
99
+ self.classes = classes
100
+ self.name = name
101
+
102
+ @classmethod
103
+ def create(
104
+ cls,
105
+ classes: Sequence[str | int | float],
106
+ name: str | None = None,
107
+ ) -> LabelClasses:
108
+ """Creates a new :class:`~LabelClasses` instance.
109
+
110
+ Args:
111
+ classes : The different possible class values.
112
+ name : The property key within the asset's each Feature corresponding
113
+ to class labels. If labels are raster-formatted, do not supply;
114
+ required otherwise.
115
+ """
116
+ c = cls({})
117
+ c.apply(classes, name)
118
+ return c
119
+
120
+ @property
121
+ def classes(self) -> Sequence[str | int | float]:
122
+ """Gets or sets the class values."""
123
+ return get_required(self.properties.get("classes"), self, "classes")
124
+
125
+ @classes.setter
126
+ def classes(self, v: Sequence[str | int | float]) -> None:
127
+ self.properties["classes"] = v
128
+
129
+ @property
130
+ def name(self) -> str | None:
131
+ """Gets or sets the property key within each Feature in the asset corresponding
132
+ to class labels. If labels are raster-formatted, use ``None``.
133
+ """
134
+ return self.properties.get("name")
135
+
136
+ @name.setter
137
+ def name(self, v: str | None) -> None:
138
+ # The "name" property is required but may be null
139
+ self.properties["name"] = v
140
+
141
+ def __repr__(self) -> str:
142
+ return "<ClassObject classes={}>".format(
143
+ ",".join([str(x) for x in self.classes])
144
+ )
145
+
146
+ def __eq__(self, o: object) -> bool:
147
+ if isinstance(o, LabelClasses):
148
+ o = o.to_dict()
149
+
150
+ if not isinstance(o, dict):
151
+ return NotImplemented
152
+
153
+ return self.to_dict() == o
154
+
155
+ def to_dict(self) -> dict[str, Any]:
156
+ """Returns this label classes object as a dictionary."""
157
+ return self.properties
158
+
159
+
160
+ class LabelCount:
161
+ """Contains counts for categorical data.
162
+
163
+ Use :meth:`LabelCount.create` to create a new instance.
164
+ """
165
+
166
+ properties: dict[str, Any]
167
+
168
+ def __init__(self, properties: dict[str, Any]):
169
+ self.properties = properties
170
+
171
+ def apply(self, name: str, count: int) -> None:
172
+ """Sets the properties for this instance.
173
+
174
+ Args:
175
+ name : One of the different possible classes within the property.
176
+ count : The number of occurrences of the class.
177
+ """
178
+ self.name = name
179
+ self.count = count
180
+
181
+ @classmethod
182
+ def create(cls, name: str, count: int) -> LabelCount:
183
+ """Creates a :class:`LabelCount` instance.
184
+
185
+ Args:
186
+ name : One of the different possible classes within the property.
187
+ count : The number of occurrences of the class.
188
+ """
189
+ x = cls({})
190
+ x.apply(name, count)
191
+ return x
192
+
193
+ @property
194
+ def name(self) -> str:
195
+ """Gets or sets the class that this count represents."""
196
+ return cast(str, get_required(self.properties.get("name"), self, "name"))
197
+
198
+ @name.setter
199
+ def name(self, v: str) -> None:
200
+ self.properties["name"] = v
201
+
202
+ @property
203
+ def count(self) -> int:
204
+ """Get or sets the number of occurrences of the class."""
205
+ return cast(int, get_required(self.properties.get("count"), self, "count"))
206
+
207
+ @count.setter
208
+ def count(self, v: int) -> None:
209
+ self.properties["count"] = v
210
+
211
+ def to_dict(self) -> dict[str, Any]:
212
+ """Returns this label count object as a dictionary."""
213
+ return self.properties
214
+
215
+ def __eq__(self, o: object) -> bool:
216
+ if isinstance(o, LabelCount):
217
+ o = o.to_dict()
218
+
219
+ if not isinstance(o, dict):
220
+ return NotImplemented
221
+
222
+ return self.to_dict() == o
223
+
224
+
225
+ class LabelStatistics:
226
+ """Contains statistics for regression/continuous numeric value data.
227
+
228
+ Use :meth:`LabelStatistics.create` to create a new instance.
229
+ """
230
+
231
+ properties: dict[str, Any]
232
+
233
+ def __init__(self, properties: dict[str, Any]) -> None:
234
+ self.properties = properties
235
+
236
+ def apply(self, name: str, value: float) -> None:
237
+ """Sets the property values for this instance.
238
+
239
+ Args:
240
+ name : The name of the statistic being reported.
241
+ value : The value of the statistic
242
+ """
243
+ self.name = name
244
+ self.value = value
245
+
246
+ @classmethod
247
+ def create(cls, name: str, value: float) -> LabelStatistics:
248
+ """Creates a new :class:`LabelStatistics` instance.
249
+
250
+ Args:
251
+ name : The name of the statistic being reported.
252
+ value : The value of the statistic
253
+ """
254
+ x = cls({})
255
+ x.apply(name, value)
256
+ return x
257
+
258
+ @property
259
+ def name(self) -> str:
260
+ """Gets or sets the name of the statistic being reported."""
261
+ return cast(str, get_required(self.properties.get("name"), self, "name"))
262
+
263
+ @name.setter
264
+ def name(self, v: str) -> None:
265
+ self.properties["name"] = v
266
+
267
+ @property
268
+ def value(self) -> float:
269
+ """Gets or sets the value of the statistic."""
270
+ return cast(float, get_required(self.properties.get("value"), self, "value"))
271
+
272
+ @value.setter
273
+ def value(self, v: float) -> None:
274
+ self.properties["value"] = v
275
+
276
+ def to_dict(self) -> dict[str, Any]:
277
+ """Returns this label statistics object as a dictionary."""
278
+ return self.properties
279
+
280
+ def __eq__(self, o: object) -> bool:
281
+ if isinstance(o, LabelStatistics):
282
+ o = o.to_dict()
283
+
284
+ if not isinstance(o, dict):
285
+ return NotImplemented
286
+
287
+ return self.to_dict() == o
288
+
289
+
290
+ class LabelOverview:
291
+ """Stores counts (for classification-type data) or summary statistics (for
292
+ continuous numerical/regression data).
293
+
294
+ Use :meth:`LabelOverview.create` to create a new instance.
295
+ """
296
+
297
+ properties: dict[str, Any]
298
+
299
+ def __init__(self, properties: dict[str, Any]):
300
+ self.properties = properties
301
+
302
+ def apply(
303
+ self,
304
+ property_key: str | None,
305
+ counts: list[LabelCount] | None = None,
306
+ statistics: list[LabelStatistics] | None = None,
307
+ ) -> None:
308
+ """Sets the properties for this instance.
309
+
310
+ Either ``counts`` or ``statistics``, or both, can be placed in an overview;
311
+ at least one is required.
312
+
313
+ Args:
314
+ property_key : The property key within the asset corresponding to
315
+ class labels that these counts or statistics are referencing. If the
316
+ label data is raster data, this should be None.
317
+ counts: Optional list of :class:`LabelCounts` containing counts
318
+ for categorical data.
319
+ statistics: Optional list of :class:`LabelStatistics` containing statistics
320
+ for regression/continuous numeric value data.
321
+ """
322
+ self.property_key = property_key
323
+ self.counts = counts
324
+ self.statistics = statistics
325
+
326
+ @classmethod
327
+ def create(
328
+ cls,
329
+ property_key: str | None,
330
+ counts: list[LabelCount] | None = None,
331
+ statistics: list[LabelStatistics] | None = None,
332
+ ) -> LabelOverview:
333
+ """Creates a new instance.
334
+
335
+ Either ``counts`` or ``statistics``, or both, can be placed in an overview;
336
+ at least one is required.
337
+
338
+ Args:
339
+ property_key : The property key within the asset corresponding to
340
+ class labels.
341
+ counts: Optional list of :class:`LabelCounts` containing counts for
342
+ categorical data.
343
+ statistics: Optional list of :class:`LabelStatistics` containing statistics
344
+ for regression/continuous numeric value data.
345
+ """
346
+ x = LabelOverview({})
347
+ x.apply(property_key, counts, statistics)
348
+ return x
349
+
350
+ @property
351
+ def property_key(self) -> str | None:
352
+ """Gets or sets the property key within the asset corresponding to class
353
+ labels."""
354
+ return self.properties.get("property_key")
355
+
356
+ @property_key.setter
357
+ def property_key(self, v: str | None) -> None:
358
+ self.properties["property_key"] = v
359
+
360
+ @property
361
+ def counts(self) -> list[LabelCount] | None:
362
+ """Gets or sets the list of :class:`LabelCounts` containing counts for
363
+ categorical data."""
364
+ counts = self.properties.get("counts")
365
+ if counts is None:
366
+ return None
367
+ return [LabelCount(c) for c in counts]
368
+
369
+ @counts.setter
370
+ def counts(self, v: list[LabelCount] | None) -> None:
371
+ if v is None:
372
+ self.properties.pop("counts", None)
373
+ else:
374
+ if not isinstance(v, list):
375
+ raise pystac.STACError(f"counts must be a list! Invalid input: {v}")
376
+
377
+ self.properties["counts"] = [c.to_dict() for c in v]
378
+
379
+ @property
380
+ def statistics(self) -> list[LabelStatistics] | None:
381
+ """Gets or sets the list of :class:`LabelStatistics` containing statistics for
382
+ regression/continuous numeric value data."""
383
+ statistics = self.properties.get("statistics")
384
+ if statistics is None:
385
+ return None
386
+
387
+ return [LabelStatistics(s) for s in statistics]
388
+
389
+ @statistics.setter
390
+ def statistics(self, v: list[LabelStatistics] | None) -> None:
391
+ if v is None:
392
+ self.properties.pop("statistics", None)
393
+ else:
394
+ self.properties["statistics"] = [s.to_dict() for s in v]
395
+
396
+ def merge_counts(self, other: LabelOverview) -> LabelOverview:
397
+ """Merges the counts associated with this overview with another overview.
398
+ Creates a new instance.
399
+
400
+ Args:
401
+ other : The other LabelOverview to merge.
402
+
403
+ Returns:
404
+ A new LabelOverview with the counts merged. This will
405
+ drop any statistics associated with either of the LabelOverviews.
406
+ """
407
+ assert self.property_key == other.property_key
408
+
409
+ if self.counts is None:
410
+ new_counts = other.counts
411
+ else:
412
+ if other.counts is None:
413
+ new_counts = self.counts
414
+ else:
415
+ count_by_prop: dict[str, int] = {}
416
+
417
+ def add_counts(counts: list[LabelCount]) -> None:
418
+ for c in counts:
419
+ if c.name not in count_by_prop:
420
+ count_by_prop[c.name] = c.count
421
+ else:
422
+ count_by_prop[c.name] += c.count
423
+
424
+ add_counts(self.counts)
425
+ add_counts(other.counts)
426
+ new_counts = [LabelCount.create(k, v) for k, v in count_by_prop.items()]
427
+ return LabelOverview.create(self.property_key, counts=new_counts)
428
+
429
+ def to_dict(self) -> dict[str, Any]:
430
+ """Returns this label overview as a dictionary."""
431
+ return self.properties
432
+
433
+ def __eq__(self, o: object) -> bool:
434
+ if isinstance(o, LabelOverview):
435
+ o = o.to_dict()
436
+
437
+ if not isinstance(o, dict):
438
+ return NotImplemented
439
+
440
+ return self.to_dict() == o
441
+
442
+
443
+ class LabelExtension(ExtensionManagementMixin[pystac.Item | pystac.Collection]):
444
+ """A class that can be used to extend the properties of an
445
+ :class:`~pystac.Item` with properties from the :stac-ext:`Label Extension <label>`.
446
+
447
+ To create an instance of :class:`LabeExtension`, use the
448
+ :meth:`LabelExtension.ext` method. For example:
449
+
450
+ .. code-block:: python
451
+
452
+ >>> item: pystac.Item = ...
453
+ >>> label_ext = LabelExtension.ext(item)
454
+ """
455
+
456
+ name: Literal["label"] = "label"
457
+ obj: pystac.Item
458
+ schema_uri: str
459
+
460
+ def __init__(self, item: pystac.Item) -> None:
461
+ self.obj = item
462
+ self.schema_uri = SCHEMA_URI
463
+
464
+ def apply(
465
+ self,
466
+ label_description: str,
467
+ label_type: LabelType,
468
+ label_properties: list[str] | None = None,
469
+ label_classes: list[LabelClasses] | None = None,
470
+ label_tasks: list[LabelTask | str] | None = None,
471
+ label_methods: list[LabelMethod | str] | None = None,
472
+ label_overviews: list[LabelOverview] | None = None,
473
+ ) -> None:
474
+ """Applies label extension properties to the extended Item.
475
+
476
+ Args:
477
+ label_description : A description of the label, how it was created,
478
+ and what it is recommended for
479
+ label_type : An Enum of either vector label type or raster label type. Use
480
+ one of :class:`~pystac.LabelType`.
481
+ label_properties : These are the names of the property field(s) in each
482
+ Feature of the label asset's FeatureCollection that contains the classes
483
+ (keywords from label:classes if the property defines classes).
484
+ If labels are rasters, this should be None.
485
+ label_classes : Optional, but required if using categorical data.
486
+ A list of :class:`LabelClasses` instances defining the list of possible
487
+ class names for each label:properties. (e.g., tree, building, car,
488
+ hippo)
489
+ label_tasks : Recommended to be a subset of 'regression', 'classification',
490
+ 'detection', or 'segmentation', but may be an arbitrary value.
491
+ label_methods: Recommended to be a subset of 'automated' or 'manual',
492
+ but may be an arbitrary value.
493
+ label_overviews : Optional list of :class:`LabelOverview` instances
494
+ that store counts (for classification-type data) or summary statistics
495
+ (for continuous numerical/regression data).
496
+ """
497
+ self.label_description = label_description
498
+ self.label_type = label_type
499
+ self.label_properties = label_properties
500
+ self.label_classes = label_classes
501
+ self.label_tasks = label_tasks
502
+ self.label_methods = label_methods
503
+ self.label_overviews = label_overviews
504
+
505
+ @property
506
+ def label_description(self) -> str:
507
+ """Gets or sets a description of the label, how it was created,
508
+ and what it is recommended for."""
509
+ return cast(
510
+ str,
511
+ get_required(
512
+ self.obj.properties.get(DESCRIPTION_PROP), self.obj, DESCRIPTION_PROP
513
+ ),
514
+ )
515
+
516
+ @label_description.setter
517
+ def label_description(self, v: str) -> None:
518
+ self.obj.properties[DESCRIPTION_PROP] = v
519
+
520
+ @property
521
+ def label_type(self) -> LabelType:
522
+ """Gets or sets an Enum of either vector label type or raster label type."""
523
+ return LabelType(
524
+ get_required(self.obj.properties.get(TYPE_PROP), self.obj, TYPE_PROP)
525
+ )
526
+
527
+ @label_type.setter
528
+ def label_type(self, v: LabelType) -> None:
529
+ self.obj.properties[TYPE_PROP] = v
530
+
531
+ @property
532
+ def label_properties(self) -> list[str] | None:
533
+ """Gets or sets the names of the property field(s) in each
534
+ Feature of the label asset's FeatureCollection that contains the classes
535
+ (keywords from label:classes if the property defines classes).
536
+ If labels are rasters, this should be None."""
537
+ return self.obj.properties.get(PROPERTIES_PROP)
538
+
539
+ @label_properties.setter
540
+ def label_properties(self, v: list[str] | None) -> None:
541
+ self.obj.properties[PROPERTIES_PROP] = v
542
+
543
+ @property
544
+ def label_classes(self) -> list[LabelClasses] | None:
545
+ """Gets or set a list of :class:`LabelClasses` defining the list of possible
546
+ class names for each label:properties. (e.g., tree, building, car, hippo).
547
+
548
+ Optional, but required if using categorical data."""
549
+ label_classes = self.obj.properties.get(CLASSES_PROP)
550
+ if label_classes is not None:
551
+ return [LabelClasses(classes) for classes in label_classes]
552
+ else:
553
+ return None
554
+
555
+ @label_classes.setter
556
+ def label_classes(self, v: list[LabelClasses] | None) -> None:
557
+ if v is None:
558
+ self.obj.properties.pop(CLASSES_PROP, None)
559
+ else:
560
+ if not isinstance(v, list):
561
+ raise pystac.STACError(
562
+ f"label_classes must be a list! Invalid input: {v}"
563
+ )
564
+
565
+ classes = [x.to_dict() for x in v]
566
+ self.obj.properties[CLASSES_PROP] = classes
567
+
568
+ @property
569
+ def label_tasks(self) -> list[LabelTask | str] | None:
570
+ """Gets or set a list of tasks these labels apply to. Usually a subset of
571
+ 'regression', 'classification', 'detection', or 'segmentation', but may be
572
+ arbitrary values."""
573
+ return self.obj.properties.get(TASKS_PROP)
574
+
575
+ @label_tasks.setter
576
+ def label_tasks(self, v: list[LabelTask | str] | None) -> None:
577
+ if v is None:
578
+ self.obj.properties.pop(TASKS_PROP, None)
579
+ else:
580
+ self.obj.properties[TASKS_PROP] = v
581
+
582
+ @property
583
+ def label_methods(self) -> list[LabelMethod | str] | None:
584
+ """Gets or set a list of methods used for labeling.
585
+
586
+ Usually a subset of 'automated' or 'manual', but may be arbitrary values."""
587
+ return self.obj.properties.get("label:methods")
588
+
589
+ @label_methods.setter
590
+ def label_methods(self, v: list[LabelMethod | str] | None) -> None:
591
+ if v is None:
592
+ self.obj.properties.pop("label:methods", None)
593
+ else:
594
+ self.obj.properties["label:methods"] = v
595
+
596
+ @property
597
+ def label_overviews(self) -> list[LabelOverview] | None:
598
+ """Gets or set a list of :class:`LabelOverview` instances
599
+ that store counts (for classification-type data) or summary statistics (for
600
+ continuous numerical/regression data)."""
601
+ overviews = self.obj.properties.get(OVERVIEWS_PROP)
602
+ if overviews is not None:
603
+ return [LabelOverview(overview) for overview in overviews]
604
+ else:
605
+ return None
606
+
607
+ @label_overviews.setter
608
+ def label_overviews(self, v: list[LabelOverview] | None) -> None:
609
+ if v is None:
610
+ self.obj.properties.pop(OVERVIEWS_PROP, None)
611
+ else:
612
+ self.obj.properties[OVERVIEWS_PROP] = [x.to_dict() for x in v]
613
+
614
+ def __repr__(self) -> str:
615
+ return f"<LabelItemExt Item id={self.obj.id}>"
616
+
617
+ def add_source(
618
+ self,
619
+ source_item: pystac.Item,
620
+ title: str | None = None,
621
+ assets: list[str] | None = None,
622
+ ) -> None:
623
+ """Adds a link to a source item.
624
+
625
+ Args:
626
+ source_item : Source imagery that the LabelItem applies to.
627
+ title : Optional title for the link.
628
+ assets : Optional list of assets that determine what
629
+ assets in the source item this label item data applies to.
630
+ """
631
+ extra_fields = None
632
+ if assets is not None:
633
+ extra_fields = {"label:assets": assets}
634
+ link = pystac.Link(
635
+ "source",
636
+ source_item,
637
+ title=title,
638
+ media_type=pystac.MediaType.JSON,
639
+ extra_fields=extra_fields,
640
+ )
641
+ self.obj.add_link(link)
642
+
643
+ def get_sources(self) -> Iterable[pystac.Item]:
644
+ """Gets any source items that describe the source imagery used to generate
645
+ this LabelItem.
646
+
647
+ Returns:
648
+ A possibly empty list of source imagery items. Determined by links of this
649
+ LabelItem that have ``rel=='source'``.
650
+ """
651
+ return map(lambda x: cast(pystac.Item, x), self.obj.get_stac_objects("source"))
652
+
653
+ def add_labels(
654
+ self,
655
+ href: str,
656
+ title: str | None = None,
657
+ media_type: str | None = None,
658
+ properties: dict[str, Any] | None = None,
659
+ ) -> None:
660
+ """Adds a label asset to this LabelItem.
661
+
662
+ Args:
663
+ href : Link to the asset object. Relative and absolute links are both
664
+ allowed.
665
+ title : Optional displayed title for clients and users.
666
+ media_type : Optional description of the media type. Registered Media
667
+ Types are preferred. See :class:`~pystac.MediaType` for common
668
+ media types.
669
+ properties : Optional, additional properties for this asset. This is
670
+ used by extensions as a way to serialize and deserialize properties on
671
+ asset object JSON.
672
+ """
673
+
674
+ self.obj.add_asset(
675
+ "labels",
676
+ pystac.Asset(
677
+ href=href, title=title, media_type=media_type, extra_fields=properties
678
+ ),
679
+ )
680
+
681
+ def add_geojson_labels(
682
+ self,
683
+ href: str,
684
+ title: str | None = None,
685
+ properties: dict[str, Any] | None = None,
686
+ ) -> None:
687
+ """Adds a GeoJSON label asset to this LabelItem.
688
+
689
+ Args:
690
+ href : Link to the asset object. Relative and absolute links are both
691
+ allowed.
692
+ title : Optional displayed title for clients and users.
693
+ properties : Optional, additional properties for this asset. This is
694
+ used by extensions as a way to serialize and deserialize properties on
695
+ asset object JSON.
696
+ """
697
+ self.add_labels(
698
+ href,
699
+ title=title,
700
+ properties=properties,
701
+ media_type=pystac.MediaType.GEOJSON,
702
+ )
703
+
704
+ @classmethod
705
+ def get_schema_uri(cls) -> str:
706
+ return SCHEMA_URI
707
+
708
+ @classmethod
709
+ def get_schema_uris(cls) -> list[str]:
710
+ warnings.warn(
711
+ "get_schema_uris is deprecated and will be removed in v2",
712
+ DeprecationWarning,
713
+ )
714
+ return SCHEMA_URIS
715
+
716
+ @classmethod
717
+ def ext(cls, obj: pystac.Item, add_if_missing: bool = False) -> LabelExtension:
718
+ """Extends the given STAC Object with properties from the :stac-ext:`Label
719
+ Extension <label>`.
720
+
721
+ This extension can be applied to instances of :class:`~pystac.Item`.
722
+ """
723
+ if isinstance(obj, pystac.Item):
724
+ cls.ensure_has_extension(obj, add_if_missing)
725
+ return cls(obj)
726
+ else:
727
+ raise pystac.ExtensionTypeError(cls._ext_error_message(obj))
728
+
729
+ @classmethod
730
+ def summaries(
731
+ cls, obj: pystac.Collection, add_if_missing: bool = False
732
+ ) -> SummariesLabelExtension:
733
+ """Returns the extended summaries object for the given collection."""
734
+ cls.ensure_has_extension(obj, add_if_missing)
735
+ return SummariesLabelExtension(obj)
736
+
737
+
738
+ class SummariesLabelExtension(SummariesExtension):
739
+ """A concrete implementation of :class:`~pystac.extensions.base.SummariesExtension`
740
+ that extends the ``summaries`` field of a :class:`~pystac.Collection` to include
741
+ properties defined in the :stac-ext:`Label Extension <label>`.
742
+ """
743
+
744
+ @property
745
+ def label_properties(self) -> list[str] | None:
746
+ """Get or sets the summary of :attr:`LabelExtension.label_properties` values
747
+ for this Collection.
748
+ """
749
+
750
+ return self.summaries.get_list(PROPERTIES_PROP)
751
+
752
+ @label_properties.setter
753
+ def label_properties(self, v: list[str] | None) -> None:
754
+ self._set_summary(PROPERTIES_PROP, v)
755
+
756
+ @property
757
+ def label_classes(self) -> list[LabelClasses] | None:
758
+ """Get or sets the summary of :attr:`LabelExtension.label_classes` values
759
+ for this Collection.
760
+ """
761
+
762
+ return map_opt(
763
+ lambda classes: [LabelClasses(c) for c in classes],
764
+ self.summaries.get_list(CLASSES_PROP),
765
+ )
766
+
767
+ @label_classes.setter
768
+ def label_classes(self, v: list[LabelClasses] | None) -> None:
769
+ self._set_summary(
770
+ CLASSES_PROP, map_opt(lambda classes: [c.to_dict() for c in classes], v)
771
+ )
772
+
773
+ @property
774
+ def label_type(self) -> list[LabelType] | None:
775
+ """Get or sets the summary of :attr:`LabelExtension.label_type` values
776
+ for this Collection.
777
+ """
778
+
779
+ return self.summaries.get_list(TYPE_PROP)
780
+
781
+ @label_type.setter
782
+ def label_type(self, v: list[LabelType] | None) -> None:
783
+ self._set_summary(TYPE_PROP, v)
784
+
785
+ @property
786
+ def label_tasks(self) -> list[LabelTask | str] | None:
787
+ """Get or sets the summary of :attr:`LabelExtension.label_tasks` values
788
+ for this Collection.
789
+ """
790
+
791
+ return self.summaries.get_list(TASKS_PROP)
792
+
793
+ @label_tasks.setter
794
+ def label_tasks(self, v: list[LabelTask | str] | None) -> None:
795
+ self._set_summary(TASKS_PROP, v)
796
+
797
+ @property
798
+ def label_methods(self) -> list[LabelMethod | str] | None:
799
+ """Get or sets the summary of :attr:`LabelExtension.label_methods` values
800
+ for this Collection.
801
+ """
802
+
803
+ return self.summaries.get_list(METHODS_PROP)
804
+
805
+ @label_methods.setter
806
+ def label_methods(self, v: list[LabelMethod | str] | None) -> None:
807
+ self._set_summary(METHODS_PROP, v)
808
+
809
+
810
+ class LabelExtensionHooks(ExtensionHooks):
811
+ schema_uri: str = SCHEMA_URI
812
+ prev_extension_ids = {
813
+ "label",
814
+ *[uri for uri in SCHEMA_URIS if uri != SCHEMA_URI],
815
+ }
816
+ stac_object_types = {pystac.STACObjectType.ITEM}
817
+
818
+ def get_object_links(
819
+ self, obj: pystac.STACObject
820
+ ) -> list[str | pystac.RelType] | None:
821
+ if isinstance(obj, pystac.Item):
822
+ return [LabelRelType.SOURCE]
823
+ return None
824
+
825
+ def migrate(
826
+ self, obj: dict[str, Any], version: STACVersionID, info: STACJSONDescription
827
+ ) -> None:
828
+ if info.object_type == pystac.STACObjectType.ITEM and version < "1.0.0":
829
+ props = obj["properties"]
830
+ # Migrate 0.8.0-rc1 non-pluralized forms
831
+ # As it's a common mistake, convert for any pre-1.0.0 version.
832
+ if "label:property" in props and PROPERTIES_PROP not in props:
833
+ props[PROPERTIES_PROP] = props["label:property"]
834
+ del props["label:property"]
835
+
836
+ if "label:task" in props and TASKS_PROP not in props:
837
+ props[TASKS_PROP] = props["label:task"]
838
+ del props["label:task"]
839
+
840
+ if "label:overview" in props and OVERVIEWS_PROP not in props:
841
+ props[OVERVIEWS_PROP] = props["label:overview"]
842
+ del props["label:overview"]
843
+
844
+ if "label:method" in props and "label:methods" not in props:
845
+ props["label:methods"] = props["label:method"]
846
+ del props["label:method"]
847
+
848
+ super().migrate(obj, version, info)
849
+
850
+
851
+ LABEL_EXTENSION_HOOKS: ExtensionHooks = LabelExtensionHooks()
File without changes