예제 #1
0
	def testContextSanitisation( self ) :

		plane = GafferScene.Plane()

		attributeQuery = GafferScene.AttributeQuery()
		attributeQuery.setup( Gaffer.StringVectorDataPlug( defaultValue = IECore.StringVectorData() ) )
		attributeQuery["scene"].setInput( plane["out"] )
		attributeQuery["location"].setValue( "/plane" )
		attributeQuery["attribute"].setValue( "test" )
		attributeQuery["default"].setValue( IECore.StringVectorData( [ "/plane" ] ) )

		pathFilter = GafferScene.PathFilter()
		pathFilter["paths"].setInput( attributeQuery["value"] )

		attributes = GafferScene.StandardAttributes()
		attributes["in"].setInput( plane["out"] )
		attributes["filter"].setInput( pathFilter["out"] )
		attributes["attributes"]["doubleSided"]["enabled"].setValue( True )

		# This exposes a bug whereby the PathFilter leaked the `scene:filter:inputScene`
		# and `scene:path` context variables when evaluating `paths`.
		with Gaffer.ContextMonitor( attributeQuery["value"] ) as contextMonitor :
			self.assertEqual( attributes["out"].attributes( "/plane")["doubleSided"].value, True )

		self.assertNotIn( "scene:filter:inputScene", contextMonitor.combinedStatistics().variableNames() )
		self.assertNotIn( "scene:path", contextMonitor.combinedStatistics().variableNames() )
예제 #2
0
    def testShaderNetworkGeneratedInGlobalContext(self):

        constant = GafferImage.Constant()

        outLayer = GafferOSL.OSLCode()
        outLayer["out"]["layer"] = GafferOSL.ClosurePlug(
            direction=Gaffer.Plug.Direction.Out,
            flags=Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic)
        outLayer["code"].setValue('layer = outLayer( "", color( 0, 1, 0) )')

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

        oslImage = GafferOSL.OSLImage()
        oslImage["in"].setInput(constant["out"])
        oslImage["shader"].setInput(outImage["out"]["out"])

        with Gaffer.ContextMonitor(oslImage["__oslCode"]) as cm:
            GafferImageTest.processTiles(oslImage["out"])

        cs = cm.combinedStatistics()
        self.assertEqual(cs.numUniqueContexts(), 1)
        self.assertNotIn("image:tileOrigin", cs.variableNames())
        self.assertNotIn("image:channelName", cs.variableNames())
예제 #3
0
    def testNoUnecessaryHistoryCalls(self):

        script = Gaffer.ScriptNode()
        script["camera"] = GafferScene.Camera()

        view = GafferSceneUI.SceneView()
        view["in"].setInput(script["camera"]["out"])

        view["camera"]["lookThroughEnabled"].setValue(True)
        view["camera"]["lookThroughCamera"].setValue("/camera")

        tool = GafferSceneUI.CameraTool(view)
        tool["active"].setValue(True)

        # Force CameraTool update, since it is done lazily just prior to render.
        view.viewportGadget().preRenderSignal()(view.viewportGadget())

        with Gaffer.ContextMonitor() as cm:

            view.viewportGadget().setCameraTransform(imath.M44f().translate(
                imath.V3f(1, 2, 3)))

            # Force update
            view.viewportGadget().preRenderSignal()(view.viewportGadget())

        # We do not want the CameraTool to have performed a `SceneAlgo::history`
        # query during the edit, as they can be expensive and aren't suitable
        # for repeated use during drags etc.
        self.assertNotIn(GafferScene.SceneAlgo.historyIDContextName(),
                         cm.combinedStatistics().variableNames())

        self.assertEqual(script["camera"]["transform"]["translate"].getValue(),
                         imath.V3f(1, 2, 3))
