Example #1
0
class TaskMutexTest(GafferTest.TestCase):
    @unittest.skipIf(
        GafferTest.inCI(),
        "Causes intermittent failures in CI due to limited core availability")
    def test(self):

        GafferTest.testTaskMutex()

    def testWithinIsolate(self):

        GafferTest.testTaskMutexWithinIsolate()

    @unittest.skipIf(
        GafferTest.inCI(),
        "Causes intermittent failures in CI due to limited core availability")
    def testJoiningOuterTasks(self):

        GafferTest.testTaskMutexJoiningOuterTasks()

    @GafferTest.TestRunner.PerformanceTestMethod()
    def testHeavyContentionWithWorkAcceptance(self):

        GafferTest.testTaskMutexHeavyContention(True)

    @GafferTest.TestRunner.PerformanceTestMethod()
    def testHeavyContentionWithoutWorkAcceptance(self):

        GafferTest.testTaskMutexHeavyContention(False)

    def testWorkerRecursion(self):

        GafferTest.testTaskMutexWorkerRecursion()

    def testAcquireOr(self):

        GafferTest.testTaskMutexAcquireOr()

    def testExceptions(self):

        GafferTest.testTaskMutexExceptions()

    def testWorkerExceptions(self):

        GafferTest.testTaskMutexWorkerExceptions()

    def testDontSilentlyCancel(self):

        GafferTest.testTaskMutexDontSilentlyCancel()

    def testCancellation(self):

        GafferTest.testTaskMutexCancellation()
Example #2
0
    def testCancellation(self):

        c = GafferImage.Constant()

        r = GafferImage.Resample()
        r["in"].setInput(c["out"])
        r["filterScale"].setValue(imath.V2f(2000))

        bt = Gaffer.ParallelAlgo.callOnBackgroundThread(
            r["out"], lambda: GafferImageTest.processTiles(r["out"]))
        # Give background tasks time to get into full swing
        time.sleep(0.1)

        # Check that we can cancel them in reasonable time
        acceptableCancellationDelay = 4.0 if GafferTest.inCI() else 0.25
        t = time.time()
        bt.cancelAndWait()
        self.assertLess(time.time() - t, acceptableCancellationDelay)

        # Check that we can do the same when using a non-separable filter
        r["filter"].setValue("disk")

        bt = Gaffer.ParallelAlgo.callOnBackgroundThread(
            r["out"], lambda: GafferImageTest.processTiles(r["out"]))
        time.sleep(0.1)

        t = time.time()
        bt.cancelAndWait()
        self.assertLess(time.time() - t, acceptableCancellationDelay)
Example #3
0
    def testCancellation(self):

        c = GafferImage.Constant()

        m = GafferImage.Median()
        m["in"].setInput(c["out"])
        m["radius"].setValue(imath.V2i(2000))

        bt = Gaffer.ParallelAlgo.callOnBackgroundThread(
            m["out"], lambda: GafferImageTest.processTiles(m["out"]))
        # Give background tasks time to get into full swing
        time.sleep(0.1)

        # Check that we can cancel them in reasonable time
        acceptableCancellationDelay = 4.0 if GafferTest.inCI() else 0.25
        t = time.time()
        bt.cancelAndWait()
        self.assertLess(time.time() - t, acceptableCancellationDelay)

        # Check that we can do the same when using a master
        # channel.
        m["masterChannel"].setValue("R")

        bt = Gaffer.ParallelAlgo.callOnBackgroundThread(
            m["out"], lambda: GafferImageTest.processTiles(m["out"]))
        time.sleep(0.1)

        t = time.time()
        bt.cancelAndWait()
        self.assertLess(time.time() - t, acceptableCancellationDelay)
Example #4
0
#  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##########################################################################

import os
import unittest

import Gaffer
import GafferTest
import GafferScene
import GafferSceneTest
import GafferOSL
import GafferDelight


@unittest.skipIf(GafferTest.inCI(), "No license available in cloud")
class InteractiveDelightRenderTest(GafferSceneTest.InteractiveRenderTest):

    # Temporarily disable this test (which is implemented in the
    # base class) because it fails. The issue is that we're automatically
    # instancing the geometry for the two lights, and that appears to
    # trigger a bug in 3delight where the sampling goes awry.
    @unittest.skip("Awaiting feedback from 3delight developers")
    def testAddLight(self):

        pass

    # Disable this test for now as we don't have light linking support in
    # 3Delight, yet.
    @unittest.skip("No light linking support just yet")
    def testLightLinking(self):
Example #5
0
import unittest
import imath
import inspect

import IECore
import IECoreScene
import IECoreGL

import Gaffer
import GafferTest
import GafferImage
import GafferScene
import GafferSceneTest


@unittest.skipIf(GafferTest.inCI(), "OpenGL not set up")
class OpenGLShaderTest(GafferSceneTest.SceneTestCase):
    def test(self):

        s = GafferScene.OpenGLShader()
        s.loadShader("Texture")

        self.assertEqual(len(s["parameters"]), 3)
        self.assertTrue(isinstance(s["parameters"]["mult"], Gaffer.FloatPlug))
        self.assertTrue(isinstance(s["parameters"]["tint"],
                                   Gaffer.Color4fPlug))
        self.assertTrue(
            isinstance(s["parameters"]["texture"], GafferImage.ImagePlug))

        s["parameters"]["mult"].setValue(0.5)
        s["parameters"]["tint"].setValue(imath.Color4f(1, 0.5, 0.25, 1))
Example #6
0
import Gaffer
import GafferArnold
import GafferImage
import GafferImageUI
import GafferScene
import GafferSceneTest
import GafferTest
import GafferUI
import GafferUITest
import GafferImageTest

from Qt import QtCore


@unittest.skipIf(GafferTest.inCI(), "Performance not relevant on CI platform")
class InteractiveArnoldRenderPerformanceTest(GafferUITest.TestCase):

    # Arnold outputs licensing warnings that would cause failures
    failureMessageLevel = IECore.MessageHandler.Level.Error

    def runInteractive(self, useUI, useBlur, resolution):

        script = Gaffer.ScriptNode()

        script["Camera"] = GafferScene.Camera()
        script["Camera"]["transform"]["translate"]["z"].setValue(6)

        script["Sphere"] = GafferScene.Sphere("Sphere")
        script["Sphere"]["radius"].setValue(10)
