Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New command line tool for working with tasks #732

Merged
merged 2 commits into from
Sep 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ability to dump/load annotations in several formats from UI (CVAT, Pascal VOC, YOLO, MS COCO, png mask, TFRecord)
- Auth for REST API (api/v1/auth/): login, logout, register, ...
- Preview for the new CVAT UI (dashboard only) is available: http://localhost:9080/
- Added command line tool for performing common task operations (/utils/cli/)

### Changed
- Outside and keyframe buttons in the side panel for all interpolation shapes (they were only for boxes before)
Expand Down
42 changes: 42 additions & 0 deletions utils/cli/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env python3
#
# SPDX-License-Identifier: MIT
import logging
import requests
import sys
from http.client import HTTPConnection
from core.core import CLI, CVAT_API_V1
from core.definition import parser
log = logging.getLogger(__name__)


def config_log(level):
log = logging.getLogger('core')
log.addHandler(logging.StreamHandler(sys.stdout))
log.setLevel(level)
if level <= logging.DEBUG:
HTTPConnection.debuglevel = 1


def main():
actions = {'create': CLI.tasks_create,
'delete': CLI.tasks_delete,
'ls': CLI.tasks_list,
'frames': CLI.tasks_frame,
'dump': CLI.tasks_dump}
args = parser.parse_args()
config_log(args.loglevel)
with requests.Session() as session:
session.auth = args.auth
api = CVAT_API_V1(args.server_host, args.server_port)
cli = CLI(session, api)
try:
actions[args.action](cli, **args.__dict__)
except (requests.exceptions.HTTPError,
requests.exceptions.ConnectionError,
requests.exceptions.RequestException) as e:
log.info(e)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log.critical



if __name__ == '__main__':
main()
3 changes: 3 additions & 0 deletions utils/cli/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: MIT
from .definition import parser, ResourceType # noqa
from .core import CLI, CVAT_API_V1 # noqa
140 changes: 140 additions & 0 deletions utils/cli/core/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# SPDX-License-Identifier: MIT
import json
import logging
import os
import requests
from io import BytesIO
from PIL import Image
from .definition import ResourceType
log = logging.getLogger(__name__)


class CLI():

def __init__(self, session, api):
self.api = api
self.session = session

def tasks_data(self, task_id, resource_type, resources):
""" Add local, remote, or shared files to an existing task. """
url = self.api.tasks_id_data(task_id)
data = None
files = None
if resource_type == ResourceType.LOCAL:
files = {f'client_files[{i}]': open(f, 'rb') for i, f in enumerate(resources)}
elif resource_type == ResourceType.REMOTE:
data = {f'remote_files[{i}]': f for i, f in enumerate(resources)}
elif resource_type == ResourceType.SHARE:
data = {f'server_files[{i}]': f for i, f in enumerate(resources)}
response = self.session.post(url, data=data, files=files)
response.raise_for_status()

def tasks_list(self, use_json_output, **kwargs):
""" List all tasks in either basic or JSON format. """
url = self.api.tasks
response = self.session.get(url)
response.raise_for_status()
page = 1
while True:
response_json = response.json()
for r in response_json['results']:
if use_json_output:
log.info(json.dumps(r, indent=4))
else:
log.info(f'{r["id"]},{r["name"]},{r["status"]}')
if not response_json['next']:
return
page += 1
url = self.api.tasks_page(page)
response = self.session.get(url)
response.raise_for_status()

def tasks_create(self, name, labels, bug, resource_type, resources, **kwargs):
""" Create a new task with the given name and labels JSON and
add the files to it. """
url = self.api.tasks
data = {'name': name,
'labels': labels,
'bug_tracker': bug,
'image_quality': 50}
response = self.session.post(url, json=data)
response.raise_for_status()
response_json = response.json()
log.info(f'Created task ID: {response_json["id"]} '
f'NAME: {response_json["name"]}')
self.tasks_data(response_json['id'], resource_type, resources)

def tasks_delete(self, task_ids, **kwargs):
""" Delete a list of tasks, ignoring those which don't exist. """
for task_id in task_ids:
url = self.api.tasks_id(task_id)
response = self.session.delete(url)
try:
response.raise_for_status()
log.info(f'Task ID {task_id} deleted')
except requests.exceptions.HTTPError as e:
if response.status_code == 404:
log.info(f'Task ID {task_id} not found')
else:
raise e

def tasks_frame(self, task_id, frame_ids, outdir='', **kwargs):
""" Download the requested frame numbers for a task and save images as
task_<ID>_frame_<FRAME>.jpg."""
for frame_id in frame_ids:
url = self.api.tasks_id_frame_id(task_id, frame_id)
response = self.session.get(url)
response.raise_for_status()
im = Image.open(BytesIO(response.content))
outfile = f'task_{task_id}_frame_{frame_id:06d}.jpg'
im.save(os.path.join(outdir, outfile))

def tasks_dump(self, task_id, fileformat, filename, **kwargs):
""" Download annotations for a task in the specified format
(e.g. 'YOLO ZIP 1.0')."""
url = self.api.tasks_id(task_id)
response = self.session.get(url)
response.raise_for_status()
response_json = response.json()

url = self.api.tasks_id_annotations_filename(task_id,
response_json['name'],
fileformat)
while True:
response = self.session.get(url)
response.raise_for_status()
log.info(f'STATUS {response.status_code}')
if response.status_code == 201:
break

response = self.session.get(url + '&action=download')
response.raise_for_status()

with open(filename, 'wb') as fp:
fp.write(response.content)


class CVAT_API_V1():
""" Build parameterized API URLs """

def __init__(self, host, port):
self.base = f'http://{host}:{port}/api/v1/'

@property
def tasks(self):
return f'{self.base}tasks'

def tasks_page(self, page_id):
return f'{self.tasks}?page={page_id}'

def tasks_id(self, task_id):
return f'{self.tasks}/{task_id}'

def tasks_id_data(self, task_id):
return f'{self.tasks}/{task_id}/data'

def tasks_id_frame_id(self, task_id, frame_id):
return f'{self.tasks}/{task_id}/frames/{frame_id}'

def tasks_id_annotations_filename(self, task_id, name, fileformat):
return f'{self.tasks}/{task_id}/annotations/{name}?format={fileformat}'
Loading