예제 #4
0
    def testContextLeaks(self):

        script = Gaffer.ScriptNode()

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

        script["contextVariables"] = Gaffer.ContextVariables()
        script["contextVariables"].setup(GafferScene.ScenePlug())
        script["contextVariables"]["in"].setInput(script["plane"]["out"])
        script["contextVariables"]["variables"].addOptionalMember(
            "a", IECore.StringData("aardvark"), plugName="a")

        script["expression"] = Gaffer.Expression()
        script["expression"].setExpression(
            inspect.cleandoc("""
			parent["contextVariables"]["enabled"] = True
			parent["contextVariables"]["variables"]["a"]["enabled"] = True
			parent["contextVariables"]["variables"]["a"]["name"] = "b"
			parent["contextVariables"]["variables"]["a"]["value"] = "b"
			"""))

        with Gaffer.ContextMonitor(script["expression"]) as cm:
            self.assertSceneValid(script["contextVariables"]["out"])

        self.assertFalse(
            set(cm.combinedStatistics().variableNames()).intersection(
                {"scene:path", "scene:setName", "scene:filter:inputScene"}))
    def testRoot(self):

        a1 = GafferTest.AddNode()
        a2 = GafferTest.AddNode()

        with Gaffer.Context() as c:
            with Gaffer.ContextMonitor(a2) as m:
                a1["sum"].getValue()
                a2["sum"].getValue()

        self.assertFalse(a1["sum"] in m.allStatistics())
        self.assertTrue(a2["sum"] in m.allStatistics())
예제 #6
0
    def testNoUnwantedBoundEvaluations(self):

        reader = GafferScene.SceneReader()
        reader["fileName"].setValue(
            "${GAFFER_ROOT}/resources/gafferBot/caches/gafferBot.scc")

        group = GafferScene.Group()

        parent = GafferScene.Parent()
        parent["in"].setInput(reader["out"])
        parent["children"][0].setInput(group["out"])
        parent["parent"].setValue("/")
        parent["destination"].setValue("/children")

        # Computing the root bound should not require more than the bounds
        # of `/` and `/GAFFERBOT` to be queried from the input scene.

        with Gaffer.ContextMonitor(reader["out"]["bound"]) as contextMonitor:
            parent["out"].bound("/")

        self.assertEqual(
            contextMonitor.combinedStatistics().numUniqueContexts(), 2)

        # If we parent to `/GAFFERBOT/children` then computing the bound of `/GAFFERBOT`
        # should only query `/GAFFERBOT` and `/GAFFERBOT/C_torso_GRP` from the input.

        Gaffer.ValuePlug.clearCache()
        Gaffer.ValuePlug.clearHashCache()

        parent["destination"].setValue("/GAFFERBOT/children")
        with Gaffer.ContextMonitor(reader["out"]["bound"]) as contextMonitor:
            parent["out"].bound("/GAFFERBOT")

        self.assertEqual(
            contextMonitor.combinedStatistics().numUniqueContexts(), 2)

        # The bounds for children of `/GAFFERBOT` should be perfect pass throughs.

        self.assertEqual(parent["out"].boundHash("/GAFFERBOT/C_torso_GRP"),
                         parent["in"].boundHash("/GAFFERBOT/C_torso_GRP"))
예제 #7
0
    def testFileNameContext(self):

        s = Gaffer.ScriptNode()
        s["reader"] = GafferImage.OpenImageIOReader()

        s["expression"] = Gaffer.Expression()
        s["expression"].setExpression('parent["reader"]["fileName"] = "%s"' %
                                      self.fileName)

        with Gaffer.ContextMonitor(root=s["expression"]) as cm:
            GafferImage.ImageAlgo.tiles(s["reader"]["out"])

        self.assertEqual(set(cm.combinedStatistics().variableNames()),
                         set(['frame', 'framesPerSecond']))
    def test(self):

        c = Gaffer.Context()
        defaultVariableNames = c.keys()

        a1 = GafferTest.AddNode()
        a2 = GafferTest.AddNode()

        m = Gaffer.ContextMonitor()
        with c, m:
            a1["sum"].getValue()

        a1s = m.plugStatistics(a1["sum"])
        self.assertEqual(a1s.numUniqueContexts(), 1)
        self.assertEqual(set(a1s.variableNames()), set(defaultVariableNames))
        for n in defaultVariableNames:
            self.assertEqual(a1s.numUniqueValues(n), 1)

        self.assertFalse(a2["sum"] in m.allStatistics())

        with c, m:
            c.setFrame(10)
            a1["sum"].getValue()
            a2["sum"].getValue()

        a1s = m.plugStatistics(a1["sum"])
        self.assertEqual(a1s.numUniqueContexts(), 2)
        self.assertEqual(set(a1s.variableNames()), set(defaultVariableNames))
        for n in defaultVariableNames:
            self.assertEqual(a1s.numUniqueValues(n), 1 if n != "frame" else 2)

        a2s = m.plugStatistics(a2["sum"])
        self.assertEqual(a2s.numUniqueContexts(), 1)
        self.assertEqual(set(a2s.variableNames()), set(defaultVariableNames))
        for n in defaultVariableNames:
            self.assertEqual(a2s.numUniqueValues(n), 1)

        with c, m:
            c["test"] = 10
            a1["sum"].getValue()
            c["test"] = 20
            a2["sum"].getValue()

        cs = m.combinedStatistics()
        self.assertEqual(cs.numUniqueContexts(), 4)
        self.assertEqual(set(cs.variableNames()),
                         set(defaultVariableNames + ["test"]))
        self.assertEqual(cs.numUniqueValues("frame"), 2)
        self.assertEqual(cs.numUniqueValues("test"), 2)
