kopia lustrzana https://github.com/OpenDroneMap/ODM
Merge branch 'master' of https://github.com/OpenDroneMap/OpenDroneMap into update-vtk
# Conflicts: # opendm/tasks.py # scripts/resize.pypull/644/head
commit
dda829f74e
|
@ -1,3 +0,0 @@
|
||||||
[submodule "src/bundler"]
|
|
||||||
path = src/bundler
|
|
||||||
url = https://github.com/chris-cooper/bundler_sfm
|
|
|
@ -0,0 +1 @@
|
||||||
|
opendronemap.org
|
|
@ -14,12 +14,12 @@ RUN apt-get install --no-install-recommends -y git cmake python-pip build-essent
|
||||||
libgtk2.0-dev libavcodec-dev libavformat-dev libswscale-dev python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libflann-dev \
|
libgtk2.0-dev libavcodec-dev libavformat-dev libswscale-dev python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libflann-dev \
|
||||||
libproj-dev libxext-dev liblapack-dev libeigen3-dev libvtk5-dev python-networkx libgoogle-glog-dev libsuitesparse-dev libboost-filesystem-dev libboost-iostreams-dev \
|
libproj-dev libxext-dev liblapack-dev libeigen3-dev libvtk5-dev python-networkx libgoogle-glog-dev libsuitesparse-dev libboost-filesystem-dev libboost-iostreams-dev \
|
||||||
libboost-regex-dev libboost-python-dev libboost-date-time-dev libboost-thread-dev python-pyproj python-empy python-nose python-pyside python-pyexiv2 python-scipy \
|
libboost-regex-dev libboost-python-dev libboost-date-time-dev libboost-thread-dev python-pyproj python-empy python-nose python-pyside python-pyexiv2 python-scipy \
|
||||||
jhead liblas-bin python-matplotlib libatlas-base-dev libgmp-dev libmpfr-dev
|
jhead liblas-bin python-matplotlib libatlas-base-dev libgmp-dev libmpfr-dev swig2.0 python-wheel libboost-log-dev libjsoncpp-dev
|
||||||
|
|
||||||
RUN apt-get remove libdc1394-22-dev
|
RUN apt-get remove libdc1394-22-dev
|
||||||
RUN pip install --upgrade pip
|
RUN pip install --upgrade pip
|
||||||
RUN pip install setuptools
|
RUN pip install setuptools
|
||||||
RUN pip install -U PyYAML exifread gpxpy xmltodict catkin-pkg appsettings
|
RUN pip install -U PyYAML exifread gpxpy xmltodict catkin-pkg appsettings https://github.com/OpenDroneMap/gippy/archive/v0.3.9.tar.gz
|
||||||
|
|
||||||
ENV PYTHONPATH="$PYTHONPATH:/code/SuperBuild/install/lib/python2.7/dist-packages"
|
ENV PYTHONPATH="$PYTHONPATH:/code/SuperBuild/install/lib/python2.7/dist-packages"
|
||||||
ENV PYTHONPATH="$PYTHONPATH:/code/SuperBuild/src/opensfm"
|
ENV PYTHONPATH="$PYTHONPATH:/code/SuperBuild/src/opensfm"
|
||||||
|
@ -44,6 +44,7 @@ COPY /SuperBuild/CMakeLists.txt /code/SuperBuild/CMakeLists.txt
|
||||||
COPY docker.settings.yaml /code/settings.yaml
|
COPY docker.settings.yaml /code/settings.yaml
|
||||||
COPY VERSION /code/VERSION
|
COPY VERSION /code/VERSION
|
||||||
|
|
||||||
|
|
||||||
#Compile code in SuperBuild and root directories
|
#Compile code in SuperBuild and root directories
|
||||||
|
|
||||||
RUN cd SuperBuild && mkdir build && cd build && cmake .. && make -j$(nproc) && cd ../.. && mkdir build && cd build && cmake .. && make -j$(nproc)
|
RUN cd SuperBuild && mkdir build && cd build && cmake .. && make -j$(nproc) && cd ../.. && mkdir build && cd build && cmake .. && make -j$(nproc)
|
||||||
|
|
25
README.md
25
README.md
|
@ -20,15 +20,22 @@ In a word, OpenDroneMap is a toolchain for processing raw civilian UAS imagery t
|
||||||
|
|
||||||
Open Drone Map now includes state-of-the-art 3D reconstruction work by Michael Waechter, Nils Moehrle, and Michael Goesele. See their publication at http://www.gcc.tu-darmstadt.de/media/gcc/papers/Waechter-2014-LTB.pdf.
|
Open Drone Map now includes state-of-the-art 3D reconstruction work by Michael Waechter, Nils Moehrle, and Michael Goesele. See their publication at http://www.gcc.tu-darmstadt.de/media/gcc/papers/Waechter-2014-LTB.pdf.
|
||||||
|
|
||||||
|
|
||||||
## QUICKSTART
|
## QUICKSTART
|
||||||
|
|
||||||
OpenDroneMap can run natively on Ubuntu 14.04 or later, see [Build and Run Using Docker](#build-and-run-using-docker) for running on Windows / MacOS. A [Vagrant VM](https://github.com/OpenDroneMap/odm_vagrant) is also available.
|
### Docker (All platforms)
|
||||||
|
|
||||||
*Support for Ubuntu 12.04 is currently BROKEN with the addition of OpenSfM and Ceres-Solver. It is likely to remain broken unless a champion is found to fix it.*
|
The easiest way to run ODM is through Docker. If you don't have it installed,
|
||||||
|
see the [Docker Ubuntu installation tutorial](https://docs.docker.com/engine/installation/linux/ubuntulinux/) and follow the
|
||||||
|
instructions through "Create a Docker group". The Docker image workflow
|
||||||
|
has equivalent procedures for Mac OS X and Windows found at [docs.docker.com](docs.docker.com). Then run the following command which will build a pre-built image and run on images found in `$(pwd)/images` (you can change this if you need to, see the [wiki](https://github.com/OpenDroneMap/OpenDroneMap/wiki/Docker) for more detailed instructions.
|
||||||
|
|
||||||
**[Download the latest release here](https://github.com/OpenDroneMap/OpenDroneMap/releases)**
|
```
|
||||||
|
docker run -it --rm -v $(pwd)/images:/code/images -v $(pwd)/odm_orthophoto:/code/odm_orthophoto -v $(pwd)/odm_texturing:/code/odm_texturing opendronemap/opendronemap
|
||||||
|
```
|
||||||
|
|
||||||
|
### Native Install (Ubuntu 14.04 or later)
|
||||||
|
|
||||||
|
**[Download the latest release here](https://github.com/OpenDroneMap/OpenDroneMap/releases)**
|
||||||
Current version: 0.3.1 (this software is in beta)
|
Current version: 0.3.1 (this software is in beta)
|
||||||
|
|
||||||
1. Extract and enter the OpenDroneMap directory
|
1. Extract and enter the OpenDroneMap directory
|
||||||
|
@ -37,11 +44,13 @@ Current version: 0.3.1 (this software is in beta)
|
||||||
3. Download a sample dataset from [here](https://github.com/OpenDroneMap/odm_data_aukerman/archive/master.zip) (about 550MB) and extract it as a subdirectory in your project directory.
|
3. Download a sample dataset from [here](https://github.com/OpenDroneMap/odm_data_aukerman/archive/master.zip) (about 550MB) and extract it as a subdirectory in your project directory.
|
||||||
4. Run `./run.sh odm_data_aukerman`
|
4. Run `./run.sh odm_data_aukerman`
|
||||||
5. Enter dataset directory to view results:
|
5. Enter dataset directory to view results:
|
||||||
- orthophoto: odm_orthophoto/odm_orthophoto.tif
|
- orthophoto: odm_orthophoto/odm_orthophoto.tif
|
||||||
- textured mesh model: odm_texturing/odm_textured_model_geo.obj
|
- textured mesh model: odm_texturing/odm_textured_model_geo.obj
|
||||||
- point cloud (georeferenced): odm_georeferencing/odm_georeferenced_model.ply
|
- point cloud (georeferenced): odm_georeferencing/odm_georeferenced_model.ply
|
||||||
|
|
||||||
See [here](https://github.com/OpenDroneMap/OpenDroneMap/blob/3964f21377e27c261c305b30537f699853ac2004/README.md#installation) for more detailed installation instructions.
|
See below for more detailed installation instructions.
|
||||||
|
|
||||||
|
## Diving Deeper
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,10 @@ option(ODM_BUILD_CGAL "Force to build CGAL library" OFF)
|
||||||
|
|
||||||
SETUP_EXTERNAL_PROJECT(CGAL ${ODM_CGAL_Version} ${ODM_BUILD_CGAL})
|
SETUP_EXTERNAL_PROJECT(CGAL ${ODM_CGAL_Version} ${ODM_BUILD_CGAL})
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------------
|
||||||
|
# Hexer
|
||||||
|
#
|
||||||
|
SETUP_EXTERNAL_PROJECT(Hexer 1.4 ON)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------------
|
||||||
# Open Geometric Vision (OpenGV)
|
# Open Geometric Vision (OpenGV)
|
||||||
|
@ -114,6 +118,7 @@ set(custom_libs OpenGV
|
||||||
Ecto
|
Ecto
|
||||||
PDAL
|
PDAL
|
||||||
MvsTexturing
|
MvsTexturing
|
||||||
|
Lidar2dems
|
||||||
)
|
)
|
||||||
|
|
||||||
# Dependencies of the SLAM module
|
# Dependencies of the SLAM module
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
set(_proj_name hexer)
|
||||||
|
set(_SB_BINARY_DIR "${SB_BINARY_DIR}/${_proj_name}")
|
||||||
|
|
||||||
|
ExternalProject_Add(${_proj_name}
|
||||||
|
DEPENDS
|
||||||
|
PREFIX ${_SB_BINARY_DIR}
|
||||||
|
TMP_DIR ${_SB_BINARY_DIR}/tmp
|
||||||
|
STAMP_DIR ${_SB_BINARY_DIR}/stamp
|
||||||
|
#--Download step--------------
|
||||||
|
DOWNLOAD_DIR ${SB_DOWNLOAD_DIR}
|
||||||
|
URL https://github.com/hobu/hexer/archive/2898b96b1105991e151696391b9111610276258f.tar.gz
|
||||||
|
URL_MD5 e8f2788332ad212cf78efa81a82e95dd
|
||||||
|
#--Update/Patch step----------
|
||||||
|
UPDATE_COMMAND ""
|
||||||
|
#--Configure step-------------
|
||||||
|
SOURCE_DIR ${SB_SOURCE_DIR}/${_proj_name}
|
||||||
|
CMAKE_ARGS
|
||||||
|
-DCMAKE_INSTALL_PREFIX:PATH=${SB_INSTALL_DIR}
|
||||||
|
#--Build step-----------------
|
||||||
|
BINARY_DIR ${_SB_BINARY_DIR}
|
||||||
|
#--Install step---------------
|
||||||
|
INSTALL_DIR ${SB_INSTALL_DIR}
|
||||||
|
#--Output logging-------------
|
||||||
|
LOG_DOWNLOAD OFF
|
||||||
|
LOG_CONFIGURE OFF
|
||||||
|
LOG_BUILD OFF
|
||||||
|
)
|
|
@ -0,0 +1,24 @@
|
||||||
|
set(_proj_name lidar2dems)
|
||||||
|
set(_SB_BINARY_DIR "${SB_BINARY_DIR}/${_proj_name}")
|
||||||
|
|
||||||
|
ExternalProject_Add(${_proj_name}
|
||||||
|
PREFIX ${_SB_BINARY_DIR}
|
||||||
|
TMP_DIR ${_SB_BINARY_DIR}/tmp
|
||||||
|
STAMP_DIR ${_SB_BINARY_DIR}/stamp
|
||||||
|
#--Download step--------------
|
||||||
|
DOWNLOAD_DIR ${SB_DOWNLOAD_DIR}/${_proj_name}
|
||||||
|
URL https://github.com/OpenDroneMap/lidar2dems/archive/master.zip
|
||||||
|
#--Update/Patch step----------
|
||||||
|
UPDATE_COMMAND ""
|
||||||
|
#--Configure step-------------
|
||||||
|
SOURCE_DIR ${SB_SOURCE_DIR}/${_proj_name}
|
||||||
|
CONFIGURE_COMMAND ""
|
||||||
|
#--Build step-----------------
|
||||||
|
BUILD_COMMAND ""
|
||||||
|
#--Install step---------------
|
||||||
|
INSTALL_COMMAND "${SB_SOURCE_DIR}/${_proj_name}/install.sh" "${SB_INSTALL_DIR}"
|
||||||
|
#--Output logging-------------
|
||||||
|
LOG_DOWNLOAD OFF
|
||||||
|
LOG_CONFIGURE OFF
|
||||||
|
LOG_BUILD OFF
|
||||||
|
)
|
|
@ -2,13 +2,14 @@ set(_proj_name pdal)
|
||||||
set(_SB_BINARY_DIR "${SB_BINARY_DIR}/${_proj_name}")
|
set(_SB_BINARY_DIR "${SB_BINARY_DIR}/${_proj_name}")
|
||||||
|
|
||||||
ExternalProject_Add(${_proj_name}
|
ExternalProject_Add(${_proj_name}
|
||||||
|
DEPENDS hexer
|
||||||
PREFIX ${_SB_BINARY_DIR}
|
PREFIX ${_SB_BINARY_DIR}
|
||||||
TMP_DIR ${_SB_BINARY_DIR}/tmp
|
TMP_DIR ${_SB_BINARY_DIR}/tmp
|
||||||
STAMP_DIR ${_SB_BINARY_DIR}/stamp
|
STAMP_DIR ${_SB_BINARY_DIR}/stamp
|
||||||
#--Download step--------------
|
#--Download step--------------
|
||||||
DOWNLOAD_DIR ${SB_DOWNLOAD_DIR}
|
DOWNLOAD_DIR ${SB_DOWNLOAD_DIR}
|
||||||
URL https://github.com/PDAL/PDAL/archive/aea5bb0cacc64b91d626eff491fbdbb5668c06d7.tar.gz
|
URL https://github.com/PDAL/PDAL/archive/e881b581e3b91a928105d67db44c567f3b6d1afe.tar.gz
|
||||||
URL_MD5 726933f63f661e11e13775d6ce4e5ed0
|
URL_MD5 438acbb736ba01fbe8f9ca7cdbf113bf
|
||||||
#--Update/Patch step----------
|
#--Update/Patch step----------
|
||||||
UPDATE_COMMAND ""
|
UPDATE_COMMAND ""
|
||||||
#--Configure step-------------
|
#--Configure step-------------
|
||||||
|
@ -19,7 +20,7 @@ ExternalProject_Add(${_proj_name}
|
||||||
-BUILD_PLUGIN_PGPOINTCLOUD=ON
|
-BUILD_PLUGIN_PGPOINTCLOUD=ON
|
||||||
-DBUILD_PLUGIN_CPD=OFF
|
-DBUILD_PLUGIN_CPD=OFF
|
||||||
-DBUILD_PLUGIN_GREYHOUND=OFF
|
-DBUILD_PLUGIN_GREYHOUND=OFF
|
||||||
-DBUILD_PLUGIN_HEXBIN=OFF
|
-DBUILD_PLUGIN_HEXBIN=ON
|
||||||
-DBUILD_PLUGIN_ICEBRIDGE=OFF
|
-DBUILD_PLUGIN_ICEBRIDGE=OFF
|
||||||
-DBUILD_PLUGIN_MRSID=OFF
|
-DBUILD_PLUGIN_MRSID=OFF
|
||||||
-DBUILD_PLUGIN_NITF=OFF
|
-DBUILD_PLUGIN_NITF=OFF
|
||||||
|
|
|
@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
|
||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
|
reported by contacting the project team at `svm at clevelandmetroparks dot com`. All
|
||||||
complaints will be reviewed and investigated and will result in a response that
|
complaints will be reviewed and investigated and will result in a response that
|
||||||
is deemed necessary and appropriate to the circumstances. The project team is
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
|
14
configure.sh
14
configure.sh
|
@ -21,7 +21,8 @@ install() {
|
||||||
libgdal-dev \
|
libgdal-dev \
|
||||||
gdal-bin \
|
gdal-bin \
|
||||||
libgeotiff-dev \
|
libgeotiff-dev \
|
||||||
pkg-config
|
pkg-config \
|
||||||
|
libjsoncpp-dev
|
||||||
|
|
||||||
echo "Getting CMake 3.1 for MVS-Texturing"
|
echo "Getting CMake 3.1 for MVS-Texturing"
|
||||||
sudo apt-get install -y software-properties-common python-software-properties
|
sudo apt-get install -y software-properties-common python-software-properties
|
||||||
|
@ -72,7 +73,7 @@ install() {
|
||||||
appsettings
|
appsettings
|
||||||
|
|
||||||
echo "Installing CGAL dependencies"
|
echo "Installing CGAL dependencies"
|
||||||
sudo apt-get install libgmp-dev libmpfr-dev
|
sudo apt-get install -y -qq libgmp-dev libmpfr-dev
|
||||||
|
|
||||||
echo "Installing Ecto Dependencies"
|
echo "Installing Ecto Dependencies"
|
||||||
sudo pip install -U catkin-pkg
|
sudo pip install -U catkin-pkg
|
||||||
|
@ -86,6 +87,13 @@ install() {
|
||||||
jhead \
|
jhead \
|
||||||
liblas-bin
|
liblas-bin
|
||||||
|
|
||||||
|
echo "Installing lidar2dems Dependencies"
|
||||||
|
sudo apt-get install -y -qq swig2.0 \
|
||||||
|
python-wheel \
|
||||||
|
libboost-log-dev
|
||||||
|
|
||||||
|
sudo pip install -U https://github.com/OpenDroneMap/gippy/archive/v0.3.9.tar.gz
|
||||||
|
|
||||||
echo "Compiling SuperBuild"
|
echo "Compiling SuperBuild"
|
||||||
cd ${RUNPATH}/SuperBuild
|
cd ${RUNPATH}/SuperBuild
|
||||||
mkdir -p build && cd build
|
mkdir -p build && cd build
|
||||||
|
@ -134,4 +142,4 @@ else
|
||||||
echo "Invalid instructions." >&2
|
echo "Invalid instructions." >&2
|
||||||
usage
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Visible Vegetation Indexes
|
||||||
|
|
||||||
|
This script produces a Vegetation Index raster from a RGB orthophoto (odm_orthophoto.tif in your project)
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
* rasterio (pip install rasterio)
|
||||||
|
* numpy python package (included in ODM build)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
```
|
||||||
|
vegind.py <orthophoto.tif> index
|
||||||
|
|
||||||
|
positional arguments:
|
||||||
|
<orthophoto.tif> The RGB orthophoto. Must be a GeoTiff.
|
||||||
|
index Index identifier. Allowed values: ngrdi, tgi, vari
|
||||||
|
```
|
||||||
|
Output will be generated with index suffix in the same directory as input.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
`python vegind.py /path/to/odm_orthophoto.tif tgi`
|
||||||
|
|
||||||
|
Orthophoto photo of Koniaków grass field and forest in QGIS: 
|
||||||
|
The Triangular Greenness Index output in QGIS (with a spectral pseudocolor): 
|
||||||
|
Visible Atmospheric Resistant Index: 
|
||||||
|
Normalized green-red difference index: 
|
||||||
|
|
||||||
|
## Bibliography
|
||||||
|
|
||||||
|
1. Hunt, E. Raymond, et al. "A Visible Band Index for Remote Sensing Leaf Chlorophyll Content At the Canopy Scale." ITC journal 21(2013): 103-112. doi: 10.1016/j.jag.2012.07.020
|
||||||
|
(https://doi.org/10.1016/j.jag.2012.07.020)
|
|
@ -0,0 +1,95 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import rasterio, os, sys
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
class bcolors:
|
||||||
|
OKBLUE = '\033[94m'
|
||||||
|
OKGREEN = '\033[92m'
|
||||||
|
WARNING = '\033[93m'
|
||||||
|
FAIL = '\033[91m'
|
||||||
|
ENDC = '\033[0m'
|
||||||
|
BOLD = '\033[1m'
|
||||||
|
UNDERLINE = '\033[4m'
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = sys.argv[1]
|
||||||
|
typ = sys.argv[2]
|
||||||
|
(fileRoot, fileExt) = os.path.splitext(file)
|
||||||
|
outFileName = fileRoot + "_" + typ + fileExt
|
||||||
|
if typ not in ['vari', 'tgi', 'ngrdi']:
|
||||||
|
raise IndexError
|
||||||
|
except (TypeError, IndexError, NameError):
|
||||||
|
print bcolors.FAIL + 'Arguments messed up. Check arguments order and index name' + bcolors.ENDC
|
||||||
|
print 'Usage: ./vegind.py orto index'
|
||||||
|
print ' orto - filepath to RGB orthophoto'
|
||||||
|
print ' index - Vegetation Index'
|
||||||
|
print bcolors.OKGREEN + 'Available indexes: vari, ngrdi, tgi' + bcolors.ENDC
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
def calcNgrdi(red, green):
|
||||||
|
"""
|
||||||
|
Normalized green red difference index
|
||||||
|
Tucker,C.J.,1979.
|
||||||
|
Red and photographic infrared linear combinations for monitoring vegetation.
|
||||||
|
Remote Sensing of Environment 8, 127–150
|
||||||
|
:param red: red visible channel
|
||||||
|
:param green: green visible channel
|
||||||
|
:return: ngrdi index array
|
||||||
|
"""
|
||||||
|
mask = np.not_equal(np.add(red,green), 0.0)
|
||||||
|
return np.choose(mask, (-9999.0, np.true_divide(
|
||||||
|
np.subtract(green,red),
|
||||||
|
np.add(red,green))))
|
||||||
|
|
||||||
|
def calcVari(red,green,blue):
|
||||||
|
"""
|
||||||
|
Calculates Visible Atmospheric Resistant Index
|
||||||
|
Gitelson, A.A., Kaufman, Y.J., Stark, R., Rundquist, D., 2002.
|
||||||
|
Novel algorithms for remote estimation of vegetation fraction.
|
||||||
|
Remote Sensing of Environment 80, 76–87.
|
||||||
|
:param red: red visible channel
|
||||||
|
:param green: green visible channel
|
||||||
|
:param blue: blue visible channel
|
||||||
|
:return: vari index array, that will be saved to tiff
|
||||||
|
"""
|
||||||
|
mask = np.not_equal(np.subtract(np.add(green,red),blue), 0.0)
|
||||||
|
return np.choose(mask, (-9999.0, np.true_divide(np.subtract(green,red),np.subtract(np.add(green,red),blue))))
|
||||||
|
|
||||||
|
def calcTgi(red,green,blue):
|
||||||
|
"""
|
||||||
|
Calculates Triangular Greenness Index
|
||||||
|
Hunt, E. Raymond Jr.; Doraiswamy, Paul C.; McMurtrey, James E.; Daughtry, Craig S.T.; Perry, Eileen M.; and Akhmedov, Bakhyt,
|
||||||
|
A visible band index for remote sensing leaf chlorophyll content at the canopy scale (2013).
|
||||||
|
Publications from USDA-ARS / UNL Faculty. Paper 1156.
|
||||||
|
http://digitalcommons.unl.edu/usdaarsfacpub/1156
|
||||||
|
:param red: red channel
|
||||||
|
:param green: green channel
|
||||||
|
:param blue: blue channel
|
||||||
|
:return: tgi index array, that will be saved to tiff
|
||||||
|
"""
|
||||||
|
mask = np.not_equal(green-red+blue-255.0, 0.0)
|
||||||
|
return np.choose(mask, (-9999.0, np.subtract(green, np.multiply(0.39,red), np.multiply(0.61, blue))))
|
||||||
|
|
||||||
|
try:
|
||||||
|
with rasterio.Env():
|
||||||
|
ds = rasterio.open(file)
|
||||||
|
profile = ds.profile
|
||||||
|
profile.update(dtype=rasterio.float32, count=1, nodata=-9999)
|
||||||
|
red = np.float32(ds.read(1))
|
||||||
|
green = np.float32(ds.read(2))
|
||||||
|
blue = np.float32(ds.read(3))
|
||||||
|
np.seterr(divide='ignore', invalid='ignore')
|
||||||
|
if typ == 'ngrdi':
|
||||||
|
indeks = calcNgrdi(red,green)
|
||||||
|
elif typ == 'vari':
|
||||||
|
indeks = calcVari(red, green, blue)
|
||||||
|
elif typ == 'tgi':
|
||||||
|
indeks = calcTgi(red, green, blue)
|
||||||
|
|
||||||
|
with rasterio.open(outFileName, 'w', **profile) as dst:
|
||||||
|
dst.write(indeks.astype(rasterio.float32), 1)
|
||||||
|
except rasterio.errors.RasterioIOError:
|
||||||
|
print bcolors.FAIL + 'Orthophoto file not found or access denied' + bcolors.ENDC
|
||||||
|
sys.exit()
|
|
@ -14,12 +14,12 @@ RUN apt-get install --no-install-recommends -y git cmake python-pip build-essent
|
||||||
libgtk2.0-dev libavcodec-dev libavformat-dev libswscale-dev python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libflann-dev \
|
libgtk2.0-dev libavcodec-dev libavformat-dev libswscale-dev python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libflann-dev \
|
||||||
libproj-dev libxext-dev liblapack-dev libeigen3-dev libvtk5-dev python-networkx libgoogle-glog-dev libsuitesparse-dev libboost-filesystem-dev libboost-iostreams-dev \
|
libproj-dev libxext-dev liblapack-dev libeigen3-dev libvtk5-dev python-networkx libgoogle-glog-dev libsuitesparse-dev libboost-filesystem-dev libboost-iostreams-dev \
|
||||||
libboost-regex-dev libboost-python-dev libboost-date-time-dev libboost-thread-dev python-pyproj python-empy python-nose python-pyside python-pyexiv2 python-scipy \
|
libboost-regex-dev libboost-python-dev libboost-date-time-dev libboost-thread-dev python-pyproj python-empy python-nose python-pyside python-pyexiv2 python-scipy \
|
||||||
jhead liblas-bin python-matplotlib libatlas-base-dev libgmp-dev libmpfr-dev
|
jhead liblas-bin python-matplotlib libatlas-base-dev libgmp-dev libmpfr-dev swig2.0 python-wheel libboost-log-dev libjsoncpp-dev
|
||||||
|
|
||||||
RUN apt-get remove libdc1394-22-dev
|
RUN apt-get remove libdc1394-22-dev
|
||||||
RUN pip install --upgrade pip
|
RUN pip install --upgrade pip
|
||||||
RUN pip install setuptools
|
RUN pip install setuptools
|
||||||
RUN pip install -U PyYAML exifread gpxpy xmltodict catkin-pkg appsettings
|
RUN pip install -U PyYAML exifread gpxpy xmltodict catkin-pkg appsettings https://github.com/OpenDroneMap/gippy/archive/v0.3.9.tar.gz
|
||||||
|
|
||||||
ENV PYTHONPATH="$PYTHONPATH:/code/SuperBuild/install/lib/python2.7/dist-packages"
|
ENV PYTHONPATH="$PYTHONPATH:/code/SuperBuild/install/lib/python2.7/dist-packages"
|
||||||
ENV PYTHONPATH="$PYTHONPATH:/code/SuperBuild/src/opensfm"
|
ENV PYTHONPATH="$PYTHONPATH:/code/SuperBuild/src/opensfm"
|
||||||
|
|
|
@ -43,10 +43,15 @@ project_path: '/' #DO NOT CHANGE THIS OR DOCKER WILL NOT WORK. It should be '/'
|
||||||
#texturing_keep_unseen_faces: False
|
#texturing_keep_unseen_faces: False
|
||||||
#texturing_tone_mapping: 'none'
|
#texturing_tone_mapping: 'none'
|
||||||
#gcp: !!null # YAML tag for None
|
#gcp: !!null # YAML tag for None
|
||||||
#dem: False
|
#dtm: False # Use this tag to build a DTM (Digital Terrain Model
|
||||||
#dem_sample_radius: 1.0
|
#dsm: False # Use this tag to build a DSM (Digital Surface Model
|
||||||
#dem_resolution: 2
|
#dem-gapfill-steps: 4
|
||||||
#dem_radius: 0.5
|
#dem-resolution: 0.1
|
||||||
|
#dem-maxangle:20
|
||||||
|
#dem-maxsd: 2.5
|
||||||
|
#dem-approximate: False
|
||||||
|
#dem-decimation: 1
|
||||||
|
#dem-terrain-type: ComplexForest
|
||||||
#use_exif: False # Set to True if you have a GCP file (it auto-detects) and want to use EXIF
|
#use_exif: False # Set to True if you have a GCP file (it auto-detects) and want to use EXIF
|
||||||
#orthophoto_resolution: 20.0 # Pixels/meter
|
#orthophoto_resolution: 20.0 # Pixels/meter
|
||||||
#orthophoto_target_srs: !!null # Currently does nothing
|
#orthophoto_target_srs: !!null # Currently does nothing
|
||||||
|
|
102
opendm/config.py
102
opendm/config.py
|
@ -10,7 +10,7 @@ import sys
|
||||||
# parse arguments
|
# parse arguments
|
||||||
processopts = ['resize', 'opensfm', 'slam', 'cmvs', 'pmvs',
|
processopts = ['resize', 'opensfm', 'slam', 'cmvs', 'pmvs',
|
||||||
'odm_meshing', 'odm_25dmeshing', 'mvs_texturing', 'odm_georeferencing',
|
'odm_meshing', 'odm_25dmeshing', 'mvs_texturing', 'odm_georeferencing',
|
||||||
'odm_orthophoto']
|
'odm_dem', 'odm_orthophoto']
|
||||||
|
|
||||||
with open(io.join_paths(context.root_path, 'VERSION')) as version_file:
|
with open(io.join_paths(context.root_path, 'VERSION')) as version_file:
|
||||||
__version__ = version_file.read().strip()
|
__version__ = version_file.read().strip()
|
||||||
|
@ -111,21 +111,6 @@ def config():
|
||||||
'More features leads to better results but slower '
|
'More features leads to better results but slower '
|
||||||
'execution. Default: %(default)s'))
|
'execution. Default: %(default)s'))
|
||||||
|
|
||||||
parser.add_argument('--matcher-threshold',
|
|
||||||
metavar='<percent>',
|
|
||||||
default=2.0,
|
|
||||||
type=float,
|
|
||||||
help=('Ignore matched keypoints if the two images share '
|
|
||||||
'less than <float> percent of keypoints. Default:'
|
|
||||||
' %(default)s'))
|
|
||||||
|
|
||||||
parser.add_argument('--matcher-ratio',
|
|
||||||
metavar='<float>',
|
|
||||||
default=0.6,
|
|
||||||
type=float,
|
|
||||||
help=('Ratio of the distance to the next best matched '
|
|
||||||
'keypoint. Default: %(default)s'))
|
|
||||||
|
|
||||||
parser.add_argument('--matcher-neighbors',
|
parser.add_argument('--matcher-neighbors',
|
||||||
type=int,
|
type=int,
|
||||||
metavar='<integer>',
|
metavar='<integer>',
|
||||||
|
@ -342,34 +327,87 @@ def config():
|
||||||
help=('Use this tag if you have a gcp_list.txt but '
|
help=('Use this tag if you have a gcp_list.txt but '
|
||||||
'want to use the exif geotags instead'))
|
'want to use the exif geotags instead'))
|
||||||
|
|
||||||
parser.add_argument('--dem',
|
parser.add_argument('--dtm',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
help='Use this tag to build a DEM using a progressive '
|
help='Use this tag to build a DTM (Digital Terrain Model, ground only) using a progressive '
|
||||||
'morphological filter in PDAL.')
|
'morphological filter. Check the --dem* parameters for fine tuning.')
|
||||||
|
|
||||||
|
parser.add_argument('--dsm',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Use this tag to build a DSM (Digital Surface Model, ground + objects) using a progressive '
|
||||||
|
'morphological filter. Check the --dem* parameters for fine tuning.')
|
||||||
|
|
||||||
parser.add_argument('--dem-sample-radius',
|
parser.add_argument('--dem-gapfill-steps',
|
||||||
metavar='<float>',
|
metavar='<positive integer>',
|
||||||
default=1.0,
|
default=4,
|
||||||
type=float,
|
type=int,
|
||||||
help='Minimum distance between samples for DEM '
|
help='Number of steps used to fill areas with gaps. Set to 0 to disable gap filling. '
|
||||||
'generation.\nDefault=%(default)s')
|
'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. '
|
||||||
|
'\nDefault=%(default)s')
|
||||||
|
|
||||||
parser.add_argument('--dem-resolution',
|
parser.add_argument('--dem-resolution',
|
||||||
metavar='<float>',
|
metavar='<float>',
|
||||||
type=float,
|
type=float,
|
||||||
default=2,
|
default=0.1,
|
||||||
help='Length of raster cell edges in X/Y units.'
|
help='Length of raster cell edges in meters.'
|
||||||
'\nDefault: %(default)s')
|
'\nDefault: %(default)s')
|
||||||
|
|
||||||
parser.add_argument('--dem-radius',
|
parser.add_argument('--dem-maxangle',
|
||||||
metavar='<float>',
|
metavar='<positive float>',
|
||||||
type=float,
|
type=float,
|
||||||
default=0.5,
|
default=20,
|
||||||
help='Radius about cell center bounding points to '
|
help='Points that are more than maxangle degrees off-nadir are discarded. '
|
||||||
'use to calculate a cell value.\nDefault: '
|
'\nDefault: '
|
||||||
'%(default)s')
|
'%(default)s')
|
||||||
|
|
||||||
|
parser.add_argument('--dem-maxsd',
|
||||||
|
metavar='<positive float>',
|
||||||
|
type=float,
|
||||||
|
default=2.5,
|
||||||
|
help='Points that deviate more than maxsd standard deviations from the local mean '
|
||||||
|
'are discarded. \nDefault: '
|
||||||
|
'%(default)s')
|
||||||
|
|
||||||
|
parser.add_argument('--dem-initial-distance',
|
||||||
|
metavar='<positive float>',
|
||||||
|
type=float,
|
||||||
|
default=0.15,
|
||||||
|
help='Used to classify ground vs non-ground points. Set this value to account for Z noise in meters. '
|
||||||
|
'If you have an uncertainty of around 15 cm, set this value large enough to not exclude these points. '
|
||||||
|
'Too small of a value will exclude valid ground points, while too large of a value will misclassify non-ground points for ground ones. '
|
||||||
|
'\nDefault: '
|
||||||
|
'%(default)s')
|
||||||
|
|
||||||
|
parser.add_argument('--dem-approximate',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Use this tag use the approximate progressive '
|
||||||
|
'morphological filter, which computes DEMs faster '
|
||||||
|
'but is not as accurate.')
|
||||||
|
|
||||||
|
parser.add_argument('--dem-decimation',
|
||||||
|
metavar='<positive integer>',
|
||||||
|
default=1,
|
||||||
|
type=int,
|
||||||
|
help='Decimate the points before generating the DEM. 1 is no decimation (full quality). '
|
||||||
|
'100 decimates ~99%% of the points. Useful for speeding up '
|
||||||
|
'generation.\nDefault=%(default)s')
|
||||||
|
|
||||||
|
parser.add_argument('--dem-terrain-type',
|
||||||
|
metavar='<string>',
|
||||||
|
choices=['FlatNonForest', 'FlatForest', 'ComplexNonForest', 'ComplexForest'],
|
||||||
|
default='ComplexForest',
|
||||||
|
help='One of: %(choices)s. Specifies the type of terrain. This mainly helps reduce processing time. '
|
||||||
|
'\nFlatNonForest: Relatively flat region with little to no vegetation'
|
||||||
|
'\nFlatForest: Relatively flat region that is forested'
|
||||||
|
'\nComplexNonForest: Varied terrain with little to no vegetation'
|
||||||
|
'\nComplexForest: Varied terrain that is forested'
|
||||||
|
'\nDefault=%(default)s')
|
||||||
|
|
||||||
parser.add_argument('--orthophoto-resolution',
|
parser.add_argument('--orthophoto-resolution',
|
||||||
metavar='<float > 0.0>',
|
metavar='<float > 0.0>',
|
||||||
default=20.0,
|
default=20.0,
|
||||||
|
|
|
@ -8,6 +8,7 @@ scripts_path = os.path.abspath(os.path.dirname(__file__))
|
||||||
root_path, _ = os.path.split(scripts_path)
|
root_path, _ = os.path.split(scripts_path)
|
||||||
|
|
||||||
superbuild_path = os.path.join(root_path, 'SuperBuild')
|
superbuild_path = os.path.join(root_path, 'SuperBuild')
|
||||||
|
superbuild_bin_path = os.path.join(superbuild_path, 'install', 'bin')
|
||||||
tests_path = os.path.join(root_path, 'tests')
|
tests_path = os.path.join(root_path, 'tests')
|
||||||
tests_data_path = os.path.join(root_path, 'tests/test_data')
|
tests_data_path = os.path.join(root_path, 'tests/test_data')
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,16 @@ def get_ccd_widths():
|
||||||
return dict(zip(map(string.lower, sensor_data.keys()), sensor_data.values()))
|
return dict(zip(map(string.lower, sensor_data.keys()), sensor_data.values()))
|
||||||
|
|
||||||
|
|
||||||
def run(cmd):
|
def run(cmd, env_paths=[]):
|
||||||
"""Run a system command"""
|
"""Run a system command"""
|
||||||
log.ODM_DEBUG('running %s' % cmd)
|
log.ODM_DEBUG('running %s' % cmd)
|
||||||
retcode = subprocess.call(cmd, shell=True)
|
|
||||||
|
env = None
|
||||||
|
if len(env_paths) > 0:
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["PATH"] = env["PATH"] + ":" + ":".join(env_paths)
|
||||||
|
|
||||||
|
retcode = subprocess.call(cmd, shell=True, env=env)
|
||||||
|
|
||||||
if retcode < 0:
|
if retcode < 0:
|
||||||
raise Exception("Child was terminated by signal {}".format(-retcode))
|
raise Exception("Child was terminated by signal {}".format(-retcode))
|
||||||
|
|
|
@ -13,8 +13,9 @@ tasks_dict = {'1': 'opensfm',
|
||||||
'4': 'odm_meshing',
|
'4': 'odm_meshing',
|
||||||
'5': 'mvs_texturing',
|
'5': 'mvs_texturing',
|
||||||
'6': 'odm_georeferencing',
|
'6': 'odm_georeferencing',
|
||||||
'7': 'odm_orthophoto',
|
'7': 'odm_dem',
|
||||||
'8': 'zip_results'}
|
'8': 'odm_orthophoto',
|
||||||
|
'9': 'zip_results'}
|
||||||
|
|
||||||
|
|
||||||
class ODMTaskManager(object):
|
class ODMTaskManager(object):
|
||||||
|
|
|
@ -221,56 +221,6 @@ class ODM_GeoRef(object):
|
||||||
system.run('{bin}/pdal pipeline -i {json} --readers.ply.filename={f_in} '
|
system.run('{bin}/pdal pipeline -i {json} --readers.ply.filename={f_in} '
|
||||||
'--writers.las.filename={f_out}'.format(**kwargs))
|
'--writers.las.filename={f_out}'.format(**kwargs))
|
||||||
|
|
||||||
def convert_to_dem(self, _file, _file_out, pdalJSON, sample_radius, gdal_res, gdal_radius):
|
|
||||||
# Check if exists f_in
|
|
||||||
if not io.file_exists(_file):
|
|
||||||
log.ODM_ERROR('LAS file does not exist')
|
|
||||||
return False
|
|
||||||
|
|
||||||
kwargs = {
|
|
||||||
'bin': context.pdal_path,
|
|
||||||
'f_in': _file,
|
|
||||||
'sample_radius': sample_radius,
|
|
||||||
'gdal_res': gdal_res,
|
|
||||||
'gdal_radius': gdal_radius,
|
|
||||||
'f_out': _file_out,
|
|
||||||
'json': pdalJSON
|
|
||||||
}
|
|
||||||
|
|
||||||
pipelineJSON = '{{' \
|
|
||||||
' "pipeline":[' \
|
|
||||||
' "input.las",' \
|
|
||||||
' {{' \
|
|
||||||
' "type":"filters.sample",' \
|
|
||||||
' "radius":"{sample_radius}"' \
|
|
||||||
' }},' \
|
|
||||||
' {{' \
|
|
||||||
' "type":"filters.pmf"' \
|
|
||||||
' }},' \
|
|
||||||
' {{' \
|
|
||||||
' "type":"filters.range",' \
|
|
||||||
' "limits":"Classification[2:2]"' \
|
|
||||||
' }},' \
|
|
||||||
' {{' \
|
|
||||||
' "resolution": {gdal_res},' \
|
|
||||||
' "radius": {gdal_radius},' \
|
|
||||||
' "output_type":"idw",' \
|
|
||||||
' "filename":"outputfile.tif"' \
|
|
||||||
' }}' \
|
|
||||||
' ]' \
|
|
||||||
'}}'.format(**kwargs)
|
|
||||||
|
|
||||||
with open(pdalJSON, 'w') as f:
|
|
||||||
f.write(pipelineJSON)
|
|
||||||
|
|
||||||
system.run('{bin}/pdal pipeline {json} --readers.las.filename={f_in} '
|
|
||||||
'--writers.gdal.filename={f_out}'.format(**kwargs))
|
|
||||||
|
|
||||||
if io.file_exists(kwargs['f_out']):
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def utm_to_latlon(self, _file, _photo, idx):
|
def utm_to_latlon(self, _file, _photo, idx):
|
||||||
|
|
||||||
gcp = self.gcps[idx]
|
gcp = self.gcps[idx]
|
||||||
|
@ -478,8 +428,6 @@ class ODM_Tree(object):
|
||||||
self.odm_georeferencing, 'odm_georeferenced_model.las')
|
self.odm_georeferencing, 'odm_georeferenced_model.las')
|
||||||
self.odm_georeferencing_dem = io.join_paths(
|
self.odm_georeferencing_dem = io.join_paths(
|
||||||
self.odm_georeferencing, 'odm_georeferencing_model_dem.tif')
|
self.odm_georeferencing, 'odm_georeferencing_model_dem.tif')
|
||||||
self.odm_georeferencing_dem_json = io.join_paths(
|
|
||||||
self.odm_georeferencing, 'dem.json')
|
|
||||||
|
|
||||||
# odm_orthophoto
|
# odm_orthophoto
|
||||||
self.odm_orthophoto_file = io.join_paths(self.odm_orthophoto, 'odm_orthophoto.png')
|
self.odm_orthophoto_file = io.join_paths(self.odm_orthophoto, 'odm_orthophoto.png')
|
||||||
|
@ -488,3 +436,6 @@ class ODM_Tree(object):
|
||||||
self.odm_orthophoto_log = io.join_paths(self.odm_orthophoto, 'odm_orthophoto_log.txt')
|
self.odm_orthophoto_log = io.join_paths(self.odm_orthophoto, 'odm_orthophoto_log.txt')
|
||||||
self.odm_orthophoto_tif_log = io.join_paths(self.odm_orthophoto, 'gdal_translate_log.txt')
|
self.odm_orthophoto_tif_log = io.join_paths(self.odm_orthophoto, 'gdal_translate_log.txt')
|
||||||
self.odm_orthophoto_gdaladdo_log = io.join_paths(self.odm_orthophoto, 'gdaladdo_log.txt')
|
self.odm_orthophoto_gdaladdo_log = io.join_paths(self.odm_orthophoto, 'gdaladdo_log.txt')
|
||||||
|
|
||||||
|
def path(self, *args):
|
||||||
|
return io.join_paths(self.root_path, *args)
|
|
@ -15,6 +15,7 @@ from odm_meshing import ODMeshingCell
|
||||||
from mvstex import ODMMvsTexCell
|
from mvstex import ODMMvsTexCell
|
||||||
from odm_georeferencing import ODMGeoreferencingCell
|
from odm_georeferencing import ODMGeoreferencingCell
|
||||||
from odm_orthophoto import ODMOrthoPhotoCell
|
from odm_orthophoto import ODMOrthoPhotoCell
|
||||||
|
from odm_dem import ODMDEMCell
|
||||||
|
|
||||||
|
|
||||||
class ODMApp(ecto.BlackBox):
|
class ODMApp(ecto.BlackBox):
|
||||||
|
@ -71,11 +72,8 @@ class ODMApp(ecto.BlackBox):
|
||||||
'georeferencing': ODMGeoreferencingCell(img_size=p.args.resize_to,
|
'georeferencing': ODMGeoreferencingCell(img_size=p.args.resize_to,
|
||||||
gcp_file=p.args.gcp,
|
gcp_file=p.args.gcp,
|
||||||
use_exif=p.args.use_exif,
|
use_exif=p.args.use_exif,
|
||||||
dem=p.args.dem,
|
|
||||||
sample_radius=p.args.dem_sample_radius,
|
|
||||||
gdal_res=p.args.dem_resolution,
|
|
||||||
gdal_radius=p.args.dem_radius,
|
|
||||||
verbose=p.args.verbose),
|
verbose=p.args.verbose),
|
||||||
|
'dem': ODMDEMCell(verbose=p.args.verbose),
|
||||||
'orthophoto': ODMOrthoPhotoCell(resolution=p.args.orthophoto_resolution,
|
'orthophoto': ODMOrthoPhotoCell(resolution=p.args.orthophoto_resolution,
|
||||||
t_srs=p.args.orthophoto_target_srs,
|
t_srs=p.args.orthophoto_target_srs,
|
||||||
no_tiled=p.args.orthophoto_no_tiled,
|
no_tiled=p.args.orthophoto_no_tiled,
|
||||||
|
@ -148,7 +146,12 @@ class ODMApp(ecto.BlackBox):
|
||||||
self.args[:] >> self.georeferencing['args'],
|
self.args[:] >> self.georeferencing['args'],
|
||||||
self.dataset['photos'] >> self.georeferencing['photos'],
|
self.dataset['photos'] >> self.georeferencing['photos'],
|
||||||
self.texturing['reconstruction'] >> self.georeferencing['reconstruction']]
|
self.texturing['reconstruction'] >> self.georeferencing['reconstruction']]
|
||||||
|
|
||||||
|
# create odm dem
|
||||||
|
connections += [self.tree[:] >> self.dem['tree'],
|
||||||
|
self.args[:] >> self.dem['args'],
|
||||||
|
self.georeferencing['reconstruction'] >> self.dem['reconstruction']]
|
||||||
|
|
||||||
# create odm orthophoto
|
# create odm orthophoto
|
||||||
connections += [self.tree[:] >> self.orthophoto['tree'],
|
connections += [self.tree[:] >> self.orthophoto['tree'],
|
||||||
self.args[:] >> self.orthophoto['args'],
|
self.args[:] >> self.orthophoto['args'],
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
import ecto, os, json
|
||||||
|
from shutil import copyfile
|
||||||
|
|
||||||
|
from opendm import io
|
||||||
|
from opendm import log
|
||||||
|
from opendm import system
|
||||||
|
from opendm import context
|
||||||
|
from opendm import types
|
||||||
|
|
||||||
|
|
||||||
|
class ODMDEMCell(ecto.Cell):
|
||||||
|
def declare_params(self, params):
|
||||||
|
params.declare("verbose", 'print additional messages to console', False)
|
||||||
|
|
||||||
|
def declare_io(self, params, inputs, outputs):
|
||||||
|
inputs.declare("tree", "Struct with paths", [])
|
||||||
|
inputs.declare("args", "The application arguments.", {})
|
||||||
|
inputs.declare("reconstruction", "list of ODMReconstructions", [])
|
||||||
|
|
||||||
|
def process(self, inputs, outputs):
|
||||||
|
# Benchmarking
|
||||||
|
start_time = system.now_raw()
|
||||||
|
|
||||||
|
log.ODM_INFO('Running ODM DEM Cell')
|
||||||
|
|
||||||
|
# get inputs
|
||||||
|
args = self.inputs.args
|
||||||
|
tree = self.inputs.tree
|
||||||
|
las_model_found = io.file_exists(tree.odm_georeferencing_model_las)
|
||||||
|
env_paths = [context.superbuild_bin_path]
|
||||||
|
|
||||||
|
# Just to make sure
|
||||||
|
l2d_module_installed = True
|
||||||
|
try:
|
||||||
|
system.run('l2d_classify --help > /dev/null', env_paths)
|
||||||
|
except:
|
||||||
|
log.ODM_WARNING('lidar2dems is not installed properly')
|
||||||
|
l2d_module_installed = False
|
||||||
|
|
||||||
|
log.ODM_INFO('Create DSM: ' + str(args.dsm))
|
||||||
|
log.ODM_INFO('Create DTM: ' + str(args.dtm))
|
||||||
|
log.ODM_INFO('DEM input file {0} found: {1}'.format(tree.odm_georeferencing_model_las, str(las_model_found)))
|
||||||
|
|
||||||
|
# Do we need to process anything here?
|
||||||
|
if (args.dsm or args.dtm) and las_model_found and l2d_module_installed:
|
||||||
|
|
||||||
|
# define paths and create working directories
|
||||||
|
odm_dem_root = tree.path('odm_dem')
|
||||||
|
system.mkdir_p(odm_dem_root)
|
||||||
|
|
||||||
|
dsm_output_filename = os.path.join(odm_dem_root, 'dsm.tif')
|
||||||
|
dtm_output_filename = os.path.join(odm_dem_root, 'dtm.tif')
|
||||||
|
|
||||||
|
# check if we rerun cell or not
|
||||||
|
rerun_cell = (args.rerun is not None and
|
||||||
|
args.rerun == 'odm_dem') or \
|
||||||
|
(args.rerun_all) or \
|
||||||
|
(args.rerun_from is not None and
|
||||||
|
'odm_dem' in args.rerun_from)
|
||||||
|
|
||||||
|
if (args.dtm and not io.file_exists(dtm_output_filename)) or \
|
||||||
|
(args.dsm and not io.file_exists(dsm_output_filename)) or \
|
||||||
|
rerun_cell:
|
||||||
|
|
||||||
|
# Extract boundaries and srs of point cloud
|
||||||
|
summary_file_path = os.path.join(odm_dem_root, 'odm_georeferenced_model.summary.json')
|
||||||
|
boundary_file_path = os.path.join(odm_dem_root, 'odm_georeferenced_model.boundary.json')
|
||||||
|
|
||||||
|
system.run('pdal info --summary {0} > {1}'.format(tree.odm_georeferencing_model_las, summary_file_path), env_paths)
|
||||||
|
system.run('pdal info --boundary {0} > {1}'.format(tree.odm_georeferencing_model_las, boundary_file_path), env_paths)
|
||||||
|
|
||||||
|
pc_proj4 = ""
|
||||||
|
pc_geojson_bounds_feature = None
|
||||||
|
|
||||||
|
with open(summary_file_path, 'r') as f:
|
||||||
|
json_f = json.loads(f.read())
|
||||||
|
pc_proj4 = json_f['summary']['srs']['proj4']
|
||||||
|
|
||||||
|
with open(boundary_file_path, 'r') as f:
|
||||||
|
json_f = json.loads(f.read())
|
||||||
|
pc_geojson_boundary_feature = json_f['boundary']['boundary_json']
|
||||||
|
|
||||||
|
# Write bounds to GeoJSON
|
||||||
|
bounds_geojson_path = os.path.join(odm_dem_root, 'odm_georeferenced_model.bounds.geojson')
|
||||||
|
with open(bounds_geojson_path, "w") as f:
|
||||||
|
f.write(json.dumps({
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": pc_geojson_boundary_feature
|
||||||
|
}]
|
||||||
|
}))
|
||||||
|
|
||||||
|
bounds_shapefile_path = os.path.join(odm_dem_root, 'bounds.shp')
|
||||||
|
|
||||||
|
# Convert bounds to Shapefile
|
||||||
|
kwargs = {
|
||||||
|
'input': bounds_geojson_path,
|
||||||
|
'output': bounds_shapefile_path,
|
||||||
|
'proj4': pc_proj4
|
||||||
|
}
|
||||||
|
system.run('ogr2ogr -overwrite -a_srs "{proj4}" {output} {input}'.format(**kwargs))
|
||||||
|
|
||||||
|
# Process with lidar2dems
|
||||||
|
terrain_params_map = {
|
||||||
|
'flatnonforest': (1, 3),
|
||||||
|
'flatforest': (1, 2),
|
||||||
|
'complexnonforest': (5, 2),
|
||||||
|
'complexforest': (10, 2)
|
||||||
|
}
|
||||||
|
terrain_params = terrain_params_map[args.dem_terrain_type.lower()]
|
||||||
|
|
||||||
|
kwargs = {
|
||||||
|
'verbose': '-v' if self.params.verbose else '',
|
||||||
|
'slope': terrain_params[0],
|
||||||
|
'cellsize': terrain_params[1],
|
||||||
|
'outdir': odm_dem_root,
|
||||||
|
'site': bounds_shapefile_path
|
||||||
|
}
|
||||||
|
|
||||||
|
l2d_params = '--slope {slope} --cellsize {cellsize} ' \
|
||||||
|
'{verbose} ' \
|
||||||
|
'-o -s {site} ' \
|
||||||
|
'--outdir {outdir}'.format(**kwargs)
|
||||||
|
|
||||||
|
approximate = '--approximate' if args.dem_approximate else ''
|
||||||
|
|
||||||
|
# Classify only if we need a DTM
|
||||||
|
run_classification = args.dtm
|
||||||
|
|
||||||
|
if run_classification:
|
||||||
|
system.run('l2d_classify {0} --decimation {1} '
|
||||||
|
'{2} --initialDistance {3} {4}'.format(
|
||||||
|
l2d_params, args.dem_decimation, approximate,
|
||||||
|
args.dem_initial_distance, tree.odm_georeferencing), env_paths)
|
||||||
|
else:
|
||||||
|
log.ODM_INFO("Will skip classification, only DSM is needed")
|
||||||
|
copyfile(tree.odm_georeferencing_model_las, os.path.join(odm_dem_root, 'bounds-0_l2d_s{slope}c{cellsize}.las'.format(**kwargs)))
|
||||||
|
|
||||||
|
products = []
|
||||||
|
if args.dsm: products.append('dsm')
|
||||||
|
if args.dtm: products.append('dtm')
|
||||||
|
|
||||||
|
radius_steps = [args.dem_resolution]
|
||||||
|
for _ in range(args.dem_gapfill_steps - 1):
|
||||||
|
radius_steps.append(radius_steps[-1] * 3) # 3 is arbitrary, maybe there's a better value?
|
||||||
|
|
||||||
|
for product in products:
|
||||||
|
demargs = {
|
||||||
|
'product': product,
|
||||||
|
'indir': odm_dem_root,
|
||||||
|
'l2d_params': l2d_params,
|
||||||
|
'maxsd': args.dem_maxsd,
|
||||||
|
'maxangle': args.dem_maxangle,
|
||||||
|
'resolution': args.dem_resolution,
|
||||||
|
'radius_steps': ' '.join(map(str, radius_steps)),
|
||||||
|
'gapfill': '--gapfill' if args.dem_gapfill_steps > 0 else '',
|
||||||
|
|
||||||
|
# If we didn't run a classification, we should pass the decimate parameter here
|
||||||
|
'decimation': '--decimation {0}'.format(args.dem_decimation) if not run_classification else ''
|
||||||
|
}
|
||||||
|
|
||||||
|
system.run('l2d_dems {product} {indir} {l2d_params} '
|
||||||
|
'--maxsd {maxsd} --maxangle {maxangle} '
|
||||||
|
'--resolution {resolution} --radius {radius_steps} '
|
||||||
|
'{decimation} '
|
||||||
|
'{gapfill} '.format(**demargs), env_paths)
|
||||||
|
|
||||||
|
# Rename final output
|
||||||
|
if product == 'dsm':
|
||||||
|
os.rename(os.path.join(odm_dem_root, 'bounds-0_dsm.idw.tif'), dsm_output_filename)
|
||||||
|
elif product == 'dtm':
|
||||||
|
os.rename(os.path.join(odm_dem_root, 'bounds-0_dtm.idw.tif'), dtm_output_filename)
|
||||||
|
|
||||||
|
else:
|
||||||
|
log.ODM_WARNING('Found existing outputs in: %s' % odm_dem_root)
|
||||||
|
else:
|
||||||
|
log.ODM_WARNING('DEM will not be generated')
|
||||||
|
|
||||||
|
if args.time:
|
||||||
|
system.benchmark(start_time, tree.benchmarking, 'Dem')
|
||||||
|
|
||||||
|
log.ODM_INFO('Running ODM DEM Cell - Finished')
|
||||||
|
return ecto.OK if args.end_with != 'odm_dem' else ecto.QUIT
|
|
@ -17,10 +17,6 @@ class ODMGeoreferencingCell(ecto.Cell):
|
||||||
'northing height pixelrow pixelcol imagename', 'gcp_list.txt')
|
'northing height pixelrow pixelcol imagename', 'gcp_list.txt')
|
||||||
params.declare("img_size", 'image size used in calibration', 2400)
|
params.declare("img_size", 'image size used in calibration', 2400)
|
||||||
params.declare("use_exif", 'use exif', False)
|
params.declare("use_exif", 'use exif', False)
|
||||||
params.declare("dem", 'Generate a dem', False)
|
|
||||||
params.declare("sample_radius", "Minimum distance between samples for DEM gen", 3)
|
|
||||||
params.declare("gdal_res", "Length of raster cell edges in X/Y units ", 2)
|
|
||||||
params.declare("gdal_radius", "Radius about cell center bounding points to use to calculate a cell value", 0.5)
|
|
||||||
params.declare("verbose", 'print additional messages to console', False)
|
params.declare("verbose", 'print additional messages to console', False)
|
||||||
|
|
||||||
def declare_io(self, params, inputs, outputs):
|
def declare_io(self, params, inputs, outputs):
|
||||||
|
@ -175,19 +171,6 @@ class ODMGeoreferencingCell(ecto.Cell):
|
||||||
tree.odm_georeferencing_model_las,
|
tree.odm_georeferencing_model_las,
|
||||||
tree.odm_georeferencing_las_json)
|
tree.odm_georeferencing_las_json)
|
||||||
|
|
||||||
# If --dem, create a DEM
|
|
||||||
if args.dem:
|
|
||||||
demcreated = geo_ref.convert_to_dem(tree.odm_georeferencing_model_las,
|
|
||||||
tree.odm_georeferencing_dem,
|
|
||||||
tree.odm_georeferencing_dem_json,
|
|
||||||
self.params.sample_radius,
|
|
||||||
self.params.gdal_res,
|
|
||||||
self.params.gdal_radius)
|
|
||||||
if not demcreated:
|
|
||||||
log.ODM_WARNING('Something went wrong. Check the logs in odm_georeferencing.')
|
|
||||||
else:
|
|
||||||
log.ODM_INFO('DEM created at {0}'.format(tree.odm_georeferencing_dem))
|
|
||||||
|
|
||||||
# XYZ point cloud output
|
# XYZ point cloud output
|
||||||
log.ODM_INFO("Creating geo-referenced CSV file (XYZ format)")
|
log.ODM_INFO("Creating geo-referenced CSV file (XYZ format)")
|
||||||
with open(tree.odm_georeferencing_xyz_file, "wb") as csvfile:
|
with open(tree.odm_georeferencing_xyz_file, "wb") as csvfile:
|
||||||
|
|
|
@ -44,10 +44,15 @@ project_path: '' # Example: '/home/user/ODMProjects
|
||||||
#texturing_tone_mapping: 'none'
|
#texturing_tone_mapping: 'none'
|
||||||
#gcp: !!null # YAML tag for None
|
#gcp: !!null # YAML tag for None
|
||||||
#use_exif: False # Set to True if you have a GCP file (it auto-detects) and want to use EXIF
|
#use_exif: False # Set to True if you have a GCP file (it auto-detects) and want to use EXIF
|
||||||
#dem: False
|
#dtm: False # Use this tag to build a DTM (Digital Terrain Model
|
||||||
#dem_sample_radius: 1.0
|
#dsm: False # Use this tag to build a DSM (Digital Surface Model
|
||||||
#dem_resolution: 2
|
#dem-gapfill-steps: 4
|
||||||
#dem_radius: 0.5
|
#dem-resolution: 0.1
|
||||||
|
#dem-maxangle:20
|
||||||
|
#dem-maxsd: 2.5
|
||||||
|
#dem-approximate: False
|
||||||
|
#dem-decimation: 1
|
||||||
|
#dem-terrain-type: ComplexForest
|
||||||
#orthophoto_resolution: 20.0 # Pixels/meter
|
#orthophoto_resolution: 20.0 # Pixels/meter
|
||||||
#orthophoto_target_srs: !!null # Currently does nothing
|
#orthophoto_target_srs: !!null # Currently does nothing
|
||||||
#orthophoto_no_tiled: False
|
#orthophoto_no_tiled: False
|
||||||
|
|
Ładowanie…
Reference in New Issue