Skip to content

Commit 313c1fd

Browse files
committed
Use get_required and property constants in Label Ext
1 parent 9c2ad6d commit 313c1fd

File tree

1 file changed

+58
-117
lines changed

1 file changed

+58
-117
lines changed

pystac/extensions/label.py

Lines changed: 58 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,20 @@
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
1314

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

17+
PREFIX = "label:"
18+
19+
PROPERTIES_PROP = PREFIX + "properties"
20+
CLASSES_PROP = PREFIX + "classes"
21+
DESCRIPTION_PROP = PREFIX + "description"
22+
TYPE_PROP = PREFIX + "type"
23+
TASKS_PROP = PREFIX + "tasks"
24+
METHODS_PROP = PREFIX + "methods"
25+
OVERVIEWS_PROP = PREFIX + "overviews"
26+
1627

1728
class LabelRelType(str, Enum):
1829
"""A list of rel types defined in the Label Extension.
@@ -44,16 +55,15 @@ def __str__(self) -> str:
4455
class LabelClasses:
4556
"""Defines the list of possible class names (e.g., tree, building, car, hippo).
4657
47-
Use :meth:`LabelClasses.create` to create a new instance of ``LabelClasses`` from
48-
property values.
58+
Use :meth:`LabelClasses.create` to create a new instance from property values.
4959
"""
5060

5161
def __init__(self, properties: Dict[str, Any]):
5262
self.properties = properties
5363

5464
def apply(
5565
self,
56-
classes: Union[List[str], List[int], List[float]],
66+
classes: List[Union[str, int, float]],
5767
name: Optional[str] = None,
5868
) -> None:
5969
"""Sets the properties for this instance.
@@ -70,10 +80,10 @@ def apply(
7080
@classmethod
7181
def create(
7282
cls,
73-
classes: Union[List[str], List[int], List[float]],
83+
classes: List[Union[str, int, float]],
7484
name: Optional[str] = None,
7585
) -> "LabelClasses":
76-
"""Creates a new ``LabelClasses`` instance.
86+
"""Creates a new :class:`~LabelClasses` instance.
7787
7888
Args:
7989
classes : The different possible class values.
@@ -86,48 +96,33 @@ def create(
8696
return c
8797

8898
@property
89-
def classes(self) -> Union[List[str], List[int], List[float]]:
99+
def classes(self) -> List[Union[str, int, float]]:
90100
"""Gets or sets the class values."""
91-
result: Optional[
92-
Union[List[str], List[int], List[float]]
93-
] = self.properties.get("classes")
94-
if result is None:
95-
raise pystac.STACError(
96-
f"LabelClasses does not contain classes property: {self.properties}"
97-
)
98-
return result
101+
return get_required(self.properties.get("classes"), self, "classes")
99102

100103
@classes.setter
101-
def classes(self, v: Union[List[str], List[int], List[float]]) -> None:
102-
if not type(v) is list:
103-
raise pystac.STACError(
104-
"classes must be a list! Invalid input: {}".format(v)
105-
)
106-
104+
def classes(self, v: List[Union[str, int, float]]) -> None:
107105
self.properties["classes"] = v
108106

109107
@property
110108
def name(self) -> Optional[str]:
111109
"""Gets or sets the property key within each Feature in the asset corresponding
112-
to class labels. If labels are raster-formatted, do not supply; required
113-
otherwise.
110+
to class labels. If labels are raster-formatted, use ``None``.
114111
"""
115112
return self.properties.get("name")
116113

117114
@name.setter
118115
def name(self, v: Optional[str]) -> None:
119-
if v is not None:
120-
self.properties["name"] = v
121-
else:
122-
self.properties.pop("name", None)
116+
# The "name" property is required but may be null
117+
self.properties["name"] = v
123118

124119
def __repr__(self) -> str:
125-
return "<LabelClasses classes={}>".format(
120+
return "<ClassObject classes={}>".format(
126121
",".join([str(x) for x in self.classes])
127122
)
128123

129124
def to_dict(self) -> Dict[str, Any]:
130-
"""Returns the dictionary representing the JSON of this LabelClasses."""
125+
"""Returns the dictionary representing the JSON of this instance."""
131126
return self.properties
132127

133128

@@ -141,7 +136,7 @@ def __init__(self, properties: Dict[str, Any]):
141136
self.properties = properties
142137

143138
def apply(self, name: str, count: int) -> None:
144-
"""Sets the properties for this LabelCount.
139+
"""Sets the properties for this instance.
145140
146141
Args:
147142
name : One of the different possible classes within the property.
@@ -152,7 +147,7 @@ def apply(self, name: str, count: int) -> None:
152147

153148
@classmethod
154149
def create(cls, name: str, count: int) -> "LabelCount":
155-
"""Creates a ``LabelCount`` instance.
150+
"""Creates a :class:`LabelCount` instance.
156151
157152
Args:
158153
name : One of the different possible classes within the property.
@@ -165,34 +160,25 @@ def create(cls, name: str, count: int) -> "LabelCount":
165160
@property
166161
def name(self) -> str:
167162
"""Gets or sets the class that this count represents."""
168-
result: Optional[str] = self.properties.get("name")
169-
if result is None:
170-
raise pystac.STACError(
171-
f"Label count has no name property: {self.properties}"
172-
)
173-
return result
163+
return get_required(self.properties.get("name"), self, "name")
174164

175165
@name.setter
176166
def name(self, v: str) -> None:
167+
177168
self.properties["name"] = v
178169

179170
@property
180171
def count(self) -> int:
181172
"""Get or sets the number of occurrences of the class."""
182-
result: Optional[int] = self.properties.get("count")
183-
if result is None:
184-
raise pystac.STACError(
185-
f"Label count has no count property: {self.properties}"
186-
)
187-
return result
173+
return get_required(self.properties.get("count"), self, "count")
188174

189175
@count.setter
190176
def count(self, v: int) -> None:
191177
self.properties["count"] = v
192178

193179
def to_dict(self) -> Dict[str, Any]:
194180
"""Returns the dictionary representing the JSON of this instance."""
195-
return {"name": self.name, "count": self.count}
181+
return self.properties
196182

197183

198184
class LabelStatistics:
@@ -216,7 +202,7 @@ def apply(self, name: str, value: float) -> None:
216202

217203
@classmethod
218204
def create(cls, name: str, value: float) -> "LabelStatistics":
219-
"""Sets the property values for this instance.
205+
"""Creates a new :class:`LabelStatistics` instance.
220206
221207
Args:
222208
name : The name of the statistic being reported.
@@ -229,12 +215,7 @@ def create(cls, name: str, value: float) -> "LabelStatistics":
229215
@property
230216
def name(self) -> str:
231217
"""Gets or sets the name of the statistic being reported."""
232-
result: Optional[str] = self.properties.get("name")
233-
if result is None:
234-
raise pystac.STACError(
235-
f"Label statistics has no name property: {self.properties}"
236-
)
237-
return result
218+
return get_required(self.properties.get("name"), self, "name")
238219

239220
@name.setter
240221
def name(self, v: str) -> None:
@@ -243,20 +224,15 @@ def name(self, v: str) -> None:
243224
@property
244225
def value(self) -> float:
245226
"""Gets or sets the value of the statistic."""
246-
result: Optional[float] = self.properties.get("value")
247-
if result is None:
248-
raise pystac.STACError(
249-
f"Label statistics has no value property: {self.properties}"
250-
)
251-
return result
227+
return get_required(self.properties.get("value"), self, "value")
252228

253229
@value.setter
254230
def value(self, v: float) -> None:
255231
self.properties["value"] = v
256232

257233
def to_dict(self) -> Dict[str, Any]:
258234
"""Returns the dictionary representing the JSON of this LabelStatistics."""
259-
return {"name": self.name, "value": self.value}
235+
return self.properties
260236

261237

262238
class LabelOverview:
@@ -363,11 +339,6 @@ def statistics(self, v: Optional[List[LabelStatistics]]) -> None:
363339
if v is None:
364340
self.properties.pop("statistics", None)
365341
else:
366-
if not isinstance(v, list):
367-
raise pystac.STACError(
368-
"statistics must be a list! Invalid input: {}".format(v)
369-
)
370-
371342
self.properties["statistics"] = [s.to_dict() for s in v]
372343

373344
def merge_counts(self, other: "LabelOverview") -> "LabelOverview":
@@ -471,58 +442,44 @@ class names for each label:properties. (e.g., tree, building, car,
471442
def label_description(self) -> str:
472443
"""Gets or sets a description of the label, how it was created,
473444
and what it is recommended for."""
474-
result: Optional[str] = self.obj.properties.get("label:description")
475-
if result is None:
476-
raise pystac.STACError(f"label:description not set for item {self.obj.id}")
477-
return result
445+
return get_required(
446+
self.obj.properties.get(DESCRIPTION_PROP), self.obj, DESCRIPTION_PROP
447+
)
478448

479449
@label_description.setter
480450
def label_description(self, v: str) -> None:
481-
self.obj.properties["label:description"] = v
451+
self.obj.properties[DESCRIPTION_PROP] = v
482452

483453
@property
484454
def label_type(self) -> LabelType:
485455
"""Gets or sets an Enum of either vector label type or raster label type."""
486-
result = self.obj.properties.get("label:type")
487-
if result is None:
488-
raise pystac.STACError(f"label:type is not set for item {self.obj.id}")
489-
return LabelType(result)
456+
return LabelType(
457+
get_required(self.obj.properties.get(TYPE_PROP), self.obj, TYPE_PROP)
458+
)
490459

491460
@label_type.setter
492461
def label_type(self, v: LabelType) -> None:
493-
if v not in LabelType.ALL:
494-
raise pystac.STACError(
495-
"label_type must be one of "
496-
"{}. Invalid input: {}".format(LabelType.ALL, v)
497-
)
498-
499-
self.obj.properties["label:type"] = v
462+
self.obj.properties[TYPE_PROP] = v
500463

501464
@property
502465
def label_properties(self) -> Optional[List[str]]:
503466
"""Gets or sets the names of the property field(s) in each
504467
Feature of the label asset's FeatureCollection that contains the classes
505468
(keywords from label:classes if the property defines classes).
506469
If labels are rasters, this should be None."""
507-
return self.obj.properties.get("label:properties")
470+
return self.obj.properties.get(PROPERTIES_PROP)
508471

509472
@label_properties.setter
510473
def label_properties(self, v: Optional[List[str]]) -> None:
511-
if v is not None:
512-
if not isinstance(v, list):
513-
raise pystac.STACError(
514-
"label_properties must be a list! Invalid input: {}".format(v)
515-
)
516-
517-
self.obj.properties["label:properties"] = v
474+
self.obj.properties[PROPERTIES_PROP] = v
518475

519476
@property
520477
def label_classes(self) -> Optional[List[LabelClasses]]:
521478
"""Gets or set a list of :class:`LabelClasses` defining the list of possible
522479
class names for each label:properties. (e.g., tree, building, car, hippo).
523480
524481
Optional, but required if using categorical data."""
525-
label_classes = self.obj.properties.get("label:classes")
482+
label_classes = self.obj.properties.get(CLASSES_PROP)
526483
if label_classes is not None:
527484
return [LabelClasses(classes) for classes in label_classes]
528485
else:
@@ -531,34 +488,29 @@ class names for each label:properties. (e.g., tree, building, car, hippo).
531488
@label_classes.setter
532489
def label_classes(self, v: Optional[List[LabelClasses]]) -> None:
533490
if v is None:
534-
self.obj.properties.pop("label:classes", None)
491+
self.obj.properties.pop(CLASSES_PROP, None)
535492
else:
536493
if not isinstance(v, list):
537494
raise pystac.STACError(
538495
"label_classes must be a list! Invalid input: {}".format(v)
539496
)
540497

541498
classes = [x.to_dict() for x in v]
542-
self.obj.properties["label:classes"] = classes
499+
self.obj.properties[CLASSES_PROP] = classes
543500

544501
@property
545502
def label_tasks(self) -> Optional[List[str]]:
546503
"""Gets or set a list of tasks these labels apply to. Usually a subset of 'regression',
547504
'classification', 'detection', or 'segmentation', but may be arbitrary
548505
values."""
549-
return self.obj.properties.get("label:tasks")
506+
return self.obj.properties.get(TASKS_PROP)
550507

551508
@label_tasks.setter
552509
def label_tasks(self, v: Optional[List[str]]) -> None:
553510
if v is None:
554-
self.obj.properties.pop("label:tasks", None)
511+
self.obj.properties.pop(TASKS_PROP, None)
555512
else:
556-
if not isinstance(v, list):
557-
raise pystac.STACError(
558-
"label_tasks must be a list! Invalid input: {}".format(v)
559-
)
560-
561-
self.obj.properties["label:tasks"] = v
513+
self.obj.properties[TASKS_PROP] = v
562514

563515
@property
564516
def label_methods(self) -> Optional[List[str]]:
@@ -572,19 +524,14 @@ def label_methods(self, v: Optional[List[str]]) -> None:
572524
if v is None:
573525
self.obj.properties.pop("label:methods", None)
574526
else:
575-
if not isinstance(v, list):
576-
raise pystac.STACError(
577-
"label_methods must be a list! Invalid input: {}".format(v)
578-
)
579-
580527
self.obj.properties["label:methods"] = v
581528

582529
@property
583530
def label_overviews(self) -> Optional[List[LabelOverview]]:
584531
"""Gets or set a list of :class:`LabelOverview` instances
585532
that store counts (for classification-type data) or summary statistics (for
586533
continuous numerical/regression data)."""
587-
overviews = self.obj.properties.get("label:overviews")
534+
overviews = self.obj.properties.get(OVERVIEWS_PROP)
588535
if overviews is not None:
589536
return [LabelOverview(overview) for overview in overviews]
590537
else:
@@ -593,15 +540,9 @@ def label_overviews(self) -> Optional[List[LabelOverview]]:
593540
@label_overviews.setter
594541
def label_overviews(self, v: Optional[List[LabelOverview]]) -> None:
595542
if v is None:
596-
self.obj.properties.pop("label:overviews", None)
543+
self.obj.properties.pop(OVERVIEWS_PROP, None)
597544
else:
598-
if not isinstance(v, list):
599-
raise pystac.STACError(
600-
"label_overviews must be a list! Invalid input: {}".format(v)
601-
)
602-
603-
overviews = [x.to_dict() for x in v]
604-
self.obj.properties["label:overviews"] = overviews
545+
self.obj.properties[OVERVIEWS_PROP] = [x.to_dict() for x in v]
605546

606547
def __repr__(self) -> str:
607548
return "<LabelItemExt Item id={}>".format(self.obj.id)
@@ -730,16 +671,16 @@ def migrate(
730671
props = obj["properties"]
731672
# Migrate 0.8.0-rc1 non-pluralized forms
732673
# As it's a common mistake, convert for any pre-1.0.0 version.
733-
if "label:property" in props and "label:properties" not in props:
734-
props["label:properties"] = props["label:property"]
674+
if "label:property" in props and PROPERTIES_PROP not in props:
675+
props[PROPERTIES_PROP] = props["label:property"]
735676
del props["label:property"]
736677

737-
if "label:task" in props and "label:tasks" not in props:
738-
props["label:tasks"] = props["label:task"]
678+
if "label:task" in props and TASKS_PROP not in props:
679+
props[TASKS_PROP] = props["label:task"]
739680
del props["label:task"]
740681

741-
if "label:overview" in props and "label:overviews" not in props:
742-
props["label:overviews"] = props["label:overview"]
682+
if "label:overview" in props and OVERVIEWS_PROP not in props:
683+
props[OVERVIEWS_PROP] = props["label:overview"]
743684
del props["label:overview"]
744685

745686
if "label:method" in props and "label:methods" not in props:

0 commit comments

Comments
 (0)