00001 /** @file Psycho/SearchArray.C jittered array of search elements */ 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/Psycho/SearchArray.C $ 00035 // $Id: SearchArray.C 14200 2010-11-04 03:10:43Z jshen $ 00036 // 00037 00038 // Code herein is derived from GroovX, also licensed under the GPL 00039 // Copyright (c) 2002-2004 California Institute of Technology 00040 // Copyright (c) 2004-2007 University of Southern California 00041 // [http://ilab.usc.edu/rjpeters/groovx/] 00042 00043 #ifndef PSYCHO_SEARCHARRAY_C_DEFINED 00044 #define PSYCHO_SEARCHARRAY_C_DEFINED 00045 00046 #include "Psycho/SearchArray.H" 00047 00048 #include "Image/CutPaste.H" 00049 #include "Image/Image.H" 00050 #include "Raster/Raster.H" 00051 #include "Image/geom.h" 00052 #include "Image/Rectangle.H" 00053 #include "rutz/rand.h" 00054 #include "Util/StringConversions.H" 00055 #include <cstdio> 00056 #include <algorithm> //for random_shuffle 00057 00058 using geom::vec2d; 00059 00060 namespace 00061 { 00062 void dumpFrame(const Image<byte>& bmap) 00063 { 00064 static int framecount = 0; 00065 00066 Raster::WriteGray(bmap, sformat("frame_%06d.png", framecount++)); 00067 } 00068 } 00069 00070 SearchArray::SearchArray(const Dims& dims, 00071 double gridSpacing, 00072 double minSpacing, 00073 double margin) 00074 : 00075 itsDims(dims), 00076 itsGridSpacing(gridSpacing), 00077 itsMinSpacing(minSpacing), 00078 itsFillResolution(15.0), 00079 itsMargin(margin), 00080 00081 itsArray(), 00082 00083 itsDumpingFrames(false), 00084 itsFrameDumpPeriod(20) 00085 {} 00086 00087 SearchArray::~SearchArray() throw() {} 00088 00089 void SearchArray::addElement(const rutz::shared_ptr<SearchItem>& e, const bool displace) const 00090 { 00091 if (tooClose(e->pos, -1)) 00092 { 00093 if (!displace) 00094 LFATAL("element @ %g,%g is too close to an existing element", 00095 e->pos.x(), e->pos.y()); 00096 else 00097 { 00098 std::vector<rutz::shared_ptr<SearchItem> > isTooClose; 00099 const double minSpacingSqr = itsMinSpacing*itsMinSpacing; 00100 for (size_t n = 0; n < itsArray.size(); ++n) 00101 { 00102 const vec2d v = e->pos; 00103 const double dx = itsArray[n]->pos.x() - v.x(); 00104 const double dy = itsArray[n]->pos.y() - v.y(); 00105 00106 if(dx*dx+dy*dy <= minSpacingSqr) 00107 isTooClose.push_back(itsArray[n]); 00108 } 00109 for (size_t i = 0; i < isTooClose.size(); ++i) 00110 doRemoveElement(isTooClose[i]); 00111 } 00112 } 00113 doAddElement(e); 00114 } 00115 00116 void SearchArray::replaceElementAtSamePos(size_t i, const rutz::shared_ptr<SearchItem>& e) 00117 { 00118 if (i >= itsArray.size()) 00119 LFATAL("expected idx < %"ZU", got idx = %"ZU, itsArray.size(), i); 00120 00121 const rutz::shared_ptr<SearchItem> old = itsArray[i]; 00122 itsArray[i] = e; 00123 itsArray[i]->pos = old->pos; 00124 } 00125 00126 void SearchArray::generateBackground(SearchItemFactory& factory, 00127 const int diffusionCycles, 00128 const bool doFinalFill, 00129 const int jitterIters, 00130 const bool doJitterForeground, 00131 const int backgSeed) const 00132 { 00133 const size_t foregSize = itsArray.size(); 00134 00135 backgHexGrid(factory); 00136 00137 rutz::urand urand(backgSeed); 00138 00139 for (int i = 0; i < diffusionCycles; ++i) 00140 { 00141 backgJitter(urand, jitterIters, doJitterForeground); 00142 if (i+1 < diffusionCycles || doFinalFill) 00143 backgFill(factory); 00144 00145 // const double spacing = sqrt(2.0*itsDims.sz()/(sqrt(3.0)*itsArray.size())); 00146 // LINFO("%"ZU" elements, ave spacing %f", itsArray.size(), spacing); 00147 } 00148 00149 size_t insideNumber = foregSize; 00150 00151 for (size_t n = 0; n < itsArray.size(); ++n) 00152 { 00153 if (itsArray[n]->type == SearchItem::FOREGROUND) 00154 continue; 00155 00156 bool inside = true; 00157 00158 for (size_t i = 0; i < foregSize; ++i) 00159 { 00160 const size_t j = (i+1) % foregSize; 00161 00162 // This is the vector perpendicular to the line connecting 00163 // contour nodes i and j 00164 const double Yij = itsArray[i]->pos.x() - itsArray[j]->pos.x(); 00165 const double Xij = itsArray[j]->pos.y() - itsArray[i]->pos.y(); 00166 00167 // This is the vector connecting from contour node i to the 00168 // background node 00169 const double Xin = itsArray[n]->pos.x() - itsArray[i]->pos.x(); 00170 const double Yin = itsArray[n]->pos.y() - itsArray[i]->pos.y(); 00171 00172 // If the dot product of those two vectors is less than zero, 00173 // then the background node is "outside". 00174 const double vp = Xij*Xin + Yij*Yin; 00175 00176 if (vp < 0.0) { inside = false; break; } 00177 } 00178 00179 if (inside) 00180 { 00181 ++insideNumber; 00182 } 00183 } 00184 00185 // LINFO(" FOREG_NUMBER %"ZU" PATCH_NUMBER %"ZU" TOTAL_NUMBER %"ZU, 00186 // foregSize, insideNumber, itsArray.size()); 00187 } 00188 00189 Image<byte> SearchArray::getImage(const double lo, const double hi, 00190 const double bg, 00191 bool doTagLast) const 00192 { 00193 Image<double> tmpwin(itsDims, NO_INIT); 00194 tmpwin.clear(bg); 00195 00196 const Image<double>::iterator win = tmpwin.beginw(); 00197 00198 for (size_t i = 0; i < itsArray.size(); ++i) 00199 { 00200 const int xcenter = int(itsArray[i]->pos.x() + itsDims.w() / 2.0 + 0.5); 00201 const int ycenter = int(itsArray[i]->pos.y() + itsDims.h() / 2.0 + 0.5); 00202 00203 const Image<double> p = itsArray[i]->getPatch(); 00204 00205 // top left: 00206 const int x0 = xcenter - p.getWidth() / 2; 00207 const int y0 = ycenter - p.getHeight() / 2; 00208 // bottom right: 00209 const int x1 = x0 + p.getWidth(); 00210 const int y1 = y0 + p.getHeight(); 00211 00212 for (int y = y0; y < y1; ++y) 00213 for (int x = x0; x < x1; ++x) 00214 { 00215 if (x >= 0 && x < itsDims.w() && y >=0 && y < itsDims.h()) 00216 win[x+y*itsDims.w()] += p.getVal(x-x0, y-y0); 00217 } 00218 00219 if (doTagLast && (i+1) == itsArray.size()) 00220 { 00221 const double outer = 0.4 * p.getWidth(); 00222 const double inner = outer - 3; 00223 00224 for (int y = y0; y < y1; ++y) 00225 for (int x = x0; x < x1; ++x) 00226 { 00227 if (x >= 0 && x < itsDims.w() && y >=0 && y < itsDims.h()) 00228 { 00229 const double r = sqrt((x-xcenter)*(x-xcenter) + 00230 (y-ycenter)*(y-ycenter)); 00231 00232 if (r >= inner && r <= outer) 00233 { 00234 win[x+y*itsDims.w()] = 1.0; 00235 } 00236 } 00237 } 00238 } 00239 } 00240 00241 Image<byte> result(tmpwin.getDims(), ZEROS); 00242 00243 unsigned char* bytes = result.beginw(); 00244 00245 bool clip = false; 00246 00247 const int npix = result.getSize(); 00248 00249 const double rng = hi - lo; 00250 00251 for (int k = 0; k < npix; ++k) 00252 { 00253 int val = int((win[k]-lo)/rng*255); 00254 00255 if (val < 0) { clip = true; val = 0; } 00256 else if (val > 255) { clip = true; val = 255; } 00257 00258 GVX_ASSERT(val >= 0); 00259 GVX_ASSERT(val <= 255); 00260 00261 *bytes++ = val; 00262 } 00263 00264 if (clip) 00265 LERROR("some values were clipped"); 00266 00267 return result; 00268 } 00269 00270 void SearchArray::pareBackground(const uint shrunkSize) const 00271 { 00272 // removes elements at random from the array down to the shrunkSize 00273 // elements need to be shuffled first. 00274 random_shuffle(itsArray.begin(),itsArray.end()); 00275 while(itsArray.size() > shrunkSize) 00276 { 00277 const rutz::shared_ptr<SearchItem> last = itsArray.back(); 00278 doRemoveElement(last); 00279 if (last->type == SearchItem::FOREGROUND) 00280 doAddElement(last,true); 00281 } 00282 } 00283 00284 void SearchArray::clear() const 00285 { 00286 itsArray.clear(); 00287 } 00288 00289 void SearchArray::saveCoords(const std::string& filename) const 00290 { 00291 FILE* f = fopen(filename.c_str(), "w"); 00292 if (f == 0) 00293 LFATAL("couldn't open %s for writing", filename.c_str()); 00294 00295 fprintf(f, "%% fg? x0 y0 x1 y1\n"); 00296 00297 for (size_t i = 0; i < itsArray.size(); ++i) 00298 { 00299 const int xcenter = int(itsArray[i]->pos.x() + itsDims.w() / 2.0 + 0.5); 00300 const int ycenter = int(itsArray[i]->pos.y() + itsDims.h() / 2.0 + 0.5); 00301 00302 const Image<double> p = itsArray[i]->getPatch(); 00303 00304 // top left: 00305 const int x0 = xcenter - p.getWidth() / 2; 00306 const int y0 = ycenter - p.getHeight() / 2; 00307 // bottom right: 00308 const int x1 = x0 + p.getWidth(); 00309 const int y1 = y0 + p.getHeight(); 00310 00311 fprintf(f, " %d %d %d %d %d\n", 00312 itsArray[i]->type == SearchItem::FOREGROUND ? 1 : 0, 00313 x0, y0, x1, y1); 00314 } 00315 00316 fclose(f); 00317 } 00318 00319 Rectangle SearchArray::itemBounds() const 00320 { 00321 Rectangle a; 00322 return a.centerDims(Point2D<int>(0,0),itsDims-itsMargin*2); 00323 } 00324 00325 void SearchArray::doAddElement(const rutz::shared_ptr<SearchItem>& e, bool toFront) const 00326 { 00327 if (!toFront) 00328 itsArray.push_back(e); 00329 else 00330 itsArray.insert(itsArray.begin(),e); 00331 00332 // this double call is on purpose so that the added elements don't fly by 00333 // quite so quickly in the resulting movie 00334 if (itsDumpingFrames) 00335 { 00336 const Image<byte> bmap = getImage(true); 00337 00338 dumpFrame(bmap); 00339 dumpFrame(bmap); 00340 } 00341 } 00342 00343 bool SearchArray::tooClose(const vec2d& v, int except) const 00344 { 00345 if ((v.x() + itsDims.w()*0.5) < itsMargin 00346 || (itsDims.w()*0.5 - v.x()) <= itsMargin 00347 || (v.y() + itsDims.h()*0.5) < itsMargin 00348 || (itsDims.h()*0.5 - v.y()) <= itsMargin) 00349 return true; 00350 00351 const double minSpacingSqr = itsMinSpacing*itsMinSpacing; 00352 00353 for (size_t n = 0; n < itsArray.size(); ++n) 00354 { 00355 const double dx = itsArray[n]->pos.x() - v.x(); 00356 const double dy = itsArray[n]->pos.y() - v.y(); 00357 00358 if (dx*dx+dy*dy <= minSpacingSqr && int(n) != except) 00359 return true; 00360 } 00361 00362 return false; 00363 } 00364 00365 void SearchArray::backgHexGrid(SearchItemFactory& factory) const 00366 { 00367 // lay down a hexagonal grid of elements 00368 00369 const double dx = itsGridSpacing; 00370 const double dy = sqrt(3.0) * itsGridSpacing / 2.0; 00371 00372 const int nx = int((itsDims.w() - itsMinSpacing) / dx - 0.5); 00373 const int ny = int((itsDims.h() - itsMinSpacing) / dy); 00374 00375 double y = -0.5 * (ny-1) * dy; 00376 00377 for (int j = 0; j < ny; ++j, y += dy) 00378 { 00379 double x = -0.5 * (nx-1) * dx - 0.25 * dx; 00380 00381 // this is a hexagonal grid, so every other row is staggered by half 00382 // a step in the x direction 00383 if (j%2) x += 0.5*dx; 00384 00385 for (int i = 0; i < nx; ++i, x += dx) 00386 { 00387 if (!tooClose(vec2d(x, y), -1)) 00388 doAddElement(factory.make(vec2d(x, y))); 00389 } 00390 } 00391 } 00392 00393 void SearchArray::backgFill(SearchItemFactory& factory) const 00394 { 00395 const double dx = itsMinSpacing / itsFillResolution; 00396 00397 const double halfX = 0.5 * itsDims.w(); 00398 const double halfY = 0.5 * itsDims.h(); 00399 00400 for (double x = -halfX; x <= halfX; x += dx) 00401 for (double y = -halfY; y <= halfY; y += dx) 00402 { 00403 if (!tooClose(vec2d(x, y), -1)) 00404 doAddElement(factory.make(vec2d(x, y))); 00405 } 00406 } 00407 00408 void SearchArray::backgJitter(rutz::urand& urand, const int jitterIters, 00409 const bool doJitterForeground) const 00410 { 00411 const double jitter = (itsMinSpacing/16.0); 00412 00413 const double halfX = 0.5 * itsDims.w(); 00414 const double halfY = 0.5 * itsDims.h(); 00415 00416 for (int niter = 0; niter < jitterIters; ++niter) 00417 { 00418 for (size_t n = 0; n < itsArray.size(); ++n) 00419 { 00420 if (!doJitterForeground 00421 && itsArray[n]->type == SearchItem::FOREGROUND) 00422 continue; 00423 00424 vec2d v; 00425 v.x() = itsArray[n]->pos.x() + jitter*(urand.fdraw_range(-1.0, 1.0)); 00426 v.y() = itsArray[n]->pos.y() + jitter*(urand.fdraw_range(-1.0, 1.0)); 00427 00428 if (v.x() < -halfX) v.x() += itsDims.w(); 00429 if (v.x() > halfX) v.x() -= itsDims.w(); 00430 if (v.y() < -halfY) v.y() += itsDims.h(); 00431 if (v.y() > halfY) v.y() -= itsDims.h(); 00432 00433 if (!tooClose(v, n)) 00434 { 00435 itsArray[n]->pos = v; 00436 } 00437 } 00438 00439 if (itsDumpingFrames && 00440 niter % itsFrameDumpPeriod == 0) 00441 { 00442 const Image<byte> bmap = getImage(true); 00443 dumpFrame(bmap); 00444 } 00445 } 00446 } 00447 00448 void SearchArray::doRemoveElement(const rutz::shared_ptr<SearchItem>& e) const 00449 { 00450 for (size_t i = 0; i < itsArray.size(); ++i) 00451 { 00452 if (e->pos.x()==itsArray[i]->pos.x() && 00453 e->pos.y()==itsArray[i]->pos.y()) 00454 { 00455 itsArray.erase(itsArray.begin()+i); 00456 break; 00457 } 00458 } 00459 00460 // this double call is on purpose so that the added elements don't fly by 00461 // quite so quickly in the resulting movie 00462 if (itsDumpingFrames) 00463 { 00464 const Image<byte> bmap = getImage(true); 00465 00466 dumpFrame(bmap); 00467 dumpFrame(bmap); 00468 } 00469 00470 } 00471 00472 // ###################################################################### 00473 /* So things look consistent in everyone's emacs... */ 00474 /* Local Variables: */ 00475 /* mode: c++ */ 00476 /* indent-tabs-mode: nil */ 00477 /* End: */ 00478 00479 #endif // PSYCHO_SEARCHARRAY_C_DEFINED