latest version v1.9 - last update 10 Apr 2010 |
00001 /* 00002 * Copyright (C) 2003, 2004, 2005, 2006 00003 * Lehrstuhl fuer Technische Informatik, RWTH-Aachen, Germany 00004 * 00005 * This file is part of the LTI-Computer Vision Library (LTI-Lib) 00006 * 00007 * The LTI-Lib is free software; you can redistribute it and/or 00008 * modify it under the terms of the GNU Lesser General Public License (LGPL) 00009 * as published by the Free Software Foundation; either version 2.1 of 00010 * the License, or (at your option) any later version. 00011 * 00012 * The LTI-Lib is distributed in the hope that it will be 00013 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty 00014 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00015 * GNU Lesser General Public License for more details. 00016 * 00017 * You should have received a copy of the GNU Lesser General Public 00018 * License along with the LTI-Lib; see the file LICENSE. If 00019 * not, write to the Free Software Foundation, Inc., 59 Temple Place - 00020 * Suite 330, Boston, MA 02111-1307, USA. 00021 */ 00022 00023 00024 /*-------------------------------------------------------------------- 00025 * project ....: LTI-Lib: Image Processing and Computer Vision Library 00026 * file .......: ltiFaceThreshold.h 00027 * authors ....: Thorsten Dick 00028 * organization: LTI, RWTH Aachen 00029 * creation ...: 17.12.2003 00030 * revisions ..: $Id: ltiFaceThreshold.h,v 1.10 2007/01/10 02:25:51 alvarado Exp $ 00031 */ 00032 00033 #ifndef _LTI_FACE_THRESHOLD_H_ 00034 #define _LTI_FACE_THRESHOLD_H_ 00035 00036 #include "ltiImage.h" 00037 #include "ltiConvolution.h" 00038 #include "ltiGaussKernels.h" 00039 #include "ltiPolygonPoints.h" 00040 #include "ltiGeometricFeatures.h" 00041 #include "ltiFastObjectsFromMask.h" 00042 00043 #include <iostream> 00044 #include "ltiFunctor.h" 00045 00046 namespace lti { 00047 00048 /** 00049 * The ltiFaceThreshold-class calculates for a given skin probability mask of 00050 * an image an extraction-threshold that allows to 'cut-out' the face (blob) 00051 * of the shown person. 00052 * Depending on the apply()-method that is being called, the resulting 00053 * face-blob is also returned. <BR> 00054 * In order to do so, a blob-ranking is performed, which can be done in two 00055 * ways: 00056 * <ul> 00057 * <li> linear mode: every n-th threshold is tested 00058 * <li> time-optimized mode: binary search like threshold testing 00059 * </ul> 00060 * For every regarded threshold, all blobs (with size greater than a 00061 * specified minimum) are extracted and then ranked considering the 00062 * following criteria: 00063 * <ol> 00064 * <li> the compactness 00065 * <li> the main axis orientation 00066 * <li> the relation of the the bounding box's edges' lengths 00067 * <li> the horizontal position in the image 00068 * <li> the vertical position in the image 00069 * <li> the size (relative to image size) 00070 * <li> the skin-probability 00071 * </ol> 00072 * The result is the threshold that causes the blob with the highest score. 00073 * For a detailed description of the functions that benchmark the above 00074 * mentioned criteria see the individual parameters 00075 * (lti::faceThreshold::parameters) that determine computation. 00076 */ 00077 00078 class faceThreshold : public functor { 00079 public: 00080 00081 // ############################### parameters ############################ 00082 00083 /** 00084 * The parameters for the class faceThreshold. <BR> 00085 * Note: for reasons of simplicity, in the blob ranking parameters 00086 * formulas, all parameters (i.e.not the arguments) are written without 00087 * the method-related prefix that makes them unique in the code. For 00088 * example, both 'compactnessExponent' and 'mainAxisOrientationExponent' 00089 * are just named 'exponent'.(Since, after all, one can conclude the full 00090 * name by the methods's name...) 00091 */ 00092 class parameters : public functor::parameters { 00093 public: 00094 00095 /** 00096 * Constants for the possible values of 00097 * lti::faceThreshold::parameters::computationMode 00098 * <ul> 00099 * <li> linearMode = 0 00100 * <li> timeOptimizedMode = 1 00101 * </ul> 00102 */ 00103 enum OperationMode { 00104 linearMode = 0, 00105 timeOptimizedMode = 1, 00106 }; 00107 00108 /** 00109 * default constructor 00110 */ 00111 parameters(); 00112 00113 /** 00114 * copy constructor 00115 * @param other the parameters object to be copied 00116 */ 00117 parameters(const parameters& other); 00118 00119 /** 00120 * destructor 00121 */ 00122 ~parameters(); 00123 00124 /** 00125 * returns name of this type 00126 */ 00127 const char* getTypeName() const; 00128 00129 /** 00130 * copy the contents of a parameters object 00131 * @param other the parameters object to be copied 00132 * @return a reference to this parameters object 00133 */ 00134 parameters& copy(const parameters& other); 00135 00136 /** 00137 * copy the contents of a parameters object 00138 * @param other the parameters object to be copied 00139 * @return a reference to this parameters object 00140 */ 00141 parameters& operator=(const parameters& other); 00142 00143 /** 00144 * returns a pointer to a clone of the parameters 00145 */ 00146 virtual functor::parameters* clone() const; 00147 00148 /** 00149 * write the parameters in the given ioHandler 00150 * @param handler the ioHandler to be used 00151 * @param complete if true (the default) the enclosing begin/end will 00152 * be also written, otherwise only the data block will be written. 00153 * @return true if write was successful 00154 */ 00155 virtual bool write(ioHandler& handler,const bool complete=true) const; 00156 00157 /** 00158 * read the parameters from the given ioHandler 00159 * @param handler the ioHandler to be used 00160 * @param complete if true (the default) the enclosing begin/end will 00161 * be also written, otherwise only the data block will be written. 00162 * @return true if read was successful 00163 */ 00164 virtual bool read(ioHandler& handler,const bool complete=true); 00165 00166 # ifdef _LTI_MSC_6 00167 /** 00168 * this function is required by MSVC only, as a workaround for a 00169 * very awful bug, which exists since MSVC V.4.0, and still by 00170 * V.6.0 with all bugfixes (so called "service packs") remains 00171 * there... This method is also public due to another bug, so please 00172 * NEVER EVER call this method directly: use read() instead 00173 */ 00174 bool readMS(ioHandler& handler,const bool complete=true); 00175 00176 /** 00177 * this function is required by MSVC only, as a workaround for a 00178 * very awful bug, which exists since MSVC V.4.0, and still by 00179 * V.6.0 with all bugfixes (so called "service packs") remains 00180 * there... This method is also public due to another bug, so please 00181 * NEVER EVER call this method directly: use write() instead 00182 */ 00183 bool writeMS(ioHandler& handler,const bool complete=true) const; 00184 # endif 00185 00186 // --------------------------------------------------------------------- 00187 // the parameters: computation mode parameters 00188 // --------------------------------------------------------------------- 00189 00190 /** 00191 * Specification of computation mode: 00192 * <ul> 00193 * <li> 0 (alias linearMode) <BR> 00194 * Linear threshold search mode, i.e.every 00195 * lti::faceThreshold::parameters::thresholdStep-th threshold 00196 * will be tested.<BR> 00197 * Note: the parameter 00198 * lti::faceThreshold::parameters::optModePrecision has no effect 00199 * in this mode. 00200 * <li> 1 (alias timeOptimizedMode) <BR> 00201 * Binary-search like mode, i.e.in a first iteration every 16-th 00202 * threshold is tested. The two best ones determine the region 00203 * for the next iteration and so on. The iteration is aborted, if 00204 * the difference between the two best thresholds is less than 00205 * lti::faceThreshold::parameters::optModePrecision. <BR> 00206 * Note: the parameter 00207 * lti::faceThreshold::parameters::thresholdStep has no effect 00208 * in this mode. 00209 * </ul> 00210 * See lti::faceThreshold::parameters::OperationMode for a list of 00211 * predefined mode-constants. <BR> 00212 * Default: 0 00213 */ 00214 int computationMode; 00215 00216 /** 00217 * Except for time-optimized mode (see 00218 * lti::faceThreshold::parameters::computationMode), this parameter 00219 * specifies the distance between two tested thresholds for the linear 00220 * threshold search. <BR> 00221 * Thus, a greater value results in a faster but more inexact blob 00222 * ranking. <BR> 00223 * Range: [1..127], Default: 5 00224 */ 00225 int thresholdStep; 00226 00227 /** 00228 * Besides interpolation (see 00229 * lti::faceThreshold::parameters::interpolationStep), smoothing 00230 * provides better results for a blob's compactness value, since the 00231 * blob's 'fringes' are reduced. Possible values of this parameter: 00232 * <ul> 00233 * <li> <= 1 : smoothing disabled 00234 * <li> 2 <= n: smoothing of the skin probability mask with kernel of 00235 * size n 00236 * </ul> 00237 * Note: in case the input image has not yet been smoothed, a kernel 00238 * size of 19 has shown good results. 00239 * <BR> 00240 * Default: 0 00241 */ 00242 int gaussKernelSize; 00243 00244 /** 00245 * To reduce a blob's 'fringes', its border points can be interpolated, 00246 * where this parameter determines the distance between two unchanged 00247 * border points. 00248 * Possible values: 00249 * <ul> 00250 * <li> <= 0: interpolation disabled 00251 * <li> 0 < n < 100: interpolation with the distance between two 00252 * unchanged border points being n\% of the maximum 00253 * of the image's height and width 00254 * </ul> 00255 * Thus, with increasing value, the roughness of the resulting polygon 00256 * blob's outline increases, too. <BR> 00257 * Default: 1.0 00258 */ 00259 float interpolationStep; 00260 00261 /** 00262 * If the time-optimized threshold search mode is used (see 00263 * lti::faceThreshold::parameters::computationMode), this 00264 * parameter specifies, at which precision to stop the threshold search 00265 * (where a greater value results in a faster computation but more 00266 * inexact threshold). <BR> 00267 * Range: [1..15], Default: 2 00268 */ 00269 int optModePrecision; 00270 00271 // --------------------------------------------------------------------- 00272 // the parameters: blob ranking parameters 00273 // --------------------------------------------------------------------- 00274 00275 /** 00276 * Blob ranking parameter: score for a blob's compactness. 00277 * <BR> 00278 * Compactness is a measure for a blob's area-to-circumference ratio and 00279 * ranges from 0 to 1, where 1 is the compactness of a circle. Thus, 00280 * higher compactness values result in higher scores. The corresponding 00281 * score formula is: 00282 * \f[ 00283 * \text{compactness} (c) := 100 - 100 \cdot (1-c)^{\text{exponent}} 00284 * \f] 00285 * Effect of this parameter: the greater, the more scores are achieved 00286 * with relative small compactness values. 00287 * <BR> 00288 * Range: >=0, Default: 2, Disable: 0 00289 */ 00290 int compactnessExponent; 00291 00292 /** 00293 * Blob ranking parameter: score for a blob's main axis orientation. 00294 * <BR> 00295 * The more vertical a blob's main axis is, the more score is achieved. 00296 * The corresponding score formula is: 00297 * \f[ 00298 * \text{axisOrientation} (a) := 100 \cdot \left\arrowvert 00299 * \dfrac{a}{90} \right\arrowvert^{\text{exponent}} 00300 * \f] 00301 * Effect of this parameter: the greater, the stricter the ranking. 00302 * <BR> 00303 * Range: >=0, Default: 2, Disable: 0 00304 */ 00305 int mainAxisOrientationExponent; 00306 00307 /** 00308 * Blob ranking parameter: score for a blob's ratio of border points 00309 * distances relative to the main axis. 00310 * <BR> 00311 * The sum of the leftmost and rightmost blob points distances to the 00312 * main axis (i.e.orthogonal to the main axis) is set in relation to 00313 * the sum of the frontmost and rearmost blob points distances to the 00314 * blob's center of gravity (i.e.along the main axis). This ratio is 00315 * compared to the specified desired ratio 00316 * 1 : lti::faceThreshold::parameters::dRatioOptRatio. The closer these 00317 * two values are, the more score is achieved. This results in the 00318 * rating of a main axis dependent bounding box's sides proportion. 00319 * The corresponding formula is: 00320 * \f[ 00321 * \text{dRatio} (\Sigma_{\,ortho},\Sigma_{\,along}) := 100 - 100 00322 * \cdot \left( \text{width} \cdot 00323 * \left\arrowvert \dfrac{\Sigma_{\,ortho}}{\Sigma_{\,along}} - 00324 * \dfrac{1}{\text{optRatio}} \right\arrowvert 00325 * \right)^{\text{exponent}} 00326 * \f] 00327 * Effect of this parameter: the greater, the more binary is the ranking 00328 * (i.e.in extreme cases almost equal scores for tolerable blobs and a 00329 * sharp switch to penalty scores), but without changing the null 00330 * (i.e.the sign itself of the score remains unchanged). The tolerable 00331 * deviation is determined by 00332 * lti::faceThreshold::parameters::dRatioWidth. 00333 * <BR> 00334 * Range: >=0, Default: 2, Disable: 0 00335 */ 00336 int dRatioExponent; 00337 00338 /** 00339 * Blob ranking parameter: score for a blob's ratio of border points 00340 * distances relative to the main axis (i.e.a kind of main axis 00341 * dependent bounding box rating). For formula and more detailed 00342 * description see lti::faceThreshold::parameters::dRatioExponent. 00343 * <BR> 00344 * Effect of this parameter: specification of the desired bounding box 00345 * edges relation, e.g. a value of 1 would specify a quadrat and a 00346 * value of 2 a rectangle with edges parallel to main axis twice as 00347 * long as the edges orthogonal to it. 00348 * <BR> 00349 * Range: >=1, Default: 1.5 (to disable this criterion, set 00350 * lti::faceThreshold::parameters::dRatioExponent to 0) 00351 */ 00352 float dRatioOptRatio; 00353 00354 /** 00355 * Blob ranking parameter: score for a blob's ratio of border points 00356 * distances relative to the main axis (i.e.a kind of main axis 00357 * dependent bounding box rating). For formula and more detailed 00358 * description see lti::faceThreshold::parameters::dRatioExponent . 00359 * <BR> 00360 * Effect of this parameter: determines the width of the region for 00361 * positive scores around the ratio that reaches maximum score. Note 00362 * that a smaller value results in a wider region, i.e.a more generous 00363 * ranking. 00364 * <BR> 00365 * Range: >=1, Default: 2 (to disable this criterion, set 00366 * lti::faceThreshold::parameters::dRatioExponent to 0) 00367 */ 00368 int dRatioWidth; 00369 00370 /** 00371 * Blob ranking parameter: score for a blob's horizontal position.<BR> 00372 * The closer a blob's center of gravity is to a specified horizontal 00373 * position (see lti::faceThreshold::parameters::hDistExpectedPosition), 00374 * the more score is reached. The corresponding formula is: 00375 * \f[ 00376 * \text{hDist} (x, cols) := 100 - 100 \cdot \left( \text{width} 00377 * \cdot \dfrac { | x - \frac{cols\cdot\text{expectedPosition}}{100} 00378 * | } {\frac{cols}{2}} \right)^{\text{exponent}} 00379 * \f] 00380 * Effect of this parameter: the greater, the more generous the ranking 00381 * (without changing null). <BR> 00382 * Range: >=0, Default: 2, Disable: 0 00383 */ 00384 int hDistExponent; 00385 00386 /** 00387 * Blob ranking parameter: score for a blob's horizontal position. For 00388 * corresponding formula see 00389 * lti::faceThreshold::parameters::hDistExponent. 00390 * <BR> 00391 * Effect of this parameter: determines where the face has to be 00392 * expected relative to image width, i.e.50 (\%) means in the middle. 00393 * <BR> 00394 * Range: [0..100], Default: 50 (to disable this criterion, set 00395 * lti::faceThreshold::parameters::hDistExponent to 0) 00396 */ 00397 int hDistExpectedPosition; 00398 00399 /** 00400 * Blob ranking parameter: score for a blob's horizontal position. For 00401 * corresponding formula see 00402 * lti::faceThreshold::parameters::hDistExponent. 00403 * <BR> 00404 * Effect of this parameter: the greater, the smaller the region for 00405 * positive scores 00406 * <BR> 00407 * Range: >=1, Default: 1 (to disable this criterion, set 00408 * lti::faceThreshold::parameters::hDistExponent to 0) 00409 */ 00410 int hDistWidth; 00411 00412 /** 00413 * Blob ranking parameter: score for a blob's vertical position. 00414 * <BR> 00415 * The closer a blob's center of gravity is to a specified vertical 00416 * position (see lti::faceThreshold::parameters::vDistExpectedPosition), 00417 * the more score is reached. 00418 * The corresponding formula is: 00419 * \f[ 00420 * \text{vDist} (y, rows) := 100 - 100 \cdot \left( \text{width} 00421 * \cdot \dfrac {| \frac{y-rows\cdot\text{expectedPosition}}{100} |} 00422 * {\frac{rows}{2}} \right)^{\text{exponent}} 00423 * \f] 00424 * Effect of this parameter: the greater, the more generous the ranking 00425 * (without changing null). <BR> 00426 * Range: >=0, Default: 3, Disable: 0 00427 */ 00428 int vDistExponent; 00429 00430 /** 00431 * Blob ranking parameter: score for a blob's vertical position. For 00432 * corresponding formula see 00433 * lti::faceThreshold::parameters::vDistExponent. 00434 * <BR> 00435 * Effect of this parameter: determines where the face has to be 00436 * expected relative to image height, i.e.50(\%) means in the middle. 00437 * <BR> 00438 * Range: [0..100], Default: 30 (to disable this criterion, set 00439 * lti::faceThreshold::parameters::vDistExponent to 0) 00440 */ 00441 int vDistExpectedPosition; 00442 00443 /** 00444 * Blob ranking parameter: score for a blob's vertical position. For 00445 * corresponding formula see 00446 * lti::faceThreshold::parameters::vDistExponent. 00447 * <BR> 00448 * Effect of this parameter: the greater, the smaller the region for 00449 * positive scores 00450 * <BR> 00451 * Range: >=1, Default: 3 (to disable this criterion, set 00452 * lti::faceThreshold::parameters::vDistExponent to 0) 00453 */ 00454 int vDistWidth; 00455 00456 /** 00457 * Blob ranking parameter: score for a blob's size. <BR> 00458 * Blobs with an area size between the minimal an maximal size (both 00459 * specified relative to image size by 00460 * lti::faceThreshold::parameters::sizeMin and 00461 * lti::faceThreshold::parameters::sizeMax) gain positive score, 00462 * favouring the middel. The penalty score increases for both smaller 00463 * and bigger blobs. The corresponding forumla is: 00464 * \f[ 00465 * \text{blobSize} (A_{\,blob}, A_{\,image}) := 100 - 100 \cdot 00466 * \left( \dfrac {| 2 \cdot A_{\,blob} - \frac{\text{min}\cdot 00467 * A_{\,image}}{100} - \frac{\text{max}\cdot A_{\,image}}{100} | } 00468 * {\frac{\text{max}\cdot A_{\,image}}{100} - \frac{\text{min}\cdot 00469 * A_{\,image}}{100}} \right)^{exponent} 00470 * \f] 00471 * Effect of this parameter: The greater, the more generous the ranking 00472 * (i.e.higher score for blobs with size within range but close to min 00473 * or max). 00474 * <BR> 00475 * Range: >= 0, Default: 3, Disable: 0 00476 */ 00477 int sizeExponent; 00478 00479 /** 00480 * Blob ranking parameter: score for a blob's size. For corresponding 00481 * formula see lti::faceThreshold::parameters::sizeExponent. 00482 * <BR> 00483 * Effect of this parameter: specification of the minimal blob size 00484 * relative to image size. 00485 * <BR> 00486 * Range: [0..100] , Default: 0.5 (to disable this criterion, set 00487 * lti::faceThreshold::parameters::sizeExponent to 0) 00488 */ 00489 float sizeMin; 00490 00491 /** 00492 * Blob ranking parameter: score for a blob's size. For corresponding 00493 * formula see lti::faceThreshold::parameters::sizeExponent. 00494 * <BR> 00495 * Effect of this parameter: specification of the maximal blob size 00496 * relative to image size. 00497 * <BR> 00498 * Range: [0..100] , Default: 5.5 (to disable this criterion, set 00499 * lti::faceThreshold::parameters::sizeExponent to 0) 00500 */ 00501 float sizeMax; 00502 00503 /** 00504 * Blob ranking parameter: Since a higher treshold means that at least 00505 * the outer points of a blob are skin with equivalent higher 00506 * probability, higher thresholds are favoured. Furthermore a threshold 00507 * increase in lower regions is rewarded more than in higher regions. 00508 * The corresponding formula is: 00509 * \f[ 00510 * \text{thresholdProbability}(t) := 100 \cdot 00511 * \left( \dfrac{t}{255} \right)^ 00512 * {\frac{1}{\text{exponent}}} 00513 * \f] 00514 * Effect of this parameter: The greater, the greater the score for 00515 * increase in low threshold regions. 00516 * <BR> 00517 * Range: >= 0, Default: 2, Disable: 0 00518 */ 00519 int thresholdProbabilityExponent; 00520 00521 }; 00522 // #################### faceThreshold (public) ########################### 00523 00524 /** 00525 * default constructor 00526 */ 00527 faceThreshold(); 00528 00529 /** 00530 * Construct a functor using the given parameters 00531 */ 00532 faceThreshold(const parameters& par); 00533 00534 /** 00535 * copy constructor 00536 * @param other the object to be copied 00537 */ 00538 faceThreshold(const faceThreshold& other); 00539 00540 /** 00541 * destructor 00542 */ 00543 virtual ~faceThreshold(); 00544 00545 /** 00546 * returns the name of this type ("faceThreshold") 00547 */ 00548 virtual const char* getTypeName() const; 00549 00550 /** 00551 * Apply 00552 * 00553 * @param input channel with the source data 00554 * @param threshold resulting threshold 00555 * @return true if apply successful or false otherwise. 00556 */ 00557 bool apply(const channel& input, float& threshold) const; 00558 00559 /** 00560 * Apply 00561 * 00562 * @param input channel with the source data 00563 * @param threshold resulting threshold 00564 * @param faceBlob resulting face blob 00565 * @return true if apply successful or false otherwise. 00566 */ 00567 bool apply(const channel& input, 00568 float& threshold, 00569 borderPoints& faceBlob) const; 00570 00571 /** 00572 * Apply 00573 * 00574 * @param input channel8 with the source data 00575 * @param threshold resulting threshold 00576 * @return true if apply successful or false otherwise. 00577 */ 00578 bool apply(const channel8& input, int& threshold) const; 00579 00580 /** 00581 * Apply 00582 * 00583 * @param input channel8 with the source data 00584 * @param threshold resulting threshold 00585 * @param faceBlob resulting face blob 00586 * @return true if apply successful or false otherwise. 00587 */ 00588 bool apply(const channel8& input, 00589 int& threshold, 00590 borderPoints& faceBlob) const; 00591 00592 /** 00593 * Copy data of "other" functor. 00594 * @param other the functor to be copied 00595 * @return a reference to this functor object 00596 */ 00597 faceThreshold& copy(const faceThreshold& other); 00598 00599 /** 00600 * alias for copy member 00601 * @param other the functor to be copied 00602 * @return a reference to this functor object 00603 */ 00604 faceThreshold& operator=(const faceThreshold& other); 00605 00606 /** 00607 * returns a pointer to a clone of this functor. 00608 */ 00609 virtual functor* clone() const; 00610 00611 /** 00612 * returns used parameters 00613 */ 00614 const faceThreshold::parameters& getParameters() const; 00615 00616 // ###################### faceThreshold (protected) ######################## 00617 00618 protected: 00619 // protected, internal constant 00620 enum { 00621 numOfCriteria = 8 00622 }; 00623 00624 // protected method, is called by apply() 00625 bool linearThresholdSearch (const channel8 &mask, 00626 int &faceThreshold, 00627 borderPoints &faceBlob) const; 00628 00629 // protected method, is called by apply() 00630 bool optimizedThresholdSearch (const channel8 &mask, 00631 int &faceThreshold, 00632 borderPoints &faceBlob) const; 00633 00634 // protected method, is called by linearThresholdSearch() and 00635 // optimizedThresholdSearch() 00636 void getScoreForBlob (std::map<std::string, double> &featureMap, 00637 const int &cols, 00638 const int &rows, 00639 const int &threshold, 00640 int &score) const; 00641 00642 // protected method, is called by linearThresholdSearch() and 00643 // optimizedThresholdSearch() 00644 void interpolate (borderPoints &blob, 00645 const int &cols, 00646 const int &rows) const; 00647 }; 00648 } 00649 00650 #endif