class ArnoldTextureBakeTest( GafferSceneTest.SceneTestCase ) :

	class SimpleEdgeDetect( GafferImage.ImageProcessor ):

		def __init__( self, name = "SimpleEdgeDetect" ) :
			GafferImage.ImageProcessor.__init__( self, name )

			self["HorizTransform"] = GafferImage.ImageTransform()
			self["HorizTransform"]["in"].setInput( self["in"] )
			self["HorizTransform"]["transform"]["translate"].setValue( imath.V2f( 1, 0 ) )

			self["HorizDiff"] = GafferImage.Merge()
			self["HorizDiff"]["in"]["in0"].setInput( self["HorizTransform"]["out"] )
			self["HorizDiff"]["in"]["in1"].setInput( self["in"] )
			self["HorizDiff"]["operation"].setValue( 10 )

			self["VertTransform"] = GafferImage.ImageTransform()
			self["VertTransform"]["in"].setInput( self["in"] )
			self["VertTransform"]["transform"]["translate"].setValue( imath.V2f( 0, 1 ) )

			self["VertDiff"] = GafferImage.Merge()
			self["VertDiff"]["in"]["in0"].setInput( self["VertTransform"]["out"] )
			self["VertDiff"]["in"]["in1"].setInput( self["in"] )
			self["VertDiff"]["operation"].setValue( 10 )

			self["Max"] = GafferImage.Merge()
			self["Max"]["in"]["in0"].setInput( self["HorizDiff"]["out"] )
			self["Max"]["in"]["in1"].setInput( self["VertDiff"]["out"] )
			self["Max"]["operation"].setValue( 13 )

			self["out"].setInput( self["Max"]["out"] )
			self["out"].setFlags( Gaffer.Plug.Flags.Serialisable, False )

	def testManyImages( self ):

		allFilter = GafferScene.PathFilter()
		allFilter["paths"].setValue( IECore.StringVectorData( [ '/...' ] ) )

		sphere = GafferScene.Sphere()
		sphere["transform"]["translate"].setValue( imath.V3f( -3, 0, 0 ) )

		standardSurface = GafferArnold.ArnoldShader()
		standardSurface.loadShader( "standard_surface" )

		shaderAssignment = GafferScene.ShaderAssignment()
		shaderAssignment["in"].setInput( sphere["out"] )
		shaderAssignment["filter"].setInput( allFilter["out"] )
		shaderAssignment["shader"].setInput( standardSurface["out"] )

		uvScaleCode = GafferOSL.OSLCode()
		uvScaleCode["out"].addChild( Gaffer.V3fPlug( "uvScaled", direction = Gaffer.Plug.Direction.Out ) )
		uvScaleCode["code"].setValue( 'uvScaled = vector( u * 2, v * 2, 0 );' )

		outUV = GafferOSL.OSLShader()
		outUV.loadShader( "ObjectProcessing/OutUV" )
		outUV["parameters"]["value"].setInput( uvScaleCode["out"]["uvScaled"] )

		outObject2 = GafferOSL.OSLShader()
		outObject2.loadShader( "ObjectProcessing/OutObject" )
		outObject2["parameters"]["in0"].setInput( outUV["out"]["primitiveVariable"] )

		uvScaleOSL = GafferOSL.OSLObject()
		uvScaleOSL["in"].setInput( shaderAssignment["out"] )
		uvScaleOSL["filter"].setInput( allFilter["out"] )
		uvScaleOSL["shader"].setInput( outObject2["out"]["out"] )
		uvScaleOSL["interpolation"].setValue( 5 )


		mapOffset = GafferScene.MapOffset()
		mapOffset["in"].setInput( uvScaleOSL["out"] )
		mapOffset["filter"].setInput( allFilter["out"] )
		mapOffset["udim"].setValue( 1033 )

		offsetGroup = GafferScene.Group()
		offsetGroup["in"]["in0"].setInput( mapOffset["out"] )
		offsetGroup["name"].setValue( 'offset' )
		offsetGroup["transform"]["translate"].setValue( imath.V3f( 6, 0, 3 ) )

		combineGroup = GafferScene.Group()
		combineGroup["in"]["in0"].setInput( uvScaleOSL["out"] )
		combineGroup["in"]["in1"].setInput( offsetGroup["out"] )

		lights = []
		for color, rotate in [
			( ( 1, 0, 0 ), ( 0, 0, 0) ),
			( ( 0, 1, 0 ), ( 0, 90, 0 ) ),
			( ( 0, 0, 1 ), ( -90, 0, 0 ) )
		] :
			light = GafferArnold.ArnoldLight()
			light.loadShader( "distant_light" )
			light["parameters"]["color"].setValue( imath.Color3f( *color ) )
			light["transform"]["rotate"].setValue( imath.V3f( *rotate ) )
			combineGroup["in"][-1].setInput( light["out"] )
			lights.append( light )

		arnoldTextureBake = GafferArnold.ArnoldTextureBake()
		arnoldTextureBake["in"].setInput( combineGroup["out"] )
		arnoldTextureBake["filter"].setInput( allFilter["out"] )
		arnoldTextureBake["bakeDirectory"].setValue( self.temporaryDirectory() + '/bakeSpheres/' )
		arnoldTextureBake["defaultResolution"].setValue( 32 )
		arnoldTextureBake["aovs"].setValue( 'beauty:RGBA diffuse:diffuse' )
		arnoldTextureBake["tasks"].setValue( 3 )
		arnoldTextureBake["cleanupIntermediateFiles"].setValue( True )

		# Dispatch the bake
		script = Gaffer.ScriptNode()
		script.addChild( arnoldTextureBake )
		dispatcher = GafferDispatch.LocalDispatcher()
		dispatcher["jobsDirectory"].setValue( self.temporaryDirectory() )
		dispatcher.dispatch( [ arnoldTextureBake ] )

		# Test that we are writing all expected files, and that we have cleaned up all temp files
		expectedUdims = [ i + j for j in [ 1001, 1033 ] for i in [ 0, 1, 10, 11 ] ]
		self.assertEqual( sorted( os.listdir( self.temporaryDirectory() + '/bakeSpheres/' ) ),
			[ "beauty", "diffuse" ] )
		self.assertEqual( sorted( os.listdir( self.temporaryDirectory() + '/bakeSpheres/beauty' ) ),
			[ "beauty.%i.tx"%i for i in expectedUdims ] )
		self.assertEqual( sorted( os.listdir( self.temporaryDirectory() + '/bakeSpheres/diffuse' ) ),
			[ "diffuse.%i.tx"%i for i in expectedUdims ] )


		# Read back in the 4 udim tiles of a sphere

		reader = GafferImage.ImageReader()

		imageTransform = GafferImage.ImageTransform()
		imageTransform["in"].setInput( reader["out"] )

		exprBox = Gaffer.Box()
		expression = Gaffer.Expression()
		exprBox.addChild( reader )
		exprBox.addChild( imageTransform )
		exprBox.addChild( expression )
		expression.setExpression( inspect.cleandoc(
			"""
			i = context.get( "loop:index", 0 )
			layer = context.get( "collect:layerName", "beauty" )
			x = i % 2
			y = i // 2
			parent["ImageReader"]["fileName"] = '""" + self.temporaryDirectory() + """/bakeSpheres/%s/%s.%i.tx' % ( layer, layer, 1001 + x + y * 10 )

			parent["ImageTransform"]["transform"]["translate"] = imath.V2f( 32 * x, 32 * y )
			"""
		), "python" )

		udimLoop = Gaffer.Loop()
		udimLoop.setup( GafferImage.ImagePlug() )
		udimLoop["iterations"].setValue( 4 )

		udimMerge = GafferImage.Merge()
		udimMerge["in"]["in0"].setInput( imageTransform["out"] )
		udimMerge["in"]["in1"].setInput( udimLoop["previous"] )

		udimLoop["next"].setInput( udimMerge["out"] )

		aovCollect = GafferImage.CollectImages()
		aovCollect["in"].setInput( udimLoop["out"] )
		aovCollect["rootLayers"].setValue( IECore.StringVectorData( [ 'beauty', 'diffuse' ] ) )


		# We have a little reference image for how the diffuse should look
		imageReaderRef = GafferImage.ImageReader()
		imageReaderRef["fileName"].setValue( os.path.dirname( __file__ ) + "/images/sphereLightBake.exr" )

		resizeRef = GafferImage.Resize()
		resizeRef["in"].setInput( imageReaderRef["out"] )
		resizeRef["format"].setValue( GafferImage.Format( 64, 64, 1.000 ) )

		shuffleRef = GafferImage.Shuffle()
		shuffleRef["in"].setInput( resizeRef["out"] )
		for layer in [ "beauty", "diffuse" ]:
			for channel in [ "R", "G", "B" ]:
				shuffleRef["channels"].addChild( GafferImage.Shuffle.ChannelPlug() )
				shuffleRef["channels"][-1]["in"].setValue( channel )
				shuffleRef["channels"][-1]["out"].setValue( layer + "." + channel )

		differenceMerge = GafferImage.Merge()
		differenceMerge["in"]["in0"].setInput( aovCollect["out"] )
		differenceMerge["in"]["in1"].setInput( shuffleRef["out"] )
		differenceMerge["operation"].setValue( GafferImage.Merge.Operation.Difference )

		stats = GafferImage.ImageStats()
		stats["in"].setInput( differenceMerge["out"] )
		stats["area"].setValue( imath.Box2i( imath.V2i( 0, 0 ), imath.V2i( 64, 64 ) ) )

		# We should get a very close match to our single tile low res reference bake
		stats["channels"].setValue( IECore.StringVectorData( [ 'diffuse.R', 'diffuse.G', 'diffuse.B', 'diffuse.A' ] ) )
		for i in range( 3 ):
			self.assertLess( stats["average"].getValue()[i], 0.002 )
			self.assertLess( stats["max"].getValue()[i], 0.02 )

		# The beauty should be mostly a close match, but with a high max difference due to the spec pings
		stats["channels"].setValue( IECore.StringVectorData( [ 'beauty.R', 'beauty.G', 'beauty.B', 'beauty.A' ] ) )
		for i in range( 3 ):
			self.assertLess( stats["average"].getValue()[i], 0.1 )
			self.assertGreater( stats["max"].getValue()[i], 0.3 )

	def testTasks( self ):

		allFilter = GafferScene.PathFilter()
		allFilter["paths"].setValue( IECore.StringVectorData( [ '/...' ] ) )

		sphere = GafferScene.Sphere()
		sphere["transform"]["translate"].setValue( imath.V3f( -3, 0, 0 ) )

		uvScaleCode = GafferOSL.OSLCode()
		uvScaleCode["out"].addChild( Gaffer.V3fPlug( "uvScaled", direction = Gaffer.Plug.Direction.Out ) )
		uvScaleCode["code"].setValue( 'uvScaled = vector( u * 2, v * 2, 0 );' )

		outUV = GafferOSL.OSLShader()
		outUV.loadShader( "ObjectProcessing/OutUV" )
		outUV["parameters"]["value"].setInput( uvScaleCode["out"]["uvScaled"] )

		outObject2 = GafferOSL.OSLShader()
		outObject2.loadShader( "ObjectProcessing/OutObject" )
		outObject2["parameters"]["in0"].setInput( outUV["out"]["primitiveVariable"] )

		uvScaleOSL = GafferOSL.OSLObject()
		uvScaleOSL["in"].setInput( sphere["out"] )
		uvScaleOSL["filter"].setInput( allFilter["out"] )
		uvScaleOSL["shader"].setInput( outObject2["out"]["out"] )
		uvScaleOSL["interpolation"].setValue( 5 )

		mapOffset = GafferScene.MapOffset()
		mapOffset["in"].setInput( uvScaleOSL["out"] )
		mapOffset["filter"].setInput( allFilter["out"] )
		mapOffset["udim"].setValue( 1033 )

		offsetGroup = GafferScene.Group()
		offsetGroup["in"]["in0"].setInput( mapOffset["out"] )
		offsetGroup["name"].setValue( 'offset' )
		offsetGroup["transform"]["translate"].setValue( imath.V3f( 6, 0, 3 ) )

		combineGroup = GafferScene.Group()
		combineGroup["in"]["in0"].setInput( uvScaleOSL["out"] )
		combineGroup["in"]["in1"].setInput( offsetGroup["out"] )

		arnoldTextureBake = GafferArnold.ArnoldTextureBake()
		arnoldTextureBake["in"].setInput( combineGroup["out"] )
		arnoldTextureBake["filter"].setInput( allFilter["out"] )
		arnoldTextureBake["bakeDirectory"].setValue( self.temporaryDirectory() + '/bakeSpheres/' )
		arnoldTextureBake["defaultResolution"].setValue( 1 )
		arnoldTextureBake["aovs"].setValue( 'beauty:RGBA diffuse:diffuse' )
		arnoldTextureBake["tasks"].setValue( 3 )
		arnoldTextureBake["cleanupIntermediateFiles"].setValue( False )

		# Dispatch the bake
		script = Gaffer.ScriptNode()
		script.addChild( arnoldTextureBake )
		dispatcher = GafferDispatch.LocalDispatcher()
		dispatcher["jobsDirectory"].setValue( self.temporaryDirectory() )
		dispatcher.dispatch( [ arnoldTextureBake ] )

		self.assertEqual( sorted( os.listdir( self.temporaryDirectory() + '/bakeSpheres/' ) ),
			[ "BAKE_FILE_INDEX_0.0001.txt", "BAKE_FILE_INDEX_1.0001.txt", "BAKE_FILE_INDEX_2.0001.txt", "beauty", "diffuse" ] )
		# Make sure the 16 images that need writing get divided into very approximate thirds
		for i in range( 3 ):
			l = len( open( self.temporaryDirectory() + '/bakeSpheres/BAKE_FILE_INDEX_%i.0001.txt'%i ).readlines() )
			self.assertGreater( l, 2 )
			self.assertLess( l, 8 )

	@unittest.skipIf( GafferTest.inCI() or os.environ.get( "ARNOLD_LICENSE_ORDER" ) == "none", "Arnold license not available" )
	def testMerging( self ):

		allFilter = GafferScene.PathFilter()
		allFilter["paths"].setValue( IECore.StringVectorData( [ '/...' ] ) )

		plane = GafferScene.Plane()
		plane["divisions"].setValue( imath.V2i( 20, 20 ) )

		# Assign a basic gradient shader
		uvGradientCode = GafferOSL.OSLCode()
		uvGradientCode["out"].addChild( Gaffer.Color3fPlug( "out", direction = Gaffer.Plug.Direction.Out ) )
		uvGradientCode["code"].setValue( 'out = color( u, v, 0.5 );' )

		shaderAssignment = GafferScene.ShaderAssignment()
		shaderAssignment["in"].setInput( plane["out"] )
		shaderAssignment["filter"].setInput( allFilter["out"] )
		shaderAssignment["shader"].setInput( uvGradientCode["out"]["out"] )

		# Set up a random id from 0 - 3 on each face

		randomCode = GafferOSL.OSLCode()
		randomCode["out"].addChild( Gaffer.IntPlug( "randomId", direction = Gaffer.Plug.Direction.Out ) )
		randomCode["code"].setValue( 'randomId = int(cellnoise( P * 100 ) * 4);' )

		outInt = GafferOSL.OSLShader()
		outInt.loadShader( "ObjectProcessing/OutInt" )
		outInt["parameters"]["name"].setValue( 'randomId' )
		outInt["parameters"]["value"].setInput( randomCode["out"]["randomId"] )

		outObject = GafferOSL.OSLShader()
		outObject.loadShader( "ObjectProcessing/OutObject" )
		outObject["parameters"]["in0"].setInput( outInt["out"]["primitiveVariable"] )

		oSLObject = GafferOSL.OSLObject()
		oSLObject["in"].setInput( shaderAssignment["out"] )
		oSLObject["filter"].setInput( allFilter["out"] )
		oSLObject["shader"].setInput( outObject["out"]["out"] )
		oSLObject["interpolation"].setValue( 2 )

		# Create 4 meshes by picking each of the 4 ids

		deleteContextVariables = Gaffer.DeleteContextVariables()
		deleteContextVariables.setup( GafferScene.ScenePlug() )
		deleteContextVariables["variables"].setValue( 'collect:rootName' )
		deleteContextVariables["in"].setInput( oSLObject["out"] )

		pickCode = GafferOSL.OSLCode()
		pickCode["parameters"].addChild( Gaffer.IntPlug( "targetId" ) )
		pickCode["out"].addChild( Gaffer.IntPlug( "cull", direction = Gaffer.Plug.Direction.Out ) )
		pickCode["code"].setValue( 'int randomId; getattribute( "randomId", randomId ); cull = randomId != targetId;' )

		expression = Gaffer.Expression()
		pickCode.addChild( expression )
		expression.setExpression( 'parent.parameters.targetId = stoi( context( "collect:rootName", "0" ) );', "OSL" )

		outInt1 = GafferOSL.OSLShader()
		outInt1.loadShader( "ObjectProcessing/OutInt" )
		outInt1["parameters"]["name"].setValue( 'deleteFaces' )
		outInt1["parameters"]["value"].setInput( pickCode["out"]["cull"] )

		outObject1 = GafferOSL.OSLShader()
		outObject1.loadShader( "ObjectProcessing/OutObject" )
		outObject1["parameters"]["in0"].setInput( outInt1["out"]["primitiveVariable"] )

		oSLObject1 = GafferOSL.OSLObject()
		oSLObject1["in"].setInput( deleteContextVariables["out"] )
		oSLObject1["filter"].setInput( allFilter["out"] )
		oSLObject1["shader"].setInput( outObject1["out"]["out"] )
		oSLObject1["interpolation"].setValue( 2 )

		deleteFaces = GafferScene.DeleteFaces()
		deleteFaces["in"].setInput( oSLObject1["out"] )
		deleteFaces["filter"].setInput( allFilter["out"] )

		collectScenes = GafferScene.CollectScenes()
		collectScenes["in"].setInput( deleteFaces["out"] )
		collectScenes["rootNames"].setValue( IECore.StringVectorData( [ '0', '1', '2', '3' ] ) )
		collectScenes["sourceRoot"].setValue( '/plane' )

		# First variant:  bake everything, covering the whole 1001 UDIM

		customAttributes1 = GafferScene.CustomAttributes()
		customAttributes1["attributes"].addChild( Gaffer.NameValuePlug( 'bake:fileName', IECore.StringData( '${bakeDirectory}/complete/<AOV>/<AOV>.<UDIM>.exr' ) ) )
		customAttributes1["in"].setInput( collectScenes["out"] )

		# Second vaiant: bake just 2 of the 4 meshes, leaving lots of holes that will need filling
		pruneFilter = GafferScene.PathFilter()
		pruneFilter["paths"].setValue( IECore.StringVectorData( [ '/2', '/3' ] ) )

		prune = GafferScene.Prune()
		prune["in"].setInput( collectScenes["out"] )
		prune["filter"].setInput( pruneFilter["out"] )


		customAttributes2 = GafferScene.CustomAttributes()
		customAttributes2["attributes"].addChild( Gaffer.NameValuePlug( 'bake:fileName', IECore.StringData( '${bakeDirectory}/incomplete/<AOV>/<AOV>.<UDIM>.exr' ) ) )
		customAttributes2["in"].setInput( prune["out"] )


		# Third variant: bake everything, but with one mesh at a higher resolution

		customAttributes3 = GafferScene.CustomAttributes()
		customAttributes3["attributes"].addChild( Gaffer.NameValuePlug( 'bake:fileName', IECore.StringData( '${bakeDirectory}/mismatch/<AOV>/<AOV>.<UDIM>.exr' ) ) )
		customAttributes3["in"].setInput( collectScenes["out"] )

		pathFilter2 = GafferScene.PathFilter()
		pathFilter2["paths"].setValue( IECore.StringVectorData( [ '/2' ] ) )

		customAttributes = GafferScene.CustomAttributes()
		customAttributes["attributes"].addChild( Gaffer.NameValuePlug( 'bake:resolution', IECore.IntData( 200 ) ) )
		customAttributes["filter"].setInput( pathFilter2["out"] )
		customAttributes["in"].setInput( customAttributes3["out"] )

		# Merge the 3 variants
		mergeGroup = GafferScene.Group()
		mergeGroup["in"][-1].setInput( customAttributes["out"] )
		mergeGroup["in"][-1].setInput( customAttributes1["out"] )
		mergeGroup["in"][-1].setInput( customAttributes2["out"] )

		arnoldTextureBake = GafferArnold.ArnoldTextureBake()
		arnoldTextureBake["in"].setInput( mergeGroup["out"] )
		arnoldTextureBake["filter"].setInput( allFilter["out"] )
		arnoldTextureBake["bakeDirectory"].setValue( self.temporaryDirectory() + '/bakeMerge/' )
		arnoldTextureBake["defaultResolution"].setValue( 128 )

		# We want to check the intermediate results
		arnoldTextureBake["cleanupIntermediateFiles"].setValue( False )

		# Dispatch the bake
		script = Gaffer.ScriptNode()
		script.addChild( arnoldTextureBake )
		dispatcher = GafferDispatch.LocalDispatcher()
		dispatcher["jobsDirectory"].setValue( self.temporaryDirectory() )
		dispatcher.dispatch( [ arnoldTextureBake ] )

		# Check results
		imageReader = GafferImage.ImageReader()

		outLayer = GafferOSL.OSLShader()
		outLayer.loadShader( "ImageProcessing/OutLayer" )
		outLayer["parameters"]["layerColor"].setInput( uvGradientCode["out"]["out"] )

		outImage = GafferOSL.OSLShader()
		outImage.loadShader( "ImageProcessing/OutImage" )
		outImage["parameters"]["in0"].setInput( outLayer["out"]["layer"] )
		oSLImage = GafferOSL.OSLImage()
		oSLImage["in"].setInput( imageReader["out"] )
		oSLImage["shader"].setInput( outImage["out"]["out"] )

		merge3 = GafferImage.Merge()
		merge3["in"]["in0"].setInput( oSLImage["out"] )
		merge3["in"]["in1"].setInput( imageReader["out"] )
		merge3["operation"].setValue( 10 )

		edgeDetect = self.SimpleEdgeDetect()
		edgeDetect["in"].setInput( imageReader["out"] )

		edgeStats = GafferImage.ImageStats()
		edgeStats["in"].setInput( edgeDetect["out"] )

		refDiffStats = GafferImage.ImageStats()
		refDiffStats["in"].setInput( merge3["out"] )

		oneLayerReader = GafferImage.ImageReader()

		grade = GafferImage.Grade()
		grade["in"].setInput( oneLayerReader["out"] )
		grade["channels"].setValue( '[A]' )
		grade["blackPoint"].setValue( imath.Color4f( 0, 0, 0, 0.999899983 ) )

		copyChannels = GafferImage.CopyChannels()
		copyChannels["in"]["in0"].setInput( merge3["out"] )
		copyChannels["in"]["in1"].setInput( grade["out"] )
		copyChannels["channels"].setValue( '[A]' )

		premultiply = GafferImage.Premultiply()
		premultiply["in"].setInput( copyChannels["out"] )

		refDiffCoveredStats = GafferImage.ImageStats()
		refDiffCoveredStats["in"].setInput( premultiply["out"] )

		# We are testing 3 different cases:
		# complete : Should be an exact match.
		# incomplete : Expect some mild variance of slopes and some error, because we have to
		#              reconstruct a lot of missing data.
		# mismatch : We should get a larger image, sized to the highest override on any mesh.
		#            Match won't be as perfect, because we're combining source images at
		#            different resolutions
		for name, expectedSize, maxEdge, maxRefDiff, maxMaskedDiff in [
				( "complete",   128, 0.01, 0.000001, 0.000001 ),
				( "incomplete", 128, 0.05,     0.15, 0.000001 ),
				( "mismatch",   200, 0.01,     0.01,     0.01 ) ]:
			imageReader["fileName"].setValue( self.temporaryDirectory() + "/bakeMerge/" + name + "/beauty/beauty.1001.tx"  )
			oneLayerReader["fileName"].setValue( self.temporaryDirectory() + "/bakeMerge/" + name + "/beauty/beauty.1001.exr"  )

			self.assertEqual( imageReader["out"]["format"].getValue().width(), expectedSize )
			self.assertEqual( imageReader["out"]["format"].getValue().height(), expectedSize )

			edgeStats["area"].setValue( imath.Box2i( imath.V2i( 1 ), imath.V2i( expectedSize - 1 ) ) )
			refDiffStats["area"].setValue( imath.Box2i( imath.V2i( 1 ), imath.V2i( expectedSize - 1 ) ) )
			refDiffCoveredStats["area"].setValue( imath.Box2i( imath.V2i( 0 ), imath.V2i( expectedSize ) ) )

			# Blue channel is constant, so everything should line up perfectly
			self.assertEqual( 0, edgeStats["max"].getValue()[2] )
			self.assertEqual( 0, refDiffStats["max"].getValue()[2] )
			self.assertEqual( 0, refDiffCoveredStats["max"].getValue()[2] )

			for i in range(2):

				# Make sure we've got actual data, by checking that we have some error ( we're not expecting
				# to perfectly reconstruct the gradient when the input is incomplete )
				self.assertGreater( edgeStats["max"].getValue()[i], 0.005 )
				if name == "incomplete":
					self.assertGreater( edgeStats["max"].getValue()[i], 0.03 )
					self.assertGreater( refDiffStats["max"].getValue()[i], 0.06 )

				self.assertLess( edgeStats["max"].getValue()[i], maxEdge )
				self.assertLess( refDiffStats["max"].getValue()[i], maxRefDiff )
				self.assertLess( refDiffCoveredStats["max"].getValue()[i], maxMaskedDiff )
