Merge pull request #2436 from alicevision/fix/compatibilityUpgrade

Fix compatibility upgrade issue
This commit is contained in:
Fabien Castan 2024-06-15 19:40:47 +02:00 committed by GitHub
commit 9a09310f07
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 79 additions and 63 deletions

View file

@ -38,6 +38,8 @@ def attributeFactory(description, value, isOutput, node, root=None, parent=None)
attr = cls(node, description, isOutput, root, parent)
if value is not None:
attr.value = value
else:
attr.resetToDefaultValue()
return attr
@ -72,8 +74,7 @@ class Attribute(BaseObject):
self._invalidationValue = ""
self._value = None
# do not emit value changed on initialization
self.resetValue(emitSignals=False)
self.initValue()
@property
def node(self):
@ -240,7 +241,11 @@ class Attribute(BaseObject):
def upgradeValue(self, exportedValue):
self._set_value(exportedValue)
def resetValue(self, emitSignals=True):
def initValue(self):
if self.desc._valueType is not None:
self._value = self.desc._valueType()
def resetToDefaultValue(self, emitSignals=True):
self._set_value(copy.copy(self.defaultValue()), emitSignals=emitSignals)
def requestGraphUpdate(self):
@ -321,7 +326,7 @@ class Attribute(BaseObject):
return
if isinstance(v, Attribute):
g.addEdge(v, self)
self.resetValue()
self.resetToDefaultValue()
elif self.isInput and Attribute.isLinkExpression(v):
# value is a link to another attribute
link = v[1:-1]
@ -330,7 +335,7 @@ class Attribute(BaseObject):
g.addEdge(g.node(linkNode).attribute(linkAttr), self)
except KeyError as err:
logging.warning('Connect Attribute from Expression failed.\nExpression: "{exp}"\nError: "{err}".'.format(exp=v, err=err))
self.resetValue()
self.resetToDefaultValue()
def getExportValue(self):
if self.isLink:
@ -370,7 +375,13 @@ class Attribute(BaseObject):
def defaultValue(self):
if isinstance(self.desc.value, types.FunctionType):
return self.desc.value(self)
try:
return self.desc.value(self)
except Exception as e:
if not self.node.isCompatibilityNode:
# log message only if we are not in compatibility mode
logging.warning("Failed to evaluate default value (node lambda) for attribute '{}': {}".format(self.name, e))
return None
# Need to force a copy, for the case where the value is a list (avoid reference to the desc value)
return copy.copy(self.desc.value)
@ -504,7 +515,10 @@ class ListAttribute(Attribute):
def index(self, item):
return self._value.indexOf(item)
def resetValue(self, emitSignals=True):
def initValue(self):
self.resetToDefaultValue(emitSignals=False)
def resetToDefaultValue(self, emitSignals=True):
self._value = ListModel(parent=self)
if emitSignals:
self.valueChanged.emit()
@ -650,14 +664,6 @@ class GroupAttribute(Attribute):
def __init__(self, node, attributeDesc, isOutput, root=None, parent=None):
super(GroupAttribute, self).__init__(node, attributeDesc, isOutput, root, parent)
subAttributes = []
for subAttrDesc in self.attributeDesc.groupDesc:
childAttr = attributeFactory(subAttrDesc, None, self.isOutput, self.node, self)
subAttributes.append(childAttr)
childAttr.valueChanged.connect(self.valueChanged)
self._value.reset(subAttributes)
def __getattr__(self, key):
try:
return super(GroupAttribute, self).__getattr__(key)
@ -696,10 +702,18 @@ class GroupAttribute(Attribute):
else:
raise AttributeError("Failed to set on GroupAttribute: {}".format(str(value)))
def resetValue(self, emitSignals=True):
def initValue(self):
self._value = DictModel(keyAttrName='name', parent=self)
if emitSignals:
self.valueChanged.emit()
subAttributes = []
for subAttrDesc in self.attributeDesc.groupDesc:
childAttr = attributeFactory(subAttrDesc, None, self.isOutput, self.node, self)
subAttributes.append(childAttr)
childAttr.valueChanged.connect(self.valueChanged)
self._value.reset(subAttributes)
def resetToDefaultValue(self, emitSignals=True):
for attrDesc in self.desc._groupDesc:
self._value.get(attrDesc.name).resetToDefaultValue()
@Slot(str, result=Attribute)
def childAttribute(self, key):

