00001 /*!@file Channels/IntegerRawVisualCortex.C */ 00002 00003 // //////////////////////////////////////////////////////////////////// // 00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005 // 00005 // by the University of Southern California (USC) and the iLab at USC. // 00006 // See http://iLab.usc.edu for information about this project. // 00007 // //////////////////////////////////////////////////////////////////// // 00008 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00009 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00010 // in Visual Environments, and Applications'' by Christof Koch and // 00011 // Laurent Itti, California Institute of Technology, 2001 (patent // 00012 // pending; application number 09/912,225 filed July 23, 2001; see // 00013 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00014 // //////////////////////////////////////////////////////////////////// // 00015 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00016 // // 00017 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00018 // redistribute it and/or modify it under the terms of the GNU General // 00019 // Public License as published by the Free Software Foundation; either // 00020 // version 2 of the License, or (at your option) any later version. // 00021 // // 00022 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00023 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00024 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00025 // PURPOSE. See the GNU General Public License for more details. // 00026 // // 00027 // You should have received a copy of the GNU General Public License // 00028 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00029 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00030 // Boston, MA 02111-1307 USA. // 00031 // //////////////////////////////////////////////////////////////////// // 00032 // 00033 // Primary maintainer for this file: Rob Peters <rjpeters at usc dot edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Channels/IntegerRawVisualCortex.C $ 00035 // $Id: IntegerRawVisualCortex.C 11584 2009-08-12 05:24:46Z itti $ 00036 // 00037 00038 #include "Channels/IntegerRawVisualCortex.H" 00039 00040 #include "Channels/ChannelOpts.H" 00041 #include "Channels/IntegerColorChannel.H" 00042 #include "Channels/IntegerFlickerChannel.H" 00043 #include "Channels/IntegerIntensityChannel.H" 00044 #include "Channels/IntegerMotionChannel.H" 00045 #include "Channels/IntegerOrientationChannel.H" 00046 #include "Component/GlobalOpts.H" 00047 #include "Component/OptionManager.H" 00048 #include "Component/ParamMap.H" 00049 #include "Image/ColorOps.H" // for luminance() 00050 #include "Image/IntegerMathOps.H" 00051 #include "Image/MathOps.H" // for distance() 00052 #include "Image/Pixels.H" 00053 #include "Image/PyramidCache.H" 00054 #include "Image/ShapeOps.H" 00055 #include "Image/Transforms.H" // for chamfer34() 00056 #include "Channels/ChannelOpts.H" 00057 #include "Transport/FrameInfo.H" 00058 #include "Transport/FrameOstream.H" 00059 #include "Util/TextLog.H" 00060 #include "Util/sformat.H" 00061 #include "rutz/mutex.h" 00062 #include "rutz/trace.h" 00063 00064 #include <algorithm> 00065 #include <cctype> 00066 #include <cstdio> 00067 #include <vector> 00068 #include <sstream> 00069 00070 // ###################################################################### 00071 IntegerRawVisualCortex:: 00072 IntegerRawVisualCortex(OptionManager& mgr, 00073 nub::ref<IntegerMathEngine> eng, 00074 const std::string& descrName, 00075 const std::string& tag) : 00076 IntegerComplexChannel(mgr, descrName, tag, UNKNOWN, eng), 00077 itsType(&OPT_IntegerRawVisualCortexChans, this), 00078 itsLogFile(&OPT_TextLogFile, this), 00079 itsNormType(&OPT_MaxNormType, this), // see Channels/ChannelOpts.{H,C} 00080 itsUseRandom(&OPT_UseRandom, this), // see Component/ModelManager.{H,C} 00081 itsOutputFactor(&OPT_RawVisualCortexOutputFactor, this), // idem 00082 itsUseOlderVersion(&OPT_UseOlderVersion, this), // Channels/ChannelOpts.{H,C} 00083 itsLevelSpec(&OPT_LevelSpec, this), 00084 itsSaveOutput(&OPT_RawVisualCortexSaveOutput, this), 00085 itsUseMax(&OPT_VCXuseMax, this) 00086 { 00087 GVX_TRACE(__PRETTY_FUNCTION__); 00088 } 00089 00090 // ###################################################################### 00091 IntegerRawVisualCortex::~IntegerRawVisualCortex() 00092 { 00093 GVX_TRACE(__PRETTY_FUNCTION__); 00094 } 00095 00096 // ###################################################################### 00097 void IntegerRawVisualCortex::start1() 00098 { 00099 GVX_TRACE(__PRETTY_FUNCTION__); 00100 00101 IntegerComplexChannel::start1(); 00102 00103 for (uint i = 0; i < this->numChans(); ++i) 00104 LINFO("Top-level channel (%u/%u): level=%s feature=%s submaps=%u name=%s", 00105 i+1, uint(this->numChans()), 00106 featureHierarchyName(featureHierarchyLevel(this->subChan(i)->visualFeature())), 00107 featureName(this->subChan(i)->visualFeature()), 00108 this->subChan(i)->numSubmaps(), this->subChan(i)->descriptiveName().c_str()); 00109 } 00110 00111 // ###################################################################### 00112 void IntegerRawVisualCortex::stop2() 00113 { 00114 GVX_TRACE(__PRETTY_FUNCTION__); 00115 00116 IntegerComplexChannel::stop2(); 00117 } 00118 00119 // ###################################################################### 00120 Image<int> IntegerRawVisualCortex::getChannelOutputMap(IntegerChannel& chan) const 00121 { 00122 float total_weight = 0.0; 00123 00124 for (uint i = 0; i < this->numChans(); ++i) 00125 { 00126 nub::ref<IntegerChannel> c = this->subChan(i); 00127 00128 if (itsUseOlderVersion.getVal()) 00129 { 00130 const float w = float(this->getSubchanTotalWeight(*c) / c->numSubmaps()); 00131 total_weight += w; 00132 } 00133 else 00134 { 00135 const float w = float(this->getSubchanTotalWeight(*c)); 00136 total_weight += w; 00137 } 00138 } 00139 00140 /* We want to compute 00141 00142 weight 00143 img * ------------ 00144 total_weight 00145 00146 00147 To do that without overflowing, we compute it as 00148 00149 00150 weight 256 00151 img * ------------ * --- 00152 total_weight 256 00153 00154 img weight * 256 00155 = ( --- ) * ( ------------ ) 00156 256 total_weight 00157 */ 00158 00159 const int scalebits = 8; 00160 00161 Image<int> chanOut = chan.getOutputInt(); 00162 chanOut >>= scalebits; 00163 00164 if (itsUseOlderVersion.getVal()) 00165 { 00166 const float w = float(this->getSubchanTotalWeight(chan) / chan.numSubmaps()); 00167 00168 const int iw = int( 0.5 + (w*(1<<scalebits)) / total_weight ); 00169 00170 chanOut *= iw; 00171 LINFO("%s weight %d/%d", 00172 chan.tagName().c_str(), iw, (1<<scalebits)); 00173 } 00174 else 00175 { 00176 const float w = float(this->getSubchanTotalWeight(chan)); 00177 00178 const int iw = int( 0.5 + (w*(1<<scalebits)) / total_weight ); 00179 00180 chanOut *= iw; 00181 LINFO("%s weight %d/%d", chan.tagName().c_str(), iw, (1<<scalebits)); 00182 } 00183 00184 return chanOut; 00185 } 00186 00187 // ###################################################################### 00188 Image<int> IntegerRawVisualCortex::postProcessOutputMap(const Image<int>& outmap) 00189 { 00190 Image<int> result = outmap; 00191 00192 // let's apply a last maxNormalization to the map: 00193 switch(itsNormType.getVal()) 00194 { 00195 case VCXNORM_LANDMARK: 00196 case VCXNORM_SURPRISE: 00197 LFATAL("Unsupported VCXNORM type"); 00198 break; 00199 default: 00200 result = intgMaxNormalize(result, 0, 32768, itsNormType.getVal()); 00201 LDEBUG("%s(%d .. %d)", maxNormTypeName(itsNormType.getVal()), 0, 32768); 00202 break; 00203 } 00204 00205 return result; 00206 } 00207 00208 // ###################################################################### 00209 void IntegerRawVisualCortex::doInputInt(const IntegerInput& inp, 00210 const SimTime& t, 00211 PyramidCache<int>* cache, 00212 const Image<byte>& clipMask) 00213 { 00214 ASSERT(inp.grayInt().initialized()); 00215 00216 // optimization: if we have no channels, then do a quick return: 00217 if (this->numChans() == 0) return; 00218 00219 rutz::mutex_lock_class lock; 00220 if (cache && cache->gaussian5.beginSet(inp.grayInt(), &lock)) 00221 { 00222 cache->gaussian5.endSet 00223 (inp.grayInt(), 00224 intgBuildPyrGaussian 00225 (inp.grayInt(), itsLevelSpec.getVal().maxDepth(), 00226 5, this->getImath()), 00227 &lock); 00228 } 00229 00230 // process the channels: 00231 for (uint i = 0; i < this->numChans(); ++i) 00232 { 00233 // get a pointer to the channel of interest: 00234 nub::ref<IntegerChannel> chan = this->subChan(i); 00235 00236 // feed the input to the channel of interest: 00237 chan->inputInt(inp, t, cache, clipMask); 00238 LINFO("Input to %s channel %s ok.", featureHierarchyName(featureHierarchyLevel(chan->visualFeature())), 00239 chan->tagName().c_str()); 00240 } 00241 } 00242 00243 // ###################################################################### 00244 Image<int> IntegerRawVisualCortex::combineOutputsInt() 00245 { 00246 GVX_TRACE(__PRETTY_FUNCTION__); 00247 00248 Image<int> output; 00249 00250 // ... OK, we have to recompute the output: 00251 for (uint i = 0; i < this->numChans(); ++i) 00252 { 00253 nub::ref<IntegerChannel> ch = subChan(i); 00254 00255 // CAUTION: if you modify code here, modify input() as well, 00256 // in the section that is triggered by itsConserveMemory: 00257 if (this->getSubchanTotalWeight(*ch) == 0.0) continue; 00258 if (ch->outputAvailable() == false) continue; 00259 00260 const Image<int> chanOut = getChannelOutputMap(*ch); 00261 00262 // downSizeClean() gracefully does nothing if chanOut is 00263 // already the right size. (Eventually we might like to have a 00264 // way for IntegerRawVisualCortex to enforce a particular chanOut size 00265 // on its own, rather than just taking the size from the first 00266 // channel in the array.) 00267 if (output.initialized() == false) 00268 output = chanOut; 00269 else 00270 { 00271 // sum or take max across channels? 00272 Image<int> o = intgDownSize(chanOut, output.getDims(), 9, this->getImath()); 00273 if (itsUseMax.getVal()) output = takeMax(output, o); else output += o; 00274 } 00275 } 00276 00277 // Ensure that we still return a valid image even if we have no channels 00278 // that has a valid output in the IntegerRawVisualCortex: 00279 if (output.initialized() == false) 00280 { 00281 int sml = itsLevelSpec.getVal().mapLevel(); 00282 output = Image<int>(this->getInputDims().w() >> sml, this->getInputDims().h() >> sml, ZEROS); 00283 return output; 00284 } 00285 00286 if (MYLOGVERB >= LOG_DEBUG) 00287 { 00288 int mi, ma; getMinMax(output, mi, ma); 00289 LDEBUG("Raw output range is [%d .. %d]", mi, ma); 00290 } 00291 00292 // post-process the output in a manner than may depend on which 00293 // variant of IntegerRawVisualCortex we may embody: 00294 output = postProcessOutputMap(output); 00295 00296 LINFO("Computed IntegerRawVisualCortex output."); 00297 return output; 00298 } 00299 00300 // ###################################################################### 00301 Image<float> IntegerRawVisualCortex::getOutput() 00302 { 00303 // using Image::operator*() we will simultenaously convert to float 00304 // and apply our output factor, looping only once over the pixels: 00305 const float fac = itsOutputFactor.getVal() / (1 << this->getMathEngine()->getNbits()); 00306 Image<float> output = getOutputInt() * fac; 00307 00308 float mi, ma; getMinMax(output, mi, ma); 00309 LINFO("Salmap input range is [%f .. %f] nA", mi * 1.0e9F, ma * 1.0e9F); 00310 00311 return output; 00312 } 00313 00314 // ###################################################################### 00315 void IntegerRawVisualCortex::paramChanged(ModelParamBase* const param, 00316 const bool valueChanged, 00317 ParamClient::ChangeStatus* status) 00318 { 00319 ModelComponent::paramChanged(param, valueChanged, status); 00320 OptionManager& mgr = this->getManager(); 00321 nub::ref<IntegerMathEngine> eng = this->getMathEngine(); 00322 00323 if (param == &itsType) { 00324 // kill any existing channels: 00325 this->removeAllSubChans(); 00326 ASSERT(this->numChans() == 0); 00327 00328 // Parse the param string. format here is a series of "X[:w.w]" 00329 // where X is any of "ICOFM..." and the optional w.w may be 00330 // channel weights. See note in RawVisualCortex for why we here 00331 // first parse the arg string (where letters may be specified in 00332 // various orders), and then implement the channels (in fixed order): 00333 uint i = 0; const std::string& str = itsType.getVal(); const uint len = str.length(); 00334 double wc = 0.0, wi = 0.0, wo = 0.0, wf = 0.0, wm = 0.0; 00335 00336 while (i < len) { 00337 char c = str[i]; // the channel to implement 00338 double weight = 1.0; // default weight value 00339 00340 // do we have a weight specification? 00341 if (i < len - 1 && str[i+1] == ':') { 00342 if (i >= len - 2) LFATAL("Missing channel weight value"); 00343 uint end = str.find_first_not_of(".0123456789", i+2); 00344 std::stringstream s; s << str.substr(i+2, end); s >> weight; 00345 i = end; // ready for next one 00346 } else ++i; 00347 00348 switch(c) { 00349 case 'C': wc = weight; break; 00350 case 'I': wi = weight; break; 00351 case 'O': wo = weight; break; 00352 case 'F': wf = weight; break; 00353 case 'M': wm = weight; break; 00354 default: LFATAL("Unsupported channel type '%c' with weight %f", c, weight); 00355 } 00356 } 00357 00358 // Create the channel and assign weight: 00359 if (wc) { 00360 LINFO("Using a Double Opponent ColorChannel with weight %f", wc); 00361 addSubChan(makeSharedComp(new IntegerColorChannel(mgr, eng)), "int-color"); 00362 setSubchanTotalWeight("int-color", wc); 00363 } 00364 if (wf) { 00365 LINFO("Using a FlickerChannel with weight %f", wf); 00366 addSubChan(makeSharedComp(new IntegerFlickerChannel(mgr, eng)), "int-flicker"); 00367 setSubchanTotalWeight("int-flicker", wf); 00368 } 00369 if (wi) { 00370 LINFO("Using an IntensityChannel with weight %f", wi); 00371 addSubChan(makeSharedComp(new IntegerIntensityChannel(mgr, eng)), "int-intensity"); 00372 setSubchanTotalWeight("int-intensity", wi); 00373 } 00374 if (wo) { 00375 LINFO("Using an OrientationChannel with weight %f", wo); 00376 addSubChan(makeSharedComp(new IntegerOrientationChannel(mgr, eng)), "int-orientation"); 00377 setSubchanTotalWeight("int-orientation", wo); 00378 } 00379 if (wm) { 00380 LINFO("Using a MotionChannel with weight %f", wm); 00381 addSubChan(makeSharedComp(new IntegerMotionChannel(mgr, eng)), "int-motion"); 00382 setSubchanTotalWeight("int-motion", wm); 00383 } 00384 00385 // make sure options are exported for all channels: 00386 for (uint i = 0; i < this->numChans(); ++i) this->subChan(i)->exportOptions(MC_RECURSE); 00387 } 00388 } 00389 00390 // ###################################################################### 00391 void IntegerRawVisualCortex::saveResults(const nub::ref<FrameOstream>& ofs) 00392 { 00393 GVX_TRACE(__PRETTY_FUNCTION__); 00394 // save our own output: 00395 if (itsSaveOutput.getVal()) 00396 ofs->writeFloat(Image<float>(getOutputInt()), FLOAT_NORM_0_255, "IVCO", 00397 FrameInfo("integer visual cortex output (input to saliency map)", SRC_POS)); 00398 00399 // save our channel outputs: 00400 for (uint i = 0; i < numChans(); ++i) subChan(i)->saveResults(ofs); 00401 } 00402 00403 // ###################################################################### 00404 /* So things look consistent in everyone's emacs... */ 00405 /* Local Variables: */ 00406 /* mode: c++ */ 00407 /* indent-tabs-mode: nil */ 00408 /* End: */