Ability to load potree measurements

pull/999/head
Piero Toffanin 2021-06-10 15:08:57 -04:00
rodzic aeac63d8fc
commit cf554fdbfd
7 zmienionych plików z 510 dodań i 2 usunięć

11
app/api/potree.py 100644
Wyświetl plik

@ -0,0 +1,11 @@
from .tasks import TaskNestedView
from rest_framework.response import Response
class Scene(TaskNestedView):
def get(self, request, pk=None, project_pk=None):
"""
Retrieve Potree scene information
"""
task = self.get_and_check_task(request, pk)
return Response(task.potree_scene)

Wyświetl plik

@ -10,6 +10,7 @@ from .admin import UserViewSet, GroupViewSet
from rest_framework_nested import routers
from rest_framework_jwt.views import obtain_jwt_token
from .tiler import TileJson, Bounds, Metadata, Tiles, Export
from .potree import Scene
from .workers import CheckTask, GetTaskResult
router = routers.DefaultRouter()
@ -45,6 +46,8 @@ urlpatterns = [
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/images/thumbnail/(?P<image_filename>.+)$', Thumbnail.as_view()),
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/images/download/(?P<image_filename>.+)$', ImageDownload.as_view()),
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/3d/scene$', Scene.as_view()),
url(r'workers/check/(?P<celery_task_id>.+)', CheckTask.as_view()),
url(r'workers/get/(?P<celery_task_id>.+)', GetTaskResult.as_view()),

Wyświetl plik

@ -0,0 +1,410 @@
# Generated by Django 2.1.15 on 2021-06-10 18:50
import app.models.image_upload
import app.models.task
import colorfield.fields
from django.conf import settings
import django.contrib.gis.db.models.fields
import django.contrib.postgres.fields
import django.contrib.postgres.fields.jsonb
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import uuid
class Migration(migrations.Migration):
dependencies = [
('app', '0030_assure_cogeo'),
]
operations = [
migrations.AlterModelOptions(
name='imageupload',
options={'verbose_name': 'Image Upload', 'verbose_name_plural': 'Image Uploads'},
),
migrations.AlterModelOptions(
name='plugin',
options={'verbose_name': 'Plugin', 'verbose_name_plural': 'Plugins'},
),
migrations.AlterModelOptions(
name='plugindatum',
options={'verbose_name': 'Plugin Datum', 'verbose_name_plural': 'Plugin Datum'},
),
migrations.AlterModelOptions(
name='preset',
options={'verbose_name': 'Preset', 'verbose_name_plural': 'Presets'},
),
migrations.AlterModelOptions(
name='project',
options={'verbose_name': 'Project', 'verbose_name_plural': 'Projects'},
),
migrations.AlterModelOptions(
name='setting',
options={'verbose_name': 'Settings', 'verbose_name_plural': 'Settings'},
),
migrations.AlterModelOptions(
name='task',
options={'verbose_name': 'Task', 'verbose_name_plural': 'Tasks'},
),
migrations.AlterModelOptions(
name='theme',
options={'verbose_name': 'Theme', 'verbose_name_plural': 'Theme'},
),
migrations.AddField(
model_name='task',
name='potree_scene',
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict, help_text='Serialized potree scene information used to save/load measurements and camera view angle', verbose_name='Potree Scene'),
),
migrations.AlterField(
model_name='imageupload',
name='image',
field=models.ImageField(help_text='File uploaded by a user', max_length=512, upload_to=app.models.image_upload.image_directory_path, verbose_name='Image'),
),
migrations.AlterField(
model_name='imageupload',
name='task',
field=models.ForeignKey(help_text='Task this image belongs to', on_delete=django.db.models.deletion.CASCADE, to='app.Task', verbose_name='Task'),
),
migrations.AlterField(
model_name='plugin',
name='enabled',
field=models.BooleanField(db_index=True, default=True, help_text='Whether this plugin is turned on.', verbose_name='Enabled'),
),
migrations.AlterField(
model_name='plugin',
name='name',
field=models.CharField(help_text='Plugin name', max_length=255, primary_key=True, serialize=False, verbose_name='Name'),
),
migrations.AlterField(
model_name='plugindatum',
name='bool_value',
field=models.NullBooleanField(default=None, verbose_name='Bool value'),
),
migrations.AlterField(
model_name='plugindatum',
name='float_value',
field=models.FloatField(blank=True, default=None, null=True, verbose_name='Float value'),
),
migrations.AlterField(
model_name='plugindatum',
name='int_value',
field=models.IntegerField(blank=True, default=None, null=True, verbose_name='Integer value'),
),
migrations.AlterField(
model_name='plugindatum',
name='json_value',
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=None, null=True, verbose_name='JSON value'),
),
migrations.AlterField(
model_name='plugindatum',
name='key',
field=models.CharField(db_index=True, help_text='Setting key', max_length=255, verbose_name='Key'),
),
migrations.AlterField(
model_name='plugindatum',
name='string_value',
field=models.TextField(blank=True, default=None, null=True, verbose_name='String value'),
),
migrations.AlterField(
model_name='plugindatum',
name='user',
field=models.ForeignKey(default=None, help_text='The user this setting belongs to. If NULL, the setting is global.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
migrations.AlterField(
model_name='preset',
name='created_at',
field=models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date', verbose_name='Created at'),
),
migrations.AlterField(
model_name='preset',
name='name',
field=models.CharField(help_text='A label used to describe the preset', max_length=255, verbose_name='Name'),
),
migrations.AlterField(
model_name='preset',
name='options',
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=list, help_text="Options that define this preset (same format as in a Task's options).", validators=[app.models.task.validate_task_options], verbose_name='Options'),
),
migrations.AlterField(
model_name='preset',
name='owner',
field=models.ForeignKey(blank=True, help_text='The person who owns this preset', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Owner'),
),
migrations.AlterField(
model_name='preset',
name='system',
field=models.BooleanField(db_index=True, default=False, help_text='Whether this preset is available to every user in the system or just to its owner.', verbose_name='System'),
),
migrations.AlterField(
model_name='project',
name='created_at',
field=models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date', verbose_name='Created at'),
),
migrations.AlterField(
model_name='project',
name='deleting',
field=models.BooleanField(db_index=True, default=False, help_text='Whether this project has been marked for deletion. Projects that have running tasks need to wait for tasks to be properly cleaned up before they can be deleted.', verbose_name='Deleting'),
),
migrations.AlterField(
model_name='project',
name='description',
field=models.TextField(blank=True, default='', help_text='More in-depth description of the project', verbose_name='Description'),
),
migrations.AlterField(
model_name='project',
name='name',
field=models.CharField(help_text='A label used to describe the project', max_length=255, verbose_name='Name'),
),
migrations.AlterField(
model_name='project',
name='owner',
field=models.ForeignKey(help_text='The person who created the project', on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Owner'),
),
migrations.AlterField(
model_name='setting',
name='app_logo',
field=models.ImageField(help_text='A 512x512 logo of your application (.png or .jpeg)', upload_to='settings/', verbose_name='App logo'),
),
migrations.AlterField(
model_name='setting',
name='app_name',
field=models.CharField(help_text='The name of your application', max_length=255, verbose_name='App name'),
),
migrations.AlterField(
model_name='setting',
name='organization_name',
field=models.CharField(blank=True, default='WebODM', help_text='The name of your organization', max_length=255, null=True, verbose_name='Organization name'),
),
migrations.AlterField(
model_name='setting',
name='organization_website',
field=models.URLField(blank=True, default='https://github.com/OpenDroneMap/WebODM/', help_text='The website URL of your organization', max_length=255, null=True, verbose_name='Organization website'),
),
migrations.AlterField(
model_name='setting',
name='theme',
field=models.ForeignKey(help_text='Active theme', on_delete=django.db.models.deletion.DO_NOTHING, to='app.Theme', verbose_name='Theme'),
),
migrations.AlterField(
model_name='task',
name='auto_processing_node',
field=models.BooleanField(default=True, help_text='A flag indicating whether this task should be automatically assigned a processing node', verbose_name='Auto Processing Node'),
),
migrations.AlterField(
model_name='task',
name='available_assets',
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=80), blank=True, default=list, help_text='List of available assets to download', size=None, verbose_name='Available Assets'),
),
migrations.AlterField(
model_name='task',
name='console_output',
field=models.TextField(blank=True, default='', help_text='Console output of the processing node', verbose_name='Console Output'),
),
migrations.AlterField(
model_name='task',
name='created_at',
field=models.DateTimeField(default=django.utils.timezone.now, help_text='Creation date', verbose_name='Created at'),
),
migrations.AlterField(
model_name='task',
name='dsm_extent',
field=django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the DSM', null=True, srid=4326, verbose_name='DSM Extent'),
),
migrations.AlterField(
model_name='task',
name='dtm_extent',
field=django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the DTM', null=True, srid=4326, verbose_name='DTM Extent'),
),
migrations.AlterField(
model_name='task',
name='id',
field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='Id'),
),
migrations.AlterField(
model_name='task',
name='images_count',
field=models.IntegerField(blank=True, default=0, help_text='Number of images associated with this task', verbose_name='Images Count'),
),
migrations.AlterField(
model_name='task',
name='import_url',
field=models.TextField(blank=True, default='', help_text='URL this task is imported from (only for imported tasks)', verbose_name='Import URL'),
),
migrations.AlterField(
model_name='task',
name='last_error',
field=models.TextField(blank=True, help_text='The last processing error received', null=True, verbose_name='Last Error'),
),
migrations.AlterField(
model_name='task',
name='name',
field=models.CharField(blank=True, help_text='A label for the task', max_length=255, null=True, verbose_name='Name'),
),
migrations.AlterField(
model_name='task',
name='options',
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict, help_text='Options that are being used to process this task', validators=[app.models.task.validate_task_options], verbose_name='Options'),
),
migrations.AlterField(
model_name='task',
name='orthophoto_extent',
field=django.contrib.gis.db.models.fields.GeometryField(blank=True, help_text='Extent of the orthophoto', null=True, srid=4326, verbose_name='Orthophoto Extent'),
),
migrations.AlterField(
model_name='task',
name='partial',
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.', verbose_name='Partial'),
),
migrations.AlterField(
model_name='task',
name='pending_action',
field=models.IntegerField(blank=True, choices=[(1, 'CANCEL'), (2, 'REMOVE'), (3, 'RESTART'), (4, 'RESIZE'), (5, 'IMPORT')], db_index=True, help_text='A requested action to be performed on the task. The selected action will be performed by the worker at the next iteration.', null=True, verbose_name='Pending Action'),
),
migrations.AlterField(
model_name='task',
name='processing_node',
field=models.ForeignKey(blank=True, help_text='Processing node assigned to this task (or null if this task has not been associated yet)', null=True, on_delete=django.db.models.deletion.SET_NULL, to='nodeodm.ProcessingNode', verbose_name='Processing Node'),
),
migrations.AlterField(
model_name='task',
name='processing_time',
field=models.IntegerField(default=-1, help_text='Number of milliseconds that elapsed since the beginning of this task (-1 indicates that no information is available)', verbose_name='Processing Time'),
),
migrations.AlterField(
model_name='task',
name='project',
field=models.ForeignKey(help_text='Project that this task belongs to', on_delete=django.db.models.deletion.CASCADE, to='app.Project', verbose_name='Project'),
),
migrations.AlterField(
model_name='task',
name='public',
field=models.BooleanField(default=False, help_text='A flag indicating whether this task is available to the public', verbose_name='Public'),
),
migrations.AlterField(
model_name='task',
name='resize_progress',
field=models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the resize progress of this task's images", verbose_name='Resize Progress'),
),
migrations.AlterField(
model_name='task',
name='resize_to',
field=models.IntegerField(default=-1, help_text='When set to a value different than -1, indicates that the images for this task have been / will be resized to the size specified here before processing.', verbose_name='Resize To'),
),
migrations.AlterField(
model_name='task',
name='running_progress',
field=models.FloatField(blank=True, default=0.0, help_text='Value between 0 and 1 indicating the running progress (estimated) of this task', verbose_name='Running Progress'),
),
migrations.AlterField(
model_name='task',
name='status',
field=models.IntegerField(blank=True, choices=[(10, 'QUEUED'), (20, 'RUNNING'), (30, 'FAILED'), (40, 'COMPLETED'), (50, 'CANCELED')], db_index=True, help_text='Current status of the task', null=True, verbose_name='Status'),
),
migrations.AlterField(
model_name='task',
name='upload_progress',
field=models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the upload progress of this task's files to the processing node", verbose_name='Upload Progress'),
),
migrations.AlterField(
model_name='task',
name='uuid',
field=models.CharField(blank=True, db_index=True, default='', help_text='Identifier of the task (as returned by NodeODM API)', max_length=255, verbose_name='UUID'),
),
migrations.AlterField(
model_name='theme',
name='border',
field=colorfield.fields.ColorField(default='#e7e7e7', help_text='The color of most borders.', max_length=18, verbose_name='Border'),
),
migrations.AlterField(
model_name='theme',
name='button_danger',
field=colorfield.fields.ColorField(default='#e74c3c', help_text='Delete button color.', max_length=18, verbose_name='Button Danger'),
),
migrations.AlterField(
model_name='theme',
name='button_default',
field=colorfield.fields.ColorField(default='#95a5a6', help_text='Default button color.', max_length=18, verbose_name='Button Default'),
),
migrations.AlterField(
model_name='theme',
name='button_primary',
field=colorfield.fields.ColorField(default='#2c3e50', help_text='Primary button color.', max_length=18, verbose_name='Button Primary'),
),
migrations.AlterField(
model_name='theme',
name='css',
field=models.TextField(blank=True, default='', verbose_name='CSS'),
),
migrations.AlterField(
model_name='theme',
name='dialog_warning',
field=colorfield.fields.ColorField(default='#f39c12', help_text='The border color of warning dialogs.', max_length=18, verbose_name='Dialog Warning'),
),
migrations.AlterField(
model_name='theme',
name='failed',
field=colorfield.fields.ColorField(default='#ffcbcb', help_text='The background color of failed notifications.', max_length=18, verbose_name='Failed'),
),
migrations.AlterField(
model_name='theme',
name='header_background',
field=colorfield.fields.ColorField(default='#3498db', help_text="Background color of the site's header.", max_length=18, verbose_name='Header Background'),
),
migrations.AlterField(
model_name='theme',
name='header_primary',
field=colorfield.fields.ColorField(default='#ffffff', help_text="Text and icons in the site's header.", max_length=18, verbose_name='Header Primary'),
),
migrations.AlterField(
model_name='theme',
name='highlight',
field=colorfield.fields.ColorField(default='#f7f7f7', help_text='The background color of panels and some borders.', max_length=18, verbose_name='Highlight'),
),
migrations.AlterField(
model_name='theme',
name='html_after_body',
field=models.TextField(blank=True, default='', verbose_name='HTML (after body)'),
),
migrations.AlterField(
model_name='theme',
name='html_after_header',
field=models.TextField(blank=True, default='', verbose_name='HTML (after header)'),
),
migrations.AlterField(
model_name='theme',
name='html_before_header',
field=models.TextField(blank=True, default='', verbose_name='HTML (before header)'),
),
migrations.AlterField(
model_name='theme',
name='html_footer',
field=models.TextField(blank=True, default='', verbose_name='HTML (footer)'),
),
migrations.AlterField(
model_name='theme',
name='name',
field=models.CharField(help_text='Name of theme', max_length=255, verbose_name='Name'),
),
migrations.AlterField(
model_name='theme',
name='primary',
field=colorfield.fields.ColorField(default='#2c3e50', help_text='Most text, icons, and borders.', max_length=18, verbose_name='Primary'),
),
migrations.AlterField(
model_name='theme',
name='secondary',
field=colorfield.fields.ColorField(default='#ffffff', help_text='The main background color, and text color of some buttons.', max_length=18, verbose_name='Secondary'),
),
migrations.AlterField(
model_name='theme',
name='success',
field=colorfield.fields.ColorField(default='#cbffcd', help_text='The background color of success notifications.', max_length=18, verbose_name='Success'),
),
migrations.AlterField(
model_name='theme',
name='tertiary',
field=colorfield.fields.ColorField(default='#3498db', help_text='Navigation links.', max_length=18, verbose_name='Tertiary'),
),
]