예제 #9
0
def __contextMonitor(menu, createIfMissing=False):

    # We store a monitor per script, so that we don't pollute
    # other scripts with metrics collected as a side effect
    # of monitoring this one.
    script = menu.ancestor(GafferUI.ScriptWindow).scriptNode()
    monitor = getattr(script, "__contextMonitor", None)
    if monitor is not None:
        return monitor

    if createIfMissing:
        monitor = Gaffer.ContextMonitor(script.selection()[-1])
        monitor.__running = False
        script.__contextMonitor = monitor
        return monitor
    else:
        return None
예제 #10
0
	def testEnabledEvaluationUsesGlobalContext( self ) :

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

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

		with Gaffer.ContextMonitor( script["expression"] ) as monitor :
			self.assertSceneValid( script["plane"]["out"] )

		self.assertEqual( monitor.combinedStatistics().numUniqueValues( "scene:path" ), 0 )
예제 #11
0
	def testExtraAttributesOnlyEvaluatedForFilteredLocations( self ) :

		script = Gaffer.ScriptNode()
		script["grid"] = GafferScene.Grid()

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

		script["customAttributes"] = GafferScene.CustomAttributes()
		script["customAttributes"]["in"].setInput( script["grid"]["out"] )
		script["customAttributes"]["filter"].setInput( script["filter"]["out"] )

		script["expression"] = Gaffer.Expression()
		script["expression"].setExpression( """parent["customAttributes"]["extraAttributes"] = IECore.CompoundData( { "a" : IECore.StringData( str( context.get( "scene:path" ) ) ) } )""" )

		with Gaffer.ContextMonitor( script["expression"] ) as monitor :
			GafferSceneTest.traverseScene( script["customAttributes"]["out"] )

		self.assertEqual( monitor.combinedStatistics().numUniqueValues( "scene:path" ), 1 )
예제 #12
0
    def testSetVariableDoesntLeakToScene(self):

        sphere = GafferScene.Sphere()
        sphere["sets"].setValue("testSource")

        setFilter = GafferScene.SetFilter()
        setFilter["set"].setValue("${setVariable}Source")

        setNode = GafferScene.Set()
        setNode["in"].setInput(sphere["out"])
        setNode["filter"].setInput(setFilter["out"])
        setNode["name"].setValue("test")
        setNode["setVariable"].setValue("setVariable")

        with Gaffer.ContextMonitor(sphere) as monitor:
            self.assertEqual(setNode["out"].set("test"),
                             sphere["out"].set("testSource"))

        self.assertNotIn("setVariable",
                         monitor.combinedStatistics().variableNames())
예제 #13
0
    def testNoContextLeakage(self):

        c = GafferImage.Constant()

        t1 = GafferImage.ImageTransform()
        t1["in"].setInput(c["out"])

        t2 = GafferImage.ImageTransform()
        t2["in"].setInput(t1["out"])

        with Gaffer.ContextMonitor(root=c) as cm:
            self.assertImagesEqual(t2["out"], t2["out"])

        self.assertEqual(
            set(cm.combinedStatistics().variableNames()),
            {
                "frame", "framesPerSecond", "image:channelName",
                "image:tileOrigin"
            },
        )
