Esempio n. 1
0
    def testUnpremultiplied(self):

        i = GafferImage.ImageReader()
        i["fileName"].setValue(self.checkerFile)

        shuffleAlpha = GafferImage.Shuffle()
        shuffleAlpha["channels"].addChild(
            GafferImage.Shuffle.ChannelPlug("channel"))
        shuffleAlpha["in"].setInput(i["out"])
        shuffleAlpha["channels"]["channel"]["out"].setValue('A')
        shuffleAlpha["channels"]["channel"]["in"].setValue('R')

        gradeAlpha = GafferImage.Grade()
        gradeAlpha["in"].setInput(shuffleAlpha["out"])
        gradeAlpha["channels"].setValue('[RGBA]')
        gradeAlpha["offset"].setValue(imath.Color4f(0, 0, 0, 0.1))

        unpremultipliedGrade = GafferImage.Grade()
        unpremultipliedGrade["in"].setInput(gradeAlpha["out"])
        unpremultipliedGrade["processUnpremultiplied"].setValue(True)
        unpremultipliedGrade["gamma"].setValue(imath.Color4f(2, 2, 2, 1.0))

        unpremultiply = GafferImage.Unpremultiply()
        unpremultiply["in"].setInput(gradeAlpha["out"])

        bareGrade = GafferImage.Grade()
        bareGrade["in"].setInput(unpremultiply["out"])
        bareGrade["gamma"].setValue(imath.Color4f(2, 2, 2, 1.0))

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

        # Assert that with a non-zero alpha, processUnpremultiplied is identical to:
        # unpremult, grade, and premult
        self.assertImagesEqual(unpremultipliedGrade["out"], premultiply["out"])

        # Assert that grading alpha to 0 inside the grade still premults at the end correctly
        unpremultipliedGrade["channels"].setValue('[RGBA]')
        unpremultipliedGrade["multiply"].setValue(imath.Color4f(1, 1, 1, 0))

        zeroGrade = GafferImage.Grade()
        zeroGrade["channels"].setValue('[RGBA]')
        zeroGrade["in"].setInput(gradeAlpha["out"])
        zeroGrade["multiply"].setValue(imath.Color4f(0, 0, 0, 0))

        self.assertImagesEqual(unpremultipliedGrade["out"], zeroGrade["out"])

        unpremultipliedGrade["multiply"].setValue(imath.Color4f(1, 1, 1, 1))

        # Assert that when input alpha is zero, processUnpremultiplied doesn't affect the result

        gradeAlpha["multiply"].setValue(imath.Color4f(1, 1, 1, 0.0))
        gradeAlpha["offset"].setValue(imath.Color4f(0, 0, 0, 0.0))

        defaultGrade = GafferImage.Grade()
        defaultGrade["in"].setInput(gradeAlpha["out"])
        defaultGrade["gamma"].setValue(imath.Color4f(2, 2, 2, 1.0))

        self.assertImagesEqual(unpremultipliedGrade["out"],
                               defaultGrade["out"])
Esempio n. 2
0
    def testDeepWithFlatMask(self):

        # Set up a mask
        maskReader = GafferImage.ImageReader()
        maskReader["fileName"].setValue(self.radialPath)

        maskText = GafferImage.Text()
        maskText["in"].setInput(maskReader["out"])
        maskText["area"].setValue(
            imath.Box2i(imath.V2i(0, 0), imath.V2i(256, 256)))
        maskText["text"].setValue('Test\nTest\nTest\nTest')

        maskOffset = GafferImage.Offset()
        maskOffset["in"].setInput(maskText["out"])

        representativeDeepImage = GafferImage.ImageReader()
        representativeDeepImage["fileName"].setValue(
            self.representativeDeepImagePath)

        deepGradeBlack = GafferImage.Grade()
        deepGradeBlack["in"].setInput(representativeDeepImage["out"])
        deepGradeBlack["multiply"].setValue(imath.Color4f(0, 0, 0, 1))

        deepMix = GafferImage.Mix()
        deepMix["in"]["in0"].setInput(representativeDeepImage["out"])
        deepMix["in"]["in1"].setInput(deepGradeBlack["out"])
        deepMix["mask"].setInput(maskOffset["out"])
        deepMix["maskChannel"].setValue('R')

        postFlatten = GafferImage.DeepToFlat()
        postFlatten["in"].setInput(deepMix["out"])

        preFlatten = GafferImage.DeepToFlat()
        preFlatten["in"].setInput(representativeDeepImage["out"])

        flatGradeBlack = GafferImage.Grade()
        flatGradeBlack["in"].setInput(preFlatten["out"])
        flatGradeBlack["multiply"].setValue(imath.Color4f(0, 0, 0, 1))

        flatMix = GafferImage.Mix()
        flatMix["in"]["in0"].setInput(preFlatten["out"])
        flatMix["in"]["in1"].setInput(flatGradeBlack["out"])
        flatMix["mask"].setInput(maskOffset["out"])
        flatMix["maskChannel"].setValue('R')

        for o in [
                imath.V2i(0, 0),
                imath.V2i(-3, -8),
                imath.V2i(-7, -79),
                imath.V2i(12, 8)
        ]:
            maskOffset["offset"].setValue(o)

            self.assertImagesEqual(postFlatten["out"],
                                   flatMix["out"],
                                   maxDifference=0.000003)
