[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
(does not stop the visit, this is a post-process only)
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 = []
edges = []

View file

@ -58,8 +58,9 @@ Item {
}
MenuItem {
text: "Define As Center Image"
enabled: !root.readOnly && _viewpoint.viewId != -1 && _reconstruction && _reconstruction.sfmTransform
onClicked: _reconstruction.sfmTransform.attribute("transformation").value = _viewpoint.viewId.toString()
property var activeNode: _reconstruction.activeNodes.get("SfMTransform").node
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 cameraInit
property variant hdrCameraInit
property variant tempCameraInit
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
@ -38,7 +38,7 @@ Panel {
QtObject {
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 bool readOnly: root.readOnly || displayHDR.checked
}
@ -194,7 +194,7 @@ Panel {
// Center of SfMTransform
Loader {
id: sfmTransformIndicator
active: (viewpoint.get("viewId").value == centerViewId)
active: viewpoint && (viewpoint.get("viewId").value == centerViewId)
sourceComponent: ImageBadge {
text: MaterialIcons.gamepad
ToolTip.text: "Camera used to define the center of the scene."
@ -343,54 +343,114 @@ Panel {
}
footerContent: RowLayout {
// Image count
RowLayout {
Layout.fillWidth: true
spacing: 8
RowLayout {
MaterialLabel { text: MaterialIcons.image }
Label { text: grid.model.count }
}
RowLayout {
visible: _reconstruction.cameraInit && _reconstruction.nbCameras
MaterialLabel { text: MaterialIcons.videocam }
Label { text: _reconstruction.cameraInit ? _reconstruction.nbCameras : 0 }
}
// Images count
MaterialToolLabel {
ToolTip.text: grid.model.count + " Input Images"
iconText: MaterialIcons.image
label: grid.model.count.toString()
// enabled: grid.model.count > 0
// margin: 4
}
// cameras count
MaterialToolLabel {
ToolTip.text: label + " Estimated Cameras"
iconText: MaterialIcons.videocam
label: _reconstruction ? _reconstruction.nbCameras.toString() : "0"
// margin: 4
// enabled: _reconstruction.cameraInit && _reconstruction.nbCameras
}
MaterialToolButton {
Item { Layout.fillHeight: true; Layout.fillWidth: true }
MaterialToolLabelButton {
id: displayHDR
font.pointSize: 20
padding: 0
anchors.margins: 0
implicitHeight: 14
ToolTip.text: "Visualize HDR images: " + (_reconstruction.ldr2hdr ? _reconstruction.ldr2hdr.label : "No Node")
text: MaterialIcons.hdr_on
visible: _reconstruction.ldr2hdr
enabled: _reconstruction.ldr2hdr && _reconstruction.ldr2hdr.isComputed
property string nodeID: _reconstruction.ldr2hdr ? (_reconstruction.ldr2hdr.label + _reconstruction.ldr2hdr.isComputed) : ""
property var activeNode: _reconstruction.activeNodes.get("LdrToHdrMerge").node
ToolTip.text: "Visualize HDR images: " + (activeNode ? activeNode.label : "No Node")
iconText: MaterialIcons.filter
label: activeNode ? activeNode.attribute("nbBrackets").value : ""
// visible: activeNode
enabled: activeNode && activeNode.isComputed
property string nodeID: activeNode ? (activeNode.label + activeNode.isComputed) : ""
onNodeIDChanged: {
if(checked)
{
_reconstruction.setupLDRToHDRCameraInit();
if(checked) {
open();
}
}
onEnabledChanged: {
// Reset the toggle to avoid getting stuck
// with the HDR node checked but disabled.
checked = false;
if(checked) {
checked = false;
close();
}
}
checkable: true
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
MaterialToolButton {
text: MaterialIcons.photo_size_select_large
ToolTip.text: "Thumbnails Scale"
padding: 0
anchors.margins: 0
font.pointSize: 11
@ -404,5 +464,4 @@ Panel {
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.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.
*/
ToolButton {
id: control
font.family: MaterialIcons.fontFamily
padding: 4
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
singleton MaterialIcons 2.2 MaterialIcons.qml
MaterialToolButton 2.2 MaterialToolButton.qml
MaterialToolLabelButton 2.2 MaterialToolLabelButton.qml
MaterialToolLabel 2.2 MaterialToolLabel.qml
MaterialLabel 2.2 MaterialLabel.qml
MLabel 2.2 MLabel.qml

View file

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

View file

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

View file

@ -703,7 +703,6 @@ ApplicationWindow {
}
}
GraphEditor {
id: graphEditor
@ -713,7 +712,7 @@ ApplicationWindow {
readOnly: graphLocked
onNodeDoubleClicked: {
_reconstruction.setActiveNodeOfType(node);
_reconstruction.setActiveNode(node);
let viewable = false;
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
import meshroom.core
import meshroom.common
from meshroom import multiview
from meshroom.common.qt import QObjectListModel
from meshroom.core import Version
@ -196,6 +197,7 @@ class ViewpointWrapper(QObject):
self._reconstructed = False
# PrepareDenseScene
self._undistortedImagePath = ''
self._activeNode_PrepareDenseScene = self._reconstruction.activeNodes.get("PrepareDenseScene")
# update internally cached variables
self._updateInitialParams()
@ -205,7 +207,7 @@ class ViewpointWrapper(QObject):
# trigger internal members updates when reconstruction members changes
self._reconstruction.cameraInitChanged.connect(self._updateInitialParams)
self._reconstruction.sfmReportChanged.connect(self._updateSfMParams)
self._reconstruction.prepareDenseSceneChanged.connect(self._updateDenseSceneParams)
self._activeNode_PrepareDenseScene.nodeChanged.connect(self._updateDenseSceneParams)
def _updateInitialParams(self):
""" Update internal members depending on CameraInit. """
@ -235,11 +237,11 @@ class ViewpointWrapper(QObject):
def _updateDenseSceneParams(self):
""" Update internal members depending on PrepareDenseScene. """
# undistorted image path
if not self._reconstruction.prepareDenseScene:
if not self._activeNode_PrepareDenseScene.node:
self._undistortedImagePath = ''
else:
filename = "{}.{}".format(self._viewpoint.viewId.value, self._reconstruction.prepareDenseScene.outputFileType.value)
self._undistortedImagePath = os.path.join(self._reconstruction.prepareDenseScene.output.value, filename)
filename = "{}.{}".format(self._viewpoint.viewId.value, self._activeNode_PrepareDenseScene.node.outputFileType.value)
self._undistortedImagePath = os.path.join(self._activeNode_PrepareDenseScene.node.output.value, filename)
self.denseSceneParamsChanged.emit()
@Property(type=QObject, constant=True)
@ -388,37 +390,50 @@ def parseSfMJsonFile(sfmJsonFile):
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):
"""
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):
super(Reconstruction, self).__init__(parent)
# initialize member variables for key steps of the 3D reconstruction pipeline
self._activeNodes = meshroom.common.DictModel(keyAttrName="nodeType")
self.initActiveNodes()
# - CameraInit
self._cameraInit = None # current CameraInit node
self._cameraInits = QObjectListModel(parent=self) # all CameraInit nodes
self._buildingIntrinsics = False
self.intrinsicsBuilt.connect(self.onIntrinsicsAvailable)
self._hdrCameraInit = None
self.cameraInitChanged.connect(self.onCameraInitChanged)
self._tempCameraInit = None
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
self._sfm = None
self._views = None
@ -428,28 +443,6 @@ class Reconstruction(UIGraph):
self._selectedViewpoint = None
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
self.graphChanged.connect(self.onGraphChanged)
@ -458,6 +451,18 @@ class Reconstruction(UIGraph):
def setDefaultPipeline(self, 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(str)
def new(self, pipeline=None):
@ -528,16 +533,8 @@ class Reconstruction(UIGraph):
""" React to the change of the internal graph. """
self._liveSfmManager.reset()
self.selectedViewId = "-1"
self.featureExtraction = None
self.featureMatching = None
self.sfm = None
self.prepareDenseScene = None
self.depthMap = None
self.texturing = None
self.ldr2hdr = None
self.hdrCameraInit = None
self.panoramaInit = None
self.sfmTransform = None
self.tempCameraInit = None
self.updateCameraInits()
if not self._graph:
return
@ -578,35 +575,23 @@ class Reconstruction(UIGraph):
camInit = self._cameraInits[idx] if self._cameraInits else None
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()
def setupLDRToHDRCameraInit(self):
if not self.ldr2hdr:
self.hdrCameraInit = Node("CameraInit")
def clearTempCameraInit(self):
self.tempCameraInit = None
@Slot(QObject, str)
def setupTempCameraInit(self, node, attrName):
if not node or not attrName:
self.tempCameraInit = None
return
sfmFile = self.ldr2hdr.attribute("outSfMDataFilename").value
sfmFile = node.attribute(attrName).value
if not sfmFile or not os.path.isfile(sfmFile):
self.hdrCameraInit = Node("CameraInit")
self.tempCameraInit = None
return
nodeDesc = meshroom.core.nodesDesc["CameraInit"]()
views, intrinsics = nodeDesc.readSfMData(sfmFile)
tmpCameraInit = Node("CameraInit", viewpoints=views, intrinsics=intrinsics)
self.hdrCameraInit = tmpCameraInit
self.tempCameraInit = tmpCameraInit
@Slot(QObject, result=QVector3D)
def getAutoFisheyeCircle(self, panoramaInit):
@ -633,17 +618,9 @@ class Reconstruction(UIGraph):
float(intrinsic.get("fisheyeCircleRadius", 0.0)))
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):
""" 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):
"""
@ -938,10 +915,11 @@ class Reconstruction(UIGraph):
self._buildingIntrinsics = value
self.buildingIntrinsicsChanged.emit()
activeNodes = makeProperty(QObject, "_activeNodes", resetOnDestroy=True)
cameraInitChanged = Signal()
cameraInit = makeProperty(QObject, "_cameraInit", cameraInitChanged, resetOnDestroy=True)
hdrCameraInitChanged = Signal()
hdrCameraInit = makeProperty(QObject, "_hdrCameraInit", hdrCameraInitChanged, resetOnDestroy=True)
tempCameraInitChanged = Signal()
tempCameraInit = makeProperty(QObject, "_tempCameraInit", tempCameraInitChanged, resetOnDestroy=True)
cameraInitIndex = Property(int, getCameraInitIndex, setCameraInitIndex, notify=cameraInitChanged)
viewpoints = Property(QObject, getViewpoints, notify=cameraInitChanged)
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)
@Slot(QObject)
def setActiveNodeOfType(self, node):
def setActiveNode(self, node):
""" Set node as the active node of its type. """
if node.nodeType in sfmHolderNodeTypes:
self.sfm = node
for category, nodeTypes in self.activeNodeCategories.iteritems():
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":
self.featureExtraction = node
elif node.nodeType == "FeatureMatching":
self.featureMatching = node
elif node.nodeType == "CameraInit":
self.cameraInit = node
elif node.nodeType == "PrepareDenseScene":
self.prepareDenseScene = node
elif node.nodeType in ("DepthMap", "DepthMapFilter"):
self.depthMap = node
elif node.nodeType == "LdrToHdrMerge":
self.ldr2hdr = node
elif node.nodeType == "PanoramaInit":
self.panoramaInit = node
elif node.nodeType == "SfMTransform":
self.sfmTransform = node
@Slot(QObject)
def setActiveNodes(self, nodes):
""" Set node as the active node of its type. """
# Setup the active node per category only once, on the last one
nodesByCategory = {}
for node in nodes:
for category, nodeTypes in self.activeNodeCategories.iteritems():
if node.nodeType in nodeTypes:
nodesByCategory[category] = node
for category, node in nodesByCategory.iteritems():
self.activeNodes.get(category).node = node
if category == 'sfm':
self.setSfm(node)
for node in nodes:
self.activeNodes.get(node.nodeType).node = node
def updateSfMResults(self):
"""
@ -1021,9 +1002,6 @@ class Reconstruction(UIGraph):
self._sfm.destroyed.disconnect(self._unsetSfm)
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)
def isInViews(self, viewpoint):
if not viewpoint:
@ -1129,35 +1107,11 @@ class Reconstruction(UIGraph):
sfmChanged = Signal()
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()
# convenient property for QML binding re-evaluation when sfm report changes
sfmReport = Property(bool, lambda self: len(self._poses) > 0, notify=sfmReportChanged)
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)
# Signals to propagate high-level messages