예제 #14
0
파일: stats-1.py 프로젝트: wizofe/gaffer
    def _run(self, args):

        if args["cacheMemoryLimit"].value:
            Gaffer.ValuePlug.setCacheMemoryLimit(
                1024 * 1024 * args["cacheMemoryLimit"].value)
        if args["hashCacheSizeLimit"].value:
            Gaffer.ValuePlug.setHashCacheSizeLimit(
                args["hashCacheSizeLimit"].value)

        self.__timers = collections.OrderedDict()
        self.__memory = collections.OrderedDict()

        self.__memory["Application"] = _Memory.maxRSS()

        script = Gaffer.ScriptNode()
        script["fileName"].setValue(os.path.abspath(args["script"].value))

        with _Timer() as loadingTimer:
            script.load(continueOnError=True)
        self.__timers["Loading"] = loadingTimer

        self.root()["scripts"].addChild(script)

        self.__memory["Script"] = _Memory.maxRSS(
        ) - self.__memory["Application"]

        if args["performanceMonitor"].value:
            self.__performanceMonitor = Gaffer.PerformanceMonitor()
        else:
            self.__performanceMonitor = None

        if args["contextMonitor"].value:
            contextMonitorRoot = None
            if args["contextMonitorRoot"].value:
                contextMonitorRoot = script.descendant(
                    args["contextMonitorRoot"].value)
                if contextMonitorRoot is None:
                    IECore.msg(
                        IECore.Msg.Level.Error, "stats",
                        "Context monitor root \"%s\" does not exist" %
                        args["contextMonitorRoot"].value)
                    return 1
            self.__contextMonitor = Gaffer.ContextMonitor(contextMonitorRoot)
        else:
            self.__contextMonitor = None

        if args["vtune"].value:
            try:
                self.__vtuneMonitor = Gaffer.VTuneMonitor()
                self.__vtuneMonitor.setActive(True)
            except AttributeError:
                IECore.msg(IECore.Msg.Level.Error, "gui",
                           "unable to create requested VTune monitor")

        self.__output = file(args["outputFile"].value,
                             "w") if args["outputFile"].value else sys.stdout

        self.__writeVersion(script)

        self.__output.write("\n")

        self.__writeArgs(args)

        self.__output.write("\n")

        self.__writeSettings(script)

        self.__output.write("\n")

        self.__writeVariables(script)

        self.__output.write("\n")

        if args["nodeSummary"].value:

            self.__writeNodes(script)

        if args["scene"].value:

            self.__writeScene(script, args)

        if args["image"].value:

            self.__writeImage(script, args)

        if args["task"].value:

            self.__writeTask(script, args)

        self.__output.write("\n")

        self.__writeMemory()

        self.__output.write("\n")

        self.__writePerformance(script, args)

        self.__output.write("\n")

        self.__writeContext(script, args)

        self.__output.write("\n")

        self.__output.close()

        if args["annotatedScript"].value:

            if self.__performanceMonitor is not None:
                Gaffer.MonitorAlgo.annotate(
                    script, self.__performanceMonitor,
                    Gaffer.MonitorAlgo.PerformanceMetric.TotalDuration)
                Gaffer.MonitorAlgo.annotate(
                    script, self.__performanceMonitor,
                    Gaffer.MonitorAlgo.PerformanceMetric.HashCount)
            if self.__contextMonitor is not None:
                Gaffer.MonitorAlgo.annotate(script, self.__contextMonitor)

            script.serialiseToFile(args["annotatedScript"].value)

        return 0