Esempio n. 3
0
    def testChannelDataHashesAreIndependent(self):

        # changing only one channel of any of the grading plugs should not
        # affect the hash of any of the other channels.

        s = Gaffer.ScriptNode()
        s["c"] = GafferImage.Constant()
        s["g"] = GafferImage.Grade()
        s["g"]["in"].setInput(s["c"]["out"])

        channelNames = ("R", "G", "B")
        for channelIndex, channelName in enumerate(channelNames):
            for plugName in ("blackPoint", "whitePoint", "lift", "gain",
                             "multiply", "offset", "gamma"):
                oldChannelHashes = [
                    s["g"]["out"].channelDataHash(c, imath.V2i(0))
                    for c in channelNames
                ]
                s["g"][plugName][channelIndex].setValue(
                    s["g"][plugName][channelIndex].getValue() + 0.01)
                newChannelHashes = [
                    s["g"]["out"].channelDataHash(c, imath.V2i(0))
                    for c in channelNames
                ]
                for hashChannelIndex in range(0, 3):
                    if channelIndex == hashChannelIndex:
                        self.assertNotEqual(oldChannelHashes[hashChannelIndex],
                                            newChannelHashes[hashChannelIndex])
                    else:
                        self.assertEqual(oldChannelHashes[hashChannelIndex],
                                         newChannelHashes[hashChannelIndex])
Esempio n. 4
0
	def testDefaultFormatContext( self ) :

		# Create a node to make sure that we have a default format...
		s = Gaffer.ScriptNode()
		n = GafferImage.Grade()
		s.addChild( n )
		s.context().get("image:defaultFormat")
Esempio n. 5
0
    def testDefaultFormatWrite(self):

        s = Gaffer.ScriptNode()
        w = GafferImage.ImageWriter()
        g = GafferImage.Grade()

        s.addChild(g)
        s.addChild(w)

        testFile = self.__testFilePath + "testBlack.exr"
        self.failIf(os.path.exists(testFile))

        GafferImage.Format.setDefaultFormat(
            s,
            GafferImage.Format(
                IECore.Box2i(IECore.V2i(-7, -2), IECore.V2i(22, 24)), 1.))
        w["in"].setInput(g["out"])
        w["fileName"].setValue(testFile)
        w["channels"].setValue(
            IECore.StringVectorData(g["out"]["channelNames"].getValue()))

        # Try to execute. In older versions of the ImageWriter this would throw an exception.
        with s.context():
            w.execute()
        self.failUnless(os.path.exists(testFile))

        # Check the output.
        expectedFile = self.__defaultFormatFile
        expectedOutput = IECore.Reader.create(expectedFile).read()
        expectedOutput.blindData().clear()

        writerOutput = IECore.Reader.create(testFile).read()
        writerOutput.blindData().clear()

        self.assertEqual(writerOutput, expectedOutput)
Esempio n. 6
0
	def testChannelPassThrough( self ) :

		# we should get a perfect pass-through without cache duplication when
		# all the colour plugs are at their defaults.

		s = Gaffer.ScriptNode()
		s["c"] = GafferImage.Constant()
		s["g"] = GafferImage.Grade()
		s["g"]["in"].setInput( s["c"]["out"] )

		for channelName in ( "R", "G", "B", "A" ) :
			self.assertEqual(
				s["g"]["out"].channelDataHash( channelName, IECore.V2i( 0 ) ),
				s["c"]["out"].channelDataHash( channelName, IECore.V2i( 0 ) ),
			)

			c = Gaffer.Context( s.context() )
			c["image:channelName"] = channelName
			c["image:tileOrigin"] = IECore.V2i( 0 )
			with c :
				self.assertTrue(
					s["g"]["out"]["channelData"].getValue( _copy=False ).isSame(
						s["c"]["out"]["channelData"].getValue( _copy=False )
					)
				)
Esempio n. 7
0
    def testPassThrough(self):

        i = GafferImage.ImageReader()
        i["fileName"].setValue(self.checkerFile)

        g = GafferImage.Grade()
        g["in"].setInput(i["out"])
        g["gain"].setValue(imath.Color3f(2., 2., 2.))

        self.assertEqual(i["out"]["format"].hash(), g["out"]["format"].hash())
        self.assertEqual(i["out"]["dataWindow"].hash(),
                         g["out"]["dataWindow"].hash())
        self.assertEqual(i["out"]["metadata"].hash(),
                         g["out"]["metadata"].hash())
        self.assertEqual(i["out"]["channelNames"].hash(),
                         g["out"]["channelNames"].hash())

        self.assertEqual(i["out"]["format"].getValue(),
                         g["out"]["format"].getValue())
        self.assertEqual(i["out"]["dataWindow"].getValue(),
                         g["out"]["dataWindow"].getValue())
        self.assertEqual(i["out"]["metadata"].getValue(),
                         g["out"]["metadata"].getValue())
        self.assertEqual(i["out"]["channelNames"].getValue(),
                         g["out"]["channelNames"].getValue())
Esempio n. 8
0
    def testIterationsContext(self):

        script = Gaffer.ScriptNode()

        script["c"] = GafferImage.Constant()
        script["loop"] = Gaffer.Loop()
        script["loop"].setup(GafferImage.ImagePlug())
        script["loop"]["in"].setInput(script["c"]["out"])

        script["grade"] = GafferImage.Grade()
        script["grade"]["offset"].setValue(imath.Color3f(.1))
        script["grade"]["in"].setInput(script["loop"]["previous"])
        script["loop"]["next"].setInput(script["grade"]["out"])

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

        script["expression"] = Gaffer.Expression()
        script["expression"].setExpression(
            inspect.cleandoc("""
			assert( context.get( "image:channelName", None ) is None )
			assert( context.get( "image:tileOrigin", None ) is None )
			parent["loop"]["iterations"] = 4
			"""))

        with script.context():

            self.assertAlmostEqual(script["sampler"]["color"]["r"].getValue(),
                                   .4)
