#include "GaborTest.h" #include "rimage_matlab.h" #include "rimage_ser.h" #include "mp_SVMWeights.h" #include "mp_threadedGabor.h" #include "mpt_AUIterator.h" #include #include // for cout //#define DISPLAY_AUS #ifdef DISPLAY_AUS #include // for setprecision( int p ) #endif #include // For auto_ptr #include #include // for std::accumulate #include #include #include #include #include using namespace std; static const string weightsPrefix("CERT4_0_Weights"); /** Compare two RImages * First check dimentions, then compare every pixel. Currently using an arbitrary small * value to compare pixels, but something a little more flexible that measured how * different images are in some way relevant to CERT would be better. */ bool operator==( const RImage &lhs, const RImage &rhs ) { if( lhs.width != rhs.width ) return false; if( lhs.height != rhs.height ) return false; if( lhs.numpixels != rhs.numpixels ) return false; float diffSum = 0.0; for( int index=0; index < rhs.numpixels; ++index ) { float diff = fabs( lhs.getPixel(index) - rhs.getPixel(index) ); // These diff values are empirical, based on doing tests in Matlab, // with a bit of extra leeway. if( diff > 1.0e-07 ) return false; } if( diffSum > 2.0e-04) return false; return true; } CPPUNIT_TEST_SUITE_REGISTRATION( GaborTest ); GaborTest::GaborTest() : image(NULL), ffted(NULL), gabor(NULL) { } /** Load an RImage from a file. * Currently we load an image from a boost serialized file. */ void GaborTest::setUp() { image = new RImage; restoreRImageFromFile( *image, "unit_orig.rimage" ); if( image ) gabor = new MP_Gabor( *image, 96, 96 ); // Set the SVM directory once mp_SVMWeights *svm = mp_SVMWeights::getSVMWeights( weightsPrefix ); svm->setDirectory( "SVMWeights_dir" ); std::vector< std::string > categories; categories.push_back( weightsPrefix ); gabor->setCategories( categories ); } /** Delete all test RImages and the gabor object */ void GaborTest::tearDown() { if( image ) delete image; if( gabor ) delete gabor; } /** Rotate an RImage and compare. * Rotate the input RImage and compare with one we know is properly rotated. */ void GaborTest::testNormalize() { if( !image ) CPPUNIT_FAIL( "Failed to load the test image from a serialized file." ); if( !gabor ) CPPUNIT_FAIL( "Failed to create a gabor object." ); gabor->normalize(); RImage &test = gabor->getPixels(); RImage emptyImage; RImage normalized; restoreRImageFromFile( normalized, "unit_mat2gray.rimage" ); CPPUNIT_ASSERT( test.numpixels > 0 ); CPPUNIT_ASSERT( !(test == emptyImage) ); CPPUNIT_ASSERT( test == normalized ); } // Boost serialization code for vectors and complex values. namespace boost { namespace serialization { template inline void serialize (Archive &ar, std::complex& z, const unsigned int file_version) { double r; double i; ar & boost::serialization::make_nvp ("real", r ); ar & boost::serialization::make_nvp ("imag", i ); std::real(z) = r; std::imag(z) = i; } template inline void save (Archive &ar, const std::vector &v, const unsigned int) { unsigned int count = v.size(); ar << BOOST_SERIALIZATION_NVP (count); typename std::vector::const_iterator it = v.begin(); while (count-- > 0) { ar << boost::serialization::make_nvp ("item", *it++); } } template inline void load (Archive &ar, std::vector &v, const unsigned int) { unsigned int count; ar >> BOOST_SERIALIZATION_NVP (count); v.resize (count); typename std::vector::iterator it = v.begin(); while (count-- > 0) { ar >> boost::serialization::make_nvp ("item", *it++); } } template inline void serialize (Archive &ar, std::vector& v, const unsigned int file_version) { boost::serialization::split_free (ar, v, file_version); } } } void GaborTest::testFFT() { gabor->fft(); myComplex *fft = gabor->getImgStar(); CPPUNIT_ASSERT( fft != NULL ); // Read in the good FFT values from a boost serialized file. // The data originally came from the Matlab version of CERT. std::vector goodFFT; std::string fname( "newGoodFFT.xml" ); std::ifstream ifs( fname.c_str() ); if( !ifs.good() ) CPPUNIT_FAIL( "Failed to read newGoodFFT.xml." ); boost::archive::xml_iarchive ia(ifs); ia >> boost::serialization::make_nvp( "goodFFT", goodFFT ); unsigned int cnt = gabor->getWidth() * gabor->getHeight(); if( goodFFT.size() != cnt ) CPPUNIT_FAIL( "Size of loaded known good fft data does not match calculated fft size." ); double diffSum = 0.0; for( unsigned int index=0; index < cnt; ++index ) { double diff = std::abs( fft[index] - goodFFT[index] ); // Diff values are based on tests done in Matlab if( diff > 0.0002 ) CPPUNIT_FAIL( "FFT data failed to match known good data." ); diffSum += diff; } if( diffSum > 0.01 ) CPPUNIT_FAIL( "FFT diff sums were too large." ); // This is how the new good fft values were converted from a matlab file to a boost serilized vector // std::string fname( "newGoodFFT.xml" ); // std::ofstream ofs( fname.c_str() ); // if( !ofs.good() ) // CPPUNIT_FAIL( "Failed to open newGoodFFT.xml." ); // boost::archive::xml_oarchive oa(ofs); // oa << boost::serialization::make_nvp( "goodFFT", newGoodFFT ); } // We really should try to insure that this one runs last so we don't need to load/reload weights files void GaborTest::testSVM() { mp_SVMWeights *svm = mp_SVMWeights::getSVMWeights( weightsPrefix ); const double *weights = svm->getWeights( 1, 2 ); CPPUNIT_ASSERT_MESSAGE( "Probable missing weights files", (weights != NULL) ); weights = svm->getWeights( -9999, -9999 ); CPPUNIT_ASSERT( weights == NULL ); // Now clear the weights files, set the directory to an unlikely location and try getting a // good set of weights. svm->clearWeights(); const std::string saveDirectory = svm->getDirectory(); svm->setDirectory( "asdf@$%#adsf*&" ); weights = svm->getWeights( 1, 2 ); // If the clearWeights worked, then this should fail CPPUNIT_ASSERT( weights == NULL ); svm->clearWeights(); svm->setDirectory( saveDirectory ); } double AUDiff( const std::vector &res, const std::vector &good ) { unsigned int resSize = res.size(); unsigned int goodSize = good.size(); unsigned int maxSize = std::max( resSize, goodSize ); double diff = 0.0; for( unsigned int i=0; i= resSize ) diff += fabs( good[i] ); else if( i >= goodSize ) diff += fabs( res[i] ); else diff += fabs( good[i] - res[i] ); } return diff; } static void printResults( std::vector &results ) { std::cout.precision(8); for( unsigned int i=0; i < results.size(); ++i ) { std::cout << results[i]; if( i < (results.size() - 1) ) std::cout << ", "; } std::cout << std::endl; } static const double maxDiff = 1.0e-5; static const int AUcnt = 10; /* Recalculated the AU outputs on August 31, 2006. */ void GaborTest::testCERT() { // Results from processing the same face patch in Matlab static const double _matlabRes[] = { -0.97767568, -1.7614114, 0.46247908, -1.0068477, -1.319282, -1.2308271, -0.54711207, -0.69344958 }; std::vector matlabRes( _matlabRes, _matlabRes+AUcnt ); gabor->CERT_Gabor(); vector res = gabor->resultsForCategory( weightsPrefix ); double diff = AUDiff( res, matlabRes ); #ifdef DEBUG std::cout << "AUs for image 1: "; printResults( res ); #endif CPPUNIT_ASSERT( diff < maxDiff ); // Test setting a new image RImage face2; restoreRImageFromFile( face2, "unit_face2.rimage" ); gabor->setPixels( face2 ); static const double _matlabRes2[] = { -1.5109611, -0.90098601, -1.800163, -1.6979643, -0.85815242, -1.2595893, -2.2391609, -2.1446403 }; std::vector matlabRes2( _matlabRes2, _matlabRes2+AUcnt ); gabor->CERT_Gabor(); vector res2 = gabor->resultsForCategory( weightsPrefix ); diff = AUDiff( res2, matlabRes2 ); #ifdef DEBUG std::cout << "AUs for image 2: "; printResults( res2 ); #endif CPPUNIT_ASSERT_MESSAGE( "Failed to calculate second face AU's", (diff < maxDiff) ); } void GaborTest::testCERTImages() { std::string datapath( "testImages.txt" ); mpt_AUIterator iter( datapath ); mp_threadedGabor *gabor1 = new mp_threadedGabor( *image, 96, 96 ); auto_ptr gaborDeleter(gabor1); // Make sure it gets deleted std::string fname; std::vector AUs; unsigned int testCount = 0; while( iter.nextDataLine( fname, AUs ) ) { RImage testImage; restoreRImageFromFile( testImage, fname ); gabor1->setPixels( testImage ); gabor1->CERT_Gabor(); vector res = gabor1->resultsForCategory( weightsPrefix ); std::string errorMessage( "Image: " ); errorMessage += fname; double diff = AUDiff( res, AUs ); ++testCount; #ifdef DISPLAY_AUS std::cout << endl << errorMessage << " AUs: " << setprecision( 8 ); for( unsigned int ind=0; ind < res.size(); ++ind ) { std::cout << res[ind] << " "; } std::cout << std::endl; std::cout << endl << " Known Good AUs: " << setprecision( 8 ); for( unsigned int ind=0; ind < AUs.size(); ++ind ) { std::cout << AUs[ind] << " "; } std::cout << std::endl; if( diff >= maxDiff ) std::cout << endl << "Diff is too great: " << diff << endl; #else CPPUNIT_ASSERT_MESSAGE( errorMessage, diff < maxDiff ); #endif } CPPUNIT_ASSERT_MESSAGE( "There should have been at least three test images.", testCount >= 3 ); } /* Make sure that the clearYhat method works. */ void GaborTest::testClearYhat() { gabor->CERT_Gabor(); vector res = gabor->resultsForCategory( weightsPrefix ); // Just make sure we got some kind of non-zero results. double sum = std::accumulate( res.begin(), res.end(), 0.0 ); // AUDiff( res, zeros ); CPPUNIT_ASSERT_MESSAGE( "testClearYhat could not run becuase no results were generated.", sum != 0.0 ); gabor->clearYhat(); res = gabor->resultsForCategory( weightsPrefix ); sum = std::accumulate( res.begin(), res.end(), 0.0 ); // AUDiff( res, zeros ); CPPUNIT_ASSERT_MESSAGE( "clearYhat did not zero out results.", sum < maxDiff ); } /* * Copyright (c) 2006 Machine Perception Laboratory * University of California San Diego. * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */