kopia lustrzana https://github.com/OpenDroneMap/WebODM
PoC alignment UI
rodzic
a1681c94d5
commit
5c26ff0742
|
@ -91,6 +91,7 @@ class TaskViewSet(viewsets.ViewSet):
|
|||
|
||||
parser_classes = (parsers.MultiPartParser, parsers.JSONParser, parsers.FormParser, )
|
||||
ordering_fields = '__all__'
|
||||
filter_fields = ('status', ) # TODO: add filter fields
|
||||
|
||||
def get_permissions(self):
|
||||
"""
|
||||
|
@ -238,11 +239,25 @@ class TaskViewSet(viewsets.ViewSet):
|
|||
def create(self, request, project_pk=None):
|
||||
project = get_and_check_project(request, project_pk, ('change_project', ))
|
||||
|
||||
# Check if an alignment field is set to a valid task
|
||||
# this means a user wants to align this task with another
|
||||
align_to = request.data.get('align_to')
|
||||
align_task = None
|
||||
if align_to is not None and align_to != "auto" and align_to != "":
|
||||
try:
|
||||
align_task = models.Task.objects.get(pk=align_to)
|
||||
get_and_check_project(request, align_task.project.id, ('view_project', ))
|
||||
except ObjectDoesNotExist:
|
||||
raise exceptions.ValidationError(detail=_("Cannot create task, alignment task is not valid"))
|
||||
|
||||
# If this is a partial task, we're going to upload images later
|
||||
# for now we just create a placeholder task.
|
||||
if request.data.get('partial'):
|
||||
task = models.Task.objects.create(project=project,
|
||||
pending_action=pending_actions.RESIZE if 'resize_to' in request.data else None)
|
||||
if align_task is not None:
|
||||
task.set_alignment_file_from(align_task)
|
||||
|
||||
serializer = TaskSerializer(task, data=request.data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
|
@ -255,7 +270,8 @@ class TaskViewSet(viewsets.ViewSet):
|
|||
with transaction.atomic():
|
||||
task = models.Task.objects.create(project=project,
|
||||
pending_action=pending_actions.RESIZE if 'resize_to' in request.data else None)
|
||||
|
||||
if align_task is not None:
|
||||
task.set_alignment_file_from(align_task)
|
||||
task.handle_images_upload(files)
|
||||
task.images_count = len(task.scan_images())
|
||||
|
||||
|
|
|
@ -1241,7 +1241,26 @@ class Task(models.Model):
|
|||
def get_image_path(self, filename):
|
||||
p = self.task_path(filename)
|
||||
return path_traversal_check(p, self.task_path())
|
||||
|
||||
|
||||
def set_alignment_file_from(self, align_task):
|
||||
tp = self.task_path()
|
||||
if not os.path.exists(tp):
|
||||
os.makedirs(tp, exist_ok=True)
|
||||
|
||||
alignment_file = align_task.assets_path(self.ASSETS_MAP['georeferenced_model.laz'])
|
||||
dst_file = self.task_path("align.laz")
|
||||
|
||||
if os.path.exists(dst_file):
|
||||
os.unlink(dst_file)
|
||||
|
||||
if os.path.exists(alignment_file):
|
||||
try:
|
||||
os.link(alignment_file, dst_file)
|
||||
except:
|
||||
shutil.copy(alignment_file, dst_file)
|
||||
else:
|
||||
logger.warn("Cannot set alignment file for {}, {} does not exist".format(self, alignment_file))
|
||||
|
||||
def handle_images_upload(self, files):
|
||||
uploaded = {}
|
||||
for file in files:
|
||||
|
|
|
@ -24,12 +24,14 @@ const Colors = {
|
|||
class MapPreview extends React.Component {
|
||||
static defaultProps = {
|
||||
getFiles: null,
|
||||
onPolygonChange: () => {}
|
||||
onPolygonChange: () => {},
|
||||
onImagesBboxChanged: () => {}
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
getFiles: PropTypes.func.isRequired,
|
||||
onPolygonChange: PropTypes.func
|
||||
onPolygonChange: PropTypes.func,
|
||||
onImagesBboxChanged: PropTypes.func
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -214,6 +216,8 @@ _('Example:'),
|
|||
this.map.fitBounds(this.imagesGroup.getBounds());
|
||||
}
|
||||
|
||||
this.props.onImagesBboxChanged(this.computeBbox(this.exifData));
|
||||
|
||||
this.setState({showLoading: false});
|
||||
|
||||
}).catch(e => {
|
||||
|
@ -221,6 +225,20 @@ _('Example:'),
|
|||
});
|
||||
}
|
||||
|
||||
computeBbox = exifData => {
|
||||
// minx, maxx, miny, maxy
|
||||
let bbox = [Infinity, -Infinity, Infinity, -Infinity];
|
||||
exifData.forEach(ed => {
|
||||
if (ed.gps){
|
||||
bbox[0] = Math.min(bbox[0], ed.gps.longitude);
|
||||
bbox[1] = Math.max(bbox[1], ed.gps.longitude);
|
||||
bbox[2] = Math.min(bbox[2], ed.gps.latitude);
|
||||
bbox[3] = Math.max(bbox[3], ed.gps.latitude);
|
||||
}
|
||||
});
|
||||
return bbox;
|
||||
}
|
||||
|
||||
readExifData = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const files = this.props.getFiles();
|
||||
|
|
|
@ -7,12 +7,15 @@ import ResizeModes from '../classes/ResizeModes';
|
|||
import MapPreview from './MapPreview';
|
||||
import update from 'immutability-helper';
|
||||
import PluginsAPI from '../classes/plugins/API';
|
||||
import statusCodes from '../classes/StatusCodes';
|
||||
import { _, interpolate } from '../classes/gettext';
|
||||
|
||||
class NewTaskPanel extends React.Component {
|
||||
static defaultProps = {
|
||||
filesCount: 0,
|
||||
showResize: false
|
||||
showResize: false,
|
||||
showAlign: false,
|
||||
projectId: null
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
|
@ -20,7 +23,9 @@ class NewTaskPanel extends React.Component {
|
|||
onCancel: PropTypes.func,
|
||||
filesCount: PropTypes.number,
|
||||
showResize: PropTypes.bool,
|
||||
showAlign: PropTypes.bool,
|
||||
getFiles: PropTypes.func,
|
||||
projectId: PropTypes.number,
|
||||
suggestedTaskName: PropTypes.oneOfType([PropTypes.string, PropTypes.func])
|
||||
};
|
||||
|
||||
|
@ -31,6 +36,9 @@ class NewTaskPanel extends React.Component {
|
|||
editTaskFormLoaded: false,
|
||||
resizeMode: Storage.getItem('resize_mode') === null ? ResizeModes.YES : ResizeModes.fromString(Storage.getItem('resize_mode')),
|
||||
resizeSize: parseInt(Storage.getItem('resize_size')) || 2048,
|
||||
alignTo: "auto",
|
||||
alignTasks: [], // loaded on mount if showAlign is true
|
||||
loadingAlignTasks: false,
|
||||
items: [], // Coming from plugins,
|
||||
taskInfo: {},
|
||||
inReview: false,
|
||||
|
@ -63,6 +71,26 @@ class NewTaskPanel extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
if (this.alignTasksRequest) this.alignTasksRequest.abort();
|
||||
}
|
||||
|
||||
loadAlignTasks = (bbox) => {
|
||||
// TODO: filter by status on server
|
||||
this.setState({alignTasks: [], alignTo: "auto", loadingAlignTasks: true});
|
||||
|
||||
this.alignTasksRequest =
|
||||
$.getJSON(`/api/projects/${this.props.projectId}/tasks/?ordering=-created_at`, tasks => {
|
||||
if (Array.isArray(tasks)){
|
||||
this.setState({loadingAlignTasks: false, alignTasks: tasks.filter(t => t.status === statusCodes.COMPLETED && t.available_assets.indexOf("georeferenced_model.laz") !== -1)});
|
||||
}else{
|
||||
this.setState({loadingAlignTasks: false});
|
||||
}
|
||||
}).fail(() => {
|
||||
this.setState({loadingAlignTasks: false});
|
||||
});
|
||||
}
|
||||
|
||||
save(e){
|
||||
if (!this.state.inReview){
|
||||
this.setState({inReview: true});
|
||||
|
@ -99,7 +127,8 @@ class NewTaskPanel extends React.Component {
|
|||
getTaskInfo(){
|
||||
return Object.assign(this.taskForm.getTaskInfo(), {
|
||||
resizeSize: this.state.resizeSize,
|
||||
resizeMode: this.state.resizeMode
|
||||
resizeMode: this.state.resizeMode,
|
||||
alignTo: this.state.alignTo
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -148,6 +177,21 @@ class NewTaskPanel extends React.Component {
|
|||
if (this.taskForm) this.taskForm.forceUpdate();
|
||||
}
|
||||
|
||||
handleImagesBboxChange = (bbox) => {
|
||||
if (this.props.showAlign){
|
||||
console.log("TODO! Load alignment tasks that fit within", bbox);
|
||||
this.loadAlignTasks(bbox);
|
||||
}
|
||||
}
|
||||
|
||||
handleAlignToChanged = e => {
|
||||
this.setState({alignTo: e.target.value});
|
||||
|
||||
setTimeout(() => {
|
||||
this.handleFormChanged();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
render() {
|
||||
let filesCountOk = true;
|
||||
if (this.taskForm && !this.taskForm.checkFilesCount(this.props.filesCount)) filesCountOk = false;
|
||||
|
@ -176,6 +220,7 @@ class NewTaskPanel extends React.Component {
|
|||
{this.state.showMapPreview ? <MapPreview
|
||||
getFiles={this.props.getFiles}
|
||||
onPolygonChange={this.handlePolygonChange}
|
||||
onImagesBboxChanged={this.handleImagesBboxChange}
|
||||
ref={(domNode) => {this.mapPreview = domNode; }}
|
||||
/> : ""}
|
||||
|
||||
|
@ -189,6 +234,22 @@ class NewTaskPanel extends React.Component {
|
|||
ref={(domNode) => { if (domNode) this.taskForm = domNode; }}
|
||||
/>
|
||||
|
||||
{this.state.editTaskFormLoaded && this.props.showAlign && this.state.showMapPreview ?
|
||||
<div>
|
||||
<div className="form-group">
|
||||
<label className="col-sm-2 control-label">{_("Alignment")}</label>
|
||||
<div className="col-sm-10">
|
||||
<select className="form-control" disabled={this.state.loadingAlignTasks} value={this.state.alignTo} onChange={this.handleAlignToChanged}>
|
||||
<option value="auto" key="auto">{this.state.loadingAlignTasks ? _("Loading...") : _("Automatic")}</option>
|
||||
{this.state.alignTasks.map(t =>
|
||||
<option value={t.id} key={t.id}>{t.name}</option>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
: ""}
|
||||
|
||||
{this.state.editTaskFormLoaded && this.props.showResize ?
|
||||
<div>
|
||||
<div className="form-group">
|
||||
|
@ -228,6 +289,7 @@ class NewTaskPanel extends React.Component {
|
|||
</div>)}
|
||||
</div>
|
||||
: ""}
|
||||
|
||||
</div>
|
||||
|
||||
{this.state.editTaskFormLoaded ?
|
||||
|
|
|
@ -418,7 +418,8 @@ class ProjectListItem extends React.Component {
|
|||
options: taskInfo.options,
|
||||
processing_node: taskInfo.selectedNode.id,
|
||||
auto_processing_node: taskInfo.selectedNode.key == "auto",
|
||||
partial: true
|
||||
partial: true,
|
||||
align_to: taskInfo.alignTo
|
||||
};
|
||||
|
||||
if (taskInfo.resizeMode === ResizeModes.YES){
|
||||
|
@ -780,6 +781,8 @@ class ProjectListItem extends React.Component {
|
|||
suggestedTaskName={this.handleTaskTitleHint}
|
||||
filesCount={this.state.upload.totalCount}
|
||||
showResize={true}
|
||||
showAlign={numTasks > 0}
|
||||
projectId={this.state.data.id}
|
||||
getFiles={() => this.state.upload.files }
|
||||
/>
|
||||
: ""}
|
||||
|
|
Ładowanie…
Reference in New Issue