Skip to content

Commit 8f0a8a8

Browse files
authored
Merge pull request #688 from pierotofy/cameracalib
Camera Calibration and other fixes
2 parents c324049 + c83ee57 commit 8f0a8a8

32 files changed

+550
-245
lines changed

app/api/tasks.py

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def set_pending_action(self, pending_action, request, pk=None, project_pk=None,
102102
raise exceptions.NotFound()
103103

104104
task.pending_action = pending_action
105+
task.partial = False # Otherwise this will not be processed
105106
task.last_error = None
106107
task.save()
107108

@@ -158,28 +159,84 @@ def retrieve(self, request, pk=None, project_pk=None):
158159
serializer = TaskSerializer(task)
159160
return Response(serializer.data)
160161

161-
def create(self, request, project_pk=None):
162-
project = get_and_check_project(request, project_pk, ('change_project', ))
163-
162+
@detail_route(methods=['post'])
163+
def commit(self, request, pk=None, project_pk=None):
164+
"""
165+
Commit a task after all images have been uploaded
166+
"""
167+
get_and_check_project(request, project_pk, ('change_project', ))
168+
try:
169+
task = self.queryset.get(pk=pk, project=project_pk)
170+
except (ObjectDoesNotExist, ValidationError):
171+
raise exceptions.NotFound()
172+
173+
# TODO: check at least two images are present
174+
175+
task.partial = False
176+
task.images_count = models.ImageUpload.objects.filter(task=task).count()
177+
178+
if task.images_count < 2:
179+
raise exceptions.ValidationError(detail="You need to upload at least 2 images before commit")
180+
181+
task.save()
182+
worker_tasks.process_task.delay(task.id)
183+
184+
serializer = TaskSerializer(task)
185+
return Response(serializer.data, status=status.HTTP_200_OK)
186+
187+
@detail_route(methods=['post'])
188+
def upload(self, request, pk=None, project_pk=None):
189+
"""
190+
Add images to a task
191+
"""
192+
get_and_check_project(request, project_pk, ('change_project', ))
193+
try:
194+
task = self.queryset.get(pk=pk, project=project_pk)
195+
except (ObjectDoesNotExist, ValidationError):
196+
raise exceptions.NotFound()
197+
164198
files = flatten_files(request.FILES)
165199

166-
if len(files) <= 1:
167-
raise exceptions.ValidationError(detail="Cannot create task, you need at least 2 images")
200+
if len(files) == 0:
201+
raise exceptions.ValidationError(detail="No files uploaded")
168202

169203
with transaction.atomic():
170-
task = models.Task.objects.create(project=project,
171-
pending_action=pending_actions.RESIZE if 'resize_to' in request.data else None)
172-
173204
for image in files:
174205
models.ImageUpload.objects.create(task=task, image=image)
175-
task.images_count = len(files)
176206

177-
# Update other parameters such as processing node, task name, etc.
207+
return Response({'success': True}, status=status.HTTP_200_OK)
208+
209+
def create(self, request, project_pk=None):
210+
project = get_and_check_project(request, project_pk, ('change_project', ))
211+
212+
# If this is a partial task, we're going to upload images later
213+
# for now we just create a placeholder task.
214+
if request.data.get('partial'):
215+
task = models.Task.objects.create(project=project,
216+
pending_action=pending_actions.RESIZE if 'resize_to' in request.data else None)
178217
serializer = TaskSerializer(task, data=request.data, partial=True)
179218
serializer.is_valid(raise_exception=True)
180219
serializer.save()
220+
else:
221+
files = flatten_files(request.FILES)
181222

182-
worker_tasks.process_task.delay(task.id)
223+
if len(files) <= 1:
224+
raise exceptions.ValidationError(detail="Cannot create task, you need at least 2 images")
225+
226+
with transaction.atomic():
227+
task = models.Task.objects.create(project=project,
228+
pending_action=pending_actions.RESIZE if 'resize_to' in request.data else None)
229+
230+
for image in files:
231+
models.ImageUpload.objects.create(task=task, image=image)
232+
task.images_count = len(files)
233+
234+
# Update other parameters such as processing node, task name, etc.
235+
serializer = TaskSerializer(task, data=request.data, partial=True)
236+
serializer.is_valid(raise_exception=True)
237+
serializer.save()
238+
239+
worker_tasks.process_task.delay(task.id)
183240

184241
return Response(serializer.data, status=status.HTTP_201_CREATED)
185242

app/migrations/0028_task_partial.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 2.1.7 on 2019-06-26 18:31
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('app', '0027_plugin'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='task',
15+
name='partial',
16+
field=models.BooleanField(default=False, help_text='A flag indicating whether this task is currently waiting for information or files to be uploaded before being considered for processing.'),
17+
),
18+
]

