Meshroom/meshroom/ui/qml/Viewer/Viewer2D.qml

1209 lines
56 KiB
QML

import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
import MaterialIcons 2.2
import Controls 1.0
FocusScope {
id: root
clip: true
property var displayedNode: _reconstruction.cameraInit
property url source
property var metadata
property var viewIn3D
property Component floatViewerComp: Qt.createComponent("FloatImage.qml")
property Component panoramaViewerComp: Qt.createComponent("PanoramaViewer.qml")
property alias useFloatImageViewer: displayHDR.checked
property alias useLensDistortionViewer: displayLensDistortionViewer.checked
property alias usePanoramaViewer: displayPanoramaViewer.checked
property var activeNodeFisheye: _reconstruction.activeNodes.get("PanoramaInit").node
property bool cropFisheye : activeNodeFisheye ? activeNodeFisheye.attribute("useFisheye").value : false
QtObject {
id: m
property variant imgMetadata: {
// I did not find a direct way to check if the map is empty or not...
var sfmHasImgMetadata = 0;
for(var key in root.metadata) { sfmHasImgMetadata = 1; break; }
if(sfmHasImgMetadata) // check if root.metadata is not empty
{
return root.metadata
}
if(floatImageViewerLoader.active)
{
return floatImageViewerLoader.item.metadata
}
return {}
}
}
Loader {
id: aliceVisionPluginLoader
active: true
source: "TestAliceVisionPlugin.qml"
}
Loader {
id: oiioPluginLoader
active: true
source: "TestOIIOPlugin.qml"
}
readonly property bool aliceVisionPluginAvailable: aliceVisionPluginLoader.status === Component.Ready
readonly property bool oiioPluginAvailable: oiioPluginLoader.status === Component.Ready
Component.onCompleted: {
if(!aliceVisionPluginAvailable)
console.warn("Missing plugin qtAliceVision.")
if(!oiioPluginAvailable)
console.warn("Missing plugin qtOIIO.")
}
property string loadingModules: {
if(!imgContainer.image)
return "";
var res = "";
if(imgContainer.image.status === Image.Loading)
res += " Image";
if(featuresViewerLoader.status === Loader.Ready && featuresViewerLoader.item)
{
for (var i = 0; i < featuresViewerLoader.item.count; ++i) {
if(featuresViewerLoader.item.itemAt(i).loadingFeatures)
{
res += " Features";
break;
}
}
}
if(mfeaturesLoader.status === Loader.Ready)
{
if(mfeaturesLoader.item.status === MFeatures.Loading)
res += " Features";
}
if(mtracksLoader.status === Loader.Ready)
{
if(mtracksLoader.item.status === MTracks.Loading)
res += " Tracks";
}
if(msfmDataLoader.status === Loader.Ready)
{
if(msfmDataLoader.item.status === MSfMData.Loading)
{
res += " SfMData";
}
}
return res;
}
function clear()
{
source = ''
metadata = {}
}
// slots
Keys.onPressed: {
if(event.key == Qt.Key_F) {
root.fit();
event.accepted = true;
}
}
// mouse area
MouseArea {
anchors.fill: parent
property double factor: 1.2
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressed: {
imgContainer.forceActiveFocus()
if(mouse.button & Qt.MiddleButton || (mouse.button & Qt.LeftButton && mouse.modifiers & Qt.ShiftModifier))
drag.target = imgContainer // start drag
}
onReleased: {
drag.target = undefined // stop drag
if(mouse.button & Qt.RightButton) {
var menu = contextMenu.createObject(root);
menu.x = mouse.x;
menu.y = mouse.y;
menu.open()
}
}
onWheel: {
var zoomFactor = wheel.angleDelta.y > 0 ? factor : 1/factor
if(Math.min(imgContainer.width, imgContainer.image.height) * imgContainer.scale * zoomFactor < 10)
return
var point = mapToItem(imgContainer, wheel.x, wheel.y)
imgContainer.x += (1-zoomFactor) * point.x * imgContainer.scale
imgContainer.y += (1-zoomFactor) * point.y * imgContainer.scale
imgContainer.scale *= zoomFactor
}
}
// functions
function fit() {
if(imgContainer.image.status != Image.Ready)
return;
imgContainer.scale = Math.min(imgLayout.width / imgContainer.image.width, root.height / imgContainer.image.height)
imgContainer.x = Math.max((imgLayout.width - imgContainer.image.width * imgContainer.scale)*0.5, 0)
imgContainer.y = Math.max((imgLayout.height - imgContainer.image.height * imgContainer.scale)*0.5, 0)
}
function tryLoadNode(node) {
// safety check
if (!node) {
return false;
}
// node must be computed or at least running
if (!node.isPartiallyFinished()) {
return false;
}
// node must have at least one output attribute with the image semantic
var hasImageOutputAttr = false;
for (var i = 0; i < node.attributes.count; i++) {
var attr = node.attributes.at(i);
if (attr.isOutput && attr.desc.semantic == "image") {
hasImageOutputAttr = true;
break;
}
}
if (!hasImageOutputAttr) {
return false;
}
displayedNode = node;
return true;
}
function getImageFile() {
// entry point for getting the image file URL that corresponds to
// the displayed node, selected output attribute and selected viewId
if (!displayedNode || outputAttribute.name == "" || outputAttribute.name == "gallery") {
return getViewpointPath(_reconstruction.selectedViewId);
}
return getFileAttributePath(displayedNode, outputAttribute.name, _reconstruction.selectedViewId);
}
function getFileAttributePath(node, attrName, viewId) {
// get output attribute with matching name
// and parse its value to get the image filepath
for (var i = 0; i < node.attributes.count; i++) {
var attr = node.attributes.at(i);
if (attr.name == attrName) {
let pattern = String(attr.value).replace("<VIEW_ID>", viewId);
let path = Filepath.globFirst(pattern);
return Filepath.stringToUrl(path);
}
}
return "";
}
function getViewpointPath(viewId) {
// get viewpoint from cameraInit with matching id
// and get its image filepath
for (var i = 0; i < _reconstruction.viewpoints.count; i++) {
var vp = _reconstruction.viewpoints.at(i);
if (vp.childAttribute("viewId").value == viewId) {
return Filepath.stringToUrl(vp.childAttribute("path").value);
}
}
return "";
}
function getViewpointMetadata(viewId) {
// get viewpoint from cameraInit with matching id
// and get its image filepath
for (var i = 0; i < _reconstruction.viewpoints.count; i++) {
var vp = _reconstruction.viewpoints.at(i);
if (vp.childAttribute("viewId").value == viewId) {
return vp.childAttribute("metadata").value;
}
}
return "";
}
onDisplayedNodeChanged: {
var names = [];
// safety check
if (displayedNode) {
// store attr name for output attributes that represent images
for (var i = 0; i < displayedNode.attributes.count; i++) {
var attr = displayedNode.attributes.at(i);
if (attr.isOutput && attr.desc.semantic == "image") {
names.push(attr.name);
}
}
// ensure that we can always visualize the gallery
if (names.length > 0) {
names.push("gallery");
}
}
outputAttribute.names = names;
}
Connections {
target: _reconstruction
onSelectedViewIdChanged: {
root.source = getImageFile();
root.metadata = getViewpointMetadata(_reconstruction.selectedViewId);
}
}
// context menu
property Component contextMenu: Menu {
MenuItem {
text: "Fit"
onTriggered: fit()
}
MenuItem {
text: "Zoom 100%"
onTriggered: {
imgContainer.scale = 1
imgContainer.x = Math.max((imgLayout.width-imgContainer.width*imgContainer.scale)*0.5, 0)
imgContainer.y = Math.max((imgLayout.height-imgContainer.height*imgContainer.scale)*0.5, 0)
}
}
}
ColumnLayout {
anchors.fill: parent
HdrImageToolbar {
id: hdrImageToolbar
anchors.margins: 0
visible: displayImageToolBarAction.checked && displayImageToolBarAction.enabled
Layout.fillWidth: true
onVisibleChanged: {
resetDefaultValues();
}
colorPickerVisible: {
return !displayPanoramaViewer.checked
}
colorRGBA: {
if(!floatImageViewerLoader.item ||
floatImageViewerLoader.item.status !== Image.Ready)
{
return null;
}
if(floatImageViewerLoader.item.containsMouse == false)
{
return null;
}
var pix = floatImageViewerLoader.item.pixelValueAt(Math.floor(floatImageViewerLoader.item.mouseX), Math.floor(floatImageViewerLoader.item.mouseY));
return pix;
}
}
LensDistortionToolbar {
id: lensDistortionImageToolbar
anchors.margins: 0
visible: displayLensDistortionToolBarAction.checked && displayLensDistortionToolBarAction.enabled
Layout.fillWidth: true
}
PanoramaToolbar {
id: panoramaViewerToolbar
anchors.margins: 0
visible: displayPanoramaToolBarAction.checked && displayPanoramaToolBarAction.enabled
Layout.fillWidth: true
}
// Image
Item {
id: imgLayout
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
Image {
id: alphaBackground
anchors.fill: parent
visible: displayAlphaBackground.checked
fillMode: Image.Tile
horizontalAlignment: Image.AlignLeft
verticalAlignment: Image.AlignTop
source: "../../img/checkerboard_light.png"
scale: 4
smooth: false
}
Item {
id: imgContainer
transformOrigin: Item.TopLeft
// qtAliceVision Image Viewer
Loader {
id: floatImageViewerLoader
active: root.aliceVisionPluginAvailable && (root.useFloatImageViewer || root.useLensDistortionViewer) && !panoramaViewerLoader.active
visible: (floatImageViewerLoader.status === Loader.Ready) && active
anchors.centerIn: parent
// handle rotation/position based on available metadata
rotation: {
var orientation = m.imgMetadata ? m.imgMetadata["Orientation"] : 0
switch(orientation) {
case "6": return 90;
case "8": return -90;
default: return 0;
}
}
onActiveChanged: {
if(active) {
// instantiate and initialize a FeaturesViewer component dynamically using Loader.setSource
// Note: It does not work to use previously created component, so we re-create it with setSource.
// floatViewerComp.createObject(floatImageViewerLoader, {
setSource("FloatImage.qml", {
'source': Qt.binding(function() { return getImageFile(); }),
'gamma': Qt.binding(function() { return hdrImageToolbar.gammaValue; }),
'gain': Qt.binding(function() { return hdrImageToolbar.gainValue; }),
'channelModeString': Qt.binding(function() { return hdrImageToolbar.channelModeValue; }),
'isPrincipalPointsDisplayed' : Qt.binding(function(){ return lensDistortionImageToolbar.displayPrincipalPoint;}),
'surface.displayGrid' : Qt.binding(function(){ return lensDistortionImageToolbar.visible && lensDistortionImageToolbar.displayGrid;}),
'surface.gridOpacity' : Qt.binding(function(){ return lensDistortionImageToolbar.opacityValue;}),
'surface.gridColor' : Qt.binding(function(){ return lensDistortionImageToolbar.color;}),
'surface.subdivisions' : Qt.binding(function(){ return root.useFloatImageViewer ? 1 : lensDistortionImageToolbar.subdivisionsValue;}),
'viewerTypeString': Qt.binding(function(){ return displayLensDistortionViewer.checked ? "distortion" : "hdr";}),
'sfmRequired': Qt.binding(function(){ return displayLensDistortionViewer.checked ? true : false;}),
'surface.msfmData': Qt.binding(function() { return (msfmDataLoader.status === Loader.Ready && msfmDataLoader.item.status === 2) ? msfmDataLoader.item : null; }),
'canBeHovered': false,
'idView': Qt.binding(function() { return _reconstruction.selectedViewId; }),
'cropFisheye': false
})
} else {
// Force the unload (instead of using Component.onCompleted to load it once and for all) is necessary since Qt 5.14
setSource("", {})
}
}
}
// qtAliceVision Panorama Viewer
Loader {
id: panoramaViewerLoader
active: root.aliceVisionPluginAvailable && root.usePanoramaViewer && _reconstruction.activeNodes.get('sfm').node
visible: (panoramaViewerLoader.status === Loader.Ready) && active
anchors.centerIn: parent
onActiveChanged: {
if(active) {
setSource("PanoramaViewer.qml", {
'subdivisionsPano': Qt.binding(function(){ return panoramaViewerToolbar.subdivisionsValue;}),
'cropFisheyePano': Qt.binding(function(){ return root.cropFisheye;}),
'downscale': Qt.binding(function(){ return panoramaViewerToolbar.downscaleValue;}),
'isEditable': Qt.binding(function(){ return panoramaViewerToolbar.enableEdit;}),
'isHighlightable': Qt.binding(function(){ return panoramaViewerToolbar.enableHover;}),
'displayGridPano': Qt.binding(function(){ return panoramaViewerToolbar.displayGrid;}),
'mouseMultiplier': Qt.binding(function(){ return panoramaViewerToolbar.mouseSpeed;}),
'msfmData': Qt.binding(function() { return (msfmDataLoader.status === Loader.Ready
&& msfmDataLoader.item.status === 2) ? msfmDataLoader.item : null; }),
})
} else {
// Force the unload (instead of using Component.onCompleted to load it once and for all) is necessary since Qt 5.14
setSource("", {})
displayPanoramaViewer.checked = false;
}
}
}
// Simple QML Image Viewer (using Qt or qtOIIO to load images)
Loader {
id: qtImageViewerLoader
active: !floatImageViewerLoader.active && !panoramaViewerLoader.active
anchors.centerIn: parent
sourceComponent: Image {
id: qtImageViewer
asynchronous: true
smooth: false
fillMode: Image.PreserveAspectFit
autoTransform: true
onWidthChanged: if(status==Image.Ready) fit()
source: getImageFile()
onStatusChanged: {
// update cache source when image is loaded
if(status === Image.Ready)
qtImageViewerCache.source = source
}
// Image cache of the last loaded image
// Only visible when the main one is loading, to maintain a displayed image for smoother transitions
Image {
id: qtImageViewerCache
anchors.fill: parent
asynchronous: true
smooth: parent.smooth
fillMode: parent.fillMode
autoTransform: parent.autoTransform
visible: qtImageViewer.status === Image.Loading
}
}
}
property var image: {
if (floatImageViewerLoader.active)
floatImageViewerLoader.item
else if (panoramaViewerLoader.active)
panoramaViewerLoader.item
else
qtImageViewerLoader.item
}
width: image ? image.width : 1
height: image ? image.height : 1
scale: 1.0
// FeatureViewer: display view extracted feature points
// 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: {
var orientation = m.imgMetadata ? m.imgMetadata["Orientation"] : 0
switch(orientation) {
case "6": return 90;
case "8": return -90;
default: return 0;
}
}
x: (imgContainer.image && rotation === 90) ? imgContainer.image.paintedWidth : 0
y: (imgContainer.image && rotation === -90) ? imgContainer.image.paintedHeight : 0
onActiveChanged: {
if(active) {
// instantiate and initialize a FeaturesViewer component dynamically using Loader.setSource
setSource("FeaturesViewer.qml", {
'model': Qt.binding(function() { return activeNode ? activeNode.attribute("describerTypes").value : ""; }),
'features': Qt.binding(function() { return mfeaturesLoader.status === Loader.Ready ? mfeaturesLoader.item : null; }),
})
} else {
// Force the unload (instead of using Component.onCompleted to load it once and for all) is necessary since Qt 5.14
setSource("", {})
}
}
}
// FisheyeCircleViewer: display fisheye circle
// note: use a Loader to evaluate if a PanoramaInit node exist and displayFisheyeCircle checked at runtime
Loader {
anchors.centerIn: parent
property var activeNode: _reconstruction.activeNodes.get("PanoramaInit").node
active: (displayFisheyeCircleLoader.checked && activeNode)
// handle rotation/position based on available metadata
rotation: {
var orientation = m.imgMetadata ? m.imgMetadata["Orientation"] : 0
switch(orientation) {
case "6": return 90;
case "8": return -90;
default: return 0;
}
}
sourceComponent: CircleGizmo {
property bool useAuto: activeNode.attribute("estimateFisheyeCircle").value
readOnly: useAuto
visible: (!useAuto) || activeNode.isComputed
property real userFisheyeRadius: activeNode.attribute("fisheyeRadius").value
property variant fisheyeAutoParams: _reconstruction.getAutoFisheyeCircle(activeNode)
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(
activeNode.attribute("fisheyeCenterOffset"),
JSON.stringify([x, y])
);
}
}
onIncrementRadius: {
if(!useAuto)
{
_reconstruction.setAttribute(activeNode.attribute("fisheyeRadius"), activeNode.attribute("fisheyeRadius").value + radiusOffset)
}
}
}
}
// ColorCheckerViewer: display color checker detection results
// note: use a Loader to evaluate if a ColorCheckerDetection node exist and displayColorChecker checked at runtime
Loader {
id: colorCheckerViewerLoader
anchors.centerIn: parent
property var activeNode: _reconstruction.activeNodes.get("ColorCheckerDetection").node
active: (displayColorCheckerViewerLoader.checked && activeNode)
sourceComponent: ColorCheckerViewer {
visible: activeNode.isComputed && json !== undefined && imgContainer.image.status === Image.Ready
source: Filepath.stringToUrl(activeNode.attribute("outputData").value)
image: imgContainer.image
viewpoint: _reconstruction.selectedViewpoint
zoom: imgContainer.scale
updatePane: function() {
colorCheckerPane.colors = getColors();
}
}
}
}
ColumnLayout {
anchors.fill: parent
spacing: 0
FloatingPane {
id: imagePathToolbar
Layout.fillWidth: true
Layout.fillHeight: false
Layout.preferredHeight: childrenRect.height
visible: displayImagePathAction.checked
RowLayout {
width: parent.width
height: childrenRect.height
// selectable filepath to source image
TextField {
padding: 0
background: Item {}
horizontalAlignment: TextInput.AlignLeft
Layout.fillWidth: true
height: contentHeight
font.pointSize: 8
readOnly: true
selectByMouse: true
text: Filepath.urlToString(getImageFile())
}
// write which node is being displayed
Label {
id: displayedNodeName
text: root.displayedNode ? root.displayedNode.label : ""
font.pointSize: 8
horizontalAlignment: TextInput.AlignLeft
Layout.fillWidth: false
Layout.preferredWidth: contentWidth
height: contentHeight
}
}
}
Item {
id: imgPlaceholder
Layout.fillWidth: true
Layout.fillHeight: true
// Image Metadata overlay Pane
ImageMetadataView {
width: 350
anchors {
top: parent.top
right: parent.right
bottom: parent.bottom
}
visible: metadataCB.checked
// only load metadata model if visible
metadata: visible ? m.imgMetadata : {}
}
ColorCheckerPane {
id: colorCheckerPane
width: 250
height: 170
anchors {
top: parent.top
right: parent.right
}
visible: displayColorCheckerViewerLoader.checked && colorCheckerPane.colors !== null
}
Loader {
id: mfeaturesLoader
property bool isUsed: displayFeatures.checked
property var activeNode: root.aliceVisionPluginAvailable ? _reconstruction.activeNodes.get("FeatureExtraction").node : null
property bool isComputed: activeNode && activeNode.isComputed
active: false
onIsUsedChanged: {
active = (!active && isUsed && isComputed);
}
onIsComputedChanged: {
active = (!active && isUsed && isComputed);
}
onActiveNodeChanged: {
active = (!active && isUsed && isComputed);
}
onActiveChanged: {
if(active) {
// instantiate and initialize a MFeatures component dynamically using Loader.setSource
// so it can fail safely if the c++ plugin is not available
setSource("MFeatures.qml", {
'currentViewId': Qt.binding(function() { return _reconstruction.selectedViewId; }),
'describerTypes': Qt.binding(function() { return activeNode ? activeNode.attribute("describerTypes").value : {}; }),
'featureFolder': Qt.binding(function() { return activeNode ? Filepath.stringToUrl(activeNode.attribute("output").value) : ""; }),
'mtracks': Qt.binding(function() { return mtracksLoader.status === Loader.Ready ? mtracksLoader.item : null; }),
'msfmData': Qt.binding(function() { return msfmDataLoader.status === Loader.Ready ? msfmDataLoader.item : null; }),
})
} else {
// Force the unload (instead of using Component.onCompleted to load it once and for all) is necessary since Qt 5.14
setSource("", {})
}
}
}
Loader {
id: msfmDataLoader
property bool isUsed: displayFeatures.checked || displaySfmStatsView.checked || displaySfmDataGlobalStats.checked
|| displayPanoramaViewer.checked || displayLensDistortionViewer.checked
property var activeNode: {
if(! root.aliceVisionPluginAvailable){
return null
}
// For lens distortion viewer: use all nodes creating a sfmData file
var nodeType = displayLensDistortionViewer.checked ? 'sfmData' : 'sfm'
var sfmNode = _reconstruction.activeNodes.get(nodeType).node
if(sfmNode === null){
return null
}
if(displayPanoramaViewer.checked){
sfmNode = _reconstruction.activeNodes.get('SfMTransform').node
var previousNode = sfmNode.attribute("input").rootLinkParam.node
return previousNode
}
else{
return sfmNode
}
}
property bool isComputed: activeNode && activeNode.isComputed
property string filepath: {
var sfmValue = ""
if(!isComputed){
return Filepath.stringToUrl(sfmValue)
}
else{
if(activeNode.hasAttribute("output")){
sfmValue = activeNode.attribute("output").value
}
return Filepath.stringToUrl(sfmValue)
}
}
active: false
// It takes time to load tracks, so keep them looaded, if we may use it again.
// If we load another node, we can trash them (to eventually load the new node data).
onIsUsedChanged: {
if(!active && isUsed && isComputed)
{
active = true;
}
}
onIsComputedChanged: {
if(!isComputed)
{
active = false;
}
else if(!active && isUsed)
{
active = true;
}
}
onActiveNodeChanged: {
if(!isUsed)
{
active = false;
}
else if(!isComputed)
{
active = false;
}
else
{
active = true;
}
}
onActiveChanged: {
if(active) {
// instantiate and initialize a SfmStatsView component dynamically using Loader.setSource
// so it can fail safely if the c++ plugin is not available
setSource("MSfMData.qml", {
'sfmDataPath': Qt.binding(function() { return filepath; }),
})
} else {
// Force the unload (instead of using Component.onCompleted to load it once and for all) is necessary since Qt 5.14
setSource("", {})
}
}
}
Loader {
id: mtracksLoader
property bool isUsed: displayFeatures.checked || displaySfmStatsView.checked || displaySfmDataGlobalStats.checked || displayPanoramaViewer.checked
property var activeNode: root.aliceVisionPluginAvailable ? _reconstruction.activeNodes.get('FeatureMatching').node : null
property bool isComputed: activeNode && activeNode.isComputed
active: false
// It takes time to load tracks, so keep them looaded, if we may use it again.
// If we load another node, we can trash them (to eventually load the new node data).
onIsUsedChanged: {
if(!active && isUsed && isComputed) {
active = true;
}
}
onIsComputedChanged: {
if(!isComputed) {
active = false;
}
else if(!active && isUsed) {
active = true;
}
}
onActiveNodeChanged: {
if(!isUsed) {
active = false;
}
else if(!isComputed) {
active = false;
}
else {
active = true;
}
}
onActiveChanged: {
if(active) {
// instantiate and initialize a SfmStatsView component dynamically using Loader.setSource
// so it can fail safely if the c++ plugin is not available
setSource("MTracks.qml", {
'matchingFolder': Qt.binding(function() { return Filepath.stringToUrl(isComputed ? activeNode.attribute("output").value : ""); }),
})
} else {
// Force the unload (instead of using Component.onCompleted to load it once and for all) is necessary since Qt 5.14
setSource("", {})
}
}
}
Loader {
id: sfmStatsView
anchors.fill: parent
active: msfmDataLoader.status === Loader.Ready && displaySfmStatsView.checked
Component.onCompleted: {
// instantiate and initialize a SfmStatsView component dynamically using Loader.setSource
// so it can fail safely if the c++ plugin is not available
setSource("SfmStatsView.qml", {
'msfmData': Qt.binding(function() { return msfmDataLoader.item; }),
'viewId': Qt.binding(function() { return _reconstruction.selectedViewId; }),
})
}
}
Loader {
id: sfmGlobalStats
anchors.fill: parent
active: msfmDataLoader.status === Loader.Ready && displaySfmDataGlobalStats.checked
Component.onCompleted: {
// instantiate and initialize a SfmStatsView component dynamically using Loader.setSource
// so it can fail safely if the c++ plugin is not available
setSource("SfmGlobalStats.qml", {
'msfmData': Qt.binding(function() { return msfmDataLoader.item; }),
'mTracks': Qt.binding(function() { return mtracksLoader.item; }),
})
}
}
Loader {
id: featuresOverlay
anchors {
bottom: parent.bottom
left: parent.left
margins: 2
}
active: root.aliceVisionPluginAvailable && displayFeatures.checked && featuresViewerLoader.status === Loader.Ready
sourceComponent: FeaturesInfoOverlay {
featureExtractionNode: _reconstruction.activeNodes.get('FeatureExtraction').node
pluginStatus: featuresViewerLoader.status
featuresViewer: featuresViewerLoader.item
mfeatures: mfeaturesLoader.item
}
}
Loader {
id: ldrHdrCalibrationGraph
anchors.fill: parent
property var activeNode: _reconstruction.activeNodes.get('LdrToHdrCalibration').node
property var isEnabled: displayLdrHdrCalibrationGraph.checked && activeNode && activeNode.isComputed
// active: isEnabled
// Setting "active" from true to false creates a crash on linux with Qt 5.14.2.
// As a workaround, we clear the CameraResponseGraph with an empty node
// and hide the loader content.
visible: isEnabled
sourceComponent: CameraResponseGraph {
ldrHdrCalibrationNode: isEnabled ? activeNode : null
}
}
}
FloatingPane {
id: bottomToolbar
padding: 4
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
RowLayout {
anchors.fill: parent
// zoom label
MLabel {
text: ((imgContainer.image && (imgContainer.image.status === Image.Ready)) ? imgContainer.scale.toFixed(2) : "1.00") + "x"
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if(mouse.button & Qt.LeftButton) {
fit()
}
else if(mouse.button & Qt.RightButton) {
var menu = contextMenu.createObject(root);
var point = mapToItem(root, mouse.x, mouse.y)
menu.x = point.x;
menu.y = point.y;
menu.open()
}
}
}
ToolTip.text: "Zoom"
}
MaterialToolButton {
id: displayAlphaBackground
ToolTip.text: "Alpha Background"
text: MaterialIcons.texture
font.pointSize: 11
Layout.minimumWidth: 0
checkable: true
}
MaterialToolButton
{
id: displayHDR
ToolTip.text: "High-Dynamic-Range Image Viewer"
text: MaterialIcons.hdr_on
// larger font but smaller padding,
// so it is visually similar.
font.pointSize: 20
padding: 0
Layout.minimumWidth: 0
checkable: true
checked: false
enabled: root.aliceVisionPluginAvailable
onCheckedChanged : {
if(displayLensDistortionViewer.checked && checked){
displayLensDistortionViewer.checked = false;
}
}
}
MaterialToolButton {
id: displayLensDistortionViewer
property var activeNode: root.aliceVisionPluginAvailable ? _reconstruction.activeNodes.get('sfmData').node : null
property bool isComputed: {
if(!activeNode)
return false;
if(activeNode.isComputed)
{
return true;
}
var inputAttr = activeNode.attribute("input");
if(!inputAttr)
return false;
var inputAttrLink = inputAttr.rootLinkParam;
if(!inputAttrLink)
return false;
return inputAttrLink.node.isComputed;
}
ToolTip.text: "Lens Distortion Viewer" + (isComputed ? (": " + activeNode.label) : "")
text: MaterialIcons.panorama_horizontal
font.pointSize: 16
padding: 0
Layout.minimumWidth: 0
checkable: true
checked: false
enabled: activeNode && isComputed
onCheckedChanged : {
if((displayHDR.checked || displayPanoramaViewer.checked) && checked){
displayHDR.checked = false;
displayPanoramaViewer.checked = false;
}
}
}
MaterialToolButton {
id: displayPanoramaViewer
property var activeNode: root.aliceVisionPluginAvailable ? _reconstruction.activeNodes.get('SfMTransform').node : null
property bool isComputed: {
if(!activeNode)
return false;
if(activeNode.attribute("method").value !== "manual")
return false;
var inputAttr = activeNode.attribute("input");
if(!inputAttr)
return false;
var inputAttrLink = inputAttr.rootLinkParam;
if(!inputAttrLink)
return false;
return inputAttrLink.node.isComputed;
}
ToolTip.text: activeNode ? "Panorama Viewer " + activeNode.label : "Panorama Viewer"
text: MaterialIcons.panorama_sphere
font.pointSize: 16
padding: 0
Layout.minimumWidth: 0
checkable: true
checked: false
enabled: activeNode && isComputed
onCheckedChanged : {
if(displayLensDistortionViewer.checked && checked){
displayLensDistortionViewer.checked = false;
}
if(displayFisheyeCircleLoader.checked && checked){
displayFisheyeCircleLoader.checked = false;
}
}
onEnabledChanged : {
if(!enabled){
checked = false;
}
}
}
MaterialToolButton {
id: displayFeatures
ToolTip.text: "Display Features"
text: MaterialIcons.scatter_plot
font.pointSize: 11
Layout.minimumWidth: 0
checkable: true
checked: false
enabled: root.aliceVisionPluginAvailable && !displayPanoramaViewer.checked
onEnabledChanged : {
if(enabled == false) checked = false;
}
}
MaterialToolButton {
id: displayFisheyeCircleLoader
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: activeNode && activeNode.attribute("useFisheye").value && !displayPanoramaViewer.checked
visible: activeNode
}
MaterialToolButton {
id: displayColorCheckerViewerLoader
property var activeNode: _reconstruction.activeNodes.get('ColorCheckerDetection').node
ToolTip.text: "Display Color Checker: " + (activeNode ? activeNode.label : "No Node")
text: MaterialIcons.view_comfy //view_module grid_on gradient view_comfy border_all
font.pointSize: 11
Layout.minimumWidth: 0
checkable: true
enabled: activeNode && activeNode.isComputed && _reconstruction.selectedViewId != -1
checked: false
visible: activeNode
onEnabledChanged: {
if(enabled == false)
checked = false
}
onCheckedChanged: {
if(checked == true)
{
displaySfmDataGlobalStats.checked = false
displaySfmStatsView.checked = false
metadataCB.checked = false
}
}
}
MaterialToolButton {
id: displayLdrHdrCalibrationGraph
property var activeNode: _reconstruction.activeNodes.get("LdrToHdrCalibration").node
property bool isComputed: activeNode && activeNode.isComputed
ToolTip.text: "Display Camera Response Function: " + (activeNode ? activeNode.label : "No Node")
text: MaterialIcons.timeline
font.pointSize: 11
Layout.minimumWidth: 0
checkable: true
checked: false
enabled: activeNode && activeNode.isComputed
visible: activeNode
onIsComputedChanged: {
if(!isComputed)
checked = false
}
}
Label {
id: resolutionLabel
Layout.fillWidth: true
text: (imgContainer.image && imgContainer.image.sourceSize.width > 0) ? (imgContainer.image.sourceSize.width + "x" + imgContainer.image.sourceSize.height) : ""
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
ComboBox {
id: outputAttribute
clip: true
Layout.minimumWidth: 0
flat: true
property var names: []
property string name: names[currentIndex] ? names[currentIndex] : ""
model: displayedNode ? names.map(n => (n == "gallery") ? "Gallery" : displayedNode.attributes.get(n).label) : []
enabled: count > 0
FontMetrics {
id: fontMetrics
}
Layout.preferredWidth: model.reduce((acc, label) => Math.max(acc, fontMetrics.boundingRect(label).width), 0) + 3.0*Qt.application.font.pixelSize
}
MaterialToolButton {
property var activeNode: root.oiioPluginAvailable ? _reconstruction.activeNodes.get('allDepthMap').node : null
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
onClicked: {
root.viewIn3D(root.getFileAttributePath(activeNode, "depth", _reconstruction.selectedViewId));
}
}
MaterialToolButton {
id: displaySfmStatsView
property var activeNode: root.aliceVisionPluginAvailable ? _reconstruction.activeNodes.get('sfm').node : null
font.family: MaterialIcons.fontFamily
text: MaterialIcons.assessment
ToolTip.text: "StructureFromMotion Statistics"
ToolTip.visible: hovered
font.pointSize: 14
padding: 2
smooth: false
flat: true
checkable: enabled
enabled: activeNode && activeNode.isComputed && _reconstruction.selectedViewId >= 0
onCheckedChanged: {
if(checked == true) {
displaySfmDataGlobalStats.checked = false
metadataCB.checked = false
displayColorCheckerViewerLoader.checked = false
}
}
}
MaterialToolButton {
id: displaySfmDataGlobalStats
property var activeNode: root.aliceVisionPluginAvailable ? _reconstruction.activeNodes.get('sfm').node : null
font.family: MaterialIcons.fontFamily
text: MaterialIcons.language
ToolTip.text: "StructureFromMotion Global Statistics"
ToolTip.visible: hovered
font.pointSize: 14
padding: 2
smooth: false
flat: true
checkable: enabled
enabled: activeNode && activeNode.isComputed
onCheckedChanged: {
if(checked == true) {
displaySfmStatsView.checked = false
metadataCB.checked = false
displayColorCheckerViewerLoader.checked = false
}
}
}
MaterialToolButton {
id: metadataCB
font.family: MaterialIcons.fontFamily
text: MaterialIcons.info_outline
ToolTip.text: "Image Metadata"
ToolTip.visible: hovered
font.pointSize: 14
padding: 2
smooth: false
flat: true
checkable: enabled
onCheckedChanged: {
if(checked == true)
{
displaySfmDataGlobalStats.checked = false
displaySfmStatsView.checked = false
displayColorCheckerViewerLoader.checked = false
}
}
}
}
}
}
}
}
// Busy indicator
BusyIndicator {
anchors.centerIn: parent
// running property binding seems broken, only dynamic binding assignment works
Component.onCompleted: {
running = Qt.binding(function() {
return (root.usePanoramaViewer === true && imgContainer.image && imgContainer.image.allImagesLoaded === false)
|| (imgContainer.image && imgContainer.image.status === Image.Loading)
})
}
// disable the visibility when unused to avoid stealing the mouseEvent to the image color picker
visible: running
onVisibleChanged: {
if (panoramaViewerLoader.active)
fit();
}
}
}