Merge pull request #2235 from alicevision/dev/resectionIdFiltering

[Viewer3D] Add slider to display cameras based on their resection IDs
This commit is contained in:
Fabien Castan 2023-11-17 10:20:12 +01:00 committed by GitHub
commit df6d38f391
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 301 additions and 18 deletions

View file

@ -0,0 +1,76 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.11
import MaterialIcons 2.2
/**
* A custom GroupBox with predefined header that can be hidden and expanded.
*/
GroupBox {
id: root
title: ""
property int sidePadding: 6
property alias labelBackground: labelBg
property alias toolBarContent: toolBar.data
property bool expanded: expandButton.checked
padding: 2
leftPadding: sidePadding
rightPadding: sidePadding
topPadding: label.height + padding
background: Item {}
label: Pane {
background: Rectangle {
id: labelBg
color: palette.base
opacity: 0.8
MouseArea {
anchors.fill: parent
onClicked: {
expandButton.checked = !expandButton.checked
}
}
}
padding: 2
width: root.width
RowLayout {
width: parent.width
Label {
text: root.title
Layout.fillWidth: true
elide: Text.ElideRight
padding: 3
font.bold: true
font.pointSize: 8
}
RowLayout {
id: toolBar
height: parent.height
MaterialToolButton {
id: expandButton
ToolTip.text: "Expand More"
text: MaterialIcons.expand_more
font.pointSize: 10
implicitHeight: parent.height
checkable: true
checked: false
onCheckedChanged: {
if (checked) {
ToolTip.text = "Expand Less"
text = MaterialIcons.expand_less
} else {
ToolTip.text = "Expand More"
text = MaterialIcons.expand_more
}
}
}
}
}
}
}

View file

@ -1,6 +1,7 @@
module Controls module Controls
ColorChart 1.0 ColorChart.qml ColorChart 1.0 ColorChart.qml
ExpandableGroup 1.0 ExpandableGroup.qml
FloatingPane 1.0 FloatingPane.qml FloatingPane 1.0 FloatingPane.qml
Group 1.0 Group.qml Group 1.0 Group.qml
KeyValue 1.0 KeyValue.qml KeyValue 1.0 KeyValue.qml

View file

