Merge pull request #1060 from pierotofy/gcp

Display ground control points on map
pull/1062/head v1.9.7
Piero Toffanin 2021-09-26 15:48:12 -04:00 zatwierdzone przez GitHub
commit af32e3aa29
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
73 zmienionych plików z 525 dodań i 95695 usunięć

1
.dockerignore 100644
Wyświetl plik

@ -0,0 +1 @@
**/.git

Wyświetl plik

@ -13,6 +13,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: 'recursive'
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx

Wyświetl plik

@ -14,6 +14,7 @@ jobs:
- name: Build and Test
run: |
docker-compose -f docker-compose.yml -f docker-compose.build.yml up --build -d
sleep 30
docker-compose -f docker-compose.yml -f docker-compose.build.yml build --build-arg TEST_BUILD=ON
docker-compose -f docker-compose.yml -f docker-compose.build.yml up -d
sleep 20
docker-compose exec -T webapp /webodm/webodm.sh test

3
.gitmodules vendored
Wyświetl plik

@ -1,3 +1,6 @@
[submodule "nodeodm/external/NodeODM"]
path = nodeodm/external/NodeODM
url = https://github.com/OpenDroneMap/NodeODM
[submodule "locale"]
path = locale
url = https://github.com/OpenDroneMap/WebODM-Locale

Wyświetl plik

@ -1,6 +1,7 @@
FROM ubuntu:21.04
MAINTAINER Piero Toffanin <pt@masseranolabs.com>
ARG TEST_BUILD
ENV PYTHONUNBUFFERED 1
ENV PYTHONPATH $PYTHONPATH:/webodm
ENV PROJ_LIB=/usr/share/proj
@ -20,7 +21,7 @@ RUN apt-get -qq update && apt-get -qq install -y --no-install-recommends wget cu
pip install -U pip && pip install -r requirements.txt "boto3==1.14.14" && \
# Setup cron
ln -s /webodm/nginx/crontab /var/spool/cron/crontabs/root && chmod 0644 /webodm/nginx/crontab && service cron start && chmod +x /webodm/nginx/letsencrypt-autogen.sh && \
cd /webodm/nodeodm/external/NodeODM && npm install --quiet && cd /webodm && \
/webodm/nodeodm/setup.sh && /webodm/nodeodm/cleanup.sh && cd /webodm && \
npm install --quiet -g webpack@4.16.5 && npm install --quiet -g webpack-cli@4.2.0 && npm install --quiet && webpack --mode production && \
echo "UTC" > /etc/timezone && \
python manage.py collectstatic --noinput && \

Wyświetl plik

@ -1 +1 @@
fr es it de tr ru pt_BR ko zh_Hant az pl
fr es it de tr ru pt_BR ko zh_Hant az pl hu id ja th

Wyświetl plik

