kopia lustrzana https://github.com/OpenDroneMap/WebODM
				
				
				
			Export point clouds
							rodzic
							
								
									3378e67a2a
								
							
						
					
					
						commit
						b6ce0ae3cd
					
				|  | @ -14,8 +14,8 @@ WORKDIR /webodm | |||
| RUN apt-get -qq update && apt-get -qq install -y --no-install-recommends wget curl && \ | ||||
|     wget --no-check-certificate https://deb.nodesource.com/setup_12.x -O /tmp/node.sh && bash /tmp/node.sh && \ | ||||
|     apt-get -qq update && apt-get -qq install -y nodejs && \ | ||||
|     # Install Python3, GDAL, nginx, letsencrypt, psql | ||||
|     apt-get -qq update && apt-get -qq install -y --no-install-recommends python3 python3-pip python3-setuptools python3-wheel git g++ python3-dev python2.7-dev libpq-dev binutils libproj-dev gdal-bin libgdal-dev python3-gdal nginx certbot grass-core gettext-base cron postgresql-client-13 gettext tzdata && \ | ||||
|     # Install Python3, GDAL, PDAL, nginx, letsencrypt, psql | ||||
|     apt-get -qq update && apt-get -qq install -y --no-install-recommends python3 python3-pip python3-setuptools python3-wheel git g++ python3-dev python2.7-dev libpq-dev binutils libproj-dev gdal-bin pdal libgdal-dev python3-gdal nginx certbot grass-core gettext-base cron postgresql-client-13 gettext tzdata && \ | ||||
|     update-alternatives --install /usr/bin/python python /usr/bin/python2.7 1 && update-alternatives --install /usr/bin/python python /usr/bin/python3.9 2 && \ | ||||
|     # Install pip reqs | ||||
|     pip install -U pip && pip install -r requirements.txt "boto3==1.14.14" && \ | ||||
|  |  | |||
|  | @ -20,14 +20,14 @@ from rio_tiler.io import COGReader | |||
| from rio_tiler.errors import InvalidColorMapName | ||||
| import numpy as np | ||||
| from .custom_colormaps_helper import custom_colormaps | ||||
| from app.raster_utils import export_raster, extension_for_export_format, ZOOM_EXTRA_LEVELS | ||||
| from app.raster_utils import extension_for_export_format, ZOOM_EXTRA_LEVELS | ||||
| from .hsvblend import hsv_blend | ||||
| from .hillshade import LightSource | ||||
| from .formulas import lookup_formula, get_algorithm_list | ||||
| from .tasks import TaskNestedView | ||||
| from rest_framework import exceptions | ||||
| from rest_framework.response import Response | ||||
| from worker.tasks import export_raster | ||||
| from worker.tasks import export_raster, export_pointcloud | ||||
| from django.utils.translation import gettext as _ | ||||
| 
 | ||||
| 
 | ||||
|  | @ -76,6 +76,9 @@ def get_extent(task, tile_type): | |||
| def get_raster_path(task, tile_type): | ||||
|     return task.get_asset_download_path(tile_type + ".tif") | ||||
| 
 | ||||
| def get_pointcloud_path(task): | ||||
|     return task.get_asset_download_path("georeferenced_model.laz") | ||||
| 
 | ||||
| 
 | ||||
| class TileJson(TaskNestedView): | ||||
|     def get(self, request, pk=None, project_pk=None, tile_type=""): | ||||
|  | @ -491,7 +494,9 @@ class Export(TaskNestedView): | |||
| 
 | ||||
|         expr = None | ||||
| 
 | ||||
|         if not export_format in ['gtiff', 'gtiff-rgb', 'jpg', 'png', 'kmz']: | ||||
|         if asset_type in ['orthophoto', 'dsm', 'dtm'] and not export_format in ['gtiff', 'gtiff-rgb', 'jpg', 'png', 'kmz']: | ||||
|             raise exceptions.ValidationError(_("Unsupported format: %(value)s") % {'value': export_format}) | ||||
|         if asset_type == 'georeferenced_model' and not export_format in ['laz', 'las', 'ply', 'csv']: | ||||
|             raise exceptions.ValidationError(_("Unsupported format: %(value)s") % {'value': export_format}) | ||||
|          | ||||
|         if epsg is not None: | ||||
|  | @ -527,8 +532,11 @@ class Export(TaskNestedView): | |||
|                     raise Exception("Hillshade must be > 0") | ||||
|             except: | ||||
|                 raise exceptions.ValidationError(_("Invalid hillshade value: %(value)s") % {'value': hillshade}) | ||||
| 
 | ||||
