00001 /*!@file SceneUnderstanding/Geons3D.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: Lior Elazary <elazary@usc.edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/plugins/SceneUnderstanding/Geons3D.C $ 00035 // $Id: Geons3D.C 13875 2010-09-03 00:54:58Z lior $ 00036 // 00037 00038 #ifndef Geons3D_C_DEFINED 00039 #define Geons3D_C_DEFINED 00040 00041 #include "plugins/SceneUnderstanding/Geons3D.H" 00042 00043 #include "Image/DrawOps.H" 00044 #include "Image/MathOps.H" 00045 #include "Image/Kernels.H" 00046 #include "Image/FilterOps.H" 00047 #include "Image/Transforms.H" 00048 #include "Image/fancynorm.H" 00049 #include "Image/Convolutions.H" 00050 #include "Image/MatrixOps.H" 00051 #include "Simulation/SimEventQueue.H" 00052 #include "GUI/DebugWin.H" 00053 #include <math.h> 00054 #include <fcntl.h> 00055 #include <limits> 00056 #include <string> 00057 00058 const ModelOptionCateg MOC_Geons3D = { 00059 MOC_SORTPRI_3, "Geons3D-Related Options" }; 00060 00061 // Used by: SimulationViewerEyeMvt 00062 const ModelOptionDef OPT_Geons3DShowDebug = 00063 { MODOPT_ARG(bool), "Geons3DShowDebug", &MOC_Geons3D, OPTEXP_CORE, 00064 "Show debug img", 00065 "geons3d-debug", '\0', "<true|false>", "false" }; 00066 00067 //Define the inst function name 00068 SIMMODULEINSTFUNC(Geons3D); 00069 00070 00071 // ###################################################################### 00072 Geons3D::Geons3D(OptionManager& mgr, const std::string& descrName, 00073 const std::string& tagName) : 00074 SimModule(mgr, descrName, tagName), 00075 SIMCALLBACK_INIT(SimEventTwoHalfDSketchOutput), 00076 SIMCALLBACK_INIT(SimEventSaveOutput), 00077 SIMCALLBACK_INIT(SimEventUserInput), 00078 SIMCALLBACK_INIT(SimEventGeons3DPrior), 00079 itsShowDebug(&OPT_Geons3DShowDebug, this) 00080 { 00081 00082 itsViewPort = new ViewPort3D(320,240, true, false, true); 00083 00084 double trans[3][4] = { 00085 {-0.999143, -0.041185, -0.004131, -1.142130}, 00086 {-0.017002, 0.499358, -0.866229, 17.284269}, 00087 {0.037739, -0.865416, -0.499630, 220.977236}}; 00088 00089 itsViewPort->setCamera(trans); 00090 00091 //itsGHough.readTable("hough.dat"); 00092 // itsGHough2.readTable("hough2.dat"); 00093 00094 initRandomNumbers(); 00095 00096 itsLearn = true; 00097 00098 //itsViewPort->setWireframeMode(false); 00099 //itsViewPort->setLightsMode(false); 00100 //itsViewPort->setFeedbackMode(false); 00101 00102 ////Build lookup table from ground plane 00103 //itsViewPort->initFrame(); 00104 //itsViewPort->drawGround(Point2D<float>(1000, 1000), PixRGB<byte>(256,256,256)); 00105 //itsTableDepth = flipVertic(itsViewPort->getDepthFrame()); 00106 00107 //itsTrainingThreshold = 0.65; 00108 00109 //itsThreadServer.reset(new WorkThreadServer("Geons3D", 4)); 00110 00111 00112 00113 //The user proposals 00114 GeonState gs; 00115 gs.pos = Point3D<float>(-31,-34,0); 00116 gs.rot = Point3D<float>(10,0,0); 00117 00118 gs.superQuadric.its_a1 = 25; 00119 gs.superQuadric.its_a2 = 30; 00120 gs.superQuadric.its_a3 = 25; 00121 gs.superQuadric.its_alpha = -1; //25; 00122 gs.superQuadric.its_n = 0.0; 00123 gs.superQuadric.its_e = 1.0; 00124 gs.superQuadric.its_u1 = -M_PI ; 00125 gs.superQuadric.its_u2 = M_PI; 00126 gs.superQuadric.its_v1 = -M_PI; 00127 gs.superQuadric.its_v2 = M_PI; 00128 gs.superQuadric.its_s1 = 0.0f; 00129 gs.superQuadric.its_t1 = 0.0f; 00130 gs.superQuadric.its_s2 = 1.0f; 00131 gs.superQuadric.its_t2 = 1.0f; 00132 00133 itsProposals.push_back(gs); 00134 } 00135 00136 // ###################################################################### 00137 Geons3D::~Geons3D() 00138 { 00139 } 00140 00141 00142 // ###################################################################### 00143 void Geons3D::onSimEventTwoHalfDSketchOutput(SimEventQueue& q, 00144 rutz::shared_ptr<SimEventTwoHalfDSketchOutput>& e) 00145 { 00146 00147 //Check if we have metadata 00148 if (SeC<SimEventLGNOutput> lgn = q.check<SimEventLGNOutput>(this)) 00149 { 00150 rutz::shared_ptr<GenericFrame::MetaData> metaData = lgn->getMetaData(); 00151 if (metaData.get() != 0) { 00152 itsObjectsData.dyn_cast_from(metaData); 00153 } 00154 itsLGNInput = lgn->getCells(); 00155 } 00156 00157 //Check if we have the smap 00158 if (SeC<SimEventSMapOutput> smap = q.check<SimEventSMapOutput>(this)) 00159 itsSMap = smap->getSMap(); 00160 00161 itsSurfaces = e->getSurfaces(); 00162 00163 evolve(q); 00164 00165 } 00166 00167 // ###################################################################### 00168 void Geons3D::onSimEventGeons3DPrior(SimEventQueue& q, 00169 rutz::shared_ptr<SimEventGeons3DPrior>& e) 00170 { 00171 itsGeonsState = e->getGeons(); 00172 itsGlobalRotation = e->getRotation(); 00173 itsGlobalPos = e->getPos(); 00174 00175 } 00176 00177 // ###################################################################### 00178 void Geons3D::onSimEventSaveOutput(SimEventQueue& q, rutz::shared_ptr<SimEventSaveOutput>& e) 00179 { 00180 if (itsShowDebug.getVal()) 00181 { 00182 // get the OFS to save to, assuming sinfo is of type 00183 // SimModuleSaveInfo (will throw a fatal exception otherwise): 00184 nub::ref<FrameOstream> ofs = 00185 dynamic_cast<const SimModuleSaveInfo&>(e->sinfo()).ofs; 00186 Layout<PixRGB<byte> > disp = getDebugImage(q); 00187 ofs->writeRgbLayout(disp, "Geons3D", FrameInfo("Geons3D", SRC_POS)); 00188 } 00189 } 00190 00191 // ###################################################################### 00192 void Geons3D::onSimEventUserInput(SimEventQueue& q, rutz::shared_ptr<SimEventUserInput>& e) 00193 { 00194 00195 LINFO("Got event %s %ix%i key=%i", 00196 e->getWinName(), 00197 e->getMouseClick().i, 00198 e->getMouseClick().j, 00199 e->getKey()); 00200 00201 if (strcmp(e->getWinName(), "Geons3D")) 00202 return; 00203 00204 itsLearn = !itsLearn; 00205 //LINFO("Learning: %i", itsLearn); 00206 if (e->getMouseClick().isValid()) 00207 { 00208 } 00209 00210 GeonState& geon = itsProposals[0]; 00211 switch(e->getKey()) 00212 { 00213 case 104: //up 00214 geon.pos.x -= 1.0; 00215 break; 00216 case 98: //down 00217 geon.pos.x += 1.0; 00218 break; 00219 case 100: //left 00220 geon.pos.y -= 1.0; 00221 break; 00222 case 102: //right 00223 geon.pos.y += 1.0; 00224 break; 00225 case 21: //= 00226 geon.pos.z += 1.0; 00227 break; 00228 case 20: //- 00229 geon.pos.z -= 1.0; 00230 break; 00231 case 38: //a 00232 geon.rot.x += 5; 00233 break; 00234 case 52: //z 00235 geon.rot.x -= 5; 00236 break; 00237 case 39: //s 00238 geon.rot.y += 5; 00239 break; 00240 case 53: //x 00241 geon.rot.y -= 5; 00242 break; 00243 case 40: //d 00244 geon.rot.z += 5; 00245 break; 00246 case 54: //c 00247 geon.rot.z -= 5; 00248 break; 00249 00250 case 10: //1 00251 geon.superQuadric.its_a1 += 1; 00252 break; 00253 case 24: //q 00254 geon.superQuadric.its_a1 -= 1; 00255 break; 00256 case 11: //2 00257 geon.superQuadric.its_a2 += 1; 00258 break; 00259 case 25: //w 00260 geon.superQuadric.its_a2 -= 1; 00261 break; 00262 case 12: //3 00263 geon.superQuadric.its_a3 += 1; 00264 break; 00265 case 26: //e 00266 geon.superQuadric.its_a3 -= 1; 00267 break; 00268 case 13: //4 00269 geon.superQuadric.its_n += 0.1; 00270 break; 00271 case 27: //r 00272 geon.superQuadric.its_n -= 0.1; 00273 break; 00274 case 14: //5 00275 geon.superQuadric.its_e += 0.1; 00276 break; 00277 case 28: //t 00278 geon.superQuadric.its_e -= 0.1; 00279 break; 00280 case 15: //5 00281 geon.superQuadric.its_alpha += 0.1; 00282 break; 00283 case 29: //t 00284 geon.superQuadric.its_alpha -= 0.1; 00285 break; 00286 } 00287 00288 LINFO("Pos(%0.2f,%0.2f,%0.2f), rotation(%0.2f,%0.2f,%0.2f) (%f,%f,%f,%f,%f,%f)", 00289 geon.pos.x, geon.pos.y, geon.pos.z, 00290 geon.rot.x, geon.rot.y, geon.rot.z, 00291 geon.superQuadric.its_a1, geon.superQuadric.its_a2, geon.superQuadric.its_a3, 00292 geon.superQuadric.its_n, geon.superQuadric.its_e, 00293 geon.superQuadric.its_alpha); 00294 00295 00296 //evolve(q); 00297 00298 } 00299 00300 void Geons3D::calcGeonLikelihood(GeonState& geon) 00301 { 00302 Image<float> edges; 00303 Image<float> surface; 00304 double edgeProb = calcGeonEdgeLikelihood(geon, edges, surface); 00305 double surfaceProb = calcGeonSurfaceLikelihood(geon, edges, surface); 00306 00307 geon.prob = edgeProb * surfaceProb; 00308 00309 } 00310 00311 void Geons3D::drawGeon(const GeonState& geon) 00312 { 00313 } 00314 00315 void Geons3D::renderScene(const GeonState& geon, std::vector<ViewPort3D::Line>& lines, Image<PixRGB<byte> >& frame) 00316 { 00317 itsViewPort->setWireframeMode(true); 00318 itsViewPort->setLightsMode(true); 00319 itsViewPort->setFeedbackMode(true); 00320 itsViewPort->initFrame(); 00321 00322 drawGeon(geon); 00323 lines = itsViewPort->getFrameLines(); 00324 00325 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); 00326 drawGeon(geon); 00327 00328 frame = flipVertic(itsViewPort->getFrame()); 00329 } 00330 00331 double Geons3D::calcGeonEdgeLikelihood(GeonState& geon, Image<float>& edges, Image<float>& surface) 00332 { 00333 00334 itsViewPort->setWireframeMode(true); 00335 itsViewPort->setLightsMode(true); 00336 itsViewPort->setFeedbackMode(true); 00337 itsViewPort->initFrame(); 00338 00339 drawGeon(geon); 00340 std::vector<ViewPort3D::Line> lines = itsViewPort->getFrameLines(); 00341 00342 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); 00343 drawGeon(geon); 00344 00345 Image<PixRGB<byte> > frame = flipVertic(itsViewPort->getFrame()); 00346 surface = luminance(frame); 00347 00348 00349 Image<float> edgesMag, edgesOri; 00350 gradientSobel(surface, edgesMag, edgesOri); 00351 00352 Image<float> mag(itsEdgesDT.getDims(),ZEROS); 00353 Image<float> ori(itsEdgesDT.getDims(),ZEROS); 00354 00355 inplaceNormalize(edgesMag, 0.0F, 100.0F); 00356 for(uint i=0; i<lines.size(); i++) 00357 { 00358 00359 double dx = lines[i].p2.i - lines[i].p1.i; 00360 double dy = lines[i].p2.j - lines[i].p1.j; 00361 double ang = atan2(dx,dy) + M_PI/2; 00362 //Change orientation from 0 to M_PI 00363 if (ang < 0) ang += M_PI; 00364 if (ang >= M_PI) ang -= M_PI; 00365 00366 //Get the center of the line 00367 Point2D<float> center = lines[i].p1 + Point2D<float>(dx/2,dy/2); 00368 if (edgesMag.coordsOk(Point2D<int>(lines[i].p1)) && 00369 edgesMag.coordsOk(Point2D<int>(lines[i].p2)) && 00370 edgesMag.getVal(Point2D<int>(lines[i].p1)) > 10 && 00371 edgesMag.getVal(Point2D<int>(lines[i].p2)) > 10 && 00372 edgesMag.getVal(Point2D<int>(center)) > 10) 00373 { 00374 drawLine(mag, Point2D<int>(lines[i].p1), Point2D<int>(lines[i].p2), 1.0F); 00375 drawLine(ori, Point2D<int>(lines[i].p1), Point2D<int>(lines[i].p2), float(ang)); 00376 } 00377 } 00378 00379 00380 edges = mag; 00381 00382 return getEdgeProb(itsEdgesDT, ori, mag); 00383 00384 } 00385 00386 double Geons3D::calcGeonSurfaceLikelihood(GeonState& geon, Image<float>& edges, Image<float>& surface) 00387 { 00388 //Remove the edges from the surface 00389 for(uint i=0; i<surface.size(); i++) 00390 if (edges[i] > 0) 00391 surface[i] = 0; 00392 00393 return getSurfaceProb(itsEdgesDT,surface); 00394 00395 } 00396 00397 00398 double Geons3D::getEdgeProb(Image<float>& mag, Image<float>& modelOri, Image<float>& modelMag) 00399 { 00400 00401 //SHOWIMG(data); 00402 double prob = 0; 00403 int pixCount = 0; 00404 00405 //double r = 20; 00406 00407 int numOfEntries = itsOriEdgesDT.size(); 00408 double D = M_PI/numOfEntries; 00409 00410 //SHOWIMG(modelOri); 00411 //SHOWIMG(itsLinesOri); 00412 00413 ImageSet<float> modelOriImg(numOfEntries, modelMag.getDims(), ZEROS); 00414 00415 for(int y=0; y < modelMag.getHeight(); y++) 00416 for(int x=0; x < modelMag.getWidth(); x++) 00417 if (modelMag.getVal(x,y) > 0) 00418 { 00419 00420 float phi = modelOri.getVal(x,y); 00421 00422 int oriIdx = (int)floor(phi/D); 00423 00424 modelOriImg[oriIdx].setVal(x,y, 255.0F); 00425 00426 int oriIdxMin = (oriIdx-1)%numOfEntries; 00427 if (oriIdxMin < 0) 00428 oriIdxMin += numOfEntries; 00429 int oriIdxMax = (oriIdx+1)%numOfEntries; 00430 00431 float v1 = itsOriEdgesDT[oriIdxMin].getVal(x,y); 00432 float v2 = itsOriEdgesDT[oriIdx].getVal(x,y); 00433 float v3 = itsOriEdgesDT[oriIdxMax].getVal(x,y); 00434 00435 prob += std::min(v1, std::min(v2,v3)); 00436 //prob += ( (v1*2) + v2 + (v3*2) )/5; 00437 00438 //prob += itsEdgesDT.getVal(x,y); 00439 pixCount++; 00440 } 00441 00442 return exp(-prob/ double(pixCount*20)); 00443 } 00444 00445 double Geons3D::getSurfaceProb(Image<float>& data, Image<float>& model) 00446 { 00447 00448 double prob = 0; 00449 int pixCount = 0; 00450 00451 for(int y=0; y < model.getHeight(); y++) 00452 for(int x=0; x < model.getWidth(); x++) 00453 if (model.getVal(x,y) > 0) 00454 { 00455 prob += (10-data.getVal(x,y)); 00456 pixCount++; 00457 } 00458 prob /= (10*pixCount); 00459 00460 return exp(-prob); 00461 } 00462 00463 00464 std::vector<Geons3D::GeonState> Geons3D::proposeGeons(Rectangle& attenLoc) 00465 { 00466 00467 std::vector<Geons3D::GeonState> geons; 00468 00469 return geons; 00470 00471 } 00472 00473 void Geons3D::testLikelihood() 00474 { 00475 00476 //For Frame 390 00477 //Image<PixRGB<byte> > img = calcGeonLikelihood(geon); 00478 //double trueLocProb = geon.prob; 00479 00480 00481 //geon.pos = Point3D<float>(26.80,-44.90,15.00); 00482 //img = calcGeonLikelihood(geon); 00483 //double emptyLocProb = geon.prob; 00484 00485 //geon.pos = Point3D<float>(5.90,-251.11,15.00); 00486 //img = calcGeonLikelihood(geon); 00487 //double noiseLocProb = geon.prob; 00488 00489 //geon.pos = Point3D<float>(-6.014052,-206.406448,15.00); 00490 //geon.rotation = Point3D<float>(0,0,55.973370); 00491 //img = calcGeonLikelihood(geon); 00492 //double noise2LocProb = geon.prob; 00493 // 00494 //LINFO("TrueLocProb %f", trueLocProb); 00495 //LINFO("EmptyLocProb %f", emptyLocProb); 00496 //LINFO("NoiseLocProb %f", noiseLocProb); 00497 //LINFO("Noise2LocProb %f", noise2LocProb); 00498 00499 //for frame 0 00500 00501 //LINFO("Get true prob"); 00502 //calcGeonLikelihood(geon); 00503 //double trueLocProb = geon.prob; 00504 00505 //geon.pos = Point3D<float>(-4.964483, 45.255852,5.188423); 00506 //geon.rotation = Point3D<float>(0,0,85); 00507 00508 //LINFO("Get bad prob"); 00509 //calcGeonLikelihood(geon); 00510 //double badLocProb = geon.prob; 00511 00512 //LINFO("True prob %f", trueLocProb); 00513 //LINFO("Bad prob %f", badLocProb); 00514 } 00515 00516 // ###################################################################### 00517 void Geons3D::evolve(SimEventQueue& q) 00518 { 00519 00520 //std::vector<Geons3D::GeonState> geonsState = proposeGeons(); 00521 //for(uint j=0; j<geonsState.size(); j++) 00522 //{ 00523 // calcGeonLikelihood(geonsState[j]); 00524 // totalEvaluated++; 00525 //} 00526 00527 } 00528 00529 Image<PixRGB<byte> > Geons3D::getGeonImage(GeonState& geon) 00530 { 00531 00532 itsViewPort->setWireframeMode(false); 00533 itsViewPort->setLightsMode(true); 00534 itsViewPort->setFeedbackMode(false); 00535 00536 itsViewPort->initFrame(); 00537 00538 itsViewPort->setColor(PixRGB<byte>(255,100,100)); 00539 glTranslatef(geon.pos.x, geon.pos.y, geon.pos.z); 00540 glRotatef(geon.rot.x, 1,0,0); 00541 glRotatef(geon.rot.y, 0,1,0); 00542 glRotatef(geon.rot.z, 0,0,1); 00543 00544 if (geon.superQuadric.its_alpha >= 0) 00545 geon.superQuadric.solidToroid(); 00546 else 00547 geon.superQuadric.solidEllipsoid(); 00548 00549 return flipVertic(itsViewPort->getFrame()); 00550 } 00551 00552 00553 Layout<PixRGB<byte> > Geons3D::getDebugImage(SimEventQueue& q) 00554 { 00555 Layout<PixRGB<byte> > outDisp; 00556 00557 //Image<float> input = itsLinesMag; 00558 //inplaceNormalize(input, 0.0F, 255.0F); 00559 //Image<PixRGB<byte> > tmp = toRGB(Image<byte>(input)); 00560 00561 Image<PixRGB<byte> > inputFrame = itsLGNInput[0]; 00562 float nSeg = 20; 00563 const float dTheta = 2*M_PI / (float)nSeg; 00564 00565 for(uint i=0; i<itsSurfaces.size(); i++) 00566 { 00567 const TwoHalfDSketch::SurfaceState& surface = itsSurfaces[i]; 00568 float a = surface.a; 00569 float b = surface.b; 00570 float e = surface.e; 00571 float k1 = surface.k1; 00572 float k2 = surface.k2; 00573 float rot = surface.rot; 00574 Point2D<float> p = surface.pos; 00575 00576 for (float theta=surface.start; theta < surface.end; theta += dTheta) 00577 { 00578 Point2D<float> p1 = ellipsoid(a,b, e, theta); 00579 Point2D<float> p2 = ellipsoid(a,b, e, theta + dTheta); 00580 00581 Point2D<float> tmpPos1; 00582 Point2D<float> tmpPos2; 00583 00584 //Sheer 00585 tmpPos1.i = p1.i + p1.j*k1; 00586 tmpPos1.j = p1.i*k2 + p1.j; 00587 00588 tmpPos2.i = p2.i + p2.j*k1; 00589 tmpPos2.j = p2.i*k2 + p2.j; 00590 00591 //Rotate and move to p 00592 p1.i = (cos(rot)*tmpPos1.i - sin(rot)*tmpPos1.j) + p.i; 00593 p1.j = (sin(rot)*tmpPos1.i + cos(rot)*tmpPos1.j) + p.j; 00594 00595 p2.i = (cos(rot)*tmpPos2.i - sin(rot)*tmpPos2.j) + p.i; 00596 p2.j = (sin(rot)*tmpPos2.i + cos(rot)*tmpPos2.j) + p.j; 00597 00598 drawLine(inputFrame, (Point2D<int>)p1, (Point2D<int>)p2, PixRGB<byte>(255,0,0)); 00599 00600 } 00601 } 00602 00603 Image<PixRGB<byte> > proposalsImg(inputFrame.getDims(), ZEROS);; 00604 for(uint i=0; i<itsProposals.size(); i++) 00605 { 00606 Image<PixRGB<byte> > frame = getGeonImage(itsProposals[i]); 00607 proposalsImg += rescale(frame, inputFrame.getDims()); 00608 } 00609 00610 outDisp = hcat(inputFrame, proposalsImg); 00611 00612 return outDisp; 00613 00614 } 00615 00616 00617 // ###################################################################### 00618 /* So things look consistent in everyone's emacs... */ 00619 /* Local Variables: */ 00620 /* indent-tabs-mode: nil */ 00621 /* End: */ 00622 00623 #endif 00624