Esempio n. 9
0
    def testEnableBehaviour(self):

        g = GafferImage.Grade()
        self.assertTrue(g.enabledPlug().isSame(g["enabled"]))
        self.assertTrue(g.correspondingInput(g["out"]).isSame(g["in"]))
        self.assertEqual(g.correspondingInput(g["in"]), None)
        self.assertEqual(g.correspondingInput(g["enabled"]), None)
        self.assertEqual(g.correspondingInput(g["gain"]), None)
Esempio n. 10
0
    def testCacheThreadSafety(self):

        c = GafferImage.Constant()
        c["format"].setValue(GafferImage.Format(200, 200, 1.0))
        g = GafferImage.Grade()
        g["in"].setInput(c["out"])
        g["multiply"].setValue(imath.Color3f(0.4, 0.5, 0.6))

        gradedImage = GafferImage.ImageAlgo.image(g["out"])

        # not enough for both images - will cause cache thrashing
        Gaffer.ValuePlug.setCacheMemoryLimit(
            2 * g["out"].channelData("R", imath.V2i(0)).memoryUsage())

        images = []
        exceptions = []

        def grader():

            try:
                images.append(GafferImage.ImageAlgo.image(g["out"]))
            except Exception as e:
                exceptions.append(e)

        def processer():

            try:
                GafferImageTest.processTiles(g["out"])
            except Exception as e:
                exceptions.append(e)

        graderThreads = []
        for i in range(0, 10):
            thread = threading.Thread(target=grader)
            graderThreads.append(thread)
            thread.start()

        for thread in graderThreads:
            thread.join()

        for image in images:
            self.assertEqual(image, gradedImage)

        processerThreads = []
        for i in range(0, 10):
            thread = threading.Thread(target=processer)
            processerThreads.append(thread)
            thread.start()

        for thread in processerThreads:
            thread.join()

        for e in exceptions:
            raise e
Esempio n. 11
0
	def testDefaultFormatPlugExists( self ) :
		# Create a node to make sure that we have a default format...
		s = Gaffer.ScriptNode()
		n = GafferImage.Grade()
		s.addChild( n )

		try:
			# Now assert that the default format plug exists. If it doesn't then an exception is raised.
			s["defaultFormat"]
		except:
			self.assertTrue(False)
Esempio n. 12
0
    def testDefaultFormatWrite(self):

        s = Gaffer.ScriptNode()
        w1 = GafferImage.ImageWriter()
        w2 = GafferImage.ImageWriter()
        g = GafferImage.Grade()

        s.addChild(g)
        s.addChild(w2)

        testScanlineFile = self.temporaryDirectory(
        ) + "/test.defaultFormat.scanline.exr"
        testTileFile = self.temporaryDirectory(
        ) + "/test.defaultFormat.tile.exr"
        self.failIf(os.path.exists(testScanlineFile))
        self.failIf(os.path.exists(testTileFile))

        GafferImage.FormatPlug.acquireDefaultFormatPlug(s).setValue(
            GafferImage.Format(
                IECore.Box2i(IECore.V2i(-7, -2), IECore.V2i(23, 25)), 1.))

        w1["in"].setInput(g["out"])
        w1["fileName"].setValue(testScanlineFile)
        w1["channels"].setValue(
            IECore.StringVectorData(g["out"]["channelNames"].getValue()))
        w1["openexr"]["mode"].setValue(GafferImage.ImageWriter.Mode.Scanline)

        w2["in"].setInput(g["out"])
        w2["fileName"].setValue(testTileFile)
        w2["channels"].setValue(
            IECore.StringVectorData(g["out"]["channelNames"].getValue()))
        w2["openexr"]["mode"].setValue(GafferImage.ImageWriter.Mode.Tile)

        # Try to execute. In older versions of the ImageWriter this would throw an exception.
        with s.context():
            w1.execute()
            w2.execute()
        self.failUnless(os.path.exists(testScanlineFile))
        self.failUnless(os.path.exists(testTileFile))

        # Check the output.
        expectedFile = self.__defaultFormatFile
        expectedOutput = IECore.Reader.create(expectedFile).read()
        expectedOutput.blindData().clear()

        writerScanlineOutput = IECore.Reader.create(testScanlineFile).read()
        writerScanlineOutput.blindData().clear()

        writerTileOutput = IECore.Reader.create(testTileFile).read()
        writerTileOutput.blindData().clear()

        self.assertEqual(writerScanlineOutput, expectedOutput)
        self.assertEqual(writerTileOutput, expectedOutput)