Example #8
0
        self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(1)),
                             ["/group/plane"])

        sg.setSelectionMask(IECore.StringVectorData(["Camera"]))

        self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(1)),
                             ["/group/camera"])

    def setUp(self):

        GafferUITest.TestCase.setUp(self)

        self.__cachedConverterMaxMemory = IECoreGL.CachedConverter.defaultCachedConverter(
        ).getMaxMemory()

    def tearDown(self):

        GafferUITest.TestCase.tearDown(self)

        IECoreGL.CachedConverter.defaultCachedConverter().setMaxMemory(
            self.__cachedConverterMaxMemory)


if GafferTest.inCI({'travis'}):
    SceneGadgetTest.testExpansion = unittest.expectedFailure(
        SceneGadgetTest.testExpansion)

if __name__ == "__main__":
    unittest.main()
Example #9
0
class SerialisationTest(GafferTest.TestCase):
    class SerialisationTestNode(Gaffer.Node):
        def __init__(self, name="SerialisationTestNode", initArgument=10):

            Gaffer.Node.__init__(self, name)

            self.initArgument = initArgument
            self.needsAdditionalModules = False

            self["childNodeNeedingSerialisation"] = GafferTest.AddNode()
            self["childNodeNotNeedingSerialisation"] = GafferTest.AddNode()

    IECore.registerRunTimeTyped(SerialisationTestNode)

    def testCustomSerialiser(self):
        class CustomSerialiser(Gaffer.NodeSerialiser):
            def moduleDependencies(self, node, serialisation):

                return {"GafferTest"
                        } | Gaffer.NodeSerialiser.moduleDependencies(
                            self, node, serialisation)

            def constructor(self, node, serialisation):

                if node.needsAdditionalModules:
                    serialisation.addModule("ConstructorModule")
                return (
                    "GafferTest.SerialisationTest.SerialisationTestNode( \"%s\", %d )"
                    % (node.getName(), node.initArgument))

            def postConstructor(self, node, identifier, serialisation):

                result = Gaffer.NodeSerialiser.postConstructor(
                    self, node, identifier, serialisation)
                result += identifier + ".postConstructorWasHere = True\n"
                if node.needsAdditionalModules:
                    serialisation.addModule("PostConstructorModule")
                return result

            def postHierarchy(self, node, identifier, serialisation):

                result = Gaffer.NodeSerialiser.postHierarchy(
                    self, node, identifier, serialisation)
                result += identifier + ".postHierarchyWasHere = True\n"
                if node.needsAdditionalModules:
                    serialisation.addModule("PostHierarchyModule")
                return result

            def postScript(self, node, identifier, serialisation):

                result = Gaffer.NodeSerialiser.postScript(
                    self, node, identifier, serialisation)
                result += identifier + ".postScriptWasHere = True\n"
                if node.needsAdditionalModules:
                    serialisation.addModule("PostScriptModule")
                return result

            def childNeedsSerialisation(self, child, serialisation):

                if isinstance(child, Gaffer.Node) and child.getName(
                ) == "childNodeNeedingSerialisation":
                    return True

                return Gaffer.NodeSerialiser.childNeedsSerialisation(
                    self, child, serialisation)

            def childNeedsConstruction(self, child, serialisation):

                if isinstance(child, Gaffer.Node):
                    return False

                return Gaffer.NodeSerialiser.childNeedsConstruction(
                    self, child, serialisation)

        customSerialiser = CustomSerialiser()
        Gaffer.Serialisation.registerSerialiser(self.SerialisationTestNode,
                                                customSerialiser)

        s = Gaffer.ScriptNode()
        s["n"] = self.SerialisationTestNode("a", initArgument=20)
        s["n"]["childNodeNeedingSerialisation"]["op1"].setValue(101)
        s["n"]["childNodeNotNeedingSerialisation"]["op1"].setValue(101)
        s["n"]["dynamicPlug"] = Gaffer.FloatPlug(
            defaultValue=10,
            flags=Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic)

        self.assertTrue(
            Gaffer.Serialisation.acquireSerialiser(
                s["n"]).isSame(customSerialiser))

        s2 = Gaffer.ScriptNode()
        s2.execute(s.serialise())

        self.assertTrue(isinstance(s2["n"], self.SerialisationTestNode))
        self.assertEqual(s["n"].keys(), s2["n"].keys())

        self.assertEqual(s2["n"].initArgument, 20)
        self.assertEqual(
            s2["n"]["childNodeNeedingSerialisation"]["op1"].getValue(), 101)
        self.assertEqual(
            s2["n"]["childNodeNotNeedingSerialisation"]["op1"].getValue(), 0)
        self.assertEqual(s2["n"]["dynamicPlug"].getValue(), 10)
        self.assertEqual(s2["n"].postConstructorWasHere, True)
        self.assertEqual(s2["n"].postHierarchyWasHere, True)
        self.assertEqual(s2["n"].postScriptWasHere, True)

        # Test calls to `Serialisation.addModule()`

        s["n"].needsAdditionalModules = True
        ss = s.serialise()
        self.assertIn("import ConstructorModule", ss)
        self.assertIn("import PostConstructorModule", ss)
        self.assertIn("import PostHierarchyModule", ss)
        self.assertIn("import PostScriptModule", ss)

    def testParentAccessor(self):

        n = Gaffer.Node()
        s = Gaffer.Serialisation(n)
        self.assertTrue(s.parent().isSame(n))

    def testClassPath(self):

        self.assertEqual(Gaffer.Serialisation.classPath(Gaffer.Node()),
                         "Gaffer.Node")
        self.assertEqual(Gaffer.Serialisation.classPath(Gaffer.Node),
                         "Gaffer.Node")

        self.assertEqual(Gaffer.Serialisation.classPath(GafferTest.AddNode()),
                         "GafferTest.AddNode")
        self.assertEqual(Gaffer.Serialisation.classPath(GafferTest.AddNode),
                         "GafferTest.AddNode")

    def testModulePath(self):

        self.assertEqual(Gaffer.Serialisation.modulePath(Gaffer.Node()),
                         "Gaffer")
        self.assertEqual(Gaffer.Serialisation.modulePath(Gaffer.Node),
                         "Gaffer")

        self.assertEqual(Gaffer.Serialisation.modulePath(GafferTest.AddNode()),
                         "GafferTest")
        self.assertEqual(Gaffer.Serialisation.modulePath(GafferTest.AddNode),
                         "GafferTest")

    def testIncludeParentMetadataWhenExcludingChildren(self):

        n1 = Gaffer.Node()
        Gaffer.Metadata.registerValue(n1, "test", imath.Color3f(1, 2, 3))

        with Gaffer.Context() as c:
            c["serialiser:includeParentMetadata"] = IECore.BoolData(True)
            s = Gaffer.Serialisation(n1, filter=Gaffer.StandardSet())

        scope = {"parent": Gaffer.Node()}
        exec(s.result(), scope, scope)

        self.assertEqual(Gaffer.Metadata.value(scope["parent"], "test"),
                         imath.Color3f(1, 2, 3))

    class Outer(object):
        class Inner(object):

            # Emulate feature coming in Python 3.
            # See https://www.python.org/dev/peps/pep-3155/
            __qualname__ = "Outer.Inner"

    def testClassPathForNestedClasses(self):

        self.assertEqual(Gaffer.Serialisation.classPath(self.Outer.Inner),
                         "GafferTest.SerialisationTest.Outer.Inner")

    def testVersionMetadata(self):

        n = Gaffer.Node()
        serialisationWithMetadata = Gaffer.Serialisation(n).result()

        with Gaffer.Context() as c:
            c["serialiser:includeVersionMetadata"] = IECore.BoolData(False)
            serialisationWithoutMetadata = Gaffer.Serialisation(n).result()

        scope = {"parent": Gaffer.Node()}
        exec(serialisationWithMetadata, scope, scope)

        self.assertEqual(
            Gaffer.Metadata.value(scope["parent"],
                                  "serialiser:milestoneVersion"),
            Gaffer.About.milestoneVersion())
        self.assertEqual(
            Gaffer.Metadata.value(scope["parent"], "serialiser:majorVersion"),
            Gaffer.About.majorVersion())
        self.assertEqual(
            Gaffer.Metadata.value(scope["parent"], "serialiser:minorVersion"),
            Gaffer.About.minorVersion())
        self.assertEqual(
            Gaffer.Metadata.value(scope["parent"], "serialiser:patchVersion"),
            Gaffer.About.patchVersion())

        scope = {"parent": Gaffer.Node()}
        exec(serialisationWithoutMetadata, scope, scope)

        self.assertEqual(
            Gaffer.Metadata.value(scope["parent"],
                                  "serialiser:milestoneVersion"), None)
        self.assertEqual(
            Gaffer.Metadata.value(scope["parent"], "serialiser:majorVersion"),
            None)
        self.assertEqual(
            Gaffer.Metadata.value(scope["parent"], "serialiser:minorVersion"),
            None)
        self.assertEqual(
            Gaffer.Metadata.value(scope["parent"], "serialiser:patchVersion"),
            None)

    def testProtectParentNamespace(self):

        n = Gaffer.Node()
        n["a"] = GafferTest.AddNode()
        n["b"] = GafferTest.AddNode()
        n["b"]["op1"].setInput(n["a"]["sum"])

        serialisationWithProtection = Gaffer.Serialisation(n).result()

        with Gaffer.Context() as c:
            c["serialiser:protectParentNamespace"] = IECore.BoolData(False)
            serialisationWithoutProtection = Gaffer.Serialisation(n).result()

        scope = {"parent": Gaffer.Node()}
        scope["parent"]["a"] = Gaffer.StringPlug()
        exec(serialisationWithProtection, scope, scope)

        self.assertIsInstance(scope["parent"]["a"], Gaffer.StringPlug)
        self.assertIn("a1", scope["parent"])
        self.assertEqual(scope["parent"]["b"]["op1"].getInput(),
                         scope["parent"]["a1"]["sum"])

        scope = {"parent": Gaffer.Node()}
        scope["parent"]["a"] = Gaffer.StringPlug()
        exec(serialisationWithoutProtection, scope, scope)

        self.assertIsInstance(scope["parent"]["a"], GafferTest.AddNode)
        self.assertNotIn("a1", scope["parent"])
        self.assertEqual(scope["parent"]["b"]["op1"].getInput(),
                         scope["parent"]["a"]["sum"])

    def testChildIdentifier(self):

        node = Gaffer.Node()
        node["a"] = GafferTest.AddNode()
        node["b"] = GafferTest.AddNode()

        filter = Gaffer.StandardSet([node["a"]])
        serialisation = Gaffer.Serialisation(node, filter=filter)

        aIdentifier = serialisation.identifier(node["a"])
        self.assertEqual(
            serialisation.identifier(node["a"]["op1"]),
            serialisation.childIdentifier(aIdentifier, node["a"]["op1"]))

        bIdentifier = serialisation.identifier(node["b"])
        self.assertEqual(bIdentifier, "")
        self.assertEqual(
            serialisation.childIdentifier(bIdentifier, node["b"]["op1"]), "")

    def testBase64ObjectConversions(self):

        # Test StringData with all possible byte values (except 0,
        # because we can't construct a StringData with a null at the start).
        allBytes = bytes().join([six.int2byte(i) for i in range(1, 256)])
        for i in range(0, len(allBytes)):
            o = IECore.StringData(allBytes[:i])
            b = Gaffer.Serialisation.objectToBase64(o)
            self.assertEqual(Gaffer.Serialisation.objectFromBase64(b), o)

        # Test CompoundData
        o = IECore.CompoundData({
            "a":
            10,
            "b":
            20,
            "c":
            IECore.CompoundData({"d": imath.V3f(1, 2, 3)})
        })
        b = Gaffer.Serialisation.objectToBase64(o)
        self.assertEqual(Gaffer.Serialisation.objectFromBase64(b), o)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=1)
    def testSwitchPerformance(self):

        script = Gaffer.ScriptNode()

        def build(maxDepth, upstreamNode=None, depth=0):

            node = Gaffer.Switch()
            node.setup(Gaffer.V3iPlug())
            if upstreamNode is not None:
                node["in"][0].setInput(upstreamNode["out"])

            script.addChild(node)

            if depth < maxDepth:
                build(maxDepth, node, depth + 1)
                build(maxDepth, node, depth + 1)

        with Gaffer.DirtyPropagationScope():
            build(13)

        with GafferTest.TestRunner.PerformanceScope():
            script.serialise()

    def testAddModule(self):

        node = Gaffer.Node()
        serialisation = Gaffer.Serialisation(node)
        serialisation.addModule("MyModule")
        serialisation.addModule("MyModule")

        self.assertEqual(serialisation.result().count("import MyModule"), 1)
