diff --git a/meshroom/nodes/aliceVision/LightingCalibration.py b/meshroom/nodes/aliceVision/LightingCalibration.py new file mode 100644 index 0000000000..0b9a80a867 --- /dev/null +++ b/meshroom/nodes/aliceVision/LightingCalibration.py @@ -0,0 +1,64 @@ +__version__ = "1.0" + +from meshroom.core import desc + + +class LightingCalibration(desc.CommandLineNode): + commandLine = 'aliceVision_lightingCalibration {allParams}' + category = 'Photometry' + documentation = ''' +Evaluate the lighting in a scene using spheres placed in the scene. +Can also be used to calibrate a lighting dome (RTI type). +''' + + inputs = [ + desc.File( + name='inputPath', + label='SfMData', + description='Input file. Could be SfMData file or folder.', + value='', + uid=[0] + ), + desc.File( + name='inputJSON', + label='Sphere Detection File', + description='Input JSON file containing spheres centers and radius.', + value='', + uid=[0] + ), + desc.BoolParam( + name='saveAsModel', + label='Save As Model', + description='Check if this calibration file will be used with other datasets.', + value=False, + uid=[0] + ), + desc.ChoiceParam( + name='method', + label='Calibration Method', + description='Method used for light calibration. Use "brightestPoint" for shiny spheres and "whiteSphere" for white matte spheres.', + values=['brightestPoint', 'whiteSphere'], + value='brightestPoint', + exclusive=True, + 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='outputFile', + label='Light File', + description='Light information will be written here.', + value=desc.Node.internalFolder +'/lights.json' , + uid=[] + ) + ] diff --git a/meshroom/nodes/aliceVision/NormalIntegration.py b/meshroom/nodes/aliceVision/NormalIntegration.py new file mode 100644 index 0000000000..6fbe440742 --- /dev/null +++ b/meshroom/nodes/aliceVision/NormalIntegration.py @@ -0,0 +1,55 @@ +__version__ = "1.0" + +from meshroom.core import desc + +class NormalIntegration(desc.CommandLineNode): + commandLine = 'aliceVision_normalIntegration {allParams}' + category = 'Photometry' + documentation = ''' +TODO. +''' + + inputs = [ + desc.File( + name='inputPath', + label='Normal Maps Folder', + description='Path to the folder containing the normal maps and the masks.', + value='', + uid=[0] + ), + desc.File( + name='sfmDataFile', + label='SfMData', + description='Input SfMData file.', + value='', + uid=[0], + ), + desc.IntParam( + name='downscale', + label='Downscale Factor', + description='Downscale factor for faster results.', + value=1, + range=(1, 10, 1), + advanced=True, + 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='outputPath', + label='Output Path', + description='Path to the output folder.', + value=desc.Node.internalFolder, + uid=[] + ) + ] diff --git a/meshroom/nodes/aliceVision/PhotometricStereo.py b/meshroom/nodes/aliceVision/PhotometricStereo.py new file mode 100644 index 0000000000..33c753a2ba --- /dev/null +++ b/meshroom/nodes/aliceVision/PhotometricStereo.py @@ -0,0 +1,147 @@ +__version__ = "1.0" + +from meshroom.core import desc + +class PhotometricStereo(desc.CommandLineNode): + commandLine = 'aliceVision_photometricStereo {allParams}' + category = 'Photometry' + documentation = ''' +Reconstruction using Photometric Stereo. A normal map is evaluated from several photographs taken from the same point of view, but under different lighting conditions. +The lighting conditions are assumed to be known. +''' + + inputs = [ + desc.File( + name='inputPath', + label='SfMData', + description='Input file. Could be an SfMData file or folder containing images.', + value='', + uid=[0] + ), + desc.File( + name='pathToJSONLightFile', + label='Light File', + description='Path to a JSON file containing the lighting information.\n' + 'If empty, .txt files are expected in the image folder.', + value='defaultJSON.txt', + uid=[0] + ), + desc.File( + name='maskPath', + label='Mask Folder Path', + description='Path to a folder containing masks or to a mask directly.', + value='', + uid=[0] + ), + desc.ChoiceParam( + name='SHOrder', + label='Spherical Harmonics Order', + description='Order of the spherical harmonics.\n' + '- 0: directional\n' + '- 1: directional + ambiant\n' + '- 2: second order spherical harmonics', + values=['0', '1', '2'], + value='0', + exclusive=True, + advanced=True, + uid=[0] + ), + desc.BoolParam( + name='removeAmbiant', + label='Remove Ambiant Light', + description='True if the ambiant light is to be removed on the PS images, false otherwise.', + value=False, + advanced=True, + uid=[0] + ), + desc.BoolParam( + name='isRobust', + label='Use Robust Algorithm', + description='True to use the robust algorithm, false otherwise.', + value=False, + advanced=True, + uid=[0] + ), + desc.IntParam( + name='downscale', + label='Downscale Factor', + description='Downscale factor for faster results.', + value=1, + range=(1, 10, 1), + advanced=True, + 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='outputPath', + label='Output Folder', + description='Path to the output folder', + value=desc.Node.internalFolder, + uid=[], + ), + desc.File( + name='outputSfmData', + label='SfMData', + description='Output path for the Sfm', + value=desc.Node.internalFolder + '/sfmData.sfm', + uid=[], + group='', # remove from command line + ), + desc.File( + name='outputSfmDataAlbedo', + label='SfMData Albedo', + description='', + value=desc.Node.internalFolder + '/albedoMaps.sfm', + uid=[], + group='', # remove from command line + ), + desc.File( + name='outputSfmDataNormal', + label='SfMData Normal', + description='', + value=desc.Node.internalFolder + '/normalMaps.sfm', + uid=[], + group='', # remove from command line + ), + # these attributes are only here to describe more accurately the output of the node + # by specifying that it generates 2 sequences of images + # (see in Viewer2D.qml how these attributes can be used) + desc.File( + name='normals', + label='Normal Maps Camera', + description='Generated normal maps in the camera coordinate system.', + semantic='image', + value=desc.Node.internalFolder + '_normals.exr', + uid=[], + group='', # do not export on the command line + ), + desc.File( + name='normalsWorld', + label='Normal Maps World', + description='Generated normal maps in the world coordinate system.', + semantic='image', + value=desc.Node.internalFolder + '_normals_w.exr', + uid=[], + group='', # do not export on the command line + ), + desc.File( + name='albedo', + label='Albedo Maps', + description='Generated albedo maps.', + semantic='image', + value=desc.Node.internalFolder + '_albedo.exr', + uid=[], + group='', # do not export on the command line + ), + ] diff --git a/meshroom/nodes/aliceVision/SfMTransfer.py b/meshroom/nodes/aliceVision/SfMTransfer.py index b0ea1a6705..c88dee13d7 100644 --- a/meshroom/nodes/aliceVision/SfMTransfer.py +++ b/meshroom/nodes/aliceVision/SfMTransfer.py @@ -1,4 +1,4 @@ -__version__ = "2.0" +__version__ = "2.1" from meshroom.core import desc @@ -80,6 +80,13 @@ class SfMTransfer(desc.AVCommandLineNode): value=True, uid=[0] ), + desc.BoolParam( + name='transferLandmarks', + label='Landmarks', + description='Transfer landmarks.', + value=True, + uid=[0] + ), desc.ChoiceParam( name='verboseLevel', label='Verbose Level', diff --git a/meshroom/nodes/aliceVision/SphereDetection.py b/meshroom/nodes/aliceVision/SphereDetection.py new file mode 100644 index 0000000000..417127d663 --- /dev/null +++ b/meshroom/nodes/aliceVision/SphereDetection.py @@ -0,0 +1,92 @@ +__version__ = "1.0" + +from meshroom.core import desc + + +class SphereDetection(desc.CommandLineNode): + commandLine = 'aliceVision_sphereDetection {allParams}' + category = 'Photometry' + documentation = ''' +Detect spheres in pictures. These spheres will be used for lighting calibration. +Spheres can be automatically detected or manually defined in the interface. +''' + + inputs = [ + desc.File( + name='input', + label="SfMData", + description='Input SfMData file.', + value='', + uid=[0] + ), + desc.File( + name='modelPath', + label='Detection Network', + description='Deep learning network for automatic calibration sphere detection.', + value='${ALICEVISION_SPHERE_DETECTION_MODEL}', + uid=[0] + ), + desc.BoolParam( + name='autoDetect', + label='Automatic Sphere Detection', + description='Automatic detection of calibration spheres', + value=False, + uid=[0] + ), + desc.FloatParam( + name="minScore", + label="Minimum Score", + description="Minimum score for the detection.", + value=0.0, + range=(0.0, 50.0, 0.01), + advanced=True, + uid=[0] + ), + desc.GroupAttribute( + name="sphereCenter", + label="Sphere Center", + description="Center of the circle (XY offset to the center of the image in pixels).", + groupDesc=[ + desc.FloatParam( + name="x", label="x", description="X Offset in pixels", + value=0.0, + uid=[0], + range=(-1000.0, 10000.0, 1.0)), + desc.FloatParam( + name="y", label="y", description="Y Offset in pixels", + value=0.0, + uid=[0], + range=(-1000.0, 10000.0, 1.0)), + ], + enabled=lambda node: not node.autoDetect.value, + group=None # skip group from command line + ), + desc.FloatParam( + name='sphereRadius', + label='Radius', + description='Sphere radius in pixels.', + value=500.0, + range=(0.0, 1000.0, 0.1), + enabled=lambda node: not node.autoDetect.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='output', + label='Light File Folder', + description='Light information will be written here.', + value=desc.Node.internalFolder, + uid=[] + ) + ] diff --git a/meshroom/pipelines/stereoPhotometry.mg b/meshroom/pipelines/stereoPhotometry.mg new file mode 100644 index 0000000000..0adf694ef0 --- /dev/null +++ b/meshroom/pipelines/stereoPhotometry.mg @@ -0,0 +1,75 @@ +{ + "header": { + "pipelineVersion": "2.2", + "releaseVersion": "2023.2.0-develop", + "fileVersion": "1.1", + "template": true, + "nodesVersions": { + "SphereDetection": "1.0", + "LightingCalibration": "1.0", + "Publish": "1.2", + "CameraInit": "9.0", + "PhotometricStereo": "1.0" + } + }, + "graph": { + "CameraInit_1": { + "nodeType": "CameraInit", + "position": [ + 0, + 0 + ], + "inputs": {} + }, + "SphereDetection_1": { + "nodeType": "SphereDetection", + "position": [ + 200, + 0 + ], + "inputs": { + "input": "{CameraInit_1.output}", + "autoDetect": true + } + }, + "LightingCalibration_1": { + "nodeType": "LightingCalibration", + "position": [ + 400, + 0 + ], + "inputs": { + "inputPath": "{SphereDetection_1.input}", + "inputJSON": "{SphereDetection_1.output}" + } + }, + "PhotometricStereo_1": { + "nodeType": "PhotometricStereo", + "position": [ + 600, + 0 + ], + "inputs": { + "inputPath": "{LightingCalibration_1.inputPath}", + "pathToJSONLightFile": "{LightingCalibration_1.outputFile}" + } + }, + "Publish_1": { + "nodeType": "Publish", + "position": [ + 800, + 0 + ], + "inputs": { + "inputFiles": [ + "{PhotometricStereo_1.outputSfmData}", + "{PhotometricStereo_1.outputSfmDataNormal}", + "{PhotometricStereo_1.normals}", + "{PhotometricStereo_1.normalsWorld}", + "{PhotometricStereo_1.albedo}", + "{PhotometricStereo_1.outputSfmDataAlbedo}" + ] + } + } + } +} \ No newline at end of file diff --git a/meshroom/ui/qml/Viewer/Viewer2D.qml b/meshroom/ui/qml/Viewer/Viewer2D.qml index 4bd1cade1b..7c437ba43a 100644 --- a/meshroom/ui/qml/Viewer/Viewer2D.qml +++ b/meshroom/ui/qml/Viewer/Viewer2D.qml @@ -214,13 +214,15 @@ FocusScope { var viewId = -1; var intrinsicId = -1; + var poseId = -1; var fileName = ""; if (_reconstruction) { viewId = _reconstruction.selectedViewId; intrinsicId = getViewpointAttribute("intrinsicId", viewId); - fileName = Filepath.removeExtension(Filepath.basename(getViewpointAttribute("path",viewId))); + poseId = getViewpointAttribute("poseId", viewId); + fileName = Filepath.removeExtension(Filepath.basename(getViewpointAttribute("path", viewId))); } - var patterns = {"": viewId,"": intrinsicId, "": fileName} + var patterns = {"": viewId, "": intrinsicId, "": poseId, "": fileName} return getFileAttributePath(displayedNode, outputAttribute.name,patterns); } @@ -592,6 +594,42 @@ FocusScope { } } + // LightingCalibration: display circle + // note: use a Loader to evaluate if a PanoramaInit node exist and displayFisheyeCircle checked at runtime + Loader { + anchors.centerIn: parent + property var activeNode: _reconstruction.activeNodes.get("SphereDetection").node + active: (displayLightingCircleLoader.checked && activeNode) + + // handle rotation/position based on available metadata + rotation: { + var orientation = m.imgMetadata ? m.imgMetadata["Orientation"] : 0 + switch(orientation) { + case "6": return 90; + case "8": return -90; + default: return 0; + } + } + + sourceComponent: CircleGizmo { + readOnly: false + x: activeNode.attribute("sphereCenter.x").value + y: activeNode.attribute("sphereCenter.y").value + radius: activeNode.attribute("sphereRadius").value + + border.width: Math.max(1, (3.0 / imgContainer.scale)) + onMoved: { + _reconstruction.setAttribute( + activeNode.attribute("sphereCenter"), + JSON.stringify([x, y]) + ); + } + onIncrementRadius: { + _reconstruction.setAttribute(activeNode.attribute("sphereRadius"), activeNode.attribute("sphereRadius").value + radiusOffset) + } + } + } + // ColorCheckerViewer: display color checker detection results // note: use a Loader to evaluate if a ColorCheckerDetection node exist and displayColorChecker checked at runtime Loader { @@ -1080,6 +1118,19 @@ FocusScope { enabled: activeNode && activeNode.attribute("useFisheye").value && !displayPanoramaViewer.checked visible: activeNode } + MaterialToolButton { + id: displayLightingCircleLoader + property var activeNode: _reconstruction.activeNodes.get('SphereDetection').node + ToolTip.text: "Display Lighting Circle: " + (activeNode ? activeNode.label : "No Node") + text: MaterialIcons.vignette + // text: MaterialIcons.panorama_fish_eye + font.pointSize: 11 + Layout.minimumWidth: 0 + checkable: true + checked: false + enabled: activeNode + visible: activeNode + } MaterialToolButton { id: displayColorCheckerViewerLoader property var activeNode: _reconstruction ? _reconstruction.activeNodes.get('ColorCheckerDetection').node : null