@ -92,6 +92,7 @@ QtObject {
readonly property string attachment: "\ue2bc" readonly property string attachment: "\ue2bc"
readonly property string audiotrack: "\ue3a1" readonly property string audiotrack: "\ue3a1"
readonly property string autorenew: "\ue863" readonly property string autorenew: "\ue863"
readonly property string auto_awesome_motion: "\ue661"
readonly property string av_timer: "\ue01b" readonly property string av_timer: "\ue01b"
readonly property string backspace: "\ue14a" readonly property string backspace: "\ue14a"
readonly property string backup: "\ue864" readonly property string backup: "\ue864"

View file

@ -31,7 +31,8 @@ FloatingPane {
anchors.fill: parent anchors.fill: parent
spacing: 4 spacing: 4
Group { ExpandableGroup {
id: displayGroup
Layout.fillWidth: true Layout.fillWidth: true
title: "DISPLAY" title: "DISPLAY"
@ -43,6 +44,7 @@ FloatingPane {
Flow { Flow {
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
visible: displayGroup.expanded
spacing: 1 spacing: 1
MaterialToolButton { MaterialToolButton {
text: MaterialIcons.grid_on text: MaterialIcons.grid_on
@ -63,8 +65,13 @@ FloatingPane {
onClicked: Viewer3DSettings.displayOrigin = !Viewer3DSettings.displayOrigin onClicked: Viewer3DSettings.displayOrigin = !Viewer3DSettings.displayOrigin
} }
} }
MaterialLabel { text: MaterialIcons.grain; padding: 2 } MaterialLabel {
text: MaterialIcons.grain
padding: 2
visible: displayGroup.expanded
}
RowLayout { RowLayout {
visible: displayGroup.expanded
Slider { Slider {
Layout.fillWidth: true; from: 0; to: 5; stepSize: 0.001 Layout.fillWidth: true; from: 0; to: 5; stepSize: 0.001
value: Viewer3DSettings.pointSize value: Viewer3DSettings.pointSize
@ -83,8 +90,13 @@ FloatingPane {
} }
} }
MaterialLabel { text: MaterialIcons.videocam; padding: 2 } MaterialLabel {
text: MaterialIcons.videocam
padding: 2
visible: displayGroup.expanded
}
Slider { Slider {
visible: displayGroup.expanded
value: Viewer3DSettings.cameraScale value: Viewer3DSettings.cameraScale
from: 0 from: 0
to: 2 to: 2
@ -99,25 +111,30 @@ FloatingPane {
} }
} }
Group { ExpandableGroup {
id: cameraGroup
Layout.fillWidth: true Layout.fillWidth: true
title: "CAMERA" title: "CAMERA"
ColumnLayout { ColumnLayout {
width: parent.width width: parent.width
// Image/Camera synchronization // Image/Camera synchronization
Flow { Flow {
Layout.fillWidth: true Layout.fillWidth: true
visible: cameraGroup.expanded
spacing: 2 spacing: 2
// Synchronization // Synchronization
MaterialToolButton { MaterialToolButton {
id: syncViewpointCamera id: syncViewpointCamera
enabled: _reconstruction ? _reconstruction.sfmReport : false enabled: _reconstruction ? _reconstruction.sfmReport : false
text: MaterialIcons.linked_camera text: MaterialIcons.linked_camera
ToolTip.text: "Sync with Image Selection" ToolTip.text: "View Through The Active Camera"
checked: enabled && Viewer3DSettings.syncViewpointCamera checked: enabled && Viewer3DSettings.syncViewpointCamera
onClicked: Viewer3DSettings.syncViewpointCamera = !Viewer3DSettings.syncViewpointCamera onClicked: Viewer3DSettings.syncViewpointCamera = !Viewer3DSettings.syncViewpointCamera
} }
// Image Overlay controls // Image Overlay controls
RowLayout { RowLayout {
visible: syncViewpointCamera.enabled && Viewer3DSettings.syncViewpointCamera visible: syncViewpointCamera.enabled && Viewer3DSettings.syncViewpointCamera
@ -152,6 +169,109 @@ FloatingPane {
onClicked: Viewer3DSettings.viewpointImageFrame = !Viewer3DSettings.viewpointImageFrame onClicked: Viewer3DSettings.viewpointImageFrame = !Viewer3DSettings.viewpointImageFrame
} }
} }
ColumnLayout {
Layout.fillWidth: true
spacing: 2
visible: cameraGroup.expanded
RowLayout {
Layout.fillHeight: true
spacing: 2
MaterialToolButton {
id: resectionIdButton
text: MaterialIcons.switch_video
ToolTip.text: "Timeline Of Camera Reconstruction Groups"
ToolTip.visible: hovered
enabled: Viewer3DSettings.resectionIdCount
checked: enabled && Viewer3DSettings.displayResectionIds
onClicked: {
Viewer3DSettings.displayResectionIds = !Viewer3DSettings.displayResectionIds
Viewer3DSettings.resectionId = Viewer3DSettings.resectionIdCount
resectionIdSlider.value = Viewer3DSettings.resectionId
}
onEnabledChanged: {
Viewer3DSettings.resectionId = Viewer3DSettings.resectionIdCount
resectionIdSlider.value = Viewer3DSettings.resectionId
if (!enabled) {
Viewer3DSettings.displayResectionIds = false
}
}
}
Slider {
id: resectionIdSlider
value: Viewer3DSettings.resectionId
from: 0
to: Viewer3DSettings.resectionIdCount
stepSize: 1
onMoved: Viewer3DSettings.resectionId = value
Layout.fillWidth: true
leftPadding: 2
rightPadding: 2
visible: Viewer3DSettings.displayResectionIds
}
Label {
text: resectionIdSlider.value + "/" + Viewer3DSettings.resectionIdCount
visible: Viewer3DSettings.displayResectionIds
}
}
RowLayout {
spacing: 10
Layout.fillWidth: true
Layout.margins: 2
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
MaterialToolLabel {
iconText: MaterialIcons.stop
label: {
var id = undefined
// Ensure there are entries in resectionGroups and a valid resectionId before accessing anything
if (Viewer3DSettings.resectionId !== undefined && Viewer3DSettings.resectionGroups.length > 0)
id = Math.min(Viewer3DSettings.resectionId, Viewer3DSettings.resectionIdCount)
if (id !== undefined && Viewer3DSettings.resectionGroups[id] !== undefined)
return Viewer3DSettings.resectionGroups[id]
return 0
}
ToolTip.text: "Number Of Cameras In Current Resection Group"
visible: Viewer3DSettings.displayResectionIds
}
MaterialToolLabel {
iconText: MaterialIcons.auto_awesome_motion
label: {
let currentCameras = 0
for (let i = 0; i <= Viewer3DSettings.resectionIdCount; i++) {
if (i <= Viewer3DSettings.resectionId)
currentCameras += Viewer3DSettings.resectionGroups[i]
}
return currentCameras
}
ToolTip.text: "Number Of Cumulated Cameras"
visible: Viewer3DSettings.displayResectionIds
}
MaterialToolLabel {
iconText: MaterialIcons.videocam
label: {
let totalCameras = 0
for (let i = 0; i <= Viewer3DSettings.resectionIdCount; i++) {
totalCameras += Viewer3DSettings.resectionGroups[i]
}
return totalCameras
}
ToolTip.text: "Total Number Of Cameras"
visible: Viewer3DSettings.displayResectionIds
}
}
}
} }
} }
@ -192,6 +312,12 @@ FloatingPane {
ScrollBar.vertical: ScrollBar { id: scrollBar } ScrollBar.vertical: ScrollBar { id: scrollBar }
onCountChanged: {
if (mediaListView.count === 0) {
Viewer3DSettings.resectionIdCount = 0
}
}
currentIndex: -1 currentIndex: -1
Connections { Connections {
@ -223,7 +349,19 @@ FloatingPane {
onIsSelectedNodeChanged: updateCurrentIndex() onIsSelectedNodeChanged: updateCurrentIndex()
function updateCurrentIndex() { function updateCurrentIndex() {
if(isSelectedNode) { mediaListView.currentIndex = index } if (isSelectedNode) {
mediaListView.currentIndex = index
}
// If the index is updated, and the resection ID count is available, update every resection-related variable:
// this covers the changes of index that occur when a node whose output is already loaded in the 3D viewer is
// clicked/double-clicked, and when the active entry is removed from the list.
if (model.resectionIdCount) {
Viewer3DSettings.resectionIdCount = model.resectionIdCount
Viewer3DSettings.resectionGroups = model.resectionGroups
Viewer3DSettings.resectionId = model.resectionId
resectionIdSlider.value = model.resectionId
}
} }
height: childrenRect.height height: childrenRect.height
@ -234,22 +372,41 @@ FloatingPane {
} }
hoverEnabled: true hoverEnabled: true
onEntered: { if (model.attribute) uigraph.hoveredNode = model.attribute.node } onEntered: {
onExited: { if (model.attribute) uigraph.hoveredNode = null } if (model.attribute)
uigraph.hoveredNode = model.attribute.node
}
onExited: {
if (model.attribute)
uigraph.hoveredNode = null
}
onClicked: { onClicked: {
if (model.attribute) if (model.attribute)
uigraph.selectedNode = model.attribute.node; uigraph.selectedNode = model.attribute.node
else else
uigraph.selectedNode = null; uigraph.selectedNode = null
if (mouse.button == Qt.RightButton) if (mouse.button == Qt.RightButton)
contextMenu.popup(); contextMenu.popup()
mediaListView.currentIndex = index; mediaListView.currentIndex = index
// Update the resection ID-related objects based on the active model
Viewer3DSettings.resectionIdCount = model.resectionIdCount
Viewer3DSettings.resectionGroups = model.resectionGroups
Viewer3DSettings.resectionId = model.resectionId
resectionIdSlider.value = model.resectionId
} }
onDoubleClicked: { onDoubleClicked: {
model.visible = true; model.visible = true;
nodeActivated(model.attribute.node); nodeActivated(model.attribute.node);
} }
Connections {
target: resectionIdSlider
function onValueChanged() {
model.resectionId = resectionIdSlider.value
}
}
RowLayout { RowLayout {
width: parent.width width: parent.width
spacing: 2 spacing: 2

View file

@ -58,6 +58,9 @@ Entity {
"faceCount": 0, "faceCount": 0,
"cameraCount": 0, "cameraCount": 0,
"textureCount": 0, "textureCount": 0,
"resectionIdCount": 0,
"resectionId": 0,
"resectionGroups": [],
"status": SceneLoader.None "status": SceneLoader.None
}) })
} }
@ -308,12 +311,11 @@ Entity {
if (object) { if (object) {
// bind media info to corresponding model roles // bind media info to corresponding model roles
// (test for object validity to avoid error messages right after object has been deleted) // (test for object validity to avoid error messages right after object has been deleted)
var boundProperties = ["vertexCount", "faceCount", "cameraCount", "textureCount"] var boundProperties = ["vertexCount", "faceCount", "cameraCount", "textureCount", "resectionIdCount", "resectionId", "resectionGroups"]
boundProperties.forEach(function(prop) { boundProperties.forEach(function(prop) {
model[prop] = Qt.binding(function() { return object ? object[prop] : 0 }) model[prop] = Qt.binding(function() { return object ? object[prop] : 0 })
}) })
} } else if (finalSource && status === Component.Ready) {
else if (finalSource && status === Component.Ready) {
// source was valid but no loader was created, remove element // source was valid but no loader was created, remove element
// check if component is ready to avoid removing element from the model before adding instance to the node // check if component is ready to avoid removing element from the model before adding instance to the node
remove(index) remove(index)

View file

@ -99,8 +99,9 @@ import Utils 1.0
'source': source, 'source': source,
'pointSize': Qt.binding(function() { return 0.01 * Viewer3DSettings.pointSize }), 'pointSize': Qt.binding(function() { return 0.01 * Viewer3DSettings.pointSize }),
'locatorScale': Qt.binding(function() { return Viewer3DSettings.cameraScale }), 'locatorScale': Qt.binding(function() { return Viewer3DSettings.cameraScale }),
'cameraPickingEnabled': Qt.binding(function() { return root.enabled }) 'cameraPickingEnabled': Qt.binding(function() { return root.enabled }),
}) 'resectionId': Qt.binding(function() { return Viewer3DSettings.resectionId })
});
obj.statusChanged.connect(function() { obj.statusChanged.connect(function() {
if (obj.status === SceneLoader.Ready) { if (obj.status === SceneLoader.Ready) {
@ -109,6 +110,11 @@ import Utils 1.0
} }
cameraCount = obj.spawnCameraSelectors(); cameraCount = obj.spawnCameraSelectors();
} }
Viewer3DSettings.resectionIdCount = obj.countResectionIds();
Viewer3DSettings.resectionGroups = obj.countResectionGroups(Viewer3DSettings.resectionIdCount + 1);
resectionIdCount = Viewer3DSettings.resectionIdCount
resectionGroups = Viewer3DSettings.resectionGroups
resectionId = Viewer3DSettings.resectionIdCount
root.status = obj.status; root.status = obj.status;
}) })
} }

View file

@ -17,4 +17,10 @@ Entity {
property int cameraCount property int cameraCount
/// Number of textures /// Number of textures
property int textureCount property int textureCount
/// Number of resection IDs
property int resectionIdCount
/// Current resection ID
property int resectionId
/// Groups of cameras based on resection IDs
property var resectionGroups
} }

View file

@ -33,6 +33,34 @@ SfmDataEntity {
return validCameras; return validCameras;
} }
function countResectionIds() {
var maxResectionId = 0
for (var i = 0; i < root.cameras.length; i++) {
var cam = root.cameras[i]
var resectionId = cam.resectionId
if (resectionId === undefined || resectionId === 4294967295)
continue
if (resectionId > maxResectionId)
maxResectionId = resectionId
}
return maxResectionId
}
function countResectionGroups(resectionIdCount) {
var arr = Array(resectionIdCount).fill(0)
for (var i = 0; i < root.cameras.length; i++) {
var cam = root.cameras[i]
var resectionId = cam.resectionId
if (resectionId === undefined || resectionId === 4294967295)
continue
arr[resectionId] = arr[resectionId] + 1
}
return arr
}
SystemPalette { SystemPalette {
id: activePalette id: activePalette
} }

View file

@ -56,4 +56,10 @@ Item {
readonly property bool showViewpointImageOverlay: syncViewpointCamera && viewpointImageOverlay readonly property bool showViewpointImageOverlay: syncViewpointCamera && viewpointImageOverlay
property bool viewpointImageFrame: false property bool viewpointImageFrame: false
readonly property bool showViewpointImageFrame: syncViewpointCamera && viewpointImageFrame readonly property bool showViewpointImageFrame: syncViewpointCamera && viewpointImageFrame
// Cameras' resection IDs
property bool displayResectionIds: false
property int resectionIdCount: 0
property int resectionId: resectionIdCount
property var resectionGroups: [] // Number of cameras for each resection ID
} }