View file

@ -32,6 +32,7 @@ class Attribute(BaseObject):
self._errorMessage = errorMessage
self._visible = visible
self._isExpression = (isinstance(self._value, str) and "{" in self._value) or isinstance(self._value, types.FunctionType)
self._valueType = None
name = Property(str, lambda self: self._name, constant=True)
label = Property(str, lambda self: self._label, constant=True)
@ -228,6 +229,7 @@ class File(Attribute):
"""
def __init__(self, name, label, description, value, uid, group='allParams', advanced=False, semantic='', enabled=True, visible=True):
super(File, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled, visible=visible)
self._valueType = str
def validateValue(self, value):
if not isinstance(value, str):
@ -247,6 +249,7 @@ class BoolParam(Param):
"""
def __init__(self, name, label, description, value, uid, group='allParams', advanced=False, semantic='', enabled=True, visible=True):
super(BoolParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled, visible=visible)
self._valueType = bool
def validateValue(self, value):
try:
@ -270,6 +273,7 @@ class IntParam(Param):
self._range = range
super(IntParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled,
validValue=validValue, errorMessage=errorMessage, visible=visible)
self._valueType = int
def validateValue(self, value):
# handle unsigned int values that are translated to int by shiboken and may overflow
@ -293,6 +297,7 @@ class FloatParam(Param):
self._range = range
super(FloatParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled,
validValue=validValue, errorMessage=errorMessage, visible=visible)
self._valueType = float
def validateValue(self, value):
try:
@ -312,6 +317,8 @@ class PushButtonParam(Param):
"""
def __init__(self, name, label, description, uid, group='allParams', advanced=False, semantic='', enabled=True, visible=True):
super(PushButtonParam, self).__init__(name=name, label=label, description=description, value=None, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled, visible=visible)
self._valueType = None
def validateValue(self, value):
pass
def checkValueTypes(self):
@ -373,6 +380,7 @@ class StringParam(Param):
def __init__(self, name, label, description, value, uid, group='allParams', advanced=False, semantic='', enabled=True, uidIgnoreValue=None, validValue=True, errorMessage="", visible=True):
super(StringParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled,
uidIgnoreValue=uidIgnoreValue, validValue=validValue, errorMessage=errorMessage, visible=visible)
self._valueType = str
def validateValue(self, value):
if not isinstance(value, str):
@ -390,6 +398,7 @@ class ColorParam(Param):
"""
def __init__(self, name, label, description, value, uid, group='allParams', advanced=False, semantic='', enabled=True, visible=True):
super(ColorParam, self).__init__(name=name, label=label, description=description, value=value, uid=uid, group=group, advanced=advanced, semantic=semantic, enabled=enabled, visible=visible)
self._valueType = str
def validateValue(self, value):
if not isinstance(value, str) or len(value.split(" ")) > 1:
@ -746,7 +755,7 @@ class InitNode:
"""
for attrName in attributeNames:
if node.hasAttribute(attrName):
node.attribute(attrName).resetValue()
node.attribute(attrName).resetToDefaultValue()
def extendAttributes(self, node, attributesDict):
"""

View file