Esempio n. 13
0
    def testDriverChannel(self):

        rRaw = GafferImage.ImageReader()
        rRaw["fileName"].setValue(
            os.path.dirname(__file__) + "/images/circles.exr")

        r = GafferImage.Grade()
        r["in"].setInput(rRaw["out"])
        # Trim off the noise in the blacks so that areas with no visible color are actually flat
        r["blackPoint"].setValue(imath.Color4f(0.03))

        masterMedian = GafferImage.Median()
        masterMedian["in"].setInput(r["out"])
        masterMedian["radius"].setValue(imath.V2i(2))

        masterMedian["masterChannel"].setValue("G")

        expected = GafferImage.ImageReader()
        expected["fileName"].setValue(
            os.path.dirname(__file__) + "/images/circlesGreenMedian.exr")

        # Note that in this expected image, the green channel is nicely medianed, and red and blue are completely
        # unchanged in areas where there is no green.  In areas where red and blue overlap with a noisy green,
        # they get a bit scrambled.  This is why in practice, you would use something like luminance, rather
        # than just the green channel
        self.assertImagesEqual(masterMedian["out"],
                               expected["out"],
                               ignoreMetadata=True,
                               maxDifference=0.0005)

        defaultMedian = GafferImage.Median()
        defaultMedian["in"].setInput(r["out"])
        defaultMedian["radius"].setValue(imath.V2i(2))

        masterMedianSingleChannel = GafferImage.DeleteChannels()
        masterMedianSingleChannel["in"].setInput(masterMedian["out"])
        masterMedianSingleChannel["mode"].setValue(
            GafferImage.DeleteChannels.Mode.Keep)

        defaultMedianSingleChannel = GafferImage.DeleteChannels()
        defaultMedianSingleChannel["in"].setInput(defaultMedian["out"])
        defaultMedianSingleChannel["mode"].setValue(
            GafferImage.DeleteChannels.Mode.Keep)

        for c in ["R", "G", "B"]:
            masterMedian["masterChannel"].setValue(c)
            masterMedianSingleChannel["channels"].setValue(c)
            defaultMedianSingleChannel["channels"].setValue(c)

            # When we look at just the channel being used as the master, it matches a default median not using
            # a master
            self.assertImagesEqual(masterMedianSingleChannel["out"],
                                   defaultMedianSingleChannel["out"])
Esempio n. 14
0
    def testAllChannels(self):
        c = GafferImage.Constant()
        c["format"].setValue(GafferImage.Format(50, 50, 1.0))
        c["color"].setValue(imath.Color4f(0.125, 0.25, 0.5, 0.75))

        s = GafferImage.Shuffle()
        s["channels"].addChild(
            GafferImage.Shuffle.ChannelPlug('customChannel', '__white'))
        s["in"].setInput(c["out"])

        g = GafferImage.Grade()
        g["in"].setInput(s["out"])

        def sample(x, y):
            redSampler = GafferImage.Sampler(
                g["out"], "R",
                g["out"]["format"].getValue().getDisplayWindow())
            greenSampler = GafferImage.Sampler(
                g["out"], "G",
                g["out"]["format"].getValue().getDisplayWindow())
            blueSampler = GafferImage.Sampler(
                g["out"], "B",
                g["out"]["format"].getValue().getDisplayWindow())
            alphaSampler = GafferImage.Sampler(
                g["out"], "A",
                g["out"]["format"].getValue().getDisplayWindow())
            customSampler = GafferImage.Sampler(
                g["out"], "customChannel",
                g["out"]["format"].getValue().getDisplayWindow())

            return [
                redSampler.sample(x, y),
                greenSampler.sample(x, y),
                blueSampler.sample(x, y),
                alphaSampler.sample(x, y),
                customSampler.sample(x, y)
            ]

        self.assertEqual(sample(25, 25), [0.125, 0.25, 0.5, 0.75, 1.0])

        g["offset"].setValue(imath.Color4f(3, 3, 3, 3))
        self.assertEqual(sample(25, 25), [3.125, 3.25, 3.5, 0.75, 1.0])

        g["channels"].setValue(IECore.StringVectorData(["A"]))
        self.assertEqual(sample(25, 25), [0.125, 0.25, 0.5, 3.75, 1.0])

        g["channels"].setValue(IECore.StringVectorData(["customChannel"]))
        self.assertEqual(sample(25, 25), [0.125, 0.25, 0.5, 0.75, 4.0])

        g["channels"].setValue(
            IECore.StringVectorData(["R", "G", "B", "A", "customChannel"]))
        self.assertEqual(sample(25, 25), [3.125, 3.25, 3.5, 3.75, 4.0])
Esempio n. 15
0
	def __createDepthGrade():
		# \todo - this is simple and generally useful node
		# Get John's permission to make it available to our users
		depthGrade = GafferImage.ImageProcessor( "DepthGrade" )
		depthGrade.addChild( GafferImage.Grade("Grade") )
		Gaffer.PlugAlgo.promote( depthGrade["Grade"]["multiply"]["r"] ).setName( "depthMultiply" )
		Gaffer.PlugAlgo.promote( depthGrade["Grade"]["offset"]["r"] ).setName( "depthOffset" )

		depthGrade["Grade"]["in"].setInput( depthGrade["in"] )
		depthGrade["Grade"]["enabled"].setInput( depthGrade["enabled"] )
		depthGrade["out"].setInput( depthGrade["Grade"]["out"] )
		depthGrade["Grade"]["channels"].setValue( 'Z ZBack' )
		depthGrade["Grade"]["blackClamp"].setValue( False )
		return depthGrade
	def testCreation( self ):
		
		# Create a node to make sure that we have a default format...
		s = Gaffer.ScriptNode()
		n = GafferImage.Grade()
		s.addChild( n )
		
		# Get the format names
		formatNames = GafferImage.Format.formatNames()
		
		# Create the plug's ui element.
		fw = GafferImageUI.FormatPlugValueWidget( s["defaultFormat"], lazy=False )
		
		# Now compare the format names against those in the UI element.
		self.assertEqual( len( fw ), len( formatNames ) )
