diff options
author | Fabien Castan <fabcastan@gmail.com> | 2022-10-19 17:16:57 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-19 17:16:57 +0300 |
commit | 1f78158c3bcc6971b76c22ce262ede76e94afba1 (patch) | |
tree | 1f2c4769bcfb2152e73dc665155d72071c5cdbaa | |
parent | ab0e132121a0372e6712f346f63c3ffd7348a070 (diff) | |
parent | 0424ae13c0aae341bae2ae1ecdcc1e729d849fe5 (diff) |
Merge pull request #1776 from alicevision/dev/lv/adapt2DViewerToNode
Show generated images in 2D viewer when double-clicking on node
45 files changed, 303 insertions, 130 deletions
diff --git a/meshroom/core/node.py b/meshroom/core/node.py index 372c5384..29df5be3 100644 --- a/meshroom/core/node.py +++ b/meshroom/core/node.py @@ -393,6 +393,9 @@ class NodeChunk(BaseObject): def isStopped(self): return self._status.status == Status.STOPPED + def isFinished(self): + return self._status.status == Status.SUCCESS + def process(self, forceCompute=False): if not forceCompute and self._status.status == Status.SUCCESS: logging.info("Node chunk already computed: {}".format(self.name)) @@ -752,6 +755,11 @@ class BaseNode(BaseObject): """ Return True if all chunks of this Node is either finished or running, False otherwise. """ return all(chunk.isFinishedOrRunning() for chunk in self._chunks) + @Slot(result=bool) + def isPartiallyFinished(self): + """ Return True is at least one chunk of this Node is finished, False otherwise. """ + return any(chunk.isFinished() for chunk in self._chunks) + def alreadySubmittedChunks(self): return [ch for ch in self._chunks if ch.isAlreadySubmitted()] diff --git a/meshroom/nodes/aliceVision/CameraLocalization.py b/meshroom/nodes/aliceVision/CameraLocalization.py index d8aabe9a..1f98119d 100644 --- a/meshroom/nodes/aliceVision/CameraLocalization.py +++ b/meshroom/nodes/aliceVision/CameraLocalization.py @@ -209,14 +209,14 @@ class CameraLocalization(desc.CommandLineNode): outputs = [ desc.File( name='outputAlembic', - label='Output Alembic', + label='Alembic', description='''Filename for the SfMData export file (where camera poses will be stored)''', value=desc.Node.internalFolder + 'trackedCameras.abc', uid=[], ), desc.File( name='outputJSON', - label='Output JSON', + label='JSON', description='''Filename for the localization results as .json''', value=desc.Node.internalFolder + 'trackedCameras.json', uid=[], diff --git a/meshroom/nodes/aliceVision/CameraRigCalibration.py b/meshroom/nodes/aliceVision/CameraRigCalibration.py index 117457c3..3fa103e1 100644 --- a/meshroom/nodes/aliceVision/CameraRigCalibration.py +++ b/meshroom/nodes/aliceVision/CameraRigCalibration.py @@ -165,7 +165,7 @@ class CameraRigCalibration(desc.CommandLineNode): outputs = [ desc.File( name='outfile', - label='Output File', + label='File', description='''The name of the file where to store the calibration data''', value=desc.Node.internalFolder + 'cameraRigCalibration.rigCal', uid=[], diff --git a/meshroom/nodes/aliceVision/CameraRigLocalization.py b/meshroom/nodes/aliceVision/CameraRigLocalization.py index 6cff2d31..ca4875c2 100644 --- a/meshroom/nodes/aliceVision/CameraRigLocalization.py +++ b/meshroom/nodes/aliceVision/CameraRigLocalization.py @@ -172,7 +172,7 @@ class CameraRigLocalization(desc.CommandLineNode): outputs = [ desc.File( name='outputAlembic', - label='Output Alembic', + label='Alembic', description='''Filename for the SfMData export file (where camera poses will be stored).''', value=desc.Node.internalFolder + 'trackedcameras.abc', uid=[], diff --git a/meshroom/nodes/aliceVision/ColorCheckerCorrection.py b/meshroom/nodes/aliceVision/ColorCheckerCorrection.py index 1fb02baa..0b5e6554 100644 --- a/meshroom/nodes/aliceVision/ColorCheckerCorrection.py +++ b/meshroom/nodes/aliceVision/ColorCheckerCorrection.py @@ -63,7 +63,7 @@ If multiple color charts are submitted, only the first one will be taken in acco outputs = [ desc.File( name='outSfMData', - label='Output sfmData', + label='SfmData', description='Output sfmData.', value=lambda attr: (desc.Node.internalFolder + os.path.basename(attr.node.input.value)) if (os.path.splitext(attr.node.input.value)[1] in ['.abc', '.sfm']) else '', uid=[], @@ -71,7 +71,7 @@ If multiple color charts are submitted, only the first one will be taken in acco ), desc.File( name='output', - label='Output Folder', + label='Folder', description='Output Images Folder.', value=desc.Node.internalFolder, uid=[], diff --git a/meshroom/nodes/aliceVision/ConvertMesh.py b/meshroom/nodes/aliceVision/ConvertMesh.py index 55dac2dc..9ea7b570 100644 --- a/meshroom/nodes/aliceVision/ConvertMesh.py +++ b/meshroom/nodes/aliceVision/ConvertMesh.py @@ -41,7 +41,7 @@ class ConvertMesh(desc.CommandLineNode): outputs = [ desc.File( name='output', - label='Output Mesh', + label='Mesh', description='''Output mesh (*.obj, *.mesh, *.meshb, *.ply, *.off, *.stl).''', value=desc.Node.internalFolder + 'mesh.' + '{outputMeshFileTypeValue}', uid=[], diff --git a/meshroom/nodes/aliceVision/DepthMap.py b/meshroom/nodes/aliceVision/DepthMap.py index e1d84225..6f150854 100644 --- a/meshroom/nodes/aliceVision/DepthMap.py +++ b/meshroom/nodes/aliceVision/DepthMap.py @@ -293,9 +293,30 @@ Use a downscale factor of one (full-resolution) only if the quality of the input outputs = [ desc.File( name='output', - label='Output', + label='Folder', description='Output folder for generated depth maps.', value=desc.Node.internalFolder, uid=[], ), + # 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='depth', + label='Depth Maps', + description='Generated depth maps.', + semantic='image', + value=desc.Node.internalFolder + '<VIEW_ID>_depthMap.exr', + uid=[], + group='', # do not export on the command line + ), + desc.File( + name='sim', + label='Sim Maps', + description='Generated sim maps.', + semantic='image', + value=desc.Node.internalFolder + '<VIEW_ID>_simMap.exr', + uid=[], + group='', # do not export on the command line + ), ] diff --git a/meshroom/nodes/aliceVision/DepthMapFilter.py b/meshroom/nodes/aliceVision/DepthMapFilter.py index 5043d074..c52dd233 100644 --- a/meshroom/nodes/aliceVision/DepthMapFilter.py +++ b/meshroom/nodes/aliceVision/DepthMapFilter.py @@ -128,4 +128,25 @@ This allows to filter unstable points before starting the fusion of all depth ma value=desc.Node.internalFolder, uid=[], ), + # 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='depth', + label='Depth Maps', + description='Filtered depth maps.', + semantic='image', + value=desc.Node.internalFolder + '<VIEW_ID>_depthMap.exr', + uid=[], + group='', # do not export on the command line + ), + desc.File( + name='sim', + label='Sim Maps', + description='Filtered sim maps.', + semantic='image', + value=desc.Node.internalFolder + '<VIEW_ID>_simMap.exr', + uid=[], + group='', # do not export on the command line + ), ] diff --git a/meshroom/nodes/aliceVision/DistortionCalibration.py b/meshroom/nodes/aliceVision/DistortionCalibration.py index 87fa9606..8f006062 100644 --- a/meshroom/nodes/aliceVision/DistortionCalibration.py +++ b/meshroom/nodes/aliceVision/DistortionCalibration.py @@ -45,7 +45,7 @@ class DistortionCalibration(desc.CommandLineNode): outputs = [ desc.File( name='outSfMData', - label='Output SfmData File', + label='SfmData File', description='Path to the output sfmData file', value=desc.Node.internalFolder + 'sfmData.sfm', uid=[], diff --git a/meshroom/nodes/aliceVision/ExportAnimatedCamera.py b/meshroom/nodes/aliceVision/ExportAnimatedCamera.py index 4eb378d8..1fc1ac95 100644 --- a/meshroom/nodes/aliceVision/ExportAnimatedCamera.py +++ b/meshroom/nodes/aliceVision/ExportAnimatedCamera.py @@ -87,14 +87,14 @@ Based on the input image filenames, it will recognize the input video sequence t outputs = [ desc.File( name='output', - label='Output filepath', + label='Filepath', description='Output filepath for the alembic animated camera.', value=desc.Node.internalFolder, uid=[], ), desc.File( name='outputCamera', - label='Output Camera Filepath', + label='Camera Filepath', description='Output filename for the alembic animated camera.', value=desc.Node.internalFolder + 'camera.abc', group='', # exclude from command line @@ -102,7 +102,7 @@ Based on the input image filenames, it will recognize the input video sequence t ), desc.File( name='outputUndistorted', - label='Output Undistorted images Filepath', + label='Undistorted images Filepath', description='Output Undistorted images.', value=desc.Node.internalFolder + 'undistort', group='', # exclude from command line diff --git a/meshroom/nodes/aliceVision/ExportColoredPointCloud.py b/meshroom/nodes/aliceVision/ExportColoredPointCloud.py index a922a4a9..7ab7855b 100644 --- a/meshroom/nodes/aliceVision/ExportColoredPointCloud.py +++ b/meshroom/nodes/aliceVision/ExportColoredPointCloud.py @@ -30,7 +30,7 @@ class ExportColoredPointCloud(desc.CommandLineNode): outputs = [ desc.File( name='output', - label='Output Point Cloud Filepath', + label='Point Cloud Filepath', description='Output point cloud with visibilities as SfMData file.', value="{cache}/{nodeType}/{uid0}/pointCloud.abc", uid=[], diff --git a/meshroom/nodes/aliceVision/ExportMatches.py b/meshroom/nodes/aliceVision/ExportMatches.py index b90c6358..2a1aeebc 100644 --- a/meshroom/nodes/aliceVision/ExportMatches.py +++ b/meshroom/nodes/aliceVision/ExportMatches.py @@ -65,7 +65,7 @@ class ExportMatches(desc.CommandLineNode): outputs = [ desc.File( name='output', - label='Output Folder', + label='Folder', description='Output path for the features and descriptors files (*.feat, *.desc).', value=desc.Node.internalFolder, uid=[], diff --git a/meshroom/nodes/aliceVision/ExportMaya.py b/meshroom/nodes/aliceVision/ExportMaya.py index aeea1b81..99dacf12 100644 --- a/meshroom/nodes/aliceVision/ExportMaya.py +++ b/meshroom/nodes/aliceVision/ExportMaya.py @@ -27,7 +27,7 @@ MeshroomMaya contains a user interface to browse all cameras. outputs = [ desc.File( name='output', - label='Output Folder', + label='Folder', description='Folder for MeshroomMaya outputs: undistorted images and thumbnails.', value=desc.Node.internalFolder, uid=[], diff --git a/meshroom/nodes/aliceVision/FeatureRepeatability.py b/meshroom/nodes/aliceVision/FeatureRepeatability.py index 12cd9012..ed22cf34 100644 --- a/meshroom/nodes/aliceVision/FeatureRepeatability.py +++ b/meshroom/nodes/aliceVision/FeatureRepeatability.py @@ -125,7 +125,7 @@ Compare feature/descriptor matching repeatability on some dataset with known hom outputs = [ desc.File( name='output', - label='Output Folder', + label='Folder', description='Output path for the features and descriptors files (*.feat, *.desc).', value=desc.Node.internalFolder, uid=[], diff --git a/meshroom/nodes/aliceVision/GlobalSfM.py b/meshroom/nodes/aliceVision/GlobalSfM.py index a400570a..8d6ebe0b 100644 --- a/meshroom/nodes/aliceVision/GlobalSfM.py +++ b/meshroom/nodes/aliceVision/GlobalSfM.py @@ -119,7 +119,7 @@ It is known to be faster but less robust to challenging datasets than the Increm ), desc.File( name='extraInfoFolder', - label='Output Folder', + label='Folder', description='Folder for intermediate reconstruction files and additional reconstruction information files.', value=desc.Node.internalFolder, uid=[], diff --git a/meshroom/nodes/aliceVision/ImageMatchingMultiSfM.py b/meshroom/nodes/aliceVision/ImageMatchingMultiSfM.py index 313534b1..c11776e7 100644 --- a/meshroom/nodes/aliceVision/ImageMatchingMultiSfM.py +++ b/meshroom/nodes/aliceVision/ImageMatchingMultiSfM.py @@ -141,14 +141,14 @@ Thanks to this node, the FeatureMatching node will only compute the matches betw outputs = [ desc.File( name='output', - label='Output List File', + label='List File', description='Filepath to the output file with the list of selected image pairs.', value=desc.Node.internalFolder + 'imageMatches.txt', uid=[], ), desc.File( name='outputCombinedSfM', - label='Output Combined SfM', + label='Combined SfM', description='Path for the combined SfMData file', value=desc.Node.internalFolder + 'combineSfM.sfm', uid=[], diff --git a/meshroom/nodes/aliceVision/ImageProcessing.py b/meshroom/nodes/aliceVision/ImageProcessing.py index e276c365..8aaebff6 100644 --- a/meshroom/nodes/aliceVision/ImageProcessing.py +++ b/meshroom/nodes/aliceVision/ImageProcessing.py @@ -12,7 +12,7 @@ def outputImagesValueFunct(attr): if inputExt in ['.abc', '.sfm']: # If we have an SfM in input - return desc.Node.internalFolder + '*' + (outputExt or '.*') + return desc.Node.internalFolder + '<VIEW_ID>' + (outputExt or '.*') if inputExt: # if we have one or multiple files in input @@ -325,7 +325,7 @@ Convert or apply filtering to the input images. outputs = [ desc.File( name='outSfMData', - label='Output sfmData', + label='SfmData', description='Output sfmData.', value=lambda attr: (desc.Node.internalFolder + os.path.basename(attr.node.input.value)) if (os.path.splitext(attr.node.input.value)[1] in ['.abc', '.sfm']) else '', uid=[], @@ -333,15 +333,16 @@ Convert or apply filtering to the input images. ), desc.File( name='output', - label='Output Folder', + label='Folder', description='Output Images Folder.', value=desc.Node.internalFolder, uid=[], ), desc.File( name='outputImages', - label='Output Images', + label='Images', description='Output Image Files.', + semantic='image', value= outputImagesValueFunct, group='', # do not export on the command line uid=[], diff --git a/meshroom/nodes/aliceVision/KeyframeSelection.py b/meshroom/nodes/aliceVision/KeyframeSelection.py index e1037cd3..63ecb1f6 100644 --- a/meshroom/nodes/aliceVision/KeyframeSelection.py +++ b/meshroom/nodes/aliceVision/KeyframeSelection.py @@ -182,7 +182,7 @@ You can extract frames at regular interval by configuring only the min/maxFrameS outputs = [ desc.File( name='outputFolder', - label='Output Folder', + label='Folder', description='''Output keyframes folder for extracted frames.''', value=desc.Node.internalFolder, uid=[], diff --git a/meshroom/nodes/aliceVision/LdrToHdrCalibration.py b/meshroom/nodes/aliceVision/LdrToHdrCalibration.py index 5b6ebbbf..a1a384a3 100644 --- a/meshroom/nodes/aliceVision/LdrToHdrCalibration.py +++ b/meshroom/nodes/aliceVision/LdrToHdrCalibration.py @@ -139,7 +139,7 @@ class LdrToHdrCalibration(desc.CommandLineNode): outputs = [ desc.File( name='response', - label='Output response File', + label='Response File', description='Path to the output response file', value=desc.Node.internalFolder + 'response.csv', uid=[], diff --git a/meshroom/nodes/aliceVision/LdrToHdrMerge.py b/meshroom/nodes/aliceVision/LdrToHdrMerge.py index 391ae9e3..cc1ee60f 100644 --- a/meshroom/nodes/aliceVision/LdrToHdrMerge.py +++ b/meshroom/nodes/aliceVision/LdrToHdrMerge.py @@ -177,7 +177,7 @@ class LdrToHdrMerge(desc.CommandLineNode): outputs = [ desc.File( name='outSfMData', - label='Output SfMData File', + label='SfMData File', description='Path to the output sfmdata file', value=desc.Node.internalFolder + 'sfmData.sfm', uid=[], diff --git a/meshroom/nodes/aliceVision/LdrToHdrSampling.py b/meshroom/nodes/aliceVision/LdrToHdrSampling.py index a32f86c1..3324733b 100644 --- a/meshroom/nodes/aliceVision/LdrToHdrSampling.py +++ b/meshroom/nodes/aliceVision/LdrToHdrSampling.py @@ -146,7 +146,7 @@ class LdrToHdrSampling(desc.CommandLineNode): outputs = [ desc.File( name='output', - label='Output Folder', + label='Folder', description='Output path for the samples.', value=desc.Node.internalFolder, uid=[], diff --git a/meshroom/nodes/aliceVision/LightingEstimation.py b/meshroom/nodes/aliceVision/LightingEstimation.py index c9d44b1a..077b3538 100644 --- a/meshroom/nodes/aliceVision/LightingEstimation.py +++ b/meshroom/nodes/aliceVision/LightingEstimation.py @@ -83,7 +83,7 @@ class LightingEstimation(desc.CommandLineNode): outputs = [ desc.File( name='output', - label='Output Folder', + label='Folder', description='Folder for output lighting vector files.', value=desc.Node.internalFolder, uid=[], diff --git a/meshroom/nodes/aliceVision/MergeMeshes.py b/meshroom/nodes/aliceVision/MergeMeshes.py index 1669dde5..739e5b04 100644 --- a/meshroom/nodes/aliceVision/MergeMeshes.py +++ b/meshroom/nodes/aliceVision/MergeMeshes.py @@ -69,7 +69,7 @@ Operation types used to merge two meshes: outputs = [ desc.File( name='output', - label='Output mesh', + label='Mesh', description='''Output mesh (*.obj, *.mesh, *.meshb, *.ply, *.off, *.stl).''', value=desc.Node.internalFolder + 'mesh.stl', uid=[], diff --git a/meshroom/nodes/aliceVision/MeshDecimate.py b/meshroom/nodes/aliceVision/MeshDecimate.py index 4294e858..29b51e42 100644 --- a/meshroom/nodes/aliceVision/MeshDecimate.py +++ b/meshroom/nodes/aliceVision/MeshDecimate.py @@ -77,7 +77,7 @@ This node allows to reduce the density of the Mesh. outputs = [ desc.File( name="output", - label="Output mesh", + label="Mesh", description="Output mesh (OBJ file format).", value=desc.Node.internalFolder + 'mesh.obj', uid=[], diff --git a/meshroom/nodes/aliceVision/MeshMasking.py b/meshroom/nodes/aliceVision/MeshMasking.py index 9188072b..1888f563 100644 --- a/meshroom/nodes/aliceVision/MeshMasking.py +++ b/meshroom/nodes/aliceVision/MeshMasking.py @@ -100,7 +100,7 @@ Decimate triangles based on image masks. outputs = [ desc.File( name='outputMesh', - label='Output Mesh', + label='Mesh', description='''Output mesh.''', value=desc.Node.internalFolder + 'mesh.{outputMeshFileTypeValue}', uid=[], diff --git a/meshroom/nodes/aliceVision/MeshResampling.py b/meshroom/nodes/aliceVision/MeshResampling.py index d9a836cd..047d572b 100644 --- a/meshroom/nodes/aliceVision/MeshResampling.py +++ b/meshroom/nodes/aliceVision/MeshResampling.py @@ -82,7 +82,7 @@ This node allows to recompute the mesh surface with a new topology and uniform d outputs = [ desc.File( name="output", - label="Output mesh", + label="Mesh", description="Output mesh (OBJ file format).", value=desc.Node.internalFolder + 'mesh.obj', uid=[], diff --git a/meshroom/nodes/aliceVision/PanoramaCompositing.py b/meshroom/nodes/aliceVision/PanoramaCompositing.py index 896759e7..756d76db 100644 --- a/meshroom/nodes/aliceVision/PanoramaCompositing.py +++ b/meshroom/nodes/aliceVision/PanoramaCompositing.py @@ -106,7 +106,7 @@ Multiple cameras are contributing to the low frequencies and only the best one c outputs = [ desc.File( name='output', - label='Output Folder', + label='Folder', description='', value=desc.Node.internalFolder, uid=[], diff --git a/meshroom/nodes/aliceVision/PanoramaEstimation.py b/meshroom/nodes/aliceVision/PanoramaEstimation.py index 6e78dd7c..476d29b1 100644 --- a/meshroom/nodes/aliceVision/PanoramaEstimation.py +++ b/meshroom/nodes/aliceVision/PanoramaEstimation.py @@ -178,14 +178,14 @@ Estimate relative camera rotations between input images. outputs = [ desc.File( name='output', - label='Output SfMData File', + label='SfMData File', description='Path to the output sfmdata file', value=desc.Node.internalFolder + 'panorama.abc', uid=[], ), desc.File( name='outputViewsAndPoses', - label='Output Poses', + label='Poses', description='''Path to the output sfmdata file with cameras (views and poses).''', value=desc.Node.internalFolder + 'cameras.sfm', uid=[], diff --git a/meshroom/nodes/aliceVision/PanoramaInit.py b/meshroom/nodes/aliceVision/PanoramaInit.py index 947f0922..edac5cd6 100644 --- a/meshroom/nodes/aliceVision/PanoramaInit.py +++ b/meshroom/nodes/aliceVision/PanoramaInit.py @@ -156,7 +156,7 @@ This node allows to setup the Panorama: outputs = [ desc.File( name='outSfMData', - label='Output SfMData File', + label='SfMData File', description='Path to the output sfmdata file', value=desc.Node.internalFolder + 'sfmData.sfm', uid=[], diff --git a/meshroom/nodes/aliceVision/PanoramaMerging.py b/meshroom/nodes/aliceVision/PanoramaMerging.py index 99a14ec7..f0fc6fca 100644 --- a/meshroom/nodes/aliceVision/PanoramaMerging.py +++ b/meshroom/nodes/aliceVision/PanoramaMerging.py @@ -69,8 +69,9 @@ Merge all inputs coming from the PanoramaCompositing node. outputs = [ desc.File( name='outputPanorama', - label='Output Folder', + label='Panorama', description='', + semantic='image', value=desc.Node.internalFolder + 'panorama.{outputFileTypeValue}', uid=[], ), diff --git a/meshroom/nodes/aliceVision/PanoramaPrepareImages.py b/meshroom/nodes/aliceVision/PanoramaPrepareImages.py index 1add6c47..89099f19 100644 --- a/meshroom/nodes/aliceVision/PanoramaPrepareImages.py +++ b/meshroom/nodes/aliceVision/PanoramaPrepareImages.py @@ -36,7 +36,7 @@ Prepare images for Panorama pipeline: ensures that images orientations are coher outputs = [ desc.File( name='output', - label='Output sfmData', + label='SfmData', description='Output sfmData.', value=lambda attr: desc.Node.internalFolder + os.path.basename(attr.node.input.value), uid=[], diff --git a/meshroom/nodes/aliceVision/PanoramaSeams.py b/meshroom/nodes/aliceVision/PanoramaSeams.py index c36beb92..6817d984 100644 --- a/meshroom/nodes/aliceVision/PanoramaSeams.py +++ b/meshroom/nodes/aliceVision/PanoramaSeams.py @@ -61,7 +61,7 @@ Estimate the seams lines between the inputs to provide an optimal compositing in outputs = [ desc.File( name='output', - label='Output Labels', + label='Labels', description='', value=desc.Node.internalFolder + 'labels.exr', uid=[], diff --git a/meshroom/nodes/aliceVision/PanoramaWarping.py b/meshroom/nodes/aliceVision/PanoramaWarping.py index 33d3e0a9..99a38d5c 100644 --- a/meshroom/nodes/aliceVision/PanoramaWarping.py +++ b/meshroom/nodes/aliceVision/PanoramaWarping.py @@ -92,9 +92,36 @@ Compute the image warping for each input image in the panorama coordinate system outputs = [ desc.File( name='output', - label='Output directory', + label='Folder', description='', value=desc.Node.internalFolder, uid=[], ), + desc.File( + name='warping', + label='Warping', + description='', + group='', # do not export on the command line + semantic='image', + value=desc.Node.internalFolder+'<VIEW_ID>.exr', + uid=[] + ), + desc.File( + name='mask', + label='Mask', + description='', + group='', # do not export on the command line + semantic='image', + value=desc.Node.internalFolder+'<VIEW_ID>_mask.exr', + uid=[] + ), + desc.File( + name='weight', + label='Weight', + description='', + group='', # do not export on the command line + semantic='image', + value=desc.Node.internalFolder+'<VIEW_ID>_weight.exr', + uid=[] + ), ] diff --git a/meshroom/nodes/aliceVision/PrepareDenseScene.py b/meshroom/nodes/aliceVision/PrepareDenseScene.py index 31d0ecf1..5d475e51 100644 --- a/meshroom/nodes/aliceVision/PrepareDenseScene.py +++ b/meshroom/nodes/aliceVision/PrepareDenseScene.py @@ -100,10 +100,11 @@ This node export undistorted images so the depth map and texturing can be comput uid=[], ), desc.File( - name='outputUndistorted', + name='undistorted', label='Undistorted Images', description='List of undistorted images.', - value=desc.Node.internalFolder + '*.{outputFileTypeValue}', + semantic='image', + value=desc.Node.internalFolder + '<VIEW_ID>.{outputFileTypeValue}', uid=[], group='', advanced=True diff --git a/meshroom/nodes/aliceVision/SfMAlignment.py b/meshroom/nodes/aliceVision/SfMAlignment.py index 60d6d2e3..accd9bfb 100644 --- a/meshroom/nodes/aliceVision/SfMAlignment.py +++ b/meshroom/nodes/aliceVision/SfMAlignment.py @@ -110,14 +110,14 @@ The alignment can be based on: outputs = [ desc.File( name='output', - label='Output SfMData File', + label='SfMData File', description='SfMData file.', value=lambda attr: desc.Node.internalFolder + (os.path.splitext(os.path.basename(attr.node.input.value))[0] or 'sfmData') + '.abc', uid=[], ), desc.File( name='outputViewsAndPoses', - label='Output Poses', + label='Poses', description='''Path to the output sfmdata file with cameras (views and poses).''', value=desc.Node.internalFolder + 'cameras.sfm', uid=[], diff --git a/meshroom/nodes/aliceVision/SfMTransfer.py b/meshroom/nodes/aliceVision/SfMTransfer.py index dd0f8c4a..edc5acff 100644 --- a/meshroom/nodes/aliceVision/SfMTransfer.py +++ b/meshroom/nodes/aliceVision/SfMTransfer.py @@ -94,14 +94,14 @@ This node allows to transfer poses and/or intrinsics form one SfM scene onto ano outputs = [ desc.File( name='output', - label='Output SfMData File', + label='SfMData File', description='SfMData file.', value=lambda attr: desc.Node.internalFolder + (os.path.splitext(os.path.basename(attr.node.input.value))[0] or 'sfmData') + '.abc', uid=[], ), desc.File( name='outputViewsAndPoses', - label='Output Poses', + label='Poses', description='''Path to the output sfmdata file with cameras (views and poses).''', value=desc.Node.internalFolder + 'cameras.sfm', uid=[], diff --git a/meshroom/nodes/aliceVision/SfMTransform.py b/meshroom/nodes/aliceVision/SfMTransform.py index 0466ec50..439ba3af 100644 --- a/meshroom/nodes/aliceVision/SfMTransform.py +++ b/meshroom/nodes/aliceVision/SfMTransform.py @@ -197,14 +197,14 @@ The transformation can be based on: outputs = [ desc.File( name='output', - label='Output SfMData File', + label='SfMData File', description='''Aligned SfMData file .''', value=lambda attr: desc.Node.internalFolder + (os.path.splitext(os.path.basename(attr.node.input.value))[0] or 'sfmData') + '.abc', uid=[], ), desc.File( name='outputViewsAndPoses', - label='Output Poses', + label='Poses', description='''Path to the output sfmdata file with cameras (views and poses).''', value=desc.Node.internalFolder + 'cameras.sfm', uid=[], diff --git a/meshroom/nodes/aliceVision/Split360Images.py b/meshroom/nodes/aliceVision/Split360Images.py index daee4598..4ff56bb9 100644 --- a/meshroom/nodes/aliceVision/Split360Images.py +++ b/meshroom/nodes/aliceVision/Split360Images.py @@ -89,7 +89,7 @@ class Split360Images(desc.CommandLineNode): outputs = [ desc.File( name='output', - label='Output Folder', + label='Folder', description="Output folder for extracted frames.", value=desc.Node.internalFolder, uid=[], diff --git a/meshroom/nodes/aliceVision/StructureFromMotion.py b/meshroom/nodes/aliceVision/StructureFromMotion.py index 4cacd6a1..08a4541c 100644 --- a/meshroom/nodes/aliceVision/StructureFromMotion.py +++ b/meshroom/nodes/aliceVision/StructureFromMotion.py @@ -367,7 +367,7 @@ It iterates like that, adding cameras and triangulating new 2D features into 3D ), desc.File( name='extraInfoFolder', - label='Output Folder', + label='Folder', description='Folder for intermediate reconstruction files and additional reconstruction information files.', value=desc.Node.internalFolder, uid=[], diff --git a/meshroom/nodes/aliceVision/Texturing.py b/meshroom/nodes/aliceVision/Texturing.py index 34ed45d4..83d46d90 100644 --- a/meshroom/nodes/aliceVision/Texturing.py +++ b/meshroom/nodes/aliceVision/Texturing.py @@ -348,8 +348,8 @@ Many cameras are contributing to the low frequencies and only the best ones cont description='Output Texture files.', value= lambda attr: desc.Node.internalFolder + 'texture_*.' + attr.node.colorMapping.colorMappingFileType.value if attr.node.colorMapping.enable.value else '', uid=[], - group='', - ), + group='' + ) ] def upgradeAttributeValues(self, attrValues, fromVersion): diff --git a/meshroom/ui/qml/ImageGallery/ImageGallery.qml b/meshroom/ui/qml/ImageGallery/ImageGallery.qml index 94c6637c..0ab31577 100644 --- a/meshroom/ui/qml/ImageGallery/ImageGallery.qml +++ b/meshroom/ui/qml/ImageGallery/ImageGallery.qml @@ -30,7 +30,7 @@ Panel { signal removeImageRequest(var attribute) signal filesDropped(var drop, var augmentSfm) - title: "Images" + title: "Image Gallery" implicitWidth: (root.defaultCellSize + 2) * 2 QtObject { @@ -332,17 +332,14 @@ Panel { } else { - grid.updateSelectedViewFromGrid = false if(event.key == Qt.Key_Right) { grid.moveCurrentIndexRight() - // grid.setCurrentIndex(Math.min(grid.model.count - 1, grid.currentIndex + 1)) event.accepted = true } else if(event.key == Qt.Key_Left) { grid.moveCurrentIndexLeft() - // grid.setCurrentIndex(Math.max(0, grid.currentIndex - 1)) event.accepted = true } else if(event.key == Qt.Key_Up) @@ -355,7 +352,6 @@ Panel { grid.moveCurrentIndexDown() event.accepted = true } - grid.updateSelectedViewFromGrid = true } } diff --git a/meshroom/ui/qml/Viewer/Viewer2D.qml b/meshroom/ui/qml/Viewer/Viewer2D.qml index 8bd2ceb1..b407199a 100644 --- a/meshroom/ui/qml/Viewer/Viewer2D.qml +++ b/meshroom/ui/qml/Viewer/Viewer2D.qml @@ -9,6 +9,11 @@ FocusScope { clip: true + property var displayedNode: null + + property bool useExternal: false + property url sourceExternal + property url source property var metadata property var viewIn3D @@ -153,18 +158,129 @@ FocusScope { imgContainer.y = Math.max((imgLayout.height - imgContainer.image.height * imgContainer.scale)*0.5, 0) } - function getImageFile(type) { - if(!_reconstruction.activeNodes) - return ""; - var depthMapNode = _reconstruction.activeNodes.get('allDepthMap').node; - if (type == "image") { - return root.source; - } else if (depthMapNode != undefined && _reconstruction.selectedViewId >= 0) { - return Filepath.stringToUrl(depthMapNode.internalFolder+_reconstruction.selectedViewId+"_"+type+"Map.exr"); + function tryLoadNode(node) { + useExternal = false; + + // safety check + if (!node) { + return false; + } + + // node must be computed or at least running + if (!node.isPartiallyFinished()) { + return false; + } + + // node must have at least one output attribute with the image semantic + var hasImageOutputAttr = false; + for (var i = 0; i < node.attributes.count; i++) { + var attr = node.attributes.at(i); + if (attr.isOutput && attr.desc.semantic == "image") { + hasImageOutputAttr = true; + break; + } + } + if (!hasImageOutputAttr) { + return false; + } + + displayedNode = node; + return true; + } + + function loadExternal(path) { + useExternal = true; + sourceExternal = path; + displayedNode = null; + } + + function getImageFile() { + // entry point for getting the image file URL + if (useExternal) { + return sourceExternal; + } + if (!displayedNode || outputAttribute.name == "gallery") { + return getViewpointPath(_reconstruction.selectedViewId); + } + return getFileAttributePath(displayedNode, outputAttribute.name, _reconstruction.selectedViewId); + } + + function getMetadata() { + // entry point for getting the image metadata + if (useExternal) { + return {}; + } else { + return getViewpointMetadata(_reconstruction.selectedViewId); + } + } + + function getFileAttributePath(node, attrName, viewId) { + // get output attribute with matching name + // and parse its value to get the image filepath + for (var i = 0; i < node.attributes.count; i++) { + var attr = node.attributes.at(i); + if (attr.name == attrName) { + let pattern = String(attr.value).replace("<VIEW_ID>", viewId); + let path = Filepath.globFirst(pattern); + return Filepath.stringToUrl(path); + } + } + return ""; + } + + function getViewpointPath(viewId) { + // get viewpoint from cameraInit with matching id + // and get its image filepath + for (var i = 0; i < _reconstruction.viewpoints.count; i++) { + var vp = _reconstruction.viewpoints.at(i); + if (vp.childAttribute("viewId").value == viewId) { + return Filepath.stringToUrl(vp.childAttribute("path").value); + } } return ""; } + function getViewpointMetadata(viewId) { + // get viewpoint from cameraInit with matching id + // and get its image filepath + for (var i = 0; i < _reconstruction.viewpoints.count; i++) { + var vp = _reconstruction.viewpoints.at(i); + if (vp.childAttribute("viewId").value == viewId) { + return JSON.parse(vp.childAttribute("metadata").value); + } + } + return {}; + } + + onDisplayedNodeChanged: { + // clear metadata if no displayed node + if (!displayedNode) { + metadata = {}; + } + + // update output attribute names + var names = []; + if (displayedNode) { + // store attr name for output attributes that represent images + for (var i = 0; i < displayedNode.attributes.count; i++) { + var attr = displayedNode.attributes.at(i); + if (attr.isOutput && attr.desc.semantic == "image") { + names.push(attr.name); + } + } + } + names.push("gallery"); + outputAttribute.names = names; + } + + Connections { + target: _reconstruction + onSelectedViewIdChanged: { + root.source = getImageFile(); + root.metadata = getMetadata(); + } + } + // context menu property Component contextMenu: Menu { MenuItem { @@ -272,7 +388,7 @@ FocusScope { // Note: It does not work to use previously created component, so we re-create it with setSource. // floatViewerComp.createObject(floatImageViewerLoader, { setSource("FloatImage.qml", { - 'source': Qt.binding(function() { return getImageFile(imageType.type); }), + 'source': Qt.binding(function() { return getImageFile(); }), 'gamma': Qt.binding(function() { return hdrImageToolbar.gammaValue; }), 'gain': Qt.binding(function() { return hdrImageToolbar.gainValue; }), 'channelModeString': Qt.binding(function() { return hdrImageToolbar.channelModeValue; }), @@ -336,7 +452,7 @@ FocusScope { fillMode: Image.PreserveAspectFit autoTransform: true onWidthChanged: if(status==Image.Ready) fit() - source: getImageFile(imageType.type) + source: getImageFile() onStatusChanged: { // update cache source when image is loaded if(status === Image.Ready) @@ -500,15 +616,13 @@ FocusScope { font.pointSize: 8 readOnly: true selectByMouse: true - text: Filepath.urlToString(getImageFile(imageType.type)) + text: Filepath.urlToString(getImageFile()) } - // show which depthmap node is active + // write which node is being displayed Label { - id: depthMapNodeName - property var activeNode: root.oiioPluginAvailable ? _reconstruction.activeNodes.get("allDepthMap").node : null - visible: (imageType.type != "image") && activeNode - text: activeNode ? activeNode.label : "" + id: displayedNodeName + text: root.displayedNode ? root.displayedNode.label : "" font.pointSize: 8 horizontalAlignment: TextInput.AlignLeft @@ -516,6 +630,17 @@ FocusScope { Layout.preferredWidth: contentWidth height: contentHeight } + + // button to clear currently displayed node + MaterialToolButton { + id: clearDisplayedNode + text: MaterialIcons.close + ToolTip.text: "Clear node" + enabled: root.displayedNode + onClicked: { + root.displayedNode = null + } + } } } Item { @@ -990,19 +1115,21 @@ FocusScope { } ComboBox { - id: imageType - property var activeNode: root.oiioPluginAvailable ? _reconstruction.activeNodes.get('allDepthMap').node : null - // set min size to 5 characters + one margin for the combobox + id: outputAttribute clip: true Layout.minimumWidth: 0 - Layout.preferredWidth: 6.0 * Qt.application.font.pixelSize flat: true - property var types: ["image", "depth", "sim"] - property string type: enabled ? types[currentIndex] : types[0] + property var names: ["gallery"] + property string name: names[currentIndex] - model: types - enabled: activeNode + model: names.map(n => (n == "gallery") ? "Image Gallery" : displayedNode.attributes.get(n).label) + enabled: count > 0 + + FontMetrics { + id: fontMetrics + } + Layout.preferredWidth: model.reduce((acc, label) => Math.max(acc, fontMetrics.boundingRect(label).width), 0) + 3.0*Qt.application.font.pixelSize } MaterialToolButton { @@ -1014,7 +1141,7 @@ FocusScope { Layout.minimumWidth: 0 onClicked: { - root.viewIn3D(root.getImageFile("depth")) + root.viewIn3D(root.getFileAttributePath(activeNode, "depth", _reconstruction.selectedViewId)); } } diff --git a/meshroom/ui/qml/WorkspaceView.qml b/meshroom/ui/qml/WorkspaceView.qml index ced16fe2..2c4d6ae2 100644 --- a/meshroom/ui/qml/WorkspaceView.qml +++ b/meshroom/ui/qml/WorkspaceView.qml @@ -148,20 +148,11 @@ Item { viewIn3D: root.load3DMedia - Connections { - target: imageGallery - onCurrentItemChanged: { - viewer2D.source = imageGallery.currentItemSource - viewer2D.metadata = imageGallery.currentItemMetadata - } - } - DropArea { anchors.fill: parent keys: ["text/uri-list"] onDropped: { - viewer2D.source = drop.urls[0] - viewer2D.metadata = {} + viewer2D.loadExternal(drop.urls[0]); } } Rectangle { diff --git a/meshroom/ui/qml/main.qml b/meshroom/ui/qml/main.qml index db218e19..22550f80 100755 --- a/meshroom/ui/qml/main.qml +++ b/meshroom/ui/qml/main.qml @@ -851,11 +851,16 @@ ApplicationWindow { reconstruction: _reconstruction readOnly: _reconstruction.computing - function viewAttribute(attribute, mouse) { - let viewable = false; - viewable = workspaceView.viewIn2D(attribute); - viewable |= workspaceView.viewIn3D(attribute, mouse); - return viewable; + function viewNode(node, mouse) { + // 2D viewer + viewer2D.tryLoadNode(node); + + // 3D viewer + for (var i = 0; i < node.attributes.count; i++) { + var attr = node.attributes.at(i) + if(attr.isOutput && attr.desc.semantic != "image" && workspaceView.viewIn3D(attr, mouse)) + break; + } } function viewIn3D(attribute, mouse) { @@ -867,29 +872,6 @@ ApplicationWindow { panel3dViewer.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; - } } } @@ -955,14 +937,7 @@ ApplicationWindow { onNodeDoubleClicked: { _reconstruction.setActiveNode(node); - - let viewable = false; - for(var i=0; i < node.attributes.count; ++i) - { - var attr = node.attributes.at(i) - if(attr.isOutput && workspaceView.viewAttribute(attr, mouse)) - break; - } + workspaceView.viewNode(node, mouse); } onComputeRequest: computeManager.compute(node) onSubmitRequest: computeManager.submit(node) @@ -989,7 +964,6 @@ ApplicationWindow { // Make NodeEditor readOnly when computing readOnly: node ? node.locked : false - onAttributeDoubleClicked: workspaceView.viewAttribute(attribute, mouse) onUpgradeRequest: { var n = _reconstruction.upgradeNode(node); _reconstruction.selectedNode = n; diff --git a/meshroom/ui/reconstruction.py b/meshroom/ui/reconstruction.py index 8d303101..4e9683c0 100755 --- a/meshroom/ui/reconstruction.py +++ b/meshroom/ui/reconstruction.py @@ -565,7 +565,12 @@ class Reconstruction(UIGraph): def getViewpoints(self): """ Return the Viewpoints model. """ # TODO: handle multiple Viewpoints models - return self._cameraInit.viewpoints.value if self._cameraInit else QObjectListModel(parent=self) + if self.tempCameraInit: + return self.tempCameraInit.viewpoints.value + elif self._cameraInit: + return self._cameraInit.viewpoints.value + else: + return QObjectListModel(parent=self) def updateCameraInits(self): cameraInits = self._graph.nodesOfType("CameraInit", sortedByIndex=True) |