@ -268,7 +268,7 @@ class Graph(BaseObject):
if not isinstance(graphData, dict):
raise RuntimeError('loadGraph error: Graph is not a dict. File: {}'.format(filepath))
self._fileDateVersion = os.path.getmtime(filepath)
self.header = fileData.get(Graph.IO.Keys.Header, {})
@ -551,13 +551,13 @@ class Graph(BaseObject):
# find top-level links
if Attribute.isLinkExpression(attr.value):
skippedEdges[attr] = attr.value
attr.resetValue()
attr.resetToDefaultValue()
# find links in ListAttribute children
elif isinstance(attr, ListAttribute):
for child in attr.value:
if Attribute.isLinkExpression(child.value):
skippedEdges[child] = child.value
child.resetValue()
child.resetToDefaultValue()
return node, skippedEdges
def duplicateNodes(self, srcNodes):
@ -933,7 +933,7 @@ class Graph(BaseObject):
for edge in self.getEdges(dependenciesOnly=dependenciesOnly):
nodeEdges[edge.src.node].add(edge.dst.node)
return nodeEdges
def dfs(self, visitor, startNodes=None, longestPathFirst=False):
@ -1548,7 +1548,7 @@ class Graph(BaseObject):
@property
def fileDateVersion(self):
return self._fileDateVersion
@fileDateVersion.setter
def fileDateVersion(self, value):
self._fileDateVersion = value

View file

@ -760,13 +760,14 @@ class BaseNode(BaseObject):
except AttributeError as e:
# If we load an old scene, the lambda associated to the 'value' could try to access other params that could not exist yet
logging.warning('Invalid lambda evaluation for "{nodeName}.{attrName}"'.format(nodeName=self.name, attrName=attr.name))
try:
attr.value = defaultValue.format(**self._cmdVars)
attr._invalidationValue = defaultValue.format(**cmdVarsNoCache)
except KeyError as e:
logging.warning('Invalid expression with missing key on "{nodeName}.{attrName}" with value "{defaultValue}".\nError: {err}'.format(nodeName=self.name, attrName=attr.name, defaultValue=defaultValue, err=str(e)))
except ValueError as e:
logging.warning('Invalid expression value on "{nodeName}.{attrName}" with value "{defaultValue}".\nError: {err}'.format(nodeName=self.name, attrName=attr.name, defaultValue=defaultValue, err=str(e)))
if defaultValue is not None:
try:
attr.value = defaultValue.format(**self._cmdVars)
attr._invalidationValue = defaultValue.format(**cmdVarsNoCache)
except KeyError as e:
logging.warning('Invalid expression with missing key on "{nodeName}.{attrName}" with value "{defaultValue}".\nError: {err}'.format(nodeName=self.name, attrName=attr.name, defaultValue=defaultValue, err=str(e)))
except ValueError as e:
logging.warning('Invalid expression value on "{nodeName}.{attrName}" with value "{defaultValue}".\nError: {err}'.format(nodeName=self.name, attrName=attr.name, defaultValue=defaultValue, err=str(e)))
v = attr.getValueStr(withQuotes=True)
@ -1272,13 +1273,13 @@ class Node(BaseNode):
self._internalFolder = self.nodeDesc.internalFolder
for attrDesc in self.nodeDesc.inputs:
self._attributes.add(attributeFactory(attrDesc, None, False, self))
self._attributes.add(attributeFactory(attrDesc, kwargs.get(attrDesc.name, None), isOutput=False, node=self))
for attrDesc in self.nodeDesc.outputs:
self._attributes.add(attributeFactory(attrDesc, None, True, self))
self._attributes.add(attributeFactory(attrDesc, kwargs.get(attrDesc.name, None), isOutput=True, node=self))
for attrDesc in self.nodeDesc.internalInputs:
self._internalAttributes.add(attributeFactory(attrDesc, None, False, self))
self._internalAttributes.add(attributeFactory(attrDesc, kwargs.get(attrDesc.name, None), isOutput=False, node=self))
# List attributes per uid
for attr in self._attributes:
@ -1292,8 +1293,6 @@ class Node(BaseNode):
for uidIndex in attr.attributeDesc.uid:
self.attributesPerUid[uidIndex].add(attr)
self.setAttributeValues(kwargs)
self.setInternalAttributeValues(kwargs)
self.optionalCallOnDescriptor("onNodeCreated")
def optionalCallOnDescriptor(self, methodName, *args, **kwargs):
@ -1641,7 +1640,7 @@ class CompatibilityNode(BaseNode):
try:
upgradedAttrValues = node.nodeDesc.upgradeAttributeValues(attrValues, self.version)
except Exception as e:
logging.error("Error in the upgrade implementation of the node: {}.\n{}".format(self.name, str(e)))
logging.error("Error in the upgrade implementation of the node: {}.\n{}".format(self.name, repr(e)))
upgradedAttrValues = attrValues
if not isinstance(upgradedAttrValues, dict):
@ -1650,13 +1649,8 @@ class CompatibilityNode(BaseNode):
node.upgradeAttributeValues(upgradedAttrValues)
try:
upgradedIntAttrValues = node.nodeDesc.upgradeAttributeValues(intAttrValues, self.version)
except Exception as e:
logging.error("Error in the upgrade implementation of the node: {}.\n{}".format(self.name, str(e)))
upgradedIntAttrValues = intAttrValues
node.upgradeInternalAttributeValues(intAttrValues)
node.upgradeInternalAttributeValues(upgradedIntAttrValues)
return node
compatibilityIssue = Property(int, lambda self: self.issue.value, constant=True)
@ -1753,8 +1747,7 @@ def nodeFactory(nodeDict, name=None, template=False, uidConflict=False):
break
if compatibilityIssue is None:
node = Node(nodeType, position, **inputs, **outputs)
node.setInternalAttributeValues(internalInputs)
node = Node(nodeType, position, **inputs, **internalInputs, **outputs)
else:
logging.debug("Compatibility issue detected for node '{}': {}".format(name, compatibilityIssue.name))
node = CompatibilityNode(nodeType, nodeDict, position, compatibilityIssue)

