SingleChannel.C

Go to the documentation of this file.
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: */
Generated on Sun May 8 08:04:41 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3