Ejemplo n.º 1
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)
Ejemplo n.º 2
0
    def testEnabled(self):

        script = Gaffer.ScriptNode()

        script["sphere"] = GafferScene.Sphere()
        script["loop"] = Gaffer.Loop()
        script["loop"].setup(GafferScene.ScenePlug())
        script["loop"]["in"].setInput(script["sphere"]["out"])

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

        script["transform"] = GafferScene.Transform()
        script["transform"]["transform"]["translate"]["x"].setValue(1)
        script["transform"]["in"].setInput(script["loop"]["previous"])
        script["transform"]["filter"].setInput(script["filter"]["out"])
        script["loop"]["next"].setInput(script["transform"]["out"])

        script["loop"]["iterations"].setValue(2)
        self.assertEqual(script["loop"]["out"].transform("/sphere"),
                         imath.M44f().translate(imath.V3f(2, 0, 0)))

        script["loop"]["enabled"].setValue(False)
        self.assertEqual(script["loop"]["out"].transform("/sphere"),
                         imath.M44f())

        self.assertScenesEqual(script["loop"]["out"], script["sphere"]["out"])
        self.assertSceneHashesEqual(script["loop"]["out"],
                                    script["sphere"]["out"])

        self.assertTrue(script["loop"].correspondingInput(
            script["loop"]["out"]).isSame(script["loop"]["in"]))
Ejemplo n.º 3
0
    def testIterationsContext(self):

        script = Gaffer.ScriptNode()

        script["sphere"] = GafferScene.Sphere()
        script["loop"] = Gaffer.Loop()
        script["loop"].setup(GafferScene.ScenePlug())
        script["loop"]["in"].setInput(script["sphere"]["out"])

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

        script["transform"] = GafferScene.Transform()
        script["transform"]["transform"]["translate"]["x"].setValue(1)
        script["transform"]["in"].setInput(script["loop"]["previous"])
        script["transform"]["filter"].setInput(script["filter"]["out"])
        script["loop"]["next"].setInput(script["transform"]["out"])

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

        self.assertEqual(script["loop"]["out"].transform("/sphere"),
                         imath.M44f().translate(imath.V3f(4, 0, 0)))
Ejemplo n.º 4
0
	def testChildBoundsCancellation( self ) :

		# Make Sierpinski triangle type thing.

		script = Gaffer.ScriptNode()

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

		script["loop"] = Gaffer.Loop()
		script["loop"].setup( script["sphere"]["out"] )
		script["loop"]["in"].setInput( script["sphere"]["out"] )
		script["loop"]["iterations"].setValue( 12 )

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

		script["transform1"] = GafferScene.Transform()
		script["transform1"]["in"].setInput( script["loop"]["previous"] )
		script["transform1"]["filter"].setInput( script["filter"]["out"] )
		script["transform1"]["transform"]["translate"]["x"].setValue( 1 )

		script["transform2"] = GafferScene.Transform()
		script["transform2"]["in"].setInput( script["loop"]["previous"] )
		script["transform2"]["filter"].setInput( script["filter"]["out"] )
		script["transform2"]["transform"]["translate"]["y"].setValue( 1 )

		script["transform3"] = GafferScene.Transform()
		script["transform3"]["in"].setInput( script["loop"]["previous"] )
		script["transform3"]["filter"].setInput( script["filter"]["out"] )
		script["transform3"]["transform"]["translate"]["z"].setValue( 1 )

		script["group"] = GafferScene.Group()
		script["group"]["in"][0].setInput( script["transform1"]["out"] )
		script["group"]["in"][1].setInput( script["transform2"]["out"] )
		script["group"]["in"][2].setInput( script["transform3"]["out"] )
		script["group"]["transform"]["scale"].setValue( imath.V3f( 0.666 ) )

		script["loop"]["next"].setInput( script["group"]["out"] )

		for i in range( 0, 10 ) :

			# Launch background task to compute root bounds. This will perform
			# a deep recursion through the hierarchy using parallel tasks.

			backgroundTask = Gaffer.ParallelAlgo.callOnBackgroundThread(
				script["loop"]["out"],
				lambda : script["loop"]["out"].bound( "/" )
			)

			time.sleep( 0.1 )

			# Cancel background task so that we don't perform all the work.
			# This triggered a crash bug in TaskMutex, but should return
			# cleanly if it has been fixed.
			backgroundTask.cancelAndWait()
Ejemplo n.º 5
0
    def testSetup(self):

        n = Gaffer.Loop()

        self.assertNotIn("in", n)
        self.assertNotIn("out", n)
        self.assertNotIn("previous", n)
        self.assertNotIn("next", n)

        n.setup(Gaffer.StringPlug())

        self.assertIsInstance(n["in"], Gaffer.StringPlug)
        self.assertIsInstance(n["out"], Gaffer.StringPlug)
        self.assertIsInstance(n["previous"], Gaffer.StringPlug)
        self.assertIsInstance(n["next"], Gaffer.StringPlug)