View file

@ -378,12 +378,12 @@ class RemoveImagesCommand(GraphCommand):
def redoImpl(self):
for i in range(len(self.cameraInits)):
# Reset viewpoints
self.cameraInits[i].viewpoints.resetValue()
self.cameraInits[i].viewpoints.resetToDefaultValue()
self.cameraInits[i].viewpoints.valueChanged.emit()
self.cameraInits[i].viewpoints.requestGraphUpdate()
# Reset intrinsics
self.cameraInits[i].intrinsics.resetValue()
self.cameraInits[i].intrinsics.resetToDefaultValue()
self.cameraInits[i].intrinsics.valueChanged.emit()
self.cameraInits[i].intrinsics.requestGraphUpdate()
@ -434,7 +434,8 @@ class UpgradeNodeCommand(GraphCommand):
self.graph.removeNode(self.nodeName)
# recreate compatibility node
with GraphModification(self.graph):
node = nodeFactory(self.nodeDict)
# We come back from an upgrade, so we enforce uidConflict=True as there was a uid conflict before
node = nodeFactory(self.nodeDict, name=self.nodeName, uidConflict=True)
self.graph.addNode(node, self.nodeName)
# recreate out edges
for dstAttr, srcAttr in self.outEdges.items():

View file

@ -103,14 +103,15 @@ class FilepathHelper(QObject):
def resolve(self, path, vp):
# Resolve dynamic path that depends on viewpoint
vpPath = vp.childAttribute("path").value
replacements = {
"<VIEW_ID>": str(vp.childAttribute("viewId").value),
"<INTRINSIC_ID>": str(vp.childAttribute("intrinsicId").value),
"<POSE_ID>": str(vp.childAttribute("poseId").value),
"<PATH>": vp.childAttribute("path").value,
"<FILENAME>": FilepathHelper.basename(FilepathHelper, vp.childAttribute("path").value),
"<FILESTEM>": FilepathHelper.removeExtension(FilepathHelper, FilepathHelper.basename(FilepathHelper, vp.childAttribute("path").value)),
"<EXTENSION>": FilepathHelper.extension(FilepathHelper, vp.childAttribute("path").value),
"<PATH>": vpPath,
"<FILENAME>": FilepathHelper.basename(FilepathHelper, vpPath),
"<FILESTEM>": FilepathHelper.removeExtension(FilepathHelper, FilepathHelper.basename(FilepathHelper, vpPath)),
"<EXTENSION>": FilepathHelper.extension(FilepathHelper, vpPath),
}
resolved = path

