diff --git a/README.md b/README.md index 5b2e95a..a6bc532 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ - [Task](#task) - [Image](#image) - [Image Classification](#image-classification) + - [Multi Image Classification](#multi-image-classification) - [Sequential Image](#sequential-image) - [Video](#video) - [Video Classification](#video-classification) @@ -560,6 +561,129 @@ Example of a single image classification task object } ``` +### Multi Image Classification + +Supported following project types: + +- Multi Image - Classification + +#### Create Task + +Create a new task. + +```python +task = client.create_multi_image_classification_task( + project="YOUR_PROJECT_SLUG", + name="sample", + folder_path="./sample", + priority=10, # (optional) none: 0, low: 10, medium: 20, high: 30 + attributes=[ + { + "type": "text", + "key": "attribute-key", + "value": "attribute-value" + } + ] +) +``` + +##### Limitation + + +- You can upload up to a size of 20 MB. +- You can upload up to a total size of 2 GB. +- You can upload up to 6 files in total. + +#### Find Task + +Find a single task. + +```python +task = client.find_multi_image_classification_task(task_id="YOUR_TASK_ID") +``` + +Find a single task by name. + +```python +tasks = client.find_multi_image_classification_task_by_name(project="YOUR_PROJECT_SLUG", task_name="YOUR_TASK_NAME") +``` + +#### Get Tasks + +Get tasks. + +```python +tasks = client.get_multi_image_classification_tasks(project="YOUR_PROJECT_SLUG") +``` + +#### Update Task + +Update a single task. + +```python +task_id = client.update_multi_image_classification_task( + task_id="YOUR_TASK_ID", + status="approved", + assignee="USER_SLUG", + tags=["tag1", "tag2"], + priority=10, # (optional) none: 0, low: 10, medium: 20, high: 30 + attributes=[ + { + "type": "text", + "key": "attribute-key", + "value": "attribute-value" + } + ] +) +``` + +#### Response + +Example of a single task object + +```python +{ + "id": "YOUR_TASK_ID", + "name": "sample", + "contents": [ + { + "name": "content-name-1", + "url": "content-url-1", + "width": 100, + "height": 100, + }, + { + "name": "content-name-2", + "url": "content-url-2", + "width": 100, + "height": 100, + } + ], + "status": "registered", + "externalStatus": "registered", + "priority": 10, + "tags": [], + "assignee": "ASSIGNEE_NAME", + "reviewer": "REVIEWER_NAME", + "externalAssignee": "EXTERNAL_ASSIGNEE_NAME", + "externalReviewer": "EXTERNAL_REVIEWER_NAME", + "attributes": [ + { + "type": "text", + "key": "attribute-key-1", + "value": "attribute-value-1" + }, + { + "type": "text", + "key": "attribute-key-2", + "value": "attribute-value-2" + } + ], + "createdAt": "2021-02-22T11:25:27.158Z", + "updatedAt": "2021-02-22T11:25:27.158Z" +} +``` + ### Sequential Image Supported following project types: diff --git a/fastlabel/__init__.py b/fastlabel/__init__.py index dd28ffe..c771ba4 100644 --- a/fastlabel/__init__.py +++ b/fastlabel/__init__.py @@ -70,6 +70,29 @@ def find_image_classification_task(self, task_id: str) -> dict: """ endpoint = "tasks/image/classification/" + task_id return self.api.get_request(endpoint) + + def find_multi_image_classification_task(self, task_id: str) -> dict: + """ + Find a single multi image classification task. + """ + endpoint = "tasks/multi-image/classification/" + task_id + return self.api.get_request(endpoint) + + def find_multi_image_classification_task_by_name( + self, project: str, task_name: str + ) -> dict: + """ + Find a single multi image classification task by name. + + project is slug of your project (Required). + task_name is a task name (Required). + """ + tasks = self.get_multi_image_classification_tasks( + project=project, task_name=task_name + ) + if not tasks: + return None + return tasks[0] def find_image_classification_task_by_name( self, project: str, task_name: str @@ -409,6 +432,46 @@ def get_image_classification_tasks( if limit: params["limit"] = limit return self.api.get_request(endpoint, params=params) + + def get_multi_image_classification_tasks( + self, + project: str, + status: str = None, + external_status: str = None, + tags: list = [], + task_name: str = None, + offset: int = None, + limit: int = 100, + ) -> list: + """ + Returns a list of multi 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', 'completed', 'skipped', 'reviewed', 'sent_back', + 'approved', 'declined' (Optional). + external_status can be 'registered', 'completed', 'skipped', 'reviewed', + 'sent_back', 'approved', 'declined', 'customer_declined' (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/multi-image/classification" + params = {"project": project} + if status: + params["status"] = status + if external_status: + params["externalStatus"] = external_status + if tags: + params["tags"] = tags + 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 get_sequential_image_tasks( self, @@ -1134,6 +1197,96 @@ def create_integrated_image_classification_task( self.__fill_assign_users(payload, **kwargs) return self.api.post_request(endpoint, payload=payload) + + def create_multi_image_classification_task( + self, + project: str, + name: str, + folder_path: str, + status: str = None, + external_status: str = None, + priority: Priority = None, + attributes: list = [], + tags: list = [], + is_delete_exif: bool = False, + **kwargs, + ) -> str: + """ + Create a single multi image classification task. + + project is slug of your project (Required). + name is an unique identifier of task in your project (Required). + folder_path is a path to data folder. Files should be under the folder. + status can be 'registered', 'completed', 'skipped', 'reviewed', 'sent_back', + 'approved', 'declined' (Optional). + external_status can be 'registered', 'completed', 'skipped', 'reviewed', + 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, + 'sent_back', 'approved', 'declined', 'customer_declined' (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). + 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/multi-image/classification" + if not os.path.isdir(folder_path): + raise FastLabelInvalidException("Folder does not exist.", 422) + file_paths = glob.glob(os.path.join(folder_path, "*")) + if not file_paths: + raise FastLabelInvalidException("Folder does not have any file.", 422) + contents = [] + contents_size = 0 + for file_path in file_paths: + if not utils.is_image_supported_ext(file_path): + raise FastLabelInvalidException( + "Supported extensions are png, jpg, jpeg.", 422 + ) + + if not utils.is_image_supported_size(file_path): + raise FastLabelInvalidException( + "Supported image size is under 20 MB.", 422 + ) + + if len(contents) == 6: + raise FastLabelInvalidException( + "The count of files should be under 6", 422 + ) + + file = utils.base64_encode(file_path) + contents.append({"name": os.path.basename(file_path), "file": file}) + contents_size += utils.get_json_length(contents[-1]) + if contents_size > const.SUPPORTED_CONTENTS_SIZE: + raise FastLabelInvalidException( + "Supported contents size is under" + f" {const.SUPPORTED_CONTENTS_SIZE}.", + 422, + ) + + payload = {"project": project, "name": name, "contents": contents} + if status: + payload["status"] = status + if external_status: + payload["externalStatus"] = external_status + if priority is not None: + payload["priority"] = priority + if attributes: + payload["attributes"] = delete_extra_attributes_parameter(attributes) + if tags: + payload["tags"] = tags + if is_delete_exif: + payload["isDeleteExif"] = is_delete_exif + + self.__fill_assign_users(payload, **kwargs) + + return self.api.post_request(endpoint, payload=payload) def create_sequential_image_task( self, @@ -1982,6 +2135,56 @@ def update_image_classification_task( self.__fill_assign_users(payload, **kwargs) return self.api.put_request(endpoint, payload=payload) + + def update_multi_image_classification_task( + self, + task_id: str, + status: str = None, + external_status: str = None, + priority: Priority = None, + attributes: list = [], + tags: list = [], + **kwargs, + ) -> str: + """ + Update a single multi image classification task. + + task_id is an id of the task (Required). + status can be 'registered', 'completed', 'skipped', 'reviewed', 'sent_back', + 'approved', 'declined' (Optional). + external_status can be 'registered', 'completed', 'skipped', 'reviewed', + 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, + 'sent_back', 'approved', 'declined', 'customer_declined' (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). + 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/multi-image/classification/" + task_id + payload = {} + if status: + payload["status"] = status + if external_status: + payload["externalStatus"] = external_status + if priority is not None: + payload["priority"] = priority + if attributes: + payload["attributes"] = delete_extra_attributes_parameter(attributes) + if tags: + payload["tags"] = tags + + self.__fill_assign_users(payload, **kwargs) + + return self.api.put_request(endpoint, payload=payload) def update_sequential_image_task( self,