00001 /*!@file Neuro/SimulationViewerHand.C comparison between saliency and 00002 human hand movement reactions */ 00003 00004 // //////////////////////////////////////////////////////////////////// // 00005 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2003 // 00006 // by the University of Southern California (USC) and the iLab at USC. // 00007 // See http://iLab.usc.edu for information about this project. // 00008 // //////////////////////////////////////////////////////////////////// // 00009 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00010 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00011 // in Visual Environments, and Applications'' by Christof Koch and // 00012 // Laurent Itti, California Institute of Technology, 2001 (patent // 00013 // pending; application number 09/912,225 filed July 23, 2001; see // 00014 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00015 // //////////////////////////////////////////////////////////////////// // 00016 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00017 // // 00018 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00019 // redistribute it and/or modify it under the terms of the GNU General // 00020 // Public License as published by the Free Software Foundation; either // 00021 // version 2 of the License, or (at your option) any later version. // 00022 // // 00023 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00024 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00025 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00026 // PURPOSE. See the GNU General Public License for more details. // 00027 // // 00028 // You should have received a copy of the GNU General Public License // 00029 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00030 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00031 // Boston, MA 02111-1307 USA. // 00032 // //////////////////////////////////////////////////////////////////// // 00033 // 00034 // Primary maintainer for this file: Dicky Nauli Sihite <sihite@usc.edu> 00035 // $HeadURL: 00036 // $Id: 00037 // 00038 00039 #include "Neuro/SimulationViewerHand.H" 00040 00041 //#include "Channels/ChannelBase.H" 00042 //#include "Channels/ChannelOpts.H" // for LevelSpec option 00043 #include "Component/OptionManager.H" 00044 //#include "Component/ModelOptionDef.H" 00045 #include "Image/ColorOps.H" // for contrastModulate(), toRGB() 00046 //#include "Image/CutPaste.H" // for concatX() 00047 #include "Image/DrawOps.H" // for colGreyCombo() 00048 #include "Image/FilterOps.H" // for lowPass3() 00049 //#include "Image/MathOps.H" // for scramble() 00050 #include "Image/Transforms.H" // for segmentObjectClean(), contour2D() 00051 #include "Image/ShapeOps.H" // for rescale() 00052 #include "Neuro/NeuroOpts.H" 00053 #include "Neuro/NeuroSimEvents.H" 00054 #include "Neuro/SpatialMetrics.H" 00055 #include "Psycho/HandData.H" 00056 #include "Simulation/SimEventQueue.H" 00057 //#include "Transport/FrameInfo.H" 00058 #include "Transport/FrameOstream.H" 00059 #include "Util/sformat.H" 00060 #include "rutz/trace.h" 00061 00062 #include <fstream> 00063 #include <iostream> 00064 00065 // for some reason this is not recognized when it appears in NeuroOpts.C 00066 /*const ModelOptionDef OPT_SMhistoryQlen = 00067 { MODOPT_ARG(uint), "SMhistoryQ", &MOC_DISPLAY, OPTEXP_CORE, 00068 "Keep a historical queue of salieny maps (one per new input frame), " 00069 "and report the history of saliency values over the entire queue " 00070 "and at a saccade target location, for each saccade", 00071 "sm-history-qlen", '\0', "<uint>", "0" }; 00072 //*/ 00073 00074 // ###################################################################### 00075 SimulationViewerHand:: 00076 SimulationViewerHand(OptionManager& mgr, const std::string& descrName, 00077 const std::string& tagName) : 00078 SimulationViewer(mgr, descrName, tagName), 00079 SIMCALLBACK_INIT(SimEventClockTick), 00080 SIMCALLBACK_INIT(SimEventSaveOutput), 00081 itsMetrics(new SpatialMetrics(mgr)), 00082 itsSaveTraj(&OPT_SVsaveTraj, this), 00083 itsSaveCombo(&OPT_SVHandSaveCombo, this), 00084 itsDisplayHand(&OPT_SVHandDisplay, this), 00085 itsPatchSize(&OPT_SVpatchSize, this), 00086 itsEraseMarker(&OPT_SVeraseMarker, this), 00087 itsMaxComboWidth(&OPT_SVHandMaxComboWidth, this), 00088 itsDrawings(), itsCurrTime(), itsFrameNumber(-1), 00089 itsHeaderCrafted(false), itsOutFields(), itsHandStyles(), 00090 itsTargets() 00091 { 00092 GVX_TRACE(__PRETTY_FUNCTION__); 00093 00094 this->addSubComponent(itsMetrics); 00095 00096 // Select HandTrack for the HandControllerType 00097 getManager().setOptionValString(&OPT_HandControllerType, "HandTrack"); 00098 } 00099 00100 // ###################################################################### 00101 SimulationViewerHand::~SimulationViewerHand() 00102 { 00103 GVX_TRACE(__PRETTY_FUNCTION__); 00104 } 00105 00106 // ###################################################################### 00107 void SimulationViewerHand::start1() 00108 { 00109 GVX_TRACE(__PRETTY_FUNCTION__); 00110 00111 SimulationViewer::start1(); 00112 } 00113 00114 // ###################################################################### 00115 void SimulationViewerHand::stop1() 00116 { 00117 GVX_TRACE(__PRETTY_FUNCTION__); 00118 } 00119 00120 // ###################################################################### 00121 void SimulationViewerHand:: 00122 onSimEventClockTick(SimEventQueue& q, rutz::shared_ptr<SimEventClockTick>& ect) 00123 { 00124 // get the current time (mostly for SVHandRegion) 00125 itsCurrTime = q.now(); 00126 // any new input frame from the retina? If so, let's initialize or 00127 // clear all our drawings: 00128 if (SeC<SimEventRetinaImage> e = q.check<SimEventRetinaImage>(this)) { 00129 itsDrawings.resize(e->frame().getDims(), true); 00130 itsFrameNumber++; // used for derived class SVHandRegion 00131 // this member is never used in SVHand 00132 } 00133 00134 // Let's update our sliding caches with the current SM/AGM/etc: 00135 const Image<float> currsm = getMap(q, false); 00136 00137 // all right, get the latest retina image (whether new or not) as we 00138 // will need it to do coordinate transforms, coordinate clippings, etc: 00139 SeC<SimEventRetinaImage> retev = q.check<SimEventRetinaImage>(this, SEQ_ANY); 00140 if (retev.is_valid() == false) 00141 LFATAL("I need some SimEventRetinaImage in the queue to function."); 00142 00143 // any hand-tracker action? 00144 SeC<SimEventHandTrackerData> e = q.check<SimEventHandTrackerData>(this); 00145 00146 while(e.is_valid()) { 00147 // parse that event: 00148 const rutz::shared_ptr<HandData> trackCurrPos = e->data(); 00149 const uint tnum = e->trackerNum(); 00150 const std::string tfn = e->trackerFilename(); 00151 // add entry to style vector when we get a new trackernum 00152 if (itsHandStyles.size() <= tnum) { 00153 itsHandStyles.resize(tnum+1); 00154 00155 itsHandStyles[tnum].pSize = itsPatchSize.getVal(); 00156 itsHandStyles[tnum].col = e->trackerColor(); 00157 00158 // give distinct label, TODO: maybe write a switch for this 00159 // itsHandStyles[tnum].label = convertToString(tnum); 00160 //huge hack right now, relies on directory tree data/<subject>/ pieces 00161 itsHandStyles[tnum].label = tfn.substr(tfn.find("data")+5,2); 00162 } 00163 00164 // draw the conditions of current hand conditions: 00165 if (itsDisplayHand.getVal()) { 00166 00167 // erase previous hand markers first 00168 if (itsEraseMarker.getVal()) itsDrawings.resize(retev->frame().getDims(), true); 00169 00170 // draw marker at hand position 00171 drawHand(trackCurrPos, tnum); 00172 } 00173 00174 // do we want to do any other sample processing? 00175 // (for SVHandRegion or other derived classes) 00176 extraSampleProcessing(trackCurrPos); 00177 00178 // any more events like that in the queue? 00179 e = q.check<SimEventHandTrackerData>(this); 00180 } 00181 } 00182 00183 00184 00185 // ###################################################################### 00186 void SimulationViewerHand::drawHand(const rutz::shared_ptr<HandData> rawPos, const uint tN) 00187 { 00188 /* The output that we expect on screen: 00189 * .--------------------. 00190 * | | | 00191 * | | | 00192 * | Y | 00193 * | | B B B B ... | 00194 * | ------X-------- | 00195 * '--------------------' 00196 * X & Y = x & y (left-right & accl-brake) direction respectively 00197 * It will show a line for range of movement and a box 00198 * indicating the current position 00199 * B's = buttons, there will be circles with numbers on it indicating 00200 * whether the button is pressed(green) or not(red) 00201 * Hopefully the B's on screen is wrapped around incase too many 00202 * buttons are avail 00203 */ 00204 00205 // Our screen dimensions 00206 const Dims dims = itsDrawings.getDims(); 00207 00208 // Default one grid cell based on the screen dimensions 00209 // Assuming we always have 4:3 tv ratio to get a square grid 00210 const Dims numgrid (80,60); // Under 640x480, its expected to have 8x8 per grid 00211 const Dims grid (dims.w()/numgrid.w(),dims.h()/numgrid.h()); 00212 00213 // Get raw X and Y position 00214 int rawX = rawPos->getX(); 00215 int rawY = rawPos->getY(); 00216 00217 // Get the dims of X and Y position 00218 /*! Assuming grid [1,1]'s coord is (8,8) --> [ 0, 0] is ( 0, 0) 00219 * --> [80,60] is (640,480) 00220 * We only want the X to be in between grid [2,59] to [79,59] 00221 * and Y in [1,1] to [1,58]. We set those instead of [1,59] to prevent 00222 * collision incase both meet eachother (rawX=0/255, rawY=0/255) 00223 * |----------------|-------[]------| 00224 * X1 XC X_ X2 00225 */ 00226 Point2D<int> X1 (getGridCoord(grid, 2, numgrid.h()-1)); 00227 Point2D<int> X2 (getGridCoord(grid, numgrid.w()-1, numgrid.h()-1)); 00228 Point2D<int> XC ((127*(X2.i-X1.i)/255)+X1.i,X1.j); 00229 Point2D<int> XD (0,grid.h()/2); // for difference 00230 Point2D<int> Y1 (getGridCoord(grid, 1, 1)); 00231 Point2D<int> Y2 (getGridCoord(grid, 1, numgrid.h()-2)); 00232 Point2D<int> YC (grid.w(),(127*(Y2.j-Y1.j)/255)+Y1.j); 00233 Point2D<int> YD (grid.w()/2,0); // for difference 00234 // The patch coords 00235 Point2D<int> X_ ((rawX*(X2.i-X1.i)/255)+X1.i, dims.h()-grid.h()); 00236 Point2D<int> Y_ (grid.w(), (rawY*(Y2.j-Y1.j)/255)+Y1.j); 00237 00238 // First draw the white line for the X-Y coords 00239 const PixRGB<byte> colWhite (255,255,255); 00240 // The X-line 00241 drawLine(itsDrawings, X1, X2, colWhite); 00242 drawLine(itsDrawings, X1-XD, X1+XD, colWhite); 00243 drawLine(itsDrawings, XC-XD, XC+XD, colWhite); 00244 drawLine(itsDrawings, X2-XD, X2+XD, colWhite); 00245 // The Y-line 00246 drawLine(itsDrawings, Y1, Y2, colWhite); 00247 drawLine(itsDrawings, Y1-YD, Y1+YD, colWhite); 00248 drawLine(itsDrawings, YC-YD, YC+YD, colWhite); 00249 drawLine(itsDrawings, Y2-YD, Y2+YD, colWhite); 00250 00251 00252 // Draw the box for the X-Y 00253 drawPatchBB(itsDrawings, X_, itsHandStyles[tN].pSize, 00254 colWhite, PixRGB<byte>(1)); 00255 drawPatchBB(itsDrawings, Y_, itsHandStyles[tN].pSize, 00256 colWhite, PixRGB<byte>(1)); 00257 00258 PixRGB<byte> colB; 00259 // For the buttons 00260 if (rawPos->isButtonEmpty() == false) { 00261 for (uint i = 0; i < rawPos->numButton(); i++) { 00262 // Blue if pressed, red if not 00263 if (rawPos->isPressed(i)) 00264 colB = PixRGB<byte> (255, 0, 0); // Red 00265 else 00266 colB = PixRGB<byte> (0, 0, 0); // Transparent 00267 00268 // For buttons position, we want them to be @ bottom of the screen 00269 // TODO: wrapped around in case the buttons grid over [80,_] 00270 00271 // Write text of numbers into this thing 00272 writeText(itsDrawings, Point2D<int>(getGridCoord(grid, 3*i+10, numgrid.h()-5)), 00273 sformat("%2d",i+1).c_str(), PixRGB<byte>(255), colB,SimpleFont::FIXED(10),false); 00274 00275 // Draw circle 00276 //drawCircle(itsDrawings, Point2D<int>(getGridCoord(grid, 3*i+3, 1)),6,colB,1); 00277 } 00278 } 00279 } 00280 00281 // ###################################################################### 00282 void SimulationViewerHand::extraSampleProcessing(const rutz::shared_ptr<HandData>) {} 00283 // empty here, written so that HandRegion can do extra output on each hand position 00284 00285 // ###################################################################### 00286 Image< PixRGB<byte> > SimulationViewerHand::getTraj(SimEventQueue& q) 00287 { 00288 GVX_TRACE(__PRETTY_FUNCTION__); 00289 00290 // get the latest retina image: 00291 Image< PixRGB<byte> > input; 00292 if (SeC<SimEventRetinaImage> e = q.check<SimEventRetinaImage>(this, SEQ_ANY)) 00293 input = e->frame().colorByte(); 00294 else 00295 LFATAL("Could not find required SimEventRetinaImage"); 00296 00297 // make a composite of the input + the drawings: 00298 Image< PixRGB<byte> > comp = composite(itsDrawings, input); 00299 00300 // return a plain traj? 00301 if (itsSaveTraj.getVal()) return comp; 00302 00303 // otherwise, return mega combo; we have two formats: if we have a 00304 // max-cache, it will be a 4-image format, otherwise a 2-image 00305 // format: 00306 const Dims dims = itsDrawings.getDims(); 00307 Image< PixRGB<byte> > ret; 00308 00309 if (itsSaveCombo.getVal()) { 00310 ret = concatX(input, itsDrawings); 00311 drawLine(ret, Point2D<int>(dims.w()-1, 0), Point2D<int>(dims.w()-1, dims.h()-1), 00312 PixRGB<byte>(255, 255, 0), 1); 00313 drawLine(ret, Point2D<int>(dims.w(), 0), Point2D<int>(dims.w(), dims.h()-1), 00314 PixRGB<byte>(255, 255, 0), 1); 00315 } 00316 00317 // make sure image is not unreasonably large: 00318 while (ret.getWidth() > itsMaxComboWidth.getVal()) 00319 ret = decY(lowPass3y(decX(lowPass3x(ret)))); 00320 00321 return ret; 00322 } 00323 00324 // ###################################################################### 00325 void SimulationViewerHand:: 00326 onSimEventSaveOutput(SimEventQueue& q, rutz::shared_ptr<SimEventSaveOutput>& e) 00327 { 00328 this->save1(e->sinfo()); 00329 } 00330 00331 // ###################################################################### 00332 void SimulationViewerHand::save1(const ModelComponentSaveInfo& sinfo) 00333 { 00334 GVX_TRACE(__PRETTY_FUNCTION__); 00335 SimEventQueue *q = dynamic_cast<const SimModuleSaveInfo&>(sinfo).q; 00336 00337 // update the trajectory: 00338 Image< PixRGB<byte> > res = getTraj(*q); 00339 00340 // save results? 00341 if (itsSaveCombo.getVal() || itsSaveTraj.getVal()) 00342 { 00343 // get the OFS to save to, assuming sinfo is of type 00344 // SimModuleSaveInfo (will throw a fatal exception otherwise): 00345 nub::ref<FrameOstream> ofs = 00346 dynamic_cast<const SimModuleSaveInfo&>(sinfo).ofs; 00347 00348 ofs->writeRGB(res, "T", 00349 FrameInfo("SimulationViewerHand trajectory", SRC_POS)); 00350 00351 } 00352 } 00353 00354 // ###################################################################### 00355 /* So things look consistent in everyone's emacs... */ 00356 /* Local Variables: */ 00357 /* indent-tabs-mode: nil */ 00358 /* End: */