diff --git a/meshroom/core/node.py b/meshroom/core/node.py index 8fc50d66..049c0803 100644 --- a/meshroom/core/node.py +++ b/meshroom/core/node.py @@ -606,6 +606,10 @@ class BaseNode(BaseObject): return False return True + @Slot(result=bool) + def isComputed(self): + return self.hasStatus(Status.SUCCESS) + @Slot() def clearData(self): """ Delete this Node internal folder. diff --git a/meshroom/nodes/aliceVision/CameraInit.py b/meshroom/nodes/aliceVision/CameraInit.py index 22c0ccab..eff321e1 100644 --- a/meshroom/nodes/aliceVision/CameraInit.py +++ b/meshroom/nodes/aliceVision/CameraInit.py @@ -160,6 +160,9 @@ class CameraInit(desc.CommandLineNode): ), ] + def readSfMData(self, sfmFile): + return readSfMData(sfmFile) + def buildIntrinsics(self, node, additionalViews=()): """ Build intrinsics from node current views and optional additional views diff --git a/meshroom/ui/qml/ImageGallery/ImageGallery.qml b/meshroom/ui/qml/ImageGallery/ImageGallery.qml index 82edc3fb..d4dcd759 100644 --- a/meshroom/ui/qml/ImageGallery/ImageGallery.qml +++ b/meshroom/ui/qml/ImageGallery/ImageGallery.qml @@ -16,13 +16,13 @@ Panel { property variant cameraInits property variant cameraInit + property variant hdrCameraInit readonly property alias currentItem: grid.currentItem readonly property string currentItemSource: grid.currentItem ? grid.currentItem.source : "" readonly property var currentItemMetadata: grid.currentItem ? grid.currentItem.metadata : undefined property int defaultCellSize: 160 property int currentIndex: 0 property bool readOnly: false - readonly property variant viewpoints: cameraInit.attribute('viewpoints').value signal removeImageRequest(var attribute) signal filesDropped(var drop, var augmentSfm) @@ -30,6 +30,17 @@ Panel { title: "Images" implicitWidth: (root.defaultCellSize + 2) * 2 + function changeCurrentIndex(newIndex) { + _reconstruction.cameraInitIndex = newIndex + } + + QtObject { + id: m + property variant currentCameraInit: displayHDR.checked ? _reconstruction.hdrCameraInit : root.cameraInit + property variant viewpoints: currentCameraInit ? currentCameraInit.attribute('viewpoints').value : undefined + property bool readOnly: root.readOnly || displayHDR.checked + } + headerBar: RowLayout { MaterialToolButton { text: MaterialIcons.more_vert @@ -99,7 +110,7 @@ Panel { model: SortFilterDelegateModel { id: sortedModel - model: _reconstruction.viewpoints + model: m.viewpoints sortRole: "path" // TODO: provide filtering on reconstruction status // filterRole: _reconstruction.sfmReport ? "reconstructed" : "" @@ -123,7 +134,7 @@ Panel { viewpoint: object.value width: grid.cellWidth height: grid.cellHeight - readOnly: root.readOnly + readOnly: m.readOnly displayViewId: displayViewIdsAction.checked isCurrentItem: GridView.isCurrentItem @@ -202,9 +213,9 @@ Panel { { event.accepted = true if(event.key == Qt.Key_Right) - root.currentIndex = Math.min(root.cameraInits.count - 1, root.currentIndex + 1) + root.changeCurrentIndex(Math.min(root.cameraInits.count - 1, root.currentIndex + 1)) else if(event.key == Qt.Key_Left) - root.currentIndex = Math.max(0, root.currentIndex - 1) + root.changeCurrentIndex(Math.max(0, root.currentIndex - 1)) } } @@ -227,7 +238,7 @@ Panel { DropArea { id: dropArea anchors.fill: parent - enabled: !root.readOnly + enabled: !m.readOnly keys: ["text/uri-list"] // TODO: onEntered: call specific method to filter files based on extension onDropped: { @@ -274,7 +285,7 @@ Panel { text: "Augment Reconstruction" font.bold: true wrapMode: Text.WrapAtWordBoundaryOrAnywhere - visible: viewpoints.count > 0 + visible: m.viewpoints ? m.viewpoints.count > 0 : false background: Rectangle { color: parent.hovered ? palette.highlight : palette.window opacity: 0.8 @@ -299,14 +310,13 @@ Panel { enabled: nodesCB.currentIndex > 0 onClicked: nodesCB.decrementCurrentIndex() } - Label { text: "Group " } + Label { id: groupLabel; text: "Group " } ComboBox { id: nodesCB model: root.cameraInits.count implicitWidth: 40 currentIndex: root.currentIndex - onActivated: root.currentIndex = currentIndex - + onActivated: root.changeCurrentIndex(currentIndex) } Label { text: "/ " + (root.cameraInits.count - 1) } ToolButton { @@ -337,6 +347,26 @@ Panel { } } + MaterialToolButton { + id: displayHDR + font.pointSize: 20 + padding: 0 + anchors.margins: 0 + implicitHeight: 14 + ToolTip.text: "Visualize HDR images" + text: MaterialIcons.hdr_on + visible: _reconstruction.ldr2hdr + enabled: visible && _reconstruction.ldr2hdr.isComputed() + onEnabledChanged: { + // Reset the toggle to avoid getting stuck + // with the HDR node checked but disabled. + checked = false; + } + checkable: true + checked: false + onClicked: { _reconstruction.setupLDRToHDRCameraInit(); } + } + Item { Layout.fillHeight: true; Layout.fillWidth: true } // Thumbnail size icon and slider diff --git a/meshroom/ui/qml/WorkspaceView.qml b/meshroom/ui/qml/WorkspaceView.qml index d339705b..fe88f546 100644 --- a/meshroom/ui/qml/WorkspaceView.qml +++ b/meshroom/ui/qml/WorkspaceView.qml @@ -64,9 +64,9 @@ Item { Layout.fillHeight: true readOnly: root.readOnly cameraInits: root.cameraInits - cameraInit: _reconstruction.cameraInit + cameraInit: reconstruction.cameraInit + hdrCameraInit: reconstruction.hdrCameraInit currentIndex: reconstruction.cameraInitIndex - onCurrentIndexChanged: reconstruction.cameraInitIndex = currentIndex onRemoveImageRequest: reconstruction.removeAttribute(attribute) onFilesDropped: reconstruction.handleFilesDrop(drop, augmentSfm ? null : cameraInit) } diff --git a/meshroom/ui/reconstruction.py b/meshroom/ui/reconstruction.py index 5ccb89d1..997d82d1 100755 --- a/meshroom/ui/reconstruction.py +++ b/meshroom/ui/reconstruction.py @@ -7,6 +7,7 @@ from threading import Thread from PySide2.QtCore import QObject, Slot, Property, Signal, QUrl, QSizeF from PySide2.QtGui import QMatrix4x4, QMatrix3x3, QQuaternion, QVector3D, QVector2D +import meshroom.core from meshroom import multiview from meshroom.common.qt import QObjectListModel from meshroom.core import Version @@ -369,6 +370,8 @@ class Reconstruction(UIGraph): self._buildingIntrinsics = False self.intrinsicsBuilt.connect(self.onIntrinsicsAvailable) + self._hdrCameraInit = None + self.importImagesFailed.connect(self.onImportImagesFailed) # - Feature Extraction @@ -394,6 +397,10 @@ class Reconstruction(UIGraph): # - Texturing self._texturing = None + # - LDR2HDR + self._ldr2hdr = None + self.cameraInitChanged.connect(self.updateLdr2hdrNode) + # react to internal graph changes to update those variables self.graphChanged.connect(self.onGraphChanged) @@ -449,6 +456,8 @@ class Reconstruction(UIGraph): self.prepareDenseScene = None self.depthMap = None self.texturing = None + self.ldr2hdr = None + self.hdrCameraInit = None self.updateCameraInits() if not self._graph: return @@ -478,6 +487,10 @@ class Reconstruction(UIGraph): def getCameraInitIndex(self): if not self._cameraInit: + # No CameraInit node + return -1 + if not self._cameraInit.graph: + # The CameraInit node is a temporary one not attached to a graph return -1 return self._cameraInits.indexOf(self._cameraInit) @@ -493,6 +506,24 @@ class Reconstruction(UIGraph): """ Set the current FeatureExtraction node based on the current CameraInit node. """ self.depthMap = self.lastNodeOfType('DepthMapFilter', self.cameraInit) if self.cameraInit else None + def updateLdr2hdrNode(self): + """ Set the current LDR2HDR node based on the current CameraInit node. """ + self.ldr2hdr = self.lastNodeOfType('LDRToHDR', self.cameraInit) if self.cameraInit else None + + @Slot() + def setupLDRToHDRCameraInit(self): + if not self.ldr2hdr: + self.hdrCameraInit = Node("CameraInit") + return + sfmFile = self.ldr2hdr.attribute("outSfMDataFilename").value + if not sfmFile or not os.path.isfile(sfmFile): + self.hdrCameraInit = Node("CameraInit") + return + nodeDesc = meshroom.core.nodesDesc["CameraInit"]() + views, intrinsics = nodeDesc.readSfMData(sfmFile) + tmpCameraInit = Node("CameraInit", viewpoints=views, intrinsics=intrinsics) + self.hdrCameraInit = tmpCameraInit + def lastSfmNode(self): """ Retrieve the last SfM node from the initial CameraInit node. """ return self.lastNodeOfType("StructureFromMotion", self._cameraInit, Status.SUCCESS) @@ -778,6 +809,8 @@ class Reconstruction(UIGraph): cameraInitChanged = Signal() cameraInit = makeProperty(QObject, "_cameraInit", cameraInitChanged, resetOnDestroy=True) + hdrCameraInitChanged = Signal() + hdrCameraInit = makeProperty(QObject, "_hdrCameraInit", hdrCameraInitChanged, resetOnDestroy=True) cameraInitIndex = Property(int, getCameraInitIndex, setCameraInitIndex, notify=cameraInitChanged) viewpoints = Property(QObject, getViewpoints, notify=cameraInitChanged) cameraInits = Property(QObject, lambda self: self._cameraInits, constant=True) @@ -970,6 +1003,9 @@ class Reconstruction(UIGraph): texturingChanged = Signal() texturing = makeProperty(QObject, "_texturing", notify=texturingChanged) + ldr2hdrChanged = Signal() + ldr2hdr = makeProperty(QObject, "_ldr2hdr", notify=ldr2hdrChanged, resetOnDestroy=True) + nbCameras = Property(int, reconstructedCamerasCount, notify=sfmReportChanged) # Signals to propagate high-level messages