00001 /*!@file Channels/OrientationChannel.C */ 00002 00003 // //////////////////////////////////////////////////////////////////// // 00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005 // 00005 // by the University of Southern California (USC) and the iLab at USC. // 00006 // See http://iLab.usc.edu for information about this project. // 00007 // //////////////////////////////////////////////////////////////////// // 00008 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00009 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00010 // in Visual Environments, and Applications'' by Christof Koch and // 00011 // Laurent Itti, California Institute of Technology, 2001 (patent // 00012 // pending; application number 09/912,225 filed July 23, 2001; see // 00013 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00014 // //////////////////////////////////////////////////////////////////// // 00015 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00016 // // 00017 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00018 // redistribute it and/or modify it under the terms of the GNU General // 00019 // Public License as published by the Free Software Foundation; either // 00020 // version 2 of the License, or (at your option) any later version. // 00021 // // 00022 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00023 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00024 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00025 // PURPOSE. See the GNU General Public License for more details. // 00026 // // 00027 // You should have received a copy of the GNU General Public License // 00028 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00029 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00030 // Boston, MA 02111-1307 USA. // 00031 // //////////////////////////////////////////////////////////////////// // 00032 // 00033 // Primary maintainer for this file: 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Channels/OrientationChannel.C $ 00035 // $Id: OrientationChannel.C 12074 2009-11-24 07:51:51Z itti $ 00036 // 00037 00038 #ifndef ORIENTATIONCHANNEL_C_DEFINED 00039 #define ORIENTATIONCHANNEL_C_DEFINED 00040 00041 #include "Channels/OrientationChannel.H" 00042 00043 #include "Channels/ChannelOpts.H" 00044 #include "Channels/GaborChannel.H" 00045 #include "Component/OptionManager.H" 00046 #include "Image/ImageSetOps.H" 00047 #include "Image/PyramidOps.H" 00048 #include "Image/PyramidCache.H" 00049 #include "rutz/mutex.h" 00050 #include "rutz/trace.h" 00051 #include "Util/sformat.H" 00052 00053 #include <cstdio> // for sscanf() 00054 00055 // ###################################################################### 00056 // OrientationChannel member definitions: 00057 // ###################################################################### 00058 00059 // ###################################################################### 00060 OrientationChannel::OrientationChannel(OptionManager& mgr) : 00061 ComplexChannel(mgr, "Orientation", "orientation", ORI), 00062 itsNumOrients(&OPT_NumOrientations, this), // see Channels/ChannelOpts.{H,C} 00063 itsInteractString(&OPT_OriInteraction, this), 00064 itsInteractType(NONE), 00065 itsDidInteractions(false), 00066 itsOverideTagName(false) 00067 { 00068 GVX_TRACE(__PRETTY_FUNCTION__); 00069 // let's build our channels; we may have to re-build them if 00070 // itsNumOrient get changed on us before we start(): 00071 buildSubChans(); 00072 } 00073 00074 // ###################################################################### 00075 OrientationChannel::OrientationChannel(OptionManager& mgr, const char* tag, const char* desc, const char* gabortag) : 00076 ComplexChannel(mgr, "Orientation", "orientation", ORI), 00077 itsNumOrients(&OPT_NumOrientations, this), // see Channels/ChannelOpts.{H,C} 00078 itsInteractString(&OPT_OriInteraction, this), 00079 itsInteractType(NONE), 00080 itsDidInteractions(false), 00081 itsOverideTagName(true) 00082 { 00083 GVX_TRACE(__PRETTY_FUNCTION__); 00084 // let's build our channels; we may have to re-build them if 00085 // itsNumOrient get changed on us before we start(): 00086 00087 setDescriptiveName(sformat("%s", desc)); 00088 setTagName(sformat("%s", tag)); 00089 00090 itsGaborOverideTag = gabortag; 00091 00092 buildSubChans(); 00093 00094 } 00095 // ###################################################################### 00096 void OrientationChannel::buildSubChans() 00097 { 00098 GVX_TRACE(__PRETTY_FUNCTION__); 00099 // kill any subchans we may have had... 00100 this->removeAllSubChans(); 00101 00102 // let's instantiate our Gabor subchannels now that we know how many 00103 // we want. They will inherit the current values (typically 00104 // post-command-line parsing) of all their options as they are 00105 // constructed: 00106 if(itsOverideTagName) 00107 { 00108 LINFO("Using %d %s orientations spanning [0..180]deg", itsNumOrients.getVal(),itsGaborOverideTag.c_str()); 00109 for (uint ori = 0; ori < itsNumOrients.getVal(); ++ori) 00110 { 00111 nub::ref<GaborChannel> chan = 00112 makeSharedComp 00113 (new GaborChannel(getManager(), ori, 00114 180.0 * double(ori) / 00115 double(itsNumOrients.getVal()), 00116 itsGaborOverideTag.c_str(), 00117 itsGaborOverideTag.c_str())); 00118 this->addSubChan(chan); 00119 00120 // let's export options on our newly built channels: 00121 chan->exportOptions(MC_RECURSE); 00122 } 00123 } 00124 else 00125 { 00126 LINFO("Using %d orientations spanning [0..180]deg", itsNumOrients.getVal()); 00127 for (uint ori = 0; ori < itsNumOrients.getVal(); ++ori) 00128 { 00129 nub::ref<GaborChannel> chan = 00130 makeSharedComp 00131 (new GaborChannel(getManager(), ori, 00132 180.0 * double(ori) / 00133 double(itsNumOrients.getVal()))); 00134 this->addSubChan(chan); 00135 00136 // let's export options on our newly built channels: 00137 chan->exportOptions(MC_RECURSE); 00138 } 00139 } 00140 // Here, we just use GaborChannel's default tag names 00141 } 00142 00143 // ###################################################################### 00144 void OrientationChannel::parseInteractString(const std::string& value) 00145 { 00146 GVX_TRACE(__PRETTY_FUNCTION__); 00147 00148 // need to clear the coefficients in any case 00149 itsInteractCoeffs.clear(); 00150 00151 if (value.compare("None") == 0) 00152 { 00153 itsInteractType = NONE; 00154 return; 00155 } 00156 00157 if (value.compare("SubtractMean") == 0) 00158 { 00159 itsInteractType = SUB_MEAN; 00160 return; 00161 } 00162 00163 itsInteractType = CUSTOM; 00164 00165 // format here is "c.c,...,c.c" 00166 int curpos = 0, len = value.length(); 00167 while (curpos < len) 00168 { 00169 // get end of next number 00170 int nextpos = value.find_first_not_of("-.0123456789eE",curpos); 00171 if (nextpos == -1) nextpos = len; 00172 00173 // no number characters found -> bummer 00174 if (nextpos == curpos) 00175 LFATAL("Error parsing the OriInteract string '%s' - found '%c' " 00176 "instead of a number.",value.c_str(),value[curpos]); 00177 00178 // now let's see - can we get a number here? 00179 float coeff; 00180 int nscan = sscanf(value.substr(curpos,nextpos-curpos).c_str(),"%g",&coeff); 00181 00182 // couldn't read a number -> bummer 00183 if (nscan != 1) 00184 LFATAL("Error parsing OriInteract string '%s' - found '%s' instead of " 00185 "a number.", value.c_str(), 00186 value.substr(curpos,nextpos-curpos).c_str()); 00187 00188 // yeah! found a number -> store it 00189 itsInteractCoeffs.push_back(coeff); 00190 00191 LDEBUG("coeff = %g; value[nextpos] = '%c'",coeff,value[nextpos]); 00192 00193 // not a comma -> bummer 00194 if ((nextpos < len) && (value[nextpos] != ',')) 00195 LFATAL("Error parsing the OriInteract string '%s' - found '%c' " 00196 "instead of ','.",value.c_str(),value[nextpos]); 00197 00198 // the character right after the comma should be a number again 00199 curpos = nextpos + 1; 00200 } 00201 00202 // end of string, done 00203 return; 00204 } 00205 00206 // ###################################################################### 00207 void OrientationChannel::paramChanged(ModelParamBase* const param, 00208 const bool valueChanged, 00209 ParamClient::ChangeStatus* status) 00210 { 00211 GVX_TRACE(__PRETTY_FUNCTION__); 00212 ComplexChannel::paramChanged(param, valueChanged, status); 00213 00214 // if the param is our number of orientations and it has become 00215 // different from our number of channels, let's reconfigure: 00216 if (param == &itsNumOrients && 00217 numChans() != itsNumOrients.getVal()) 00218 buildSubChans(); 00219 00220 // if the param is our OriInteraction, then parse the string 00221 else if (param == &itsInteractString) 00222 parseInteractString(itsInteractString.getVal()); 00223 } 00224 00225 // ###################################################################### 00226 OrientationChannel::~OrientationChannel() 00227 { 00228 GVX_TRACE(__PRETTY_FUNCTION__); 00229 } 00230 00231 // ###################################################################### 00232 GaborChannel& OrientationChannel::gabor(const uint idx) const 00233 { 00234 GVX_TRACE(__PRETTY_FUNCTION__); 00235 // Since we are dynamic_cast'ing a reference, this operation will either 00236 // succeed or throw an exception. 00237 return *(dynCast<GaborChannel>(subChan(idx))); 00238 } 00239 00240 // ###################################################################### 00241 void OrientationChannel::doInput(const InputFrame& inframe) 00242 { 00243 GVX_TRACE(__PRETTY_FUNCTION__); 00244 ASSERT(inframe.grayFloat().initialized()); 00245 00246 if (numChans() == 0) 00247 return; 00248 00249 rutz::mutex_lock_class lock; 00250 if (inframe.pyrCache().get() != 0 00251 && inframe.pyrCache()->laplacian9.beginSet(inframe.grayFloat(), &lock)) 00252 { 00253 inframe.pyrCache()->laplacian9.endSet 00254 (inframe.grayFloat(), 00255 buildPyrLaplacian 00256 (inframe.grayFloat(), gabor(0).getMinPyrLevel(), 00257 gabor(0).getMaxPyrLevel(), 9), 00258 &lock); 00259 } 00260 00261 for (uint i = 0; i < numChans(); ++i) 00262 { 00263 gabor(i).input(inframe); 00264 LINFO("Orientation pyramid (%d/%d) ok.", i+1, numChans()); 00265 } 00266 itsDidInteractions = false; 00267 } 00268 00269 // ###################################################################### 00270 void OrientationChannel::doInteractions() 00271 { 00272 GVX_TRACE(__PRETTY_FUNCTION__); 00273 LINFO("OriInteractionType is %s.",itsInteractString.getVal().c_str()); 00274 switch(itsInteractType) 00275 { 00276 case NONE: 00277 { 00278 // compute oriented gabor pyramids in several basis directions: 00279 break; 00280 } 00281 00282 case SUB_MEAN: 00283 { 00284 // compute the average and subtract it from each GaborPyramid 00285 // this is actually a special case of the CUSTOM one, but 00286 // this method here is more efficient 00287 ImageSet<float> mean; 00288 00289 // compute the mean 00290 for (uint i = 0; i < numChans(); ++i) 00291 { 00292 gabor(i).getOutput(); // make sure that gabor(i) has a pyramid 00293 if (mean.isEmpty()) 00294 mean = gabor(i).itsPq.back().pyr; 00295 else 00296 mean += gabor(i).itsPq.back().pyr; 00297 } 00298 mean /= (float)numChans(); 00299 00300 // clampdiff each pyramid with the mean and feed them back 00301 // into the channel: 00302 for (uint i = 0; i < numChans(); ++i) 00303 { 00304 gabor(i).getOutput(); // make sure that gabor(i) has a pyramid 00305 gabor(i).itsPq.back().pyr = 00306 clampedDiff(gabor(i).itsPq.back().pyr,mean); 00307 LINFO("Orientation pyramid interactions (%d/%d) ok.", 00308 i+1, numChans()); 00309 } 00310 00311 // done 00312 break; 00313 } 00314 00315 case CUSTOM: 00316 { 00317 // In this case we have arbitrary linear interactions between the 00318 // orientations - takes a bit of computation 00319 00320 // make sure we have the correct number of coefficients 00321 ASSERT(itsInteractCoeffs.size() == numChans()); 00322 std::vector< ImageSet<float> > resPyrs(numChans()); 00323 00324 // compute the pyramid interactions 00325 for (uint i = 0; i < numChans(); ++i) 00326 { 00327 ImageSet<float> curPyr = gabor(i).itsPq.back().pyr; 00328 uint coeff_ptr = i; 00329 00330 // add this new pyramid with the correct coefficients 00331 for (uint j = 0; j < numChans(); ++j) 00332 { 00333 if (resPyrs[j].isEmpty()) 00334 resPyrs[j] = (curPyr * itsInteractCoeffs[coeff_ptr]); 00335 else 00336 resPyrs[j] += (curPyr * itsInteractCoeffs[coeff_ptr]); 00337 00338 // count down the coeff_ptr and wrap it around 00339 // We count down here, because at parsing the input string 00340 // we have used push_back and hence reversed the order. 00341 coeff_ptr = (coeff_ptr - 1) % numChans(); 00342 } 00343 } 00344 00345 // now clamp these pyramids and feed them back to the channel 00346 for (uint i = 0; i < numChans(); ++i) 00347 { 00348 doLowThresh(resPyrs[i],0.0f,0.0f); 00349 gabor(i).itsPq.back().pyr = resPyrs[i]; 00350 LINFO("Orientation pyramid interactions (%d/%d) ok.", 00351 i+1, numChans()); 00352 } 00353 00354 // done 00355 break; 00356 } 00357 default: LFATAL("Unknown orientation interaction type: %d", 00358 itsInteractType); 00359 } // end switch 00360 } 00361 00362 // ###################################################################### 00363 Image<float> OrientationChannel::getOutput() 00364 { 00365 GVX_TRACE(__PRETTY_FUNCTION__); 00366 if (!itsDidInteractions) 00367 { 00368 doInteractions(); 00369 itsDidInteractions = true; 00370 } 00371 return ComplexChannel::getOutput(); 00372 } 00373 00374 // ###################################################################### 00375 OrientationChannel::InteractType OrientationChannel::getInteractType() 00376 { 00377 GVX_TRACE(__PRETTY_FUNCTION__); 00378 return itsInteractType; 00379 } 00380 00381 // ###################################################################### 00382 void OrientationChannel::setInteractType(OrientationChannel::InteractType type) 00383 { 00384 GVX_TRACE(__PRETTY_FUNCTION__); 00385 itsInteractType = type; 00386 } 00387 00388 // ###################################################################### 00389 std::vector<float> OrientationChannel::getInteractCoeffs() 00390 { 00391 GVX_TRACE(__PRETTY_FUNCTION__); 00392 return itsInteractCoeffs; 00393 } 00394 00395 // ###################################################################### 00396 void OrientationChannel::setInteractCoeffs(std::vector<float>& coeffs) 00397 { 00398 GVX_TRACE(__PRETTY_FUNCTION__); 00399 itsInteractCoeffs = coeffs; 00400 } 00401 00402 00403 00404 // ###################################################################### 00405 /* So things look consistent in everyone's emacs... */ 00406 /* Local Variables: */ 00407 /* indent-tabs-mode: nil */ 00408 /* End: */ 00409 00410 #endif // ORIENTATIONCHANNEL_C_DEFINED