From bd88d16a476e175a11327c5c48a50c8659efede3 Mon Sep 17 00:00:00 2001 From: Anouk Liberge Date: Thu, 8 Aug 2019 18:29:06 +0200 Subject: [PATCH 01/42] [nodes][aliceVision] New node for hdri panorama stitching --- meshroom/nodes/aliceVision/HDRIstitching.py | 89 +++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 meshroom/nodes/aliceVision/HDRIstitching.py diff --git a/meshroom/nodes/aliceVision/HDRIstitching.py b/meshroom/nodes/aliceVision/HDRIstitching.py new file mode 100644 index 0000000000..af81410eec --- /dev/null +++ b/meshroom/nodes/aliceVision/HDRIstitching.py @@ -0,0 +1,89 @@ +__version__ = "1.0" + +from meshroom.core import desc + + +class HDRIstitching(desc.CommandLineNode): + commandLine = 'aliceVision_utils_fisheyeProjection {allParams}' + + inputs = [ + desc.ListAttribute( + elementDesc=desc.File( + name='inputFile', + label='Input File/Folder', + description="", + value='', + uid=[0], + ), + name='input', + label='Input Folder', + description="List of fisheye images or folder containing them." + ), + desc.FloatParam( + name='blurWidth', + label='Blur Width', + description="Blur width of alpha channel for all fisheye (between 0 and 1). \n" + "Determine the transitions sharpness.", + value=0.2, + range=(0, 1, 0.1), + uid=[0], + ), + desc.ListAttribute( + elementDesc=desc.FloatParam( + name='imageXRotation', + label='Image X Rotation', + description="", + value=0, + range=(-20, 20, 1), + uid=[0], + ), + name='xRotation', + label='X Rotations', + description="Rotations in degree on axis X (horizontal axis) for each image.", + ), + desc.ListAttribute( + elementDesc=desc.FloatParam( + name='imageYRotation', + label='Image Y Rotation', + description="", + value=0, + range=(-30, 30, 5), + uid=[0], + ), + name='yRotation', + label='Y Rotations', + description="Rotations in degree on axis Y (vertical axis) for each image.", + ), + desc.ListAttribute( + elementDesc=desc.FloatParam( + name='imageZRotation', + label='Image Z Rotation', + description="", + value=0, + range=(-10, 10, 1), + uid=[0], + ), + name='zRotation', + label='Z Rotations', + description="Rotations in degree on axis Z (depth axis) for each image.", + ), + desc.ChoiceParam( + name='verboseLevel', + label='Verbose Level', + description="Verbosity level (fatal, error, warning, info, debug, trace).", + value='info', + values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'], + exclusive=True, + uid=[], + ), + ] + + outputs = [ + desc.File( + name='output', + label='Output Panorama', + description="Output folder for panorama", + value=desc.Node.internalFolder, + uid=[], + ), + ] \ No newline at end of file From 7fc3d500721630c2a4ab5b3a4808db97fe9d8f06 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Mon, 16 Sep 2019 16:46:50 +0200 Subject: [PATCH 02/42] [nodes] New GlobalSfM node --- meshroom/nodes/aliceVision/GlobalSfM.py | 114 ++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 meshroom/nodes/aliceVision/GlobalSfM.py diff --git a/meshroom/nodes/aliceVision/GlobalSfM.py b/meshroom/nodes/aliceVision/GlobalSfM.py new file mode 100644 index 0000000000..a60b7a7895 --- /dev/null +++ b/meshroom/nodes/aliceVision/GlobalSfM.py @@ -0,0 +1,114 @@ +__version__ = "1.0" + +import json +import os + +from meshroom.core import desc + + +class GlobalSfM(desc.CommandLineNode): + commandLine = 'aliceVision_globalSfM {allParams}' + size = desc.DynamicNodeSize('input') + + inputs = [ + desc.File( + name='input', + label='Input', + description="SfM Data File", + value='', + uid=[0], + ), + desc.ListAttribute( + elementDesc=desc.File( + name='featuresFolder', + label='Features Folder', + description="", + value='', + uid=[0], + ), + name='featuresFolders', + label='Features Folders', + description="Folder(s) containing the extracted features." + ), + desc.ListAttribute( + elementDesc=desc.File( + name='matchesFolder', + label='Matches Folder', + description="", + value='', + uid=[0], + ), + name='matchesFolders', + label='Matches Folders', + description="Folder(s) in which computed matches are stored." + ), + desc.ChoiceParam( + name='describerTypes', + label='Describer Types', + description='Describer types used to describe an image.', + value=['sift'], + values=['sift', 'sift_float', 'sift_upright', 'akaze', 'akaze_liop', 'akaze_mldb', 'cctag3', 'cctag4', + 'sift_ocv', 'akaze_ocv'], + exclusive=False, + uid=[0], + joinChar=',', + ), + desc.ChoiceParam( + name='rotationAveraging', + label='Rotation Averaging Method', + description="Method for rotation averaging :\n" + " * L1 minimization\n" + " * L2 minimization\n", + values=['L1_minimization', 'L2_minimization'], + value='L2_minimization', + exclusive=True, + uid=[0], + ), + desc.ChoiceParam( + name='translationAveraging', + label='Translation Averaging Method', + description="Method for translation averaging :\n" + " * L1 minimization\n" + " * L2 minimization of sum of squared Chordal distances\n" + " * L1 soft minimization", + values=['L1_minimization', 'L2_minimization', 'L1_soft_minimization'], + value='L1_soft_minimization', + exclusive=True, + uid=[0], + ), + desc.BoolParam( + name='lockAllIntrinsics', + label='Force Lock of All Intrinsic Camera Parameters.', + description='Force to keep constant all the intrinsics parameters of the cameras (focal length, \n' + 'principal point, distortion if any) during the reconstruction.\n' + 'This may be helpful if the input cameras are already fully calibrated.', + value=False, + uid=[0], + ), + desc.ChoiceParam( + name='verboseLevel', + label='Verbose Level', + description='Verbosity level (fatal, error, warning, info, debug, trace).', + value='info', + values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'], + exclusive=True, + uid=[], + ) + ] + + outputs = [ + desc.File( + name='output', + label='Output Folder', + description='', + value=desc.Node.internalFolder, + uid=[], + ), + desc.File( + name='outSfMDataFilename', + label='Output SfMData File', + description='Path to the output sfmdata file', + value=desc.Node.internalFolder + 'SfmData.abc', + uid=[], + ), + ] From 0cdb1932b7c1b77b8e6295c54cd141eed48078c5 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Mon, 16 Sep 2019 16:47:12 +0200 Subject: [PATCH 03/42] [nodes] New Panorama estimation node --- meshroom/nodes/aliceVision/Panorama.py | 113 +++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 meshroom/nodes/aliceVision/Panorama.py diff --git a/meshroom/nodes/aliceVision/Panorama.py b/meshroom/nodes/aliceVision/Panorama.py new file mode 100644 index 0000000000..18fb36c10c --- /dev/null +++ b/meshroom/nodes/aliceVision/Panorama.py @@ -0,0 +1,113 @@ +__version__ = "1.0" + +import json +import os + +from meshroom.core import desc + + +class Panorama(desc.CommandLineNode): + commandLine = 'aliceVision_panorama {allParams}' + size = desc.DynamicNodeSize('input') + + inputs = [ + desc.File( + name='input', + label='Input', + description="SfM Data File", + value='', + uid=[0], + ), + desc.ListAttribute( + elementDesc=desc.File( + name='featuresFolder', + label='Features Folder', + description="", + value='', + uid=[0], + ), + name='featuresFolders', + label='Features Folders', + description="Folder(s) containing the extracted features." + ), + desc.ListAttribute( + elementDesc=desc.File( + name='matchesFolder', + label='Matches Folder', + description="", + value='', + uid=[0], + ), + name='matchesFolders', + label='Matches Folders', + description="Folder(s) in which computed matches are stored." + ), + desc.ChoiceParam( + name='describerTypes', + label='Describer Types', + description='Describer types used to describe an image.', + value=['sift'], + values=['sift', 'sift_float', 'sift_upright', 'akaze', 'akaze_liop', 'akaze_mldb', 'cctag3', 'cctag4', + 'sift_ocv', 'akaze_ocv'], + exclusive=False, + uid=[0], + joinChar=',', + ), + desc.ChoiceParam( + name='rotationAveraging', + label='Rotation Averaging Method', + description="Method for rotation averaging :\n" + " * L1 minimization\n" + " * L2 minimization\n", + values=['L1_minimization', 'L2_minimization'], + value='L2_minimization', + exclusive=True, + uid=[0], + ), + desc.ChoiceParam( + name='relativeRotation', + label='Relative Rotation Method', + description="Method for relative rotation :\n" + " * from essential matrix\n" + " * from homography matrix", + values=['essential_matrix', 'homography_matrix'], + value='essential_matrix', + exclusive=True, + uid=[0], + ), + desc.BoolParam( + name='lockAllIntrinsics', + label='Force Lock of All Intrinsic Camera Parameters.', + description='Force to keep constant all the intrinsics parameters of the cameras (focal length, \n' + 'principal point, distortion if any) during the reconstruction.\n' + 'This may be helpful if the input cameras are already fully calibrated.', + value=False, + uid=[0], + ), + desc.ChoiceParam( + name='verboseLevel', + label='Verbose Level', + description='Verbosity level (fatal, error, warning, info, debug, trace).', + value='info', + values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'], + exclusive=True, + uid=[], + ) + ] + + outputs = [ + desc.File( + name='output', + label='Output Folder', + description='', + value=desc.Node.internalFolder, + uid=[], + ), + desc.File( + name='outSfMDataFilename', + label='Output SfMData File', + description='Path to the output sfmdata file', + value=desc.Node.internalFolder + 'SfmData.abc', + uid=[], + ), + ] \ No newline at end of file From fad4d537a6933af01fd8090d6e5fc9cdc9140aed Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Wed, 18 Sep 2019 12:40:23 +0200 Subject: [PATCH 04/42] [nodes] split Panorama into PanoramaEstimation and PanoramaStitching --- .../{Panorama.py => PanoramaEstimation.py} | 21 +++-- .../nodes/aliceVision/PanoramaStitching.py | 81 +++++++++++++++++++ 2 files changed, 97 insertions(+), 5 deletions(-) rename meshroom/nodes/aliceVision/{Panorama.py => PanoramaEstimation.py} (88%) create mode 100644 meshroom/nodes/aliceVision/PanoramaStitching.py diff --git a/meshroom/nodes/aliceVision/Panorama.py b/meshroom/nodes/aliceVision/PanoramaEstimation.py similarity index 88% rename from meshroom/nodes/aliceVision/Panorama.py rename to meshroom/nodes/aliceVision/PanoramaEstimation.py index 18fb36c10c..0ab5f6fe75 100644 --- a/meshroom/nodes/aliceVision/Panorama.py +++ b/meshroom/nodes/aliceVision/PanoramaEstimation.py @@ -6,8 +6,8 @@ from meshroom.core import desc -class Panorama(desc.CommandLineNode): - commandLine = 'aliceVision_panorama {allParams}' +class PanoramaEstimation(desc.CommandLineNode): + commandLine = 'aliceVision_panoramaEstimation {allParams}' size = desc.DynamicNodeSize('input') inputs = [ @@ -53,6 +53,14 @@ class Panorama(desc.CommandLineNode): uid=[0], joinChar=',', ), + desc.IntParam( + name='orientation', + label='Orientation', + description='Orientation', + value=0, + range=(0, 6, 1), + uid=[0], + ), desc.ChoiceParam( name='rotationAveraging', label='Rotation Averaging Method', @@ -63,6 +71,7 @@ class Panorama(desc.CommandLineNode): value='L2_minimization', exclusive=True, uid=[0], + advanced=True, ), desc.ChoiceParam( name='relativeRotation', @@ -74,6 +83,7 @@ class Panorama(desc.CommandLineNode): value='essential_matrix', exclusive=True, uid=[0], + advanced=True, ), desc.BoolParam( name='lockAllIntrinsics', @@ -83,6 +93,7 @@ class Panorama(desc.CommandLineNode): 'This may be helpful if the input cameras are already fully calibrated.', value=False, uid=[0], + advanced=True, ), desc.ChoiceParam( name='verboseLevel', @@ -92,7 +103,7 @@ class Panorama(desc.CommandLineNode): values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'], exclusive=True, uid=[], - ) + ), ] outputs = [ @@ -107,7 +118,7 @@ class Panorama(desc.CommandLineNode): name='outSfMDataFilename', label='Output SfMData File', description='Path to the output sfmdata file', - value=desc.Node.internalFolder + 'SfmData.abc', + value=desc.Node.internalFolder + 'sfmData.abc', uid=[], ), - ] \ No newline at end of file + ] diff --git a/meshroom/nodes/aliceVision/PanoramaStitching.py b/meshroom/nodes/aliceVision/PanoramaStitching.py new file mode 100644 index 0000000000..7b353d7218 --- /dev/null +++ b/meshroom/nodes/aliceVision/PanoramaStitching.py @@ -0,0 +1,81 @@ +__version__ = "1.0" + +import json +import os + +from meshroom.core import desc + + +class PanoramaStitching(desc.CommandLineNode): + commandLine = 'aliceVision_panoramaStitching {allParams}' + size = desc.DynamicNodeSize('input') + + inputs = [ + desc.File( + name='input', + label='Input', + description="SfM Data File", + value='', + uid=[0], + ), + desc.ChoiceParam( + name='outputFileType', + label='Output File Type', + description='Output file type for the undistorted images.', + value='exr', + values=['jpg', 'png', 'tif', 'exr'], + exclusive=True, + uid=[0], + group='', # not part of allParams, as this is not a parameter for the command line + ), + desc.FloatParam( + name='scaleFactor', + label='Scale Factor', + description='Scale factor to resize the output resolution (e.g. 0.5 for downscaling to half resolution).', + value=0.2, + range=(0.0, 2.0, 0.1), + uid=[0], + ), + desc.BoolParam( + name='fisheyeMasking', + label='Enable Fisheye Masking', + description='For fisheye images, skip the invalid pixels on the borders.', + value=False, + uid=[0], + ), + desc.FloatParam( + name='fisheyeMaskingMargin', + label='Fisheye Masking Margin', + description='Margin for fisheye images.', + value=0.05, + range=(0.0, 0.5, 0.01), + uid=[0], + ), + desc.FloatParam( + name='transitionSize', + label='Transition Size', + description='Size of the transition between images (in pixels).', + value=10.0, + range=(0.0, 100.0, 1.0), + uid=[0], + ), + desc.ChoiceParam( + name='verboseLevel', + label='Verbose Level', + description='Verbosity level (fatal, error, warning, info, debug, trace).', + value='info', + values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'], + exclusive=True, + uid=[], + ) + ] + + outputs = [ + desc.File( + name='output', + label='Output Panorama', + description='', + value=desc.Node.internalFolder + 'panorama.{outputFileTypeValue}', + uid=[], + ), + ] From 36b591f9d53a6096df3999f727971b8caff0eeed Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Wed, 18 Sep 2019 15:35:26 +0200 Subject: [PATCH 05/42] [nodes] PanoramaStitching: add a new debug param --- .../nodes/aliceVision/PanoramaStitching.py | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/meshroom/nodes/aliceVision/PanoramaStitching.py b/meshroom/nodes/aliceVision/PanoramaStitching.py index 7b353d7218..602b54084c 100644 --- a/meshroom/nodes/aliceVision/PanoramaStitching.py +++ b/meshroom/nodes/aliceVision/PanoramaStitching.py @@ -59,15 +59,24 @@ class PanoramaStitching(desc.CommandLineNode): range=(0.0, 100.0, 1.0), uid=[0], ), - desc.ChoiceParam( - name='verboseLevel', - label='Verbose Level', - description='Verbosity level (fatal, error, warning, info, debug, trace).', - value='info', - values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'], - exclusive=True, - uid=[], - ) + desc.IntParam( + name='maxNbImages', + label='Max Nb Images', + description='Max number of images to merge.', + value=0, + range=(0, 80, 1), + uid=[0], + advanced=True, + ), + desc.ChoiceParam( + name='verboseLevel', + label='Verbose Level', + description='Verbosity level (fatal, error, warning, info, debug, trace).', + value='info', + values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'], + exclusive=True, + uid=[], + ), ] outputs = [ From e7022f0487ec8f6cc6ccbc1efb60c1168324d5e2 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Wed, 18 Sep 2019 15:50:26 +0200 Subject: [PATCH 06/42] [nodes] PanoramaEstimation: declare a param as "advanced" --- meshroom/nodes/aliceVision/PanoramaEstimation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/meshroom/nodes/aliceVision/PanoramaEstimation.py b/meshroom/nodes/aliceVision/PanoramaEstimation.py index 0ab5f6fe75..e8947d84d9 100644 --- a/meshroom/nodes/aliceVision/PanoramaEstimation.py +++ b/meshroom/nodes/aliceVision/PanoramaEstimation.py @@ -60,6 +60,7 @@ class PanoramaEstimation(desc.CommandLineNode): value=0, range=(0, 6, 1), uid=[0], + advanced=True, ), desc.ChoiceParam( name='rotationAveraging', From 28624586bba3327c60bed612e8a29d54a46f05c1 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Wed, 18 Sep 2019 17:24:40 +0200 Subject: [PATCH 07/42] [nodes] PanoramaStitching: add a new debug option --- meshroom/nodes/aliceVision/PanoramaStitching.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/meshroom/nodes/aliceVision/PanoramaStitching.py b/meshroom/nodes/aliceVision/PanoramaStitching.py index 602b54084c..9369b7a59e 100644 --- a/meshroom/nodes/aliceVision/PanoramaStitching.py +++ b/meshroom/nodes/aliceVision/PanoramaStitching.py @@ -60,9 +60,18 @@ class PanoramaStitching(desc.CommandLineNode): uid=[0], ), desc.IntParam( - name='maxNbImages', - label='Max Nb Images', - description='Max number of images to merge.', + name='debugSubsetStart', + label='Debug: Start Index of SubSet', + description='Start index of subset images to merge.', + value=0, + range=(0, 80, 1), + uid=[0], + advanced=True, + ), + desc.IntParam( + name='debugSubsetSize', + label='Debug: Nb Images in SubSet', + description='Number of images in subset to merge.', value=0, range=(0, 80, 1), uid=[0], From ade9360b1f26869d27445ccfec7b956031b46fe5 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Wed, 18 Sep 2019 18:29:13 +0200 Subject: [PATCH 08/42] [ui] WorkspaceView: open images in the 2D Viewer from the Graph/NodeEditor Double click on a node or an attribute with an image file to display it in the 2D viewer. --- meshroom/ui/components/filepath.py | 11 ++++++++ meshroom/ui/qml/WorkspaceView.qml | 2 +- meshroom/ui/qml/main.qml | 45 +++++++++++++++++++++++++++--- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/meshroom/ui/components/filepath.py b/meshroom/ui/components/filepath.py index 9e9a047574..f15d970005 100644 --- a/meshroom/ui/components/filepath.py +++ b/meshroom/ui/components/filepath.py @@ -78,3 +78,14 @@ def stringToUrl(self, path): def normpath(self, path): """ Returns native normalized path """ return os.path.normpath(self.asStr(path)) + + @Slot(str, result=str) + @Slot(QUrl, result=str) + def globFirst(self, path): + """ Returns the first from a list of paths matching a pathname pattern. """ + import glob + fileList = glob.glob(self.asStr(path)) + fileList.sort() + if fileList: + return fileList[0] + return "" diff --git a/meshroom/ui/qml/WorkspaceView.qml b/meshroom/ui/qml/WorkspaceView.qml index 1a9dc3d3d0..007bc4c299 100644 --- a/meshroom/ui/qml/WorkspaceView.qml +++ b/meshroom/ui/qml/WorkspaceView.qml @@ -23,7 +23,7 @@ Item { readonly property variant cameraInits: _reconstruction.cameraInits property bool readOnly: false readonly property Viewer3D viewer3D: viewer3D - + readonly property Viewer2D viewer2D: viewer2D implicitWidth: 300 implicitHeight: 400 diff --git a/meshroom/ui/qml/main.qml b/meshroom/ui/qml/main.qml index d340ebef5d..39e391f95d 100755 --- a/meshroom/ui/qml/main.qml +++ b/meshroom/ui/qml/main.qml @@ -531,6 +531,28 @@ ApplicationWindow { viewer3D.solo(attribute); return loaded; } + function viewIn2D(attribute) { + var imageExts = ['.exr', '.jpg', '.tif', '.png']; + var ext = Filepath.extension(attribute.value); + if(imageExts.indexOf(ext) == -1) + { + return false; + } + + if(attribute.value.includes('*')) + { + // For now, the viewer only supports a single image. + var firstFile = Filepath.globFirst(attribute.value) + viewer2D.source = Filepath.stringToUrl(firstFile); + } + else + { + viewer2D.source = Filepath.stringToUrl(attribute.value); + return true; + } + + return false; + } } } @@ -606,10 +628,16 @@ ApplicationWindow { for(var i=0; i < node.attributes.count; ++i) { var attr = node.attributes.at(i) - if(attr.isOutput - && workspaceView.viewIn3D(attr, mouse)) + if(attr.isOutput) { - break; + if(workspaceView.viewIn2D(attr)) + { + break; + } + if(workspaceView.viewIn3D(attr, mouse)) + { + break; + } } } } @@ -623,7 +651,16 @@ ApplicationWindow { node: _reconstruction.selectedNode // Make NodeEditor readOnly when computing readOnly: graphLocked - onAttributeDoubleClicked: workspaceView.viewIn3D(attribute, mouse) + onAttributeDoubleClicked: { + if(workspaceView.viewIn2D(attribute)) + { + return; + } + if(workspaceView.viewIn3D(attribute, mouse)) + { + return; + } + } onUpgradeRequest: { var n = _reconstruction.upgradeNode(node); _reconstruction.selectedNode = n; From b87432fe1efe6171bf34fa1bf3d25764d03281b6 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Wed, 18 Sep 2019 20:31:40 +0200 Subject: [PATCH 09/42] [nodes] Panorama: new param "refine" --- meshroom/nodes/aliceVision/PanoramaEstimation.py | 8 +++++++- meshroom/nodes/aliceVision/PanoramaStitching.py | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/meshroom/nodes/aliceVision/PanoramaEstimation.py b/meshroom/nodes/aliceVision/PanoramaEstimation.py index e8947d84d9..bb5a904e77 100644 --- a/meshroom/nodes/aliceVision/PanoramaEstimation.py +++ b/meshroom/nodes/aliceVision/PanoramaEstimation.py @@ -86,6 +86,13 @@ class PanoramaEstimation(desc.CommandLineNode): uid=[0], advanced=True, ), + desc.BoolParam( + name='refine', + label='Refine', + description='Refine camera relative poses, points and optionally internal camera parameter', + value=False, + uid=[0], + ), desc.BoolParam( name='lockAllIntrinsics', label='Force Lock of All Intrinsic Camera Parameters.', @@ -94,7 +101,6 @@ class PanoramaEstimation(desc.CommandLineNode): 'This may be helpful if the input cameras are already fully calibrated.', value=False, uid=[0], - advanced=True, ), desc.ChoiceParam( name='verboseLevel', diff --git a/meshroom/nodes/aliceVision/PanoramaStitching.py b/meshroom/nodes/aliceVision/PanoramaStitching.py index 9369b7a59e..d3123fb09f 100644 --- a/meshroom/nodes/aliceVision/PanoramaStitching.py +++ b/meshroom/nodes/aliceVision/PanoramaStitching.py @@ -55,8 +55,8 @@ class PanoramaStitching(desc.CommandLineNode): name='transitionSize', label='Transition Size', description='Size of the transition between images (in pixels).', - value=10.0, - range=(0.0, 100.0, 1.0), + value=100.0, + range=(0.0, 500.0, 1.0), uid=[0], ), desc.IntParam( From 38d27c9d45622d26de7c95e3d0f158bc630d2eb1 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Wed, 18 Sep 2019 20:31:58 +0200 Subject: [PATCH 10/42] [nodes] new debug node: ExportMatches --- meshroom/nodes/aliceVision/ExportMatches.py | 71 +++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 meshroom/nodes/aliceVision/ExportMatches.py diff --git a/meshroom/nodes/aliceVision/ExportMatches.py b/meshroom/nodes/aliceVision/ExportMatches.py new file mode 100644 index 0000000000..8df3b39181 --- /dev/null +++ b/meshroom/nodes/aliceVision/ExportMatches.py @@ -0,0 +1,71 @@ +__version__ = "1.1" + +from meshroom.core import desc + + +class ExportMatches(desc.CommandLineNode): + commandLine = 'aliceVision_exportMatches {allParams}' + size = desc.DynamicNodeSize('input') + + inputs = [ + desc.File( + name='input', + label='Input', + description='SfMData file.', + value='', + uid=[0], + ), + desc.ChoiceParam( + name='describerTypes', + label='Describer Types', + description='Describer types used to describe an image.', + value=['sift'], + values=['sift', 'sift_float', 'sift_upright', 'akaze', 'akaze_liop', 'akaze_mldb', 'cctag3', 'cctag4', 'sift_ocv', 'akaze_ocv'], + exclusive=False, + uid=[0], + joinChar=',', + ), + desc.ListAttribute( + elementDesc=desc.File( + name="featuresFolder", + label="Features Folder", + description="", + value="", + uid=[0], + ), + name="featuresFolders", + label="Features Folders", + description="Folder(s) containing the extracted features and descriptors." + ), + desc.ListAttribute( + elementDesc=desc.File( + name="matchesFolder", + label="Matches Folder", + description="", + value="", + uid=[0], + ), + name="matchesFolders", + label="Matches Folders", + description="Folder(s) in which computed matches are stored." + ), + desc.ChoiceParam( + name='verboseLevel', + label='Verbose Level', + description='verbosity level (fatal, error, warning, info, debug, trace).', + value='info', + values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'], + exclusive=True, + uid=[], + ) + ] + + outputs = [ + desc.File( + name='output', + label='Output Folder', + description='Output path for the features and descriptors files (*.feat, *.desc).', + value=desc.Node.internalFolder, + uid=[], + ), + ] From ee6772b193a92da4f20889a3b532009aa0c1b008 Mon Sep 17 00:00:00 2001 From: Fabien Servant Date: Tue, 1 Oct 2019 14:00:55 +0200 Subject: [PATCH 11/42] adding a node for rotating head xml --- .../nodes/aliceVision/PanoramaExternalInfo.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 meshroom/nodes/aliceVision/PanoramaExternalInfo.py diff --git a/meshroom/nodes/aliceVision/PanoramaExternalInfo.py b/meshroom/nodes/aliceVision/PanoramaExternalInfo.py new file mode 100644 index 0000000000..69e8e50bab --- /dev/null +++ b/meshroom/nodes/aliceVision/PanoramaExternalInfo.py @@ -0,0 +1,47 @@ +__version__ = "1.0" + +import json +import os + +from meshroom.core import desc + + +class PanoramaExternalInfo(desc.CommandLineNode): + commandLine = 'aliceVision_panoramaExternalInfo {allParams}' + size = desc.DynamicNodeSize('input') + + inputs = [ + desc.File( + name='input', + label='Input', + description="SfM Data File", + value='', + uid=[0], + ), + desc.File( + name='config', + label='Xml Config', + description="XML Data File", + value='', + uid=[0], + ), + desc.ChoiceParam( + name='verboseLevel', + label='Verbose Level', + description='Verbosity level (fatal, error, warning, info, debug, trace).', + value='info', + values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'], + exclusive=True, + uid=[], + ), + ] + + outputs = [ + desc.File( + name='outSfMDataFilename', + label='Output SfMData File', + description='Path to the output sfmdata file', + value=desc.Node.internalFolder + 'sfmData.abc', + uid=[], + ) + ] From 77b867c492006f37bcdc0626d2d699036be9fd86 Mon Sep 17 00:00:00 2001 From: Fabien Servant Date: Tue, 15 Oct 2019 15:18:16 +0200 Subject: [PATCH 12/42] restart stitching --- .../nodes/aliceVision/PanoramaStitching.py | 65 +++---------------- 1 file changed, 8 insertions(+), 57 deletions(-) diff --git a/meshroom/nodes/aliceVision/PanoramaStitching.py b/meshroom/nodes/aliceVision/PanoramaStitching.py index d3123fb09f..5158d9911e 100644 --- a/meshroom/nodes/aliceVision/PanoramaStitching.py +++ b/meshroom/nodes/aliceVision/PanoramaStitching.py @@ -28,64 +28,15 @@ class PanoramaStitching(desc.CommandLineNode): uid=[0], group='', # not part of allParams, as this is not a parameter for the command line ), - desc.FloatParam( - name='scaleFactor', - label='Scale Factor', - description='Scale factor to resize the output resolution (e.g. 0.5 for downscaling to half resolution).', - value=0.2, - range=(0.0, 2.0, 0.1), - uid=[0], + desc.ChoiceParam( + name='verboseLevel', + label='Verbose Level', + description='Verbosity level (fatal, error, warning, info, debug, trace).', + value='info', + values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'], + exclusive=True, + uid=[], ), - desc.BoolParam( - name='fisheyeMasking', - label='Enable Fisheye Masking', - description='For fisheye images, skip the invalid pixels on the borders.', - value=False, - uid=[0], - ), - desc.FloatParam( - name='fisheyeMaskingMargin', - label='Fisheye Masking Margin', - description='Margin for fisheye images.', - value=0.05, - range=(0.0, 0.5, 0.01), - uid=[0], - ), - desc.FloatParam( - name='transitionSize', - label='Transition Size', - description='Size of the transition between images (in pixels).', - value=100.0, - range=(0.0, 500.0, 1.0), - uid=[0], - ), - desc.IntParam( - name='debugSubsetStart', - label='Debug: Start Index of SubSet', - description='Start index of subset images to merge.', - value=0, - range=(0, 80, 1), - uid=[0], - advanced=True, - ), - desc.IntParam( - name='debugSubsetSize', - label='Debug: Nb Images in SubSet', - description='Number of images in subset to merge.', - value=0, - range=(0, 80, 1), - uid=[0], - advanced=True, - ), - desc.ChoiceParam( - name='verboseLevel', - label='Verbose Level', - description='Verbosity level (fatal, error, warning, info, debug, trace).', - value='info', - values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'], - exclusive=True, - uid=[], - ), ] outputs = [ From e97676fbc9b2aeeee899c52da9f1352ac3f98aa8 Mon Sep 17 00:00:00 2001 From: fabienservant Date: Thu, 24 Oct 2019 09:42:15 +0200 Subject: [PATCH 13/42] Control panorama width --- meshroom/nodes/aliceVision/PanoramaStitching.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/meshroom/nodes/aliceVision/PanoramaStitching.py b/meshroom/nodes/aliceVision/PanoramaStitching.py index 5158d9911e..8621fe3b41 100644 --- a/meshroom/nodes/aliceVision/PanoramaStitching.py +++ b/meshroom/nodes/aliceVision/PanoramaStitching.py @@ -28,6 +28,14 @@ class PanoramaStitching(desc.CommandLineNode): uid=[0], group='', # not part of allParams, as this is not a parameter for the command line ), + desc.IntParam( + name='panoramaWidth', + label='Panorama Width', + description='Panorama width (pixels). 0 For automatic size', + value=1000, + range=(0, 50000, 1000), + uid=[0] + ), desc.ChoiceParam( name='verboseLevel', label='Verbose Level', From 9d78c7be32ba5f5f8463c7f9abc0c1bd7d8c7da6 Mon Sep 17 00:00:00 2001 From: fabienservant Date: Thu, 24 Oct 2019 16:01:01 +0200 Subject: [PATCH 14/42] Make a compatible LDRToHDR node --- meshroom/nodes/aliceVision/LDRToHDR.py | 128 ++++++------------------- 1 file changed, 28 insertions(+), 100 deletions(-) diff --git a/meshroom/nodes/aliceVision/LDRToHDR.py b/meshroom/nodes/aliceVision/LDRToHDR.py index ad36c17d85..2b214be746 100644 --- a/meshroom/nodes/aliceVision/LDRToHDR.py +++ b/meshroom/nodes/aliceVision/LDRToHDR.py @@ -1,128 +1,56 @@ __version__ = "1.0" +import json +import os + from meshroom.core import desc class LDRToHDR(desc.CommandLineNode): commandLine = 'aliceVision_convertLDRToHDR {allParams}' + size = desc.DynamicNodeSize('input') inputs = [ - desc.ListAttribute( - elementDesc=desc.File( - name='inputFolder', - label='Input File/Folder', - description="Folder containing LDR images", - value='', - uid=[0], - ), - name="input", - label="Input Files or Folders", - description='Folders containing LDR images.', - ), - desc.BoolParam( - name='fisheyeLens', - label='Fisheye Lens', - description="Enable if a fisheye lens has been used.\n " - "This will improve the estimation of the Camera's Response Function by considering only the pixels in the center of the image\n" - "and thus ignore undefined/noisy pixels outside the circle defined by the fisheye lens.", - value=True, - uid=[0], - ), - desc.ChoiceParam( - name='calibrationMethod', - label='Calibration Method', - description="Method used for camera calibration \n" - " * linear \n" - " * robertson \n" - " * debevec \n" - " * grossberg", - values=['linear', 'robertson', 'debevec', 'grossberg'], - value='linear', - exclusive=True, - uid=[0], - ), desc.File( - name='inputResponse', - label='Input Response', - description="external camera response file path to fuse all LDR images together.", - value='', - uid=[0], - ), - desc.StringParam( - name='targetExposureImage', - label='Target Exposure Image', - description="LDR image(s) name(s) at the target exposure for the output HDR image(s) to be centered.", + name='input', + label='Input', + description="SfM Data File", value='', uid=[0], ), - desc.ChoiceParam( - name='calibrationWeight', - label='Calibration Weight', - description="Weight function used to calibrate camera response \n" - " * default (automatically selected according to the calibrationMethod) \n" - " * gaussian \n" - " * triangle \n" - " * plateau", - value='default', - values=['default', 'gaussian', 'triangle', 'plateau'], - exclusive=True, - uid=[0], - ), - desc.ChoiceParam( - name='fusionWeight', - label='Fusion Weight', - description="Weight function used to fuse all LDR images together \n" - " * gaussian \n" - " * triangle \n" - " * plateau", - value='gaussian', - values=['gaussian', 'triangle', 'plateau'], - exclusive=True, - uid=[0], - ), - desc.FloatParam( - name='expandDynamicRange', - label='Expand Dynamic Range', - description="Correction of clamped high values in dynamic range: \n" - " - use 0 for no correction \n" - " - use 0.5 for interior lighting \n" - " - use 1 for outdoor lighting", - value=1, - range=(0, 1, 0.1), - uid=[0], - ), desc.ChoiceParam( name='verboseLevel', label='Verbose Level', - description="Verbosity level (fatal, error, warning, info, debug, trace).", + description='Verbosity level (fatal, error, warning, info, debug, trace).', value='info', values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'], exclusive=True, uid=[], ), - desc.File( - name='recoverPath', - label='Output Recovered Files', - description="(debug) Folder for recovered LDR images at target exposures.", - advanced=True, - value='', - uid=[], + desc.IntParam( + name='groupSize', + label='Exposure bracket count', + description='Number of exposure brackets used per HDR image', + value=3, + range=(0, 10, 1), + uid=[0] + ), + desc.FloatParam( + name='expandDynamicRange', + label='Expand Dynamic Range', + description='float value between 0 and 1 to correct clamped high values in dynamic range: use 0 for no correction, 0.5 for interior lighting and 1 for outdoor lighting.', + value=1.0, + range=(0.0, 1.0, 0.01), + uid=[0], ), ] outputs = [ desc.File( - name='output', - label='Output Folder', - description="Output folder for HDR images", - value=desc.Node.internalFolder, + name='outSfMDataFilename', + label='Output SfMData File', + description='Path to the output sfmdata file', + value=desc.Node.internalFolder + 'sfmData.abc', uid=[], - ), - desc.File( - name='outputResponse', - label='Output Response', - description="Output response function path.", - value=desc.Node.internalFolder + 'response.csv', - uid=[], - ), + ) ] From 54a57a7d5e34a39f5f34ea8816c72e3cb8f84f95 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Fri, 25 Oct 2019 11:55:39 +0200 Subject: [PATCH 15/42] [nodes] LDR2HDR: resize dynamic node size based on groupSize param --- meshroom/nodes/aliceVision/LDRToHDR.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/meshroom/nodes/aliceVision/LDRToHDR.py b/meshroom/nodes/aliceVision/LDRToHDR.py index 2b214be746..4b301573b9 100644 --- a/meshroom/nodes/aliceVision/LDRToHDR.py +++ b/meshroom/nodes/aliceVision/LDRToHDR.py @@ -6,9 +6,23 @@ from meshroom.core import desc +class DividedInputNodeSize(desc.DynamicNodeSize): + """ + The LDR2HDR will reduce the amount of views in the SfMData. + This class converts the number of LDR input views into the number of HDR output views. + """ + def __init__(self, param, divParam): + super(DividedInputNodeSize, self).__init__(param) + self._divParam = divParam + def computeSize(self, node): + s = super(DividedInputNodeSize, self).computeSize(node) + divParam = node.attribute(self._divParam) + return s / divParam.value + + class LDRToHDR(desc.CommandLineNode): commandLine = 'aliceVision_convertLDRToHDR {allParams}' - size = desc.DynamicNodeSize('input') + size = DividedInputNodeSize('input', 'groupSize') inputs = [ desc.File( From 07f564d6e065d33e6a98dbddfdfe38066f750f18 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Mon, 4 Nov 2019 00:30:22 +0100 Subject: [PATCH 16/42] [bin] meshroom: option to choose HDRI pipeline --- meshroom/multiview.py | 92 ++++++++++++++++++++++++++++++++++- meshroom/ui/app.py | 15 ++---- meshroom/ui/graph.py | 10 +--- meshroom/ui/reconstruction.py | 19 +++++--- 4 files changed, 108 insertions(+), 28 deletions(-) diff --git a/meshroom/multiview.py b/meshroom/multiview.py index b72011b3ab..65acb15461 100644 --- a/meshroom/multiview.py +++ b/meshroom/multiview.py @@ -6,13 +6,27 @@ from meshroom.core.graph import Graph, GraphModification # Supported image extensions -imageExtensions = ('.jpg', '.jpeg', '.tif', '.tiff', '.png', '.exr', '.rw2', '.cr2', '.nef', '.arw', '.dng') - +imageExtensions = ('.jpg', '.jpeg', '.tif', '.tiff', '.png', '.exr', '.rw2', '.cr2', '.nef', '.arw') +videoExtensions = ('.avi', '.mov', '.qt', + '.mkv', '.webm', + '.mp4', '.mpg', '.mpeg', '.m2v', '.m4v', + '.wmv', + '.ogv', '.ogg', + '.mxf') +panoramaExtensions = ('.xml') def isImageFile(filepath): """ Return whether filepath is a path to an image file supported by Meshroom. """ return os.path.splitext(filepath)[1].lower() in imageExtensions +def isVideoFile(filepath): + """ Return whether filepath is a path to a video file supported by Meshroom. """ + return os.path.splitext(filepath)[1].lower() in videoExtensions + +def isPanoramaFile(filepath): + """ Return whether filepath is a path to a panorama info file supported by Meshroom. """ + return os.path.splitext(filepath)[1].lower() in panoramaExtensions + def findImageFiles(folder, recursive=False): """ @@ -46,6 +60,80 @@ def findImageFiles(folder, recursive=False): return output +def hdri(inputImages=list(), inputViewpoints=list(), inputIntrinsics=list(), output=''): + """ + Create a new Graph with a complete HDRI pipeline. + + Args: + 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('HDRI') + with GraphModification(graph): + nodes = hdriPipeline(graph) + cameraInit = nodes[0] + cameraInit.viewpoints.extend([{'path': image} for image in inputImages]) + cameraInit.viewpoints.extend(inputViewpoints) + cameraInit.intrinsics.extend(inputIntrinsics) + + if output: + stitching = nodes[-1] + graph.addNewNode('Publish', output=output, inputFiles=[stitching.output]) + + return graph + + +def hdriPipeline(graph): + """ + Instantiate an HDRI 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') + + ldr2hdr = graph.addNewNode('LDRToHDR', + input=cameraInit.output) + + featureExtraction = graph.addNewNode('FeatureExtraction', + input=ldr2hdr.outSfMDataFilename) + imageMatching = graph.addNewNode('ImageMatching', + input=featureExtraction.input, + featuresFolders=[featureExtraction.output]) + featureMatching = graph.addNewNode('FeatureMatching', + input=imageMatching.input, + featuresFolders=imageMatching.featuresFolders, + imagePairsList=imageMatching.output) + + panoramaExternalInfo = graph.addNewNode('PanoramaExternalInfo', + input=ldr2hdr.input) + + panoramaEstimation = graph.addNewNode('PanoramaEstimation', + input=panoramaExternalInfo.outSfMDataFilename, + featuresFolders=featureMatching.featuresFolders, + matchesFolders=[featureMatching.output]) + + panoramaStitching = graph.addNewNode('PanoramaStitching', + input=panoramaEstimation.outSfMDataFilename) + + return [ + cameraInit, + featureExtraction, + imageMatching, + featureMatching, + panoramaExternalInfo, + panoramaEstimation, + panoramaStitching, + ] + + + def photogrammetry(inputImages=list(), inputViewpoints=list(), inputIntrinsics=list(), output=''): """ Create a new Graph with a complete photogrammetry pipeline. diff --git a/meshroom/ui/app.py b/meshroom/ui/app.py index e8e22fc90c..ec4a3b5dab 100644 --- a/meshroom/ui/app.py +++ b/meshroom/ui/app.py @@ -65,7 +65,7 @@ def __init__(self, args): 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, + parser.add_argument('-p', '--pipeline', metavar='MESHROOM_FILE/photogrammetry/hdri', type=str, default=os.environ.get("MESHROOM_DEFAULT_PIPELINE", "photogrammetry"), help='Override the default Meshroom pipeline with this external graph.') args = parser.parse_args(args[1:]) @@ -101,7 +101,7 @@ def __init__(self, args): self.engine.rootContext().setContextProperty("_nodeTypes", sorted(nodesDesc.keys())) # instantiate Reconstruction object - r = Reconstruction(parent=self) + r = Reconstruction(defaultPipeline=args.pipeline, parent=self) self.engine.rootContext().setContextProperty("_reconstruction", r) # those helpers should be available from QML Utils module as singletons, but: @@ -119,15 +119,6 @@ def __init__(self, args): # request any potential computation to stop on exit self.aboutToQuit.connect(r.stopChildThreads) - 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" @@ -135,6 +126,8 @@ def __init__(self, args): if args.project: r.load(args.project) + else: + r.new() # import is a python keyword, so we have to access the attribute by a string if getattr(args, "import", None): diff --git a/meshroom/ui/graph.py b/meshroom/ui/graph.py index d0e8bd3dc3..f735882cde 100644 --- a/meshroom/ui/graph.py +++ b/meshroom/ui/graph.py @@ -9,6 +9,7 @@ from PySide2.QtCore import Slot, QJsonValue, QObject, QUrl, Property, Signal, QPoint +from meshroom import multiview from meshroom.common.qt import QObjectListModel from meshroom.core.attribute import Attribute, ListAttribute from meshroom.core.graph import Graph, Edge, submitGraph, executeGraph @@ -242,7 +243,7 @@ class UIGraph(QObject): UIGraph exposes undoable methods on its graph and computation in a separate thread. It also provides a monitoring of all its computation units (NodeChunks). """ - def __init__(self, filepath='', parent=None): + def __init__(self, parent=None): super(UIGraph, self).__init__(parent) self._undoStack = commands.UndoStack(self) self._graph = Graph('', self) @@ -254,9 +255,6 @@ def __init__(self, filepath='', parent=None): self._layout = GraphLayout(self) self._selectedNode = None self._hoveredNode = None - self._defaultPipelineFilepath = None - if filepath: - self.load(filepath) def setGraph(self, g): """ Set the internal graph. """ @@ -311,10 +309,6 @@ def stopChildThreads(self): self.stopExecution() self._chunksMonitor.stop() - def setDefaultPipeline(self, pipelineFilepath): - self._defaultPipelineFilepath = pipelineFilepath - self._graph.load(pipelineFilepath, setupProjectFile=False) - def load(self, filepath, setupProjectFile=True): g = Graph('') g.load(filepath, setupProjectFile) diff --git a/meshroom/ui/reconstruction.py b/meshroom/ui/reconstruction.py index a87dcd9a14..baf34e0040 100755 --- a/meshroom/ui/reconstruction.py +++ b/meshroom/ui/reconstruction.py @@ -401,12 +401,15 @@ def __init__(self, graphFilepath='', parent=None): @Slot() def new(self): """ Create a new photogrammetry pipeline. """ - if self._defaultPipelineFilepath: - # use the user-provided default photogrammetry project file - self.load(self._defaultPipelineFilepath, setupProjectFile=False) - else: + if self._defaultPipeline.lower() == "photogrammetry": # default photogrammetry pipeline self.setGraph(multiview.photogrammetry()) + elif self._defaultPipeline.lower() == "hdri": + # default hdri pipeline + self.setGraph(multiview.hdri()) + else: + # use the user-provided default photogrammetry project file + self.load(self._defaultPipeline, setupProjectFile=False) def load(self, filepath, setupProjectFile=True): try: @@ -590,9 +593,11 @@ def getImageFilesFromDrop(drop): images.extend(multiview.findImageFiles(localFile)) elif multiview.isImageFile(localFile): images.append(localFile) - else: - otherFiles.append(localFile) - return images, otherFiles + elif multiview.isVideoFile(localFile): + images.append(localFile) + elif multiview.isPanoramaFile(localFile): + pass + return images def importImagesFromFolder(self, path, recursive=False): """ From f03b52e415223cd28265b7d41bc9ca4f8bff1764 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Mon, 4 Nov 2019 12:12:08 +0100 Subject: [PATCH 17/42] [multiview] setup xml files to Panorama node, create KeyframeSelection for video files --- bin/meshroom_photogrammetry | 12 ++-- meshroom/multiview.py | 56 +++++++++++++------ meshroom/ui/reconstruction.py | 101 ++++++++++++++++++++++++---------- 3 files changed, 116 insertions(+), 53 deletions(-) diff --git a/bin/meshroom_photogrammetry b/bin/meshroom_photogrammetry index c5ac365e88..1f3bbec5b2 100755 --- a/bin/meshroom_photogrammetry +++ b/bin/meshroom_photogrammetry @@ -78,7 +78,7 @@ if not args.input and not args.inputRecursive: views, intrinsics = [], [] # Build image files list from inputImages arguments -images = [] +filesByType = multiview.FilesByType() hasSearchedForImages = False @@ -88,14 +88,14 @@ if args.input: from meshroom.nodes.aliceVision.CameraInit import readSfMData views, intrinsics = readSfMData(args.input[0]) else: - images += multiview.findImageFiles(args.input, recursive=False) + filesByType.extend(multiview.findFilesByTypeInFolder(args.input, recursive=False)) hasSearchedForImages = True if args.inputRecursive: - images += multiview.findImageFiles(args.inputRecursive, recursive=True) + filesByType.extend(multiview.findFilesByTypeInFolder(args.inputRecursive, recursive=True)) hasSearchedForImages = True -if hasSearchedForImages and not images: +if hasSearchedForImages and not filesByType.images: print("No image found") exit(-1) @@ -122,8 +122,8 @@ else: graph = multiview.photogrammetry(inputViewpoints=views, inputIntrinsics=intrinsics, output=args.output) cameraInit = getOnlyNodeOfType(graph, 'CameraInit') -if images: - views, intrinsics = cameraInit.nodeDesc.buildIntrinsics(cameraInit, images) +if filesByType.images: + views, intrinsics = cameraInit.nodeDesc.buildIntrinsics(cameraInit, filesByType.images) cameraInit.viewpoints.value = views cameraInit.intrinsics.value = intrinsics diff --git a/meshroom/multiview.py b/meshroom/multiview.py index 65acb15461..01776990b6 100644 --- a/meshroom/multiview.py +++ b/meshroom/multiview.py @@ -13,22 +13,46 @@ '.wmv', '.ogv', '.ogg', '.mxf') -panoramaExtensions = ('.xml') +panoramaInfoExtensions = ('.xml') -def isImageFile(filepath): - """ Return whether filepath is a path to an image file supported by Meshroom. """ - return os.path.splitext(filepath)[1].lower() in imageExtensions -def isVideoFile(filepath): - """ Return whether filepath is a path to a video file supported by Meshroom. """ - return os.path.splitext(filepath)[1].lower() in videoExtensions +def hasExtension(filepath, extensions): + """ Return whether filepath is one of the following extensions. """ + return os.path.splitext(filepath)[1].lower() in extensions -def isPanoramaFile(filepath): - """ Return whether filepath is a path to a panorama info file supported by Meshroom. """ - return os.path.splitext(filepath)[1].lower() in panoramaExtensions +class FilesByType: + def __init__(self): + self.images = [] + self.videos = [] + self.panoramaInfo = [] + self.other = [] -def findImageFiles(folder, recursive=False): + def __bool__(self): + return self.images or self.videos or self.panoramaInfo + + def extend(self, other): + self.images.extend(other.images) + self.videos.extend(other.videos) + self.panoramaInfo.extend(other.panoramaInfo) + self.other.extend(other.other) + + def addFile(self, file): + if hasExtension(file, imageExtensions): + self.images.append(file) + elif hasExtension(file, videoExtensions): + self.videos.append(file) + elif hasExtension(file, panoramaInfoExtensions): + self.panoramaInfo.append(file) + else: + self.other.append(file) + + def addFiles(self, files): + for file in files: + self.addFile(file) + + +def findFilesByTypeInFolder(folder, recursive=False): """ Return all files that are images in 'folder' based on their extensions. @@ -44,19 +68,17 @@ def findImageFiles(folder, recursive=False): else: inputFolders.append(folder) - output = [] + output = FilesByType() for currentFolder in inputFolders: if os.path.isfile(currentFolder): - if isImageFile(currentFolder): - output.append(currentFolder) + output.addFile(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)) + output.addFile(os.path.join(root, filename)) else: - output.extend([os.path.join(currentFolder, filename) for filename in os.listdir(currentFolder) if isImageFile(filename)]) + output.addFiles([os.path.join(currentFolder, filename) for filename in os.listdir(currentFolder)]) return output diff --git a/meshroom/ui/reconstruction.py b/meshroom/ui/reconstruction.py index baf34e0040..28c87384b2 100755 --- a/meshroom/ui/reconstruction.py +++ b/meshroom/ui/reconstruction.py @@ -10,7 +10,7 @@ from meshroom import multiview from meshroom.common.qt import QObjectListModel from meshroom.core import Version -from meshroom.core.node import Node, Status +from meshroom.core.node import Node, Status, Position from meshroom.ui.graph import UIGraph from meshroom.ui.utils import makeProperty @@ -102,7 +102,7 @@ def update(self): to include those images to the reconstruction. """ # Get all new images in the watched folder - imagesInFolder = multiview.findImageFiles(self._folder) + imagesInFolder = multiview.findFilesByTypeInFolder(self._folder) newImages = set(imagesInFolder).difference(self.allImages) for imagePath in newImages: # print('[LiveSfmManager] New image file : {}'.format(imagePath)) @@ -358,8 +358,8 @@ class Reconstruction(UIGraph): Specialization of a UIGraph designed to manage a 3D reconstruction. """ - def __init__(self, graphFilepath='', parent=None): - super(Reconstruction, self).__init__(graphFilepath, parent) + def __init__(self, defaultPipeline='', parent=None): + super(Reconstruction, self).__init__(parent) # initialize member variables for key steps of the 3D reconstruction pipeline @@ -393,10 +393,10 @@ def __init__(self, graphFilepath='', parent=None): # react to internal graph changes to update those variables self.graphChanged.connect(self.onGraphChanged) - if graphFilepath: - self.onGraphChanged() - else: - self.new() + self.setDefaultPipeline(defaultPipeline) + + def setDefaultPipeline(self, defaultPipeline): + self._defaultPipeline = defaultPipeline @Slot() def new(self): @@ -560,21 +560,67 @@ def handleFilesDrop(self, drop, cameraInit): Fetching urls from dropEvent is generally expensive in QML/JS (bug ?). This method allows to reduce process time by doing it on Python side. """ - images, urls = self.getImageFilesFromDrop(drop) - if not images: - extensions = set([os.path.splitext(url)[1] for url in urls]) - self.error.emit( + filesByType = self.getFilesByTypeFromDrop(drop) + if filesByType.images: + self.importImagesAsync(filesByType.images, cameraInit) + if filesByType.videos: + boundingBox = self.layout.boundingBox() + keyframeNode = self.addNewNode("KeyframeSelection", position=Position(boundingBox[0], boundingBox[1] + boundingBox[3])) + keyframeNode.mediaPaths.value = filesByType.videos + if len(filesByType.videos) == 1: + newVideoNodeMessage = "New node '{}' added for the input video.".format(keyframeNode.getLabel()) + else: + newVideoNodeMessage = "New node '{}' added for a rig of {} synchronized cameras.".format(keyframeNode.getLabel(), len(filesByType.videos)) + self.info.emit( Message( - "No Recognized Image", - "No recognized image file in the {} dropped files".format(len(urls)), - "File extensions: " + ', '.join(extensions) + "Video Input", + newVideoNodeMessage, + "Warning: You need to manually compute the KeyframeSelection node \n" + "and then reimport the created images into Meshroom for the reconstruction.\n\n" + "If you know the Camera Make/Model, it is highly recommended to declare them in the Node." + )) + + if filesByType.panoramaInfo: + if len(filesByType.panoramaInfo) > 1: + self.error.emit( + Message( + "Multiple XML files in input", + "Ignore the xml Panorama files:\n\n'{}'.".format(',\n'.join(filesByType.panoramaInfo)), + "", + )) + else: + panoramaExternalInfoNodes = self.graph.nodesByType('PanoramaExternalInfo') + for panoramaInfoFile in filesByType.panoramaInfo: + for panoramaInfoNode in panoramaExternalInfoNodes: + panoramaInfoNode.attribute('config').value = panoramaInfoFile + if panoramaExternalInfoNodes: + self.info.emit( + Message( + "Panorama XML", + "XML file declared on PanoramaExternalInfo node", + "XML file '{}' set on node '{}'".format(','.join(filesByType.panoramaInfo), ','.join([n.getLabel() for n in panoramaExternalInfoNodes])), + )) + else: + self.error.emit( + Message( + "No PanoramaExternalInfo Node", + "No PanoramaExternalInfo Node to set the Panorama file:\n'{}'.".format(','.join(filesByType.panoramaInfo)), + "", + )) + + if not filesByType.images and not filesByType.videos and not filesByType.panoramaInfo: + if filesByType.other: + extensions = set([os.path.splitext(url)[1] for url in filesByType.other]) + self.error.emit( + Message( + "No Recognized Input File", + "No recognized input file in the {} dropped files".format(len(filesByType.other)), + "Unknown file extensions: " + ', '.join(extensions) + ) ) - ) - return - self.importImagesAsync(images, cameraInit) @staticmethod - def getImageFilesFromDrop(drop): + def getFilesByTypeFromDrop(drop): """ Args: @@ -585,19 +631,14 @@ def getImageFilesFromDrop(drop): """ urls = drop.property("urls") # Build the list of images paths - images = [] - otherFiles = [] + filesByType = multiview.FilesByType() for url in urls: localFile = url.toLocalFile() if os.path.isdir(localFile): # get folder content - images.extend(multiview.findImageFiles(localFile)) - elif multiview.isImageFile(localFile): - images.append(localFile) - elif multiview.isVideoFile(localFile): - images.append(localFile) - elif multiview.isPanoramaFile(localFile): - pass - return images + filesByType.extend(multiview.findFilesByTypeInFolder(localFile)) + else: + filesByType.addFile(localFile) + return filesByType def importImagesFromFolder(self, path, recursive=False): """ @@ -615,7 +656,7 @@ def importImagesFromFolder(self, path, recursive=False): paths.append(path) for p in paths: if os.path.isdir(p): # get folder content - images.extend(multiview.findImageFiles(p, recursive)) + images.extend(multiview.findFilesByTypeInFolder(p, recursive)) elif multiview.isImageFile(p): images.append(p) if images: From 00e9067b898ed83dc81951d4078685720de552ed Mon Sep 17 00:00:00 2001 From: fabienservant Date: Fri, 8 Nov 2019 09:36:00 +0100 Subject: [PATCH 18/42] Adding node for camera downscale --- meshroom/nodes/aliceVision/CameraDownscale.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 meshroom/nodes/aliceVision/CameraDownscale.py diff --git a/meshroom/nodes/aliceVision/CameraDownscale.py b/meshroom/nodes/aliceVision/CameraDownscale.py new file mode 100644 index 0000000000..894c3cc3c6 --- /dev/null +++ b/meshroom/nodes/aliceVision/CameraDownscale.py @@ -0,0 +1,49 @@ +__version__ = "1.0" + +import json +import os + +from meshroom.core import desc + + +class CameraDownscale(desc.CommandLineNode): + commandLine = 'aliceVision_cameraDownscale {allParams}' + size = desc.DynamicNodeSize('input') + + inputs = [ + desc.File( + name='input', + label='Input', + description="SfM Data File", + value='', + uid=[0], + ), + desc.FloatParam( + name='rescalefactor', + label='RescaleFactor', + description='Newsize = rescalefactor * oldsize', + value=0.5, + range=(0.0, 1.0, 0.1), + uid=[0], + advanced=True, + ), + desc.ChoiceParam( + name='verboseLevel', + label='Verbose Level', + description='Verbosity level (fatal, error, warning, info, debug, trace).', + value='info', + values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'], + exclusive=True, + uid=[], + ), + ] + + outputs = [ + desc.File( + name='outSfMDataFilename', + label='Output SfMData File', + description='Path to the output sfmdata file', + value=desc.Node.internalFolder + 'sfmData.abc', + uid=[], + ) + ] From 45efa8bb0ee9cd2f197ed95e3dddee4750160822 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Tue, 12 Nov 2019 12:57:41 +0100 Subject: [PATCH 19/42] [nodes] LDRToHDR: expose more options --- meshroom/nodes/aliceVision/LDRToHDR.py | 83 +++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/meshroom/nodes/aliceVision/LDRToHDR.py b/meshroom/nodes/aliceVision/LDRToHDR.py index 4b301573b9..22616e5b46 100644 --- a/meshroom/nodes/aliceVision/LDRToHDR.py +++ b/meshroom/nodes/aliceVision/LDRToHDR.py @@ -32,15 +32,6 @@ class LDRToHDR(desc.CommandLineNode): value='', uid=[0], ), - desc.ChoiceParam( - name='verboseLevel', - label='Verbose Level', - description='Verbosity level (fatal, error, warning, info, debug, trace).', - value='info', - values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'], - exclusive=True, - uid=[], - ), desc.IntParam( name='groupSize', label='Exposure bracket count', @@ -57,6 +48,80 @@ class LDRToHDR(desc.CommandLineNode): range=(0.0, 1.0, 0.01), uid=[0], ), + desc.BoolParam( + name='fisheyeLens', + label='Fisheye Lens', + description="Enable if a fisheye lens has been used.\n " + "This will improve the estimation of the Camera's Response Function by considering only the pixels in the center of the image\n" + "and thus ignore undefined/noisy pixels outside the circle defined by the fisheye lens.", + value=False, + uid=[0], + ), + desc.ChoiceParam( + name='calibrationMethod', + label='Calibration Method', + description="Method used for camera calibration \n" + " * linear \n" + " * robertson \n" + " * debevec \n" + " * grossberg", + values=['linear', 'robertson', 'debevec', 'grossberg'], + value='linear', + exclusive=True, + uid=[0], + ), + desc.ChoiceParam( + name='calibrationWeight', + label='Calibration Weight', + description="Weight function used to calibrate camera response \n" + " * default (automatically selected according to the calibrationMethod) \n" + " * gaussian \n" + " * triangle \n" + " * plateau", + value='default', + values=['default', 'gaussian', 'triangle', 'plateau'], + exclusive=True, + uid=[0], + ), + desc.ChoiceParam( + name='fusionWeight', + label='Fusion Weight', + description="Weight function used to fuse all LDR images together \n" + " * gaussian \n" + " * triangle \n" + " * plateau", + value='gaussian', + values=['gaussian', 'triangle', 'plateau'], + exclusive=True, + uid=[0], + ), + desc.IntParam( + name='calibrationNbPoints', + label='Calibration Nb Points', + description='Internal number of points used for calibration.', + value=0, + range=(0, 10000000, 1000), + uid=[0], + advanced=True, + ), + desc.IntParam( + name='channelQuantizationPower', + label='Channel Quantization Power', + description='Quantization level like 8 bits or 10 bits.', + value=10, + range=(8, 14, 1), + uid=[0], + advanced=True, + ), + desc.ChoiceParam( + name='verboseLevel', + label='Verbose Level', + description='Verbosity level (fatal, error, warning, info, debug, trace).', + value='info', + values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'], + exclusive=True, + uid=[], + ), ] outputs = [ From 8b34d085ebab4b994f2f629a9b81436dd8d23be9 Mon Sep 17 00:00:00 2001 From: Fabien Servant Date: Wed, 13 Nov 2019 17:42:53 +0100 Subject: [PATCH 20/42] Splitting compositing and warping --- ...amaStitching.py => PanoramaCompositing.py} | 14 ++---- meshroom/nodes/aliceVision/PanoramaWarping.py | 48 +++++++++++++++++++ 2 files changed, 51 insertions(+), 11 deletions(-) rename meshroom/nodes/aliceVision/{PanoramaStitching.py => PanoramaCompositing.py} (75%) create mode 100644 meshroom/nodes/aliceVision/PanoramaWarping.py diff --git a/meshroom/nodes/aliceVision/PanoramaStitching.py b/meshroom/nodes/aliceVision/PanoramaCompositing.py similarity index 75% rename from meshroom/nodes/aliceVision/PanoramaStitching.py rename to meshroom/nodes/aliceVision/PanoramaCompositing.py index 8621fe3b41..538215b16d 100644 --- a/meshroom/nodes/aliceVision/PanoramaStitching.py +++ b/meshroom/nodes/aliceVision/PanoramaCompositing.py @@ -6,15 +6,15 @@ from meshroom.core import desc -class PanoramaStitching(desc.CommandLineNode): - commandLine = 'aliceVision_panoramaStitching {allParams}' +class PanoramaCompositing(desc.CommandLineNode): + commandLine = 'aliceVision_panoramaCompositing {allParams}' size = desc.DynamicNodeSize('input') inputs = [ desc.File( name='input', label='Input', - description="SfM Data File", + description="Panorama Warping result", value='', uid=[0], ), @@ -28,14 +28,6 @@ class PanoramaStitching(desc.CommandLineNode): uid=[0], group='', # not part of allParams, as this is not a parameter for the command line ), - desc.IntParam( - name='panoramaWidth', - label='Panorama Width', - description='Panorama width (pixels). 0 For automatic size', - value=1000, - range=(0, 50000, 1000), - uid=[0] - ), desc.ChoiceParam( name='verboseLevel', label='Verbose Level', diff --git a/meshroom/nodes/aliceVision/PanoramaWarping.py b/meshroom/nodes/aliceVision/PanoramaWarping.py new file mode 100644 index 0000000000..0034630fa1 --- /dev/null +++ b/meshroom/nodes/aliceVision/PanoramaWarping.py @@ -0,0 +1,48 @@ +__version__ = "1.0" + +import json +import os + +from meshroom.core import desc + + +class PanoramaWarping(desc.CommandLineNode): + commandLine = 'aliceVision_panoramaWarping {allParams}' + size = desc.DynamicNodeSize('input') + + inputs = [ + desc.File( + name='input', + label='Input', + description="SfM Data File", + value='', + uid=[0], + ), + desc.IntParam( + name='panoramaWidth', + label='Panorama Width', + description='Panorama width (pixels). 0 For automatic size', + value=1000, + range=(0, 50000, 1000), + uid=[0] + ), + desc.ChoiceParam( + name='verboseLevel', + label='Verbose Level', + description='Verbosity level (fatal, error, warning, info, debug, trace).', + value='info', + values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'], + exclusive=True, + uid=[], + ), + ] + + outputs = [ + desc.File( + name='output', + label='Output directory', + description='', + value=desc.Node.internalFolder, + uid=[], + ), + ] From 6730dd3996cbf72e8bd034af0a8873bf0ae07f6a Mon Sep 17 00:00:00 2001 From: fabienservant Date: Mon, 25 Nov 2019 08:49:52 +0100 Subject: [PATCH 21/42] add option for multiband --- meshroom/nodes/aliceVision/PanoramaCompositing.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/meshroom/nodes/aliceVision/PanoramaCompositing.py b/meshroom/nodes/aliceVision/PanoramaCompositing.py index 538215b16d..3b587c5a52 100644 --- a/meshroom/nodes/aliceVision/PanoramaCompositing.py +++ b/meshroom/nodes/aliceVision/PanoramaCompositing.py @@ -28,6 +28,13 @@ class PanoramaCompositing(desc.CommandLineNode): uid=[0], group='', # not part of allParams, as this is not a parameter for the command line ), + desc.BoolParam( + name='multiband', + label='Use Multiband', + description='Use multi band algorithm for compositing', + value=False, + uid=[0], + ), desc.ChoiceParam( name='verboseLevel', label='Verbose Level', From d72dd03ce647a3df4bc8ce0a8267d243709d4c2a Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Tue, 26 Nov 2019 20:53:24 +0100 Subject: [PATCH 22/42] [hdri] update default hdri pipeline --- meshroom/multiview.py | 2 +- meshroom/nodes/aliceVision/PanoramaEstimation.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/meshroom/multiview.py b/meshroom/multiview.py index 01776990b6..d57016574d 100644 --- a/meshroom/multiview.py +++ b/meshroom/multiview.py @@ -134,7 +134,7 @@ def hdriPipeline(graph): imagePairsList=imageMatching.output) panoramaExternalInfo = graph.addNewNode('PanoramaExternalInfo', - input=ldr2hdr.input) + input=ldr2hdr.outSfMDataFilename) panoramaEstimation = graph.addNewNode('PanoramaEstimation', input=panoramaExternalInfo.outSfMDataFilename, diff --git a/meshroom/nodes/aliceVision/PanoramaEstimation.py b/meshroom/nodes/aliceVision/PanoramaEstimation.py index bb5a904e77..b785b87223 100644 --- a/meshroom/nodes/aliceVision/PanoramaEstimation.py +++ b/meshroom/nodes/aliceVision/PanoramaEstimation.py @@ -81,7 +81,7 @@ class PanoramaEstimation(desc.CommandLineNode): " * from essential matrix\n" " * from homography matrix", values=['essential_matrix', 'homography_matrix'], - value='essential_matrix', + value='homography_matrix', exclusive=True, uid=[0], advanced=True, @@ -90,7 +90,7 @@ class PanoramaEstimation(desc.CommandLineNode): name='refine', label='Refine', description='Refine camera relative poses, points and optionally internal camera parameter', - value=False, + value=True, uid=[0], ), desc.BoolParam( From 7eff5fb29bf5a8c80ab2867612a5965c2a187ce9 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Mon, 9 Dec 2019 21:56:46 +0100 Subject: [PATCH 23/42] [multiview] update hdri pipeline with PanoramaWarping/Compositing --- meshroom/multiview.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/meshroom/multiview.py b/meshroom/multiview.py index d57016574d..af9f6296b1 100644 --- a/meshroom/multiview.py +++ b/meshroom/multiview.py @@ -141,9 +141,12 @@ def hdriPipeline(graph): featuresFolders=featureMatching.featuresFolders, matchesFolders=[featureMatching.output]) - panoramaStitching = graph.addNewNode('PanoramaStitching', + panoramaWarping = graph.addNewNode('PanoramaWarping', input=panoramaEstimation.outSfMDataFilename) + panoramaCompositing = graph.addNewNode('PanoramaCompositing', + input=panoramaWarping.output) + return [ cameraInit, featureExtraction, @@ -151,7 +154,8 @@ def hdriPipeline(graph): featureMatching, panoramaExternalInfo, panoramaEstimation, - panoramaStitching, + panoramaWarping, + panoramaCompositing, ] From d21079e08266682c302ead5e10f9ad3ecd17db86 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Mon, 9 Dec 2019 21:57:23 +0100 Subject: [PATCH 24/42] [nodes] ldr2hdr: add laguerre, refine exposures and calibration downscale --- meshroom/nodes/aliceVision/LDRToHDR.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/meshroom/nodes/aliceVision/LDRToHDR.py b/meshroom/nodes/aliceVision/LDRToHDR.py index 22616e5b46..1ccb452598 100644 --- a/meshroom/nodes/aliceVision/LDRToHDR.py +++ b/meshroom/nodes/aliceVision/LDRToHDR.py @@ -57,6 +57,13 @@ class LDRToHDR(desc.CommandLineNode): value=False, uid=[0], ), + desc.BoolParam( + name='calibrationRefineExposures', + label='Refine Exposures', + description="Refine exposures provided by metadata (shutter speed, f-number, iso). Only available for 'laguerre' calibration method.", + value=False, + uid=[0], + ), desc.ChoiceParam( name='calibrationMethod', label='Calibration Method', @@ -64,8 +71,9 @@ class LDRToHDR(desc.CommandLineNode): " * linear \n" " * robertson \n" " * debevec \n" - " * grossberg", - values=['linear', 'robertson', 'debevec', 'grossberg'], + " * grossberg \n" + " * laguerre", + values=['linear', 'robertson', 'debevec', 'grossberg', 'laguerre'], value='linear', exclusive=True, uid=[0], @@ -104,6 +112,15 @@ class LDRToHDR(desc.CommandLineNode): uid=[0], advanced=True, ), + desc.IntParam( + name='calibrationDownscale', + label='Calibration Downscale', + description='Scaling factor applied to images before calibration of the response function to reduce the impact of misalignment.', + value=4, + range=(1, 16, 1), + uid=[0], + advanced=True, + ), desc.IntParam( name='channelQuantizationPower', label='Channel Quantization Power', From eeff5c2a681ac8dd1b3dba15012fa14dbc80561f Mon Sep 17 00:00:00 2001 From: fabienservant Date: Wed, 11 Dec 2019 09:48:17 +0100 Subject: [PATCH 25/42] add option to bypass HDR --- meshroom/nodes/aliceVision/LDRToHDR.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/meshroom/nodes/aliceVision/LDRToHDR.py b/meshroom/nodes/aliceVision/LDRToHDR.py index 1ccb452598..7bba4f2bb7 100644 --- a/meshroom/nodes/aliceVision/LDRToHDR.py +++ b/meshroom/nodes/aliceVision/LDRToHDR.py @@ -64,6 +64,13 @@ class LDRToHDR(desc.CommandLineNode): value=False, uid=[0], ), + desc.BoolParam( + name='byPass', + label='bypass convert', + description="Bypass HDR creation and use the medium bracket as the source for the next steps", + value=False, + uid=[0], + ), desc.ChoiceParam( name='calibrationMethod', label='Calibration Method', From d29b48e2397d28179dfe19d6a6cdd57f3a850e20 Mon Sep 17 00:00:00 2001 From: fabienservant Date: Wed, 11 Dec 2019 09:48:32 +0100 Subject: [PATCH 26/42] add rotation offsets --- .../nodes/aliceVision/PanoramaEstimation.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/meshroom/nodes/aliceVision/PanoramaEstimation.py b/meshroom/nodes/aliceVision/PanoramaEstimation.py index b785b87223..6aaff58f36 100644 --- a/meshroom/nodes/aliceVision/PanoramaEstimation.py +++ b/meshroom/nodes/aliceVision/PanoramaEstimation.py @@ -62,6 +62,24 @@ class PanoramaEstimation(desc.CommandLineNode): uid=[0], advanced=True, ), + desc.FloatParam( + name='offsetLongitude', + label='Longitude offset (deg.)', + description='''Offset to the panorama longitude''', + value=0.0, + range=(-180.0, 180.0, 1.0), + uid=[0], + advanced=True, + ), + desc.FloatParam( + name='offsetLatitude', + label='Latitude offset (deg.)', + description='''Offset to the panorama latitude''', + value=0.0, + range=(-90.0, 90.0, 1.0), + uid=[0], + advanced=True, + ), desc.ChoiceParam( name='rotationAveraging', label='Rotation Averaging Method', From 1e7c201216c2275d3833c194ffeed7857cf77cfa Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Wed, 11 Dec 2019 15:56:05 +0100 Subject: [PATCH 27/42] [hdri] update default values for hdri pipeline --- meshroom/multiview.py | 1 + meshroom/nodes/aliceVision/PanoramaCompositing.py | 2 +- meshroom/nodes/aliceVision/PanoramaWarping.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/meshroom/multiview.py b/meshroom/multiview.py index af9f6296b1..fb56a58698 100644 --- a/meshroom/multiview.py +++ b/meshroom/multiview.py @@ -125,6 +125,7 @@ def hdriPipeline(graph): featureExtraction = graph.addNewNode('FeatureExtraction', input=ldr2hdr.outSfMDataFilename) + featureExtraction.describerPreset.value = 'high' imageMatching = graph.addNewNode('ImageMatching', input=featureExtraction.input, featuresFolders=[featureExtraction.output]) diff --git a/meshroom/nodes/aliceVision/PanoramaCompositing.py b/meshroom/nodes/aliceVision/PanoramaCompositing.py index 3b587c5a52..4b5a3792b1 100644 --- a/meshroom/nodes/aliceVision/PanoramaCompositing.py +++ b/meshroom/nodes/aliceVision/PanoramaCompositing.py @@ -32,7 +32,7 @@ class PanoramaCompositing(desc.CommandLineNode): name='multiband', label='Use Multiband', description='Use multi band algorithm for compositing', - value=False, + value=True, uid=[0], ), desc.ChoiceParam( diff --git a/meshroom/nodes/aliceVision/PanoramaWarping.py b/meshroom/nodes/aliceVision/PanoramaWarping.py index 0034630fa1..a127fe3524 100644 --- a/meshroom/nodes/aliceVision/PanoramaWarping.py +++ b/meshroom/nodes/aliceVision/PanoramaWarping.py @@ -22,7 +22,7 @@ class PanoramaWarping(desc.CommandLineNode): name='panoramaWidth', label='Panorama Width', description='Panorama width (pixels). 0 For automatic size', - value=1000, + value=10000, range=(0, 50000, 1000), uid=[0] ), From d92e7613eb06c291e274d1bdb791e51dad03e5ef Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Thu, 12 Dec 2019 20:42:03 +0100 Subject: [PATCH 28/42] [nodes] LDRToHDR: add highlight params --- meshroom/nodes/aliceVision/LDRToHDR.py | 32 +++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/meshroom/nodes/aliceVision/LDRToHDR.py b/meshroom/nodes/aliceVision/LDRToHDR.py index 7bba4f2bb7..46cb5d16dc 100644 --- a/meshroom/nodes/aliceVision/LDRToHDR.py +++ b/meshroom/nodes/aliceVision/LDRToHDR.py @@ -41,13 +41,39 @@ class LDRToHDR(desc.CommandLineNode): uid=[0] ), desc.FloatParam( - name='expandDynamicRange', - label='Expand Dynamic Range', - description='float value between 0 and 1 to correct clamped high values in dynamic range: use 0 for no correction, 0.5 for interior lighting and 1 for outdoor lighting.', + name='highlightCorrectionFactor', + label='Highlights correction', + description='Pixels saturated in all input images have a partial information about their real luminance.\n' + 'We only know that the value should be >= to the standard hdr fusion.\n' + 'This parameters allows to perform a post-processing step to put saturated pixels to a constant ' + 'value defined by the `highlightsMaxLuminance` parameter.\n' + 'This parameter is float to enable to weight this correction.', value=1.0, range=(0.0, 1.0, 0.01), uid=[0], ), + desc.FloatParam( + name='highlightTargetLux', + label='Highlight Target Luminance (Lux)', + description='This is an arbitrary target value (in Lux) used to replace the unknown luminance value of the saturated pixels.\n' + '\n' + 'Some Outdoor Reference Light Levels:\n' + ' * 120,000 lux : Brightest sunlight\n' + ' * 110,000 lux : Bright sunlight\n' + ' * 20,000 lux : Shade illuminated by entire clear blue sky, midday\n' + ' * 1,000 lux : Typical overcast day, midday\n' + ' * 400 lux : Sunrise or sunset on a clear day\n' + ' * 40 lux : Fully overcast, sunset/sunrise\n' + '\n' + 'Some Indoor Reference Light Levels:\n' + ' * 20000 lux : Max Usually Used Indoor\n' + ' * 750 lux : Supermarkets\n' + ' * 500 lux : Office Work\n' + ' * 150 lux : Home\n', + value=120000.0, + range=(1000.0, 150000.0, 1.0), + uid=[0], + ), desc.BoolParam( name='fisheyeLens', label='Fisheye Lens', From a1c98024508a313f61c6592e77cbff004e45a007 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Fri, 13 Dec 2019 19:36:22 +0100 Subject: [PATCH 29/42] [hdri] workaround for HDRI pipeline on tractor --- meshroom/multiview.py | 4 +++- meshroom/nodes/aliceVision/PanoramaExternalInfo.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/meshroom/multiview.py b/meshroom/multiview.py index fb56a58698..86f2e55769 100644 --- a/meshroom/multiview.py +++ b/meshroom/multiview.py @@ -135,7 +135,9 @@ def hdriPipeline(graph): imagePairsList=imageMatching.output) panoramaExternalInfo = graph.addNewNode('PanoramaExternalInfo', - input=ldr2hdr.outSfMDataFilename) + input=ldr2hdr.outSfMDataFilename, + matchesFolders=[featureMatching.output] # Workaround for tractor submission with a fake dependency + ) panoramaEstimation = graph.addNewNode('PanoramaEstimation', input=panoramaExternalInfo.outSfMDataFilename, diff --git a/meshroom/nodes/aliceVision/PanoramaExternalInfo.py b/meshroom/nodes/aliceVision/PanoramaExternalInfo.py index 69e8e50bab..4fca9880ad 100644 --- a/meshroom/nodes/aliceVision/PanoramaExternalInfo.py +++ b/meshroom/nodes/aliceVision/PanoramaExternalInfo.py @@ -25,6 +25,19 @@ class PanoramaExternalInfo(desc.CommandLineNode): value='', uid=[0], ), + desc.ListAttribute( + elementDesc=desc.File( + name='matchesFolder', + label='Matches Folder', + description="", + value='', + uid=[0], + ), + name='matchesFolders', + label='Matches Folders', + description="Folder(s) in which computed matches are stored. (WORKAROUND for valid Tractor graph submission)", + group='forDependencyOnly', + ), desc.ChoiceParam( name='verboseLevel', label='Verbose Level', From 9b1962f963802b9fa17ac3b89783a912a48300ad Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Fri, 13 Dec 2019 19:37:29 +0100 Subject: [PATCH 30/42] [hdri] update default values for FeatureExtraction and LDRToHDR --- meshroom/multiview.py | 2 +- meshroom/nodes/aliceVision/LDRToHDR.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/meshroom/multiview.py b/meshroom/multiview.py index 86f2e55769..5217a211f5 100644 --- a/meshroom/multiview.py +++ b/meshroom/multiview.py @@ -125,7 +125,7 @@ def hdriPipeline(graph): featureExtraction = graph.addNewNode('FeatureExtraction', input=ldr2hdr.outSfMDataFilename) - featureExtraction.describerPreset.value = 'high' + featureExtraction.describerPreset.value = 'ultra' imageMatching = graph.addNewNode('ImageMatching', input=featureExtraction.input, featuresFolders=[featureExtraction.output]) diff --git a/meshroom/nodes/aliceVision/LDRToHDR.py b/meshroom/nodes/aliceVision/LDRToHDR.py index 46cb5d16dc..25b74ed540 100644 --- a/meshroom/nodes/aliceVision/LDRToHDR.py +++ b/meshroom/nodes/aliceVision/LDRToHDR.py @@ -107,7 +107,7 @@ class LDRToHDR(desc.CommandLineNode): " * grossberg \n" " * laguerre", values=['linear', 'robertson', 'debevec', 'grossberg', 'laguerre'], - value='linear', + value='debevec', exclusive=True, uid=[0], ), From 180a2565e1e36e977e2c1a0686b91f1b81a15b8c Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Fri, 13 Dec 2019 20:24:25 +0100 Subject: [PATCH 31/42] [bin] meshroom_photogrammetry: add hdri pipeline option --- bin/meshroom_photogrammetry | 48 +++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/bin/meshroom_photogrammetry b/bin/meshroom_photogrammetry index 1f3bbec5b2..1df80e61ba 100755 --- a/bin/meshroom_photogrammetry +++ b/bin/meshroom_photogrammetry @@ -10,7 +10,7 @@ meshroom.setupEnvironment() import meshroom.core.graph from meshroom import multiview -parser = argparse.ArgumentParser(description='Launch the full photogrammetry pipeline.') +parser = argparse.ArgumentParser(description='Launch the full photogrammetry or HDRI 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) ' @@ -19,9 +19,8 @@ parser.add_argument('-I', '--inputRecursive', metavar='FOLDERS/IMAGES', type=str default=[], help='Input folders containing all images recursively.') -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. ' +parser.add_argument('-p', '--pipeline', metavar='photogrammetry/hdri/MG_FILE', type=str, default='photogrammetry', + help='"photogrammetry" pipeline, "hdri" pipeline 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.') @@ -100,27 +99,30 @@ if hasSearchedForImages and not filesByType.images: exit(-1) # initialize photogrammetry pipeline -if args.pipeline: +if args.pipeline.lower() == "photogrammetry": + # default photogrammetry pipeline + graph = multiview.photogrammetry(inputViewpoints=views, inputIntrinsics=intrinsics, output=args.output) +elif args.pipeline.lower() == "hdri": + # default hdri pipeline + graph = multiview.hdri(inputViewpoints=views, inputIntrinsics=intrinsics, output=args.output) +else: # custom pipeline graph = meshroom.core.graph.loadGraph(args.pipeline) - cameraInit = getOnlyNodeOfType(graph, 'CameraInit') - # reset graph inputs - cameraInit.viewpoints.resetValue() - cameraInit.intrinsics.resetValue() - # add views and intrinsics (if any) read from args.input - cameraInit.viewpoints.extend(views) - cameraInit.intrinsics.extend(intrinsics) - - if not graph.canComputeLeaves: - raise RuntimeError("Graph cannot be computed. Check for compatibility issues.") - - if args.output: - publish = getOnlyNodeOfType(graph, 'Publish') - publish.output.value = args.output -else: - # default pipeline - graph = multiview.photogrammetry(inputViewpoints=views, inputIntrinsics=intrinsics, output=args.output) - cameraInit = getOnlyNodeOfType(graph, 'CameraInit') + +cameraInit = getOnlyNodeOfType(graph, 'CameraInit') +# reset graph inputs +cameraInit.viewpoints.resetValue() +cameraInit.intrinsics.resetValue() +# add views and intrinsics (if any) read from args.input +cameraInit.viewpoints.extend(views) +cameraInit.intrinsics.extend(intrinsics) + +if not graph.canComputeLeaves: + raise RuntimeError("Graph cannot be computed. Check for compatibility issues.") + +if args.output: + publish = getOnlyNodeOfType(graph, 'Publish') + publish.output.value = args.output if filesByType.images: views, intrinsics = cameraInit.nodeDesc.buildIntrinsics(cameraInit, filesByType.images) From b3653aaae215652a2b0c88e7de21b02302ff23f3 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Fri, 13 Dec 2019 20:24:41 +0100 Subject: [PATCH 32/42] [bin] meshroom_photogrammetry: add submit on renderfarm --- bin/meshroom_photogrammetry | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/bin/meshroom_photogrammetry b/bin/meshroom_photogrammetry index 1df80e61ba..bd6855f17b 100755 --- a/bin/meshroom_photogrammetry +++ b/bin/meshroom_photogrammetry @@ -59,6 +59,13 @@ parser.add_argument('--forceStatus', help='Force computation if status is RUNNIN 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('--submitter', + type=str, + default='SimpleFarm', + help='Execute job with a specific submitter.') + args = parser.parse_args() @@ -179,6 +186,11 @@ if not args.output: # find end nodes (None will compute all graph) toNodes = graph.findNodes(args.toNode) if args.toNode else None -if args.compute: +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=toNodes) +elif args.compute: # start computation meshroom.core.graph.executeGraph(graph, toNodes=toNodes, forceCompute=args.forceCompute, forceStatus=args.forceStatus) From ec94a21d5bd7e05972a135322e29e19b5bbd263f Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Fri, 13 Dec 2019 20:56:51 +0100 Subject: [PATCH 33/42] [nodes] LDRToHDR: rename param to nbBrackets --- meshroom/nodes/aliceVision/LDRToHDR.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/meshroom/nodes/aliceVision/LDRToHDR.py b/meshroom/nodes/aliceVision/LDRToHDR.py index 25b74ed540..ecf6352c31 100644 --- a/meshroom/nodes/aliceVision/LDRToHDR.py +++ b/meshroom/nodes/aliceVision/LDRToHDR.py @@ -22,7 +22,7 @@ def computeSize(self, node): class LDRToHDR(desc.CommandLineNode): commandLine = 'aliceVision_convertLDRToHDR {allParams}' - size = DividedInputNodeSize('input', 'groupSize') + size = DividedInputNodeSize('input', 'nbBrackets') inputs = [ desc.File( @@ -33,19 +33,19 @@ class LDRToHDR(desc.CommandLineNode): uid=[0], ), desc.IntParam( - name='groupSize', - label='Exposure bracket count', - description='Number of exposure brackets used per HDR image', - value=3, - range=(0, 10, 1), + name='nbBrackets', + label='Number of Brackets', + description='Number of exposure brackets per HDR image.', + value=0, + range=(0, 15, 1), uid=[0] ), desc.FloatParam( name='highlightCorrectionFactor', - label='Highlights correction', + label='Highlights Correction', description='Pixels saturated in all input images have a partial information about their real luminance.\n' 'We only know that the value should be >= to the standard hdr fusion.\n' - 'This parameters allows to perform a post-processing step to put saturated pixels to a constant ' + 'This parameter allows to perform a post-processing step to put saturated pixels to a constant ' 'value defined by the `highlightsMaxLuminance` parameter.\n' 'This parameter is float to enable to weight this correction.', value=1.0, From 7d70da2483baea6572d746f37a87ca3950fe5571 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Fri, 13 Dec 2019 20:58:36 +0100 Subject: [PATCH 34/42] [nodes] LDRToHDR: particular case for nbBrackets=0 --- meshroom/nodes/aliceVision/LDRToHDR.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meshroom/nodes/aliceVision/LDRToHDR.py b/meshroom/nodes/aliceVision/LDRToHDR.py index ecf6352c31..c21c227a90 100644 --- a/meshroom/nodes/aliceVision/LDRToHDR.py +++ b/meshroom/nodes/aliceVision/LDRToHDR.py @@ -17,6 +17,8 @@ def __init__(self, param, divParam): def computeSize(self, node): s = super(DividedInputNodeSize, self).computeSize(node) divParam = node.attribute(self._divParam) + if divParam.value == 0: + return s return s / divParam.value From 7e634ea9f55974e56c2f52628b1308f152ab6f10 Mon Sep 17 00:00:00 2001 From: Fabien Servant Date: Mon, 16 Dec 2019 09:40:19 +0100 Subject: [PATCH 35/42] Add a selector for compositer type --- meshroom/nodes/aliceVision/PanoramaCompositing.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/meshroom/nodes/aliceVision/PanoramaCompositing.py b/meshroom/nodes/aliceVision/PanoramaCompositing.py index 4b5a3792b1..441d62b933 100644 --- a/meshroom/nodes/aliceVision/PanoramaCompositing.py +++ b/meshroom/nodes/aliceVision/PanoramaCompositing.py @@ -28,12 +28,14 @@ class PanoramaCompositing(desc.CommandLineNode): uid=[0], group='', # not part of allParams, as this is not a parameter for the command line ), - desc.BoolParam( - name='multiband', - label='Use Multiband', - description='Use multi band algorithm for compositing', - value=True, - uid=[0], + desc.ChoiceParam( + name='compositerType', + label='Compositer Type', + description='Which compositer should be used to blend images', + value=['multiband'], + values=['replace', 'alpha', 'multiband'], + exclusive=True, + uid=[0] ), desc.ChoiceParam( name='verboseLevel', From 18f2161e06c63058ff4bb3593ba4ea3492aa3df2 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Mon, 16 Dec 2019 12:33:22 +0100 Subject: [PATCH 36/42] [ui] reconstruction: avoid problems on empty metadata --- meshroom/ui/reconstruction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshroom/ui/reconstruction.py b/meshroom/ui/reconstruction.py index 28c87384b2..085508c449 100755 --- a/meshroom/ui/reconstruction.py +++ b/meshroom/ui/reconstruction.py @@ -207,7 +207,7 @@ def _updateInitialParams(self): self._metadata = {} else: self._initialIntrinsics = self._reconstruction.getIntrinsic(self._viewpoint) - self._metadata = json.loads(self._viewpoint.metadata.value) + self._metadata = json.loads(self._viewpoint.metadata.value) if self._viewpoint.metadata.value else {} self.initialParamsChanged.emit() def _updateSfMParams(self): From cbb176dfdfe4255466eacb22b79da4f33327f6c9 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Mon, 16 Dec 2019 12:38:06 +0100 Subject: [PATCH 37/42] [nodes] LDRToHDR: add automatic detection of the number of brackets --- meshroom/nodes/aliceVision/LDRToHDR.py | 77 ++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 4 deletions(-) diff --git a/meshroom/nodes/aliceVision/LDRToHDR.py b/meshroom/nodes/aliceVision/LDRToHDR.py index c21c227a90..5eff20da4e 100644 --- a/meshroom/nodes/aliceVision/LDRToHDR.py +++ b/meshroom/nodes/aliceVision/LDRToHDR.py @@ -1,4 +1,4 @@ -__version__ = "1.0" +__version__ = "2.0" import json import os @@ -35,12 +35,22 @@ class LDRToHDR(desc.CommandLineNode): uid=[0], ), desc.IntParam( - name='nbBrackets', + name='userNbBrackets', label='Number of Brackets', - description='Number of exposure brackets per HDR image.', + description='Number of exposure brackets per HDR image (0 for automatic).', value=0, range=(0, 15, 1), - uid=[0] + uid=[0], + group='user', # not used directly on the command line + ), + desc.IntParam( + name='nbBrackets', + label='Automatic Nb Brackets', + description='Number of exposure brackets used per HDR image. It is detected automatically from input Viewpoints metadata if "userNbBrackets" is 0, else it is equal to "userNbBrackets".', + value=0, + range=(0, 10, 1), + uid=[], + advanced=True, ), desc.FloatParam( name='highlightCorrectionFactor', @@ -185,3 +195,62 @@ class LDRToHDR(desc.CommandLineNode): uid=[], ) ] + + @classmethod + def update(cls, node): + if not isinstance(node.nodeDesc, cls): + raise ValueError("Node {} is not an instance of type {}".format(node, cls)) + # TODO: use Node version for this test + if 'userNbBrackets' not in node.getAttributes().keys(): + # Old version of the node + return + if node.userNbBrackets.value != 0: + node.nbBrackets.value = node.userNbBrackets.value + return + # logging.info("[LDRToHDR] Update start: version:" + str(node.packageVersion)) + cameraInitOutput = node.input.getLinkParam() + if not cameraInitOutput: + node.nbBrackets.value = 0 + return + viewpoints = cameraInitOutput.node.viewpoints.value + + # logging.info("[LDRToHDR] Update start: nb viewpoints:" + str(len(viewpoints))) + inputs = [] + for viewpoint in viewpoints: + jsonMetadata = viewpoint.metadata.value + if not jsonMetadata: + # no metadata, we cannot found the number of brackets + node.nbBrackets.value = 0 + return + d = json.loads(jsonMetadata) + fnumber = d.get("FNumber", d.get("Exif:ApertureValue", "")) + shutterSpeed = d.get("Exif:ShutterSpeedValue", "") # also "ExposureTime"? + iso = d.get("Exif:ISOSpeedRatings", "") + if not fnumber and not shutterSpeed: + # if one image without shutter or fnumber, we cannot found the number of brackets + node.nbBrackets.value = 0 + return + inputs.append((viewpoint.path.value, (fnumber, shutterSpeed, iso))) + inputs.sort() + + exposureGroups = [] + exposures = [] + for path, exp in inputs: + if exposures and exp != exposures[-1] and exp == exposures[0]: + exposureGroups.append(exposures) + exposures = [exp] + else: + exposures.append(exp) + exposureGroups.append(exposures) + exposures = None + bracketSizes = set() + for expGroup in exposureGroups: + bracketSizes.add(len(expGroup)) + if len(bracketSizes) == 1: + node.nbBrackets.value = bracketSizes.pop() + # logging.info("[LDRToHDR] nb bracket size:" + str(node.nbBrackets.value)) + else: + node.nbBrackets.value = 0 + # logging.info("[LDRToHDR] Update end") + + From e0f4fcbcf0c34709b13429c796b37e9ab6792ade Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Mon, 16 Dec 2019 13:49:20 +0100 Subject: [PATCH 38/42] [nodes] Panorama: minor fix to compositerType default value --- meshroom/nodes/aliceVision/PanoramaCompositing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshroom/nodes/aliceVision/PanoramaCompositing.py b/meshroom/nodes/aliceVision/PanoramaCompositing.py index 441d62b933..34af53ad70 100644 --- a/meshroom/nodes/aliceVision/PanoramaCompositing.py +++ b/meshroom/nodes/aliceVision/PanoramaCompositing.py @@ -32,7 +32,7 @@ class PanoramaCompositing(desc.CommandLineNode): name='compositerType', label='Compositer Type', description='Which compositer should be used to blend images', - value=['multiband'], + value='multiband', values=['replace', 'alpha', 'multiband'], exclusive=True, uid=[0] From 9775924df0e8262e72ce9e8ab55899f382e0e015 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Mon, 16 Dec 2019 17:49:04 +0100 Subject: [PATCH 39/42] [bin] photogrammetry: reduce the number of updates with "GraphModification" group --- bin/meshroom_photogrammetry | 150 ++++++++++++++++++------------------ meshroom/multiview.py | 10 ++- 2 files changed, 83 insertions(+), 77 deletions(-) diff --git a/bin/meshroom_photogrammetry b/bin/meshroom_photogrammetry index bd6855f17b..b7d8fecc6e 100755 --- a/bin/meshroom_photogrammetry +++ b/bin/meshroom_photogrammetry @@ -105,80 +105,84 @@ if hasSearchedForImages and not filesByType.images: print("No image found") exit(-1) -# initialize photogrammetry pipeline -if args.pipeline.lower() == "photogrammetry": - # default photogrammetry pipeline - graph = multiview.photogrammetry(inputViewpoints=views, inputIntrinsics=intrinsics, output=args.output) -elif args.pipeline.lower() == "hdri": - # default hdri pipeline - graph = multiview.hdri(inputViewpoints=views, inputIntrinsics=intrinsics, output=args.output) -else: - # custom pipeline - graph = meshroom.core.graph.loadGraph(args.pipeline) - -cameraInit = getOnlyNodeOfType(graph, 'CameraInit') -# reset graph inputs -cameraInit.viewpoints.resetValue() -cameraInit.intrinsics.resetValue() -# add views and intrinsics (if any) read from args.input -cameraInit.viewpoints.extend(views) -cameraInit.intrinsics.extend(intrinsics) - -if not graph.canComputeLeaves: - raise RuntimeError("Graph cannot be computed. Check for compatibility issues.") - -if args.output: - publish = getOnlyNodeOfType(graph, 'Publish') - publish.output.value = args.output - -if filesByType.images: - views, intrinsics = cameraInit.nodeDesc.buildIntrinsics(cameraInit, filesByType.images) - cameraInit.viewpoints.value = views - cameraInit.intrinsics.value = intrinsics - -if args.overrides: - import io - import json - with io.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+)=(.*)') - 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: +graph = multiview.Graph(name=args.pipeline) + +with multiview.GraphModification(graph): + # initialize photogrammetry pipeline + if args.pipeline.lower() == "photogrammetry": + # default photogrammetry pipeline + multiview.photogrammetry(inputViewpoints=views, inputIntrinsics=intrinsics, output=args.output, graph=graph) + elif args.pipeline.lower() == "hdri": + # default hdri pipeline + graph = multiview.hdri(inputViewpoints=views, inputIntrinsics=intrinsics, output=args.output, graph=graph) + else: + # custom pipeline + graph.load(args.pipeline) + # graph.update() + + cameraInit = getOnlyNodeOfType(graph, 'CameraInit') + # reset graph inputs + cameraInit.viewpoints.resetValue() + cameraInit.intrinsics.resetValue() + # add views and intrinsics (if any) read from args.input + cameraInit.viewpoints.extend(views) + cameraInit.intrinsics.extend(intrinsics) + + if not graph.canComputeLeaves: + raise RuntimeError("Graph cannot be computed. Check for compatibility issues.") + + if args.output: + publish = getOnlyNodeOfType(graph, 'Publish') + publish.output.value = args.output + + if filesByType.images: + views, intrinsics = cameraInit.nodeDesc.buildIntrinsics(cameraInit, filesByType.images) + cameraInit.viewpoints.value = views + cameraInit.intrinsics.value = intrinsics + + if args.overrides: + import io + import json + with io.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+)=(.*)') + 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)) - 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 - -# 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)) + 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 + + # 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)) diff --git a/meshroom/multiview.py b/meshroom/multiview.py index 5217a211f5..73fe6fa5f3 100644 --- a/meshroom/multiview.py +++ b/meshroom/multiview.py @@ -82,7 +82,7 @@ def findFilesByTypeInFolder(folder, recursive=False): return output -def hdri(inputImages=list(), inputViewpoints=list(), inputIntrinsics=list(), output=''): +def hdri(inputImages=list(), inputViewpoints=list(), inputIntrinsics=list(), output='', graph=None): """ Create a new Graph with a complete HDRI pipeline. @@ -94,7 +94,8 @@ def hdri(inputImages=list(), inputViewpoints=list(), inputIntrinsics=list(), out Returns: Graph: the created graph """ - graph = Graph('HDRI') + if not graph: + graph = Graph('HDRI') with GraphModification(graph): nodes = hdriPipeline(graph) cameraInit = nodes[0] @@ -163,7 +164,7 @@ def hdriPipeline(graph): -def photogrammetry(inputImages=list(), inputViewpoints=list(), inputIntrinsics=list(), output=''): +def photogrammetry(inputImages=list(), inputViewpoints=list(), inputIntrinsics=list(), output='', graph=None): """ Create a new Graph with a complete photogrammetry pipeline. @@ -175,7 +176,8 @@ def photogrammetry(inputImages=list(), inputViewpoints=list(), inputIntrinsics=l Returns: Graph: the created graph """ - graph = Graph('Photogrammetry') + if not graph: + graph = Graph('Photogrammetry') with GraphModification(graph): sfmNodes, mvsNodes = photogrammetryPipeline(graph) cameraInit = sfmNodes[0] From 69a1f157ac830185db2e4bca650cfa718b96e46d Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Mon, 16 Dec 2019 20:10:04 +0100 Subject: [PATCH 40/42] [nodes] CameraInit: remove subprocess wait (communicate is enough) --- meshroom/nodes/aliceVision/CameraInit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshroom/nodes/aliceVision/CameraInit.py b/meshroom/nodes/aliceVision/CameraInit.py index 0c01b41c99..22c0ccab99 100644 --- a/meshroom/nodes/aliceVision/CameraInit.py +++ b/meshroom/nodes/aliceVision/CameraInit.py @@ -186,7 +186,7 @@ def buildIntrinsics(self, node, additionalViews=()): # logging.debug(' - commandLine:', cmd) proc = psutil.Popen(cmd, stdout=None, stderr=None, shell=True) stdout, stderr = proc.communicate() - proc.wait() + # proc.wait() if proc.returncode != 0: raise RuntimeError('CameraInit failed with error code {}.\nCommand was: "{}".\n'.format( proc.returncode, cmd) From be0fb571f94c5f6cf63b796167597b8c80f3440f Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Wed, 18 Dec 2019 19:03:53 +0100 Subject: [PATCH 41/42] [ui] set AA_EnableHighDpiScaling earlier to fix qt warnings --- meshroom/ui/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/meshroom/ui/app.py b/meshroom/ui/app.py index ec4a3b5dab..5e6589a364 100644 --- a/meshroom/ui/app.py +++ b/meshroom/ui/app.py @@ -70,11 +70,12 @@ def __init__(self, args): args = parser.parse_args(args[1:]) + QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) + super(MeshroomApp, self).__init__(QtArgs) self.setOrganizationName('AliceVision') self.setApplicationName('Meshroom') - self.setAttribute(Qt.AA_EnableHighDpiScaling) self.setApplicationVersion(meshroom.__version_name__) font = self.font() From 25127bb71bfff545e7a5c552ac3182f095ece2c0 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Wed, 18 Dec 2019 19:05:09 +0100 Subject: [PATCH 42/42] [nodes] LDRToHDR: declare cpu/ram usages --- meshroom/nodes/aliceVision/LDRToHDR.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/meshroom/nodes/aliceVision/LDRToHDR.py b/meshroom/nodes/aliceVision/LDRToHDR.py index 5eff20da4e..9da8f57f32 100644 --- a/meshroom/nodes/aliceVision/LDRToHDR.py +++ b/meshroom/nodes/aliceVision/LDRToHDR.py @@ -26,6 +26,9 @@ class LDRToHDR(desc.CommandLineNode): commandLine = 'aliceVision_convertLDRToHDR {allParams}' size = DividedInputNodeSize('input', 'nbBrackets') + cpu = desc.Level.INTENSIVE + ram = desc.Level.NORMAL + inputs = [ desc.File( name='input',