Skip to content

Commit

Permalink
Merge pull request #5867 from johnhaddon/cryptomatteFix
Browse files Browse the repository at this point in the history
Cryptomatte fixes
  • Loading branch information
murraystevenson authored May 29, 2024
2 parents 403421c + 804c92c commit 2d1fe7b
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 68 deletions.
3 changes: 3 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ Fixes
- Fixed behaviour of `editingFinishedSignal()` to match TextWidget : it is now also emitted when the text is activated (see `activatedSignal()`).
- MultiLineStringMetadataWidget : The <kbd>Ctrl</kbd>+<kbd>Return</kbd> shortcut now updates the metadata value immediately.
- UIEditor : The <kbd>Ctrl</kbd>+<kbd>Return</kbd> shortcut now updates the button code immediately.
- Cryptomatte :
- Fixed errors when the input image didn't contain the main `RGBA` channels.
- Fixed inaccurate hash.

API
---
Expand Down
30 changes: 30 additions & 0 deletions python/GafferSceneTest/CryptomatteTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,36 @@ def testPreviewChannels( self ) :

self.assertEqual( s["color"].getValue(), imath.Color4f(-7.18267809e-20, 0.448745549, 0.928857431, 1) )

def testPreviewChannelsWithoutRGBAInput( self ) :

# As for `testPreviewChannels` but with some additional
# protection against bad accesses to non-existent RGBA
# input channels.

reader = GafferImage.ImageReader()
reader["fileName"].setValue( self.testImage )

# ImageReader doesn't check for the existence of channels
# in `hash()`, but Shuffle does. So we insert a shuffle
# as additional protection against bugs in Cryptomatte.

shuffle = GafferImage.Shuffle()
shuffle["in"].setInput( reader["out"] )

cryptomatte = GafferScene.Cryptomatte()
cryptomatte["in"].setInput( shuffle["out"] )
cryptomatte["layer"].setValue( "crypto_object" )

sampler = GafferImage.ImageSampler()
sampler["image"].setInput( cryptomatte["out"] )
sampler["pixel"].setValue( imath.V2f( 32, 108 ) )

self.assertEqual( sampler["color"].getValue(), imath.Color4f(-7.18267809e-20, 0.198745549, 0.178857431, 0) )

cryptomatte["matteNames"].setValue( IECore.StringVectorData( [ "/cow1" ] ) )

self.assertEqual( sampler["color"].getValue(), imath.Color4f(-7.18267809e-20, 0.448745549, 0.928857431, 1) )

def testWildcardMatch( self ) :

