10
10
import pystac
11
11
from pystac .serialization .identify import STACJSONDescription , STACVersionID
12
12
from pystac .extensions .hooks import ExtensionHooks
13
+ from pystac .utils import get_required
13
14
14
15
SCHEMA_URI = "https://stac-extensions.github.io/label/v1.0.0/schema.json"
15
16
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
+
16
27
17
28
class LabelRelType (str , Enum ):
18
29
"""A list of rel types defined in the Label Extension.
@@ -44,16 +55,15 @@ def __str__(self) -> str:
44
55
class LabelClasses :
45
56
"""Defines the list of possible class names (e.g., tree, building, car, hippo).
46
57
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.
49
59
"""
50
60
51
61
def __init__ (self , properties : Dict [str , Any ]):
52
62
self .properties = properties
53
63
54
64
def apply (
55
65
self ,
56
- classes : Union [ List [str ], List [ int ], List [ float ]],
66
+ classes : List [Union [ str , int , float ]],
57
67
name : Optional [str ] = None ,
58
68
) -> None :
59
69
"""Sets the properties for this instance.
@@ -70,10 +80,10 @@ def apply(
70
80
@classmethod
71
81
def create (
72
82
cls ,
73
- classes : Union [ List [str ], List [ int ], List [ float ]],
83
+ classes : List [Union [ str , int , float ]],
74
84
name : Optional [str ] = None ,
75
85
) -> "LabelClasses" :
76
- """Creates a new `` LabelClasses` ` instance.
86
+ """Creates a new :class:`~ LabelClasses` instance.
77
87
78
88
Args:
79
89
classes : The different possible class values.
@@ -86,48 +96,33 @@ def create(
86
96
return c
87
97
88
98
@property
89
- def classes (self ) -> Union [ List [str ], List [ int ], List [ float ]]:
99
+ def classes (self ) -> List [Union [ str , int , float ]]:
90
100
"""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" )
99
102
100
103
@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 :
107
105
self .properties ["classes" ] = v
108
106
109
107
@property
110
108
def name (self ) -> Optional [str ]:
111
109
"""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``.
114
111
"""
115
112
return self .properties .get ("name" )
116
113
117
114
@name .setter
118
115
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
123
118
124
119
def __repr__ (self ) -> str :
125
- return "<LabelClasses classes={}>" .format (
120
+ return "<ClassObject classes={}>" .format (
126
121
"," .join ([str (x ) for x in self .classes ])
127
122
)
128
123
129
124
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 ."""
131
126
return self .properties
132
127
133
128
@@ -141,7 +136,7 @@ def __init__(self, properties: Dict[str, Any]):
141
136
self .properties = properties
142
137
143
138
def apply (self , name : str , count : int ) -> None :
144
- """Sets the properties for this LabelCount .
139
+ """Sets the properties for this instance .
145
140
146
141
Args:
147
142
name : One of the different possible classes within the property.
@@ -152,7 +147,7 @@ def apply(self, name: str, count: int) -> None:
152
147
153
148
@classmethod
154
149
def create (cls , name : str , count : int ) -> "LabelCount" :
155
- """Creates a `` LabelCount` ` instance.
150
+ """Creates a :class:` LabelCount` instance.
156
151
157
152
Args:
158
153
name : One of the different possible classes within the property.
@@ -165,34 +160,25 @@ def create(cls, name: str, count: int) -> "LabelCount":
165
160
@property
166
161
def name (self ) -> str :
167
162
"""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" )
174
164
175
165
@name .setter
176
166
def name (self , v : str ) -> None :
167
+
177
168
self .properties ["name" ] = v
178
169
179
170
@property
180
171
def count (self ) -> int :
181
172
"""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" )
188
174
189
175
@count .setter
190
176
def count (self , v : int ) -> None :
191
177
self .properties ["count" ] = v
192
178
193
179
def to_dict (self ) -> Dict [str , Any ]:
194
180
"""Returns the dictionary representing the JSON of this instance."""
195
- return { "name" : self .name , "count" : self . count }
181
+ return self .properties
196
182
197
183
198
184
class LabelStatistics :
@@ -216,7 +202,7 @@ def apply(self, name: str, value: float) -> None:
216
202
217
203
@classmethod
218
204
def create (cls , name : str , value : float ) -> "LabelStatistics" :
219
- """Sets the property values for this instance.
205
+ """Creates a new :class:`LabelStatistics` instance.
220
206
221
207
Args:
222
208
name : The name of the statistic being reported.
@@ -229,12 +215,7 @@ def create(cls, name: str, value: float) -> "LabelStatistics":
229
215
@property
230
216
def name (self ) -> str :
231
217
"""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" )
238
219
239
220
@name .setter
240
221
def name (self , v : str ) -> None :
@@ -243,20 +224,15 @@ def name(self, v: str) -> None:
243
224
@property
244
225
def value (self ) -> float :
245
226
"""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" )
252
228
253
229
@value .setter
254
230
def value (self , v : float ) -> None :
255
231
self .properties ["value" ] = v
256
232
257
233
def to_dict (self ) -> Dict [str , Any ]:
258
234
"""Returns the dictionary representing the JSON of this LabelStatistics."""
259
- return { "name" : self .name , "value" : self . value }
235
+ return self .properties
260
236
261
237
262
238
class LabelOverview :
@@ -363,11 +339,6 @@ def statistics(self, v: Optional[List[LabelStatistics]]) -> None:
363
339
if v is None :
364
340
self .properties .pop ("statistics" , None )
365
341
else :
366
- if not isinstance (v , list ):
367
- raise pystac .STACError (
368
- "statistics must be a list! Invalid input: {}" .format (v )
369
- )
370
-
371
342
self .properties ["statistics" ] = [s .to_dict () for s in v ]
372
343
373
344
def merge_counts (self , other : "LabelOverview" ) -> "LabelOverview" :
@@ -471,58 +442,44 @@ class names for each label:properties. (e.g., tree, building, car,
471
442
def label_description (self ) -> str :
472
443
"""Gets or sets a description of the label, how it was created,
473
444
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
+ )
478
448
479
449
@label_description .setter
480
450
def label_description (self , v : str ) -> None :
481
- self .obj .properties ["label:description" ] = v
451
+ self .obj .properties [DESCRIPTION_PROP ] = v
482
452
483
453
@property
484
454
def label_type (self ) -> LabelType :
485
455
"""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
+ )
490
459
491
460
@label_type .setter
492
461
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
500
463
501
464
@property
502
465
def label_properties (self ) -> Optional [List [str ]]:
503
466
"""Gets or sets the names of the property field(s) in each
504
467
Feature of the label asset's FeatureCollection that contains the classes
505
468
(keywords from label:classes if the property defines classes).
506
469
If labels are rasters, this should be None."""
507
- return self .obj .properties .get ("label:properties" )
470
+ return self .obj .properties .get (PROPERTIES_PROP )
508
471
509
472
@label_properties .setter
510
473
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
518
475
519
476
@property
520
477
def label_classes (self ) -> Optional [List [LabelClasses ]]:
521
478
"""Gets or set a list of :class:`LabelClasses` defining the list of possible
522
479
class names for each label:properties. (e.g., tree, building, car, hippo).
523
480
524
481
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 )
526
483
if label_classes is not None :
527
484
return [LabelClasses (classes ) for classes in label_classes ]
528
485
else :
@@ -531,34 +488,29 @@ class names for each label:properties. (e.g., tree, building, car, hippo).
531
488
@label_classes .setter
532
489
def label_classes (self , v : Optional [List [LabelClasses ]]) -> None :
533
490
if v is None :
534
- self .obj .properties .pop ("label:classes" , None )
491
+ self .obj .properties .pop (CLASSES_PROP , None )
535
492
else :
536
493
if not isinstance (v , list ):
537
494
raise pystac .STACError (
538
495
"label_classes must be a list! Invalid input: {}" .format (v )
539
496
)
540
497
541
498
classes = [x .to_dict () for x in v ]
542
- self .obj .properties ["label:classes" ] = classes
499
+ self .obj .properties [CLASSES_PROP ] = classes
543
500
544
501
@property
545
502
def label_tasks (self ) -> Optional [List [str ]]:
546
503
"""Gets or set a list of tasks these labels apply to. Usually a subset of 'regression',
547
504
'classification', 'detection', or 'segmentation', but may be arbitrary
548
505
values."""
549
- return self .obj .properties .get ("label:tasks" )
506
+ return self .obj .properties .get (TASKS_PROP )
550
507
551
508
@label_tasks .setter
552
509
def label_tasks (self , v : Optional [List [str ]]) -> None :
553
510
if v is None :
554
- self .obj .properties .pop ("label:tasks" , None )
511
+ self .obj .properties .pop (TASKS_PROP , None )
555
512
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
562
514
563
515
@property
564
516
def label_methods (self ) -> Optional [List [str ]]:
@@ -572,19 +524,14 @@ def label_methods(self, v: Optional[List[str]]) -> None:
572
524
if v is None :
573
525
self .obj .properties .pop ("label:methods" , None )
574
526
else :
575
- if not isinstance (v , list ):
576
- raise pystac .STACError (
577
- "label_methods must be a list! Invalid input: {}" .format (v )
578
- )
579
-
580
527
self .obj .properties ["label:methods" ] = v
581
528
582
529
@property
583
530
def label_overviews (self ) -> Optional [List [LabelOverview ]]:
584
531
"""Gets or set a list of :class:`LabelOverview` instances
585
532
that store counts (for classification-type data) or summary statistics (for
586
533
continuous numerical/regression data)."""
587
- overviews = self .obj .properties .get ("label:overviews" )
534
+ overviews = self .obj .properties .get (OVERVIEWS_PROP )
588
535
if overviews is not None :
589
536
return [LabelOverview (overview ) for overview in overviews ]
590
537
else :
@@ -593,15 +540,9 @@ def label_overviews(self) -> Optional[List[LabelOverview]]:
593
540
@label_overviews .setter
594
541
def label_overviews (self , v : Optional [List [LabelOverview ]]) -> None :
595
542
if v is None :
596
- self .obj .properties .pop ("label:overviews" , None )
543
+ self .obj .properties .pop (OVERVIEWS_PROP , None )
597
544
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 ]
605
546
606
547
def __repr__ (self ) -> str :
607
548
return "<LabelItemExt Item id={}>" .format (self .obj .id )
@@ -730,16 +671,16 @@ def migrate(
730
671
props = obj ["properties" ]
731
672
# Migrate 0.8.0-rc1 non-pluralized forms
732
673
# 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" ]
735
676
del props ["label:property" ]
736
677
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" ]
739
680
del props ["label:task" ]
740
681
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" ]
743
684
del props ["label:overview" ]
744
685
745
686
if "label:method" in props and "label:methods" not in props :
0 commit comments