Skip to content

Commit 5ed67e2

Browse files
authored
Merge pull request #2867 from alicevision/dev/batchPublish
[bin] `meshroom_batch`: Add support for setting multiple `Publish` nodes' output folders
2 parents ccf2993 + c0b8027 commit 5ed67e2

File tree

3 files changed

+68
-30
lines changed

3 files changed

+68
-30
lines changed

.git-blame-ignore-revs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# [bin] `meshroom_batch`: Minor clean-up in the file
2+
15d9ecd888faa7216cfc5d97d473f5717a3118a3
13
# [core] Linting following CI's flake8 report
24
9b4bd68d5aa9e5c3af5e4bfc4fe6aae06437ca88
35
# [tests] Linting following CI's flake8 report

bin/meshroom_batch

Lines changed: 65 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
#!/usr/bin/env python
22
import argparse
3-
import os
4-
import sys
53
import distutils.util
64
import json
5+
import logging
6+
import os
77
import re
8+
import sys
89

10+
import meshroom.core.graph
911
from meshroom import setupEnvironment
1012

1113
setupEnvironment()
1214

13-
import meshroom.core.graph
14-
import logging
15-
1615
meshroom.core.initPipelines()
1716

1817
parser = argparse.ArgumentParser(
@@ -56,19 +55,25 @@ general_group.add_argument(
5655
metavar='FILE.mg / PIPELINE',
5756
type=str,
5857
default=os.environ.get('MESHROOM_DEFAULT_PIPELINE', 'photogrammetry'),
59-
help='Template pipeline among those listed or a Meshroom file containing a custom pipeline to run on input images:\n'
60-
+ '\n'.join([' - ' + p for p in meshroom.core.pipelineTemplates])
61-
+ '\nRequirements: the graph must contain one CameraInit node, and one Publish node if --output is set.',
58+
help='Template pipeline among those listed or a Meshroom file containing a custom pipeline '
59+
'to run on input images:\n' +
60+
'\n'.join([' - ' + p for p in meshroom.core.pipelineTemplates]) +
61+
'\nRequirements: the graph must contain at least one CameraInit node, and at least '
62+
'one Publish node if --output is set.',
6263
)
6364

6465
general_group.add_argument(
65-
'-o', '--output', metavar='FOLDER', type=str, required=False,
66+
'-o', '--output', metavar='FOLDER PUBLISH_INSTANCE=FOLDER',
67+
type=str, required=False, nargs='*',
6668
help='Output folder where results should be copied to. '
69+
'If the output folder is provided without specifiying the instance of a Publish node, '
70+
'all the Publish nodes in the scene will be set with the same ouput folder value. '
6771
'If not set, results will have to be retrieved directly from the cache folder.')
6872

6973
general_group.add_argument(
7074
'-s', '--save', metavar='FILE', type=str, required=False,
71-
help='Save the configured Meshroom graph to a project file. It will setup the cache folder accordingly if not explicitly changed by --cache.')
75+
help='Save the configured Meshroom graph to a project file. It will setup the cache folder '
76+
'accordingly if not explicitly changed by --cache.')
7277

