[ VIGRA Homepage | Function Index | Class Index | Namespaces | File List | Main Page ]

vigra/multi_morphology.hxx

00001 /************************************************************************/
00002 /*                                                                      */
00003 /*     Copyright 2003-2007 by Kasim Terzic, Christian-Dennis Rahn       */
00004 /*                        and Ullrich Koethe                            */
00005 /*       Cognitive Systems Group, University of Hamburg, Germany        */
00006 /*                                                                      */
00007 /*    This file is part of the VIGRA computer vision library.           */
00008 /*    The VIGRA Website is                                              */
00009 /*        http://kogs-www.informatik.uni-hamburg.de/~koethe/vigra/      */
00010 /*    Please direct questions, bug reports, and contributions to        */
00011 /*        ullrich.koethe@iwr.uni-heidelberg.de    or                    */
00012 /*        vigra@informatik.uni-hamburg.de                               */
00013 /*                                                                      */
00014 /*    Permission is hereby granted, free of charge, to any person       */
00015 /*    obtaining a copy of this software and associated documentation    */
00016 /*    files (the "Software"), to deal in the Software without           */
00017 /*    restriction, including without limitation the rights to use,      */
00018 /*    copy, modify, merge, publish, distribute, sublicense, and/or      */
00019 /*    sell copies of the Software, and to permit persons to whom the    */
00020 /*    Software is furnished to do so, subject to the following          */
00021 /*    conditions:                                                       */
00022 /*                                                                      */
00023 /*    The above copyright notice and this permission notice shall be    */
00024 /*    included in all copies or substantial portions of the             */
00025 /*    Software.                                                         */
00026 /*                                                                      */
00027 /*    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND    */
00028 /*    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES   */
00029 /*    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND          */
00030 /*    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT       */
00031 /*    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,      */
00032 /*    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING      */
00033 /*    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR     */
00034 /*    OTHER DEALINGS IN THE SOFTWARE.                                   */
00035 /*                                                                      */
00036 /************************************************************************/
00037 
00038 #ifndef VIGRA_MULTI_MORPHOLOGY_HXX
00039 #define VIGRA_MULTI_MORPHOLOGY_HXX
00040 
00041 #include <vector>
00042 #include <cmath>
00043 #include "multi_distance.hxx"
00044 #include "array_vector.hxx"
00045 #include "multi_array.hxx"
00046 #include "accessor.hxx"
00047 #include "numerictraits.hxx"
00048 #include "navigator.hxx"
00049 #include "metaprogramming.hxx"
00050 #include "multi_pointoperators.hxx"
00051 #include "functorexpression.hxx"
00052 
00053 namespace vigra
00054 {
00055 
00056 /** \addtogroup MultiArrayMorphology Morphological operators for multi-dimensional arrays.
00057 
00058     These functions perform morphological operations on an arbitrary
00059     dimensional array that is specified by iterators (compatible to \ref MultiIteratorPage)
00060     and shape objects. It can therefore be applied to a wide range of data structures
00061     (\ref vigra::MultiArrayView, \ref vigra::MultiArray etc.).
00062 */
00063 //@{
00064 
00065 /********************************************************/
00066 /*                                                      */
00067 /*             multiBinaryErosion                       */
00068 /*                                                      */
00069 /********************************************************/
00070 /** \brief Binary erosion on multi-dimensional arrays.
00071 
00072     This function applies a flat circular erosion operator with a given radius. The
00073     operation is isotropic.
00074     The input is a binary multi-dimensional array where non-zero pixels represent 
00075     foreground and zero pixels represent background.
00076     
00077     This function may work in-place, which means that <tt>siter == diter</tt> is allowed.
00078     A full-sized internal array is only allocated if working on the destination
00079     array directly would cause overflow errors (i.e. if
00080     <tt> typeid(typename DestAccessor::value_type) < N * M*M</tt>, where M is the
00081     size of the largest dimension of the array.
00082            
00083     <b> Declarations:</b>
00084 
00085     pass arguments explicitly:
00086     \code
00087     namespace vigra {
00088         template <class SrcIterator, class SrcShape, class SrcAccessor,
00089                   class DestIterator, class DestAccessor>
00090         void
00091         multiBinaryErosion(SrcIterator siter, SrcShape const & shape, SrcAccessor src,
00092                                     DestIterator diter, DestAccessor dest, int radius);
00093 
00094     }
00095     \endcode
00096 
00097     use argument objects in conjunction with \ref ArgumentObjectFactories :
00098     \code
00099     namespace vigra {
00100         template <class SrcIterator, class SrcShape, class SrcAccessor,
00101                   class DestIterator, class DestAccessor>
00102         void
00103         multiBinaryErosion(triple<SrcIterator, SrcShape, SrcAccessor> const & source,
00104                                     pair<DestIterator, DestAccessor> const & dest, 
00105                                     int radius);
00106 
00107     }
00108     \endcode
00109 
00110     <b> Usage:</b>
00111 
00112     <b>\#include</b> <<a href="multi__morphology_8hxx-source.html">vigra/multi_morphology.hxx</a>>
00113 
00114     \code
00115     MultiArray<3, unsigned char>::size_type shape(width, height, depth);
00116     MultiArray<3, unsigned char> source(shape);
00117     MultiArray<3, unsigned char> dest(shape);
00118     ...
00119 
00120     // perform isotropic binary erosion
00121     multiBinaryErosion(srcMultiArrayRange(source), destMultiArray(dest), 3);
00122     \endcode
00123 
00124     \see vigra::discErosion()
00125 */
00126 doxygen_overloaded_function(template <...> void multiBinaryErosion)
00127 
00128 template <class SrcIterator, class SrcShape, class SrcAccessor,
00129           class DestIterator, class DestAccessor>
00130 void
00131 multiBinaryErosion( SrcIterator s, SrcShape const & shape, SrcAccessor src,
00132                              DestIterator d, DestAccessor dest, float radius)
00133 {
00134     typedef typename NumericTraits<typename DestAccessor::value_type>::ValueType DestType;
00135     typedef typename NumericTraits<typename DestAccessor::value_type>::Promote TmpType;
00136     DestType MaxValue = NumericTraits<DestType>::max();
00137     float radius2 = (float) radius * radius;
00138     enum { N = 1 + SrcIterator::level };
00139     
00140     int MaxDim = 0; 
00141     for( int i=0; i<N; i++)
00142         if(MaxDim < shape[i]) MaxDim = shape[i];
00143    
00144     using namespace vigra::functor;
00145 
00146     // Get the distance squared transform of the image
00147     if(N*MaxDim*MaxDim > MaxValue)
00148     {
00149         // Allocate a new temporary array if the distances squared wouldn't fit
00150         MultiArray<SrcShape::static_size, TmpType> tmpArray(shape);
00151         //detail::internalSeparableMultiArrayDistTmp( s, shape, src, tmpArray.traverser_begin(),
00152         //    typename AccessorTraits<TmpType>::default_accessor()/*, false*/ );
00153         
00154         separableMultiDistSquared(s, shape, src, tmpArray.traverser_begin(),
00155                 typename AccessorTraits<TmpType>::default_accessor(), false );
00156         
00157         // threshold everything less than radius away from the edge
00158         // std::cerr << "Thresholding!!!!!" << std::endl;
00159         transformMultiArray( tmpArray.traverser_begin(), shape, 
00160             typename AccessorTraits<TmpType>::default_accessor(), d, dest, 
00161             ifThenElse( Arg1() > Param(radius2),
00162                 Param(MaxValue), Param(0) ) );
00163     }
00164     else    // work directly on the destination array
00165     {
00166         //detail::internalSeparableMultiArrayDistTmp( s, shape, src, d, dest/*, false*/ );
00167         separableMultiDistSquared( s, shape, src, d, dest, false );
00168         
00169         // threshold everything less than radius away from the edge
00170         transformMultiArray( d, shape, dest, d, dest, 
00171             ifThenElse( Arg1() > Param(radius2),
00172                 Param(MaxValue), Param(0) ) );
00173     }
00174 }
00175 
00176 template <class SrcIterator, class SrcShape, class SrcAccessor,
00177           class DestIterator, class DestAccessor>
00178 inline
00179 void multiBinaryErosion(
00180     triple<SrcIterator, SrcShape, SrcAccessor> const & source,
00181     pair<DestIterator, DestAccessor> const & dest, int radius)
00182 {
00183     multiBinaryErosion( source.first, source.second, source.third,
00184                                  dest.first, dest.second, radius );
00185 }
00186 
00187 
00188 /********************************************************/
00189 /*                                                      */
00190 /*             multiBinaryDilation                      */
00191 /*                                                      */
00192 /********************************************************/
00193 
00194 /** \brief Binary dilation on multi-dimensional arrays.
00195 
00196     This function applies a flat circular dilation operator with a given radius. The
00197     operation is isotropic.
00198     The input is a binary multi-dimensional array where non-zero pixels represent
00199     foreground and zero pixels represent background.
00200     
00201     This function may work in-place, which means that <tt>siter == diter</tt> is allowed.
00202     A full-sized internal array is only allocated if working on the destination
00203     array directly would cause overflow errors (i.e. if
00204     <tt> typeid(typename DestAccessor::value_type) < N * M*M</tt>, where M is the
00205     size of the largest dimension of the array.
00206            
00207     <b> Declarations:</b>
00208 
00209     pass arguments explicitly:
00210     \code
00211     namespace vigra {
00212         template <class SrcIterator, class SrcShape, class SrcAccessor,
00213                   class DestIterator, class DestAccessor>
00214         void
00215         multiBinaryDilation(SrcIterator siter, SrcShape const & shape, SrcAccessor src,
00216                                     DestIterator diter, DestAccessor dest, int radius);
00217 
00218     }
00219     \endcode
00220 
00221     use argument objects in conjunction with \ref ArgumentObjectFactories :
00222     \code
00223     namespace vigra {
00224         template <class SrcIterator, class SrcShape, class SrcAccessor,
00225                   class DestIterator, class DestAccessor>
00226         void
00227         multiBinaryDilation(triple<SrcIterator, SrcShape, SrcAccessor> const & source,
00228                                     pair<DestIterator, DestAccessor> const & dest, 
00229                                     int radius);
00230 
00231     }
00232     \endcode
00233 
00234     <b> Usage:</b>
00235 
00236     <b>\#include</b> <<a href="multi__morphology_8hxx-source.html">vigra/multi_morphology.hxx</a>>
00237 
00238     \code
00239     MultiArray<3, unsigned char>::size_type shape(width, height, depth);
00240     MultiArray<3, unsigned char> source(shape);
00241     MultiArray<3, unsigned char> dest(shape);
00242     ...
00243 
00244     // perform isotropic binary erosion
00245     multiBinaryDilation(srcMultiArrayRange(source), destMultiArray(dest), 3);
00246     \endcode
00247 
00248     \see vigra::discDilation()
00249 */
00250 doxygen_overloaded_function(template <...> void multiBinaryDilation)
00251 
00252 template <class SrcIterator, class SrcShape, class SrcAccessor,
00253           class DestIterator, class DestAccessor>
00254 void
00255 multiBinaryDilation( SrcIterator s, SrcShape const & shape, SrcAccessor src,
00256                              DestIterator d, DestAccessor dest, float radius)
00257 {
00258     typedef typename NumericTraits<typename DestAccessor::value_type>::ValueType DestType;
00259     typedef typename NumericTraits<typename DestAccessor::value_type>::Promote TmpType;
00260     DestType MaxValue = NumericTraits<DestType>::max();
00261     float radius2 = (float) radius * radius;
00262     enum { N = 1 + SrcIterator::level };
00263     
00264     int MaxDim = 0; 
00265     for( int i=0; i<N; i++)
00266         if(MaxDim < shape[i]) MaxDim = shape[i];
00267    
00268     using namespace vigra::functor;
00269 
00270     // Get the distance squared transform of the image
00271     if(N*MaxDim*MaxDim > MaxValue)
00272     {
00273         // Allocate a new temporary array if the distances squared wouldn't fit
00274         MultiArray<SrcShape::static_size, TmpType> tmpArray(shape);
00275         //detail::internalSeparableMultiArrayDistTmp( s, shape, src, tmpArray.traverser_begin(),
00276         //    typename AccessorTraits<TmpType>::default_accessor(), true );
00277         
00278         separableMultiDistSquared(s, shape, src, tmpArray.traverser_begin(),
00279             typename AccessorTraits<TmpType>::default_accessor(), true );
00280       
00281         // threshold everything less than radius away from the edge
00282         transformMultiArray( tmpArray.traverser_begin(), shape, 
00283             typename AccessorTraits<TmpType>::default_accessor(), d, dest, 
00284             ifThenElse( Arg1() > Param(radius2),
00285                 Param(0), Param(MaxValue) ) );
00286     }
00287     else    // work directly on the destination array
00288     {
00289         //detail::internalSeparableMultiArrayDistTmp( s, shape, src, d, dest, true );
00290         separableMultiDistSquared( s, shape, src, d, dest, true );
00291         
00292         // threshold everything less than radius away from the edge
00293         transformMultiArray( d, shape, dest, d, dest, 
00294             ifThenElse( Arg1() > Param(radius2),
00295                 Param(0), Param(MaxValue) ) );
00296     }
00297 }
00298 
00299 template <class SrcIterator, class SrcShape, class SrcAccessor,
00300           class DestIterator, class DestAccessor>
00301 inline
00302 void multiBinaryDilation(
00303     triple<SrcIterator, SrcShape, SrcAccessor> const & source,
00304     pair<DestIterator, DestAccessor> const & dest, int radius)
00305 {
00306     multiBinaryDilation( source.first, source.second, source.third,
00307                                  dest.first, dest.second, radius );
00308 }
00309 
00310 /********************************************************/
00311 /*                                                      */
00312 /*             multiGrayscaleErosion                    */
00313 /*                                                      */
00314 /********************************************************/
00315 /** \brief Parabolic grayscale erosion on multi-dimensional arrays.
00316 
00317     This function applies a parabolic erosion operator with a given spread (sigma) on
00318     a grayscale array. The operation is isotropic.
00319     The input is a grayscale multi-dimensional array.
00320     
00321     This function may work in-place, which means that <tt>siter == diter</tt> is allowed.
00322     A full-sized internal array is only allocated if working on the destination
00323     array directly would cause overflow errors (i.e. if
00324     <tt> typeid(typename DestAccessor::value_type) < N * M*M</tt>, where M is the
00325     size of the largest dimension of the array.
00326            
00327     <b> Declarations:</b>
00328 
00329     pass arguments explicitly:
00330     \code
00331     namespace vigra {
00332         template <class SrcIterator, class SrcShape, class SrcAccessor,
00333                   class DestIterator, class DestAccessor>
00334         void
00335         multiGrayscaleErosion(SrcIterator siter, SrcShape const & shape, SrcAccessor src,
00336                                     DestIterator diter, DestAccessor dest, float sigma);
00337 
00338     }
00339     \endcode
00340 
00341     use argument objects in conjunction with \ref ArgumentObjectFactories :
00342     \code
00343     namespace vigra {
00344         template <class SrcIterator, class SrcShape, class SrcAccessor,
00345                   class DestIterator, class DestAccessor>
00346         void
00347         multiGrayscaleErosion(triple<SrcIterator, SrcShape, SrcAccessor> const & source,
00348                                     pair<DestIterator, DestAccessor> const & dest, 
00349                                     float sigma);
00350 
00351     }
00352     \endcode
00353 
00354     <b> Usage:</b>
00355 
00356     <b>\#include</b> <<a href="multi__morphology_8hxx-source.html">vigra/multi_morphology.hxx</a>>
00357 
00358     \code
00359     MultiArray<3, unsigned char>::size_type shape(width, height, depth);
00360     MultiArray<3, unsigned char> source(shape);
00361     MultiArray<3, unsigned char> dest(shape);
00362     ...
00363 
00364     // perform isotropic grayscale erosion
00365     multiGrayscaleErosion(srcMultiArrayRange(source), destMultiArray(dest), 3.0);
00366     \endcode
00367 
00368     \see vigra::discErosion()
00369 */
00370 doxygen_overloaded_function(template <...> void multiGrayscaleErosion)
00371 
00372 template <class SrcIterator, class SrcShape, class SrcAccessor,
00373           class DestIterator, class DestAccessor>
00374 void
00375 multiGrayscaleErosion( SrcIterator s, SrcShape const & shape, SrcAccessor src,
00376                        DestIterator d, DestAccessor dest, float sigma)
00377 {
00378     typedef typename NumericTraits<typename DestAccessor::value_type>::ValueType DestType;
00379     typedef typename NumericTraits<typename DestAccessor::value_type>::Promote TmpType;
00380     DestType MaxValue = NumericTraits<DestType>::max();
00381     enum { N = 1 + SrcIterator::level };
00382     
00383     // temporay array to hold the current line to enable in-place operation
00384     ArrayVector<TmpType> tmp( shape[0] );
00385         
00386     typedef MultiArrayNavigator<SrcIterator, N> SNavigator;
00387     typedef MultiArrayNavigator<DestIterator, N> DNavigator;
00388     
00389     int MaxDim = 0; 
00390     for( int i=0; i<N; i++)
00391         if(MaxDim < shape[i]) MaxDim = shape[i];
00392     
00393     using namespace vigra::functor;
00394     
00395     // Allocate a new temporary array if the distances squared wouldn't fit
00396     if(N*MaxDim*MaxDim > MaxValue)
00397     {
00398         MultiArray<SrcShape::static_size, TmpType> tmpArray(shape);
00399 
00400         detail::internalSeparableMultiArrayDistTmp( s, shape, src, tmpArray.traverser_begin(),
00401             typename AccessorTraits<TmpType>::default_accessor(), sigma );
00402         
00403         transformMultiArray( tmpArray.traverser_begin(), shape,
00404                 typename AccessorTraits<TmpType>::default_accessor(), d, dest,
00405                 ifThenElse( Arg1() > Param(MaxValue), Param(MaxValue), Arg1() ) );
00406         //copyMultiArray( tmpArray.traverser_begin(), shape,
00407         //        typename AccessorTraits<TmpType>::default_accessor(), d, dest );
00408     }
00409     else
00410     {
00411         detail::internalSeparableMultiArrayDistTmp( s, shape, src, d, dest, sigma );
00412     }
00413 
00414 }
00415 
00416 template <class SrcIterator, class SrcShape, class SrcAccessor,
00417           class DestIterator, class DestAccessor>
00418 inline 
00419 void multiGrayscaleErosion(
00420     triple<SrcIterator, SrcShape, SrcAccessor> const & source,
00421     pair<DestIterator, DestAccessor> const & dest, float sigma)
00422 {
00423     multiGrayscaleErosion( source.first, source.second, source.third, 
00424             dest.first, dest.second, sigma);
00425 }
00426 
00427 /********************************************************/
00428 /*                                                      */
00429 /*             multiGrayscaleDilation                   */
00430 /*                                                      */
00431 /********************************************************/
00432 /** \brief Parabolic grayscale dilation on multi-dimensional arrays.
00433 
00434     This function applies a parabolic dilation operator with a given spread (sigma) on
00435     a grayscale array. The operation is isotropic.
00436     The input is a grayscale multi-dimensional array.
00437     
00438     This function may work in-place, which means that <tt>siter == diter</tt> is allowed.
00439     A full-sized internal array is only allocated if working on the destination
00440     array directly would cause overflow errors (i.e. if
00441     <tt> typeid(typename DestAccessor::value_type) < N * M*M</tt>, where M is the
00442     size of the largest dimension of the array.
00443            
00444     <b> Declarations:</b>
00445 
00446     pass arguments explicitly:
00447     \code
00448     namespace vigra {
00449         template <class SrcIterator, class SrcShape, class SrcAccessor,
00450                   class DestIterator, class DestAccessor>
00451         void
00452         multiGrayscaleDilation(SrcIterator siter, SrcShape const & shape, SrcAccessor src,
00453                                     DestIterator diter, DestAccessor dest, float sigma);
00454 
00455     }
00456     \endcode
00457 
00458     use argument objects in conjunction with \ref ArgumentObjectFactories :
00459     \code
00460     namespace vigra {
00461         template <class SrcIterator, class SrcShape, class SrcAccessor,
00462                   class DestIterator, class DestAccessor>
00463         void
00464         multiGrayscaleDilation(triple<SrcIterator, SrcShape, SrcAccessor> const & source,
00465                                     pair<DestIterator, DestAccessor> const & dest, 
00466                                     float sigma);
00467 
00468     }
00469     \endcode
00470 
00471     <b> Usage:</b>
00472 
00473     <b>\#include</b> <<a href="multi__morphology_8hxx-source.html">vigra/multi_morphology.hxx</a>>
00474 
00475     \code
00476     MultiArray<3, unsigned char>::size_type shape(width, height, depth);
00477     MultiArray<3, unsigned char> source(shape);
00478     MultiArray<3, unsigned char> dest(shape);
00479     ...
00480 
00481     // perform isotropic grayscale erosion
00482     multiGrayscaleDilation(srcMultiArrayRange(source), destMultiArray(dest), 3.0);
00483     \endcode
00484 
00485     \see vigra::discErosion()
00486 */
00487 doxygen_overloaded_function(template <...> void multiGrayscaleDilation)
00488 
00489 template <class SrcIterator, class SrcShape, class SrcAccessor,
00490           class DestIterator, class DestAccessor>
00491 void multiGrayscaleDilation( SrcIterator s, SrcShape const & shape, SrcAccessor src,
00492                              DestIterator d, DestAccessor dest, float sigma)
00493 {
00494     typedef typename NumericTraits<typename DestAccessor::value_type>::ValueType DestType;
00495     typedef typename NumericTraits<typename DestAccessor::value_type>::Promote TmpType;
00496     DestType MinValue = NumericTraits<DestType>::min();
00497     DestType MaxValue = NumericTraits<DestType>::max();
00498     enum { N = 1 + SrcIterator::level };
00499         
00500     // temporay array to hold the current line to enable in-place operation
00501     ArrayVector<TmpType> tmp( shape[0] );
00502         
00503     typedef MultiArrayNavigator<SrcIterator, N> SNavigator;
00504     typedef MultiArrayNavigator<DestIterator, N> DNavigator;
00505     
00506     int MaxDim = 0; 
00507     for( int i=0; i<N; i++)
00508         if(MaxDim < shape[i]) MaxDim = shape[i];
00509     
00510     using namespace vigra::functor;
00511 
00512     // Allocate a new temporary array if the distances squared wouldn't fit
00513     if(-N*MaxDim*MaxDim < MinValue || N*MaxDim*MaxDim > MaxValue)
00514     {
00515         MultiArray<SrcShape::static_size, TmpType> tmpArray(shape);
00516 
00517         detail::internalSeparableMultiArrayDistTmp( s, shape, src, tmpArray.traverser_begin(),
00518             typename AccessorTraits<TmpType>::default_accessor(), sigma, true );
00519         
00520         transformMultiArray( tmpArray.traverser_begin(), shape,
00521                 typename AccessorTraits<TmpType>::default_accessor(), d, dest,
00522                 ifThenElse( Arg1() > Param(MaxValue), Param(MaxValue), 
00523                     ifThenElse( Arg1() < Param(MinValue), Param(MinValue), Arg1() ) ) );
00524     }
00525     else
00526     {
00527         detail::internalSeparableMultiArrayDistTmp( s, shape, src, d, dest, sigma, true );
00528     }
00529 
00530 }
00531 
00532 
00533 template <class SrcIterator, class SrcShape, class SrcAccessor,
00534           class DestIterator, class DestAccessor>
00535 inline 
00536 void multiGrayscaleDilation(
00537     triple<SrcIterator, SrcShape, SrcAccessor> const & source,
00538     pair<DestIterator, DestAccessor> const & dest, float sigma)
00539 {
00540     multiGrayscaleDilation( source.first, source.second, source.third, 
00541             dest.first, dest.second, sigma);
00542 }
00543 
00544 
00545 //@}
00546 
00547 } //-- namespace vigra
00548 
00549 
00550 #endif        //-- VIGRA_MULTI_MORPHOLOGY_HXX

© Ullrich Köthe (ullrich.koethe@iwr.uni-heidelberg.de)
Heidelberg Collaboratory for Image Processing, University of Heidelberg, Germany

html generated using doxygen and Python
VIGRA 1.6.0 (5 Nov 2009)