Skip to content

Commit c0f0b6d

Browse files
author
Jon Duckworth
authored
Merge pull request #484 from duckontheweb/add/label-ext-summaries
Add Label Extension summaries
2 parents 1415f95 + c99a54b commit c0f0b6d

File tree

4 files changed

+359
-9
lines changed

4 files changed

+359
-9
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
### Added
66

77
- (Experimental) support for Python 3.10 ([#473](https://github.com/stac-utils/pystac/pull/473))
8+
- `LabelTask` enum in `pystac.extensions.label` with recommended values for
9+
`"label:tasks"` field ([#484](https://github.com/stac-utils/pystac/pull/484))
10+
- `LabelMethod` enum in `pystac.extensions.label` with recommended values for
11+
`"label:methods"` field ([#484](https://github.com/stac-utils/pystac/pull/484))
12+
- Label Extension summaries ([#484](https://github.com/stac-utils/pystac/pull/484))
813

914
### Changed
1015

pystac/extensions/label.py

Lines changed: 143 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
"""
55

66
from enum import Enum
7-
from pystac.extensions.base import ExtensionManagementMixin
7+
from pystac.extensions.base import ExtensionManagementMixin, SummariesExtension
88
from typing import Any, Dict, Iterable, List, Optional, Set, Union, cast
99

1010
import pystac
1111
from pystac.serialization.identify import STACJSONDescription, STACVersionID
1212
from pystac.extensions.hooks import ExtensionHooks
13-
from pystac.utils import get_required
13+
from pystac.utils import get_required, map_opt
1414

1515
SCHEMA_URI = "https://stac-extensions.github.io/label/v1.0.0/schema.json"
1616

@@ -52,6 +52,28 @@ def __str__(self) -> str:
5252
"""Convenience attribute for checking if values are valid label types"""
5353

5454

55+
class LabelTask(str, Enum):
56+
"""Enumerates recommended values for "label:tasks" field."""
57+
58+
def __str__(self) -> str:
59+
return str(self.value)
60+
61+
REGRESSION = "regression"
62+
CLASSIFICATION = "classification"
63+
DETECTION = "detection"
64+
SEGMENTATION = "segmentation"
65+
66+
67+
class LabelMethod(str, Enum):
68+
"""Enumerates recommended values for "label:methods" field."""
69+
70+
def __str__(self) -> str:
71+
return str(self.value)
72+
73+
AUTOMATED = "automated"
74+
MANUAL = "manual"
75+
76+
5577
class LabelClasses:
5678
"""Defines the list of possible class names (e.g., tree, building, car, hippo).
5779
@@ -121,6 +143,15 @@ def __repr__(self) -> str:
121143
",".join([str(x) for x in self.classes])
122144
)
123145

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+
124155
def to_dict(self) -> Dict[str, Any]:
125156
"""Returns the dictionary representing the JSON of this instance."""
126157
return self.properties
@@ -180,6 +211,15 @@ def to_dict(self) -> Dict[str, Any]:
180211
"""Returns the dictionary representing the JSON of this instance."""
181212
return self.properties
182213

214+
def __eq__(self, o: object) -> bool:
215+
if isinstance(o, LabelCount):
216+
o = o.to_dict()
217+
218+
if not isinstance(o, dict):
219+
return NotImplemented
220+
221+
return self.to_dict() == o
222+
183223

184224
class LabelStatistics:
185225
"""Contains statistics for regression/continuous numeric value data.
@@ -234,6 +274,15 @@ def to_dict(self) -> Dict[str, Any]:
234274
"""Returns the dictionary representing the JSON of this LabelStatistics."""
235275
return self.properties
236276

277+
def __eq__(self, o: object) -> bool:
278+
if isinstance(o, LabelStatistics):
279+
o = o.to_dict()
280+
281+
if not isinstance(o, dict):
282+
return NotImplemented
283+
284+
return self.to_dict() == o
285+
237286

238287
class LabelOverview:
239288
"""Stores counts (for classification-type data) or summary statistics (for
@@ -379,6 +428,15 @@ def to_dict(self) -> Dict[str, Any]:
379428
"""Returns the dictionary representing the JSON of this LabelOverview."""
380429
return self.properties
381430

431+
def __eq__(self, o: object) -> bool:
432+
if isinstance(o, LabelOverview):
433+
o = o.to_dict()
434+
435+
if not isinstance(o, dict):
436+
return NotImplemented
437+
438+
return self.to_dict() == o
439+
382440

383441
class LabelExtension(ExtensionManagementMixin[pystac.Item]):
384442
"""A class that can be used to extend the properties of an
@@ -403,8 +461,8 @@ def apply(
403461
label_type: LabelType,
404462
label_properties: Optional[List[str]] = None,
405463
label_classes: Optional[List[LabelClasses]] = None,
406-
label_tasks: Optional[List[str]] = None,
407-
label_methods: Optional[List[str]] = None,
464+
label_tasks: Optional[List[Union[LabelTask, str]]] = None,
465+
label_methods: Optional[List[Union[LabelMethod, str]]] = None,
408466
label_overviews: Optional[List[LabelOverview]] = None,
409467
) -> None:
410468
"""Applies label extension properties to the extended Item.
@@ -499,28 +557,28 @@ def label_classes(self, v: Optional[List[LabelClasses]]) -> None:
499557
self.obj.properties[CLASSES_PROP] = classes
500558

501559
@property
502-
def label_tasks(self) -> Optional[List[str]]:
560+
def label_tasks(self) -> Optional[List[Union[LabelTask, str]]]:
503561
"""Gets or set a list of tasks these labels apply to. Usually a subset of 'regression',
504562
'classification', 'detection', or 'segmentation', but may be arbitrary
505563
values."""
506564
return self.obj.properties.get(TASKS_PROP)
507565

508566
@label_tasks.setter
509-
def label_tasks(self, v: Optional[List[str]]) -> None:
567+
def label_tasks(self, v: Optional[List[Union[LabelTask, str]]]) -> None:
510568
if v is None:
511569
self.obj.properties.pop(TASKS_PROP, None)
512570
else:
513571
self.obj.properties[TASKS_PROP] = v
514572

515573
@property
516-
def label_methods(self) -> Optional[List[str]]:
574+
def label_methods(self) -> Optional[List[Union[LabelMethod, str]]]:
517575
"""Gets or set a list of methods used for labeling.
518576
519577
Usually a subset of 'automated' or 'manual', but may be arbitrary values."""
520578
return self.obj.properties.get("label:methods")
521579

522580
@label_methods.setter
523-
def label_methods(self, v: Optional[List[str]]) -> None:
581+
def label_methods(self, v: Optional[List[Union[LabelMethod, str]]]) -> None:
524582
if v is None:
525583
self.obj.properties.pop("label:methods", None)
526584
else:
@@ -655,6 +713,83 @@ def ext(cls, obj: pystac.Item, add_if_missing: bool = False) -> "LabelExtension"
655713
f"Label extension does not apply to type {type(obj)}"
656714
)
657715

716+
@staticmethod
717+
def summaries(obj: pystac.Collection) -> "SummariesLabelExtension":
718+
"""Returns the extended summaries object for the given collection."""
719+
return SummariesLabelExtension(obj)
720+
721+
722+
class SummariesLabelExtension(SummariesExtension):
723+
"""A concrete implementation of :class:`~SummariesExtension` that extends
724+
the ``summaries`` field of a :class:`~pystac.Collection` to include properties
725+
defined in the :stac-ext:`Label Extension <label>`.
726+
"""
727+
728+
@property
729+
def label_properties(self) -> Optional[List[str]]:
730+
"""Get or sets the summary of :attr:`LabelExtension.label_properties` values
731+
for this Collection.
732+
"""
733+
734+
return self.summaries.get_list(PROPERTIES_PROP)
735+
736+
@label_properties.setter
737+
def label_properties(self, v: Optional[List[LabelClasses]]) -> None:
738+
self._set_summary(PROPERTIES_PROP, v)
739+
740+
@property
741+
def label_classes(self) -> Optional[List[LabelClasses]]:
742+
"""Get or sets the summary of :attr:`LabelExtension.label_classes` values
743+
for this Collection.
744+
"""
745+
746+
return map_opt(
747+
lambda classes: [LabelClasses(c) for c in classes],
748+
self.summaries.get_list(CLASSES_PROP),
749+
)
750+
751+
@label_classes.setter
752+
def label_classes(self, v: Optional[List[LabelClasses]]) -> None:
753+
self._set_summary(
754+
CLASSES_PROP, map_opt(lambda classes: [c.to_dict() for c in classes], v)
755+
)
756+
757+
@property
758+
def label_type(self) -> Optional[List[LabelType]]:
759+
"""Get or sets the summary of :attr:`LabelExtension.label_type` values
760+
for this Collection.
761+
"""
762+
763+
return self.summaries.get_list(TYPE_PROP)
764+
765+
@label_type.setter
766+
def label_type(self, v: Optional[List[LabelType]]) -> None:
767+
self._set_summary(TYPE_PROP, v)
768+
769+
@property
770+
def label_tasks(self) -> Optional[List[Union[LabelTask, str]]]:
771+
"""Get or sets the summary of :attr:`LabelExtension.label_tasks` values
772+
for this Collection.
773+
"""
774+
775+
return self.summaries.get_list(TASKS_PROP)
776+
777+
@label_tasks.setter
778+
def label_tasks(self, v: Optional[List[Union[LabelTask, str]]]) -> None:
779+
self._set_summary(TASKS_PROP, v)
780+
781+
@property
782+
def label_methods(self) -> Optional[List[Union[LabelMethod, str]]]:
783+
"""Get or sets the summary of :attr:`LabelExtension.label_methods` values
784+
for this Collection.
785+
"""
786+
787+
return self.summaries.get_list(METHODS_PROP)
788+
789+
@label_methods.setter
790+
def label_methods(self, v: Optional[List[Union[LabelMethod, str]]]) -> None:
791+
self._set_summary(METHODS_PROP, v)
792+
658793

659794
class LabelExtensionHooks(ExtensionHooks):
660795
schema_uri: str = SCHEMA_URI
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"stac_version": "1.0.0-rc.1",
3+
"type": "Collection",
4+
"id": "spacenet-roads-sample",
5+
"description": "A sample of the SpaceNet Roads dataset built during STAC Sprint 4. The dataset contains hand-labeled roads.",
6+
"keywords": [
7+
"spacenet",
8+
"roads",
9+
"labels"
10+
],
11+
"license": "CC-BY-SA-4.0",
12+
"providers": [
13+
{
14+
"name": "SpaceNet",
15+
"roles": [
16+
"licensor",
17+
"host",
18+
"producer",
19+
"processor"
20+
],
21+
"url": "https://spacenet.ai"
22+
}
23+
],
24+
"extent": {
25+
"spatial": {
26+
"bbox": [
27+
[
28+
2.23379639995,
29+
49.0178709,
30+
2.23730639995,
31+
49.0213809
32+
]
33+
]
34+
},
35+
"temporal": {
36+
"interval": [
37+
[
38+
"2016-08-26T22:41:55.000000Z",
39+
null
40+
]
41+
]
42+
}
43+
},
44+
"links": [
45+
{
46+
"href": "roads_collection.json",
47+
"rel": "root",
48+
"title": "sample SpaceNet roads label collection"
49+
},
50+
{
51+
"rel": "item",
52+
"href": "roads_item.json"
53+
}
54+
]
55+
}

0 commit comments

Comments
 (0)