TuttleOFX
1
|
00001 #include <sam/common/utility.hpp> 00002 #include <sam/common/options.hpp> 00003 00004 #include <tuttle/host/Graph.hpp> 00005 00006 #include <boost/filesystem.hpp> 00007 #include <boost/program_options.hpp> 00008 #include <boost/algorithm/string.hpp> 00009 00010 #include <Sequence.hpp> 00011 00012 #include <limits> 00013 00014 using namespace tuttle::host; 00015 namespace bfs = boost::filesystem; 00016 namespace bpo = boost::program_options; 00017 00018 00019 static int _notNullImage = 0; 00020 static int _nullFileSize = 0; 00021 static int _corruptedImage = 0; 00022 static int _missingFiles = 0; 00023 static int _processedImages = 0; 00024 00025 enum EReturnCode { 00026 eReturnCodeOK = 0, 00027 eReturnCodeErrorInImages = 1, 00028 eReturnCodeApplicationError = 2 00029 }; 00030 00031 enum EImageStatus { 00032 eImageStatusDiffNull = 0, 00033 eImageStatusDiffNotNull, 00034 eImageStatusFileSizeError, 00035 eImageStatusNoFile, 00036 eImageStatusImageError 00037 }; 00038 00039 /** 00040 * @brief Process the difference between 2 readers and return status. 00041 */ 00042 EImageStatus diffImageStatus(Graph::Node& read1, Graph::Node& read2, Graph::Node& stat, Graph& graph, const bfs::path& filename1, const bfs::path& filename2) 00043 { 00044 if (bfs::exists(filename1) == 0 || bfs::exists(filename2) == 0) 00045 return eImageStatusNoFile; 00046 00047 if (bfs::file_size(filename1) == 0 || bfs::file_size(filename2) == 0) 00048 return eImageStatusFileSizeError; 00049 00050 try 00051 { 00052 // Setup parameters 00053 read1.getParam("filename").setValue(filename1.string()); 00054 read2.getParam("filename").setValue(filename2.string()); 00055 00056 graph.compute(stat); 00057 00058 std::stringstream stream; 00059 stream << "diff = "; 00060 for (unsigned int i = 0; i < 3; ++i) 00061 { 00062 stream << stat.getParam("quality").getDoubleValueAtIndex(i) << " "; 00063 } 00064 TUTTLE_LOG_TRACE( stream.str() ); 00065 00066 for (unsigned int i = 0; i < 3; ++i) 00067 { 00068 if (stat.getParam("quality").getDoubleValueAtIndex(i) != 0.0 ) 00069 return eImageStatusDiffNotNull; 00070 } 00071 //TUTTLE_LOG_TRACE( stat ); 00072 00073 return eImageStatusDiffNull; 00074 } 00075 catch (...) 00076 { 00077 TUTTLE_LOG_ERROR( boost::current_exception() ); 00078 TUTTLE_LOG_ERROR( boost::current_exception_diagnostic_information() ); 00079 return eImageStatusImageError; 00080 } 00081 } 00082 00083 /** 00084 * @brief Process difference between a reader and a generator. 00085 */ 00086 EImageStatus diffImageStatus(Graph::Node& read1, Graph::Node& read2, Graph::Node& stat, Graph& graph, const bfs::path& filename, const std::vector<std::string>& generatorOptions ) 00087 { 00088 if ( ! bfs::exists(filename)) 00089 return eImageStatusNoFile; 00090 00091 if ( bfs::file_size(filename) == 0 ) 00092 return eImageStatusFileSizeError; 00093 00094 try 00095 { 00096 // Setup parameters 00097 read1.getParam("filename").setValue(filename.string()); 00098 for( size_t i=0; i<generatorOptions.size(); i++ ) 00099 { 00100 std::vector<std::string> opt; 00101 boost::split(opt, generatorOptions.at(i), boost::is_any_of("=")); 00102 int optvalue = atoi ( opt.at(1).c_str() ); 00103 if( optvalue == 0 ) 00104 { 00105 std::vector<std::string> optList; 00106 boost::split(optList, opt.at(1), boost::is_any_of(",")); 00107 switch( optList.size() ) 00108 { 00109 case 1 : 00110 { // not a number, set a string parameter 00111 read2.getParam(opt.at(0)).setValue( opt.at(1) ); 00112 break; 00113 } 00114 case 2 : 00115 { 00116 float opt0 = atof ( optList.at(0).c_str() ); 00117 float opt1 = atof ( optList.at(1).c_str() ); 00118 read2.getParam(opt.at(0)).setValue( opt0, opt1 ); 00119 break; 00120 } 00121 case 3 : 00122 { 00123 float opt0 = atof ( optList.at(0).c_str() ); 00124 float opt1 = atof ( optList.at(1).c_str() ); 00125 float opt2 = atof ( optList.at(2).c_str() ); 00126 read2.getParam(opt.at(0)).setValue( opt0, opt1, opt2 ); 00127 break; 00128 }/* 00129 case 4 : 00130 { 00131 double opt0 = atof ( optList.at(0).c_str() ); 00132 double opt1 = atof ( optList.at(1).c_str() ); 00133 double opt2 = atof ( optList.at(2).c_str() ); 00134 //double opt3 = atof ( optList.at(3).c_str() ); 00135 read2.getParam(opt.at(0)).setValue( opt0, opt1, opt2 ); 00136 }*/ 00137 default : 00138 { 00139 for(size_t i=0; i<opt.size(); i++) 00140 TUTTLE_LOG_VAR( TUTTLE_INFO, opt.at(i)); 00141 TUTTLE_LOG_ERROR( "unable to process " << optList.size() << " arguments" ); 00142 break; 00143 } 00144 } 00145 00146 00147 } 00148 else 00149 { 00150 read2.getParam(opt.at(0)).setValue( optvalue ); 00151 } 00152 } 00153 00154 graph.compute(stat); 00155 00156 std::stringstream stream; 00157 stream << "diff = "; 00158 for (unsigned int i = 0; i < 3; ++i) 00159 { 00160 stream << stat.getParam("quality").getDoubleValueAtIndex(i) << " "; 00161 } 00162 TUTTLE_LOG_TRACE( stream.str() ); 00163 00164 for (unsigned int i = 0; i < 3; ++i) 00165 { 00166 if (stat.getParam("quality").getDoubleValueAtIndex(i) != 0.0 ) 00167 return eImageStatusDiffNotNull; 00168 } 00169 //TUTTLE_LOG_TRACE( stat ); 00170 00171 return eImageStatusDiffNull; 00172 } 00173 catch (...) 00174 { 00175 TUTTLE_LOG_ERROR( boost::current_exception() ); 00176 TUTTLE_LOG_ERROR( boost::current_exception_diagnostic_information() ); 00177 return eImageStatusImageError; 00178 } 00179 } 00180 00181 /** 00182 * @brief Difference between 2 reader's node associated at 2 files 00183 */ 00184 EImageStatus diffFile(Graph::Node& read1, Graph::Node& read2, Graph::Node& stat, Graph& graph, const bfs::path& filename1, const bfs::path& filename2) 00185 { 00186 EImageStatus s = diffImageStatus(read1, read2, stat, graph, filename1, filename2); 00187 00188 std::string message; 00189 switch (s) { 00190 case eImageStatusDiffNull: 00191 break; 00192 case eImageStatusDiffNotNull: 00193 message = "Diff not null: "; 00194 ++_notNullImage; 00195 break; 00196 case eImageStatusFileSizeError: 00197 message = "Null file size: "; 00198 ++_nullFileSize; 00199 break; 00200 case eImageStatusNoFile: 00201 message = "Missing file: "; 00202 ++_missingFiles; 00203 break; 00204 case eImageStatusImageError: 00205 message = "Corrupted image: "; 00206 ++_corruptedImage; 00207 break; 00208 } 00209 ++_processedImages; 00210 TUTTLE_LOG_WARNING( message << filename1 << " and: " << filename2 ); 00211 return s; 00212 } 00213 00214 /** 00215 * @brief Difference between 1 reader and 1 generator associated with 1 file and vector of options for the generator 00216 */ 00217 EImageStatus diffFile(Graph::Node& read1, Graph::Node& read2, Graph::Node& stat, Graph& graph, const bfs::path& filename1, const std::vector<std::string>& generatorOptions) 00218 { 00219 EImageStatus s = diffImageStatus(read1, read2, stat, graph, filename1, generatorOptions); 00220 00221 std::string message; 00222 switch (s) { 00223 case eImageStatusDiffNull: 00224 break; 00225 case eImageStatusDiffNotNull: 00226 message = "Diff not null: "; 00227 ++_notNullImage; 00228 break; 00229 case eImageStatusFileSizeError: 00230 message = "Null file size: "; 00231 ++_nullFileSize; 00232 break; 00233 case eImageStatusNoFile: 00234 message = "Missing file: "; 00235 ++_missingFiles; 00236 break; 00237 case eImageStatusImageError: 00238 message = "Corrupted image: "; 00239 ++_corruptedImage; 00240 break; 00241 } 00242 ++_processedImages; 00243 TUTTLE_LOG_WARNING( message << filename1 << " and the generator" ); 00244 return s; 00245 } 00246 00247 void diffSequence(Graph::Node& read1, Graph::Node& read2, Graph::Node& stat, Graph& graph, const sequenceParser::Sequence& seq1, const sequenceParser::Sequence& seq2) 00248 { 00249 for (sequenceParser::Time t = seq1.getFirstTime(); t <= seq1.getLastTime(); ++t) 00250 { 00251 diffFile(read1, read2, stat, graph, seq1.getAbsoluteFilenameAt(t), seq2.getAbsoluteFilenameAt(t)); 00252 } 00253 } 00254 00255 void diffSequence(Graph::Node& read1, Graph::Node& read2, Graph::Node& stat, Graph& graph, const sequenceParser::Sequence& seq1, const sequenceParser::Sequence& seq2, const sequenceParser::Time first, 00256 const sequenceParser::Time last) 00257 { 00258 for (sequenceParser::Time t = first; t <= last; ++t) 00259 { 00260 diffFile(read1, read2, stat, graph, seq1.getAbsoluteFilenameAt(t), seq2.getAbsoluteFilenameAt(t)); 00261 } 00262 } 00263 00264 void displayHelp(bpo::options_description &desc) 00265 { 00266 using namespace sam; 00267 boost::shared_ptr<tuttle::common::Color> color( tuttle::common::Color::get() ); 00268 00269 TUTTLE_LOG_INFO( color->_blue << "TuttleOFX project [" << kUrlTuttleofxProject << "]" << color->_std ); 00270 TUTTLE_LOG_INFO( "" ); 00271 TUTTLE_LOG_INFO( color->_blue << "NAME" << color->_std); 00272 TUTTLE_LOG_INFO( color->_green << "\tsam-diff - compute difference between 2 images/sequences" << color->_std ); 00273 TUTTLE_LOG_INFO( "" ); 00274 TUTTLE_LOG_INFO( color->_blue << "SYNOPSIS" << color->_std); 00275 TUTTLE_LOG_INFO( color->_green << "\tsam-diff [reader] [input] [reader] [input] [options]" << color->_std ); 00276 TUTTLE_LOG_INFO( "" ); 00277 TUTTLE_LOG_INFO( color->_blue << "DESCRIPTION" << color->_std); 00278 TUTTLE_LOG_INFO( color->_green << "\tDiff if sequence have black images." << color->_std ); 00279 TUTTLE_LOG_INFO( color->_green << "\tThis tools process the PSNR of an image, and if it's null, the image is considered black." << color->_std ); 00280 TUTTLE_LOG_INFO( color->_green << "\tOnly compare RGB layout, not Alpha." << color->_std ); 00281 TUTTLE_LOG_INFO( "" ); 00282 TUTTLE_LOG_INFO( color->_blue << "OPTIONS" << color->_std ); 00283 TUTTLE_LOG_INFO( "" ); 00284 TUTTLE_LOG_INFO( desc); 00285 00286 TUTTLE_LOG_INFO( color->_blue << "EXAMPLES" << color->_std << std::left ); 00287 SAM_EXAMPLE_TITLE_COUT( "Sequence possible definitions: "); 00288 SAM_EXAMPLE_LINE_COUT ( "Auto-detect padding : ", "seq.@.jpg"); 00289 SAM_EXAMPLE_LINE_COUT ( "Padding of 8 (usual style): ", "seq.########.jpg"); 00290 SAM_EXAMPLE_TITLE_COUT( "Compare two images: "); 00291 SAM_EXAMPLE_LINE_COUT ( "", "sam-diff --reader tuttle.jpegreader --input path/image.jpg --reader tuttle.jpegreader --input anotherPath/image.jpg"); 00292 SAM_EXAMPLE_TITLE_COUT( "Compare two sequences: "); 00293 SAM_EXAMPLE_LINE_COUT ( "", "sam-diff --reader tuttle.jpegreader --input path/seq.@.jpg --reader tuttle.jpegreader --input anotherPath/seq.@.jpg --range 677836 677839"); 00294 SAM_EXAMPLE_TITLE_COUT( "Compare one sequence with one generator (generator need to be every time the second node): "); 00295 SAM_EXAMPLE_LINE_COUT ( "", "sam-diff --reader tuttle.jpegreader --input path/seq.@.jpg --reader tuttle.constant --generator-args width=500 components=rgb --range 677836 677839" ); 00296 TUTTLE_LOG_INFO( "" ); 00297 } 00298 00299 int main( int argc, char** argv ) 00300 { 00301 signal(SIGINT, signal_callback_handler); 00302 00303 using namespace tuttle::common; 00304 using namespace sam; 00305 00306 boost::shared_ptr<formatters::Formatter> formatter( formatters::Formatter::get() ); 00307 boost::shared_ptr<Color> color( Color::get() ); 00308 00309 try { 00310 std::vector<std::string> inputs; 00311 std::vector<std::string> nodeId; 00312 bool hasRange = false; 00313 bool script = false; 00314 std::vector<int> range; 00315 00316 std::vector<std::string> generator; 00317 00318 bpo::options_description desc; 00319 bpo::options_description hidden; 00320 00321 formatter->init_logging(); 00322 00323 desc.add_options() 00324 ( kHelpOptionString, kHelpOptionMessage ) 00325 ( kReaderOptionString, bpo::value(&nodeId), kReaderOptionMessage ) 00326 ( kInputOptionString, bpo::value(&inputs), kInputOptionMessage ) 00327 ( kRangeOptionString, bpo::value(&range)->multitoken(), kRangeOptionMessage ) 00328 ( kGeneratorArgsOptionString, bpo::value(&generator)->multitoken(), kGeneratorArgsOptionMessage ) 00329 ( kVerboseOptionString, bpo::value<int>()->default_value( 2 ), kVerboseOptionMessage ) 00330 ( kQuietOptionString, kQuietOptionMessage ) 00331 ( kBriefOptionString, kBriefOptionMessage ) 00332 ( kColorOptionString, kColorOptionMessage ) 00333 ( kScriptOptionString, kScriptOptionMessage ); 00334 00335 // describe hidden options 00336 hidden.add_options() 00337 ( kEnableColorOptionString, bpo::value<std::string>(), kEnableColorOptionMessage ); 00338 00339 bpo::options_description cmdline_options; 00340 cmdline_options.add(desc).add(hidden); 00341 00342 bpo::positional_options_description pod; 00343 pod.add(kInputOptionLongName, -1); 00344 00345 bpo::variables_map vm; 00346 00347 try { 00348 //parse the command line, and put the result in vm 00349 bpo::store(bpo::command_line_parser(argc, argv).options(cmdline_options).positional(pod).run(), vm); 00350 00351 // get environment options and parse them 00352 if (const char* env_diff_options = std::getenv("SAM_DIFF_OPTIONS")) { 00353 const std::vector<std::string> vecOptions = bpo::split_unix(env_diff_options, " "); 00354 bpo::store(bpo::command_line_parser(vecOptions).options(cmdline_options).positional(pod).run(), vm); 00355 } 00356 if (const char* env_diff_options = std::getenv("SAM_OPTIONS")) { 00357 const std::vector<std::string> vecOptions = bpo::split_unix(env_diff_options, " "); 00358 bpo::store(bpo::command_line_parser(vecOptions).options(cmdline_options).positional(pod).run(), vm); 00359 } 00360 bpo::notify(vm); 00361 } catch (const bpo::error& e) { 00362 TUTTLE_LOG_ERROR( "sam-diff: command line error: " << e.what() ); 00363 exit(254); 00364 } catch (...) { 00365 TUTTLE_LOG_ERROR( "sam-diff: unknown error in command line." ); 00366 exit(254); 00367 } 00368 00369 if (vm.count(kScriptOptionLongName)) { 00370 // disable color, disable directory printing and set relative path by default 00371 script = true; 00372 } 00373 00374 switch( vm[ kVerboseOptionLongName ].as< int >() ) 00375 { 00376 case 0 : formatter->setLogLevel( boost::log::trivial::trace ); break; 00377 case 1 : formatter->setLogLevel( boost::log::trivial::debug ); break; 00378 case 2 : formatter->setLogLevel( boost::log::trivial::info ); break; 00379 case 3 : formatter->setLogLevel( boost::log::trivial::warning ); break; 00380 case 4 : formatter->setLogLevel( boost::log::trivial::error ); break; 00381 case 5 : formatter->setLogLevel( boost::log::trivial::fatal ); break; 00382 default : formatter->setLogLevel( boost::log::trivial::warning ); break; 00383 } 00384 if( vm.count(kQuietOptionLongName) ) 00385 { 00386 formatter->setLogLevel( boost::log::trivial::fatal ); 00387 } 00388 00389 if( vm.count( kColorOptionLongName ) && !script ) 00390 { 00391 color->enable(); 00392 } 00393 00394 if( vm.count( kEnableColorOptionLongName ) && !script ) 00395 { 00396 const std::string str = vm[kEnableColorOptionLongName].as<std::string>(); 00397 if( string_to_boolean(str) ) 00398 { 00399 color->enable(); 00400 } 00401 else 00402 { 00403 color->disable(); 00404 } 00405 } 00406 00407 if( vm.count( kBriefOptionLongName ) ) 00408 { 00409 TUTTLE_LOG_INFO( color->_green << "diff image files" << color->_std ); 00410 return 0; 00411 } 00412 00413 if (vm.count(kHelpOptionLongName)) { 00414 displayHelp(desc); 00415 return 0; 00416 } 00417 if (!vm.count(kReaderOptionLongName)) { 00418 TUTTLE_LOG_ERROR( "sam-diff : no reader specified." ); 00419 displayHelp(desc); 00420 return 254; 00421 } 00422 if (!vm.count(kInputOptionLongName)) { 00423 TUTTLE_LOG_ERROR( "sam-diff : no input specified." ); 00424 displayHelp(desc); 00425 return 254; 00426 } 00427 00428 if (vm.count(kGeneratorArgsOptionLongName)) { 00429 generator = vm[kGeneratorArgsOptionLongName].as<std::vector<std::string> >(); 00430 } 00431 00432 nodeId = vm[kReaderOptionLongName].as<std::vector<std::string> >(); 00433 inputs = vm[kInputOptionLongName].as<std::vector<std::string> >(); 00434 00435 if( nodeId.size() != 2 ) 00436 { 00437 TUTTLE_LOG_ERROR( "sam-diff : require 2 input nodes." ); 00438 displayHelp(desc); 00439 return 254; 00440 } 00441 if (vm.count(kRangeOptionLongName)) 00442 { 00443 range = vm[kRangeOptionLongName].as<std::vector<int> >(); 00444 hasRange = (range.size() == 2); 00445 } 00446 00447 core().preload(); 00448 Graph graph; 00449 00450 TUTTLE_LOG_TRACE( "in1: " << nodeId.at(0) ); 00451 TUTTLE_LOG_TRACE( "in2: " << nodeId.at(1) ); 00452 00453 Graph::Node& read1 = graph.createNode(nodeId.at(0)); 00454 Graph::Node& read2 = graph.createNode(nodeId.at(1)); 00455 //Graph::Node& viewer = graph.createNode( "tuttle.viewer" ); 00456 Graph::Node& stat = graph.createNode("tuttle.diff"); 00457 //graph.connect( viewer, stat ); 00458 graph.connect(read1, stat); 00459 graph.connect(read2, stat.getAttribute("SourceB")); 00460 00461 switch( inputs.size() ) 00462 { 00463 case 0 : 00464 { 00465 break; 00466 } 00467 case 1 : 00468 { 00469 bfs::path path1 = inputs.at(0); 00470 00471 if ( bfs::exists(path1)) { 00472 // process a file 00473 diffFile(read1, read2, stat, graph, path1, generator ); 00474 } 00475 else 00476 { 00477 // process a sequence 00478 /* 00479 try { 00480 Sequence s1(path1); 00481 if (hasRange) { 00482 diffSequence(read1, read2, stat, graph, s1, generator, range[0], range[1]); 00483 } else { 00484 diffSequence(read1, read2, stat, graph, s1, generator); 00485 } 00486 } catch (...) { 00487 std::cerr << "Unrecognized pattern " << path1 << std::endl; 00488 return eReturnCodeApplicationError; 00489 }*/ 00490 } 00491 break; 00492 } 00493 case 2 : 00494 { 00495 bfs::path path1 = inputs.at(0); 00496 bfs::path path2 = inputs.at(1); 00497 00498 00499 if (bfs::exists(path1)) 00500 { 00501 if (!bfs::exists(path2)) 00502 { 00503 TUTTLE_LOG_ERROR( "could not find file or directory 2 (first is not a sequence)." ); 00504 return 0; 00505 } 00506 /* 00507 if( bfs::is_directory( path1 ) ) 00508 { 00509 std::list<boost::shared_ptr<FileObject> > fObjects; 00510 fObjects = fileObjectsInDir( path ); 00511 BOOST_FOREACH( const boost::shared_ptr<FileObject> fObj, fObjects ) 00512 { 00513 switch( fObj->getMaskType() ) 00514 { 00515 case eMaskTypeSequence: 00516 { 00517 diffSequence( read1, read2, stat, graph, dynamic_cast<const Sequence&>( *fObj ) ); 00518 break; 00519 } 00520 case eMaskTypeFile: 00521 { 00522 const File fFile = dynamic_cast<const File&>( *fObj ); 00523 diffFile( read1, read2, stat, graph, fFile.getAbsoluteFilename(), fFile.getAbsoluteFilename() ); 00524 break; 00525 } 00526 case eMaskTypeDirectory: 00527 case eMaskTypeUndefined: 00528 break; 00529 } 00530 } 00531 } 00532 else 00533 {*/ 00534 diffFile(read1, read2, stat, graph, path1, path2); 00535 //} 00536 } 00537 else 00538 { 00539 try 00540 { 00541 sequenceParser::Sequence s1(path1); 00542 sequenceParser::Sequence s2(path2); 00543 if (hasRange) 00544 { 00545 diffSequence(read1, read2, stat, graph, s1, s2, range[0], range[1]); 00546 } 00547 else 00548 { 00549 diffSequence(read1, read2, stat, graph, s1, s2); 00550 } 00551 } 00552 catch(...) 00553 { 00554 TUTTLE_LOG_WARNING( "Unrecognized pattern " << path1 << " or " << path2 ); 00555 return eReturnCodeApplicationError; 00556 } 00557 } 00558 } 00559 } 00560 } 00561 catch (...) 00562 { 00563 TUTTLE_LOG_ERROR( "sam-diff error " << boost::current_exception_diagnostic_information() ); 00564 return 255; 00565 } 00566 00567 TUTTLE_LOG_INFO( "________________________________________"); 00568 TUTTLE_LOG_INFO( "Processed images: " << _processedImages); 00569 TUTTLE_LOG_INFO( "Different images: " << _notNullImage); 00570 TUTTLE_LOG_INFO( "Null file size: " << _nullFileSize); 00571 TUTTLE_LOG_INFO( "Corrupted images: " << _corruptedImage); 00572 TUTTLE_LOG_INFO( "Holes in sequence: " << _missingFiles); 00573 TUTTLE_LOG_INFO( "________________________________________"); 00574 00575 return _notNullImage + _nullFileSize + _corruptedImage + _missingFiles; 00576 } 00577