From 793bda314b124a2c625879f02c44277b06bba032 Mon Sep 17 00:00:00 2001 From: Daniel Dresser Date: Tue, 2 Jan 2024 16:17:24 -0800 Subject: [PATCH] ImageStats : Added areaSource plug --- Changes.md | 1 + include/GafferImage/ImageStats.h | 10 +++ python/GafferImageTest/ImageStatsTest.py | 29 ++++++- python/GafferImageUI/ImageStatsUI.py | 25 +++++- src/GafferImage/ImageStats.cpp | 85 ++++++++++++++++---- src/GafferImageModule/UtilityNodeBinding.cpp | 10 ++- 6 files changed, 140 insertions(+), 20 deletions(-) diff --git a/Changes.md b/Changes.md index d2544a99f4e..62a92992f09 100644 --- a/Changes.md +++ b/Changes.md @@ -34,6 +34,7 @@ Improvements - Backdrop : Improved drawing order for nested backdrops : - Larger backdrops are automatically drawn behind smaller ones, so that nested backdrops will always appear on top. - Added a `depth` plug to assign a manual drawing depth for the rare cases where the automatic depth is unwanted. +- ImageStats : Added `areaSource` plug, allowing area to be driven by the input display window or data window. Fixes ----- diff --git a/include/GafferImage/ImageStats.h b/include/GafferImage/ImageStats.h index 391f903a576..26b8900d337 100644 --- a/include/GafferImage/ImageStats.h +++ b/include/GafferImage/ImageStats.h @@ -59,6 +59,13 @@ class GAFFERIMAGE_API ImageStats : public Gaffer::ComputeNode GAFFER_NODE_DECLARE_TYPE( GafferImage::ImageStats, ImageStatsTypeId, Gaffer::ComputeNode ); + enum AreaSource + { + Area = 0, + DataWindow = 1, + DisplayWindow = 2, + }; + void affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const override; GafferImage::ImagePlug *inPlug(); @@ -70,6 +77,9 @@ class GAFFERIMAGE_API ImageStats : public Gaffer::ComputeNode Gaffer::StringVectorDataPlug *channelsPlug(); const Gaffer::StringVectorDataPlug *channelsPlug() const; + Gaffer::IntPlug *areaSourcePlug(); + const Gaffer::IntPlug *areaSourcePlug() const; + Gaffer::Box2iPlug *areaPlug(); const Gaffer::Box2iPlug *areaPlug() const; diff --git a/python/GafferImageTest/ImageStatsTest.py b/python/GafferImageTest/ImageStatsTest.py index 5fc1dac5e18..646fb8b2434 100644 --- a/python/GafferImageTest/ImageStatsTest.py +++ b/python/GafferImageTest/ImageStatsTest.py @@ -115,7 +115,7 @@ def testDisconnectHash( self ) : r["fileName"].setValue( self.__rgbFilePath ) s = GafferImage.ImageStats() - s["area"].setValue( r["out"]["format"].getValue().getDisplayWindow() ) + s["areaSource"].setValue( GafferImage.ImageStats.AreaSource.DisplayWindow ) # Get the hashes of the outputs when there is no input. minHash = s["min"].hash() @@ -137,7 +137,7 @@ def testStats( self ) : s["in"].setInput( r["out"] ) s["channels"].setValue( IECore.StringVectorData( [ "R", "G", "B", "A" ] ) ) - s["area"].setValue( r["out"]["format"].getValue().getDisplayWindow() ) + s["areaSource"].setValue( GafferImage.ImageStats.AreaSource.DisplayWindow ) self.__assertColour( s["average"].getValue(), imath.Color4f( 0.0544, 0.0744, 0.1250, 0.2537 ) ) self.__assertColour( s["min"].getValue(), imath.Color4f( 0, 0, 0, 0 ) ) self.__assertColour( s["max"].getValue(), imath.Color4f( 0.5, 0.5, 0.5, 0.875 ) ) @@ -152,6 +152,31 @@ def testROI( self ) : s["in"].setInput( r["out"] ) s["channels"].setValue( IECore.StringVectorData( [ "R", "G", "B", "A" ] ) ) + s["areaSource"].setValue( GafferImage.ImageStats.AreaSource.DisplayWindow ) + self.__assertColour( s["average"].getValue(), imath.Color4f(0.054375, 0.074375, 0.125, 0.25375) ) + self.__assertColour( s["min"].getValue(), imath.Color4f( 0, 0, 0, 0 ) ) + self.__assertColour( s["max"].getValue(), imath.Color4f( 0.5, 0.5, 0.5, 0.875 ) ) + + s["areaSource"].setValue( GafferImage.ImageStats.AreaSource.Area ) + s["area"].setValue( imath.Box2i( imath.V2i( 0, 0 ), imath.V2i( 100, 100 ) ) ) + + self.__assertColour( s["average"].getValue(), imath.Color4f(0.054375, 0.074375, 0.125, 0.25375) ) + self.__assertColour( s["min"].getValue(), imath.Color4f( 0, 0, 0, 0 ) ) + self.__assertColour( s["max"].getValue(), imath.Color4f( 0.5, 0.5, 0.5, 0.875 ) ) + + s["areaSource"].setValue( GafferImage.ImageStats.AreaSource.DataWindow ) + self.__assertColour( s["average"].getValue(), imath.Color4f(0.151042, 0.206597, 0.347222, 0.704861) ) + self.__assertColour( s["min"].getValue(), imath.Color4f( 0, 0, 0, 0 ) ) + self.__assertColour( s["max"].getValue(), imath.Color4f( 0.5, 0.5, 0.5, 0.875 ) ) + + s["areaSource"].setValue( GafferImage.ImageStats.AreaSource.Area ) + s["area"].setValue( imath.Box2i( imath.V2i( 20, 20 ), imath.V2i( 80, 80 ) ) ) + + self.__assertColour( s["average"].getValue(), imath.Color4f(0.151042, 0.206597, 0.347222, 0.704861) ) + self.__assertColour( s["min"].getValue(), imath.Color4f( 0, 0, 0, 0 ) ) + self.__assertColour( s["max"].getValue(), imath.Color4f( 0.5, 0.5, 0.5, 0.875 ) ) + + s["area"].setValue( imath.Box2i( imath.V2i( 20, 20 ), imath.V2i( 25, 25 ) ) ) self.__assertColour( s["average"].getValue(), imath.Color4f( 0.5, 0, 0, 0.5 ) ) self.__assertColour( s["max"].getValue(), imath.Color4f( 0.5, 0, 0, 0.5 ) ) diff --git a/python/GafferImageUI/ImageStatsUI.py b/python/GafferImageUI/ImageStatsUI.py index 8ec3cec8b11..a28408672db 100644 --- a/python/GafferImageUI/ImageStatsUI.py +++ b/python/GafferImageUI/ImageStatsUI.py @@ -62,6 +62,8 @@ def postCreate( node, menu ) : within the node graph. """, + "layout:activator:areaSourceIsArea", lambda node : node["areaSource"].getValue() == GafferImage.ImageStats.AreaSource.Area, + plugs = { "in" : [ @@ -98,14 +100,35 @@ def postCreate( node, menu ) : ], + "areaSource" : [ + + "description", + """ + Where to source the area to be analysed. If this is + set to DataWindow, it will use the input's Data Window, + if it is set to DisplayWindow, it will use the input's + Display Window, and if it is set to Area, it will use + the Area plug. + """, + + "preset:Area", GafferImage.ImageStats.AreaSource.Area, + "preset:DataWindow", GafferImage.ImageStats.AreaSource.DataWindow, + "preset:DisplayWindow", GafferImage.ImageStats.AreaSource.DisplayWindow, + + "nodule:type", "", + "plugValueWidget:type", "GafferUI.PresetsPlugValueWidget", + + ], + "area" : [ "description", """ The area of the image to be analysed. + This plug is only used if 'Area Source' is set to Area. """, - "nodule:type", "", + "layout:activator", "areaSourceIsArea", ], diff --git a/src/GafferImage/ImageStats.cpp b/src/GafferImage/ImageStats.cpp index d8a0b56a8bd..ea82384e48f 100644 --- a/src/GafferImage/ImageStats.cpp +++ b/src/GafferImage/ImageStats.cpp @@ -112,6 +112,7 @@ ImageStats::ImageStats( const std::string &name ) defaultChannels.push_back( "A" ); addChild( new StringVectorDataPlug( "channels", Plug::In, defaultChannelsData ) ); + addChild( new IntPlug( "areaSource", Gaffer::Plug::In, ImageStats::Area, ImageStats::Area, ImageStats::DisplayWindow ) ); addChild( new Box2iPlug( "area", Gaffer::Plug::In ) ); addChild( new Color4fPlug( "average", Gaffer::Plug::Out, Imath::Color4f( 0, 0, 0, 1 ) ) ); addChild( new Color4fPlug( "min", Gaffer::Plug::Out, Imath::Color4f( 0, 0, 0, 1 ) ) ); @@ -164,75 +165,85 @@ const Gaffer::StringVectorDataPlug *ImageStats::channelsPlug() const return getChild( g_firstPlugIndex + 2 ); } +IntPlug *ImageStats::areaSourcePlug() +{ + return getChild( g_firstPlugIndex + 3 ); +} + +const IntPlug *ImageStats::areaSourcePlug() const +{ + return getChild( g_firstPlugIndex + 3 ); +} + Box2iPlug *ImageStats::areaPlug() { - return getChild( g_firstPlugIndex + 3 ); + return getChild( g_firstPlugIndex + 4 ); } const Box2iPlug *ImageStats::areaPlug() const { - return getChild( g_firstPlugIndex + 3 ); + return getChild( g_firstPlugIndex + 4 ); } Color4fPlug *ImageStats::averagePlug() { - return getChild( g_firstPlugIndex + 4 ); + return getChild( g_firstPlugIndex + 5 ); } const Color4fPlug *ImageStats::averagePlug() const { - return getChild( g_firstPlugIndex + 4 ); + return getChild( g_firstPlugIndex + 5 ); } Color4fPlug *ImageStats::minPlug() { - return getChild( g_firstPlugIndex + 5 ); + return getChild( g_firstPlugIndex + 6 ); } const Color4fPlug *ImageStats::minPlug() const { - return getChild( g_firstPlugIndex + 5 ); + return getChild( g_firstPlugIndex + 6 ); } Color4fPlug *ImageStats::maxPlug() { - return getChild( g_firstPlugIndex + 6 ); + return getChild( g_firstPlugIndex + 7 ); } const Color4fPlug *ImageStats::maxPlug() const { - return getChild( g_firstPlugIndex + 6 ); + return getChild( g_firstPlugIndex + 7 ); } ObjectPlug *ImageStats::tileStatsPlug() { - return getChild( g_firstPlugIndex + 7 ); + return getChild( g_firstPlugIndex + 8 ); } const ObjectPlug *ImageStats::tileStatsPlug() const { - return getChild( g_firstPlugIndex + 7 ); + return getChild( g_firstPlugIndex + 8 ); } ObjectPlug *ImageStats::allStatsPlug() { - return getChild( g_firstPlugIndex + 8 ); + return getChild( g_firstPlugIndex + 9 ); } const ObjectPlug *ImageStats::allStatsPlug() const { - return getChild( g_firstPlugIndex + 8 ); + return getChild( g_firstPlugIndex + 9 ); } ImagePlug *ImageStats::flattenedInPlug() { - return getChild( g_firstPlugIndex + 9 ); + return getChild( g_firstPlugIndex + 10 ); } const ImagePlug *ImageStats::flattenedInPlug() const { - return getChild( g_firstPlugIndex + 9 ); + return getChild( g_firstPlugIndex + 10 ); } void ImageStats::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const @@ -243,7 +254,9 @@ void ImageStats::affects( const Gaffer::Plug *input, AffectedPlugsContainer &out input == viewPlug() || input == flattenedInPlug()->viewNamesPlug() || input == flattenedInPlug()->dataWindowPlug() || + input == flattenedInPlug()->formatPlug() || input == flattenedInPlug()->channelDataPlug() || + input == areaSourcePlug() || areaPlug()->isAncestorOf( input ) ) { @@ -255,6 +268,8 @@ void ImageStats::affects( const Gaffer::Plug *input, AffectedPlugsContainer &out input == flattenedInPlug()->viewNamesPlug() || input == tileStatsPlug() || input == flattenedInPlug()->dataWindowPlug() || + input == flattenedInPlug()->formatPlug() || + input == areaSourcePlug() || areaPlug()->isAncestorOf( input ) ) { @@ -317,7 +332,26 @@ void ImageStats::hash( const ValuePlug *output, const Context *context, IECore:: { ImagePlug::GlobalScope s( viewScope.context() ); - const Imath::Box2i area = areaPlug()->getValue(); + int areaSource = areaSourcePlug()->getValue(); + Imath::Box2i area; + switch ( areaSource ) + { + case ImageStats::DataWindow: + { + area = inPlug()->dataWindowPlug()->getValue(); + break; + } + case ImageStats::DisplayWindow: + { + area = inPlug()->formatPlug()->getValue().getDisplayWindow(); + break; + } + default: + { + area = areaPlug()->getValue(); + break; + } + } const Imath::Box2i dataWindow = flattenedInPlug()->dataWindowPlug()->getValue(); boundsIntersection = BufferAlgo::intersection( area, dataWindow ); areaMult = double(area.size().x) * area.size().y; @@ -403,7 +437,26 @@ void ImageStats::compute( ValuePlug *output, const Context *context ) const { ImagePlug::GlobalScope s( viewScope.context() ); - const Imath::Box2i area = areaPlug()->getValue(); + int areaSource = areaSourcePlug()->getValue(); + Imath::Box2i area; + switch ( areaSource ) + { + case ImageStats::DataWindow: + { + area = inPlug()->dataWindowPlug()->getValue(); + break; + } + case ImageStats::DisplayWindow: + { + area = inPlug()->formatPlug()->getValue().getDisplayWindow(); + break; + } + default: + { + area = areaPlug()->getValue(); + break; + } + } const Imath::Box2i dataWindow = flattenedInPlug()->dataWindowPlug()->getValue(); boundsIntersection = BufferAlgo::intersection( area, dataWindow ); areaMult = double(area.size().x) * area.size().y; diff --git a/src/GafferImageModule/UtilityNodeBinding.cpp b/src/GafferImageModule/UtilityNodeBinding.cpp index ba0c7b840fa..6059b945a8c 100644 --- a/src/GafferImageModule/UtilityNodeBinding.cpp +++ b/src/GafferImageModule/UtilityNodeBinding.cpp @@ -48,8 +48,16 @@ using namespace GafferImage; void GafferImageModule::bindUtilityNodes() { + { + scope s = GafferBindings::DependencyNodeClass(); + + enum_( "AreaSource" ) + .value( "Area", ImageStats::Area ) + .value( "DataWindow", ImageStats::DataWindow ) + .value( "DisplayWindow", ImageStats::DisplayWindow ) + ; + } - DependencyNodeClass(); DependencyNodeClass(); DependencyNodeClass();