00001 /*!@file Surprise/SurpriseMap.C a surprise map */ 00002 00003 // //////////////////////////////////////////////////////////////////// // 00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2003 // 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: Laurent Itti <itti@usc.edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Surprise/SurpriseMap.C $ 00035 // $Id: SurpriseMap.C 11562 2009-08-08 00:35:40Z dberg $ 00036 // 00037 00038 #include "Surprise/SurpriseMap.H" 00039 00040 #include "Image/Kernels.H" // for gaussianBlob() 00041 #include "Image/MathOps.H" 00042 #include "Util/Assert.H" 00043 00044 // ###################################################################### 00045 template <class T> 00046 SurpriseMap<T>::SurpriseMap() : 00047 itsModels(), itsQlen(0), itsInitialModel(), 00048 itsNeighSigma(0.0f), itsLocSigma(0.0f), itsNweights(), itsNWmin(0.0f), 00049 itsNeighUpdFac(0.7), itsProbe(-1, -1), itsSLfac(1.0), itsSSfac(0.1), 00050 itsJointKLBiasType(SU_KL_NONE), 00051 itsTakeSTMax(false) 00052 { } 00053 00054 // ###################################################################### 00055 template <class T> 00056 void SurpriseMap<T>::init(const uint qlen, const double updatefac, 00057 const double neighupdatefac, 00058 const double sampleval, const double samplevar, 00059 const float neighsigma, const float locsigma, 00060 const Point2D<int>& probe, const double slfac, 00061 const double ssfac, const SU_KL_BIAS klbias, 00062 const bool takeSTMax) 00063 { 00064 itsModels.clear(); 00065 itsNweights.freeMem(); 00066 itsInitialModel.init(updatefac, sampleval, samplevar); 00067 00068 itsQlen = qlen; 00069 itsNeighSigma = neighsigma; 00070 itsLocSigma = locsigma; 00071 itsNeighUpdFac = neighupdatefac; 00072 itsProbe = probe; 00073 itsSLfac = slfac; 00074 itsSSfac = ssfac; 00075 itsJointKLBiasType = klbias; 00076 itsTakeSTMax = takeSTMax; 00077 } 00078 00079 // ###################################################################### 00080 template <class T> 00081 SurpriseMap<T>::~SurpriseMap() 00082 { } 00083 00084 // ###################################################################### 00085 template <class T> 00086 void SurpriseMap<T>::reset() 00087 { 00088 for (uint i = 0; i < itsModels.size(); i ++) 00089 itsModels[i].reset(); 00090 } 00091 00092 // ###################################################################### 00093 template <class T> 00094 void SurpriseMap<T>::initModels(const SurpriseImage<T>& sample, 00095 const bool setBias) 00096 { 00097 // resize and reset our queue of models: 00098 SurpriseImage<T> models(sample.getDims()); // NOTE: uninitialized models 00099 00100 if(setBias) 00101 itsInitialModel.setBias(itsSLfac,itsSSfac,itsJointKLBiasType); 00102 00103 models.clear(itsInitialModel); 00104 models.reset(); 00105 00106 itsModels.clear(); 00107 00108 for (uint i = 0; i < itsQlen; i ++) itsModels.push_back(models); 00109 00110 // compute our Difference-of-Gaussians mask of weights: 00111 const int w = sample.getWidth(); 00112 const int h = sample.getHeight(); 00113 const float sigma = itsNeighSigma * float(std::max(w, h)); 00114 const Dims d(w * 2 + 1, h * 2 + 1); 00115 const Point2D<int> p(w, h); 00116 00117 itsNweights = gaussianBlob<float>(d, p, sigma, sigma); 00118 itsNweights -= gaussianBlob<float>(d, p, itsLocSigma, itsLocSigma) * 00119 (itsLocSigma*itsLocSigma / (sigma * sigma) * 1.5f); 00120 00121 inplaceRectify(itsNweights); // eliminate negative values 00122 00123 float mi, ma; 00124 getMinMax(itsNweights, mi, ma); 00125 itsNWmin = 0.01f * ma; 00126 00127 // zero low weights 00128 for(Image<float>::iterator w = itsNweights.beginw(); 00129 w != itsNweights.endw(); w++) 00130 if(*w <= itsNWmin) *w = 0; 00131 00132 } 00133 00134 // ###################################################################### 00135 template <> 00136 Image<double> SurpriseMap<SurpriseModelPM>:: 00137 surprise(const SurpriseImage<SurpriseModelPM>& sample) 00138 { 00139 // the generic version for other models is implemented in the next function 00140 00141 // is it the first time we are called? if so, we need to setup our 00142 // size and reset our neighborhood cache: 00143 if (itsModels.empty()) 00144 initModels(sample); 00145 else if (itsModels[0].isSameSize(sample) == false) 00146 LFATAL("Inconsistent input size!"); 00147 00148 // each model feeds into the next one. The first (fastest) model 00149 // receives the sample from the image as input, and is updated. The 00150 // updated model then serves as input to the next slower model, and 00151 // so on. Total surprise is the product from all models: 00152 SurpriseImage<SurpriseModelPM> input(sample); 00153 Image<double> s; 00154 const bool doprobe = input.coordsOk(itsProbe); 00155 double locmean = 0.0, locvar = 0.0, neimean = 0.0, neivar = 0.0; // for probe 00156 00157 for (uint i = 0; i < itsModels.size(); i ++) 00158 { 00159 itsModels[i].resetUpdFac(0.85F); 00160 // For covariant models we may need to analyze the covarance factors 00161 // and update the hyper parameters from the updates before we go 00162 // ahead and compute surprise as usual. 00163 itsModels[i].preComputeHyperParams(input); 00164 // In this instance, the model is covaried with itself. 00165 // Spatial and temporal concerns are computed at one time. 00166 itsModels[i].neighborhoods(itsModels[i], itsNweights, true); 00167 00168 // show values at a probe location? 00169 if (doprobe) 00170 { 00171 locmean = itsModels[i].getMean().getVal(itsProbe); 00172 locvar = itsModels[i].getVar().getVal(itsProbe); 00173 } 00174 00175 // update local models and compute the local temporal surprise: 00176 const Image<double> sl = itsModels[i].surprise(input); 00177 00178 00179 // the total surprise is a weighted sum of local temporal 00180 // surprise and spatial surprise: 00181 Image<double> stot(sl.getDims(), ZEROS); 00182 if (itsSLfac) 00183 { 00184 if (itsSLfac != 1.0) stot = sl * itsSLfac; else stot = sl; 00185 } 00186 00187 // total surprise combines multiplicatively across models with 00188 // different time scales: 00189 if (i == 0) s = stot; else s *= stot; 00190 00191 // save debug output from a probe location: 00192 if (doprobe) 00193 { 00194 LERROR("MODELS: %d %g %g %g %g %g %g", i, 00195 input.getMean().getVal(itsProbe), 00196 input.getVar().getVal(itsProbe), 00197 locmean, locvar, neimean, neivar); 00198 LERROR("SURPRISE: %d %g %g", i, sl.getVal(itsProbe), 00199 sl.getVal(itsProbe)); 00200 } 00201 00202 // the updated models are the input to the next iteration: 00203 input = itsModels[i]; 00204 } 00205 00206 // the results here should always be positive but sometimes turn 00207 // negative due to rounding errors in the surprise computation. 00208 // Let's clamp just to be sure, otherwise we'll end up with some 00209 // NaNs at later stages: 00210 inplaceRectify(s); 00211 00212 // calm down total surprise and preserve units of wows: 00213 // We multiply times 10 to scale with other surprise better 00214 s = toPower(s, 1.0 / (3.0 * double(itsModels.size()))) * 5; 00215 00216 // return total surprise: 00217 return s; 00218 00219 } 00220 00221 // ###################################################################### 00222 template <> 00223 Image<double> SurpriseMap<SurpriseModelGG>:: 00224 surprise(const SurpriseImage<SurpriseModelGG>& sample) 00225 { 00226 // is it the first time we are called? if so, we need to setup our 00227 // size and reset our neighborhood cache: 00228 if (itsModels.empty()) 00229 initModels(sample,true); 00230 else if (itsModels[0].isSameSize(sample) == false) 00231 LFATAL("Inconsistent input size!"); 00232 00233 // each model feeds into the next one. The first (fastest) model 00234 // receives the sample from the image as input, and is updated. The 00235 // updated model then serves as input to the next slower model, and 00236 // so on. Total surprise is the product from all models: 00237 SurpriseImage<SurpriseModelGG> input(sample); 00238 Image<double> s; 00239 const bool doprobe = input.coordsOk(itsProbe); 00240 double locmean = 0.0, locvar = 0.0, neimean = 0.0, neivar = 0.0; // for probe 00241 00242 for (uint i = 0; i < itsModels.size(); i ++) 00243 { 00244 // compute neighborhood models from our current (old) local models: 00245 itsModels[i].neighborhoods(input, itsNweights); 00246 //itsModels[i].neighborhoods(input, itsNweights, itsNWmin, true); 00247 //itsModels[i].neighborhoods(input,20); 00248 00249 // show values at a probe location? 00250 if (doprobe) 00251 { 00252 locmean = itsModels[i].getMean().getVal(itsProbe); 00253 locvar = itsModels[i].getVar().getVal(itsProbe); 00254 } 00255 00256 // update local models and compute the local temporal surprise: 00257 const Image<double> sl = itsModels[i].surprise(input); 00258 00259 // the total surprise is a weighted sum of local temporal 00260 // surprise and spatial surprise: 00261 Image<double> stot(sl.getDims(), ZEROS); 00262 if (itsSLfac) 00263 { 00264 if (itsSLfac != 1.0) stot = sl * itsSLfac; else stot = sl; 00265 } 00266 00267 // total surprise combines multiplicatively across models with 00268 // different time scales: 00269 if (i == 0) s = stot; else s *= stot; 00270 00271 // save debug output from a probe location: 00272 if (doprobe) 00273 { 00274 LERROR("MODELS: %d %g %g %g %g %g %g", i, 00275 input.getMean().getVal(itsProbe), 00276 input.getVar().getVal(itsProbe), 00277 locmean, locvar, neimean, neivar); 00278 LERROR("SURPRISE: %d %g", i, sl.getVal(itsProbe)); 00279 } 00280 00281 // the updated models are the input to the next iteration: 00282 input = itsModels[i]; 00283 } 00284 00285 // the results here should always be positive but sometimes turn 00286 // negative due to rounding errors in the surprise computation. 00287 // Let's clamp just to be sure, otherwise we'll end up with some 00288 // NaNs at later stages: 00289 inplaceRectify(s); 00290 00291 // calm down total surprise and preserve units of wows: 00292 s = toPower(s, 1.0 / (3.0 * double(itsModels.size()))); 00293 s = logSig(s,1,1); 00294 //double min, max; 00295 //getMinMax(s,min,max); 00296 //LINFO("S max %f min %f",min,max); 00297 00298 // return total surprise: 00299 return s; 00300 } 00301 00302 // ###################################################################### 00303 template <class T> 00304 Image<double> SurpriseMap<T>::surprise(const SurpriseImage<T>& sample) 00305 { 00306 // is it the first time we are called? if so, we need to setup our 00307 // size and reset our neighborhood cache: 00308 if (itsModels.empty()) 00309 initModels(sample); 00310 else if (itsModels[0].isSameSize(sample) == false) 00311 LFATAL("Inconsistent input size!"); 00312 00313 // each model feeds into the next one. The first (fastest) model 00314 // receives the sample from the image as input, and is updated. The 00315 // updated model then serves as input to the next slower model, and 00316 // so on. Total surprise is the product from all models: 00317 SurpriseImage<T> input(sample); 00318 Image<double> s; 00319 const bool doprobe = input.coordsOk(itsProbe); 00320 double locmean = 0.0, locvar = 0.0, neimean = 0.0, neivar = 0.0; // for probe 00321 00322 // Combine space and time using Max or should we combine them as a product? 00323 if(itsTakeSTMax) 00324 { 00325 Image<double> t; 00326 for (uint i = 0; i < itsModels.size(); i ++) 00327 { 00328 // compute neighborhood models from our current (old) local models: 00329 SurpriseImage<T> neigh; 00330 neigh.neighborhoods(input, itsNweights); 00331 if (itsNeighUpdFac != 0.0) // use different update fac for neighs? 00332 neigh.resetUpdFac(itsNeighUpdFac); // higher fac -> stronger popout 00333 00334 // show values at a probe location? 00335 if (doprobe) 00336 { 00337 locmean = itsModels[i].getMean().getVal(itsProbe); 00338 locvar = itsModels[i].getVar().getVal(itsProbe); 00339 neimean = neigh.getMean().getVal(itsProbe); 00340 neivar = neigh.getVar().getVal(itsProbe); 00341 } 00342 00343 // update local models and compute the local temporal surprise: 00344 const Image<double> sl = itsModels[i].surprise(input) * itsSLfac; 00345 // compute spatial surprise: 00346 const Image<double> ss = neigh.surprise(input) * itsSSfac; 00347 00348 // Compute product of space and time seperatly 00349 if (i == 0) { s = ss; t = sl; } 00350 else { s *= ss; t *= sl; } 00351 00352 // save debug output from a probe location: 00353 if (doprobe) 00354 { 00355 LERROR("MODELS: %d %g %g %g %g %g %g", i, 00356 input.getMean().getVal(itsProbe), 00357 input.getVar().getVal(itsProbe), 00358 locmean, locvar, neimean, neivar); 00359 LERROR("SURPRISE: %d %g %g", i, sl.getVal(itsProbe), 00360 ss.getVal(itsProbe)); 00361 } 00362 00363 // the updated models are the input to the next iteration: 00364 input = itsModels[i]; 00365 } 00366 // take the max of either space or temporal surprise 00367 s = takeMax(s,t); 00368 } 00369 else 00370 { 00371 for (uint i = 0; i < itsModels.size(); i ++) 00372 { 00373 // compute neighborhood models from our current (old) local models: 00374 SurpriseImage<T> neigh; 00375 neigh.neighborhoods(input, itsNweights); 00376 if (itsNeighUpdFac != 0.0) // use different update fac for neighs? 00377 neigh.resetUpdFac(itsNeighUpdFac); // higher fac -> stronger popout 00378 00379 // show values at a probe location? 00380 if (doprobe) 00381 { 00382 locmean = itsModels[i].getMean().getVal(itsProbe); 00383 locvar = itsModels[i].getVar().getVal(itsProbe); 00384 neimean = neigh.getMean().getVal(itsProbe); 00385 neivar = neigh.getVar().getVal(itsProbe); 00386 } 00387 00388 // update local models and compute the local temporal surprise: 00389 const Image<double> sl = itsModels[i].surprise(input); 00390 // compute spatial surprise: 00391 const Image<double> ss = neigh.surprise(input); 00392 00393 // the total surprise is a weighted sum of local temporal 00394 // surprise and spatial surprise: 00395 Image<double> stot(sl.getDims(), ZEROS); 00396 if (itsSLfac) 00397 { 00398 if (itsSLfac != 1.0) stot = sl * itsSLfac; else stot = sl; 00399 } 00400 if (itsSSfac) 00401 { 00402 if (itsSSfac != 1.0) stot += ss * itsSSfac; else stot += ss; 00403 } 00404 00405 if (i == 0) s = stot; 00406 else s *= stot; 00407 00408 // save debug output from a probe location: 00409 if (doprobe) 00410 { 00411 LERROR("MODELS: %d %g %g %g %g %g %g", i, 00412 input.getMean().getVal(itsProbe), 00413 input.getVar().getVal(itsProbe), 00414 locmean, locvar, neimean, neivar); 00415 LERROR("SURPRISE: %d %g %g", i, sl.getVal(itsProbe), 00416 ss.getVal(itsProbe)); 00417 } 00418 00419 // the updated models are the input to the next iteration: 00420 input = itsModels[i]; 00421 } 00422 } 00423 00424 00425 // the results here should always be positive but sometimes turn 00426 // negative due to rounding errors in the surprise computation. 00427 // Let's clamp just to be sure, otherwise we'll end up with some 00428 // NaNs at later stages: 00429 inplaceRectify(s); 00430 00431 // calm down total surprise and preserve units of wows: 00432 s = toPower(s, 1.0 / (3.0 * double(itsModels.size()))); 00433 00434 // return total surprise: 00435 return s; 00436 } 00437 00438 // ###################################################################### 00439 template <class T> 00440 const SurpriseImage<T>& SurpriseMap<T>::getSurpriseImage(const 00441 uint index) const 00442 { 00443 ASSERT(index < itsModels.size()); 00444 return itsModels[index]; 00445 } 00446 00447 00448 // ###################################################################### 00449 // explicit instantiations: 00450 template class SurpriseMap<SurpriseModelSG>; 00451 template class SurpriseMap<SurpriseModelSP>; 00452 template class SurpriseMap<SurpriseModelSP1>; 00453 template class SurpriseMap<SurpriseModelSPC>; 00454 template class SurpriseMap<SurpriseModelSPF>; 00455 template class SurpriseMap<SurpriseModelCS>; 00456 template class SurpriseMap<SurpriseModelGG>; 00457 template class SurpriseMap<SurpriseModelPM>; 00458 template class SurpriseMap<SurpriseModelOD>; 00459 00460 // ###################################################################### 00461 /* So things look consistent in everyone's emacs... */ 00462 /* Local Variables: */ 00463 /* indent-tabs-mode: nil */ 00464 /* End: */