Merge branch 'dev/ImageGalleryFilter' of https://github.com/N0Ls/meshroom into N0Ls-dev/ImageGalleryFilter

This commit is contained in:
Fabien Castan 2021-09-01 11:03:55 +02:00
commit af4dae9a77
7 changed files with 549 additions and 48 deletions

View file

@ -71,25 +71,30 @@ class Attribute(BaseObject):
def root(self): def root(self):
return self._root() if self._root else None return self._root() if self._root else None
def absoluteName(self):
return '{}.{}.{}'.format(self.node.graph.name, self.node.name, self._name)
def getFullName(self):
""" Name inside the Graph: nodeName.name """
if isinstance(self.root, ListAttribute):
return '{}[{}]'.format(self.root.getFullName(), self.root.index(self))
elif isinstance(self.root, GroupAttribute):
return '{}.{}'.format(self.root.getFullName(), self._name)
return '{}.{}'.format(self.node.name, self._name)
def asLinkExpr(self):
""" Return link expression for this Attribute """
return "{" + self.getFullName() + "}"
def getName(self): def getName(self):
""" Attribute name """ """ Attribute name """
return self._name return self._name
def getFullName(self):
""" Name inside the Graph: groupName.name """
if isinstance(self.root, ListAttribute):
return '{}[{}]'.format(self.root.getFullName(), self.root.index(self))
elif isinstance(self.root, GroupAttribute):
return '{}.{}'.format(self.root.getFullName(), self.getName())
return self.getName()
def getFullNameToNode(self):
""" Name inside the Graph: nodeName.groupName.name """
return '{}.{}'.format(self.node.name, self.getFullName())
def getFullNameToGraph(self):
""" Name inside the Graph: graphName.nodeName.groupName.name """
return '{}.{}'.format(self.node.graph.name, self.getFullNameToNode())
def asLinkExpr(self):
""" Return link expression for this Attribute """
return "{" + self.getFullNameToNode() + "}"
def getType(self): def getType(self):
return self.attributeDesc.__class__.__name__ return self.attributeDesc.__class__.__name__
@ -102,6 +107,22 @@ class Attribute(BaseObject):
def getLabel(self): def getLabel(self):
return self._label return self._label
def getFullLabel(self):
""" Full Label includes the name of all parent groups, e.g. 'groupLabel subGroupLabel Label' """
if isinstance(self.root, ListAttribute):
return self.root.getFullLabel()
elif isinstance(self.root, GroupAttribute):
return '{} {}'.format(self.root.getFullLabel(), self.getLabel())
return self.getLabel()
def getFullLabelToNode(self):
""" Label inside the Graph: nodeLabel groupLabel Label """
return '{} {}'.format(self.node.label, self.getFullLabel())
def getFullLabelToGraph(self):
""" Label inside the Graph: graphName nodeLabel groupLabel Label """
return '{} {}'.format(self.node.graph.name, self.getFullLabelToNode())
def getEnabled(self): def getEnabled(self):
if isinstance(self.desc.enabled, types.FunctionType): if isinstance(self.desc.enabled, types.FunctionType):
try: try:
@ -265,7 +286,12 @@ class Attribute(BaseObject):
name = Property(str, getName, constant=True) name = Property(str, getName, constant=True)
fullName = Property(str, getFullName, constant=True) fullName = Property(str, getFullName, constant=True)
fullNameToNode = Property(str, getFullNameToNode, constant=True)
fullNameToGraph = Property(str, getFullNameToGraph, constant=True)
label = Property(str, getLabel, constant=True) label = Property(str, getLabel, constant=True)
fullLabel = Property(str, getFullLabel, constant=True)
fullLabelToNode = Property(str, getFullLabelToNode, constant=True)
fullLabelToGraph = Property(str, getFullLabelToGraph, constant=True)
type = Property(str, getType, constant=True) type = Property(str, getType, constant=True)
baseType = Property(str, getType, constant=True) baseType = Property(str, getType, constant=True)
isReadOnly = Property(bool, _isReadOnly, constant=True) isReadOnly = Property(bool, _isReadOnly, constant=True)

