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: */