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

details vigra/resampling_convolution.hxx VIGRA

00001 /************************************************************************/
00002 /*                                                                      */
00003 /*               Copyright 1998-2004 by Ullrich Koethe                  */
00004 /*       Cognitive Systems Group, University of Hamburg, Germany        */
00005 /*                                                                      */
00006 /*    This file is part of the VIGRA computer vision library.           */
00007 /*    ( Version 1.4.0, Dec 21 2005 )                                    */
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 /*        koethe@informatik.uni-hamburg.de          or                  */
00012 /*        vigra@kogs1.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_RESAMPLING_CONVOLUTION_HXX
00039 #define VIGRA_RESAMPLING_CONVOLUTION_HXX
00040 
00041 #include <cmath>
00042 #include "vigra/stdimage.hxx"
00043 #include "vigra/array_vector.hxx"
00044 #include "vigra/rational.hxx"
00045 #include "vigra/functortraits.hxx"
00046 
00047 namespace vigra {
00048 
00049 namespace resampling_detail
00050 {
00051 
00052 struct MapTargetToSourceCoordinate
00053 {
00054     MapTargetToSourceCoordinate(Rational<int> const & samplingRatio, 
00055                                 Rational<int> const & offset)
00056     : a(samplingRatio.denominator()*offset.denominator()),
00057       b(samplingRatio.numerator()*offset.numerator()),
00058       c(samplingRatio.numerator()*offset.denominator())
00059     {}
00060     
00061 //        the following funcions are more efficient realizations of:
00062 //             rational_cast<T>(i / samplingRatio + offset);
00063 //        we need efficiency because this may be called in the inner loop
00064 
00065     int operator()(int i) const
00066     {
00067         return (i * a + b) / c;
00068     }
00069     
00070     double toDouble(int i) const
00071     {
00072         return double(i * a + b) / c;
00073     }
00074 
00075     Rational<int> toRational(int i) const
00076     {
00077         return Rational<int>(i * a + b, c);
00078     }
00079 
00080     int a, b, c;
00081 };
00082 
00083 } // namespace resampling_detail
00084 
00085 template <>
00086 class FunctorTraits<resampling_detail::MapTargetToSourceCoordinate>
00087 : public FunctorTraitsBase<resampling_detail::MapTargetToSourceCoordinate>
00088 {
00089   public:
00090     typedef VigraTrueType isUnaryFunctor;
00091 };
00092 
00093 template <class SrcIter, class SrcAcc,
00094           class DestIter, class DestAcc,
00095           class KernelArray,
00096           class Functor>
00097 void 
00098 resamplingConvolveLine(SrcIter s, SrcIter send, SrcAcc src,
00099                        DestIter d, DestIter dend, DestAcc dest,
00100                        KernelArray const & kernels,
00101                        Functor mapTargetToSourceCoordinate)
00102 {
00103     typedef typename 
00104         NumericTraits<typename SrcAcc::value_type>::RealPromote
00105         TmpType;
00106     typedef typename KernelArray::value_type Kernel;
00107     typedef typename Kernel::const_iterator KernelIter;
00108     
00109     int wo = send - s;
00110     int wn = dend - d;
00111     int wo2 = 2*wo - 2;
00112     
00113     int i;
00114     typename KernelArray::const_iterator kernel = kernels.begin();
00115     for(i=0; i<wn; ++i, ++d, ++kernel)
00116     {
00117         // use the kernels periodically
00118         if(kernel == kernels.end())
00119             kernel = kernels.begin();
00120         
00121         // calculate current target point into source location
00122         int is = mapTargetToSourceCoordinate(i);
00123         
00124         TmpType sum = NumericTraits<TmpType>::zero();
00125 
00126         int lbound = is - kernel->right(), 
00127             hbound = is - kernel->left();
00128                     
00129         KernelIter k = kernel->center() + kernel->right();
00130         if(lbound < 0 || hbound >= wo)
00131         {    
00132             vigra_precondition(-lbound < wo && wo2 - hbound >= 0,
00133                 "resamplingConvolveLine(): kernel or offset larger than image.");
00134             for(int m=lbound; m <= hbound; ++m, --k)
00135             {
00136                 int mm = (m < 0) ?
00137                             -m :
00138                             (m >= wo) ?
00139                                 wo2 - m :
00140                                 m;
00141                 sum += *k * src(s, mm);
00142             }
00143         }
00144         else
00145         {
00146             SrcIter ss = s + lbound;
00147             SrcIter ssend = s + hbound;
00148             
00149             for(; ss <= ssend; ++ss, --k)
00150             {
00151                 sum += *k * src(ss);
00152             }
00153         }
00154         
00155         dest.set(sum, d);
00156     }
00157 }
00158 
00159 template <class Kernel, class MapCoordinate, class KernelArray>
00160 void
00161 createResamplingKernels(Kernel const & kernel, 
00162              MapCoordinate const & mapCoordinate, KernelArray & kernels)
00163 {
00164     for(unsigned int idest = 0; idest < kernels.size(); ++idest)
00165     {
00166         int isrc = mapCoordinate(idest);
00167         double idsrc = mapCoordinate.toDouble(idest);
00168         double offset = idsrc - isrc;
00169         double radius = kernel.radius();
00170         int left = int(ceil(-radius - offset));
00171         int right = int(floor(radius - offset));
00172         kernels[idest].initExplicitly(left, right);
00173         
00174         double x = left + offset;
00175         for(int i = left; i <= right; ++i, ++x)
00176             kernels[idest][i] = kernel(x);
00177         kernels[idest].normalize(1.0, kernel.derivativeOrder(), offset);
00178     }
00179 }
00180 
00181 /** \addtogroup ResamplingConvolutionFilters Resampling Convolution Filters
00182 
00183     These functions implement the convolution operation when the source and target images
00184     have different sizes. This is realized by accessing a continous kernel at the
00185     appropriate non-integer positions. The technique is, for example, described in
00186     D. Schumacher: <i>General Filtered Image Rescaling</i>, in: Graphics Gems III, 
00187     Academic Press, 1992.
00188 */
00189 //@{
00190 
00191 /********************************************************/
00192 /*                                                      */
00193 /*                  resamplingConvolveX                 */
00194 /*                                                      */
00195 /********************************************************/
00196 
00197 /** \brief Apply a resampling filter in the x-direction.
00198 
00199     This function implements a convolution operation in x-direction
00200     (i.e. applies a 1D filter to every row) where the width of the source
00201     and destination images differ. This is typically used to avoid aliasing if 
00202     the image is scaled down, or to interpolate smoothly if the image is scaled up.
00203     The target coordinates are transformed into source coordinates by
00204     
00205     \code
00206     xsource = (xtarget - offset) / samplingRatio
00207     \endcode
00208     
00209     The <tt>samplingRatio</tt> and <tt>offset</tt> must be given as \ref vigra::Rational 
00210     in order to avoid rounding errors in this transformation. It is required that for all
00211     pixels of the target image, <tt>xsource</tt> remains within the range of the source
00212     image (i.e. <tt>0 <= xsource <= sourceWidth-1</tt>. Since <tt>xsource</tt> is
00213     in general not an integer, the <tt>kernel</tt> must be a functor that can be accessed at 
00214     arbitrary (<tt>double</tt>) coordinates. It must also provide a member function <tt>radius()</tt>
00215     which specifies the support (non-zero interval) of the kernel. VIGRA already
00216     provides a number of suitable functors, e.g. \ref vigra::Gaussian, \ref vigra::BSpline
00217     \ref vigra::CatmullRomSpline, and \ref vigra::CoscotFunction. The function
00218     \ref resizeImageSplineInterpolation() is implemented by means resamplingConvolveX() and 
00219     resamplingConvolveY().
00220 
00221     <b> Declarations:</b>
00222 
00223     pass arguments explicitly:
00224     \code
00225     namespace vigra {
00226         template <class SrcIter, class SrcAcc,
00227                   class DestIter, class DestAcc,
00228                   class Kernel>
00229         void 
00230         resamplingConvolveX(SrcIter sul, SrcIter slr, SrcAcc src,
00231                             DestIter dul, DestIter dlr, DestAcc dest,
00232                             Kernel const & kernel,
00233                             Rational<int> const & samplingRatio, Rational<int> const & offset);
00234     }
00235     \endcode
00236 
00237 
00238     use argument objects in conjunction with \ref ArgumentObjectFactories:
00239     \code
00240     namespace vigra {
00241         template <class SrcIter, class SrcAcc,
00242                   class DestIter, class DestAcc,
00243                   class Kernel>
00244         void 
00245         resamplingConvolveX(triple<SrcIter, SrcIter, SrcAcc> src,
00246                             triple<DestIter, DestIter, DestAcc> dest,
00247                             Kernel const & kernel,
00248                             Rational<int> const & samplingRatio, Rational<int> const & offset);
00249     }
00250     \endcode
00251 
00252     <b> Usage:</b>
00253 
00254     <b>\#include</b> "<a href="resampling_convolution_8hxx-source.html">vigra/resampling_convolution.hxx</a>"
00255 
00256 
00257     \code
00258     Rational<int> ratio(2), offset(0);
00259 
00260     FImage src(w,h), 
00261            dest(rational_cast<int>(ratio*w), h);
00262 
00263     float sigma = 2.0;
00264     Gaussian<float> smooth(sigma);
00265     ...
00266 
00267     // simpultaneously enlarge and smooth source image
00268     resamplingConvolveX(srcImageRange(src), destImageRange(dest), 
00269                         smooth, ratio, offset);
00270     \endcode
00271 
00272     <b> Required Interface:</b>
00273     
00274     \code
00275     Kernel kernel;
00276     int kernelRadius = kernel.radius();
00277     double x = ...;  // must be <= radius()
00278     double value = kernel(x);
00279     \endcode
00280 */
00281 template <class SrcIter, class SrcAcc,
00282           class DestIter, class DestAcc,
00283           class Kernel>
00284 void 
00285 resamplingConvolveX(SrcIter sul, SrcIter slr, SrcAcc src,
00286                     DestIter dul, DestIter dlr, DestAcc dest,
00287                     Kernel const & kernel,
00288                     Rational<int> const & samplingRatio, Rational<int> const & offset)
00289 {
00290     int wold = slr.x - sul.x;
00291     int wnew = dlr.x - dul.x;
00292     
00293     vigra_precondition(!samplingRatio.is_inf() && samplingRatio > 0,
00294                 "resamplingConvolveX(): sampling ratio must be > 0 and < infinity");
00295     vigra_precondition(!offset.is_inf(),
00296                 "resamplingConvolveX(): offset must be < infinity");
00297 
00298     int period = lcm(samplingRatio.numerator(), samplingRatio.denominator());
00299     resampling_detail::MapTargetToSourceCoordinate mapCoordinate(samplingRatio, offset);
00300     
00301     ArrayVector<Kernel1D<double> > kernels(period);
00302     
00303     createResamplingKernels(kernel, mapCoordinate, kernels);
00304 
00305     for(; sul.y < slr.y; ++sul.y, ++dul.y)
00306     {
00307         typename SrcIter::row_iterator sr = sul.rowIterator();
00308         typename DestIter::row_iterator dr = dul.rowIterator();
00309         resamplingConvolveLine(sr, sr+wold, src, dr, dr+wnew, dest,
00310                                kernels, mapCoordinate);
00311     }
00312 }
00313 
00314 template <class SrcIter, class SrcAcc,
00315           class DestIter, class DestAcc,
00316           class Kernel>
00317 inline void 
00318 resamplingConvolveX(triple<SrcIter, SrcIter, SrcAcc> src,
00319                     triple<DestIter, DestIter, DestAcc> dest,
00320                     Kernel const & kernel,
00321                     Rational<int> const & samplingRatio, Rational<int> const & offset)
00322 {
00323     resamplingConvolveX(src.first, src.second, src.third,
00324                         dest.first, dest.second, dest.third,
00325                         kernel, samplingRatio, offset);
00326 }
00327 
00328 /********************************************************/
00329 /*                                                      */
00330 /*                  resamplingConvolveY                 */
00331 /*                                                      */
00332 /********************************************************/
00333 
00334 /** \brief Apply a resampling filter in the y-direction.
00335 
00336     This function implements a convolution operation in y-direction
00337     (i.e. applies a 1D filter to every column) where the height of the source
00338     and destination images differ. This is typically used to avoid aliasing if 
00339     the image is scaled down, or to interpolate smoothly if the image is scaled up.
00340     The target coordinates are transformed into source coordinates by
00341     
00342     \code
00343     ysource = (ytarget - offset) / samplingRatio
00344     \endcode
00345     
00346     The <tt>samplingRatio</tt> and <tt>offset</tt> must be given as \ref vigra::Rational 
00347     in order to avoid rounding errors in this transformation. It is required that for all
00348     pixels of the target image, <tt>ysource</tt> remains within the range of the source
00349     image (i.e. <tt>0 <= ysource <= sourceHeight-1</tt>. Since <tt>ysource</tt> is
00350     in general not an integer, the <tt>kernel</tt> must be a functor that can be accessed at 
00351     arbitrary (<tt>double</tt>) coordinates. It must also provide a member function <tt>radius()</tt>
00352     which specifies the support (non-zero interval) of the kernel. VIGRA already
00353     provides a number of suitable functors, e.g. \ref vigra::Gaussian, \ref vigra::BSpline
00354     \ref vigra::CatmullRomSpline, and \ref vigra::CoscotFunction. The function
00355     \ref resizeImageSplineInterpolation() is implemented by means resamplingConvolveX() and 
00356     resamplingConvolveY().
00357 
00358     <b> Declarations:</b>
00359 
00360     pass arguments explicitly:
00361     \code
00362     namespace vigra {
00363         template <class SrcIter, class SrcAcc,
00364                   class DestIter, class DestAcc,
00365                   class Kernel>
00366         void 
00367         resamplingConvolveY(SrcIter sul, SrcIter slr, SrcAcc src,
00368                             DestIter dul, DestIter dlr, DestAcc dest,
00369                             Kernel const & kernel,
00370                             Rational<int> const & samplingRatio, Rational<int> const & offset);
00371     }
00372     \endcode
00373 
00374 
00375     use argument objects in conjunction with \ref ArgumentObjectFactories:
00376     \code
00377     namespace vigra {
00378         template <class SrcIter, class SrcAcc,
00379                   class DestIter, class DestAcc,
00380                   class Kernel>
00381         void 
00382         resamplingConvolveY(triple<SrcIter, SrcIter, SrcAcc> src,
00383                             triple<DestIter, DestIter, DestAcc> dest,
00384                             Kernel const & kernel,
00385                             Rational<int> const & samplingRatio, Rational<int> const & offset);
00386     }
00387     \endcode
00388 
00389     <b> Usage:</b>
00390 
00391     <b>\#include</b> "<a href="resampling_convolution_8hxx-source.html">vigra/resampling_convolution.hxx</a>"
00392 
00393 
00394     \code
00395     Rational<int> ratio(2), offset(0);
00396 
00397     FImage src(w,h), 
00398            dest(w, rational_cast<int>(ratio*h));
00399 
00400     float sigma = 2.0;
00401     Gaussian<float> smooth(sigma);
00402     ...
00403 
00404     // simpultaneously enlarge and smooth source image
00405     resamplingConvolveY(srcImageRange(src), destImageRange(dest), 
00406                         smooth, ratio, offset);
00407     \endcode
00408 
00409     <b> Required Interface:</b>
00410     
00411     \code
00412     Kernel kernel;
00413     int kernelRadius = kernel.radius();
00414     double y = ...;  // must be <= radius()
00415     double value = kernel(y);
00416     \endcode
00417 */
00418 template <class SrcIter, class SrcAcc,
00419           class DestIter, class DestAcc,
00420           class Kernel>
00421 void 
00422 resamplingConvolveY(SrcIter sul, SrcIter slr, SrcAcc src,
00423                     DestIter dul, DestIter dlr, DestAcc dest,
00424                     Kernel const & kernel,
00425                     Rational<int> const & samplingRatio, Rational<int> const & offset)
00426 {
00427     int hold = slr.y - sul.y;
00428     int hnew = dlr.y - dul.y;
00429     
00430     vigra_precondition(!samplingRatio.is_inf() && samplingRatio > 0,
00431                 "resamplingConvolveY(): sampling ratio must be > 0 and < infinity");
00432     vigra_precondition(!offset.is_inf(),
00433                 "resamplingConvolveY(): offset must be < infinity");
00434 
00435     int period = lcm(samplingRatio.numerator(), samplingRatio.denominator());
00436     
00437     resampling_detail::MapTargetToSourceCoordinate mapCoordinate(samplingRatio, offset);
00438     
00439     ArrayVector<Kernel1D<double> > kernels(period);
00440     
00441     createResamplingKernels(kernel, mapCoordinate, kernels);
00442 
00443     for(; sul.x < slr.x; ++sul.x, ++dul.x)
00444     {
00445         typename SrcIter::column_iterator sc = sul.columnIterator();
00446         typename DestIter::column_iterator dc = dul.columnIterator();
00447         resamplingConvolveLine(sc, sc+hold, src, dc, dc+hnew, dest,
00448                                kernels, mapCoordinate);
00449     }
00450 }
00451 
00452 template <class SrcIter, class SrcAcc,
00453           class DestIter, class DestAcc,
00454           class Kernel>
00455 inline void 
00456 resamplingConvolveY(triple<SrcIter, SrcIter, SrcAcc> src,
00457                     triple<DestIter, DestIter, DestAcc> dest,
00458                     Kernel const & kernel,
00459                     Rational<int> const & samplingRatio, Rational<int> const & offset)
00460 {
00461     resamplingConvolveY(src.first, src.second, src.third,
00462                         dest.first, dest.second, dest.third,
00463                         kernel, samplingRatio, offset);
00464 }
00465 
00466 /********************************************************/
00467 /*                                                      */
00468 /*               resamplingConvolveImage                */
00469 /*                                                      */
00470 /********************************************************/
00471 
00472 /** \brief Apply two separable resampling filters successively, the first in x-direction, 
00473            the second in y-direction.
00474 
00475     This function is a shorthand for the concatenation of a call to
00476     \link ResamplingConvolutionFilters#resamplingConvolveX resamplingConvolveX\endlink()
00477     and \link ResamplingConvolutionFilters#resamplingConvolveY resamplingConvolveY\endlink() 
00478     with the given kernels. See there for detailed documentation.
00479 
00480     <b> Declarations:</b>
00481 
00482     pass arguments explicitly:
00483     \code
00484     namespace vigra {
00485         template <class SrcIterator, class SrcAccessor,
00486                   class DestIterator, class DestAccessor,
00487                   class KernelX, class KernelY>
00488         void resamplingConvolveImage(SrcIterator sul,SrcIterator slr, SrcAccessor src,
00489                            DestIterator dul, DestIterator dlr, DestAccessor dest,
00490                            KernelX const & kx, 
00491                            Rational<int> const & samplingRatioX, Rational<int> const & offsetX,
00492                            KernelY const & ky, 
00493                            Rational<int> const & samplingRatioY, Rational<int> const & offsetY);
00494     }
00495     \endcode
00496 
00497 
00498     use argument objects in conjunction with \ref ArgumentObjectFactories:
00499     \code
00500     namespace vigra {
00501         template <class SrcIterator, class SrcAccessor,
00502                   class DestIterator, class DestAccessor,
00503                   class KernelX, class KernelY>
00504         void
00505         resamplingConvolveImage(triple<SrcIterator, SrcIterator, SrcAccessor> src,
00506                            triple<DestIterator, DestIterator, DestAccessor> dest,
00507                            KernelX const & kx, 
00508                            Rational<int> const & samplingRatioX, Rational<int> const & offsetX,
00509                            KernelY const & ky, 
00510                            Rational<int> const & samplingRatioY, Rational<int> const & offsetY);
00511     }
00512     \endcode
00513 
00514     <b> Usage:</b>
00515 
00516     <b>\#include</b> "<a href="resampling_convolution_8hxx-source.html">vigra/resampling_convolution.hxx</a>"
00517 
00518 
00519     \code
00520     Rational<int> xratio(2), yratio(3), offset(0);
00521 
00522     FImage src(w,h), 
00523            dest(rational_cast<int>(xratio*w), rational_cast<int>(yratio*h));
00524 
00525     float sigma = 2.0;
00526     Gaussian<float> smooth(sigma);
00527     ...
00528 
00529     // simpultaneously enlarge and smooth source image
00530     resamplingConvolveImage(srcImageRange(src), destImageRange(dest), 
00531                             smooth, xratio, offset,
00532                             smooth, yratio, offset);
00533 
00534     \endcode
00535 
00536 */
00537 template <class SrcIterator, class SrcAccessor,
00538           class DestIterator, class DestAccessor,
00539           class KernelX, class KernelY>
00540 void resamplingConvolveImage(SrcIterator sul,SrcIterator slr, SrcAccessor src,
00541                    DestIterator dul, DestIterator dlr, DestAccessor dest,
00542                    KernelX const & kx, 
00543                    Rational<int> const & samplingRatioX, Rational<int> const & offsetX,
00544                    KernelY const & ky, 
00545                    Rational<int> const & samplingRatioY, Rational<int> const & offsetY)
00546 {
00547     typedef typename
00548         NumericTraits<typename SrcAccessor::value_type>::RealPromote
00549         TmpType;
00550     
00551     BasicImage<TmpType> tmp(dlr.x - dul.x, slr.y - sul.y);
00552 
00553     resamplingConvolveX(srcIterRange(sul, slr, src),
00554                         destImageRange(tmp), 
00555                         kx, samplingRatioX, offsetX);
00556     resamplingConvolveY(srcImageRange(tmp),
00557                         destIterRange(dul, dlr, dest), 
00558                         ky, samplingRatioY, offsetY);
00559 }
00560 
00561 template <class SrcIterator, class SrcAccessor,
00562           class DestIterator, class DestAccessor,
00563           class KernelX, class KernelY>
00564 inline void
00565 resamplingConvolveImage(triple<SrcIterator, SrcIterator, SrcAccessor> src,
00566                    triple<DestIterator, DestIterator, DestAccessor> dest,
00567                    KernelX const & kx, 
00568                    Rational<int> const & samplingRatioX, Rational<int> const & offsetX,
00569                    KernelY const & ky, 
00570                    Rational<int> const & samplingRatioY, Rational<int> const & offsetY)
00571 {
00572     resamplingConvolveImage(src.first, src.second, src.third,
00573                             dest.first, dest.second, dest.third,
00574                             kx, samplingRatioX, offsetX,
00575                             ky, samplingRatioY, offsetY);
00576 }
00577 
00578 } // namespace vigra 
00579 
00580 
00581 #endif /* VIGRA_RESAMPLING_CONVOLUTION_HXX */

© Ullrich Köthe (koethe@informatik.uni-hamburg.de)
Cognitive Systems Group, University of Hamburg, Germany

html generated using doxygen and Python
VIGRA 1.4.0 (21 Dec 2005)