pull/1615/head
Piero Toffanin 2025-02-18 20:00:19 +01:00
rodzic 5c26ff0742
commit 8570bf8e42
5 zmienionych plików z 135 dodań i 50 usunięć

Wyświetl plik

@ -4,6 +4,10 @@ import shutil
from wsgiref.util import FileWrapper
import mimetypes
import rasterio
from rasterio.enums import ColorInterp
from PIL import Image
import io
from shutil import copyfileobj, move
from django.core.exceptions import ObjectDoesNotExist, SuspiciousFileOperation, ValidationError
@ -403,6 +407,65 @@ class TaskDownloads(TaskNestedView):
else:
return download_file_response(request, asset_fs, 'attachment', download_filename=download_filename)
class TaskThumbnail(TaskNestedView):
def get(self, request, pk=None, project_pk=None):
"""
Generate a thumbnail on the fly for a particular task
"""
task = self.get_and_check_task(request, pk)
orthophoto_path = task.get_check_file_asset_path("orthophoto.tif")
if orthophoto_path is None:
raise exceptions.NotFound()
try:
thumb_size = int(self.request.query_params.get('size', 512))
if thumb_size < 1 or thumb_size > 2048:
raise ValueError()
quality = int(self.request.query_params.get('quality', 75))
if quality < 0 or quality > 100:
raise ValueError()
except ValueError:
raise exceptions.ValidationError("Invalid query parameters")
with rasterio.open(orthophoto_path, "r") as raster:
ci = raster.colorinterp
indexes = (1, 2, 3,)
# More than 4 bands?
if len(ci) > 4:
# Try to find RGBA band order
if ColorInterp.red in ci and \
ColorInterp.green in ci and \
ColorInterp.blue in ci:
indexes = (ci.index(ColorInterp.red) + 1,
ci.index(ColorInterp.green) + 1,
ci.index(ColorInterp.blue) + 1,)
elif len(ci) < 3:
raise exceptions.NotFound()
if ColorInterp.alpha in ci:
indexes += (ci.index(ColorInterp.alpha) + 1, )
img = raster.read(indexes=indexes, boundless=True, fill_value=255, out_shape=(
len(indexes),
thumb_size,
thumb_size,
), resampling=rasterio.enums.Resampling.nearest).transpose((1, 2, 0))
img = Image.fromarray(img)
output = io.BytesIO()
img.save(output, format='PNG', quality=quality)
res = HttpResponse(content_type="image/png")
res['Content-Disposition'] = 'inline'
res.write(output.getvalue())
output.close()
return res
"""
Raw access to the task's asset folder resources
Useful when accessing a textured 3d model, or the Potree point cloud data

Wyświetl plik

@ -3,7 +3,7 @@ from django.conf.urls import url, include
from app.api.presets import PresetViewSet
from app.plugins.views import api_view_handler
from .projects import ProjectViewSet
from .tasks import TaskViewSet, TaskDownloads, TaskAssets, TaskBackup, TaskAssetsImport
from .tasks import TaskViewSet, TaskDownloads, TaskThumbnail, TaskAssets, TaskBackup, TaskAssetsImport
from .imageuploads import Thumbnail, ImageDownload
from .processingnodes import ProcessingNodeViewSet, ProcessingNodeOptionsView
from .admin import AdminUserViewSet, AdminGroupViewSet, AdminProfileViewSet
@ -46,6 +46,7 @@ urlpatterns = [
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()),
url(r'projects/(?P<project_pk>[^/.]+)/tasks/import$', TaskAssetsImport.as_view()),
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/thumbnail$', TaskThumbnail.as_view()),
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/backup$', TaskBackup.as_view()),
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()),

Wyświetl plik

@ -1261,6 +1261,11 @@ class Task(models.Model):
else:
logger.warn("Cannot set alignment file for {}, {} does not exist".format(self, alignment_file))
def get_check_file_asset_path(self, asset):
file = self.assets_path(self.ASSETS_MAP[asset])
if isinstance(file, str) and os.path.isfile(file):
return file
def handle_images_upload(self, files):
uploaded = {}
for file in files:

Wyświetl plik

@ -171,6 +171,10 @@ class TaskListItem extends React.Component {
return `/api/projects/${this.state.task.project}/tasks/${this.state.task.id}/output/?line=${line}`;
}
thumbnailUrl = () => {
return `/api/projects/${this.state.task.project}/tasks/${this.state.task.id}/thumbnail?size=192`;
}
hoursMinutesSecs(t){
if (t === 0 || t === -1) return "-- : -- : --";
@ -556,6 +560,7 @@ class TaskListItem extends React.Component {
<div className="expanded-panel">
<div className="row">
<div className="col-md-12 no-padding">
<div className="col-md-9 col-sm-10 no-padding">
<table className="table table-condensed info-table">
<tbody>
<tr>
@ -604,6 +609,13 @@ class TaskListItem extends React.Component {
</tr>
</tbody>
</table>
</div>
{task.status === statusCodes.COMPLETED ?
<div className="col-md-3 col-sm-2 text-center">
<a href={`/map/project/${task.project}/task/${task.id}/`}>
<img className="task-thumbnail" src={this.thumbnailUrl()} alt={_("Thumbnail")}/>
</a>
</div> : ""}
{this.state.view === 'console' ?
<Console

Wyświetl plik

@ -156,4 +156,8 @@
.name-link{
margin-right: 4px;
}
.task-thumbnail{
max-width: 100%;
}
}