Esempio n. 17
0
	def testBoxPromotion( self ) :

		b = Gaffer.Box()
		b["n"] = GafferImage.Grade()

		self.assertTrue( Gaffer.PlugAlgo.canPromote( b["n"]["in"] ) )
		self.assertTrue( Gaffer.PlugAlgo.canPromote( b["n"]["out"] ) )

		i = Gaffer.PlugAlgo.promote( b["n"]["in"] )
		o = Gaffer.PlugAlgo.promote( b["n"]["out"] )

		self.assertEqual( b["n"]["in"].getInput(), i )
		self.assertEqual( o.getInput(), b["n"]["out"] )

		self.assertTrue( Gaffer.PlugAlgo.isPromoted( b["n"]["in"] ) )
		self.assertTrue( Gaffer.PlugAlgo.isPromoted( b["n"]["out"] ) )
	def testAccessors( self ) :
		
		# Create a node to make sure that we have a default format...
		s = Gaffer.ScriptNode()
		n = GafferImage.Grade()
		s.addChild( n )
		
		# Create the plug's ui element.
		fw = GafferImageUI.FormatPlugValueWidget( s["defaultFormat"], lazy=False )
		
		# Test the accessors
		formatNameAndValue = fw[0]
		self.assertTrue( isinstance( formatNameAndValue[0], str ) )
		self.assertTrue( isinstance( formatNameAndValue[1], GafferImage.Format ) )
		self.assertEqual( fw[ formatNameAndValue[0] ], formatNameAndValue[1] )
		self.assertEqual( fw[ formatNameAndValue[1] ], formatNameAndValue[0] )
Esempio n. 19
0
	def testBoxPromotion( self ) :

		b = Gaffer.Box()
		b["n"] = GafferImage.Grade()

		self.assertTrue( b.canPromotePlug( b["n"]["in"], asUserPlug=False ) )
		self.assertTrue( b.canPromotePlug( b["n"]["out"], asUserPlug=False ) )

		i = b.promotePlug( b["n"]["in"], asUserPlug=False )
		o = b.promotePlug( b["n"]["out"], asUserPlug=False )

		self.assertEqual( b["n"]["in"].getInput(), i )
		self.assertEqual( o.getInput(), b["n"]["out"] )

		self.assertTrue( b.plugIsPromoted( b["n"]["in"] ) )
		self.assertTrue( b.plugIsPromoted( b["n"]["out"] ) )
Esempio n. 20
0
	def testHashChanged( self ) :
		# Create a grade node and check that the format changes if it is unconnected.
		n = GafferImage.Grade()
		s = Gaffer.ScriptNode()
		s.addChild( n )
		
		h1 = n["out"]["format"].hash()
		
		# Change the default format.
		GafferImage.Format.registerFormat( self.__testFormatValue(), self.__testFormatName() )
		GafferImage.Format.setDefaultFormat( s, self.__testFormatValue() )
		
		# Check that the hash has changed.
		h2 = n["out"]["format"].hash()
		
		self.assertNotEqual( h1, h2 )
Esempio n. 21
0
	def testChannelDataHashes( self ) :
		# Create a grade node and save the hash of a tile from each channel.
		i = GafferImage.ImageReader()
		i["fileName"].setValue( self.checkerFile )

		grade = GafferImage.Grade()
		grade["in"].setInput(i["out"])
		grade["gain"].setValue( IECore.Color3f( 2., 2., 2. ) )

		h1 = grade["out"].channelData( "R", IECore.V2i( 0 ) ).hash()
		h2 = grade["out"].channelData( "R", IECore.V2i( GafferImage.ImagePlug().tileSize() ) ).hash()
		self.assertNotEqual( h1, h2 )

		# Test that two tiles within the same image have the same hash when disabled.
		grade["enabled"].setValue(False)
		h1 = grade["out"].channelData( "R", IECore.V2i( 0 ) ).hash()
		h2 = grade["out"].channelData( "R", IECore.V2i( GafferImage.ImagePlug().tileSize() ) ).hash()
		self.assertNotEqual( h1, h2 )
Esempio n. 22
0
    def __init__(self, name="MultiGrade"):

        GafferImage.ImageProcessor.__init__(self, name)

        grade = GafferImage.Grade()
        self["__Grade"] = grade
        grade["in"].setInput(self["in"])

        disableSwitch = Gaffer.Switch()
        self["__DisableSwitch"] = disableSwitch
        disableSwitch.setup(self["in"])
        disableSwitch["in"][0].setInput(self["in"])
        disableSwitch["in"][1].setInput(grade["out"])
        disableSwitch["index"].setInput(self["enabled"])
        self["out"].setInput(disableSwitch["out"])
        self['out'].setFlags(Gaffer.Plug.Flags.Serialisable, False)

        spreadsheet = Gaffer.Spreadsheet()
        self["__Spreadsheet"] = spreadsheet
        spreadsheet["selector"].setValue("${image:channelName}")

        Gaffer.Metadata.registerValue(spreadsheet["rows"],
                                      "spreadsheet:columnsNeedSerialisation",
                                      False,
                                      persistent=False)

        for p in ("blackPoint", "whitePoint", "gamma"):
            grade[p].gang()
            spreadsheet["rows"].addColumn(grade[p]["r"], p)
            grade[p]["r"].setInput(spreadsheet["out"][p])

        channelsExpression = Gaffer.Expression()
        self["__ChannelsExpression"] = channelsExpression
        channelsExpression.setExpression(
            inspect.cleandoc("""
				rowNames = parent["__Spreadsheet"]["activeRowNames"]
				parent["__Grade"]["channels"] = " ".join( rowNames )
				"""), "python")

        promotedRowsPlug = Gaffer.PlugAlgo.promote(spreadsheet["rows"])
        Gaffer.Metadata.registerValue(promotedRowsPlug,
                                      "spreadsheet:columnsNeedSerialisation",
                                      False,
                                      persistent=False)
