ContourChannel.C

Go to the documentation of this file.
00001 /*! @file Channels/ContourChannel.C [put description here] */
00002 
00003 // ContourChannel.C -- Contour facilitation channel
00004 
00005 // //////////////////////////////////////////////////////////////////// //
00006 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2001 by the //
00007 // University of Southern California (USC) and the iLab at USC.         //
00008 // See http://iLab.usc.edu for information about this project.          //
00009 // //////////////////////////////////////////////////////////////////// //
00010 // Major portions of the iLab Neuromorphic Vision Toolkit are protected //
00011 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency //
00012 // in Visual Environments, and Applications'' by Christof Koch and      //
00013 // Laurent Itti, California Institute of Technology, 2001 (patent       //
00014 // pending; filed July 23, 2001, following provisional applications     //
00015 // No. 60/274,674 filed March 8, 2001 and 60/288,724 filed May 4, 2001).//
00016 // //////////////////////////////////////////////////////////////////// //
00017 // This file is part of the iLab Neuromorphic Vision C++ Toolkit.       //
00018 //                                                                      //
00019 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can   //
00020 // redistribute it and/or modify it under the terms of the GNU General  //
00021 // Public License as published by the Free Software Foundation; either  //
00022 // version 2 of the License, or (at your option) any later version.     //
00023 //                                                                      //
00024 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope  //
00025 // that it will be useful, but WITHOUT ANY WARRANTY; without even the   //
00026 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR      //
00027 // PURPOSE.  See the GNU General Public License for more details.       //
00028 //                                                                      //
00029 // You should have received a copy of the GNU General Public License    //
00030 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write   //
00031 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,   //
00032 // Boston, MA 02111-1307 USA.                                           //
00033 // //////////////////////////////////////////////////////////////////// //
00034 //
00035 // Primary maintainer for this file: Rob Peters <rjpeters@klab.caltech.edu>
00036 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Channels/ContourChannel.C $
00037 // $Id: ContourChannel.C 12820 2010-02-11 05:44:51Z itti $
00038 //
00039 
00040 #include "Channels/ContourChannel.H"
00041 
00042 #include "Channels/ChannelBase.H"
00043 #include "Channels/ChannelOpts.H"
00044 #include "Channels/ComplexChannel.H"
00045 #include "Component/ModelOptionDef.H"
00046 #include "Component/OptionManager.H"
00047 #include "Image/ColorOps.H"
00048 #include "Image/DrawOps.H"
00049 #include "Image/FilterOps.H"
00050 #include "Image/ImageSet.H"
00051 #include "Image/ImageSetOps.H"
00052 #include "Image/LevelSpec.H"
00053 #include "Image/MathOps.H"
00054 #include "Image/PyramidOps.H"
00055 #include "Image/Range.H"
00056 #include "Image/ShapeOps.H"
00057 #include "Image/fancynorm.H"
00058 #include "Raster/GenericFrame.H"
00059 #include "Transport/FrameOstream.H"
00060 #include "Util/Assert.H"
00061 #include "Util/CpuTimer.H"
00062 #include "Util/StringConversions.H"
00063 #include "Util/StringUtil.H"
00064 #include "Util/Types.H"
00065 #include "Util/log.H"
00066 #include "Util/sformat.H"
00067 #include "rutz/shared_ptr.h"
00068 
00069 #include <cmath>
00070 #include <map>
00071 #include <iostream>
00072 #include <fstream>
00073 
00074 namespace dummy_namespace_to_avoid_gcc411_bug_ContourChannel_C
00075 {
00076   // Find the absolute value of the orientation difference between two
00077   // angles, such that the result is between 0 and 90 degrees (from
00078   // Braun J, 1996).
00079   float angDist(float ang1, float ang2)
00080   {
00081     while (ang1 < 0.0f) ang1 += 180.0f;
00082     while (ang2 < 0.0f) ang2 += 180.0f;
00083     while (ang1 >= 180.0f) ang1 -= 180.0f;
00084     while (ang2 >= 180.0f) ang2 -= 180.0f;
00085 
00086     float diff = fabs(ang1 - ang2);
00087 
00088     if (diff > 90.0f) diff = 180.0f - diff;
00089 
00090     return diff;
00091   }
00092 
00093   // Remap v into the range [0.0 - 1.0]
00094   //   v == 0.0       --> sigmoid ~= 0.12
00095   //   v == thresh/2  --> sigmoid  = 0.5
00096   //   v == thresh    --> sigmoid ~= 0.88
00097   float sigmoid(float v, float thresh)
00098   {
00099     return 1.0f / ( 1.0f + exp(2.0f - 4.0f*v/thresh) );
00100   }
00101 
00102   Image<float> sigmoid(const Image<float>& input, float thresh)
00103   {
00104     Image<float> result(input.getDims(), NO_INIT);
00105 
00106     Image<float>::iterator dptr = result.beginw();
00107     Image<float>::iterator stop = result.endw();
00108     Image<float>::const_iterator sptr = input.begin();
00109 
00110     while (dptr != stop)
00111       {
00112         *dptr++ = sigmoid(*sptr++, thresh);
00113       }
00114 
00115     return result;
00116   }
00117 
00118   struct Accum
00119   {
00120     double s;
00121     int c;
00122 
00123     Accum() : s(0.0), c(0) {}
00124 
00125     void add(double d) { s+=d; ++c; }
00126 
00127     int count() const { return c; }
00128     double sum() const { return s; }
00129     double mean() const { return c != 0.0 ? s/c : 0.0; }
00130   };
00131 
00132   inline double sigmoid2(double x)
00133   {
00134     return 1.0 / (1.0 + exp(-x));
00135   }
00136 
00137   inline double excitFiring(double x)
00138   {
00139     double result = 2.0*sigmoid2(0.2 * x) - 1;
00140     return (result < 0.0) ? 0.0 : result;
00141   }
00142 
00143   inline double PHI(double x)
00144   {
00145     return sigmoid2(10.0 * x);
00146   }
00147 
00148   inline double inhibFiring(double x)
00149   {
00150     const double R_THR = 0.5;
00151     const double R_CUT = 0.2;
00152     double result = PHI(x - R_THR) - PHI(R_CUT - R_THR);
00153     return (result < 0.0) ? 0.0 : result;
00154   }
00155 
00156   float getScaleBias(int scaleNumber)
00157   {
00158     switch(scaleNumber)
00159       {
00160       case 0: return 1.0f;
00161       case 1: return 0.9f;
00162       case 2: return 0.7f;
00163       }
00164     return 0.7f;
00165   }
00166 
00167   void printStats(const Image<float>& img, const char* name)
00168   {
00169     float mi, ma, me;
00170     getMinMaxAvg(img, mi, ma, me);
00171 
00172     LINFO("%s: min %.2f, max %.2f, mean %.2f", name, mi, ma, me);
00173   }
00174 
00175   Image<float> boxDownSizeClean(const Image<float>& src,
00176                                 const Dims& new_dims,
00177                                 const int filterWidth)
00178   {
00179     if (src.getDims() == new_dims) return src;
00180 
00181     ASSERT(new_dims.isNonEmpty());
00182 
00183     Image<float> result = src;
00184 
00185     Image<float> filt(filterWidth, 1, ZEROS);
00186     filt.clear(1.0f / filterWidth);
00187 
00188     while (result.getWidth() >= new_dims.w() * 2 &&
00189            result.getHeight() >= new_dims.h() * 2)
00190       {
00191         result = decX(sepFilter(result, filt, Image<float>(),
00192                                 CONV_BOUNDARY_CLEAN));
00193         result = decY(sepFilter(result, Image<float>(), filt,
00194                                 CONV_BOUNDARY_CLEAN));
00195       }
00196 
00197     return rescaleBilinear(result, new_dims);
00198   }
00199 
00200   struct SaveSet
00201   {
00202   private:
00203     typedef std::map<std::string, GenericFrame> map_type;
00204     map_type imgmap;
00205 
00206   public:
00207     void clear()
00208     {
00209       map_type().swap(imgmap);
00210     }
00211 
00212     void add(const std::string& nm, const GenericFrame& f)
00213     {
00214       imgmap.insert(map_type::value_type(nm, f));
00215     }
00216 
00217     void saveAll(FrameOstream& ofs, const char* savePfx) const
00218     {
00219       for (map_type::const_iterator itr = imgmap.begin(), stop = imgmap.end();
00220            itr != stop; ++itr)
00221         ofs.writeFrame((*itr).second,
00222                        sformat("%s.%s", savePfx, (*itr).first.c_str()));
00223     }
00224   };
00225 }
00226 
00227 using namespace dummy_namespace_to_avoid_gcc411_bug_ContourChannel_C;
00228 
00229 // ######################################################################
00230 // ######################################################################
00231 // ##### ContourConnection class
00232 // ######################################################################
00233 // ######################################################################
00234 
00235 enum ContourConnectionType
00236   {
00237     CC_CXN_CINNIC = 1,
00238     CC_CXN_BRAUN = 2,
00239     CC_CXN_BRAUN_NORM = 3
00240   };
00241 
00242 std::string convertToString(const ContourConnectionType val)
00243 {
00244   switch (val)
00245     {
00246     case CC_CXN_CINNIC: return "Cinnic"; break;
00247     case CC_CXN_BRAUN: return "Braun"; break;
00248     case CC_CXN_BRAUN_NORM: return "BraunNorm"; break;
00249     }
00250 
00251   LFATAL("invalid ContourConnectionType '%d'", int(val));
00252   /* can't happen */ return std::string();
00253 }
00254 
00255 void convertFromString(const std::string& str1, ContourConnectionType& val)
00256 {
00257   const std::string str = toLowerCase(str1);
00258 
00259   if      (str.compare("cinnic") == 0) { val = CC_CXN_CINNIC; }
00260   else if (str.compare("braun") == 0) { val = CC_CXN_BRAUN; }
00261   else if (str.compare("braunnorm") == 0)  { val = CC_CXN_BRAUN_NORM; }
00262   else
00263     conversion_error::raise<ContourConnectionType>(str1);
00264 }
00265 
00266 //! The connection kernel between orientation-tuned units.
00267 class ContourConnection : public ModelComponent
00268 {
00269 public:
00270   ContourConnection(OptionManager& manager);
00271 
00272   virtual ~ContourConnection();
00273 
00274   float weight(int a, int b, int x, int y) const
00275   {
00276     return itsWeights[index(a,b,x,y)];
00277   }
00278 
00279   const float* weightAXY(int a, int x, int y) const
00280   {
00281     return itsWeights + index(a, 0, x, y);
00282   }
00283 
00284   const float* excitAXY(int a, int x, int y) const
00285   {
00286     return itsExcit + index(a, 0, x, y);
00287   }
00288 
00289   const float* inhibAXY(int a, int x, int y) const
00290   {
00291     return itsInhib + index(a, 0, x, y);
00292   }
00293 
00294   int numAngles() const { return itsNumAngles.getVal(); }
00295 
00296   Dims getDims() const { return itsDims.getVal(); }
00297 
00298   Point2D<int> getCenter() const
00299   {
00300     return Point2D<int>((itsDims.getVal().w()-1) / 2,
00301                    (itsDims.getVal().h()-1) / 2);
00302   }
00303 
00304   float angle(int i) const { return itsAngles[i]; }
00305 
00306   const float* angles() const { return itsAngles; }
00307 
00308   Image<byte> getSummaryImage() const;
00309 
00310 private:
00311   ContourConnection(const ContourConnection&);
00312   ContourConnection& operator=(const ContourConnection&);
00313 
00314   virtual void start2();
00315 
00316   virtual void stop1();
00317 
00318   virtual void setupConnectionWeights() = 0;
00319 
00320   void printStats() const;
00321 
00322 protected:
00323   int index(int a, int b, int x, int y) const
00324   {
00325     return b + itsNA*(y + itsH*(x + a*itsW));
00326   }
00327 
00328   float& weight(int a, int b, int x, int y)
00329   {
00330     return itsWeights[index(a,b,x,y)];
00331   }
00332 
00333   OModelParam<Dims> itsDims;
00334   OModelParam<int> itsNumAngles;
00335 
00336   int itsW;  ///< set to itsDims.getVal().w() during start()
00337   int itsH;  ///< set to itsDims.getVal().w() during start()
00338   int itsNA; ///< set to itsNumAngles.getVal() during start()
00339   int itsNumWeights;
00340   float* itsAngles;
00341   float* itsWeights;
00342   float* itsExcit;
00343   float* itsInhib;
00344 };
00345 
00346 // ######################################################################
00347 // ######################################################################
00348 // ##### ContourConnection implementation
00349 // ######################################################################
00350 // ######################################################################
00351 
00352 // Used by: ContourConnection
00353 static const ModelOptionDef OPT_ContourConnectionDims =
00354   { MODOPT_ARG(Dims), "ContourConnectionDims", &MOC_CHANNEL, OPTEXP_CORE,
00355     "Dimensions of the individual connection kernels in the contour channel "
00356     "(corresponds to old ParamMap parameters 'sizeX=13' and 'sizeY=13')",
00357     "contour-cxn-dims", '\0', "<w>x<h>", "13x13" };
00358 
00359 // Used by: ContourConnection
00360 static const ModelOptionDef OPT_ContourConnectionNumOrient =
00361   { MODOPT_ARG(int), "ContourConnectionNumOrient", &MOC_CHANNEL, OPTEXP_CORE,
00362     "Number of orientations to use in the contour channel's connection kernel "
00363     "(corresponds to old ParamMap parameter 'numAngles=12')",
00364     "contour-num-orient", '\0', "<int>", "12" };
00365 
00366 ContourConnection::ContourConnection(OptionManager& mgr)
00367   :
00368   ModelComponent(mgr, "Contour Connection Kernel", "ContourConnection"),
00369   itsDims         ( &OPT_ContourConnectionDims, this ),
00370   itsNumAngles    ( &OPT_ContourConnectionNumOrient, this),
00371   itsW            ( -1 ),
00372   itsH            ( -1 ),
00373   itsNA           ( -1 ),
00374   itsNumWeights   ( -1 ),
00375   itsAngles       ( 0 ),
00376   itsWeights      ( 0 ),
00377   itsExcit        ( 0 ),
00378   itsInhib        ( 0 )
00379 {}
00380 
00381 ContourConnection::~ContourConnection()
00382 {}
00383 
00384 void ContourConnection::start2()
00385 {
00386   // width&height must be odd
00387   if ((itsDims.getVal().w() % 2) == 0)
00388     LFATAL("Expected --%s to have odd width, but got --%s=%dx%d",
00389            itsDims.getOptionDef()->longoptname,
00390            itsDims.getOptionDef()->longoptname,
00391            itsDims.getVal().w(), itsDims.getVal().h());
00392 
00393   if ((itsDims.getVal().h() % 2) == 0)
00394     LFATAL("Expected --%s to have odd height, but got --%s=%dx%d",
00395            itsDims.getOptionDef()->longoptname,
00396            itsDims.getOptionDef()->longoptname,
00397            itsDims.getVal().w(), itsDims.getVal().h());
00398 
00399   if (itsNumAngles.getVal() <= 0)
00400     LFATAL("Expected a positive value for --%s, but got --%s=%d",
00401            itsNumAngles.getOptionDef()->longoptname,
00402            itsNumAngles.getOptionDef()->longoptname,
00403            itsNumAngles.getVal());
00404 
00405   itsW = itsDims.getVal().w();
00406   itsH = itsDims.getVal().h();
00407   itsNA = itsNumAngles.getVal();
00408   itsNumWeights = itsNA*itsNA*itsW*itsH;
00409 
00410   ASSERT(itsAngles == 0);  itsAngles = new float[itsNA];
00411   ASSERT(itsWeights == 0); itsWeights = new float[itsNumWeights];
00412   ASSERT(itsExcit == 0);   itsExcit = new float[itsNumWeights];
00413   ASSERT(itsInhib == 0);   itsInhib = new float[itsNumWeights];
00414 
00415   for (int i = 0; i < itsNA; ++i)
00416     {
00417       itsAngles[i] = i * (180.0 / itsNA);
00418     }
00419 
00420   for (int i = 0; i < itsNumWeights; ++i)
00421     {
00422       itsWeights[i] = 0.0f;
00423     }
00424 
00425   this->setupConnectionWeights();
00426 
00427   printStats();
00428 
00429   ModelComponent::start2();
00430 }
00431 
00432 void ContourConnection::stop1()
00433 {
00434   itsW = -1;
00435   itsH = -1;
00436   itsNumWeights = -1;
00437   delete [] itsAngles;   itsAngles = 0;
00438   delete [] itsWeights;  itsWeights = 0;
00439   delete [] itsExcit;    itsExcit = 0;
00440   delete [] itsInhib;    itsInhib = 0;
00441 }
00442 
00443 void ContourConnection::printStats() const
00444 {
00445   int countAll = 0;
00446   int countSuppression = 0;
00447   int countExcitation = 0;
00448   double sumAll = 0;
00449   double sumSuppression = 0;
00450   double sumExcitation = 0;
00451 
00452   ASSERT(itsNumWeights > 0);
00453 
00454   for (int i = 0; i < itsNumWeights; ++i)
00455     {
00456       const float w = itsWeights[i];
00457 
00458       ++countAll;
00459       sumAll += w;
00460 
00461       if (w < 0.0)
00462         {
00463           ++countSuppression;
00464           sumSuppression += w;
00465         }
00466       else if (w > 0.0)
00467         {
00468           ++countExcitation;
00469           sumExcitation += w;
00470         }
00471     }
00472 
00473   LINFO("%+8.2f (%5d) all", sumAll, countAll);
00474   LINFO("%+8.2f (%5d) suppression", sumSuppression, countSuppression);
00475   LINFO("%+8.2f (%5d) excitation", sumExcitation, countExcitation);
00476 }
00477 
00478 Image<byte> ContourConnection::getSummaryImage() const
00479 {
00480   const int nori = itsNumAngles.getVal();
00481 
00482   Image<float> weights[(nori+1)*(nori+1)];
00483 
00484   weights[0] = Image<float>(itsDims.getVal() * 4, ZEROS);
00485 
00486   int c = 1;
00487 
00488   for (int b = 0; b < nori; ++b)
00489     {
00490       Image<float> tmp(itsDims.getVal() * 4, ZEROS);
00491       writeText(tmp, Point2D<int>(0,0),
00492                 sformat("%.1f", angle(b)).c_str());
00493       inplaceNormalize(tmp, 0.0f, 1.0f);
00494       weights[c++] = tmp;
00495     }
00496 
00497   for (int angle_a = 0; angle_a < nori; ++angle_a)
00498     {
00499       Image<float> foo(itsDims.getVal() * 4, ZEROS);
00500       writeText(foo, Point2D<int>(0,0),
00501                 sformat("%.1f", angle(angle_a)).c_str());
00502       inplaceNormalize(foo, 0.0f, 1.0f);
00503       weights[c++] = foo;
00504 
00505       for (int angle_b = 0; angle_b < nori; ++angle_b)
00506         {
00507           Image<float> tmp(itsDims.getVal(), NO_INIT);
00508 
00509           for (int y = 0; y < itsH; ++y)
00510             for (int x = 0; x < itsW; ++x)
00511               tmp.setVal(x, y, weight(angle_a, angle_b, x, y));
00512 
00513           weights[c++] = intXY(intXY(tmp, true), true);
00514         }
00515     }
00516 
00517   Image<float> img = concatArray(&weights[0],
00518                                  (nori+1)*(nori+1),
00519                                  nori+1);
00520   inplaceNormalize(img, 0.0f, 255.0f);
00521 
00522   return Image<byte>(img);
00523 }
00524 
00525 // ######################################################################
00526 // ######################################################################
00527 // ##### ContourConnectionCinnic class
00528 // ######################################################################
00529 // ######################################################################
00530 
00531 // Based on CINNIC kernel (Nathan Mundhenk)
00532 class ContourConnectionCinnic : public ContourConnection
00533 {
00534 public:
00535   ContourConnectionCinnic(OptionManager& manager);
00536 
00537   virtual ~ContourConnectionCinnic() {}
00538 
00539 private:
00540   virtual void setupConnectionWeights();
00541 
00542   OModelParam<float> itsAngleDropOff;
00543   OModelParam<float> itsAngleSuppress;
00544   OModelParam<float> itsSupMult;
00545   OModelParam<float> itsOrthMult;
00546   OModelParam<float> itsOrthA;
00547   OModelParam<float> itsOrthB;
00548   OModelParam<float> itsNeuronExSize;
00549   OModelParam<float> itsNeuronSupSize;
00550   OModelParam<float> itsNeuronSupStart;
00551   OModelParam<float> itsNeuronOrthSize;
00552   OModelParam<float> itsValueCutoff;
00553   OModelParam<float> itsColinearDiff;
00554 };
00555 
00556 // Used by: ContourConnectionCinnic
00557 static const ModelOptionDef OPT_CinnicAngleDropOff =
00558   { MODOPT_ARG(float), "CinnicAngleDropOff", &MOC_CHANNEL, OPTEXP_CORE,
00559     "AngleDropOff parameter for the cinnic kernel in the contour channel "
00560     "(corresponds to old ParamMap parameter 'AngleDropOff=35.0')",
00561     "cinnic-angle-dropoff", '\0', "<float>", "35.0" };
00562 
00563 // Used by: ContourConnectionCinnic
00564 static const ModelOptionDef OPT_CinnicAngleSuppress =
00565   { MODOPT_ARG(float), "CinnicAngleSuppress", &MOC_CHANNEL, OPTEXP_CORE,
00566     "AngleSuppress parameter for the cinnic kernel in the contour channel "
00567     "(corresponds to old ParamMap parameter 'AngleSuppress=60.0')",
00568     "cinnic-angle-suppress", '\0', "<float>", "60.0" };
00569 
00570 // Used by: ContourConnectionCinnic
00571 static const ModelOptionDef OPT_CinnicSupMult =
00572   { MODOPT_ARG(float), "CinnicSupMult", &MOC_CHANNEL, OPTEXP_CORE,
00573     "SupMult parameter for the cinnic kernel in the contour channel "
00574     "(corresponds to old ParamMap parameter 'SupMult=1.5')",
00575     "cinnic-sup-mult", '\0', "<float>", "1.5" };
00576 
00577 // Used by: ContourConnectionCinnic
00578 static const ModelOptionDef OPT_CinnicOrthMult =
00579   { MODOPT_ARG(float), "CinnicOrthMult", &MOC_CHANNEL, OPTEXP_CORE,
00580     "OrthMult parameter for the cinnic kernel in the contour channel "
00581     "(corresponds to old ParamMap parameter 'OrthMult=0.0')",
00582     "cinnic-orth-mult", '\0', "<float>", "0.0" };
00583 
00584 // Used by: ContourConnectionCinnic
00585 static const ModelOptionDef OPT_CinnicAngleOrthRangeA =
00586   { MODOPT_ARG(float), "CinnicAngleOrthRangeA", &MOC_CHANNEL, OPTEXP_CORE,
00587     "AngleOrthRangeA parameter for the cinnic kernel in the contour channel "
00588     "(corresponds to old ParamMap parameter 'AngleOrthRangeA=45.0')",
00589     "cinnic-angle-orth-range-a", '\0', "<float>", "45.0" };
00590 
00591 // Used by: ContourConnectionCinnic
00592 static const ModelOptionDef OPT_CinnicAngleOrthRangeB =
00593   { MODOPT_ARG(float), "CinnicAngleOrthRangeB", &MOC_CHANNEL, OPTEXP_CORE,
00594     "AngleOrthRangeB parameter for the cinnic kernel in the contour channel "
00595     "(corresponds to old ParamMap parameter 'AngleOrthRangeB=35.0')",
00596     "cinnic-angle-orth-range-b", '\0', "<float>", "35.0" };
00597 
00598 // Used by: ContourConnectionCinnic
00599 static const ModelOptionDef OPT_CinnicNeuronExSize =
00600   { MODOPT_ARG(float), "CinnicNeuronExSize", &MOC_CHANNEL, OPTEXP_CORE,
00601     "NeuronExSize parameter for the cinnic kernel in the contour channel "
00602     "(corresponds to old ParamMap parameter 'NeuronExSize=12.0')",
00603     "cinnic-neuron-ex-size", '\0', "<float>", "12.0" };
00604 
00605 // Used by: ContourConnectionCinnic
00606 static const ModelOptionDef OPT_CinnicNeuronSupSize =
00607   { MODOPT_ARG(float), "CinnicNeuronSupSize", &MOC_CHANNEL, OPTEXP_CORE,
00608     "NeuronSupSize parameter for the cinnic kernel in the contour channel "
00609     "(corresponds to old ParamMap parameter 'NeuronSupSize=10.0')",
00610     "cinnic-neuron-sup-size", '\0', "<float>", "10.0" };
00611 
00612 // Used by: ContourConnectionCinnic
00613 static const ModelOptionDef OPT_CinnicNeuronSupStart =
00614   { MODOPT_ARG(float), "CinnicNeuronSupStart", &MOC_CHANNEL, OPTEXP_CORE,
00615     "NeuronSupStart parameter for the cinnic kernel in the contour channel "
00616     "(corresponds to old ParamMap parameter 'NeuronSupStart=1.0')",
00617     "cinnic-neuron-sup-start", '\0', "<float>", "1.0" };
00618 
00619 // Used by: ContourConnectionCinnic
00620 static const ModelOptionDef OPT_CinnicNeuronOrthSize =
00621   { MODOPT_ARG(float), "CinnicNeuronOrthSize", &MOC_CHANNEL, OPTEXP_CORE,
00622     "NeuronOrthSize parameter for the cinnic kernel in the contour channel "
00623     "(corresponds to old ParamMap parameter 'NeuronOrthSize=12.0')",
00624     "cinnic-neuron-orth-size", '\0', "<float>", "12.0" };
00625 
00626 // Used by: ContourConnectionCinnic
00627 static const ModelOptionDef OPT_CinnicValueCutoff =
00628   { MODOPT_ARG(float), "CinnicValueCutoff", &MOC_CHANNEL, OPTEXP_CORE,
00629     "valueCutOff parameter for the cinnic kernel in the contour channel "
00630     "(corresponds to old ParamMap parameter 'valueCutOff=0.0001')",
00631     "cinnic-value-cutoff", '\0', "<float>", "0.0001" };
00632 
00633 // Used by: ContourConnectionCinnic
00634 static const ModelOptionDef OPT_CinnicColinearDiff =
00635   { MODOPT_ARG(float), "CinnicColinearDiff", &MOC_CHANNEL, OPTEXP_CORE,
00636     "ColinearDiff parameter for the cinnic kernel in the contour channel "
00637     "(corresponds to old ParamMap parameter 'CoLinearDiff=16.0')",
00638     "cinnic-colinear-diff", '\0', "<float>", "16.0" };
00639 
00640 ContourConnectionCinnic::
00641 ContourConnectionCinnic(OptionManager& manager)
00642   :
00643   ContourConnection(manager),
00644   itsAngleDropOff   (&OPT_CinnicAngleDropOff, this),
00645   itsAngleSuppress  (&OPT_CinnicAngleSuppress, this),
00646   itsSupMult        (&OPT_CinnicSupMult, this),
00647   itsOrthMult       (&OPT_CinnicOrthMult, this),
00648   itsOrthA          (&OPT_CinnicAngleOrthRangeA, this),
00649   itsOrthB          (&OPT_CinnicAngleOrthRangeB, this),
00650   itsNeuronExSize   (&OPT_CinnicNeuronExSize, this),
00651   itsNeuronSupSize  (&OPT_CinnicNeuronSupSize, this),
00652   itsNeuronSupStart (&OPT_CinnicNeuronSupStart, this),
00653   itsNeuronOrthSize (&OPT_CinnicNeuronOrthSize, this),
00654   itsValueCutoff    (&OPT_CinnicValueCutoff, this),
00655   itsColinearDiff   (&OPT_CinnicColinearDiff, this)
00656 {}
00657 
00658 void ContourConnectionCinnic::setupConnectionWeights()
00659 {
00660   ASSERT(itsValueCutoff.getVal() >= 0.0f);
00661 
00662   ASSERT(itsW > 0);
00663   ASSERT(itsH > 0);
00664 
00665   const int nori = itsNumAngles.getVal();
00666 
00667   const Point2D<int> center = this->getCenter();
00668 
00669   for (int x = 0; x < itsW; ++x) {
00670     for (int y = 0; y < itsH; ++y) {
00671 
00672       const float distance =
00673         sqrt( (center.i-x)*(center.i-x) + (center.j-y)*(center.j-y) );
00674 
00675       if (distance == 0) // center is always 0
00676         continue;
00677 
00678       // We negate the y component to account for the difference
00679       // between "math" coordinates where y runs from bottom to top,
00680       // and "image coordinates", where y runs from top to bottom
00681       const float connectingAngle = (180/M_PI) * atan2(center.j-y, x-center.i);
00682 
00683       for (int a = 0; a < nori; ++a) {
00684 
00685         // Find the angle between the line pointing the the other unit
00686         // and the alignment of this unit
00687         const float angDistAC = angDist(connectingAngle, angle(a));
00688 
00689         for (int b = 0; b < nori; ++b) {
00690 
00691           const float angDistAB = angDist(angle(a), angle(b));
00692 
00693           // find if orthogonal values need to be calculated and
00694           // calculate their value
00695           const bool is_orth =
00696             (angDistAC > 90.0f-itsOrthA.getVal())
00697             &&
00698             (angDistAB > 90.0f-itsOrthB.getVal());
00699 
00700           if (is_orth)
00701             {
00702               if (itsOrthMult.getVal() <= 0.0f)
00703                 continue;
00704 
00705               const float alphaAngleMod = 1.0f - (90.0f-angDistAC)/itsOrthA.getVal();
00706               ASSERT(alphaAngleMod >= 0);
00707 
00708               const float distanceMod = 1.0f - distance/(itsNeuronOrthSize.getVal()/2);
00709 
00710               if (distanceMod <= 0.0f)
00711                 continue;
00712 
00713               weight(a, b, x, y) =
00714                 itsOrthMult.getVal() * alphaAngleMod * distanceMod;
00715             }
00716           else if (angDistAC > itsAngleDropOff.getVal()) // "butterfly" suppression
00717             {
00718               ASSERT(itsAngleDropOff.getVal() > (90.0f - itsAngleSuppress.getVal()));
00719 
00720               // Make sure we are colinear "enough"
00721               if (angDistAB >= itsColinearDiff.getVal())
00722                 continue;
00723 
00724               // Make sure we are in the right distance range to allow
00725               // suppression
00726               if ((distance < itsNeuronSupStart.getVal()) ||
00727                   (distance >= (itsNeuronSupSize.getVal()/2)))
00728                 continue;
00729 
00730               const float distanceMod = 1.0f - distance/(itsNeuronSupSize.getVal()/2);
00731 
00732               ASSERT(distanceMod > 0.0f);
00733 
00734               const float alphaAngleMod =
00735                 1.0f - (90.0f-angDistAC)/itsAngleSuppress.getVal();
00736 
00737               ASSERT(alphaAngleMod >= 0.0f);
00738 
00739               const float angleMod =
00740                 alphaAngleMod * (1.0f - angDistAB / itsColinearDiff.getVal());
00741 
00742               weight(a, b, x, y) =
00743                 -1.0f * itsSupMult.getVal() * ((angleMod + distanceMod)/2);
00744             }
00745           else // "butterfly" excitation
00746             {
00747               // Find the angle between the line pointing to the other
00748               // unit and the alignment of that unit.
00749               const float angDistBC = angDist(connectingAngle, angle(b));
00750 
00751               const float betaAngleMod = 1.0f - angDistBC/itsAngleDropOff.getVal();
00752 
00753               if (betaAngleMod <= itsValueCutoff.getVal())
00754                 continue;
00755 
00756               const float alphaAngleMod = 1.0f - angDistAC/itsAngleDropOff.getVal();
00757               ASSERT(alphaAngleMod >= 0.0f);
00758 
00759               // Alpha/beta combo for excitation.
00760               const float angleMod = alphaAngleMod * betaAngleMod;
00761 
00762               if (angleMod <= itsValueCutoff.getVal())
00763                 continue;
00764 
00765               const float distanceMod = 1.0f - distance/(itsNeuronExSize.getVal()/2);
00766 
00767               if (distanceMod <= itsValueCutoff.getVal())
00768                 continue;
00769 
00770               weight(a, b, x, y) = (angleMod + distanceMod)/2;
00771             }
00772         }
00773       }
00774     }
00775   }
00776 }
00777 
00778 // ######################################################################
00779 // ######################################################################
00780 // ##### ContourConnectionBraun class
00781 // ######################################################################
00782 // ######################################################################
00783 
00784 // based on Achim Braun's unpublished "Contour integration in striate
00785 // cortex: a model" (submitted to Neural Computation (1996))
00786 class ContourConnectionBraun : public ContourConnection
00787 {
00788 public:
00789   ContourConnectionBraun(OptionManager& manager,
00790                          bool doOneNormalize);
00791 
00792   virtual ~ContourConnectionBraun() {}
00793 
00794 private:
00795   virtual void setupConnectionWeights();
00796 
00797   OModelParam<float> itsPeakDistance;
00798   OModelParam<float> itsExcitStrength;
00799   OModelParam<float> itsInhibStrength;
00800   OModelParam<float> itsBeta;
00801   OModelParam<bool>  itsOrthoIntxn;
00802 
00803   const bool itsDoOneNormalize;
00804 };
00805 
00806 // Used by: ContourConnectionBraun
00807 static const ModelOptionDef OPT_ContourConnectionPeakDistance =
00808   { MODOPT_ARG(float), "ContourConnectionPeakDistance", &MOC_CHANNEL, OPTEXP_CORE,
00809     "PeakDistance parameter for the Braun kernel in the contour channel "
00810     "(corresponds to old ParamMap parameter 'peakDistance=2.0')",
00811     "contour-cxn-peak-distance", '\0', "<float>", "2.0" };
00812 
00813 // Used by: ContourConnectionBraun
00814 static const ModelOptionDef OPT_ContourConnectionExcitStrength =
00815   { MODOPT_ARG(float), "ContourConnectionExcitStrength", &MOC_CHANNEL, OPTEXP_CORE,
00816     "ExcitStrength parameter for the Braun kernel in the contour channel "
00817     "(corresponds to old ParamMap parameter 'excitStrength=1.7')",
00818     "contour-cxn-excit-strength", '\0', "<float>", "1.7" };
00819 
00820 // Used by: ContourConnectionBraun
00821 static const ModelOptionDef OPT_ContourConnectionInhibStrength =
00822   { MODOPT_ARG(float), "ContourConnectionInhibStrength", &MOC_CHANNEL, OPTEXP_CORE,
00823     "InhibStrength parameter for the Braun kernel in the contour channel "
00824     "(corresponds to old ParamMap parameter 'inhibStrength=-0.3')",
00825     "contour-cxn-inhib-strength", '\0', "<float>", "-0.3" };
00826 
00827 // Used by: ContourConnectionBraun
00828 static const ModelOptionDef OPT_ContourConnectionBeta =
00829   { MODOPT_ARG(float), "ContourConnectionBeta", &MOC_CHANNEL, OPTEXP_CORE,
00830     "Beta parameter for the Braun kernel in the contour channel "
00831     "(corresponds to old ParamMap parameter 'beta=0.07')",
00832     "contour-cxn-beta", '\0', "<float>", "0.07" };
00833 
00834 // Used by: ContourConnectionBraun
00835 static const ModelOptionDef OPT_ContourConnectionOrthoIntxn =
00836   { MODOPT_FLAG, "ContourConnectionOrthoIntxn", &MOC_CHANNEL, OPTEXP_CORE,
00837     "Whether to include (inhibitory) interactions between (near-)orthogonal "
00838     "orientations in the contour channel "
00839     "(corresponds to old ParamMap parameter 'orthoIntxn=0')",
00840     "contour-ortho-interaction", '\0', "", "false" };
00841 
00842 ContourConnectionBraun::
00843 ContourConnectionBraun(OptionManager& manager,
00844                        bool doOneNormalize)
00845   :
00846   ContourConnection(manager),
00847   itsPeakDistance(&OPT_ContourConnectionPeakDistance, this),
00848   itsExcitStrength(&OPT_ContourConnectionExcitStrength, this),
00849   itsInhibStrength(&OPT_ContourConnectionInhibStrength, this),
00850   itsBeta(&OPT_ContourConnectionBeta, this),
00851   itsOrthoIntxn(&OPT_ContourConnectionOrthoIntxn, this),
00852   itsDoOneNormalize(doOneNormalize)
00853 {}
00854 
00855 void ContourConnectionBraun::setupConnectionWeights()
00856 {
00857   ASSERT(itsW > 0);
00858   ASSERT(itsH > 0);
00859 
00860   const int nori = itsNumAngles.getVal();
00861   const Point2D<int> center = this->getCenter();
00862 
00863   for (int x = 0; x < itsW; ++x) {
00864     for (int y = 0; y < itsH; ++y) {
00865 
00866       const float distance =
00867         sqrt( (center.i-x)*(center.i-x) + (center.j-y)*(center.j-y) );
00868 
00869       const float distRatio = distance/itsPeakDistance.getVal();
00870 
00871       const float rho = pow(distRatio * exp(1.0 - distRatio), 2.0);
00872 
00873       const float connectingAngle = (180/M_PI) * atan2(center.j-y, x-center.i);
00874 
00875       for (int a = 0; a < nori; ++a) {
00876 
00877         const float angDistAC = angDist(angle(a), connectingAngle);
00878 
00879         for (int b = 0; b < nori; ++b) {
00880 
00881           const float angDistAB = angDist(angle(a), angle(b));
00882           const float angDistBC = angDist(angle(b), connectingAngle);
00883 
00884           const float avgAngle = fabs(angle(a)-angle(b)) > 90.0f
00885             ? (angle(a)+angle(b)-180.0f) / 2.0f
00886             : (angle(a)+angle(b)) / 2.0f;
00887 
00888           const bool colinear =
00889             angDistAB < 45.0f && angDistAC < 45.0f && angDistBC < 45.0f;
00890 
00891           const float psi = colinear
00892             ? exp(-itsBeta.getVal() * angDist(avgAngle, connectingAngle))
00893             : 0.0f;
00894 
00895           const float excitPart = rho * psi * itsExcitStrength.getVal();
00896 
00897           const bool haveInhib = (angDistAB < 45.0f || itsOrthoIntxn.getVal());
00898 
00899           const float inhibPart =
00900             haveInhib
00901             ? rho * itsInhibStrength.getVal()
00902             : 0.0f;
00903 
00904           weight(a, b, x, y) = excitPart + inhibPart;
00905           itsInhib[index(a,b,x,y)] = inhibPart;
00906           itsExcit[index(a,b,x,y)] = excitPart;
00907         }
00908       }
00909     }
00910   }
00911 
00912   if (itsDoOneNormalize)
00913     {
00914       float maxw = 0.0f;
00915 
00916       ASSERT(itsNumWeights > 0);
00917 
00918       for (int i = 0; i < itsNumWeights; ++i)
00919         {
00920           if (fabs(itsWeights[i]) > maxw) maxw = fabs(itsWeights[i]);
00921         }
00922 
00923       if (maxw > 0.0f)
00924         {
00925           for (int i = 0; i < itsNumWeights; ++i)
00926             {
00927               itsWeights[i] /= maxw;
00928             }
00929         }
00930     }
00931 }
00932 
00933 // ######################################################################
00934 // ######################################################################
00935 // ##### ContourLayer class
00936 // ######################################################################
00937 // ######################################################################
00938 
00939 //! Represents a single scale band in which contour facilitation is run.
00940 class ContourLayer : public ModelComponent
00941 {
00942 public:
00943   ContourLayer(OptionManager& mgr,
00944                const std::string& descrName,
00945                const std::string& tagName);
00946 
00947   virtual ~ContourLayer();
00948 
00949   Image<float> compute(const ImageSet<float>& input,
00950                        const ContourConnection& connection,
00951                        const std::string& saveSuffix,
00952                        int normFlags,
00953                        SaveSet& save,
00954                        const unsigned int niter,
00955                        const Dims& origdims);
00956 
00957 protected:
00958   OModelParam<bool> itsSaveLayerOutput;
00959   OModelParam<bool> itsSaveLayerDetails;
00960 
00961 private:
00962   virtual void startNewInput(const ImageSet<float>& input,
00963                              const ContourConnection& connection) = 0;
00964 
00965   // Returns this iteration's saliency map
00966   virtual Image<float> iterate(const ContourConnection& connection,
00967                                const char* saveSuffix,
00968                                int normFlags,
00969                                SaveSet& save) = 0;
00970 };
00971 
00972 // Used by: ContourLayer
00973 static const ModelOptionDef OPT_ContourLayerSaveOutput =
00974   { MODOPT_FLAG, "ContourLayerSaveOutput", &MOC_CHANNEL, OPTEXP_SAVE,
00975     "Whether to save per-iteration output from individual scale bands "
00976     "in the contour channel",
00977     "save-contour-layer-output", '\0', "", "false" };
00978 
00979 // Used by: ContourLayer
00980 static const ModelOptionDef OPT_ContourLayerSaveDetails =
00981   { MODOPT_FLAG, "ContourLayerSaveDetails", &MOC_CHANNEL, OPTEXP_SAVE,
00982     "Whether to save detailed intermediate maps from individual scale "
00983     "bands in the contour channel",
00984     "save-contour-layer-details", '\0', "", "false" };
00985 
00986 ContourLayer::ContourLayer(OptionManager& mgr,
00987                            const std::string& descrName,
00988                            const std::string& tagName)
00989   :
00990   ModelComponent(mgr, descrName, tagName),
00991   itsSaveLayerOutput(&OPT_ContourLayerSaveOutput, this),
00992   itsSaveLayerDetails(&OPT_ContourLayerSaveDetails, this)
00993 {}
00994 
00995 ContourLayer::~ContourLayer() {}
00996 
00997 Image<float> ContourLayer::compute(const ImageSet<float>& input,
00998                                    const ContourConnection& connection,
00999                                    const std::string& saveSuffix,
01000                                    int normFlags,
01001                                    SaveSet& save,
01002                                    const unsigned int niter,
01003                                    const Dims& origdims)
01004 {
01005   this->startNewInput(input, connection);
01006 
01007   Image<float> salmap;
01008 
01009   for (unsigned int iter = 0; iter < niter; ++iter)
01010     {
01011       LINFO("scale %s, iteration %u / %u ... ",
01012             saveSuffix.c_str(), iter+1, niter);
01013 
01014       const std::string sfx2 =
01015         sformat("%s.i-%02u", saveSuffix.c_str(), iter);
01016 
01017       salmap = this->iterate(connection, sfx2.c_str(), normFlags,
01018                              save);
01019 
01020       if (itsSaveLayerOutput.getVal())
01021         save.add(sformat("potential.%s", sfx2.c_str()),
01022                  GenericFrame(rescale(salmap, origdims),
01023                               normFlags));
01024     }
01025 
01026   return salmap;
01027 }
01028 
01029 // ######################################################################
01030 // ######################################################################
01031 // ##### ContourLayerDynamic class
01032 // ######################################################################
01033 // ######################################################################
01034 
01035 //! Based on Achim Braun's unpublished Neural Computation paper.
01036 class ContourLayerDynamic : public ContourLayer
01037 {
01038 private:
01039   OModelParam<float>    itsTimestep;
01040   OModelParam<float>    itsFilterThresh;
01041 
01042   ImageSet<float>       itsInput;
01043   ImageSet<float>       itsNeuronsCur;
01044   ImageSet<float>       itsNeuronsTempSpace;
01045 
01046 public:
01047   ContourLayerDynamic(OptionManager& mgr);
01048 
01049   virtual ~ContourLayerDynamic();
01050 
01051 private:
01052   virtual void startNewInput(const ImageSet<float>& input,
01053                              const ContourConnection& connection);
01054 
01055   virtual Image<float> iterate(const ContourConnection& connection,
01056                                const char* saveSuffix,
01057                                int normFlags,
01058                                SaveSet& save);
01059 };
01060 
01061 // ######################################################################
01062 // ######################################################################
01063 // ##### ContourLayerDynamic implementation
01064 // ######################################################################
01065 // ######################################################################
01066 
01067 // Used by: ContourLayerDynamic
01068 static const ModelOptionDef OPT_ContourLayerDynamicTimestep =
01069   { MODOPT_ARG(float), "ContourLayerDynamicTimestep", &MOC_CHANNEL, OPTEXP_CORE,
01070     "Timestep for the contour channel with --contour-dynamics-type=Dynamic "
01071     "(corresponds to old ParamMap parameter 'timestep=0.5')",
01072     "contour-layer-timestep", '\0', "<float>", "0.5" };
01073 
01074 // Used by: ContourLayerDynamic
01075 static const ModelOptionDef OPT_ContourLayerDynamicFilterThresh =
01076   { MODOPT_ARG(float), "ContourLayerDynamicFilterThresh", &MOC_CHANNEL, OPTEXP_CORE,
01077     "Timestep for the contour channel with --contour-dynamics-type=Dynamic "
01078     "(corresponds to old ParamMap parameter 'filterThresh=0.0001')",
01079     "contour-layer-filter-thresh", '\0', "<float>", "0.0001" };
01080 
01081 ContourLayerDynamic::ContourLayerDynamic(OptionManager& mgr)
01082   :
01083   ContourLayer(mgr, "Dynamic Contour Layer", "ContourLayerDynamic"),
01084   itsTimestep           ( &OPT_ContourLayerDynamicTimestep, this),
01085   itsFilterThresh       ( &OPT_ContourLayerDynamicFilterThresh, this),
01086   itsInput              ( ),
01087   itsNeuronsCur         ( ),
01088   itsNeuronsTempSpace   ( )
01089 {}
01090 
01091 ContourLayerDynamic::~ContourLayerDynamic()
01092 {}
01093 
01094 void ContourLayerDynamic::startNewInput(const ImageSet<float>& input,
01095                                         const ContourConnection& cxn)
01096 {
01097   ASSERT(rangeOf(input).max() == 1.0f);
01098 
01099   itsInput = input;
01100   itsNeuronsCur = ImageSet<float>(cxn.numAngles(), input[0].getDims());
01101   itsNeuronsTempSpace = ImageSet<float>();
01102 }
01103 
01104 Image<float> ContourLayerDynamic::iterate(const ContourConnection& cxn,
01105                                           const char* saveSuffix,
01106                                           int normFlags,
01107                                           SaveSet& save)
01108 {
01109   ASSERT(cxn.started());
01110 
01111   const Dims dims = itsInput[0].getDims();
01112 
01113   // fetch these values once and store them so that we don't need
01114   // repeated getVal() calls within inner loops below
01115   const int nori = cxn.numAngles();
01116   const float timestep = itsTimestep.getVal();
01117   const float fthresh = itsFilterThresh.getVal();
01118   const Dims kerndims = cxn.getDims();
01119   const Point2D<int> kerncenter = cxn.getCenter();
01120 
01121   itsNeuronsTempSpace = itsNeuronsCur;
01122   doAddWeighted(itsNeuronsTempSpace, itsInput, timestep);
01123 
01124   itsNeuronsTempSpace *= (1.0f - timestep); // electrical leak
01125 
01126   // We set up these arrays of iterators ahead of time so that we can avoid
01127   // more costly image-indexing operations inside the inner loop below.
01128   Image<float>::const_iterator input_itrs[nori];
01129   Image<float>::const_iterator prev_charge_itrs[nori];
01130   Image<float>::iterator       charge_itrs[nori];
01131 
01132   Image<float> exc[nori];
01133   Image<float> inh[nori];
01134   Image<float> chg[nori];
01135 
01136   for (int b = 0; b < nori; ++b)
01137     {
01138       input_itrs[b] = itsInput[b].begin();
01139       prev_charge_itrs[b] = itsNeuronsCur[b].begin();
01140       charge_itrs[b] = itsNeuronsTempSpace[b].beginw();
01141 
01142       exc[b] = Image<float>(itsInput[b].getDims(), ZEROS);
01143       inh[b] = Image<float>(itsInput[b].getDims(), ZEROS);
01144       chg[b] = Image<float>(itsInput[b].getDims(), ZEROS);
01145     }
01146 
01147   // Do the three-dimensional x/y/angle convolution
01148 
01149   for (int angle_a = 0; angle_a < nori; ++angle_a)
01150     {
01151       Image<float>& charges_a = itsNeuronsCur[angle_a];
01152 
01153       Image<float>::const_iterator input_a_itr = itsInput[angle_a].begin();
01154 
01155       Image<float>::const_iterator charges_a_itr = charges_a.begin();
01156 
01157       for (int a_y = 0; a_y < dims.h(); ++a_y)
01158         {
01159           for (int a_x = 0;
01160                a_x < dims.w();
01161                ++a_x, ++input_a_itr, ++charges_a_itr)
01162             {
01163               const float input_a = *input_a_itr;
01164 
01165               if (input_a <= fthresh)
01166                 continue;
01167 
01168               const float charge_a = *charges_a_itr;
01169 
01170               const float excit_a = excitFiring(charge_a);
01171 
01172               const float inhib_a = inhibFiring(excit_a);
01173 
01174               chg[angle_a].setVal(a_x, a_y, charge_a);
01175               exc[angle_a].setVal(a_x, a_y, excit_a);
01176               inh[angle_a].setVal(a_x, a_y, inhib_a);
01177 
01178               for (int rf_x = 0; rf_x < kerndims.w(); ++rf_x)
01179                 {
01180                   // Current position plus its field - center
01181                   const int b_x = a_x + (rf_x-kerncenter.i);
01182 
01183                   if (b_x < 0 || b_x >= dims.w())
01184                     continue;
01185 
01186                   for (int rf_y = 0; rf_y < kerndims.h(); ++rf_y)
01187                     {
01188                       const int b_y = a_y - (rf_y-kerncenter.j);
01189 
01190                       if (b_y < 0 || b_y >= dims.h()) // stay inside of image
01191                         continue;
01192 
01193                       const int offset = b_x + b_y*dims.w();
01194 
01195                       const float* weights_ptr =
01196                         cxn.weightAXY(angle_a, rf_x, rf_y);
01197 
01198                       const float* const w_excit_ptr =
01199                         cxn.excitAXY(angle_a, rf_x, rf_y);
01200 
01201                       const float* const w_inhib_ptr =
01202                         cxn.inhibAXY(angle_a, rf_x, rf_y);
01203 
01204                       for (int angle_b = 0; angle_b < nori; ++angle_b)
01205                         {
01206                           const float cxn_strength = *weights_ptr++;
01207 
01208                           if (cxn_strength == 0.0f)
01209                             continue;
01210 
01211                           const float input_b = input_itrs[angle_b][offset];
01212 
01213                           const float charge_b = prev_charge_itrs[angle_b][offset];
01214 
01215                           // excitation is modulated by fast plasticity
01216                           const float excit =
01217                             excit_a * input_b * w_excit_ptr[angle_b];
01218 
01219                           // suppression is modulated by group activity
01220                           const float inhib =
01221                             inhib_a * charge_b * w_inhib_ptr[angle_b];
01222 
01223                           // add energy to salmap
01224                           charge_itrs[angle_b][offset] +=
01225                             timestep * (excit + inhib);
01226                         }
01227                     }
01228                 }
01229             }
01230         }
01231     }
01232 
01233   if (itsSaveLayerDetails.getVal())
01234     {
01235       const Image<float> all_chg = concatArray(&chg[0], nori, 4);
01236       const Image<float> all_exc = concatArray(&exc[0], nori, 4);
01237       const Image<float> all_inh = concatArray(&inh[0], nori, 4);
01238 
01239       save.add(sformat("all_chg.%s", saveSuffix), GenericFrame(all_chg, normFlags));
01240       save.add(sformat("all_exc.%s", saveSuffix), GenericFrame(all_exc, normFlags));
01241       save.add(sformat("all_inh.%s", saveSuffix), GenericFrame(all_inh, normFlags));
01242     }
01243 
01244   itsNeuronsCur.swap(itsNeuronsTempSpace);
01245 
01246   const Image<float> activation = sum(itsNeuronsCur);
01247 
01248   printStats(activation, "activation");
01249 
01250   return activation;
01251 }
01252 
01253 // ######################################################################
01254 // ######################################################################
01255 // ##### ContourLayerStatic class
01256 // ######################################################################
01257 // ######################################################################
01258 
01259 //! Based on CINNIC (Nathan Mundhenk).
01260 class ContourLayerStatic : public ContourLayer
01261 {
01262 private:
01263   OModelParam<float>    itsFastPlast;
01264   OModelParam<float>    itsGroupBottom;
01265   OModelParam<float>    itsGroupTop;
01266   OModelParam<int>      itsGroupSize;
01267   OModelParam<float>    itsLeak;
01268   OModelParam<float>    itsSuppressionAdd;
01269   OModelParam<float>    itsSuppressionSub;
01270   OModelParam<float>    itsSigmoidThresh;
01271   OModelParam<float>    itsFilterThresh;
01272 
01273   ImageSet<float>       itsInput;
01274 
01275   Image<float>          itsSalmap;
01276 
01277   Image<float>          itsEnergyMap;
01278 
01279   ImageSet<float>       itsNeuronsCur;
01280   ImageSet<float>       itsNeuronsTempSpace;
01281 
01282   Image<float>          itsGroupModMap;
01283 
01284   void convolveAll(const ContourConnection& cxn);
01285 
01286 public:
01287   ContourLayerStatic(OptionManager& mgr);
01288 
01289   virtual ~ContourLayerStatic();
01290 
01291 private:
01292   virtual void startNewInput(const ImageSet<float>& input,
01293                              const ContourConnection& connection);
01294 
01295   virtual Image<float> iterate(const ContourConnection& connection,
01296                                const char* saveSuffix,
01297                                int normFlags,
01298                                SaveSet& save);
01299 };
01300 
01301 // ######################################################################
01302 // ######################################################################
01303 // ##### ContourLayerStatic implementation
01304 // ######################################################################
01305 // ######################################################################
01306 
01307 // Used by: ContourLayerStatic
01308 static const ModelOptionDef OPT_CinnicLayerFastPlast =
01309   { MODOPT_ARG(float), "CinnicLayerFastPlast", &MOC_CHANNEL, OPTEXP_CORE,
01310     "Fast-plasticity strength under --contour-dynamics-type=Static "
01311     "(corresponds to old ParamMap parameter 'fastPlast=7.2077')",
01312     "cinnic-fast-plast", '\0', "<float>", "7.2077" };
01313 
01314 // Used by: ContourLayerStatic
01315 static const ModelOptionDef OPT_CinnicLayerGroupBottom =
01316   { MODOPT_ARG(float), "CinnicLayerGroupBottom", &MOC_CHANNEL, OPTEXP_CORE,
01317     "Threshold beneath which group suppression is removed, under "
01318     "--contour-dynamics-type=Static "
01319     "(corresponds to old ParamMap parameter 'groupAvgBottom=0.0')",
01320     "cinnic-group-bottom", '\0', "<float>", "0.0" };
01321 
01322 // Used by: ContourLayerStatic
01323 static const ModelOptionDef OPT_CinnicLayerGroupTop =
01324   { MODOPT_ARG(float), "CinnicLayerGroupTop", &MOC_CHANNEL, OPTEXP_CORE,
01325     "Threshold above which group suppression is added, under "
01326     "--contour-dynamics-type=Static "
01327     "(corresponds to old ParamMap parameter 'groupAvgTop=0.03125')",
01328     "cinnic-group-top", '\0', "<float>", "0.03125" };
01329 
01330 // Used by: ContourLayerStatic
01331 static const ModelOptionDef OPT_CinnicLayerGroupSize =
01332   { MODOPT_ARG(int), "CinnicLayerGroupSize", &MOC_CHANNEL, OPTEXP_CORE,
01333     "Width of low-pass kernel used to compute group suppression, under "
01334     "--contour-dynamics-type=Static "
01335     "(corresponds to old ParamMap parameter 'groupSize=15')",
01336     "cinnic-group-size", '\0', "<int>", "15" };
01337 
01338 // Used by: ContourLayerStatic
01339 static const ModelOptionDef OPT_CinnicLayerLeak =
01340   { MODOPT_ARG(float), "CinnicLayerLeak", &MOC_CHANNEL, OPTEXP_CORE,
01341     "Amount of global leak during each iteration under "
01342     "--contour-dynamics-type=Static "
01343     "(corresponds to old ParamMap parameter 'leakFactor=0.5')",
01344     "cinnic-leak", '\0', "<float>", "0.5" };
01345 
01346 // Used by: ContourLayerStatic
01347 static const ModelOptionDef OPT_CinnicLayerSuppressionAdd =
01348   { MODOPT_ARG(float), "CinnicLayerSuppressionAdd", &MOC_CHANNEL, OPTEXP_CORE,
01349     "Scale factor for group suppression added where the rate of change "
01350     "exceeds --cinnic-group-top, --contour-dynamics-type=Static "
01351     "(corresponds to old ParamMap parameter 'suppressionAdd=1.0')",
01352     "cinnic-suppression-add", '\0', "<float>", "1.0" };
01353 
01354 // Used by: ContourLayerStatic
01355 static const ModelOptionDef OPT_CinnicLayerSuppressionSub =
01356   { MODOPT_ARG(float), "CinnicLayerSuppressionSub", &MOC_CHANNEL, OPTEXP_CORE,
01357     "Scale factor for group suppression removed where the rate of change "
01358     "is less than --cinnic-group-bottom, --contour-dynamics-type=Static "
01359     "(corresponds to old ParamMap parameter 'suppressionSub=1.0')",
01360     "cinnic-suppression-sub", '\0', "<float>", "1.0" };
01361 
01362 // Used by: ContourLayerStatic
01363 static const ModelOptionDef OPT_CinnicLayerSigmoidThresh =
01364   { MODOPT_ARG(float), "CinnicLayerSigmoidThresh", &MOC_CHANNEL, OPTEXP_CORE,
01365     "Threshold for the output sigmoid nonlinearity, under "
01366     "--contour-dynamics-type=Static "
01367     "(corresponds to old ParamMap parameter 'sigmoidThresh=9.0')",
01368     "cinnic-sigmoid-thresh", '\0', "<float>", "9.0" };
01369 
01370 // Used by: ContourLayerStatic
01371 static const ModelOptionDef OPT_CinnicLayerFilterThresh =
01372   { MODOPT_ARG(float), "CinnicLayerFilterThresh", &MOC_CHANNEL, OPTEXP_CORE,
01373     "Filter values less than this threshold will be forced to 0, under "
01374     "--contour-dynamics-type=Static "
01375     "(corresponds to old ParamMap parameter 'filterThresh=0.0001')",
01376     "cinnic-filter-thresh", '\0', "<float>", "0.0001" };
01377 
01378 ContourLayerStatic::ContourLayerStatic(OptionManager& mgr)
01379   :
01380   ContourLayer(mgr, "Static Contour Layer", "ContourLayerStatic"),
01381   itsFastPlast          ( &OPT_CinnicLayerFastPlast, this ),
01382   itsGroupBottom        ( &OPT_CinnicLayerGroupBottom, this ),
01383   itsGroupTop           ( &OPT_CinnicLayerGroupTop, this ),
01384   itsGroupSize          ( &OPT_CinnicLayerGroupSize, this ),
01385   itsLeak               ( &OPT_CinnicLayerLeak, this ),
01386   itsSuppressionAdd     ( &OPT_CinnicLayerSuppressionAdd, this ),
01387   itsSuppressionSub     ( &OPT_CinnicLayerSuppressionSub, this ),
01388   itsSigmoidThresh      ( &OPT_CinnicLayerSigmoidThresh, this ),
01389   itsFilterThresh       ( &OPT_CinnicLayerFilterThresh, this ),
01390   itsInput              ( ),
01391   itsSalmap             ( ),
01392   itsEnergyMap          ( ),
01393   itsNeuronsCur         ( ),
01394   itsNeuronsTempSpace   ( ),
01395   itsGroupModMap        ( )
01396 {}
01397 
01398 ContourLayerStatic::~ContourLayerStatic()
01399 {}
01400 
01401 void ContourLayerStatic::startNewInput(const ImageSet<float>& input,
01402                                        const ContourConnection& cxn)
01403 {
01404   ASSERT(rangeOf(input).max() == 1.0f);
01405 
01406   itsInput = input;
01407   itsSalmap = Image<float>(input[0].getDims(), ZEROS);
01408   itsEnergyMap = Image<float>(input[0].getDims(), ZEROS);
01409   itsNeuronsCur = ImageSet<float>(cxn.numAngles(), input[0].getDims());
01410   itsNeuronsTempSpace = ImageSet<float>(cxn.numAngles(), input[0].getDims());
01411   itsGroupModMap = Image<float>(input[0].getDims(), ZEROS);
01412   itsGroupModMap.clear(1.0f);
01413 }
01414 
01415 void ContourLayerStatic::convolveAll(const ContourConnection& cxn)
01416 {
01417   ASSERT(cxn.started());
01418 
01419   const Dims dims = itsInput[0].getDims();
01420 
01421   doClear(itsNeuronsTempSpace, 0.0f);
01422 
01423   // For monitoring fast-plasticity behavior
01424   Accum fpTotal;
01425   Accum fpHi;
01426   Accum fpLo;
01427 
01428   // fetch these values once and store them so that we don't need
01429   // repeated getVal() calls during inner loops below
01430   const int nori = cxn.numAngles();
01431   const float cfgFastPlast = itsFastPlast.getVal();
01432   const float cfgFilterThresh = itsFilterThresh.getVal();
01433   const Dims kerndims = cxn.getDims();
01434   const Point2D<int> kerncenter = cxn.getCenter();
01435 
01436   // We set up these arrays of iterators ahead of time so that we can avoid
01437   // more costly image-indexing operations inside the inner loop below.
01438   Image<float>::iterator       charges_b_itrs[nori];
01439   Image<float>::const_iterator input_b_itrs[nori];
01440 
01441   for (int b = 0; b < nori; ++b)
01442     {
01443       charges_b_itrs[b] = itsNeuronsTempSpace[b].beginw();
01444       input_b_itrs[b] = itsInput[b].begin();
01445     }
01446 
01447   // For monitoring execution speed
01448   CpuTimer t1, t2;
01449 
01450   // For monitoring progress through the convolution
01451   int c = 0;
01452   const int maxc = nori * dims.h() * dims.w();
01453 
01454   // Here's the actual three-dimensional x/y/angle convolution
01455 
01456   for (int angle_a = 0; angle_a < nori; ++angle_a)
01457     {
01458       Image<float>& charges_a = itsNeuronsCur[angle_a];
01459 
01460       Image<float>::const_iterator input_a_itr = itsInput[angle_a].begin();
01461 
01462       Image<float>::const_iterator charges_a_itr = charges_a.begin();
01463 
01464       for (int a_y = 0; a_y < dims.h(); ++a_y)
01465         {
01466           for (int a_x = 0;
01467                a_x < dims.w();
01468                ++a_x, ++input_a_itr, ++charges_a_itr)
01469             {
01470               const float input_a = *input_a_itr;
01471 
01472               if (input_a <= cfgFilterThresh)
01473                 continue;
01474 
01475               const float charge_a = *charges_a_itr;
01476 
01477               // Fast plasticity based on potential at sending neuron
01478               float fastPlastMod = charge_a * cfgFastPlast;
01479               fpTotal.add(fastPlastMod);
01480               if      (fastPlastMod < 1.0f) { fpLo.add(fastPlastMod); fastPlastMod = 1.0f; }
01481               else if (fastPlastMod > 5.0f) { fpHi.add(fastPlastMod); fastPlastMod = 5.0f; }
01482 
01483               const float groupMod = itsGroupModMap.getVal(a_x, a_y);
01484 
01485               for (int rf_x = 0; rf_x < kerndims.w(); ++rf_x)
01486                 {
01487                   // Current position plus its field - center
01488                   const int b_x = a_x + (rf_x-kerncenter.i);
01489 
01490                   if (b_x < 0 || b_x >= dims.w())
01491                     continue;
01492 
01493                   for (int rf_y = 0; rf_y < kerndims.h(); ++rf_y)
01494                     {
01495                       const int b_y = a_y - (rf_y-kerncenter.j);
01496 
01497                       if (b_y < 0 || b_y >= dims.h()) // stay inside of image
01498                         continue;
01499 
01500                       const int offset = b_x + b_y*dims.w();
01501 
01502                       const float* weights_ptr =
01503                         cxn.weightAXY(angle_a, rf_x, rf_y);
01504 
01505                       for (int angle_b = 0; angle_b < nori; ++angle_b)
01506                         {
01507                           const float cxn_strength = *weights_ptr++;
01508 
01509                           if (cxn_strength == 0.0f)
01510                             continue;
01511 
01512                           const float input_b = input_b_itrs[angle_b][offset];
01513 
01514                           if (input_b <= cfgFilterThresh)
01515                             continue;
01516 
01517                           // excitation is modulated by fast plasticity
01518                           // suppression is modulated by group activity
01519                           float charge =
01520                             input_a * input_b
01521                             * cxn_strength * fastPlastMod;
01522 
01523                           if (cxn_strength < 0)
01524                             charge *= groupMod;
01525 
01526                           // add energy to salmap
01527                           charges_b_itrs[angle_b][offset] += charge;
01528                         }
01529                     }
01530                 }
01531 
01532               ++c;
01533 
01534               t2.mark();
01535 
01536               if (t2.user_secs() > 20.0)
01537                 {
01538                   t1.mark();
01539                   t1.report("convolve");
01540                   t2.reset();
01541                   LINFO("%.2f%% complete", (100.0*c)/maxc);
01542                 }
01543             }
01544         }
01545     }
01546 
01547   itsNeuronsCur.swap(itsNeuronsTempSpace);
01548 
01549   LINFO("fast plast: total N (%d), avg (%.2f)",
01550         fpTotal.count(), fpTotal.mean());
01551 
01552   LINFO("fast plast: hi    N (%d), avg (%.2f)",
01553         fpHi.count(), fpHi.mean());
01554   LINFO("fast plast: lo    N (%d), avg (%.2f)",
01555         fpLo.count(), fpLo.mean());
01556 }
01557 
01558 Image<float> ContourLayerStatic::iterate(const ContourConnection& cxn,
01559                                          const char* saveSuffix,
01560                                          int normFlags,
01561                                          SaveSet& save)
01562 {
01563   // fetch these values once and store them so that we don't need
01564   // repeated getVal() calls during inner loops below
01565   const float cfgGroupBottom = itsGroupBottom.getVal();
01566   const float cfgGroupTop = itsGroupTop.getVal();
01567   const float cfgSuppressionAdd = itsSuppressionAdd.getVal();
01568   const float cfgSuppressionSub = itsSuppressionSub.getVal();
01569 
01570   convolveAll(cxn);
01571 
01572   doRectify(itsNeuronsCur);
01573 
01574   const Image<float> activation = sum(itsNeuronsCur);
01575 
01576   printStats(activation, "activation");
01577 
01578   // Update the salmap with (1) global leak, (2) adding the activation
01579   // maps for all orientations, (3) rectification.
01580   itsSalmap -= itsLeak.getVal();
01581   itsSalmap += activation;
01582 
01583   printStats(itsSalmap, "salmap");
01584 
01585   inplaceRectify(itsSalmap);
01586 
01587   // Save the old energy map in case we need to compute the energy
01588   // change for group suppression
01589   const Image<float> prevEnergyMap = itsEnergyMap;
01590 
01591   // Compute the energy map by remapping the salmap into the range [0,1]
01592   itsEnergyMap = sigmoid(itsSalmap, itsSigmoidThresh.getVal());
01593 
01594   printStats(itsEnergyMap, "energy");
01595 
01596   // Now modify suppression values
01597 
01598   Image<float> delta = lowPass(itsGroupSize.getVal(),
01599                                itsEnergyMap - prevEnergyMap);
01600 
01601   Image<float>::const_iterator delta_itr = delta.begin();
01602   Image<float>::iterator gmod_itr = itsGroupModMap.beginw();
01603   Image<float>::iterator stop = itsGroupModMap.endw();
01604 
01605   int numTooHigh = 0;
01606   int numTooLow = 0;
01607 
01608   while (gmod_itr != stop)
01609     {
01610       const float pctChange = *delta_itr;
01611 
01612       // If values are too big then add suppression...
01613       if (pctChange > cfgGroupTop)
01614         {
01615           *gmod_itr += cfgSuppressionAdd * (pctChange - cfgGroupTop);
01616           ++numTooHigh;
01617         }
01618 
01619       // ... if values are too small then remove suppression
01620       else if (pctChange < cfgGroupBottom)
01621         {
01622           *gmod_itr += cfgSuppressionSub * (pctChange - cfgGroupTop);
01623           ++numTooLow;
01624         }
01625 
01626       ++gmod_itr;
01627       ++delta_itr;
01628     }
01629 
01630   inplaceRectify(itsGroupModMap);
01631 
01632   LINFO("suppression: N (%d), + (%d), - (%d)",
01633         delta.getSize(), numTooHigh, numTooLow);
01634 
01635   if (itsSaveLayerDetails.getVal())
01636     save.add(sformat("gmod.%s", saveSuffix),
01637              GenericFrame(itsGroupModMap, normFlags));
01638 
01639   return itsEnergyMap;
01640 }
01641 
01642 // ######################################################################
01643 // ######################################################################
01644 // ##### ContourChannel class
01645 // ######################################################################
01646 // ######################################################################
01647 
01648 enum ContourChannelFilterStyle
01649   {
01650     CC_FILTER_STEERABLE = 1,
01651     CC_FILTER_GABOR = 2,
01652     CC_FILTER_GAUSSIAN_STEERABLE = 3,
01653   };
01654 
01655 std::string convertToString(const ContourChannelFilterStyle val)
01656 {
01657   switch (val)
01658     {
01659     case CC_FILTER_STEERABLE: return "Steerable"; break;
01660     case CC_FILTER_GABOR: return "Gabor"; break;
01661     case CC_FILTER_GAUSSIAN_STEERABLE: return "GaussianSteerable"; break;
01662     }
01663 
01664   LFATAL("invalid ContourChannelFilterStyle '%d'", int(val));
01665   /* can't happen */ return std::string();
01666 }
01667 
01668 void convertFromString(const std::string& str1, ContourChannelFilterStyle& val)
01669 {
01670   const std::string str = toLowerCase(str1);
01671 
01672   if      (str.compare("steerable") == 0) { val = CC_FILTER_STEERABLE; }
01673   else if (str.compare("gabor") == 0) { val = CC_FILTER_GABOR; }
01674   else if (str.compare("gaussiansteerable") == 0)  { val = CC_FILTER_GAUSSIAN_STEERABLE; }
01675   else
01676     conversion_error::raise<ContourChannelFilterStyle>(str1);
01677 }
01678 
01679 enum ContourChannelDynamicsType
01680   {
01681     CC_STATIC = 1,
01682     CC_DYNAMIC = 2
01683   };
01684 
01685 std::string convertToString(const ContourChannelDynamicsType val)
01686 {
01687   switch (val)
01688     {
01689     case CC_STATIC: return "Static"; break;
01690     case CC_DYNAMIC: return "Dynamic"; break;
01691     }
01692 
01693   LFATAL("invalid ContourChannelDynamicsType '%d'", int(val));
01694   /* can't happen */ return std::string();
01695 }
01696 
01697 void convertFromString(const std::string& str1, ContourChannelDynamicsType& val)
01698 {
01699   const std::string str = toLowerCase(str1);
01700 
01701   if      (str.compare("static") == 0) { val = CC_STATIC; }
01702   else if (str.compare("dynamic") == 0) { val = CC_DYNAMIC; }
01703   else
01704     conversion_error::raise<ContourChannelDynamicsType>(str1);
01705 }
01706 
01707 class ContourChannel : public ChannelBase
01708 {
01709 public:
01710   ContourChannel(OptionManager& mgr,
01711                  const std::string& saveprefix = "contourout");
01712   virtual ~ContourChannel();
01713 
01714   virtual bool outputAvailable() const;
01715 
01716   virtual Dims getMapDims() const;
01717 
01718   virtual uint numSubmaps() const;
01719 
01720   //! Not implemented for ContourChannel
01721   virtual Image<float> getSubmap(unsigned int index) const
01722   {
01723     LFATAL("getSubmap() not implemented for ContourChannel");
01724     return Image<float>(); // placate compiler with a return statement
01725   }
01726 
01727   virtual std::string getSubmapName(unsigned int index) const;
01728 
01729   virtual std::string getSubmapNameShort(unsigned int index) const;
01730 
01731   //! Not implemented for ContourChannel
01732   virtual void getFeatures(const Point2D<int>& locn,
01733                            std::vector<float>& mean) const
01734   {
01735     LFATAL("getFeatures() not implemented for ContourChannel");
01736   }
01737 
01738   virtual void getFeaturesBatch(std::vector<Point2D<int>*> *locn,
01739                                 std::vector<std::vector<float> > *mean,
01740                                 int *count) const
01741   {
01742     LFATAL("getFeatures() not implemented for ContourChannel");
01743   }
01744 
01745   virtual Image<float> getOutput();
01746 
01747   //! Save our various maps using a FrameOstream
01748   virtual void saveResults(const nub::ref<FrameOstream>& ofs);
01749 
01750 protected:
01751   virtual void paramChanged(ModelParamBase* param,
01752                             const bool valueChanged,
01753                             ParamClient::ChangeStatus* status);
01754 
01755   virtual void start1()
01756   {
01757     ChannelBase::start1();
01758     itsFrameIdx = 0;
01759     if (itsNumIterations.getVal() <= 0)
01760       LFATAL("expected positive value for --%s, but got %u",
01761              itsNumIterations.getOptionDef()->longoptname,
01762              itsNumIterations.getVal());
01763 
01764     if (itsNumScales.getVal() <= 0)
01765       LFATAL("expected positive value for --%s, but got %u",
01766              itsNumScales.getOptionDef()->longoptname,
01767              itsNumScales.getVal());
01768   }
01769 
01770   virtual void doInput(const InputFrame& inframe);
01771 
01772   virtual void killCaches();
01773 
01774   virtual void saveStats(const Image<float> img, const short idx);
01775 
01776 private:
01777   ContourChannel(const ContourChannel&);
01778   ContourChannel& operator=(const ContourChannel&);
01779 
01780   OModelParam<ContourConnectionType> itsCxnType;
01781   OModelParam<unsigned int>    itsNumIterations;
01782   OModelParam<unsigned int>    itsLowPassWidth;
01783   OModelParam<unsigned int>    itsFirstScale;
01784   OModelParam<unsigned int>    itsNumScales;
01785   OModelParam<Dims>            itsMaxInputDims;
01786   OModelParam<ContourChannelFilterStyle> itsFilterStyle;
01787   OModelParam<float>           itsGaborPeriod;
01788   OModelParam<unsigned int>    itsFilterReduction;
01789   OModelParam<ContourChannelDynamicsType> itsDynamicsType;
01790   OModelParam<MaxNormType>     itsNormType;
01791   OModelParam<int>             itsNormFlags;
01792   OModelParam<LevelSpec>       itsLevelSpec;
01793   OModelParam<int>             itsOutputBlur;
01794   OModelParam<bool>            itsSaveWeights;
01795   OModelParam<bool>            itsSaveFilterOutput;
01796   OModelParam<bool>            itsSaveOutput;
01797   const std::string            itsSavePrefix;
01798 
01799   //! Save basic single channel stats after combineSubMaps
01800   OModelParam<bool> itsGetSingleChannelStats;
01801 
01802   //! If saving stats, should we put each feature in its own file?
01803   OModelParam<bool> itsSaveStatsPerChannel;
01804 
01805   //! File name for single channel stats after combineSubMaps
01806   OModelParam<std::string> itsGetSingleChannelStatsFile;
01807 
01808   //! Tag name for single channel stats after combineSubMaps
01809   OModelParam<std::string> itsGetSingleChannelStatsTag;
01810 
01811   nub::ref<ContourConnection>  itsConnection;
01812   nub::ref<ContourLayer>       itsScaleBand;
01813 
01814   Image<float>                 itsRawInput;
01815   Image<float>                 itsFloatInput;
01816   Image<float>                 itsOutput;
01817 
01818   SaveSet                      itsSaveSet;
01819   uint                         itsFrameIdx; // for logging purposes
01820 };
01821 
01822 // ######################################################################
01823 // ######################################################################
01824 // ##### ContourChannel implementation
01825 // ######################################################################
01826 // ######################################################################
01827 
01828 // Used by: ContourChannel
01829 static const ModelOptionDef OPT_ContourConnectionType =
01830   { MODOPT_ARG(ContourConnectionType), "ContourConnectionType", &MOC_CHANNEL, OPTEXP_CORE,
01831     "Type of connection kernel to use in the contour channel "
01832     "(corresponds to old ParamMap parameter 'cxnType=3')",
01833     "contour-cxn-type", '\0', "<Cinnic|Braun|BraunNorm>", "BraunNorm" };
01834 
01835 // Used by: ContourChannel
01836 static const ModelOptionDef OPT_ContourChannelIterations =
01837   { MODOPT_ARG(unsigned int), "ContourChannelIterations", &MOC_CHANNEL, OPTEXP_CORE,
01838     "Number of iterations to run in the contour channel "
01839     "(corresponds to old ParamMap parameter 'iterations=3')",
01840     "contour-num-iterations", '\0', "<uint>", "3" };
01841 
01842 // Used by: ContourChannel
01843 static const ModelOptionDef OPT_ContourChannelLowPassWidth =
01844   { MODOPT_ARG(unsigned int), "ContourChannelLowPassWidth", &MOC_CHANNEL, OPTEXP_CORE,
01845     "Width of low-pass filter to use in the contour channel "
01846     "(corresponds to old ParamMap parameter 'lowPassWidth=9')",
01847     "contour-lowpass-width", '\0', "<uint>", "9" };
01848 
01849 // Used by: ContourChannel
01850 static const ModelOptionDef OPT_ContourChannelFirstScale =
01851   { MODOPT_ARG(unsigned int), "ContourChannelFirstScale", &MOC_CHANNEL, OPTEXP_CORE,
01852     "Lowest pyramid scale to use in the contour channel "
01853     "(corresponds to old ParamMap parameter 'firstScale=0')",
01854     "contour-first-scale", '\0', "<uint>", "0" };
01855 
01856 // Used by: ContourChannel
01857 static const ModelOptionDef OPT_ContourChannelNumScales =
01858   { MODOPT_ARG(unsigned int), "ContourChannelNumScales", &MOC_CHANNEL, OPTEXP_CORE,
01859     "Number of pyramid scale to use in the contour channel "
01860     "(corresponds to old ParamMap parameter 'scalesNumber=3')",
01861     "contour-num-scales", '\0', "<uint>", "3" };
01862 
01863 // Used by: ContourChannel
01864 static const ModelOptionDef OPT_ContourChannelMaxInputDims =
01865   { MODOPT_ARG(Dims), "ContourChannelMaxInputDims", &MOC_CHANNEL, OPTEXP_CORE,
01866     "If input to the contour channel is larger than these dims, then "
01867     "it will be rescaled down to fit "
01868     "(corresponds to old ParamMap parameters 'maxInputW=10000' and 'maxInputH=10000')",
01869     "contour-max-input-dims", '\0', "<w>x<h>", "10000x10000" };
01870 
01871 // Used by: ContourChannel
01872 static const ModelOptionDef OPT_ContourChannelFilterStyle =
01873   { MODOPT_ARG(ContourChannelFilterStyle), "ContourChannelFilterStyle", &MOC_CHANNEL, OPTEXP_CORE,
01874     "Type of orientation filtering to use in the contour channel "
01875     "(corresponds to old ParamMap parameter 'filterStyle=2')",
01876     "contour-filter-style", '\0', "<Steerable|Gabor|GaussianSteerable>", "Gabor" };
01877 
01878 // Used by: ContourChannel
01879 static const ModelOptionDef OPT_ContourChannelGaborPeriod =
01880   { MODOPT_ARG(float), "ContourChannelGaborPeriod", &MOC_CHANNEL, OPTEXP_CORE,
01881     "Period of the sine-wave component of the gabor filter used in the "
01882     "contour channel "
01883     "(corresponds to old ParamMap parameter 'gaborPeriod=5.0')",
01884     "contour-gabor-width", '\0', "<float>", "5.0" };
01885 
01886 // Used by: ContourChannel
01887 static const ModelOptionDef OPT_ContourChannelFilterReduction =
01888   { MODOPT_ARG(unsigned int), "ContourChannelFilterReduction", &MOC_CHANNEL, OPTEXP_CORE,
01889     "Number of octaves by which to downsize the oriented filter outputs "
01890     "in the contour channel "
01891     "(corresponds to old ParamMap parameter 'filterReduction=1')",
01892     "contour-filter-reduction", '\0', "<uint>", "1" };
01893 
01894 // Used by: ContourChannel
01895 static const ModelOptionDef OPT_ContourChannelDynamicsType =
01896   { MODOPT_ARG(ContourChannelDynamicsType), "ContourChannelDynamicsType", &MOC_CHANNEL, OPTEXP_CORE,
01897     "The type of iterative dynamics to use in the contour channel "
01898     "(corresponds to old ParamMap parameter 'dynamicsType=1')",
01899     "contour-dynamics-type", '\0', "<Static|Dynamic>", "Static" };
01900 
01901 // Used by: ContourChannel
01902 static const ModelOptionDef OPT_ContourChannelNormFlags =
01903   { MODOPT_ARG(int), "ContourChannelNormFlags", &MOC_CHANNEL, OPTEXP_CORE,
01904     "Bitwise-OR'ed combination of Flags to use when normaling float images "
01905     "in the contour channel (FLOAT_NORM_0_255=1, FLOAT_NORM_WITH_SCALE=2, "
01906     "FLOAT_NORM_PRESERVE=4) "
01907     "(corresponds to old ParamMap parameter 'normFlags=3')",
01908     "contour-norm-flags", '\0', "<int>", "3" };
01909 
01910 // Used by: ContourChannel
01911 static const ModelOptionDef OPT_ContourChannelOutputBlur =
01912   { MODOPT_ARG(int), "ContourChannelOutputBlur", &MOC_CHANNEL, OPTEXP_CORE,
01913     "Box-filter width to use when blurring+decimating to rescale "
01914     "the output of the contour channel",
01915     "contour-output-blur", '\0', "<int>", "4" };
01916 
01917 // Used by: ContourChannel
01918 static const ModelOptionDef OPT_ContourChannelSaveWeights =
01919   { MODOPT_FLAG, "ContourChannelSaveWeights", &MOC_CHANNEL, OPTEXP_SAVE,
01920     "Whether to save a summary image of the contour channel's kernel "
01921     "weights",
01922     "save-contour-weights", '\0', "", "false" };
01923 
01924 // Used by: ContourChannel
01925 static const ModelOptionDef OPT_ContourChannelSaveFilterOutput =
01926   { MODOPT_FLAG, "ContourChannelSaveFilterOutput", &MOC_CHANNEL, OPTEXP_SAVE,
01927     "Whether to save summary images of the contour channel's oriented "
01928     "filter outputs",
01929     "save-contour-filter-output", '\0', "", "false" };
01930 
01931 // Used by: ContourChannel
01932 static const ModelOptionDef OPT_ContourChannelSaveOutput =
01933   { MODOPT_FLAG, "ContourChannelSaveOutput", &MOC_CHANNEL, OPTEXP_SAVE,
01934     "Whether to save output maps from the contour channel",
01935     "save-contour-output", '\0', "", "false" };
01936 
01937 
01938 // Used by: ContourChannel
01939 static const ModelOptionDef OPT_ALIAScontourModel0027 =
01940   { MODOPT_ALIAS, "ALIAScontourModel0027", &MOC_ALIAS, OPTEXP_CORE,
01941     "Contour model #0027",
01942     "contour-model-0027", '\0', "",
01943     "--contour-cxn-type=BraunNorm "
01944     "--contour-cxn-beta=0.07 "
01945     "--contour-cxn-excit-strength=1.7 "
01946     "--contour-cxn-inhib-strength=-0.3 "
01947     "--contour-cxn-dims=25x25 "
01948     "--contour-cxn-peak-distance=2.5 "
01949     "--contour-dynamics-type=Static "
01950     "--cinnic-fast-plast=5.405775 "
01951     "--cinnic-group-top=0.0048828125 "
01952     "--cinnic-leak=0.0003662109375 "
01953     "--cinnic-sigmoid-thresh=35.15625 "
01954     "--cinnic-suppression-add=1.6875 "
01955     "--cinnic-suppression-sub=2.0 "
01956     "--contour-filter-reduction=1 "
01957     "--contour-first-scale=2 "
01958     "--contour-gabor-width=5.0 "
01959     "--contour-num-iterations=3 "
01960     "--contour-num-scales=1 "
01961   };
01962 
01963 // Used by: ContourChannel
01964 static const ModelOptionDef OPT_ALIAScontourModel0032 =
01965   { MODOPT_ALIAS, "ALIAScontourModel0032", &MOC_ALIAS, OPTEXP_CORE,
01966     "Contour model #0032",
01967     "contour-model-0032", '\0', "",
01968     "--contour-cxn-type=BraunNorm "
01969     "--contour-cxn-beta=0.000720977783205 "
01970     "--contour-cxn-dims=31x31 "
01971     "--contour-cxn-excit-strength=2.33459472656 "
01972     "--contour-cxn-inhib-strength=-0.0030920982361 "
01973     "--contour-cxn-peak-distance=54.931640625 "
01974     "--contour-dynamics-type=Static "
01975     "--cinnic-fast-plast=0.0791861572262 "
01976     "--cinnic-group-size=11 "
01977     "--cinnic-group-top=0.15625 "
01978     "--cinnic-leak=1.318359375 "
01979     "--cinnic-sigmoid-thresh=474.609375 "
01980     "--cinnic-suppression-add=0.75 "
01981     "--cinnic-suppression-sub=2.0 "
01982     "--contour-filter-reduction=2 "
01983     "--contour-first-scale=1 "
01984     "--contour-gabor-width=2.02549743327 "
01985     "--contour-num-iterations=1 "
01986     "--contour-num-scales=1 "
01987   };
01988 
01989 
01990 ContourChannel::ContourChannel(OptionManager& mgr,
01991                                const std::string& saveprefix)
01992   :
01993   ChannelBase(mgr, "Contours", "contours", UNKNOWN),
01994 
01995   itsCxnType              ( &OPT_ContourConnectionType, this ),
01996   itsNumIterations        ( &OPT_ContourChannelIterations, this ),
01997   itsLowPassWidth         ( &OPT_ContourChannelLowPassWidth, this ),
01998   itsFirstScale           ( &OPT_ContourChannelFirstScale, this ),
01999   itsNumScales            ( &OPT_ContourChannelNumScales, this ),
02000   itsMaxInputDims         ( &OPT_ContourChannelMaxInputDims, this ),
02001   itsFilterStyle          ( &OPT_ContourChannelFilterStyle, this ),
02002   itsGaborPeriod          ( &OPT_ContourChannelGaborPeriod, this ),
02003   itsFilterReduction      ( &OPT_ContourChannelFilterReduction, this ),
02004   itsDynamicsType         ( &OPT_ContourChannelDynamicsType, this ),
02005   itsNormType             ( &OPT_MaxNormType, this ),
02006   itsNormFlags            ( &OPT_ContourChannelNormFlags, this ),
02007   itsLevelSpec            ( &OPT_LevelSpec, this ),
02008   itsOutputBlur           ( &OPT_ContourChannelOutputBlur, this ),
02009   itsSaveWeights          ( &OPT_ContourChannelSaveWeights, this ),
02010   itsSaveFilterOutput     ( &OPT_ContourChannelSaveFilterOutput, this ),
02011   itsSaveOutput           ( &OPT_ContourChannelSaveOutput, this ),
02012   itsSavePrefix           ( saveprefix ), // pmap->getStringParam("savePrefix", "contourout"),
02013   itsGetSingleChannelStats(&OPT_GetSingleChannelStats, this),
02014   itsSaveStatsPerChannel(&OPT_SaveStatsPerChannel, this),
02015   itsGetSingleChannelStatsFile(&OPT_GetSingleChannelStatsFile, this),
02016   itsGetSingleChannelStatsTag(&OPT_GetSingleChannelStatsTag, this),
02017   itsConnection           ( new ContourConnectionBraun(mgr, true) ),
02018   itsScaleBand            ( new ContourLayerStatic(mgr) ),
02019 
02020   itsRawInput             ( ),
02021   itsFloatInput           ( ),
02022   itsOutput               ( )
02023 {
02024   mgr.requestOptionAlias(&OPT_ALIAScontourModel0027);
02025   mgr.requestOptionAlias(&OPT_ALIAScontourModel0032);
02026 
02027   this->addSubComponent(itsConnection);
02028   this->addSubComponent(itsScaleBand);
02029 }
02030 
02031 // ######################################################################
02032 ContourChannel::~ContourChannel()
02033 {}
02034 
02035 // ######################################################################
02036 bool ContourChannel::outputAvailable() const
02037 {
02038   return itsOutput.initialized();
02039 }
02040 
02041 // ######################################################################
02042 Dims ContourChannel::getMapDims() const
02043 {
02044   const int lev = itsLevelSpec.getVal().mapLevel();
02045 
02046   return this->getInputDims() / (1 << lev);
02047 }
02048 
02049 // ######################################################################
02050 uint ContourChannel::numSubmaps() const
02051 {
02052   return itsNumScales.getVal() * itsConnection->numAngles();
02053 }
02054 
02055 // ######################################################################
02056 std::string ContourChannel::getSubmapName(unsigned int index) const
02057 {
02058   const unsigned int scale = index / itsNumScales.getVal();
02059   const unsigned int angle = index % itsNumScales.getVal();
02060 
02061   return sformat("%s scale: %u, angle: %u", descriptiveName().c_str(), scale, angle);
02062 }
02063 
02064 // ######################################################################
02065 std::string ContourChannel::getSubmapNameShort(unsigned int index) const
02066 {
02067   const unsigned int scale = index / itsNumScales.getVal();
02068   const unsigned int angle = index % itsNumScales.getVal();
02069 
02070   return sformat("%s(%u,%u)", tagName().c_str(), scale, angle);
02071 }
02072 
02073 // ######################################################################
02074 Image<float> ContourChannel::getOutput()
02075 {
02076   if(itsGetSingleChannelStats.getVal())
02077     saveStats(itsOutput, -1);
02078 
02079   return itsOutput;
02080 }
02081 
02082 // ######################################################################
02083 void ContourChannel::saveStats(const Image<float> img, const short idx)
02084 {
02085   std::string fileName;
02086 
02087   if(itsSaveStatsPerChannel.getVal())
02088   {
02089     std::string dot = ".";
02090     std::string txt = ".txt";
02091     fileName = itsGetSingleChannelStatsFile.getVal()+ dot + tagName() + txt;
02092   }
02093   else
02094   {
02095     fileName = itsGetSingleChannelStatsFile.getVal();
02096   }
02097 
02098   //LINFO("SAVING single channel stats %d - %s to file %s",
02099   //      idx, descriptiveName().c_str(),fileName.c_str());
02100   ushort minx = 0, miny = 0, maxx = 0, maxy = 0;
02101   float  min,  max,  avg,  std;
02102   uint   N;
02103   // get a whole bunch of stats about this output image
02104   getMinMaxAvgEtc(img, min, max, avg, std, minx, miny, maxx, maxy, N);
02105 
02106   std::ofstream statsFile(fileName.c_str(), std::ios::app);
02107 
02108   statsFile << itsGetSingleChannelStatsTag.getVal() << "\t";
02109 
02110   statsFile << itsFrameIdx << "\t";
02111 
02112   // differentiate between scale image stats and the combined max norm
02113   // for this channel
02114   if(idx == -1)
02115   {
02116     statsFile << "COMBINED\t-1\t";
02117     itsFrameIdx++;
02118   }
02119   else
02120     statsFile << "SCALE\t" << idx << "\t";
02121 
02122   statsFile << tagName().c_str() << "\t" << descriptiveName().c_str() << "\t";
02123   statsFile << min  << "\t" << max  << "\t" << avg  << "\t" << std  << "\t"
02124             << minx << "\t" << miny << "\t" << maxx << "\t" << maxy << "\t"
02125             << N    << "\n";
02126   statsFile.close();
02127 }
02128 
02129 // ######################################################################
02130 void ContourChannel::saveResults(const nub::ref<FrameOstream>& ofs)
02131 {
02132   itsSaveSet.saveAll(*ofs, itsSavePrefix.c_str());
02133 }
02134 
02135 // ######################################################################
02136 void ContourChannel::paramChanged(ModelParamBase* param,
02137                                   const bool valueChanged,
02138                                   ParamClient::ChangeStatus* status)
02139 {
02140   if (param == &itsCxnType)
02141     {
02142       this->removeSubComponent(*itsConnection);
02143 
02144       switch (itsCxnType.getVal())
02145         {
02146         case CC_CXN_CINNIC:     itsConnection.reset(new ContourConnectionCinnic(getManager())); break;
02147         case CC_CXN_BRAUN:      itsConnection.reset(new ContourConnectionBraun(getManager(), false)); break;
02148         case CC_CXN_BRAUN_NORM: itsConnection.reset(new ContourConnectionBraun(getManager(), true)); break;
02149         default:
02150           LFATAL("unknown connection type '%d'", int(itsCxnType.getVal()));
02151         }
02152 
02153       this->addSubComponent(itsConnection);
02154       itsConnection->exportOptions(MC_RECURSE);
02155     }
02156   else if (param == &itsDynamicsType)
02157     {
02158       this->removeSubComponent(*itsScaleBand);
02159 
02160       switch (itsDynamicsType.getVal())
02161         {
02162         case CC_STATIC: itsScaleBand.reset(new ContourLayerStatic(getManager())); break;
02163         case CC_DYNAMIC: itsScaleBand.reset(new ContourLayerDynamic(getManager())); break;
02164         default:
02165           LFATAL("unknown dynamics type '%d'", int(itsDynamicsType.getVal()));
02166         }
02167 
02168       this->addSubComponent(itsScaleBand);
02169       itsScaleBand->exportOptions(MC_RECURSE);
02170     }
02171 }
02172 
02173 // ######################################################################
02174 void ContourChannel::doInput(const InputFrame& inframe)
02175 {
02176   ASSERT(inframe.grayFloat().initialized());
02177 
02178   CpuTimer t;
02179 
02180   itsSaveSet.clear();
02181 
02182   if (itsSaveWeights.getVal())
02183     itsSaveSet.add("weights", GenericFrame(itsConnection->getSummaryImage()));
02184 
02185   itsRawInput = inframe.grayFloat();
02186 
02187   itsFloatInput =
02188     (inframe.getWidth() > itsMaxInputDims.getVal().w() ||
02189      inframe.getHeight() > itsMaxInputDims.getVal().h())
02190     ? rescale(inframe.grayFloat(), itsMaxInputDims.getVal())
02191     : inframe.grayFloat();
02192 
02193   ImageSet<float> filterSets[itsNumScales.getVal()];
02194 
02195   switch (itsFilterStyle.getVal())
02196     {
02197     case CC_FILTER_STEERABLE:
02198       {
02199         const ImageSet<float> lpyr =
02200           buildPyrLaplacian(itsFloatInput,
02201                             0, itsFirstScale.getVal()+itsNumScales.getVal(),
02202                             itsLowPassWidth.getVal());
02203 
02204         for (unsigned int s = 0; s < itsNumScales.getVal(); ++s)
02205           {
02206             filterSets[s] =
02207               orientedFilterSet(lpyr[itsFirstScale.getVal()+s],
02208                                 itsGaborPeriod.getVal(),
02209                                 itsConnection->angles(),
02210                                 itsConnection->numAngles());
02211             doMeanNormalize(filterSets[s]);
02212             doRectify(filterSets[s]);
02213           }
02214       }
02215       break;
02216     case CC_FILTER_GABOR:
02217     case CC_FILTER_GAUSSIAN_STEERABLE:
02218       {
02219         ImageSet<float> gausspyr =
02220           buildPyrGaussian(itsFloatInput,
02221                            0, itsFirstScale.getVal()+itsNumScales.getVal(),
02222                            itsLowPassWidth.getVal());
02223 
02224         doEnergyNorm(gausspyr);
02225 
02226         if (itsFilterStyle.getVal() == CC_FILTER_GABOR)
02227           {
02228             ImageSet<float> gpyr[itsConnection->numAngles()];
02229 
02230             for (int i = 0; i < itsConnection->numAngles(); ++i)
02231               {
02232                 // Note, we don't need to pass the DO_ENERGY_NORM flag
02233                 // to buildPyrGabor() since we've already called
02234                 // doEnergyNorm() a few lines up from here.
02235                 gpyr[i] = buildPyrGabor(gausspyr,
02236                                         itsConnection->angle(i),
02237                                         itsGaborPeriod.getVal(),
02238                                         1.0 /*elongation ratio*/,
02239                                         -1 /*size: -1 gives auto-default*/,
02240                                         0 /*flags*/);
02241 
02242                 // Memory optimization: discard any lower pyramid
02243                 // levels, up to (but not including) itsFirstScale. It
02244                 // is only the higher levels that will be needed in
02245                 // the forthcoming takeSlice(), and we can save a lot
02246                 // of memory usage by releasing the unused memory in
02247                 // the lower levels.
02248                 for (unsigned int n = 0; n < itsFirstScale.getVal(); ++n)
02249                   gpyr[i].getImageMut(n) = Image<float>();
02250               }
02251 
02252             for (unsigned int s = 0; s < itsNumScales.getVal(); ++s)
02253               {
02254                 filterSets[s] = takeSlice(gpyr,
02255                                           itsConnection->numAngles(),
02256                                           itsFirstScale.getVal()+s);
02257                 doMeanNormalize(filterSets[s]);
02258                 doRectify(filterSets[s]);
02259               }
02260           }
02261         else // itsFilterStyle.getVal() == CC_FILTER_GAUSSIAN_STEERABLE
02262           {
02263             for (unsigned int s = 0; s < itsNumScales.getVal(); ++s)
02264               {
02265                 filterSets[s] =
02266                   orientedFilterSet(gausspyr[itsFirstScale.getVal()+s],
02267                                     itsGaborPeriod.getVal(),
02268                                     itsConnection->angles(),
02269                                     itsConnection->numAngles());
02270                 doMeanNormalize(filterSets[s]);
02271                 doRectify(filterSets[s]);
02272               }
02273           }
02274       }
02275       break;
02276     default:
02277       LFATAL("invalid filterStyle '%d'", int(itsFilterStyle.getVal()));
02278     }
02279 
02280   t.mark();
02281   t.report();
02282 
02283   const Dims imgDims = itsFloatInput.getDims();
02284 
02285   itsOutput = Image<float>(imgDims, ZEROS);
02286   Image<float> maxout(imgDims, ZEROS);
02287 
02288   for (unsigned int s = 0; s < itsNumScales.getVal(); ++s)
02289     {
02290       filterSets[s] = reduce(filterSets[s], itsFilterReduction.getVal());
02291 
02292       if (itsSaveFilterOutput.getVal())
02293         itsSaveSet.add(sformat("filter.s-%u", s),
02294                        GenericFrame(makeImageArray(filterSets[s]),
02295                                     itsNormFlags.getVal()));
02296 
02297       doOneNormalize(filterSets[s]);
02298 
02299       const std::string saveSuffix = sformat("s-%u", s);
02300 
02301       const Image<float> salmap =
02302         itsScaleBand->compute(filterSets[s],
02303                               *itsConnection,
02304                               saveSuffix,
02305                               itsNormFlags.getVal(),
02306                               itsSaveSet,
02307                               itsNumIterations.getVal(),
02308                               imgDims);
02309 
02310       const Image<float> scale_sm = rescale(salmap, imgDims);
02311 
02312       const float bias = getScaleBias(s);
02313       itsOutput += (scale_sm * bias);
02314       maxout = takeMax(maxout, scale_sm);
02315 
02316       t.mark();
02317       t.report();
02318     }
02319 
02320   if (itsSaveOutput.getVal())
02321     {
02322       itsSaveSet.add("avg",
02323                      GenericFrame(itsOutput, itsNormFlags.getVal()));
02324 
02325       itsSaveSet.add("max",
02326                      GenericFrame(maxout, itsNormFlags.getVal()));
02327     }
02328 
02329   itsOutput = boxDownSizeClean(itsOutput, this->getMapDims(),
02330                                itsOutputBlur.getVal());
02331 
02332   itsOutput = maxNormalize(itsOutput,
02333                            MAXNORMMIN, MAXNORMMAX,
02334                            itsNormType.getVal());
02335 
02336   if (itsSaveOutput.getVal())
02337     {
02338       itsSaveSet.add("final",
02339                      GenericFrame(itsOutput, itsNormFlags.getVal()));
02340     }
02341 }
02342 
02343 // ######################################################################
02344 void ContourChannel::killCaches()
02345 {
02346   ChannelBase::killCaches();
02347 }
02348 
02349 // ######################################################################
02350 // ######################################################################
02351 // ##### Factory function for ContourChannel
02352 // ######################################################################
02353 // ######################################################################
02354 
02355 nub::ref<ChannelBase> makeContourChannel(OptionManager& mgr,
02356                                          const std::string& saveprefix)
02357 {
02358   return makeSharedComp(new ContourChannel(mgr, saveprefix));
02359 }
02360 
02361 // ######################################################################
02362 /* So things look consistent in everyone's emacs... */
02363 /* Local Variables: */
02364 /* indent-tabs-mode: nil */
02365 /* End: */
Generated on Sun May 8 08:40:21 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3