Skip to content

Commit 159d0f6

Browse files
authored
Merge pull request #237 from roboflow/upload-more
Expand upload capabilities from the command line
2 parents bd5f01a + 6da06f7 commit 159d0f6

File tree

54 files changed

+930
-186
lines changed

Some content is hidden

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

54 files changed

+930
-186
lines changed

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/tests/manual/data

.github/workflows/test.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,4 @@ jobs:
3131
run: |
3232
make check_code_quality
3333
- name: 🧪 Test
34-
env:
35-
ROBOFLOW_API_KEY: ${{ secrets.ROBOFLOW_API_KEY }}
36-
PROJECT_NAME: ${{ secrets.PROJECT_NAME }}
37-
PROJECT_VERSION: ${{ secrets.PROJECT_VERSION }}
3834
run: "python -m unittest"

CLI-COMMANDS.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ options:
7878
-w WORKSPACE specify a workspace url or id (will use default workspace if not specified)
7979
-p PROJECT project will be created if it does not exist
8080
-c CONCURRENCY how many image uploads to perform concurrently (default: 10)
81-
-f FORMAT dataset format. Valid options are [voc, yolov8, yolov5, auto] (use auto for autodetect)
81+
-n BATCH_NAME name of batch to upload to within project
8282
```
8383

8484
## Example: download dataset
@@ -105,6 +105,26 @@ drwxr-xr-x@ 1214 tony staff 38K Jan 5 10:32 train
105105
drwxr-xr-x@ 118 tony staff 3.7K Jan 5 10:32 valid
106106
```
107107

108+
## Example: import a dataset
109+
110+
Upload a dataset from a folder to a project in your workspace
111+
112+
```bash
113+
roboflow import -w my-workspace -p my-chess ~/tmp/chess
114+
```
115+
116+
```
117+
loading Roboflow workspace...
118+
loading Roboflow project...
119+
Uploading to existing project my-workspace/my-chess
120+
[UPLOADED] /home/jonny/tmp/chess/102_jpg.rf.205e2a0cb0fabbbf32b4a936e2d6f1e4.jpg (sFpTfnyLpLA8QcqPwdvf) / annotations = OK
121+
[UPLOADED] /home/jonny/tmp/chess/2_jpg.rf.c1a4ed4e0c3947743b22ede09f7e1212.jpg (wDA2yxnLJWY5YwYwO7dP) / annotations = OK
122+
[UPLOADED] /home/jonny/tmp/chess/221_jpg.rf.e841c9bbb31a135b8f6274643f522686.jpg (UCv7MeuvEqo7PYElatEn) / annotations = OK
123+
[UPLOADED] /home/jonny/tmp/chess/10_jpg.rf.841f3ccdfc4b93ee68566e602025c03f.jpg (HnkCpUcYzxStvQF49VQW) / annotations = OK
124+
[UPLOADED] /home/jonny/tmp/chess/130_jpg.rf.29f756d510d2e488eb5e12769c7707ff.jpg (WxrFIhfaJ9H1JvaXMgfF) / annotations = OK
125+
[UPLOADED] /home/jonny/tmp/chess/112_jpg.rf.1a6e7b87410fa3f787f10e82bd02b54e.jpg (7tWtAn573cKrefeg5pIO) / annotations = OK
126+
```
127+
108128
## Example: list workspaces
109129
List the workspaces you have access to
110130

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ tests = ["B201", "B301"]
3030

3131
[tool.autoflake]
3232
check = true
33-
imports = ["cv2", "roboflow", "supervision"]
34-
33+
imports = ["cv2", "roboflow"]
3534

3635
[tool.ruff]
3736
target-version = "py38"

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ python-dateutil
1111
python-dotenv
1212
requests
1313
six
14-
supervision
1514
urllib3>=1.26.6
1615
tqdm>=4.41.0
1716
PyYAML>=5.3.1

roboflow/__init__.py

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

17-
__version__ = "1.1.21"
17+
__version__ = "1.1.22"
1818

1919

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

roboflow/adapters/rfapi.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import requests
66
from requests_toolbelt.multipart.encoder import MultipartEncoder
77

8-
from roboflow.config import API_URL, DEFAULT_BATCH_NAME
8+
from roboflow.config import API_URL, DEFAULT_BATCH_NAME, DEFAULT_JOB_NAME
99
from roboflow.util import image_utils
1010

1111