View file

@ -415,7 +415,7 @@ class Graph(BaseObject):
def removeNode(self, nodeName): def removeNode(self, nodeName):
""" """
Remove the node identified by 'nodeName' from the graph Remove the node identified by 'nodeName' from the graph
and return in and out edges removed by this operation in two dicts {dstAttr.getFullName(), srcAttr.getFullName()} and return in and out edges removed by this operation in two dicts {dstAttr.getFullNameToNode(), srcAttr.getFullNameToNode()}
""" """
node = self.node(nodeName) node = self.node(nodeName)
inEdges = {} inEdges = {}
@ -425,10 +425,10 @@ class Graph(BaseObject):
with GraphModification(self): with GraphModification(self):
for edge in self.nodeOutEdges(node): for edge in self.nodeOutEdges(node):
self.removeEdge(edge.dst) self.removeEdge(edge.dst)
outEdges[edge.dst.getFullName()] = edge.src.getFullName() outEdges[edge.dst.getFullNameToNode()] = edge.src.getFullNameToNode()
for edge in self.nodeInEdges(node): for edge in self.nodeInEdges(node):
self.removeEdge(edge.dst) self.removeEdge(edge.dst)
inEdges[edge.dst.getFullName()] = edge.src.getFullName() inEdges[edge.dst.getFullNameToNode()] = edge.src.getFullNameToNode()
node.alive = False node.alive = False
self._nodes.remove(node) self._nodes.remove(node)
@ -583,7 +583,7 @@ class Graph(BaseObject):
if srcAttr.node.graph != self or dstAttr.node.graph != self: if srcAttr.node.graph != self or dstAttr.node.graph != self:
raise RuntimeError('The attributes of the edge should be part of a common graph.') raise RuntimeError('The attributes of the edge should be part of a common graph.')
if dstAttr in self.edges.keys(): if dstAttr in self.edges.keys():
raise RuntimeError('Destination attribute "{}" is already connected.'.format(dstAttr.getFullName())) raise RuntimeError('Destination attribute "{}" is already connected.'.format(dstAttr.getFullNameToNode()))
edge = Edge(srcAttr, dstAttr) edge = Edge(srcAttr, dstAttr)
self.edges.add(edge) self.edges.add(edge)
self.markNodesDirty(dstAttr.node) self.markNodesDirty(dstAttr.node)
@ -600,7 +600,7 @@ class Graph(BaseObject):
@changeTopology @changeTopology
def removeEdge(self, dstAttr): def removeEdge(self, dstAttr):
if dstAttr not in self.edges.keys(): if dstAttr not in self.edges.keys():
raise RuntimeError('Attribute "{}" is not connected'.format(dstAttr.getFullName())) raise RuntimeError('Attribute "{}" is not connected'.format(dstAttr.getFullNameToNode()))
edge = self.edges.pop(dstAttr) edge = self.edges.pop(dstAttr)
self.markNodesDirty(dstAttr.node) self.markNodesDirty(dstAttr.node)
dstAttr.valueChanged.emit() dstAttr.valueChanged.emit()
@ -1202,4 +1202,3 @@ def loadGraph(filepath):
graph.load(filepath) graph.load(filepath)
graph.update() graph.update()
return graph return graph

View file

