diff --git a/README.md b/README.md index 58ac262..4b28588 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ - [PCD](#pcd) - [Sequential PCD](#sequential-pcd) - [DICOM](#dicom) + - [Robotics](#robotics) - [Common](#common) - [Appendix](#appendix) - [Annotation](#annotation) @@ -2064,6 +2065,34 @@ Example of a single dicom task object } ``` +### Robotics + +Supported following project types: + +- Robotics - Task Classification + +#### Create Tasks + +Create a new task (Content creation is required separately). + +```python +task_id = client.create_robotics_task( + project="YOUR_PROJECT_SLUG", + name="TASK_NAME", +) +``` + +#### Import Contents + +Import contents zip file + +```python +history = client.import_robotics_contents_file( + project="YOUR_PROJECT_SLUG", + file_path="ZIP_FILE_PATH", +) +``` + ### Common APIs for update and delete and count are same over all tasks. @@ -2104,6 +2133,31 @@ client.delete_task_annotations(task_id="YOUR_TASK_ID") id_name_map = client.get_task_id_name_map(project="YOUR_PROJECT_SLUG") ``` +#### Get Task Appendix Data + +Get appendix data (URLs and parameters) for tasks. + +```python +appendix_data = client.get_task_appendix_data(project="YOUR_PROJECT_SLUG") +``` + +Filter by task name: + +```python +appendix_data = client.get_task_appendix_data( + project="YOUR_PROJECT_SLUG", + task_name="YOUR_TASK_NAME" +) +``` + +Response includes: + +- `id`: UUID of the appendix +- `url`: Image file URL +- `name`: Format is `{task_name}/{content_name}/{file_name}` +- `format`: `yml`, `kitti`, or `none` +- `calibration`: Calibration data + #### Count Task ```python diff --git a/fastlabel/__init__.py b/fastlabel/__init__.py index 5a79d51..990aea0 100644 --- a/fastlabel/__init__.py +++ b/fastlabel/__init__.py @@ -1975,6 +1975,53 @@ def create_sequential_pcd_task( return self.api.post_request(endpoint, payload=payload) + def create_robotics_task( + self, + project: str, + name: str, + status: Optional[str] = None, + external_status: Optional[str] = None, + priority: Optional[Priority] = None, + tags: Optional[list[str]] = None, + **kwargs, + ) -> str: + """ + Create a single robotics task without content. + + project is slug of your project (Required). + name is an unique identifier of task in your project (Required). + status can be 'registered', 'completed', 'skipped', 'reviewed', 'sent_back', 'approved', 'declined' (Optional). + external_status can be 'registered', 'completed', 'skipped', 'reviewed', 'sent_back', 'approved', 'declined', 'customer_declined' (Optional). + priority is the priority of the task (default: none) (Optional). + Set one of the numbers corresponding to: + none = 0, + low = 10, + medium = 20, + high = 30, + tags is a list of tag to be set in advance (Optional). + assignee is slug of assigned user (Optional). + reviewer is slug of review user (Optional). + approver is slug of approve user (Optional). + external_assignee is slug of external assigned user (Optional). + external_reviewer is slug of external review user (Optional). + external_approver is slug of external approve user (Optional). + """ + endpoint = "tasks/robotics" + + payload = {"project": project, "name": name} + if status: + payload["status"] = status + if external_status: + payload["externalStatus"] = external_status + if priority is not None: + payload["priority"] = priority + if tags: + payload["tags"] = tags or [] + + self.__fill_assign_users(payload, **kwargs) + + return self.api.post_request(endpoint, payload=payload) + def import_appendix_file( self, project: str, @@ -2053,6 +2100,69 @@ def get_appendix_data( return self.api.get_request(endpoint, params=params) + def get_task_appendix_data( + self, + project: str, + task_name: Optional[str] = None, + offset: Optional[int] = None, + limit: int = 10000, + ) -> list: + """ + Returns a list of appendixes urls and params. + params = { + id: uuid, + url: image file url, + name: {task_name}/{content_name}/{file_name}, + format: yml or kitti or none, + calibration: calibration data, + } + + project is slug of your project (Required). + task_name is a task name (Optional). + offset is the starting position number to fetch (Optional). + limit is the max number to fetch (Optional). + """ + if limit > 10000: + raise FastLabelInvalidException( + "Limit must be less than or equal to 10000.", 422 + ) + endpoint = "tasks/appendix" + params = {"project": project} + if task_name: + params["taskName"] = task_name + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + + return self.api.get_request(endpoint, params=params) + + def import_robotics_contents_file( + self, + project: str, + file_path: str, + ) -> list: + """ + Import robotics contents file zip. + project is slug of your project (Required). + file_path is a path to data. Supported extensions are zip (Required). + """ + + if not utils.is_robotics_contents_supported_ext(file_path): + raise FastLabelInvalidException("Supported extensions are zip.", 422) + + endpoint = "contents/imports/robotics-contents/batch" + payload = {"project": project} + signed_url = self.__get_signed_path( + project=project, + file_name=os.path.basename(file_path), + file_type="application/zip", + ) + self.api.upload_zipfile(url=signed_url["url"], file_path=file_path) + payload["fileKey"] = signed_url["name"] + + return self.api.post_request(endpoint, payload=payload) + # Task Update def update_task( diff --git a/fastlabel/utils/__init__.py b/fastlabel/utils/__init__.py index 3b90d73..ae435ae 100644 --- a/fastlabel/utils/__init__.py +++ b/fastlabel/utils/__init__.py @@ -46,6 +46,10 @@ def is_appendix_supported_ext(file_path: str) -> bool: return file_path.lower().endswith((".zip")) +def is_robotics_contents_supported_ext(file_path: str) -> bool: + return file_path.lower().endswith((".zip")) + + def is_pcd_supported_ext(file_path: str) -> bool: # .ply is not yet supported. To support it, modification of the API # needs to be considered as well.