00001 /*!@file Channels/SingleChannel.C Channel for a single stream of processing. */ 00002 00003 // //////////////////////////////////////////////////////////////////// // 00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2001 by the // 00005 // 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: Rob Peters <rjpeters@klab.caltech.edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Channels/SingleChannel.C $ 00035 // $Id: SingleChannel.C 14632 2011-03-23 20:08:44Z dberg $ 00036 // 00037 00038 #include "Channels/SingleChannel.H" 00039 00040 #include "Channels/ChannelOpts.H" 00041 #include "Channels/ChannelFacets.H" 00042 #include "Channels/ChannelVisitor.H" 00043 #include "Channels/SubmapAlgorithmStd.H" 00044 #include "Component/GlobalOpts.H" 00045 #include "Component/OptionManager.H" 00046 #include "Component/ParamMap.H" 00047 #include "Image/FFTWWrapper.H" 00048 #include "Image/FilterOps.H" // for single-opponent's centerSurround() 00049 #include "Image/Image.H" 00050 #include "Image/ImageSetOps.H" 00051 #include "Image/MathOps.H" // for binaryReverse() 00052 #include "Image/PyrBuilder.H" 00053 #include "Image/PyramidOps.H" 00054 #include "Image/ShapeOps.H" 00055 #include "Image/Transforms.H" // for learningCoeff() 00056 #include "Image/fancynorm.H" 00057 #include "Transport/FrameInfo.H" 00058 #include "Transport/FrameOstream.H" 00059 #include "Util/Assert.H" 00060 #include "Util/MathFunctions.H" 00061 #include "Util/log.H" 00062 #include "Util/sformat.H" 00063 #include "rutz/trace.h" 00064 00065 #include "Image/CutPaste.H" 00066 #include <cmath> 00067 #include <iostream> 00068 #include <fstream> 00069 00070 // ###################################################################### 00071 SingleChannel::SingleChannel(OptionManager& mgr, const std::string& descrName, 00072 const std::string& tag, 00073 const VisualFeature vs, 00074 rutz::shared_ptr<PyrBuilder<float> > pbuild) : 00075 ChannelBase(mgr, descrName, tag, vs), 00076 itsTakeAbs("SingleChannelTakeAbs", this, false), 00077 itsNormalizeOutput("SingleChannelNormalizeOutput", this, false), 00078 itsScaleNoiseToMax("SingleChannelScaleNoiseToMax", this, false), 00079 itsLowThresh("SingleChannelLowThresh", this, 0.0F), 00080 itsRectifyPyramid("SingleChannelRectifyPyramid", this, false), 00081 itsComputeFullPyramid("SingleChannelComputeFullPyramid", this, false), 00082 itsUseRandom(&OPT_UseRandom, this), // see Component/ModelManager.{H,C} 00083 itsUseSplitCS(&OPT_SingleChannelUseSplitCS, this), // ModelOptionDefs.C 00084 itsLevelSpec(&OPT_LevelSpec, this), // see Channels/ChannelOpts.{H,C} 00085 itsNormType(&OPT_MaxNormType, this), // see Channels/ChannelOpts.{H,C} 00086 itsQlen(&OPT_SingleChannelQueueLen, this), // see Channels/ChannelOpts.{H,C} 00087 itsUseOlderVersion(&OPT_UseOlderVersion, this), // Channels/ChannelOpts.{H,C} 00088 itsTimeDecay(&OPT_SingleChannelTimeDecay, this), //Channels/ChannelOpts.{H,C} 00089 itsSaveRawMaps(&OPT_SingleChannelSaveRawMaps, this), 00090 itsComputeFullPyramidForGist(&OPT_SingleChannelComputeFullPyramidForGist, this), 00091 itsSaveFeatureMaps(&OPT_SingleChannelSaveFeatureMaps, this), 00092 itsSaveOutputMap(&OPT_SingleChannelSaveOutputMap, this), 00093 itsSubmapAlgoType(&OPT_SubmapAlgoType, this), 00094 itsGetSingleChannelStats(&OPT_GetSingleChannelStats, this), 00095 itsSaveStatsPerChannel(&OPT_SaveStatsPerChannel, this), 00096 itsSaveStatsPerChannelFreq(&OPT_SaveStatsPerChannelFreq, this), 00097 itsGetSingleChannelStatsFile(&OPT_GetSingleChannelStatsFile, this), 00098 itsGetSingleChannelStatsTag(&OPT_GetSingleChannelStatsTag, this), 00099 itsOutputRangeMin(&OPT_ChannelOutputRangeMin, this), 00100 itsOutputRangeMax(&OPT_ChannelOutputRangeMax, this), 00101 itsPq(), 00102 itsOutputCache(), 00103 itsSubmapCache(NULL), 00104 itsTempl(), 00105 itsPyrBuilder(pbuild), 00106 itsClipPyr(), 00107 itsInputHandler(), 00108 itsSubmapAlgo(new SubmapAlgorithmStd(mgr)) 00109 { 00110 GVX_TRACE(__PRETTY_FUNCTION__); 00111 00112 ComponentFactory<SubmapAlgorithm>& f = SubmapAlgorithm::getFactory(); 00113 if (!f.is_valid_key("Std")) 00114 f.registerType<SubmapAlgorithmStd>("Std", mgr); 00115 00116 itsSubmapAlgo = SubmapAlgorithm::make(itsSubmapAlgoType.getVal()); 00117 00118 this->addSubComponent(itsSubmapAlgo); 00119 } 00120 00121 // ###################################################################### 00122 SingleChannel::~SingleChannel() 00123 { 00124 GVX_TRACE(__PRETTY_FUNCTION__); 00125 } 00126 00127 // ###################################################################### 00128 void SingleChannel::start1() 00129 { 00130 GVX_TRACE(__PRETTY_FUNCTION__); 00131 const int maxind = maxIndex(); 00132 itsSubmapCache = new Image<float>[maxind]; 00133 itsTempl = ImageSet<float>(maxind); 00134 itsFrameIdx = 0; 00135 00136 // in the new version, leave our output range open rather than 00137 // forcing it to a given range of values: 00138 if (itsUseOlderVersion.getVal() == false) 00139 { 00140 itsOutputRangeMin.setVal(0.0f); 00141 itsOutputRangeMax.setVal(0.0f); 00142 } 00143 00144 // in the older version, we used to set the map range as we would 00145 // also apply spatial competition for salience to the output map, 00146 // only if using the MAXNORM type of competition, and otherwise we 00147 // would not touch the range: 00148 if (itsUseOlderVersion.getVal() && itsNormType.getVal() != VCXNORM_MAXNORM) 00149 { 00150 itsOutputRangeMin.setVal(0.0f); 00151 itsOutputRangeMax.setVal(0.0f); 00152 } 00153 } 00154 00155 // ###################################################################### 00156 void SingleChannel::stop2() 00157 { 00158 GVX_TRACE(__PRETTY_FUNCTION__); 00159 delete [] itsSubmapCache; itsSubmapCache = 0; 00160 } 00161 00162 // ###################################################################### 00163 void SingleChannel::reset1() 00164 { 00165 GVX_TRACE(__PRETTY_FUNCTION__); 00166 // reset some stuff for SingleChannel 00167 itsPyrBuilder->reset(); 00168 itsPq.clear(); 00169 itsClipPyr.clear(); 00170 00171 // propagate to our base class: 00172 ChannelBase::reset1(); 00173 } 00174 00175 // ###################################################################### 00176 void SingleChannel::accept(ChannelVisitor& v) 00177 { 00178 GVX_TRACE(__PRETTY_FUNCTION__); 00179 v.visitSingleChannel(*this); 00180 } 00181 00182 // ###################################################################### 00183 void SingleChannel::paramChanged(ModelParamBase* const param, 00184 const bool valueChanged, 00185 ParamClient::ChangeStatus* status) 00186 { 00187 GVX_TRACE(__PRETTY_FUNCTION__); 00188 ChannelBase::paramChanged(param, valueChanged, status); 00189 00190 if (param == &itsSubmapAlgoType && valueChanged) 00191 { 00192 nub::ref<SubmapAlgorithm> algo = 00193 SubmapAlgorithm::make(itsSubmapAlgoType.getVal()); 00194 00195 this->setSubmapAlgorithm(algo); 00196 00197 algo->exportOptions(MC_RECURSE); 00198 } 00199 } 00200 00201 // ###################################################################### 00202 void SingleChannel::readFrom(const ParamMap& pmap) 00203 { 00204 GVX_TRACE(__PRETTY_FUNCTION__); 00205 00206 ChannelBase::readFrom(pmap); 00207 ChannelFacetMap::readFacetsFrom(pmap); 00208 } 00209 00210 // ###################################################################### 00211 void SingleChannel::writeTo(ParamMap& pmap) const 00212 { 00213 GVX_TRACE(__PRETTY_FUNCTION__); 00214 00215 ChannelBase::writeTo(pmap); 00216 ChannelFacetMap::writeFacetsTo(pmap); 00217 } 00218 00219 // ###################################################################### 00220 void SingleChannel::setTempl(const uint cntr, const uint surr, 00221 Image<float> &templ) 00222 { 00223 GVX_TRACE(__PRETTY_FUNCTION__); 00224 killCaches(); 00225 itsTempl[csToIndex(cntr, surr)] = templ; 00226 } 00227 00228 // ###################################################################### 00229 void SingleChannel::setBiasMask(Image<float> &biasMask) 00230 { 00231 GVX_TRACE(__PRETTY_FUNCTION__); 00232 killCaches(); 00233 itsBiasMask = biasMask; 00234 } 00235 00236 // ###################################################################### 00237 Image<float> SingleChannel::getBiasMask() const 00238 { 00239 GVX_TRACE(__PRETTY_FUNCTION__); 00240 return itsBiasMask; 00241 } 00242 00243 // ###################################################################### 00244 Image<float> SingleChannel::getTempl(const uint cntr, const uint surr) const 00245 { 00246 GVX_TRACE(__PRETTY_FUNCTION__); 00247 return itsTempl[csToIndex(cntr, surr)]; 00248 00249 } 00250 00251 // ###################################################################### 00252 bool SingleChannel::outputAvailable() const 00253 { 00254 GVX_TRACE(__PRETTY_FUNCTION__); 00255 // if we have an input handler, let's have it wait for output to 00256 // become available: 00257 if (itsInputHandler.is_valid()) 00258 itsInputHandler->waitForOutput(const_cast<SingleChannel&>(*this)); 00259 00260 return (itsPq.empty() == false) || itsOutputCache.initialized(); 00261 } 00262 00263 // ###################################################################### 00264 bool SingleChannel::hasPyramid() const 00265 { 00266 GVX_TRACE(__PRETTY_FUNCTION__); 00267 return (itsPq.empty() == false); 00268 } 00269 00270 // ###################################################################### 00271 bool SingleChannel::hasOutputCache() const 00272 { 00273 GVX_TRACE(__PRETTY_FUNCTION__); 00274 return itsOutputCache.initialized(); 00275 } 00276 00277 // ###################################################################### 00278 Dims SingleChannel::getMapDims() const 00279 { 00280 GVX_TRACE(__PRETTY_FUNCTION__); 00281 const int lev = itsLevelSpec.getVal().mapLevel(); 00282 00283 return Dims(this->getInputDims().w() / (1 << lev), 00284 this->getInputDims().h() / (1 << lev)); 00285 } 00286 00287 // ###################################################################### 00288 const Image<float>& SingleChannel::getImage(const uint lev) const 00289 { 00290 GVX_TRACE(__PRETTY_FUNCTION__); 00291 if (itsPq.empty()) CLFATAL("I have no input pyramid yet!"); 00292 return itsPq.front().pyr.getImage(lev); 00293 } 00294 00295 // ###################################################################### 00296 Image<float> SingleChannel::centerSurround(const uint cntrlev, 00297 const uint surrlev) const 00298 { 00299 GVX_TRACE(__PRETTY_FUNCTION__); 00300 ASSERT(itsLevelSpec.getVal().csOK(cntrlev, surrlev)); 00301 if (itsPq.empty()) CLFATAL("I have no input pyramid yet!"); 00302 00303 // do basic center-surround for the front (latest) pyramid in queue: 00304 const ImageSet<float>& pyr = itsPq.front().pyr; 00305 double t = itsPq.front().t.secs(); 00306 00307 // compute center-surround: 00308 Image<float> cs = ::centerSurround(pyr, cntrlev, surrlev, 00309 itsTakeAbs.getVal(), &itsClipPyr); 00310 00311 // do additional processing with other pyramids in queue: 00312 for (uint i = 1; i < itsPq.size(); ++i) 00313 { 00314 const ImageSet<float>& pyr2 = itsPq[i].pyr; 00315 double t2 = itsPq[i].t.secs(); 00316 00317 // compute a decay factor based on how old the second pyramid is 00318 // compared to the latest one: 00319 float fac = exp( (t2 - t) * itsTimeDecay.getVal()); 00320 cs += ::centerSurroundDiff(pyr, pyr2, cntrlev, surrlev, 00321 itsTakeAbs.getVal(), &itsClipPyr) * fac; 00322 } 00323 00324 return cs; 00325 } 00326 00327 // ###################################################################### 00328 void SingleChannel::centerSurround(const uint cntrlev, const uint surrlev, 00329 Image<float>& pos, Image<float>& neg) const 00330 { 00331 GVX_TRACE(__PRETTY_FUNCTION__); 00332 ASSERT(itsLevelSpec.getVal().csOK(cntrlev, surrlev)); 00333 if (itsPq.empty()) CLFATAL("I have no input pyramid yet!"); 00334 00335 // do basic center-surround for the front (latest) pyramid in queue: 00336 const ImageSet<float>& pyr = itsPq.front().pyr; 00337 double t = itsPq.front().t.secs(); 00338 00339 // compute center-surround: 00340 ::centerSurround(pyr, cntrlev, surrlev, pos, neg, &itsClipPyr); 00341 00342 // do additional processing with other pyramids in queue: 00343 for (uint i = 1; i < itsPq.size(); ++i) 00344 { 00345 const ImageSet<float>& pyr2 = itsPq[i].pyr; 00346 double t2 = itsPq[i].t.secs(); 00347 00348 // compute a decay factor based on how old the second pyramid is 00349 // compared to the latest one: 00350 float fac = exp( (t2 - t) * itsTimeDecay.getVal()); 00351 Image<float> pos2, neg2; 00352 ::centerSurroundDiff(pyr, pyr2, cntrlev, surrlev, 00353 pos2, neg2, &itsClipPyr); 00354 pos += pos2 * fac; 00355 neg += neg2 * fac; 00356 } 00357 } 00358 00359 // ###################################################################### 00360 uint SingleChannel::numSubmaps() const 00361 { 00362 GVX_TRACE(__PRETTY_FUNCTION__); 00363 return maxIndex(); 00364 } 00365 00366 // ###################################################################### 00367 Image<float> SingleChannel::getSubmap(const uint idx) const 00368 { 00369 GVX_TRACE(__PRETTY_FUNCTION__); 00370 // cache the results for later use if we don't already have cached it: 00371 if (itsSubmapCache[idx].initialized() == false) 00372 itsSubmapCache[idx] = itsSubmapAlgo->compute(*this, idx); 00373 00374 return itsSubmapCache[idx]; 00375 } 00376 00377 // ###################################################################### 00378 Image<float> SingleChannel::getRawCSmap(const uint idx) const 00379 { 00380 GVX_TRACE(__PRETTY_FUNCTION__); 00381 ASSERT(itsLevelSpec.getVal().indexOK(idx)); 00382 if (itsPq.empty()) CLFATAL("I have no input pyramid yet!"); 00383 00384 // determine any gain factor (possibly from a 00385 // ChannelFacetGainSingle). But note that we don't apply that gain 00386 // factor here, rather we just care to see whether we can avoid 00387 // computing the map if its gain factor is zero. Applying the gain 00388 // factors is done in combineSubmaps(): 00389 float w = 1.0; 00390 if (hasFacet<ChannelFacetGainSingle>()) 00391 w = getFacet<ChannelFacetGainSingle>()->getVal(idx); 00392 00393 // if we have a zero weight, just return an empty image: 00394 if (w == 0.0f) return Image<float>(getMapDims(), ZEROS); 00395 00396 // otherwise, compute center-surround map: 00397 uint clev = 0, slev = 0; 00398 indexToCS(idx, clev, slev); 00399 00400 Image<float> submap; 00401 if (itsUseSplitCS.getVal()) 00402 { 00403 // if using split center-surround, compute both the positive 00404 // and negative submaps, normalize them, sum them and we 00405 // will below further normalize the sum: 00406 Image<float> subpos, subneg, subpos2, subneg2; 00407 00408 // positive and negative submaps are computed from a 00409 // single center-surround difference: 00410 this->centerSurround(clev, slev, subpos, subneg); 00411 00412 // also compute maps containing max(map)-map: 00413 float pmin, pmax, nmin, nmax; 00414 getMinMax(subpos, pmin, pmax); subpos2 = binaryReverse(subpos, pmax); 00415 getMinMax(subneg, nmin, nmax); subneg2 = binaryReverse(subneg, nmax); 00416 00417 // apply spatial competition for salience, independently to 00418 // both positive and negative parts of the submap, 00419 // preserving their original range rather than first 00420 // normalizing them to [MAXNORMMIN..MAXNORMMAX] as we 00421 // usually do, so that those maps who contain nothing do not 00422 // get artificially amplified: 00423 subpos = maxNormalize(subpos, 0.0f, 0.0f, itsNormType.getVal()); 00424 subneg = maxNormalize(subneg, 0.0f, 0.0f, itsNormType.getVal()); 00425 subpos2 = maxNormalize(subpos2, 0.0f, 0.0f, itsNormType.getVal()); 00426 subneg2 = maxNormalize(subneg2, 0.0f, 0.0f, itsNormType.getVal()); 00427 00428 // the raw submap is the sum of the normalized positive and 00429 // negative sides: 00430 submap = subpos + subneg + subpos2 + subneg2; 00431 } 00432 else 00433 // submap is computed from a center-surround difference: 00434 submap = this->centerSurround(clev, slev); 00435 00436 // print some debug info if in debug mode: 00437 if (MYLOGVERB >= LOG_DEBUG) 00438 { 00439 float mi, ma; getMinMax(submap, mi, ma); 00440 LDEBUG("%s(%d,%d): raw range [%f .. %f]", tagName().c_str(), 00441 clev, slev, mi, ma); 00442 } 00443 00444 return submap; 00445 } 00446 00447 // ###################################################################### 00448 Image<float> SingleChannel::postProcessMap(const Image<float>& smap, 00449 const uint idx) const 00450 { 00451 GVX_TRACE(__PRETTY_FUNCTION__); 00452 Image<float> submap(smap); 00453 uint clev = 0, slev = 0; 00454 indexToCS(idx, clev, slev); 00455 00456 00457 // resize submap to fixed scale if necessary: 00458 if (submap.getWidth() > getMapDims().w()) 00459 submap = downSize(submap, getMapDims()); 00460 else if (submap.getWidth() < getMapDims().w()) 00461 submap = rescale(submap, getMapDims()); 00462 00463 // add noise if wanted: 00464 if (itsUseRandom.getVal()) 00465 { 00466 // check if we want to scale noise to maximum of image 00467 if (itsScaleNoiseToMax.getVal()) 00468 { 00469 float fMin, fMax; getMinMax(submap, fMin, fMax); 00470 inplaceAddBGnoise(submap, fMax); 00471 } 00472 else 00473 inplaceAddBGnoise(submap, 255.0); 00474 } 00475 00476 // if using the older version: first normalize the submap to a 00477 // fixed dynamic range and then apply spatial competition for 00478 // salience to the submap; otherwise, just apply competition: 00479 if (itsUseOlderVersion.getVal()) 00480 { 00481 LDEBUG("%s(%d,%d): applying %s(%f .. %f)", tagName().c_str(), clev, slev, 00482 maxNormTypeName(itsNormType.getVal()), MAXNORMMIN, MAXNORMMAX); 00483 submap = maxNormalize(submap, MAXNORMMIN, MAXNORMMAX, 00484 itsNormType.getVal()); 00485 } 00486 else 00487 { 00488 LDEBUG("%s(%d,%d): applying %s(0.0 .. 0.0)", tagName().c_str(), 00489 clev, slev, maxNormTypeName(itsNormType.getVal())); 00490 submap = maxNormalize(submap, 0.0f, 0.0f, itsNormType.getVal()); 00491 } 00492 00493 // print some debug info if in debug mode: 00494 if (MYLOGVERB >= LOG_DEBUG) 00495 { 00496 float mi, ma; getMinMax(submap, mi, ma); 00497 LDEBUG("%s(%d,%d): final range [%f .. %f]", 00498 tagName().c_str(), clev, slev, mi, ma); 00499 } 00500 00501 return submap; 00502 } 00503 00504 // ###################################################################### 00505 std::string SingleChannel::getSubmapName(const uint idx) const 00506 { 00507 GVX_TRACE(__PRETTY_FUNCTION__); 00508 ASSERT( itsLevelSpec.getVal().indexOK(idx) ); 00509 00510 uint clev = 0, slev = 0; 00511 indexToCS(idx, clev, slev); 00512 00513 return sformat("%s lev: %d delta: %d", 00514 descriptiveName().c_str(), clev, slev-clev); 00515 } 00516 00517 // ###################################################################### 00518 std::string SingleChannel::getSubmapNameShort(const uint idx) const 00519 { 00520 GVX_TRACE(__PRETTY_FUNCTION__); 00521 ASSERT( itsLevelSpec.getVal().indexOK(idx) ); 00522 00523 uint clev = 0, slev = 0; 00524 indexToCS(idx, clev, slev); 00525 00526 return sformat("%s(%d,%d)", tagName().c_str(), clev, slev); 00527 } 00528 00529 // ###################################################################### 00530 void SingleChannel::getFeatures(const Point2D<int>& locn, 00531 std::vector<float>& mean) const 00532 { 00533 GVX_TRACE(__PRETTY_FUNCTION__); 00534 if (!this->outputAvailable()) 00535 { 00536 CLDEBUG("I have no input pyramid yet -- RETURNING ZEROS"); 00537 for (uint idx = 0; idx < numSubmaps(); idx ++) mean.push_back(0.0F); 00538 return; 00539 } 00540 00541 // The coordinates we receive are at the scale of the original 00542 // image, and we will need to rescale them to the size of the 00543 // various submaps we read from. The first image in our first 00544 // pyramid has the dims of the input: 00545 const ImageSet<float>& pyr = itsPq.front().pyr; 00546 const Dims indims = this->getInputDims(); 00547 00548 for (uint idx = 0; idx < numSubmaps(); idx ++) 00549 { 00550 // get center and surround scales for this submap index: 00551 uint clev = 0, slev = 0; 00552 indexToCS(idx, clev, slev); 00553 00554 // read center value with bilinear interpolation: 00555 ASSERT(pyr[clev].initialized()); 00556 const float cval = pyr[clev].getValInterpScaled(locn, indims); 00557 00558 // read surround value with bilinear interpolation: 00559 ASSERT(pyr[slev].initialized()); 00560 const float sval = pyr[slev].getValInterpScaled(locn, indims); 00561 00562 // compute center - surround and take absolute value if our 00563 // channel wants that: 00564 float val = cval - sval; if (itsTakeAbs.getVal()) val = fabs(val); 00565 00566 // store the submap value at the chosen location: 00567 mean.push_back(val); 00568 } 00569 } 00570 00571 // ###################################################################### 00572 void SingleChannel::getFeaturesBatch(std::vector<Point2D<int>*> *locn, 00573 std::vector<std::vector<float> > *mean, 00574 int *count) const 00575 { 00576 GVX_TRACE(__PRETTY_FUNCTION__); 00577 if (!this->outputAvailable()) 00578 { 00579 CLDEBUG("I have no input pyramid yet -- RETURNING ZEROS"); 00580 for (uint idx = 0; idx < numSubmaps(); idx ++) 00581 { 00582 std::vector<std::vector<float> >::iterator imean = mean->begin(); 00583 for(int i = 0; i < *count; i++, ++imean) 00584 imean->push_back(0.0); 00585 } 00586 return; 00587 } 00588 00589 // The coordinates we receive are at the scale of the original 00590 // image, and we will need to rescale them to the size of the 00591 // various submaps we read from. The first image in our first 00592 // pyramid has the dims of the input: 00593 const ImageSet<float>& pyr = itsPq.front().pyr; 00594 const Dims indims = this->getInputDims(); 00595 const uint sm = numSubmaps(); 00596 for (uint idx = 0; idx < sm; ++idx) 00597 { 00598 // get center and surround scales for this submap index: 00599 uint clev = 0, slev = 0; 00600 indexToCS(idx, clev, slev); 00601 std::vector<Point2D<int>*>::iterator ilocn = locn->begin(); 00602 std::vector<std::vector<float> >::iterator imean = mean->begin(); 00603 00604 for (int i = 0; i < *count; ++i, ++ilocn, ++imean) 00605 { 00606 // read center value with bilinear interpolation: 00607 ASSERT(pyr[clev].initialized()); 00608 const float cval = pyr[clev].getValInterpScaled(**ilocn, indims); 00609 00610 // read surround value with bilinear interpolation: 00611 ASSERT(pyr[slev].initialized()); 00612 const float sval = pyr[slev].getValInterpScaled(**ilocn, indims); 00613 00614 const float val = cval - sval; 00615 00616 // store the submap value at the chosen location: 00617 imean->push_back(itsTakeAbs.getVal() 00618 ? fabs(val) 00619 : val); 00620 } 00621 } 00622 } 00623 00624 // ###################################################################### 00625 void SingleChannel::killCaches() 00626 { 00627 GVX_TRACE(__PRETTY_FUNCTION__); 00628 ChannelBase::killCaches(); // call our base class's implementation 00629 00630 itsOutputCache.freeMem(); 00631 00632 // our caches exist only when we are in started() state: 00633 if (started()) 00634 for (uint i = 0; i < maxIndex(); ++i) 00635 itsSubmapCache[i] = Image<float>(); 00636 } 00637 00638 // ###################################################################### 00639 void SingleChannel::doInput(const InputFrame& inframe) 00640 { 00641 GVX_TRACE(__PRETTY_FUNCTION__); 00642 00643 if (!this->started()) 00644 CLFATAL("must be start()-ed before using receiving any input"); 00645 00646 ASSERT(inframe.grayFloat().initialized()); 00647 00648 // if we have an input handler, that will override our default 00649 // behavior: 00650 if (itsInputHandler.is_valid()) 00651 { 00652 itsInputHandler->handleInput(*this, inframe.grayFloat(), 00653 inframe.time(), inframe.clipMask(), 00654 inframe.pyrCache()); 00655 } 00656 else 00657 { 00658 setClipPyramid(inframe.clipMask()); 00659 storePyramid(computePyramid(inframe.grayFloat(), inframe.pyrCache()), 00660 inframe.time()); 00661 } 00662 } 00663 00664 // ###################################################################### 00665 void SingleChannel::inputPyramid(const ImageSet<float>& pyr, 00666 const SimTime& t, 00667 const Image<byte>& clipMask) 00668 { 00669 GVX_TRACE(__PRETTY_FUNCTION__); 00670 killCaches(); 00671 setClipPyramid(clipMask); 00672 00673 storePyramid(pyr, t); 00674 } 00675 00676 // ###################################################################### 00677 ImageSet<float> SingleChannel:: 00678 computePyramid(const Image<float>& bwimg, 00679 const rutz::shared_ptr<PyramidCache<float> >& cache) 00680 { 00681 GVX_TRACE(__PRETTY_FUNCTION__); 00682 00683 // Compute our pyramid: 00684 ImageSet<float> py = itsPyrBuilder-> 00685 build(bwimg, this->getMinPyrLevel(), 00686 this->getMaxPyrLevel(), cache.get()); 00687 00688 // Eliminate small values if and/or rectify the pyramid desired: 00689 if (itsLowThresh.getVal() > 0.0f) 00690 { 00691 if (itsRectifyPyramid.getVal()) 00692 doLowThresh(py, itsLowThresh.getVal()); 00693 else 00694 doLowThreshAbs(py, itsLowThresh.getVal()); 00695 } 00696 else if (itsRectifyPyramid.getVal()) 00697 doRectify(py); 00698 00699 // return pyramid: 00700 return py; 00701 } 00702 00703 // ###################################################################### 00704 void SingleChannel::storeOutputCache(const Image<float>& m) 00705 { 00706 GVX_TRACE(__PRETTY_FUNCTION__); 00707 itsOutputCache = m; 00708 } 00709 00710 // ###################################################################### 00711 void SingleChannel::setClipPyramid(const Image<byte>& clipMask) 00712 { 00713 GVX_TRACE(__PRETTY_FUNCTION__); 00714 // if necessary create pyramid of clipping masks 00715 if (clipMask.initialized()) 00716 { 00717 itsClipPyr = buildPyrGaussian(Image<float>(clipMask)/255.0f, 00718 0, itsLevelSpec.getVal().maxDepth(), 9); 00719 doLowThresh(itsClipPyr, 1.0f, 0.0f); 00720 } 00721 else 00722 itsClipPyr.clear(); 00723 } 00724 00725 // ###################################################################### 00726 void SingleChannel::storeClipPyramid(const ImageSet<float>& p) 00727 { 00728 GVX_TRACE(__PRETTY_FUNCTION__); 00729 itsClipPyr = p; 00730 } 00731 00732 // ###################################################################### 00733 void SingleChannel::storePyramid(const ImageSet<float>& p, 00734 const SimTime& t) 00735 { 00736 GVX_TRACE(__PRETTY_FUNCTION__); 00737 // load our pyramid into our front pyramid: 00738 itsPq.push_front(TPyr(p, t)); 00739 00740 // truncate the pyramid queue if necessary: 00741 while(int(itsPq.size()) > itsQlen.getVal()) itsPq.pop_back(); 00742 00743 // We only want dyadic pyramids here: 00744 ASSERT(isDyadic(itsPq.front().pyr.subSet 00745 (this->getMinPyrLevel(), 00746 this->getMaxPyrLevel()))); 00747 } 00748 00749 // ###################################################################### 00750 void SingleChannel::storeSubmapCache(const ImageSet<float>& p) 00751 { 00752 GVX_TRACE(__PRETTY_FUNCTION__); 00753 ASSERT(this->numSubmaps() == p.size()); 00754 const uint maxind = this->numSubmaps(); 00755 for (uint i = 0; i < maxind; ++i) 00756 this->itsSubmapCache[i] = p[i]; 00757 } 00758 00759 // ###################################################################### 00760 size_t SingleChannel::numPyramids() const 00761 { 00762 GVX_TRACE(__PRETTY_FUNCTION__); 00763 return itsPq.size(); 00764 } 00765 00766 // ###################################################################### 00767 const ImageSet<float>& SingleChannel::pyramid(const uint index) const 00768 { 00769 GVX_TRACE(__PRETTY_FUNCTION__); 00770 if (itsPq.empty()) CLFATAL("I have no input pyramid yet!"); 00771 ASSERT(index < itsPq.size()); 00772 return itsPq[index].pyr; 00773 } 00774 00775 // ###################################################################### 00776 SimTime SingleChannel::pyramidTime(const uint index) const 00777 { 00778 GVX_TRACE(__PRETTY_FUNCTION__); 00779 if (itsPq.empty()) return SimTime::ZERO(); 00780 ASSERT(index < itsPq.size()); 00781 return itsPq[index].t; 00782 } 00783 00784 // ###################################################################### 00785 const ImageSet<float>& SingleChannel::clipPyramid() const 00786 { 00787 GVX_TRACE(__PRETTY_FUNCTION__); 00788 return itsClipPyr; 00789 } 00790 00791 // ###################################################################### 00792 LevelSpec SingleChannel::getLevelSpec() const 00793 { 00794 GVX_TRACE(__PRETTY_FUNCTION__); 00795 return itsLevelSpec.getVal(); 00796 } 00797 00798 // ###################################################################### 00799 int SingleChannel::getNormType() const 00800 { 00801 GVX_TRACE(__PRETTY_FUNCTION__); 00802 return itsNormType.getVal(); 00803 } 00804 00805 // ###################################################################### 00806 Image<float> SingleChannel::combineSubMaps() 00807 { 00808 GVX_TRACE(__PRETTY_FUNCTION__); 00809 Image<float> output(getMapDims(), ZEROS); 00810 00811 // compute max-normalized weighted sum of center-surround at all levels: 00812 for (uint idx = 0; idx < maxIndex(); ++idx) 00813 { 00814 // determine any gain factor (possibly from a ChannelFacetGainSingle): 00815 float w = 1.0; 00816 if (hasFacet<ChannelFacetGainSingle>()) 00817 w = getFacet<ChannelFacetGainSingle>()->getVal(idx); 00818 00819 if (w != 0.0f) 00820 { 00821 Image<float> submap = getSubmap(idx); // get the unweighted map 00822 00823 // Perform templ matching on submap? 00824 if (itsTempl[idx].initialized()) 00825 { 00826 Image<float> img = templMatch(submap, itsTempl[idx]); 00827 //after templ matching the min is the winner, reverse the map 00828 //TODO (faster way?) 00829 Point2D<int> p; float maxval; findMax(img, p, maxval); 00830 img *= -1; img += maxval; 00831 //Expand the img to the origianl size since the tmplMatch returns 00832 //a smaller image by templ size 00833 //TODO (faster way?) 00834 submap.clear(0.0F); 00835 inplacePaste(submap, img, //center the image 00836 Point2D<int>(itsTempl[idx].getWidth()/2, 00837 itsTempl[idx].getHeight()/2)); 00838 } 00839 00840 if (w != 1.0f) submap *= w; // weigh the submap 00841 output += submap; // add submap to our sum 00842 00843 if (MYLOGVERB >= LOG_DEBUG) 00844 { 00845 uint clev = 0, slev = 0; 00846 indexToCS(idx, clev, slev); 00847 LDEBUG("%s(%d,%d): weight %f", tagName().c_str(), clev, slev, w); 00848 } 00849 00850 if (itsGetSingleChannelStats.getVal()) saveStats(submap, idx); 00851 } 00852 } 00853 00854 // apply max-normalization on the output as needed: 00855 if(itsNormType.getVal() == VCXNORM_LANDMARK) 00856 { 00857 float goodness = pow(goodness_map(output), 0.1); 00858 LINFO("GOODNESS = %f", goodness); 00859 output *= goodness; 00860 } 00861 else if (itsNormalizeOutput.getVal()) 00862 { 00863 LDEBUG("%s: Normalizing output: %s(%f .. %f)", tagName().c_str(), 00864 maxNormTypeName(itsNormType.getVal()), itsOutputRangeMin.getVal(), 00865 itsOutputRangeMax.getVal()); 00866 00867 output = maxNormalize(output, itsOutputRangeMin.getVal(), 00868 itsOutputRangeMax.getVal(), itsNormType.getVal()); 00869 } 00870 00871 // print some debug info if in debug mode: 00872 if (MYLOGVERB >= LOG_DEBUG) 00873 { 00874 float mi, ma; getMinMax(output, mi, ma); 00875 LDEBUG("%s: final range [%f .. %f]", tagName().c_str(), mi, ma); 00876 } 00877 00878 LINFO("Computed %s Conspicuity Map", descriptiveName().c_str()); 00879 00880 if (itsGetSingleChannelStats.getVal()) saveStats(output, -1); 00881 00882 return output; 00883 } 00884 00885 // ###################################################################### 00886 void SingleChannel::saveStats(const Image<float> img, const short idx) 00887 { 00888 std::string fileName; 00889 00890 if(itsSaveStatsPerChannel.getVal()) 00891 { 00892 std::string dot = "."; 00893 std::string txt = ".txt"; 00894 fileName = itsGetSingleChannelStatsFile.getVal()+ dot + tagName() + txt; 00895 } 00896 else 00897 fileName = itsGetSingleChannelStatsFile.getVal(); 00898 00899 ushort minx = 0, miny = 0, maxx = 0, maxy = 0; 00900 float min, max, avg, std; 00901 uint N; 00902 // get a whole bunch of stats about this output image 00903 getMinMaxAvgEtc(img, min, max, avg, std, minx, miny, maxx, maxy, N); 00904 00905 std::ofstream statsFile(fileName.c_str(), std::ios::app); 00906 00907 statsFile << itsGetSingleChannelStatsTag.getVal() << "\t"; 00908 00909 statsFile << itsFrameIdx << "\t"; 00910 00911 // differentiate between scale image stats and the combined max norm 00912 // for this channel 00913 if(idx == -1) 00914 { 00915 statsFile << "COMBINED\t-1\t"; 00916 itsFrameIdx++; 00917 } 00918 else 00919 statsFile << "SCALE\t" << idx << "\t"; 00920 00921 statsFile << tagName().c_str() << "\t" << descriptiveName().c_str() << "\t"; 00922 statsFile << min << "\t" << max << "\t" << avg << "\t" << std << "\t" 00923 << minx << "\t" << miny << "\t" << maxx << "\t" << maxy << "\t" 00924 << N << "\n"; 00925 statsFile.close(); 00926 00927 if(itsSaveStatsPerChannelFreq.getVal()) 00928 { 00929 std::string dot = "."; 00930 std::string txt = ".freq.txt"; 00931 fileName = itsGetSingleChannelStatsFile.getVal()+ dot + tagName() + txt; 00932 00933 FFTWWrapper fft(img.getWidth(), img.getHeight()); 00934 double dimg[img.getHeight() * img.getWidth()]; 00935 Image<float>::const_iterator itr = img.begin(); 00936 double *ditr = &dimg[0]; 00937 while(itr != img.end()) *ditr++ = double(*itr++); 00938 fft.init(dimg); 00939 double mag[img.getHeight() * (img.getWidth()/2 + 1)]; 00940 fft.compute(mag); 00941 00942 std::ofstream freqFile(fileName.c_str(), std::ios::app); 00943 freqFile << itsGetSingleChannelStatsTag.getVal() << "\t"; 00944 freqFile << itsFrameIdx << "\t"; 00945 00946 // differentiate between scale image stats and the combined max norm 00947 // for this channel 00948 if(idx == -1) freqFile << "COMBINED\t-1\t"; 00949 else freqFile << "SCALE\t" << idx << "\t"; 00950 00951 freqFile << "SIZE\t" << img.getWidth() <<"\t"<< img.getHeight() << "\n"; 00952 00953 for(int i = 0; i < img.getHeight(); i++) 00954 { 00955 for(int j = 0; j < (img.getWidth()/2 + 1); j++) 00956 freqFile << mag[i * (img.getWidth()/2 + 1) + j] << "\t"; 00957 freqFile << "\n"; 00958 } 00959 freqFile.close(); 00960 } 00961 } 00962 00963 // ###################################################################### 00964 Image<float> SingleChannel::getOutput() 00965 { 00966 GVX_TRACE(__PRETTY_FUNCTION__); 00967 00968 if (!this->hasInput()) 00969 // if you think this LFATAL() has been triggered incorrectly, then 00970 // first make sure that somebody has called setInputDims() 00971 CLFATAL("Oops! can't get output -- I don't even have any input yet"); 00972 00973 if (!this->outputAvailable()) 00974 { 00975 // it's possible that we have input but don't yet have output in 00976 // the case of a channel that requires several input frames 00977 // before it can start generating output (such as a flicker or 00978 // motion channel); in that case we just return an empty image 00979 // of the appropriate size: 00980 LERROR("No %s channel yet! -- IGNORING.", this->tagName().c_str()); 00981 return Image<float>(this->getMapDims(), ZEROS); 00982 } 00983 00984 if (!itsOutputCache.initialized()) 00985 itsOutputCache = combineSubMaps(); 00986 00987 return itsOutputCache; 00988 } 00989 00990 // ###################################################################### 00991 void SingleChannel::setPyramid(rutz::shared_ptr<PyrBuilder<float> > pbuild) 00992 { 00993 GVX_TRACE(__PRETTY_FUNCTION__); 00994 ASSERT(pbuild.get() != 0); 00995 itsPq.clear(); // forget about our old pyramids 00996 itsPyrBuilder = pbuild; 00997 } 00998 00999 // ###################################################################### 01000 ImageSet<float>& SingleChannel::pyrMut(const uint index) 01001 { 01002 GVX_TRACE(__PRETTY_FUNCTION__); 01003 ASSERT(index < itsPq.size()); 01004 return itsPq[index].pyr; 01005 } 01006 01007 // ###################################################################### 01008 uint SingleChannel::csToIndex(const uint centerlev, 01009 const uint surroundlev) const 01010 { 01011 GVX_TRACE(__PRETTY_FUNCTION__); 01012 return itsLevelSpec.getVal().csToIndex(centerlev, surroundlev); 01013 } 01014 01015 01016 // ###################################################################### 01017 void SingleChannel::indexToCS(const uint index, uint& centerlev, uint& surroundlev) const 01018 { 01019 GVX_TRACE(__PRETTY_FUNCTION__); 01020 itsLevelSpec.getVal().indexToCS(index, centerlev, surroundlev); 01021 } 01022 01023 // ###################################################################### 01024 uint SingleChannel::maxIndex() const 01025 { 01026 GVX_TRACE(__PRETTY_FUNCTION__); 01027 return itsLevelSpec.getVal().maxIndex(); 01028 } 01029 01030 // ###################################################################### 01031 void SingleChannel::saveResults(const nub::ref<FrameOstream>& ofs) 01032 { 01033 GVX_TRACE(__PRETTY_FUNCTION__); 01034 if (itsPq.empty() == false) 01035 { 01036 // save raw pyramid levels? 01037 if (itsSaveRawMaps.getVal()) { 01038 const ImageSet<float>& pyr = itsPq.front().pyr; 01039 for (uint i = 0; i < pyr.size(); i ++) 01040 { 01041 // use --save-raw-map 01042 ofs->writeFloat(pyr[i], FLOAT_NORM_0_255, 01043 sformat("SR%s-%d-", tagName().c_str(),i), 01044 FrameInfo(sformat("%s SingleChannel raw map (%u of %u)", 01045 this->descriptiveName().c_str(), 01046 i, pyr.size()), 01047 SRC_POS)); 01048 } 01049 } 01050 01051 // save center-surround feature submaps? 01052 if (itsSaveFeatureMaps.getVal()) 01053 for (uint i = 0; i < numSubmaps(); i ++) { 01054 uint clev = 0, slev = 0; 01055 indexToCS(i, clev, slev); 01056 ofs->writeFloat(getSubmap(i), 01057 FLOAT_NORM_0_255, 01058 sformat("SF%s-%d-%d-", tagName().c_str(),clev, slev), 01059 FrameInfo(sformat("%s SingleChannel center-surround map (c=%u s=%u)", 01060 this->descriptiveName().c_str(), 01061 clev, slev), 01062 SRC_POS)); 01063 } 01064 } 01065 01066 // save output map? 01067 // --save-channel-outputs 01068 if (itsSaveOutputMap.getVal()) 01069 ofs->writeFloat(getOutput(), FLOAT_NORM_0_255, 01070 sformat("SO%s-", tagName().c_str()), 01071 FrameInfo(sformat("%s SingleChannel output", 01072 this->descriptiveName().c_str()), 01073 SRC_POS)); 01074 } 01075 01076 // ###################################################################### 01077 void SingleChannel::setInputHandler(rutz::shared_ptr<InputHandler> h) 01078 { 01079 GVX_TRACE(__PRETTY_FUNCTION__); 01080 itsInputHandler = InputHandler::clone(h); 01081 } 01082 01083 // ###################################################################### 01084 rutz::shared_ptr<InputHandler> SingleChannel::cloneInputHandler() const 01085 { 01086 GVX_TRACE(__PRETTY_FUNCTION__); 01087 return InputHandler::clone(itsInputHandler); 01088 } 01089 01090 // ###################################################################### 01091 void SingleChannel::setSubmapAlgorithm(nub::ref<SubmapAlgorithm> algo) 01092 { 01093 GVX_TRACE(__PRETTY_FUNCTION__); 01094 this->removeSubComponent(*itsSubmapAlgo); 01095 itsSubmapAlgo = algo; 01096 this->addSubComponent(itsSubmapAlgo); 01097 01098 if (this->started() && !algo->started()) 01099 itsSubmapAlgo->start(); 01100 } 01101 01102 // ###################################################################### 01103 /* So things look consistent in everyone's emacs... */ 01104 /* Local Variables: */ 01105 /* indent-tabs-mode: nil */ 01106 /* End: */