[ui] new generic way to manage "active" nodes per node type

This commit is contained in:
Fabien Castan 2020-07-07 22:05:53 +02:00
parent ea5b639245
commit ec67c772fa
12 changed files with 344 additions and 205 deletions

View file

@ -881,7 +881,7 @@ class Graph(BaseObject):
filterTypes (str list): (optional) only return the nodes of the given types filterTypes (str list): (optional) only return the nodes of the given types
(does not stop the visit, this is a post-process only) (does not stop the visit, this is a post-process only)
Returns: Returns:
The list of nodes from startNode to the graph leaves following edges. The list of nodes and edges, from startNode to the graph leaves following edges.
""" """
nodes = [] nodes = []
edges = [] edges = []

View file

@ -58,8 +58,9 @@ Item {
} }
MenuItem { MenuItem {
text: "Define As Center Image" text: "Define As Center Image"
enabled: !root.readOnly && _viewpoint.viewId != -1 && _reconstruction && _reconstruction.sfmTransform property var activeNode: _reconstruction.activeNodes.get("SfMTransform").node
onClicked: _reconstruction.sfmTransform.attribute("transformation").value = _viewpoint.viewId.toString() enabled: !root.readOnly && _viewpoint.viewId != -1 && _reconstruction && activeNode
onClicked: activeNode.attribute("transformation").value = _viewpoint.viewId.toString()
} }
} }

View file

