Skip to content

Commit

Permalink
ImageStats : Added areaSource plug
Browse files Browse the repository at this point in the history
  • Loading branch information
danieldresser-ie committed Jan 3, 2024
1 parent edb395b commit 793bda3
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 20 deletions.
1 change: 1 addition & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----
Expand Down
10 changes: 10 additions & 0 deletions include/GafferImage/ImageStats.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;

Expand Down
29 changes: 27 additions & 2 deletions python/GafferImageTest/ImageStatsTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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 ) )
Expand All @@ -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 ) )
Expand Down
25 changes: 24 additions & 1 deletion python/GafferImageUI/ImageStatsUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -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" : [
Expand Down Expand Up @@ -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",

],

Expand Down
85 changes: 69 additions & 16 deletions src/GafferImage/ImageStats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) ) );
Expand Down Expand Up @@ -164,75 +165,85 @@ const Gaffer::StringVectorDataPlug *ImageStats::channelsPlug() const
return getChild<StringVectorDataPlug>( g_firstPlugIndex + 2 );
}

IntPlug *ImageStats::areaSourcePlug()
{
return getChild<IntPlug>( g_firstPlugIndex + 3 );
}

const IntPlug *ImageStats::areaSourcePlug() const
{
return getChild<IntPlug>( g_firstPlugIndex + 3 );
}

Box2iPlug *ImageStats::areaPlug()
{
return getChild<Box2iPlug>( g_firstPlugIndex + 3 );
return getChild<Box2iPlug>( g_firstPlugIndex + 4 );
}

const Box2iPlug *ImageStats::areaPlug() const
{
return getChild<Box2iPlug>( g_firstPlugIndex + 3 );
return getChild<Box2iPlug>( g_firstPlugIndex + 4 );
}

Color4fPlug *ImageStats::averagePlug()
{
return getChild<Color4fPlug>( g_firstPlugIndex + 4 );
return getChild<Color4fPlug>( g_firstPlugIndex + 5 );
}

const Color4fPlug *ImageStats::averagePlug() const
{
return getChild<Color4fPlug>( g_firstPlugIndex + 4 );
return getChild<Color4fPlug>( g_firstPlugIndex + 5 );
}

Color4fPlug *ImageStats::minPlug()
{
return getChild<Color4fPlug>( g_firstPlugIndex + 5 );
return getChild<Color4fPlug>( g_firstPlugIndex + 6 );
}

const Color4fPlug *ImageStats::minPlug() const
{
return getChild<Color4fPlug>( g_firstPlugIndex + 5 );
return getChild<Color4fPlug>( g_firstPlugIndex + 6 );
}

Color4fPlug *ImageStats::maxPlug()
{
return getChild<Color4fPlug>( g_firstPlugIndex + 6 );
return getChild<Color4fPlug>( g_firstPlugIndex + 7 );
}

const Color4fPlug *ImageStats::maxPlug() const
{
return getChild<Color4fPlug>( g_firstPlugIndex + 6 );
return getChild<Color4fPlug>( g_firstPlugIndex + 7 );
}

ObjectPlug *ImageStats::tileStatsPlug()
{
return getChild<ObjectPlug>( g_firstPlugIndex + 7 );
return getChild<ObjectPlug>( g_firstPlugIndex + 8 );
}

const ObjectPlug *ImageStats::tileStatsPlug() const
{
return getChild<ObjectPlug>( g_firstPlugIndex + 7 );
return getChild<ObjectPlug>( g_firstPlugIndex + 8 );
}

ObjectPlug *ImageStats::allStatsPlug()
{
return getChild<ObjectPlug>( g_firstPlugIndex + 8 );
return getChild<ObjectPlug>( g_firstPlugIndex + 9 );
}

const ObjectPlug *ImageStats::allStatsPlug() const
{
return getChild<ObjectPlug>( g_firstPlugIndex + 8 );
return getChild<ObjectPlug>( g_firstPlugIndex + 9 );
}


ImagePlug *ImageStats::flattenedInPlug()
{
return getChild<ImagePlug>( g_firstPlugIndex + 9 );
return getChild<ImagePlug>( g_firstPlugIndex + 10 );
}

const ImagePlug *ImageStats::flattenedInPlug() const
{
return getChild<ImagePlug>( g_firstPlugIndex + 9 );
return getChild<ImagePlug>( g_firstPlugIndex + 10 );
}

void ImageStats::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const
Expand All @@ -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 )
)
{
Expand All @@ -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 )
)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 9 additions & 1 deletion src/GafferImageModule/UtilityNodeBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,16 @@ using namespace GafferImage;

void GafferImageModule::bindUtilityNodes()
{
{
scope s = GafferBindings::DependencyNodeClass<ImageStats>();

enum_<ImageStats::AreaSource>( "AreaSource" )
.value( "Area", ImageStats::Area )
.value( "DataWindow", ImageStats::DataWindow )
.value( "DisplayWindow", ImageStats::DisplayWindow )
;
}

DependencyNodeClass<ImageStats>();
DependencyNodeClass<ImageSampler>();
DependencyNodeClass<FormatQuery>();

Expand Down

0 comments on commit 793bda3

Please sign in to comment.