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"])
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)
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])
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")
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)
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 ) ) )
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())
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)
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)
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
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)
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)
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"])
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])
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 ) )
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] )
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"] ) )
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 )
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 )
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)
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 )
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 )
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)
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)
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))
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"])
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"])
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)