Esempio n. 23
0
	def testDefaultFormatChanged( self ) :
		# Create a grade node and check that the format changes if it is unconnected.
		n = GafferImage.Grade()
		s = Gaffer.ScriptNode()
		s.addChild( n )
		
		p = GafferImage.ImagePlug( "test", GafferImage.ImagePlug.Direction.In )
		p.setInput( n["out"] )
		
		with s.context() :
			f1 = p["format"].getValue()

			# Change the default format.
			GafferImage.Format.registerFormat( self.__testFormatValue(), self.__testFormatName() )
			GafferImage.Format.setDefaultFormat( s, self.__testFormatValue() )
		
			# Check that the hash has changed.
			f2 = p["format"].getValue()
		
			self.assertNotEqual( f1, f2 )
Esempio n. 24
0
	def testCacheThreadSafety( self ) :
	
		c = GafferImage.Constant()
		c["format"].setValue( GafferImage.Format( 200, 200, 1.0 ) )
		g = GafferImage.Grade()
		g["in"].setInput( c["out"] )
		g["multiply"].setValue( IECore.Color3f( 0.4, 0.5, 0.6 ) )
		
		gradedImage = g["out"].image()

		# not enough for both images - will cause cache thrashing
		Gaffer.ValuePlug.setCacheMemoryLimit( 2 * g["out"].channelData( "R", IECore.V2i( 0 ) ).memoryUsage() )
		
		images = []
		exceptions = []
		def grader() :
		
			try :
				images.append( g["out"].image() )
			except Exception, e :
				exceptions.append( e )
Esempio n. 25
0
    def testLoop(self):

        script = Gaffer.ScriptNode()

        script["c"] = GafferImage.Constant()
        script["loop"] = Gaffer.Loop()
        script["loop"].setup(GafferImage.ImagePlug())
        script["loop"]["in"].setInput(script["c"]["out"])

        script["grade"] = GafferImage.Grade()
        script["grade"]["offset"].setValue(imath.Color3f(.1))
        script["grade"]["in"].setInput(script["loop"]["previous"])
        script["loop"]["next"].setInput(script["grade"]["out"])

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

        with script.context():

            script["loop"]["iterations"].setValue(2)
            self.assertAlmostEqual(script["sampler"]["color"]["r"].getValue(),
                                   .2)

            script["loop"]["iterations"].setValue(4)
            self.assertAlmostEqual(script["sampler"]["color"]["r"].getValue(),
                                   .4)

        script2 = Gaffer.ScriptNode()
        script2.execute(script.serialise())

        with script2.context():

            script2["loop"]["iterations"].setValue(3)
            self.assertAlmostEqual(script2["sampler"]["color"]["r"].getValue(),
                                   .3)

            script2["loop"]["iterations"].setValue(5)
            self.assertAlmostEqual(script2["sampler"]["color"]["r"].getValue(),
                                   .5)
Esempio n. 26
0
    def testChannelEnable(self):
        i = GafferImage.ImageReader()
        i["fileName"].setValue(self.checkerFile)

        # Create a grade node and save the hash of a tile from each channel.
        grade = GafferImage.Grade()
        grade["in"].setInput(i["out"])
        grade["gain"].setValue(imath.Color3f(2., 2., 2.))
        hashRed = grade["out"].channelData("R", imath.V2i(0)).hash()
        hashGreen = grade["out"].channelData("G", imath.V2i(0)).hash()
        hashBlue = grade["out"].channelData("B", imath.V2i(0)).hash()

        # Now we set the gamma on the green channel to 0 which should disable it's output.
        # The red and blue channels should still be graded as before.
        grade["gamma"].setValue(imath.Color3f(1., 0., 1.))
        hashRed2 = grade["out"].channelData("R", imath.V2i(0)).hash()
        hashGreen2 = grade["out"].channelData("G", imath.V2i(0)).hash()
        hashBlue2 = grade["out"].channelData("B", imath.V2i(0)).hash()

        self.assertEqual(hashRed, hashRed2)
        self.assertNotEqual(hashGreen, hashGreen2)
        self.assertEqual(hashBlue, hashBlue2)
Esempio n. 27
0
    def testPerLayerExpression(self):

        script = Gaffer.ScriptNode()

        script["c1"] = GafferImage.Constant()
        script["c1"]["color"].setValue(imath.Color4f(1))

        script["c2"] = GafferImage.Constant()
        script["c2"]["color"].setValue(imath.Color4f(1))
        script["c2"]["layer"].setValue("B")

        script["copyChannels"] = GafferImage.CopyChannels()
        script["copyChannels"]["in"][0].setInput(script["c1"]["out"])
        script["copyChannels"]["in"][1].setInput(script["c2"]["out"])
        script["copyChannels"]["channels"].setValue("*")

        script["grade"] = GafferImage.Grade()
        script["grade"]["in"].setInput(script["copyChannels"]["out"])
        script["grade"]["channels"].setValue("*")

        script["expression"] = Gaffer.Expression()
        script["expression"].setExpression(
            inspect.cleandoc("""
			import GafferImage
			layerName = GafferImage.ImageAlgo.layerName( context["image:channelName" ] )
			parent["grade"]["gain"] = imath.Color4f( 1 if layerName == "B" else 0.5 )
			"""))

        sampler = GafferImage.ImageSampler()
        sampler["image"].setInput(script["grade"]["out"])
        sampler["pixel"].setValue(imath.V2f(10.5))

        sampler["channels"].setValue(
            IECore.StringVectorData(["R", "G", "B", "A"]))
        self.assertEqual(sampler["color"].getValue(), imath.Color4f(0.5))

        sampler["channels"].setValue(
            IECore.StringVectorData(["B.R", "B.G", "B.B", "B.A"]))
        self.assertEqual(sampler["color"].getValue(), imath.Color4f(1))