@ -1,12 +1,12 @@
import os
import io
import math
from .tasks import TaskNestedView
from rest_framework import exceptions
from app.models import ImageUpload
from app.models.task import assets_directory_path
from PIL import Image
from PIL import Image, ImageDraw, ImageOps
from django.http import HttpResponse
from .tasks import download_file_response
import numpy as np
@ -26,6 +26,18 @@ def normalize(img):
return Image.fromarray(arr)
def hex2rgb(hex_color):
"""
Adapted from https://stackoverflow.com/questions/29643352/converting-hex-to-rgb-value-in-python/29643643
"""
hex_color = hex_color.lstrip('#')
if len(hex_color) != 6:
return tuple((255, 255, 255))
try:
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
except ValueError:
return tuple((255, 255, 255))
class Thumbnail(TaskNestedView):
def get(self, request, pk=None, project_pk=None, image_filename=""):
"""
@ -49,6 +61,35 @@ class Thumbnail(TaskNestedView):
quality = int(self.request.query_params.get('quality', 75))
if quality < 0 or quality > 100:
raise ValueError()
center_x = float(self.request.query_params.get('center_x', '0.5'))
center_y = float(self.request.query_params.get('center_y', '0.5'))
if center_x < -0.5 or center_x > 1.5 or center_y < -0.5 or center_y > 1.5:
raise ValueError()
draw_points = self.request.query_params.getlist('draw_point')
point_colors = self.request.query_params.getlist('point_color')
point_radiuses = self.request.query_params.getlist('point_radius')
points = []
i = 0
for p in draw_points:
coords = list(map(float, p.split(",")))
if len(coords) != 2:
raise ValueError()
points.append({
'x': coords[0],
'y': coords[1],
'color': hex2rgb(point_colors[i]) if i < len(point_colors) else (255, 255, 255),
'radius': float(point_radiuses[i]) if i < len(point_radiuses) else 1.0,
})
i += 1
zoom = float(self.request.query_params.get('zoom', '1'))
if zoom < 0.1 or zoom > 10:
raise ValueError()
except ValueError:
raise exceptions.ValidationError("Invalid query parameters")
@ -57,6 +98,48 @@ class Thumbnail(TaskNestedView):
if img.mode != 'RGB':
img = normalize(img)
img = img.convert('RGB')
w, h = img.size
thumb_size = min(max(w, h), thumb_size)
# Move image center
if center_x != 0.5 or center_y != 0.5:
img = img.crop((
w * (center_x - 0.5),
h * (center_y - 0.5),
w * (center_x + 0.5),
h * (center_y + 0.5)
))
# Scale
scale_factor = 1
off_x = 0
off_y = 0
if zoom != 1:
scale_factor = (2 ** (zoom - 1))
off_x = w / 2.0 - w / scale_factor / 2.0
off_y = h / 2.0 - h / scale_factor / 2.0
win = img.crop((off_x, off_y,
off_x + (w / scale_factor),
off_y + (h / scale_factor)
))
img = ImageOps.scale(win, scale_factor, Image.NEAREST)
sw, sh = w * scale_factor, h * scale_factor
# Draw points
for p in points:
d = ImageDraw.Draw(img)
r = p['radius'] * max(w, h) / 100.0
sx = (p['x'] + (0.5 - center_x)) * sw
sy = (p['y'] + (0.5 - center_y)) * sh
x = sx - off_x * scale_factor
y = sy - off_y * scale_factor
d.ellipse([(x - r, y - r),
(x + r, y + r)], outline=p['color'], width=int(max(1.0, math.floor(r / 3.0))))
img.thumbnail((thumb_size, thumb_size))
output = io.BytesIO()
img.save(output, format='JPEG', quality=quality)

Wyświetl plik

@ -210,7 +210,6 @@ class TaskViewSet(viewsets.ViewSet):
task.images_count = models.ImageUpload.objects.filter(task=task).count()
# Update other parameters such as processing node, task name, etc.
print(f"Amount of images currently in task: {task.images_count}")
serializer = TaskSerializer(task, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()

Wyświetl plik

@ -196,6 +196,7 @@ class Task(models.Model):
'cameras.json': 'cameras.json',
'shots.geojson': os.path.join('odm_report', 'shots.geojson'),
'report.pdf': os.path.join('odm_report', 'report.pdf'),
'ground_control_points.geojson': os.path.join('odm_georeferencing', 'ground_control_points.geojson'),
}
STATUS_CODES = (
@ -860,6 +861,9 @@ class Task(models.Model):
camera_shots = ''
if 'shots.geojson' in self.available_assets: camera_shots = '/api/projects/{}/tasks/{}/download/shots.geojson'.format(self.project.id, self.id)
ground_control_points = ''
if 'ground_control_points.geojson' in self.available_assets: ground_control_points = '/api/projects/{}/tasks/{}/download/ground_control_points.geojson'.format(self.project.id, self.id)
return {
'tiles': [{'url': self.get_tile_base_url(t), 'type': t} for t in types],
'meta': {
@ -867,7 +871,8 @@ class Task(models.Model):
'id': str(self.id),
'project': self.project.id,
'public': self.public,
'camera_shots': camera_shots
'camera_shots': camera_shots,
'ground_control_points': ground_control_points
}
}
}

Wyświetl plik

@ -49,9 +49,11 @@ const api = {
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(_("Quality Report"),"report.pdf","far fa-file-pdf"),
new AssetDownloadSeparator(),
new AssetDownload(_("All Assets"),"all.zip","far fa-file-archive")
];

Wyświetl plik

@ -0,0 +1,187 @@
import React from 'react';
import PropTypes from 'prop-types';
import AssetDownloads from '../classes/AssetDownloads';
import '../css/GCPPopup.scss';
import { _ } from '../classes/gettext';
class GCPPopup extends React.Component {
static propTypes = {
feature: PropTypes.object.isRequired,
task: PropTypes.object.isRequired,
};
constructor(props){
super(props);
this.state = {
error: "",
loading: true,
expandGCPImage: false,
selectedShot: "",
zoom: 4
}
}
selectShot = (shotId) => {
if (shotId !== this.state.selectedShot){
this.setState({loading: true, selectedShot: shotId, error: ""});
}
}
_getCoords = (shotId, key) => {
if (!shotId) return [0.5, 0.5];
const { feature } = this.props;
const ob = feature.properties.observations.find(o => o.shot_id === shotId);
if (ob){
return ob[key];
}else{
return [0.5, 0.5];
}
}
getAnnotationCoords = (shotId) => {
return this._getCoords(shotId, 'annotated');
}
getReprojectedCoords = (shotId) => {
return this._getCoords(shotId, 'reprojected');
}
getThumbUrl = (size) => {
const { task } = this.props;
const { selectedShot, zoom } = this.state;
const annotated = this.getAnnotationCoords(selectedShot);
const reprojected = this.getReprojectedCoords(selectedShot);
return `/api/projects/${task.project}/tasks/${task.id}/images/thumbnail/${selectedShot}?size=${size}&center_x=${annotated[0]}&center_y=${annotated[1]}&draw_point=${annotated[0]},${annotated[1]}&point_color=f29900&point_radius=2&draw_point=${reprojected[0]},${reprojected[1]}&&point_color=00ff00&point_radius=2&zoom=${zoom}`;
}
componentDidMount(){
const { feature } = this.props;
document.addEventListener("fullscreenchange", this.onFullscreenChange);
if (feature.properties.observations) this.selectShot(feature.properties.observations[0].shot_id);
if (this.imageContainer) this.imageContainer.addEventListener("mousewheel", this.onImageWheel); // onWheel doesn't work :/
}
componentWillUnmount(){
document.removeEventListener("fullscreenchange", this.onFullscreenChange);
if (this.imageContainer) this.imageContainer.removeEventListener("mousewheel", this.onImageWheel);
}
onFullscreenChange = (e) => {
if (!document.fullscreenElement){
this.setState({expandGCPImage: false});
}
}
imageOnError = () => {
this.setState({error: _("Image missing"), loading: false});
}
imageOnLoad = () => {
this.setState({loading: false});
}
canZoomIn = () => {
return this.state.zoom < 10 && !this.state.loading;
}
canZoomOut = () => {
return this.state.zoom > 1 && !this.state.loading;
}
zoomIn = () => {
if (this.canZoomIn()){
this.setState({loading: true, zoom: this.state.zoom + 1});
}
}
zoomOut = () => {
if (this.canZoomOut()){
this.setState({loading: true, zoom: this.state.zoom - 1});
}
}
onImageWheel = e => {
if (e.deltaY > 0){
this.zoomIn();
}else{
this.zoomOut();
}
}
onImgClick = () => {
const { expandGCPImage } = this.state;
if (!expandGCPImage){
this.image.requestFullscreen();
this.setState({ loading: true, expandGCPImage: true});
}else{
document.exitFullscreen();
this.setState({ loading: true, expandGCPImage: false });
}
}
render(){
const { error, loading, expandGCPImage, selectedShot } = this.state;
const { feature, task } = this.props;
const downloadGCPLink = `/api/projects/${task.project}/tasks/${task.id}/download/ground_control_points.geojson`;
const assetDownload = AssetDownloads.only(["ground_control_points.geojson"])[0];
const imageUrl = expandGCPImage ? this.getThumbUrl(999999999) : this.getThumbUrl(320);
const shotLinks = [];
for (let i = 0; i < feature.properties.observations.length; i++){
const obs = feature.properties.observations[i];
if (obs.shot_id === selectedShot){
shotLinks.push(<span key={obs.shot_id}>{obs.shot_id}</span>);
}else{
shotLinks.push(<a key={obs.shot_id} className="gcp-image-link" href="javascript:void(0)" onClick={() => this.selectShot(obs.shot_id)}>{obs.shot_id}</a>);
}
if (i+1 < feature.properties.observations.length) shotLinks.push(<span key={"divider-" + i}> | </span>);
}
const imgStyle = {
borderRadius: "4px",
minHeight: "32px"
};
return (<div className="gcp-popup">
<div className="title" title={feature.properties.id}>{feature.properties.id}</div>
<div>
{shotLinks}
</div>
<div className="image-container" ref={(domNode) => this.imageContainer = domNode }>
{loading ? <div className="spinner"><i className="fa fa-circle-notch fa-spin fa-fw"></i></div> : ""}
{error ? <div style={{marginTop: "8px"}}>{error}</div> : ""}
{!error && selectedShot !== "" ?
<div className={`image ${expandGCPImage ? "fullscreen" : ""} ${loading ? "loading" : ""}`} style={{marginTop: "8px"}} ref={(domNode) => { this.image = domNode;}}>
{loading && expandGCPImage ? <div><i className="fa fa-circle-notch fa-spin fa-fw"></i></div> : ""}
<a onClick={this.onImgClick} href="javascript:void(0);" title={selectedShot}><img style={imgStyle} src={imageUrl} onLoad={this.imageOnLoad} onError={this.imageOnError} /></a>
</div> : ""}
</div>
<div className="btn-group zoom-buttons">
<button onClick={this.zoomOut} disabled={!this.canZoomOut()} type="button" className="btn btn-xs btn-default" title="-">-</button>
<button onClick={this.zoomIn} disabled={!this.canZoomIn()} type="button" className="btn btn-xs btn-default" title="+">+</button>
</div>
<div>
<strong>{_("Horizontal error:")}</strong> {Math.abs(Math.max(feature.properties.error[0], feature.properties.error[1])).toFixed(3)} {_("(meters)")}
</div>
<div>
<strong>{_("Vertical error:")}</strong> {Math.abs(feature.properties.error[2]).toFixed(3)} {_("(meters)")}
</div>
<div>
<a href={downloadGCPLink}><i className={assetDownload.icon}></i> {assetDownload.label} </a>
</div>
</div>);
}
}
export default GCPPopup;

Wyświetl plik

@ -13,6 +13,7 @@ import Dropzone from '../vendor/dropzone';
import $ from 'jquery';
import ErrorMessage from './ErrorMessage';
import ImagePopup from './ImagePopup';
import GCPPopup from './GCPPopup';
import SwitchModeButton from './SwitchModeButton';
import ShareButton from './ShareButton';
import AssetDownloads from '../classes/AssetDownloads';
@ -266,6 +267,55 @@ class Map extends React.Component {
this.addedCameraShots = true;
}
// Add ground control points layer if available
if (meta.task && meta.task.ground_control_points && !this.addedGroundControlPoints){
const gcpMarker = L.AwesomeMarkers.icon({
icon: 'dot-circle',
markerColor: 'blue',
prefix: 'fa'
});
const gcpLayer = new L.GeoJSON.AJAX(meta.task.ground_control_points, {
style: function (feature) {
return {
opacity: 1,
fillOpacity: 0.7,
color: "#000000"
}
},
pointToLayer: function (feature, latlng) {
return new L.marker(latlng, {
icon: gcpMarker
});
},
onEachFeature: function (feature, layer) {
if (feature.properties && feature.properties.observations) {
// TODO!
let root = null;
const lazyrender = () => {
if (!root) root = document.createElement("div");
ReactDOM.render(<GCPPopup task={meta.task} feature={feature}/>, root);
return root;
}
layer.bindPopup(L.popup(
{
lazyrender,
maxHeight: 450,
minWidth: 320
}));
}
}
});
gcpLayer[Symbol.for("meta")] = {name: name + " " + _("(GCPs)"), icon: "far fa-dot-circle fa-fw"};
this.setState(update(this.state, {
overlays: {$push: [gcpLayer]}
}));
this.addedGroundControlPoints = true;
}
done();
})
.fail((_, __, err) => done(err))

Wyświetl plik

@ -0,0 +1,10 @@
import React from 'react';
import { mount } from 'enzyme';
import GCPPopup from '../GCPPopup';
describe('<GCPPopup />', () => {
it('renders without exploding', () => {
const wrapper = mount(<GCPPopup task={{id: 1, project: 1}} feature={{properties: {id: "test", error: [0,1,2], observations: [{shot_id: "test", annotated: [0, 0], reprojected: [0,0]}]}}} />);
expect(wrapper.exists()).toBe(true);
})
});

Wyświetl plik

@ -0,0 +1,59 @@
.gcp-popup{
div{
margin-top: 8px;
}
div.title{
margin-top: 0;
font-weight: bold;
max-width: 300px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.image-container{
position: relative;
.spinner{
position: absolute;
left: 4px;
min-height: 32px;
}
}
.zoom-buttons{
float: right;
}
div.image{
overflow: hidden;
a{
cursor: zoom-in;
}
&.loading{
opacity: 0.2;
}
&.fullscreen{
&.loading{
opacity: 1;
}
display: flex;
align-items: center;
justify-content: center;
color: white;
i{
font-size: 200%;
position: absolute;
left: 50%;
top: 45%;
}
a{
cursor: zoom-out;
}
img{
max-width: 100%;
max-height: 100%;
}
}
}
}

Wyświetl plik

@ -19,6 +19,7 @@
&.fullscreen{
display: flex;
align-items: center;
justify-content: center;
color: white;
i{
font-size: 200%;

Wyświetl plik

@ -1,84 +1,84 @@
// Auto-generated with extract_odm_strings.py, do not edit!
_("Turn on gamma tone mapping or none for no tone mapping. Can be one of %(choices)s. Default: %(default)s ");
_("Filters the point cloud by keeping only a single point around a radius N (in meters). This can be useful to limit the output resolution of the point cloud and remove duplicate points. Set to 0 to disable sampling. Default: %(default)s");
_("Type of photometric outlier removal method. Can be one of: %(choices)s. Default: %(default)s");
_("Number of nearest images to pre-match based on GPS exif data. Set to 0 to skip pre-matching. Neighbors works together with Distance parameter, set both to 0 to not use pre-matching. Default: %(default)s");
_("Use the camera parameters computed from another dataset instead of calculating them. Can be specified either as path to a cameras.json file or as a JSON string representing the contents of a cameras.json file. Default: %(default)s");
_("Export the georeferenced point cloud in LAS format. Default: %(default)s");
_("Filters the point cloud by removing points that deviate more than N standard deviations from the local mean. Set to 0 to disable filtering. Default: %(default)s");
_("Simple Morphological Filter elevation threshold parameter (meters). Default: %(default)s");
_("Path to the project folder. Your project folder should contain subfolders for each dataset. Each dataset should have an \"images\" folder.");
_("Path to the image geolocation file containing the camera center coordinates used for georeferencing. Note that omega/phi/kappa are currently not supported (you can set them to 0). The file needs to use the following format: EPSG:<code> or <+proj definition>image_name geo_x geo_y geo_z [omega (degrees)] [phi (degrees)] [kappa (degrees)] [horz accuracy (meters)] [vert accuracy (meters)]Default: %(default)s");
_("Reduce the memory usage needed for depthmap fusion by splitting large scenes into tiles. Turn this on if your machine doesn't have much RAM and/or you've set --pc-quality to high or ultra. Experimental. Default: %(default)s");
_("Improve the accuracy of the point cloud by computing geometrically consistent depthmaps. This increases processing time, but can improve results in urban scenes. Default: %(default)s");
_("Decimate the points before generating the DEM. 1 is no decimation (full quality). 100 decimates ~99%% of the points. Useful for speeding up generation of DEM results in very large datasets. Default: %(default)s");
_("Number of steps used to fill areas with gaps. Set to 0 to disable gap filling. Starting with a radius equal to the output resolution, N different DEMs are generated with progressively bigger radius using the inverse distance weighted (IDW) algorithm and merged together. Remaining gaps are then merged using nearest neighbor interpolation. Default: %(default)s");
_("Skip the blending of colors near seams. Default: %(default)s");
_("Generates a benchmark file with runtime info. Default: %(default)s");
_("When processing multispectral datasets, you can specify the name of the primary band that will be used for reconstruction. It's recommended to choose a band which has sharp details and is in focus. Default: %(default)s");
_("Use this tag to build a DSM (Digital Surface Model, ground + objects) using a progressive morphological filter. Check the --dem* parameters for finer tuning. Default: %(default)s");
_("Octree depth used in the mesh reconstruction, increase to get more vertices, recommended values are 8-12. Default: %(default)s");
_("Ignore Ground Sampling Distance (GSD). GSD caps the maximum resolution of image outputs and resizes images when necessary, resulting in faster processing and lower memory usage. Since GSD is an estimate, sometimes ignoring it can result in slightly better image output quality. Default: %(default)s");
_("Radius of the overlap between submodels. After grouping images into clusters, images that are closer than this radius to a cluster are added to the cluster. This is done to ensure that neighboring submodels overlap. Default: %(default)s");
_("The maximum vertex count of the output mesh. Default: %(default)s");
_("Use a full 3D mesh to compute the orthophoto instead of a 2.5D mesh. This option is a bit faster and provides similar results in planar areas. Default: %(default)s");
_("Minimum number of features to extract per image. More features can be useful for finding more matches between images, potentially allowing the reconstruction of areas with little overlap or insufficient features. More features also slow down processing. Default: %(default)s");
_("Legacy option (use --feature-quality instead). Resizes images by the largest side for feature extraction purposes only. Set to -1 to disable. This does not affect the final orthophoto resolution quality and will not resize the original images. Default: %(default)s");
_("Computes an euclidean raster map for each DEM. The map reports the distance from each cell to the nearest NODATA value (before any hole filling takes place). This can be useful to isolate the areas that have been filled. Default: %(default)s");
_("Print debug messages. Default: %(default)s");
_("Set the compression to use for orthophotos. Can be one of: %(choices)s. Default: %(default)s");
_("Matcher algorithm, Fast Library for Approximate Nearest Neighbors or Bag of Words. FLANN is slower, but more stable. BOW is faster, but can sometimes miss valid matches. Can be one of: %(choices)s. Default: %(default)s");
_("Displays version number and exits. ");
_("When processing multispectral datasets, ODM will automatically align the images for each band. If the images have been postprocessed and are already aligned, use this option. Default: %(default)s");
_("Skip normalization of colors across all images. Useful when processing radiometric data. Default: %(default)s");
_("End processing at this stage. Can be one of: %(choices)s. Default: %(default)s");
_("Run local bundle adjustment for every image added to the reconstruction and a global adjustment every 100 images. Speeds up reconstruction for very large datasets. Default: %(default)s");
_("Print additional messages to the console. Default: %(default)s");
_("Skip generation of PDF report. This can save time if you don't need a report. Default: %(default)s");
_("Delete heavy intermediate files to optimize disk space usage. This affects the ability to restart the pipeline from an intermediate stage, but allows datasets to be processed on machines that don't have sufficient disk space available. Default: %(default)s");
_("Use images' GPS exif data for reconstruction, even if there are GCPs present.This flag is useful if you have high precision GPS measurements. If there are no GCPs, this flag does nothing. Default: %(default)s");
_("Generates a benchmark file with runtime info. Default: %(default)s");
_("Simple Morphological Filter window radius parameter (meters). Default: %(default)s");
_("Set this parameter if you want a striped GeoTIFF. Default: %(default)s");
_("Distance threshold in meters to find pre-matching images based on GPS exif data. Set both matcher-neighbors and this to 0 to skip pre-matching. Default: %(default)s");
_("Path to the file containing the ground control points used for georeferencing. The file needs to use the following format: EPSG:<code> or <+proj definition>geo_x geo_y geo_z im_x im_y image_name [gcp_name] [extra1] [extra2]Default: %(default)s");
_("Generates a polygon around the cropping area that cuts the orthophoto around the edges of features. This polygon can be useful for stitching seamless mosaics with multiple overlapping orthophotos. Default: %(default)s");
_("URL to a ClusterODM instance for distributing a split-merge workflow on multiple nodes in parallel. Default: %(default)s");
_("Rerun processing from this stage. Can be one of: %(choices)s. Default: %(default)s");
_("show this help message and exit");
_("Simple Morphological Filter slope parameter (rise over run). Default: %(default)s");
_("When texturing the 3D mesh, for each triangle, choose to prioritize images with sharp features (gmi) or those that cover the largest area (area). Default: %(default)s");
_("Set point cloud quality. Higher quality generates better, denser point clouds, but requires more memory and takes longer. Each step up in quality increases processing time roughly by a factor of 4x.Can be one of: %(choices)s. Default: %(default)s");
_("Rerun this stage only and stop. Can be one of: %(choices)s. Default: %(default)s");
_("Run local bundle adjustment for every image added to the reconstruction and a global adjustment every 100 images. Speeds up reconstruction for very large datasets. Default: %(default)s");
_("Permanently delete all previous results and rerun the processing pipeline.");
_("Print additional messages to the console. Default: %(default)s");
_("Use this tag if you have a GCP File but want to use the EXIF information for georeferencing instead. Default: %(default)s");
_("DSM/DTM resolution in cm / pixel. Note that this value is capped by a ground sampling distance (GSD) estimate. To remove the cap, check --ignore-gsd also. Default: %(default)s");
_("Path to the image groups file that controls how images should be split into groups. The file needs to use the following format: image_name group_nameDefault: %(default)s");
_("When processing multispectral datasets, ODM will automatically align the images for each band. If the images have been postprocessed and are already aligned, use this option. Default: %(default)s");
_("Legacy option (use --feature-quality instead). Resizes images by the largest side for feature extraction purposes only. Set to -1 to disable. This does not affect the final orthophoto resolution quality and will not resize the original images. Default: %(default)s");
_("Legacy option (use --pc-quality instead). Controls the density of the point cloud by setting the resolution of the depthmap images. Higher values take longer to compute but produce denser point clouds. Default: %(default)s");
_("Name of dataset (i.e subfolder name within project folder). Default: %(default)s");
_("Simple Morphological Filter elevation threshold parameter (meters). Default: %(default)s");
_("Set this parameter if you want to generate a PNG rendering of the orthophoto. Default: %(default)s");
_("Path to the project folder. Your project folder should contain subfolders for each dataset. Each dataset should have an \"images\" folder.");
_("Matcher algorithm, Fast Library for Approximate Nearest Neighbors or Bag of Words. FLANN is slower, but more stable. BOW is faster, but can sometimes miss valid matches. Can be one of: %(choices)s. Default: %(default)s");
_("Choose what to merge in the merge step in a split dataset. By default all available outputs are merged. Options: %(choices)s. Default: %(default)s");
_("Export the georeferenced point cloud in Entwine Point Tile (EPT) format. Default: %(default)s");
_("When processing multispectral datasets, you can specify the name of the primary band that will be used for reconstruction. It's recommended to choose a band which has sharp details and is in focus. Default: %(default)s");
_("Use a full 3D mesh to compute the orthophoto instead of a 2.5D mesh. This option is a bit faster and provides similar results in planar areas. Default: %(default)s");
_("Displays version number and exits. ");
_("When texturing the 3D mesh, for each triangle, choose to prioritize images with sharp features (gmi) or those that cover the largest area (area). Default: %(default)s");
_("URL to a ClusterODM instance for distributing a split-merge workflow on multiple nodes in parallel. Default: %(default)s");
_("Use this tag to build a DTM (Digital Terrain Model, ground only) using a simple morphological filter. Check the --dem* and --smrf* parameters for finer tuning. Default: %(default)s");
_("Use this tag to build a DSM (Digital Surface Model, ground + objects) using a progressive morphological filter. Check the --dem* parameters for finer tuning. Default: %(default)s");
_("Generates a polygon around the cropping area that cuts the orthophoto around the edges of features. This polygon can be useful for stitching seamless mosaics with multiple overlapping orthophotos. Default: %(default)s");
_("Turn on gamma tone mapping or none for no tone mapping. Can be one of %(choices)s. Default: %(default)s ");
_("Set the compression to use for orthophotos. Can be one of: %(choices)s. Default: %(default)s");
_("Set this parameter if you want a striped GeoTIFF. Default: %(default)s");
_("Simple Morphological Filter elevation scalar parameter. Default: %(default)s");
_("Rerun processing from this stage. Can be one of: %(choices)s. Default: %(default)s");
_("Simple Morphological Filter slope parameter (rise over run). Default: %(default)s");
_("Classify the point cloud outputs using a Simple Morphological Filter. You can control the behavior of this option by tweaking the --dem-* parameters. Default: %(default)s");
_("Export the georeferenced point cloud in LAS format. Default: %(default)s");
_("Number of nearest images to pre-match based on GPS exif data. Set to 0 to skip pre-matching. Neighbors works together with Distance parameter, set both to 0 to not use pre-matching. Default: %(default)s");
_("Set this parameter if you want to generate a Google Earth (KMZ) rendering of the orthophoto. Default: %(default)s");
_("Path to the file containing the ground control points used for georeferencing. The file needs to use the following format: EPSG:<code> or <+proj definition>geo_x geo_y geo_z im_x im_y image_name [gcp_name] [extra1] [extra2]Default: %(default)s");
_("Reduce the memory usage needed for depthmap fusion by splitting large scenes into tiles. Turn this on if your machine doesn't have much RAM and/or you've set --pc-quality to high or ultra. Experimental. Default: %(default)s");
_("Radius of the overlap between submodels. After grouping images into clusters, images that are closer than this radius to a cluster are added to the cluster. This is done to ensure that neighboring submodels overlap. Default: %(default)s");
_("Average number of images per submodel. When splitting a large dataset into smaller submodels, images are grouped into clusters. This value regulates the number of images that each cluster should have on average. Default: %(default)s");
_("Minimum number of features to extract per image. More features can be useful for finding more matches between images, potentially allowing the reconstruction of areas with little overlap or insufficient features. More features also slow down processing. Default: %(default)s");
_("Distance threshold in meters to find pre-matching images based on GPS exif data. Set both matcher-neighbors and this to 0 to skip pre-matching. Default: %(default)s");
_("Skip the blending of colors near seams. Default: %(default)s");
_("Improve the accuracy of the point cloud by computing geometrically consistent depthmaps. This increases processing time, but can improve results in urban scenes. Default: %(default)s");
_("Computes an euclidean raster map for each DEM. The map reports the distance from each cell to the nearest NODATA value (before any hole filling takes place). This can be useful to isolate the areas that have been filled. Default: %(default)s");
_("Filters the point cloud by removing points that deviate more than N standard deviations from the local mean. Set to 0 to disable filtering. Default: %(default)s");
_("Set point cloud quality. Higher quality generates better, denser point clouds, but requires more memory and takes longer. Each step up in quality increases processing time roughly by a factor of 4x.Can be one of: %(choices)s. Default: %(default)s");
_("Skip generation of PDF report. This can save time if you don't need a report. Default: %(default)s");
_("Generate static tiles for orthophotos and DEMs that are suitable for viewers like Leaflet or OpenLayers. Default: %(default)s");
_("Ignore Ground Sampling Distance (GSD). GSD caps the maximum resolution of image outputs and resizes images when necessary, resulting in faster processing and lower memory usage. Since GSD is an estimate, sometimes ignoring it can result in slightly better image output quality. Default: %(default)s");
_("Choose the algorithm for extracting keypoints and computing descriptors. Can be one of: %(choices)s. Default: %(default)s");
_("Perform ground rectification on the point cloud. This means that wrongly classified ground points will be re-classified and gaps will be filled. Useful for generating DTMs. Default: %(default)s");
_("Build orthophoto overviews for faster display in programs such as QGIS. Default: %(default)s");
_("show this help message and exit");
_("Copy output results to this folder after processing.");
_("Octree depth used in the mesh reconstruction, increase to get more vertices, recommended values are 8-12. Default: %(default)s");
_("Turn off camera parameter optimization during bundle adjustment. This can be sometimes useful for improving results that exhibit doming/bowling or when images are taken with a rolling shutter camera. Default: %(default)s");
_("Set a camera projection type. Manually setting a value can help improve geometric undistortion. By default the application tries to determine a lens type from the images metadata. Can be one of: %(choices)s. Default: %(default)s");
_("Set the radiometric calibration to perform on images. When processing multispectral and thermal images you should set this option to obtain reflectance/temperature values (otherwise you will get digital number values). [camera] applies black level, vignetting, row gradient gain/exposure compensation (if appropriate EXIF tags are found) and computes absolute temperature values. [camera+sun] is experimental, applies all the corrections of [camera], plus compensates for spectral radiance registered via a downwelling light sensor (DLS) taking in consideration the angle of the sun. Can be one of: %(choices)s. Default: %(default)s");
_("Use this tag to build a DTM (Digital Terrain Model, ground only) using a simple morphological filter. Check the --dem* and --smrf* parameters for finer tuning. Default: %(default)s");
_("Orthophoto resolution in cm / pixel. Note that this value is capped by a ground sampling distance (GSD) estimate. To remove the cap, check --ignore-gsd also. Default: %(default)s");
_("Copy output results to this folder after processing.");
_("Perform ground rectification on the point cloud. This means that wrongly classified ground points will be re-classified and gaps will be filled. Useful for generating DTMs. Default: %(default)s");
_("Simple Morphological Filter elevation scalar parameter. Default: %(default)s");
_("Keep faces in the mesh that are not seen in any camera. Default: %(default)s");
_("Choose what to merge in the merge step in a split dataset. By default all available outputs are merged. Options: %(choices)s. Default: %(default)s");
_("Set feature extraction quality. Higher quality generates better features, but requires more memory and takes longer. Can be one of: %(choices)s. Default: %(default)s");
_("Build orthophoto overviews for faster display in programs such as QGIS. Default: %(default)s");
_("Name of dataset (i.e subfolder name within project folder). Default: %(default)s");
_("Use this tag if you have a GCP File but want to use the EXIF information for georeferencing instead. Default: %(default)s");
_("Use images' GPS exif data for reconstruction, even if there are GCPs present.This flag is useful if you have high precision GPS measurements. If there are no GCPs, this flag does nothing. Default: %(default)s");
_("Set this parameter if you want to generate a Google Earth (KMZ) rendering of the orthophoto. Default: %(default)s");
_("Create Cloud-Optimized GeoTIFFs instead of normal GeoTIFFs. Default: %(default)s");
_("Delete heavy intermediate files to optimize disk space usage. This affects the ability to restart the pipeline from an intermediate stage, but allows datasets to be processed on machines that don't have sufficient disk space available. Default: %(default)s");
_("Permanently delete all previous results and rerun the processing pipeline.");
_("Automatically crop image outputs by creating a smooth buffer around the dataset boundaries, shrinked by N meters. Use 0 to disable cropping. Default: %(default)s");
_("Skip normalization of colors across all images. Useful when processing radiometric data. Default: %(default)s");
_("Skip generation of a full 3D model. This can save time if you only need 2D results such as orthophotos and DEMs. Default: %(default)s");
_("DSM/DTM resolution in cm / pixel. Note that this value is capped by a ground sampling distance (GSD) estimate. To remove the cap, check --ignore-gsd also. Default: %(default)s");
_("Rerun this stage only and stop. Can be one of: %(choices)s. Default: %(default)s");
_("The maximum number of processes to use in various processes. Peak memory requirement is ~1GB per thread and 2 megapixel image resolution. Default: %(default)s");
_("Legacy option (use --pc-quality instead). Controls the density of the point cloud by setting the resolution of the depthmap images. Higher values take longer to compute but produce denser point clouds. Default: %(default)s");
_("Generate static tiles for orthophotos and DEMs that are suitable for viewers like Leaflet or OpenLayers. Default: %(default)s");
_("Export the georeferenced point cloud in CSV format. Default: %(default)s");
_("Path to the image groups file that controls how images should be split into groups. The file needs to use the following format: image_name group_nameDefault: %(default)s");
_("Set a value in meters for the GPS Dilution of Precision (DOP) information for all images. If your images are tagged with high precision GPS information (RTK), this value will be automatically set accordingly. You can use this option to manually set it in case the reconstruction fails. Lowering this option can sometimes help control bowling-effects over large areas. Default: %(default)s");
_("Set this parameter if you want to generate a PNG rendering of the orthophoto. Default: %(default)s");
_("Classify the point cloud outputs using a Simple Morphological Filter. You can control the behavior of this option by tweaking the --dem-* parameters. Default: %(default)s");
_("Number of steps used to fill areas with gaps. Set to 0 to disable gap filling. Starting with a radius equal to the output resolution, N different DEMs are generated with progressively bigger radius using the inverse distance weighted (IDW) algorithm and merged together. Remaining gaps are then merged using nearest neighbor interpolation. Default: %(default)s");
_("Skips dense reconstruction and 3D model generation. It generates an orthophoto directly from the sparse reconstruction. If you just need an orthophoto and do not need a full 3D model, turn on this option. Default: %(default)s");
_("Average number of images per submodel. When splitting a large dataset into smaller submodels, images are grouped into clusters. This value regulates the number of images that each cluster should have on average. Default: %(default)s");
_("Use the camera parameters computed from another dataset instead of calculating them. Can be specified either as path to a cameras.json file or as a JSON string representing the contents of a cameras.json file. Default: %(default)s");
_("Path to the image geolocation file containing the camera center coordinates used for georeferencing. Note that omega/phi/kappa are currently not supported (you can set them to 0). The file needs to use the following format: EPSG:<code> or <+proj definition>image_name geo_x geo_y geo_z [omega (degrees)] [phi (degrees)] [kappa (degrees)] [horz accuracy (meters)] [vert accuracy (meters)]Default: %(default)s");
_("Filters the point cloud by keeping only a single point around a radius N (in meters). This can be useful to limit the output resolution of the point cloud and remove duplicate points. Set to 0 to disable sampling. Default: %(default)s");
_("Set the radiometric calibration to perform on images. When processing multispectral and thermal images you should set this option to obtain reflectance/temperature values (otherwise you will get digital number values). [camera] applies black level, vignetting, row gradient gain/exposure compensation (if appropriate EXIF tags are found) and computes absolute temperature values. [camera+sun] is experimental, applies all the corrections of [camera], plus compensates for spectral radiance registered via a downwelling light sensor (DLS) taking in consideration the angle of the sun. Can be one of: %(choices)s. Default: %(default)s");
_("Type of photometric outlier removal method. Can be one of: %(choices)s. Default: %(default)s");
_("Skip generation of a full 3D model. This can save time if you only need 2D results such as orthophotos and DEMs. Default: %(default)s");
_("Orthophoto resolution in cm / pixel. Note that this value is capped by a ground sampling distance (GSD) estimate. To remove the cap, check --ignore-gsd also. Default: %(default)s");
_("Decimate the points before generating the DEM. 1 is no decimation (full quality). 100 decimates ~99%% of the points. Useful for speeding up generation of DEM results in very large datasets. Default: %(default)s");
_("Set feature extraction quality. Higher quality generates better features, but requires more memory and takes longer. Can be one of: %(choices)s. Default: %(default)s");
_("The maximum number of processes to use in various processes. Peak memory requirement is ~1GB per thread and 2 megapixel image resolution. Default: %(default)s");
_("Export the georeferenced point cloud in CSV format. Default: %(default)s");
_("Print debug messages. Default: %(default)s");
_("Keep faces in the mesh that are not seen in any camera. Default: %(default)s");
_("Set a value in meters for the GPS Dilution of Precision (DOP) information for all images. If your images are tagged with high precision GPS information (RTK), this value will be automatically set accordingly. You can use this option to manually set it in case the reconstruction fails. Lowering this option can sometimes help control bowling-effects over large areas. Default: %(default)s");
_("Create Cloud-Optimized GeoTIFFs instead of normal GeoTIFFs. Default: %(default)s");

Wyświetl plik

@ -362,6 +362,7 @@ class TestApiTask(BootTransactionTestCase):
# Can download assets
for asset in list(task.ASSETS_MAP.keys()):
res = client.get("/api/projects/{}/tasks/{}/download/{}".format(project.id, task.id, asset))
print("DOWLOAD: " + asset)
self.assertEqual(res.status_code, status.HTTP_200_OK)
# We can stream downloads
@ -420,6 +421,14 @@ class TestApiTask(BootTransactionTestCase):
self.assertEqual(i.width, 48)
self.assertEqual(i.height, 36)
# Can plot points, recenter thumbnails, zoom
res = client.get("/api/projects/{}/tasks/{}/images/thumbnail/tiny_drone_image.jpg?size=9999999&center_x=0.3&center_y=0.2&draw_point=0.4,0.4&point_color=ff0000&point_radius=3&zoom=2".format(project.id, task.id))
self.assertTrue(res.status_code == status.HTTP_200_OK)
with Image.open(io.BytesIO(res.content)) as i:
# Thumbnail has been resized to the max allowed (oringinal image size)
self.assertEqual(i.width, 48)
self.assertEqual(i.height, 36)
# Can download images
res = client.get("/api/projects/{}/tasks/{}/images/download/tiny_drone_image.jpg".format(project.id, task.id))
self.assertTrue(res.status_code == status.HTTP_200_OK)

Wyświetl plik

@ -15,3 +15,4 @@ _("A plugin to add a button for quickly opening OpenStreetMap's iD editor and se
_("A plugin to create GCP files from images")
_("A plugin to create GCP files from images")
_("Create short links when sharing task URLs")
_("Create editable short links when sharing task URLs")

Wyświetl plik

@ -59,9 +59,6 @@ def dev_tools(request, action):
raise Exception(_("Cannot find locale/ folder in .zip archive"))
webodm_locale_dir = os.path.join(settings.BASE_DIR, "locale")
if os.path.isdir(webodm_locale_dir):
logger.info("Removing %s" % webodm_locale_dir)
shutil.rmtree(webodm_locale_dir)
for locale_path in locale_paths:
logger.info("Found locale at %s" % locale_path)

1
locale 160000

@ -0,0 +1 @@
Subproject commit 111500b587c2264b32f2b37c981cc177995b7bc5

Plik diff jest za duży Load Diff

Plik diff jest za duży Load Diff

12
nodeodm/cleanup.sh 100755
Wyświetl plik

@ -0,0 +1,12 @@
#!/bin/bash
set -eo pipefail
__dirname=$(cd $(dirname "$0"); pwd -P)
cd "${__dirname}"
# Only execute if TEST_BUILD is not set
if [[ -z ${TEST_BUILD+x} ]]; then
rm -fr external/NodeODM
else
echo "Nothing to do"
fi

@ -1 +1 @@
Subproject commit 019629c0df581d0bcab56178918120a5b0ed882e
Subproject commit 2fb254e378abfa42ddd04bd0662583b9dbe277bf

14
nodeodm/setup.sh 100755
Wyświetl plik

@ -0,0 +1,14 @@
#!/bin/bash
set -eo pipefail
__dirname=$(cd $(dirname "$0"); pwd -P)
cd "${__dirname}"
# Only execute if TEST_BUILD is set to true
if [[ ! -z ${TEST_BUILD+x} ]]; then
cd external/NodeODM
npm install --quiet
echo "NodeODM is setup"
else
echo "TEST_BUILD is not set"
fi

Wyświetl plik

@ -143,7 +143,7 @@ class TestClientApi(TestCase):
# Verify that options have been updated after restarting the task
task_info = api.get_task(uuid).info()
self.assertTrue(len(task_info.options) == 1)
self.assertTrue(len(task_info.options) == 2) # pc-ept has been added
self.assertTrue(task_info.options[0]['name'] == 'mesh-size')
self.assertTrue(task_info.options[0]['value'] == 12345)

Wyświetl plik

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