@ -197,10 +197,10 @@ class DuplicateNodesCommand(GraphCommand):
class SetAttributeCommand(GraphCommand): class SetAttributeCommand(GraphCommand):
def __init__(self, graph, attribute, value, parent=None): def __init__(self, graph, attribute, value, parent=None):
super(SetAttributeCommand, self).__init__(graph, parent) super(SetAttributeCommand, self).__init__(graph, parent)
self.attrName = attribute.getFullName() self.attrName = attribute.getFullNameToNode()
self.value = value self.value = value
self.oldValue = attribute.getExportValue() self.oldValue = attribute.getExportValue()
self.setText("Set Attribute '{}'".format(attribute.getFullName())) self.setText("Set Attribute '{}'".format(attribute.getFullNameToNode()))
def redoImpl(self): def redoImpl(self):
if self.value == self.oldValue: if self.value == self.oldValue:
@ -215,8 +215,8 @@ class SetAttributeCommand(GraphCommand):
class AddEdgeCommand(GraphCommand): class AddEdgeCommand(GraphCommand):
def __init__(self, graph, src, dst, parent=None): def __init__(self, graph, src, dst, parent=None):
super(AddEdgeCommand, self).__init__(graph, parent) super(AddEdgeCommand, self).__init__(graph, parent)
self.srcAttr = src.getFullName() self.srcAttr = src.getFullNameToNode()
self.dstAttr = dst.getFullName() self.dstAttr = dst.getFullNameToNode()
self.setText("Connect '{}'->'{}'".format(self.srcAttr, self.dstAttr)) self.setText("Connect '{}'->'{}'".format(self.srcAttr, self.dstAttr))
if src.baseType != dst.baseType: if src.baseType != dst.baseType:
@ -233,8 +233,8 @@ class AddEdgeCommand(GraphCommand):
class RemoveEdgeCommand(GraphCommand): class RemoveEdgeCommand(GraphCommand):
def __init__(self, graph, edge, parent=None): def __init__(self, graph, edge, parent=None):
super(RemoveEdgeCommand, self).__init__(graph, parent) super(RemoveEdgeCommand, self).__init__(graph, parent)
self.srcAttr = edge.src.getFullName() self.srcAttr = edge.src.getFullNameToNode()
self.dstAttr = edge.dst.getFullName() self.dstAttr = edge.dst.getFullNameToNode()
self.setText("Disconnect '{}'->'{}'".format(self.srcAttr, self.dstAttr)) self.setText("Disconnect '{}'->'{}'".format(self.srcAttr, self.dstAttr))
def redoImpl(self): def redoImpl(self):
@ -250,7 +250,7 @@ class ListAttributeAppendCommand(GraphCommand):
def __init__(self, graph, listAttribute, value, parent=None): def __init__(self, graph, listAttribute, value, parent=None):
super(ListAttributeAppendCommand, self).__init__(graph, parent) super(ListAttributeAppendCommand, self).__init__(graph, parent)
assert isinstance(listAttribute, ListAttribute) assert isinstance(listAttribute, ListAttribute)
self.attrName = listAttribute.getFullName() self.attrName = listAttribute.getFullNameToNode()
self.index = None self.index = None
self.count = 1 self.count = 1
self.value = value if value else None self.value = value if value else None
@ -276,10 +276,10 @@ class ListAttributeRemoveCommand(GraphCommand):
super(ListAttributeRemoveCommand, self).__init__(graph, parent) super(ListAttributeRemoveCommand, self).__init__(graph, parent)
listAttribute = attribute.root listAttribute = attribute.root
assert isinstance(listAttribute, ListAttribute) assert isinstance(listAttribute, ListAttribute)
self.listAttrName = listAttribute.getFullName() self.listAttrName = listAttribute.getFullNameToNode()
self.index = listAttribute.index(attribute) self.index = listAttribute.index(attribute)
self.value = attribute.getExportValue() self.value = attribute.getExportValue()
self.setText("Remove {}".format(attribute.getFullName())) self.setText("Remove {}".format(attribute.getFullNameToNode()))
def redoImpl(self): def redoImpl(self):
listAttribute = self.graph.attribute(self.listAttrName) listAttribute = self.graph.attribute(self.listAttrName)

View file