app/models/task.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ class Task(models.Model):
156156
'deferred_path': 'orthophoto_tiles.zip',
157157
'deferred_compress_dir': 'orthophoto_tiles'
158158
},
159+
'cameras.json': 'cameras.json',
159160
}
160161

161162
STATUS_CODES = (
@@ -212,6 +213,7 @@ class Task(models.Model):
212213
blank=True)
213214
import_url = models.TextField(null=False, default="", blank=True, help_text="URL this task is imported from (only for imported tasks)")
214215
images_count = models.IntegerField(null=False, blank=True, default=0, help_text="Number of images associated with this task")
216+
partial = models.BooleanField(default=False, help_text="A flag indicating whether this task is currently waiting for information or files to be uploaded before being considered for processing.")
215217

216218
def __init__(self, *args, **kwargs):
217219
super(Task, self).__init__(*args, **kwargs)
@@ -444,7 +446,6 @@ def callback(progress):
444446
nonlocal last_update
445447

446448
time_has_elapsed = time.time() - last_update >= 2
447-
448449
if time_has_elapsed:
449450
testWatch.manual_log_call("Task.process.callback")
450451
self.check_if_canceled()
@@ -482,13 +483,11 @@ def callback(progress):
482483
self.status = status_codes.CANCELED
483484
self.pending_action = None
484485
self.save()
485-
elif self.import_url:
486-
# Imported tasks need no special action
486+
else:
487+
# Tasks with no processing node or UUID need no special action
487488
self.status = status_codes.CANCELED
488489
self.pending_action = None
489490
self.save()
490-
else:
491-
raise NodeServerError("Cannot cancel a task that has no processing node or UUID")
492491

493492
elif self.pending_action == pending_actions.RESTART:
494493
logger.info("Restarting {}".format(self))

app/static/app/css/bootstrap.min.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/static/app/css/theme.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ body,
7272
a, a:hover, a:focus{
7373
color: theme("tertiary");
7474
}
75+
.progress-bar-success{
76+
background-color: theme("tertiary");
77+
}
7578

7679
/* Button primary */
7780
#navbar-top .navbar-top-links,{

app/static/app/fonts/Lato.css

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/* latin-ext */
2+
@font-face {
3+
font-family: 'Lato';
4+
font-style: italic;
5+
font-weight: 400;
6+
src: local('Lato Italic'), local('Lato-Italic'), url(latoItalic400.woff2) format('woff2');
7+
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
8+
}
9+
/* latin */
10+
@font-face {
11+
font-family: 'Lato';
12+
font-style: italic;
13+
font-weight: 400;
14+
src: local('Lato Italic'), local('Lato-Italic'), url(latoItalic400-2.woff2) format('woff2');
15+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
16+
}
17+
/* latin-ext */
18+
@font-face {
19+
font-family: 'Lato';
20+
font-style: normal;
21+
font-weight: 400;
22+
src: local('Lato Regular'), local('Lato-Regular'), url(latoRegular.woff2) format('woff2');
23+
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
24+
}
25+
/* latin */
26+
@font-face {
27+
font-family: 'Lato';
28+
font-style: normal;
29+
font-weight: 400;
30+
src: local('Lato Regular'), local('Lato-Regular'), url(latoRegular-2.woff2) format('woff2');
31+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
32+
}
33+
/* latin-ext */
34+
@font-face {
35+
font-family: 'Lato';
36+
font-style: normal;
37+
font-weight: 700;
38+
src: local('Lato Bold'), local('Lato-Bold'), url(latoBold.woff2) format('woff2');
39+
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
40+
}
41+
/* latin */
42+
@font-face {
43+
font-family: 'Lato';
44+
font-style: normal;
45+
font-weight: 700;
46+
src: local('Lato Bold'), local('Lato-Bold'), url(latoBold-2.woff2) format('woff2');
47+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
48+
}

app/static/app/fonts/latoBold-2.woff2

22.5 KB
Binary file not shown.

app/static/app/fonts/latoBold.woff2

