diff --git a/README.md b/README.md index a5a515c..ef265ad 100644 --- a/README.md +++ b/README.md @@ -35,18 +35,19 @@ API is allowed to call 10000 times per 10 minutes. If you create/delete a large Supported following project types: -- Bounding Box -- Polygon -- Keypoint -- Line -- Segmentation +- Image - Bounding Box +- Image - Polygon +- Image - Keypoint +- Image - Line +- Image - Segmentation +- Image - All #### Create Task - Create a new task. ```python -task_id = client.create_task( +task_id = client.create_image_task( project="YOUR_PROJECT_SLUG", name="sample.jpg", file_path="./sample.jpg" @@ -56,7 +57,7 @@ task_id = client.create_task( - Create a new task with pre-defined annotations. (Class should be configured on your project in advance) ```python -task_id = client.create_task( +task_id = client.create_image_task( project="YOUR_PROJECT_SLUG", name="sample.jpg", file_path="./sample.jpg", @@ -79,26 +80,14 @@ task_id = client.create_task( ) ``` -> Check [examples/create_task.py](/examples/create_task.py). - -#### Update Task - -- Update a single task status and tags. - -```python -task_id = client.update_task( - task_id="YOUR_TASK_ID", - status="approved", - tags=["tag1", "tag2"] -) -``` +> Check [examples/create_image_task.py](/examples/create_image_task.py). #### Find Task - Find a single task. ```python -task = client.find_task(task_id="YOUR_TASK_ID") +task = client.find_image_task(task_id="YOUR_TASK_ID") ``` #### Get Tasks @@ -106,13 +95,13 @@ task = client.find_task(task_id="YOUR_TASK_ID") - Get tasks. (Up to 1000 tasks) ```python -tasks = client.get_tasks(project="YOUR_PROJECT_SLUG") +tasks = client.get_image_tasks(project="YOUR_PROJECT_SLUG") ``` - Filter and Get tasks. (Up to 1000 tasks) ```python -tasks = client.get_tasks( +tasks = client.get_image_tasks( project="YOUR_PROJECT_SLUG", status="approved", # status can be 'registered', 'in_progress', 'completed', 'skipped', 'in_review', 'send_backed', 'approved', 'customer_in_review', 'customer_send_backed', 'customer_approved' tags=["tag1", "tag2"] # up to 10 tags @@ -130,8 +119,7 @@ offset = None while True: time.sleep(1) - tasks = client.get_tasks( - project="YOUR_PROJECT_SLUG", offset=offset) + tasks = client.get_image_tasks(project="YOUR_PROJECT_SLUG", offset=offset) all_tasks.extend(tasks) if len(tasks) > 0: @@ -142,25 +130,22 @@ while True: > Please wait a second before sending another requests! -#### Delete Task +#### Response -- Delete a single task. - -```python -client.delete_task(task_id="YOUR_TASK_ID") -``` - -#### Task Response - -- Example of a single task object +- Example of a single image task object ```python { "id": "YOUR_TASK_ID", "name": "cat.jpg", + "width": 100, # image width + "height": 100, # image height "url": "YOUR_TASK_URL", "status": "registered", "tags": [], + "assignee": "ASSIGNEE_NAME", + "reviewer": "REVIEWER_NAME", + "customerReviewer": "CUSTOMER_REVIEWER_NAME", "annotations": [ { "attributes": [ @@ -183,15 +168,84 @@ client.delete_task(task_id="YOUR_TASK_ID") } ``` +### Image Classification + +Supported following project types: + +- Image - Classification + +#### Create Task + +- Create a new task. + +```python +task_id = client.create_image_classification_task( + project="YOUR_PROJECT_SLUG", + name="sample.jpg", + file_path="./sample.jpg", + attributes=[ + { + "key": "attribute-key", + "value": "attribute-value" + } + ], +) +``` + +#### Find Task + +- Find a single task. + +```python +task = client.find_image_classification_task(task_id="YOUR_TASK_ID") +``` + +#### Get Tasks + +- Get tasks. (Up to 1000 tasks) + +```python +tasks = client.get_image_classification_tasks(project="YOUR_PROJECT_SLUG") +``` + +#### Response + +- Example of a single image classification task object + +```python +{ + "id": "YOUR_TASK_ID", + "name": "cat.jpg", + "width": 100, # image width + "height": 100, # image height + "url": "YOUR_TASK_URL", + "status": "registered", + "tags": [], + "assignee": "ASSIGNEE_NAME", + "reviewer": "REVIEWER_NAME", + "customerReviewer": "CUSTOMER_REVIEWER_NAME", + "attributes": [ + { + "key": "kind", + "name": "Kind", + "type": "text", + "value": "Scottish field" + } + ], + "createdAt": "2021-02-22T11:25:27.158Z", + "updatedAt": "2021-02-22T11:25:27.158Z" +} +``` + ### Multi Image Supported following project types: -- Bounding Box -- Polygon -- Keypoint -- Line -- Segmentation +- Multi Image - Bounding Box +- Multi Image - Polygon +- Multi Image - Keypoint +- Multi Image - Line +- Multi Image - Segmentation #### Create Task @@ -228,10 +282,6 @@ task = client.create_multi_image_task( ) ``` -#### Update Task - -- Same as image task. - #### Find Task - Find a single task. @@ -248,11 +298,7 @@ task = client.find_multi_image_task(task_id="YOUR_TASK_ID") tasks = client.get_multi_image_tasks(project="YOUR_PROJECT_SLUG") ``` -#### Delete Task - -- Same as image task. - -#### Task Response +#### Response - Example of a single task object @@ -264,12 +310,15 @@ tasks = client.get_multi_image_tasks(project="YOUR_PROJECT_SLUG") { "name": "content-name", "url": "content-url", - "width": "content-width", - "height": "content-height", + "width": 100, + "height": 100, } ], "status": "registered", "tags": [], + "assignee": "ASSIGNEE_NAME", + "reviewer": "REVIEWER_NAME", + "customerReviewer": "CUSTOMER_REVIEWER_NAME", "annotations": [ { "content": "content-name" @@ -297,6 +346,126 @@ tasks = client.get_multi_image_tasks(project="YOUR_PROJECT_SLUG") } ``` +### Video + +Supported following project types: + +- Video - Bounding Box + +#### Create Task + +- Create a new task. + +```python +task_id = client.create_video_task( + project="YOUR_PROJECT_SLUG", + name="sample.mp4", + file_path="./sample.mp4" +) +``` + +#### Find Task + +- Find a single task. + +```python +task = client.find_video_task(task_id="YOUR_TASK_ID") +``` + +#### Get Tasks + +- Get tasks. (Up to 10 tasks) + +```python +tasks = client.get_video_tasks(project="YOUR_PROJECT_SLUG") +``` + +#### Response + +- Example of a single image classification task object + +```python +{ + "id": "YOUR_TASK_ID", + "name": "cat.jpg", + "width": 100, # image width + "height": 100, # image height + "fps": 30.0, # frame per seconds + "frameCount": 480, # total frame count of video + "duration": 16.0, # total duration of video + "url": "YOUR_TASK_URL", + "status": "registered", + "tags": [], + "assignee": "ASSIGNEE_NAME", + "reviewer": "REVIEWER_NAME", + "customerReviewer": "CUSTOMER_REVIEWER_NAME", + "annotations": [ + { + "attributes": [], + "color": "#b36d18", + "points": { + "1": { # number of frame + "value": [ + 100, # top-left x + 100, # top-left y + 200, # bottom-right x + 200 # bottom-right y + ], + "autogenerated": False # False when annotated manually. True when auto-generated by system. + }, + "2": { + "value": [ + 110, + 110, + 220, + 220 + ], + "autogenerated": True + }, + "3": { + "value": [ + 120, + 120, + 240, + 240 + ], + "autogenerated": False + } + }, + "title": "Cat", + "type": "bbox", + "value": "cat" + } + ], + "createdAt": "2021-02-22T11:25:27.158Z", + "updatedAt": "2021-02-22T11:25:27.158Z" +} +``` + +### Common + +APIs for update and delete are same over all tasks. + +#### Update Task + +- Update a single task status and tags. + +```python +task_id = client.update_task( + task_id="YOUR_TASK_ID", + status="approved", + tags=["tag1", "tag2"] +) +``` + +#### Delete Task + +- Delete a single task. + +```python +client.delete_task(task_id="YOUR_TASK_ID") +``` + ## Converter ### COCO @@ -304,7 +473,7 @@ tasks = client.get_multi_image_tasks(project="YOUR_PROJECT_SLUG") - Get tasks and convert to [COCO format](https://cocodataset.org/#format-data) (supporting bbox or polygon annotation type). ```python -tasks = client.get_tasks(project="YOUR_PROJECT_SLUG") +tasks = client.get_image_tasks(project="YOUR_PROJECT_SLUG") pprint(client.to_coco(tasks)) ``` diff --git a/examples/create_task.py b/examples/create_image_task.py similarity index 94% rename from examples/create_task.py rename to examples/create_image_task.py index 44daf27..d26515d 100644 --- a/examples/create_task.py +++ b/examples/create_image_task.py @@ -25,7 +25,7 @@ ] }] -task_id = client.create_task( +task_id = client.create_image_task( project=project, name=name, file_path=file_path, diff --git a/fastlabel/__init__.py b/fastlabel/__init__.py index 4c34e79..d237a23 100644 --- a/fastlabel/__init__.py +++ b/fastlabel/__init__.py @@ -127,21 +127,35 @@ def __putrequest(self, endpoint, payload=None): else: raise FastLabelException(error, r.status_code) - def find_task(self, task_id: str) -> dict: + def find_image_task(self, task_id: str) -> dict: """ - Find a signle task. + Find a signle image task. """ - endpoint = "tasks/" + task_id + endpoint = "tasks/image/" + task_id + return self.__getrequest(endpoint) + + def find_image_classification_task(self, task_id: str) -> dict: + """ + Find a signle image classification task. + """ + endpoint = "tasks/image/classification/" + task_id return self.__getrequest(endpoint) def find_multi_image_task(self, task_id: str) -> dict: """ Find a signle multi image task. """ - endpoint = "tasks/multi/image/" + task_id + endpoint = "tasks/multi-image/" + task_id return self.__getrequest(endpoint) - def get_tasks( + def find_video_task(self, task_id: str) -> dict: + """ + Find a signle video task. + """ + endpoint = "tasks/video/" + task_id + return self.__getrequest(endpoint) + + def get_image_tasks( self, project: str, status: str = None, @@ -150,7 +164,7 @@ def get_tasks( limit: int = 100, ) -> list: """ - Returns a list of tasks. + Returns a list of image tasks. Returns up to 1000 at a time, to get more, set offset as the starting position to fetch. project is slug of your project. (Required) @@ -159,7 +173,37 @@ def get_tasks( offset is the starting position number to fetch. (Optional) limit is the max number to fetch. (Optional) """ - endpoint = "tasks" + endpoint = "tasks/image" + params = {"project": project} + if status: + params["status"] = status + if tags: + params["tags"] = tags + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + return self.__getrequest(endpoint, params=params) + + def get_image_classification_tasks( + self, + project: str, + status: str = None, + tags: list = [], + offset: int = None, + limit: int = 100, + ) -> list: + """ + Returns a list of image classification tasks. + Returns up to 1000 at a time, to get more, set offset as the starting position to fetch. + + project is slug of your project. (Required) + status can be 'registered', 'in_progress', 'completed', 'skipped', 'in_review', 'send_backed', 'approved', 'customer_in_review', 'customer_send_backed', 'customer_approved'. (Optional) + tags is a list of tag. (Optional) + offset is the starting position number to fetch. (Optional) + limit is the max number to fetch. (Optional) + """ + endpoint = "tasks/image/classification" params = {"project": project} if status: params["status"] = status @@ -180,7 +224,40 @@ def get_multi_image_tasks( limit: int = 10, ) -> dict: """ - Returns a list of tasks. + Returns a list of multi image tasks. + Returns up to 10 at a time, to get more, set offset as the starting position to fetch. + + project is slug of your project. (Required) + status can be 'registered', 'in_progress', 'completed', 'skipped', 'in_review', 'send_backed', 'approved', 'customer_in_review', 'customer_send_backed', 'customer_approved'. (Optional) + tags is a list of tag. (Optional) + offset is the starting position number to fetch. (Optional) + limit is the max number to fetch. (Optional) + """ + if limit > 10: + raise FastLabelInvalidException( + "Limit must be less than or equal to 10.", 422) + endpoint = "tasks/multi-image" + params = {"project": project} + if status: + params["status"] = status + if tags: + params["tags"] = tags + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + return self.__getrequest(endpoint, params=params) + + def get_video_tasks( + self, + project: str, + status: str = None, + tags: list = [], + offset: int = None, + limit: int = 10, + ) -> dict: + """ + Returns a list of video tasks. Returns up to 10 at a time, to get more, set offset as the starting position to fetch. project is slug of your project. (Required) @@ -192,7 +269,7 @@ def get_multi_image_tasks( if limit > 10: raise FastLabelInvalidException( "Limit must be less than or equal to 10.", 422) - endpoint = "tasks/multi/image" + endpoint = "tasks/video" params = {"project": project} if status: params["status"] = status @@ -204,7 +281,7 @@ def get_multi_image_tasks( params["limit"] = limit return self.__getrequest(endpoint, params=params) - def create_task( + def create_image_task( self, project: str, name: str, @@ -214,7 +291,7 @@ def create_task( tags: list = [], ) -> str: """ - Create a single task. + Create a single image task. project is slug of your project. (Required) name is an unique identifier of task in your project. (Required) @@ -223,8 +300,8 @@ def create_task( annotations is a list of annotation to be set in advance. (Optional) tags is a list of tag to be set in advance. (Optional) """ - endpoint = "tasks" - if not self.__is_supported_ext(file_path): + endpoint = "tasks/image" + if not self.__is_image_supported_ext(file_path): raise FastLabelInvalidException( "Supported extensions are png, jpg, jpeg.", 422) file = self.__base64_encode(file_path) @@ -239,6 +316,39 @@ def create_task( payload["tags"] = tags return self.__postrequest(endpoint, payload=payload) + def create_image_classification_task( + self, + project: str, + name: str, + file_path: str, + status: str = None, + attributes: list = [], + tags: list = [], + ) -> str: + """ + Create a single image classification task. + + project is slug of your project. (Required) + name is an unique identifier of task in your project. (Required) + file_path is a path to data. Supported extensions are png, jpg, jpeg. (Required) + status can be 'registered', 'in_progress', 'completed', 'skipped', 'in_review', 'send_backed', 'approved', 'customer_in_review', 'customer_send_backed', 'customer_approved'. (Optional) + attributes is a list of attribute to be set in advance. (Optional) + tags is a list of tag to be set in advance. (Optional) + """ + endpoint = "tasks/image/classification" + if not self.__is_image_supported_ext(file_path): + raise FastLabelInvalidException( + "Supported extensions are png, jpg, jpeg.", 422) + file = self.__base64_encode(file_path) + payload = {"project": project, "name": name, "file": file} + if status: + payload["status"] = status + if attributes: + payload["attributes"] = attributes + if tags: + payload["tags"] = tags + return self.__postrequest(endpoint, payload=payload) + def create_multi_image_task( self, project: str, @@ -247,7 +357,7 @@ def create_multi_image_task( status: str = None, annotations: list = [], tags: list = [], - ) -> dict: + ) -> str: """ Create a single multi image task. @@ -262,11 +372,11 @@ def create_multi_image_task( raise FastLabelInvalidException( "Folder does not exist.", 422) - endpoint = "tasks/multi/image" + endpoint = "tasks/multi-image" file_paths = glob.glob(os.path.join(folder_path, "*")) contents = [] for file_path in file_paths: - if not self.__is_supported_ext(file_path): + if not self.__is_image_supported_ext(file_path): raise FastLabelInvalidException( "Supported extensions are png, jpg, jpeg.", 422) file = self.__base64_encode(file_path) @@ -283,6 +393,35 @@ def create_multi_image_task( payload["tags"] = tags return self.__postrequest(endpoint, payload=payload) + def create_video_task( + self, + project: str, + name: str, + file_path: str, + status: str = None, + tags: list = [], + ) -> str: + """ + Create a single video task. + + project is slug of your project. (Required) + name is an unique identifier of task in your project. (Required) + file_path is a path to data. Supported extensions are png, jpg, jpeg. (Required) + status can be 'registered', 'in_progress', 'completed', 'skipped', 'in_review', 'send_backed', 'approved', 'customer_in_review', 'customer_send_backed', 'customer_approved'. (Optional) + tags is a list of tag to be set in advance. (Optional) + """ + endpoint = "tasks/video" + if not self.__is_video_supported_ext(file_path): + raise FastLabelInvalidException( + "Supported extensions are mp4.", 422) + file = self.__base64_encode(file_path) + payload = {"project": project, "name": name, "file": file} + if status: + payload["status"] = status + if tags: + payload["tags"] = tags + return self.__postrequest(endpoint, payload=payload) + def update_task( self, task_id: str, @@ -355,9 +494,12 @@ def __base64_encode(self, file_path: str) -> str: with open(file_path, "rb") as f: return base64.b64encode(f.read()).decode() - def __is_supported_ext(self, file_path: str) -> bool: + def __is_image_supported_ext(self, file_path: str) -> bool: return file_path.lower().endswith(('.png', '.jpg', '.jpeg')) + def __is_video_supported_ext(self, file_path: str) -> bool: + return file_path.lower().endswith(('.mp4')) + def __get_categories(self, tasks: list) -> list: values = [] for task in tasks: diff --git a/setup.py b/setup.py index 94a52ef..9779540 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setuptools.setup( name="fastlabel", - version="0.6.0", + version="0.7.0", author="eisuke-ueta", author_email="eisuke.ueta@fastlabel.ai", description="The official Python SDK for FastLabel API, the Data Platform for AI",