Ejemplo n.º 6
0
    def testSerialisationUsesSetup(self):

        s1 = Gaffer.ScriptNode()
        s1["c"] = Gaffer.Loop()
        s1["c"].setup(Gaffer.IntPlug())

        ss = s1.serialise()
        self.assertIn("setup", ss)
        self.assertEqual(ss.count("addChild"), 1)
        self.assertNotIn("Dynamic", ss)
        self.assertNotIn("Serialisable", ss)
        self.assertNotIn("setInput", ss)

        s2 = Gaffer.ScriptNode()
        s2.execute(ss)
        self.assertIn("in", s2["c"])
        self.assertIn("out", s2["c"])
        self.assertIsInstance(s2["c"]["in"], Gaffer.IntPlug)
        self.assertIsInstance(s2["c"]["out"], Gaffer.IntPlug)
        self.assertIsInstance(s2["c"]["previous"], Gaffer.IntPlug)
        self.assertIsInstance(s2["c"]["next"], Gaffer.IntPlug)
Ejemplo n.º 7
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)
Ejemplo n.º 8
0
    def testLoop(self):

        script = Gaffer.ScriptNode()

        script["sphere"] = GafferScene.Sphere()
        script["loop"] = Gaffer.Loop()
        script["loop"].setup(GafferScene.ScenePlug())
        script["loop"]["in"].setInput(script["sphere"]["out"])

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

        script["transform"] = GafferScene.Transform()
        script["transform"]["transform"]["translate"]["x"].setValue(1)
        script["transform"]["in"].setInput(script["loop"]["previous"])
        script["transform"]["filter"].setInput(script["filter"]["out"])
        script["loop"]["next"].setInput(script["transform"]["out"])

        script["loop"]["iterations"].setValue(2)
        self.assertEqual(script["loop"]["out"].transform("/sphere"),
                         imath.M44f().translate(imath.V3f(2, 0, 0)))

        script["loop"]["iterations"].setValue(4)
        self.assertEqual(script["loop"]["out"].transform("/sphere"),
                         imath.M44f().translate(imath.V3f(4, 0, 0)))

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

        script2["loop"]["iterations"].setValue(3)
        self.assertEqual(script2["loop"]["out"].transform("/sphere"),
                         imath.M44f().translate(imath.V3f(3, 0, 0)))

        script2["loop"]["iterations"].setValue(5)
        self.assertEqual(script2["loop"]["out"].transform("/sphere"),
                         imath.M44f().translate(imath.V3f(5, 0, 0)))
Ejemplo n.º 9
0
    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"])
        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)
Ejemplo n.º 10
0
    def intLoop(self):

        result = Gaffer.Loop()
        result.setup(Gaffer.IntPlug())
        return result
Ejemplo n.º 11
0
    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)
Ejemplo n.º 12
0
    def testBlurRange(self):

        constant = GafferImage.Constant()
        constant["format"].setValue(GafferImage.Format(5, 5, 1.000))
        constant["color"].setValue(imath.Color4f(1, 1, 1, 1))

        cropDot = GafferImage.Crop()
        cropDot["area"].setValue(imath.Box2i(imath.V2i(2, 2), imath.V2i(3, 3)))
        cropDot["affectDisplayWindow"].setValue(False)
        cropDot["in"].setInput(constant["out"])

        blur = GafferImage.Blur()
        blur["expandDataWindow"].setValue(True)
        blur["in"].setInput(cropDot["out"])
        blur["radius"]["y"].setInput(blur["radius"]["x"])

        expression = Gaffer.Expression()
        blur.addChild(expression)
        expression.setExpression(
            'parent["radius"]["x"] = context[ "loop:index" ] * 0.2', "python")

        loopInit = GafferImage.Constant()
        loopInit["format"].setValue(GafferImage.Format(5, 5, 1.000))

        imageLoop = Gaffer.Loop()
        imageLoop.setup(GafferImage.ImagePlug())
        imageLoop["in"].setInput(loopInit["out"])

        merge = GafferImage.Merge()
        merge["in"].addChild(
            GafferImage.ImagePlug(
                "in2",
                flags=Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic,
            ))
        merge["in"]["in0"].setInput(blur["out"])
        merge["in"]["in1"].setInput(imageLoop["previous"])

        offset = GafferImage.Offset()
        offset["offset"].setValue(imath.V2i(-5, 0))
        offset["in"].setInput(merge["out"])

        imageLoop["next"].setInput(offset["out"])

        deleteChannels = GafferImage.DeleteChannels()
        deleteChannels["mode"].setValue(GafferImage.DeleteChannels.Mode.Keep)
        deleteChannels["channels"].setValue(IECore.StringVectorData(['R']))
        deleteChannels["in"].setInput(imageLoop["out"])

        finalCrop = GafferImage.Crop()
        finalCrop["areaSource"].setValue(1)
        finalCrop["in"].setInput(deleteChannels["out"])

        # Enable to write out images for visual comparison
        if False:
            testWriter = GafferImage.ImageWriter()
            testWriter["in"].setInput(finalCrop["out"])
            testWriter["fileName"].setValue("/tmp/blurRange.exr")
            testWriter["openexr"]["dataType"].setValue('float')
            testWriter["task"].execute()

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

        self.assertImagesEqual(finalCrop["out"],
                               expectedReader["out"],
                               maxDifference=0.00001,
                               ignoreMetadata=True)