예제 #15
0
    def _run(self, args):

        self.__timers = collections.OrderedDict()
        self.__memory = collections.OrderedDict()

        self.__memory["Application"] = _Memory.maxRSS()

        script = Gaffer.ScriptNode()
        script["fileName"].setValue(os.path.abspath(args["script"].value))

        with _Timer() as loadingTimer:
            script.load(continueOnError=True)
        self.__timers["Loading"] = loadingTimer

        self.__memory["Script"] = _Memory.maxRSS(
        ) - self.__memory["Application"]

        if args["performanceMonitor"].value:
            self.__performanceMonitor = Gaffer.PerformanceMonitor()
        else:
            self.__performanceMonitor = None

        if args["contextMonitor"].value:
            contextMonitorRoot = None
            if args["contextMonitorRoot"].value:
                contextMonitorRoot = script.descendant(
                    args["contextMonitorRoot"].value)
                if contextMonitorRoot is None:
                    IECore.msg(
                        IECore.Msg.Level.Error, "stats",
                        "Context monitor root \"%s\" does not exist" %
                        args["contextMonitorRoot"].value)
                    return 1
            self.__contextMonitor = Gaffer.ContextMonitor(contextMonitorRoot)
        else:
            self.__contextMonitor = None

        if args["vtune"].value:
            try:
                self.__vtuneMonitor = Gaffer.VTuneMonitor()
                self.__vtuneMonitor.setActive(True)
            except AttributeError:
                IECore.msg(IECore.Msg.Level.Error, "gui",
                           "unable to create requested VTune monitor")

        self.__output = file(args["outputFile"].value,
                             "w") if args["outputFile"].value else sys.stdout

        with Gaffer.Context(script.context()) as context:

            context.setFrame(args["frame"].value)

            self.__writeVersion(script)

            self.__output.write("\n")

            self.__writeArgs(args)

            self.__output.write("\n")

            self.__writeSettings(script)

            self.__output.write("\n")

            self.__writeVariables(script)

            self.__output.write("\n")

            if args["nodeSummary"].value:

                self.__writeNodes(script)

            if args["scene"].value:

                self.__writeScene(script, args)

            if args["image"].value:

                self.__writeImage(script, args)

            if args["task"].value:

                self.__writeTask(script, args)

        self.__output.write("\n")

        self.__writeMemory()

        self.__output.write("\n")

        self.__writePerformance(script, args)

        self.__output.write("\n")

        self.__writeContext(script, args)

        self.__output.write("\n")

        self.__output.close()

        return 0
예제 #16
0
	def _run( self, args ) :

		self.__timers = collections.OrderedDict()
		self.__memory = collections.OrderedDict()

		self.__memory["Application"] = _Memory.maxRSS()

		script = Gaffer.ScriptNode()
		script["fileName"].setValue( os.path.abspath( args["script"].value ) )

		with _Timer() as loadingTimer :
			script.load( continueOnError = True )
		self.__timers["Loading"] = loadingTimer

		self.__memory["Script"] = _Memory.maxRSS() - self.__memory["Application"]

		if args["performanceMonitor"].value :
			self.__performanceMonitor = Gaffer.PerformanceMonitor()
		else :
			self.__performanceMonitor = None

		if args["contextMonitor"].value :
			contextMonitorRoot = None
			if args["contextMonitorRoot"].value :
				contextMonitorRoot = script.descendant( args["contextMonitorRoot"].value )
				if contextMonitorRoot is None :
					IECore.msg( IECore.Msg.Level.Error, "stats", "Context monitor root \"%s\" does not exist" % args["contextMonitorRoot"].value )
					return 1
			self.__contextMonitor = Gaffer.ContextMonitor( contextMonitorRoot )
		else :
			self.__contextMonitor = None

		with Gaffer.Context( script.context() ) as context :

			context.setFrame( args["frame"].value )

			self.__printVersion( script )

			print ""

			self.__printArgs( args )

			print ""

			self.__printSettings( script )

			print ""

			self.__printVariables( script )

			print ""

			self.__printNodes( script )

			if args["scene"].value :

				self.__printScene( script, args )

			if args["image"].value :

				self.__printImage( script, args )

		print ""

		self.__printMemory()

		print ""

		self.__printPerformance( script, args )

		print ""

		self.__printContext( script, args )

		print

		return 0
