# coding: utf-8
"""Get a list of :class:`~supervisely.annotation.obj_class.ObjClass` from a Supervisely project."""
from __future__ import annotations
from typing import Dict, List, NamedTuple, Optional
from supervisely.api.module_api import ApiField, ModuleApi
from supervisely.video_annotation.key_id_map import KeyIdMap
[docs]
class ObjectClassApi(ModuleApi):
"""API for working with ObjClass objects."""
def __init__(self, api):
"""
:param api: :class:`~supervisely.api.api.Api` object to use for API connection.
:type api: :class:`~supervisely.api.api.Api`
:Usage Example:
.. code-block:: python
import supervisely as sly
api = sly.Api.from_env()
obj_class_infos = api.object_class.get_list(project_id)
"""
super().__init__(api)
[docs]
@staticmethod
def info_sequence():
"""
Sequence of fields that are returned by the API to represent ObjectClassInfo.
:Usage Example:
.. code-block:: python
ObjectClassInfo(
id=22309,
name="lemon",
description="",
shape="bitmap",
color="#51C6AA",
settings={},
created_at="2021-03-02T10:04:33.973Z",
updated_at="2021-03-11T09:37:07.111Z",
)
"""
return [
ApiField.ID,
ApiField.NAME,
ApiField.DESCRIPTION,
ApiField.SHAPE,
ApiField.COLOR,
ApiField.SETTINGS,
ApiField.CREATED_AT,
ApiField.UPDATED_AT,
]
[docs]
@staticmethod
def info_tuple_name():
"""
Name of the tuple that represents ObjectClassInfo.
"""
return "ObjectClassInfo"
[docs]
def get_list(
self, project_id: int, filters: Optional[List[Dict[str, str]]] = None
) -> List[NamedTuple]:
"""
List of Object Classes in the given Project.
:param project_id: Project ID in which the ObjClasses are located.
:type project_id: int
:param filters: List of params to sort output ObjClasses.
:type filters: List[dict], optional
:returns: List of ObjClasses with information from the given Project.
:rtype: List[NamedTuple]
:Usage Example:
.. code-block:: python
import os
from dotenv import load_dotenv
import supervisely as sly
# Load secrets and create API object from .env file (recommended)
# Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
if sly.is_development():
load_dotenv(os.path.expanduser("~/supervisely.env"))
api = sly.Api.from_env()
project_id = 1951
obj_class_infos = api.object_class.get_list(project_id)
print(obj_class_infos)
# Output: [ObjectClassInfo(id=22309,
# name='lemon',
# description='',
# shape='bitmap',
# color='#51C6AA',
# settings={},
# created_at='2021-03-02T10:04:33.973Z',
# updated_at='2021-03-11T09:37:07.111Z'),
# ObjectClassInfo(id=22310,
# name='kiwi',
# description='',
# shape='bitmap',
# color='#FF0000',
# settings={},
# created_at='2021-03-02T10:04:33.973Z',
# updated_at='2021-03-11T09:37:07.111Z')
# ]
obj_class_list = api.object_class.get_list(1951, filters=[{'field': 'name', 'operator': '=', 'value': 'lemon' }])
print(obj_class_list)
# Output: [
# [
# 22309,
# "lemon",
# "",
# "bitmap",
# "#51C6AA",
# {},
# "2021-03-02T10:04:33.973Z",
# "2021-03-11T09:37:07.111Z"
# ]
# ]
"""
return self.get_list_all_pages(
"advanced.object_classes.list",
{ApiField.PROJECT_ID: project_id, "filter": filters or []},
)
[docs]
def get_name_to_id_map(self, project_id: int) -> Dict[str, int]:
"""
:param project_id: Project ID in which the ObjClasses are located.
:type project_id: int
:returns: Dictionary mapping object class name to object class ID.
:rtype: Dict[str, int]
:Usage Example:
.. code-block:: python
import os
from dotenv import load_dotenv
import supervisely as sly
# Load secrets and create API object from .env file (recommended)
# Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
if sly.is_development():
load_dotenv(os.path.expanduser("~/supervisely.env"))
api = sly.Api.from_env()
obj_class_map = api.object_class.get_name_to_id_map(1951)
print(obj_class_map)
# Output: {'lemon': 22309, 'kiwi': 22310, 'cucumber': 22379}
"""
objects_infos = self.get_list(project_id)
return {object_info.name: object_info.id for object_info in objects_infos}
def _get_info_by_id(self, id, method, fields=None):
response = self._get_response_by_id(id, method, id_field=ApiField.ID, fields=fields)
return (
self._convert_json_info(response.json(), skip_missing=True)
if (response is not None)
else None
)
def get_info_by_id(self, id):
return self._get_info_by_id(
id,
"advanced.object_classes.info",
)
# def _object_classes_to_json(self, object_classes: KeyIndexedCollection, objclasses_name_id_map=None, project_id=None):
# pass #@TODO: implement
# # if objclasses_name_id_map is None and project_id is None:
# # raise RuntimeError("Impossible to get ids for projectTags")
# # if objclasses_name_id_map is None:
# # objclasses_name_id_map = self.get_name_to_id_map(project_id)
# # tags_json = []
# # for tag in tags:
# # tag_json = tag.to_json()
# # tag_json[ApiField.TAG_ID] = tag_name_id_map[tag.name]
# # tags_json.append(tag_json)
# # return tags_json
#
# def append_to_video(self, video_id, tags: KeyIndexedCollection, key_id_map: KeyIdMap = None):
# if len(tags) == 0:
# return []
# video_info = self._api.video.get_info_by_id(video_id)
# tags_json = self._tags_to_json(tags, project_id=video_info.project_id)
# ids = self.append_to_video_json(video_id, tags_json)
# KeyIdMap.add_tags_to(key_id_map, [tag.key() for tag in tags], ids)
# return ids
#
# def append_to_video_json(self, video_id, tags_json):
# if len(tags_json) == 0:
# return
# response = self._api.post('videos.tags.bulk.add', {ApiField.VIDEO_ID: video_id, ApiField.TAGS: tags_json})
# ids = [obj[ApiField.ID] for obj in response.json()]
# return ids
[docs]
def update(
self,
id: int,
name: Optional[str] = None,
description: Optional[str] = None,
hotkey: Optional[str] = None,
shape: Optional[str] = None,
color: Optional[str] = None,
settings: Optional[dict] = None,
) -> NamedTuple:
"""
Update the class with the given ID on the server.
Returned object contains updated information about the class except settings.
:param id: ID of the class to update.
:type id: int
:param name: New name of the class.
:type name: str, optional
:param description: New description of the class.
:type description: str, optional
:param hotkey: New hotkey of the class (e.g., "K").
:type hotkey: str, optional
:param shape: New shape of the class.
:type shape: str, optional
:param color: New color of the class in HEX format (e.g., #FFFFFF).
:type color: str, optional
:param settings: New settings of the class.
Do not pass "availableShapes" for shape other than "any".
Do not pass "availableShapes" that does not contain the current shape.
:type settings: dict, optional
:returns: Updated class information
:rtype: :class:`~supervisely.api.object_class_api.ObjectClassInfo`
:Usage Example:
.. code-block:: python
import os
from dotenv import load_dotenv
import supervisely as sly
# Load secrets and create API object from .env file (recommended)
# Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
if sly.is_development():
load_dotenv(os.path.expanduser("~/supervisely.env"))
api = sly.Api.from_env()
obj_class_info = api.object_class.update(
id=22309,
shape='any',
settings={
"availableShapes": [
"bitmap",
"polygon",
],
},
)
"""
if all(arg is None for arg in [name, description, hotkey, shape, color, settings]):
raise ValueError(
f"To update the class with ID: {id}, you must specify at least one parameter to update; all are currently None"
)
data = {ApiField.ID: id}
if name is not None:
data[ApiField.NAME] = name
if description is not None:
data[ApiField.DESCRIPTION] = description
if hotkey is not None:
data[ApiField.HOTKEY] = hotkey
if shape is not None:
data[ApiField.SHAPE] = shape
if color is not None:
data[ApiField.COLOR] = color
if settings is not None:
data[ApiField.SETTINGS] = settings
response = self._api.post("advanced.object_classes.editInfo", data)
return self._convert_json_info(response.json(), skip_missing=True)