r = GafferImage.ImageReader()
Expand Down
148 changes: 80 additions & 68 deletions src/GafferScene/Cryptomatte.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -563,26 +563,39 @@ void Cryptomatte::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *
}
else if( output == matteChannelDataPlug() )
{
inPlug()->channelDataPlug()->hash( h );

std::string cryptomatteLayer;
ConstStringVectorDataPtr channelNamesData;
{
GafferImage::ImagePlug::GlobalScope globalScope( context );
cryptomatteLayer = layerPlug()->getValue();
channelNamesData = inPlug()->channelNamesPlug()->getValue();

cryptomatteLayer = layerPlug()->getValue();
matteValuesPlug()->hash( h );
}

const std::vector<std::string> &channelNames = channelNamesData->readable();

boost::regex channelNameRegex( fmt::format( g_cryptomatteChannelPattern, cryptomatteLayer ) );
GafferImage::ImagePlug::ChannelDataScope channelDataScope( context );
for( const auto &c : channelNamesData->readable() )
for( const auto &c : channelNames )
{
if( boost::regex_match( c, channelNameRegex ) )
{
ChannelMap::const_iterator cIt = g_channelMap.find( GafferImage::ImageAlgo::baseName( c ) );
if( cIt == g_channelMap.end() )
{
continue;
}

const std::string &alphaChannel = GafferImage::ImageAlgo::channelName( GafferImage::ImageAlgo::layerName( c ), cIt->second );
if( !GafferImage::ImageAlgo::channelExists( channelNames, alphaChannel ) )
{
continue;
}

channelDataScope.setChannelName( &c );
inPlug()->channelDataPlug()->hash( h );
channelDataScope.setChannelName( &alphaChannel );
inPlug()->channelDataPlug()->hash( h );
}
}
}
Expand Down Expand Up @@ -864,13 +877,15 @@ IECore::ConstStringVectorDataPtr Cryptomatte::computeChannelNames( const Gaffer:

void Cryptomatte::hashChannelData( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const
{
const std::string &channelName = context->get<std::string>( GafferImage::ImagePlug::channelNameContextName );
const Imath::V2i &tileOrigin = context->get<Imath::V2i>( GafferImage::ImagePlug::tileOriginContextName );

std::string alphaChannel;
{
GafferImage::ImagePlug::GlobalScope globalScope( context );
alphaChannel = outputChannelPlug()->getValue();
}

const std::string channelName = context->get<std::string>( GafferImage::ImagePlug::channelNameContextName );
if( channelName != "R" && channelName != "G" && channelName != "B" && channelName != alphaChannel )
{
h = inPlug()->channelDataPlug()->hash();
Expand Down Expand Up @@ -899,46 +914,42 @@ void Cryptomatte::hashChannelData( const GafferImage::ImagePlug *output, const G
}
}

const std::string &firstDataChannel = cryptomatteLayer + g_firstDataChannelSuffix;
if( !GafferImage::ImageAlgo::channelExists( channelNamesData->readable(), firstDataChannel ) )
if( channelName == alphaChannel )
{
h = GafferImage::ImagePlug::blackTile()->Object::hash();
h = matteChannelDataPlug()->hash();
return;
}

h.append( channelName );
// `channelName` is in RGB

const std::string firstDataChannel = cryptomatteLayer + g_firstDataChannelSuffix;
if( !GafferImage::ImageAlgo::channelExists( channelNamesData->readable(), firstDataChannel ) )
{
GafferImage::ImagePlug::GlobalScope globalScope( context );
layerPlug()->hash( h );
matteValuesPlug()->hash( h );
inPlug()->metadataPlug()->hash( h );

if( channelName == alphaChannel )
{
outputChannelPlug()->hash( h );
}
h = GafferImage::ImagePlug::blackTile()->Object::hash();
return;
}

if( channelName != "R" )
if( channelName == "R" )
{
matteChannelDataPlug()->hash( h );
h = inPlug()->channelDataHash( firstDataChannel, tileOrigin );
return;
}

GafferImage::ImagePlug::ChannelDataScope channelDataScope( context );
channelDataScope.setChannelName( &firstDataChannel );
inPlug()->channelDataPlug()->hash( h );
// `channelName` is in GB

FlatImageProcessor::hashChannelData( output, context, h );

h.append( inPlug()->channelDataHash( firstDataChannel, tileOrigin ) );
matteChannelDataPlug()->hash( h );
h.append( channelName );

}

IECore::ConstFloatVectorDataPtr Cryptomatte::computeChannelData( const std::string &channelName, const Imath::V2i &tileOrigin, const Gaffer::Context *context, const GafferImage::ImagePlug *parent ) const
{
std::string cryptomatteLayer;
std::string alphaChannel;
ConstStringVectorDataPtr channelNamesData;
{
GafferImage::ImagePlug::GlobalScope globalScope( context );
channelNamesData = inPlug()->channelNamesPlug()->getValue();
cryptomatteLayer = layerPlug()->getValue();
alphaChannel = outputChannelPlug()->getValue();
}

Expand All @@ -947,11 +958,17 @@ IECore::ConstFloatVectorDataPtr Cryptomatte::computeChannelData( const std::stri
return inPlug()->channelDataPlug()->getValue();
}

const std::vector<std::string> &channelNames = channelNamesData->readable();
std::string cryptomatteLayer;
ConstStringVectorDataPtr channelNamesData;
{
GafferImage::ImagePlug::GlobalScope globalScope( context );
channelNamesData = inPlug()->channelNamesPlug()->getValue();
cryptomatteLayer = layerPlug()->getValue();
}

if( cryptomatteLayer == "" )
{
if( GafferImage::ImageAlgo::channelExists( channelNames, channelName ) )
if( GafferImage::ImageAlgo::channelExists( channelNamesData->readable(), channelName ) )
{
return inPlug()->channelDataPlug()->getValue();
}
Expand All @@ -961,55 +978,50 @@ IECore::ConstFloatVectorDataPtr Cryptomatte::computeChannelData( const std::stri
}
}

if( channelName == "R" || channelName == "G" || channelName == "B" )
if( channelName == alphaChannel )
{
const std::string &firstDataChannel = cryptomatteLayer + g_firstDataChannelSuffix;
if( !GafferImage::ImageAlgo::channelExists( channelNames, firstDataChannel ) )
{
return GafferImage::ImagePlug::blackTile();
}
return matteChannelDataPlug()->getValue();
}

GafferImage::ImagePlug::ChannelDataScope channelDataScope( context );
channelDataScope.setChannelName( &firstDataChannel );
// `channelName` is in RGB

if( channelName == "R" )
{
return inPlug()->channelDataPlug()->getValue();
}
else if( channelName == "G" || channelName == "B" )
{
FloatVectorDataPtr resultData = new FloatVectorData;
std::vector<float> &result = resultData->writable();
result.resize( GafferImage::ImagePlug::tilePixels(), 0.0f );
const std::string firstDataChannel = cryptomatteLayer + g_firstDataChannelSuffix;
if( !GafferImage::ImageAlgo::channelExists( channelNamesData->readable(), firstDataChannel ) )
{
return GafferImage::ImagePlug::blackTile();
}

if( channelName == "R" )
{
return inPlug()->channelData( firstDataChannel, tileOrigin );
}

ConstFloatVectorDataPtr valueData = inPlug()->channelDataPlug()->getValue();
const std::vector<float> &values = valueData->readable();
// `channelName` is in GB

ConstFloatVectorDataPtr alphaData = matteChannelDataPlug()->getValue();
const std::vector<float> &alphas = alphaData->readable();
FloatVectorDataPtr resultData = new FloatVectorData;
std::vector<float> &result = resultData->writable();
result.resize( GafferImage::ImagePlug::tilePixels(), 0.0f );

const size_t shift = channelName == "G" ? 8 : 16;
const float mult = channelName == "G" ? 0.25f : 0.75f;
uint32_t h;
ConstFloatVectorDataPtr valueData = inPlug()->channelData( firstDataChannel, tileOrigin );
const std::vector<float> &values = valueData->readable();

std::vector<float>::const_iterator vIt = values.begin();
std::vector<float>::const_iterator aIt = alphas.begin();
for( std::vector<float>::iterator it = result.begin(), eIt = result.end(); it != eIt; ++it, ++vIt, ++aIt )
{
// Adapted from the Cryptomatte specification
std::memcpy( &h, &(*vIt), sizeof( uint32_t ) );
*it = (float)(h << shift) / (float)UINT32_MAX * 0.3f + *aIt * mult;
}
ConstFloatVectorDataPtr alphaData = matteChannelDataPlug()->getValue();
const std::vector<float> &alphas = alphaData->readable();

return resultData;
}
}
else if( channelName == alphaChannel )
const size_t shift = channelName == "G" ? 8 : 16;
const float mult = channelName == "G" ? 0.25f : 0.75f;
uint32_t h;

std::vector<float>::const_iterator vIt = values.begin();
std::vector<float>::const_iterator aIt = alphas.begin();
for( std::vector<float>::iterator it = result.begin(), eIt = result.end(); it != eIt; ++it, ++vIt, ++aIt )
{
return matteChannelDataPlug()->getValue();
// Adapted from the Cryptomatte specification
std::memcpy( &h, &(*vIt), sizeof( uint32_t ) );
*it = (float)(h << shift) / (float)UINT32_MAX * 0.3f + *aIt * mult;
}

return GafferImage::ImagePlug::blackTile();
return resultData;
}

} // namespace GafferScene

0 comments on commit 2d1fe7b

Please sign in to comment.