5.23 KB
Binary file not shown.
23.9 KB
Binary file not shown.
5.41 KB
Binary file not shown.
22.9 KB
Binary file not shown.
5.35 KB
Binary file not shown.

app/static/app/js/Console.jsx

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ class Console extends React.Component {
2626
this.handleMouseOver = this.handleMouseOver.bind(this);
2727
this.handleMouseOut = this.handleMouseOut.bind(this);
2828
this.downloadTxt = this.downloadTxt.bind(this);
29-
this.copyTxt = this.copyTxt.bind(this);
3029
this.enterFullscreen = this.enterFullscreen.bind(this);
3130
this.exitFullscreen = this.exitFullscreen.bind(this);
3231
}
@@ -69,17 +68,7 @@ class Console extends React.Component {
6968
}
7069

7170
downloadTxt(filename="console.txt"){
72-
Utils.saveAs(this.state.lines.join("\r\n"), filename);
73-
}
74-
75-
copyTxt(){
76-
const el = document.createElement('textarea');
77-
el.value = this.state.lines.join("\r\n");
78-
document.body.appendChild(el);
79-
el.select();
80-
document.execCommand('copy');
81-
document.body.removeChild(el);
82-
console.log("Output copied to clipboard");
71+
Utils.saveAs(this.state.lines.join("\n"), filename);
8372
}
8473

8574
enterFullscreen(){
@@ -172,9 +161,6 @@ class Console extends React.Component {
172161
<a href="javascript:void(0);" onClick={() => this.downloadTxt()} className="btn btn-sm btn-primary" title="Download To File">
173162
<i className="fa fa-download"></i>
174163
</a>
175-
<a href="javascript:void(0);" onClick={this.copyTxt} className="btn btn-sm btn-primary" title="Copy To Clipboard">
176-
<i className="fa fa-clipboard"></i>
177-
</a>
178164
<a href="javascript:void(0);" onClick={this.enterFullscreen} className="btn btn-sm btn-primary" title="Toggle Fullscreen">
179165
<i className="fa fa-expand"></i>
180166
</a>

app/static/app/js/classes/AssetDownloads.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const api = {
4444
new AssetDownload("Point Cloud (PLY)","georeferenced_model.ply","fa fa-cube"),
4545
new AssetDownload("Point Cloud (CSV)","georeferenced_model.csv","fa fa-cube"),
4646
new AssetDownload("Textured Model","textured_model.zip","fa fa-connectdevelop"),
47+
new AssetDownload("Camera Parameters","cameras.json","fa fa-camera"),
4748
new AssetDownloadSeparator(),
4849
new AssetDownload("All Assets","all.zip","fa fa-file-archive-o")
4950
];

app/static/app/js/classes/ResizeModes.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
const dict = [
22
{k: 'NO', v: 0, human: "No"}, // Don't resize
3-
{k: 'YES', v: 1, human: "Yes"}, // Resize on server
4-
{k: 'YESINBROWSER', v: 2, human: "Yes (In browser)"} // Resize on browser
3+
{k: 'YES', v: 1, human: "Yes"} // Resize on server
54
];
65

76
const exp = {
87
all: () => dict.map(d => d.v),
98
fromString: (s) => {
109
let v = parseInt(s);
11-
if (!isNaN(v) && v >= 0 && v <= 2) return v;
10+
if (!isNaN(v) && v >= 0 && v <= 1) return v;
1211
else return 0;
1312
},
1413
toHuman: (v) => {

app/static/app/js/classes/Utils.js

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const FileSaver = require('file-saver');
2+
13
let escapeEntityMap = {
24
"&": "&amp;",
35
"<": "&lt;",
@@ -83,26 +85,8 @@ export default {
8385
},
8486

8587
saveAs: function(text, filename){
86-
function save(uri, filename) {
87-
let link = document.createElement('a');
88-
if (typeof link.download === 'string') {
89-
link.href = uri;
90-
link.download = filename;
91-
92-
//Firefox requires the link to be in the body
93-
document.body.appendChild(link);
94-
95-
//simulate click
96-
link.click();
97-
98-
//remove the link when done
99-
document.body.removeChild(link);
100-
} else {
101-
window.open(uri);
102-
}
103-
}
104-
105-
save("data:application/octet-stream," + encodeURIComponent(text), filename);
88+
var blob = new Blob([text], {type: "text/plain;charset=utf-8"});
89+
FileSaver.saveAs(blob, filename);
10690
}
10791
};
10892

0 commit comments

Comments
 (0)