class FilterResultsTest(GafferSceneTest.SceneTestCase):
    def assertExpectedOutStrings(self, node):

        self.assertEqual(
            node["outStrings"].getValue(),
            IECore.StringVectorData(node["out"].getValue().value.paths()))

    def testChangingFilter(self):

        p = GafferScene.Plane()
        s = GafferScene.Sphere()
        g = GafferScene.Group()
        g["in"][0].setInput(p["out"])
        g["in"][1].setInput(s["out"])

        f = GafferScene.PathFilter()
        f["paths"].setValue(IECore.StringVectorData(["/group/*"]))

        n = GafferScene.FilterResults()
        n["scene"].setInput(g["out"])
        n["filter"].setInput(f["out"])

        self.assertEqual(n["out"].getValue().value,
                         IECore.PathMatcher(["/group/sphere", "/group/plane"]))
        self.assertExpectedOutStrings(n)

        f["paths"].setValue(IECore.StringVectorData(["/group/p*"]))

        self.assertEqual(n["out"].getValue().value,
                         IECore.PathMatcher(["/group/plane"]))
        self.assertExpectedOutStrings(n)

    def testChangingScene(self):

        p = GafferScene.Plane()
        g = GafferScene.Group()
        g["in"][0].setInput(p["out"])

        f = GafferScene.PathFilter()
        f["paths"].setValue(IECore.StringVectorData(["/group/plain"]))

        n = GafferScene.FilterResults()
        n["scene"].setInput(g["out"])
        n["filter"].setInput(f["out"])

        self.assertEqual(n["out"].getValue().value, IECore.PathMatcher())
        self.assertExpectedOutStrings(n)

        p["name"].setValue("plain")

        self.assertEqual(n["out"].getValue().value,
                         IECore.PathMatcher(["/group/plain"]))
        self.assertExpectedOutStrings(n)

    def testDirtyPropagation(self):

        p = GafferScene.Plane()
        p["sets"].setValue("A")

        f = GafferScene.SetFilter()
        f["setExpression"].setValue("A")

        n = GafferScene.FilterResults()
        n["scene"].setInput(p["out"])
        n["filter"].setInput(f["out"])

        cs = GafferTest.CapturingSlot(n.plugDirtiedSignal())

        f["setExpression"].setValue("planeSet")
        self.assertTrue(n["out"] in {x[0] for x in cs})
        del cs[:]

        p["name"].setValue("thing")
        self.assertTrue(n["out"] in {x[0] for x in cs})

    def testOutputIntoExpression(self):

        script = Gaffer.ScriptNode()

        script["plane"] = GafferScene.Plane()

        script["sphere"] = GafferScene.Sphere()

        script["instancer"] = GafferScene.Instancer()
        script["instancer"]["in"].setInput(script["plane"]["out"])
        script["instancer"]["prototypes"].setInput(script["sphere"]["out"])
        script["instancer"]["parent"].setValue("/plane")

        script["filter"] = GafferScene.PathFilter()
        script["filter"]["paths"].setValue(
            IECore.StringVectorData(["/plane/instances/sphere/*"]))

        script["filterResults"] = GafferScene.FilterResults()
        script["filterResults"]["scene"].setInput(script["instancer"]["out"])
        script["filterResults"]["filter"].setInput(script["filter"]["out"])

        script["filterResults"]["user"][
            "strings"] = Gaffer.StringVectorDataPlug(
                defaultValue=IECore.StringVectorData())

        script["expression"] = Gaffer.Expression()
        script["expression"].setExpression(
            "parent['filterResults']['user']['strings'] = IECore.StringVectorData( sorted( parent['filterResults']['out'].value.paths() ) )"
        )

        self.assertEqual(
            script["filterResults"]["user"]["strings"].getValue(),
            IECore.StringVectorData([
                "/plane/instances/sphere/0",
                "/plane/instances/sphere/1",
                "/plane/instances/sphere/2",
                "/plane/instances/sphere/3",
            ]))

    def testComputeCacheRecursion(self):

        script = Gaffer.ScriptNode()

        script["plane"] = GafferScene.Plane()

        script["filter1"] = GafferScene.PathFilter()
        script["filter1"]["paths"].setValue(IECore.StringVectorData(["/*"]))

        script["filterResults1"] = GafferScene.FilterResults()
        script["filterResults1"]["scene"].setInput(script["plane"]["out"])
        script["filterResults1"]["filter"].setInput(script["filter1"]["out"])

        script["filter2"] = GafferScene.PathFilter()
        script["expression"] = Gaffer.Expression()
        script["expression"].setExpression(
            'parent["filter2"]["paths"] = IECore.StringVectorData( parent["filterResults1"]["out"].value.paths() )'
        )

        script["filterResults2"] = GafferScene.FilterResults()
        script["filterResults2"]["scene"].setInput(script["plane"]["out"])
        script["filterResults2"]["filter"].setInput(script["filter2"]["out"])

        h = script["filterResults2"]["out"].hash()
        Gaffer.ValuePlug.clearCache()
        script["filterResults2"]["out"].getValue(h)

    def testRoot(self):

        # /group
        #    /group
        #        /plane
        #    /plane

        plane = GafferScene.Plane()

        innerGroup = GafferScene.Group()
        innerGroup["in"][0].setInput(plane["out"])

        outerGroup = GafferScene.Group()
        outerGroup["in"][0].setInput(innerGroup["out"])
        outerGroup["in"][1].setInput(plane["out"])

        filter = GafferScene.PathFilter()
        filter["paths"].setValue(IECore.StringVectorData(["/..."]))

        filterResults = GafferScene.FilterResults()
        filterResults["scene"].setInput(outerGroup["out"])
        filterResults["filter"].setInput(filter["out"])

        self.assertEqual(
            filterResults["out"].getValue().value,
            IECore.PathMatcher([
                "/",
                "/group",
                "/group/group",
                "/group/group/plane",
                "/group/plane",
            ]))

        hash = filterResults["out"].hash()

        filterResults["root"].setValue("/group/group")
        self.assertEqual(
            filterResults["out"].getValue().value,
            IECore.PathMatcher([
                "/group/group",
                "/group/group/plane",
            ]))
        self.assertNotEqual(filterResults["out"].hash(), hash)

    def testRootMatchVsNoMatch(self):

        plane = GafferScene.Plane()
        pathFilter = GafferScene.PathFilter()

        filterResults = GafferScene.FilterResults()
        filterResults["scene"].setInput(plane["out"])
        filterResults["filter"].setInput(pathFilter["out"])
        self.assertEqual(filterResults["outStrings"].getValue(),
                         IECore.StringVectorData())

        pathFilter["paths"].setValue(IECore.StringVectorData(["/"]))
        self.assertEqual(filterResults["outStrings"].getValue(),
                         IECore.StringVectorData(["/"]))

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod()
    def testHashPerformance(self):

        sphere = GafferScene.Sphere()

        duplicate = GafferScene.Duplicate()
        duplicate["in"].setInput(sphere["out"])
        duplicate["target"].setValue('/sphere')
        duplicate["copies"].setValue(1000000)

        pathFilter = GafferScene.PathFilter()
        pathFilter["paths"].setValue(IECore.StringVectorData(['...']))

        filterResults = GafferScene.FilterResults()
        filterResults["scene"].setInput(duplicate["out"])
        filterResults["filter"].setInput(pathFilter["out"])

        # Evaluate the root childNames beforehand to focus our timing on the hash
        duplicate["out"].childNames("/")

        with GafferTest.TestRunner.PerformanceScope():
            filterResults["out"].hash()

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod()
    def testComputePerformance(self):

        sphere = GafferScene.Sphere()

        duplicate = GafferScene.Duplicate()
        duplicate["in"].setInput(sphere["out"])
        duplicate["target"].setValue('/sphere')
        duplicate["copies"].setValue(1000000)

        pathFilter = GafferScene.PathFilter()
        pathFilter["paths"].setValue(IECore.StringVectorData(['...']))

        filterResults = GafferScene.FilterResults()
        filterResults["scene"].setInput(duplicate["out"])
        filterResults["filter"].setInput(pathFilter["out"])

        # Evaluate the root childNames beforehand to focus our timing on the compute
        duplicate["out"].childNames("/")

        with GafferTest.TestRunner.PerformanceScope():
            filterResults["out"].getValue()
Example #11
0
    def testDurations(self):
        class DurationNode(Gaffer.ComputeNode):
            def __init__(self, name="DurationNode"):

                Gaffer.ComputeNode.__init__(self, name)

                self["in"] = Gaffer.FloatPlug()
                self["out"] = Gaffer.FloatPlug(
                    direction=Gaffer.Plug.Direction.Out)

                self["hashDuration"] = Gaffer.FloatPlug()
                self["computeDuration"] = Gaffer.FloatPlug()

            def affects(self, input):

                result = Gaffer.ComputeNode.affects(self, input)
                if input in (self["in"], self["hashDuration"],
                             self["computeDuration"]):
                    result.append(self["out"])

                return result

            def hash(self, output, context, h):

                if output.isSame(self["out"]):

                    self["in"].hash(h)
                    self["computeDuration"].hash(h)

                    time.sleep(self["hashDuration"].getValue())

            def compute(self, plug, context):

                if plug.isSame(self["out"]):

                    d = self["computeDuration"].getValue()
                    time.sleep(d)

                    self["out"].setValue(self["in"].getValue() + d)

                else:

                    ComputeNode.compute(self, plug, context)

        IECore.registerRunTimeTyped(DurationNode)

        n1 = DurationNode("n1")
        n1["hashDuration"].setValue(0.2)
        n1["computeDuration"].setValue(0.4)

        n2 = DurationNode("n2")
        n2["in"].setInput(n1["out"])
        n2["hashDuration"].setValue(0.1)
        n2["computeDuration"].setValue(0.2)

        with Gaffer.PerformanceMonitor() as m:

            n2["out"].getValue()

        def seconds(n):

            return n / (1000000000.0)

        self.assertEqual(len(m.allStatistics()), 2)

        # 1.0 is an excessively wide tolerance but we've seen huge variations
        # in CI due to machine contention. We're leaving the test in as it
        # would potentially still catch some catastrophic orders-of-magnitude
        # timing increase bug...
        delta = 1.0 if GafferTest.inCI() else 0.01

        self.assertEqual(m.plugStatistics(n1["out"]).hashCount, 1)
        self.assertEqual(m.plugStatistics(n1["out"]).computeCount, 1)
        self.assertAlmostEqual(seconds(
            m.plugStatistics(n1["out"]).hashDuration),
                               0.2,
                               delta=delta)
        self.assertAlmostEqual(seconds(
            m.plugStatistics(n1["out"]).computeDuration),
                               0.4,
                               delta=delta)

        self.assertEqual(m.plugStatistics(n2["out"]).hashCount, 1)
        self.assertEqual(m.plugStatistics(n2["out"]).computeCount, 1)
        self.assertAlmostEqual(seconds(
            m.plugStatistics(n2["out"]).hashDuration),
                               0.1,
                               delta=delta)
        self.assertAlmostEqual(seconds(
            m.plugStatistics(n2["out"]).computeDuration),
                               0.2,
                               delta=delta)

        with m:
            with Gaffer.Context() as c:
                c["test"] = 1  # force rehash, but not recompute
                n2["out"].getValue()

        self.assertEqual(m.plugStatistics(n1["out"]).hashCount, 2)
        self.assertEqual(m.plugStatistics(n1["out"]).computeCount, 1)
        self.assertAlmostEqual(seconds(
            m.plugStatistics(n1["out"]).hashDuration),
                               0.4,
                               delta=delta)
        self.assertAlmostEqual(seconds(
            m.plugStatistics(n1["out"]).computeDuration),
                               0.4,
                               delta=delta)

        self.assertEqual(m.plugStatistics(n2["out"]).hashCount, 2)
        self.assertEqual(m.plugStatistics(n2["out"]).computeCount, 1)
        self.assertAlmostEqual(seconds(
            m.plugStatistics(n2["out"]).hashDuration),
                               0.2,
                               delta=delta)
        self.assertAlmostEqual(seconds(
            m.plugStatistics(n2["out"]).computeDuration),
                               0.2,
                               delta=delta)