Wyświetl plik

@ -255,6 +255,7 @@ class Task(models.Model):
import_url = models.TextField(null=False, default="", blank=True, help_text=_("URL this task is imported from (only for imported tasks)"), verbose_name=_("Import URL"))
images_count = models.IntegerField(null=False, blank=True, default=0, help_text=_("Number of images associated with this task"), verbose_name=_("Images Count"))
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."), verbose_name=_("Partial"))
potree_scene = fields.JSONField(default=dict, blank=True, help_text=_("Serialized potree scene information used to save/load measurements and camera view angle"), verbose_name=_("Potree Scene"))
class Meta:
verbose_name = _("Task")

Wyświetl plik

@ -274,7 +274,17 @@ class ModelView extends React.Component {
material.size = 1;
viewer.fitToScreen();
});
// Load saved scene (if any)
$.ajax({
type: "GET",
url: `/api/projects/${this.props.task.project}/tasks/${this.props.task.id}/3d/scene`
}).done(sceneData => {
Potree.loadProject(viewer, sceneData);
}).fail(e => {
console.error("Cannot load 3D scene information", e);
});
});
});
viewer.renderer.domElement.addEventListener( 'mousedown', this.handleRenderMouseClick );

Wyświetl plik

@ -0,0 +1,73 @@
# Generated by Django 2.1.15 on 2021-06-10 18:50
import django.contrib.postgres.fields.jsonb
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nodeodm', '0008_rename_default_odm_node'),
]
operations = [
migrations.AlterModelOptions(
name='processingnode',
options={'verbose_name': 'Processing Node', 'verbose_name_plural': 'Processing Nodes'},
),
migrations.AlterField(
model_name='processingnode',
name='api_version',
field=models.CharField(help_text='API version used by the node', max_length=32, null=True, verbose_name='API Version'),
),
migrations.AlterField(
model_name='processingnode',
name='available_options',
field=django.contrib.postgres.fields.jsonb.JSONField(default=dict, help_text='Description of the options that can be used for processing', verbose_name='Available Options'),
),
migrations.AlterField(
model_name='processingnode',
name='engine',
field=models.CharField(help_text='Engine used by the node.', max_length=255, null=True, verbose_name='Engine'),
),
migrations.AlterField(
model_name='processingnode',
name='engine_version',
field=models.CharField(help_text='Engine version used by the node.', max_length=32, null=True, verbose_name='Engine Version'),
),
migrations.AlterField(
model_name='processingnode',
name='hostname',
field=models.CharField(help_text='Hostname or IP address where the node is located (can be an internal hostname as well). If you are using Docker, this is never 127.0.0.1 or localhost. Find the IP address of your host machine by running ifconfig on Linux or by checking your network settings.', max_length=255, verbose_name='Hostname'),
),
migrations.AlterField(
model_name='processingnode',
name='label',
field=models.CharField(blank=True, default='', help_text='Optional label for this node. When set, this label will be shown instead of the hostname:port name.', max_length=255, verbose_name='Label'),
),
migrations.AlterField(
model_name='processingnode',
name='last_refreshed',
field=models.DateTimeField(help_text='When was the information about this node last retrieved?', null=True, verbose_name='Last Refreshed'),
),
migrations.AlterField(
model_name='processingnode',
name='max_images',
field=models.PositiveIntegerField(blank=True, help_text='Maximum number of images accepted by this node.', null=True, verbose_name='Max Images'),
),
migrations.AlterField(
model_name='processingnode',
name='port',
field=models.PositiveIntegerField(help_text="Port that connects to the node's API", verbose_name='Port'),
),
migrations.AlterField(
model_name='processingnode',
name='queue_count',
field=models.PositiveIntegerField(default=0, help_text='Number of tasks currently being processed by this node (as reported by the node itself)', verbose_name='Queue Count'),
),
migrations.AlterField(
model_name='processingnode',
name='token',
field=models.CharField(blank=True, default='', help_text="Token to use for authentication. If the node doesn't have authentication, you can leave this field blank.", max_length=1024, verbose_name='Token'),
),
]

Wyświetl plik

@ -1,6 +1,6 @@
{
"name": "WebODM",
"version": "1.9.1",
"version": "1.9.2",
"description": "User-friendly, extendable application and API for processing aerial imagery.",
"main": "index.js",
"scripts": {