From 663a5d679dc0c6e4fe0b8079463ff3fa57ddaf9c Mon Sep 17 00:00:00 2001 From: Yann Lanthony Date: Mon, 21 Jan 2019 10:35:29 +0100 Subject: [PATCH] [ui] ImageGallery: introduce IntrinsicsIndicator New IntrinsicsIndicator component that displays the initialization mode of each Viewpoint's intrinsic with explanatory tooltip. --- .../ui/qml/ImageGallery/ImageDelegate.qml | 2 +- meshroom/ui/qml/ImageGallery/ImageGallery.qml | 39 ++++---- .../qml/ImageGallery/IntrinsicsIndicator.qml | 94 +++++++++++++++++++ meshroom/ui/qml/Utils/Colors.qml | 1 + meshroom/ui/reconstruction.py | 32 +++++++ 5 files changed, 144 insertions(+), 24 deletions(-) create mode 100644 meshroom/ui/qml/ImageGallery/IntrinsicsIndicator.qml diff --git a/meshroom/ui/qml/ImageGallery/ImageDelegate.qml b/meshroom/ui/qml/ImageGallery/ImageDelegate.qml index d29420ec..367ca097 100644 --- a/meshroom/ui/qml/ImageGallery/ImageDelegate.qml +++ b/meshroom/ui/qml/ImageGallery/ImageDelegate.qml @@ -26,7 +26,7 @@ Item { id: _viewpoint property url source: viewpoint ? Filepath.stringToUrl(viewpoint.get("path").value) : '' property string metadataStr: viewpoint ? viewpoint.get("metadata").value : '' - property var metadata: metadataStr ? JSON.parse(viewpoint.get("metadata").value) : null + property var metadata: metadataStr ? JSON.parse(viewpoint.get("metadata").value) : {} } MouseArea { diff --git a/meshroom/ui/qml/ImageGallery/ImageGallery.qml b/meshroom/ui/qml/ImageGallery/ImageGallery.qml index 8813818f..62f1b92c 100644 --- a/meshroom/ui/qml/ImageGallery/ImageGallery.qml +++ b/meshroom/ui/qml/ImageGallery/ImageGallery.qml @@ -80,6 +80,7 @@ Panel { } delegate: ImageDelegate { + id: imageDelegate viewpoint: object.value width: grid.cellWidth @@ -108,37 +109,29 @@ Panel { onRemoveRequest: sendRemoveRequest() Keys.onDeletePressed: sendRemoveRequest() - Row { + RowLayout { anchors.top: parent.top + anchors.left: parent.left anchors.right: parent.right - anchors.margins: 4 + anchors.margins: 2 spacing: 2 property bool valid: Qt.isQtObject(object) // object can be evaluated to null at some point during creation/deletion - property bool noMetadata: valid && !_reconstruction.hasMetadata(model.object) - property bool noIntrinsic: valid && !_reconstruction.hasValidIntrinsic(model.object) + property string intrinsicInitMode: valid ? _reconstruction.getIntrinsicInitMode(object) : "" property bool inViews: valid && _reconstruction.sfmReport && _reconstruction.isInViews(object) - // Missing metadata indicator - Loader { - active: parent.noMetadata - visible: active - sourceComponent: MaterialLabel { - text: MaterialIcons.info_outline - color: "#FF9800" - ToolTip.text: "No Metadata" - } - } - // Unknown camera instrinsics indicator - Loader { - active: parent.noIntrinsic - visible: active - sourceComponent: MaterialLabel { - text: MaterialIcons.camera - color: "#FF9800" - ToolTip.text: "No Camera Instrinsic Parameters (missing Metadata?)" - } + + // Camera Initialization indicator + IntrinsicsIndicator { + intrinsicInitMode: parent.intrinsicInitMode + metadata: imageDelegate.metadata + font.pointSize: 10 + padding: 2 + background: Rectangle { color: Colors.sysPalette.window; opacity: 0.6 } } + + Item { Layout.fillWidth: true } + // Reconstruction status indicator Loader { active: parent.inViews diff --git a/meshroom/ui/qml/ImageGallery/IntrinsicsIndicator.qml b/meshroom/ui/qml/ImageGallery/IntrinsicsIndicator.qml new file mode 100644 index 00000000..17e28d78 --- /dev/null +++ b/meshroom/ui/qml/ImageGallery/IntrinsicsIndicator.qml @@ -0,0 +1,94 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.4 +import MaterialIcons 2.2 +import Utils 1.0 + + +/** + * Display camera initialization status and the value of metadata + * that take part in this process. + */ +MaterialLabel { + id: root + + property string intrinsicInitMode + property var metadata: ({}) + + // access useful metadata + readonly property string make: metadata["Make"] + readonly property string model: metadata["Model"] + readonly property string focalLength: metadata["Exif:FocalLength"] + readonly property string focalLength35: metadata["Exif:FocalLengthIn35mmFilm"] + readonly property string bodySerialNumber: metadata["Exif:BodySerialNumber"] + readonly property string lensSerialNumber: metadata["Exif:LensSerialNumber"] + readonly property string sensorWidth: metadata["AliceVision:SensorWidth"] + readonly property string sensorWidthEstimation: metadata["AliceVision:SensorWidthEstimation"] + + property string statusText: "" + property string detailsText: "" + property string helperText: "" + + text: MaterialIcons.camera + + function metaStr(value) { + return value || "undefined" + } + + ToolTip.text: "Camera Intrinsics: " + statusText + "
" + + (detailsText ? detailsText + "
" : "") + + (helperText ? helperText + "
" : "") + + "
" + + "[Metadata]
" + + " - Make: " + metaStr(make) + "
" + + " - Model: " + metaStr(model) + "
" + + " - FocalLength: " + metaStr(focalLength) + "
" + + ((focalLength && sensorWidth) ? "" : " - FocalLengthIn35mmFilm: " + metaStr(focalLength35) + "
") + + " - SensorWidth: " + metaStr(sensorWidth) + (sensorWidthEstimation ? " (estimation: "+ sensorWidthEstimation + ")" : "") + + ((bodySerialNumber || lensSerialNumber) ? "" : "