Esempio n. 28
0
    def __init__(self, name='BleedFill'):

        GafferImage.ImageProcessor.__init__(self, name)

        self.addChild(Gaffer.BoolPlug("expandDataWindow"))

        self.addChild(Gaffer.IntPlug("__blurIterations"))
        self['__blurIterations'].setFlags(Gaffer.Plug.Flags.Serialisable,
                                          False)

        self["__blurIterationsExpression"] = Gaffer.Expression()
        self["__blurIterationsExpression"].setExpression(
            inspect.cleandoc("""
			import math
			f = parent["in"]["format"]
			parent["__blurIterations"] = int( math.log( min( f.width(), f.height() ), 2 ) )
			"""), "python")

        self["__displayWindowConstant"] = GafferImage.Constant()
        self["__displayWindowConstant"]["color"].setValue(
            imath.Color4f(0, 0, 0, 0))

        self["__displayWindowExpression"] = Gaffer.Expression()
        self["__displayWindowExpression"].setExpression(
            'parent["__displayWindowConstant"]["format"] = parent["in"]["format"]',
            "python")

        self["__expandMerge"] = GafferImage.Merge()
        self["__expandMerge"]["in"][0].setInput(self["in"])
        self["__expandMerge"]["in"][1].setInput(
            self["__displayWindowConstant"]["out"])
        self["__expandMerge"]["operation"].setValue(
            GafferImage.Merge.Operation.Over)

        self["__expandSwitch"] = Gaffer.Switch()
        self["__expandSwitch"].setup(self["in"])
        self["__expandSwitch"]["in"][0].setInput(self["in"])
        self["__expandSwitch"]["in"][1].setInput(self["__expandMerge"]["out"])
        self["__expandSwitch"]["index"].setInput(self["expandDataWindow"])

        # First blur via repeated downsampling
        self["__blurLoop"] = GafferImage.ImageLoop()
        self["__blurLoop"]["iterations"].setInput(self["__blurIterations"])
        self["__blurLoop"]["in"].setInput(self["__expandSwitch"]["out"])

        self["__downsample"] = GafferImage.Resize()
        self["__downsample"]["in"].setInput(self["__blurLoop"]["previous"])
        self["__downsample"]["filter"].setValue("sharp-gaussian")

        self["__downsampleExpression"] = Gaffer.Expression()
        self["__downsampleExpression"].setExpression(
            inspect.cleandoc("""
			import GafferImage
			import IECore

			f = parent["in"]["format"]

			divisor = 2 <<  context.get("loop:index", 0)

			parent["__downsample"]["format"] =  GafferImage.Format( imath.Box2i( f.getDisplayWindow().min() / divisor, f.getDisplayWindow().max() / divisor ), 1.0 )
			"""), "python")

        # Multiply each successive octave by a falloff factor so that we prioritize higher frequencies when they exist
        self["__grade"] = GafferImage.Grade("Grade")
        self["__grade"]['channels'].setValue("*")
        self["__grade"]['multiply'].setValue(imath.Color4f(0.1))
        self["__grade"]["in"].setInput(self["__downsample"]["out"])

        self["__blurLoop"]["next"].setInput(self["__grade"]["out"])

        self["__reverseLoopContext"] = GafferImage.ImageContextVariables()
        self["__reverseLoopContext"]["in"].setInput(
            self["__blurLoop"]["previous"])
        self["__reverseLoopContext"]["variables"].addMember(
            "loop:index", IECore.IntData(0), "loopIndex")

        self["__reverseLoopExpression"] = Gaffer.Expression()
        self["__reverseLoopExpression"].setExpression(
            inspect.cleandoc("""
			parent["__reverseLoopContext"]["variables"]["loopIndex"]["value"] = parent["__blurIterations"] - context.get( "loop:index", 0 )
			"""), "python")

        # Loop through image resolution levels combining the most downsampled image with less downsampled versions,
        # one level at a time
        self["__combineLoop"] = GafferImage.ImageLoop()

        self["__combineLoopExpression"] = Gaffer.Expression()
        self["__combineLoopExpression"].setExpression(
            'parent["__combineLoop"]["iterations"] = parent["__blurIterations"] + 1',
            "python")

        self["__upsample"] = GafferImage.Resize()
        self["__upsample"]["in"].setInput(self["__combineLoop"]["previous"])
        self["__upsample"]["filter"].setValue("smoothGaussian")

        self["__upsampleExpression"] = Gaffer.Expression()
        self["__upsampleExpression"].setExpression(
            inspect.cleandoc("""
			import GafferImage
			import IECore

			f = parent["in"]["format"]

			divisor = 1 <<  (  parent["__blurIterations"] - context.get("loop:index", 0) )

			parent["__upsample"]["format"] =  GafferImage.Format( imath.Box2i( f.getDisplayWindow().min() / divisor, f.getDisplayWindow().max() / divisor ), 1.0 )
			"""), "python")

        self["__merge"] = GafferImage.Merge()
        self["__merge"]["operation"].setValue(GafferImage.Merge.Operation.Over)
        self["__merge"]["in"][0].setInput(self["__upsample"]["out"])
        self["__merge"]["in"][1].setInput(self["__reverseLoopContext"]["out"])

        self["__combineLoop"]["next"].setInput(self["__merge"]["out"])

        # When downsampling to target display window sizes with a non-square image,
        # the data window size gets rounded up to the nearest integer, potentially introducing
        # a small error in data window size that gets amplified during repeated upsampling.
        # To fix this, crop to the data window after scaling.
        self["__restoreDataSize"] = GafferImage.Crop()
        self["__restoreDataSize"]["in"].setInput(self["__combineLoop"]["out"])
        self["__restoreDataSize"]["affectDisplayWindow"].setValue(False)

        self["__restoreDataExpression"] = Gaffer.Expression()
        self["__restoreDataExpression"].setExpression(
            'parent["__restoreDataSize"]["area"] = parent["__expandSwitch"]["out"]["dataWindow"]',
            "python")

        self["__unpremult"] = GafferImage.Unpremultiply()
        self["__unpremult"]['channels'].setValue("*")
        self["__unpremult"]["in"].setInput(self["__restoreDataSize"]["out"])

        self["__resetAlpha"] = GafferImage.Shuffle()
        self["__resetAlpha"]["channels"].addChild(
            GafferImage.Shuffle.ChannelPlug("A", "__white"))
        self["__resetAlpha"]["in"].setInput(self["__unpremult"]["out"])

        self["__disableSwitch"] = Gaffer.Switch()
        self["__disableSwitch"].setup(self["in"])
        self["__disableSwitch"]["in"][0].setInput(self["in"])
        self["__disableSwitch"]["in"][1].setInput(self["__resetAlpha"]["out"])
        self["__disableSwitch"]["index"].setInput(self["enabled"])

        self['out'].setFlags(Gaffer.Plug.Flags.Serialisable, False)
        self["out"].setInput(self["__disableSwitch"]["out"])
