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
ColorChart 1.0 ColorChart.qml
ExpandableGroup 1.0 ExpandableGroup.qml
FloatingPane 1.0 FloatingPane.qml
Group 1.0 Group.qml
KeyValue 1.0 KeyValue.qml

View file

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

View file

@ -31,7 +31,8 @@ FloatingPane {
anchors.fill: parent
spacing: 4
Group {
ExpandableGroup {
id: displayGroup
Layout.fillWidth: true
title: "DISPLAY"
@ -43,6 +44,7 @@ FloatingPane {
Flow {
Layout.columnSpan: 2
Layout.fillWidth: true
visible: displayGroup.expanded
spacing: 1
MaterialToolButton {
text: MaterialIcons.grid_on
@ -63,8 +65,13 @@ FloatingPane {
onClicked: Viewer3DSettings.displayOrigin = !Viewer3DSettings.displayOrigin
}
}
MaterialLabel { text: MaterialIcons.grain; padding: 2 }
MaterialLabel {
text: MaterialIcons.grain
padding: 2
visible: displayGroup.expanded
}
RowLayout {
visible: displayGroup.expanded
Slider {
Layout.fillWidth: true; from: 0; to: 5; stepSize: 0.001
value: Viewer3DSettings.pointSize
@ -83,8 +90,13 @@ FloatingPane {
}
}
MaterialLabel { text: MaterialIcons.videocam; padding: 2 }
MaterialLabel {
text: MaterialIcons.videocam
padding: 2
visible: displayGroup.expanded
}
Slider {
visible: displayGroup.expanded
value: Viewer3DSettings.cameraScale
from: 0
to: 2
@ -99,25 +111,30 @@ FloatingPane {
}
}
Group {
ExpandableGroup {
id: cameraGroup
Layout.fillWidth: true
title: "CAMERA"
ColumnLayout {
width: parent.width
// Image/Camera synchronization
Flow {
Layout.fillWidth: true
visible: cameraGroup.expanded
spacing: 2
// Synchronization
MaterialToolButton {
id: syncViewpointCamera
enabled: _reconstruction ? _reconstruction.sfmReport : false
text: MaterialIcons.linked_camera
ToolTip.text: "Sync with Image Selection"
ToolTip.text: "View Through The Active Camera"
checked: enabled && Viewer3DSettings.syncViewpointCamera
onClicked: Viewer3DSettings.syncViewpointCamera = !Viewer3DSettings.syncViewpointCamera
}
// Image Overlay controls
RowLayout {
visible: syncViewpointCamera.enabled && Viewer3DSettings.syncViewpointCamera
@ -152,6 +169,109 @@ FloatingPane {
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 }
onCountChanged: {
if (mediaListView.count === 0) {
Viewer3DSettings.resectionIdCount = 0
}
}
currentIndex: -1
Connections {
@ -217,13 +343,25 @@ FloatingPane {
// add mediaLibrary.count in the binding to ensure 'entity'
// is re-evaluated when mediaLibrary delegates are modified
property bool loading: model.status === SceneLoader.Loading
property bool hovered: model.attribute ? (uigraph ? uigraph.hoveredNode === model.attribute.node : false) : containsMouse
property bool hovered: model.attribute ? (uigraph ? uigraph.hoveredNode === model.attribute.node : false) : containsMouse
property bool isSelectedNode: model.attribute ? (uigraph ? uigraph.selectedNode === model.attribute.node : false) : false
onIsSelectedNodeChanged: 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
@ -234,22 +372,41 @@ FloatingPane {
}
hoverEnabled: true
onEntered: { if (model.attribute) uigraph.hoveredNode = model.attribute.node }
onExited: { if (model.attribute) uigraph.hoveredNode = null }
onEntered: {
if (model.attribute)
uigraph.hoveredNode = model.attribute.node
}
onExited: {
if (model.attribute)
uigraph.hoveredNode = null
}
onClicked: {
if (model.attribute)
uigraph.selectedNode = model.attribute.node;
uigraph.selectedNode = model.attribute.node
else
uigraph.selectedNode = null;
uigraph.selectedNode = null
if (mouse.button == Qt.RightButton)
contextMenu.popup();
mediaListView.currentIndex = index;
contextMenu.popup()
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: {
model.visible = true;
nodeActivated(model.attribute.node);
}
Connections {
target: resectionIdSlider
function onValueChanged() {
model.resectionId = resectionIdSlider.value
}
}
RowLayout {
width: parent.width
spacing: 2

View file

@ -58,6 +58,9 @@ Entity {
"faceCount": 0,
"cameraCount": 0,
"textureCount": 0,
"resectionIdCount": 0,
"resectionId": 0,
"resectionGroups": [],
"status": SceneLoader.None
})
}
@ -308,12 +311,11 @@ Entity {
if (object) {
// bind media info to corresponding model roles
// (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) {
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
// check if component is ready to avoid removing element from the model before adding instance to the node
remove(index)

View file

@ -99,8 +99,9 @@ import Utils 1.0
'source': source,
'pointSize': Qt.binding(function() { return 0.01 * Viewer3DSettings.pointSize }),
'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() {
if (obj.status === SceneLoader.Ready) {
@ -109,6 +110,11 @@ import Utils 1.0
}
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;
})
}

View file

@ -17,4 +17,10 @@ Entity {
property int cameraCount
/// Number of textures
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;
}
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 {
id: activePalette
}

View file

@ -56,4 +56,10 @@ Item {
readonly property bool showViewpointImageOverlay: syncViewpointCamera && viewpointImageOverlay
property bool viewpointImageFrame: false
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
}