Merge branch 'add_smvs' of ssh://git.masseranolabs.com:/home/odm/OpenDroneMap into add-poissonrecon

Former-commit-id: e99df871f7
pull/1161/head
Dakota Benjamin 2018-07-05 15:39:08 -04:00
commit 200b9ea436
24 zmienionych plików z 250 dodań i 512 usunięć

Wyświetl plik

@ -6,12 +6,9 @@ SuperBuild/install
SuperBuild/src
build
opensfm
pmvs
odm_orthophoto
odm_texturing
odm_meshing
odm_georeferencing
images_resize
.git

Wyświetl plik

@ -105,7 +105,7 @@ or
python run.py --rerun-from odm_meshing project-name
The options for rerunning are: 'resize', 'opensfm', 'slam', 'cmvs', 'pmvs', 'odm_meshing', 'mvs_texturing', 'odm_georeferencing', 'odm_orthophoto'
The options for rerunning are: 'resize', 'opensfm', 'slam', 'smvs', 'odm_meshing', 'mvs_texturing', 'odm_georeferencing', 'odm_orthophoto'
### View Results
@ -151,7 +151,7 @@ You can also view the orthophoto GeoTIFF in [QGIS](http://www.qgis.org/) or othe
## Build and Run Using Docker
(Instructions below apply to Ubuntu 14.04, but the Docker image workflow
(Instructions below apply to Ubuntu 14.04, but the Docker image workflow
has equivalent procedures for Mac OS X and Windows. See [docs.docker.com](https://docs.docker.com/))
OpenDroneMap is Dockerized, meaning you can use containerization to build and run it without tampering with the configuration of libraries and packages already
@ -177,7 +177,7 @@ If you want to build your own Docker image from sources, type:
Using this method, the containerized ODM will process the images in the OpenDroneMap/images directory and output results
to the OpenDroneMap/odm_orthophoto and OpenDroneMap/odm_texturing directories as described in the [Viewing Results](https://github.com/OpenDroneMap/OpenDroneMap/wiki/Output-and-Results) section.
If you want to view other results outside the Docker image simply add which directories you're interested in to the run command in the same pattern
established above. For example, if you're interested in the dense cloud results generated by PMVS and in the orthophoto,
established above. For example, if you're interested in the dense cloud results generated by OpenSfM and in the orthophoto,
simply use the following `docker run` command after building the image:
docker run -it --rm \
@ -195,7 +195,7 @@ If you want to get all intermediate outputs, run the following command:
-v "$(pwd)/odm_orthophoto:/code/odm_orthophoto" \
-v "$(pwd)/odm_texturing:/code/odm_texturing" \
-v "$(pwd)/opensfm:/code/opensfm" \
-v "$(pwd)/pmvs:/code/pmvs" \
-v "$(pwd)/smvs:/code/smvs" \
opendronemap/opendronemap
To pass in custom parameters to the run.py script, simply pass it as arguments to the `docker run` command. For example:
@ -222,7 +222,7 @@ When building your own Docker image, if image size is of importance to you, you
This will clean up intermediate steps in the Docker build process, resulting in a significantly smaller image (about half the size).
Experimental flags need to be enabled in Docker to use the ```--squash``` flag. To enable this, insert the following into the file ```/etc/docker/daemon.json```:
{
"experimental": true
}
@ -244,7 +244,7 @@ Coming soon...
## Documentation:
For documentation, everything is being moved to [http://docs.opendronemap.org/](http://docs.opendronemap.org/) but you can also take a look at our [wiki](https://github.com/OpenDroneMap/OpenDroneMap/wiki). Check those places first if you are having problems. There's also help at [community forum](http://community.opendronemap.org/), and if you still need help and think you've found a bug or need an enhancement, look through the issue queue or create one.
For documentation, everything is being moved to [http://docs.opendronemap.org/](http://docs.opendronemap.org/) but you can also take a look at our [wiki](https://github.com/OpenDroneMap/OpenDroneMap/wiki). Check those places first if you are having problems. There's also help at [community forum](http://community.opendronemap.org/), and if you still need help and think you've found a bug or need an enhancement, look through the issue queue or create one.
## Developers

Wyświetl plik

@ -98,7 +98,7 @@ SETUP_EXTERNAL_PROJECT(Ceres ${ODM_Ceres_Version} ${ODM_BUILD_Ceres})
# ---------------------------------------------------------------------------------------------
# VTK7
# We need to build VTK from sources because Debian packages
# are built with DVTK_SMP_IMPLEMENTATION_TYPE set to
# are built with DVTK_SMP_IMPLEMENTATION_TYPE set to
# "Sequential" which means no multithread support.
set(ODM_VTK7_Version 7.1.1)
@ -114,14 +114,12 @@ SETUP_EXTERNAL_PROJECT(Hexer 1.4 ON)
# ---------------------------------------------------------------------------------------------
# Open Geometric Vision (OpenGV)
# Open Structure from Motion (OpenSfM)
# Clustering Views for Multi-view Stereo (CMVS)
# Catkin
# Ecto
#
set(custom_libs OpenGV
OpenSfM
CMVS
Catkin
Ecto
LASzip
@ -140,3 +138,24 @@ foreach(lib ${custom_libs})
SETUP_EXTERNAL_PROJECT_CUSTOM(${lib})
endforeach()
## Add smvs Build
externalproject_add(smvs
GIT_REPOSITORY https://github.com/flanggut/smvs.git
UPDATE_COMMAND ""
SOURCE_DIR ${SB_SOURCE_DIR}/elibs/smvs
CONFIGURE_COMMAND ""
BUILD_IN_SOURCE 1
BUILD_COMMAND make
INSTALL_COMMAND ""
)
externalproject_add(mve
GIT_REPOSITORY https://github.com/simonfuhrmann/mve.git
UPDATE_COMMAND ""
SOURCE_DIR ${SB_SOURCE_DIR}/elibs/mve
CONFIGURE_COMMAND ""
BUILD_IN_SOURCE 1
BUILD_COMMAND make
INSTALL_COMMAND ""
)

Wyświetl plik

@ -1,28 +0,0 @@
set(_proj_name cmvs)
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/edgarriba/CMVS-PMVS/archive/master.zip
URL_MD5 dbb1493f49ca099b4208381bd20d1435
#--Update/Patch step----------
UPDATE_COMMAND ""
#--Configure step-------------
SOURCE_DIR ${SB_SOURCE_DIR}/${_proj_name}
CONFIGURE_COMMAND cmake <SOURCE_DIR>/program
-DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${SB_INSTALL_DIR}/bin
-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
)

Wyświetl plik

@ -102,6 +102,7 @@ install() {
pip install -U https://github.com/gipit/gippy/archive/v1.0.0.zip psutil
echo "Compiling SuperBuild"
cd ${RUNPATH}/SuperBuild
mkdir -p build && cd build

Wyświetl plik

@ -10,8 +10,6 @@ Licensing for portions of OpenDroneMap are as follows:
* libcv - BSD - http://opencv.org/license.html
* libcvaux - BSD - http://opencv.org/license.html
* bundler - GPLv3 - http://www.gnu.org/copyleft/gpl.html
* cmvs - GPLv3 - http://www.gnu.org/copyleft/gpl.html
* pmvs2 - GPLv3 - http://www.gnu.org/copyleft/gpl.html
* parallel - GPLv3 - http://www.gnu.org/copyleft/gpl.html
* PoissonRecon - BSD - http://www.cs.jhu.edu/~misha/Code/PoissonRecon/license.txt
* vlfeat - BSD - http://www.vlfeat.org/license.html

Wyświetl plik

@ -7,7 +7,7 @@ from appsettings import SettingsParser
import sys
# parse arguments
processopts = ['dataset', 'opensfm', 'slam', 'cmvs', 'pmvs',
processopts = ['dataset', 'opensfm', 'slam', 'smvs',
'odm_meshing', 'odm_25dmeshing', 'mvs_texturing', 'odm_georeferencing',
'odm_dem', 'odm_orthophoto']
@ -140,12 +140,13 @@ def config():
default=False,
help='Turn off camera parameter optimization during bundler')
parser.add_argument('--opensfm-processes',
parser.add_argument('--max-concurrency',
metavar='<positive integer>',
default=context.num_cores,
type=int,
help=('The maximum number of processes to use in dense '
'reconstruction. Default: %(default)s'))
help=('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'))
parser.add_argument('--opensfm-depthmap-resolution',
metavar='<positive float>',
@ -192,67 +193,53 @@ def config():
default=False,
help='Use a 2.5D mesh to compute the orthophoto. This option tends to provide better results for planar surfaces. Experimental.')
parser.add_argument('--use-pmvs',
parser.add_argument('--use-opensfm-dense',
action='store_true',
default=False,
help='Use pmvs to compute point cloud alternatively')
help='Use opensfm to compute dense point cloud alternatively')
parser.add_argument('--cmvs-maxImages',
metavar='<integer>',
default=500,
type=int,
help='The maximum number of images per cluster. '
'Default: %(default)s')
parser.add_argument('--smvs-alpha',
metavar='<float>',
default=1.0,
type=float,
help='Regularization parameter, a higher alpha leads to '
'smoother surfaces. Default: %(default)s')
parser.add_argument('--pmvs-level',
metavar='<positive integer>',
parser.add_argument('--smvs-scale',
metavar='<non-negative integer>',
default=1,
type=int,
help=('The level in the image pyramid that is used '
'for the computation. see '
'http://www.di.ens.fr/pmvs/documentation.html for '
'more pmvs documentation. Default: %(default)s'))
help='Scales the input images, which affects the output'
' density. 0 is original scale but takes longer '
'to process. 2 is 1/4 scale. Default: %(default)s')
parser.add_argument('--pmvs-csize',
parser.add_argument('--smvs-output-scale',
metavar='<positive integer>',
default=2,
type=int,
help='Cell size controls the density of reconstructions'
'Default: %(default)s')
help='The scale of the optimization - the '
'finest resolution of the bicubic patches will have the'
' size of the respective power of 2 (e.g. 2 will '
'optimize patches covering down to 4x4 pixels). '
'Default: %(default)s')
parser.add_argument('--pmvs-threshold',
metavar='<float: -1.0 <= x <= 1.0>',
default=0.7,
type=float,
help=('A patch reconstruction is accepted as a success '
'and kept if its associated photometric consistency '
'measure is above this threshold. Default: %(default)s'))
parser.add_argument('--smvs-enable-shading',
action='store_true',
default=False,
help='Use shading-based optimization. This model cannot '
'handle complex scenes. Try to supply linear images to '
'the reconstruction pipeline that are not tone mapped '
'or altered as this can also have very negative effects '
'on the reconstruction. If you have simple JPGs with SRGB '
'gamma correction you can remove it with the --smvs-gamma-srgb '
'option. Default: %(default)s')
parser.add_argument('--pmvs-wsize',
metavar='<positive integer>',
default=7,
type=int,
help='pmvs samples wsize x wsize pixel colors from '
'each image to compute photometric consistency '
'score. For example, when wsize=7, 7x7=49 pixel '
'colors are sampled in each image. Increasing the '
'value leads to more stable reconstructions, but '
'the program becomes slower. Default: %(default)s')
parser.add_argument('--pmvs-min-images',
metavar='<positive integer>',
default=3,
type=int,
help=('Each 3D point must be visible in at least '
'minImageNum images for being reconstructed. 3 is '
'suggested in general. Default: %(default)s'))
parser.add_argument('--pmvs-num-cores',
metavar='<positive integer>',
default=context.num_cores,
type=int,
help=('The maximum number of cores to use in dense '
'reconstruction. Default: %(default)s'))
parser.add_argument('--smvs-gamma-srgb',
action='store_true',
default=False,
help='Apply inverse SRGB gamma correction. To be used '
'with --smvs-enable-shading when you have simple JPGs with '
'SRGB gamma correction. Default: %(default)s')
parser.add_argument('--mesh-size',
metavar='<positive integer>',
@ -285,7 +272,7 @@ def config():
'Increasing this value increases computation '
'times slightly but helps reduce memory usage. '
'Default: %(default)s'))
parser.add_argument('--mesh-neighbors',
metavar='<positive integer>',
default=24,
@ -568,11 +555,6 @@ def config():
log.ODM_INFO('Fast orthophoto is turned on, automatically setting --use-25dmesh')
args.use_25dmesh = True
# Cannot use pmvs
if args.use_pmvs:
log.ODM_INFO('Fast orthophoto is turned on, cannot use pmvs (removing --use-pmvs)')
args.use_pmvs = False
if args.dtm and args.pc_classify == 'none':
log.ODM_INFO("DTM is turned on, automatically turning on point cloud classification")
args.pc_classify = "smrf"

Wyświetl plik

@ -23,10 +23,9 @@ ccd_widths_path = os.path.join(opensfm_path, 'opensfm/data/sensor_data.json')
# define orb_slam2 path
orb_slam2_path = os.path.join(superbuild_path, "src/orb_slam2")
# define pmvs path
cmvs_path = os.path.join(superbuild_path, "install/bin/cmvs")
cmvs_opts_path = os.path.join(superbuild_path, "install/bin/genOption")
pmvs2_path = os.path.join(superbuild_path, "install/bin/pmvs2")
# define smvs join_paths
makescene_path = os.path.join(superbuild_path, 'src', 'elibs', 'mve', 'apps', 'makescene', 'makescene') #TODO: don't install in source
smvs_path = os.path.join(superbuild_path, 'src', 'elibs', 'smvs', 'app', 'smvsrecon')
# define mvstex path
mvstex_path = os.path.join(superbuild_path, "install/bin/texrecon")
@ -44,5 +43,5 @@ settings_path = os.path.join(root_path, 'settings.yaml')
# Define supported image extensions
supported_extensions = {'.jpg','.jpeg','.png'}
# Define the number of cores
# Define the number of cores
num_cores = multiprocessing.cpu_count()

Wyświetl plik

@ -34,13 +34,23 @@ def dir_exists(dirname):
def copy(src, dst):
try:
try:
shutil.copytree(src, dst)
except OSError as e:
if e.errno == errno.ENOTDIR:
shutil.copy(src, dst)
else: raise
def rename_file(src, dst):
try:
os.rename(src, dst)
return True
except OSError as e:
if e.errno == errno.ENOENT:
return False
else:
raise
# find a file in the root directory
def find(filename, folder):

Wyświetl plik

@ -8,8 +8,6 @@ from scripts.opensfm import opensfm
# Define pipeline tasks
tasks_dict = {'1': 'opensfm',
'2': 'cmvs',
'3': 'pmvs',
'4': 'odm_meshing',
'5': 'mvs_texturing',
'6': 'odm_georeferencing',
@ -52,7 +50,7 @@ class ODMTaskManager(object):
'args': _odm_app.args,
'photos': _odm_app.photos}
elif task_name in ['cmvs', 'pmvs', 'odm_meshing', 'mvs_texturing', 'odm_georeferencing', 'odm_orthophoto', 'zip_results']:
elif task_name in [ 'odm_meshing', 'mvs_texturing', 'odm_georeferencing', 'odm_orthophoto', 'zip_results']:
# setup this task
command = None
inputs = {}

Wyświetl plik

@ -61,7 +61,7 @@ class ODM_Photo:
metadata.read()
# loop over image tags
for key in metadata:
# try/catch tag value due to weird bug in pyexiv2
# try/catch tag value due to weird bug in pyexiv2
# ValueError: invalid literal for int() with base 10: ''
GPS = 'Exif.GPSInfo.GPS'
try:
@ -274,7 +274,7 @@ class ODM_GeoRef(object):
with open(json_file, 'w') as f:
f.write(pipeline)
# call pdal
# call pdal
system.run('{bin}/pdal pipeline -i {json} --readers.ply.filename={f_in}'.format(**kwargs))
def utm_to_latlon(self, _file, _photo, idx):
@ -389,7 +389,7 @@ class ODM_GeoRef(object):
# Create a nested list for the transformation matrix
with open(_file) as f:
for line in f:
# Handle matrix formats that either
# Handle matrix formats that either
# have leading or trailing brakets or just plain numbers.
line = re.sub(r"[\[\],]", "", line).strip()
self.transform += [[float(i) for i in line.split()]]
@ -414,7 +414,7 @@ class ODM_Tree(object):
# whole reconstruction process.
self.dataset_raw = io.join_paths(self.root_path, 'images')
self.opensfm = io.join_paths(self.root_path, 'opensfm')
self.pmvs = io.join_paths(self.root_path, 'pmvs')
self.smvs = io.join_paths(self.root_path, 'smvs')
self.odm_meshing = io.join_paths(self.root_path, 'odm_meshing')
self.odm_texturing = io.join_paths(self.root_path, 'odm_texturing')
self.odm_25dtexturing = io.join_paths(self.root_path, 'odm_texturing_25d')
@ -439,12 +439,12 @@ class ODM_Tree(object):
self.opensfm_model = io.join_paths(self.opensfm, 'depthmaps/merged.ply')
self.opensfm_transformation = io.join_paths(self.opensfm, 'geocoords_transformation.txt')
# pmvs
self.pmvs_rec_path = io.join_paths(self.pmvs, 'recon0')
self.pmvs_bundle = io.join_paths(self.pmvs_rec_path, 'bundle.rd.out')
self.pmvs_visdat = io.join_paths(self.pmvs_rec_path, 'vis.dat')
self.pmvs_options = io.join_paths(self.pmvs_rec_path, 'pmvs_options.txt')
self.pmvs_model = io.join_paths(self.pmvs_rec_path, 'models/option-0000.ply')
# smvs
self.smvs_model = io.join_paths(self.smvs, 'smvs_dense_point_cloud.ply')
self.mve_path = io.join_paths(self.opensfm, 'mve')
self.mve_image_list = io.join_paths(self.mve_path, 'list.txt')
self.mve_bundle = io.join_paths(self.mve_path, 'bundle/bundle.out')
self.mve_views = io.join_paths(self.smvs, 'views')
# odm_meshing
self.odm_mesh = io.join_paths(self.odm_meshing, 'odm_mesh.ply')

2
run.py
Wyświetl plik

@ -33,7 +33,7 @@ if __name__ == '__main__':
+ args.project_path + "/odm_orthophoto "
+ args.project_path + "/odm_texturing "
+ args.project_path + "/opensfm "
+ args.project_path + "/pmvs")
+ args.project_path + "/smvs")
# create an instance of my App BlackBox
# internally configure all tasks

Wyświetl plik

@ -1,68 +0,0 @@
import ecto
from opendm import io
from opendm import log
from opendm import system
from opendm import context
class ODMCmvsCell(ecto.Cell):
def declare_params(self, params):
params.declare("max_images", 'The maximum number of images '
'per cluster', 500)
params.declare("cores", 'The maximum number of cores to use '
'in dense reconstruction.', context.num_cores)
def declare_io(self, params, inputs, outputs):
inputs.declare("tree", "Struct with paths", [])
inputs.declare("args", "Struct with paths", [])
inputs.declare("reconstruction", "list of ODMReconstructions", [])
outputs.declare("reconstruction", "list of ODMReconstructions", [])
def process(self, inputs, outputs):
# Benchmarking
start_time = system.now_raw()
log.ODM_INFO('Running ODM CMVS Cell')
# get inputs
args = self.inputs.args
tree = self.inputs.tree
# check if we rerun cell or not
rerun_cell = (args.rerun is not None and
args.rerun == 'cmvs') or \
(args.rerun_all) or \
(args.rerun_from is not None and
'cmvs' in args.rerun_from)
if not io.file_exists(tree.pmvs_bundle) or rerun_cell:
log.ODM_DEBUG('Writing CMVS vis in: %s' % tree.pmvs_bundle)
# copy bundle file to pmvs dir
from shutil import copyfile
copyfile(tree.opensfm_bundle,
tree.pmvs_bundle)
kwargs = {
'bin': context.cmvs_path,
'prefix': self.inputs.tree.pmvs_rec_path,
'max_images': self.params.max_images,
'cores': self.params.cores
}
# run cmvs
system.run('{bin} {prefix}/ {max_images} {cores}'.format(**kwargs))
else:
log.ODM_WARNING('Found a valid CMVS file in: %s' %
tree.pmvs_bundle)
outputs.reconstruction = inputs.reconstruction
if args.time:
system.benchmark(start_time, tree.benchmarking, 'CMVS')
log.ODM_INFO('Running ODM CMVS Cell - Finished')
return ecto.OK if args.end_with != 'cmvs' else ecto.QUIT

Wyświetl plik

@ -5,8 +5,6 @@ from opendm import io
from opendm import system
from opendm import context
import pmvs2nvmcams
class ODMMvsTexCell(ecto.Cell):
def declare_params(self, params):
params.declare("data_term", 'Data term: [area, gmi] default: gmi', "gmi")
@ -38,7 +36,7 @@ class ODMMvsTexCell(ecto.Cell):
# define paths and create working directories
system.mkdir_p(tree.odm_texturing)
if args.use_25dmesh: system.mkdir_p(tree.odm_25dtexturing)
if args.use_25dmesh: system.mkdir_p(tree.odm_25dtexturing)
# check if we rerun cell or not
rerun_cell = (args.rerun is not None and
@ -62,11 +60,11 @@ class ODMMvsTexCell(ecto.Cell):
'model': tree.odm_25dmesh,
# We always skip the visibility test when using the 2.5D mesh
# because many faces end up being narrow, and almost perpendicular
# because many faces end up being narrow, and almost perpendicular
# to the ground plane. The visibility test improperly classifies
# them as "not seen" since the test is done on a single triangle vertex,
# and while one vertex might be occluded, the other two might not.
'force_skip_vis_test': True
'force_skip_vis_test': True
}]
for r in runs:
@ -82,7 +80,7 @@ class ODMMvsTexCell(ecto.Cell):
skipLocalSeamLeveling = ""
skipHoleFilling = ""
keepUnseenFaces = ""
if (self.params.skip_vis_test or r['force_skip_vis_test']):
skipGeometricVisibilityTest = "--skip_geometric_visibility_test"
if (self.params.skip_glob_seam_leveling):
@ -98,8 +96,6 @@ class ODMMvsTexCell(ecto.Cell):
kwargs = {
'bin': context.mvstex_path,
'out_dir': io.join_paths(r['out_dir'], "odm_textured_model"),
'pmvs_folder': tree.pmvs_rec_path,
'nvm_file': io.join_paths(tree.pmvs_rec_path, "nvmCams.nvm"),
'model': r['model'],
'dataTerm': self.params.data_term,
'outlierRemovalType': self.params.outlier_rem_type,
@ -111,16 +107,11 @@ class ODMMvsTexCell(ecto.Cell):
'toneMapping': self.params.tone_mapping
}
if not args.use_pmvs:
if args.use_opensfm_dense:
kwargs['nvm_file'] = io.join_paths(tree.opensfm,
"reconstruction.nvm")
else:
log.ODM_DEBUG('Generating .nvm file from pmvs output: %s'
% '{nvm_file}'.format(**kwargs))
# Create .nvm camera file.
pmvs2nvmcams.run('{pmvs_folder}'.format(**kwargs),
'{nvm_file}'.format(**kwargs))
kwargs['nvm_file'] = tree.smvs + "::undistorted"
# Make sure tmp directory is empty
mvs_tmp_dir = os.path.join(r['out_dir'], 'tmp')

Wyświetl plik

@ -8,9 +8,8 @@ from opendm import system
from dataset import ODMLoadDatasetCell
from run_opensfm import ODMOpenSfMCell
from smvs import ODMSmvsCell
from odm_slam import ODMSlamCell
from pmvs import ODMPmvsCell
from cmvs import ODMCmvsCell
from odm_meshing import ODMeshingCell
from mvstex import ODMMvsTexCell
from odm_georeferencing import ODMGeoreferencingCell
@ -44,19 +43,19 @@ class ODMApp(ecto.BlackBox):
'opensfm': ODMOpenSfMCell(use_exif_size=False,
feature_process_size=p.args.resize_to,
feature_min_frames=p.args.min_num_features,
processes=p.args.opensfm_processes,
processes=p.args.max_concurrency,
matching_gps_neighbors=p.args.matcher_neighbors,
matching_gps_distance=p.args.matcher_distance,
fixed_camera_params=p.args.use_fixed_camera_params,
hybrid_bundle_adjustment=p.args.use_hybrid_bundle_adjustment),
'slam': ODMSlamCell(),
'cmvs': ODMCmvsCell(max_images=p.args.cmvs_maxImages),
'pmvs': ODMPmvsCell(level=p.args.pmvs_level,
csize=p.args.pmvs_csize,
thresh=p.args.pmvs_threshold,
wsize=p.args.pmvs_wsize,
min_imgs=p.args.pmvs_min_images,
cores=p.args.pmvs_num_cores),
'smvs': ODMSmvsCell(alpha=p.args.smvs_alpha,
scale=p.args.smvs_scale,
threads=p.args.max_concurrency,
output_scale=p.args.smvs_output_scale,
shading=p.args.smvs_enable_shading,
gamma_srgb=p.args.smvs_gamma_srgb,
verbose=p.args.verbose),
'meshing': ODMeshingCell(max_vertex=p.args.mesh_size,
oct_tree=p.args.mesh_octree_depth,
samples=p.args.mesh_samples,
@ -73,13 +72,15 @@ class ODMApp(ecto.BlackBox):
'georeferencing': ODMGeoreferencingCell(gcp_file=p.args.gcp,
use_exif=p.args.use_exif,
verbose=p.args.verbose),
'dem': ODMDEMCell(verbose=p.args.verbose),
'dem': ODMDEMCell(max_concurrency=p.args.max_concurrency,
verbose=p.args.verbose),
'orthophoto': ODMOrthoPhotoCell(resolution=p.args.orthophoto_resolution,
t_srs=p.args.orthophoto_target_srs,
no_tiled=p.args.orthophoto_no_tiled,
compress=p.args.orthophoto_compression,
bigtiff=p.args.orthophoto_bigtiff,
build_overviews=p.args.build_overviews,
max_concurrency=p.args.max_concurrency,
verbose=p.args.verbose)
}
@ -116,26 +117,21 @@ class ODMApp(ecto.BlackBox):
self.args[:] >> self.opensfm['args'],
self.dataset['reconstruction'] >> self.opensfm['reconstruction']]
if not p.args.use_pmvs:
if p.args.use_opensfm_dense:
# create odm mesh from opensfm point cloud
connections += [self.tree[:] >> self.meshing['tree'],
self.args[:] >> self.meshing['args'],
self.opensfm['reconstruction'] >> self.meshing['reconstruction']]
else:
# run cmvs
connections += [self.tree[:] >> self.cmvs['tree'],
self.args[:] >> self.cmvs['args'],
self.opensfm['reconstruction'] >> self.cmvs['reconstruction']]
# run smvs
connections += [self.tree[:] >> self.smvs['tree'],
self.args[:] >> self.smvs['args'],
self.opensfm['reconstruction'] >> self.smvs['reconstruction']]
# run pmvs
connections += [self.tree[:] >> self.pmvs['tree'],
self.args[:] >> self.pmvs['args'],
self.cmvs['reconstruction'] >> self.pmvs['reconstruction']]
# create odm mesh from pmvs point cloud
# create odm mesh from smvs point cloud
connections += [self.tree[:] >> self.meshing['tree'],
self.args[:] >> self.meshing['args'],
self.pmvs['reconstruction'] >> self.meshing['reconstruction']]
self.smvs['reconstruction'] >> self.meshing['reconstruction']]
# create odm texture
connections += [self.tree[:] >> self.texturing['tree'],
@ -166,20 +162,14 @@ class ODMApp(ecto.BlackBox):
connections += [self.tree[:] >> self.slam['tree'],
self.args[:] >> self.slam['args']]
# run cmvs
connections += [self.tree[:] >> self.cmvs['tree'],
self.args[:] >> self.cmvs['args'],
self.slam['reconstruction'] >> self.cmvs['reconstruction']]
# run pmvs
connections += [self.tree[:] >> self.pmvs['tree'],
self.args[:] >> self.pmvs['args'],
self.cmvs['reconstruction'] >> self.pmvs['reconstruction']]
connections += [self.tree[:] >> self.smvs['tree'],
self.args[:] >> self.smvs['args'],
self.slam['reconstruction'] >> self.smvs['reconstruction']]
# create odm mesh
connections += [self.tree[:] >> self.meshing['tree'],
self.args[:] >> self.meshing['args'],
self.pmvs['reconstruction'] >> self.meshing['reconstruction']]
self.smvs['reconstruction'] >> self.meshing['reconstruction']]
# create odm texture
connections += [self.tree[:] >> self.texturing['tree'],

Wyświetl plik

@ -13,6 +13,7 @@ from opendm.cropper import Cropper
class ODMDEMCell(ecto.Cell):
def declare_params(self, params):
params.declare("verbose", 'print additional messages to console', False)
params.declare("max_concurrency", "Number of threads", context.num_cores)
def declare_io(self, params, inputs, outputs):
inputs.declare("tree", "Struct with paths", [])
@ -44,12 +45,12 @@ class ODMDEMCell(ecto.Cell):
# Setup terrain parameters
terrain_params_map = {
'flatnonforest': (1, 3),
'flatforest': (1, 2),
'complexnonforest': (5, 2),
'flatnonforest': (1, 3),
'flatforest': (1, 2),
'complexnonforest': (5, 2),
'complexforest': (10, 2)
}
terrain_params = terrain_params_map[args.dem_terrain_type.lower()]
terrain_params = terrain_params_map[args.dem_terrain_type.lower()]
slope, cellsize = terrain_params
# define paths and create working directories
@ -62,7 +63,7 @@ class ODMDEMCell(ecto.Cell):
if not io.file_exists(pc_classify_marker) or rerun_cell:
log.ODM_INFO("Classifying {} using {}".format(tree.odm_georeferencing_model_laz, args.pc_classify))
commands.classify(tree.odm_georeferencing_model_laz,
commands.classify(tree.odm_georeferencing_model_laz,
args.pc_classify == "smrf",
slope,
cellsize,
@ -70,7 +71,7 @@ class ODMDEMCell(ecto.Cell):
initialDistance=args.dem_initial_distance,
verbose=args.verbose
)
with open(pc_classify_marker, 'w') as f:
with open(pc_classify_marker, 'w') as f:
f.write('Classify: {}\n'.format(args.pc_classify))
f.write('Slope: {}\n'.format(slope))
f.write('Cellsize: {}\n'.format(cellsize))
@ -87,7 +88,7 @@ class ODMDEMCell(ecto.Cell):
rerun_cell:
products = []
if args.dsm: products.append('dsm')
if args.dsm: products.append('dsm')
if args.dtm: products.append('dtm')
radius_steps = [args.dem_resolution]
@ -96,7 +97,7 @@ class ODMDEMCell(ecto.Cell):
for product in products:
commands.create_dems(
[tree.odm_georeferencing_model_laz],
[tree.odm_georeferencing_model_laz],
product,
radius=map(str, radius_steps),
gapfill=True,
@ -116,7 +117,7 @@ class ODMDEMCell(ecto.Cell):
'COMPRESS': 'LZW',
'BLOCKXSIZE': 512,
'BLOCKYSIZE': 512,
'NUM_THREADS': 'ALL_CPUS'
'NUM_THREADS': self.params.max_concurrency
})
else:
log.ODM_WARNING('Found existing outputs in: %s' % odm_dem_root)

Wyświetl plik

@ -92,13 +92,13 @@ class ODMGeoreferencingCell(ecto.Cell):
'verbose': verbose
}
if not args.use_pmvs:
if args.fast_orthophoto:
kwargs['pc'] = os.path.join(tree.opensfm, 'reconstruction.ply')
else:
kwargs['pc'] = tree.opensfm_model
if args.fast_orthophoto:
kwargs['pc'] = os.path.join(tree.opensfm, 'reconstruction.ply')
elif args.use_opensfm_dense:
kwargs['pc'] = tree.opensfm_model
else:
kwargs['pc'] = tree.pmvs_model
kwargs['pc'] = tree.smvs_model
# Check to see if the GCP file exists

Wyświetl plik

@ -50,9 +50,9 @@ class ODMeshingCell(ecto.Cell):
(args.rerun_from is not None and
'odm_meshing' in args.rerun_from)
infile = tree.opensfm_model
if args.use_pmvs:
infile = tree.pmvs_model
infile = tree.smvs_model
if args.use_opensfm_dense:
infile = tree.opensfm_model
elif args.fast_orthophoto:
infile = os.path.join(tree.opensfm, 'reconstruction.ply')

Wyświetl plik

@ -18,6 +18,7 @@ class ODMOrthoPhotoCell(ecto.Cell):
params.declare("bigtiff", 'Make BigTIFF orthophoto', 'IF_SAFER')
params.declare("build_overviews", 'Build overviews', False)
params.declare("verbose", 'print additional messages to console', False)
params.declare("max_concurrency", "number of threads", context.num_cores)
def declare_io(self, params, inputs, outputs):
inputs.declare("tree", "Struct with paths", [])
@ -125,7 +126,8 @@ class ODMOrthoPhotoCell(ecto.Cell):
'png': tree.odm_orthophoto_file,
'tiff': tree.odm_orthophoto_tif,
'log': tree.odm_orthophoto_tif_log,
'max_memory': max(5, (100 - virtual_memory().percent) / 2)
'max_memory': max(5, (100 - virtual_memory().percent) / 2),
'threads': self.params.max_concurrency
}
system.run('gdal_translate -a_ullr {ulx} {uly} {lrx} {lry} '
@ -135,7 +137,7 @@ class ODMOrthoPhotoCell(ecto.Cell):
'{predictor} '
'-co BLOCKXSIZE=512 '
'-co BLOCKYSIZE=512 '
'-co NUM_THREADS=ALL_CPUS '
'-co NUM_THREADS={threads} '
'-a_srs \"{proj}\" '
'--config GDAL_CACHEMAX {max_memory}% '
'{png} {tiff} > {log}'.format(**kwargs))
@ -149,7 +151,7 @@ class ODMOrthoPhotoCell(ecto.Cell):
'BIGTIFF': self.params.bigtiff,
'BLOCKXSIZE': 512,
'BLOCKYSIZE': 512,
'NUM_THREADS': 'ALL_CPUS'
'NUM_THREADS': self.params.max_concurrency
})
if self.params.build_overviews:

