Started refactoring ecto out

Former-commit-id: 9e921f44ac
pull/1161/head
Piero Toffanin 2019-04-22 15:14:39 -04:00
rodzic 3bb2fd9993
commit 730e81748e
18 zmienionych plików z 286 dodań i 770 usunięć

Wyświetl plik

@ -48,14 +48,11 @@ RUN apt-get install --no-install-recommends -y \
libvtk6-dev \
libxext-dev \
python-dev \
python-empy \
python-gdal \
python-matplotlib \
python-networkx \
python-nose \
python-pip \
python-pyproj \
python-pyside \
python-software-properties \
python-wheel \
swig2.0
@ -65,7 +62,6 @@ RUN pip install --upgrade pip
RUN pip install setuptools
RUN pip install -U \
appsettings \
catkin-pkg \
exifread \
gpxpy \
loky \

Wyświetl plik

@ -80,12 +80,6 @@ install() {
loky \
repoze.lru
echo "Installing Ecto Dependencies"
pip install -U catkin-pkg
apt-get install -y -qq python-empy \
python-nose \
python-pyside
echo "Installing OpenDroneMap Dependencies"
apt-get install -y -qq python-scipy \
liblas-bin

Wyświetl plik

@ -329,3 +329,57 @@ class ODM_Tree(object):
def path(self, *args):
return io.join_paths(self.root_path, *args)
class ODM_Stage:
def __init__(self, name, args, **params):
self.name = name
self.args = args
self.params = params
if self.params is None:
self.params = {}
self.next_stage = None
def connect(self, stage):
self.next_stage = stage
return stage
def rerun(self):
"""
Does this stage need to be rerun?
"""
return (self.args.rerun is not None and
self.args.rerun == self.name) or \
(self.args.rerun_all) or \
(self.args.rerun_from is not None and
self.name in self.args.rerun_from)
def run(self, outputs = {}):
start_time = system.now_raw()
log.ODM_INFO('Running %s stage' % self.name)
self.process(self.args, outputs)
# The tree variable should always be populated at this point
if outputs.get('tree') is None:
raise Exception("Assert violation: tree variable is missing from outputs dictionary.")
if self.args.time:
system.benchmark(start_time, outputs['tree'].benchmarking, self.name)
log.ODM_INFO('Finished %s stage' % self.name)
# Last stage?
if self.args.end_with == self.name:
exit(0)
# Run next stage?
elif self.next_stage is not None:
self.next_stage.run(outputs)
else:
log.ODM_INFO("No more stages to run")
def process(self, args, outputs):
raise NotImplementedError

Wyświetl plik

@ -48,14 +48,11 @@ RUN apt-get install --no-install-recommends -y \
libvtk6-dev \
libxext-dev \
python-dev \
python-empy \
python-gdal \
python-matplotlib \
python-networkx \
python-nose \
python-pip \
python-pyproj \
python-pyside \
python-software-properties \
python-wheel \
swig2.0
@ -65,7 +62,6 @@ RUN pip install --upgrade pip
RUN pip install setuptools
RUN pip install -U \
appsettings \
catkin-pkg \
exifread \
gpxpy \
loky \

35
run.py
Wyświetl plik

@ -5,8 +5,8 @@ from opendm import config
from opendm import system
from opendm import io
import ecto
import os
from pipes import quote
from scripts.odm_app import ODMApp
@ -23,28 +23,23 @@ if __name__ == '__main__':
system.mkdir_p(os.path.abspath(args.project_path))
# If user asks to rerun everything, delete all of the existing progress directories.
# TODO: Move this somewhere it's not hard-coded
if args.rerun_all:
log.ODM_DEBUG("Rerun all -- Removing old data")
os.system("rm -rf "
+ args.project_path + "/images_resize "
+ args.project_path + "/odm_georeferencing "
+ args.project_path + "/odm_meshing "
+ args.project_path + "/odm_orthophoto "
+ args.project_path + "/odm_texturing "
+ args.project_path + "/opensfm "
+ args.project_path + "/mve")
os.system("rm -rf " +
" ".join([
quote(os.path.join(args.project_path, "odm_georeferencing")),
quote(os.path.join(args.project_path, "odm_meshing")),
quote(os.path.join(args.project_path, "odm_orthophoto")),
quote(os.path.join(args.project_path, "odm_texturing")),
quote(os.path.join(args.project_path, "opensfm")),
quote(os.path.join(args.project_path, "odm_filterpoints")),
quote(os.path.join(args.project_path, "odm_25dmeshing")),
quote(os.path.join(args.project_path, "odm_25dtexturing")),
quote(os.path.join(args.project_path, "mve")),
]) + "")
# create an instance of my App BlackBox
# internally configure all tasks
app = ODMApp(args=args)
# create a plasm that only contains the BlackBox
plasm = ecto.Plasm()
plasm.insert(app)
# execute the plasm
plasm.execute(niter=1)
app = ODMApp(args)
app.execute()
log.ODM_INFO('MMMMMMMMMMMNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNNNMMMMMMMMMMM')
log.ODM_INFO('MMMMMMdo:..---../sNMMMMMMMMMMMMMMMMMMMMMMMMMMNs/..---..:odMMMMMM')

Wyświetl plik

@ -1,5 +1,4 @@
import os
import ecto
import json
from opendm import context
@ -37,18 +36,18 @@ def load_images_database(database_file):
return result
class ODMLoadDatasetCell(ecto.Cell):
class ODMLoadDatasetStage(types.ODM_Stage):
def process(self, args, outputs):
# Load tree
tree = types.ODM_Tree(args.project_path, args.images, args.gcp)
outputs['tree'] = tree
def declare_params(self, params):
params.declare("verbose", 'indicate verbosity', False)
params.declare("proj", 'Geographic projection', None)
def declare_io(self, params, inputs, outputs):
inputs.declare("tree", "Struct with paths", [])
outputs.declare("reconstruction", "ODMReconstruction", [])
inputs.declare("args", "The application arguments.", {})
def process(self, inputs, outputs):
if args.time and io.file_exists(tree.benchmarking):
# Delete the previously made file
os.remove(tree.benchmarking)
with open(tree.benchmarking, 'a') as b:
b.write('ODM Benchmarking file created %s\nNumber of Cores: %s\n\n' % (system.now(), context.num_cores))
# check if the extension is supported
def supported_extension(file_name):
(pathfn, ext) = os.path.splitext(file_name)
@ -60,12 +59,6 @@ class ODMLoadDatasetCell(ecto.Cell):
log.ODM_DEBUG(in_dir)
return [f for f in io.get_files_list(in_dir) if supported_extension(f)]
log.ODM_INFO('Running ODM Load Dataset Cell')
# get inputs
tree = self.inputs.tree
args = self.inputs.args
# get images directory
input_dir = tree.input_images
images_dir = tree.dataset_raw
@ -82,14 +75,8 @@ class ODMLoadDatasetCell(ecto.Cell):
log.ODM_DEBUG('Loading dataset from: %s' % images_dir)
# check if we rerun cell or not
rerun_cell = (args.rerun is not None and
args.rerun == 'dataset') or \
(args.rerun_all) or \
(args.rerun_from is not None and
'dataset' in args.rerun_from)
images_database_file = io.join_paths(tree.root_path, 'images.json')
if not io.file_exists(images_database_file) or rerun_cell:
if not io.file_exists(images_database_file) or self.rerun():
files = get_images(images_dir)
if files:
# create ODMPhoto list
@ -105,7 +92,7 @@ class ODMLoadDatasetCell(ecto.Cell):
save_images_database(photos, images_database_file)
else:
log.ODM_ERROR('Not enough supported images in %s' % images_dir)
return ecto.QUIT
exit(1)
else:
# We have an images database, just load it
photos = load_images_database(images_database_file)
@ -113,13 +100,13 @@ class ODMLoadDatasetCell(ecto.Cell):
log.ODM_INFO('Found %s usable images' % len(photos))
# append photos to cell output
if not self.params.proj:
if not self.params.get('proj'):
if tree.odm_georeferencing_gcp:
outputs.reconstruction = types.ODM_Reconstruction(photos, coords_file=tree.odm_georeferencing_gcp)
outputs['reconstruction'] = types.ODM_Reconstruction(photos, coords_file=tree.odm_georeferencing_gcp)
else:
# Generate UTM from images
try:
if not io.file_exists(tree.odm_georeferencing_coords) or rerun_cell:
if not io.file_exists(tree.odm_georeferencing_coords) or self.rerun():
location.extract_utm_coords(photos, tree.dataset_raw, tree.odm_georeferencing_coords)
else:
log.ODM_INFO("Coordinates file already exist: %s" % tree.odm_georeferencing_coords)
@ -127,15 +114,13 @@ class ODMLoadDatasetCell(ecto.Cell):
log.ODM_WARNING('Could not generate coordinates file. '
'Ignore if there is a GCP file')
outputs.reconstruction = types.ODM_Reconstruction(photos, coords_file=tree.odm_georeferencing_coords)
outputs['reconstruction'] = types.ODM_Reconstruction(photos, coords_file=tree.odm_georeferencing_coords)
else:
outputs.reconstruction = types.ODM_Reconstruction(photos, projstring=self.params.proj)
outputs['reconstruction'] = types.ODM_Reconstruction(photos, projstring=self.params.get('proj'))
# Save proj to file for future use (unless this
# dataset is not georeferenced)
if outputs.reconstruction.projection:
if outputs['reconstruction'].projection:
with open(io.join_paths(tree.odm_georeferencing, tree.odm_georeferencing_proj), 'w') as f:
f.write(outputs.reconstruction.projection.srs)
f.write(outputs['reconstruction'].projection.srs)
log.ODM_INFO('Running ODM Load Dataset Cell - Finished')
return ecto.OK if args.end_with != 'dataset' else ecto.QUIT

Wyświetl plik

@ -1,35 +0,0 @@
import ecto
import numpy as np
class ClusterDetector(ecto.Cell):
def declare_params(self, params):
params.declare("n", "Max number of clusters.", 10)
def declare_io(self, params, inputs, outputs):
outputs.declare("clusters", "Clusters output. list of tuples", [])
def process(self, inputs, outputs):
clusters = []
for i in range(int(np.random.uniform(0, self.params.n))):
clusters.append( (i, 'c%d'%i) )
outputs.clusters = clusters
class ClusterPrinter(ecto.Cell):
def declare_io(self, params, inputs, outputs):
inputs.declare("clusters", "Clusters input")
def process(self, inputs, outputs):
print "Clusters: ",
for c in inputs.clusters:
print c,
print "\n"
def app():
cd = ClusterDetector(n=20)
cp = ClusterPrinter()
plasm = ecto.Plasm()
plasm.connect(cd['clusters'] >> cp['clusters'])
sched = ecto.Scheduler(plasm)
sched.execute(niter=3)
if __name__ == "__main__":
app()

Wyświetl plik

@ -1,45 +1,27 @@
import ecto, shutil, os, glob, math
import shutil, os, glob, math
from opendm import log
from opendm import io
from opendm import system
from opendm import context
from opendm import point_cloud
from opendm import types
class ODMMveCell(ecto.Cell):
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 MVE Cell')
class ODMMveStage(types.ODM_Stage):
def process(self, args, outputs):
# get inputs
tree = inputs.tree
args = inputs.args
reconstruction = inputs.reconstruction
tree = outputs['tree']
reconstruction = outputs['reconstruction']
photos = reconstruction.photos
if not photos:
log.ODM_ERROR('Not enough photos in photos array to start MVE')
return ecto.QUIT
# check if we rerun cell or not
rerun_cell = (args.rerun is not None and
args.rerun == 'mve') or \
(args.rerun_all) or \
(args.rerun_from is not None and
'mve' in args.rerun_from)
exit(1)
# check if reconstruction was done before
if not io.file_exists(tree.mve_model) or rerun_cell:
if not io.file_exists(tree.mve_model) or self.rerun():
# cleanup if a rerun
if io.dir_exists(tree.mve_path) and rerun_cell:
if io.dir_exists(tree.mve_path) and self.rerun():
shutil.rmtree(tree.mve_path)
# make bundle directory
@ -134,11 +116,3 @@ class ODMMveCell(ecto.Cell):
else:
log.ODM_WARNING('Found a valid MVE reconstruction file in: %s' %
tree.mve_model)
outputs.reconstruction = reconstruction
if args.time:
system.benchmark(start_time, tree.benchmarking, 'MVE')
log.ODM_INFO('Running ODM MVE Cell - Finished')
return ecto.OK if args.end_with != 'mve' else ecto.QUIT

Wyświetl plik

@ -1,50 +1,20 @@
import ecto, os, shutil
import os, shutil
from opendm import log
from opendm import io
from opendm import system
from opendm import context
from opendm import types
class ODMMvsTexCell(ecto.Cell):
def declare_params(self, params):
params.declare("data_term", 'Data term: [area, gmi] default: gmi', "gmi")
params.declare("outlier_rem_type", 'Type of photometric outlier removal method: [none, gauss_damping, gauss_clamping]. default: none', "none")
params.declare("skip_vis_test", 'Skip geometric visibility test based on ray intersection.', False)
params.declare("skip_glob_seam_leveling", 'Skip global seam leveling.', False)
params.declare("skip_loc_seam_leveling", 'Skip local seam leveling (Poisson editing).', False)
params.declare("skip_hole_fill", 'Skip hole filling.', False)
params.declare("keep_unseen_faces", 'Keep unseen faces.', False)
params.declare("tone_mapping", 'Type of tone mapping: [none, gamma]. Default: gamma', "gamma")
def declare_io(self, params, inputs, outputs):
inputs.declare("tree", "Struct with paths", [])
inputs.declare("args", "The application arguments.", {})
inputs.declare("reconstruction", "Clusters output. list of ODMReconstructions", [])
outputs.declare("reconstruction", "Clusters output. list of ODMReconstructions", [])
def process(self, inputs, outputs):
# Benchmarking
start_time = system.now_raw()
log.ODM_INFO('Running MVS Texturing Cell')
# get inputs
args = inputs.args
tree = inputs.tree
reconstruction = inputs.reconstruction
class ODMMvsTexStage(types.ODM_Stage):
def process(self, args, outputs):
tree = outputs['tree']
reconstruction = outputs['reconstruction']
# define paths and create working directories
system.mkdir_p(tree.odm_texturing)
if not args.use_3dmesh: system.mkdir_p(tree.odm_25dtexturing)
# check if we rerun cell or not
rerun_cell = (args.rerun is not None and
args.rerun == 'mvs_texturing') or \
(args.rerun_all) or \
(args.rerun_from is not None and
'mvs_texturing' in args.rerun_from)
runs = [{
'out_dir': tree.odm_texturing,
'model': tree.odm_mesh,
@ -64,7 +34,7 @@ class ODMMvsTexCell(ecto.Cell):
for r in runs:
odm_textured_model_obj = os.path.join(r['out_dir'], tree.odm_textured_model_obj)
if not io.file_exists(odm_textured_model_obj) or rerun_cell:
if not io.file_exists(odm_textured_model_obj) or self.rerun():
log.ODM_DEBUG('Writing MVS Textured file in: %s'
% odm_textured_model_obj)
@ -76,15 +46,15 @@ class ODMMvsTexCell(ecto.Cell):
keepUnseenFaces = ""
nadir = ""
if (self.params.skip_vis_test):
if (self.params.get('skip_vis_test')):
skipGeometricVisibilityTest = "--skip_geometric_visibility_test"
if (self.params.skip_glob_seam_leveling):
if (self.params.get('skip_glob_seam_leveling')):
skipGlobalSeamLeveling = "--skip_global_seam_leveling"
if (self.params.skip_loc_seam_leveling):
if (self.params.get('skip_loc_seam_leveling')):
skipLocalSeamLeveling = "--skip_local_seam_leveling"
if (self.params.skip_hole_fill):
if (self.params.get('skip_hole_fill')):
skipHoleFilling = "--skip_hole_filling"
if (self.params.keep_unseen_faces):
if (self.params.get('keep_unseen_faces')):
keepUnseenFaces = "--keep_unseen_faces"
if (r['nadir']):
nadir = '--nadir_mode'
@ -94,14 +64,14 @@ class ODMMvsTexCell(ecto.Cell):
'bin': context.mvstex_path,
'out_dir': io.join_paths(r['out_dir'], "odm_textured_model"),
'model': r['model'],
'dataTerm': self.params.data_term,
'outlierRemovalType': self.params.outlier_rem_type,
'dataTerm': self.params.get('data_term'),
'outlierRemovalType': self.params.get('outlier_rem_type'),
'skipGeometricVisibilityTest': skipGeometricVisibilityTest,
'skipGlobalSeamLeveling': skipGlobalSeamLeveling,
'skipLocalSeamLeveling': skipLocalSeamLeveling,
'skipHoleFilling': skipHoleFilling,
'keepUnseenFaces': keepUnseenFaces,
'toneMapping': self.params.tone_mapping,
'toneMapping': self.params.get('tone_mapping'),
'nadirMode': nadir,
'nadirWeight': 2 ** args.texturing_nadir_weight - 1,
'nvm_file': io.join_paths(tree.opensfm, "reconstruction.nvm")
@ -128,10 +98,3 @@ class ODMMvsTexCell(ecto.Cell):
log.ODM_WARNING('Found a valid ODM Texture file in: %s'
% odm_textured_model_obj)
outputs.reconstruction = reconstruction
if args.time:
system.benchmark(start_time, tree.benchmarking, 'Texturing')
log.ODM_INFO('Running ODM Texturing Cell - Finished')
return ecto.OK if args.end_with != 'mvs_texturing' else ecto.QUIT

Wyświetl plik

@ -1,174 +1,104 @@
import ecto
import os
from opendm import context
from opendm import types
from opendm import io
from opendm import system
from opendm import log
from dataset import ODMLoadDatasetCell
from run_opensfm import ODMOpenSfMCell
from mve import ODMMveCell
from odm_slam import ODMSlamCell
from odm_meshing import ODMeshingCell
from mvstex import ODMMvsTexCell
from odm_georeferencing import ODMGeoreferencingCell
from odm_orthophoto import ODMOrthoPhotoCell
from odm_dem import ODMDEMCell
from dataset import ODMLoadDatasetStage
from run_opensfm import ODMOpenSfMStage
from mve import ODMMveStage
from odm_slam import ODMSlamStage
from odm_meshing import ODMeshingStage
from mvstex import ODMMvsTexStage
from odm_georeferencing import ODMGeoreferencingStage
from odm_orthophoto import ODMOrthoPhotoStage
from odm_dem import ODMDEMStage
from odm_filterpoints import ODMFilterPoints
class ODMApp(ecto.BlackBox):
"""ODMApp - a class for ODM Activities
"""
def __init__(self, *args, **kwargs):
ecto.BlackBox.__init__(self, *args, **kwargs)
self.tree = None
@staticmethod
def declare_direct_params(p):
p.declare("args", "The application arguments.", {})
@staticmethod
def declare_cells(p):
class ODMApp:
def __init__(self, args):
"""
Implement the virtual function from the base class
Only cells from which something is forwarded have to be declared
Initializes the application and defines the ODM application pipeline stages
"""
cells = {'args': ecto.Constant(value=p.args),
'dataset': ODMLoadDatasetCell(verbose=p.args.verbose,
proj=p.args.proj),
'opensfm': ODMOpenSfMCell(use_exif_size=False,
feature_process_size=p.args.resize_to,
feature_min_frames=p.args.min_num_features,
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(),
'mve': ODMMveCell(),
dataset = ODMLoadDatasetStage('dataset', args,
verbose=args.verbose,
proj=args.proj)
opensfm = ODMOpenSfMStage('opensfm', args,
use_exif_size=False,
feature_process_size=args.resize_to,
feature_min_frames=args.min_num_features,
processes=args.max_concurrency,
matching_gps_neighbors=args.matcher_neighbors,
matching_gps_distance=args.matcher_distance,
fixed_camera_params=args.use_fixed_camera_params,
hybrid_bundle_adjustment=args.use_hybrid_bundle_adjustment)
slam = ODMSlamStage('slam', args)
mve = ODMMveStage('mve', args)
filterpoints = ODMFilterPoints('odm_filterpoints', args)
meshing = ODMeshingStage('odm_meshing', args,
max_vertex=args.mesh_size,
oct_tree=args.mesh_octree_depth,
samples=args.mesh_samples,
point_weight=args.mesh_point_weight,
max_concurrency=args.max_concurrency,
verbose=args.verbose)
texturing = ODMMvsTexStage('mvs_texturing', args,
data_term=args.texturing_data_term,
outlier_rem_type=args.texturing_outlier_removal_type,
skip_vis_test=args.texturing_skip_visibility_test,
skip_glob_seam_leveling=args.texturing_skip_global_seam_leveling,
skip_loc_seam_leveling=args.texturing_skip_local_seam_leveling,
skip_hole_fill=args.texturing_skip_hole_filling,
keep_unseen_faces=args.texturing_keep_unseen_faces,
tone_mapping=args.texturing_tone_mapping)
georeferencing = ODMGeoreferencingStage('odm_georeferencing', args,
gcp_file=args.gcp,
use_exif=args.use_exif,
verbose=args.verbose)
dem = ODMDEMStage('odm_dem', args,
max_concurrency=args.max_concurrency,
verbose=args.verbose)
orthophoto = ODMOrthoPhotoStage('odm_orthophoto', args,
resolution=args.orthophoto_resolution,
no_tiled=args.orthophoto_no_tiled,
compress=args.orthophoto_compression,
bigtiff=args.orthophoto_bigtiff,
build_overviews=args.build_overviews,
max_concurrency=args.max_concurrency,
verbose=args.verbose)
if not args.video:
# Normal pipeline
self.first_stage = dataset
'filterpoints': ODMFilterPoints(),
'meshing': ODMeshingCell(max_vertex=p.args.mesh_size,
oct_tree=p.args.mesh_octree_depth,
samples=p.args.mesh_samples,
point_weight=p.args.mesh_point_weight,
max_concurrency=p.args.max_concurrency,
verbose=p.args.verbose),
'texturing': ODMMvsTexCell(data_term=p.args.texturing_data_term,
outlier_rem_type=p.args.texturing_outlier_removal_type,
skip_vis_test=p.args.texturing_skip_visibility_test,
skip_glob_seam_leveling=p.args.texturing_skip_global_seam_leveling,
skip_loc_seam_leveling=p.args.texturing_skip_local_seam_leveling,
skip_hole_fill=p.args.texturing_skip_hole_filling,
keep_unseen_faces=p.args.texturing_keep_unseen_faces,
tone_mapping=p.args.texturing_tone_mapping),
'georeferencing': ODMGeoreferencingCell(gcp_file=p.args.gcp,
use_exif=p.args.use_exif,
verbose=p.args.verbose),
'dem': ODMDEMCell(max_concurrency=p.args.max_concurrency,
verbose=p.args.verbose),
'orthophoto': ODMOrthoPhotoCell(resolution=p.args.orthophoto_resolution,
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)
}
dataset.connect(opensfm)
return cells
def configure(self, p, _i, _o):
tree = types.ODM_Tree(p.args.project_path, p.args.images, p.args.gcp)
self.tree = ecto.Constant(value=tree)
# TODO(dakota) put this somewhere better maybe
if p.args.time and io.file_exists(tree.benchmarking):
# Delete the previously made file
os.remove(tree.benchmarking)
with open(tree.benchmarking, 'a') as b:
b.write('ODM Benchmarking file created %s\nNumber of Cores: %s\n\n' % (system.now(), context.num_cores))
def connections(self, p):
if p.args.video:
return self.slam_connections(p)
# load the dataset
connections = [self.tree[:] >> self.dataset['tree'],
self.args[:] >> self.dataset['args']]
# run opensfm with images from load dataset
connections += [self.tree[:] >> self.opensfm['tree'],
self.args[:] >> self.opensfm['args'],
self.dataset['reconstruction'] >> self.opensfm['reconstruction']]
if p.args.use_opensfm_dense or p.args.fast_orthophoto:
# filter points from opensfm point cloud
connections += [self.tree[:] >> self.filterpoints['tree'],
self.args[:] >> self.filterpoints['args'],
self.opensfm['reconstruction'] >> self.filterpoints['reconstruction']]
if args.use_opensfm_dense or args.fast_orthophoto:
opensfm.connect(filterpoints)
else:
opensfm.connect(mve) \
.connect(filterpoints)
filterpoints \
.connect(meshing) \
.connect(texturing) \
.connect(georeferencing) \
.connect(dem) \
.connect(orthophoto)
else:
# run mve
connections += [self.tree[:] >> self.mve['tree'],
self.args[:] >> self.mve['args'],
self.opensfm['reconstruction'] >> self.mve['reconstruction']]
# SLAM pipeline
# TODO: this is broken and needs work
log.ODM_WARNING("SLAM module is currently broken. We could use some help fixing this. If you know Python, get in touch at https://community.opendronemap.org.")
self.first_stage = slam
# filter points from mve point cloud
connections += [self.tree[:] >> self.filterpoints['tree'],
self.args[:] >> self.filterpoints['args'],
self.mve['reconstruction'] >> self.filterpoints['reconstruction']]
slam.connect(mve) \
.connect(meshing) \
.connect(texturing)
# create mesh
connections += [self.tree[:] >> self.meshing['tree'],
self.args[:] >> self.meshing['args'],
self.filterpoints['reconstruction'] >> self.meshing['reconstruction']]
# create odm texture
connections += [self.tree[:] >> self.texturing['tree'],
self.args[:] >> self.texturing['args'],
self.meshing['reconstruction'] >> self.texturing['reconstruction']]
# create odm georeference
connections += [self.tree[:] >> self.georeferencing['tree'],
self.args[:] >> self.georeferencing['args'],
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
connections += [self.tree[:] >> self.orthophoto['tree'],
self.args[:] >> self.orthophoto['args'],
self.georeferencing['reconstruction'] >> self.orthophoto['reconstruction']]
return connections
def slam_connections(self, p):
"""Get connections used when running from video instead of images."""
connections = []
# run slam cell
connections += [self.tree[:] >> self.slam['tree'],
self.args[:] >> self.slam['args']]
connections += [self.tree[:] >> self.mve['tree'],
self.args[:] >> self.mve['args'],
self.slam['reconstruction'] >> self.mve['reconstruction']]
# create odm mesh
connections += [self.tree[:] >> self.meshing['tree'],
self.args[:] >> self.meshing['args'],
self.mve['reconstruction'] >> self.meshing['reconstruction']]
# create odm texture
connections += [self.tree[:] >> self.texturing['tree'],
self.args[:] >> self.texturing['args'],
self.meshing['reconstruction'] >> self.texturing['reconstruction']]
return connections
def execute(self):
self.first_stage.run()

Wyświetl plik

@ -1,4 +1,4 @@
import ecto, os, json
import os, json
from shutil import copyfile
from opendm import io
@ -10,34 +10,11 @@ from opendm import gsd
from opendm.dem import commands
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", [])
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
class ODMDEMStage(types.ODM_Stage):
def process(self, args, outputs):
tree = outputs['tree']
las_model_found = io.file_exists(tree.odm_georeferencing_model_laz)
# 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)
log.ODM_INFO('Classify: ' + str(args.pc_classify))
log.ODM_INFO('Create DSM: ' + str(args.dsm))
log.ODM_INFO('Create DTM: ' + str(args.dtm))
@ -51,7 +28,7 @@ class ODMDEMCell(ecto.Cell):
if args.pc_classify and las_model_found:
pc_classify_marker = os.path.join(odm_dem_root, 'pc_classify_done.txt')
if not io.file_exists(pc_classify_marker) or rerun_cell:
if not io.file_exists(pc_classify_marker) or self.rerun():
log.ODM_INFO("Classifying {} using Simple Morphological Filter".format(tree.odm_georeferencing_model_laz))
commands.classify(tree.odm_georeferencing_model_laz,
args.smrf_scalar,
@ -75,7 +52,7 @@ class ODMDEMCell(ecto.Cell):
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:
self.rerun():
products = []
if args.dsm: products.append('dsm')
@ -108,15 +85,9 @@ class ODMDEMCell(ecto.Cell):
'COMPRESS': 'LZW',
'BLOCKXSIZE': 512,
'BLOCKYSIZE': 512,
'NUM_THREADS': self.params.max_concurrency
'NUM_THREADS': self.params.get('max_concurrency')
})
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

Wyświetl plik

@ -1,39 +1,21 @@
import ecto, os
import os
from opendm import log
from opendm import io
from opendm import system
from opendm import context
from opendm import point_cloud
from opendm import types
class ODMFilterPoints(ecto.Cell):
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", [])
class ODMFilterPoints(types.ODM_Stage):
def process(self, args, outputs):
tree = outputs['tree']
reconstruction = outputs['reconstruction']
def process(self, inputs, outputs):
# Benchmarking
start_time = system.now_raw()
log.ODM_INFO('Running ODM FilterPoints Cell')
# get inputs
tree = inputs.tree
args = inputs.args
reconstruction = inputs.reconstruction
# check if we rerun cell or not
rerun_cell = (args.rerun is not None and
args.rerun == 'odm_filterpoints') or \
(args.rerun_all) or \
(args.rerun_from is not None and
'odm_filterpoints' in args.rerun_from)
if not os.path.exists(tree.odm_filterpoints): system.mkdir_p(tree.odm_filterpoints)
# check if reconstruction was done before
if not io.file_exists(tree.filtered_point_cloud) or rerun_cell:
if not io.file_exists(tree.filtered_point_cloud) or self.rerun():
if args.fast_orthophoto:
inputPointCloud = os.path.join(tree.opensfm, 'reconstruction.ply')
elif args.use_opensfm_dense:
@ -49,11 +31,3 @@ class ODMFilterPoints(ecto.Cell):
else:
log.ODM_WARNING('Found a valid point cloud file in: %s' %
tree.filtered_point_cloud)
outputs.reconstruction = reconstruction
if args.time:
system.benchmark(start_time, tree.benchmarking, 'MVE')
log.ODM_INFO('Running ODM FilterPoints Cell - Finished')
return ecto.OK if args.end_with != 'odm_filterpoints' else ecto.QUIT

Wyświetl plik

@ -1,4 +1,3 @@
import ecto
import os
import struct
import pipes
@ -12,45 +11,17 @@ from opendm.cropper import Cropper
from opendm import point_cloud
class ODMGeoreferencingCell(ecto.Cell):
def declare_params(self, params):
params.declare("gcp_file", 'path to the file containing the ground control '
'points used for georeferencing.The file needs to '
'be on the following line format: \neasting '
'northing height pixelrow pixelcol imagename', 'gcp_list.txt')
params.declare("use_exif", 'use exif', False)
params.declare("verbose", 'print additional messages to console', False)
class ODMGeoreferencingStage(types.ODM_Stage):
def process(self, args, outputs):
tree = outputs['tree']
reconstruction = outputs['reconstruction']
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 ODM Georeferencing Cell')
# get inputs
args = inputs.args
tree = inputs.tree
reconstruction = inputs.reconstruction
gcpfile = tree.odm_georeferencing_gcp
doPointCloudGeo = True
transformPointCloud = True
verbose = '-verbose' if self.params.verbose else ''
verbose = '-verbose' if self.params.get('verbose') else ''
geo_ref = reconstruction.georef
# check if we rerun cell or not
rerun_cell = (args.rerun is not None and
args.rerun == 'odm_georeferencing') or \
(args.rerun_all) or \
(args.rerun_from is not None and
'odm_georeferencing' in args.rerun_from)
runs = [{
'georeferencing_dir': tree.odm_georeferencing,
'texturing_dir': tree.odm_texturing,
@ -78,7 +49,7 @@ class ODMGeoreferencingCell(ecto.Cell):
odm_georeferencing_model_txt_geo_file = os.path.join(r['georeferencing_dir'], tree.odm_georeferencing_model_txt_geo)
if not io.file_exists(odm_georeferencing_model_obj_geo) or \
not io.file_exists(tree.odm_georeferencing_model_laz) or rerun_cell:
not io.file_exists(tree.odm_georeferencing_model_laz) or self.rerun():
# odm_georeference definitions
kwargs = {
@ -111,7 +82,7 @@ class ODMGeoreferencingCell(ecto.Cell):
# Check to see if the GCP file exists
if not self.params.use_exif and (self.params.gcp_file or tree.odm_georeferencing_gcp):
if not self.params.get('use_exif') and (self.params.get('gcp_file') or tree.odm_georeferencing_gcp):
log.ODM_INFO('Found %s' % gcpfile)
try:
system.run('{bin}/odm_georef -bundleFile {bundle} -imagesPath {imgs} -imagesListPath {imgs_list} '
@ -121,7 +92,7 @@ class ODMGeoreferencingCell(ecto.Cell):
'-outputCoordFile {coords}'.format(**kwargs))
except Exception:
log.ODM_EXCEPTION('Georeferencing failed. ')
return ecto.QUIT
exit(1)
elif io.file_exists(tree.opensfm_transformation) and io.file_exists(tree.odm_georeferencing_coords):
log.ODM_INFO('Running georeferencing with OpenSfM transformation matrix')
system.run('{bin}/odm_georef -bundleFile {bundle} -inputTransformFile {input_trans_file} -inputCoordFile {coords} '
@ -188,11 +159,3 @@ class ODMGeoreferencingCell(ecto.Cell):
else:
log.ODM_WARNING('Found a valid georeferenced model in: %s'
% tree.odm_georeferencing_model_laz)
outputs.reconstruction = reconstruction
if args.time:
system.benchmark(start_time, tree.benchmarking, 'Georeferencing')
log.ODM_INFO('Running ODM Georeferencing Cell - Finished')
return ecto.OK if args.end_with != 'odm_georeferencing' else ecto.QUIT

Wyświetl plik

@ -1,4 +1,4 @@
import ecto, os, math
import os, math
from opendm import log
from opendm import io
@ -6,62 +6,29 @@ from opendm import system
from opendm import context
from opendm import mesh
from opendm import gsd
from opendm import types
class ODMeshingCell(ecto.Cell):
def declare_params(self, params):
params.declare("max_vertex", 'The maximum vertex count of the output '
'mesh', 100000)
params.declare("oct_tree", 'Oct-tree depth used in the mesh reconstruction, '
'increase to get more vertices, recommended '
'values are 8-12', 9)
params.declare("samples", 'Number of points per octree node, recommended '
'value: 1.0', 1)
params.declare("point_weight", "specifies the importance that interpolation of the point samples is given in the formulation of the screened Poisson equation.", 4)
params.declare("max_concurrency", 'max threads', context.num_cores)
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", "Clusters output. list of ODMReconstructions", [])
outputs.declare("reconstruction", "Clusters output. list of ODMReconstructions", [])
def process(self, inputs, outputs):
# Benchmarking
start_time = system.now_raw()
log.ODM_INFO('Running ODM Meshing Cell')
# get inputs
args = inputs.args
tree = inputs.tree
reconstruction = inputs.reconstruction
class ODMeshingStage(types.ODM_Stage):
def process(self, args, outputs):
tree = outputs['tree']
reconstruction = outputs['reconstruction']
# define paths and create working directories
system.mkdir_p(tree.odm_meshing)
# check if we rerun cell or not
rerun_cell = (args.rerun is not None and
args.rerun == 'odm_meshing') or \
(args.rerun_all) or \
(args.rerun_from is not None and
'odm_meshing' in args.rerun_from)
# Create full 3D model unless --skip-3dmodel is set
if not args.skip_3dmodel:
if not io.file_exists(tree.odm_mesh) or rerun_cell:
if not io.file_exists(tree.odm_mesh) or self.rerun():
log.ODM_DEBUG('Writing ODM Mesh file in: %s' % tree.odm_mesh)
mesh.screened_poisson_reconstruction(tree.filtered_point_cloud,
tree.odm_mesh,
depth=self.params.oct_tree,
samples=self.params.samples,
maxVertexCount=self.params.max_vertex,
pointWeight=self.params.point_weight,
threads=self.params.max_concurrency,
verbose=self.params.verbose)
depth=self.params.get('oct_tree'),
samples=self.params.get('samples'),
maxVertexCount=self.params.get('max_vertex'),
pointWeight=self.params.get('point_weight'),
threads=self.params.get('max_concurrency'),
verbose=self.params.get('verbose'))
else:
log.ODM_WARNING('Found a valid ODM Mesh file in: %s' %
@ -70,7 +37,7 @@ class ODMeshingCell(ecto.Cell):
# Always generate a 2.5D mesh
# unless --use-3dmesh is set.
if not args.use_3dmesh:
if not io.file_exists(tree.odm_25dmesh) or rerun_cell:
if not io.file_exists(tree.odm_25dmesh) or self.rerun():
log.ODM_DEBUG('Writing ODM 2.5D Mesh file in: %s' % tree.odm_25dmesh)
ortho_resolution = gsd.cap_resolution(args.orthophoto_resolution, tree.opensfm_reconstruction, ignore_gsd=args.ignore_gsd) / 100.0
@ -94,20 +61,13 @@ class ODMeshingCell(ecto.Cell):
mesh.create_25dmesh(tree.filtered_point_cloud, tree.odm_25dmesh,
dsm_radius=dsm_radius,
dsm_resolution=dsm_resolution,
depth=self.params.oct_tree,
maxVertexCount=self.params.max_vertex,
samples=self.params.samples,
verbose=self.params.verbose,
depth=self.params.get('oct_tree'),
maxVertexCount=self.params.get('max_vertex'),
samples=self.params.get('samples'),
verbose=self.params.get('verbose'),
available_cores=args.max_concurrency,
method='poisson' if args.fast_orthophoto else 'gridded')
else:
log.ODM_WARNING('Found a valid ODM 2.5D Mesh file in: %s' %
tree.odm_25dmesh)
outputs.reconstruction = reconstruction
if args.time:
system.benchmark(start_time, tree.benchmarking, 'Meshing')
log.ODM_INFO('Running ODM Meshing Cell - Finished')
return ecto.OK if args.end_with != 'odm_meshing' else ecto.QUIT

Wyświetl plik

@ -1,4 +1,4 @@
import ecto, os
import os
from opendm import io
from opendm import log
@ -10,45 +10,16 @@ from opendm.concurrency import get_max_memory
from opendm.cropper import Cropper
class ODMOrthoPhotoCell(ecto.Cell):
def declare_params(self, params):
params.declare("resolution", 'Orthophoto resolution in cm / pixel', 5)
params.declare("no_tiled", 'Do not tile tiff', False)
params.declare("compress", 'Compression type', 'DEFLATE')
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", [])
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 Orthophoto Cell')
# get inputs
args = self.inputs.args
tree = self.inputs.tree
reconstruction = inputs.reconstruction
verbose = '-verbose' if self.params.verbose else ''
class ODMOrthoPhotoStage(types.ODM_Stage):
def process(self, args, outputs):
tree = outputs['tree']
reconstruction = outputs['reconstruction']
verbose = '-verbose' if self.params.get('verbose') else ''
# define paths and create working directories
system.mkdir_p(tree.odm_orthophoto)
# check if we rerun cell or not
rerun_cell = (args.rerun is not None and
args.rerun == 'odm_orthophoto') or \
(args.rerun_all) or \
(args.rerun_from is not None and
'odm_orthophoto' in args.rerun_from)
if not io.file_exists(tree.odm_orthophoto_file) or rerun_cell:
if not io.file_exists(tree.odm_orthophoto_file) or self.rerun():
# odm_orthophoto definitions
kwargs = {
@ -56,7 +27,7 @@ class ODMOrthoPhotoCell(ecto.Cell):
'log': tree.odm_orthophoto_log,
'ortho': tree.odm_orthophoto_file,
'corners': tree.odm_orthophoto_corners,
'res': 1.0 / (gsd.cap_resolution(self.params.resolution, tree.opensfm_reconstruction, ignore_gsd=args.ignore_gsd) / 100.0),
'res': 1.0 / (gsd.cap_resolution(self.params.get('resolution'), tree.opensfm_reconstruction, ignore_gsd=args.ignore_gsd) / 100.0),
'verbose': verbose
}
@ -118,17 +89,17 @@ class ODMOrthoPhotoCell(ecto.Cell):
'uly': uly,
'lrx': lrx,
'lry': lry,
'tiled': '' if self.params.no_tiled else '-co TILED=yes ',
'compress': self.params.compress,
'predictor': '-co PREDICTOR=2 ' if self.params.compress in
'tiled': '' if self.params.get('no_tiled') else '-co TILED=yes ',
'compress': self.params.get('compress'),
'predictor': '-co PREDICTOR=2 ' if self.params.get('compress') in
['LZW', 'DEFLATE'] else '',
'proj': georef.projection.srs,
'bigtiff': self.params.bigtiff,
'bigtiff': self.params.get('bigtiff'),
'png': tree.odm_orthophoto_file,
'tiff': tree.odm_orthophoto_tif,
'log': tree.odm_orthophoto_tif_log,
'max_memory': get_max_memory(),
'threads': self.params.max_concurrency
'threads': self.params.get('max_concurrency')
}
system.run('gdal_translate -a_ullr {ulx} {uly} {lrx} {lry} '
@ -146,16 +117,16 @@ class ODMOrthoPhotoCell(ecto.Cell):
if args.crop > 0:
shapefile_path = os.path.join(tree.odm_georeferencing, 'odm_georeferenced_model.bounds.shp')
Cropper.crop(shapefile_path, tree.odm_orthophoto_tif, {
'TILED': 'NO' if self.params.no_tiled else 'YES',
'COMPRESS': self.params.compress,
'PREDICTOR': '2' if self.params.compress in ['LZW', 'DEFLATE'] else '1',
'BIGTIFF': self.params.bigtiff,
'TILED': 'NO' if self.params.get('no_tiled') else 'YES',
'COMPRESS': self.params.get('compress'),
'PREDICTOR': '2' if self.params.get('compress') in ['LZW', 'DEFLATE'] else '1',
'BIGTIFF': self.params.get('bigtiff'),
'BLOCKXSIZE': 512,
'BLOCKYSIZE': 512,
'NUM_THREADS': self.params.max_concurrency
'NUM_THREADS': self.params.get('max_concurrency')
})
if self.params.build_overviews:
if self.params.get('build_overviews'):
log.ODM_DEBUG("Building Overviews")
kwargs = {
'orthophoto': tree.odm_orthophoto_tif,
@ -174,9 +145,3 @@ class ODMOrthoPhotoCell(ecto.Cell):
else:
log.ODM_WARNING('Found a valid orthophoto in: %s' % tree.odm_orthophoto_file)
if args.time:
system.benchmark(start_time, tree.benchmarking, 'Orthophoto')
log.ODM_INFO('Running ODM OrthoPhoto Cell - Finished')
return ecto.OK if args.end_with != 'odm_orthophoto' else ecto.QUIT

Wyświetl plik

@ -2,40 +2,23 @@
import os
import ecto
from opendm import log
from opendm import io
from opendm import system
from opendm import context
from opendm import types
class ODMSlamCell(ecto.Cell):
class ODMSlamStage(types.ODM_Stage):
"""Run odm_slam on a video and export to opensfm format."""
def declare_params(self, params):
"""Cell parameters."""
pass
def declare_io(self, params, inputs, outputs):
"""Cell inputs and outputs."""
inputs.declare("tree", "Struct with paths", [])
inputs.declare("args", "The application arguments.", {})
outputs.declare("reconstruction", "list of ODMReconstructions", [])
def process(self, inputs, outputs):
"""Run the cell."""
log.ODM_INFO('Running OMD Slam Cell')
# get inputs
tree = self.inputs.tree
args = self.inputs.args
def process(self, args, outputs):
tree = outputs['tree']
video = os.path.join(tree.root_path, args.video)
slam_config = os.path.join(tree.root_path, args.slam_config)
if not video:
log.ODM_ERROR('No video provided')
return ecto.QUIT
exit(1)
# create working directories
system.mkdir_p(tree.opensfm)
@ -46,11 +29,8 @@ class ODMSlamCell(ecto.Cell):
trajectory = os.path.join(tree.opensfm, 'KeyFrameTrajectory.txt')
map_points = os.path.join(tree.opensfm, 'MapPoints.txt')
# check if we rerun cell or not
rerun_cell = args.rerun == 'slam'
# check if slam was run before
if not io.file_exists(trajectory) or rerun_cell:
if not io.file_exists(trajectory) or self.rerun():
# run slam binary
system.run(' '.join([
'cd {} &&'.format(tree.opensfm),
@ -64,7 +44,7 @@ class ODMSlamCell(ecto.Cell):
trajectory))
# check if trajectory was exported to opensfm before
if not io.file_exists(tree.opensfm_reconstruction) or rerun_cell:
if not io.file_exists(tree.opensfm_reconstruction) or self.rerun():
# convert slam to opensfm
system.run(' '.join([
'cd {} &&'.format(tree.opensfm),
@ -85,7 +65,7 @@ class ODMSlamCell(ecto.Cell):
tree.opensfm_reconstruction))
# check if reconstruction was exported to bundler before
if not io.file_exists(tree.opensfm_bundle_list) or rerun_cell:
if not io.file_exists(tree.opensfm_bundle_list) or self.rerun():
# convert back to bundler's format
system.run(
'PYTHONPATH={} {}/bin/export_bundler {}'.format(
@ -95,5 +75,3 @@ class ODMSlamCell(ecto.Cell):
'Found a valid Bundler file in: {}'.format(
tree.opensfm_reconstruction))
log.ODM_INFO('Running OMD Slam Cell - Finished')
return ecto.OK if args.end_with != 'odm_slam' else ecto.QUIT

Wyświetl plik

@ -1,4 +1,3 @@
import ecto
import sys
import os
@ -8,51 +7,21 @@ from opendm import system
from opendm import context
from opendm import gsd
from opendm import point_cloud
from opendm import types
class ODMOpenSfMCell(ecto.Cell):
def declare_params(self, params):
params.declare("use_exif_size", "The application arguments.", False)
params.declare("feature_process_size", "The application arguments.", 2400)
params.declare("feature_min_frames", "The application arguments.", 4000)
params.declare("processes", "The application arguments.", context.num_cores)
params.declare("matching_gps_neighbors", "The application arguments.", 8)
params.declare("matching_gps_distance", "The application arguments.", 0)
params.declare("fixed_camera_params", "Optimize internal camera parameters", True)
params.declare("hybrid_bundle_adjustment", "Use local + global bundle adjustment", 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 ODM OpenSfM Cell')
# get inputs
tree = inputs.tree
args = inputs.args
reconstruction = inputs.reconstruction
class ODMOpenSfMStage(types.ODM_Stage):
def process(self, args, outputs):
tree = outputs['tree']
reconstruction = outputs['reconstruction']
photos = reconstruction.photos
if not photos:
log.ODM_ERROR('Not enough photos in photos array to start OpenSfM')
return ecto.QUIT
exit(1)
# create working directories
system.mkdir_p(tree.opensfm)
# check if we rerun cell or not
rerun_cell = (args.rerun is not None and
args.rerun == 'opensfm') or \
(args.rerun_all) or \
(args.rerun_from is not None and
'opensfm' in args.rerun_from)
if args.fast_orthophoto:
output_file = io.join_paths(tree.opensfm, 'reconstruction.ply')
elif args.use_opensfm_dense:
@ -61,7 +30,7 @@ class ODMOpenSfMCell(ecto.Cell):
output_file = tree.opensfm_reconstruction
# check if reconstruction was done before
if not io.file_exists(output_file) or rerun_cell:
if not io.file_exists(output_file) or self.rerun():
# create file list
list_path = io.join_paths(tree.opensfm, 'image_list.txt')
has_alt = True
@ -73,16 +42,16 @@ class ODMOpenSfMCell(ecto.Cell):
# create config file for OpenSfM
config = [
"use_exif_size: %s" % ('no' if not self.params.use_exif_size else 'yes'),
"feature_process_size: %s" % self.params.feature_process_size,
"feature_min_frames: %s" % self.params.feature_min_frames,
"processes: %s" % self.params.processes,
"matching_gps_neighbors: %s" % self.params.matching_gps_neighbors,
"use_exif_size: %s" % ('no' if not self.params.get('use_exif_size') else 'yes'),
"feature_process_size: %s" % self.params.get('feature_process_size'),
"feature_min_frames: %s" % self.params.get('feature_min_frames'),
"processes: %s" % self.params.get('processes'),
"matching_gps_neighbors: %s" % self.params.get('matching_gps_neighbors'),
"depthmap_method: %s" % args.opensfm_depthmap_method,
"depthmap_resolution: %s" % args.depthmap_resolution,
"depthmap_min_patch_sd: %s" % args.opensfm_depthmap_min_patch_sd,
"depthmap_min_consistent_views: %s" % args.opensfm_depthmap_min_consistent_views,
"optimize_camera_parameters: %s" % ('no' if self.params.fixed_camera_params else 'yes')
"optimize_camera_parameters: %s" % ('no' if self.params.get('fixed_camera_params') else 'yes')
]
if has_alt:
@ -100,7 +69,7 @@ class ODMOpenSfMCell(ecto.Cell):
config.append("local_bundle_radius: 1") # Max image graph distance for images to be included in local bundle adjustment
if args.matcher_distance > 0:
config.append("matching_gps_distance: %s" % self.params.matching_gps_distance)
config.append("matching_gps_distance: %s" % self.params.get('matching_gps_distance'))
if tree.odm_georeferencing_gcp:
config.append("bundle_use_gcp: yes")
@ -114,7 +83,7 @@ class ODMOpenSfMCell(ecto.Cell):
# run OpenSfM reconstruction
matched_done_file = io.join_paths(tree.opensfm, 'matching_done.txt')
if not io.file_exists(matched_done_file) or rerun_cell:
if not io.file_exists(matched_done_file) or self.rerun():
system.run('PYTHONPATH=%s %s/bin/opensfm extract_metadata %s' %
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
system.run('PYTHONPATH=%s %s/bin/opensfm detect_features %s' %
@ -127,14 +96,14 @@ class ODMOpenSfMCell(ecto.Cell):
log.ODM_WARNING('Found a feature matching done progress file in: %s' %
matched_done_file)
if not io.file_exists(tree.opensfm_tracks) or rerun_cell:
if not io.file_exists(tree.opensfm_tracks) or self.rerun():
system.run('PYTHONPATH=%s %s/bin/opensfm create_tracks %s' %
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
else:
log.ODM_WARNING('Found a valid OpenSfM tracks file in: %s' %
tree.opensfm_tracks)
if not io.file_exists(tree.opensfm_reconstruction) or rerun_cell:
if not io.file_exists(tree.opensfm_reconstruction) or self.rerun():
system.run('PYTHONPATH=%s %s/bin/opensfm reconstruct %s' %
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
else:
@ -159,7 +128,7 @@ class ODMOpenSfMCell(ecto.Cell):
else:
image_scale = 1.0
if not io.file_exists(tree.opensfm_reconstruction_nvm) or rerun_cell:
if not io.file_exists(tree.opensfm_reconstruction_nvm) or self.rerun():
system.run('PYTHONPATH=%s %s/bin/opensfm export_visualsfm --image_extension png --scale_focal %s %s' %
(context.pyopencv_path, context.opensfm_path, image_scale, tree.opensfm))
else:
@ -188,7 +157,7 @@ class ODMOpenSfMCell(ecto.Cell):
tree.opensfm_reconstruction)
# check if reconstruction was exported to bundler before
if not io.file_exists(tree.opensfm_bundle_list) or rerun_cell:
if not io.file_exists(tree.opensfm_bundle_list) or self.rerun():
# convert back to bundler's format
system.run('PYTHONPATH=%s %s/bin/export_bundler %s' %
(context.pyopencv_path, context.opensfm_path, tree.opensfm))
@ -199,11 +168,3 @@ class ODMOpenSfMCell(ecto.Cell):
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))
outputs.reconstruction = reconstruction
if args.time:
system.benchmark(start_time, tree.benchmarking, 'OpenSfM')
log.ODM_INFO('Running ODM OpenSfM Cell - Finished')
return ecto.OK if args.end_with != 'opensfm' else ecto.QUIT

Wyświetl plik

@ -1,108 +0,0 @@
import unittest
import os
import shutil
import ecto
from opendm import config
from opendm import context
from scripts.odm_app import ODMApp
from ecto.opts import scheduler_options, run_plasm
parser = config.parser
scheduler_options(parser)
options = config.config()
def appSetup(options):
app = ODMApp(args=options)
plasm = ecto.Plasm()
plasm.insert(app)
return app, plasm
def setup_module():
# Run tests
print '%s' % options
options.project_path = context.tests_data_path
# options.rerun_all = True
app, plasm = appSetup(options)
print 'Run Setup: Initial Run'
run_plasm(options, plasm)
# options.rerun_all = False
def teardown_module():
# Delete generated test directories
dirnames = ['opensfm', 'odm_meshing',
'odm_texturing', 'odm_georeferencing', 'odm_orthophoto']
for n in dirnames:
rmpath = os.path.join(context.tests_data_path, n)
if os.path.exists(rmpath):
shutil.rmtree(rmpath)
class TestOpenSfM(unittest.TestCase):
"""
Tests the OpenSfM module
"""
def setUp(self):
options.rerun = 'opensfm'
self.app, self.plasm = appSetup(options)
run_plasm(options, self.plasm)
def test_opensfm(self):
# Test configuration
self.assertTrue(os.path.isfile(self.app.opensfm.inputs.tree.opensfm_reconstruction))
class TestMeshing(unittest.TestCase):
def setUp(self):
options.rerun = 'odm_meshing'
self.app, self.plasm = appSetup(options)
run_plasm(options, self.plasm)
def test_meshing(self):
self.assertTrue(os.path.isfile(self.app.meshing.inputs.tree.odm_mesh))
class TestTexturing(unittest.TestCase):
def setUp(self):
options.rerun = 'odm_texturing'
self.app, self.plasm = appSetup(options)
run_plasm(options, self.plasm)
def test_texturing(self):
self.assertTrue(os.path.isfile(self.app.texturing.inputs.tree.odm_textured_model_obj))
class TestGeoreferencing(unittest.TestCase):
def setUp(self):
options.rerun = 'odm_georeferencing'
self.app, self.plasm = appSetup(options)
run_plasm(options, self.plasm)
def test_georef(self):
self.assertTrue(os.path.isfile(self.app.georeferencing.inputs.tree.odm_georeferencing_coords) &
os.path.isfile(self.app.georeferencing.inputs.tree.odm_georeferencing_model_obj_geo))
def test_las_out(self):
self.assertTrue(os.path.isfile(os.path.join(self.app.georeferencing.inputs.tree.odm_georeferencing,
"odm_georeferenced_model.laz")))
class TestOrthophoto(unittest.TestCase):
def setUp(self):
options.rerun = 'odm_orthophoto'
self.app, self.plasm = appSetup(options)
run_plasm(options, self.plasm)
def test_orthophoto(self):
self.assertTrue(os.path.isfile(self.app.orthophoto.inputs.tree.odm_orthophoto_file))
if __name__ == '__main__':
unittest.main()