@ -618,14 +618,14 @@ class UIGraph(QObject):
@Slot(Attribute, Attribute) @Slot(Attribute, Attribute)
def addEdge(self, src, dst): def addEdge(self, src, dst):
if isinstance(dst, ListAttribute) and not isinstance(src, ListAttribute): if isinstance(dst, ListAttribute) and not isinstance(src, ListAttribute):
with self.groupedGraphModification("Insert and Add Edge on {}".format(dst.getFullName())): with self.groupedGraphModification("Insert and Add Edge on {}".format(dst.getFullNameToNode())):
self.appendAttribute(dst) self.appendAttribute(dst)
self._addEdge(src, dst.at(-1)) self._addEdge(src, dst.at(-1))
else: else:
self._addEdge(src, dst) self._addEdge(src, dst)
def _addEdge(self, src, dst): def _addEdge(self, src, dst):
with self.groupedGraphModification("Connect '{}'->'{}'".format(src.getFullName(), dst.getFullName())): with self.groupedGraphModification("Connect '{}'->'{}'".format(src.getFullNameToNode(), dst.getFullNameToNode())):
if dst in self._graph.edges.keys(): if dst in self._graph.edges.keys():
self.removeEdge(self._graph.edge(dst)) self.removeEdge(self._graph.edge(dst))
self.push(commands.AddEdgeCommand(self._graph, src, dst)) self.push(commands.AddEdgeCommand(self._graph, src, dst))
@ -633,7 +633,7 @@ class UIGraph(QObject):
@Slot(Edge) @Slot(Edge)
def removeEdge(self, edge): def removeEdge(self, edge):
if isinstance(edge.dst.root, ListAttribute): if isinstance(edge.dst.root, ListAttribute):
with self.groupedGraphModification("Remove Edge and Delete {}".format(edge.dst.getFullName())): with self.groupedGraphModification("Remove Edge and Delete {}".format(edge.dst.getFullNameToNode())):
self.push(commands.RemoveEdgeCommand(self._graph, edge)) self.push(commands.RemoveEdgeCommand(self._graph, edge))
self.removeAttribute(edge.dst) self.removeAttribute(edge.dst)
else: else:

View file