Wyświetl plik

@ -39,7 +39,6 @@ class ODMSlamCell(ecto.Cell):
# create working directories
system.mkdir_p(tree.opensfm)
system.mkdir_p(tree.pmvs)
vocabulary = os.path.join(context.orb_slam2_path,
'Vocabulary/ORBvoc.txt')
@ -96,16 +95,5 @@ class ODMSlamCell(ecto.Cell):
'Found a valid Bundler file in: {}'.format(
tree.opensfm_reconstruction))
# check if reconstruction was exported to pmvs before
if not io.file_exists(tree.pmvs_visdat) or rerun_cell:
# run PMVS converter
system.run(
'PYTHONPATH={} {}/bin/export_pmvs {} --output {}'.format(
context.pyopencv_path, context.opensfm_path, tree.opensfm,
tree.pmvs))
else:
log.ODM_WARNING('Found a valid CMVS file in: {}'.format(
tree.pmvs_visdat))
log.ODM_INFO('Running OMD Slam Cell - Finished')
return ecto.OK if args.end_with != 'odm_slam' else ecto.QUIT

Wyświetl plik

@ -1,84 +0,0 @@
import ecto
from opendm import io
from opendm import log
from opendm import system
from opendm import context
class ODMPmvsCell(ecto.Cell):
def declare_params(self, params):
params.declare("level", 'The level in the image pyramid that is used '
'for the computation', 1)
params.declare("csize", 'Cell size controls the density of reconstructions', 2)
params.declare("thresh", 'A patch reconstruction is accepted as a success '
'and kept, if its associcated photometric consistency '
'measure is above this threshold.', 0.7)
params.declare("wsize", 'pmvs samples wsize x wsize pixel colors from '
'each image to compute photometric consistency '
'score. For example, when wsize=7, 7x7=49 pixel '
'colors are sampled in each image. Increasing the '
'value leads to more stable reconstructions, but '
'the program becomes slower.', 7)
params.declare("min_imgs", 'Each 3D point must be visible in at least '
'minImageNum images for being reconstructed. 3 is '
'suggested in general.', 3)
params.declare("cores", 'The maximum number of cores to use in dense '
' reconstruction.', context.num_cores)
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", [])
outputs.declare("reconstruction", "list of ODMReconstructions", [])
def process(self, inputs, outputs):
# Benchmarking
start_time = system.now_raw()
log.ODM_INFO('Running OMD PMVS Cell')
# get inputs
args = self.inputs.args
tree = self.inputs.tree
# check if we rerun cell or not
rerun_cell = (args.rerun is not None and
args.rerun == 'pmvs') or \
(args.rerun_all) or \
(args.rerun_from is not None and
'pmvs' in args.rerun_from)
if not io.file_exists(tree.pmvs_model) or rerun_cell:
log.ODM_DEBUG('Creating dense pointcloud in: %s' % tree.pmvs_model)
kwargs = {
'bin': context.cmvs_opts_path,
'prefix': tree.pmvs_rec_path,
'level': self.params.level,
'csize': self.params.csize,
'thresh': self.params.thresh,
'wsize': self.params.wsize,
'min_imgs': self.params.min_imgs,
'cores': self.params.cores
}
# generate pmvs2 options
system.run('{bin} {prefix}/ {level} {csize} {thresh} {wsize} '
'{min_imgs} {cores}'.format(**kwargs))
# run pmvs2
system.run('%s %s/ option-0000' %
(context.pmvs2_path, tree.pmvs_rec_path))
else:
log.ODM_WARNING('Found a valid PMVS file in %s' % tree.pmvs_model)
outputs.reconstruction = inputs.reconstruction
if args.time:
system.benchmark(start_time, tree.benchmarking, 'PMVS')
log.ODM_INFO('Running ODM PMVS Cell - Finished')
return ecto.OK if args.end_with != 'pmvs' else ecto.QUIT

