Skip to content

Commit 8864d37

Browse files
authored
Merge pull request #196 from roboflow/upload-tests2
Add command line tools for roboflow-python
2 parents 39e838a + c1ac54d commit 8864d37

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+15995
-362
lines changed

.gitignore

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ share/python-wheels/
2828
.installed.cfg
2929
*.egg
3030
MANIFEST
31+
.gptexecthread
3132

3233
# PyInstaller
3334
# Usually these files are written by a python script from a template
@@ -146,10 +147,10 @@ tests/manual/data
146147

147148
#dataset download stuff
148149

149-
test/
150-
train/
151-
valid/
152-
data.yaml
150+
# test/
151+
# train/
152+
# valid/
153+
# data.yaml
153154
README.roboflow.txt
154155
*.zip
155156
.DS_Store

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@
1515
"editor.defaultFormatter": "ms-python.black-formatter"
1616
},
1717
"python.formatting.provider": "none",
18-
"python.testing.pytestEnabled": false
18+
"python.testing.pytestEnabled": false,
19+
"editor.inlineSuggest.showToolbar": "onHover"
1920
}

roboflow/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from roboflow.models import CLIPModel, GazeModel
1414
from roboflow.util.general import write_line
1515

16-
__version__ = "1.1.9"
16+
__version__ = "1.1.10"
1717

1818

1919
def check_key(api_key, model, notebook, num_retries=0):

roboflow/adapters/__init__.py

Whitespace-only changes.

roboflow/adapters/rfapi.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import json
2+
import os
3+
import urllib
4+
5+
import requests
6+
from requests_toolbelt.multipart.encoder import MultipartEncoder
7+
8+
from roboflow.config import API_URL, DEFAULT_BATCH_NAME
9+
from roboflow.util import image_utils
10+
11+
12+
class UploadError(Exception):
13+
pass
14+
15+
16+
def upload_image(
17+
api_key,
18+
project_url,
19+
image_path: str,
20+
hosted_image: bool = False,
21+
split: str = "train",
22+
batch_name: str = DEFAULT_BATCH_NAME,
23+
tag_names: list = [],
24+
**kwargs,
25+
):
26+
"""
27+
Upload an image to a specific project.
28+
29+
Args:
30+
image_path (str): path to image you'd like to upload
31+
hosted_image (bool): whether the image is hosted on Roboflow
32+
split (str): the dataset split the image to
33+
"""
34+
35+
# If image is not a hosted image
36+
if not hosted_image:
37+
batch_name = batch_name or DEFAULT_BATCH_NAME
38+
image_name = os.path.basename(image_path)
39+
imgjpeg = image_utils.file2jpeg(image_path)
40+
41+
upload_url = _local_upload_url(
42+
api_key, project_url, batch_name, tag_names, kwargs
43+
)
44+
m = MultipartEncoder(
45+
fields={
46+
"name": image_name,
47+
"split": split,
48+
"file": ("imageToUpload", imgjpeg, "image/jpeg"),
49+
}
50+
)
51+
response = requests.post(
52+
upload_url, data=m, headers={"Content-Type": m.content_type}
53+
)
54+
55+
else:
56+
# Hosted image upload url
57+
58+
upload_url = _hosted_upload_url(api_key, project_url, image_path, split)
59+
# Get response
60+
response = requests.post(upload_url)
61+
responsejson = None
62+
try:
63+
responsejson = response.json()
64+
except:
65+
pass
66+
if response.status_code != 200:
67+
if responsejson:
68+
raise UploadError(f"Bad response: {response.status_code}: {responsejson}")
69+
else:
70+
raise UploadError(f"Bad response: {response}")
71+
if not responsejson: # fail fast
72+
raise UploadError(
73+
f"upload image {image_path} 200 OK, unexpected response: {response}"
74+
)
75+
if not (responsejson.get("success") or responsejson.get("duplicate")):
76+
raise UploadError(f"Server rejected image: {responsejson}")
77+
return responsejson
78+
79+
80+
def save_annotation(
81+
api_key: str,
82+
project_url: str,
83+
annotation_name: str,
84+
annotation_string: str,
85+
image_id: str,
86+
is_prediction: bool = False,
87+
annotation_labelmap=None,
88+
):
89+
"""
90+
Upload an annotation to a specific project.
91+
92+
Args:
93+
annotation_path (str): path to annotation you'd like to upload
94+
image_id (str): image id you'd like to upload that has annotations for it.
95+
"""
96+
97+
upload_url = _save_annotation_url(
98+
api_key, project_url, annotation_name, image_id, is_prediction
99+
)
100+
101+
response = requests.post(
102+
upload_url,
103+
data=json.dumps(
104+
{"annotationFile": annotation_string, "labelmap": annotation_labelmap}
105+
),
106+
headers={"Content-Type": "application/json"},
107+
)
108+
responsejson = None
109+
try:
110+
responsejson = response.json()
111+
except:
112+
pass
113+
if not responsejson:
114+
raise _save_annotation_error(image_id, response)
115+
if response.status_code not in (200, 409):
116+
raise _save_annotation_error(image_id, response)
117+
if response.status_code == 409:
118+
if "already annotated" in responsejson.get("error", {}).get("message"):
119+
return {"warn": "already annotated"}
120+
else:
121+
raise _save_annotation_error(image_id, response)
122+
if responsejson.get("error"):
123+
raise _save_annotation_error(image_id, response)
124+
if not responsejson.get("success"):
125+
raise _save_annotation_error(image_id, response)
126+
return responsejson
127+
128+
129+
def _save_annotation_url(api_key, project_url, name, image_id, is_prediction):
130+
url = f"{API_URL}/dataset/{project_url}/annotate/{image_id}?api_key={api_key}&name={name}"
131+
if is_prediction:
132+
url += "&prediction=true"
133+
return url
134+
135+
136+
def _hosted_upload_url(api_key, project_url, image_path, split):
137+
url = f"{API_URL}/dataset/{project_url}/upload?api_key={api_key}"
138+
url += f"&name={os.path.basename(image_path)}&split={split}"
139+
url += f"&image={urllib.parse.quote_plus(image_path)}"
140+
return url
141+
142+
143+
def _local_upload_url(api_key, project_url, batch_name, tag_names, kwargs):
144+
url = f"{API_URL}/dataset/{project_url}/upload?api_key={api_key}&batch={batch_name}"
145+
for key, value in kwargs.items():
146+
url += f"&{str(key)}={str(value)}"
147+
for tag in tag_names:
148+
url += f"&tag={tag}"
149+
return url
150+
151+
152+
def _save_annotation_error(image_id, response):
153+
errmsg = f"save annotation for {image_id} / "
154+
responsejson = None
155+
try:
156+
responsejson = response.json()
157+
except:
158+
pass
159+
if not responsejson:
160+
errmsg += f"bad response: {response.status_code}: {response}"
161+
elif responsejson.get("error"):
162+
errmsg += f"bad response: {response.status_code}: {responsejson['error']}"
163+
else:
164+
errmsg += f"bad response: {response.status_code}: {responsejson}"
165+
return UploadError(errmsg)

0 commit comments

Comments
 (0)