diff --git a/bin/meshroom_photogrammetry b/bin/meshroom_photogrammetry index e099b18a..4770dd2c 100755 --- a/bin/meshroom_photogrammetry +++ b/bin/meshroom_photogrammetry @@ -43,7 +43,7 @@ if not args.input and not args.inputImages: print('Nothing to compute. You need to set --input or --inputImages.') exit(1) -graph = multiview.photogrammetryPipeline(output=args.output, inputFolder=args.input, inputImages=args.inputImages) +graph = multiview.photogrammetry(inputFolder=args.input, inputImages=args.inputImages, output=args.output) graph.findNode('PrepareDenseScene').scale.value = args.scale if args.save: diff --git a/meshroom/multiview.py b/meshroom/multiview.py index 9d545a4f..5117c44c 100644 --- a/meshroom/multiview.py +++ b/meshroom/multiview.py @@ -2,7 +2,7 @@ import os import fnmatch import re -from .core.graph import Graph, GraphModification +from meshroom.core.graph import Graph, GraphModification def findFiles(folder, patterns): @@ -17,11 +17,23 @@ def findFiles(folder, patterns): return outFiles -def photogrammetryPipeline(output='', inputFolder='', inputImages=[], inputViewpoints=[]): - # type: () -> Graph - graph = Graph('pipeline') +def photogrammetry(inputFolder='', inputImages=(), inputViewpoints=(), output=''): + """ + Create a new Graph with a complete photogrammetry pipeline. + + Args: + inputFolder (str, optional): folder containing image files + inputImages (list of str, optional): list of image file paths + inputViewpoints (list of Viewpoint, optional): list of Viewpoints + output (str, optional): the path to export reconstructed model to + + Returns: + Graph: the created graph + """ + graph = Graph('Photogrammetry') with GraphModification(graph): - cameraInit = graph.addNewNode('CameraInit') + sfmNodes, mvsNodes = photogrammetryPipeline(graph) + cameraInit = sfmNodes[0] if inputFolder: images = findFiles(inputFolder, ['*.jpg', '*.png']) cameraInit.viewpoints.extend([{'path': image} for image in images]) @@ -29,35 +41,101 @@ def photogrammetryPipeline(output='', inputFolder='', inputImages=[], inputViewp cameraInit.viewpoints.extend([{'path': image} for image in inputImages]) if inputViewpoints: cameraInit.viewpoints.extend(inputViewpoints) - featureExtraction = graph.addNewNode('FeatureExtraction', - input=cameraInit.output) - imageMatching = graph.addNewNode('ImageMatching', - input=featureExtraction.input, - featuresFolder=featureExtraction.output, - ) - featureMatching = graph.addNewNode('FeatureMatching', - input=imageMatching.input, - featuresFolder=imageMatching.featuresFolder, - imagePairsList=imageMatching.output) - structureFromMotion = graph.addNewNode('StructureFromMotion', - input=featureMatching.input, - featuresFolder=featureMatching.featuresFolder, - matchesFolder=featureMatching.output) - prepareDenseScene = graph.addNewNode('PrepareDenseScene', - input=structureFromMotion.output) - cameraConnection = graph.addNewNode('CameraConnection', - ini=prepareDenseScene.ini) - depthMap = graph.addNewNode('DepthMap', - ini=cameraConnection.ini) - depthMapFilter = graph.addNewNode('DepthMapFilter', - depthMapFolder=depthMap.output, - ini=depthMap.ini) - meshing = graph.addNewNode('Meshing', - depthMapFolder=depthMapFilter.depthMapFolder, - depthMapFilterFolder=depthMapFilter.output, - ini=depthMapFilter.ini) - texturing = graph.addNewNode('Texturing', - ini=meshing.ini, - inputDenseReconstruction=meshing.outputDenseReconstruction) + + if output: + texturing = mvsNodes[-1] + graph.addNewNode('Publish', output=output, inputFiles=[texturing.outputMesh, + texturing.outputMaterial, + texturing.outputTextures]) return graph + +def photogrammetryPipeline(graph): + """ + Instantiate a complete photogrammetry pipeline inside 'graph'. + + Args: + graph (Graph/UIGraph): the graph in which nodes should be instantiated + + Returns: + list of Node: the created nodes + """ + sfmNodes = sfmPipeline(graph) + mvsNodes = mvsPipeline(graph, sfmNodes[-1]) + + return sfmNodes, mvsNodes + + +def sfmPipeline(graph): + """ + Instantiate a SfM pipeline inside 'graph'. + Args: + graph (Graph/UIGraph): the graph in which nodes should be instantiated + + Returns: + list of Node: the created nodes + """ + cameraInit = graph.addNewNode('CameraInit') + + featureExtraction = graph.addNewNode('FeatureExtraction', + input=cameraInit.output) + imageMatching = graph.addNewNode('ImageMatching', + input=featureExtraction.input, + featuresFolder=featureExtraction.output, + ) + featureMatching = graph.addNewNode('FeatureMatching', + input=imageMatching.input, + featuresFolder=imageMatching.featuresFolder, + imagePairsList=imageMatching.output) + structureFromMotion = graph.addNewNode('StructureFromMotion', + input=featureMatching.input, + featuresFolder=featureMatching.featuresFolder, + matchesFolder=featureMatching.output) + return [ + cameraInit, + featureExtraction, + imageMatching, + featureMatching, + structureFromMotion + ] + + +def mvsPipeline(graph, sfm=None): + """ + Instantiate a MVS pipeline inside 'graph'. + + Args: + graph (Graph/UIGraph): the graph in which nodes should be instantiated + sfm (Node, optional): if specified, connect the MVS pipeline to this StructureFromMotion node + + Returns: + list of Node: the created nodes + """ + if sfm and not sfm.nodeType == "StructureFromMotion": + raise ValueError("Invalid node type. Expected StructureFromMotion, got {}.".format(sfm.nodeType)) + + prepareDenseScene = graph.addNewNode('PrepareDenseScene', + input=sfm.output if sfm else "") + cameraConnection = graph.addNewNode('CameraConnection', + ini=prepareDenseScene.ini) + depthMap = graph.addNewNode('DepthMap', + ini=cameraConnection.ini) + depthMapFilter = graph.addNewNode('DepthMapFilter', + depthMapFolder=depthMap.output, + ini=depthMap.ini) + meshing = graph.addNewNode('Meshing', + depthMapFolder=depthMapFilter.depthMapFolder, + depthMapFilterFolder=depthMapFilter.output, + ini=depthMapFilter.ini) + texturing = graph.addNewNode('Texturing', + ini=meshing.ini, + inputDenseReconstruction=meshing.outputDenseReconstruction) + + return [ + prepareDenseScene, + cameraConnection, + depthMap, + depthMapFilter, + meshing, + texturing + ] diff --git a/meshroom/ui/reconstruction.py b/meshroom/ui/reconstruction.py index 33f81add..40b85863 100755 --- a/meshroom/ui/reconstruction.py +++ b/meshroom/ui/reconstruction.py @@ -170,7 +170,7 @@ class Reconstruction(UIGraph): @Slot() def new(self): """ Create a new photogrammetry pipeline. """ - self.setGraph(multiview.photogrammetryPipeline()) + self.setGraph(multiview.photogrammetry()) def onGraphChanged(self): """ React to the change of the internal graph. """ diff --git a/tests/test_multiviewPipeline.py b/tests/test_multiviewPipeline.py index fc9e82fe..c312e461 100644 --- a/tests/test_multiviewPipeline.py +++ b/tests/test_multiviewPipeline.py @@ -4,15 +4,15 @@ import meshroom.multiview def test_multiviewPipeline(): - graph1 = meshroom.multiview.photogrammetryPipeline(inputImages=['/non/existing/fileA']) - graph2 = meshroom.multiview.photogrammetryPipeline(inputImages=[]) - graph2b = meshroom.multiview.photogrammetryPipeline(inputImages=[]) - graph3 = meshroom.multiview.photogrammetryPipeline(inputImages=['/non/existing/file1', '/non/existing/file2']) - graph4 = meshroom.multiview.photogrammetryPipeline(inputViewpoints=[ + graph1 = meshroom.multiview.photogrammetry(inputImages=['/non/existing/fileA']) + graph2 = meshroom.multiview.photogrammetry(inputImages=[]) + graph2b = meshroom.multiview.photogrammetry(inputImages=[]) + graph3 = meshroom.multiview.photogrammetry(inputImages=['/non/existing/file1', '/non/existing/file2']) + graph4 = meshroom.multiview.photogrammetry(inputViewpoints=[ {'path': '/non/existing/file1', 'intrinsicId': 50}, {'path': '/non/existing/file2', 'intrinsicId': 55} ]) - graph4b = meshroom.multiview.photogrammetryPipeline(inputViewpoints=[ + graph4b = meshroom.multiview.photogrammetry(inputViewpoints=[ {'path': '/non/existing/file1', 'intrinsicId': 50}, {'path': '/non/existing/file2', 'intrinsicId': 55} ])