/* * mp_SVMWeights.cpp * * A class for loading and holding SVM weights. * * Created by Andrew Salamon on Tues May 9 2006. * * Copyright (c) 2006 Machine Perception Laboratory * University of California San Diego. * Please read the license information at the end of this file. */ #include "mp_SVMWeights.h" #include #include #include #include //#include #include #include #include // For system independent path support #include #ifdef WIN32 #include #else extern "C" { #include } #define xmlFree(x) free(x) #endif #include "xmlSmartPtr.h" using namespace std; bool getIntProperty( xmlNodePtr cur, const char *name, int *val ); static map allWeights; const std::string mp_SVMWeights::defaultWeights( "SVMWeights" ); /** Factory method for getting a Singleton SVMWeights object. * This method could return an object of a sub-class, depending possibly on * operating system, and maybe filetype and method used to read the data. */ mp_SVMWeights * mp_SVMWeights::getSVMWeights( std::string _prefix ) { if( 0 == _prefix.size() ) _prefix = defaultWeights; mp_SVMWeights *temp = allWeights[_prefix]; if( !temp ) { temp = new mp_SVMWeights( _prefix ); allWeights[_prefix] = temp; } return temp; } mp_SVMWeights::mp_SVMWeights( std::string _prefix ) : count(0), loadError(false), prefix( _prefix ) { } /** SVM Weights destructor. * Currently this is not going to be called since the singleton is never deleted. */ mp_SVMWeights::~mp_SVMWeights() { clearWeights(); } /** Free memory used by the SVM Weights and clear the map holding them. * This might be useful if you want to update weights without restarting the application (unlikely). * Or you could use it to try loading weights from a different location if an error occured * the first time. */ void mp_SVMWeights::clearWeights() { // walk through all of the loaded weights and free them for( mpSVMWeightMap::iterator iter = weights.begin(); iter != weights.end(); iter++ ) { free((void *)iter->second); } weights.clear(); } /** Sets the path to the directory containing the SVM Weight files. * We will attemmpt to load SVM Weights from files in the given directory. Otherwise * we only search the current directory. * * This string will be used via Boost's * filesystem::path * library and should work with system dependent paths. * For example: /Users/Shared/SVMWeights would work on a Mac OS X machine * and C:\\AFolder\\SVMWeights should work on a Window's machine. * That hasn't been properly tested yet. * Both relative and absolute paths should work. */ void mp_SVMWeights::setDirectory( const std::string &dir ) { directory=dir; } const string & mp_SVMWeights::getDirectory() { return directory; } bool mp_SVMWeights::getLoadError() { return loadError; } const double * mp_SVMWeights::getWeights( int ni, int mu ) { const double *w = NULL; mpSVMIndex index(ni,mu); if( weights.count( index ) > 0 ) w = weights[ index ]; if( !w ) { w = loadWeights( ni, mu ); if( w ) { mpSVMIndex kindex(ni,mu); weights[ kindex ] = w; } } // If we failed to find or load a file, set our error flag if( !w ) loadError = true; return w; } const double * mp_SVMWeights::loadWeights( int ni, int mu ) { return loadWeightsXML( ni, mu ); } const double * mp_SVMWeights::loadWeightsXML( int ni, int mu ) { double *comp = NULL; // build an XML tree from the file; // We may want to switch to an event based parser (SAX) to limit memory use, but it would need to be // synchronous, or the app would at least need to insure that all files were loaded before trying to // access any. Error handling might be easier/better when using SAX, too. // We can use this alternate API to suppress error and warning messages, so we can do our own error handling xmlParserCtxtPtr xmlCtxt = xmlNewParserCtxt(); // Make sure the parser context get's deleted no matter how we exit the method xmlSmartPtr ctxtDel( xmlCtxt, (xmlSmartPtr::deleter)xmlFreeParserCtxt ); if( xmlInitParserCtxt( xmlCtxt ) ) { cerr << "Couldn't init Parser context" << endl; return comp; } if( xmlCtxtUseOptions( xmlCtxt, (XML_PARSE_NOERROR | XML_PARSE_NOWARNING) ) ) { cerr << "Couldn't set Parser context options" << endl; return comp; } ostringstream output_stream; output_stream << prefix << "_" << ni << "_" << mu << ".xml"; string filename; try { boost::filesystem::path xmlPath( output_stream.str() ); if( !directory.empty() ) { boost::filesystem::path xmlDir( directory, boost::filesystem::native ); xmlPath = xmlDir / xmlPath; } filename = xmlPath.native_file_string(); } catch ( const boost::filesystem::filesystem_error & ) { cerr << "Couldn't build valid path for file: " << output_stream.str() << " with directory: " << directory << endl; return comp; } xmlDocPtr doc = xmlCtxtReadFile( xmlCtxt, filename.c_str(), NULL, (XML_PARSE_NOERROR | XML_PARSE_NOWARNING) ); if (doc == NULL) { // Does libxml2 provide any error messages for us to access? // cerr << "Couldn't parse (or open): " << filename << endl; return comp; } // Make sure the doc get's free'd no matter what xmlSmartPtr docDel( doc, xmlFreeDoc ); xmlNodePtr cur = xmlDocGetRootElement(doc); if( !cur ) { cerr << "Empty Document in file: " << filename << endl; return comp; } if(xmlStrcmp(cur->name, (const xmlChar*) "svm")) { cerr << "Invalid root element in file: " << filename << endl; return comp; } // Grab the ni and mu attributes and make sure they match. int _ni; if( !getIntProperty( cur, "ni", &_ni ) || (_ni != ni) ) { cerr << "Missing or invalid 'ni' attribute in file: " << filename << endl; return comp; } int _mu; if( !getIntProperty( cur, "mu", &_mu ) || (_mu != mu) ) { cerr << "Missing or invalid 'mu' attribute in file: " << filename << endl; return comp; } int colcount=0; int rowcount=0; if( !getIntProperty( cur, "colcount", &colcount ) || (colcount <= 0) || (colcount > 12) ) { cerr << "Missing or invalid 'colcount' attribute in file: " << filename << endl; return comp; } count = colcount; const char *rowFormat = NULL; std::string rowString; // Build the format string we will use when reading in each row. // Old format was: "%lf %lf %lf %lf %lf %lf %lf %lf" for( unsigned int cnt = colcount; cnt > 0; --cnt ) { rowString += "%lf "; } rowFormat = rowString.c_str(); if( !getIntProperty( cur, "rowcount", &rowcount ) || (rowcount <= 0) ) { cerr << "Missing or invalid 'rowcount' attribute in file: " << filename << endl; return comp; } // Get weights creation related properties // file="FFD06_EF06_W_norm_mod6g2.mat" date="24-Oct-2006 13:48:32" cert="258:270M" char *tmpAttr = (char*)xmlGetProp( cur, (const xmlChar *)"file" ); if( tmpAttr ) sourceFile = tmpAttr; tmpAttr = (char*)xmlGetProp( cur, (const xmlChar *)"date" ); if( tmpAttr ) createDate = tmpAttr; tmpAttr = (char*)xmlGetProp( cur, (const xmlChar *)"cert" ); if( tmpAttr ) certVersion = tmpAttr; comp = (double *)malloc( sizeof(double) * rowcount * colcount ); int row=0, index=0; for( cur = cur->xmlChildrenNode; cur != NULL; cur = cur->next ) { if(!xmlStrcmp(cur->name, (const xmlChar*)"row")) { // Number of children should only be one, but we ought to check it char *rowData = (char *)xmlNodeGetContent( cur ); if( !rowData ) { cerr << "Bad row data, row " << row << ", in file: " << filename << endl; return comp; } xmlSmartPtr rowDel( rowData, (xmlSmartPtr::deleter)xmlFree ); #ifdef WIN32 double d1[12]; // this needs to be at least as large as the number of columns in the sscanf below. #else double d1[colcount]; #endif // This needs to be generalized, but will currently work for anything up to 12 columns. Dangerous, though. int cnt = sscanf( rowData, rowFormat, &d1[0],&d1[1],&d1[2],&d1[3],&d1[4],&d1[5],&d1[6],&d1[7],&d1[8],&d1[9],&d1[10],&d1[11] ); if( colcount != cnt ) { cerr << "Bad row " << row << " in file: " << filename << endl; return comp; } for( int i=0; i < colcount; ++i ) { comp[index++] = d1[i]; } // This works but was considerably slower than using sscanf. // istringstream input( rowData ); // // for( int i=1; i <= colcount; ++i ) // { // double tmp; // if( input.eof() ) // { // cerr << "Bad row " << row << " in file: " << filename << endl; // return comp; // } // input >> tmp; // comp[index++] = tmp; // } ++row; } } if( row != rowcount ) { cerr << "Document's rowcount does not match actual data: " << filename << endl; return comp; } return comp; } const double * mp_SVMWeights::loadWeightsBinary( int ni, int mu ) { double *comp = NULL; mpSVMIndex kindex(ni,mu); weights[ kindex ] = comp; return comp; } /** Pre load all SVM Weights files. * This method will preload all SVM Weights files, but it doesn't present any feedback, * so applications with a GUI will probably want to implement this themselves with some UI * feedback. */ void mp_SVMWeights::loadAllWeights( int niMax, int muMax ) { loadError = false; for( int ni=1; ni <= niMax; ++ni ) { for( int mu=1; mu <= muMax; ++mu ) { getWeights( ni, mu ); } } } bool getIntProperty( xmlNodePtr cur, const char *name, int *val ) { char *prop = (char*)xmlGetProp( cur, (const xmlChar *)name ); bool good=true; if(!prop) return false; if( sscanf( prop, "%d", val ) != 1 ) good = false; xmlFree( prop ); return good; }