diff --git a/CHANGELOG.md b/CHANGELOG.md index b5e93ba8..40eb578c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to the [Nucleus Python Client](https://github.com/scaleapi/n The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.20.1](https://github.com/scaleapi/nucleus-python-client/releases/tag/v0.20.1) - 2026-08-14 + +### Added +- **`dataset_item_id` on exported items and objects.** Batch exports now carry the Nucleus-internal dataset item id (`di_*`) everywhere `reference_id` already appeared: on `DatasetItem`, and on every exported annotation and prediction (`box`, `line`, `polygon`, `keypoints`, `cuboid`, `category`, `multicategory`, `segmentation`). Video/scene exports carry it on each track frame. Previously only `reference_id` was returned, so keying predictions back to items required a second lookup. + + The field is server-assigned and read-only: it is populated by `from_json`, left `None` on objects you construct locally, excluded from `__eq__`, and never sent in `to_payload`. Exports from an older backend that does not return it simply leave it `None`. + ## [0.20.0](https://github.com/scaleapi/nucleus-python-client/releases/tag/v0.20.0) - 2026-08-11 ### Added diff --git a/nucleus/annotation.py b/nucleus/annotation.py index cf930227..2dce7cf9 100644 --- a/nucleus/annotation.py +++ b/nucleus/annotation.py @@ -13,6 +13,7 @@ BOX_TYPE, CATEGORY_TYPE, CUBOID_TYPE, + DATASET_ITEM_ID_KEY, DIMENSIONS_KEY, EMBEDDING_VECTOR_KEY, GEOMETRY_KEY, @@ -58,6 +59,9 @@ class Annotation: """ reference_id: str + # Set on every subclass so callers can read it off the base type. See the note on + # BoxAnnotation for why it is server-assigned and excluded from equality. + dataset_item_id: Optional[str] @classmethod def from_json(cls, payload: dict): @@ -160,6 +164,14 @@ class BoxAnnotation(Annotation): # pylint: disable=R0902 embedding_vector: Optional[list] = None track_reference_id: Optional[str] = None _task_id: Optional[str] = field(default=None, repr=False) + # Nucleus-internal dataset item id (``di_*``) of the item this object sits on. + # Server-assigned and read-only: populated on objects returned by the API, ``None`` + # on ones you construct locally to upload, and never sent in ``to_payload``. + # Excluded from ``__eq__`` so a locally-built object still compares equal to its + # round-tripped self (same reason as ``DatasetItem.phash``). + dataset_item_id: Optional[str] = field( + default=None, repr=False, compare=False + ) def __post_init__(self): self.metadata = self.metadata if self.metadata else {} @@ -181,6 +193,7 @@ def from_json(cls, payload: dict): embedding_vector=payload.get(EMBEDDING_VECTOR_KEY, None), track_reference_id=payload.get(TRACK_REFERENCE_ID_KEY, None), _task_id=payload.get(TASK_ID_KEY, None), + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY, None), ) def to_payload(self) -> dict: @@ -281,6 +294,14 @@ class LineAnnotation(Annotation): metadata: Optional[Dict] = None track_reference_id: Optional[str] = None _task_id: Optional[str] = field(default=None, repr=False) + # Nucleus-internal dataset item id (``di_*``) of the item this object sits on. + # Server-assigned and read-only: populated on objects returned by the API, ``None`` + # on ones you construct locally to upload, and never sent in ``to_payload``. + # Excluded from ``__eq__`` so a locally-built object still compares equal to its + # round-tripped self (same reason as ``DatasetItem.phash``). + dataset_item_id: Optional[str] = field( + default=None, repr=False, compare=False + ) def __post_init__(self): self.metadata = self.metadata if self.metadata else {} @@ -311,6 +332,7 @@ def from_json(cls, payload: dict): metadata=payload.get(METADATA_KEY, {}), track_reference_id=payload.get(TRACK_REFERENCE_ID_KEY, None), _task_id=payload.get(TASK_ID_KEY, None), + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY, None), ) def to_payload(self) -> dict: @@ -376,6 +398,14 @@ class PolygonAnnotation(Annotation): embedding_vector: Optional[list] = None track_reference_id: Optional[str] = None _task_id: Optional[str] = field(default=None, repr=False) + # Nucleus-internal dataset item id (``di_*``) of the item this object sits on. + # Server-assigned and read-only: populated on objects returned by the API, ``None`` + # on ones you construct locally to upload, and never sent in ``to_payload``. + # Excluded from ``__eq__`` so a locally-built object still compares equal to its + # round-tripped self (same reason as ``DatasetItem.phash``). + dataset_item_id: Optional[str] = field( + default=None, repr=False, compare=False + ) def __post_init__(self): self.metadata = self.metadata if self.metadata else {} @@ -407,6 +437,7 @@ def from_json(cls, payload: dict): embedding_vector=payload.get(EMBEDDING_VECTOR_KEY, None), track_reference_id=payload.get(TRACK_REFERENCE_ID_KEY, None), _task_id=payload.get(TASK_ID_KEY, None), + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY, None), ) def to_payload(self) -> dict: @@ -519,6 +550,14 @@ class KeypointsAnnotation(Annotation): metadata: Optional[Dict] = None track_reference_id: Optional[str] = None _task_id: Optional[str] = field(default=None, repr=False) + # Nucleus-internal dataset item id (``di_*``) of the item this object sits on. + # Server-assigned and read-only: populated on objects returned by the API, ``None`` + # on ones you construct locally to upload, and never sent in ``to_payload``. + # Excluded from ``__eq__`` so a locally-built object still compares equal to its + # round-tripped self (same reason as ``DatasetItem.phash``). + dataset_item_id: Optional[str] = field( + default=None, repr=False, compare=False + ) def __post_init__(self): self.metadata = self.metadata or {} @@ -572,6 +611,7 @@ def from_json(cls, payload: dict): metadata=payload.get(METADATA_KEY, {}), track_reference_id=payload.get(TRACK_REFERENCE_ID_KEY, None), _task_id=payload.get(TASK_ID_KEY, None), + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY, None), ) def to_payload(self) -> dict: @@ -693,6 +733,14 @@ class CuboidAnnotation(Annotation): # pylint: disable=R0902 metadata: Optional[Dict] = None track_reference_id: Optional[str] = None _task_id: Optional[str] = field(default=None, repr=False) + # Nucleus-internal dataset item id (``di_*``) of the item this object sits on. + # Server-assigned and read-only: populated on objects returned by the API, ``None`` + # on ones you construct locally to upload, and never sent in ``to_payload``. + # Excluded from ``__eq__`` so a locally-built object still compares equal to its + # round-tripped self (same reason as ``DatasetItem.phash``). + dataset_item_id: Optional[str] = field( + default=None, repr=False, compare=False + ) def __post_init__(self): self.metadata = self.metadata if self.metadata else {} @@ -710,6 +758,7 @@ def from_json(cls, payload: dict): metadata=payload.get(METADATA_KEY, {}), track_reference_id=payload.get(TRACK_REFERENCE_ID_KEY, None), _task_id=payload.get(TASK_ID_KEY, None), + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY, None), ) def to_payload(self) -> dict: @@ -846,6 +895,10 @@ class SegmentationAnnotation(Annotation): reference_id: str annotation_id: Optional[str] = None # metadata: Optional[dict] = None # TODO(sc: 422637) + # See the note on BoxAnnotation.dataset_item_id — server-assigned, read-only. + dataset_item_id: Optional[str] = field( + default=None, repr=False, compare=False + ) def __post_init__(self): if not self.mask_url: @@ -863,6 +916,7 @@ def from_json(cls, payload: dict): ], reference_id=payload[REFERENCE_ID_KEY], annotation_id=payload.get(ANNOTATION_ID_KEY, None), + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY, None), # metadata=payload.get(METADATA_KEY, None), # TODO(sc: 422637) ) @@ -947,6 +1001,14 @@ class CategoryAnnotation(Annotation): metadata: Optional[Dict] = None track_reference_id: Optional[str] = None _task_id: Optional[str] = field(default=None, repr=False) + # Nucleus-internal dataset item id (``di_*``) of the item this object sits on. + # Server-assigned and read-only: populated on objects returned by the API, ``None`` + # on ones you construct locally to upload, and never sent in ``to_payload``. + # Excluded from ``__eq__`` so a locally-built object still compares equal to its + # round-tripped self (same reason as ``DatasetItem.phash``). + dataset_item_id: Optional[str] = field( + default=None, repr=False, compare=False + ) def __post_init__(self): self.metadata = self.metadata if self.metadata else {} @@ -960,6 +1022,7 @@ def from_json(cls, payload: dict): metadata=payload.get(METADATA_KEY, {}), track_reference_id=payload.get(TRACK_REFERENCE_ID_KEY, None), _task_id=payload.get(TASK_ID_KEY, None), + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY, None), ) def to_payload(self) -> dict: @@ -987,6 +1050,14 @@ class MultiCategoryAnnotation(Annotation): metadata: Optional[Dict] = None track_reference_id: Optional[str] = None _task_id: Optional[str] = field(default=None, repr=False) + # Nucleus-internal dataset item id (``di_*``) of the item this object sits on. + # Server-assigned and read-only: populated on objects returned by the API, ``None`` + # on ones you construct locally to upload, and never sent in ``to_payload``. + # Excluded from ``__eq__`` so a locally-built object still compares equal to its + # round-tripped self (same reason as ``DatasetItem.phash``). + dataset_item_id: Optional[str] = field( + default=None, repr=False, compare=False + ) def __post_init__(self): self.metadata = self.metadata if self.metadata else {} @@ -1000,6 +1071,7 @@ def from_json(cls, payload: dict): metadata=payload.get(METADATA_KEY, {}), track_reference_id=payload.get(TRACK_REFERENCE_ID_KEY, None), _task_id=payload.get(TASK_ID_KEY, None), + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY, None), ) def to_payload(self) -> dict: @@ -1050,6 +1122,14 @@ class SceneCategoryAnnotation(Annotation): taxonomy_name: Optional[str] = None metadata: Optional[Dict] = field(default_factory=dict) _task_id: Optional[str] = field(default=None, repr=False) + # Nucleus-internal dataset item id (``di_*``) of the item this object sits on. + # Server-assigned and read-only: populated on objects returned by the API, ``None`` + # on ones you construct locally to upload, and never sent in ``to_payload``. + # Excluded from ``__eq__`` so a locally-built object still compares equal to its + # round-tripped self (same reason as ``DatasetItem.phash``). + dataset_item_id: Optional[str] = field( + default=None, repr=False, compare=False + ) @classmethod def from_json(cls, payload: dict): @@ -1059,6 +1139,7 @@ def from_json(cls, payload: dict): taxonomy_name=payload.get(TAXONOMY_NAME_KEY, None), metadata=payload.get(METADATA_KEY, {}), _task_id=payload.get(TASK_ID_KEY, None), + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY, None), ) def to_payload(self) -> dict: diff --git a/nucleus/dataset.py b/nucleus/dataset.py index f9af36c9..a711e637 100644 --- a/nucleus/dataset.py +++ b/nucleus/dataset.py @@ -1584,6 +1584,7 @@ def scene_and_annotation_generator( "width": int, "height": int, "key": str, # frame key + "dataset_item_id": str, # id of the frame's dataset item "metadata": Dict[str, Any] }] } diff --git a/nucleus/dataset_item.py b/nucleus/dataset_item.py index 8f4ea894..a97720f8 100644 --- a/nucleus/dataset_item.py +++ b/nucleus/dataset_item.py @@ -10,6 +10,7 @@ from .constants import ( BACKEND_REFERENCE_ID_KEY, CAMERA_PARAMS_KEY, + DATASET_ITEM_ID_KEY, EMBEDDING_INFO_KEY, EMBEDDING_VECTOR_KEY, HEIGHT_KEY, @@ -131,6 +132,11 @@ class DatasetItem: # pylint: disable=R0902 # returned object — so locally-constructed items would otherwise spuriously # differ from round-tripped ones. phash: Optional[str] = field(default=None, compare=False) + # Nucleus-internal dataset item id (``di_*``), assigned server-side. Populated on + # items returned by the API; ``None`` on items you construct locally to upload. + # Excluded from auto-generated __eq__ for the same reason as ``phash`` — a + # locally-built item would otherwise never compare equal to its round-tripped self. + dataset_item_id: Optional[str] = field(default=None, compare=False) def __post_init__(self): assert self.reference_id is not None, "reference_id is required." @@ -187,6 +193,7 @@ def from_json(cls, payload: dict): reference_id=payload.get(REFERENCE_ID_KEY), metadata=payload.get(METADATA_KEY, {}), phash=payload.get(PHASH_KEY), + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY), ) def local_file_exists(self): diff --git a/nucleus/prediction.py b/nucleus/prediction.py index afc4dd4c..cd9ee1d8 100644 --- a/nucleus/prediction.py +++ b/nucleus/prediction.py @@ -3,6 +3,7 @@ as annotation types, but come with additional, optional data that can be attached such as confidence or probability distributions. """ + from dataclasses import dataclass, field from typing import Dict, List, Optional, Type, Union @@ -28,6 +29,7 @@ CLASS_PDF_KEY, CONFIDENCE_KEY, CUBOID_TYPE, + DATASET_ITEM_ID_KEY, DIMENSIONS_KEY, EMBEDDING_VECTOR_KEY, GEOMETRY_KEY, @@ -129,6 +131,7 @@ def from_json(cls, payload: dict): for ann in payload.get(ANNOTATIONS_KEY, []) ], reference_id=payload[REFERENCE_ID_KEY], + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY, None), annotation_id=payload.get(ANNOTATION_ID_KEY, None), # metadata=payload.get(METADATA_KEY, None), # TODO(sc: 422637) ) @@ -189,6 +192,7 @@ def __init__( class_pdf: Optional[Dict] = None, embedding_vector: Optional[list] = None, track_reference_id: Optional[str] = None, + dataset_item_id: Optional[str] = None, ): super().__init__( label=label, @@ -197,6 +201,7 @@ def __init__( width=width, height=height, reference_id=reference_id, + dataset_item_id=dataset_item_id, annotation_id=annotation_id, metadata=metadata, embedding_vector=embedding_vector, @@ -224,6 +229,7 @@ def from_json(cls, payload: dict): width=geometry.get(WIDTH_KEY, 0), height=geometry.get(HEIGHT_KEY, 0), reference_id=payload[REFERENCE_ID_KEY], + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY, None), confidence=payload.get(CONFIDENCE_KEY, None), annotation_id=payload.get(ANNOTATION_ID_KEY, None), metadata=payload.get(METADATA_KEY, {}), @@ -269,11 +275,13 @@ def __init__( metadata: Optional[Dict] = None, class_pdf: Optional[Dict] = None, track_reference_id: Optional[str] = None, + dataset_item_id: Optional[str] = None, ): super().__init__( label=label, vertices=vertices, reference_id=reference_id, + dataset_item_id=dataset_item_id, annotation_id=annotation_id, metadata=metadata, track_reference_id=track_reference_id, @@ -299,6 +307,7 @@ def from_json(cls, payload: dict): Point.from_json(_) for _ in geometry.get(VERTICES_KEY, []) ], reference_id=payload[REFERENCE_ID_KEY], + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY, None), confidence=payload.get(CONFIDENCE_KEY, None), annotation_id=payload.get(ANNOTATION_ID_KEY, None), metadata=payload.get(METADATA_KEY, {}), @@ -347,11 +356,13 @@ def __init__( class_pdf: Optional[Dict] = None, embedding_vector: Optional[list] = None, track_reference_id: Optional[str] = None, + dataset_item_id: Optional[str] = None, ): super().__init__( label=label, vertices=vertices, reference_id=reference_id, + dataset_item_id=dataset_item_id, annotation_id=annotation_id, metadata=metadata, embedding_vector=embedding_vector, @@ -378,6 +389,7 @@ def from_json(cls, payload: dict): Point.from_json(_) for _ in geometry.get(VERTICES_KEY, []) ], reference_id=payload[REFERENCE_ID_KEY], + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY, None), confidence=payload.get(CONFIDENCE_KEY, None), annotation_id=payload.get(ANNOTATION_ID_KEY, None), metadata=payload.get(METADATA_KEY, {}), @@ -428,6 +440,7 @@ def __init__( metadata: Optional[Dict] = None, class_pdf: Optional[Dict] = None, track_reference_id: Optional[str] = None, + dataset_item_id: Optional[str] = None, ): super().__init__( label=label, @@ -435,6 +448,7 @@ def __init__( names=names, skeleton=skeleton, reference_id=reference_id, + dataset_item_id=dataset_item_id, annotation_id=annotation_id, metadata=metadata, track_reference_id=track_reference_id, @@ -462,6 +476,7 @@ def from_json(cls, payload: dict): names=geometry[KEYPOINTS_NAMES_KEY], skeleton=geometry[KEYPOINTS_SKELETON_KEY], reference_id=payload[REFERENCE_ID_KEY], + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY, None), confidence=payload.get(CONFIDENCE_KEY, None), annotation_id=payload.get(ANNOTATION_ID_KEY, None), metadata=payload.get(METADATA_KEY, {}), @@ -509,6 +524,7 @@ def __init__( metadata: Optional[Dict] = None, class_pdf: Optional[Dict] = None, track_reference_id: Optional[str] = None, + dataset_item_id: Optional[str] = None, ): super().__init__( label=label, @@ -516,6 +532,7 @@ def __init__( dimensions=dimensions, yaw=yaw, reference_id=reference_id, + dataset_item_id=dataset_item_id, annotation_id=annotation_id, metadata=metadata, track_reference_id=track_reference_id, @@ -541,6 +558,7 @@ def from_json(cls, payload: dict): dimensions=Point3D.from_json(geometry.get(DIMENSIONS_KEY, {})), yaw=geometry.get(YAW_KEY, 0), reference_id=payload[REFERENCE_ID_KEY], + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY, None), confidence=payload.get(CONFIDENCE_KEY, None), annotation_id=payload.get(ANNOTATION_ID_KEY, None), metadata=payload.get(METADATA_KEY, {}), @@ -580,11 +598,13 @@ def __init__( metadata: Optional[Dict] = None, class_pdf: Optional[Dict] = None, track_reference_id: Optional[str] = None, + dataset_item_id: Optional[str] = None, ): super().__init__( label=label, taxonomy_name=taxonomy_name, reference_id=reference_id, + dataset_item_id=dataset_item_id, metadata=metadata, track_reference_id=track_reference_id, ) @@ -606,6 +626,7 @@ def from_json(cls, payload: dict): label=payload.get(LABEL_KEY, 0), taxonomy_name=payload.get(TAXONOMY_NAME_KEY, None), reference_id=payload[REFERENCE_ID_KEY], + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY, None), confidence=payload.get(CONFIDENCE_KEY, None), metadata=payload.get(METADATA_KEY, {}), class_pdf=payload.get(CLASS_PDF_KEY, None), @@ -649,11 +670,13 @@ def __init__( taxonomy_name: Optional[str] = None, confidence: Optional[float] = None, metadata: Optional[Dict] = None, + dataset_item_id: Optional[str] = None, ): super().__init__( label=label, taxonomy_name=taxonomy_name, reference_id=reference_id, + dataset_item_id=dataset_item_id, metadata=metadata, ) self.confidence = confidence @@ -671,6 +694,7 @@ def from_json(cls, payload: dict): label=payload.get(LABEL_KEY, 0), taxonomy_name=payload.get(TAXONOMY_NAME_KEY, None), reference_id=payload[REFERENCE_ID_KEY], + dataset_item_id=payload.get(DATASET_ITEM_ID_KEY, None), confidence=payload.get(CONFIDENCE_KEY, None), metadata=payload.get(METADATA_KEY, {}), ) diff --git a/nucleus/utils.py b/nucleus/utils.py index e9ade62b..c96c76c9 100644 --- a/nucleus/utils.py +++ b/nucleus/utils.py @@ -31,6 +31,7 @@ BOX_TYPE, CATEGORY_TYPE, CUBOID_TYPE, + DATASET_ITEM_ID_KEY, EXPORTED_SCALE_TASK_INFO_ROWS, ITEM_KEY, KEYPOINTS_TYPE, @@ -239,10 +240,15 @@ def convert_export_payload(api_payload, has_predictions: bool = False): for row in api_payload: return_payload_row = {} return_payload_row[ITEM_KEY] = DatasetItem.from_json(row[ITEM_KEY]) + # The backend returns reference_id and dataset_item_id on the item, not on each + # object, so stamp both down onto every annotation/prediction below. + item_reference_id = row[ITEM_KEY][REFERENCE_ID_KEY] + item_dataset_item_id = row[ITEM_KEY].get(DATASET_ITEM_ID_KEY) annotations = defaultdict(list) if row.get(SEGMENTATION_TYPE) is not None: segmentation = row[SEGMENTATION_TYPE] - segmentation[REFERENCE_ID_KEY] = row[ITEM_KEY][REFERENCE_ID_KEY] + segmentation[REFERENCE_ID_KEY] = item_reference_id + segmentation[DATASET_ITEM_ID_KEY] = item_dataset_item_id if not has_predictions: annotations[ SEGMENTATION_TYPE @@ -252,7 +258,8 @@ def convert_export_payload(api_payload, has_predictions: bool = False): SEGMENTATION_TYPE ] = SegmentationPrediction.from_json(segmentation) for polygon in row[POLYGON_TYPE]: - polygon[REFERENCE_ID_KEY] = row[ITEM_KEY][REFERENCE_ID_KEY] + polygon[REFERENCE_ID_KEY] = item_reference_id + polygon[DATASET_ITEM_ID_KEY] = item_dataset_item_id if not has_predictions: annotations[POLYGON_TYPE].append( PolygonAnnotation.from_json(polygon) @@ -262,13 +269,15 @@ def convert_export_payload(api_payload, has_predictions: bool = False): PolygonPrediction.from_json(polygon) ) for line in row[LINE_TYPE]: - line[REFERENCE_ID_KEY] = row[ITEM_KEY][REFERENCE_ID_KEY] + line[REFERENCE_ID_KEY] = item_reference_id + line[DATASET_ITEM_ID_KEY] = item_dataset_item_id if not has_predictions: annotations[LINE_TYPE].append(LineAnnotation.from_json(line)) else: annotations[LINE_TYPE].append(LinePrediction.from_json(line)) for keypoints in row[KEYPOINTS_TYPE]: - keypoints[REFERENCE_ID_KEY] = row[ITEM_KEY][REFERENCE_ID_KEY] + keypoints[REFERENCE_ID_KEY] = item_reference_id + keypoints[DATASET_ITEM_ID_KEY] = item_dataset_item_id if not has_predictions: annotations[KEYPOINTS_TYPE].append( KeypointsAnnotation.from_json(keypoints) @@ -278,13 +287,15 @@ def convert_export_payload(api_payload, has_predictions: bool = False): KeypointsPrediction.from_json(keypoints) ) for box in row[BOX_TYPE]: - box[REFERENCE_ID_KEY] = row[ITEM_KEY][REFERENCE_ID_KEY] + box[REFERENCE_ID_KEY] = item_reference_id + box[DATASET_ITEM_ID_KEY] = item_dataset_item_id if not has_predictions: annotations[BOX_TYPE].append(BoxAnnotation.from_json(box)) else: annotations[BOX_TYPE].append(BoxPrediction.from_json(box)) for cuboid in row[CUBOID_TYPE]: - cuboid[REFERENCE_ID_KEY] = row[ITEM_KEY][REFERENCE_ID_KEY] + cuboid[REFERENCE_ID_KEY] = item_reference_id + cuboid[DATASET_ITEM_ID_KEY] = item_dataset_item_id if not has_predictions: annotations[CUBOID_TYPE].append( CuboidAnnotation.from_json(cuboid) @@ -294,7 +305,8 @@ def convert_export_payload(api_payload, has_predictions: bool = False): CuboidPrediction.from_json(cuboid) ) for category in row[CATEGORY_TYPE]: - category[REFERENCE_ID_KEY] = row[ITEM_KEY][REFERENCE_ID_KEY] + category[REFERENCE_ID_KEY] = item_reference_id + category[DATASET_ITEM_ID_KEY] = item_dataset_item_id if not has_predictions: annotations[CATEGORY_TYPE].append( CategoryAnnotation.from_json(category) @@ -304,7 +316,8 @@ def convert_export_payload(api_payload, has_predictions: bool = False): CategoryPrediction.from_json(category) ) for multicategory in row[MULTICATEGORY_TYPE]: - multicategory[REFERENCE_ID_KEY] = row[ITEM_KEY][REFERENCE_ID_KEY] + multicategory[REFERENCE_ID_KEY] = item_reference_id + multicategory[DATASET_ITEM_ID_KEY] = item_dataset_item_id annotations[MULTICATEGORY_TYPE].append( MultiCategoryAnnotation.from_json(multicategory) ) diff --git a/pyproject.toml b/pyproject.toml index 6f6de6a0..1b96a267 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ ignore = ["E501", "E741", "E731", "F401"] # Easy ignore for getting it running [tool.poetry] name = "scale-nucleus" -version = "0.20.0" +version = "0.20.1" description = "The official Python client library for Nucleus, the Data Platform for AI" license = "MIT" authors = ["Scale AI Nucleus Team "]