11import json
22from dataclasses import dataclass , field
33from enum import Enum
4- from typing import Dict , List , Optional , Sequence , Union
4+ from typing import Dict , List , Optional , Sequence , Type , Union
55from urllib .parse import urlparse
66
77from .constants import (
1616 INDEX_KEY ,
1717 LABEL_KEY ,
1818 LABELS_KEY ,
19+ LINE_TYPE ,
1920 MASK_TYPE ,
2021 MASK_URL_KEY ,
2122 METADATA_KEY ,
@@ -46,18 +47,17 @@ class Annotation:
4647 @classmethod
4748 def from_json (cls , payload : dict ):
4849 """Instantiates annotation object from schematized JSON dict payload."""
49- if payload .get (TYPE_KEY , None ) == BOX_TYPE :
50- return BoxAnnotation .from_json (payload )
51- elif payload .get (TYPE_KEY , None ) == POLYGON_TYPE :
52- return PolygonAnnotation .from_json (payload )
53- elif payload .get (TYPE_KEY , None ) == CUBOID_TYPE :
54- return CuboidAnnotation .from_json (payload )
55- elif payload .get (TYPE_KEY , None ) == CATEGORY_TYPE :
56- return CategoryAnnotation .from_json (payload )
57- elif payload .get (TYPE_KEY , None ) == MULTICATEGORY_TYPE :
58- return MultiCategoryAnnotation .from_json (payload )
59- else :
60- return SegmentationAnnotation .from_json (payload )
50+ type_key_to_type : Dict [str , Type [Annotation ]] = {
51+ BOX_TYPE : BoxAnnotation ,
52+ LINE_TYPE : LineAnnotation ,
53+ POLYGON_TYPE : PolygonAnnotation ,
54+ CUBOID_TYPE : CuboidAnnotation ,
55+ CATEGORY_TYPE : CategoryAnnotation ,
56+ MULTICATEGORY_TYPE : MultiCategoryAnnotation ,
57+ }
58+ type_key = payload .get (TYPE_KEY , None )
59+ AnnotationCls = type_key_to_type .get (type_key , SegmentationAnnotation )
60+ return AnnotationCls .from_json (payload )
6161
6262 def to_payload (self ) -> dict :
6363 """Serializes annotation object to schematized JSON dict."""
@@ -177,6 +177,88 @@ def to_payload(self) -> dict:
177177 return {X_KEY : self .x , Y_KEY : self .y }
178178
179179
180+ @dataclass
181+ class LineAnnotation (Annotation ):
182+ """A polyline annotation consisting of an ordered list of 2D points.
183+ A LineAnnotation differs from a PolygonAnnotation by not forming a closed
184+ loop, and by having zero area.
185+
186+ ::
187+
188+ from nucleus import LineAnnotation
189+
190+ line = LineAnnotation(
191+ label="face",
192+ vertices=[Point(100, 100), Point(200, 300), Point(300, 200)],
193+ reference_id="person_image_1",
194+ annotation_id="person_image_1_line_1",
195+ metadata={"camera_mode": "portrait"},
196+ )
197+
198+ Parameters:
199+ label (str): The label for this annotation.
200+ vertices (List[:class:`Point`]): The list of points making up the line.
201+ reference_id (str): User-defined ID of the image to which to apply this
202+ annotation.
203+ annotation_id (Optional[str]): The annotation ID that uniquely identifies
204+ this annotation within its target dataset item. Upon ingest, a matching
205+ annotation id will be ignored by default, and updated if update=True
206+ for dataset.annotate.
207+ metadata (Optional[Dict]): Arbitrary key/value dictionary of info to
208+ attach to this annotation. Strings, floats and ints are supported best
209+ by querying and insights features within Nucleus. For more details see
210+ our `metadata guide <https://nucleus.scale.com/docs/upload-metadata>`_.
211+ """
212+
213+ label : str
214+ vertices : List [Point ]
215+ reference_id : str
216+ annotation_id : Optional [str ] = None
217+ metadata : Optional [Dict ] = None
218+
219+ def __post_init__ (self ):
220+ self .metadata = self .metadata if self .metadata else {}
221+ if len (self .vertices ) > 0 :
222+ if not hasattr (self .vertices [0 ], X_KEY ) or not hasattr (
223+ self .vertices [0 ], "to_payload"
224+ ):
225+ try :
226+ self .vertices = [
227+ Point (x = vertex [X_KEY ], y = vertex [Y_KEY ])
228+ for vertex in self .vertices
229+ ]
230+ except KeyError as ke :
231+ raise ValueError (
232+ "Use a point object to pass in vertices. For example, vertices=[nucleus.Point(x=1, y=2)]"
233+ ) from ke
234+
235+ @classmethod
236+ def from_json (cls , payload : dict ):
237+ geometry = payload .get (GEOMETRY_KEY , {})
238+ return cls (
239+ label = payload .get (LABEL_KEY , 0 ),
240+ vertices = [
241+ Point .from_json (_ ) for _ in geometry .get (VERTICES_KEY , [])
242+ ],
243+ reference_id = payload [REFERENCE_ID_KEY ],
244+ annotation_id = payload .get (ANNOTATION_ID_KEY , None ),
245+ metadata = payload .get (METADATA_KEY , {}),
246+ )
247+
248+ def to_payload (self ) -> dict :
249+ payload = {
250+ LABEL_KEY : self .label ,
251+ TYPE_KEY : LINE_TYPE ,
252+ GEOMETRY_KEY : {
253+ VERTICES_KEY : [_ .to_payload () for _ in self .vertices ]
254+ },
255+ REFERENCE_ID_KEY : self .reference_id ,
256+ ANNOTATION_ID_KEY : self .annotation_id ,
257+ METADATA_KEY : self .metadata ,
258+ }
259+ return payload
260+
261+
180262@dataclass
181263class PolygonAnnotation (Annotation ):
182264 """A polygon annotation consisting of an ordered list of 2D points.
@@ -499,6 +581,7 @@ def to_payload(self) -> dict:
499581
500582class AnnotationTypes (Enum ):
501583 BOX = BOX_TYPE
584+ LINE = LINE_TYPE
502585 POLYGON = POLYGON_TYPE
503586 CUBOID = CUBOID_TYPE
504587 CATEGORY = CATEGORY_TYPE
@@ -600,6 +683,7 @@ class AnnotationList:
600683 """Wrapper class separating a list of annotations by type."""
601684
602685 box_annotations : List [BoxAnnotation ] = field (default_factory = list )
686+ line_annotations : List [LineAnnotation ] = field (default_factory = list )
603687 polygon_annotations : List [PolygonAnnotation ] = field (default_factory = list )
604688 cuboid_annotations : List [CuboidAnnotation ] = field (default_factory = list )
605689 category_annotations : List [CategoryAnnotation ] = field (
@@ -620,6 +704,8 @@ def add_annotations(self, annotations: List[Annotation]):
620704
621705 if isinstance (annotation , BoxAnnotation ):
622706 self .box_annotations .append (annotation )
707+ elif isinstance (annotation , LineAnnotation ):
708+ self .line_annotations .append (annotation )
623709 elif isinstance (annotation , PolygonAnnotation ):
624710 self .polygon_annotations .append (annotation )
625711 elif isinstance (annotation , CuboidAnnotation ):
@@ -637,6 +723,7 @@ def add_annotations(self, annotations: List[Annotation]):
637723 def __len__ (self ):
638724 return (
639725 len (self .box_annotations )
726+ + len (self .line_annotations )
640727 + len (self .polygon_annotations )
641728 + len (self .cuboid_annotations )
642729 + len (self .category_annotations )
0 commit comments