예제 #17
0
    def test(self):
        for useClosure in [False, True]:

            getRed = GafferOSL.OSLShader()
            getRed.loadShader("ImageProcessing/InChannel")
            getRed["parameters"]["channelName"].setValue("R")

            getGreen = GafferOSL.OSLShader()
            getGreen.loadShader("ImageProcessing/InChannel")
            getGreen["parameters"]["channelName"].setValue("G")

            getBlue = GafferOSL.OSLShader()
            getBlue.loadShader("ImageProcessing/InChannel")
            getBlue["parameters"]["channelName"].setValue("B")

            floatToColor = GafferOSL.OSLShader()
            floatToColor.loadShader("Conversion/FloatToColor")
            floatToColor["parameters"]["r"].setInput(
                getBlue["out"]["channelValue"])
            floatToColor["parameters"]["g"].setInput(
                getGreen["out"]["channelValue"])
            floatToColor["parameters"]["b"].setInput(
                getRed["out"]["channelValue"])

            reader = GafferImage.ImageReader()
            reader["fileName"].setValue(
                os.path.expandvars(
                    "$GAFFER_ROOT/python/GafferImageTest/images/rgb.100x100.exr"
                ))

            shuffle = GafferImage.Shuffle()
            shuffle["in"].setInput(reader["out"])
            shuffle["channels"].addChild(
                GafferImage.Shuffle.ChannelPlug("channel"))
            shuffle["channels"]["channel"]["out"].setValue('unchangedR')
            shuffle["channels"]["channel"]["in"].setValue('R')

            image = GafferOSL.OSLImage()
            image["in"].setInput(shuffle["out"])

            # we haven't connected the shader yet, so the node should act as a pass through

            self.assertEqual(GafferImage.ImageAlgo.image(image["out"]),
                             GafferImage.ImageAlgo.image(shuffle["out"]))

            # that should all change when we hook up a shader

            if useClosure:

                outRGB = GafferOSL.OSLShader()
                outRGB.loadShader("ImageProcessing/OutLayer")
                outRGB["parameters"]["layerColor"].setInput(
                    floatToColor["out"]["c"])

                imageShader = GafferOSL.OSLShader()
                imageShader.loadShader("ImageProcessing/OutImage")
                imageShader["parameters"]["in0"].setInput(
                    outRGB["out"]["layer"])

                image["channels"].addChild(
                    Gaffer.NameValuePlug("", GafferOSL.ClosurePlug(),
                                         "testClosure"))

            else:

                image["channels"].addChild(
                    Gaffer.NameValuePlug("", imath.Color3f(), "testColor"))

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

            def checkDirtiness(expected):
                self.assertEqual([i[0].fullName() for i in cs],
                                 ["OSLImage." + i for i in expected])
                del cs[:]

            if useClosure:
                image["channels"]["testClosure"]["value"].setInput(
                    imageShader["out"]["out"])
                channelsDirtied = [
                    "channels.testClosure.value", "channels.testClosure"
                ]
            else:
                image["channels"]["testColor"]["value"].setInput(
                    floatToColor["out"]["c"])
                channelsDirtied = [
                    "channels.testColor.value.r", "channels.testColor.value.g",
                    "channels.testColor.value.b", "channels.testColor.value",
                    "channels.testColor"
                ]

            checkDirtiness(channelsDirtied + [
                "channels", "__shader", "__shading", "__affectedChannels",
                "out.channelNames", "out.channelData", "out"
            ])

            inputImage = GafferImage.ImageAlgo.image(shuffle["out"])

            with Gaffer.ContextMonitor(image["__shading"]) as monitor:
                self.assertEqual(
                    image["out"].channelNames(),
                    IECore.StringVectorData(["A", "B", "G", "R",
                                             "unchangedR"]))
                # Evaluating channel names only requires evaluating the shading plug if we have a closure
                self.assertEqual(
                    monitor.combinedStatistics().numUniqueContexts(),
                    1 if useClosure else 0)

                # Channels we don't touch should be passed through unaltered
                for channel, changed in [('B', True), ('G', True), ('R', True),
                                         ('A', False), ('unchangedR', False)]:
                    self.assertEqual(
                        image["out"].channelDataHash(channel, imath.V2i(
                            0, 0)) == shuffle["out"].channelDataHash(
                                channel, imath.V2i(0, 0)), not changed)
                    image["out"].channelData(channel, imath.V2i(0, 0))

            # Should only need one shading evaluate for all channels
            self.assertEqual(monitor.combinedStatistics().numUniqueContexts(),
                             1)

            outputImage = GafferImage.ImageAlgo.image(image["out"])

            self.assertNotEqual(inputImage, outputImage)
            self.assertEqual(outputImage["R"], inputImage["B"])
            self.assertEqual(outputImage["G"], inputImage["G"])
            self.assertEqual(outputImage["B"], inputImage["R"])

            # changes in the shader network should signal more dirtiness

            getGreen["parameters"]["channelName"].setValue("R")
            checkDirtiness(channelsDirtied + [
                "channels", "__shader", "__shading", "__affectedChannels",
                "out.channelNames", "out.channelData", "out"
            ])

            floatToColor["parameters"]["r"].setInput(
                getRed["out"]["channelValue"])
            checkDirtiness(channelsDirtied + [
                "channels", "__shader", "__shading", "__affectedChannels",
                "out.channelNames", "out.channelData", "out"
            ])

            inputImage = GafferImage.ImageAlgo.image(shuffle["out"])
            outputImage = GafferImage.ImageAlgo.image(image["out"])

            self.assertEqual(outputImage["R"], inputImage["R"])
            self.assertEqual(outputImage["G"], inputImage["R"])
            self.assertEqual(outputImage["B"], inputImage["R"])
            self.assertEqual(outputImage["A"], inputImage["A"])
            self.assertEqual(outputImage["unchangedR"],
                             inputImage["unchangedR"])

            image["in"].setInput(None)
            checkDirtiness([
                'in.format', 'in.dataWindow', 'in.metadata', 'in.deep',
                'in.sampleOffsets', 'in.channelNames', 'in.channelData', 'in',
                '__shading', '__affectedChannels', 'out.channelNames',
                'out.channelData', 'out.format', 'out.dataWindow',
                'out.metadata', 'out.deep', 'out.sampleOffsets', 'out'
            ])

            image["defaultFormat"]["displayWindow"]["max"]["x"].setValue(200)
            checkDirtiness([
                'defaultFormat.displayWindow.max.x',
                'defaultFormat.displayWindow.max',
                'defaultFormat.displayWindow', 'defaultFormat',
                '__defaultIn.format', '__defaultIn.dataWindow', '__defaultIn',
                '__shading', '__affectedChannels', 'out.channelNames',
                'out.channelData', 'out.format', 'out.dataWindow', 'out'
            ])

            constant = GafferImage.Constant()
            image["in"].setInput(constant["out"])

            checkDirtiness([
                'in.format', 'in.dataWindow', 'in.metadata', 'in.deep',
                'in.sampleOffsets', 'in.channelNames', 'in.channelData', 'in',
                '__shading', '__affectedChannels', 'out.channelNames',
                'out.channelData', 'out.format', 'out.dataWindow',
                'out.metadata', 'out.deep', 'out.sampleOffsets', 'out'
            ])

            image["in"].setInput(shuffle["out"])
            if useClosure:
                outRGB["parameters"]["layerName"].setValue("newLayer")
            else:
                image["channels"][0]["name"].setValue("newLayer")

            self.assertEqual(
                image["out"].channelNames(),
                IECore.StringVectorData([
                    "A", "B", "G", "R", "newLayer.B", "newLayer.G",
                    "newLayer.R", "unchangedR"
                ]))

            for channel in ['B', 'G', 'R', 'A', 'unchangedR']:
                self.assertEqual(
                    image["out"].channelDataHash(channel, imath.V2i(0, 0)),
                    shuffle["out"].channelDataHash(channel, imath.V2i(0, 0)))
                self.assertEqual(
                    image["out"].channelData(channel, imath.V2i(0, 0)),
                    shuffle["out"].channelData(channel, imath.V2i(0, 0)))

            crop = GafferImage.Crop()
            crop["area"].setValue(imath.Box2i(imath.V2i(0, 0), imath.V2i(0,
                                                                         0)))
            crop["in"].setInput(shuffle["out"])

            image["in"].setInput(crop["out"])

            if useClosure:
                # When using closures, we can't find out about the new channels being added if the datawindow is
                # empty
                self.assertEqual(
                    image["out"].channelNames(),
                    IECore.StringVectorData(["A", "B", "G", "R",
                                             "unchangedR"]))
            else:
                self.assertEqual(
                    image["out"].channelNames(),
                    IECore.StringVectorData([
                        "A", "B", "G", "R", "newLayer.B", "newLayer.G",
                        "newLayer.R", "unchangedR"
                    ]))