@ -16,7 +16,7 @@ Panel {
property variant cameraInits property variant cameraInits
property variant cameraInit property variant cameraInit
property variant hdrCameraInit property variant tempCameraInit
readonly property alias currentItem: grid.currentItem readonly property alias currentItem: grid.currentItem
readonly property string currentItemSource: grid.currentItem ? grid.currentItem.source : "" readonly property string currentItemSource: grid.currentItem ? grid.currentItem.source : ""
readonly property var currentItemMetadata: grid.currentItem ? grid.currentItem.metadata : undefined readonly property var currentItemMetadata: grid.currentItem ? grid.currentItem.metadata : undefined
@ -38,7 +38,7 @@ Panel {
QtObject { QtObject {
id: m id: m
property variant currentCameraInit: displayHDR.checked ? _reconstruction.hdrCameraInit : root.cameraInit property variant currentCameraInit: _reconstruction.tempCameraInit ? _reconstruction.tempCameraInit : root.cameraInit
property variant viewpoints: currentCameraInit ? currentCameraInit.attribute('viewpoints').value : undefined property variant viewpoints: currentCameraInit ? currentCameraInit.attribute('viewpoints').value : undefined
property bool readOnly: root.readOnly || displayHDR.checked property bool readOnly: root.readOnly || displayHDR.checked
} }
@ -194,7 +194,7 @@ Panel {
// Center of SfMTransform // Center of SfMTransform
Loader { Loader {
id: sfmTransformIndicator id: sfmTransformIndicator
active: (viewpoint.get("viewId").value == centerViewId) active: viewpoint && (viewpoint.get("viewId").value == centerViewId)
sourceComponent: ImageBadge { sourceComponent: ImageBadge {
text: MaterialIcons.gamepad text: MaterialIcons.gamepad
ToolTip.text: "Camera used to define the center of the scene." ToolTip.text: "Camera used to define the center of the scene."
@ -343,54 +343,114 @@ Panel {
} }
footerContent: RowLayout { footerContent: RowLayout {
// Images count
// Image count MaterialToolLabel {
RowLayout { ToolTip.text: grid.model.count + " Input Images"
Layout.fillWidth: true iconText: MaterialIcons.image
spacing: 8 label: grid.model.count.toString()
RowLayout { // enabled: grid.model.count > 0
MaterialLabel { text: MaterialIcons.image } // margin: 4
Label { text: grid.model.count } }
} // cameras count
RowLayout { MaterialToolLabel {
visible: _reconstruction.cameraInit && _reconstruction.nbCameras ToolTip.text: label + " Estimated Cameras"
MaterialLabel { text: MaterialIcons.videocam } iconText: MaterialIcons.videocam
Label { text: _reconstruction.cameraInit ? _reconstruction.nbCameras : 0 } label: _reconstruction ? _reconstruction.nbCameras.toString() : "0"
} // margin: 4
// enabled: _reconstruction.cameraInit && _reconstruction.nbCameras
} }
MaterialToolButton { Item { Layout.fillHeight: true; Layout.fillWidth: true }
MaterialToolLabelButton {
id: displayHDR id: displayHDR
font.pointSize: 20 property var activeNode: _reconstruction.activeNodes.get("LdrToHdrMerge").node
padding: 0 ToolTip.text: "Visualize HDR images: " + (activeNode ? activeNode.label : "No Node")
anchors.margins: 0 iconText: MaterialIcons.filter
implicitHeight: 14 label: activeNode ? activeNode.attribute("nbBrackets").value : ""
ToolTip.text: "Visualize HDR images: " + (_reconstruction.ldr2hdr ? _reconstruction.ldr2hdr.label : "No Node") // visible: activeNode
text: MaterialIcons.hdr_on enabled: activeNode && activeNode.isComputed
visible: _reconstruction.ldr2hdr property string nodeID: activeNode ? (activeNode.label + activeNode.isComputed) : ""
enabled: _reconstruction.ldr2hdr && _reconstruction.ldr2hdr.isComputed
property string nodeID: _reconstruction.ldr2hdr ? (_reconstruction.ldr2hdr.label + _reconstruction.ldr2hdr.isComputed) : ""
onNodeIDChanged: { onNodeIDChanged: {
if(checked) if(checked) {
{ open();
_reconstruction.setupLDRToHDRCameraInit();
} }
} }
onEnabledChanged: { onEnabledChanged: {
// Reset the toggle to avoid getting stuck // Reset the toggle to avoid getting stuck
// with the HDR node checked but disabled. // with the HDR node checked but disabled.
checked = false; if(checked) {
checked = false;
close();
}
} }
checkable: true checkable: true
checked: false checked: false
onClicked: { _reconstruction.setupLDRToHDRCameraInit(); } onClicked: {
if(checked) {
open();
} else {
close();
}
}
function open() {
if(imageProcessing.checked)
imageProcessing.checked = false;
_reconstruction.setupTempCameraInit(activeNode, "outSfMDataFilename");
}
function close() {
_reconstruction.clearTempCameraInit();
}
} }
Item { Layout.fillHeight: true; Layout.fillWidth: true } MaterialToolButton {
id: imageProcessing
property var activeNode: _reconstruction.activeNodes.get("ImageProcessing").node
font.pointSize: 15
padding: 0
ToolTip.text: "Preprocessed Images: " + (activeNode ? activeNode.label : "No Node")
text: MaterialIcons.wallpaper
visible: activeNode && activeNode.attribute("outSfMData").value
enabled: activeNode && activeNode.isComputed
property string nodeID: activeNode ? (activeNode.label + activeNode.isComputed) : ""
onNodeIDChanged: {
if(checked) {
open();
}
}
onEnabledChanged: {
// Reset the toggle to avoid getting stuck
// with the HDR node checked but disabled.
if(checked) {
checked = false;
close();
}
}
checkable: true
checked: false
onClicked: {
if(checked) {
open();
} else {
close();
}
}
function open() {
if(displayHDR.checked)
displayHDR.checked = false;
_reconstruction.setupTempCameraInit(activeNode, "outSfMData");
}
function close() {
_reconstruction.clearTempCameraInit();
}
}
Item { Layout.fillHeight: true; width: 1 }
// Thumbnail size icon and slider // Thumbnail size icon and slider
MaterialToolButton { MaterialToolButton {
text: MaterialIcons.photo_size_select_large text: MaterialIcons.photo_size_select_large
ToolTip.text: "Thumbnails Scale"
padding: 0 padding: 0
anchors.margins: 0 anchors.margins: 0
font.pointSize: 11 font.pointSize: 11
@ -404,5 +464,4 @@ Panel {
implicitWidth: 70 implicitWidth: 70
} }
} }
} }

View file

@ -0,0 +1,23 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
/**
* MLabel is a standard Label.
* If ToolTip.text is set, it shows up a tooltip when hovered.
*/
Label {
padding: 4
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
}
ToolTip.visible: mouseArea.containsMouse
ToolTip.delay: 500
background: Rectangle {
anchors.fill: parent
color: mouseArea.containsMouse ? Qt.darker(parent.palette.base, 0.6) : "transparent"
}
}

View file

@ -1,5 +1,6 @@
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
/** /**
@ -7,6 +8,7 @@ import QtQuick.Controls 2.3
* It also shows up its tooltip when hovered. * It also shows up its tooltip when hovered.
*/ */
ToolButton { ToolButton {
id: control
font.family: MaterialIcons.fontFamily font.family: MaterialIcons.fontFamily
padding: 4 padding: 4
font.pointSize: 13 font.pointSize: 13

View file

@ -0,0 +1,45 @@
import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
/**
* MaterialToolLabel is a Label with an icon (using MaterialIcons).
* It shows up its tooltip when hovered.
*/
Item {
id: control
property alias iconText: icon.text
property alias iconSize: icon.font.pointSize
property alias label: labelItem.text
width: childrenRect.width
height: childrenRect.height
RowLayout {
Label {
id: icon
font.family: MaterialIcons.fontFamily
font.pointSize: 13
padding: 0
text: ""
color: palette.text
}
Label {
id: labelItem
text: ""
color: palette.text
}
Item {
width: 5
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
}
ToolTip.visible: mouseArea.containsMouse
ToolTip.delay: 500
}

View file

@ -0,0 +1,51 @@
import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
/**
* MaterialToolButton is a standard ToolButton using MaterialIcons font.
* It also shows up its tooltip when hovered.
*/
ToolButton {
id: control
property alias iconText: icon.text
property alias iconSize: icon.font.pointSize
property alias label: labelItem.text
padding: 0
ToolTip.visible: ToolTip.text && hovered
ToolTip.delay: 100
width: childrenRect.width
height: childrenRect.height
contentItem: RowLayout {
Layout.margins: 0
Label {
id: icon
font.family: MaterialIcons.fontFamily
font.pointSize: 13
padding: 0
text: ""
color: (checked ? palette.highlight : palette.text)
}
Label {
id: labelItem
text: ""
padding: 0
color: (checked ? palette.highlight : palette.text)
}
}
background: Rectangle {
color: {
if(pressed || checked || hovered)
{
if(pressed || checked)
return Qt.darker(parent.palette.base, 1.3)
if(hovered)
return Qt.darker(parent.palette.base, 0.6)
}
return "transparent";
}
border.color: checked ? Qt.darker(parent.palette.base, 1.4) : "transparent"
}
}

View file

@ -1,4 +1,7 @@
module MaterialIcons module MaterialIcons
singleton MaterialIcons 2.2 MaterialIcons.qml singleton MaterialIcons 2.2 MaterialIcons.qml
MaterialToolButton 2.2 MaterialToolButton.qml MaterialToolButton 2.2 MaterialToolButton.qml
MaterialToolLabelButton 2.2 MaterialToolLabelButton.qml
MaterialToolLabel 2.2 MaterialToolLabel.qml
MaterialLabel 2.2 MaterialLabel.qml MaterialLabel 2.2 MaterialLabel.qml
MLabel 2.2 MLabel.qml

View file

@ -104,10 +104,11 @@ FocusScope {
} }
function getImageFile(type) { function getImageFile(type) {
var depthMapNode = _reconstruction.activeNodes.get('allDepthMap').node;
if (type == "image") { if (type == "image") {
return root.source; return root.source;
} else if (_reconstruction.depthMap != undefined && _reconstruction.selectedViewId >= 0) { } else if (depthMapNode != undefined && _reconstruction.selectedViewId >= 0) {
return Filepath.stringToUrl(_reconstruction.depthMap.internalFolder+_reconstruction.selectedViewId+"_"+type+"Map.exr"); return Filepath.stringToUrl(depthMapNode.internalFolder+_reconstruction.selectedViewId+"_"+type+"Map.exr");
} }
return ""; return "";
} }
@ -245,8 +246,8 @@ FocusScope {
// note: requires QtAliceVision plugin - use a Loader to evaluate plugin availability at runtime // note: requires QtAliceVision plugin - use a Loader to evaluate plugin availability at runtime
Loader { Loader {
id: featuresViewerLoader id: featuresViewerLoader
active: displayFeatures.checked active: displayFeatures.checked
property var activeNode: _reconstruction.activeNodes.get("FeatureExtraction").node
// handle rotation/position based on available metadata // handle rotation/position based on available metadata
rotation: { rotation: {
@ -265,8 +266,8 @@ FocusScope {
// instantiate and initialize a FeaturesViewer component dynamically using Loader.setSource // instantiate and initialize a FeaturesViewer component dynamically using Loader.setSource
setSource("FeaturesViewer.qml", { setSource("FeaturesViewer.qml", {
'viewId': Qt.binding(function() { return _reconstruction.selectedViewId; }), 'viewId': Qt.binding(function() { return _reconstruction.selectedViewId; }),
'model': Qt.binding(function() { return _reconstruction.featureExtraction ? _reconstruction.featureExtraction.attribute("describerTypes").value : ""; }), 'model': Qt.binding(function() { return activeNode ? activeNode.attribute("describerTypes").value : ""; }),
'featureFolder': Qt.binding(function() { return _reconstruction.featureExtraction ? Filepath.stringToUrl(_reconstruction.featureExtraction.attribute("output").value) : ""; }), 'featureFolder': Qt.binding(function() { return activeNode ? Filepath.stringToUrl(activeNode.attribute("output").value) : ""; }),
'tracks': Qt.binding(function() { return mtracksLoader.status === Loader.Ready ? mtracksLoader.item : null; }), 'tracks': Qt.binding(function() { return mtracksLoader.status === Loader.Ready ? mtracksLoader.item : null; }),
'sfmData': Qt.binding(function() { return msfmDataLoader.status === Loader.Ready ? msfmDataLoader.item : null; }), 'sfmData': Qt.binding(function() { return msfmDataLoader.status === Loader.Ready ? msfmDataLoader.item : null; }),
}) })
@ -281,7 +282,8 @@ FocusScope {
// note: use a Loader to evaluate if a PanoramaInit node exist and displayFisheyeCircle checked at runtime // note: use a Loader to evaluate if a PanoramaInit node exist and displayFisheyeCircle checked at runtime
Loader { Loader {
anchors.centerIn: parent anchors.centerIn: parent
active: (displayFisheyeCircleLoader.checked && _reconstruction.panoramaInit) property var activeNode: _reconstruction.activeNodes.get("PanoramaInit").node
active: (displayFisheyeCircleLoader.checked && activeNode)
// handle rotation/position based on available metadata // handle rotation/position based on available metadata
rotation: { rotation: {
@ -294,28 +296,28 @@ FocusScope {
} }
sourceComponent: CircleGizmo { sourceComponent: CircleGizmo {
property bool useAuto: _reconstruction.panoramaInit.attribute("estimateFisheyeCircle").value property bool useAuto: activeNode.attribute("estimateFisheyeCircle").value
readOnly: useAuto readOnly: useAuto
visible: (!useAuto) || _reconstruction.panoramaInit.isComputed visible: (!useAuto) || activeNode.isComputed
property real userFisheyeRadius: _reconstruction.panoramaInit.attribute("fisheyeRadius").value property real userFisheyeRadius: activeNode.attribute("fisheyeRadius").value
property variant fisheyeAutoParams: _reconstruction.getAutoFisheyeCircle(_reconstruction.panoramaInit) property variant fisheyeAutoParams: _reconstruction.getAutoFisheyeCircle(activeNode)
x: useAuto ? fisheyeAutoParams.x : _reconstruction.panoramaInit.attribute("fisheyeCenterOffset.fisheyeCenterOffset_x").value x: useAuto ? fisheyeAutoParams.x : activeNode.attribute("fisheyeCenterOffset.fisheyeCenterOffset_x").value
y: useAuto ? fisheyeAutoParams.y : _reconstruction.panoramaInit.attribute("fisheyeCenterOffset.fisheyeCenterOffset_y").value y: useAuto ? fisheyeAutoParams.y : activeNode.attribute("fisheyeCenterOffset.fisheyeCenterOffset_y").value
radius: useAuto ? fisheyeAutoParams.z : ((imgContainer.image ? Math.min(imgContainer.image.width, imgContainer.image.height) : 1.0) * 0.5 * (userFisheyeRadius * 0.01)) radius: useAuto ? fisheyeAutoParams.z : ((imgContainer.image ? Math.min(imgContainer.image.width, imgContainer.image.height) : 1.0) * 0.5 * (userFisheyeRadius * 0.01))
border.width: Math.max(1, (3.0 / imgContainer.scale)) border.width: Math.max(1, (3.0 / imgContainer.scale))
onMoved: { onMoved: {
if(!useAuto) if(!useAuto)
{ {
_reconstruction.setAttribute(_reconstruction.panoramaInit.attribute("fisheyeCenterOffset.fisheyeCenterOffset_x"), x) _reconstruction.setAttribute(activeNode.attribute("fisheyeCenterOffset.fisheyeCenterOffset_x"), x)
_reconstruction.setAttribute(_reconstruction.panoramaInit.attribute("fisheyeCenterOffset.fisheyeCenterOffset_y"), y) _reconstruction.setAttribute(activeNode.attribute("fisheyeCenterOffset.fisheyeCenterOffset_y"), y)
} }
} }
onIncrementRadius: { onIncrementRadius: {
if(!useAuto) if(!useAuto)
{ {
_reconstruction.setAttribute(_reconstruction.panoramaInit.attribute("fisheyeRadius"), _reconstruction.panoramaInit.attribute("fisheyeRadius").value + radiusOffset) _reconstruction.setAttribute(activeNode.attribute("fisheyeRadius"), activeNode.attribute("fisheyeRadius").value + radiusOffset)
} }
} }
} }
@ -352,8 +354,9 @@ FocusScope {
// show which depthmap node is active // show which depthmap node is active
Label { Label {
id: depthMapNodeName id: depthMapNodeName
visible: (_reconstruction.depthMap != undefined) && (imageType.type != "image") property var activeNode: _reconstruction.activeNodes.get("allDepthMap").node
text: (_reconstruction.depthMap != undefined ? _reconstruction.depthMap.label : "") visible: (activeNode != undefined) && (imageType.type != "image")
text: (activeNode != undefined ? activeNode.label : "")
font.pointSize: 8 font.pointSize: 8
horizontalAlignment: TextInput.AlignLeft horizontalAlignment: TextInput.AlignLeft
@ -422,10 +425,9 @@ FocusScope {
} }
Loader { Loader {
id: mtracksLoader id: mtracksLoader
// active: _reconstruction.featureMatching
property bool isUsed: displayFeatures.checked || displaySfmStatsView.checked || displaySfmDataGlobalStats.checked property bool isUsed: displayFeatures.checked || displaySfmStatsView.checked || displaySfmDataGlobalStats.checked
property var activeNode: _reconstruction.featureMatching property var activeNode: _reconstruction.activeNodes.get('FeatureMatching').node
property bool isComputed: activeNode && activeNode.isComputed property bool isComputed: activeNode && activeNode.isComputed
active: false active: false
@ -498,7 +500,7 @@ FocusScope {
active: displayFeatures.checked && featuresViewerLoader.status === Loader.Ready active: displayFeatures.checked && featuresViewerLoader.status === Loader.Ready
sourceComponent: FeaturesInfoOverlay { sourceComponent: FeaturesInfoOverlay {
featureExtractionNode: _reconstruction.featureExtraction featureExtractionNode: _reconstruction.activeNodes.get('FeatureExtraction').node
pluginStatus: featuresViewerLoader.status pluginStatus: featuresViewerLoader.status
featuresViewer: featuresViewerLoader.item featuresViewer: featuresViewerLoader.item
} }
@ -514,9 +516,8 @@ FocusScope {
anchors.fill: parent anchors.fill: parent
// zoom label // zoom label
Label { MLabel {
text: ((imgContainer.image && (imgContainer.image.status === Image.Ready)) ? imgContainer.scale.toFixed(2) : "1.00") + "x" text: ((imgContainer.image && (imgContainer.image.status === Image.Ready)) ? imgContainer.scale.toFixed(2) : "1.00") + "x"
state: "xsmall"
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
@ -533,6 +534,7 @@ FocusScope {
} }
} }
} }
ToolTip.text: "Zoom"
} }
MaterialToolButton { MaterialToolButton {
id: displayAlphaBackground id: displayAlphaBackground
@ -566,31 +568,30 @@ FocusScope {
} }
MaterialToolButton { MaterialToolButton {
id: displayFisheyeCircleLoader id: displayFisheyeCircleLoader
ToolTip.text: "Display Fisheye Circle: " + (_reconstruction.panoramaInit ? _reconstruction.panoramaInit.label : "No Node") property var activeNode: _reconstruction.activeNodes.get('PanoramaInit').node
text: MaterialIcons.panorama_fish_eye ToolTip.text: "Display Fisheye Circle: " + (activeNode ? activeNode.label : "No Node")
text: MaterialIcons.vignette
// text: MaterialIcons.panorama_fish_eye
font.pointSize: 11 font.pointSize: 11
Layout.minimumWidth: 0 Layout.minimumWidth: 0
checkable: true checkable: true
checked: false checked: false
enabled: _reconstruction.panoramaInit && _reconstruction.panoramaInit.attribute("useFisheye").value enabled: activeNode && activeNode.attribute("useFisheye").value
visible: _reconstruction.panoramaInit visible: activeNode
} }
Label { Label {
id: resolutionLabel id: resolutionLabel
Layout.fillWidth: true Layout.fillWidth: true
text: imgContainer.image ? (imgContainer.image.sourceSize.width + "x" + imgContainer.image.sourceSize.height) : "" text: (imgContainer.image && imgContainer.image.sourceSize.width > 0) ? (imgContainer.image.sourceSize.width + "x" + imgContainer.image.sourceSize.height) : ""
elide: Text.ElideRight elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
/*Rectangle {
anchors.fill: parent
color: "blue"
}*/
} }
ComboBox { ComboBox {
id: imageType id: imageType
property var activeNode: _reconstruction.activeNodes.get('allDepthMap').node
// set min size to 5 characters + one margin for the combobox // set min size to 5 characters + one margin for the combobox
clip: true clip: true
Layout.minimumWidth: 0 Layout.minimumWidth: 0
@ -601,12 +602,13 @@ FocusScope {
property string type: enabled ? types[currentIndex] : types[0] property string type: enabled ? types[currentIndex] : types[0]
model: types model: types
enabled: _reconstruction.depthMap != undefined enabled: activeNode
} }
MaterialToolButton { MaterialToolButton {
enabled: _reconstruction.depthMap != undefined property var activeNode: _reconstruction.activeNodes.get('allDepthMap').node
ToolTip.text: "View Depth Map in 3D (" + (_reconstruction.depthMap != undefined ? _reconstruction.depthMap.label : "No DepthMap Node Selected") + ")" enabled: activeNode
ToolTip.text: "View Depth Map in 3D (" + (activeNode ? activeNode.label : "No DepthMap Node Selected") + ")"
text: MaterialIcons.input text: MaterialIcons.input
font.pointSize: 11 font.pointSize: 11
Layout.minimumWidth: 0 Layout.minimumWidth: 0
@ -618,6 +620,7 @@ FocusScope {
MaterialToolButton { MaterialToolButton {
id: displaySfmStatsView id: displaySfmStatsView
property var activeNode: _reconstruction.activeNodes.get('sfm').node
font.family: MaterialIcons.fontFamily font.family: MaterialIcons.fontFamily
text: MaterialIcons.assessment text: MaterialIcons.assessment
@ -630,10 +633,9 @@ FocusScope {
smooth: false smooth: false
flat: true flat: true
checkable: enabled checkable: enabled
enabled: _reconstruction.sfm && _reconstruction.sfm.isComputed && _reconstruction.selectedViewId >= 0 enabled: activeNode && activeNode.isComputed && _reconstruction.selectedViewId >= 0
onCheckedChanged: { onCheckedChanged: {
if(checked == true) if(checked == true) {
{
displaySfmDataGlobalStats.checked = false displaySfmDataGlobalStats.checked = false
metadataCB.checked = false metadataCB.checked = false
} }
@ -642,6 +644,7 @@ FocusScope {
MaterialToolButton { MaterialToolButton {
id: displaySfmDataGlobalStats id: displaySfmDataGlobalStats
property var activeNode: _reconstruction.activeNodes.get('sfm').node
font.family: MaterialIcons.fontFamily font.family: MaterialIcons.fontFamily
text: MaterialIcons.language text: MaterialIcons.language
@ -654,10 +657,9 @@ FocusScope {
smooth: false smooth: false
flat: true flat: true
checkable: enabled checkable: enabled
enabled: _reconstruction.sfm && _reconstruction.sfm.isComputed enabled: activeNode && activeNode.isComputed
onCheckedChanged: { onCheckedChanged: {
if(checked == true) if(checked == true) {
{
displaySfmStatsView.checked = false displaySfmStatsView.checked = false
metadataCB.checked = false metadataCB.checked = false
} }

View file

@ -65,7 +65,7 @@ Item {
readOnly: root.readOnly readOnly: root.readOnly
cameraInits: root.cameraInits cameraInits: root.cameraInits
cameraInit: reconstruction.cameraInit cameraInit: reconstruction.cameraInit
hdrCameraInit: reconstruction.hdrCameraInit tempCameraInit: reconstruction.tempCameraInit
currentIndex: reconstruction.cameraInitIndex currentIndex: reconstruction.cameraInitIndex
onRemoveImageRequest: reconstruction.removeAttribute(attribute) onRemoveImageRequest: reconstruction.removeAttribute(attribute)
onFilesDropped: reconstruction.handleFilesDrop(drop, augmentSfm ? null : cameraInit) onFilesDropped: reconstruction.handleFilesDrop(drop, augmentSfm ? null : cameraInit)
@ -191,7 +191,7 @@ Item {
mediaLibrary: viewer3D.library mediaLibrary: viewer3D.library
camera: viewer3D.mainCamera camera: viewer3D.mainCamera
uigraph: reconstruction uigraph: reconstruction
onNodeActivated: _reconstruction.setActiveNodeOfType(node) onNodeActivated: _reconstruction.setActiveNode(node)
} }
} }
} }

View file

@ -703,7 +703,6 @@ ApplicationWindow {
} }
} }
GraphEditor { GraphEditor {
id: graphEditor id: graphEditor
@ -713,7 +712,7 @@ ApplicationWindow {
readOnly: graphLocked readOnly: graphLocked
onNodeDoubleClicked: { onNodeDoubleClicked: {
_reconstruction.setActiveNodeOfType(node); _reconstruction.setActiveNode(node);
let viewable = false; let viewable = false;
for(var i=0; i < node.attributes.count; ++i) for(var i=0; i < node.attributes.count; ++i)

View file

@ -8,6 +8,7 @@ from PySide2.QtCore import QObject, Slot, Property, Signal, QUrl, QSizeF
from PySide2.QtGui import QMatrix4x4, QMatrix3x3, QQuaternion, QVector3D, QVector2D from PySide2.QtGui import QMatrix4x4, QMatrix3x3, QQuaternion, QVector3D, QVector2D
import meshroom.core import meshroom.core
import meshroom.common
from meshroom import multiview from meshroom import multiview
from meshroom.common.qt import QObjectListModel from meshroom.common.qt import QObjectListModel
from meshroom.core import Version from meshroom.core import Version
@ -196,6 +197,7 @@ class ViewpointWrapper(QObject):
self._reconstructed = False self._reconstructed = False
# PrepareDenseScene # PrepareDenseScene
self._undistortedImagePath = '' self._undistortedImagePath = ''
self._activeNode_PrepareDenseScene = self._reconstruction.activeNodes.get("PrepareDenseScene")
# update internally cached variables # update internally cached variables
self._updateInitialParams() self._updateInitialParams()
@ -205,7 +207,7 @@ class ViewpointWrapper(QObject):
# trigger internal members updates when reconstruction members changes # trigger internal members updates when reconstruction members changes
self._reconstruction.cameraInitChanged.connect(self._updateInitialParams) self._reconstruction.cameraInitChanged.connect(self._updateInitialParams)
self._reconstruction.sfmReportChanged.connect(self._updateSfMParams) self._reconstruction.sfmReportChanged.connect(self._updateSfMParams)
self._reconstruction.prepareDenseSceneChanged.connect(self._updateDenseSceneParams) self._activeNode_PrepareDenseScene.nodeChanged.connect(self._updateDenseSceneParams)
def _updateInitialParams(self): def _updateInitialParams(self):
""" Update internal members depending on CameraInit. """ """ Update internal members depending on CameraInit. """
@ -235,11 +237,11 @@ class ViewpointWrapper(QObject):
def _updateDenseSceneParams(self): def _updateDenseSceneParams(self):
""" Update internal members depending on PrepareDenseScene. """ """ Update internal members depending on PrepareDenseScene. """
# undistorted image path # undistorted image path
if not self._reconstruction.prepareDenseScene: if not self._activeNode_PrepareDenseScene.node:
self._undistortedImagePath = '' self._undistortedImagePath = ''
else: else:
filename = "{}.{}".format(self._viewpoint.viewId.value, self._reconstruction.prepareDenseScene.outputFileType.value) filename = "{}.{}".format(self._viewpoint.viewId.value, self._activeNode_PrepareDenseScene.node.outputFileType.value)
self._undistortedImagePath = os.path.join(self._reconstruction.prepareDenseScene.output.value, filename) self._undistortedImagePath = os.path.join(self._activeNode_PrepareDenseScene.node.output.value, filename)
self.denseSceneParamsChanged.emit() self.denseSceneParamsChanged.emit()
@Property(type=QObject, constant=True) @Property(type=QObject, constant=True)
@ -388,37 +390,50 @@ def parseSfMJsonFile(sfmJsonFile):
return views, poses, intrinsics return views, poses, intrinsics
sfmHolderNodeTypes = ["StructureFromMotion", "GlobalSfM", "PanoramaEstimation", "SfMTransfer", "SfMTransform", "SfMAlignment"] class ActiveNode(QObject):
"""
Hold one active node for a given NodeType.
"""
def __init__(self, nodeType, parent=None):
super(ActiveNode, self).__init__(parent)
self.nodeType = nodeType
self._node = None
nodeChanged = Signal()
node = makeProperty(QObject, "_node", nodeChanged, resetOnDestroy=True)
class Reconstruction(UIGraph): class Reconstruction(UIGraph):
""" """
Specialization of a UIGraph designed to manage a 3D reconstruction. Specialization of a UIGraph designed to manage a 3D reconstruction.
""" """
activeNodeCategories = {
"sfm": ["StructureFromMotion", "GlobalSfM", "PanoramaEstimation", "SfMTransfer", "SfMTransform",
"SfMAlignment"],
"undistort": ["PrepareDenseScene", "PanoramaWarping"],
"allDepthMap": ["DepthMap", "DepthMapFilter"],
}
def __init__(self, defaultPipeline='', parent=None): def __init__(self, defaultPipeline='', parent=None):
super(Reconstruction, self).__init__(parent) super(Reconstruction, self).__init__(parent)
# initialize member variables for key steps of the 3D reconstruction pipeline # initialize member variables for key steps of the 3D reconstruction pipeline
self._activeNodes = meshroom.common.DictModel(keyAttrName="nodeType")
self.initActiveNodes()
# - CameraInit # - CameraInit
self._cameraInit = None # current CameraInit node self._cameraInit = None # current CameraInit node
self._cameraInits = QObjectListModel(parent=self) # all CameraInit nodes self._cameraInits = QObjectListModel(parent=self) # all CameraInit nodes
self._buildingIntrinsics = False self._buildingIntrinsics = False
self.intrinsicsBuilt.connect(self.onIntrinsicsAvailable) self.intrinsicsBuilt.connect(self.onIntrinsicsAvailable)
self._hdrCameraInit = None self.cameraInitChanged.connect(self.onCameraInitChanged)
self._tempCameraInit = None
self.importImagesFailed.connect(self.onImportImagesFailed) self.importImagesFailed.connect(self.onImportImagesFailed)
# - Feature Extraction
self._featureExtraction = None
self.cameraInitChanged.connect(self.updateFeatureExtraction)
# - Feature Matching
self._featureMatching = None
self.cameraInitChanged.connect(self.updateFeatureMatching)
# - SfM # - SfM
self._sfm = None self._sfm = None
self._views = None self._views = None
@ -428,28 +443,6 @@ class Reconstruction(UIGraph):
self._selectedViewpoint = None self._selectedViewpoint = None
self._liveSfmManager = LiveSfmManager(self) self._liveSfmManager = LiveSfmManager(self)
# - Prepare Dense Scene (undistorted images)
self._prepareDenseScene = None
# - Depth Map
self._depthMap = None
self.cameraInitChanged.connect(self.updateDepthMapNode)
# - Texturing
self._texturing = None
# - LDR2HDR
self._ldr2hdr = None
self.cameraInitChanged.connect(self.updateLdr2hdrNode)
# - PanoramaInit
self._panoramaInit = None
self.cameraInitChanged.connect(self.updatePanoramaInitNode)
# - PanoramaInit
self._sfmTransform = None
self.cameraInitChanged.connect(self.updateSfMTransformNode)
# react to internal graph changes to update those variables # react to internal graph changes to update those variables
self.graphChanged.connect(self.onGraphChanged) self.graphChanged.connect(self.onGraphChanged)
@ -458,6 +451,18 @@ class Reconstruction(UIGraph):
def setDefaultPipeline(self, defaultPipeline): def setDefaultPipeline(self, defaultPipeline):
self._defaultPipeline = defaultPipeline self._defaultPipeline = defaultPipeline
def initActiveNodes(self):
# Create all possible entries
for category, _ in self.activeNodeCategories.iteritems():
self._activeNodes.add(ActiveNode(category, self))
for nodeType, _ in meshroom.core.nodesDesc.iteritems():
self._activeNodes.add(ActiveNode(nodeType, self))
def onCameraInitChanged(self):
# Update active nodes when CameraInit changes
nodes = self._graph.nodesFromNode(self._cameraInit)[0]
self.setActiveNodes(nodes)
@Slot() @Slot()
@Slot(str) @Slot(str)
def new(self, pipeline=None): def new(self, pipeline=None):
@ -528,16 +533,8 @@ class Reconstruction(UIGraph):
""" React to the change of the internal graph. """ """ React to the change of the internal graph. """
self._liveSfmManager.reset() self._liveSfmManager.reset()
self.selectedViewId = "-1" self.selectedViewId = "-1"
self.featureExtraction = None
self.featureMatching = None
self.sfm = None self.sfm = None
self.prepareDenseScene = None self.tempCameraInit = None
self.depthMap = None
self.texturing = None
self.ldr2hdr = None
self.hdrCameraInit = None
self.panoramaInit = None
self.sfmTransform = None
self.updateCameraInits() self.updateCameraInits()
if not self._graph: if not self._graph:
return return
@ -578,35 +575,23 @@ class Reconstruction(UIGraph):
camInit = self._cameraInits[idx] if self._cameraInits else None camInit = self._cameraInits[idx] if self._cameraInits else None
self.cameraInit = camInit self.cameraInit = camInit
def updateFeatureExtraction(self):
""" Set the current FeatureExtraction node based on the current CameraInit node. """
self.featureExtraction = self.lastNodeOfType(['FeatureExtraction'], self.cameraInit) if self.cameraInit else None
def updateFeatureMatching(self):
""" Set the current FeatureMatching node based on the current CameraInit node. """
self.featureMatching = self.lastNodeOfType(['FeatureMatching'], self.cameraInit) if self.cameraInit else None
def updateDepthMapNode(self):
""" 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(['LdrToHdrMerge'], self.cameraInit) if self.cameraInit else None
@Slot() @Slot()
def setupLDRToHDRCameraInit(self): def clearTempCameraInit(self):
if not self.ldr2hdr: self.tempCameraInit = None
self.hdrCameraInit = Node("CameraInit")
@Slot(QObject, str)
def setupTempCameraInit(self, node, attrName):
if not node or not attrName:
self.tempCameraInit = None
return return
sfmFile = self.ldr2hdr.attribute("outSfMDataFilename").value sfmFile = node.attribute(attrName).value
if not sfmFile or not os.path.isfile(sfmFile): if not sfmFile or not os.path.isfile(sfmFile):
self.hdrCameraInit = Node("CameraInit") self.tempCameraInit = None
return return
nodeDesc = meshroom.core.nodesDesc["CameraInit"]() nodeDesc = meshroom.core.nodesDesc["CameraInit"]()
views, intrinsics = nodeDesc.readSfMData(sfmFile) views, intrinsics = nodeDesc.readSfMData(sfmFile)
tmpCameraInit = Node("CameraInit", viewpoints=views, intrinsics=intrinsics) tmpCameraInit = Node("CameraInit", viewpoints=views, intrinsics=intrinsics)
self.hdrCameraInit = tmpCameraInit self.tempCameraInit = tmpCameraInit
@Slot(QObject, result=QVector3D) @Slot(QObject, result=QVector3D)
def getAutoFisheyeCircle(self, panoramaInit): def getAutoFisheyeCircle(self, panoramaInit):
@ -633,17 +618,9 @@ class Reconstruction(UIGraph):
float(intrinsic.get("fisheyeCircleRadius", 0.0))) float(intrinsic.get("fisheyeCircleRadius", 0.0)))
return res return res
def updatePanoramaInitNode(self):
""" Set the current FeatureExtraction node based on the current CameraInit node. """
self.panoramaInit = self.lastNodeOfType(["PanoramaInit"], self.cameraInit) if self.cameraInit else None
def updateSfMTransformNode(self):
""" Set the current SfMTransform node based on the current CameraInit node. """
self.sfmTransform = self.lastNodeOfType(["SfMTransform"], self.cameraInit) if self.cameraInit else None
def lastSfmNode(self): def lastSfmNode(self):
""" Retrieve the last SfM node from the initial CameraInit node. """ """ Retrieve the last SfM node from the initial CameraInit node. """
return self.lastNodeOfType(sfmHolderNodeTypes, self._cameraInit, Status.SUCCESS) return self.lastNodeOfType(self.activeNodeCategories['sfm'], self._cameraInit, Status.SUCCESS)
def lastNodeOfType(self, nodeTypes, startNode, preferredStatus=None): def lastNodeOfType(self, nodeTypes, startNode, preferredStatus=None):
""" """
@ -938,10 +915,11 @@ class Reconstruction(UIGraph):
self._buildingIntrinsics = value self._buildingIntrinsics = value
self.buildingIntrinsicsChanged.emit() self.buildingIntrinsicsChanged.emit()
activeNodes = makeProperty(QObject, "_activeNodes", resetOnDestroy=True)
cameraInitChanged = Signal() cameraInitChanged = Signal()
cameraInit = makeProperty(QObject, "_cameraInit", cameraInitChanged, resetOnDestroy=True) cameraInit = makeProperty(QObject, "_cameraInit", cameraInitChanged, resetOnDestroy=True)
hdrCameraInitChanged = Signal() tempCameraInitChanged = Signal()
hdrCameraInit = makeProperty(QObject, "_hdrCameraInit", hdrCameraInitChanged, resetOnDestroy=True) tempCameraInit = makeProperty(QObject, "_tempCameraInit", tempCameraInitChanged, resetOnDestroy=True)
cameraInitIndex = Property(int, getCameraInitIndex, setCameraInitIndex, notify=cameraInitChanged) cameraInitIndex = Property(int, getCameraInitIndex, setCameraInitIndex, notify=cameraInitChanged)
viewpoints = Property(QObject, getViewpoints, notify=cameraInitChanged) viewpoints = Property(QObject, getViewpoints, notify=cameraInitChanged)
cameraInits = Property(QObject, lambda self: self._cameraInits, constant=True) cameraInits = Property(QObject, lambda self: self._cameraInits, constant=True)
@ -952,27 +930,30 @@ class Reconstruction(UIGraph):
liveSfmManager = Property(QObject, lambda self: self._liveSfmManager, constant=True) liveSfmManager = Property(QObject, lambda self: self._liveSfmManager, constant=True)
@Slot(QObject) @Slot(QObject)
def setActiveNodeOfType(self, node): def setActiveNode(self, node):
""" Set node as the active node of its type. """ """ Set node as the active node of its type. """
if node.nodeType in sfmHolderNodeTypes: for category, nodeTypes in self.activeNodeCategories.iteritems():
self.sfm = node if node.nodeType in nodeTypes:
self.activeNodes.get(category).node = node
if category == 'sfm':
self.setSfm(node)
self.activeNodes.get(node.nodeType).node = node
if node.nodeType == "FeatureExtraction": @Slot(QObject)
self.featureExtraction = node def setActiveNodes(self, nodes):
elif node.nodeType == "FeatureMatching": """ Set node as the active node of its type. """
self.featureMatching = node # Setup the active node per category only once, on the last one
elif node.nodeType == "CameraInit": nodesByCategory = {}
self.cameraInit = node for node in nodes:
elif node.nodeType == "PrepareDenseScene": for category, nodeTypes in self.activeNodeCategories.iteritems():
self.prepareDenseScene = node if node.nodeType in nodeTypes:
elif node.nodeType in ("DepthMap", "DepthMapFilter"): nodesByCategory[category] = node
self.depthMap = node for category, node in nodesByCategory.iteritems():
elif node.nodeType == "LdrToHdrMerge": self.activeNodes.get(category).node = node
self.ldr2hdr = node if category == 'sfm':
elif node.nodeType == "PanoramaInit": self.setSfm(node)
self.panoramaInit = node for node in nodes:
elif node.nodeType == "SfMTransform": self.activeNodes.get(node.nodeType).node = node
self.sfmTransform = node
def updateSfMResults(self): def updateSfMResults(self):
""" """
@ -1021,9 +1002,6 @@ class Reconstruction(UIGraph):
self._sfm.destroyed.disconnect(self._unsetSfm) self._sfm.destroyed.disconnect(self._unsetSfm)
self._setSfm(node) self._setSfm(node)
self.texturing = self.lastNodeOfType(["Texturing"], self._sfm, Status.SUCCESS)
self.prepareDenseScene = self.lastNodeOfType(["PrepareDenseScene"], self._sfm, Status.SUCCESS)
@Slot(QObject, result=bool) @Slot(QObject, result=bool)
def isInViews(self, viewpoint): def isInViews(self, viewpoint):
if not viewpoint: if not viewpoint:
@ -1129,35 +1107,11 @@ class Reconstruction(UIGraph):
sfmChanged = Signal() sfmChanged = Signal()
sfm = Property(QObject, getSfm, setSfm, notify=sfmChanged) sfm = Property(QObject, getSfm, setSfm, notify=sfmChanged)
featureExtractionChanged = Signal()
featureExtraction = makeProperty(QObject, "_featureExtraction", featureExtractionChanged, resetOnDestroy=True)
featureMatchingChanged = Signal()
featureMatching = makeProperty(QObject, "_featureMatching", featureMatchingChanged, resetOnDestroy=True)
sfmReportChanged = Signal() sfmReportChanged = Signal()
# convenient property for QML binding re-evaluation when sfm report changes # convenient property for QML binding re-evaluation when sfm report changes
sfmReport = Property(bool, lambda self: len(self._poses) > 0, notify=sfmReportChanged) sfmReport = Property(bool, lambda self: len(self._poses) > 0, notify=sfmReportChanged)
sfmAugmented = Signal(Node, Node) sfmAugmented = Signal(Node, Node)
prepareDenseSceneChanged = Signal()
prepareDenseScene = makeProperty(QObject, "_prepareDenseScene", notify=prepareDenseSceneChanged, resetOnDestroy=True)
depthMapChanged = Signal()
depthMap = makeProperty(QObject, "_depthMap", depthMapChanged, resetOnDestroy=True)
texturingChanged = Signal()
texturing = makeProperty(QObject, "_texturing", notify=texturingChanged, resetOnDestroy=True)
ldr2hdrChanged = Signal()
ldr2hdr = makeProperty(QObject, "_ldr2hdr", notify=ldr2hdrChanged, resetOnDestroy=True)
panoramaInitChanged = Signal()
panoramaInit = makeProperty(QObject, "_panoramaInit", notify=panoramaInitChanged, resetOnDestroy=True)
sfmTransformChanged = Signal()
sfmTransform = makeProperty(QObject, "_sfmTransform", notify=sfmTransformChanged, resetOnDestroy=True)
nbCameras = Property(int, reconstructedCamerasCount, notify=sfmReportChanged) nbCameras = Property(int, reconstructedCamerasCount, notify=sfmReportChanged)
# Signals to propagate high-level messages # Signals to propagate high-level messages