Wyświetl plik

@ -1,144 +0,0 @@
import os
import numpy as np
from opendm import log
# Go from QR-factorizatoin to corresponding RQ-factorization.
def rq(A):
Q,R = np.linalg.qr(np.flipud(A).T)
R = np.flipud(R.T)
Q = Q.T
return R[:,::-1],Q[::-1,:]
# Create a unit quaternion from rotation matrix.
def rot2quat(R):
# Float epsilon (use square root to be well with the stable region).
eps = np.sqrt(np.finfo(float).eps)
# If the determinant is not 1, it's not a rotation matrix
if np.abs(np.linalg.det(R) - 1.0) > eps:
log.ODM_ERROR('Matrix passed to rot2quat was not a rotation matrix, det != 1.0')
tr = np.trace(R)
quat = np.zeros((1,4))
# Is trace big enough be computationally stable?
if tr > eps:
S = 0.5 / np.sqrt(tr + 1.0)
quat[0,0] = 0.25 / S
quat[0,1] = (R[2,1] - R[1,2]) * S
quat[0,2] = (R[0,2] - R[2,0]) * S
quat[0,3] = (R[1,0] - R[0,1]) * S
else: # It's not, use the largest diagonal.
if R[0,0] > R[1,1] and R[0,0] > R[2,2]:
S = np.sqrt(1.0 + R[0,0] - R[1,1] - R[2,2]) * 2.0
quat[0,0] = (R[2,1] - R[1,2]) / S
quat[0,1] = 0.25 * S
quat[0,2] = (R[0,1] + R[1,0]) / S
quat[0,3] = (R[0,2] + R[2,0]) / S
elif R[1,1] > R[2,2]:
S = np.sqrt(1.0 - R[0,0] + R[1,1] - R[2,2]) * 2.0
quat[0,0] = (R[0,2] - R[2,0]) / S
quat[0,1] = (R[0,1] + R[1,0]) / S
quat[0,2] = 0.25 * S
quat[0,3] = (R[1,2] + R[2,1]) / S
else:
S = np.sqrt(1.0 - R[0,0] - R[1,1] + R[2,2]) * 2.0
quat[0,0] = (R[1,0] - R[0,1]) / S
quat[0,1] = (R[0,2] + R[2,0]) / S
quat[0,2] = (R[1,2] + R[2,1]) / S
quat[0,3] = 0.25 * S
return quat
# Decompose a projection matrix into parts
# (Intrinsic projection, Rotation, Camera position)
def decomposeProjection(projectionMatrix):
# Check input:
if projectionMatrix.shape != (3,4):
log.ODM_ERROR('Unable to decompose projection matrix, shape != (3,4)')
RQ = rq(projectionMatrix[:,:3])
# Fix sign, since we know K is upper triangular and has a positive diagonal.
signMat = np.diag(np.diag(np.sign(RQ[0])))
K = signMat*RQ[0]
R = signMat*RQ[1]
# Calculate camera position from translation vector.
t = np.linalg.inv(-1.0*projectionMatrix[:,:3])*projectionMatrix[:,3]
return K, R, t
# Parses pvms contour file.
def parseContourFile(filePath):
with open(filePath, 'r') as contourFile:
if (contourFile.readline().strip() != "CONTOUR"):
return np.array([])
else:
pMatData = np.loadtxt(contourFile, float, '#', None, None, 0)
if pMatData.shape == (3,4):
return pMatData
return np.array([])
# Creates a .nvm camera file in the pmvs folder.
def run(pmvsFolder, outputFile):
projectionFolder = pmvsFolder + "/txt"
imageFolder = pmvsFolder + "/visualize"
pMatrices = []
imageFileNames = []
# for all files in the visualize folder:
for imageFileName in os.listdir(imageFolder):
fileNameNoExt = os.path.splitext(imageFileName)[0]
# look for corresponding projection matrix txt file
projectionFilePath = os.path.join(projectionFolder, fileNameNoExt)
projectionFilePath += ".txt"
if os.path.isfile(projectionFilePath):
pMatData = parseContourFile(projectionFilePath)
if pMatData.size == 0:
log.ODM_WARNING('Unable to parse contour file, skipping: %s'
% projectionFilePath)
else:
pMatrices.append(np.matrix(pMatData))
imageFileNames.append(imageFileName)
# Decompose projection matrices
focals = []
rotations = []
translations = []
for projection in pMatrices:
KRt = decomposeProjection(projection)
focals.append(KRt[0][0,0])
rotations.append(rot2quat(KRt[1]))
translations.append(KRt[2])
# Create .nvm file
with open (outputFile, 'w') as nvmFile:
nvmFile.write("NVM_V3\n\n")
nvmFile.write('%d' % len(rotations) + "\n")
for idx, imageFileName in enumerate(imageFileNames):
nvmFile.write(os.path.join("visualize", imageFileName))
nvmFile.write(" " + '%f' % focals[idx])
nvmFile.write(" " + '%f' % rotations[idx][0,0] +
" " + '%f' % rotations[idx][0,1] +
" " + '%f' % rotations[idx][0,2] +
" " + '%f' % rotations[idx][0,3])
nvmFile.write(" " + '%f' % translations[idx][0] +
" " + '%f' % translations[idx][1] +
" " + '%f' % translations[idx][2])
nvmFile.write(" 0 0\n")
nvmFile.write("0\n\n")
nvmFile.write("0\n\n")
nvmFile.write("0")