Example #12
0
import unittest
import imath

import IECore
import IECoreScene
import IECoreImage

import Gaffer
import GafferTest
import GafferScene
import GafferSceneTest
import GafferImage
import GafferArnold


@unittest.skipIf(GafferTest.inCI({'travis'}), "No license available on Travis")
class InteractiveArnoldRenderTest(GafferSceneTest.InteractiveRenderTest):
    def testTwoRenders(self):

        s = Gaffer.ScriptNode()
        s["s"] = GafferScene.Sphere()

        s["o"] = GafferScene.Outputs()
        s["o"].addOutput(
            "beauty",
            IECoreScene.Output("test", "ieDisplay", "rgba", {
                "driverType": "ImageDisplayDriver",
                "handle": "myLovelySphere",
            }))
        s["o"]["in"].setInput(s["s"]["out"])
Example #13
0
class ParallelAlgoTest(GafferTest.TestCase):

    # Context manager used to run code which is expected to generate a
    # call to `ParallelAlgo.callOnUIThread()`. This emulates the
    # UIThreadCallHandler that would otherwise be provided by GafferUI.
    class ExpectedUIThreadCall(object):

        __conditionStack = []
        __conditionStackMutex = threading.Lock()
        __callHandlerRegistered = False

        def __enter__(self):

            self.__condition = threading.Condition()
            self.__condition.toCall = None

            with self.__conditionStackMutex:
                self.__conditionStack.append(self.__condition)

            self.__registeredCallHandler = False
            if not self.__class__.__callHandlerRegistered:
                Gaffer.ParallelAlgo.pushUIThreadCallHandler(
                    self.__callOnUIThread)
                self.__class__.__callHandlerRegistered = True
                self.__registeredCallHandler = True

        def __exit__(self, type, value, traceBack):

            with self.__condition:

                while self.__condition.toCall is None:
                    self.__condition.wait()

                self.__condition.toCall()
                self.__condition.toCall = None

            if self.__registeredCallHandler:
                assert (self.__class__.__callHandlerRegistered == True)
                Gaffer.ParallelAlgo.popUIThreadCallHandler()
                self.__class__.__callHandlerRegistered = False

        @classmethod
        def __callOnUIThread(cls, f):

            with cls.__conditionStackMutex:
                condition = cls.__conditionStack.pop()

            with condition:
                assert (condition.toCall is None)
                condition.toCall = f
                condition.notify()

    def testCallOnUIThread(self):

        s = Gaffer.ScriptNode()

        def uiThreadFunction():

            s.setName("test")
            s.uiThreadId = thread.get_ident()

        with self.ExpectedUIThreadCall():

            t = threading.Thread(target=lambda: Gaffer.ParallelAlgo.
                                 callOnUIThread(uiThreadFunction))
            t.start()
            t.join()

        self.assertEqual(s.getName(), "test")
        self.assertEqual(s.uiThreadId, thread.get_ident())

    @unittest.skipIf(GafferTest.inCI(
    ), "Unknown CI issue. TODO: Investigate why we only see this in the test harness"
                     )
    def testNestedCallOnUIThread(self):

        # This is testing our `ExpectedUIThreadCall` utility
        # class more than it's testing `ParallelAlgo`.

        s = Gaffer.ScriptNode()

        def uiThreadFunction1():

            s.setName("test")
            s.uiThreadId1 = thread.get_ident()

        def uiThreadFunction2():

            s["fileName"].setValue("test")
            s.uiThreadId2 = thread.get_ident()

        with self.ExpectedUIThreadCall():

            t1 = threading.Thread(target=lambda: Gaffer.ParallelAlgo.
                                  callOnUIThread(uiThreadFunction1))
            t1.start()

            with self.ExpectedUIThreadCall():

                t2 = threading.Thread(target=lambda: Gaffer.ParallelAlgo.
                                      callOnUIThread(uiThreadFunction2))
                t2.start()

        self.assertEqual(s.getName(), "test")
        self.assertEqual(s.uiThreadId1, thread.get_ident())
        self.assertEqual(s["fileName"].getValue(), "test")
        self.assertEqual(s.uiThreadId2, thread.get_ident())

        t1.join()
        t2.join()

    def testCallOnBackgroundThread(self):

        script = Gaffer.ScriptNode()
        script["n"] = GafferTest.AddNode()

        foregroundContext = Gaffer.Context(script.context())
        foregroundContext["a"] = "a"

        def f():

            backgroundContext = Gaffer.Context.current()
            self.assertFalse(backgroundContext.isSame(foregroundContext))
            self.assertEqual(backgroundContext, foregroundContext)

            with self.assertRaises(IECore.Cancelled):
                while True:
                    script["n"]["sum"].getValue()
                    # We might expect that `script["n"]["sum"].getValue()`
                    # would be guaranteed to throw after cancellation has been
                    # requested. But that is not the case if both the hash and the
                    # value are already cached, because cancellation is only checked
                    # for automatically when a Process is constructed. So we take
                    # a belt and braces approach and perform an explicit check here.
                    #
                    # The alternative would be to move the cancellation check outside
                    # of the Process class, so it is performed before the cache lookup.
                    # This may be the better approach, but we would need to benchmark
                    # it to ensure that performance was not adversely affected. To our
                    # knowledge, this "cache hits avoid cancellation" problem has not
                    # been responsible for unresponsive cancellation in the wild, because
                    # background tasks are typically triggered by `plugDirtiedSignal()`,
                    # and the hash cache is cleared when a plug is dirtied.
                    IECore.Canceller.check(backgroundContext.canceller())

        # Explicit cancellation

        with foregroundContext:
            backgroundTask = Gaffer.ParallelAlgo.callOnBackgroundThread(
                script["n"]["sum"], f)

        backgroundTask.cancel()

        # Implicit cancellation through graph edit

        with foregroundContext:
            backgroundTask = Gaffer.ParallelAlgo.callOnBackgroundThread(
                script["n"]["sum"], f)

        script["n"]["op1"].setValue(10)

        # Cancellation through deletion

        with foregroundContext:
            backgroundTask = Gaffer.ParallelAlgo.callOnBackgroundThread(
                script["n"]["sum"], f)

        del backgroundTask

    def testBackgroundThreadMonitoring(self):

        s = Gaffer.ScriptNode()
        s["n"] = GafferTest.MultiplyNode()
        s["n"]["op2"].setValue(1)
        s["e"] = Gaffer.Expression()
        s["e"].setExpression("""parent["n"]["op1"] = context["op1"]""")

        def backgroundFunction():

            with Gaffer.Context() as c:
                for i in range(0, 10000):
                    c["op1"] = i
                    self.assertEqual(s["n"]["product"].getValue(), i)

        with Gaffer.PerformanceMonitor() as m:
            t = Gaffer.ParallelAlgo.callOnBackgroundThread(
                s["n"]["product"], backgroundFunction)
        t.wait()

        # The monitor was active when we launched the background
        # process, so we expect it to have been transferred to the
        # background thread and remained active there for the duration.
        self.assertEqual(
            m.plugStatistics(s["n"]["product"]).computeCount, 10000)