|         url = get_raster_path(task, asset_type) | ||||
|          | ||||
|         if asset_type == 'georeferenced_model': | ||||
|             url = get_pointcloud_path(task) | ||||
|         else: | ||||
|             url = get_raster_path(task, asset_type) | ||||
| 
 | ||||
|         if not os.path.isfile(url): | ||||
|             raise exceptions.NotFound() | ||||
|  | @ -541,16 +549,25 @@ class Export(TaskNestedView): | |||
|                         extension | ||||
|                     ) | ||||
| 
 | ||||
|         # Shortcut the process if no processing is required | ||||
|         if export_format == 'gtiff' and (epsg == task.epsg or epsg is None) and expr is None: | ||||
|             return Response({'url': '/api/projects/{}/tasks/{}/download/{}.tif'.format(task.project.id, task.id, asset_type), 'filename': filename}) | ||||
|         else: | ||||
|             celery_task_id = export_raster.delay(url, epsg=epsg,  | ||||
|                                                     expression=expr,  | ||||
|                                                     format=export_format,  | ||||
|                                                     rescale=rescale,  | ||||
|                                                     color_map=color_map, | ||||
|                                                     hillshade=hillshade, | ||||
|                                                     asset_type=asset_type, | ||||
|                                                     name=task.name).task_id | ||||
|             return Response({'celery_task_id': celery_task_id, 'filename': filename}) | ||||
|         if asset_type in ['orthophoto', 'dsm', 'dtm']: | ||||
|             # Shortcut the process if no processing is required | ||||
|             if export_format == 'gtiff' and (epsg == task.epsg or epsg is None) and expr is None: | ||||
|                 return Response({'url': '/api/projects/{}/tasks/{}/download/{}.tif'.format(task.project.id, task.id, asset_type), 'filename': filename}) | ||||
|             else: | ||||
|                 celery_task_id = export_raster.delay(url, epsg=epsg,  | ||||
|                                                         expression=expr,  | ||||
|                                                         format=export_format,  | ||||
|                                                         rescale=rescale,  | ||||
|                                                         color_map=color_map, | ||||
|                                                         hillshade=hillshade, | ||||
|                                                         asset_type=asset_type, | ||||
|                                                         name=task.name).task_id | ||||
|                 return Response({'celery_task_id': celery_task_id, 'filename': filename}) | ||||
|         elif asset_type == 'georeferenced_model': | ||||
|             # Shortcut the process if no processing is required | ||||
|             if export_format == 'laz' and (epsg == task.epsg or epsg is None): | ||||
|                 return Response({'url': '/api/projects/{}/tasks/{}/download/{}.laz'.format(task.project.id, task.id, asset_type), 'filename': filename}) | ||||
|             else: | ||||
|                 celery_task_id = export_pointcloud.delay(url, epsg=epsg,  | ||||
|                                                             format=export_format).task_id | ||||
|                 return Response({'celery_task_id': celery_task_id, 'filename': filename}) | ||||
|  | @ -39,7 +39,7 @@ urlpatterns = [ | |||
|     url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/(?P<tile_type>orthophoto|dsm|dtm)/metadata$', Metadata.as_view()), | ||||
|     url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/(?P<tile_type>orthophoto|dsm|dtm)/tiles/(?P<z>[\d]+)/(?P<x>[\d]+)/(?P<y>[\d]+)\.png$', Tiles.as_view()), | ||||
|     url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/(?P<tile_type>orthophoto|dsm|dtm)/tiles/(?P<z>[\d]+)/(?P<x>[\d]+)/(?P<y>[\d]+)@(?P<scale>[\d]+)x\.png$', Tiles.as_view()), | ||||
|     url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/(?P<asset_type>orthophoto|dsm|dtm)/export$', Export.as_view()), | ||||
|     url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/(?P<asset_type>orthophoto|dsm|dtm|georeferenced_model)/export$', Export.as_view()), | ||||
| 
 | ||||
|     url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/download/(?P<asset>.+)$', TaskDownloads.as_view()), | ||||
|     url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/assets/(?P<unsafe_asset_path>.+)$', TaskAssets.as_view()), | ||||
|  |  | |||
|  | @ -0,0 +1,23 @@ | |||
| import logging | ||||
| import os | ||||
| import subprocess | ||||
| from app.security import double_quote | ||||
| 
 | ||||
| logger = logging.getLogger('app.logger') | ||||
| 
 | ||||
| def export_pointcloud(input, output, **opts): | ||||
|     epsg = opts.get('epsg') | ||||
|     export_format = opts.get('format') | ||||
| 
 | ||||
|     reprojection_args = [] | ||||
|     extra_args = [] | ||||
| 
 | ||||
|     if epsg: | ||||
|         reprojection_args = ["reprojection", | ||||
|                             "--filters.reprojection.out_srs=%s" % double_quote("EPSG:" + str(epsg))] | ||||
|      | ||||
|     if export_format == "ply": | ||||
|         extra_args = ['--writers.ply.sized_types', 'false', | ||||
|                       '--writers.ply.storage_mode', 'little endian'] | ||||
| 
 | ||||
|     subprocess.check_output(["pdal", "translate", input, output] + reprojection_args + extra_args) | ||||
|  | @ -1,4 +1,3 @@ | |||
| # Export a raster index after applying a band expression | ||||
| import rasterio | ||||
| import re | ||||
| import logging | ||||
|  | @ -23,11 +22,8 @@ def extension_for_export_format(export_format): | |||
|     extensions = { | ||||
|         'gtiff': 'tif', | ||||
|         'gtiff-rgb': 'tif', | ||||
|         'jpg': 'jpg', | ||||
|         'png': 'png', | ||||
|         'kmz': 'kmz' | ||||
|     } | ||||
|     return extensions.get(export_format, 'tif') | ||||
|     return extensions.get(export_format, export_format) | ||||
| 
 | ||||
| def export_raster(input, output, **opts): | ||||
|     epsg = opts.get('epsg') | ||||
|  |  | |||
|  | @ -1,11 +1,12 @@ | |||
| import { _ } from './gettext'; | ||||
| 
 | ||||
| class AssetDownload{ | ||||
|   constructor(label, asset, icon, exportFormats = null){ | ||||
|   constructor(label, asset, icon, exportFormats = null, exportParams = {}){ | ||||
|     this.label = label; | ||||
|     this.asset = asset; | ||||
|     this.icon = icon; | ||||
|     this.exportFormats = exportFormats; | ||||
|     this.exportParams = exportParams; | ||||
|   } | ||||
| 
 | ||||
|   downloadUrl(project_id, task_id){ | ||||
|  | @ -36,24 +37,24 @@ class AssetDownloadSeparator extends AssetDownload{ | |||
|   } | ||||
| } | ||||
| 
 | ||||
| const tiffExportFormats = ["gtiff", "gtiff-rgb", "jpg", "png", "kmz"]; | ||||
| const elevationExportParams = {'hillshade': 6, "color_map": "viridis"}; | ||||
| 
 | ||||
| const api = { | ||||
|   all: function() { | ||||
|     return [ | ||||
|       new AssetDownload(_("Orthophoto"),"orthophoto.tif","far fa-image", ["gtiff", "gtiff-rgb", "jpg", "png", "kmz"]), | ||||
|       new AssetDownload(_("Orthophoto"),"orthophoto.tif","far fa-image", tiffExportFormats), | ||||
|       new AssetDownload(_("Orthophoto (MBTiles)"),"orthophoto.mbtiles","far fa-image"), | ||||
|       new AssetDownload(_("Orthophoto (Tiles)"),"orthophoto_tiles.zip","fa fa-table"), | ||||
|       new AssetDownload(_("Terrain Model"),"dtm.tif","fa fa-chart-area"), | ||||
|       new AssetDownload(_("Terrain Model"),"dtm.tif","fa fa-chart-area", tiffExportFormats, elevationExportParams), | ||||
|       new AssetDownload(_("Terrain Model (Tiles)"),"dtm_tiles.zip","fa fa-table"), | ||||
|       new AssetDownload(_("Surface Model"),"dsm.tif","fa fa-chart-area"), | ||||
|       new AssetDownload(_("Surface Model"),"dsm.tif","fa fa-chart-area", tiffExportFormats, elevationExportParams), | ||||
|       new AssetDownload(_("Surface Model (Tiles)"),"dsm_tiles.zip","fa fa-table"), | ||||
|       new AssetDownload(_("Point Cloud (LAS)"),"georeferenced_model.las","fa fa-cube"), | ||||
|       new AssetDownload(_("Point Cloud (LAZ)"),"georeferenced_model.laz","fa fa-cube"), | ||||
|       new AssetDownload(_("Point Cloud (PLY)"),"georeferenced_model.ply","fa fa-cube"), | ||||
|       new AssetDownload(_("Point Cloud (CSV)"),"georeferenced_model.csv","fa fa-cube"), | ||||
|       new AssetDownload(_("Point Cloud"),"georeferenced_model.laz","fa fa-cube", ["laz", "las", "ply", "csv"]), | ||||
|       new AssetDownload(_("Textured Model"),"textured_model.zip","fab fa-connectdevelop"), | ||||
|       new AssetDownload(_("Camera Parameters"),"cameras.json","fa fa-camera"), | ||||
|       new AssetDownload(_("Camera Shots (GeoJSON)"),"shots.geojson","fa fa-camera"), | ||||
|       new AssetDownload(_("Ground Control Points (GeoJSON)"),"ground_control_points.geojson","far fa-dot-circle"), | ||||
|       new AssetDownload(_("Camera Shots"),"shots.geojson","fa fa-camera"), | ||||
|       new AssetDownload(_("Ground Control Points"),"ground_control_points.geojson","far fa-dot-circle"), | ||||
|       new AssetDownload(_("Quality Report"),"report.pdf","far fa-file-pdf"), | ||||
|        | ||||
|        | ||||
|  |  | |||
|  | @ -43,6 +43,7 @@ class AssetDownloadButtons extends React.Component { | |||
|             <ExportAssetDialog task={this.props.task} | ||||
|                                asset={this.state.exportDialogProps.asset} | ||||
|                                exportFormats={this.state.exportDialogProps.exportFormats}   | ||||
|                                exportParams={this.state.exportDialogProps.exportParams} | ||||
|                                onHide={this.onHide} | ||||
|                                assetLabel={this.state.exportDialogProps.assetLabel} | ||||
|             />  | ||||
|  | @ -67,6 +68,7 @@ class AssetDownloadButtons extends React.Component { | |||
|                             this.setState({exportDialogProps: { | ||||
|                                 asset: asset.exportId(), | ||||
|                                 exportFormats: asset.exportFormats, | ||||
|                                 exportParams: asset.exportParams, | ||||
|                                 assetLabel: asset.label | ||||
|                             }}); | ||||
|                         } | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ class ExportAssetDialog extends React.Component { | |||
|         asset: PropTypes.string.isRequired, | ||||
|         task: PropTypes.object.isRequired, | ||||
|         exportFormats: PropTypes.arrayOf(PropTypes.string), | ||||
|         exportParams: PropTypes.object, | ||||
|         assetLabel: PropTypes.string | ||||
|     }; | ||||
| 
 | ||||
|  | @ -43,7 +44,8 @@ class ExportAssetDialog extends React.Component { | |||
|                                     task={this.props.task} | ||||
|                                     ref={(domNode) => { this.exportAssetPanel = domNode; }} | ||||
|                                     selectorOnly | ||||
|                                     exportFormats={this.props.exportFormats} /> | ||||
|                                     exportFormats={this.props.exportFormats} | ||||
|                                     exportParams={this.props.exportParams} /> | ||||
|                 </FormDialog> | ||||
|             </div> | ||||
|         ); | ||||
|  |  | |||
|  | @ -33,25 +33,41 @@ export default class ExportAssetPanel extends React.Component { | |||
| 
 | ||||
|     this.efInfo = { | ||||
|         'gtiff-rgb': { | ||||
|             label: _("GeoTIFF (RGB)"), | ||||
|             label: "GeoTIFF (RGB)", | ||||
|             icon: "fas fa-palette" | ||||
|         }, | ||||
|         'gtiff': { | ||||
|             label: _("GeoTIFF (Raw)"), | ||||
|             label: "GeoTIFF (Raw)", | ||||
|             icon: "far fa-image" | ||||
|         }, | ||||
|         'jpg': { | ||||
|             label: _("JPEG (RGB)"), | ||||
|             label: "JPEG (RGB)", | ||||
|             icon: "fas fa-palette" | ||||
|         }, | ||||
|         'png': { | ||||
|             label: _("PNG (RGB)"), | ||||
|             label: "PNG (RGB)", | ||||
|             icon: "fas fa-palette" | ||||
|         }, | ||||
|         'kmz': { | ||||
|             label: _("KMZ (RGB)"), | ||||
|             label: "KMZ (RGB)", | ||||
|             icon: "fa fa-globe" | ||||
|         } | ||||
|         }, | ||||
|         'laz': { | ||||
|             label: "LAZ", | ||||
|             icon: "fa fa-cube" | ||||
|         }, | ||||
|         'las': { | ||||
|             label: "LAS", | ||||
|             icon: "fa fa-cube" | ||||
|         }, | ||||
|         'ply': { | ||||
|             label: "PLY", | ||||
|             icon: "fa fa-cube" | ||||
|         }, | ||||
|         'csv': { | ||||
|             label: "CSV", | ||||
|             icon: "fa fa-file-text" | ||||
|         }         | ||||
|     }; | ||||
| 
 | ||||
|     this.state = { | ||||
|  | @ -92,6 +108,7 @@ export default class ExportAssetPanel extends React.Component { | |||
|        | ||||
|       params.format = format; | ||||
|       params.epsg = this.getEpsg(); | ||||
|       console.log(params); | ||||
|       return params; | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "WebODM", | ||||
|   "version": "1.9.10", | ||||
|   "version": "1.9.11", | ||||
|   "description": "User-friendly, extendable application and API for processing aerial imagery.", | ||||
|   "main": "index.js", | ||||
|   "scripts": { | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ from webodm import settings | |||
| import worker | ||||
| from .celery import app | ||||
| from app.raster_utils import export_raster as export_raster_sync, extension_for_export_format | ||||
| from app.pointcloud_utils import export_pointcloud as export_pointcloud_sync | ||||
| import redis | ||||
| 
 | ||||
| logger = get_task_logger("app.logger") | ||||
|  | @ -162,6 +163,22 @@ def export_raster(self, input, **opts): | |||
|         export_raster_sync(input, tmpfile, **opts) | ||||
|         result = {'file': tmpfile} | ||||
| 
 | ||||
|         if settings.TESTING: | ||||
|             TestSafeAsyncResult.set(self.request.id, result) | ||||
| 
 | ||||
|         return result | ||||
|     except Exception as e: | ||||
|         logger.error(str(e)) | ||||
|         return {'error': str(e)} | ||||
| 
 | ||||
| @app.task(bind=True) | ||||
| def export_pointcloud(self, input, **opts): | ||||
|     try: | ||||
|         logger.info("Exporting point cloud {} with options: {}".format(input, json.dumps(opts))) | ||||
|         tmpfile = tempfile.mktemp('_pointcloud.{}'.format(opts.get('format', 'laz')), dir=settings.MEDIA_TMP) | ||||
|         export_pointcloud_sync(input, tmpfile, **opts) | ||||
|         result = {'file': tmpfile} | ||||
| 
 | ||||
|         if settings.TESTING: | ||||
|             TestSafeAsyncResult.set(self.request.id, result) | ||||
| 
 | ||||
|  |  | |||
		Ładowanie…
	
		Reference in New Issue
	
	 Piero Toffanin
						Piero Toffanin