Skip to content

Commit f6adc86

Browse files
committed
Merge branch 'main' of https://github.com/roboflow-ai/roboflow-python into feature/yolo-model-uploads
2 parents 257afbb + b22780d commit f6adc86

14 files changed

+736
-56
lines changed

Makefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,5 @@ check_code_quality:
1717

1818
publish:
1919
python setup.py sdist bdist_wheel
20-
twine upload -r testpypi dist/* -u ${PYPI_USERNAME} -p ${PYPI_TEST_PASSWORD} --verbose
2120
twine check dist/*
2221
twine upload dist/* -u ${PYPI_USERNAME} -p ${PYPI_PASSWORD} --verbose

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ wget
1717
tqdm>=4.41.0
1818
PyYAML>=5.3.1
1919
wget
20-
requests_toolbelt
20+
requests_toolbelt

roboflow/__init__.py

Lines changed: 110 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import json
2+
import os
23
import sys
34
import time
5+
from urllib.parse import urlparse
46

57
import requests
68

7-
from roboflow.config import API_URL, APP_URL, DEMO_KEYS
9+
from roboflow.config import API_URL, APP_URL, DEMO_KEYS, load_roboflow_api_key
810
from roboflow.core.project import Project
911
from roboflow.core.workspace import Workspace
12+
from roboflow.util.general import write_line
1013

11-
__version__ = "0.2.31"
14+
__version__ = "1.0.0"
1215

1316

1417
def check_key(api_key, model, notebook, num_retries=0):
@@ -67,14 +70,118 @@ def auth(api_key):
6770
return Roboflow(api_key, w)
6871

6972

73+
def login(workspace=None, force=False):
74+
conf_location = os.getenv(
75+
"ROBOFLOW_CONFIG_DIR",
76+
default=os.getenv("HOME") + "/.config/roboflow/config.json",
77+
)
78+
79+
if os.path.isfile(conf_location) and not force:
80+
write_line(
81+
"You are already logged into Roboflow. To make a different login, run roboflow.login(force=True)."
82+
)
83+
return None
84+
# we could eventually return the workspace object here
85+
# return Roboflow().workspace()
86+
elif os.path.isfile(conf_location) and force:
87+
os.remove(conf_location)
88+
89+
if workspace is None:
90+
write_line(
91+
"visit " + APP_URL + "/auth-cli" " to get your authentication token."
92+
)
93+
else:
94+
write_line(
95+
"visit "
96+
+ APP_URL
97+
+ "/auth-cli/?workspace="
98+
+ workspace
99+
+ " to get your authentication token."
100+
)
101+
102+
token = input("Paste the authentication here token here: ")
103+
104+
r_login = requests.get(APP_URL + "/query/cliAuthToken/" + token)
105+
106+
if r_login.status_code == 200:
107+
r_login = r_login.json()
108+
109+
# make config directory if it doesn't exist
110+
if not os.path.exists(os.path.dirname(conf_location)):
111+
os.mkdir(os.path.dirname(conf_location))
112+
113+
r_login = {"workspaces": r_login}
114+
# set first workspace as default workspace
115+
116+
default_workspace_id = list(r_login["workspaces"].keys())[0]
117+
workspace = r_login["workspaces"][default_workspace_id]
118+
r_login["RF_WORKSPACE"] = workspace["url"]
119+
120+
# write config file
121+
with open(conf_location, "w") as f:
122+
json.dump(r_login, f, indent=2)
123+
124+
else:
125+
r_login.raise_for_status()
126+
127+
return None
128+
# we could eventually return the workspace object here
129+
# return Roboflow().workspace()
130+
131+
132+
active_workspace = None
133+
134+
135+
def initialize_roboflow():
136+
global active_workspace
137+
138+
conf_location = os.getenv(
139+
"ROBOFLOW_CONFIG_DIR",
140+
default=os.getenv("HOME") + "/.config/roboflow/config.json",
141+
)
142+
143+
if not os.path.isfile(conf_location):
144+
raise RuntimeError(
145+
"To use this method, you must first login - run roboflow.login()"
146+
)
147+
else:
148+
if active_workspace == None:
149+
active_workspace = Roboflow().workspace()
150+
151+
return active_workspace
152+
153+
154+
def load_model(model_url):
155+
operate_workspace = initialize_roboflow()
156+
157+
if "universe.roboflow.com" in model_url or "app.roboflow.com" in model_url:
158+
parsed_url = urlparse(model_url)
159+
path_parts = parsed_url.path.split("/")
160+
project = path_parts[2]
161+
version = int(path_parts[-1])
162+
else:
163+
raise (
164+
"Model URL must be from either app.roboflow.com or universe.roboflow.com"
165+
)
166+
167+
project = operate_workspace.project(project)
168+
version = project.version(version)
169+
model = version.model
170+
return model
171+
172+
173+
# continue distributing this object for back compatibility
70174
class Roboflow:
71175
def __init__(
72176
self,
73-
api_key="YOUR ROBOFLOW API KEY HERE",
177+
api_key=None,
74178
model_format="undefined",
75179
notebook="undefined",
76180
):
77181
self.api_key = api_key
182+
if self.api_key == None:
183+
self.api_key = load_roboflow_api_key()
184+
78185
self.model_format = model_format
79186
self.notebook = notebook
80187
self.onboarding = False

roboflow/config.py

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,66 @@
1+
import json
12
import os
23

4+
5+
def get_conditional_configuration_variable(key, default):
6+
"""Retrieves the configuration variable conditionally.
7+
##1. check if variable is in environment
8+
##2. check if variable is in config file
9+
##3. return default value
10+
Args:
11+
key (string): The name of the configuration variable.
12+
default (string): The default value of the configuration variable.
13+
Returns:
14+
string: The value of the conditional configuration variable.
15+
"""
16+
17+
# default configuration location
18+
conf_location = os.getenv(
19+
"ROBOFLOW_CONFIG_DIR",
20+
default=os.getenv("HOME") + "/.config/roboflow/config.json",
21+
)
22+
23+
# read config file for roboflow if logged in from python or CLI
24+
if os.path.exists(conf_location):
25+
with open(conf_location) as f:
26+
config = json.load(f)
27+
else:
28+
config = {}
29+
30+
if os.getenv(key) != None:
31+
return os.getenv(key)
32+
elif key in config.keys():
33+
return config[key]
34+
else:
35+
return default
36+
37+
338
CLASSIFICATION_MODEL = os.getenv("CLASSIFICATION_MODEL", "ClassificationModel")
439
INSTANCE_SEGMENTATION_MODEL = "InstanceSegmentationModel"
540
OBJECT_DETECTION_MODEL = os.getenv("OBJECT_DETECTION_MODEL", "ObjectDetectionModel")
641
SEMANTIC_SEGMENTATION_MODEL = "SemanticSegmentationModel"
742
PREDICTION_OBJECT = os.getenv("PREDICTION_OBJECT", "Prediction")
843

9-
API_URL = os.getenv("API_URL", "https://api.roboflow.com")
10-
APP_URL = os.getenv("APP_URL", "https://app.roboflow.com")
11-
UNIVERSE_URL = os.getenv("UNIVERSE_URL", "https://universe.roboflow.com")
12-
INSTANCE_SEGMENTATION_URL = os.getenv(
44+
API_URL = get_conditional_configuration_variable("API_URL", "https://api.roboflow.com")
45+
APP_URL = get_conditional_configuration_variable("APP_URL", "https://app.roboflow.com")
46+
UNIVERSE_URL = get_conditional_configuration_variable(
47+
"UNIVERSE_URL", "https://universe.roboflow.com"
48+
)
49+
50+
INSTANCE_SEGMENTATION_URL = get_conditional_configuration_variable(
1351
"INSTANCE_SEGMENTATION_URL", "https://outline.roboflow.com"
1452
)
15-
SEMANTIC_SEGMENTATION_URL = os.getenv(
53+
SEMANTIC_SEGMENTATION_URL = get_conditional_configuration_variable(
1654
"SEMANTIC_SEGMENTATION_URL", "https://segment.roboflow.com"
1755
)
18-
OBJECT_DETECTION_URL = os.getenv(
56+
OBJECT_DETECTION_URL = get_conditional_configuration_variable(
1957
"SEMANTIC_SEGMENTATION_URL", "https://detect.roboflow.com"
2058
)
2159

22-
CLIP_FEATURIZE_URL = os.getenv("CLIP_FEATURIZE_URL", "CLIP FEATURIZE URL NOT IN ENV")
23-
OCR_URL = os.getenv("OCR_URL", "OCR URL NOT IN ENV")
60+
CLIP_FEATURIZE_URL = get_conditional_configuration_variable(
61+
"CLIP_FEATURIZE_URL", "CLIP FEATURIZE URL NOT IN ENV"
62+
)
63+
OCR_URL = get_conditional_configuration_variable("OCR_URL", "OCR URL NOT IN ENV")
2464

2565
DEMO_KEYS = ["coco-128-sample", "chess-sample-only-api-key"]
2666

@@ -30,3 +70,25 @@
3070
TYPE_SEMANTIC_SEGMENTATION = "semantic-segmentation"
3171

3272
DEFAULT_BATCH_NAME = "Pip Package Upload"
73+
74+
RF_WORKSPACES = get_conditional_configuration_variable("workspaces", default={})
75+
76+
77+
def load_roboflow_api_key():
78+
RF_WORKSPACE = get_conditional_configuration_variable("RF_WORKSPACE", default=None)
79+
RF_WORKSPACES = get_conditional_configuration_variable("workspaces", default={})
80+
81+
# DEFAULT_WORKSPACE = get_conditional_configuration_variable("default_workspace", default=None)
82+
if RF_WORKSPACE == None:
83+
RF_API_KEY = None
84+
else:
85+
RF_API_KEY = None
86+
for k in RF_WORKSPACES.keys():
87+
workspace = RF_WORKSPACES[k]
88+
if workspace["url"] == RF_WORKSPACE:
89+
RF_API_KEY = workspace["apiKey"]
90+
# ENV API_KEY OVERRIDE
91+
if os.getenv("RF_API_KEY") != None:
92+
RF_API_KEY = os.getenv("RF_API_KEY")
93+
94+
return RF_API_KEY

roboflow/core/project.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ def versions(self):
9494
workspace=self.__workspace,
9595
project=self.__project_name,
9696
public=self.public,
97+
colors=self.colors,
9798
)
9899
version_array.append(version_object)
99100
return version_array
@@ -192,12 +193,14 @@ def train(
192193
},
193194
speed=None,
194195
checkpoint=None,
196+
plot_in_notebook=False,
195197
) -> bool:
196198
"""
197199
Ask the Roboflow API to train a previously exported version's dataset.
198200
Args:
199201
speed: Whether to train quickly or accurately. Note: accurate training is a paid feature. Default speed is `fast`.
200202
checkpoint: A string representing the checkpoint to use while training
203+
plot: Whether to plot the training loss curve. Default is False.
201204
Returns:
202205
True
203206
RuntimeError: If the Roboflow API returns an error with a helpful JSON body
@@ -206,9 +209,11 @@ def train(
206209

207210
new_version = self.generate_version(settings=new_version_settings)
208211
new_version = self.version(new_version)
209-
new_version.train(speed=speed, checkpoint=checkpoint)
212+
new_model = new_version.train(
213+
speed=speed, checkpoint=checkpoint, plot_in_notebook=plot_in_notebook
214+
)
210215

211-
return True
216+
return new_model
212217

213218
def version(self, version_number, local=None):
214219
"""Retrieves information about a specific version, and throws it into an object.
@@ -251,6 +256,7 @@ def version(self, version_number, local=None):
251256
workspace=self.__workspace,
252257
project=self.__project_name,
253258
public=self.public,
259+
colors=self.colors,
254260
)
255261
return vers
256262

@@ -285,7 +291,7 @@ def __image_upload(
285291
# Construct URL for local image upload
286292
self.image_upload_url = "".join(
287293
[
288-
"https://api.roboflow.com/dataset/",
294+
API_URL + "/dataset/",
289295
project_name,
290296
"/upload",
291297
"?api_key=",
@@ -371,23 +377,28 @@ def __annotation_upload(self, annotation_path: str, image_id: str):
371377
}
372378

373379
# Set annotation upload url
380+
381+
project_name = self.id.rsplit("/")[1]
382+
374383
self.annotation_upload_url = "".join(
375384
[
376385
API_URL + "/dataset/",
377-
self.name,
386+
self.__project_name,
378387
"/annotate/",
379388
image_id,
380389
"?api_key=",
381390
self.__api_key,
382391
"&name=" + os.path.basename(annotation_path),
383392
]
384393
)
394+
385395
# Get annotation response
386396
annotation_response = requests.post(
387397
self.annotation_upload_url,
388398
data=annotation_string,
389399
headers={"Content-Type": "text/plain"},
390400
)
401+
391402
# Return annotation response
392403
return annotation_response
393404

@@ -564,10 +575,23 @@ def single_upload(
564575
annotation_response = self.__annotation_upload(annotation_path, image_id)
565576
# Check if upload was a success
566577
try:
567-
annotation_success = annotation_response.json()["success"]
568-
except Exception:
569-
warnings.warn(f"Bad response: {response}")
578+
response_data = annotation_response.json()
579+
if "success" in response_data.keys():
580+
annotation_success = True
581+
elif "error" in response_data.keys():
582+
warnings.warn(
583+
f"Uploading annotation data for image failed: {str(response_data['error'])}"
584+
)
585+
annotation_success = False
586+
else:
587+
warnings.warn(
588+
f"Uploading annotation data for image failed: {str(response_data)}"
589+
)
590+
annotation_success = False
591+
except:
592+
warnings.warn(f"Bad response: {response.status_code}")
570593
annotation_success = False
594+
571595
# Give user warning that annotation failed to upload
572596
if not annotation_success:
573597
warnings.warn(

0 commit comments

Comments
 (0)