Example #14
0
class MergeTest(GafferImageTest.ImageTestCase):

    rPath = os.path.expandvars(
        "$GAFFER_ROOT/python/GafferImageTest/images/redWithDataWindow.100x100.exr"
    )
    gPath = os.path.expandvars(
        "$GAFFER_ROOT/python/GafferImageTest/images/greenWithDataWindow.100x100.exr"
    )
    bPath = os.path.expandvars(
        "$GAFFER_ROOT/python/GafferImageTest/images/blueWithDataWindow.100x100.exr"
    )
    checkerPath = os.path.expandvars(
        "$GAFFER_ROOT/python/GafferImageTest/images/checkerboard.100x100.exr")
    checkerRGBPath = os.path.expandvars(
        "$GAFFER_ROOT/python/GafferImageTest/images/rgbOverChecker.100x100.exr"
    )
    rgbPath = os.path.expandvars(
        "$GAFFER_ROOT/python/GafferImageTest/images/rgb.100x100.exr")
    mergeBoundariesRefPath = os.path.expandvars(
        "$GAFFER_ROOT/python/GafferImageTest/images/mergeBoundariesRef.exr")

    # Do several tests to check the cache is working correctly:
    def testHashes(self):

        r1 = GafferImage.ImageReader()
        r1["fileName"].setValue(self.checkerPath)

        r2 = GafferImage.ImageReader()
        r2["fileName"].setValue(self.gPath)

        ##########################################
        # Test to see if the hash changes.
        ##########################################

        merge = GafferImage.Merge()
        merge["operation"].setValue(GafferImage.Merge.Operation.Over)

        merge["in"][0].setInput(r1["out"])
        merge["in"][1].setInput(r2["out"])
        h1 = GafferImage.ImageAlgo.imageHash(merge["out"])

        # Switch the inputs.
        merge["in"][1].setInput(r1["out"])
        merge["in"][0].setInput(r2["out"])
        h2 = GafferImage.ImageAlgo.imageHash(merge["out"])

        self.assertNotEqual(h1, h2)

        ##########################################
        # Test to see if the hash remains the same
        # when the output should be the same but the
        # input plugs used are not.
        ##########################################

        merge = GafferImage.Merge()
        merge["operation"].setValue(GafferImage.Merge.Operation.Over)

        expectedHash = h1

        # Connect up a load of inputs ...
        merge["in"][0].setInput(r1["out"])
        merge["in"][1].setInput(r1["out"])
        merge["in"][2].setInput(r1["out"])
        merge["in"][3].setInput(r2["out"])

        # but then disconnect two so that the result should still be the same...
        merge["in"][1].setInput(None)
        merge["in"][2].setInput(None)
        h1 = GafferImage.ImageAlgo.imageHash(merge["out"])

        self.assertEqual(h1, expectedHash)

    # The pass through for disabled is working, but I don't see any sign that a pass through
    # for a single input was ever implemented.  ( For a long time this test was broken )
    @unittest.expectedFailure
    def testHashPassThrough(self):

        r1 = GafferImage.ImageReader()
        r1["fileName"].setValue(self.checkerPath)

        ##########################################
        # Test to see if the input hash is always passed
        # through if only the first input is connected.
        ##########################################

        merge = GafferImage.Merge()
        merge["operation"].setValue(GafferImage.Merge.Operation.Over)

        expectedHash = GafferImage.ImageAlgo.imageHash(r1["out"])
        merge["in"][0].setInput(r1["out"])
        h1 = GafferImage.ImageAlgo.imageHash(merge["out"])

        self.assertEqual(h1, expectedHash)

        ##########################################
        # Test that if we disable the node the hash gets passed through.
        ##########################################

        merge["enabled"].setValue(False)
        h1 = GafferImage.ImageAlgo.imageHash(merge["out"])

        self.assertEqual(h1, expectedHash)

    # Overlay a red, green and blue tile of different data window sizes and check the data window is expanded on the result and looks as we expect.
    def testOverRGBA(self):

        r = GafferImage.ImageReader()
        r["fileName"].setValue(self.rPath)

        g = GafferImage.ImageReader()
        g["fileName"].setValue(self.gPath)

        b = GafferImage.ImageReader()
        b["fileName"].setValue(self.bPath)

        merge = GafferImage.Merge()
        merge["operation"].setValue(GafferImage.Merge.Operation.Over)
        merge["in"][0].setInput(r["out"])
        merge["in"][1].setInput(g["out"])
        merge["in"][2].setInput(b["out"])

        expected = GafferImage.ImageReader()
        expected["fileName"].setValue(self.rgbPath)

        self.assertImagesEqual(merge["out"],
                               expected["out"],
                               maxDifference=0.001,
                               ignoreMetadata=True)

    # Overlay a red, green and blue tile of different data window sizes and check the data window is expanded on the result and looks as we expect.
    def testOverRGBAonRGB(self):

        c = GafferImage.ImageReader()
        c["fileName"].setValue(self.checkerPath)

        r = GafferImage.ImageReader()
        r["fileName"].setValue(self.rPath)

        g = GafferImage.ImageReader()
        g["fileName"].setValue(self.gPath)

        b = GafferImage.ImageReader()
        b["fileName"].setValue(self.bPath)

        merge = GafferImage.Merge()
        merge["operation"].setValue(GafferImage.Merge.Operation.Over)
        merge["in"][0].setInput(c["out"])
        merge["in"][1].setInput(r["out"])
        merge["in"][2].setInput(g["out"])
        merge["in"][3].setInput(b["out"])

        expected = GafferImage.ImageReader()
        expected["fileName"].setValue(self.checkerRGBPath)

        self.assertImagesEqual(merge["out"],
                               expected["out"],
                               maxDifference=0.001,
                               ignoreMetadata=True)

    def testAffects(self):

        c1 = GafferImage.Constant()
        c2 = GafferImage.Constant()

        m = GafferImage.Merge()
        m["in"][0].setInput(c1["out"])
        m["in"][1].setInput(c2["out"])

        cs = GafferTest.CapturingSlot(m.plugDirtiedSignal())

        c1["color"]["r"].setValue(0.1)

        self.assertEqual(len(cs), 5)
        self.assertTrue(cs[0][0].isSame(m["in"][0]["channelData"]))
        self.assertTrue(cs[1][0].isSame(m["in"][0]))
        self.assertTrue(cs[2][0].isSame(m["in"]))
        self.assertTrue(cs[3][0].isSame(m["out"]["channelData"]))
        self.assertTrue(cs[4][0].isSame(m["out"]))

        del cs[:]

        c2["color"]["g"].setValue(0.2)

        self.assertEqual(len(cs), 5)
        self.assertTrue(cs[0][0].isSame(m["in"][1]["channelData"]))
        self.assertTrue(cs[1][0].isSame(m["in"][1]))
        self.assertTrue(cs[2][0].isSame(m["in"]))
        self.assertTrue(cs[3][0].isSame(m["out"]["channelData"]))
        self.assertTrue(cs[4][0].isSame(m["out"]))

    def testEnabledAffects(self):

        m = GafferImage.Merge()

        affected = m.affects(m["enabled"])
        self.assertTrue(m["out"]["channelData"] in affected)

    def testPassThrough(self):

        c = GafferImage.Constant()
        f = GafferImage.Resize()
        f["in"].setInput(c["out"])
        f["format"].setValue(
            GafferImage.Format(imath.Box2i(imath.V2i(0), imath.V2i(10)), 1))
        d = GafferImage.ImageMetadata()
        d["metadata"].addChild(
            Gaffer.NameValuePlug(
                "comment",
                IECore.StringData("reformated and metadata updated")))
        d["in"].setInput(f["out"])

        m = GafferImage.Merge()
        m["in"][0].setInput(c["out"])
        m["in"][1].setInput(d["out"])

        self.assertEqual(m["out"]["format"].hash(), c["out"]["format"].hash())
        self.assertEqual(m["out"]["metadata"].hash(),
                         c["out"]["metadata"].hash())

        self.assertEqual(m["out"]["format"].getValue(),
                         c["out"]["format"].getValue())
        self.assertEqual(m["out"]["metadata"].getValue(),
                         c["out"]["metadata"].getValue())

        m["in"][0].setInput(d["out"])
        m["in"][1].setInput(c["out"])

        self.assertEqual(m["out"]["format"].hash(), d["out"]["format"].hash())
        self.assertEqual(m["out"]["metadata"].hash(),
                         d["out"]["metadata"].hash())

        self.assertEqual(m["out"]["format"].getValue(),
                         d["out"]["format"].getValue())
        self.assertEqual(m["out"]["metadata"].getValue(),
                         d["out"]["metadata"].getValue())

    def testSmallDataWindowOverLarge(self):

        b = GafferImage.Constant()
        b["format"].setValue(GafferImage.Format(500, 500, 1.0))
        b["color"].setValue(imath.Color4f(1, 0, 0, 1))

        a = GafferImage.Constant()
        a["format"].setValue(GafferImage.Format(500, 500, 1.0))
        a["color"].setValue(imath.Color4f(0, 1, 0, 1))

        aCrop = GafferImage.Crop()
        aCrop["in"].setInput(a["out"])
        aCrop["areaSource"].setValue(aCrop.AreaSource.Area)
        aCrop["area"].setValue(imath.Box2i(imath.V2i(50), imath.V2i(162)))
        aCrop["affectDisplayWindow"].setValue(False)

        m = GafferImage.Merge()
        m["operation"].setValue(m.Operation.Over)
        m["in"][0].setInput(b["out"])
        m["in"][1].setInput(aCrop["out"])

        redSampler = GafferImage.Sampler(
            m["out"], "R", m["out"]["format"].getValue().getDisplayWindow())
        greenSampler = GafferImage.Sampler(
            m["out"], "G", m["out"]["format"].getValue().getDisplayWindow())
        blueSampler = GafferImage.Sampler(
            m["out"], "B", m["out"]["format"].getValue().getDisplayWindow())

        def sample(x, y):

            return imath.Color3f(
                redSampler.sample(x, y),
                greenSampler.sample(x, y),
                blueSampler.sample(x, y),
            )

        # We should only have overed green in areas which are inside
        # the data window of aCrop. Everywhere else we should have
        # red still.

        self.assertEqual(sample(49, 49), imath.Color3f(1, 0, 0))
        self.assertEqual(sample(50, 50), imath.Color3f(0, 1, 0))
        self.assertEqual(sample(161, 161), imath.Color3f(0, 1, 0))
        self.assertEqual(sample(162, 162), imath.Color3f(1, 0, 0))

    def testLargeDataWindowAddedToSmall(self):

        b = GafferImage.Constant()
        b["format"].setValue(GafferImage.Format(500, 500, 1.0))
        b["color"].setValue(imath.Color4f(1, 0, 0, 1))

        a = GafferImage.Constant()
        a["format"].setValue(GafferImage.Format(500, 500, 1.0))
        a["color"].setValue(imath.Color4f(0, 1, 0, 1))

        bCrop = GafferImage.Crop()
        bCrop["in"].setInput(b["out"])
        bCrop["areaSource"].setValue(bCrop.AreaSource.Area)
        bCrop["area"].setValue(imath.Box2i(imath.V2i(50), imath.V2i(162)))
        bCrop["affectDisplayWindow"].setValue(False)

        m = GafferImage.Merge()
        m["operation"].setValue(m.Operation.Add)
        m["in"][0].setInput(bCrop["out"])
        m["in"][1].setInput(a["out"])

        redSampler = GafferImage.Sampler(
            m["out"], "R", m["out"]["format"].getValue().getDisplayWindow())
        greenSampler = GafferImage.Sampler(
            m["out"], "G", m["out"]["format"].getValue().getDisplayWindow())
        blueSampler = GafferImage.Sampler(
            m["out"], "B", m["out"]["format"].getValue().getDisplayWindow())

        def sample(x, y):

            return imath.Color3f(
                redSampler.sample(x, y),
                greenSampler.sample(x, y),
                blueSampler.sample(x, y),
            )

        # We should only have yellow in areas where the background exists,
        # and should have just green everywhere else.

        self.assertEqual(sample(49, 49), imath.Color3f(0, 1, 0))
        self.assertEqual(sample(50, 50), imath.Color3f(1, 1, 0))
        self.assertEqual(sample(161, 161), imath.Color3f(1, 1, 0))
        self.assertEqual(sample(162, 162), imath.Color3f(0, 1, 0))

    def testCrashWithResizedInput(self):

        b = GafferImage.Constant()
        b["format"].setValue(GafferImage.Format(2048, 1556))

        bResized = GafferImage.Resize()
        bResized["in"].setInput(b["out"])
        bResized["format"].setValue(GafferImage.Format(1920, 1080))
        bResized["fitMode"].setValue(bResized.FitMode.Fit)

        a = GafferImage.Constant()
        a["format"].setValue(GafferImage.Format(1920, 1080))

        merge = GafferImage.Merge()
        merge["operation"].setValue(merge.Operation.Over)
        merge["in"][0].setInput(bResized["out"])
        merge["in"][1].setInput(a["out"])

        GafferImageTest.processTiles(merge["out"])

    def testModes(self):

        b = GafferImage.Constant()
        b["color"].setValue(imath.Color4f(0.1, 0.2, 0.3, 0.4))

        a = GafferImage.Constant()
        a["color"].setValue(imath.Color4f(1, 0.3, 0.1, 0.2))

        merge = GafferImage.Merge()
        merge["in"][0].setInput(b["out"])
        merge["in"][1].setInput(a["out"])

        sampler = GafferImage.ImageSampler()
        sampler["image"].setInput(merge["out"])
        sampler["pixel"].setValue(imath.V2f(10))

        self.longMessage = True
        for operation, expected in [
            (GafferImage.Merge.Operation.Add, (1.1, 0.5, 0.4, 0.6)),
            (GafferImage.Merge.Operation.Atop, (0.48, 0.28, 0.28, 0.4)),
            (GafferImage.Merge.Operation.Divide, (10, 1.5, 1 / 3.0, 0.5)),
            (GafferImage.Merge.Operation.In, (0.4, 0.12, 0.04, 0.08)),
            (GafferImage.Merge.Operation.Out, (0.6, 0.18, 0.06, 0.12)),
            (GafferImage.Merge.Operation.Mask, (0.02, 0.04, 0.06, 0.08)),
            (GafferImage.Merge.Operation.Matte, (0.28, 0.22, 0.26, 0.36)),
            (GafferImage.Merge.Operation.Multiply, (0.1, 0.06, 0.03, 0.08)),
            (GafferImage.Merge.Operation.Over, (1.08, 0.46, 0.34, 0.52)),
            (GafferImage.Merge.Operation.Subtract, (0.9, 0.1, -0.2, -0.2)),
            (GafferImage.Merge.Operation.Difference, (0.9, 0.1, 0.2, 0.2)),
            (GafferImage.Merge.Operation.Under, (0.7, 0.38, 0.36, 0.52)),
            (GafferImage.Merge.Operation.Min, (0.1, 0.2, 0.1, 0.2)),
            (GafferImage.Merge.Operation.Max, (1, 0.3, 0.3, 0.4))
        ]:

            merge["operation"].setValue(operation)
            self.assertAlmostEqual(sampler["color"]["r"].getValue(),
                                   expected[0],
                                   msg=operation)
            self.assertAlmostEqual(sampler["color"]["g"].getValue(),
                                   expected[1],
                                   msg=operation)
            self.assertAlmostEqual(sampler["color"]["b"].getValue(),
                                   expected[2],
                                   msg=operation)
            self.assertAlmostEqual(sampler["color"]["a"].getValue(),
                                   expected[3],
                                   msg=operation)

    def testChannelRequest(self):

        a = GafferImage.Constant()
        a["color"].setValue(imath.Color4f(0.1, 0.2, 0.3, 0.4))

        ad = GafferImage.DeleteChannels()
        ad["in"].setInput(a["out"])
        ad["mode"].setValue(GafferImage.DeleteChannels.Mode.Delete)
        ad["channels"].setValue("R")

        b = GafferImage.Constant()
        b["color"].setValue(imath.Color4f(1.0, 0.3, 0.1, 0.2))

        bd = GafferImage.DeleteChannels()
        bd["in"].setInput(b["out"])
        bd["mode"].setValue(GafferImage.DeleteChannels.Mode.Delete)
        bd["channels"].setValue("G")

        merge = GafferImage.Merge()
        merge["in"][0].setInput(ad["out"])
        merge["in"][1].setInput(bd["out"])
        merge["operation"].setValue(GafferImage.Merge.Operation.Add)

        sampler = GafferImage.ImageSampler()
        sampler["image"].setInput(merge["out"])
        sampler["pixel"].setValue(imath.V2f(10))

        self.assertAlmostEqual(sampler["color"]["r"].getValue(), 0.0 + 1.0)
        self.assertAlmostEqual(sampler["color"]["g"].getValue(), 0.2 + 0.0)
        self.assertAlmostEqual(sampler["color"]["b"].getValue(), 0.3 + 0.1)
        self.assertAlmostEqual(sampler["color"]["a"].getValue(), 0.4 + 0.2)

    def testNonFlatThrows(self):

        deep = GafferImage.Empty()
        flat = GafferImage.Constant()

        merge = GafferImage.Merge()
        merge["in"][0].setInput(flat["out"])
        merge["in"][1].setInput(flat["out"])

        self.assertNotEqual(GafferImage.ImageAlgo.imageHash(merge["out"]),
                            GafferImage.ImageAlgo.imageHash(flat["out"]))

        merge["in"][0].setInput(deep["out"])
        six.assertRaisesRegex(self, RuntimeError,
                              'Deep data not supported in input "in.in0"',
                              GafferImage.ImageAlgo.image, merge["out"])
        merge["in"][0].setInput(flat["out"])
        merge["in"][1].setInput(deep["out"])
        six.assertRaisesRegex(self, RuntimeError,
                              'Deep data not supported in input "in.in1"',
                              GafferImage.ImageAlgo.image, merge["out"])

    def testDefaultFormat(self):

        a = GafferImage.Constant()
        a["format"].setValue(GafferImage.Format(100, 200))

        m = GafferImage.Merge()
        m["in"][1].setInput(a["out"])

        with Gaffer.Context() as c:
            GafferImage.FormatPlug().setDefaultFormat(
                c, GafferImage.Format(1000, 2000))
            self.assertEqual(m["out"]["format"].getValue(),
                             GafferImage.Format(1000, 2000))

    def testDataWindowWhenBNotConnected(self):

        a = GafferImage.Constant()
        a["format"].setValue(GafferImage.Format(100, 200))

        m = GafferImage.Merge()
        m["in"][1].setInput(a["out"])

        self.assertEqual(m["out"]["dataWindow"].getValue(),
                         a["out"]["dataWindow"].getValue())

    # Make sure we don't fail by pulling tiles outside the data window when merging images with
    # misaligned data
    def testTilesOutsideDataWindow(self):

        r = GafferImage.ImageReader()
        r["fileName"].setValue(self.checkerPath)

        o = GafferImage.Offset()
        o["in"].setInput(r["out"])
        o["offset"].setValue(imath.V2i(-10))

        merge = GafferImage.Merge()

        merge["in"][0].setInput(r["out"])
        merge["in"][1].setInput(o["out"])
        GafferImage.ImageAlgo.image(merge["out"])

    def testPassthroughs(self):

        ts = GafferImage.ImagePlug.tileSize()

        checkerboardB = GafferImage.Checkerboard()
        checkerboardB["format"]["displayWindow"].setValue(
            imath.Box2i(imath.V2i(0), imath.V2i(4096)))

        checkerboardA = GafferImage.Checkerboard()
        checkerboardA["format"]["displayWindow"].setValue(
            imath.Box2i(imath.V2i(0), imath.V2i(4096)))
        checkerboardA["size"].setValue(imath.V2f(5))

        cropB = GafferImage.Crop()
        cropB["in"].setInput(checkerboardB["out"])
        cropB["area"].setValue(
            imath.Box2i(imath.V2i(ts * 0.5), imath.V2i(ts * 4.5)))
        cropB["affectDisplayWindow"].setValue(False)

        cropA = GafferImage.Crop()
        cropA["in"].setInput(checkerboardA["out"])
        cropA["area"].setValue(
            imath.Box2i(imath.V2i(ts * 2.5), imath.V2i(ts * 6.5)))
        cropA["affectDisplayWindow"].setValue(False)

        merge = GafferImage.Merge()
        merge["in"][0].setInput(cropB["out"])
        merge["in"][1].setInput(cropA["out"])
        merge["operation"].setValue(8)

        sampleTileOrigins = {
            "insideBoth": imath.V2i(ts * 3, ts * 3),
            "outsideBoth": imath.V2i(ts * 5, ts),
            "outsideEdgeB": imath.V2i(ts, 0),
            "insideB": imath.V2i(ts, ts),
            "internalEdgeB": imath.V2i(ts * 4, ts),
            "internalEdgeA": imath.V2i(ts * 5, ts * 2),
            "insideA": imath.V2i(ts * 5, ts * 5),
            "outsideEdgeA": imath.V2i(ts * 6, ts * 5)
        }

        for opName, onlyA, onlyB in [("Atop", "black", "passB"),
                                     ("Divide", "operate", "black"),
                                     ("Out", "passA", "black"),
                                     ("Multiply", "black", "black"),
                                     ("Over", "passA", "passB"),
                                     ("Subtract", "passA", "operate"),
                                     ("Difference", "operate", "operate")]:
            op = getattr(GafferImage.Merge.Operation, opName)
            merge["operation"].setValue(op)

            results = {}
            for name, tileOrigin in sampleTileOrigins.items():
                # We want to check the value pass through code independently
                # of the hash passthrough code, which we can do by dropping
                # the value cached and evaluating values first
                Gaffer.ValuePlug.clearCache()

                with Gaffer.Context() as c:
                    c["image:tileOrigin"] = tileOrigin
                    c["image:channelName"] = "R"

                    data = merge["out"]["channelData"].getValue(_copy=False)
                    if data.isSame(
                            GafferImage.ImagePlug.blackTile(_copy=False)):
                        computeMode = "black"
                    elif data.isSame(
                            cropB["out"]["channelData"].getValue(_copy=False)):
                        computeMode = "passB"
                    elif data.isSame(
                            cropA["out"]["channelData"].getValue(_copy=False)):
                        computeMode = "passA"
                    else:
                        computeMode = "operate"

                    h = merge["out"]["channelData"].hash()
                    if h == GafferImage.ImagePlug.blackTile().hash():
                        hashMode = "black"
                    elif h == cropB["out"]["channelData"].hash():
                        hashMode = "passB"
                    elif h == cropA["out"]["channelData"].hash():
                        hashMode = "passA"
                    else:
                        hashMode = "operate"

                    self.assertEqual(hashMode, computeMode)

                    results[name] = hashMode

            self.assertEqual(results["insideBoth"], "operate")
            self.assertEqual(results["outsideBoth"], "black")
            self.assertEqual(results["outsideEdgeB"], onlyB)
            self.assertEqual(results["insideB"], onlyB)
            self.assertEqual(results["outsideEdgeA"], onlyA)
            self.assertEqual(results["insideA"], onlyA)

            if onlyA == "black" or onlyB == "black":
                self.assertEqual(results["internalEdgeB"], onlyB)
                self.assertEqual(results["internalEdgeA"], onlyA)
            else:
                self.assertEqual(results["internalEdgeB"], "operate")
                self.assertEqual(results["internalEdgeA"], "operate")

    # This somewhat sloppy test cobbled together from a Gaffer scene tests a bunch of the weird cases
    # for how data windows can overlap each other and the tile.  It was added because I'm experimenting
    # with an approach for treating the tile in regions, which does add a little bit of arithmetic that
    # I could get wrong
    def runBoundaryCorrectness(self, scale):

        testMerge = GafferImage.Merge()
        subImageNodes = []
        for checkSize, col, bound in [
            (2, (0.672299981, 0.672299981, 0), ((11, 7), (61, 57))),
            (4, (0.972599983, 0.493499994, 1), ((9, 5), (59, 55))),
            (6, (0.310799986, 0.843800008, 1), ((0, 21), (1024, 41))),
            (8, (0.958999991, 0.672299981, 0.0296), ((22, 0), (42, 1024))),
            (10, (0.950900018, 0.0899000019, 0.235499993), ((7, 10), (47,
                                                                      50))),
        ]:
            checkerboard = GafferImage.Checkerboard()
            checkerboard["format"].setValue(
                GafferImage.Format(1024 * scale, 1024 * scale, 1.000))
            checkerboard["size"].setValue(imath.V2f(checkSize * scale))
            checkerboard["colorA"].setValue(
                imath.Color4f(0.1 * col[0], 0.1 * col[1], 0.1 * col[2], 0.3))
            checkerboard["colorB"].setValue(
                imath.Color4f(0.5 * col[0], 0.5 * col[1], 0.5 * col[2], 0.7))

            crop = GafferImage.Crop("Crop")
            crop["in"].setInput(checkerboard["out"])
            crop["area"].setValue(
                imath.Box2i(
                    imath.V2i(scale * bound[0][0], scale * bound[0][1]),
                    imath.V2i(scale * bound[1][0], scale * bound[1][1])))
            crop["affectDisplayWindow"].setValue(False)

            subImageNodes.append(checkerboard)
            subImageNodes.append(crop)

            testMerge["in"][-1].setInput(crop["out"])

        testMerge["expression"] = Gaffer.Expression()
        testMerge["expression"].setExpression(
            'parent["operation"] = context[ "loop:index" ]')

        inverseScale = GafferImage.ImageTransform()
        inverseScale["in"].setInput(testMerge["out"])
        inverseScale["filter"].setValue("box")
        inverseScale["transform"]["scale"].setValue(imath.V2f(1.0 / scale))

        crop1 = GafferImage.Crop()
        crop1["in"].setInput(inverseScale["out"])
        crop1["area"].setValue(imath.Box2i(imath.V2i(0, 0), imath.V2i(64, 64)))

        loopInit = GafferImage.Constant()
        loopInit["format"].setValue(GafferImage.Format(896, 64, 1.000))
        loopInit["color"].setValue(imath.Color4f(0))

        loopOffset = GafferImage.Offset()
        loopOffset["in"].setInput(crop1["out"])
        loopOffset["expression"] = Gaffer.Expression()
        loopOffset["expression"].setExpression(
            'parent["offset"]["x"] = 64 * context[ "loop:index" ]')

        loopMerge = GafferImage.Merge()
        loopMerge["in"][1].setInput(loopOffset["out"])

        loop = Gaffer.Loop()
        loop.setup(GafferImage.ImagePlug("in", ))
        loop["iterations"].setValue(14)
        loop["in"].setInput(loopInit["out"])
        loop["next"].setInput(loopMerge["out"])
        loopMerge["in"][0].setInput(loop["previous"])

        # Uncomment for debug
        #imageWriter = GafferImage.ImageWriter( "ImageWriter" )
        #imageWriter["in"].setInput( loop["out"] )
        #imageWriter['openexr']['dataType'].setValue( "float" )
        #imageWriter["fileName"].setValue( "/tmp/mergeBoundaries.exr" )
        #imageWriter.execute()

        reader = GafferImage.ImageReader()
        reader["fileName"].setValue(self.mergeBoundariesRefPath)

        self.assertImagesEqual(loop["out"],
                               reader["out"],
                               ignoreMetadata=True,
                               maxDifference=1e-5 if scale > 1 else 0)

    def testBoundaryCorrectness(self):
        self.runBoundaryCorrectness(1)
        self.runBoundaryCorrectness(2)
        self.runBoundaryCorrectness(4)
        self.runBoundaryCorrectness(8)
        self.runBoundaryCorrectness(16)
        self.runBoundaryCorrectness(32)

    def testEmptyDataWindowMerge(self):
        constant = GafferImage.Constant()
        constant["format"].setValue(GafferImage.Format(512, 512, 1.000))
        constant["color"].setValue(imath.Color4f(1))

        offset = GafferImage.Offset()
        offset["in"].setInput(constant["out"])
        offset["offset"].setValue(imath.V2i(-1024))

        emptyCrop = GafferImage.Crop()
        emptyCrop["in"].setInput(constant["out"])
        emptyCrop["area"].setValue(imath.Box2i(imath.V2i(-10),
                                               imath.V2i(-100)))

        merge = GafferImage.Merge()
        merge["in"][0].setInput(offset["out"])
        merge["in"][1].setInput(emptyCrop["out"])

        self.assertEqual(merge["out"].dataWindow(),
                         imath.Box2i(imath.V2i(-1024), imath.V2i(-512)))

    def mergePerf(self, operation, mismatch):
        r = GafferImage.Checkerboard("Checkerboard")
        r["format"].setValue(GafferImage.Format(4096, 3112, 1.000))
        # Make the size of the checkerboard not a perfect multiple of tile size
        # in case we ever fix Checkerboard to notice when tiles are repeated
        # and return an identical hash ( which would invalidate this performance
        # test )
        r["size"].setValue(imath.V2f(64.01))

        alphaShuffle = GafferImage.Shuffle()
        alphaShuffle["in"].setInput(r["out"])
        alphaShuffle["channels"].addChild(
            GafferImage.Shuffle.ChannelPlug("A", "R"))

        transform = GafferImage.Offset()
        transform["in"].setInput(alphaShuffle["out"])
        if mismatch:
            transform["offset"].setValue(imath.V2i(4000, 3000))
        else:
            transform["offset"].setValue(imath.V2i(26, 42))

        merge = GafferImage.Merge()
        merge["operation"].setValue(operation)
        merge["in"][0].setInput(alphaShuffle["out"])
        merge["in"][1].setInput(transform["out"])

        # Precache upstream network, we're only interested in the performance of Merge
        GafferImageTest.processTiles(alphaShuffle["out"])
        GafferImageTest.processTiles(transform["out"])

        with GafferTest.TestRunner.PerformanceScope():
            GafferImageTest.processTiles(merge["out"])

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testAddPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Add, False)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testAddMismatchPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Add, True)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testAtopPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Atop, False)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testAtopMismatchPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Atop, True)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testDividePerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Divide, False)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testDivideMismatchPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Divide, True)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testInPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.In, False)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testInMismatchPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.In, True)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testOutPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Out, False)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testOutMismatchPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Out, True)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testMaskPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Mask, False)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testMaskMismatchPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Mask, True)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testOutPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Out, False)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testOutMismatchPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Out, True)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testMaskPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Mask, False)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testMaskMismatchPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Mask, True)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testMattePerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Matte, False)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testMatteMismatchPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Matte, True)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testMultiplyPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Multiply, False)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testMultiplyMismatchPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Multiply, True)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testOverPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Over, False)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testOverMismatchPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Over, True)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testSubtractPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Subtract, False)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testSubtractMismatchPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Subtract, True)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testDifferencePerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Difference, False)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testDifferenceMismatchPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Difference, True)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testUnderPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Under, False)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testUnderMismatchPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Under, True)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testMinPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Min, False)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testMinMismatchPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Min, True)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testMaxPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Max, False)

    @unittest.skipIf(GafferTest.inCI(),
                     "Performance not relevant on CI platform")
    @GafferTest.TestRunner.PerformanceTestMethod(repeat=5)
    def testMaxMismatchPerf(self):
        self.mergePerf(GafferImage.Merge.Operation.Max, True)
