Skip to content

Commit

Permalink
Merge pull request #632 from alicevision/dev/commandline_args
Browse files Browse the repository at this point in the history
Improve command line arguments
  • Loading branch information
fabiencastan authored Sep 26, 2019
2 parents 6b7efdc + ab7b940 commit 7950b9f
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 54 deletions.
88 changes: 63 additions & 25 deletions bin/meshroom_photogrammetry
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import argparse
import os
import sys
import distutils.util

import meshroom
meshroom.setupEnvironment()
Expand All @@ -10,15 +11,15 @@ import meshroom.core.graph
from meshroom import multiview

parser = argparse.ArgumentParser(description='Launch the full photogrammetry pipeline.')
parser.add_argument('--input', metavar='FOLDER_OR_SFM', type=str,
default='',
help='Input folder containing images or file (.sfm or .json) '
parser.add_argument('-i', '--input', metavar='SFM/FOLDERS/IMAGES', type=str, nargs='*',
default=[],
help='Input folder containing images or folders of images or file (.sfm or .json) '
'with images paths and optionally predefined camera intrinsics.')
parser.add_argument('--inputImages', metavar='IMAGES', type=str, nargs='*',
parser.add_argument('-I', '--inputRecursive', metavar='FOLDERS/IMAGES', type=str, nargs='*',
default=[],
help='Input images.')
help='Input folders containing all images recursively.')

parser.add_argument('--pipeline', metavar='MESHROOM_FILE', type=str, required=False,
parser.add_argument('-p', '--pipeline', metavar='MESHROOM_FILE', type=str, required=False,
help='Meshroom file containing a pre-configured photogrammetry pipeline to run on input images. '
'If not set, the default photogrammetry pipeline will be used. '
'Requirements: the graph must contain one CameraInit node, '
Expand All @@ -27,7 +28,10 @@ parser.add_argument('--pipeline', metavar='MESHROOM_FILE', type=str, required=Fa
parser.add_argument('--overrides', metavar='SETTINGS', type=str, default=None,
help='A JSON file containing the graph parameters override.')

parser.add_argument('--output', metavar='FOLDER', type=str, required=False,
parser.add_argument('--paramOverrides', metavar='NODETYPE:param=value NODEINSTANCE.param=value', type=str, default=None, nargs='*',
help='Override specific parameters directly from the command line (by node type or by node names).')

parser.add_argument('-o', '--output', metavar='FOLDER', type=str, required=False,
help='Output folder where results should be copied to. '
'If not set, results will have to be retrieved directly from the cache folder.')

Expand All @@ -37,7 +41,10 @@ parser.add_argument('--cache', metavar='FOLDER', type=str,
'If not set, the default cache folder will be used: ' + meshroom.core.defaultCacheFolder)

parser.add_argument('--save', metavar='FILE', type=str, required=False,
help='Save the configured Meshroom project to a file (instead of running it).')
help='Save the configured Meshroom graph to a project file. It will setup the cache folder accordingly if not explicitly changed by --cache.')

parser.add_argument('--compute', metavar='<yes/no>', type=lambda x: bool(distutils.util.strtobool(x)), default=True, required=False,
help='You can set it to <no/false/0> to disable the computation.')

parser.add_argument('--scale', type=int, default=-1,
choices=[-1, 1, 2, 4, 8, 16],
Expand Down Expand Up @@ -65,24 +72,32 @@ def getOnlyNodeOfType(g, nodeType):
return nodes[0]


if not args.input and not args.inputImages:
print('Nothing to compute. You need to set --input or --inputImages.')
if not args.input and not args.inputRecursive:
print('Nothing to compute. You need to set --input or --inputRecursive.')
sys.exit(1)

views, intrinsics = [], []
# Build image files list from inputImages arguments
images = [f for f in args.inputImages if multiview.isImageFile(f)]
images = []

hasSearchedForImages = False

if args.input:
if os.path.isdir(args.input):
# args.input is a folder: extend images list with images in that folder
images += multiview.findImageFiles(args.input)
elif os.path.isfile(args.input) and os.path.splitext(args.input)[-1] in ('.json', '.sfm'):
if len(args.input) == 1 and os.path.isfile(args.input[0]) and os.path.splitext(args.input[0])[-1] in ('.json', '.sfm'):
# args.input is a sfmData file: setup pre-calibrated views and intrinsics
from meshroom.nodes.aliceVision.CameraInit import readSfMData
views, intrinsics = readSfMData(args.input)
views, intrinsics = readSfMData(args.input[0])
else:
raise RuntimeError(args.input + ': format not supported.')
images += multiview.findImageFiles(args.input, recursive=False)
hasSearchedForImages = True

if args.inputRecursive:
images += multiview.findImageFiles(args.inputRecursive, recursive=True)
hasSearchedForImages = True

if hasSearchedForImages and not images:
print("No image found")
exit(-1)

# initialize photogrammetry pipeline
if args.pipeline:
Expand Down Expand Up @@ -121,24 +136,47 @@ if args.overrides:
for attrName, value in overrides.items():
graph.findNode(nodeName).attribute(attrName).value = value

if args.paramOverrides:
print("\n")
import re
reExtract = re.compile('(\w+)([:.])(\w+)=(.*)')
for p in args.paramOverrides:
result = reExtract.match(p)
if not result:
raise ValueError('Invalid param override: ' + str(p))
node, t, param, value = result.groups()
if t == ':':
nodesByType = graph.nodesByType(node)
if not nodesByType:
raise ValueError('No node with the type "{}" in the scene.'.format(node))
for n in nodesByType:
print('Overrides {node}.{param}={value}'.format(node=node, param=param, value=value))
n.attribute(param).value = value
elif t == '.':
print('Overrides {node}.{param}={value}'.format(node=node, param=param, value=value))
graph.findNode(node).attribute(param).value = value
else:
raise ValueError('Invalid param override: ' + str(p))
print("\n")

# setup DepthMap downscaling
if args.scale > 0:
for node in graph.nodesByType('DepthMap'):
node.downscale.value = args.scale

if args.save:
graph.save(args.save)
print('File successfully saved:', args.save)
sys.exit(0)

# setup cache directory
graph.cacheDir = args.cache if args.cache else meshroom.core.defaultCacheFolder

if args.save:
graph.save(args.save, setupFileRef=not bool(args.cache))
print('File successfully saved: "{}"'.format(args.save))

if not args.output:
print('No output set, results will be available in {}'.format(graph.cacheDir))
print('No output set, results will be available in the cache folder: "{}"'.format(graph.cacheDir))

# find end nodes (None will compute all graph)
toNodes = graph.findNodes(args.toNode) if args.toNode else None

# start computation
meshroom.core.graph.executeGraph(graph, toNodes=toNodes, forceCompute=args.forceCompute, forceStatus=args.forceStatus)
if args.compute:
# start computation
meshroom.core.graph.executeGraph(graph, toNodes=toNodes, forceCompute=args.forceCompute, forceStatus=args.forceStatus)
2 changes: 1 addition & 1 deletion meshroom/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ def loadAllNodes(folder):
nodeTypes = loadNodes(folder, package)
for nodeType in nodeTypes:
registerNodeType(nodeType)
print('Plugins loaded: ', ', '.join([nodeType.__name__ for nodeType in nodeTypes]))
logging.debug('Plugins loaded: ', ', '.join([nodeType.__name__ for nodeType in nodeTypes]))


def registerSubmitter(s):
Expand Down
29 changes: 23 additions & 6 deletions meshroom/core/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,15 @@ def fileFeatures(self):
return Graph.IO.getFeaturesForVersion(self.header.get(Graph.IO.Keys.FileVersion, "0.0"))

@Slot(str)
def load(self, filepath):
def load(self, filepath, setupProjectFile=True):
"""
Load a meshroom graph ".mg" file.
Args:
filepath: project filepath to load
setupProjectFile: Store the reference to the project file and setup the cache directory.
If false, it only loads the graph of the project file as a template.
"""
self.clear()
with open(filepath) as jsonFile:
fileData = json.load(jsonFile)
Expand Down Expand Up @@ -265,8 +273,9 @@ def load(self, filepath):
# Add node to the graph with raw attributes values
self._addNode(n, nodeName)

# Update filepath related members
self._setFilepath(filepath)
if setupProjectFile:
# Update filepath related members
self._setFilepath(filepath)

# Create graph edges by resolving attributes expressions
self._applyExpr()
Expand Down Expand Up @@ -896,7 +905,7 @@ def toDict(self):
def asString(self):
return str(self.toDict())

def save(self, filepath=None):
def save(self, filepath=None, setupProjectFile=True):
path = filepath or self._filepath
if not path:
raise ValueError("filepath must be specified for unsaved files.")
Expand All @@ -920,7 +929,7 @@ def save(self, filepath=None):
with open(path, 'w') as jsonFile:
json.dump(data, jsonFile, indent=4)

if path != self._filepath:
if path != self._filepath and setupProjectFile:
self._setFilepath(path)

def _setFilepath(self, filepath):
Expand All @@ -930,7 +939,9 @@ def _setFilepath(self, filepath):
Args:
filepath: the graph file path
"""
assert os.path.isfile(filepath)
if not os.path.isfile(filepath):
self._unsetFilepath()
return

if self._filepath == filepath:
return
Expand All @@ -942,6 +953,12 @@ def _setFilepath(self, filepath):
self.cacheDir = os.path.join(os.path.abspath(os.path.dirname(filepath)), meshroom.core.cacheFolderName)
self.filepathChanged.emit()

def _unsetFilepath(self):
self._filepath = ""
self.name = ""
self.cacheDir = meshroom.core.defaultCacheFolder
self.filepathChanged.emit()

def updateInternals(self, startNodes=None, force=False):
nodes, edges = self.dfsOnFinish(startNodes=startNodes)
for node in nodes:
Expand Down
38 changes: 29 additions & 9 deletions meshroom/multiview.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,36 @@ def isImageFile(filepath):
return os.path.splitext(filepath)[1].lower() in imageExtensions


def findImageFiles(folder):
def findImageFiles(folder, recursive=False):
"""
Return all files that are images in 'folder' based on their extensions.
Args:
folder (str): the folder to look into
folder (str): folder to look into or list of folder/files
Returns:
list: the list of image files.
list: the list of image files with a supported extension.
"""
return [os.path.join(folder, filename) for filename in os.listdir(folder) if isImageFile(filename)]
inputFolders = []
if isinstance(folder, (list, tuple)):
inputFolders = folder
else:
inputFolders.append(folder)

output = []
for currentFolder in inputFolders:
if os.path.isfile(currentFolder):
if isImageFile(currentFolder):
output.append(currentFolder)
continue
if recursive:
for root, directories, files in os.walk(currentFolder):
for filename in files:
if isImageFile(filename):
output.append(os.path.join(root, filename))
else:
output.extend([os.path.join(currentFolder, filename) for filename in os.listdir(currentFolder) if isImageFile(filename)])
return output


def photogrammetry(inputImages=list(), inputViewpoints=list(), inputIntrinsics=list(), output=''):
Expand All @@ -47,11 +66,12 @@ def photogrammetry(inputImages=list(), inputViewpoints=list(), inputIntrinsics=l
cameraInit.viewpoints.extend(inputViewpoints)
cameraInit.intrinsics.extend(inputIntrinsics)

if output:
texturing = mvsNodes[-1]
graph.addNewNode('Publish', output=output, inputFiles=[texturing.outputMesh,
texturing.outputMaterial,
texturing.outputTextures])
if output:
texturing = mvsNodes[-1]
graph.addNewNode('Publish', output=output, inputFiles=[texturing.outputMesh,
texturing.outputMaterial,
texturing.outputTextures])

return graph


Expand Down
41 changes: 36 additions & 5 deletions meshroom/ui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ class MeshroomApp(QApplication):
""" Meshroom UI Application. """
def __init__(self, args):
QtArgs = [args[0], '-style', 'fusion'] + args[1:] # force Fusion style by default

parser = argparse.ArgumentParser(prog=args[0], description='Launch Meshroom UI.', add_help=True)

parser.add_argument('project', metavar='PROJECT', type=str, nargs='?',
help='Meshroom project file (e.g. myProject.mg) or folder with images to reconstruct.')
parser.add_argument('-i', '--import', metavar='IMAGES/FOLDERS', type=str, nargs='*',
help='Import images or folder with images to reconstruct.')
parser.add_argument('-I', '--importRecursive', metavar='FOLDERS', type=str, nargs='*',
help='Import images to reconstruct from specified folder and sub-folders.')
parser.add_argument('-p', '--pipeline', metavar='MESHROOM_FILE', type=str, required=False,
help='Override the default Meshroom pipeline with this external graph.')

args = parser.parse_args(args[1:])

super(MeshroomApp, self).__init__(QtArgs)

self.setOrganizationName('AliceVision')
Expand Down Expand Up @@ -105,12 +119,29 @@ def __init__(self, args):
# request any potential computation to stop on exit
self.aboutToQuit.connect(r.stopChildThreads)

parser = argparse.ArgumentParser(prog=args[0], description='Launch Meshroom UI.')
parser.add_argument('--project', metavar='MESHROOM_FILE', type=str, required=False,
help='Meshroom project file (e.g. myProject.mg).')
args = parser.parse_args(args[1:])
if args.pipeline:
# the pipeline from the command line has the priority
r.setDefaultPipeline(args.pipeline)
else:
# consider the environment variable
defaultPipeline = os.environ.get("MESHROOM_DEFAULT_PIPELINE", "")
if defaultPipeline:
r.setDefaultPipeline(args.pipeline)

if args.project and not os.path.isfile(args.project):
raise RuntimeError(
"Meshroom Command Line Error: 'PROJECT' argument should be a Meshroom project file (.mg).\n"
"Invalid value: '{}'".format(args.project))

if args.project:
r.loadUrl(QUrl.fromLocalFile(args.project))
r.load(args.project)

# import is a python keyword, so we have to access the attribute by a string
if getattr(args, "import", None):
r.importImagesFromFolder(getattr(args, "import"), recursive=False)

if args.importRecursive:
r.importImagesFromFolder(args.importRecursive, recursive=True)

self.engine.load(os.path.normpath(url))

Expand Down
11 changes: 8 additions & 3 deletions meshroom/ui/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ def __init__(self, filepath='', parent=None):
self._layout = GraphLayout(self)
self._selectedNode = None
self._hoveredNode = None
self._defaultPipelineFilepath = None
if filepath:
self.load(filepath)

Expand Down Expand Up @@ -310,9 +311,13 @@ def stopChildThreads(self):
self.stopExecution()
self._chunksMonitor.stop()

def load(self, filepath):
def setDefaultPipeline(self, pipelineFilepath):
self._defaultPipelineFilepath = pipelineFilepath
self._graph.load(pipelineFilepath, setupProjectFile=False)

def load(self, filepath, setupProjectFile=True):
g = Graph('')
g.load(filepath)
g.load(filepath, setupProjectFile)
if not os.path.exists(g.cacheDir):
os.mkdir(g.cacheDir)
self.setGraph(g)
Expand Down Expand Up @@ -367,7 +372,7 @@ def stopExecution(self):
def submit(self, node=None):
""" Submit the graph to the default Submitter.
If a node is specified, submit this node and its uncomputed predecessors.
Otherwise, submit the whole
Otherwise, submit the whole
Notes:
Default submitter is specified using the MESHROOM_DEFAULT_SUBMITTER environment variable.
Expand Down
Loading

0 comments on commit 7950b9f

Please sign in to comment.