def testClipOrderWithSameInputs(): """ The "merge" node has 2 input clips. The order of these input connections modify the hash only if these input are different. "Merge.A -> Read1" and "Merge.B -> Read2" is the same than "Merge.A -> Read2" and "Merge.B -> Read1" ONLY IF Read1 is the same than Read2 """ a_g = tuttle.Graph() a_read1 = a_g.createNode("tuttle.checkerboard", size=[50, 72]) a_read2 = a_g.createNode("tuttle.checkerboard", size=[50, 72]) a_merge = a_g.createNode("tuttle.merge") a_g.connect(a_read1, a_merge.getClip("A")) a_g.connect(a_read2, a_merge.getClip("B")) b_g = tuttle.Graph() b_read1 = b_g.createNode("tuttle.checkerboard", size=[50, 72]) b_read2 = b_g.createNode("tuttle.checkerboard", size=[50, 72]) b_merge = b_g.createNode("tuttle.merge") b_g.connect(b_read2, b_merge.getClip("A")) b_g.connect(b_read1, b_merge.getClip("B")) time = 0.0 aHashMap = tuttle.NodeHashContainer() a_g.computeGlobalHashAtTime(aHashMap, time) aHash = aHashMap.getHash(a_merge.getName(), time) bHashMap = tuttle.NodeHashContainer() b_g.computeGlobalHashAtTime(bHashMap, time) bHash = bHashMap.getHash(b_merge.getName(), time) assert_equal(aHash, bHash)
def testSameGraph(): """ If we create 2 identical graphs, their hashes should be the same! """ a_g = tuttle.Graph() a_read1 = a_g.createNode("tuttle.checkerboard", size=[50, 50]) a_read2 = a_g.createNode("tuttle.checkerboard", size=[50, 49]) a_merge = a_g.createNode("tuttle.merge") a_g.connect(a_read1, a_merge.getClip("A")) a_g.connect(a_read2, a_merge.getClip("B")) b_g = tuttle.Graph() b_read1 = b_g.createNode("tuttle.checkerboard", size=[50, 50]) b_read2 = b_g.createNode("tuttle.checkerboard", size=[50, 49]) b_merge = b_g.createNode("tuttle.merge") b_g.connect(b_read1, b_merge.getClip("A")) b_g.connect(b_read2, b_merge.getClip("B")) time = 0.0 aHashMap = tuttle.NodeHashContainer() a_g.computeGlobalHashAtTime(aHashMap, time) aHash = aHashMap.getHash(a_merge.getName(), time) bHashMap = tuttle.NodeHashContainer() b_g.computeGlobalHashAtTime(bHashMap, time) bHash = bHashMap.getHash(b_merge.getName(), time) assert_equal(aHash, bHash)
def testClipOrder(): """ The "merge" node has 2 input clips. The order of these input connections should modify the hash. "Merge.A -> Read1" and "Merge.B -> Read2" is NOT the same than "Merge.A -> Read2" and "Merge.B -> Read1" """ a_g = tuttle.Graph() a_read1 = a_g.createNode("tuttle.checkerboard", size=[50, 50]) a_read2 = a_g.createNode("tuttle.checkerboard", size=[50, 49]) a_merge = a_g.createNode("tuttle.merge") a_g.connect(a_read1, a_merge.getClip("A")) a_g.connect(a_read2, a_merge.getClip("B")) c_g = tuttle.Graph() c_read1 = c_g.createNode("tuttle.checkerboard", size=[50, 50]) c_read2 = c_g.createNode("tuttle.checkerboard", size=[50, 49]) c_merge = c_g.createNode("tuttle.merge") c_g.connect(c_read2, c_merge.getClip("A")) c_g.connect(c_read1, c_merge.getClip("B")) time = 0.0 aHashMap = tuttle.NodeHashContainer() a_g.computeGlobalHashAtTime(aHashMap, time) aHash = aHashMap.getHash(a_merge.getName(), time) cHashMap = tuttle.NodeHashContainer() c_g.computeGlobalHashAtTime(cHashMap, time) cHash = cHashMap.getHash(c_merge.getName(), time) assert_not_equal(aHash, cHash)
def testNotTheSameClipIsUnconnected(): """ A "fade" node with 2 optional input clips should have different hashes depending on which input is unconnected. """ a_g = tuttle.Graph() a_read1 = a_g.createNode("tuttle.checkerboard", size=[50, 50]) a_fade = a_g.createNode("tuttle.fade") a_g.connect(a_read1, a_fade.getClip("SourceFrom")) # a_fade.getClip("SourceTo") is unconnected b_g = tuttle.Graph() b_read1 = b_g.createNode("tuttle.checkerboard", size=[50, 50]) b_fade = b_g.createNode("tuttle.fade") b_g.connect(b_read1, b_fade.getClip("SourceTo")) # b_fade.getClip("SourceFrom") is unconnected time = 0.0 aHashMap = tuttle.NodeHashContainer() a_g.computeGlobalHashAtTime(aHashMap, time) aHash = aHashMap.getHash(a_fade.getName(), time) bHashMap = tuttle.NodeHashContainer() b_g.computeGlobalHashAtTime(bHashMap, time) bHash = bHashMap.getHash(b_fade.getName(), time) assert_not_equal(aHash, bHash)
def testNotValidGraphRaise(): """ To compute the hash we need to run setupAtTime first (we need to know if the node isFrameVaying, etc.). "setupAtTime()" raises if the graph is not valid, like a non optional clip is unconnected. And there is no sense to compute the hash of a node which is not valid. @todo: The computeGlobalHashAtTime should not throw, and it should compute the hash of all valid nodes. """ a_g = tuttle.Graph() a_read1 = a_g.createNode("tuttle.checkerboard", size=[50, 50]) a_read2 = a_g.createNode("tuttle.checkerboard", size=[50, 49]) a_merge = a_g.createNode("tuttle.merge") a_g.connect(a_read1, a_merge.getClip("A")) b_g = tuttle.Graph() b_read1 = b_g.createNode("tuttle.checkerboard", size=[50, 50]) b_read2 = b_g.createNode("tuttle.checkerboard", size=[50, 49]) b_merge = b_g.createNode("tuttle.merge") b_g.connect(b_read2, b_merge.getClip("B")) time = 0.0 aHashMap = tuttle.NodeHashContainer() assert_raises(Exception, a_g.computeGlobalHashAtTime, aHashMap, time) bHashMap = tuttle.NodeHashContainer() assert_raises(Exception, b_g.computeGlobalHashAtTime, bHashMap, time)
def retrieveImage(self, frame, frameChanged): """ Computes the node at the frame indicated if the frame has changed (if the time has changed). """ buttleData = ButtleDataSingleton().get() #Get the name of the currentNode of the viewer node = buttleData.getCurrentViewerNodeName() #Get the gloabl hashCode of the node if node is not None: hashMap = tuttle.NodeHashContainer() buttleData.getGraph().getGraphTuttle().computeGlobalHashAtTime( hashMap, frame) node_hashCode = hashMap.getHash(node, frame) #Get the map mapNodeToImage = buttleData.getMapNodeNameToComputedImage() try: self.setNodeError("") for key in mapNodeToImage.keys(): #If the image is already calculated if node_hashCode == key and frameChanged is False: #print "**************************Image already calculated**********************" return mapNodeToImage.get(node_hashCode) #If it is not #print "**************************Image is not already calculated**********************" return self.computeNode(node, frame) except Exception as e: logging.debug("Can't display node : " + node) self.setNodeError(str(e)) raise
def testIdentityNode(): a_g = tuttle.Graph() a_read1 = a_g.createNode("tuttle.checkerboard", size=[50, 50]) a_timeshift = a_g.createNode("tuttle.timeshift", offset=3) a_invert = a_g.createNode("tuttle.invert") a_g.connect([a_read1, a_timeshift, a_invert]) time = 0.0 aHashMap = tuttle.NodeHashContainer() a_g.computeGlobalHashAtTime(aHashMap, time) aHash = aHashMap.getHash(a_invert.getName(), time) # The Timshift node itself is an identity node, # and it only modify the timing of nodes. # So if the input doesn't change over time, it doesn't modify the hash a_timeshift.getParam("offset").setValue(0) bHashMap = tuttle.NodeHashContainer() a_g.computeGlobalHashAtTime(bHashMap, time) bHash = bHashMap.getHash(a_invert.getName(), time) assert_equal(aHash, bHash)
def computeNode(self, node, frame): """ Computes the node (displayed in the viewer) at the frame indicated. """ buttleData = ButtleDataSingleton().get() graphTuttle = buttleData.getGraph().getGraphTuttle() #Get the output where we save the result self._tuttleImageCache = tuttle.MemoryCache() if buttleData.getVideoIsPlaying(): # if a video is playing processGraph = buttleData.getProcessGraph() processGraph.setupAtTime(frame) processGraph.processAtTime(self._tuttleImageCache, frame) else: # if it's an image only processOptions = tuttle.ComputeOptions(int(frame)) processGraph = tuttle.ProcessGraph(processOptions, graphTuttle, [node]) processGraph.setup() timeRange = tuttle.TimeRange(frame, frame, 1) # buttleData.getTimeRange() processGraph.beginSequence(timeRange) processGraph.setupAtTime(frame) processGraph.processAtTime(self._tuttleImageCache, frame) processGraph.endSequence() self._computedImage = self._tuttleImageCache.get(0) #Add the computedImage to the map hashMap = tuttle.NodeHashContainer() graphTuttle.computeGlobalHashAtTime(hashMap, frame) hasCode = hashMap.getHash(node, frame) #Max 15 computedImages saved in memory if hasCode not in buttleData._mapNodeNameToComputedImage.keys( ) and len(buttleData._mapNodeNameToComputedImage) < 15: buttleData._mapNodeNameToComputedImage.update( {hasCode: self._computedImage}) elif hasCode not in buttleData._mapNodeNameToComputedImage.keys( ) and len(buttleData._mapNodeNameToComputedImage) >= 15: #Delete a computed image from the memory (random) buttleData._mapNodeNameToComputedImage.popitem() buttleData._mapNodeNameToComputedImage.update( {hasCode: self._computedImage}) return self._computedImage
def testUnusedNodes(): """ It should not have any impact, if there are invalid but unused nodes in the graph. Nodes could be invalid if they have unconnected mandatory clips. And the other hand, if you ask to compute those invalid nodes, it should raise an exception. """ g = tuttle.Graph() read1 = g.createNode("tuttle.checkerboard", size=[20, 137]).asImageEffectNode() read2 = g.createNode("tuttle.checkerboard", size=[234, 357]).asImageEffectNode() merge = g.createNode("tuttle.merge", rod="union").asImageEffectNode() # Unconnected but unused nodes should not impede the computation pushpixel_unused = g.createNode("tuttle.pushpixel").asImageEffectNode() invert_unused = g.createNode("tuttle.invert").asImageEffectNode() g.connect(read1, merge.getClip("A")) g.connect(read2, merge.getClip("B")) hashMap = tuttle.NodeHashContainer() g.computeGlobalHashAtTime(hashMap, 0, [merge]) ## TODO: Should not raise, even if there are invalid nodes. # g.computeGlobalHashAtTime(hashMap, 0) outputCache = tuttle.MemoryCache() g.compute(outputCache, merge) # Trying to compute all nodes should raise an error # on unconnected filters (invert and pushpixel). assert_raises(Exception, g.compute, outputCache)
def convertScenePatterns(scene): ''' Replace PATTERNS with real filepaths. :param scene: dict with nodes, params and connections. :return: (scene, outputFilepaths) ''' outputScene = copy.deepcopy(scene) # Preload general plugins to use getBestReader/getBestWriter. tuttle.core().getPluginCache().addDirectoryToPath(globalOfxPluginPath) tuttle.core().preload(False) logging.debug("outputScene: " + str(outputScene)) outputResources = [] for node in outputScene['nodes']: if 'plugin' in node and node['plugin'] is not 'reader': logging.debug("Retrieve bundleId from plugin: " + str(node['plugin'])) resp = requests.get(catalogRootUri + "/bundle/" + node['plugin'] + '/bundle') if resp.status_code == 404: logging.warning("Cannont retrieve bundleId for plugin: " + str(node['plugin'])) else: respJson = resp.json() node["bundleId"] = respJson['bundleId'] logging.debug("bundleId: " + str(respJson['bundleId'])) for parameter in node['parameters']: logging.warning('param: %s %s', parameter['id'], parameter['value']) if isinstance(parameter['value'], (str, unicode)): if 'plugin' not in node and '{RESOURCES_DIR}' in parameter[ 'value']: parameter['value'] = parameter['value'].replace( '{RESOURCES_DIR}', config.resourcesPath) node['plugin'] = tuttle.getBestReader( str(parameter['value'])) if 'plugin' not in node and '{UNIQUE_OUTPUT_FILE}' in parameter[ 'value']: node['plugin'] = tuttle.getBestWriter( str(parameter['value'])) # Declare Bundles paths to TuttleOFX bundleIds = [] for node in outputScene['nodes']: if 'bundleId' in node: bundleIds.append(node['bundleId']) else: logging.error("No bundle defined for node: " + str(node)) bundlePaths = [ os.path.join(pluginsStorage, str(bundleId)) for bundleId in bundleIds ] logging.debug("bundlePaths: " + str(bundlePaths)) configLocalPluginPath(bundlePaths) logging.debug("outputScene after conversion: " + str(outputScene)) # Create a Tuttle Graph to generate the UID for each node tuttleGraphTmp = loadGraph(outputScene) # logging.warning("tuttleGraphTemp" + str(tuttleGraphTmp)) # logging.warning("outputScene" + str(outputScene)) nodesHashMap = tuttle.NodeHashContainer() tuttleGraphTmp.computeGlobalHashAtTime(nodesHashMap, 0.0) for node in outputScene['nodes']: for parameter in node['parameters']: logging.warning('param: %s %s', parameter['id'], parameter['value']) if isinstance(parameter['value'], (str, unicode)): if '{UNIQUE_OUTPUT_FILE}' in parameter['value']: prefix, suffix = parameter['value'].split( '{UNIQUE_OUTPUT_FILE}') nodeHash = str(nodesHashMap.getHash(node['name'], 0.0)) node['hash'] = nodeHash filename = nodeHash + suffix filepath = os.path.join(config.renderDirectory, cache.cachePathFromFile(filename)) outputResources.append(filename) parameter['value'] = filepath return (outputScene, outputResources)