Wyświetl plik

@ -40,9 +40,8 @@ class ODMOpenSfMCell(ecto.Cell):
log.ODM_ERROR('Not enough photos in photos array to start OpenSfM')
return ecto.QUIT
# create working directories
# create working directories
system.mkdir_p(tree.opensfm)
system.mkdir_p(tree.pmvs)
# check if we rerun cell or not
rerun_cell = (args.rerun is not None and
@ -51,7 +50,7 @@ class ODMOpenSfMCell(ecto.Cell):
(args.rerun_from is not None and
'opensfm' in args.rerun_from)
if not args.use_pmvs:
if args.use_opensfm_dense:
output_file = tree.opensfm_model
if args.fast_orthophoto:
output_file = io.join_paths(tree.opensfm, 'reconstruction.ply')
@ -136,7 +135,7 @@ class ODMOpenSfMCell(ecto.Cell):
log.ODM_WARNING('Found a valid OpenSfM reconstruction file in: %s' %
tree.opensfm_reconstruction)
if not args.use_pmvs:
if args.use_opensfm_dense:
if not io.file_exists(tree.opensfm_reconstruction_nvm) or rerun_cell:
system.run('PYTHONPATH=%s %s/bin/opensfm export_visualsfm %s' %
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
@ -146,7 +145,7 @@ class ODMOpenSfMCell(ecto.Cell):
system.run('PYTHONPATH=%s %s/bin/opensfm undistort %s' %
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
# Skip dense reconstruction if necessary and export
# sparse reconstruction instead
if args.fast_orthophoto:
@ -169,15 +168,6 @@ class ODMOpenSfMCell(ecto.Cell):
log.ODM_WARNING('Found a valid Bundler file in: %s' %
tree.opensfm_reconstruction)
if args.use_pmvs:
# check if reconstruction was exported to pmvs before
if not io.file_exists(tree.pmvs_visdat) or rerun_cell:
# run PMVS converter
system.run('PYTHONPATH=%s %s/bin/export_pmvs %s --output %s' %
(context.pyopencv_path, context.opensfm_path, tree.opensfm, tree.pmvs))
else:
log.ODM_WARNING('Found a valid CMVS file in: %s' % tree.pmvs_visdat)
if reconstruction.georef:
system.run('PYTHONPATH=%s %s/bin/opensfm export_geocoords %s --transformation --proj \'%s\'' %
(context.pyopencv_path, context.opensfm_path, tree.opensfm, reconstruction.georef.projection.srs))

96
scripts/smvs.py 100644
Wyświetl plik

@ -0,0 +1,96 @@
import ecto
from opendm import log
from opendm import io
from opendm import system
from opendm import context
class ODMSmvsCell(ecto.Cell):
def declare_params(self, params):
params.declare("threads", "max number of threads", context.num_cores)
params.declare("alpha", "Regularization parameter", 1)
params.declare("scale", "input scale", 1)
params.declare("output_scale", "scale of optimization", 2)
params.declare("shading", "Enable shading-aware model", False)
params.declare("gamma_srgb", "Apply inverse SRGB gamma correction", False)
params.declare("verbose", "Increase debug level", False)
def declare_io(self, params, inputs, outputs):
inputs.declare("tree", "Struct with paths", [])
inputs.declare("args", "The application arguments.", {})
inputs.declare("reconstruction", "ODMReconstruction", [])
outputs.declare("reconstruction", "list of ODMReconstructions", [])
def process(self, inputs, outputs):
# Benchmarking
start_time = system.now_raw()
log.ODM_INFO('Running SMVS Cell')
# get inputs
tree = inputs.tree
args = inputs.args
reconstruction = inputs.reconstruction
photos = reconstruction.photos
if not photos:
log.ODM_ERROR('Not enough photos in photos array to start SMVS')
return ecto.QUIT
system.mkdir_p(tree.smvs)
# check if we rerun cell or not
rerun_cell = (args.rerun is not None and
args.rerun == 'smvs') or \
(args.rerun_all) or \
(args.rerun_from is not None and
'smvs' in args.rerun_from)
# check if reconstruction was done before
if not io.file_exists(tree.smvs_model) or rerun_cell:
# make bundle directory
if not io.file_exists(tree.mve_bundle):
system.mkdir_p(tree.mve_path) #TODO: check permissions/what happens when rerun
system.mkdir_p(io.join_paths(tree.mve_path, 'bundle'))
io.copy(tree.opensfm_image_list, tree.mve_image_list)
io.copy(tree.opensfm_bundle, tree.mve_bundle)
# run mve makescene
if not io.dir_exists(tree.mve_views):
system.run('%s %s %s' % (context.makescene_path, tree.mve_path, tree.smvs))
# config
config = [
"-t%s" % self.params.threads,
"-a%s" % self.params.alpha,
"-s%s" % self.params.scale,
"-o%s" % self.params.output_scale,
"--debug-lvl=%s" % ('1' if self.params.verbose else '0'),
"%s" % '-S' if self.params.shading else '',
"%s" % '-g' if self.params.gamma_srgb and self.params.shading else '',
"--force" if rerun_cell else ''
]
# run smvs
system.run('%s %s %s' % (context.smvs_path, ' '.join(config), tree.smvs))
# rename the file for simplicity
old_file = io.join_paths(tree.smvs, 'smvs-%s%s.ply' %
('S' if self.params.shading else 'B', self.params.scale))
if not (io.rename_file(old_file, tree.smvs_model)):
log.ODM_WARNING("File %s does not exist, cannot be renamed. " % old_file)
else:
log.ODM_WARNING('Found a valid SMVS reconstruction file in: %s' %
tree.smvs_model)
outputs.reconstruction = reconstruction
if args.time:
system.benchmark(start_time, tree.benchmarking, 'SMVS')
log.ODM_INFO('Running ODM SMVS Cell - Finished')
return ecto.OK if args.end_with != 'smvs' else ecto.QUIT