Example #15
0
def envHasMemoryIssues():
    return GafferTest.inCI() and (Gaffer.isDebug() or sys.platform == 'darwin')
Example #16
0
class SceneGadgetTest(GafferUITest.TestCase):
    def testBound(self):

        s = Gaffer.ScriptNode()
        s["p"] = GafferScene.Plane()
        s["g"] = GafferScene.Group()
        s["g"]["in"][0].setInput(s["p"]["out"])
        s["g"]["transform"]["translate"]["x"].setValue(2)

        sg = GafferSceneUI.SceneGadget()
        sg.setScene(s["g"]["out"])

        sg.waitForCompletion()
        self.assertEqual(sg.bound(), s["g"]["out"].bound("/"))

        s["g"]["transform"]["translate"]["y"].setValue(4)
        sg.waitForCompletion()
        self.assertEqual(sg.bound(), s["g"]["out"].bound("/"))

    def assertObjectAt(self, gadget, ndcPosition, path):

        viewportGadget = gadget.ancestor(GafferUI.ViewportGadget)

        rasterPosition = ndcPosition * imath.V2f(viewportGadget.getViewport())
        gadgetLine = viewportGadget.rasterToGadgetSpace(rasterPosition, gadget)

        self.assertEqual(gadget.objectAt(gadgetLine), path)

    def assertObjectsAt(self, gadget, ndcBox, paths):

        viewportGadget = gadget.ancestor(GafferUI.ViewportGadget)

        rasterMin = ndcBox.min() * imath.V2f(viewportGadget.getViewport())
        rasterMax = ndcBox.max() * imath.V2f(viewportGadget.getViewport())

        gadgetMin = viewportGadget.rasterToGadgetSpace(rasterMin, gadget).p0
        gadgetMax = viewportGadget.rasterToGadgetSpace(rasterMax, gadget).p1

        objectsAt = IECore.PathMatcher()
        gadget.objectsAt(gadgetMin, gadgetMax, objectsAt)

        objects = set(objectsAt.paths())
        expectedObjects = set(IECore.PathMatcher(paths).paths())
        self.assertEqual(objects, expectedObjects)

    def testObjectVisibility(self):

        s = Gaffer.ScriptNode()
        s["s"] = GafferScene.Sphere()
        s["g"] = GafferScene.Group()
        s["g"]["in"][0].setInput(s["s"]["out"])
        s["a"] = GafferScene.StandardAttributes()
        s["a"]["in"].setInput(s["g"]["out"])

        sg = GafferSceneUI.SceneGadget()
        sg.setMinimumExpansionDepth(1)
        sg.setScene(s["a"]["out"])

        with GafferUI.Window() as w:
            gw = GafferUI.GadgetWidget(sg)

        w.setVisible(True)
        self.waitForIdle(1000)

        sg.waitForCompletion()
        gw.getViewportGadget().frame(sg.bound())

        self.assertObjectAt(
            sg, imath.V2f(0.5),
            IECore.InternedStringVectorData(["group", "sphere"]))

        s["a"]["attributes"]["visibility"]["enabled"].setValue(True)
        s["a"]["attributes"]["visibility"]["value"].setValue(False)

        sg.waitForCompletion()
        self.assertObjectAt(sg, imath.V2f(0.5), None)

        s["a"]["attributes"]["visibility"]["enabled"].setValue(True)
        s["a"]["attributes"]["visibility"]["value"].setValue(True)

        sg.waitForCompletion()
        self.assertObjectAt(
            sg, imath.V2f(0.5),
            IECore.InternedStringVectorData(["group", "sphere"]))

    @unittest.skipIf(GafferTest.inCI(), "Unknown problem running in cloud")
    def testExpansion(self):

        s = Gaffer.ScriptNode()
        s["s"] = GafferScene.Sphere()
        s["g"] = GafferScene.Group()
        s["g"]["in"][0].setInput(s["s"]["out"])
        s["a"] = GafferScene.StandardAttributes()
        s["a"]["in"].setInput(s["g"]["out"])

        sg = GafferSceneUI.SceneGadget()
        sg.setScene(s["a"]["out"])

        with GafferUI.Window() as w:
            gw = GafferUI.GadgetWidget(sg)

        w.setVisible(True)
        self.waitForIdle(10000)

        sg.waitForCompletion()
        gw.getViewportGadget().frame(sg.bound())
        self.waitForIdle(10000)

        self.assertObjectAt(sg, imath.V2f(0.5), None)
        self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(1)),
                             ["/group"])

        sg.setExpandedPaths(IECore.PathMatcher(["/group"]))
        sg.waitForCompletion()

        self.assertObjectAt(
            sg, imath.V2f(0.5),
            IECore.InternedStringVectorData(["group", "sphere"]))
        self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(1)),
                             ["/group/sphere"])

        sg.setExpandedPaths(IECore.PathMatcher([]))
        sg.waitForCompletion()

        self.assertObjectAt(sg, imath.V2f(0.5), None)
        self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(1)),
                             ["/group"])

    def testExpressions(self):

        s = Gaffer.ScriptNode()
        s["p"] = GafferScene.Plane()
        s["g"] = GafferScene.Group()
        s["g"]["in"][0].setInput(s["p"]["out"])
        s["g"]["in"][1].setInput(s["p"]["out"])
        s["g"]["in"][2].setInput(s["p"]["out"])

        s["e"] = Gaffer.Expression()
        s["e"].setExpression(
            "parent['p']['dimensions']['x'] = 1 + context.getFrame() * 0.1")

        g = GafferSceneUI.SceneGadget()
        g.setScene(s["g"]["out"])
        g.bound()

    def testGLResourceDestruction(self):

        s = Gaffer.ScriptNode()
        s["p"] = GafferScene.Plane()
        s["g"] = GafferScene.Group()
        s["g"]["in"][0].setInput(s["p"]["out"])
        s["g"]["in"][1].setInput(s["p"]["out"])
        s["g"]["in"][2].setInput(s["p"]["out"])
        s["g"]["in"][3].setInput(s["p"]["out"])

        sg = GafferSceneUI.SceneGadget()
        sg.setScene(s["g"]["out"])
        sg.setMinimumExpansionDepth(2)

        with GafferUI.Window() as w:
            gw = GafferUI.GadgetWidget(sg)
        w.setVisible(True)

        # Reduce the GL cache size so that not everything will fit, and we'll
        # need to dispose of some objects. We can't dispose of objects on any
        # old thread, just the main GL thread, so it's important that we test
        # that we're doing that appropriately.
        IECoreGL.CachedConverter.defaultCachedConverter().setMaxMemory(100)

        for i in range(1, 1000):
            s["p"]["dimensions"]["x"].setValue(i)
            self.waitForIdle(10)

    def testExceptionsDuringCompute(self):

        # Make this scene
        #
        # - bigSphere
        #	- littleSphere (with exception in attributes expression)

        s = Gaffer.ScriptNode()

        s["s1"] = GafferScene.Sphere()
        s["s1"]["name"].setValue("bigSphere")

        s["s2"] = GafferScene.Sphere()
        s["s2"]["name"].setValue("littleSphere")
        s["s2"]["radius"].setValue(0.1)

        s["p"] = GafferScene.Parent()
        s["p"]["in"].setInput(s["s1"]["out"])
        s["p"]["children"][0].setInput(s["s2"]["out"])
        s["p"]["parent"].setValue("/bigSphere")

        s["a"] = GafferScene.StandardAttributes()
        s["a"]["in"].setInput(s["p"]["out"])
        s["a"]["attributes"]["doubleSided"]["enabled"].setValue(True)

        s["e"] = Gaffer.Expression()
        s["e"].setExpression(
            'parent["a"]["attributes"]["doubleSided"]["value"] = context["nonexistent"]'
        )

        s["f"] = GafferScene.PathFilter()
        s["f"]["paths"].setValue(
            IECore.StringVectorData(["/bigSphere/littleSphere"]))

        s["a"]["filter"].setInput(s["f"]["out"])

        # Try to view it

        sg = GafferSceneUI.SceneGadget()
        sg.setScene(s["a"]["out"])
        sg.setMinimumExpansionDepth(4)

        with GafferUI.Window() as w:
            gw = GafferUI.GadgetWidget(sg)
            gw.getViewportGadget().setPlanarMovement(False)
            gw.getViewportGadget().setCamera(
                IECoreScene.Camera(parameters={
                    "projection": "perspective",
                }))

        originalMessageHandler = IECore.MessageHandler.getDefaultHandler()
        mh = IECore.CapturingMessageHandler()
        IECore.MessageHandler.setDefaultHandler(
            IECore.LevelFilteredMessageHandler(
                mh, IECore.LevelFilteredMessageHandler.defaultLevel()))

        try:

            w.setVisible(True)
            self.waitForIdle(1000)
            sg.waitForCompletion()

            # Check we were told about the problem

            self.assertEqual(len(mh.messages), 1)
            self.assertEqual(mh.messages[0].level, mh.Level.Error)
            self.assertTrue("nonexistent" in mh.messages[0].message)

            # And that there isn't some half-assed partial scene
            # being displayed.

            self.assertTrue(sg.bound().isEmpty())
            gw.getViewportGadget().frame(
                imath.Box3f(imath.V3f(-1), imath.V3f(1)))
            self.assertObjectAt(sg, imath.V2f(0.5), None)

            # And that redraws don't cause more fruitless attempts
            # to compute the scene.

            gw.getViewportGadget().frame(
                imath.Box3f(imath.V3f(-1.1), imath.V3f(1.1)))
            self.waitForIdle(1000)

            self.assertEqual(len(mh.messages), 1)
            self.assertObjectAt(sg, imath.V2f(0.5), None)
            self.assertTrue(sg.bound().isEmpty())

            # Fix the problem with the scene, and check that we can see something now

            s["f"]["enabled"].setValue(False)
            sg.waitForCompletion()

            self.assertEqual(len(mh.messages), 1)
            self.assertFalse(sg.bound().isEmpty())
            self.assertObjectAt(sg, imath.V2f(0.5),
                                IECore.InternedStringVectorData(["bigSphere"]))

        finally:

            IECore.MessageHandler.setDefaultHandler(originalMessageHandler)

    def testObjectsAtBox(self):

        plane = GafferScene.Plane()

        sphere = GafferScene.Sphere()
        sphere["radius"].setValue(0.25)

        instancer = GafferScene.Instancer()
        instancer["in"].setInput(plane["out"])
        instancer["prototypes"].setInput(sphere["out"])
        instancer["parent"].setValue("/plane")

        subTree = GafferScene.SubTree()
        subTree["in"].setInput(instancer["out"])
        subTree["root"].setValue("/plane")

        sg = GafferSceneUI.SceneGadget()
        sg.setScene(subTree["out"])
        sg.setMinimumExpansionDepth(100)

        with GafferUI.Window() as w:
            gw = GafferUI.GadgetWidget(sg)
        w.setVisible(True)
        self.waitForIdle(10000)

        gw.getViewportGadget().frame(sg.bound())
        self.waitForIdle(10000)

        self.assertObjectsAt(
            sg, imath.Box2f(imath.V2f(0), imath.V2f(1)),
            ["/instances/sphere/{}".format(i) for i in range(0, 4)])

        self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(0.5)),
                             ["/instances/sphere/2"])

        self.assertObjectsAt(sg,
                             imath.Box2f(imath.V2f(0.5, 0), imath.V2f(1, 0.5)),
                             ["/instances/sphere/3"])

        self.assertObjectsAt(sg,
                             imath.Box2f(imath.V2f(0, 0.5), imath.V2f(0.5, 1)),
                             ["/instances/sphere/0"])

        self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0.5), imath.V2f(1)),
                             ["/instances/sphere/1"])

    def testObjectAtLine(self):

        cubes = []
        names = ("left", "center", "right")
        for i in range(3):
            cube = GafferScene.Cube()
            cube["transform"]["translate"].setValue(
                imath.V3f((i - 1) * 2.0, 0.0, -2.5))
            cube["name"].setValue(names[i])
            cubes.append(cube)

        group = GafferScene.Group()
        for i, cube in enumerate(cubes):
            group["in"][i].setInput(cube["out"])

        sg = GafferSceneUI.SceneGadget()
        sg.setScene(group["out"])
        sg.setMinimumExpansionDepth(100)

        with GafferUI.Window() as w:
            gw = GafferUI.GadgetWidget(sg)
        w.setVisible(True)
        self.waitForIdle(10000)

        vp = gw.getViewportGadget()

        # This is the single most important line in this test. If you don't set
        # this to false, you get an orthographic camera, even if you set a
        # perspective projection.
        vp.setPlanarMovement(False)

        c = IECoreScene.Camera()
        c.setProjection("perspective")
        c.setFocalLength(35)
        c.setAperture(imath.V2f(36, 24))
        vp.setCamera(c)

        cameraTransform = imath.M44f()
        cameraTransform.translate(imath.V3f(0, 0, 2))
        vp.setCameraTransform(cameraTransform)

        self.waitForIdle(10000)

        # We assume in this case, that gadget space is world space

        leftCubeDir = IECore.LineSegment3f(imath.V3f(0, 0, 2),
                                           imath.V3f(-2, 0, -2))
        pathA = sg.objectAt(leftCubeDir)
        pathB, hitPoint = sg.objectAndIntersectionAt(leftCubeDir)
        self.assertIsNotNone(pathA)
        self.assertEqual(pathA,
                         IECore.InternedStringVectorData(["group", "left"]))
        self.assertEqual(pathA, pathB)
        self.assertAlmostEqual(hitPoint.x, -2, delta=0.01)
        self.assertAlmostEqual(hitPoint.y, 0, delta=0.01)
        self.assertAlmostEqual(hitPoint.z, -2, delta=0.01)

        centerCubeDir = IECore.LineSegment3f(imath.V3f(0, 0, 1),
                                             imath.V3f(0, 0, -1))
        pathA = sg.objectAt(centerCubeDir)
        pathB, hitPoint = sg.objectAndIntersectionAt(centerCubeDir)
        self.assertIsNotNone(pathA)
        self.assertEqual(pathA,
                         IECore.InternedStringVectorData(["group", "center"]))
        self.assertEqual(pathA, pathB)
        self.assertAlmostEqual(hitPoint.x, 0, delta=0.01)
        self.assertAlmostEqual(hitPoint.y, 0, delta=0.01)
        self.assertAlmostEqual(hitPoint.z, -2, delta=0.01)

        rightCubeDir = IECore.LineSegment3f(imath.V3f(0, 0, 2),
                                            imath.V3f(2, 0, -2))
        pathA = sg.objectAt(rightCubeDir)
        pathB, hitPoint = sg.objectAndIntersectionAt(rightCubeDir)
        self.assertIsNotNone(pathA)
        self.assertEqual(pathA,
                         IECore.InternedStringVectorData(["group", "right"]))
        self.assertEqual(pathA, pathB)
        self.assertAlmostEqual(hitPoint.x, 2, delta=0.01)
        self.assertAlmostEqual(hitPoint.y, 0, delta=0.01)
        self.assertAlmostEqual(hitPoint.z, -2, delta=0.01)

        missDir = IECore.LineSegment3f(imath.V3f(0, 0, 2),
                                       imath.V3f(0, 10, -2))
        pathA = sg.objectAt(missDir)
        pathB, hitPoint = sg.objectAndIntersectionAt(missDir)
        self.assertIsNone(pathA)
        self.assertIsNone(pathB)

    def testSetAndGetScene(self):

        plane = GafferScene.Plane()
        sphere = GafferScene.Sphere()

        sg = GafferSceneUI.SceneGadget()
        self.assertEqual(sg.getScene(), None)

        sg.setScene(plane["out"])
        self.assertEqual(sg.getScene(), plane["out"])

        sg.setScene(sphere["out"])
        self.assertEqual(sg.getScene(), sphere["out"])

    def testBoundOfUnexpandedEmptyChildren(self):

        group1 = GafferScene.Group()
        group2 = GafferScene.Group()
        group2["in"][0].setInput(group1["out"])

        sg = GafferSceneUI.SceneGadget()
        sg.setScene(group2["out"])

        sg.waitForCompletion()
        self.assertEqual(sg.bound(), imath.Box3f())

    def testSelectionMaskAccessors(self):

        sg = GafferSceneUI.SceneGadget()
        self.assertEqual(sg.getSelectionMask(), None)

        m = IECore.StringVectorData(["MeshPrimitive"])
        sg.setSelectionMask(m)
        self.assertEqual(sg.getSelectionMask(), m)

        m.append("Camera")
        self.assertNotEqual(sg.getSelectionMask(), m)
        sg.setSelectionMask(m)
        self.assertEqual(sg.getSelectionMask(), m)

        sg.setSelectionMask(None)
        self.assertEqual(sg.getSelectionMask(), None)

    def testSelectionMask(self):

        plane = GafferScene.Plane()
        plane["dimensions"].setValue(imath.V2f(10))
        plane["transform"]["translate"]["z"].setValue(4)

        camera = GafferScene.Camera()
        group = GafferScene.Group()
        group["in"][0].setInput(plane["out"])
        group["in"][1].setInput(camera["out"])

        sg = GafferSceneUI.SceneGadget()
        sg.setScene(group["out"])
        sg.setMinimumExpansionDepth(100)

        with GafferUI.Window() as w:
            gw = GafferUI.GadgetWidget(sg)
        w.setVisible(True)
        self.waitForIdle(10000)

        sg.waitForCompletion()
        gw.getViewportGadget().frame(sg.bound(), imath.V3f(0, 0, -1))
        self.waitForIdle(10000)

        self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(1)),
                             ["/group/plane", "/group/camera"])

        sg.setSelectionMask(IECore.StringVectorData(["MeshPrimitive"]))

        self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(1)),
                             ["/group/plane"])

        sg.setSelectionMask(IECore.StringVectorData(["Camera"]))

        self.assertObjectsAt(sg, imath.Box2f(imath.V2f(0), imath.V2f(1)),
                             ["/group/camera"])

    def setUp(self):

        GafferUITest.TestCase.setUp(self)

        self.__cachedConverterMaxMemory = IECoreGL.CachedConverter.defaultCachedConverter(
        ).getMaxMemory()

    def tearDown(self):

        GafferUITest.TestCase.tearDown(self)

        IECoreGL.CachedConverter.defaultCachedConverter().setMaxMemory(
            self.__cachedConverterMaxMemory)