Meshroom/bin/meshroom_batch
Candice Bentéjac 8bef398bed Remove workarounds and backports for Python 2 support
In particular:
- In common/, remove the backport of weakref.WeakMethod
- In docs/ and ui/, use the standard FileNotFoundError class
- Use built-in open() instead of io.open(), and stop importing io
- In core/stats.py, use the standard implementation of xml.etree instead
of the C one
2022-12-05 17:16:21 +01:00

205 lines
8.8 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 getOnlyNodeOfType(g, nodeType):
""" Helper function to get a node of 'nodeType' in the graph 'g' and raise if no or multiple candidates. """
nodes = g.nodesOfType(nodeType)
if len(nodes) != 1:
raise RuntimeError("meshroom_batch requires a pipeline graph with exactly one '{}' node, {} found."
.format(nodeType, len(nodes)))
return nodes[0]
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)
else:
# custom pipeline
graph.load(args.pipeline, setupProjectFile=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:
publish = getOnlyNodeOfType(graph, 'Publish')
publish.output.value = args.output
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)