mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-08-03 16:58:24 +02:00
If the "save" argument has been provided, the graph should be saved outside of the `GraphModificatioǹ event, once it has been set up entirely and resolved. When it is saved within the `GraphModification`, UIDs are not yet available. This does not cause any issue for the rest of the execution if the graph is to be computed locally, since the UIDs will be computed right after (but the UIDs will still never be written in the project file). However, when submitting the graph to a renderfarm, the project file is loaded again. If it does not contain any UID: - all the nodes will present the `UidConflict` compatibility issue - key errors will be raised when trying to apply the links between nodes since they contain direct references to UIDs, which cannot be resolved as they are not present in the project file.
203 lines
8.9 KiB
Python
Executable file
203 lines
8.9 KiB
Python
Executable file
#!/usr/bin/env python
|
|
import argparse
|
|
import os
|
|
import sys
|
|
import distutils.util
|
|
|
|
import meshroom
|
|
meshroom.setupEnvironment()
|
|
|
|
import meshroom.core.graph
|
|
from meshroom import multiview
|
|
from meshroom.core.desc import InitNode
|
|
import logging
|
|
|
|
|
|
parser = argparse.ArgumentParser(description='Launch the full photogrammetry or Panorama HDR pipeline.')
|
|
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('-I', '--inputRecursive', metavar='FOLDERS/IMAGES', type=str, nargs='*',
|
|
default=[],
|
|
help='Input folders containing all images recursively.')
|
|
|
|
parser.add_argument('-p', '--pipeline', metavar='FILE.mg/' + '/'.join(meshroom.core.pipelineTemplates), type=str, default='photogrammetry',
|
|
help='Template pipeline among those listed or a Meshroom file containing a custom pipeline to run on input images. '
|
|
'Requirements: the graph must contain one CameraInit node, '
|
|
'and one Publish node if --output is set.')
|
|
|
|
parser.add_argument('--overrides', metavar='SETTINGS', type=str, default=None,
|
|
help='A JSON file containing the graph parameters override.')
|
|
|
|
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.')
|
|
|
|
parser.add_argument('--cache', metavar='FOLDER', type=str,
|
|
default=None,
|
|
help='Custom cache folder to write computation results. '
|
|
'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 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],
|
|
help='Downscale factor override for DepthMap estimation. '
|
|
'By default (-1): use pipeline default value.')
|
|
|
|
parser.add_argument('--toNode', metavar='NODE', type=str, nargs='*',
|
|
default=None,
|
|
help='Process the node(s) with its dependencies.')
|
|
|
|
parser.add_argument('--forceStatus', help='Force computation if status is RUNNING or SUBMITTED.',
|
|
action='store_true')
|
|
parser.add_argument('--forceCompute', help='Compute in all cases even if already computed.',
|
|
action='store_true')
|
|
|
|
parser.add_argument('--submit', help='Submit on renderfarm instead of local computation.',
|
|
action='store_true')
|
|
parser.add_argument("--submitLabel",
|
|
type=str,
|
|
default='{projectName} [Meshroom]',
|
|
help="Label of a node in the submitter")
|
|
parser.add_argument('--submitter',
|
|
type=str,
|
|
default='SimpleFarm',
|
|
help='Execute job with a specific submitter.')
|
|
|
|
parser.add_argument('-v', '--verbose', help="Verbosity level", default='',
|
|
choices=['', 'fatal', 'error', 'warning', 'info', 'debug', 'trace'],)
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
logStringToPython = {
|
|
'fatal': logging.FATAL,
|
|
'error': logging.ERROR,
|
|
'warning': logging.WARNING,
|
|
'info': logging.INFO,
|
|
'debug': logging.DEBUG,
|
|
'trace': logging.DEBUG,
|
|
}
|
|
if args.verbose:
|
|
logging.getLogger().setLevel(logStringToPython[args.verbose])
|
|
|
|
|
|
def getInitNode(g):
|
|
"""
|
|
Helper function to get the Init node in the graph 'g' and raise an exception if there is no or
|
|
multiple candidates.
|
|
"""
|
|
nodes = g.findInitNodes()
|
|
if len(nodes) == 0:
|
|
raise RuntimeError("meshroom_batch requires an Init node in the pipeline.")
|
|
elif len(nodes) > 1:
|
|
raise RuntimeError("meshroom_batch requires exactly one Init node in the pipeline, {} found: {}"
|
|
.format(len(nodes), str(nodes)))
|
|
return nodes[0]
|
|
|
|
|
|
if not args.input and not args.inputRecursive:
|
|
print('Nothing to compute. You need to set --input or --inputRecursive.')
|
|
sys.exit(1)
|
|
|
|
graph = multiview.Graph(name=args.pipeline)
|
|
|
|
with multiview.GraphModification(graph):
|
|
# initialize template pipeline
|
|
loweredPipelineTemplates = dict((k.lower(), v) for k, v in meshroom.core.pipelineTemplates.items())
|
|
if args.pipeline.lower() in loweredPipelineTemplates:
|
|
graph.load(loweredPipelineTemplates[args.pipeline.lower()], setupProjectFile=False, publishOutputs=True if args.output else False)
|
|
else:
|
|
# custom pipeline
|
|
graph.load(args.pipeline, setupProjectFile=False, publishOutputs=True if args.output else False)
|
|
|
|
# get init node and initialize it
|
|
initNode = getInitNode(graph)
|
|
initNode.nodeDesc.initialize(initNode, args.input, args.inputRecursive)
|
|
|
|
if not graph.canComputeLeaves:
|
|
raise RuntimeError("Graph cannot be computed. Check for compatibility issues.")
|
|
|
|
if args.verbose:
|
|
graph.setVerbose(args.verbose)
|
|
|
|
if args.output:
|
|
# if there is more than 1 Publish node, they will all be set to the same output;
|
|
# depending on what they are connected to, some input files might be overwritten in the output folder
|
|
# (e.g. if two Publish nodes are connected to two Texturing nodes)
|
|
publishNodes = graph.nodesOfType('Publish')
|
|
if len(publishNodes) > 0:
|
|
for node in publishNodes:
|
|
node.output.value = args.output
|
|
else:
|
|
raise RuntimeError("meshroom_batch requires a pipeline graph with at least one Publish node, none found.")
|
|
|
|
if args.overrides:
|
|
import json
|
|
with open(args.overrides, 'r', encoding='utf-8', errors='ignore') as f:
|
|
data = json.load(f)
|
|
for nodeName, overrides in data.items():
|
|
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[\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 == ':':
|
|
nodesOfType = graph.nodesOfType(node)
|
|
if not nodesOfType:
|
|
raise ValueError('No node with the type "{}" in the scene.'.format(node))
|
|
for n in nodesOfType:
|
|
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.nodesOfType('DepthMap'):
|
|
node.downscale.value = args.scale
|
|
|
|
# setup cache directory
|
|
graph.cacheDir = args.cache if args.cache else meshroom.core.defaultCacheFolder
|
|
|
|
if args.save:
|
|
graph.save(args.save, setupProjectFile=not bool(args.cache))
|
|
print('File successfully saved: "{}"'.format(args.save))
|
|
|
|
if not args.output:
|
|
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
|
|
|
|
if args.submit:
|
|
if not args.save:
|
|
raise ValueError('Need to save the project to file to submit on renderfarm.')
|
|
# submit on renderfarm
|
|
meshroom.core.graph.submit(args.save, args.submitter, toNode=args.toNode, submitLabel=args.submitLabel)
|
|
elif args.compute:
|
|
# 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)
|
|
|