@ -1,8 +1,9 @@
import QtQuick 2.7 import QtQuick 2.14
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import MaterialIcons 2.2 import MaterialIcons 2.2
import QtQml.Models 2.2 import QtQml.Models 2.2
import Qt.labs.qmlmodels 1.0
import Controls 1.0 import Controls 1.0
import Utils 1.0 import Utils 1.0
@ -26,6 +27,9 @@ Panel {
property int currentIndex: 0 property int currentIndex: 0
property bool readOnly: false property bool readOnly: false
property string filter: ""
property bool filterValue: true
signal removeImageRequest(var attribute) signal removeImageRequest(var attribute)
signal filesDropped(var drop, var augmentSfm) signal filesDropped(var drop, var augmentSfm)
@ -40,9 +44,66 @@ Panel {
id: m id: m
property variant currentCameraInit: _reconstruction.tempCameraInit ? _reconstruction.tempCameraInit : root.cameraInit property variant currentCameraInit: _reconstruction.tempCameraInit ? _reconstruction.tempCameraInit : root.cameraInit
property variant viewpoints: currentCameraInit ? currentCameraInit.attribute('viewpoints').value : undefined property variant viewpoints: currentCameraInit ? currentCameraInit.attribute('viewpoints').value : undefined
property variant intrinsics: currentCameraInit ? currentCameraInit.attribute('intrinsics').value : undefined
property bool readOnly: root.readOnly || displayHDR.checked property bool readOnly: root.readOnly || displayHDR.checked
} }
property variant parsedIntrinsic
property int numberOfIntrinsics : m.intrinsics ? m.intrinsics.count : 0
onNumberOfIntrinsicsChanged: {
parseIntr()
}
function populate_model()
{
intrinsicModel.clear()
for (var intr in parsedIntrinsic) {
intrinsicModel.appendRow(parsedIntrinsic[intr])
}
}
function parseIntr(){
parsedIntrinsic = []
if(!m.intrinsics)
{
return
}
//Loop through all intrinsics
for(var i = 0; i < m.intrinsics.count; ++i){
var intrinsic = {}
//Loop through all attributes
for(var j=0; j < m.intrinsics.at(i).value.count; ++j){
var currentAttribute = m.intrinsics.at(i).value.at(j)
if(currentAttribute.type === "GroupAttribute"){
for(var k=0; k < currentAttribute.value.count; ++k){
intrinsic[currentAttribute.name + "." + currentAttribute.value.at(k).name] = currentAttribute.value.at(k)
}
}
else if(currentAttribute.type === "ListAttribute"){
// not needed for now
}
else{
intrinsic[currentAttribute.name] = currentAttribute
}
}
// Table Model needs to contain an entry for each column.
// In case of old file formats, some intrinsic keys that we display may not exist in the model.
// So, here we create an empty entry to enforce that the key exists in the model.
for(var n = 0; n < intrinsicModel.columnNames.length; ++n)
{
var name = intrinsicModel.columnNames[n]
if(!(name in intrinsic)) {
intrinsic[name] = {}
}
}
parsedIntrinsic[i] = intrinsic
}
populate_model()
}
headerBar: RowLayout { headerBar: RowLayout {
MaterialToolButton { MaterialToolButton {
text: MaterialIcons.more_vert text: MaterialIcons.more_vert
@ -91,7 +152,14 @@ Panel {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
ScrollBar.vertical: ScrollBar { minimumSize: 0.05 } interactive: !intrinsicsFilterButton.checked
ScrollBar.vertical: ScrollBar {
minimumSize: 0.05
active : !intrinsicsFilterButton.checked
visible: !intrinsicsFilterButton.checked
}
focus: true focus: true
clip: true clip: true
@ -115,14 +183,16 @@ Panel {
model: m.viewpoints model: m.viewpoints
sortRole: "path" sortRole: "path"
// TODO: provide filtering on reconstruction status // TODO: provide filtering on reconstruction status
// filterRole: _reconstruction.sfmReport ? "reconstructed" : "" filterRole: _reconstruction.sfmReport ? root.filter : ""
// filterValue: true / false filterValue: root.filterValue
// in modelData: // in modelData:
// if(filterRole == roleName) // if(filterRole == roleName)
// return _reconstruction.isReconstructed(item.model.object) // return _reconstruction.isReconstructed(item.model.object)
// override modelData to return basename of viewpoint's path for sorting // override modelData to return basename of viewpoint's path for sorting
function modelData(item, roleName) { function modelData(item, roleName) {
if(filterRole == roleName)
return _reconstruction.isReconstructed(item.model.object)
var value = item.model.object.childAttribute(roleName).value var value = item.model.object.childAttribute(roleName).value
if(roleName == sortRole) if(roleName == sortRole)
return Filepath.basename(value) return Filepath.basename(value)
@ -138,6 +208,7 @@ Panel {
height: grid.cellHeight height: grid.cellHeight
readOnly: m.readOnly readOnly: m.readOnly
displayViewId: displayViewIdsAction.checked displayViewId: displayViewIdsAction.checked
visible: !intrinsicsFilterButton.checked
isCurrentItem: GridView.isCurrentItem isCurrentItem: GridView.isCurrentItem
@ -233,8 +304,9 @@ Panel {
// Explanatory placeholder when no image has been added yet // Explanatory placeholder when no image has been added yet
Column { Column {
id: dropImagePlaceholder
anchors.centerIn: parent anchors.centerIn: parent
visible: grid.model.count == 0 visible: (m.viewpoints ? m.viewpoints.count == 0 : true) && !intrinsicsFilterButton.checked
spacing: 4 spacing: 4
Label { Label {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@ -246,11 +318,106 @@ Panel {
text: "Drop Image Files / Folders" text: "Drop Image Files / Folders"
} }
} }
// Placeholder when the filtered images list is empty
Column {
id: noImageImagePlaceholder
anchors.centerIn: parent
visible: (m.viewpoints ? m.viewpoints.count != 0 : false) && !dropImagePlaceholder.visible && grid.model.count == 0 && !intrinsicsFilterButton.checked
spacing: 4
Label {
anchors.horizontalCenter: parent.horizontalCenter
text: MaterialIcons.filter_none
font.pointSize: 24
font.family: MaterialIcons.fontFamily
}
Label {
text: "No images in this filtered view"
}
}
RowLayout{
anchors.fill: parent
Layout.fillWidth: true
Layout.fillHeight: true
TapHandler {
acceptedButtons: Qt.LeftButton | Qt.RightButton
onTapped: {
intrinsicTable.focus = false
}
}
TableView{
id : intrinsicTable
visible: intrinsicsFilterButton.checked
Layout.fillHeight: true
Layout.fillWidth: true
interactive : !focus
boundsMovement : Flickable.StopAtBounds
//Provide width for column
//Note no size provided for the last column (bool comp) so it uses its automated size
columnWidthProvider: function (column) { return intrinsicModel.columnWidths[column] }
model: intrinsicModel
delegate: IntrinsicDisplayDelegate{}
ScrollBar.horizontal: ScrollBar { id: sb }
ScrollBar.vertical : ScrollBar { id: sbv }
}
TableModel{
id : intrinsicModel
// Hardcoded default width per column
property var columnWidths: [105, 75, 75, 75, 125, 60, 60, 45, 45, 200, 60, 60]
property var columnNames: [
"intrinsicId",
"pxInitialFocalLength",
"pxFocalLength.x",
"pxFocalLength.y",
"type",
"width",
"height",
"sensorWidth",
"sensorHeight",
"serialNumber",
"principalPoint.x",
"principalPoint.y",
"locked"]
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[0]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[1]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[2]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[3]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[4]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[5]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[6]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[7]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[8]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[9]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[10]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[11]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[12]]} }
//https://doc.qt.io/qt-5/qml-qt-labs-qmlmodels-tablemodel.html#appendRow-method
}
//CODE FOR HEADERS
//UNCOMMENT WHEN COMPATIBLE WITH THE RIGHT QT VERSION
// HorizontalHeaderView {
// id: horizontalHeader
// syncView: tableView
// anchors.left: tableView.left
// }
}
DropArea { DropArea {
id: dropArea id: dropArea
anchors.fill: parent anchors.fill: parent
enabled: !m.readOnly enabled: !m.readOnly && !intrinsicsFilterButton.checked
keys: ["text/uri-list"] keys: ["text/uri-list"]
// TODO: onEntered: call specific method to filter files based on extension // TODO: onEntered: call specific method to filter files based on extension
onDropped: { onDropped: {
@ -344,22 +511,123 @@ Panel {
footerContent: RowLayout { footerContent: RowLayout {
// Images count // Images count
MaterialToolLabel { id: footer
function resetButtons(){
inputImagesFilterButton.checked = false
estimatedCamerasFilterButton.checked = false
nonEstimatedCamerasFilterButton.checked = false
}
MaterialToolLabelButton {
id : inputImagesFilterButton
Layout.minimumWidth: childrenRect.width Layout.minimumWidth: childrenRect.width
ToolTip.text: grid.model.count + " Input Images" ToolTip.text: grid.model.count + " Input Images"
iconText: MaterialIcons.image iconText: MaterialIcons.image
label: grid.model.count.toString() label: (m.viewpoints ? m.viewpoints.count : 0)
// enabled: grid.model.count > 0 padding: 3
// margin: 4
checkable: true
checked: true
onCheckedChanged:{
if(checked) {
root.filter = ""
root.filterValue = true
estimatedCamerasFilterButton.checked = false
nonEstimatedCamerasFilterButton.checked = false
intrinsicsFilterButton.checked = false;
} }
// cameras count }
MaterialToolLabel { }
// Estimated cameras count
MaterialToolLabelButton {
id : estimatedCamerasFilterButton
Layout.minimumWidth: childrenRect.width Layout.minimumWidth: childrenRect.width
ToolTip.text: label + " Estimated Cameras" ToolTip.text: label + " Estimated Cameras"
iconText: MaterialIcons.videocam iconText: MaterialIcons.videocam
label: _reconstruction ? _reconstruction.nbCameras.toString() : "0" label: _reconstruction.nbCameras ? _reconstruction.nbCameras.toString() : "-"
// margin: 4 padding: 3
// enabled: _reconstruction.cameraInit && _reconstruction.nbCameras
enabled: _reconstruction.cameraInit && _reconstruction.nbCameras
checkable: true
checked: false
onCheckedChanged:{
if(checked) {
root.filter = "viewId"
root.filterValue = true
inputImagesFilterButton.checked = false
nonEstimatedCamerasFilterButton.checked = false
intrinsicsFilterButton.checked = false;
}
}
onEnabledChanged:{
if(!enabled) {
if(checked) inputImagesFilterButton.checked = true;
checked = false
}
}
}
// Non estimated cameras count
MaterialToolLabelButton {
id : nonEstimatedCamerasFilterButton
Layout.minimumWidth: childrenRect.width
ToolTip.text: label + " Non Estimated Cameras"
iconText: MaterialIcons.videocam_off
label: _reconstruction.nbCameras ? ((m.viewpoints ? m.viewpoints.count : 0) - _reconstruction.nbCameras.toString()).toString() : "-"
padding: 3
enabled: _reconstruction.cameraInit && _reconstruction.nbCameras
checkable: true
checked: false
onCheckedChanged:{
if(checked) {
filter = "viewId"
root.filterValue = false
inputImagesFilterButton.checked = false
estimatedCamerasFilterButton.checked = false
intrinsicsFilterButton.checked = false;
}
}
onEnabledChanged:{
if(!enabled) {
if(checked) inputImagesFilterButton.checked = true;
checked = false
}
}
}
MaterialToolLabelButton {
id : intrinsicsFilterButton
Layout.minimumWidth: childrenRect.width
ToolTip.text: label + " Number of intrinsics"
iconText: MaterialIcons.camera
label: _reconstruction ? (m.intrinsics ? m.intrinsics.count : 0) : "0"
padding: 3
enabled: m.intrinsics ? m.intrinsics.count > 0 : false
checkable: true
checked: false
onCheckedChanged:{
if(checked) {
inputImagesFilterButton.checked = false
estimatedCamerasFilterButton.checked = false
nonEstimatedCamerasFilterButton.checked = false
}
}
onEnabledChanged:{
if(!enabled) {
if(checked) inputImagesFilterButton.checked = true;
checked = false
}
}
} }
Item { Layout.fillHeight: true; Layout.fillWidth: true } Item { Layout.fillHeight: true; Layout.fillWidth: true }

View file

@ -0,0 +1,205 @@
import QtQuick 2.9
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.2
import MaterialIcons 2.2
import Utils 1.0
RowLayout {
id: root
Layout.fillWidth: true
property variant attribute: model.display
property int rowIndex: model.row
property int columnIndex: model.column
property bool readOnly: false
property string toolTipText: {
if(!attribute || Object.keys(attribute).length === 0)
return ""
return attribute.fullLabel
}
Pane {
Layout.minimumWidth: loaderComponent.width
Layout.minimumHeight: loaderComponent.height
Layout.fillWidth: true
padding: 0
hoverEnabled: true
// Tooltip to replace headers for now (header incompatible atm)
ToolTip.delay: 10
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: toolTipText
Rectangle {
width: parent.width
height: loaderComponent.height
color: rowIndex % 2 ? palette.window : Qt.darker(palette.window, 1.1)
border.width: 2
border.color: Qt.darker(palette.window, 1.2)
clip: true
Loader {
id: loaderComponent
active: !!model.display // convert to bool with "!!"
sourceComponent: {
if(!model.display)
return undefined
switch(model.display.type)
{
case "ChoiceParam": return choice_component
case "IntParam": return int_component
case "FloatParam": return float_component
case "BoolParam": return bool_component
case "StringParam": return textField_component
default: return undefined
}
}
}
}
}
Component {
id: textField_component
TextInput{
text: model.display.value
width: intrinsicModel.columnWidths[columnIndex]
horizontalAlignment: TextInput.AlignRight
color: 'white'
padding: 12
selectByMouse: true
selectionColor: 'white'
selectedTextColor: Qt.darker(palette.window, 1.1)
onEditingFinished: _reconstruction.setAttribute(attribute, text)
onAccepted: {
_reconstruction.setAttribute(attribute, text)
}
Component.onDestruction: {
if(activeFocus)
_reconstruction.setAttribute(attribute, text)
}
}
}
Component {
id: int_component
TextInput{
text: model.display.value
width: intrinsicModel.columnWidths[columnIndex]
horizontalAlignment: TextInput.AlignRight
color: 'white'
padding: 12
selectByMouse: true
selectionColor: 'white'
selectedTextColor: Qt.darker(palette.window, 1.1)
IntValidator {
id: intValidator
}
validator: intValidator
onEditingFinished: _reconstruction.setAttribute(attribute, Number(text))
onAccepted: {
_reconstruction.setAttribute(attribute, Number(text))
}
Component.onDestruction: {
if(activeFocus)
_reconstruction.setAttribute(attribute, Number(text))
}
}
}
Component {
id: choice_component
ComboBox {
id: combo
model: attribute.desc.values
width: intrinsicModel.columnWidths[columnIndex]
flat : true
topInset: 7
leftInset: 6
rightInset: 6
bottomInset: 7
Component.onCompleted: currentIndex = find(attribute.value)
onActivated: _reconstruction.setAttribute(attribute, currentText)
Connections {
target: attribute
onValueChanged: combo.currentIndex = combo.find(attribute.value)
}
}
}
Component {
id: bool_component
CheckBox {
checked: attribute ? attribute.value : false
padding: 12
onToggled: _reconstruction.setAttribute(attribute, !attribute.value)
}
}
Component {
id: float_component
TextInput{
readonly property real formattedValue: model.display.value.toFixed(2)
property string displayValue: String(formattedValue)
text: displayValue
width: intrinsicModel.columnWidths[columnIndex]
horizontalAlignment: TextInput.AlignRight
color: 'white'
padding: 12
selectByMouse: true
selectionColor: 'white'
selectedTextColor: Qt.darker(palette.window, 1.1)
enabled: !readOnly
clip: true;
autoScroll: activeFocus
//Use this function to ensure the left part is visible
//while keeping the trick for formatting the text
//Timing issues otherwise
onActiveFocusChanged: {
if(activeFocus) text = String(model.display.value)
else text = String(formattedValue)
cursorPosition = 0
}
DoubleValidator {
id: doubleValidator
locale: 'C' // use '.' decimal separator disregarding the system locale
}
validator: doubleValidator
onEditingFinished: _reconstruction.setAttribute(attribute, Number(text))
onAccepted: {
_reconstruction.setAttribute(attribute, Number(text))
}
Component.onDestruction: {
if(activeFocus)
_reconstruction.setAttribute(attribute, Number(text))
}
}
}
}

View file

@ -2,3 +2,6 @@ module ImageGallery
ImageGallery 1.0 ImageGallery.qml ImageGallery 1.0 ImageGallery.qml
ImageDelegate 1.0 ImageDelegate.qml ImageDelegate 1.0 ImageDelegate.qml
ImageIntrinsicDelegate 1.0 ImageIntrinsicDelegate.qml
ImageIntrinsicViewer 1.0 ImageIntrinsicViewer.qml
IntrinsicDisplayDelegate 1.0 IntrinsicDisplayDelegate.qml