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

Slightly enhance command line interface feature #746

Merged
merged 5 commits into from
Oct 2, 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
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ python:

services:
- docker

before_script:
- docker-compose -f docker-compose.yml -f docker-compose.ci.yml up --build -d

script:
- docker exec -it cvat /bin/bash -c 'python3 manage.py test cvat/apps/engine'
- docker exec -it cvat /bin/bash -c 'python3 manage.py test cvat/apps/engine utils/cli'
- docker exec -it cvat /bin/bash -c 'cd cvat-core && npm install && npm run test && npm run coveralls'
1 change: 1 addition & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
"--settings",
"cvat.settings.testing",
"cvat/apps/engine",
"utils/cli"
],
"django": true,
"cwd": "${workspaceFolder}",
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ RUN if [ "$WITH_DEXTR" = "yes" ]; then \
fi

COPY ssh ${HOME}/.ssh
COPY utils ${HOME}/utils
COPY cvat/ ${HOME}/cvat
COPY cvat-core/ ${HOME}/cvat-core
COPY tests ${HOME}/tests
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ CVAT is free, online, interactive video and image annotation tool for computer v
- [Installation guide](cvat/apps/documentation/installation.md)
- [User's guide](cvat/apps/documentation/user_guide.md)
- [Django REST API documentation](#rest-api)
- [Command line interface](utils/cli/)
- [XML annotation format](cvat/apps/documentation/xml_format.md)
- [AWS Deployment Guide](cvat/apps/documentation/AWS-Deployment-Guide.md)
- [Questions](#questions)
Expand Down
44 changes: 44 additions & 0 deletions utils/cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Command line interface (CLI)
**Description**
A simple command line interface for working with CVAT tasks. At the moment it
implements a basic feature set but may serve as the starting point for a more
comprehensive CVAT administration tool in the future.

Overview of functionality:

- Create a new task (supports name, bug tracker, labels JSON, local/share/remote files)
- Delete tasks (supports deleting a list of task IDs)
- List all tasks (supports basic CSV or JSON output)
- Download JPEG frames (supports a list of frame IDs)
- Dump annotations (supports all formats via format string)

**Usage**
```bash
usage: cli.py [-h] [--auth USER:[PASS]] [--server-host SERVER_HOST]
[--server-port SERVER_PORT] [--debug]
{create,delete,ls,frames,dump} ...

Perform common operations related to CVAT tasks.

positional arguments:
{create,delete,ls,frames,dump}

optional arguments:
-h, --help show this help message and exit
--auth USER:[PASS] defaults to the current user and supports the PASS
environment variable or password prompt.
--server-host SERVER_HOST
host (default: localhost)
--server-port SERVER_PORT
port (default: 8080)
--debug show debug output
```
**Examples**
- List all tasks
`cli.py --auth user:pass --server-host localhost --server-port 8080 ls`
- Create a task
`cli.py create --name "new task" --labels labels.json local file1.jpg file2.jpg`
- Delete some tasks
`cli.py delete 100 101 102`
- Dump annotations
`cli.py dump --format "CVAT XML 1.1 for images" 103 output.xml`
2 changes: 1 addition & 1 deletion utils/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def main():
except (requests.exceptions.HTTPError,
requests.exceptions.ConnectionError,
requests.exceptions.RequestException) as e:
log.info(e)
log.critical(e)


if __name__ == '__main__':
Expand Down
34 changes: 17 additions & 17 deletions utils/cli/core/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ def tasks_data(self, task_id, resource_type, resources):
data = None
files = None
if resource_type == ResourceType.LOCAL:
files = {f'client_files[{i}]': open(f, 'rb') for i, f in enumerate(resources)}
files = {'client_files[{}]'.format(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)}
data = {'remote_files[{}]'.format(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)}
data = {'server_files[{}]'.format(i): f for i, f in enumerate(resources)}
response = self.session.post(url, data=data, files=files)
response.raise_for_status()

Expand All @@ -41,7 +41,7 @@ def tasks_list(self, use_json_output, **kwargs):
if use_json_output:
log.info(json.dumps(r, indent=4))
else:
log.info(f'{r["id"]},{r["name"]},{r["status"]}')
log.info('{id},{name},{status}'.format(**r))
if not response_json['next']:
return
page += 1
Expand All @@ -60,8 +60,7 @@ def tasks_create(self, name, labels, bug, resource_type, resources, **kwargs):
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"]}')
log.info('Created task ID: {id} NAME: {name}'.format(**response_json))
self.tasks_data(response_json['id'], resource_type, resources)

def tasks_delete(self, task_ids, **kwargs):
Expand All @@ -71,10 +70,10 @@ def tasks_delete(self, task_ids, **kwargs):
response = self.session.delete(url)
try:
response.raise_for_status()
log.info(f'Task ID {task_id} deleted')
log.info('Task ID {} deleted'.format(task_id))
except requests.exceptions.HTTPError as e:
if response.status_code == 404:
log.info(f'Task ID {task_id} not found')
log.info('Task ID {} not found'.format(task_id))
else:
raise e

Expand All @@ -86,7 +85,7 @@ def tasks_frame(self, task_id, frame_ids, outdir='', **kwargs):
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'
outfile = 'task_{}_frame_{:06d}.jpg'.format(task_id, frame_id)
im.save(os.path.join(outdir, outfile))

def tasks_dump(self, task_id, fileformat, filename, **kwargs):
Expand All @@ -103,7 +102,7 @@ def tasks_dump(self, task_id, fileformat, filename, **kwargs):
while True:
response = self.session.get(url)
response.raise_for_status()
log.info(f'STATUS {response.status_code}')
log.info('STATUS {}'.format(response.status_code))
if response.status_code == 201:
break

Expand All @@ -118,23 +117,24 @@ class CVAT_API_V1():
""" Build parameterized API URLs """

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

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

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

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

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

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

def tasks_id_annotations_filename(self, task_id, name, fileformat):
return f'{self.tasks}/{task_id}/annotations/{name}?format={fileformat}'
return self.tasks_id(task_id) + '/annotations/{}?format={}' \
.format(name, fileformat)
4 changes: 2 additions & 2 deletions utils/cli/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ def setUpTestData(cls):

def test_tasks_list(self):
self.cli.tasks_list(False)
self.assertRegex(self.mock_stdout.getvalue(), f'.*{self.taskname}.*')
self.assertRegex(self.mock_stdout.getvalue(), '.*{}.*'.format(self.taskname))

def test_tasks_delete(self):
self.cli.tasks_delete([1])
self.cli.tasks_list(False)
self.assertNotRegex(self.mock_stdout.getvalue(), f'.*{self.taskname}.*')
self.assertNotRegex(self.mock_stdout.getvalue(), '.*{}.*'.format(self.taskname))

def test_tasks_dump(self):
path = os.path.join(settings.SHARE_ROOT, 'test_cli.xml')
Expand Down