View file

@ -375,7 +375,6 @@ class UIGraph(QObject):
oldGraph.deleteLater()
self._graph.updated.connect(self.onGraphUpdated)
self._graph.update()
self._taskManager.update(self._graph)
# perform auto-layout if graph does not provide nodes positions
if Graph.IO.Features.NodesPositions not in self._graph.fileFeatures:

View file

@ -424,7 +424,8 @@ class Reconstruction(UIGraph):
"SfMAlignment"],
# All nodes generating a sfmData file
"sfmData": ["CameraInit", "DistortionCalibration", "StructureFromMotion", "GlobalSfM",
"PanoramaEstimation", "SfMTransfer", "SfMTransform", "SfMAlignment"],
"PanoramaEstimation", "SfMTransfer", "SfMTransform", "SfMAlignment",
"ApplyCalibration"],
# All nodes generating depth map files
"allDepthMap": ["DepthMap", "DepthMapFilter"],
# Nodes that can be used to provide features folders to the UI
@ -496,10 +497,11 @@ class Reconstruction(UIGraph):
self._activeNodes.get(key).node = None
def onCameraInitChanged(self):
if self._cameraInit is None:
return
# Update active nodes when CameraInit changes
nodes = self._graph.dfsOnDiscover(startNodes=[self._cameraInit], reverse=True)[0]
self.setActiveNodes(nodes)
self.resetActiveNodePerCategory()
@Slot()
@Slot(str)
@ -574,8 +576,8 @@ class Reconstruction(UIGraph):
self.selectedViewId = "-1"
self.tempCameraInit = None
self.updateCameraInits()
self.sfm = self.lastSfmNode()
self.resetActiveNodePerCategory()
self.sfm = self.lastSfmNode()
if not self._graph:
return
@ -602,9 +604,6 @@ class Reconstruction(UIGraph):
if self.cameraInit is None or self.cameraInit not in cameraInits:
self.cameraInit = cameraInits[0] if cameraInits else None
# Manually emit the signal to ensure the active CameraInit index is always up-to-date in the UI
self.cameraInitChanged.emit()
def getCameraInitIndex(self):
if not self._cameraInit:
# No CameraInit node
@ -924,10 +923,10 @@ class Reconstruction(UIGraph):
if rebuild:
# if rebuilding all intrinsics, for each Viewpoint:
for vp in cameraInitCopy.viewpoints.value:
vp.intrinsicId.resetValue() # reset intrinsic assignation
vp.metadata.resetValue() # and metadata (to clear any previous 'SensorWidth' entries)
vp.intrinsicId.resetToDefaultValue() # reset intrinsic assignation
vp.metadata.resetToDefaultValue() # and metadata (to clear any previous 'SensorWidth' entries)
# reset existing intrinsics list
cameraInitCopy.intrinsics.resetValue()
cameraInitCopy.intrinsics.resetToDefaultValue()
try:
self.setBuildingIntrinsics(True)

View file

@ -24,7 +24,7 @@ def test_formatting_listOfFiles():
n1.featuresFolders.extend("single value with space")
assert n1.featuresFolders.getValueStr() == '"single value with space"'
n1.featuresFolders.resetValue()
n1.featuresFolders.resetToDefaultValue()
assert n1.featuresFolders.getValueStr() == ''
n1.featuresFolders.extend(inputImages)