Esempio n. 29
0
    def __init__(self, name="ColoriseSHO"):

        GafferImage.ImageProcessor.__init__(self, name)

        self["show"] = Gaffer.IntPlug(defaultValue=0, minValue=0, maxValue=3)

        # Channel colorising

        merge = GafferImage.Merge()
        self["__Merge"] = merge
        merge["operation"].setValue(0)

        outputSwitch = Gaffer.Switch()
        self["__Switch_output"] = outputSwitch
        outputSwitch.setup(self["out"])
        outputSwitch["index"].setInput(self["show"])
        outputSwitch["in"][0].setInput(merge["out"])

        for channel in GafferAstro.NarrowbandChannels:

            self["source%s" % channel] = Gaffer.StringPlug(
                defaultValue='%s.input' % channel)
            self["range%s" %
                 channel] = Gaffer.V2fPlug(defaultValue=imath.V2f(0, 1))
            self["map%s" % channel] = Gaffer.SplinefColor4fPlug(
                defaultValue=self.__mapDefaults[channel])
            self["saturation%s" % channel] = Gaffer.FloatPlug(defaultValue=1.0,
                                                              minValue=0.0)
            self["multiply%s" % channel] = Gaffer.FloatPlug(defaultValue=1.0)
            self["gamma%s" % channel] = Gaffer.FloatPlug(defaultValue=1.0)

            colorise = GafferAstro.Colorise()
            self["__Colorise_%s" % channel] = colorise

            colorise["in"].setInput(self["in"])
            colorise["channel"].setInput(self["source%s" % channel])
            colorise["mapEnabled"].setValue(True)
            colorise["range"].setInput(self["range%s" % channel])
            colorise["enabled"].setInput(colorise["channel"])
            # Work around issue where setInput doesn't sync the number of knots
            colorise["map"].setValue(self["map%s" % channel].getValue())
            colorise["map"].setInput(self["map%s" % channel])

            cdl = GafferImage.CDL()
            self["__CDL_%s" % channel] = cdl
            cdl["in"].setInput(colorise["out"])
            cdl["saturation"].setInput(self["saturation%s" % channel])

            grade = GafferImage.Grade()
            self["__Grade_%s" % channel] = grade
            grade["in"].setInput(cdl["out"])
            grade["multiply"].gang()
            grade["multiply"]["r"].setInput(self["multiply%s" % channel])
            grade["gamma"].gang()
            grade["gamma"]["r"].setInput(self["gamma%s" % channel])

            merge["in"][len(merge["in"]) - 1].setInput(grade["out"])
            outputSwitch["in"][len(outputSwitch["in"]) - 1].setInput(
                grade["out"])

        self["saturation"] = Gaffer.FloatPlug(defaultValue=1.0, minValue=0.0)
        self["blackPoint"] = Gaffer.FloatPlug(defaultValue=0.0)
        self["whitePoint"] = Gaffer.FloatPlug(defaultValue=1.0)
        self["multiply"] = Gaffer.Color4fPlug(
            defaultValue=imath.Color4f(1, 1, 1, 1))
        self["gamma"] = Gaffer.FloatPlug(defaultValue=1.0, minValue=0.0)

        outputCdl = GafferImage.CDL()
        self["__CDL_output"] = outputCdl
        outputCdl["in"].setInput(outputSwitch["out"])
        outputCdl["saturation"].setInput(self["saturation"])

        outputGrade = GafferImage.Grade()
        self["__Grade_output"] = outputGrade
        outputGrade["in"].setInput(outputCdl["out"])
        outputGrade["blackPoint"].gang()
        outputGrade["blackPoint"]["r"].setInput(self["blackPoint"])
        outputGrade["whitePoint"].gang()
        outputGrade["whitePoint"]["r"].setInput(self["whitePoint"])
        outputGrade["multiply"].setInput(self["multiply"])
        outputGrade["gamma"].gang()
        outputGrade["gamma"]["r"].setInput(self["gamma"])

        copyChannels = GafferImage.CopyChannels()
        self["__CopyChannels"] = copyChannels
        copyChannels["in"][0].setInput(self["in"])
        copyChannels["in"][1].setInput(outputGrade["out"])
        copyChannels["channels"].setValue("*")

        self["out"].setInput(copyChannels["out"])
Esempio n. 30
0
    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"])
        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"])
        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"].addMember(
            '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"].addMember(
            '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"].addMember(
            '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"].addMember('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"])

        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)