def test_output_invalidation(): graph = Graph('') n1 = graph.addNewNode('SampleNode', input='/tmp') n2 = graph.addNewNode('SampleNode') n3 = graph.addNewNode('SampleNode') graph.addEdges((n1.output, n2.input), (n1.output, n3.input)) # N1.output ----- N2.input # \ # N3.input # Compare UIDs of similar attributes on different nodes n2inputUid = n2.input.uid() n3inputUid = n3.input.uid() assert n3inputUid == n2inputUid # => UIDs are equal # Change a parameter outside UID n1.paramA.value = 'a' assert n2.input.uid() == n2inputUid # => same UID as before # Change a parameter impacting UID n1.input.value = "/a/path" assert n2.input.uid() != n2inputUid # => UID has changed assert n2.input.uid() == n3.input.uid( ) # => UIDs on both node are still equal
def test_graph_reverse_dfs(): graph = Graph('Test reverse DFS') # ------------\ # / ~ C - E - F # A - B # ~ D A = graph.addNewNode('Ls', input='/tmp') B = graph.addNewNode('AppendText', inputText=A.output) C = graph.addNewNode('AppendText', inputText=B.output) D = graph.addNewNode('AppendText', inputText=B.output) E = graph.addNewNode('Ls', input=C.output) F = graph.addNewNode('AppendText', input=A.output, inputText=E.output) # Get all nodes from A (use set, order not guaranteed) nodes = graph.nodesFromNode(A)[0] assert set(nodes) == {A, B, D, C, E, F} # Get all nodes from B nodes = graph.nodesFromNode(B)[0] assert set(nodes) == {B, D, C, E, F} # Get all nodes of type AppendText from B nodes = graph.nodesFromNode(B, filterTypes=['AppendText'])[0] assert set(nodes) == {B, D, C, F} # Get all nodes from C (order guaranteed) nodes = graph.nodesFromNode(C)[0] assert nodes == [C, E, F]
def hdri(inputImages=list(), inputViewpoints=list(), inputIntrinsics=list(), output='', graph=None): """ Create a new Graph with a complete HDRI pipeline. Args: inputImages (list of str, optional): list of image file paths inputViewpoints (list of Viewpoint, optional): list of Viewpoints output (str, optional): the path to export reconstructed model to Returns: Graph: the created graph """ if not graph: graph = Graph('HDRI') with GraphModification(graph): nodes = hdriPipeline(graph) cameraInit = nodes[0] cameraInit.viewpoints.extend([{ 'path': image } for image in inputImages]) cameraInit.viewpoints.extend(inputViewpoints) cameraInit.intrinsics.extend(inputIntrinsics) if output: stitching = nodes[-1] graph.addNewNode('Publish', output=output, inputFiles=[stitching.output]) return graph
def test_graph_reverse_dfsOnDiscover(): graph = Graph('Test dfsOnDiscover(reverse=True)') # ------------\ # / ~ C - E - F # A - B # ~ D A = graph.addNewNode('Ls', input='/tmp') B = graph.addNewNode('AppendText', inputText=A.output) C = graph.addNewNode('AppendText', inputText=B.output) D = graph.addNewNode('AppendText', inputText=B.output) E = graph.addNewNode('Ls', input=C.output) F = graph.addNewNode('AppendText', input=A.output, inputText=E.output) # Get all nodes from A (use set, order not guaranteed) nodes = graph.dfsOnDiscover(startNodes=[A], reverse=True)[0] assert set(nodes) == {A, B, D, C, E, F} # Get all nodes from B nodes = graph.dfsOnDiscover(startNodes=[B], reverse=True)[0] assert set(nodes) == {B, D, C, E, F} # Get all nodes of type AppendText from B nodes = graph.dfsOnDiscover(startNodes=[B], filterTypes=['AppendText'], reverse=True)[0] assert set(nodes) == {B, D, C, F} # Get all nodes from C (order guaranteed) nodes = graph.dfsOnDiscover(startNodes=[C], reverse=True)[0] assert nodes == [C, E, F] # Get all nodes nodes = graph.dfsOnDiscover(reverse=True)[0] assert set(nodes) == {A, B, C, D, E, F}
def test_duplicate_nodes(): """ Test nodes duplication. """ # n0 -- n1 -- n2 # \ \ # ---------- n3 g = Graph('') n0 = g.addNewNode('Ls', input='/tmp') n1 = g.addNewNode('Ls', input=n0.output) n2 = g.addNewNode('Ls', input=n1.output) n3 = g.addNewNode('AppendFiles', input=n1.output, input2=n2.output) # duplicate from n1 nMap = g.duplicateNodesFromNode(fromNode=n1) for s, d in nMap.items(): assert s.nodeType == d.nodeType # check number of duplicated nodes assert len(nMap) == 3 # check connections assert nMap[n1].input.getLinkParam() == n0.output assert nMap[n2].input.getLinkParam() == nMap[n1].output assert nMap[n3].input.getLinkParam() == nMap[n1].output assert nMap[n3].input2.getLinkParam() == nMap[n2].output
def photogrammetry(inputImages=list(), inputViewpoints=list(), inputIntrinsics=list(), output=''): """ Create a new Graph with a complete photogrammetry pipeline. Args: inputImages (list of str, optional): list of image file paths inputViewpoints (list of Viewpoint, optional): list of Viewpoints output (str, optional): the path to export reconstructed model to Returns: Graph: the created graph """ graph = Graph('Photogrammetry') with GraphModification(graph): sfmNodes, mvsNodes = photogrammetryPipeline(graph) cameraInit = sfmNodes[0] cameraInit.viewpoints.extend([{ 'path': image } for image in inputImages]) cameraInit.viewpoints.extend(inputViewpoints) cameraInit.intrinsics.extend(inputIntrinsics) if output: texturing = mvsNodes[-1] graph.addNewNode('Publish', output=output, inputFiles=[ texturing.outputMesh, texturing.outputMaterial, texturing.outputTextures ]) return graph
def panoramaHdr(inputImages=None, inputViewpoints=None, inputIntrinsics=None, output='', graph=None): """ Create a new Graph with a Panorama HDR pipeline. Args: inputImages (list of str, optional): list of image file paths inputViewpoints (list of Viewpoint, optional): list of Viewpoints output (str, optional): the path to export reconstructed model to Returns: Graph: the created graph """ if not graph: graph = Graph('PanoramaHDR') with GraphModification(graph): nodes = panoramaHdrPipeline(graph) cameraInit = nodes[0] if inputImages: cameraInit.viewpoints.extend([{'path': image} for image in inputImages]) if inputViewpoints: cameraInit.viewpoints.extend(inputViewpoints) if inputIntrinsics: cameraInit.intrinsics.extend(inputIntrinsics) if output: imageProcessing = nodes[-1] graph.addNewNode('Publish', output=output, inputFiles=[imageProcessing.outputImages]) return graph
def test_depth_diamond_graph2(): graph = Graph('Tests tasks depth') tA = graph.addNewNode('Ls', input='/tmp') tB = graph.addNewNode('AppendText', inputText='echo B') tC = graph.addNewNode('AppendText', inputText='echo C') tD = graph.addNewNode('AppendText', inputText='echo D') tE = graph.addNewNode('AppendFiles') # C # / \ # /---/---->\ # A -> B ---> E # \ / # \ / # D graph.addEdges( (tA.output, tB.input), (tB.output, tC.input), (tB.output, tD.input), (tA.output, tE.input), (tB.output, tE.input2), (tC.output, tE.input3), (tD.output, tE.input4), ) assert tA.depth == 0 assert tB.depth == 1 assert tC.depth == 2 assert tD.depth == 2 assert tE.depth == 3 nodes, edges = graph.dfsOnFinish() assert len(nodes) == 5 assert nodes[0] == tA assert nodes[-1] == tE assert len(edges) == 7 nodes, edges = graph.dfsOnFinish(startNodes=[tE]) assert len(nodes) == 5 assert nodes[0] == tA assert nodes[-1] == tE assert len(edges) == 7 nodes, edges = graph.dfsOnFinish(startNodes=[tD]) assert len(nodes) == 3 assert nodes[0] == tA assert nodes[1] == tB assert nodes[2] == tD assert len(edges) == 2 nodes, edges = graph.dfsOnFinish(startNodes=[tB]) assert len(nodes) == 2 assert nodes[0] == tA assert nodes[-1] == tB assert len(edges) == 1
def test_depth(): graph = Graph('Tests tasks depth') tA = graph.addNewNode('Ls', input='/tmp') tB = graph.addNewNode('AppendText', inputText='echo B') tC = graph.addNewNode('AppendText', inputText='echo C') graph.addEdges( (tA.output, tB.input), (tB.output, tC.input), ) assert tA.depth == 0 assert tB.depth == 1 assert tC.depth == 2
def test_upgradeAllNodes(): registerNodeType(SampleNodeV1) registerNodeType(SampleNodeV2) g = Graph('') n1 = g.addNewNode("SampleNodeV1") n2 = g.addNewNode("SampleNodeV2") n1Name = n1.name n2Name = n2.name graphFile = os.path.join(tempfile.mkdtemp(), "test_description_conflict.mg") g.save(graphFile) # make SampleNodeV2 an unknown type unregisterNodeType(SampleNodeV2) # replace SampleNodeV1 by SampleNodeV2 meshroom.core.nodesDesc[SampleNodeV1.__name__] = SampleNodeV2 # reload file g = loadGraph(graphFile) os.remove(graphFile) # both nodes are CompatibilityNodes assert len(g.compatibilityNodes) == 2 assert g.node(n1Name).canUpgrade # description conflict assert not g.node(n2Name).canUpgrade # unknown type # upgrade all upgradable nodes g.upgradeAllNodes() # only the node with an unknown type has not been upgraded assert len(g.compatibilityNodes) == 1 assert n2Name in g.compatibilityNodes.keys() unregisterNodeType(SampleNodeV1)
def test_unknown_node_type(): """ Test compatibility behavior for unknown node type. """ registerNodeType(SampleNodeV1) g = Graph('') n = g.addNewNode("SampleNodeV1", input="/dev/null", paramA="foo") graphFile = os.path.join(tempfile.mkdtemp(), "test_unknown_node_type.mg") g.save(graphFile) internalFolder = n.internalFolder nodeName = n.name unregisterNodeType(SampleNodeV1) # reload file g = loadGraph(graphFile) os.remove(graphFile) assert len(g.nodes) == 1 n = g.node(nodeName) # SampleNodeV1 is now an unknown type # check node instance type and compatibility issue type assert isinstance(n, CompatibilityNode) assert n.issue == CompatibilityIssue.UnknownNodeType # check if attributes are properly restored assert len(n.attributes) == 3 assert n.input.isInput assert n.output.isOutput # check if internal folder assert n.internalFolder == internalFolder # upgrade can't be perform on unknown node types assert not n.canUpgrade with pytest.raises(NodeUpgradeError): g.upgradeNode(nodeName)
def test_graph_nodes_sorting(): graph = Graph('') ls0 = graph.addNewNode('Ls') ls1 = graph.addNewNode('Ls') ls2 = graph.addNewNode('Ls') assert graph.nodesByType('Ls', sortedByIndex=True) == [ls0, ls1, ls2] graph = Graph('') # 'Random' creation order (what happens when loading a file) ls2 = graph.addNewNode('Ls', name='Ls_2') ls0 = graph.addNewNode('Ls', name='Ls_0') ls1 = graph.addNewNode('Ls', name='Ls_1') assert graph.nodesByType('Ls', sortedByIndex=True) == [ls0, ls1, ls2]
def test_graph_dfsOnDiscover(): graph = Graph('Test dfsOnDiscover(reverse=False)') # ------------\ # / ~ C - E - F # A - B # ~ D # G G = graph.addNewNode('Ls', input='/tmp') A = graph.addNewNode('Ls', input='/tmp') B = graph.addNewNode('AppendText', inputText=A.output) C = graph.addNewNode('AppendText', inputText=B.output) D = graph.addNewNode('AppendText', input=G.output, inputText=B.output) E = graph.addNewNode('Ls', input=C.output) F = graph.addNewNode('AppendText', input=A.output, inputText=E.output) # Get all nodes from A (use set, order not guaranteed) nodes = graph.dfsOnDiscover(startNodes=[A], reverse=False)[0] assert set(nodes) == {A} # Get all nodes from D nodes = graph.dfsOnDiscover(startNodes=[D], reverse=False)[0] assert set(nodes) == {A, B, D, G} # Get all nodes from E nodes = graph.dfsOnDiscover(startNodes=[E], reverse=False)[0] assert set(nodes) == {A, B, C, E} # Get all nodes from F nodes = graph.dfsOnDiscover(startNodes=[F], reverse=False)[0] assert set(nodes) == {A, B, C, E, F} # Get all nodes of type AppendText from C nodes = graph.dfsOnDiscover(startNodes=[C], filterTypes=['AppendText'], reverse=False)[0] assert set(nodes) == {B, C} # Get all nodes from D (order guaranteed) nodes = graph.dfsOnDiscover(startNodes=[D], longestPathFirst=True, reverse=False)[0] assert nodes == [D, B, A, G] # Get all nodes nodes = graph.dfsOnDiscover(reverse=False)[0] assert set(nodes) == {A, B, C, D, E, F, G}
def test_inputLinkInvalidation(): """ Input links should not change the invalidation. """ graph = Graph('') n1 = graph.addNewNode('SampleNode') n2 = graph.addNewNode('SampleNode') graph.addEdges((n1.input, n2.input)) assert n1.input.uid() == n2.input.uid() assert n1.output.value == n2.output.value
def test_transitive_reduction(): graph = Graph('Tests tasks depth') tA = graph.addNewNode('Ls', input='/tmp') tB = graph.addNewNode('AppendText', inputText='echo B') tC = graph.addNewNode('AppendText', inputText='echo C') tD = graph.addNewNode('AppendText', inputText='echo D') tE = graph.addNewNode('AppendFiles') # C # / \ # /---/---->\ # A -> B ---> E # \ / # \ / # D graph.addEdges( (tA.output, tE.input), (tA.output, tB.input), (tB.output, tC.input), (tB.output, tD.input), (tB.output, tE.input4), (tC.output, tE.input3), (tD.output, tE.input2), ) edgesScore = graph.dfsMaxEdgeLength() flowEdges = graph.flowEdges() flowEdgesRes = [(tB, tA), (tD, tB), (tC, tB), (tE, tD), (tE, tC), ] assert set(flowEdgesRes) == set(flowEdges) assert len(graph._nodesMinMaxDepths) == len(graph.nodes) for node, (minDepth, maxDepth) in graph._nodesMinMaxDepths.items(): assert node.depth == maxDepth
def test_depth_diamond_graph(): graph = Graph('Tests tasks depth') tA = graph.addNewNode('Ls', input='/tmp') tB = graph.addNewNode('AppendText', inputText='echo B') tC = graph.addNewNode('AppendText', inputText='echo C') tD = graph.addNewNode('AppendFiles') graph.addEdges( (tA.output, tB.input), (tA.output, tC.input), (tB.output, tD.input), (tC.output, tD.input2), ) assert tA.depth == 0 assert tB.depth == 1 assert tC.depth == 1 assert tD.depth == 2 nodes, edges = graph.dfsOnFinish() assert len(nodes) == 4 assert nodes[0] == tA assert nodes[-1] == tD assert len(edges) == 4 nodes, edges = graph.dfsOnFinish(startNodes=[tD]) assert len(nodes) == 4 assert nodes[0] == tA assert nodes[-1] == tD assert len(edges) == 4 nodes, edges = graph.dfsOnFinish(startNodes=[tB]) assert len(nodes) == 2 assert nodes[0] == tA assert nodes[-1] == tB assert len(edges) == 1
def test_conformUpgrade(): registerNodeType(SampleNodeV5) registerNodeType(SampleNodeV6) g = Graph('') n1 = g.addNewNode("SampleNodeV5") n1.paramA.value = [{ 'a': 0, 'b': [{ 'a': 0, 'b': [1.0, 2.0] }, { 'a': 1, 'b': [1.0, 2.0] }] }] n1Name = n1.name graphFile = os.path.join(tempfile.mkdtemp(), "test_conform_upgrade.mg") g.save(graphFile) # replace SampleNodeV5 by SampleNodeV6 meshroom.core.nodesDesc[SampleNodeV5.__name__] = SampleNodeV6 # reload file g = loadGraph(graphFile) os.remove(graphFile) # node is a CompatibilityNode assert len(g.compatibilityNodes) == 1 assert g.node(n1Name).canUpgrade # upgrade all upgradable nodes g.upgradeAllNodes() # only the node with an unknown type has not been upgraded assert len(g.compatibilityNodes) == 0 upgradedNode = g.node(n1Name) # check upgrade assert isinstance(upgradedNode, Node) and isinstance( upgradedNode.nodeDesc, SampleNodeV6) # check conformation assert len(upgradedNode.paramA.value) == 1 unregisterNodeType(SampleNodeV5) unregisterNodeType(SampleNodeV6)
def test_description_conflict(): """ Test compatibility behavior for conflicting node descriptions. """ # copy registered node types to be able to restore them originalNodeTypes = copy.copy(meshroom.core.nodesDesc) nodeTypes = [SampleNodeV1, SampleNodeV2, SampleNodeV3, SampleNodeV4, SampleNodeV5] nodes = [] g = Graph('') # register and instantiate instances of all node types except last one for nt in nodeTypes[:-1]: registerNodeType(nt) n = g.addNewNode(nt.__name__) if nt == SampleNodeV4: # initialize list attribute with values to create a conflict with V5 n.paramA.value = [{'a': 0, 'b': [1.0, 2.0]}] nodes.append(n) graphFile = os.path.join(tempfile.mkdtemp(), "test_description_conflict.mg") g.save(graphFile) # reload file as-is, ensure no compatibility issue is detected (no CompatibilityNode instances) g = loadGraph(graphFile) assert all(isinstance(n, Node) for n in g.nodes) # offset node types register to create description conflicts # each node type name now reference the next one's implementation for i, nt in enumerate(nodeTypes[:-1]): meshroom.core.nodesDesc[nt.__name__] = nodeTypes[i+1] # reload file g = loadGraph(graphFile) os.remove(graphFile) assert len(g.nodes) == len(nodes) for srcNode in nodes: nodeName = srcNode.name compatNode = g.node(srcNode.name) # Node description clashes between what has been saved assert isinstance(compatNode, CompatibilityNode) assert srcNode.internalFolder == compatNode.internalFolder # case by case description conflict verification if isinstance(srcNode.nodeDesc, SampleNodeV1): # V1 => V2: 'input' has been renamed to 'in' assert len(compatNode.attributes) == 3 assert hasattr(compatNode, "input") assert not hasattr(compatNode, "in") # perform upgrade upgradedNode = g.upgradeNode(nodeName)[0] assert isinstance(upgradedNode, Node) and isinstance(upgradedNode.nodeDesc, SampleNodeV2) assert not hasattr(upgradedNode, "input") assert hasattr(upgradedNode, "in") # check uid has changed (not the same set of attributes) assert upgradedNode.internalFolder != srcNode.internalFolder elif isinstance(srcNode.nodeDesc, SampleNodeV2): # V2 => V3: 'paramA' has been removed' assert len(compatNode.attributes) == 3 assert hasattr(compatNode, "paramA") # perform upgrade upgradedNode = g.upgradeNode(nodeName)[0] assert isinstance(upgradedNode, Node) and isinstance(upgradedNode.nodeDesc, SampleNodeV3) assert not hasattr(upgradedNode, "paramA") # check uid is identical (paramA not part of uid) assert upgradedNode.internalFolder == srcNode.internalFolder elif isinstance(srcNode.nodeDesc, SampleNodeV3): # V3 => V4: 'paramA' has been added assert len(compatNode.attributes) == 2 assert not hasattr(compatNode, "paramA") # perform upgrade upgradedNode = g.upgradeNode(nodeName)[0] assert isinstance(upgradedNode, Node) and isinstance(upgradedNode.nodeDesc, SampleNodeV4) assert hasattr(upgradedNode, "paramA") assert isinstance(upgradedNode.paramA.attributeDesc, desc.ListAttribute) # paramA child attributes invalidate UID assert upgradedNode.internalFolder != srcNode.internalFolder elif isinstance(srcNode.nodeDesc, SampleNodeV4): # V4 => V5: 'paramA' elementDesc has changed from SampleGroupV1 to SampleGroupV2 assert len(compatNode.attributes) == 3 assert hasattr(compatNode, "paramA") groupAttribute = compatNode.paramA.attributeDesc.elementDesc assert isinstance(groupAttribute, desc.GroupAttribute) # check that Compatibility node respect SampleGroupV1 description for elt in groupAttribute.groupDesc: assert isinstance(elt, next(a for a in SampleGroupV1 if a.name == elt.name).__class__) # perform upgrade upgradedNode = g.upgradeNode(nodeName)[0] assert isinstance(upgradedNode, Node) and isinstance(upgradedNode.nodeDesc, SampleNodeV5) assert hasattr(upgradedNode, "paramA") # parameter was incompatible, value could not be restored assert upgradedNode.paramA.isDefault assert upgradedNode.internalFolder != srcNode.internalFolder else: raise ValueError("Unexpected node type: " + srcNode.nodeType) # restore original node types meshroom.core.nodesDesc = originalNodeTypes