7378
general_group.add_argument(
7479
'--submit', help='Submit on renderfarm instead of local computation.',
@@ -130,7 +135,6 @@ advanced_group.add_argument(
130135

131136
args = parser.parse_args()
132137

133-
134138
logStringToPython = {
135139
'fatal': logging.FATAL,
136140
'error': logging.ERROR,
@@ -154,11 +158,12 @@ with meshroom.core.graph.GraphModification(graph):
154158
# initialize template pipeline
155159
loweredPipelineTemplates = {k.lower(): v for k, v in meshroom.core.pipelineTemplates.items()}
156160
if args.pipeline.lower() in loweredPipelineTemplates:
157-
graph.initFromTemplate(loweredPipelineTemplates[args.pipeline.lower()], publishOutputs=True if args.output else False)
161+
graph.initFromTemplate(loweredPipelineTemplates[args.pipeline.lower()],
162+
publishOutputs=True if args.output else False)
158163
else:
159164
# custom pipeline
160165
graph.initFromTemplate(args.pipeline, publishOutputs=True if args.output else False)
161-
166+
162167
def parseInputs(inputs, uniqueInitNode):
163168
"""Utility method for parsing the input and inputRecursive arguments."""
164169
mapInputs = {}
@@ -193,14 +198,14 @@ with meshroom.core.graph.GraphModification(graph):
193198
nodeInputs = inputGroup[-1].split(';')
194199
mapInputs[nodeName] = [os.path.abspath(path) for path in nodeInputs]
195200
return mapInputs
196-
201+
197202
# get init nodes
198203
initNodes = graph.findInitNodes()
199204
uniqueInitNode = initNodes[0] if (len(initNodes) == 1) else None
200205

201206
# parse inputs for each init node
202207
mapInput = parseInputs(args.input, uniqueInitNode)
203-
208+
204209
# parse recursive inputs for each init node
205210
mapInputRecursive = parseInputs(args.inputRecursive, uniqueInitNode)
206211

@@ -221,15 +226,48 @@ with meshroom.core.graph.GraphModification(graph):
221226
graph.setVerbose(args.verbose)
222227

223228
if args.output:
224-
# if there is more than 1 Publish node, they will all be set to the same output;
225-
# depending on what they are connected to, some input files might be overwritten in the output folder
226-
# (e.g. if two Publish nodes are connected to two Texturing nodes)
229+
# The output folders for Publish nodes can be set as follows:
230+
# - for each node, the output folder is specified following the
231+
# "Publish_name=/output/folder/path" convention.
232+
# - a path is provided without specifying which Publish node should be set with it:
233+
# all the Publish nodes will be set with it.
234+
# - some Publish nodes have their path specified, and another path is provided
235+
# without specifying a node: all Publish nodes with dedicated will have their own
236+
# output folders set, and those which have not been specified will be set with the
237+
# other path.
238+
# - some Publish nodes have their output folder specified while others do not: all
239+
# the nodes with specified folders will use the provided values, and those without
240+
# any will be set with the output folder of the first specified Publish node.
241+
# - several output folders are provided without specifying any node: the last one will
242+
# be used to set all the Publish nodes' output folders.
243+
244+
# Check that there is at least one Publish node
227245
publishNodes = graph.nodesOfType('Publish')
228-
if len(publishNodes) > 0:
229-
for node in publishNodes:
230-
node.output.value = os.path.abspath(args.output)
231-
else:
232-
raise RuntimeError("meshroom_batch requires a pipeline graph with at least one Publish node, none found.")
246+
if len(publishNodes) == 0:
247+
raise RuntimeError('meshroom_batch requires a pipeline graph with at least ' +
248+
'one Publish node, none found.')
249+
250+
reExtract = re.compile(r'(\w+)=(.*)') # NodeName=value
251+
globalPublishPath = ""
252+
for p in args.output:
253+
result = reExtract.match(p)
254+
if not result: # If the argument is only a path, set it for the global path
255+
globalPublishPath = p
256+
continue
257+
258+
node, value = result.groups()
259+
for i, n in enumerate(publishNodes): # Find the correct Publish node in the list
260+
if n.name == node: # If found, set the value, and remove it from the list
261+
n.output.value = value
262+
publishNodes.pop(i)
263+
if globalPublishPath == "": # Fallback in case some nodes would have no path
264+
globalPublishPath = value
265+
break
266+
267+
for n in publishNodes: # Set the remaining Publish nodes with the global path
268+
n.output.value = globalPublishPath
269+
else:
270+
print(f'No output set, results will be available in the cache folder: "{graph.cacheDir}"')
233271

234272
if args.overrides:
235273
with open(args.overrides, encoding='utf-8', errors='ignore') as f:
@@ -267,9 +305,6 @@ if args.save:
267305
graph.save(args.save, setupProjectFile=not bool(args.cache))
268306
print(f'File successfully saved: "{args.save}"')
269307

270-
if not args.output:
271-
print(f'No output set, results will be available in the cache folder: "{graph.cacheDir}"')
272-
273308
# find end nodes (None will compute all graph)
274309
toNodes = graph.findNodes(args.toNode) if args.toNode else None
275310

@@ -278,10 +313,11 @@ if args.submit:
278313
if not args.save:
279314
raise ValueError('Need to save the project to file to submit on renderfarm.')
280315
# submit on renderfarm
281-
meshroom.core.graph.submit(args.save, args.submitter, toNode=args.toNode, submitLabel=args.submitLabel)
316+
meshroom.core.graph.submit(args.save, args.submitter, toNode=args.toNode,
317+
submitLabel=args.submitLabel)
282318
elif args.compute:
283319
# find end nodes (None will compute all graph)
284320
toNodes = graph.findNodes(args.toNode) if args.toNode else None
285321
# start computation
286-
meshroom.core.graph.executeGraph(graph, toNodes=toNodes, forceCompute=args.forceCompute, forceStatus=args.forceStatus)
287-
322+
meshroom.core.graph.executeGraph(graph, toNodes=toNodes, forceCompute=args.forceCompute,
323+
forceStatus=args.forceStatus)

meshroom/nodes/general/Publish.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def processChunk(self, chunk):
7575
raise RuntimeError(error)
7676

7777
if not os.path.exists(chunk.node.output.value):
78-
os.mkdir(chunk.node.output.value)
78+
os.makedirs(chunk.node.output.value)
7979

8080
for iFile, oFile in outFiles.items():
8181
if os.path.isdir(iFile): # If the input is a directory, copy the directory's content

0 commit comments

Comments
 (0)