From df197a41da123d561842d785c7b828776e35696e Mon Sep 17 00:00:00 2001 From: faycute Date: Mon, 22 Feb 2021 20:04:20 +0900 Subject: [PATCH 1/3] update to 0.3.0 --- fastlabel/__init__.py | 204 +++++++++++++++++++++++++----------------- fastlabel/const.py | 3 - 2 files changed, 122 insertions(+), 85 deletions(-) delete mode 100644 fastlabel/const.py diff --git a/fastlabel/__init__.py b/fastlabel/__init__.py index f4ce5e9..4141358 100644 --- a/fastlabel/__init__.py +++ b/fastlabel/__init__.py @@ -2,23 +2,23 @@ from logging import getLogger import requests - -from fastlabel.const import AnalysisType +import base64 logger = getLogger(__name__) -APP_BASE_URL = "https://prod.fastlabel.ai/projects/" -FASTLABEL_ENDPOINT = "https://api-fastlabel-production.web.app/api/v1/" +# FASTLABEL_ENDPOINT = "https://api.fastlabel.ai/v1/" +FASTLABEL_ENDPOINT = "http://localhost:4000/v1/" class Client: - api_key = None + access_token = None def __init__(self) -> None: - if not os.environ.get("FASTLABEL_API_KEY"): - raise ValueError("FASTLABEL_API_KEY is not configured.") - self.api_key = "Bearer " + os.environ.get("FASTLABEL_API_KEY") + if not os.environ.get("FASTLABEL_ACCESS_TOKEN"): + raise ValueError("FASTLABEL_ACCESS_TOKEN is not configured.") + self.access_token = "Bearer " + \ + os.environ.get("FASTLABEL_ACCESS_TOKEN") def _getrequest(self, endpoint: str, params=None) -> dict: """Makes a get request to an endpoint. @@ -29,19 +29,19 @@ def _getrequest(self, endpoint: str, params=None) -> dict: params = params or {} headers = { "Content-Type": "application/json", - "Authorization": self.api_key, + "Authorization": self.access_token, } - r = requests.get(FASTLABEL_ENDPOINT + endpoint, headers=headers, params=params) + r = requests.get(FASTLABEL_ENDPOINT + endpoint, + headers=headers, params=params) if r.status_code == 200: return r.json() else: try: - print(r.json()) - error = r.json()["error"] + error = r.json()["message"] except ValueError: error = r.text - if r.status_code == 400: + if str(r.status_code).startswith("4"): raise FastLabelInvalidException(error, r.status_code) else: raise FastLabelException(error, r.status_code) @@ -55,20 +55,18 @@ def _deleterequest(self, endpoint: str, params=None) -> dict: params = params or {} headers = { "Content-Type": "application/json", - "Authorization": self.api_key, + "Authorization": self.access_token, } r = requests.delete( FASTLABEL_ENDPOINT + endpoint, headers=headers, params=params ) - if r.status_code == 200: - return r.json() - else: + if r.status_code != 204: try: - error = r.json()["error"] + error = r.json()["message"] except ValueError: error = r.text - if r.status_code == 400: + if str(r.status_code).startswith("4"): raise FastLabelInvalidException(error, r.status_code) else: raise FastLabelException(error, r.status_code) @@ -82,43 +80,49 @@ def _postrequest(self, endpoint, payload=None): payload = payload or {} headers = { "Content-Type": "application/json", - "Authorization": self.api_key, + "Authorization": self.access_token, } - r = requests.post(FASTLABEL_ENDPOINT + endpoint, json=payload, headers=headers) + r = requests.post(FASTLABEL_ENDPOINT + endpoint, + json=payload, headers=headers) + print(r.json()) if r.status_code == 200: return r.json() else: try: - error = r.json()["error"] + error = r.json()["message"] except ValueError: error = r.text - if r.status_code == 400: + if str(r.status_code).startswith("4"): raise FastLabelInvalidException(error, r.status_code) else: raise FastLabelException(error, r.status_code) - def upload_predictions( - self, - project_id: str, - analysis_type: AnalysisType, - threshold: int, - predictions: list, - ) -> None: - """ - Upload predictions to analyze your model. + def _putrequest(self, endpoint, payload=None): + """Makes a put request to an endpoint. + If an error occurs, assumes that endpoint returns JSON as: + { 'statusCode': XXX, + 'error': 'I failed' } """ - endpoint = "predictions/upload" - payload = { - "projectId": project_id, - "analysisType": analysis_type, - "threshold": threshold, - "predictions": predictions, + payload = payload or {} + headers = { + "Content-Type": "application/json", + "Authorization": self.access_token, } - self._postrequest(endpoint, payload=payload) - logger.warn( - "Successfully uploaded! See " + APP_BASE_URL + project_id + "/modelAnalysis" - ) + r = requests.put(FASTLABEL_ENDPOINT + endpoint, + json=payload, headers=headers) + + if r.status_code == 200: + return r.json() + else: + try: + error = r.json()["message"] + except ValueError: + error = r.text + if str(r.status_code).startswith("4"): + raise FastLabelInvalidException(error, r.status_code) + else: + raise FastLabelException(error, r.status_code) def find_task(self, task_id: str) -> dict: """ @@ -129,70 +133,106 @@ def find_task(self, task_id: str) -> dict: def get_tasks( self, - project_id: str, + project: str, status: str = None, - review_status: str = None, + tags: list = [], + offset: int = None, limit: int = 100, - start_after: str = None, ) -> dict: """ Returns a list of tasks. Returns up to 100 at a time, to get more, set task id of the last page passed back to startAfter param. - project_id is id of your project. (Required) - status can be 'registered', 'registered', 'submitted' or 'skipped'. (Optional) - review_status can be 'notReviewed', 'inProgress', 'accepted' or 'declined'. (Optional) - limit is the max number of results to display per page, (Optional) - start_after can be use to fetch the next page of tasks. (Optional) + 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 to be set in advance. (Optional) + offset is the starting position number to fetch. (Optional) + limit is the max number to fetch. (Optional) """ - endpoint = "tasks/" - params = {"projectId": project_id} + endpoint = "tasks" + params = {"project": project} if status: params["status"] = status - if review_status: - params["reviewStatus"] = review_status + if tags: + params["tags"] = tags + if offset: + params["offset"] = offset if limit: params["limit"] = limit - if start_after: - params["startAfter"] = start_after return self._getrequest(endpoint, params=params) - def delete_task(self, task_id: str) -> None: + def create_task( + self, + project: str, + name: str, + file_path: str, + status: str = None, + annotations: list = [], + tags: list = [], + ) -> dict: """ - Delete a single task. + Create a single 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) + 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/" + task_id - self._deleterequest(endpoint) + endpoint = "tasks" + if not self.__is_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 annotations: + payload["annotations"] = annotations + if tags: + payload["tags"] = tags + return self._postrequest(endpoint, payload=payload) - def create_image_task( + def update_task( self, - project_id: str, - key: str, - url: str, + task_id: str, status: str = None, - review_status: str = None, - labels: list = [], - ) -> dict: + annotations: list = [], + tags: list = [], + ) -> None: """ - Create a single task for image project. - - project_id is id of your project. - key is an unique identifier of task in your project. (Required) - url is a link to get image data. (Required) - status can be 'registered', 'inProgress', 'submitted' or 'skipped'. (Optional) - review_status can be 'notReviewed', 'inProgress', 'accepted' or 'declined'. (Optional) - labels is a list of label to be set in advance. (Optional) + Update a single task. + + task_id is an id of the task. (Required) + status can be 'registered', 'in_progress', 'completed', 'skipped', 'in_review', 'send_backed', 'approved', 'customer_in_review', 'customer_send_backed', 'customer_approved'. (Optional) + annotations is a list of annotation to be set. (Optional) + tags is a list of tag to be set. (Optional) """ - endpoint = "tasks/image" - payload = {"projectId": project_id, "key": key, "url": url} + endpoint = "tasks/" + task_id + payload = {} if status: payload["status"] = status - if review_status: - payload["review_status"] = review_status - if labels: - payload["labels"] = labels - return self._postrequest(endpoint, payload=payload) + if annotations: + payload["annotations"] = annotations + if tags: + payload["tags"] = tags + return self._putrequest(endpoint, payload=payload) + + def delete_task(self, task_id: str) -> None: + """ + Delete a single task. + """ + endpoint = "tasks/" + task_id + self._deleterequest(endpoint) + + 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: + return file_path.lower().endswith(('.png', '.jpg', '.jpeg')) class FastLabelException(Exception): diff --git a/fastlabel/const.py b/fastlabel/const.py deleted file mode 100644 index 272e8e2..0000000 --- a/fastlabel/const.py +++ /dev/null @@ -1,3 +0,0 @@ -class AnalysisType(object): - bbox = "bbox" - line = "line" From 915ebc8a85b935087753b4ad96be5f1fc19121cd Mon Sep 17 00:00:00 2001 From: faycute Date: Tue, 23 Feb 2021 11:39:01 +0900 Subject: [PATCH 2/3] update documents --- README.md | 183 ++++++++++++++++------------------ examples/create_image_task.py | 61 ------------ examples/create_task.py | 33 ++++++ fastlabel/__init__.py | 5 +- setup.py | 2 +- 5 files changed, 124 insertions(+), 160 deletions(-) delete mode 100644 examples/create_image_task.py create mode 100644 examples/create_task.py diff --git a/README.md b/README.md index 71cd0bd..e93c93b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # FastLabel Python SDK +_If you are using FastLabel prototype, please install version 0.2.2._ + ## Installation ```bash @@ -13,7 +15,7 @@ $ pip install --upgrade fastlabel Configure API Key in environment variable. ```bash -export FASTLABEL_API_KEY="YOUR_API_KEY" +export FASTLABEL_ACCESS_TOKEN="YOUR_ACCESS_TOKEN" ``` Initialize fastlabel client. @@ -25,7 +27,7 @@ client = fastlabel.Client() ## Limitation -API is allowed to call 5000 times per hour. If you create/delete a large size of tasks, please wait a second for every requests. +API is allowed to call 1000 times per 10 minutes. If you create/delete a large size of tasks, please wait a second for every requests. ## Task @@ -34,34 +36,66 @@ API is allowed to call 5000 times per hour. If you create/delete a large size of - Create a new task. ```python -task = client.create_image_task( - project_id="YOUR_PROJECT_ID", - key="sample.jpg", - url="https://sample.com/sample.jpg" +task = client.create_task( + project="YOUR_PROJECT_SLUG", + name="sample.jpg", + file_path="./sample.jpg" ) ``` -- Create a new task with pre-defined labels. (Class should be configured on your project in advance) +- Create a new task with pre-defined annotations. (Class should be configured on your project in advance) ```python -task = client.create_image_task( - project_id="YOUR_PROJECT_ID", - key="sample.jpg", - url="https://sample.com/sample.jpg", - labels=[ - { - "type": "bbox", - "value": "bbox", - "points": [ - { "x": 100, "y": 100}, # top-left - { "x": 200, "y": 200} # bottom-right - ] - } - ] +task = client.create_task( + project="YOUR_PROJECT_SLUG", + name="sample.jpg", + file_path="./sample.jpg", + annotations=[{ + "value": "annotation-value", + "attributes": [ + { + "key": "attribute-key", + "value": "attribute-value" + } + ], + "points": [ + 100, # top-left x + 100, # top-left y + 200, # bottom-right x + 200 # bottom-right y + ] + }] ) ``` -> Check [examples/create_image_task.py](/examples/create_image_task.py) for other label types, such as line, keyPoint and polygon. +> Check [examples/create_task.py](/examples/create_task.py). + +### Update Task + +- Update a single task status, tags, and annotations. + +```python +task = client.update_task( + task_id="YOUR_TASK_ID", + status="approved", + tags=["tag1", "tag2"], + annotations=[{ + "value": "annotation-value", + "attributes": [ + { + "key": "attribute-key", + "value": "attribute-value" + } + ], + "points": [ + 100, # top-left x + 100, # top-left y + 200, # bottom-right x + 200 # bottom-right y + ] + }] +) +``` ### Find Task @@ -73,38 +107,39 @@ task = client.find_task(task_id="YOUR_TASK_ID") ### Get Tasks -- Get tasks. (Up to 100 tasks) +- Get tasks. (Up to 1000 tasks) ```python -tasks = client.get_tasks(project_id="YOUR_PROJECT_ID") +tasks = client.get_tasks(project="YOUR_PROJECT_SLUG") ``` -- Filter and Get tasks. (Up to 100 tasks) +- Filter and Get tasks. (Up to 1000 tasks) ```python tasks = client.get_tasks( - project_id="YOUR_PROJECT_ID", - status="submitted", # status can be 'registered', 'registered', 'submitted' or 'skipped' - review_status="accepted" # review_status can be 'notReviewed', 'inProgress', 'accepted' or 'declined' + 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 ) ``` -- Get a large size of tasks. (Over 100 tasks) +- Get a large size of tasks. (Over 1000 tasks) ```python import time # Iterate pages until new tasks are empty. all_tasks = [] -start_after = None +offset = None while True: time.sleep(1) - tasks = client.get_tasks(project_id="YOUR_PROJECT_ID", start_after=start_after) + tasks = client.get_tasks( + project="YOUR_PROJECT_SLUG", offset=offset) all_tasks.extend(tasks) if len(tasks) > 0: - start_after = tasks[-1]["id"] # Set the last task id to start_after + offset = len(all_tasks) # Set the offset else: break ``` @@ -113,6 +148,8 @@ while True: ### Delete Task +- Delete a single task. + ```python client.delete_task(task_id="YOUR_TASK_ID") ``` @@ -124,76 +161,32 @@ client.delete_task(task_id="YOUR_TASK_ID") ```python { "id": "YOUR_TASK_ID", - "key": "sample.png", - "assigneeId": null, - "assigneeName": null, + "name": "cat.jpg", + "url": "YOUR_TASK_URL", "status": "registered", - "reviewAssigneeId": null, - "reviewAssigneeName": null, - "reviewStatus": "notReviewed", - "projectId": "YOUR_PROJECT_ID", - "datasetId": "YOUR_DATASET_ID", - "labels": [ + "tags": [], + "annotations": [ { - "id": "YOUR_LABEL_ID", - "type": "bbox", - "value": "window", - "title": "窓", - "color": "#d9713e", - "metadata": [], + "attributes": [ + { "key": "kind", "name": "猫の種類", "type": "text", "value": "三毛猫" } + ], + "color": "#b36d18", "points": [ - { "x": 100, "y": 100}, # top-left - { "x": 200, "y": 200} # bottom-right - ] + 100, # top-left x + 100, # top-left y + 200, # bottom-right x + 200 # bottom-right y + ], + "title": "Cat", + "type": "bbox", + "value": "cat" } ], - "duration": 0, - "image": { - "width": 1500, - "height": 1200 - }, - "createdAt": "2020-12-25T15:02:00.513", - "updatedAt": "2020-12-25T15:02:00.513" + "createdAt": "2021-02-22T11:25:27.158Z", + "updatedAt": "2021-02-22T11:25:27.158Z" } ``` -## Model Analysis - -### Upload Predictions - -```python -# Create your model predictions -predictions = [ - { - "fileKey": "sample.jpg", # file name exists in your project - "labels": [ - { - "value": "bbox_a", # class value exists in your project - "points": [ - { "x": 10, "y": 10 }, # top-left - { "x": 20, "y": 20 }, # botom-right - ] - }, - { - "value": "bbox_b", - "points": [ - { "x": 30, "y": 30 }, - { "x": 40, "y": 40 }, - ] - } - ] - } -] - -# Upload predictions -client.upload_predictions( - project_id="YOUR_PROJECT_ID", # your fastlabel project id - analysis_type="bbox", # annotation type to be analyze (Only "bbox" or "line" are supported) - threshold=80, # IoU percentage/pixel distance to check labels are correct. (Ex: 0 - 100) - predictions=predictions -) -``` - ## API Docs -Check [this](https://api-fastlabel-production.web.app/api/doc/) for further information. +Check [this](https://api.fastlabel.ai/docs/) for further information. diff --git a/examples/create_image_task.py b/examples/create_image_task.py deleted file mode 100644 index d5eb0f8..0000000 --- a/examples/create_image_task.py +++ /dev/null @@ -1,61 +0,0 @@ -from pprint import pprint - -import fastlabel - -# Initialize client -client = fastlabel.Client() - -project_id = "YOUR_PROJECT_ID" -key = "YOUR_IMAGE_KEY" # Should be an unique in your project -url = "YOUR_IMAGE_URL" -labels = [ - { - "type": "bbox", - "value": "bbox", - "points": [ - {"x": 100, "y": 100}, # top-left - {"x": 200, "y": 200}, # bottom-right - ], - }, - { - "type": "line", - "value": "line", - "points": [{"x": 200, "y": 200}, {"x": 250, "y": 250}], - }, - {"type": "keyPoint", "value": "keyPoint", "points": {"x": 10, "y": 10}}, - { - "type": "polygon", - "value": "polygon", - "points": [ - {"x": 300, "y": 300}, - {"x": 320, "y": 320}, - {"x": 340, "y": 220}, - {"x": 310, "y": 200}, - ], - }, - { - "type": "polyline", - "value": "polyline", - "points": [ - {"x": 100, "y": 300}, - {"x": 120, "y": 320}, - {"x": 140, "y": 220}, - {"x": 110, "y": 200}, - ], - }, - { - "type": "segmentation", - "value": "segmentation", - "points": [ - [ - {"x": 400, "y": 400}, - {"x": 420, "y": 420}, - {"x": 440, "y": 420}, - {"x": 410, "y": 400}, - ] - ], - }, -] - -task = client.create_image_task(project_id=project_id, key=key, url=url, labels=labels) -pprint(task) diff --git a/examples/create_task.py b/examples/create_task.py new file mode 100644 index 0000000..795069c --- /dev/null +++ b/examples/create_task.py @@ -0,0 +1,33 @@ +from pprint import pprint + +import fastlabel + +# Initialize client +client = fastlabel.Client() + +project = "YOUR_PROJECT_SLUG" +name = "YOUR_DATA_NAME" +file_path = "YOUR_DATA_FILE_PATH" # e.g.) ./cat.jpg +annotations = [{ + "value": "cat", + "attributes": [ + { + "key": "kind", + "value": "三毛猫" + } + ], + "points": [ + 100, # top-left x + 100, # top-left y + 200, # bottom-right x + 200 # bottom-right y + ] +}] + +task = client.create_task( + project=project, + name=name, + file_path=file_path, + annotations=annotations +) +pprint(task) diff --git a/fastlabel/__init__.py b/fastlabel/__init__.py index 4141358..12579d9 100644 --- a/fastlabel/__init__.py +++ b/fastlabel/__init__.py @@ -6,8 +6,7 @@ logger = getLogger(__name__) -# FASTLABEL_ENDPOINT = "https://api.fastlabel.ai/v1/" -FASTLABEL_ENDPOINT = "http://localhost:4000/v1/" +FASTLABEL_ENDPOINT = "https://api.fastlabel.ai/v1/" class Client: @@ -146,7 +145,7 @@ def get_tasks( 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 to be set in advance. (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) """ diff --git a/setup.py b/setup.py index 8231a72..ede2f4f 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setuptools.setup( name="fastlabel", - version="0.2.2", + version="0.3.0", author="eisuke-ueta", author_email="eisuke.ueta@fastlabel.ai", description="The official Python SDK for FastLabel API, the Data Platform for AI", From cc050ef91d66939d14a83ed275b878d8fa169ffb Mon Sep 17 00:00:00 2001 From: faycute Date: Tue, 23 Feb 2021 20:07:18 +0900 Subject: [PATCH 3/3] remove print and fix return type --- .gitignore | 5 ++++- fastlabel/__init__.py | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 013b97f..8c97b39 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,7 @@ dist build fastlabel.egg-info test*.py -annotation.json \ No newline at end of file +annotation.json +*.jpg +*.jpeg +*.png diff --git a/fastlabel/__init__.py b/fastlabel/__init__.py index 12579d9..66223bd 100644 --- a/fastlabel/__init__.py +++ b/fastlabel/__init__.py @@ -84,7 +84,6 @@ def _postrequest(self, endpoint, payload=None): r = requests.post(FASTLABEL_ENDPOINT + endpoint, json=payload, headers=headers) - print(r.json()) if r.status_code == 200: return r.json() else: @@ -200,7 +199,7 @@ def update_task( status: str = None, annotations: list = [], tags: list = [], - ) -> None: + ) -> dict: """ Update a single task.