00001 /*!@file Surprise/SurpriseMapFFT.C a surprise map */ 00002 00003 // //////////////////////////////////////////////////////////////////// // 00004 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2003 // 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: Laurent Itti <itti@usc.edu> 00034 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Surprise/SurpriseMapFFT.C $ 00035 // $Id: SurpriseMapFFT.C 9412 2008-03-10 23:10:15Z farhan $ 00036 // 00037 00038 #ifdef HAVE_FFTW3_H 00039 00040 #include "Surprise/SurpriseMapFFT.H" 00041 00042 #include "Image/All.H" // for gaussianBlob() 00043 #include "Image/Kernels.H" 00044 #include "Image/Conversions.H" 00045 #include "Image/MathOps.H" 00046 #include "Util/Assert.H" 00047 #include "Raster/Raster.H" 00048 #include "Image/Pixels.H" 00049 00050 #ifdef HAVE_FFTW3_H 00051 #include <fftw3.h> 00052 #endif 00053 00054 // ###################################################################### 00055 template <class T> 00056 SurpriseMapFFT<T>::SurpriseMapFFT() : 00057 itsModels(), itsQlen(0), itsInitialModel(), 00058 itsNeighSigma(0.0f), itsLocSigma(0.0f), itsNweights(), itsNWmin(0.0f), 00059 itsNeighUpdFac(0.7), itsProbe(-1, -1), itsSLfac(1.0), itsSSfac(0.1), 00060 itsSFSfac(1.0),itsSFPfac(1.0), itsDescrName("blank"),itsTagName("blank"), 00061 itsCounter(0) 00062 {} 00063 00064 // ###################################################################### 00065 template <class T> 00066 void SurpriseMapFFT<T>::init(const uint qlen, const double updatefac, 00067 const double neighupdatefac, 00068 const double sampleval, const double samplevar, 00069 const float neighsigma, const float locsigma, 00070 const Point2D<int>& probe, const double slfac, 00071 const double ssfac, const double sfsfac, 00072 const double sfpfac, 00073 const std::string& descrName, 00074 const std::string& tagName) 00075 { 00076 itsQlen = qlen; 00077 itsModels.clear(); 00078 for(uint j = 0; j < itsFFTPModels.size(); j ++) 00079 { 00080 itsFFTSModels[j].clear(); 00081 itsFFTPModels[j].clear(); 00082 } 00083 itsInitialModel.init(updatefac, sampleval, samplevar); 00084 itsNeighSigma = neighsigma; itsLocSigma = locsigma; 00085 itsNweights.freeMem(); 00086 itsNeighUpdFac = neighupdatefac; 00087 itsProbe = probe; 00088 itsVariance = samplevar; 00089 itsUpdatefac = updatefac; 00090 itsSLfac = slfac; itsSSfac = ssfac; itsSFSfac = sfsfac; itsSFPfac = sfpfac; 00091 itsDescrName = descrName; 00092 itsTagName = tagName; 00093 const int N = FFT_TIME_SLICE; 00094 LINFO("NEW FFT IN"); 00095 in = static_cast<fftw_complex*>(fftw_malloc(sizeof(fftw_complex) * N)); 00096 LINFO("NEW FFT OUT"); 00097 out = static_cast<fftw_complex*>(fftw_malloc(sizeof(fftw_complex) * N)); 00098 //p = fftw_plan_dft_1d(FFT_TIME_SLICE, in, out, FFTW_FORWARD, FFTW_ESTIMATE); 00099 LINFO("NEW FFT PLAN"); 00100 p = fftw_plan_dft_1d(N, in, out, FFTW_FORWARD, FFTW_PATIENT); 00101 LINFO("DONE"); 00102 itsCounter = 0; 00103 } 00104 00105 // ###################################################################### 00106 template <class T> 00107 SurpriseMapFFT<T>::~SurpriseMapFFT() 00108 {} 00109 00110 // ###################################################################### 00111 template <class T> 00112 void SurpriseMapFFT<T>::reset() 00113 { 00114 00115 for(uint j = 0; j < itsFFTSModels.size(); j ++) 00116 { 00117 itsFFTSModels[j].reset(); 00118 itsFFTPModels[j].reset(); 00119 } 00120 00121 LINFO("DESTROY FFT PLAN"); 00122 fftw_destroy_plan(p); 00123 fftw_free(in); 00124 fftw_free(out); 00125 LINFO("DONE"); 00126 } 00127 00128 // ###################################################################### 00129 template <class T> 00130 Image<double> SurpriseMapFFT<T>::surprise(const SurpriseImage<T>& sample, 00131 const Image<double>& inputI, 00132 const Image<double>& var) 00133 { 00134 // set up a stack STL deque of to contain all images over time 00135 // for use in fft 00136 itsVarianceImage = var; 00137 if(fftStack.size() != FFT_TIME_SLICE) 00138 { 00139 fftStackCount = 0; 00140 fftStackReady = false; 00141 // reduce the number of FFT models by octave sized bins 00142 fftBins = (unsigned char)(floor(log(FFT_TIME_SLICE/2)/log(2))); 00143 unsigned char tempsize = 1; 00144 // create octive sized bins and store thier size; 00145 for(unsigned char i = 0; i < fftBins; i++) 00146 { 00147 fftBinSize.push_back(tempsize); 00148 tempsize = tempsize * 2; 00149 } 00150 Image<double> temp; 00151 fftStack.resize(FFT_TIME_SLICE,temp); 00152 } 00153 // copy the sample onto the deque 00154 fftStack.pop_front(); 00155 fftStack.push_back(inputI); 00156 00157 00158 // is it the first time we are called? if so, we need to setup our 00159 // size and reset our neighborhood cache: 00160 if (itsModels.empty()) 00161 { 00162 // resize and reset our queue of models: 00163 SurpriseImage<T> models(sample.getDims()); // NOTE: uninitialized models 00164 models.clear(itsInitialModel); 00165 models.reset(); 00166 itsFFTSModels.clear(); 00167 itsFFTPModels.clear(); 00168 itsModels.clear(); 00169 for (uint i = 0; i < fftBins; i ++) 00170 { 00171 itsFFTSModels.push_back(models); 00172 itsFFTPModels.push_back(models); 00173 itsModels.clear(); 00174 } 00175 00176 // compute our Difference-of-Gaussians mask of weights: 00177 const int w = sample.getWidth(), h = sample.getHeight(); 00178 const float sigma = itsNeighSigma * float(std::max(w, h)); 00179 Dims d(w * 2 + 1, h * 2 + 1); Point2D<int> p(w, h); 00180 itsNweights = gaussianBlob<float>(d, p, sigma, sigma); 00181 itsNweights -= gaussianBlob<float>(d, p, itsLocSigma, 00182 itsLocSigma) * 00183 (itsLocSigma*itsLocSigma / (sigma * sigma) * 1.5f); 00184 00185 inplaceRectify(itsNweights); // eliminate negative values 00186 00187 float mi, ma; getMinMax(itsNweights, mi, ma); 00188 itsNWmin = 0.01f * ma; 00189 } 00190 else if (itsModels[0].isSameSize(sample) == false) 00191 LFATAL("Inconsistent input size!"); 00192 else if (itsFFTSModels[0].isSameSize(sample) == false) 00193 LFATAL("Inconsistent FFT signal mag input size!"); 00194 else if (itsFFTPModels[0].isSameSize(sample) == false) 00195 LFATAL("Inconsistent FFT phase input size!"); 00196 // each model feeds into the next one. The first (fastest) model 00197 // receives the sample from the image as input, and is updated. The 00198 // updated model then serves as input to the next slower model, and 00199 // so on. Total surprise is the product from all models: 00200 SurpriseImage<T> input(sample); 00201 Image<double> s; 00202 00203 Image<float> locstot(input.getDims(), ZEROS); 00204 std::vector<Image<double> > sfs; 00205 std::vector<Image<double> > sfp; 00206 00207 if(fftStackReady == true) 00208 { 00209 //float mi, ma; 00210 Image<double> temp; 00211 setFFTModels(input, itsNweights, itsNWmin, sfs, sfp); 00212 /* 00213 for(uint j = 0; j < itsFFTSModels.size(); j++) 00214 { 00215 //LINFO("FFT %d",j); 00216 //temp = itsFFTSModels[i][j].surprise(input); 00217 //LINFO("NEW S ****************************** %d %d",i-1,j); 00218 itsFFTSModels[j].neighborhoods(itsFFTSModels[j], itsNweights, itsNWmin); 00219 if (itsNeighUpdFac != 0.0) // use different update fac for neighs? 00220 itsFFTSModels[j].resetUpdFac(itsNeighUpdFac); 00221 // higher updfac -> stronger popout 00222 temp = itsFFTSModels[j].surprise(itsFFTSModels[j]); 00223 sfs.push_back(temp); 00224 } 00225 */ 00226 /* 00227 //itsFFTSModels[1].neighborhoods(itsFFTSModels[1], itsNweights, itsNWmin); 00228 if (itsNeighUpdFac != 0.0) // use different update fac for neighs? 00229 itsFFTSModels[1].resetUpdFac(itsNeighUpdFac); 00230 // higher updfac -> stronger popout 00231 temp = itsFFTSModels[1].surprise(itsFFTSModels[0]); 00232 sfs.push_back(temp); 00233 */ 00234 // the total surprise is roughly the pointwise product between 00235 // spatial and temporal surprises, for the current model, with a 00236 // small pedestal of spatial surprise to account for tonic 00237 // responses to static stimuli: 00238 00239 Image<double> stot(sfs[0].getDims(), ZEROS); 00240 float mi, ma; 00241 Image<float> locstot(sfs[0].getDims(), ZEROS); 00242 float mi1, ma1; 00243 Image<float> lsf(sfs[0].getDims(), ZEROS); 00244 if (itsSFSfac) 00245 { 00246 if (itsSFSfac != 1.0) 00247 { 00248 for(uint j = 0; j < sfs.size() - 2; j++) 00249 { 00250 //LINFO("AA %f %d",itsSFSfac,sfs.size()); 00251 stot += (sfs[j] * itsSFSfac)/sfs.size(); 00252 locstot = stot; 00253 lsf = sfs[j]; 00254 getMinMax(locstot, mi, ma); 00255 getMinMax(lsf, mi1, ma1); 00256 Image<byte> foo = sfs[j]; 00257 //Raster::WriteGray(foo,sformat("Sout.%d.%d.pgm",i,j)); 00258 //LINFO("FS1 min %f max %f",mi,ma); 00259 //LINFO("FS1 min %f max %f",mi1,ma1); 00260 } 00261 } 00262 else 00263 { 00264 for(uint j = 0; j < sfs.size() - 2; j++) 00265 { 00266 //LINFO("BB %f %d",itsSFSfac,sfs.size()); 00267 stot += sfs[j]/sfs.size(); 00268 locstot = stot; 00269 lsf = sfs[j]; 00270 getMinMax(locstot, mi, ma); 00271 getMinMax(lsf, mi1, ma1); 00272 Image<byte> foo = sfs[j]; 00273 //Raster::WriteGray(foo,sformat("Sout.%d.%d.pgm",i,j)); 00274 //LINFO("FS2 min %f max %f",mi,ma); 00275 //LINFO("FS2 min %f max %f",mi1,ma1); 00276 } 00277 } 00278 } 00279 00280 // total surprise combines multiplicatively across models with 00281 // different time scales: 00282 s = stot; 00283 locstot = s; 00284 getMinMax(locstot, mi, ma); 00285 //LINFO("S1 min %f max %f",mi,ma); 00286 00287 00288 // the results here should always be positive but sometimes turn 00289 // negative due to rounding errors in the surprise computation. 00290 // Let's clamp just to be sure, otherwise we'll end up with some 00291 // NaNs at later stages: 00292 inplaceRectify(s); 00293 00294 // calm down total surprise: 00295 //s = toPower(s, 1.0 / (3.0*double(itsFFTSModels.size()))); 00296 //s = s*255.0F; 00297 locstot = s; 00298 getMinMax(locstot, mi, ma); 00299 //LINFO("S2 min %f max %f",mi,ma); 00300 } 00301 else 00302 { 00303 s = locstot; 00304 } 00305 itsCounter++; 00306 00307 //LINFO("S3 min %f max %f",mi,ma); 00308 00309 // Do we have a full set of frames for fft? 00310 if(fftStackCount < FFT_TIME_SLICE) 00311 fftStackCount++; 00312 else 00313 fftStackReady = true; 00314 00315 00316 // return total surprise: 00317 return s; 00318 } 00319 00320 // ###################################################################### 00321 template <class T> 00322 const SurpriseImage<T>& SurpriseMapFFT<T>::getSurpriseImage(const 00323 uint index) const 00324 { 00325 ASSERT(index < itsModels.size()); 00326 return itsModels[index]; 00327 } 00328 00329 // ###################################################################### 00330 template <class T> 00331 const SurpriseImage<T>& SurpriseMapFFT<T>::getSurpriseImageSFFT( 00332 const uint i) const 00333 { 00334 ASSERT(i < itsFFTSModels.size()); 00335 return itsFFTSModels[i]; 00336 } 00337 00338 // ###################################################################### 00339 template <class T> 00340 const SurpriseImage<T>& SurpriseMapFFT<T>::getSurpriseImagePFFT( 00341 const uint i) const 00342 { 00343 ASSERT(i < itsFFTPModels.size()); 00344 return itsFFTPModels[i]; 00345 } 00346 // ###################################################################### 00347 template <class T> 00348 void SurpriseMapFFT<T>::setFFTModels(const SurpriseImage<T>& models, 00349 const Image<float>& weights, 00350 const float wmin, 00351 std::vector<Image<double> >& sfs, 00352 std::vector<Image<double> >& sfp) 00353 { 00354 #ifndef HAVE_FFTW3_H 00355 LFATAL("this program requires fftw3, " 00356 "but <fftw3.h> was not found during the configure process"); 00357 #else 00358 // initialize FFT things 00359 const int N = FFT_TIME_SLICE; 00360 const double veryBigNumber = pow(10,10); 00361 const double verySmallNumber = 0.0000000000001F; 00362 00363 //in = (fftw_complex*)(fftw_malloc(sizeof(fftw_complex) * N)); 00364 //out = (fftw_complex*)(fftw_malloc(sizeof(fftw_complex) * N)); 00365 00366 //fftw_print_plan(p); 00367 //std::cerr << "\n"; 00368 // initialize holder images to put FFT results into 00369 Image<double> SSpaces, PSpaces; 00370 SSpaces.resize(itsFFTSModels[0].getWidth(), 00371 itsFFTSModels[0].getHeight()); 00372 PSpaces.resize(itsFFTSModels[0].getWidth(), 00373 itsFFTSModels[0].getHeight()); 00374 00375 Image<double>::iterator iSSpace = SSpaces.beginw(); 00376 Image<double>::iterator iPSpace = PSpaces.beginw(); 00377 const unsigned int tpixels = itsFFTSModels[0].getWidth() * 00378 itsFFTSModels[0].getHeight(); 00379 for(unsigned int i = 0; i < tpixels ; i++ , ++iSSpace, ++iPSpace) 00380 { 00381 *iSSpace = 0; *iPSpace = 0; 00382 } 00383 00384 Image<float> temps; 00385 00386 /* 00387 float mis, mas; 00388 std::deque<Image<double> >::iterator ifftStack = fftStack.begin; 00389 00390 for(int k = 0; k < N; k++, ++ifftStack) 00391 { 00392 temps = ifftStack; 00393 getMinMax(temps, mis, mas); 00394 //LINFO("DEQUE %d min %f max %f",k,mis,mas); 00395 } 00396 */ 00397 // stack holder images into a vector 00398 std::vector<Image<double> > VSSpaces(itsFFTSModels.size(),SSpaces); 00399 std::vector<Image<double> > VPSpaces(itsFFTSModels.size(),PSpaces); 00400 00401 for(int i = 0; i < itsFFTSModels[0].getWidth(); i++) 00402 { 00403 for(int j = 0; j < itsFFTSModels[0].getHeight(); j++) 00404 { 00405 for(unsigned int k = 0; k < (unsigned)N; k++) 00406 { 00407 in[k][1] = fftStack[k].getVal(i,j); 00408 in[k][0] = fftStack[k].getVal(i,j); 00409 //std::cerr << "INPUT: " << k << " " << in[k][1] << "\n"; 00410 } 00411 //LINFO("DOING PLAN"); 00412 fftw_execute(p); 00413 unsigned char binSize = 1; 00414 unsigned char binCount = 1; 00415 unsigned char bin = 0; 00416 float val = 0.0F; 00417 int k = 0; 00418 //LINFO("Process FFT"); 00419 while(bin != (unsigned char)itsFFTSModels.size()) 00420 { 00421 // fftw returns a very large number if there is no response on 00422 // some given frequency what so ever. For instance, a constant 00423 // function yields a dirac delta and the rest are all large 00424 // numbers 00425 //LINFO("%f %f",(float)out[k][0],(float)out[k][1]); 00426 00427 if((fabs(out[k][1]) < veryBigNumber) && 00428 (fabs(out[k][0]) < veryBigNumber)) 00429 { 00430 if(out[k][0] != 0) 00431 val = VPSpaces[bin].getVal(i,j) + fabs(atan(out[k][1]/out[k][0])); 00432 else 00433 val = 0.0F; 00434 } 00435 else 00436 { 00437 // compute phase from FFT 00438 val = verySmallNumber; 00439 } 00440 VPSpaces[bin].setVal(i,j,val); 00441 00442 if(fabs(out[k][1]) < veryBigNumber) 00443 out[k][1] = out[k][1]; 00444 else 00445 out[k][1] = verySmallNumber; 00446 00447 if(fabs(out[k][0]) < veryBigNumber) 00448 out[k][0] = out[k][0]; 00449 else 00450 out[k][0] = verySmallNumber; 00451 00452 // compute sprectral mag. from FFT 00453 val = VSSpaces[bin].getVal(i,j) 00454 + sqrt(pow(out[k][0],2) + pow(out[k][1],2)); 00455 //std::cerr << val << " S " << out[k][0] << " " << out[k][1] << "\n"; 00456 VSSpaces[bin].setVal(i,j,val); 00457 // reduce size and noise by bin-ing by octave 00458 if(binCount == binSize) 00459 { 00460 // we normalize specta by 1/(N * sqrt(2)) to give a spectra 00461 // in the same range of values as a normal pixel 00462 val = (VSSpaces[bin].getVal(i,j)/binSize) * 1/(N * sqrt(2)); 00463 VSSpaces[bin].setVal(i,j,val); 00464 val = (VPSpaces[bin].getVal(i,j)/binSize) * (255.0F/(3.14159F/2.0F)); 00465 VPSpaces[bin].setVal(i,j,val); 00466 binSize = binSize * 2; 00467 binCount = 1; 00468 bin++; 00469 } 00470 else 00471 { 00472 binCount++; 00473 } 00474 k++; 00475 } 00476 } 00477 } 00478 00479 00480 // get our stack of fft images into a start model then 00481 // surprise the current on-going model with it. 00482 Image<double> temp; 00483 Image<float> holder; 00484 float mi, ma; 00485 for(int bin = 0; bin < (unsigned char)itsFFTSModels.size(); bin++) 00486 { 00487 //LINFO("%d",bin); 00488 Image<double> invar(VSSpaces[bin].getDims(), NO_INIT); 00489 //invar.clear(itsVariance); 00490 //std::cerr << itsUpdatefac << " " << itsVariance << "\n"; 00491 holder = VSSpaces[bin]; 00492 getMinMax(holder, mi, ma); 00493 //LINFO("BIN S %d min %f max %f",bin,mi,ma); 00494 holder = VPSpaces[bin]; 00495 getMinMax(holder, mi, ma); 00496 //LINFO("BIN P %d min %f max %f",bin,mi,ma); 00497 00498 Image<float> outImageO = VSSpaces[bin]; 00499 Image<PixRGB<float> > outImageF; 00500 outImageO = rescale(outImageO,200,(int)round(200.0F* 00501 ((float)outImageO.getHeight()/ 00502 (float)outImageO.getWidth()))); 00503 outImageF = normalizeWithScale(outImageO,0,255.0F,255.0F,1,3); 00504 Image<PixRGB<byte> > outImage = outImageF; 00505 Raster::WriteRGB(outImage,sformat("out.fft.spec.frame%d.%s.%d.%dx%d.png", 00506 itsCounter, 00507 itsTagName.c_str(), 00508 bin, 00509 VSSpaces[bin].getWidth(), 00510 VSSpaces[bin].getHeight() 00511 )); 00512 00513 00514 Image<float> outImageO2 = VPSpaces[bin]; 00515 Image<PixRGB<float> > outImageF2; 00516 outImageO2 = rescale(outImageO2,200,(int)round(200.0F* 00517 ((float)outImageO2.getHeight()/ 00518 (float)outImageO2.getWidth()))); 00519 outImageF2 = normalizeWithScale(outImageO2,0,255.0F,255.0F,1,3); 00520 outImage = outImageF2; 00521 Raster::WriteRGB(outImage,sformat("out.fft.phas.frame%d.%s.%d.%dx%d.png", 00522 itsCounter, 00523 itsTagName.c_str(), 00524 bin, 00525 VPSpaces[bin].getWidth(), 00526 VPSpaces[bin].getHeight() 00527 )); 00528 00529 00530 SurpriseImage<T> SSSpaces(itsNeighUpdFac, 00531 VSSpaces[bin],itsVarianceImage); 00532 SurpriseImage<T> SPSpaces(itsNeighUpdFac, 00533 VPSpaces[bin],itsVarianceImage); 00534 //LINFO("A"); 00535 //LINFO("FFT S ****************************** bin %d",bin); 00536 //SSSpaces.neighborhoods(SSSpaces, itsNweights, itsNWmin); 00537 //if (itsNeighUpdFac != 0.0) // use different update fac for neighs? 00538 // SSSpaces.resetUpdFac(itsNeighUpdFac); // higher updfac -> stronger popout 00539 //Image<double> temp2(SSSpaces); 00540 temp = itsFFTSModels[bin].surprise(SSSpaces); 00541 //SSSpaces.neighborhoods(temp, itsNweights, itsNWmin); 00542 //temp = SSSpaces; 00543 sfs.push_back(temp); 00544 //LINFO("B"); 00545 //LINFO("FFT P ****************************** bin %d",bin); 00546 temp = itsFFTPModels[bin].surprise(SPSpaces); 00547 sfp.push_back(temp); 00548 } 00549 //LINFO("DONE"); 00550 #endif 00551 } 00552 00553 00554 00555 00556 00557 00558 // ###################################################################### 00559 // explicit instantiations: 00560 template class SurpriseMapFFT<SurpriseModelSG>; 00561 template class SurpriseMapFFT<SurpriseModelSP>; 00562 template class SurpriseMapFFT<SurpriseModelOD>; 00563 00564 #endif // HAVE_FFTW3_H 00565 00566 // ###################################################################### 00567 /* So things look consistent in everyone's emacs... */ 00568 /* Local Variables: */ 00569 /* indent-tabs-mode: nil */ 00570 /* End: */