Warning: SerialNumber metadata is missing.
Images from different devices might incorrectly share the same camera internal settings.") + + + state: intrinsicInitMode ? intrinsicInitMode : "unknown" + + states: [ + State { + name: "calibrated" + PropertyChanges { + target: root + color: Colors.green + statusText: "Calibrated" + detailsText: "Focal Length has been initialized externally." + } + }, + State { + name: "estimated" + PropertyChanges { + target: root + statusText: sensorWidth ? "Estimated" : "Approximated" + color: sensorWidth ? Colors.green : Colors.yellow + detailsText: "Focal Length was estimated from Metadata" + (sensorWidth ? " and Sensor Database." : " only.") + helperText: !sensorWidth ? "Add your Camera Model to the Sensor Database for more accurate results." : "" + } + }, + State { + name: "unknown" + PropertyChanges { + target: root + color: focalLength ? Colors.orange : Colors.red + statusText: "Unknown" + detailsText: "Focal Length could not be determined from metadata.
" + + "The default Field of View value was used as a fallback, which may lead to inaccurate result or failure." + helperText: "Check for missing Image metadata" + + (make && model && !sensorWidth ? " and/or add your Camera Model to the Sensor Database." : ".") + } + }, + State { + // fallback status when initialization mode is unset + name: "none" + PropertyChanges { + target: root + visible: false + } + } + + ] +} diff --git a/meshroom/ui/qml/Utils/Colors.qml b/meshroom/ui/qml/Utils/Colors.qml index 15e79b9f..4dea34be 100644 --- a/meshroom/ui/qml/Utils/Colors.qml +++ b/meshroom/ui/qml/Utils/Colors.qml @@ -11,6 +11,7 @@ QtObject { readonly property color green: "#4CAF50" readonly property color orange: "#FF9800" + readonly property color yellow: "#FFEB3B" readonly property color red: "#F44336" readonly property color blue: "#03A9F4" } diff --git a/meshroom/ui/reconstruction.py b/meshroom/ui/reconstruction.py index 1c682ca3..84e4e225 100755 --- a/meshroom/ui/reconstruction.py +++ b/meshroom/ui/reconstruction.py @@ -514,6 +514,38 @@ class Reconstruction(UIGraph): allIntrinsicIds = [i.intrinsicId.value for i in self._cameraInit.intrinsics.value] return viewpoint.intrinsicId.value in allIntrinsicIds + @Slot(QObject, result=QObject) + def getIntrinsic(self, viewpoint): + """ + Get the intrinsic attribute associated to 'viewpoint' based on its intrinsicId. + + Args: + viewpoint (Attribute): the Viewpoint to consider. + Returns: + Attribute: the Viewpoint's corresponding intrinsic or None if not found. + """ + return next((i for i in self._cameraInit.intrinsics.value if i.intrinsicId.value == viewpoint.intrinsicId.value) + , None) + + @Slot(QObject, result=str) + def getIntrinsicInitMode(self, viewpoint): + """ + Get the initialization mode for the intrinsic associated to 'viewpoint'. + + Args: + viewpoint (Attribute): the Viewpoint to consider. + Returns: + str: the initialization mode of the Viewpoint's intrinsic or an empty string if none. + """ + intrinsic = self.getIntrinsic(viewpoint) + if not intrinsic: + return "" + try: + return intrinsic.initializationMode.value + except AttributeError: + # handle older versions that did not have this attribute + return "" + @Slot(QObject, result=bool) def hasMetadata(self, viewpoint): # Should be greater than 2 to avoid the particular case of ""