URLs as source data for a task (#483)

* Update CHANGELOG.md
main
zliang7 7 years ago committed by Nikita Manovich
parent e9e1865cb7
commit 076562321e

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Installation guide - Installation guide
- Linear interpolation for a single point - Linear interpolation for a single point
- Video frame filter - Video frame filter
- Remote data source (list of URLs to create an annotation task)
### Changed ### Changed
- Outside and keyframe buttons in the side panel for all interpolation shapes (they were only for boxes before) - Outside and keyframe buttons in the side panel for all interpolation shapes (they were only for boxes before)

@ -489,9 +489,11 @@ class DashboardView {
const labelsInput = $('#dashboardLabelsInput'); const labelsInput = $('#dashboardLabelsInput');
const bugTrackerInput = $('#dashboardBugTrackerInput'); const bugTrackerInput = $('#dashboardBugTrackerInput');
const localSourceRadio = $('#dashboardLocalSource'); const localSourceRadio = $('#dashboardLocalSource');
const remoteSourceRadio = $('#dashboardRemoteSource');
const shareSourceRadio = $('#dashboardShareSource'); const shareSourceRadio = $('#dashboardShareSource');
const selectFiles = $('#dashboardSelectFiles'); const selectFiles = $('#dashboardSelectFiles');
const filesLabel = $('#dashboardFilesLabel'); const filesLabel = $('#dashboardFilesLabel');
const remoteFileInput = $('#dashboardRemoteFileInput');
const localFileSelector = $('#dashboardLocalFileSelector'); const localFileSelector = $('#dashboardLocalFileSelector');
const shareFileSelector = $('#dashboardShareBrowseModal'); const shareFileSelector = $('#dashboardShareBrowseModal');
const shareBrowseTree = $('#dashboardShareBrowser'); const shareBrowseTree = $('#dashboardShareBrowser');
@ -587,15 +589,32 @@ class DashboardView {
localSourceRadio.on('click', () => { localSourceRadio.on('click', () => {
if (source === 'local') { if (source === 'local') {
return; return;
} else if (source === 'remote') {
selectFiles.parent().removeClass('hidden');
remoteFileInput.parent().addClass('hidden');
} }
source = 'local'; source = 'local';
files = []; files = [];
updateSelectedFiles(); updateSelectedFiles();
}); });
remoteSourceRadio.on('click', () => {
if (source === 'remote') {
return;
}
source = 'remote';
selectFiles.parent().addClass('hidden');
remoteFileInput.parent().removeClass('hidden');
remoteFileInput.prop('value', '');
files = [];
});
shareSourceRadio.on('click', () => { shareSourceRadio.on('click', () => {
if (source === 'share') { if (source === 'share') {
return; return;
} else if (source === 'remote') {
selectFiles.parent().removeClass('hidden');
remoteFileInput.parent().addClass('hidden');
} }
source = 'share'; source = 'share';
files = []; files = [];
@ -639,6 +658,11 @@ class DashboardView {
updateSelectedFiles(); updateSelectedFiles();
}); });
remoteFileInput.on('change', function(e) {
let text = remoteFileInput.prop('value');
files = text.split('\n').map(f => f.trim()).filter(f => f.length > 0);
});
cancelBrowseServer.on('click', () => shareFileSelector.addClass('hidden')); cancelBrowseServer.on('click', () => shareFileSelector.addClass('hidden'));
submitBrowseServer.on('click', () => { submitBrowseServer.on('click', () => {
if (!createModal.hasClass('hidden')) { if (!createModal.hasClass('hidden')) {
@ -806,8 +830,10 @@ class DashboardView {
let task = new window.cvat.classes.Task(description); let task = new window.cvat.classes.Task(description);
if (source === 'local') { if (source === 'local') {
task.clientFiles = Array.from(files); task.clientFiles = Array.from(files);
} else { } else if (source === 'share') {
task.serverFiles = Array.from(files); task.serverFiles = Array.from(files);
} else if (source === 'remote') {
task.remoteFiles = Array.from(files);
} }
submitCreate.attr('disabled', true); submitCreate.attr('disabled', true);
cancelCreate.attr('disabled', true); cancelCreate.attr('disabled', true);

@ -105,6 +105,7 @@ Example: @select=race:__undefined__,skip,asian,black,caucasian,other'/>
<td> <label class="regular h2"> Source: </label> </td> <td> <label class="regular h2"> Source: </label> </td>
<td> <td>
<input id="dashboardLocalSource" type="radio" name="sourceType" value="local" checked=true/> <label for="dashboardLocalSource" class="regular h2" for="localSource"> Local </label> <input id="dashboardLocalSource" type="radio" name="sourceType" value="local" checked=true/> <label for="dashboardLocalSource" class="regular h2" for="localSource"> Local </label>
<br> <input id="dashboardRemoteSource" type="radio" name="sourceType" value="remote"/> <label for="dashboardRemoteSource" class="regular h2"> Remote </label>
<br> <input id="dashboardShareSource" type="radio" name="sourceType" value="share"/> <label for="dashboardShareSource" class="regular h2" for="shareSource"> Share </label> <br> <input id="dashboardShareSource" type="radio" name="sourceType" value="share"/> <label for="dashboardShareSource" class="regular h2" for="shareSource"> Share </label>
</td> </td>
</tr> </tr>
@ -179,6 +180,10 @@ Example: @select=race:__undefined__,skip,asian,black,caucasian,other'/>
<label id="dashboardFilesLabel" class="regular h2" style="margin-left: 10px"> No Files </label> <label id="dashboardFilesLabel" class="regular h2" style="margin-left: 10px"> No Files </label>
<input id="dashboardLocalFileSelector" type="file" style="display: none" multiple/> <input id="dashboardLocalFileSelector" type="file" style="display: none" multiple/>
</div> </div>
<div class="hidden">
<label for="dashboardRemoteFileInput" class="regular h2"> URLs: </label><br>
<textarea id="dashboardRemoteFileInput" rows="5" placeholder="One URL per line" style="width: 100%;"></textarea>
</div>
</div> </div>
<div style="width: 100%; height: 14%; padding-top: 10px;"> <div style="width: 100%; height: 14%; padding-top: 10px;">
<div style="float: right; width: 35%; height: 50px;"> <div style="float: right; width: 35%; height: 50px;">

@ -96,6 +96,14 @@ class RemoteFileSerializer(serializers.ModelSerializer):
model = models.RemoteFile model = models.RemoteFile
fields = ('file', ) fields = ('file', )
# pylint: disable=no-self-use
def to_internal_value(self, data):
return {'file': data}
# pylint: disable=no-self-use
def to_representation(self, instance):
return instance.file
class RqStatusSerializer(serializers.Serializer): class RqStatusSerializer(serializers.Serializer):
state = serializers.ChoiceField(choices=[ state = serializers.ChoiceField(choices=[
"Queued", "Started", "Finished", "Failed"]) "Queued", "Started", "Finished", "Failed"])

File diff suppressed because one or more lines are too long

@ -13,6 +13,9 @@ import numpy as np
from PIL import Image from PIL import Image
from traceback import print_exception from traceback import print_exception
from ast import literal_eval from ast import literal_eval
from urllib import error as urlerror
from urllib import parse as urlparse
from urllib import request as urlrequest
import mimetypes import mimetypes
_SCRIPT_DIR = os.path.realpath(os.path.dirname(__file__)) _SCRIPT_DIR = os.path.realpath(os.path.dirname(__file__))
@ -319,7 +322,7 @@ def _validate_data(data):
counter = {"image": 0, "video": 0, "archive": 0, "directory": 0} counter = {"image": 0, "video": 0, "archive": 0, "directory": 0}
client_video, client_archive = count_files( client_video, client_archive = count_files(
file_mapping={ f:f for f in data['client_files']}, file_mapping={ f:f for f in data['remote_files'] or data['client_files']},
counter=counter, counter=counter,
) )
@ -343,6 +346,33 @@ def _validate_data(data):
return client_video or server_video, client_archive or server_archive return client_video or server_video, client_archive or server_archive
def _download_data(urls, upload_dir):
job = rq.get_current_job()
local_files = {}
for url in urls:
name = os.path.basename(urlrequest.url2pathname(urlparse.urlparse(url).path))
if name in local_files:
raise Exception("filename collision: {}".format(name))
slogger.glob.info("Downloading: {}".format(url))
job.meta['status'] = '{} is being downloaded..'.format(url)
job.save_meta()
req = urlrequest.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
try:
with urlrequest.urlopen(req) as fp, open(os.path.join(upload_dir, name), 'wb') as tfp:
while True:
block = fp.read(8192)
if not block:
break
tfp.write(block)
except urlerror.HTTPError as err:
raise Exception("Failed to download " + url + ". " + str(err.code) + ' - ' + err.reason)
except urlerror.URLError as err:
raise Exception("Invalid URL: " + url + ". " + err.reason)
local_files[name] = True
return list(local_files.keys())
@transaction.atomic @transaction.atomic
def _create_thread(tid, data): def _create_thread(tid, data):
slogger.glob.info("create task #{}".format(tid)) slogger.glob.info("create task #{}".format(tid))
@ -352,6 +382,8 @@ def _create_thread(tid, data):
raise NotImplementedError("Adding more data is not implemented") raise NotImplementedError("Adding more data is not implemented")
upload_dir = db_task.get_upload_dirname() upload_dir = db_task.get_upload_dirname()
if data['remote_files']:
data['remote_files'] = _download_data(data['remote_files'], upload_dir)
video, archive = _validate_data(data) video, archive = _validate_data(data)
if data['server_files']: if data['server_files']:

@ -230,7 +230,7 @@
const taskFiles = { const taskFiles = {
client_files: this.clientFiles, client_files: this.clientFiles,
server_files: this.serverFiles, server_files: this.serverFiles,
remote_files: [], // hasn't been supported yet remote_files: this.remoteFiles,
}; };
const task = await serverProxy.tasks.createTask(taskData, taskFiles, onUpdate); const task = await serverProxy.tasks.createTask(taskData, taskFiles, onUpdate);

@ -761,6 +761,34 @@
Array.prototype.push.apply(data.files.client_files, clientFiles); Array.prototype.push.apply(data.files.client_files, clientFiles);
}, },
}, },
/**
* List of files from remote host
* @name remoteFiles
* @type {File[]}
* @memberof module:API.cvat.classes.Task
* @instance
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
remoteFiles: {
get: () => [...data.files.remote_files],
set: (remoteFiles) => {
if (!Array.isArray(remoteFiles)) {
throw new window.cvat.exceptions.ArgumentError(
`Value must be an array. But ${typeof (remoteFiles)} has been got.`,
);
}
for (const value of remoteFiles) {
if (typeof (value) !== 'string') {
throw new window.cvat.exceptions.ArgumentError(
`Array values must be a string. But ${typeof (value)} has been got.`,
);
}
}
Array.prototype.push.apply(data.files.remote_files, remoteFiles);
},
},
})); }));
} }

Loading…
Cancel
Save