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

vigra/watersheds3d.hxx VIGRA

00001 /************************************************************************/
00002 /*                                                                      */
00003 /*     Copyright 2006-2007 by F. Heinrich, B. Seppke, Ullrich Koethe    */
00004 /*                                                                      */
00005 /*    This file is part of the VIGRA computer vision library.           */
00006 /*    The VIGRA Website is                                              */
00007 /*        http://hci.iwr.uni-heidelberg.de/vigra/                       */
00008 /*    Please direct questions, bug reports, and contributions to        */
00009 /*        ullrich.koethe@iwr.uni-heidelberg.de    or                    */
00010 /*        vigra@informatik.uni-hamburg.de                               */
00011 /*                                                                      */
00012 /*    Permission is hereby granted, free of charge, to any person       */
00013 /*    obtaining a copy of this software and associated documentation    */
00014 /*    files (the "Software"), to deal in the Software without           */
00015 /*    restriction, including without limitation the rights to use,      */
00016 /*    copy, modify, merge, publish, distribute, sublicense, and/or      */
00017 /*    sell copies of the Software, and to permit persons to whom the    */
00018 /*    Software is furnished to do so, subject to the following          */
00019 /*    conditions:                                                       */
00020 /*                                                                      */
00021 /*    The above copyright notice and this permission notice shall be    */
00022 /*    included in all copies or substantial portions of the             */
00023 /*    Software.                                                         */
00024 /*                                                                      */
00025 /*    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND    */
00026 /*    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES   */
00027 /*    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND          */
00028 /*    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT       */
00029 /*    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,      */
00030 /*    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING      */
00031 /*    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR     */
00032 /*    OTHER DEALINGS IN THE SOFTWARE.                                   */                
00033 /*                                                                      */
00034 /************************************************************************/
00035 
00036 #ifndef VIGRA_watersheds3D_HXX
00037 #define VIGRA_watersheds3D_HXX
00038 
00039 #include "voxelneighborhood.hxx"
00040 #include "multi_array.hxx"
00041 #include "multi_localminmax.hxx"
00042 #include "labelvolume.hxx"
00043 #include "seededregiongrowing3d.hxx"
00044 #include "watersheds.hxx"
00045 
00046 namespace vigra
00047 {
00048 
00049 template <class SrcIterator, class SrcAccessor, class SrcShape,
00050           class DestIterator, class DestAccessor, class Neighborhood3D>
00051 int preparewatersheds3D( SrcIterator s_Iter, SrcShape srcShape, SrcAccessor sa,
00052                          DestIterator d_Iter, DestAccessor da, Neighborhood3D)
00053 {
00054     //basically needed for iteration and border-checks
00055     int w = srcShape[0], h = srcShape[1], d = srcShape[2];
00056     int x,y,z, local_min_count=0;
00057         
00058     //declare and define Iterators for all three dims at src
00059     SrcIterator zs = s_Iter;
00060     SrcIterator ys(zs);
00061     SrcIterator xs(ys);
00062         
00063     //Declare Iterators for all three dims at dest
00064     DestIterator zd = d_Iter;
00065         
00066     for(z = 0; z != d; ++z, ++zs.dim2(), ++zd.dim2())
00067     {
00068         ys = zs;
00069         DestIterator yd(zd);
00070         
00071         for(y = 0; y != h; ++y, ++ys.dim1(), ++yd.dim1())
00072         {
00073             xs = ys;
00074             DestIterator xd(yd);
00075 
00076             for(x = 0; x != w; ++x, ++xs.dim0(), ++xd.dim0())
00077             {
00078                 AtVolumeBorder atBorder = isAtVolumeBorder(x,y,z,w,h,d);
00079                 typename SrcAccessor::value_type v = sa(xs);
00080                 // the following choice causes minima to point
00081                 // to their lowest neighbor -- would this be better???
00082                 // typename SrcAccessor::value_type v = NumericTraits<typename SrcAccessor::value_type>::max();
00083                 int o = 0; // means center is minimum
00084                 typename SrcAccessor::value_type my_v = v;
00085                 if(atBorder == NotAtBorder)
00086                 {
00087                     NeighborhoodCirculator<SrcIterator, Neighborhood3D>  c(xs), cend(c);
00088                     
00089                     do {
00090                         if(sa(c) < v)
00091                         {  
00092                             v = sa(c);
00093                             o = c.directionBit();
00094                         }
00095                         else if(sa(c) == my_v && my_v == v)
00096                         {
00097                             o =  o | c.directionBit();
00098                         }
00099                     }
00100                     while(++c != cend);
00101                 }
00102                 else
00103                 {
00104                     RestrictedNeighborhoodCirculator<SrcIterator, Neighborhood3D>  c(xs, atBorder), cend(c);
00105                     do {
00106                         if(sa(c) < v)
00107                         {  
00108                             v = sa(c);
00109                             o = c.directionBit();
00110                         }
00111                         else if(sa(c) == my_v && my_v == v)
00112                         {
00113                             o =  o | c.directionBit();
00114                         }
00115                     }
00116                     while(++c != cend);
00117                 }
00118                 if (o==0) local_min_count++; 
00119                 da.set(o, xd);
00120             }//end x-iteration
00121         }//end y-iteration
00122     }//end z-iteration
00123     return local_min_count;
00124 }
00125 
00126 template <class SrcIterator, class SrcAccessor,class SrcShape,
00127           class DestIterator, class DestAccessor,
00128           class Neighborhood3D>
00129 unsigned int watershedLabeling3D( SrcIterator s_Iter, SrcShape srcShape, SrcAccessor sa,
00130                                   DestIterator d_Iter, DestAccessor da,
00131                                   Neighborhood3D)
00132 {
00133     typedef typename DestAccessor::value_type LabelType;
00134     
00135     //basically needed for iteration and border-checks
00136     int w = srcShape[0], h = srcShape[1], d = srcShape[2];
00137     int x,y,z;
00138         
00139     //declare and define Iterators for all three dims at src
00140     SrcIterator zs = s_Iter;
00141     DestIterator zd = d_Iter;
00142         
00143     // temporary image to store region labels
00144     detail::UnionFindArray<LabelType> labels;
00145     
00146     // initialize the neighborhood traversers
00147     NeighborOffsetCirculator<Neighborhood3D> nc(Neighborhood3D::CausalFirst);
00148     NeighborOffsetCirculator<Neighborhood3D> nce(Neighborhood3D::CausalLast);
00149     ++nce;
00150     // pass 1: scan image from upper left front to lower right back
00151     // to find connected components
00152 
00153     // Each component will be represented by a tree of pixels. Each
00154     // pixel contains the scan order address of its parent in the
00155     // tree.  In order for pass 2 to work correctly, the parent must
00156     // always have a smaller scan order address than the child.
00157     // Therefore, we can merge trees only at their roots, because the
00158     // root of the combined tree must have the smallest scan order
00159     // address among all the tree's pixels/ nodes.  The root of each
00160     // tree is distinguished by pointing to itself (it contains its
00161     // own scan order address). This condition is enforced whenever a
00162     // new region is found or two regions are merged
00163     for(z = 0; z != d; ++z, ++zs.dim2(), ++zd.dim2())
00164     {
00165         SrcIterator ys = zs;
00166         DestIterator yd = zd;
00167 
00168         for(y = 0; y != h; ++y, ++ys.dim1(), ++yd.dim1())
00169         {
00170             SrcIterator xs = ys;
00171             DestIterator xd = yd;
00172 
00173             for(x = 0; x != w; ++x, ++xs.dim0(), ++xd.dim0())
00174             {
00175                 LabelType currentLabel = labels.nextFreeLabel(); // default: new region    
00176 
00177                 //check whether there is a special border treatment to be used or not
00178                 AtVolumeBorder atBorder = isAtVolumeBorderCausal(x,y,z,w,h,d);
00179                     
00180                 //We are not at the border!
00181                 if(atBorder == NotAtBorder)
00182                 {
00183 
00184                     nc = NeighborOffsetCirculator<Neighborhood3D>(Neighborhood3D::CausalFirst);
00185                 
00186                     do
00187                     {            
00188                         //   Direction of NTraversr       Neighbor's direction bit is pointing
00189                         // = Direction of voxel           towards us?
00190                         if((sa(xs) & nc.directionBit()) || (sa(xs,*nc) & nc.oppositeDirectionBit()))
00191                         {
00192                             currentLabel = labels.makeUnion(da(xd,*nc), currentLabel);
00193                         }
00194                         ++nc;
00195                     }while(nc!=nce);
00196                 }
00197                 //we are at a border - handle this!!
00198                 else
00199                 {
00200                     nc = NeighborOffsetCirculator<Neighborhood3D>(Neighborhood3D::nearBorderDirectionsCausal(atBorder,0));
00201                     int j=0;
00202                     while(nc.direction() != Neighborhood3D::Error)
00203                     {
00204                         //   Direction of NTraversr       Neighbor's direction bit is pointing
00205                         // = Direction of voxel           towards us?
00206                         if((sa(xs) & nc.directionBit()) || (sa(xs,*nc) & nc.oppositeDirectionBit()))
00207                         {
00208                             currentLabel = labels.makeUnion(da(xd,*nc), currentLabel);
00209                         }
00210                         nc.turnTo(Neighborhood3D::nearBorderDirectionsCausal(atBorder,++j));
00211                     }
00212                 }
00213                 da.set(labels.finalizeLabel(currentLabel), xd);
00214             }
00215         }
00216     }
00217 
00218     unsigned int count = labels.makeContiguous();
00219     
00220     // pass 2: assign one label to each region (tree)
00221     // so that labels form a consecutive sequence 1, 2, ...
00222     zd = d_Iter;
00223     for(z=0; z != d; ++z, ++zd.dim2())
00224     {
00225         DestIterator yd(zd);
00226 
00227         for(y=0; y != h; ++y, ++yd.dim1())
00228         {
00229             DestIterator xd(yd);
00230 
00231             for(x = 0; x != w; ++x, ++xd.dim0())
00232             {
00233                 da.set(labels[da(xd)], xd);
00234             }
00235         }
00236     }
00237     return count;
00238 }
00239 
00240 
00241 /** \addtogroup SeededRegionGrowing
00242 */
00243 //@{
00244 
00245 /** \brief Generate seeds for watershed computation and seeded region growing.
00246 
00247     The source image is a boundary indicator such as the gradient magnitude
00248     or the trace of the \ref boundaryTensor(). Seeds are generally generated
00249     at locations where the boundaryness (i.e. the likelihood of the point being on the
00250     boundary) is very small. In particular, seeds can be placed by either
00251     looking for local minima (possibly including minimal plateaus) of the boundaryness,
00252     of by looking at level sets (i.e. regions where the boundaryness is below a threshold).
00253     Both methods can also be combined, so that only minima below a threshold are returned.
00254     The particular seeding strategy is specified by the <tt>options</tt> object 
00255     (see \ref SeedOptions).
00256     
00257     The pixel type of the input image must be <tt>LessThanComparable</tt>.
00258     The pixel type of the output image must be large enough to hold the labels for all seeds.
00259     (typically, you will use <tt>UInt32</tt>). The function will label seeds by consecutive integers
00260     (starting from 1) and returns the largest label it used.
00261     
00262     Pass \ref vigra::EightNeighborCode or \ref vigra::FourNeighborCode to determine the 
00263     neighborhood where pixel values are compared. 
00264     
00265     The function uses accessors.
00266 
00267     <b> Declarations:</b>
00268 
00269     pass arguments explicitly:
00270     \code
00271     namespace vigra {
00272         template <class SrcIterator, class SrcAccessor,
00273                   class DestIterator, class DestAccessor,
00274                   class Neighborhood = EightNeighborCode>
00275         unsigned int
00276         generateWatershedSeeds(SrcIterator upperlefts, SrcIterator lowerrights, SrcAccessor sa,
00277                                DestIterator upperleftd, DestAccessor da, 
00278                                Neighborhood neighborhood = EightNeighborCode(),
00279                                SeedOptions const & options = SeedOptions());
00280     }
00281     \endcode
00282 
00283     use argument objects in conjunction with \ref ArgumentObjectFactories :
00284     \code
00285     namespace vigra {
00286         template <class SrcIterator, class SrcAccessor,
00287                   class DestIterator, class DestAccessor,
00288                   class Neighborhood = EightNeighborCode>
00289         unsigned int
00290         generateWatershedSeeds(triple<SrcIterator, SrcIterator, SrcAccessor> src,
00291                                pair<DestIterator, DestAccessor> dest, 
00292                                Neighborhood neighborhood = EightNeighborCode(),
00293                                SeedOptions const & options = SeedOptions());
00294     }
00295     \endcode
00296 
00297     <b> Usage:</b>
00298 
00299     <b>\#include</b> <vigra/watersheds.hxx><br>
00300     Namespace: vigra
00301 
00302     For detailed examples see watershedsRegionGrowing().
00303 */
00304 doxygen_overloaded_function(template <...> unsigned int generateWatershedSeeds3D)
00305 
00306 #if 0
00307 template <unsigned int N, class T1, class C1, class T2, class C2>
00308           class Neighborhood>
00309 unsigned int
00310 generateWatershedSeeds3D(MultiArrayView<N, T1, C1> in, MultiArrayView<N, T2, C2> out,
00311                          Neighborhood neighborhood,
00312                          SeedOptions const & options = SeedOptions())
00313 {
00314     using namespace functor;
00315     
00316     vigra_precondition(in.shape() == out.shape(),
00317         "generateWatershedSeeds3D(): Shape mismatch between input and output.");
00318         
00319     vigra_precondition(options.mini != SeedOptions::LevelSets || 
00320                        options.thresholdIsValid<SrcType>(),
00321         "generateWatershedSeeds3D(): SeedOptions.levelSets() must be specified with threshold.");
00322     
00323     MultiArray<N, UInt8> seeds(in.shape());
00324     
00325     if(options.mini == SeedOptions::LevelSets)
00326     {
00327         transformMultiArray(srcMultiArrayRange(in), destMultiArray(seeds),
00328                             ifThenElse(Arg1() <= Param(options.thresh), Param(1), Param(0)));
00329     }
00330     else
00331     {
00332         localMinima(in, seeds,
00333             LocalMinmaxOptions().neighborhood(Neighborhood::DirectionCount)
00334                                 .markWith(1.0)
00335                                 .threshold(options.thresh)
00336                                 .allowAtBorder()
00337                                 .allowPlateaus(options.mini == SeedOptions::ExtendedMinima));
00338     }
00339     
00340     return labelVolumeWithBackground(srcMultiArrayRange(seeds), destMultiArray(out), 
00341                                      neighborhood, 0);
00342 }
00343 
00344 template <class SrcIterator, class SrcAccessor,
00345           class DestIterator, class DestAccessor>
00346 inline unsigned int
00347 generateWatershedSeeds(SrcIterator upperlefts, SrcIterator lowerrights, SrcAccessor sa,
00348                        DestIterator upperleftd, DestAccessor da, 
00349                        SeedOptions const & options = SeedOptions())
00350 {
00351     return generateWatershedSeeds(upperlefts, lowerrights, sa, upperleftd, da, 
00352                                    EightNeighborCode(), options);
00353 }
00354 
00355 template <class SrcIterator, class SrcAccessor,
00356           class DestIterator, class DestAccessor,
00357           class Neighborhood>
00358 inline unsigned int
00359 generateWatershedSeeds(triple<SrcIterator, SrcIterator, SrcAccessor> src,
00360                        pair<DestIterator, DestAccessor> dest, 
00361                        Neighborhood neighborhood,
00362                        SeedOptions const & options = SeedOptions())
00363 {
00364     return generateWatershedSeeds(src.first, src.second, src.third,
00365                                    dest.first, dest.second,    
00366                                    neighborhood, options);
00367 }
00368 
00369 template <class SrcIterator, class SrcAccessor,
00370           class DestIterator, class DestAccessor>
00371 inline unsigned int
00372 generateWatershedSeeds(triple<SrcIterator, SrcIterator, SrcAccessor> src,
00373                        pair<DestIterator, DestAccessor> dest, 
00374                        SeedOptions const & options = SeedOptions())
00375 {
00376     return generateWatershedSeeds(src.first, src.second, src.third,
00377                                    dest.first, dest.second,    
00378                                    EightNeighborCode(), options);
00379 }
00380 
00381 #endif
00382 
00383 /********************************************************/
00384 /*                                                      */
00385 /*                     watersheds3D                     */
00386 /*                                                      */
00387 /********************************************************/
00388 
00389 /** \brief Region Segmentation by means of the watershed algorithm.
00390 
00391     <b> Declarations:</b>
00392 
00393     pass arguments explicitly:
00394     \code
00395     namespace vigra {
00396         template <class SrcIterator, class SrcAccessor,class SrcShape,
00397                   class DestIterator, class DestAccessor,
00398                   class Neighborhood3D>
00399         unsigned int watersheds3D(SrcIterator s_Iter, SrcShape srcShape, SrcAccessor sa,
00400                                   DestIterator d_Iter, DestAccessor da,
00401                                   Neighborhood3D neighborhood3D);
00402     }
00403     \endcode
00404 
00405     use argument objects in conjunction with \ref ArgumentObjectFactories :
00406     \code
00407     namespace vigra {
00408         template <class SrcIterator, class SrcAccessor,class SrcShape,
00409                   class DestIterator, class DestAccessor,
00410                   class Neighborhood3D>
00411         unsigned int watersheds3D(triple<SrcIterator, SrcShape, SrcAccessor> src,
00412                                   pair<DestIterator, DestAccessor> dest,
00413                                   Neighborhood3D neighborhood3D);
00414     }
00415     \endcode
00416 
00417     use with 3D-Six-Neighborhood:
00418     \code
00419     namespace vigra {    
00420     
00421         template <class SrcIterator, class SrcAccessor,class SrcShape,
00422                   class DestIterator, class DestAccessor>
00423         unsigned int watersheds3DSix(triple<SrcIterator, SrcShape, SrcAccessor> src,
00424                                      pair<DestIterator, DestAccessor> dest);
00425                                     
00426     }
00427     \endcode
00428 
00429     use with 3D-TwentySix-Neighborhood:
00430     \code
00431     namespace vigra {    
00432     
00433         template <class SrcIterator, class SrcAccessor,class SrcShape,
00434                   class DestIterator, class DestAccessor>
00435         unsigned int watersheds3DTwentySix(triple<SrcIterator, SrcShape, SrcAccessor> src,
00436                                            pair<DestIterator, DestAccessor> dest);
00437                                     
00438     }
00439     \endcode
00440 
00441     This function implements the union-find version of the watershed algorithms
00442     as described in
00443 
00444     J. Roerdink, R. Meijster: "<em>The watershed transform: definitions, algorithms,
00445     and parallelization strategies</em>", Fundamenta Informaticae, 41:187-228, 2000
00446 
00447     The source volume is a boundary indicator such as the gradient magnitude
00448     of the trace of the \ref boundaryTensor(). Local minima of the boundary indicator
00449     are used as region seeds, and all other voxels are recursively assigned to the same 
00450     region as their lowest neighbor. Pass \ref vigra::NeighborCode3DSix or 
00451     \ref vigra::NeighborCode3DTwentySix to determine the neighborhood where voxel values 
00452     are compared. The voxel type of the input volume must be <tt>LessThanComparable</tt>.
00453     The function uses accessors. 
00454     
00455     ...probably soon in VIGRA:
00456     Note that VIGRA provides an alternative implementation of the watershed transform via
00457     \ref seededRegionGrowing3D(). It is slower, but handles plateaus better 
00458     and allows to keep a one pixel wide boundary between regions.
00459     
00460     <b> Usage:</b>
00461 
00462     <b>\#include</b> <vigra/watersheds3D.hxx><br>
00463     Namespace: vigra
00464 
00465     Example: watersheds3D of the gradient magnitude.
00466 
00467     \code
00468     typedef vigra::MultiArray<3,int> IntVolume;
00469     typedef vigra::MultiArray<3,double> DVolume;
00470     DVolume src(DVolume::difference_type(w,h,d));
00471     IntVolume dest(IntVolume::difference_type(w,h,d));
00472 
00473     float gauss=1;
00474 
00475     vigra::MultiArray<3, vigra::TinyVector<float,3> > temp(IntVolume::difference_type(w,h,d));
00476     vigra::gaussianGradientMultiArray(srcMultiArrayRange(vol),destMultiArray(temp),gauss);
00477 
00478     IntVolume::iterator temp_iter=temp.begin();
00479     for(DVolume::iterator iter=src.begin(); iter!=src.end(); ++iter, ++temp_iter)
00480         *iter = norm(*temp_iter);
00481     
00482     // find 6-connected regions
00483     int max_region_label = vigra::watersheds3DSix(srcMultiArrayRange(src), destMultiArray(dest));
00484 
00485     // find 26-connected regions
00486     max_region_label = vigra::watersheds3DTwentySix(srcMultiArrayRange(src), destMultiArray(dest));
00487     
00488     \endcode
00489 
00490     <b> Required Interface:</b>
00491 
00492     \code
00493     SrcIterator src_begin;
00494     SrcShape src_shape;
00495     DestIterator dest_begin;
00496 
00497     SrcAccessor src_accessor;
00498     DestAccessor dest_accessor;
00499     
00500     // compare src values
00501     src_accessor(src_begin) <= src_accessor(src_begin)
00502 
00503     // set result
00504     int label;
00505     dest_accessor.set(label, dest_begin);
00506     \endcode
00507 */
00508 doxygen_overloaded_function(template <...> unsigned int watersheds3D)
00509 
00510 template <class SrcIterator, class SrcAccessor, class SrcShape,
00511           class DestIterator, class DestAccessor,
00512           class Neighborhood3D>
00513 unsigned int watersheds3D( SrcIterator s_Iter, SrcShape srcShape, SrcAccessor sa,
00514                            DestIterator d_Iter, DestAccessor da, Neighborhood3D neighborhood3D)
00515 {
00516     //create temporary volume to store the DAG of directions to minima
00517     if ((int)Neighborhood3D::DirectionCount>7){  //If we have 3D-TwentySix Neighborhood
00518         
00519         vigra::MultiArray<3,int> orientationVolume(srcShape);
00520 
00521         preparewatersheds3D( s_Iter, srcShape, sa, 
00522                              destMultiArray(orientationVolume).first, destMultiArray(orientationVolume).second,
00523                              neighborhood3D);
00524      
00525         return watershedLabeling3D( srcMultiArray(orientationVolume).first, srcShape, srcMultiArray(orientationVolume).second,
00526                                     d_Iter, da,
00527                                     neighborhood3D);
00528     }
00529     else{
00530                 
00531         vigra::MultiArray<3,unsigned char> orientationVolume(srcShape);
00532 
00533         preparewatersheds3D( s_Iter, srcShape, sa, 
00534                               destMultiArray(orientationVolume).first, destMultiArray(orientationVolume).second,
00535                               neighborhood3D);
00536      
00537         return watershedLabeling3D( srcMultiArray(orientationVolume).first, srcShape, srcMultiArray(orientationVolume).second,
00538                                     d_Iter, da,
00539                                     neighborhood3D);
00540     }
00541 }
00542 
00543 template <class SrcIterator, class SrcShape, class SrcAccessor,
00544           class DestIterator, class DestAccessor>
00545 inline unsigned int watersheds3DSix( vigra::triple<SrcIterator, SrcShape, SrcAccessor> src, 
00546                                      vigra::pair<DestIterator, DestAccessor> dest)
00547 {
00548     return watersheds3D(src.first, src.second, src.third, dest.first, dest.second, NeighborCode3DSix());
00549 }
00550 
00551 template <class SrcIterator, class SrcShape, class SrcAccessor,
00552           class DestIterator, class DestAccessor>
00553 inline unsigned int watersheds3DTwentySix( vigra::triple<SrcIterator, SrcShape, SrcAccessor> src, 
00554                                            vigra::pair<DestIterator, DestAccessor> dest)
00555 {
00556     return watersheds3D(src.first, src.second, src.third, dest.first, dest.second, NeighborCode3DTwentySix());
00557 }
00558 
00559 }//namespace vigra
00560 
00561 #endif //VIGRA_watersheds3D_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.9.0 (Tue Nov 6 2012)