@@ -58,7 +58,6 @@ def upload_image(
5858

5959
# If image is not a hosted image
6060
if not hosted_image:
61-
batch_name = batch_name or DEFAULT_BATCH_NAME
6261
image_name = os.path.basename(image_path)
6362
imgjpeg = image_utils.file2jpeg(image_path)
6463

@@ -103,6 +102,7 @@ def save_annotation(
103102
annotation_name: str,
104103
annotation_string: str,
105104
image_id: str,
105+
job_name: str = DEFAULT_JOB_NAME,
106106
is_prediction: bool = False,
107107
annotation_labelmap=None,
108108
overwrite: bool = False,
@@ -115,7 +115,9 @@ def save_annotation(
115115
image_id (str): image id you'd like to upload that has annotations for it.
116116
"""
117117

118-
upload_url = _save_annotation_url(api_key, project_url, annotation_name, image_id, is_prediction, overwrite)
118+
upload_url = _save_annotation_url(
119+
api_key, project_url, annotation_name, image_id, job_name, is_prediction, overwrite
120+
)
119121

120122
response = requests.post(
121123
upload_url,
@@ -143,8 +145,10 @@ def save_annotation(
143145
return responsejson
144146

145147

146-
def _save_annotation_url(api_key, project_url, name, image_id, is_prediction, overwrite=False):
148+
def _save_annotation_url(api_key, project_url, name, image_id, job_name, is_prediction, overwrite=False):
147149
url = f"{API_URL}/dataset/{project_url}/annotate/{image_id}?api_key={api_key}" f"&name={name}"
150+
if job_name:
151+
url += f"&jobName={job_name}"
148152
if is_prediction:
149153
url += "&prediction=true"
150154
if overwrite:

roboflow/config.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,14 @@ def get_conditional_configuration_variable(key, default):
7171
TYPE_SEMANTIC_SEGMENTATION = "semantic-segmentation"
7272

7373
DEFAULT_BATCH_NAME = "Pip Package Upload"
74+
DEFAULT_JOB_NAME = "Annotated via API"
7475

7576
RF_WORKSPACES = get_conditional_configuration_variable("workspaces", default={})
7677

7778

7879
def load_roboflow_api_key(workspace_url=None):
79-
if os.getenv("RF_API_KEY") is not None:
80-
return os["RF_API_KEY"]
80+
if os.getenv("ROBOFLOW_API_KEY") is not None:
81+
return os.getenv("ROBOFLOW_API_KEY")
8182
RF_WORKSPACES = get_conditional_configuration_variable("workspaces", default={})
8283
workspaces_by_url = {w["url"]: w for w in RF_WORKSPACES.values()}
8384
default_workspace_url = get_conditional_configuration_variable("RF_WORKSPACE", default=None)

roboflow/core/project.py

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
import json
33
import os
44
import sys
5+
import time
56
import warnings
67

78
import requests
89
from PIL import Image, UnidentifiedImageError
910

1011
from roboflow.adapters import rfapi
11-
from roboflow.config import API_URL, DEFAULT_BATCH_NAME, DEMO_KEYS
12+
from roboflow.config import API_URL, DEMO_KEYS
1213
from roboflow.core.version import Version
1314
from roboflow.util.general import retry
1415
from roboflow.util.image_utils import load_labelmap
@@ -361,7 +362,7 @@ def upload(
361362
image_id: str = None,
362363
split: str = "train",
363364
num_retry_uploads: int = 0,
364-
batch_name: str = DEFAULT_BATCH_NAME,
365+
batch_name: str = None,
365366
tag_names: list = [],
366367
is_prediction: bool = False,
367368
**kwargs,
@@ -454,7 +455,7 @@ def single_upload(
454455
image_id=None,
455456
split="train",
456457
num_retry_uploads=0,
457-
batch_name=DEFAULT_BATCH_NAME,
458+
batch_name=None,
458459
tag_names=[],
459460
is_prediction: bool = False,
460461
annotation_overwrite=False,
@@ -470,44 +471,64 @@ def single_upload(
470471
if isinstance(annotation_labelmap, str):
471472
annotation_labelmap = load_labelmap(annotation_labelmap)
472473
uploaded_image, uploaded_annotation = None, None
474+
upload_time = None
473475
if image_path:
474-
uploaded_image = retry(
475-
num_retry_uploads,
476-
Exception,
477-
rfapi.upload_image,
478-
self.__api_key,
479-
project_url,
480-
image_path,
481-
hosted_image=hosted_image,
482-
split=split,
483-
batch_name=batch_name,
484-
tag_names=tag_names,
485-
sequence_number=sequence_number,
486-
sequence_size=sequence_size,
487-
**kwargs,
488-
)
489-
image_id = uploaded_image["id"]
476+
t0 = time.time()
477+
try:
478+
uploaded_image = retry(
479+
num_retry_uploads,
480+
Exception,
481+
rfapi.upload_image,
482+
self.__api_key,
483+
project_url,
484+
image_path,
485+
hosted_image=hosted_image,
486+
split=split,
487+
batch_name=batch_name,
488+
tag_names=tag_names,
489+
sequence_number=sequence_number,
490+
sequence_size=sequence_size,
491+
**kwargs,
492+
)
493+
image_id = uploaded_image["id"]
494+
except BaseException as e:
495+
uploaded_image = {"error": e}
496+
finally:
497+
upload_time = time.time() - t0
490498

491-
if annotation_path:
499+
annotation_time = None
500+
if annotation_path and image_id:
492501
annotation_name, annotation_str = self._annotation_params(annotation_path)
493502
try:
503+
t0 = time.time()
494504
uploaded_annotation = rfapi.save_annotation(
495505
self.__api_key,
496506
project_url,
497507
annotation_name,
498508
annotation_str,
499509
image_id,
510+
job_name=batch_name,
500511
is_prediction=is_prediction,
501512
annotation_labelmap=annotation_labelmap,
502513
overwrite=annotation_overwrite,
503514
)
504515
except BaseException as e:
505516
uploaded_annotation = {"error": e}
506-
return {"image": uploaded_image, "annotation": uploaded_annotation}
517+
finally:
518+
annotation_time = time.time() - t0
519+
return {
520+
"image": uploaded_image,
521+
"annotation": uploaded_annotation,
522+
"upload_time": upload_time,
523+
"annotation_time": annotation_time,
524+
}
507525

508526
def _annotation_params(self, annotation_path):
509527
annotation_name, annotation_string = None, None
510-
if os.path.exists(annotation_path):
528+
if isinstance(annotation_path, dict):
529+
annotation_name = annotation_path["name"]
530+
annotation_string = json.dumps(annotation_path["parsed"])
531+
elif os.path.exists(annotation_path):
511532
with open(annotation_path, "r"):
512533
annotation_string = open(annotation_path, "r").read()
513534
annotation_name = os.path.basename(annotation_path)

0 commit comments

Comments
 (0)