diff --git a/requirements.txt b/requirements.txt index f83e4510..422e6cd8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,13 +3,13 @@ chardet==4.0.0 cycler==0.10.0 idna==2.10 kiwisolver==1.3.1 -matplotlib==3.3.4 +matplotlib numpy==1.19.5 opencv-python==4.5.3.56 -Pillow==8.3.0 +Pillow pyparsing==2.4.7 -python-dateutil==2.8.1 +python-dateutil python-dotenv==0.18.0 requests==2.25.1 -six==1.16.0 +six urllib3==1.26.6 diff --git a/roboflow/__init__.py b/roboflow/__init__.py index 0886a45a..e03a5c11 100644 --- a/roboflow/__init__.py +++ b/roboflow/__init__.py @@ -68,7 +68,7 @@ def project(self, project_name, the_workspace=None): dataset_info = dataset_info.json()['project'] - return Project(self.api_key, dataset_info['id'], dataset_info['type'], dataset_info['versions']) + return Project(self.api_key, dataset_info) def __str__(self): json_value = {'api_key': self.api_key, diff --git a/roboflow/core/project.py b/roboflow/core/project.py index ad213876..0bb97be9 100644 --- a/roboflow/core/project.py +++ b/roboflow/core/project.py @@ -3,6 +3,7 @@ import json import os import urllib +import datetime import warnings import cv2 import requests @@ -12,25 +13,33 @@ #version class that should return class Project(): - def __init__(self, api_key, dataset_slug, type, workspace): - self.api_key = api_key - self.name = dataset_slug - self.category = type - self.workspace = workspace - self.all_versions = [] + def __init__(self, api_key, a_project): + self.__api_key = api_key + self.annotation = a_project['annotation'] + self.classes = a_project['classes'] + self.colors = a_project['colors'] + self.created = datetime.datetime.fromtimestamp(a_project['created']) + self.id = a_project['id'] + self.images = a_project['images'] + self.name = a_project['name'] + self.public = a_project['public'] + self.splits = a_project['splits'] + self.type = a_project['type'] + self.unannotated = a_project['unannotated'] + self.updated = datetime.datetime.fromtimestamp(a_project['updated']) + + temp = self.id.rsplit("/") + self.__workspace = temp[0] + self.__project_name = temp[1] def get_version_information(self): - - slug_splitted = self.name.rsplit("/") - p, w = slug_splitted[0], slug_splitted[1] - - dataset_info = requests.get(API_URL + "/" + p + "/" + w + "?api_key=" + self.api_key) + dataset_info = requests.get(API_URL + "/" + self.__workspace + "/" + self.__project_name + "?api_key=" + self.__api_key) # Throw error if dataset isn't valid/user doesn't have permissions to access the dataset if dataset_info.status_code != 200: raise RuntimeError(dataset_info.text) - dataset_info = dataset_info.json()['project'] + dataset_info = dataset_info.json() return dataset_info['versions'] def list_versions(self): @@ -41,9 +50,8 @@ def versions(self): version_info = self.get_version_information() version_array = [] for a_version in version_info: - version_object = Version((self.category if 'model' in a_version else None), self.api_key, self.name, a_version['id'], local=False) + version_object = Version(a_version, (self.type if 'model' in a_version else None), self.__api_key, self.name, a_version['id'], local=False) version_array.append(version_object) - return version_array def version(self, version_number): @@ -53,8 +61,8 @@ def version(self, version_number): for version_object in version_info: current_version_num = os.path.basename(version_object['id']) - if current_version_num == version_number: - vers = Version(self.category, self.api_key, self.name, current_version_num, local=False) + if current_version_num == str(version_number): + vers = Version(version_object, self.type, self.__api_key, self.name, current_version_num, local=False) return vers raise RuntimeError("Version number {} is not found.".format(version_number)) @@ -63,12 +71,15 @@ def __image_upload(self, image_path, hosted_image=False, split="train"): # If image is not a hosted image if not hosted_image: - project_name = os.path.basename(self.name) + + project_name = self.id.rsplit("/")[1] image_name = os.path.basename(image_path) + # Construct URL for local image upload + self.image_upload_url = "".join([ "https://api.roboflow.com/dataset/", project_name, "/upload", - "?api_key=", self.api_key, + "?api_key=", self.__api_key, "&name=" + image_name, "&split=" + split ]) @@ -93,9 +104,11 @@ def __image_upload(self, image_path, hosted_image=False, split="train"): else: # Hosted image upload url + project_name = self.id.rsplit("/")[1] + upload_url = "".join([ - "https://api.roboflow.com/dataset/" + self.name + "/upload", - "?api_key=" + self.api_key, + "https://api.roboflow.com/dataset/" + self.project_name + "/upload", + "?api_key=" + self.__api_key, "&name=" + os.path.basename(image_path), "&split=" + split, "&image=" + urllib.parse.quote_plus(image_path) @@ -112,7 +125,7 @@ def __annotation_upload(self, annotation_path, image_id): # Set annotation upload url self.annotation_upload_url = "".join([ "https://api.roboflow.com/dataset/", self.name, "/annotate/", image_id, - "?api_key=", self.api_key, + "?api_key=", self.__api_key, "&name=" + os.path.basename(annotation_path) ]) # Get annotation response @@ -128,7 +141,6 @@ def upload(self, image_path=None, annotation_path=None, hosted_image=False, imag if image_path is not None: # Upload Image Response response = self.__image_upload(image_path, hosted_image=hosted_image, split=split) - # Get JSON response values try: success, image_id = response.json()['success'], response.json()['id'] @@ -166,9 +178,9 @@ def upload(self, image_path=None, annotation_path=None, hosted_image=False, imag def __str__(self): # String representation of project json_str = { - "dataset_slug": self.name, - "task_type": self.category, - "workspace": self.workspace + "name": self.name, + "type": self.type, + "workspace": self.__workspace, } return json.dumps(json_str, indent=2) diff --git a/roboflow/core/version.py b/roboflow/core/version.py index 2db639f8..a5f5d308 100644 --- a/roboflow/core/version.py +++ b/roboflow/core/version.py @@ -5,27 +5,34 @@ class Version(): - def __init__(self, type, api_key, dataset_slug, version, local): - self.api_key = api_key - self.name = dataset_slug + def __init__(self, version_dict, type, api_key, name, version, local): + self.__api_key = api_key + self.name = name self.version = version - self.category = type + self.type = type + self.augmentation = version_dict['augmentation'] + self.created = version_dict['created'] + self.id = version_dict['id'] + self.images = version_dict['images'] + self.preprocessing = version_dict['preprocessing'] + self.splits = version_dict['splits'] version_without_workspace = os.path.basename(version) - if self.category == "object-detection": - self.model = ObjectDetectionModel(self.api_key, self.name, version_without_workspace, local=local) - elif self.category == "classification": - self.model = ClassificationModel(self.api_key, self.name, version_without_workspace, local=local) + if self.type == "object-detection": + self.model = ObjectDetectionModel(self.__api_key, self.id, self.name, version_without_workspace, local=local) + elif self.type == "classification": + self.model = ClassificationModel(self.__api_key, self.id, self.name, version_without_workspace, self.id, local=local) else: self.model = None def __str__(self): - json_value = {'api_key': self.api_key, - 'name': self.name, - 'model_type': str(self.model), - 'version': self.version} + json_value = { + 'name': self.name, + 'type': self.type, + 'version': self.version, + 'augmentation': self.augmentation, + 'created': self.created, + 'preprocessing': self.preprocessing, + 'splits': self.splits} return json.dumps(json_value, indent=2) - - - diff --git a/roboflow/core/workspace.py b/roboflow/core/workspace.py index 799fc1d6..1ec7ba20 100644 --- a/roboflow/core/workspace.py +++ b/roboflow/core/workspace.py @@ -1,41 +1,38 @@ import requests +import json from roboflow.core.project import Project from roboflow.config import * class Workspace(): def __init__(self, info, api_key, default_workspace): - - self.api_key = api_key - self.name = default_workspace - workspace_info = info['workspace'] + self.name = workspace_info['name'] + self.project_list = workspace_info['projects'] self.members = workspace_info['members'] self.url = workspace_info['url'] - self.project_list = [] - for value in info['workspace']['projects']: - self.project_list.append(value) + self.__api_key = api_key + def list_projects(self): - print(self.projects) + print(self.project_list) def projects(self): projects_array = [] for a_project in self.project_list: - split = a_project['id'].rsplit("/") - workspace, project_name = split[0], split[1] - proj = Project(self.api_key, project_name, a_project['type'], workspace) + proj = Project(self.__api_key, a_project) projects_array.append(proj) return projects_array def project(self, project_name): + project_name = project_name.replace(self.url + "/", "") if "/" in project_name: - raise RuntimeError("Do not re-specify the workspace {} in your project request".format(project_name.rsplit()[0])) + raise RuntimeError("The {} project is not available in this ({}) workspace".format(project_name, self.url)) - dataset_info = requests.get(API_URL + "/" + self.name + "/" + project_name + "?api_key=" + self.api_key) + dataset_info = requests.get(API_URL + "/" + self.url + "/" + project_name + "?api_key=" + self.__api_key) # Throw error if dataset isn't valid/user doesn't have permissions to access the dataset if dataset_info.status_code != 200: @@ -43,4 +40,13 @@ def project(self, project_name): dataset_info = dataset_info.json()['project'] - return Project(self.api_key, dataset_info['id'], dataset_info['type'], dataset_info['versions']) + return Project(self.__api_key, dataset_info) + + def __str__(self): + json_value = {'name': self.name, + 'url': self.url, + 'members': self.members, + 'projects': self.projects + } + + return json.dumps(json_value, indent=2) diff --git a/roboflow/models/classification.py b/roboflow/models/classification.py index d6692951..210a9afd 100644 --- a/roboflow/models/classification.py +++ b/roboflow/models/classification.py @@ -2,6 +2,7 @@ import io import os import urllib +import json import requests from PIL import Image @@ -12,23 +13,24 @@ class ClassificationModel: - def __init__(self, api_key, dataset_slug=None, version=None, local=False): + def __init__(self, api_key, id, name=None, version=None, local=False): """ :param api_key: - :param dataset_slug: + :param name: :param version: """ # Instantiate different API URL parameters - self.api_key = api_key - self.dataset_slug = dataset_slug + self.__api_key = api_key + self.id=id + self.name = name self.version = version if not local: self.base_url = "https://classify.roboflow.com/" else: self.base_url = "http://localhost:9001/" - if dataset_slug is not None and version is not None: + if self.name is not None and version is not None: self.__generate_url() def predict(self, image_path, hosted=False): @@ -67,15 +69,15 @@ def predict(self, image_path, hosted=False): image_path=image_path, prediction_type=CLASSIFICATION_MODEL) - def load_model(self, dataset_slug, version): + def load_model(self, name, version): """ - :param dataset_slug: + :param name: :param version: :return: """ # Load model based on user defined characteristics - self.dataset_slug = dataset_slug + self.name = name self.version = version self.__generate_url() @@ -86,10 +88,12 @@ def __generate_url(self): """ # Generates URL based on all parameters - without_workspace = os.path.basename(self.dataset_slug) + splitted = self.id.rsplit("/") + without_workspace = splitted[1] + self.api_url = "".join([ self.base_url + without_workspace + '/' + str(self.version), - "?api_key=" + self.api_key, + "?api_key=" + self.__api_key, "&name=YOUR_IMAGE.jpg"]) def __exception_check(self, image_path_check=None): @@ -102,3 +106,10 @@ def __exception_check(self, image_path_check=None): if image_path_check is not None: if not os.path.exists(image_path_check) and not check_image_url(image_path_check): raise Exception("Image does not exist at " + image_path_check + "!") + + def __str__(self): + json_value = {'name': self.name, + 'version': self.version, + 'base_url': self.base_url} + + return json.dumps(json_value, indent=2) diff --git a/roboflow/models/object_detection.py b/roboflow/models/object_detection.py index ae250124..84402cd5 100644 --- a/roboflow/models/object_detection.py +++ b/roboflow/models/object_detection.py @@ -5,6 +5,7 @@ import requests import urllib from PIL import Image +import json from roboflow.config import OBJECT_DETECTION_MODEL from roboflow.util.prediction import PredictionGroup @@ -13,13 +14,13 @@ class ObjectDetectionModel: - def __init__(self, api_key, dataset_slug=None, version=None, local=False, classes=None, overlap=30, confidence=40, + def __init__(self, api_key, id, name=None, version=None, local=False, classes=None, overlap=30, confidence=40, stroke=1, labels=False, format="json"): """ From Roboflow Docs: :param api_key: Your API key (obtained via your workspace API settings page) - :param dataset_slug: The url-safe version of the dataset name. You can find it in the web UI by looking at + :param name: The url-safe version of the dataset name. You can find it in the web UI by looking at the URL on the main project view or by clicking the "Get curl command" button in the train results section of your dataset version after training your model. :param local: Boolean value dictating whether to use the local server or hosted API @@ -39,8 +40,9 @@ def __init__(self, api_key, dataset_slug=None, version=None, local=False, classe """ # Instantiate different API URL parameters # To be moved to predict - self.api_key = api_key - self.dataset_slug = dataset_slug + self.__api_key = api_key + self.id = id + self.name = name self.version = version self.classes = classes self.overlap = overlap @@ -56,10 +58,10 @@ def __init__(self, api_key, dataset_slug=None, version=None, local=False, classe self.base_url = "http://localhost:9001/" # If dataset slug not none, instantiate API URL - if dataset_slug is not None and version is not None: + if name is not None and version is not None: self.__generate_url() - def load_model(self, dataset_slug, version, local=None, classes=None, overlap=None, confidence=None, + def load_model(self, name, version, local=None, classes=None, overlap=None, confidence=None, stroke=None, labels=None, format=None): """ Loads a Model based on a Model Endpoint @@ -67,7 +69,7 @@ def load_model(self, dataset_slug, version, local=None, classes=None, overlap=No :param model_endpoint: This is the endpoint that is loaded into the api_url """ # To load a model manually, they must specify a dataset slug - self.dataset_slug = dataset_slug + self.name = name self.version = version # Generate URL based on parameters self.__generate_url(local=local, classes=classes, overlap=overlap, confidence=confidence, @@ -157,11 +159,12 @@ def __generate_url(self, local=None, classes=None, overlap=None, confidence=None self.format = format # Create the new API URL - without_workspace = os.path.basename(self.dataset_slug) + splitted = self.id.rsplit("/") + without_workspace = splitted[1] self.api_url = "".join([ self.base_url + without_workspace + '/' + str(self.version), - "?api_key=" + self.api_key, + "?api_key=" + self.__api_key, "&name=YOUR_IMAGE.jpg", "&overlap=" + str(self.overlap), "&confidence=" + str(self.confidence), @@ -172,3 +175,22 @@ def __generate_url(self, local=None, classes=None, overlap=None, confidence=None # add classes parameter to api if self.classes is not None: self.api_url += "&classes=" + self.classes + def __str__(self): + # Create the new API URL + splitted = self.id.rsplit("/") + without_workspace = splitted[1] + + json_value = { + 'id': without_workspace + '/' + str(self.version), + 'name': self.name, + 'version': self.version, + 'classes': self.classes, + 'overlap': self.overlap, + 'confidence': self.confidence, + 'stroke': self.stroke, + 'labels': self.labels, + 'format': self.format, + 'base_url': self.base_url + } + + return json.dumps(json_value, indent=2)