[med-svn] [r-bioc-ebimage] 02/04: New upstream version 4.20.0

Andreas Tille tille at debian.org
Wed Nov 8 13:07:54 UTC 2017


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository r-bioc-ebimage.

commit 5e372be36b0aac4709c0e071fb308d9f443a6670
Author: Andreas Tille <tille at debian.org>
Date:   Wed Nov 8 13:59:09 2017 +0100

    New upstream version 4.20.0
---
 CHANGES                                 | 467 +++++++++++++++
 DESCRIPTION                             |  20 +
 NAMESPACE                               | 101 ++++
 R/AllGenerics.R                         |  26 +
 R/Image.R                               | 814 ++++++++++++++++++++++++++
 R/abind.R                               |  55 ++
 R/clahe.R                               |   4 +
 R/colormap.R                            |  19 +
 R/computeFeatures.R                     | 362 ++++++++++++
 R/display.R                             | 275 +++++++++
 R/drawCircle.R                          |  28 +
 R/equalize.R                            |  42 ++
 R/filter2.R                             | 125 ++++
 R/floodFill.R                           |  67 +++
 R/gblur.R                               |   3 +
 R/localCurvature.R                      |  25 +
 R/medianFilter.R                        |   9 +
 R/morphology.R                          | 121 ++++
 R/normalize.R                           |  30 +
 R/objects.R                             |  82 +++
 R/otsu.R                                |  30 +
 R/segment.R                             |  78 +++
 R/spatial.R                             | 122 ++++
 R/tile.R                                |  36 ++
 R/tools.R                               |  47 ++
 README.md                               |  23 +
 build/vignette.rds                      | Bin 0 -> 246 bytes
 debian/README.Debian                    |  13 -
 debian/README.test                      |   5 -
 debian/changelog                        |  13 -
 debian/compat                           |   1 -
 debian/control                          |  30 -
 debian/copyright                        |  31 -
 debian/docs                             |   3 -
 debian/rules                            |  15 -
 debian/source/format                    |   1 -
 debian/tests/control                    |   3 -
 debian/tests/run-unit-test              |  13 -
 debian/upstream/metadata                |  15 -
 debian/watch                            |   4 -
 inst/CITATION                           |  23 +
 inst/NEWS.Rd                            | 348 ++++++++++++
 inst/doc/EBImage-introduction.R         | 348 ++++++++++++
 inst/doc/EBImage-introduction.Rmd       | 729 ++++++++++++++++++++++++
 inst/doc/EBImage-introduction.html      | 782 +++++++++++++++++++++++++
 inst/htmlwidgets/displayWidget.js       |  23 +
 inst/htmlwidgets/displayWidget.yaml     |   6 +
 inst/htmlwidgets/lib/viewer/display.css | 106 ++++
 inst/htmlwidgets/lib/viewer/viewer.js   | 611 ++++++++++++++++++++
 inst/images/cells.tif                   | Bin 0 -> 546751 bytes
 inst/images/nuclei.tif                  | Bin 0 -> 472093 bytes
 inst/images/sample-color.png            | Bin 0 -> 556865 bytes
 inst/images/sample.png                  | Bin 0 -> 187719 bytes
 inst/images/shapes.png                  | Bin 0 -> 3302 bytes
 inst/scripts/figDistanceMapDemo.R       |  27 +
 inst/scripts/readWriteTest.R            |  49 ++
 inst/scripts/readWriteTest.Rout.save    | 114 ++++
 inst/viewer/display.css                 |  97 ++++
 inst/viewer/display.template            |  10 +
 inst/viewer/viewer.js                   | 569 +++++++++++++++++++
 man/EBImage-defunct.Rd                  |  63 ++
 man/EBImage.Rd                          | 144 +++++
 man/Image.Rd                            | 153 +++++
 man/abind.Rd                            |  62 ++
 man/bwlabel.Rd                          |  67 +++
 man/channel.Rd                          |  85 +++
 man/clahe.Rd                            |  72 +++
 man/colorLabels.Rd                      |  42 ++
 man/colormap.Rd                         |  53 ++
 man/combine.Rd                          |  62 ++
 man/computeFeatures.Rd                  | 159 ++++++
 man/display-shiny.Rd                    |  85 +++
 man/display.Rd                          |  93 +++
 man/distmap.Rd                          |  49 ++
 man/drawCircle.Rd                       |  49 ++
 man/equalize.Rd                         |  56 ++
 man/fillHull.Rd                         |  46 ++
 man/filter2.Rd                          |  75 +++
 man/floodFill.Rd                        |  72 +++
 man/gblur.Rd                            |  49 ++
 man/io.Rd                               |  81 +++
 man/localCurvature.Rd                   |  70 +++
 man/medianFilter.Rd                     |  64 +++
 man/morphology.Rd                       | 112 ++++
 man/normalize.Rd                        |  58 ++
 man/ocontour.Rd                         |  37 ++
 man/otsu.Rd                             |  57 ++
 man/paintObjects.Rd                     |  72 +++
 man/propagate.Rd                        | 123 ++++
 man/rmObjects.Rd                        |  78 +++
 man/spatial.Rd                          | 102 ++++
 man/stackObjects.Rd                     |  91 +++
 man/thresh.Rd                           |  56 ++
 man/tile.Rd                             |  67 +++
 man/transpose.Rd                        |  49 ++
 man/watershed.Rd                        |  68 +++
 src/EBImage.c                           |  72 +++
 src/clahe.c                             | 391 +++++++++++++
 src/clahe.h                             |  30 +
 src/distmap.cpp                         | 146 +++++
 src/distmap.h                           |  17 +
 src/drawCircle.c                        |  87 +++
 src/drawCircle.h                        |  18 +
 src/floodFill.cpp                       | 396 +++++++++++++
 src/floodFill.h                         |  19 +
 src/getFrames.c                         | 107 ++++
 src/getFrames.h                         |  18 +
 src/haralick.c                          | 259 +++++++++
 src/haralick.h                          |  18 +
 src/medianFilter.c                      | 506 +++++++++++++++++
 src/medianFilter.h                      |  17 +
 src/morphology.cpp                      | 383 +++++++++++++
 src/morphology.h                        |  17 +
 src/nativeRaster.c                      |  72 +++
 src/nativeRaster.h                      |  17 +
 src/normalize.c                         | 100 ++++
 src/normalize.h                         |  17 +
 src/objects.c                           | 296 ++++++++++
 src/objects.h                           |  19 +
 src/ocontour.c                          |  92 +++
 src/ocontour.h                          |  17 +
 src/propagate.cpp                       | 216 +++++++
 src/propagate.h                         |  17 +
 src/spatial.c                           | 123 ++++
 src/spatial.h                           |  17 +
 src/thresh.cpp                          | 127 +++++
 src/thresh.h                            |  17 +
 src/tile.cpp                            | 229 ++++++++
 src/tile.h                              |  18 +
 src/tools.c                             |  85 +++
 src/tools.h                             |  46 ++
 src/transpose.cpp                       |  99 ++++
 src/transpose.h                         |  17 +
 src/watershed.cpp                       | 209 +++++++
 src/watershed.h                         |  17 +
 tests/test.R                            | 259 +++++++++
 tests/test.Rout.save                    | 977 ++++++++++++++++++++++++++++++++
 vignettes/EBImage-introduction.Rmd      | 729 ++++++++++++++++++++++++
 vignettes/logo.png                      | Bin 0 -> 15851 bytes
 139 files changed, 15907 insertions(+), 147 deletions(-)

diff --git a/CHANGES b/CHANGES
new file mode 100644
index 0000000..31dc572
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,467 @@
+version 4.5.22
+- fixed 'fg.col' and 'bg.col' handling in 'tile'
+
+version 4.5.21
+- set 'col = "white"' for grid lines in 'displayRaster'
+
+version 4.5.20
+- added grid lines to 'displayRaster'
+- cleaned up 'displayRaster' code
+
+version 4.5.19
+- 'displayRaster' does not restore 'par' graphical parameters on exit
+- fixed 'interpolate' argument passing in 'displayRaster'
+
+version 4.5.18
+- improvements to documentation
+
+version 4.5.17
+- added 'luminance' conversion mode to 'channel'
+- removed old code of 'writeImage'
+- added package news from previous versions to 'NEWS.Rd'
+- updated CHANGES
+
+version 4.5.16
+- further memory and performance improvements to 'readImage'
+- 'clipImage' now relies on the C coded 'normalize' function (up to 5x faster!)
+- significant performance improvements (up to 2x faster):
+  'Image'
+  'is.Image'
+  'selectChannel'
+  'show'
+  'flip'/'flop'
+- minor performance improvements (i.e. by using call to 'new' instead of 'Image'):
+  'validImage'
+  'filter2'
+  'colorLabels'
+  'rgbImage'
+  'affine'
+  'tile'
+  'isXbitImage'
+  'writeImage'
+- when called on an Image object 'as.Image' returns now it's argument rather than the Grayscale-coerced copy
+- Image constructor handles now properly multi-dimensional character arrays
+- fixed color mode check in 'validImageObject'
+- removed unnecessary calls to 'validObject'
+  NOTE: there is no need to check for object validity after creating it with 'new' (this is done automatically by R)
+- added some missing imports from 'methods' to the NAMESPACE file
+
+version 4.5.15
+- memory improvement to 'readImage'
+
+version 4.5.14
+- minor improvements to 'is.Image' and 'transpose'
+
+version 4.5.12
+- updated man page of 'flip', 'flop', and 'rotate'
+
+version 4.5.11
+- fix to 'getFrame' and 'combine' for single channel Color Images
+- added convenience function 'toRGB'
+
+version 4.5.10
+- fixed performance of 'readImage', especially on big image stacks
+
+version 4.5.9
+- 'colorLabels' now supports multidimensional images
+
+version 4.5.8
+- 'getFrame' now uses 'asub' from the abind package
+
+version 4.5.7
+- improved code and performance of 'colorLabels'
+
+version 4.5.6
+- updated 'getNumberOfFrames' and 'getFrame' functions to support multi-dimensional images
+
+version 4.5.5
+- added 'interpolate' parameter to the internal 'displayRaster' function
+
+version 4.5.4
+- fixed header title in the vignette
+- rolled back the change from 'array' to 'as.array' from previous commit
+
+version 4.5.3
+performance optimizations:
+- changed 'array' call to 'as.array' in Image constructor
+- 'normalize' does not reassign returned value to the original object
+
+version 4.5.2
+- minor modifications to code and documentation
+
+version 4.5.1
+- moved vignette from 'inst/doc/' to 'vignettes/'
+
+version 4.3.7
+- fixed a bug in 'getFrame' function: XY dimensions equal 1 were dropped
+
+version 4.3.5
+- minor documentation fixes
+
+version 4.3.4
+- modified the 'normalize' function to accept presetting the input intensity range (requested by Bernd Fischer)
+- added 'colorLabels' function by Bernd Fischer
+
+version 4.3.3
+- vignette uses now the 'BiocStyle' formatting
+
+version 4.3.2
+- 'normalize' now uses the generic from BiocGenerics
+
+version 4.3.1
+- fixed the redefinition of default arguments in C function templates which triggered errors in the clang compiler
+
+version 4.1.7
+- updated 'NEWS.Rd' and vignette papersize
+
+version 4.1.6
+- fixed long argument handling in 'deparse()' by adding 'width.cutoff' and 'nlines'
+- changed brush size in the 'morphology.Rd' from 100 to 99
+- maintainer changed
+
+version 4.1.5
+- fixed a bug in the 'medianFilter' function caused by incorrect [0:1] <> integer range conversion
+
+version 4.1.4
+- fixed a bug in the 'erode'/'dilate' functions introduced by the previous fix in version 3.99.2
+
+version 4.1.3
+- added function 'localCurvature' to compute local signed curvature along a line segment
+
+version 4.1.2
+- fixed a bug in the 'resize' function causing the new image width to be calculated incorrectly when only height was provided (reported by Bernd Fischer)
+- the range of pixel coordinates displayed in the Javascript viewer is now (1,1):(w,h) rather than (0,0):(w-1,h-1) and matches the indices of the corresponding Image array
+
+version 4.1.1
+- "inline double peekpixel" has been changed to "static inline double peekpixel", to be C99 compatible (thanks to Nathan Coulter)
+
+version 3.99.19
+- improved JavaScript image viewer with some minor bugs fixed
+- removed unused files from 'scripts/'
+- added 'NEWS.Rd'
+
+version 3.99.18
+- further improvements to the JavaScript image viewer; exit on-screen help using either 'ESC' or 'Q'
+- added 'transpose' to unit tests
+
+version 3.99.17:
+- removed all dependencies towards GTK+ and ImageMagick
+- deprecated: blur, equalize,
+- deprecated: drawtext, drawfont
+- deprecated: getFeatures, hullFeatures, zernikeMoments, edgeProfile, edgeFeatures
+- deprecated: haralickFeatures, haralickMatrix
+- deprecated: moments, smoments, rmoments, cmoments
+- overhauled the testing procedure in tests/test.R
+
+version 3.99.16
+- CSS fix disabling bilinear filtering of rescaled images in the JavaScript image viewer
+
+version 3.99.15
+- numerous improvements and bug fixes to the JavaScript image viewer, including: on-screen help displayed by pressing 'H', and fixed mouse wheel action (now consistent behavior across browsers and platforms)
+
+version 3.99.14
+- minor documentation updates
+
+version 3.99.13
+- improved JavaScript image viewer with upgraded user interface
+
+version 3.99.12
+- renamed 'swapXY' to 'transpose'
+- updated documentation on 'transpose', 'readImage', writeImage', and 'display'
+
+version 3.99.11
+- better argument handling in the 'display' function
+- a message rather than a warning is now issued when displaying multiframe images using the 'raster' method
+- range clipping to [0:1] in 'writeImage'
+
+version 3.99.10
+- improved 'getFrame' function: better performance by reassigning array dimension only when needed
+
+version 3.99.9
+- 'font-face' misspelling in 'display.template'
+- restored the original 'getFrame' function
+
+version 3.99.8
+- substituted the former GTK+ based 'display' function by the new one relying on 'displayInBrowser' and 'displayRaster' functions
+- substituted all calls to 'paste0' by 'paste'
+- disabled bilinear filtering of rescaled images in the JavaScript image viewer
+- fixed a minor bug in the 'displayRaster' function: a warning message was triggered for single-frame images
+
+version 3.99.7
+- changed handling of greyscale images containing an alpha channel (aka. GA): instead of discarding the second channel and reading pixel data into a 'Greyscale' Image both channels are now stored in a 'Color' Image
+- removed 'readImageOld' and 'writeImageOld'
+- removed 'inst/scripts/newAgainstOld.R'
+- updated 'inst/scripts/readWriteTest.R'
+
+version 3.99.5
+- new 'displayInBrowser' function: preview of the new JavaScript based image viewer
+
+version 3.99.4
+- readImage' can now read directly from URLs
+- some fixes and improvements to 'displayRaster'
+
+version 3.99.3
+- replaced 'readImage' and 'writeImage' by the new 'readImage2' and 'writeImage2' functions; moved the previous ones to 'readImageOld' and 'writeImageOld'
+
+version 3.99.2
+- bumped version from 4.0.1 to 3.99.2 to prepare the future 4.0.0 version
+- refactored the functions, not using ImageMagick any longer: translate, affine, rotate, resize
+- removed: animate
+- deprecated: blur, equalize
+- todo: man pages have to be synced with recent changes (blur, resize, affine, rotate, translate, display)
+- todo: overhaul the unit tests
+- todo: remove the old moment generating functions: hullFeatures...
+- todo: remove last dependencies to IM
+- fixed a bug in 'erode'/'dilate' functions: incorrect range of loop indices caused memory reads from outside the kernel vector
+- added 'readImage2' and 'writeImage2' functions: candidates for replacing the original ImageMagick based I/O functions
+- added 'swapXY' function swapping the first two indices of an image array during I/O operations
+- added 'displayRaster' function displaying images using R graphics
+- modified 'getFrame': improved performance
+- modified 'as.raster'
+- 'inst/images/lena.gif' is now 'inst/images/lena.png' (preparing for dropping GIF support)
+- in '/inst/scripts/': scripts for testing the new read/write functions against the former ones using test images from the on-line EBImage Test Images repository (http://www-huber.embl.de/EBImage/ExampleImages/)
+
+version 3.13.2
+- deprecated 'equalize'
+- fixed occurences of partial argument matching that 'R CMD check' had complained about
+
+version 3.13.1
+- fixed a bug in ImageMagickCall that cause some matrices not to be normalized to [0;1] before IM calls
+
+version 3.13.0
+- addition of greyscale functions for computation of the self-complementary top-hat (I. Kats)
+- a median filter based on Perreault's constant time median filter (J. Barry)
+
+3.11.2
+- fixed the angle computation of computeFeatures.moment (there was a sign inversion before for angles larger than pi/4)
+
+3.9.9
+- animate() does not work anymore (IM issues I guess...) and now calls display(); I will reimplement the animating function using timer messages in display()
+
+3.9.8
+- fixed flop() when working with rectangular images (bug reported by Takeo Katsuki)
+
+3.9.7
+- TrueColor is not present any more
+- fixed paintObjects using Kevin Keraudren's bug fix
+- paintObjects behavior has now changed with respect to the opacity parameter
+
+3.9.6
+- the functions getFeatures, hullFeatures, edgeProfile, edgeFeatures are now obsolete
+- the functions moments, cmoments, smoments, rmoments, haralickFeatures, haralickMatrix, zernikeMoments are now obsolete 
+- the functions are still exported for backward compatibility reasons
+- removed some TrueColor tests, which is going to be discarded from EBImage
+- removed the deprecated arguments: iter, no.GTK, main, colorize
+
+3.9.5
+- exporting computeFeatures
+
+3.9.4
+- fixed haralickFeatures and haralickMatrix when no objects are present
+
+3.9.3
+- experimental support for computeFeatures
+- ocontour does not return empty objects anymore
+
+3.9.2
+- the gaussian shape in makeBrush() is now conform to Matlab (J. Barry)
+- added the affine() transform
+
+3.9.1
+- added the gaussian shape in makeBrush()
+- fixed the quality argument in writeImage()
+
+3.7.1
+- added getFrame(), to extract the i-th frame of an image
+- from Paul Murrell: added a PROTECT/UNPROTECT statement in lib_readImages()
+- from Paul Murrell: added as.raster.Image() 
+
+3.5.5
+- now supports Cellomics .c01 image file format
+
+3.5.4
+- drawCircle() now returns the transformed image and does not modify the input image anymore 
+
+3.5.3
+- removed print click position feature
+
+3.5.2
+- added drawCircle()
+- display() now prints click positions
+
+3.5.1
+- lgomp was removed from linking options, to prevent a segfault caused in OpenSUSE 11.2 and 11.3 by dynamic linking of lgomp (optionally used by ImageMagick but not used in EBImage) together with lpthread (required and used by GTK)
+- removed Makevars.in
+- minimal IM required version is now 6.3.7
+- lower vignette image quality to save space
+
+3.3.2
+- now uses MagickCoreGenesis and MagickCoreTerminus to start/terminate ImageMagick, to be compatible with IM 6.6.x
+
+3.3.1
+- removed frameDist, matchObject
+- removed stopIfNotImage, morphKern, mkball, mkbox
+- removed header, assert, chooseImage
+- removed resample, sharpen, umask, modulate
+- removed negate, affinet, normalize2, noise
+- removed mediansmooth, cgamma, enhance, denoise
+- removed contrast, despeckle, edge, segment
+- removed cthresh, athresh
+- removed channelMix
+- removed applyGaussian and pseudoZernike
+
+3.1.3 
+- merge devel with release version 3.0.1
+
+3.0.1
+- updated CHANGES
+- updated manual pages
+- now uses is(x, 'Image') instead of class(x)=='Image' to allow people inherit from the EBImage class
+- updated INSTALL file, with new tips for Windows and MacOS users
+- removed Gene*.tif old images
+
+2.99.8
+- new INSTALL
+- new vignette
+
+2.99.7
+- stackObjects OK
+
+2.99.6
+- doc: propagate, combine, tile, untile, rmObjects, reenumerate, hullFeatures
+
+2.99.5
+- channel OK
+- paintObjects, drawtext OK
+- colors.c removed
+- doc: watershed
+- simpler combine
+
+2.99.4
+- deprecated channelMix
+- doc: filter2, moments, cmoments, smoments, rmoments
+- doc: paintObjects, getFeatures, rgbImage, channel
+
+2.99.3
+- bwlabel OK
+- safer rgbImage
+
+2.99.2
+- doc: tresh
+- floodFill and fillHull : doc + test + examples
+
+2.99.1
+- starting the pre 3.0.0 release
+- doc: equalize, blur, gblur, normalize, EBImage, EBImage-deprecated
+
+2.7.22
+- fixed a bug preventing from reading palette images in color
+- deprecated all ImageMagick functions except blur, gblur, equalize, resize and rotate
+- validImage now checks validity of arrays
+- new doc for ocontour, translate, resize, rotate , flip, flop
+- new examples for makeBrush
+
+2.7.21
+- moment features renamed from '*' to 'm.*'
+- geometric hull features  from 'h.*' to 'g.*' to not to be confused with Haralick's features 'h.*'
+- moment features now need ref to be computed
+- getFeatures now just concatenate hull, moments, edge, haralick and zernike features
+- no more s2maj and s2min in hullFeatures: now only l1 and l2 (eigenvalues of the covariance matrix)(to be consistent with moments)
+
+2.7.20
+- outline renamed in ocontour, ocontour documentation added
+- build warnings should disappear
+
+2.7.19
+- outline added
+
+2.7.16
+- fixed warnings due to unused variables in C code
+- fixed a drawtext bug which prevented EBImage to be checked on R 2.9.0
+- fixed floodFill example by restoring inst/images/holes.png
+
+2.7.15
+- no warnings anymore in R CMD check
+- S4 methods removed
+- header and assert deprecated
+- test framework added
+- documentation updated
+- morphKern, mkball and mkbox deprecated 
+- all '...' arguments removed
+- horizontal slider and clean display.c code added
+- zernikeMoments arguments 'pseudo' and 'apply.Gaussian' deprecated
+- display and readImage argument 'colormode' deprecated
+- display arguments: 'main', 'no.GTK' deprecated
+- .ImageCall and ensureStorageMode functions removed
+
+2.7.11
+- no ImageX object anymore: Image now extends array
+
+2.7.10
+- fixed ReadImage bug when filename=''
+
+2.7.9
+- created tile.c, tile.h
+- out of range [0:1] normalize for Magick
+- matrix: channel 4, untile, getFeatures fixed
+- x11 channel OK
+
+2.7.8
+- validImage in C, removed isImage in C
+- merged IndexedImage class within Image, no IndexedImage class anymore !
+- removed spurious '...'
+- classUnion ImageX instead of Image !
+- now Image supports non-numeric storage.mode (e.g. logical) !
+- ImageCall instead of Call to check Image validity and storage.mode
+- deprecated: matchObject, stopIfNotImage
+
+2.7.7
+- reenumerate, hullFeatures, rmObjects, watershed: work fine on md and Color
+- watershed: fixed bug when used on distmap(lena>0.5)
+- getFeatures, hullFeatures, edgeProfile, edgeFeatures: work fine on md and Color
+- cmoments, smoment, rmoments, moments: work fine on md and Color
+- haralickFeatures, haralickMatrix, zernikeMoments: work fine on md and Color
+- zernikeMoments: work fine on md and Color
+- frameDist deprecated
+- class.IndexedImage.R: stop message in case of TrueColor now OK
+- checked all INTEGER(GET_DIM())[2] OK
+- merged all features in features.R
+- renamed methods-display.R in display.R, filters_watershed.cpp in watershed.cpp
+- watershed is now interruptable
+- objects methods moved from class.IndexedImage.R to objects.R
+
+2.7.6
+- fixed dy instead of dx in lib_filterThresh L37 in nFramePix = (2 * dx + 1) * (2 * dx + 1); (bug submitted by Glenn Davis)
+- tile, untile, thresh: works fine on md, Color
+- vignettes build OK
+- translate: check param input OK
+- keeping correct storage.mode after binary operations on TrueColor images
+- deprecated functions moved to deprecated.R
+- vignette changes: (indexing a[,,,1] instead of a[,,1] and promote usage of rgbImage)
+- unit test inst/scripts/test.R added
+- new deprecated.R file
+
+2.7.5
+- functions copy, .isCorrectType, .correctType have been removed
+- multidimensional arrays are now handled
+- new color mode Color added
+- functions getNumberOfFrames, translate added
+- signature combine has been changed
+- validity method added
+- file filters.R renamed in magick.R
+- file spatial.R added
+- abind dependency added
+- new fast distmap algorithm, up to 1000 times faster
+- readImage now uses by default the native image color mode
+- C functions getColorMode, getNumberOfFrames, getNumberOfChannels, getColorStrides added
+- C function lib_paintFeatures renamed to paintObjects
+- C function lib_matchFeatures renamed to matchObjects
+- C function lib_deleteFeatures renamed to rmObjects
+- C function lib_stack_objects renamed to stackObjects
+- C function lib_tile_stack renamed to tile
+- C function lib_untile renamed to untile
+- display is faster since pixbuf are now directly built from SEXP
+- writeImage now uses a quality of 100 by default
+- keep correct storage.mode after binary operations 
+- the Image constructor is doing now the conversion between TrueColor and (Grayscale or Color) images
+- cleaner show, print methods
diff --git a/DESCRIPTION b/DESCRIPTION
new file mode 100644
index 0000000..9bc8e90
--- /dev/null
+++ b/DESCRIPTION
@@ -0,0 +1,20 @@
+Package: EBImage
+Version: 4.20.0
+Title: Image processing and analysis toolbox for R
+Encoding: UTF-8
+Author: Andrzej Oleś, Gregoire Pau, Mike Smith, Oleg Sklyar, Wolfgang Huber, with contributions from Joseph Barry and Philip A. Marais
+Maintainer: Andrzej Oleś <andrzej.oles at embl.de>
+Depends:
+Imports: BiocGenerics (>= 0.7.1), methods, graphics, grDevices, stats,
+        abind, tiff, jpeg, png, locfit, fftwtools (>= 0.9-7), utils,
+        htmltools, htmlwidgets, RCurl
+Suggests: BiocStyle, digest, knitr, rmarkdown, shiny
+Description: EBImage provides general purpose functionality for image processing and analysis. In the context of (high-throughput) microscopy-based cellular assays, EBImage offers tools to segment cells and extract quantitative cellular descriptors. This allows the automation of such tasks using the R programming language and facilitates the use of other tools in the R environment for signal processing, statistical modeling, machine learning and visualization with image data.
+License: LGPL
+LazyLoad: true
+biocViews: Visualization
+VignetteBuilder: knitr
+URL: https://github.com/aoles/EBImage
+BugReports: https://github.com/aoles/EBImage/issues
+NeedsCompilation: yes
+Packaged: 2017-10-30 22:43:26 UTC; biocbuild
diff --git a/NAMESPACE b/NAMESPACE
new file mode 100644
index 0000000..5145403
--- /dev/null
+++ b/NAMESPACE
@@ -0,0 +1,101 @@
+useDynLib("EBImage", .registration = TRUE, .fixes = "C_")
+
+importFrom("BiocGenerics", "combine", "normalize")
+importFrom("methods", "as", "callGeneric", "callNextMethod", "extends", "is", "new", "Ops", "slot", "show", "validObject")
+importFrom("graphics", "abline", "clip", "hist", "hist.default", "image", "image.default", "matplot", "par", "plot", "rasterImage")
+importFrom("grDevices", "as.raster", "col2rgb", "gray", "heat.colors", "rgb")
+importFrom("stats", "dnorm", "mad", "median", "predict", "quantile", "sd")
+importFrom("abind", "abind", "adrop", "asub")
+importFrom("locfit", "locfit", "lp")
+importFrom("tiff", "readTIFF", "writeTIFF")
+importFrom("jpeg", "readJPEG", "writeJPEG")
+importFrom("png", "readPNG", "writePNG")
+importFrom("fftwtools", "fftw2d")
+importFrom("utils", "browseURL")
+importFrom("RCurl", "base64Encode")
+importFrom("htmltools", "htmlDependency")
+importFrom("htmlwidgets", "createWidget", "sizingPolicy")
+
+## classes
+exportClasses(Image)
+
+## image class, accessors
+export(Image, is.Image, as.Image)
+export(Grayscale, Color)
+exportMethods(Ops, "[")
+export(colorMode, "colorMode<-", imageData, "imageData<-")
+export(getFrame, getFrames, numberOfFrames)
+S3method(as.array, Image)
+S3method(as.raster, Image)
+S3method(print, Image)
+S3method(as.Image, Image)
+S3method(as.Image, default)
+
+## image IO, display
+exportMethods(show, image)
+export(readImage, writeImage)
+
+## image display
+export(display)
+S3method(plot, Image)
+
+# shiny bindings
+export(displayOutput, renderDisplay)
+
+## spatial transform
+export(resize, rotate)
+export(flip, flop)
+export(translate)
+export(affine)
+export(transpose)
+
+## image segmentation, objects manipulation
+export(thresh, bwlabel, otsu)
+export(watershed, propagate)
+export(ocontour)
+export(paintObjects, rmObjects, reenumerate, colorLabels)
+
+## statistics
+S3method(median, Image)
+S3method(quantile, Image)
+exportMethods(hist)
+
+## image enhancement, filtering
+export(filter2)
+export(gblur)
+export(medianFilter)
+export(equalize)
+export(clahe)
+
+## morphological operations
+export(makeBrush)
+export(erode, dilate, opening, closing, whiteTopHat, blackTopHat, selfComplementaryTopHat)
+export(distmap)
+export(floodFill, fillHull)
+
+## colorspace
+export(rgbImage, channel, toRGB, colormap)
+
+## image stacking, tiling
+export(stackObjects)
+export(tile, untile)
+
+## drawables
+export(drawCircle)
+
+## features extraction
+export(computeFeatures)
+export(computeFeatures.basic, computeFeatures.haralick, computeFeatures.moment, computeFeatures.shape)
+export(standardExpandRef, gblob)
+export(localCurvature)
+
+## methods extending S4 generics from the BiocGenerics package
+exportMethods(normalize, combine)
+
+## enable abind for Image objects
+exportMethods(abind)
+
+## legacy code because of broken R 3.1.2
+importFrom("methods", "Math2")
+exportMethods(Math2)
+exportMethods(log)
diff --git a/R/AllGenerics.R b/R/AllGenerics.R
new file mode 100644
index 0000000..aef0b7a
--- /dev/null
+++ b/R/AllGenerics.R
@@ -0,0 +1,26 @@
+# Declarations of all generic methods used in the package
+
+# Copyright (c) 2005-2007 Oleg Sklyar
+
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation; either version 2.1
+# of the License, or (at your option) any later version.
+
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+# See the GNU Lesser General Public License for more details.
+# LGPL license wording: http://www.gnu.org/licenses/lgpl.html
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+## image IO, display
+setGeneric ("image", function (x, ...) standardGeneric("image") )
+
+## statistics
+setGeneric ("hist", function (x, ...) standardGeneric("hist") )
+
+# explicit signature definition to avoid dispatching on arguments other than `...`
+setGeneric("abind", signature="...")
diff --git a/R/Image.R b/R/Image.R
new file mode 100644
index 0000000..723b138
--- /dev/null
+++ b/R/Image.R
@@ -0,0 +1,814 @@
+# Class Image, definition and methods
+
+# Copyright (c) 2005-2007 Oleg Sklyar
+
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation; either version 2.1
+# of the License, or (at your option) any later version.
+
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+# See the GNU Lesser General Public License for more details.
+# LGPL license wording: http://www.gnu.org/licenses/lgpl.html
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Grayscale = 0L
+Color     = 2L
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+setClass ("Image",
+  representation (colormode="integer"),
+  prototype (colormode=Grayscale),
+  contains = "array"
+)
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Image = function(data = array(0, dim=c(1,1)), dim, colormode) {
+  if ( !missing(dim) && length(dim)<2 )
+    stop("The number of dimensions dim must be at least 2")
+  
+  setdim = function(data) {
+    if (is.array(data))
+      base::dim(data)
+    else 
+      c(1L, length(data))
+  }
+  
+  ## special character case
+  if (is.character(data)) {
+    colormode = 
+      if (missing(colormode)) 
+        Color
+      else
+        parseColorMode(colormode)
+    
+    if (missing(dim))
+      dim = setdim(data)
+      
+    if ( colormode==Color )
+      dim = c(dim[1:2], 3L, dim[-(1:2)])  
+    
+    dimnames = dimnames(data)
+    data = col2rgb(data)/255
+    
+    if ( colormode==Color ) {
+      channels = if (length(dim)<3L) 1L else seq_len(dim[3L])
+      data = abind::abind(lapply(channels, function(ch) {
+        array( if (ch>3L) 0 else data[ch,,drop=FALSE], dim[-3L], dimnames)
+      }), along = 2.5)
+      # replace a list of NULLs by the original NULL
+      if(is.null(dimnames)) dimnames(data) = NULL
+    }
+    else
+      data = array(data = (data[1,,drop=FALSE] + data[2,,drop=FALSE] + data[3,,drop=FALSE]) / 3, dim = dim, dimnames = dimnames)
+  }
+  ## default numeric case
+  else {
+    if (missing(dim))
+      dim = setdim(data)
+    
+    colormode = 
+      if (missing(colormode))
+        if (is.Image(data))
+          colorMode(data)
+        else
+          Grayscale
+      else 
+        parseColorMode(colormode)
+  }
+  
+  return( new("Image", 
+    .Data = 
+      ## improve performance by not calling array constructor on well formed arrays
+      if( is.array(data) && prod(dim)==length(data) ) {
+        if( length(dim(data)) != length(dim) || any(dim(data) != dim))
+          dim(data) = dim
+        data
+      }
+      else {
+        array(data, dim = dim)
+      },
+    colormode = colormode
+  ))    
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+is.Image <- function (x) is(x, "Image")
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+as.Image <- function(x) UseMethod("as.Image")
+
+as.Image.Image = function(x) {
+  ## true Image
+  if ( class(x)=="Image" )
+    x
+  ## coerce subclasses to Image superclass
+  else
+    as(x, "Image")
+}
+
+as.Image.default = function(x) Image(x)
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## define method for the S3 generic 'as.array'
+as.array.Image = function(x, ...) x at .Data
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+colorMode = function (y) {
+  if (is(y, 'Image')) y at colormode
+  else Grayscale
+}
+
+`colorMode<-` = function(y, value) {
+  if (is(y, 'Image')) {
+    y at colormode = parseColorMode(value)
+    validObject(y)
+    y
+  } 
+  else warning('Color mode of an array cannot be changed, the array should be cast into an Image using \'Image\'')
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+imageData = function (y) {
+  if (is(y, 'Image')) y at .Data
+  else y
+}
+
+`imageData<-` = function (y, value) {
+  if (is(y, 'Image')) {
+    y at .Data = value
+    validObject(y)
+    y
+  } 
+  else value
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+validImageObject = function(object) {
+  .Call(C_validImageObject, object) 
+}
+setValidity("Image", validImageObject)
+
+## Overloading binary operators
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+setMethod("Ops", signature(e1="Image", e2="Image"),
+	function(e1, e2) {
+          e1 at .Data = callGeneric(e1 at .Data, e2 at .Data)
+          validObject(e1)
+          return(e1)
+	}
+)
+setMethod("Ops", signature(e1="Image", e2="numeric"),
+	function(e1, e2) {
+          e1 at .Data = callGeneric(e1 at .Data, e2)
+          validObject(e1)
+          return(e1)
+	}
+)
+setMethod("Ops", signature(e1="numeric", e2="Image"),
+	function(e1, e2) {
+          e2 at .Data = callGeneric(e1, e2 at .Data)
+          validObject(e2)
+          return(e2)
+	}
+)
+
+## legacy code addressing broken 'Math2' S4GroupGenerics in R 3.1.2
+setMethod("Math2", "Image",
+          function(x, digits) {
+            x at .Data <- callGeneric(x = x at .Data, digits = digits)
+            x
+          }
+)
+
+## explicit method definition for 'log' needed because of the extra formal argument, see ?Math
+setMethod("log", signature(x="Image"),
+          function(x, base) {
+            x at .Data <- callGeneric(x = x at .Data, base = base)
+            x
+          }
+)
+
+## private
+## determines image type
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+determineFileType = function(files, type) {
+  ## helper function 
+  collapseTypeSynonyms = function(x){
+    x = tolower(x)
+    x[x=="tif"] = "tiff"
+    x[x=="jpg"] = "jpeg"
+    x
+  }
+
+  if (!(is.character(files)&&(length(files)>=1)))
+    stop("Please supply at least one filename.")
+
+  if (missing(type)) {
+    type = unique(collapseTypeSynonyms(sapply(strsplit(files, split=".", fixed=TRUE), 
+      function(x) {
+        if (length(x)>1) 
+          x[length(x)] 
+        else if (length(x)==1)
+          stop(sprintf("Unable to determine type of %s: Filename extension missing.", x)) 
+        else 
+          stop("Unable to determine type: Empty filename.") 
+      }
+    )))
+    if (length(type)>1)
+      stop(sprintf("File type implied by the file name extensions must be unique, but more than one type was found: %s.", paste(type, collapse=", ")))
+  } else {
+    if (!(is.character(type)&&(length(type)==1)))
+      stop("'type' must be a character vector of length 1.")
+    else 
+      type = collapseTypeSynonyms(type)
+  }
+  return(type)
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+readImage = function(files, type, all=TRUE, names = sub("\\.[^.]*$", "", basename(files)), ...) {
+  
+  .readURL = function(url, buffer=2^24){
+    f = try(file(url, "rb"), silent=TRUE)
+    if (inherits(f,"try-error")) 
+      stop(attr(f,"condition")$message)
+    
+    rawData = bufData = NULL;
+    
+    while( length(bufData <- readBin(f, 'raw', buffer)) > 0L )
+      rawData = c(rawData, bufData)
+    
+    try(close(f), silent=TRUE)
+    
+    rawData
+  }
+  
+  type = try (determineFileType(files, type), silent=TRUE)
+  if (inherits(type,"try-error")) 
+    stop(attr(type,"condition")$message)
+  
+  .readFun = switch(type,
+                    tiff = function(x, ...) {
+                      y = readTIFF(x, all = all, ...)
+                      if ( (l=length(y)) > 1L) {
+                        if (!is.null(names)) names(y) = seq_len(l)
+                        # make sure all frames have the same dimensions
+                        if(!all(duplicated.default(lapply(y, dim))[-1L]))
+                          stop(sprintf("Frame dimensions of the '%s' file are not equal.", x))
+                      }
+                      y
+                    },
+                    jpeg = function(x, ...) readJPEG(x, ...),
+                    png  = function(x, ...) readPNG(x, ...),
+                    stop(sprintf("Invalid type: %s. Currently supported formats are JPEG, PNG, and TIFF.", type))
+  )
+  
+  .loadFun = function(i, ...) {
+    ## first look for local files
+    if(!file.exists(i)){
+      ## might still be a remote URL  
+      w = options(warn=2)
+      i = try(.readURL(i), silent = TRUE)
+      options(w)
+      ## is not URL
+      if (inherits(i,"try-error")) {
+        warning( sub("(converted from warning) ", "", attr(i,"condition")$message, fixed = TRUE) )
+        return(NULL)
+      }
+    }
+    ## ensure that the file is not a directory
+    else if (file.info(i)$isdir){
+      warning(sprintf("Cannot open %s: Is directory.", i))
+      return(NULL)
+    }
+    
+    return(.readFun(i, ...))
+  }
+  
+  # flatten nested image list and remove null elements
+  .flatten <- function(x) {
+    while(any(vapply(x, is.list, logical(1L)))) {
+      x <- lapply(x, function(x) if(is.list(x)) x else list(x))
+      x <- unlist(x, recursive = FALSE) 
+    }
+    x[!vapply(x, is.null, logical(1L))]
+  }
+  
+  # stratify processing for single and multiple files to increase performance
+  
+  # single file
+  if(length(files) == 1L){
+    y = .loadFun(files, ...)
+  }
+  
+  #  multiple files
+  else {
+    y = lapply(files, .loadFun, ...)
+    names(y) = names
+    y = .flatten(y)   
+  }
+  
+  if(is.list(y)){
+    if(length(y)==0L) stop("Empty image stack.")
+    
+    # check whether image dimensions match
+    if(!all(duplicated.default(lapply(y, dim))[-1L]))
+      stop("Images have different dimensions")
+    
+    y1 = y[[1L]]
+    channels = channelLayout(y1)
+    if(length(y) == 1L)
+      y = y1
+    else
+      y = vapply(y, identity, FUN.VALUE=y1)
+    rm(y1)
+  }
+  else{
+    channels = channelLayout(y)
+  }
+  
+  y = transpose(y)
+  
+  new("Image", .Data = y, colormode = if(isTRUE(charmatch(channels,'G') == 1)) Grayscale else Color )
+}
+
+## private
+## returns channel layout of a pixel array: G, GA, RGB, or RGBA
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+channelLayout = function(x){
+  y = dim(x) 
+  return( switch(if(length(y)==2) 1 else if (length(y)==3 && y[3]<=4) y[3] else 5, 'G', 'GA', 'RGB', 'RGBA', 'unknown') )
+}
+
+## private
+## helper function used to check whether image data can be written on X bits without accuracy loss
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+isXbitImage = function(x, bits) {
+  b = 2^bits - 1
+  x = as.numeric(x)
+  
+  ## fast termination if not
+  y = b * x[1] 
+  if (trunc(y)!=y)
+    FALSE
+  else  {
+    y = b * x
+    all(trunc(y)==y)
+  }
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+writeImage = function (x, files, type, quality=100L, bits.per.sample, compression='none', ...) {  
+  validImage(x)
+  
+  type = try (determineFileType(files, type), silent=TRUE)
+  if (inherits(type,"try-error")) 
+    stop(attr(type,"condition")$message)
+  
+  ## automatic bits.per.sample guess
+  if ( (type=='tiff') && missing(bits.per.sample) ) {
+    if (isXbitImage(x, 8L)) 
+      bits.per.sample = 8L 
+    else 
+      bits.per.sample = 16L
+  }
+  
+  writeFun = switch(type,
+                    tiff = function(x, file, ...) writeTIFF(x, file, bits.per.sample=bits.per.sample, compression=compression, ...),
+                    jpeg = function(x, file, ...) writeJPEG(x, file, quality=quality/100, ...),
+                    png  = function(x, file, ...) writePNG(x, file, ...),
+                    stop(sprintf("Invalid type: %s. Currently supported formats are JPEG, PNG, and TIFF.", type))
+  )
+  
+  if ((quality<1L) || (quality>100L))
+    stop("'quality' must be a value between 1 and 100.")
+  
+  nf = numberOfFrames(x, type='render')
+  lf = length(files)
+  
+  if ( (lf!=1) && (lf!=nf) )
+    stop(sprintf("Image contains %g frame(s) which is different from the length of the file name list: %g. The number of files must be 1 or equal to the size of the image stack.", nf, lf))
+  
+  else {
+    frames = seq_len(nf)
+    
+    x = clipImage(x) ## clip the image and change storage mode to double
+    x = transpose(x)    
+    
+    if ( lf==1 && nf>1 ) {
+      ## store all frames into a single TIFF file
+      if (type=='tiff') {
+        
+        ## create list of image frames
+        la = lapply(frames, function(i) getFrame(x, i, 'render'))
+        
+        if (nf==writeFun(la, files, ...))
+          return(invisible(files))
+        else
+          stop(sprintf("Error writing file sequence to TIFF."))
+      }
+      ## generate file names for frames
+      else {
+        basename = unlist(strsplit(files, split=".", fixed=TRUE))
+        prefix   = basename[-length(basename)]
+        suffix   = basename[length(basename)]
+        
+        files = vapply(frames, function(i) paste0(paste0(prefix, collapse='.'), '-', i-1, '.', suffix), character(1))
+      }
+    }
+    
+    ## store image frames into individual files
+    for (i in frames)
+      writeFun(getFrame(x, i, 'render'), files[i], ...)
+    return(invisible(files))
+  }
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+setMethod ("[", "Image",
+           function(x, i , j, ..., drop = TRUE) {
+             ## list(...) doesn't work in this S4 method dispatch framework we
+             ## are using the following trick: the current call is evaluated,
+             ## but using x at .Data instead of x in the previous calling frame
+             sc = sys.call()
+             args = as.list(sc[-c(1L, 2L)])
+             numIndices = length(args) - !is.null(args$drop)
+             
+             # when subsetting with single index treat as array
+             if (numIndices == 1L) {
+               callNextMethod()
+             }
+             else {
+               # subset image array without dropping dimensions in order to
+               # preserve spatial dimensions
+               sc$drop = FALSE
+               sc[[2L]] = call('slot', sc[[2L]], '.Data')
+               y = eval.parent(sc)
+                              
+               # drop dims higher than 2 unless 'drop' explicitly set to FALSE
+               if(!isTRUE(args$drop==FALSE) && length( (d = dim(y)) ) > 2L){
+                 dims = which(d==1L)
+                 y = adrop(y, drop = dims[dims>2L]) 
+               }
+               
+               x at .Data = y
+               validObject(x)
+               x
+             }
+           })
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+getFrame = function(y, i, type = c('total', 'render')) {
+    type = match.arg(type)
+    
+    n = numberOfFrames(y, type)
+    if (i<1 || i>n) stop("'i' must belong between 1 and ", n)
+    
+    # return the argument if no subsetting neccessary
+    ld = length(dim(y))
+    fd = if (colorMode(y)==Color && type=='render' && ld>2L) 3L else 2L
+    if (ld==fd) return(y)
+    
+    type = switch(type, total = 0L, render = 1L)
+    
+    .Call(C_getFrame, y, as.integer(i), type)
+}
+
+getFrames = function(y, i, type = c('total', 'render')) {
+  type = match.arg(type)
+  
+  n = numberOfFrames(y, type)
+  
+  if ( missing(i) ) 
+    i = seq_len(n)
+  else {
+    i = as.integer(i)
+    if ( any( i<1L || i>n ) ) stop("'i' must be a vector of numbers ranging from 1 to ", n)
+  }
+  
+  type = switch(type, total = 0L, render = 1L)
+  
+  .Call(C_getFrames, y, i, type)
+}
+
+
+## numberOfFrames
+## If type='total', returns the total number of frames
+## If type='render', return the number of frames to be rendered after color channel merging
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+numberOfFrames = function(y, type = c('total', 'render')) {
+    type = match.arg(type)
+    type = switch(type, total = 0L, render = 1L)
+    .Call(C_numberOfFrames, y, type)
+}
+
+numberOfChannels = function(y, d = dim(y), cm = colorMode(y)) {
+  if ( cm==Grayscale || is.na(d[3L]) ) 1L else d[3L]
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+showImage = function (object, short=FALSE) {
+  nd = dim(object)
+  ld = length(nd)
+  dimorder = names(dimnames(object))
+  
+  cat(class(object)[1],'\n')
+  
+  cat('  colorMode    :',c('Grayscale', NA, 'Color')[1+colorMode(object)],'\n')
+  cat('  storage.mode :',typeof(object),'\n')
+  cat('  dim          :',nd,'\n')
+  if ( !is.null(dimorder) )
+  cat('  dimorder     :',dimorder,'\n')
+  cat('  frames.total :',numberOfFrames(object,'total'),'\n')
+  cat('  frames.render:',numberOfFrames(object,'render'),'\n')
+  
+  if ( !isTRUE(short) ) {
+    if (nd[1]>5) nd[1] = 5
+    if (nd[2]>6) nd[2] = 6
+    if (ld>2) nd[3:ld] = 1
+    
+    ndl = lapply(nd, seq_len)
+    
+    nds = paste0('[1:',nd[1],',1:',nd[2],paste(rep(',1',ld-2),collapse=''),']')
+    
+    cat('\nimageData(object)', nds, '\n', sep='')
+    print.default(asub(object at .Data, ndl))
+  }
+  invisible()
+}
+
+setMethod ("show", signature(object = "Image"), function(object) showImage(object))
+
+print.Image <- function(x, ...) showImage(x, ...)
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+setMethod ("image", signature(x="Image"),
+  function(x, i, xlab = "", ylab = "", axes = FALSE, col = gray((0:255) / 255), useRaster = TRUE, ...) {
+    nf = numberOfFrames(x, type="total")
+    
+    if ( missing(i) ) {
+      i = 1L
+      if ( nf > 1L ) message( "Missing frame index for an image stack, assuming 'i = 1'")
+    }
+    else {
+      i = as.integer(i[1L])
+      if ( i<1L || i>nf ) stop( "Frame index out of range: 'i' must be between 1 and ", nf)
+    }
+    
+    d <- dim(x)
+    X <- 1:d[1L]
+    Y <- 1:d[2L]
+    Z <- getFrame(x, i, "total")
+    image.default(x=X, y=Y, z=Z, asp=1, col=col, axes=axes, xlab=xlab, ylab=ylab, useRaster=useRaster, ...)
+  }
+)
+
+## private function to select a channel from a Color image
+## failsafe, will return a black image if the channel doesn't exist
+selectChannel = function(x, i) {
+  if (colorMode(x)==Grayscale) stop("in 'selectChannel', color mode must be 'Color'")
+  
+  dim = dim(x)
+  y = NULL 
+  
+  if (length(dim) < 3) {
+    if (i==1) y = x
+  } 
+  else {
+    if (i <= dim[3]) y = asub(x, i, 3)
+  }
+  
+  if (is.null(y)) 
+    y = new("Image", .Data = array(0, dim[-3]), colormode = Grayscale)
+  else
+    colorMode(y) = Grayscale
+  
+  y
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+channel = function (x, mode) {
+  if (!is.array(x)) x=Image(x)
+  if (is.character(x)) x=Image(x)
+  mode=tolower(mode)
+  validObject(x)
+  if (colorMode(x)==Grayscale) {
+    return(switch(mode,
+                  rgb=rgbImage(red=x,green=x,blue=x),
+                  grey=,gray=,luminance=x,
+                  r=,red=stop('invalid conversion mode, can\'t extract the red channel from a \'Grayscale\' image'),
+                  g=,green=stop('invalid conversion mode, can\'t extract the green channel from a \'Grayscale\' image'),
+                  b=,blue=stop('invalid conversion mode, can\'t extract the blue channel from a \'Grayscale\' image'),
+                  asred=rgbImage(red=x),
+                  asgreen=rgbImage(green=x),
+                  asblue=rgbImage(blue=x),
+                  x11=array(rgb(x,x,x),dim=dim(x)),
+                  stop('invalid conversion mode')
+                  ))
+  } else {
+    return(switch(mode,
+                  rgb=x,
+                  ## Color->Grayscale conversion is done using 1/3 uniform RGB weights
+                  grey=,gray=(selectChannel(x,1)+selectChannel(x,2)+selectChannel(x,3))/3,
+                  luminance=0.2126*selectChannel(x,1)+0.7152*selectChannel(x,2)+0.0722*selectChannel(x,3),
+                  r=,red=selectChannel(x,1),
+                  g=,green=selectChannel(x,2),
+                  b=,blue=selectChannel(x,3),
+                  asred=selectChannel(x,1),
+                  asgreen=selectChannel(x,2),
+                  asblue=selectChannel(x,3),
+                  x11=array(rgb(selectChannel(x,1),selectChannel(x,2),selectChannel(x,3)),dim=dim(selectChannel(x,1))),
+                  stop('invalid conversion mode')
+                  ))
+  }
+}
+
+toRGB = function(x) {
+  channel(x, "rgb")
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+setMethod ("hist", signature(x="Image"),
+  function (x, breaks=64L, rg=range(x, na.rm=TRUE), main=paste("Image histogram:", length(x), "pixels"), xlab="Intensity", ...) {
+    if ( !is.numeric(rg) || length(rg) != 2 ) stop("'range' must be a numeric vector of length 2.")
+    if ( colorMode(x)==Color ) {
+      d3 = dim(x)[3L]
+      nc = if ( is.na(d3) ) 1L else min(d3, 3L)
+      colores = c("red", "green", "blue")[1:nc]
+      y = sapply(colores, function(m) imageData(channel(x, m)), simplify = FALSE, USE.NAMES = TRUE)
+    } else {
+      y = list(black=imageData(x))
+    }
+
+    if(length(breaks)==1L) {
+      bins = breaks
+      breaks = seq(rg[1], rg[2], length=breaks+1L)
+    } else {
+      bins = length(breaks) - 1L
+    }
+    
+    h = lapply(y, hist.default, breaks=breaks, plot=FALSE)
+    xx = vapply(h, "[[", vector(mode = "double", length = bins+1L), "breaks")
+    yy = vapply(h, "[[", vector(mode = "integer", length = bins), "counts")
+    yy = rbind(yy, yy[bins,])
+    matplot(xx, yy, type="s", lty=1L, main=main, xlab=xlab, col=names(y), ylab="counts", ...)
+    
+    if ( length(h)==1L && names(h)=="black")
+      h = h$black
+    
+    invisible(h)
+  }
+)
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+combineImages = function (x, y, ...) {
+  dx = dim(x)
+  dy = dim(y)
+  cx = colorMode(x)
+  cy = colorMode(y)
+  
+  if ( dx[1] != dy[1] || dx[2] != dy[2] ) stop("images must have the same 2D frame size to be combined")
+  if ( cx != cy ) stop("images must have the same color mode to be combined")
+  
+  ## merging along position guided by colorMode
+  along = if (cx == Color) 4L else 3L
+  
+  ## add extra dimension in case of single frame Color Images
+  if (along == 4L) {
+    if (length(dx)==2L) dim(x) = c(dx, 1)
+    if (length(dy)==2L) dim(y) = c(dy, 1)
+  }
+  
+  z = abind::abind(x, y, along = along)
+  ## don't introduce unnecessary dimnames
+  if ( is.null(dimnames(x)) && is.null(dimnames(y)) ) dimnames(z) = NULL
+  imageData(x) = z
+    
+  validObject(x)
+  return (x)
+}
+
+## general method for the superclass of 'Image' and 'matrix'
+setMethod("combine", signature("array", "array"), combineImages)
+## explicit methods for subclasses of 'array'
+setMethod("combine", signature("matrix", "matrix"), combineImages)
+setMethod("combine", signature("Image", "Image"), combineImages)
+
+## special case of combining a list of images
+setMethod("combine", signature("list", "missing"), 
+  function(x, y, ...) {
+    if (!is.null(names(x))) names(x) <- NULL
+    do.call(combine, x)
+  }
+)
+
+## useful when combining image lists containing NULL elements
+setMethod("combine", signature("ANY", "NULL"), function(x, y, ...) x)
+setMethod("combine", signature("NULL", "ANY"), function(x, y, ...) y)
+setMethod("combine", signature("NULL", "NULL"), function(x, y, ...) NULL)
+
+## Median & quantile redefinition
+## needed to overcome an isObject() test in median() which greatly slows down median()
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+median.Image <- function(x, ...) {
+  median(x at .Data, ...)
+}
+
+quantile.Image <- function(x, ...) {
+  quantile(x at .Data, ...)
+}
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+rgbImage = function(red=NULL, green=NULL, blue=NULL) {
+  classname = function(x) as.character(class(x))  
+  
+  set_check = function(prop, ch) {
+    if ( !is.null(ch) ) {
+      if ( is.null(prop) ) {
+        prop = list(dim = dim(ch),
+                    cls = classname(ch),
+                    ref = substitute(ch),
+                    dns = !is.null(dimnames(ch)))
+      }
+      else {
+        if  ( !identical(prop$dim, dim(ch)) )
+          stop('images must have the same 2D frame size and number of frames to be combined')
+        if ( !identical(prop$cls, classname(ch)) )
+          stop('images must be object of the same class in order to be combined')
+        prop$dns = prop$dns || !is.null(dimnames(ch))
+      }
+    }
+    prop
+  }
+  
+  prop = set_check(NULL, red)
+  prop = set_check(prop, green)
+  prop = set_check(prop, blue)
+
+  if (is.null(prop)) stop('at least one non-null array must be supplied')
+  
+  if (is.null(red)) red = array(0, dim=prop$dim)
+  if (is.null(green)) green = array(0, dim=prop$dim)
+  if (is.null(blue)) blue = array(0, dim=prop$dim)
+  
+  x = abind::abind(red, green, blue, along=2.5)
+  ## don't introduce unnecessary dimnames
+  if ( !prop$dns ) dimnames(x) = NULL
+  
+  if ( extends(prop$cls, "Image") ) {
+    res = eval(prop$ref)
+    imageData(res) <- x
+    colorMode(res) <- Color
+  } else {
+    res = new("Image", .Data = x, colormode = Color)
+  }
+  
+  res
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+parseColorMode = function(colormode) {
+  icolormode=NA
+
+  if (is.numeric(colormode)) {
+    icolormode=colormode
+  } else if (is.character(colormode)) {
+    icolormode=pmatch(tolower(colormode),c('grayscale', NA, 'color'), duplicates.ok=TRUE,nomatch=NA)-1
+  }
+
+  as.integer(icolormode)
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## returns the raster representation of an image (by default the first frame)
+as.raster.Image = function(x, max = 1, i = 1L, ...) {
+  y = getFrame(x, i, type = 'render')
+  y = clipImage(y, range = c(0, max))
+  y = imageData(y)
+  d = dim(y)
+  
+  ## grayscale
+  r <- if (colorMode(x) == Grayscale)
+    rgb(y, y, y, maxColorValue = max)
+  ## color
+  else {
+    if (length(d) == 2L)
+      rgb(y, 0, 0, maxColorValue = max)
+    else
+      switch(min(d[3L], 4L),
+             rgb(y[,,1L], 0, 0, maxColorValue = max),
+             rgb(y[,,1L], y[,,2L], 0, maxColorValue = max),
+             rgb(y[,,1L], y[,,2L], y[,,3L], maxColorValue = max),
+             rgb(y[,,1L], y[,,2L], y[,,3L], y[,,4L], maxColorValue = max)
+      )
+  }
+  
+  attributes(r) <- list(dim = d[2:1], class = "raster")
+  r
+}
+
diff --git a/R/abind.R b/R/abind.R
new file mode 100644
index 0000000..2ae8b2c
--- /dev/null
+++ b/R/abind.R
@@ -0,0 +1,55 @@
+# defined outside of `setMethod` in order to subsequently call `formals` on it
+.abind.Image <- function(...) {
+  arglist = list(...)
+  
+  if (is.list(arglist[[1L]]))
+    arglist <- arglist[[1L]]
+  
+  x <- arglist[[1L]]
+  
+  cm = colorMode(x)
+  
+  if (!all(cm==vapply(arglist, colorMode, integer(1L), USE.NAMES=FALSE)))
+    stop("images have different color modes")
+  
+  y <- callNextMethod()
+  
+  ## don't introduce unnecessary dimnames
+  if (all(vapply(arglist, function(x) is.null(dimnames(x)), logical(1L), USE.NAMES=FALSE)))
+    dimnames(y) <- NULL
+  
+  ## use first object as template which might not be correct for Image subclasses
+  imageData(x) <- y
+  
+  x
+}
+
+
+## neccesary for performing correct dispatch on object lists
+.abind.list <- function(...) {
+  cl <- match.call(expand.dots = FALSE)
+  
+  ## only a single list-valued argument for ... is supported
+  if (length(cl$`...`)!=1L) {
+    callNextMethod()
+  }
+  else {
+    dots <- list(...)[[1L]]
+    
+    cl$`...` <- quote(dots)
+  
+    ## choose the apropriate method based on object classes
+    cls <- unique(unlist(lapply(dots, class)))
+    cl[[1L]] <- quote(selectMethod(abind, cls))
+  
+    eval(cl)
+  }
+}
+
+
+# have the same formal arguments as the original function
+formals(.abind.Image) <- formals(abind::abind)
+formals(.abind.list) <- formals(abind::abind)
+
+setMethod("abind", "Image", .abind.Image)
+setMethod("abind", "list", .abind.list)
diff --git a/R/clahe.R b/R/clahe.R
new file mode 100644
index 0000000..3c7a0b2
--- /dev/null
+++ b/R/clahe.R
@@ -0,0 +1,4 @@
+clahe = function(x, nx = 8, ny = nx, bins = 256, limit = 2, keep.range = FALSE) {
+  validImage(x)
+  .Call(C_clahe, castImage(x), as.integer(nx), as.integer(ny), as.integer(bins), as.double(limit), isTRUE(keep.range))
+}
diff --git a/R/colormap.R b/R/colormap.R
new file mode 100644
index 0000000..f12a91b
--- /dev/null
+++ b/R/colormap.R
@@ -0,0 +1,19 @@
+colormap <- function(x, palette = heat.colors(256L)) {
+  validImage(x)
+  if (colorMode(x) != Grayscale) 
+    stop("Color mapping can be applied only to grayscale images")
+  
+  tmp <- round(1 + x * (length(palette)-1L))
+  tmp <- Image(array(palette[tmp], dim(tmp)))
+  
+  if ( is.Image(x) ) {
+    res <- x
+    imageData(res) <- tmp
+    colorMode(res) <- Color
+  }
+  else {
+    res <- tmp
+  }
+  
+  res
+}
diff --git a/R/computeFeatures.R b/R/computeFeatures.R
new file mode 100644
index 0000000..892f34e
--- /dev/null
+++ b/R/computeFeatures.R
@@ -0,0 +1,362 @@
+## tests
+# nocov start
+tests = function() {
+
+  ## build labeled image x and reference y
+  y = readImage(system.file("images", "nuclei.tif", package="EBImage"))[,,1]
+  x = thresh(y, 10, 10, 0.05)
+  x = opening(x, makeBrush(5, shape='disc'))
+  x = bwlabel(x)
+  display(x)
+  display(y)
+
+  ## standard call 88 features
+  ft = computeFeatures(x, y)
+  pft = computeFeatures(x, y, properties=TRUE)
+  stopifnot(all(colnames(ft)==pft$name))
+  
+  ## call without expandRef 49 features
+  ft = computeFeatures(x, y, expandRef=NULL)
+  pft = computeFeatures(x, y, expandRef=NULL, properties=TRUE)
+  stopifnot(all(colnames(ft)==pft$name))
+  
+  ## changing parameters
+  ft = computeFeatures(x, y, basic.quantiles=c(0.2, 0.3), haralick.scales=c(1, 4, 8), expandRef=NULL)
+  pft = computeFeatures(x, y, basic.quantiles=c(0.2, 0.3), haralick.scales=c(1, 4, 8), expandRef=NULL, properties=TRUE)
+  stopifnot(all(colnames(ft)==pft$name))
+  
+  ## call with 3 reference images
+  yc = list(d=y, t=flip(y), a=flop(y))
+  ft = computeFeatures(x, yc)
+  pft = computeFeatures(x, yc, properties=TRUE)
+  stopifnot(all(colnames(ft)==pft$name))
+  
+  ## test standardExpandRef
+  z = standardExpandRef(yc)
+  #str(z)
+  display(combine(z))
+  
+  ## image with one point
+  x2 = array(0, dim=dim(x))
+  x2[100, 100] = 1
+  ft = computeFeatures(x2, y)
+}
+# nocov end
+
+## main function
+computeFeatures = function(x, ref, methods.noref=c("computeFeatures.moment", "computeFeatures.shape"),
+  methods.ref=c("computeFeatures.basic", "computeFeatures.moment", "computeFeatures.haralick"),
+  xname="x", refnames, properties=FALSE, expandRef=standardExpandRef, ...) {
+  ## check arguments
+  x = checkx(x)
+  ref = convertRef(ref, refnames)
+  refnames = names(ref)
+  nref = length(ref)
+ 
+  ## expand ref
+  if (!is.null(expandRef)) {
+    ref = expandRef(ref, refnames)
+    refnames = names(ref)
+    nref = length(ref)
+  }
+  
+  ## compute features
+  if ( !isTRUE(properties) ) {
+    ## prepare data
+    xs = splitObjects(x)
+    if (length(xs)==0) return(NULL)
+    
+    ## compute features without reference
+    features.noref = do.call(cbind, lapply(methods.noref, do.call, list(x=x, properties=FALSE, xs=xs, ...)))
+    
+    ## compute features with reference, for each channel
+    features.ref = lapply(1:nref, function(i) {
+      do.call(cbind, lapply(methods.ref, do.call, list(x=x, ref=ref[[i]], properties=FALSE, xs=xs, ...)))
+    })
+    names(features.ref) = refnames
+
+    ## sum up data into a single matrix
+    features = c("0"=list(features.noref), features.ref)
+    features = features[!sapply(features, is.null)]
+    for (i in 1:length(features)) colnames(features[[i]]) = paste(names(features)[i], colnames(features[[i]]), sep=".")
+    features = do.call(cbind, features)
+    colnames(features) = paste(xname, colnames(features), sep=".")
+    features
+  } else {
+    ## feature properties
+    pfeatures.noref = do.call(rbind, lapply(methods.noref, do.call, list(properties=TRUE, ...)))
+    pfeatures.ref = do.call(rbind, lapply(methods.ref, do.call, list(properties=TRUE, ...)))
+    pfeatures.ref = lapply(1:length(refnames), function(z) pfeatures.ref)
+    names(pfeatures.ref) = refnames
+
+    ## sum up data
+    pfeatures = c("0"=list(pfeatures.noref), pfeatures.ref)
+    pfeatures = pfeatures[!sapply(pfeatures, is.null)]
+    for (i in 1:length(pfeatures)) pfeatures[[i]]$name = paste(names(pfeatures)[i], pfeatures[[i]]$name, sep=".")
+    pfeatures = do.call(rbind, pfeatures)
+    pfeatures$name = paste(xname, pfeatures$name, sep=".")
+    rownames(pfeatures) = NULL
+    pfeatures
+  }
+}
+
+## standard reference expansion
+standardExpandRef = function(ref, refnames, filter = gblob()) {
+  ## check arguments
+  ref = convertRef(ref, refnames)
+  nref = length(ref)
+  refnames = names(ref)
+    
+  ## adding joint channels
+  if (nref>1) {
+    for (i in 1:(nref-1)) {
+      for (j in (i+1):nref) {
+        refi = ref[[i]]
+        refj = ref[[j]]
+        jref0 = list( (refi - mean(refi)) * (refj - mean(refj)) )
+        names(jref0) = paste0(refnames[i], refnames[j])
+        ref = c(ref, jref0)
+      }
+    }
+  }
+
+  ## adding granulometry by blob transform
+  bref = lapply(ref, function(r) filter2(r, filter)/2)
+  names(bref) = paste0("B", names(ref))
+  c(ref, bref)
+}
+
+## basic pixel-independant statistics
+computeFeatures.basic = function(x, ref, properties=FALSE, basic.quantiles=c(0.01, 0.05, 0.5, 0.95, 0.99), xs, ...) {
+  qnames = paste0('b.q', gsub('\\.', '', as.character(basic.quantiles)))
+  if ( !isTRUE(properties) ) {
+    ## check arguments
+    x = checkx(x)
+    if (missing(xs)) xs = splitObjects(x)
+    if (length(xs)==0) return(NULL)
+    ref = convertRef(ref)[[1]]
+    
+    ## compute features
+    features = do.call(rbind, lapply(xs, function(z) {
+      z = ref[z]
+      q = quantile(z, basic.quantiles)
+      names(q) = qnames
+      c(b.mean=mean(z), b.sd=sd(z), b.mad=mad(z), q)
+    }))
+
+    ## special processing for single points
+    z = sapply(xs, length)==1
+    features[,'b.sd'] = ifelse(z, 0, features[,'b.sd'])
+    features
+  }
+  else {
+    ## feature properties
+    data.frame(name=c("b.mean", "b.sd", "b.mad", qnames),
+               translation.invariant=TRUE,
+               rotation.invariant=TRUE)
+  }
+}
+
+## shape features
+computeFeatures.shape = function(x, properties=FALSE, xs, ...) {
+  if ( !isTRUE(properties) ) {
+    ## check arguments
+    x = checkx(x)
+    if (missing(xs)) xs = splitObjects(x)
+    if (length(xs)==0) return(NULL)
+    
+    contours = ocontour(x)
+    ## compute features
+    features = do.call(rbind, lapply(contours, function(z) {
+      cz = apply(z, 2, mean)
+      radius = sqrt(rowSums((z - rep(cz, each=nrow(z)))^2))
+      radius.mean = mean(radius)
+      radius.sd = sqrt(mean((radius - radius.mean)^2))
+      
+      c(s.perimeter=nrow(z), s.radius.mean=radius.mean, s.radius.sd=radius.sd, s.radius.min=min(radius),
+        s.radius.max=max(radius))
+    }))
+    cbind(s.area=sapply(xs, length), features)
+  } else {
+    ## feature properties
+    data.frame(name=c("s.area", "s.perimeter", "s.radius.mean", "s.radius.sd", "s.radius.min", "s.radius.max"),
+               translation.invariant = c(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE),
+               rotation.invariant = c(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE))
+  }
+}
+
+## image moments
+computeFeatures.moment = function(x, ref, properties=FALSE, xs, ...) {
+  if ( !isTRUE(properties) ) {
+    ## check arguments
+    x = checkx(x)
+    if (missing(xs)) xs = splitObjects(x)
+    if (length(xs)==0) return(NULL)
+    if (missing(ref)) ref = array(1, dim=dim(x))
+    ref = convertRef(ref)[[1]]
+    
+    ## image moments: computing m{pq} = sum_{x^p*y^q*f(x, y)}
+    m00 = sapply(xs, function(z) sum(ref[z]))
+    m10 = {w = row(ref)*ref ; sapply(xs, function(z) sum(w[z]))}
+    m01 = {w = col(ref)*ref ; sapply(xs, function(z) sum(w[z]))}
+    m20 = {w = row(ref)^2*ref ; sapply(xs, function(z) sum(w[z]))}
+    m02 = {w = col(ref)^2*ref ; sapply(xs, function(z) sum(w[z]))}
+    m11 = {w = col(ref)*row(ref)*ref ; sapply(xs, function(z) sum(w[z]))}
+    
+    ## derive interesting geometric quantities from image moments
+    ## done by diagonalisation of the centered image moment matrix
+    ## see http://en.wikipedia.org/wiki/Image_moment
+    suppressWarnings({
+      cx = m10/m00 ## center of mass x
+      cy = m01/m00 ## center of mass y
+      mu20 = m20/m00 - cx^2 ## temporary quantity
+      mu02 = m02/m00 - cy^2 ## temporary quantity
+      mu11 = m11/m00 - cx*cy ## temporary quantity
+      det = sqrt(4*mu11^2 + (mu20 - mu02)^2) ## temporary quantity
+      theta = atan2(2*mu11, (mu20 - mu02))/2 ## angle
+      l1 = sqrt((mu20 + mu02 + det)/2)*4 ## major axis
+      l2 = sqrt((mu20 + mu02 - det)/2)*4 ## minor axis
+      eccentricity = sqrt(1-l2^2/l1^2) ## eccentricity
+    })
+
+    ## special processing for single points
+    features = cbind(m.cx=cx, m.cy=cy, m.majoraxis=l1, m.eccentricity=eccentricity, m.theta=theta)
+    features[is.na(features) | is.nan(features)] = 0
+    features
+  } else {
+    ## feature properties
+    data.frame(name=c("m.cx", "m.cy", "m.majoraxis", "m.eccentricity", "m.theta"),
+      translation.invariant = c(FALSE, FALSE, TRUE, TRUE, TRUE),
+      rotation.invariant = c(TRUE, TRUE, TRUE, TRUE, FALSE))
+  }
+}
+
+## haralick features
+## h.*: haralick features
+computeFeatures.haralick = function(x, ref, properties=FALSE, haralick.nbins=32, haralick.scales=c(1, 2), xs, ...) {
+  snames = paste0("s", haralick.scales)
+  if ( !isTRUE(properties) ) {
+    ## check arguments
+    x = checkx(x)
+    if (missing(xs)) xs = splitObjects(x)
+    if (length(xs)==0) return(NULL)
+    ref = convertRef(ref)[[1]]
+  
+    ## clip data
+    ref[ref>1] = 1
+    ref[ref<0] = 0
+    features = lapply(haralick.scales, function(scale) {
+      if (scale>1) {
+        combx = seq(1, nrow(x), by=scale)
+        comby = seq(1, ncol(x), by=scale)
+        xscaled = x[combx, comby]
+        refscaled = ref[combx, comby]
+      } else {
+        xscaled = x
+        refscaled = ref
+      }
+      hf = .haralickFeatures(xscaled, refscaled, nc=haralick.nbins)
+      ## after scaling, xscaled may not contain the original object labels
+      ## fortunately, .haralickFeatures() leave 0-row for them, therefore, the data
+      ## is kept synceda and it is enough to fill up hf with 0-row to level up to
+      ## the original number of objects
+      rbind(hf, matrix(0, nrow=max(x)-max(xscaled), ncol=ncol(hf)))
+    })
+    for (i in 1:length(features)) colnames(features[[i]]) = paste(colnames(features[[i]]), snames[i], sep=".")
+    do.call(cbind, features)
+  } else {
+    ## feature properties
+    hnames = paste("h", c("asm", "con", "cor", "var", "idm", "sav", "sva", "sen", "ent", "dva", "den", "f12", "f13"), sep=".")
+    names = paste(hnames, rep(snames, each=length(hnames)), sep=".")
+    data.frame(name=names, translation.invariant=TRUE, rotation.invariant=TRUE)
+  }
+}
+
+## make a blob
+gblob = function(x0=15, n=49, alpha=0.8, beta=1.2) {
+  xx = seq(-x0, x0, length.out=n)
+  xx = matrix(xx, nrow=length(xx), ncol=length(xx))
+  xx = sqrt(xx^2+t(xx)^2)
+  z = dnorm(xx, mean=0, sd=alpha) - 0.65*dnorm(xx, mean=0, sd=beta)
+  z/sum(z)
+}
+
+## convert ref into a list of images, for fast processing
+convertRef = function(ref, refnames) {
+  if (!is.array(ref) && !is.list(ref)) stop("'ref' must be an array or a list containing the reference images")
+  if (is.array(ref)) {
+    nref = numberOfFrames(ref, 'total')
+    if (class(ref)=="Image") ref = imageData(ref)
+    ndim = length(dim(ref))
+    if (ndim==2) ref = list(ref)
+    else if (ndim==3) ref = lapply(1:nref, function(i) ref[,,i])
+    else stop ("'ref' must be a 2D or 3D array")
+  }
+  else if (is.list(ref)) {
+    nref = length(ref)
+    if (missing(refnames) && !is.null(names(ref))) refnames = names(ref)
+    ## sanity check
+    ref = lapply(ref, function(r) {
+      ndim = length(dim(r))
+      if (class(r)=="Image") r = imageData(r)
+      if (ndim<2 || ndim>3) stop ("'ref' must contain only 2D arrays")
+      else if (ndim==3) {
+        if (dim(r)[3]>1) stop ("'ref' must contain only 2D arrays")
+        else r = r[,,1]
+      }
+      r
+    })
+  }
+  if (missing(refnames)) refnames = letters[1:nref]
+  if (length(refnames)!=nref) stop ("'refnames' must have the same length as 'ref'")
+  names(ref) = refnames
+  ref
+}
+
+## check x
+checkx = function(x) {
+  if (!is.array(x))  stop("'x' must be a 2D array")
+  if (class(x)=="Image") x = imageData(x)
+  ndim = length(dim(x))
+  if (ndim<2||ndim>3)  stop("'x' must be a 2D array")
+  if (ndim==3) {
+    if (dim(x)[3]>1) stop("'x' must be a 2D array")
+    else x = x[,,1]
+  }
+  x
+}
+
+## split an labelled image into list of points
+splitObjects = function(x) {
+  z = which(as.integer(x)>0L)
+  split(z, x[z])
+}
+
+.haralickMatrix <- function(x, ref, nc=32) {
+  checkCompatibleImages(x, ref)
+  rref <- range.default(ref)
+  if ( rref[1] < 0 || rref[2] > 1 ) {
+    ref[ref<0] = 0
+    ref[ref>1] = 1
+    warning( "Values in 'ref' have been limited to the range [0,1]" )
+  }
+  
+  res <- .Call(C_haralickMatrix, castImage(x), castImage(ref), as.integer(nc))
+  return( res )
+}
+
+.haralickFeatures <- function(x, ref, nc=32) {
+  validImage(x)
+  hm <- .haralickMatrix(x=x, ref=ref, nc=nc)
+  if ( is.null(hm) || !(is.array(hm) || is.list(hm)) ) return( NULL )
+  do.features <- function(m) {
+    if (dim(m)[3]>0) res <- .Call(C_haralickFeatures, m)
+    else res = matrix(0, nrow=0, ncol=13) ## no objects
+    if ( is.matrix(res) )
+      colnames(res) <- c("h.asm", "h.con", "h.cor", "h.var", "h.idm", "h.sav", "h.sva", 
+                         "h.sen", "h.ent", "h.dva", "h.den", "h.f12", "h.f13")
+    res
+  }
+  if ( !is.list(hm) ) return( do.features(hm) )
+  lapply( hm, do.features )
+} 
diff --git a/R/display.R b/R/display.R
new file mode 100644
index 0000000..9a5d30e
--- /dev/null
+++ b/R/display.R
@@ -0,0 +1,275 @@
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+display = function(x, method, ...) {
+  validImage(x)
+  
+  if ( missing(method) )
+    method = getOption("EBImage.display", if ( interactiveMode() ) "browser" else "raster")
+  method = match.arg(method, c("browser", "raster"))
+  
+  switch(method,
+         browser = displayWidget(x, ...),
+         raster  = displayRaster(x, ...)
+  ) 
+}
+
+## test whether run interactively and not in `rmarkdown::render`
+## https://github.com/yihui/knitr/issues/926#issuecomment-68503962
+interactiveMode = function() interactive() && !isTRUE(getOption('knitr.in.progress', FALSE))
+
+## remove any args to fun
+filterDotsArgs = function(fun, ...) {
+  dotsArgs <- list(...)
+  dotsArgs[which(names(dotsArgs) %in% names(formals(fun)))] <- NULL
+  dotsArgs
+}
+
+## displays an image using R graphics
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+displayRaster = function(image, frame, all = FALSE, drawGrid = isTRUE(spacing==0),
+                         nx, spacing = 0, margin = 0, interpolate = TRUE, ...) {
+  ## remove any args to "browser" method
+  dotsArgs <- filterDotsArgs(displayWidget, ...)
+  
+  all = isTRUE(all)
+  nf = numberOfFrames(image, type="render")
+  
+  ## display all frames in a grid-like environment
+  if ( all ) {
+    if ( missing(nx) )
+      ncol = ceiling(sqrt(nf))
+    else {
+      ncol = as.integer(nx[1L])
+      if ( ncol==0L ) stop( "'nx' must be coercible to a non-zero integer" )
+    }
+    
+    ## negative values are interpreted as number of rows
+    if ( ncol<0 ) {
+      nrow = -ncol
+      ncol = ceiling(nf/nrow)
+    } else {
+      nrow = ceiling(nf/ncol)
+    }
+  }
+  ## display a single frame only
+  else {
+    if ( missing(frame) ) {
+      frame = 1L
+      if ( nf > 1L ) message("Only the first frame of the image stack is displayed.\nTo display all frames use 'all = TRUE'.")
+    }
+    else {
+      frame = as.integer(frame[1L])
+      if ( frame<1L || frame>nf ) stop( "Frame index out of range: 'frame' must be between 1 and ", nf)
+    }
+    ncol = nrow = 1L
+  }
+  
+  d <- dim(image)[1:2]
+  xdim <- d[1L]
+  ydim <- d[2L]
+  
+  ## returns a pair of horizontal and vertical pixel dimensions
+  asPixelDims <- function(x, d) {
+    x <- as.numeric(x)
+    x[ is.na(x) | x < 0] <- 0L
+    
+    ## values smaller than one are interpreted as fractions of image dimensions
+    frac = which( x>0 & x<1 )
+    x[frac] <- (d*x)[frac]
+    
+    ## single fraction is taken against the maximum dimension
+    if ( identical(frac, 1L) )
+      x = x * max(d)/d
+    
+    ## round to integer pixel values
+    x = as.integer(round(x))
+    x = rep(x, length.out=2L)
+  }
+  
+  spacing <- asPixelDims(spacing, d)
+  margin  <- asPixelDims(margin, d)
+  
+  ## draw grid unless spacing is set
+  if ( missing(drawGrid) )
+    drawGrid = if ( any(spacing)>0 ) FALSE else TRUE
+  
+  xmar <- margin[1L]
+  ymar <- margin[2L]
+  
+  xsep <- spacing[1L]
+  ysep <- spacing[2L]
+  
+  xran = c(0, ncol*xdim + (ncol-1)*xsep) + .5
+  yran = c(0, nrow*ydim + (nrow-1)*ysep) + .5
+  
+  xranm = xran + c(-xmar, xmar)
+  yranm = yran + c(-ymar, ymar)
+  
+  ## set graphical parameters
+  user <- do.call(par, c(list(bty="n", mai=c(0,0,0,0), xaxs="i", yaxs="i", xaxt="n", yaxt="n", col = "white"), dotsArgs))
+  on.exit(par(user))
+  plot(xranm, yranm, type="n", xlab="", ylab="", asp=1, ylim=rev(yranm))
+      
+  for(r in seq_len(nrow)) {
+    for(c in seq_len(ncol)) {
+      f = if(all) (r-1)*ncol + c else frame
+      if ( f <= nf )
+        rasterImage(as.nativeRaster(getFrame(image, f, type="render")),
+                    (c-1)*(xdim+xsep) + .5,
+                    r*(ydim+ysep)-ysep + .5,
+                    c*(xdim+xsep)-xsep + .5,
+                    (r-1)*(ydim+ysep) +.5,
+                    interpolate = interpolate)
+      else
+        break
+    }
+  }    
+  
+  ## draw grid lines
+  if ( isTRUE(drawGrid) && all && nf>1 ) {
+    clip(xran[1L], xran[2L], yran[1L], yran[2L])
+    abline(h = seq_len(nrow-1)*(ydim + ysep) - ysep/2 + .5,
+           v = seq_len(ncol-1)*(xdim + xsep) - xsep/2 + .5)
+  }
+  
+  invisible()
+}
+
+## displays an image using JavaScript
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+displayInBrowser = function(x, title, ...) {
+  if ( missing(title) )
+    title = deparse(substitute(x, parent.frame()), width.cutoff = 500L, nlines = 1L)
+  
+  ## template and script files
+  templateFile = system.file("viewer","display.template", package = "EBImage")
+  cssFile = system.file("viewer","display.css", package = "EBImage")
+  scriptFile = system.file("viewer","viewer.js", package = "EBImage")
+  tempDir = tempfile("",,"")
+  htmlFile = "display.html"
+  imageFile = tempfile("",tempDir,".png")
+
+  if(!dir.create(tempDir))
+    stop("Error creating temporary directory.")
+
+  ## read the template file
+  f = file(templateFile, "r")
+  a = readLines(f)
+  close(f)
+
+  ## get image parameters
+  d = dim(x)
+  if ( length(d)==2L ) d = c(d, 1L)
+  nf = numberOfFrames(x, "render")
+
+  ## fill-in in the template
+  a = sub("HEIGHT",d[2L], sub("WIDTH",d[1L], sub("FRAMES",nf, sub("IMAGE",basename(imageFile), sub("TITLE", title, a)))))
+
+  ## temporarily switch to tempdir and write the files
+  wd = setwd(tempDir)
+  
+  ## fill missing channels
+  if ( isTRUE(colorMode(x) == Color && d[3L] < 3L) ) {
+    fd = d
+    fd[3L] = 3L - d[3L]
+    imageData(x) = abind::abind(x, array(0, fd), along = 3L)
+  }
+  
+  writeImage(x, imageFile)
+  cat(a, file=htmlFile, sep="\n")
+  file.copy(scriptFile, tempDir)
+  file.copy(cssFile, tempDir)
+  setwd(wd)
+
+  ## create browser query
+  query = paste0("file://", normalizePath(file.path(tempDir,"display.html")))
+
+  browseURL(query)
+}
+
+plot.Image = function(x, ...) displayRaster(x, ...)
+
+as.nativeRaster = function(x) .Call(C_nativeRaster, castImage(x))
+
+## Display Widget
+
+displayWidget <- function(x, title, embed = !interactiveMode(), tempDir = tempfile(""), ...) {
+  ## remove any args to "raster" method
+  dotsArgs <- filterDotsArgs(displayRaster, ...)
+
+  ## get image parameters
+  d = dim(x)
+  if ( length(d)==2L ) d = c(d, 1L)
+  
+  ## fill missing channels
+  if ( isTRUE(colorMode(x) == Color && d[3L] < 3L) ) {
+    fd = d
+    fd[3L] = 3L - d[3L]
+    imageData(x) = abind::abind(x, array(0, fd), along = 3L)
+  }
+  
+  x = clipImage(x) ## clip the image and change storage mode to double
+  x = transpose(x)
+  
+  frames = seq_len(numberOfFrames(x, type='render'))
+  dependencies = NULL
+  
+  if ( isTRUE(embed) ) {
+    
+    data <- sapply(frames, function(i) base64Encode(writePNG(getFrame(x, i, 'render'))))
+    data <- sprintf("data:image/png;base64,%s", data)
+    
+  } else {
+    if ( !dir.exists(tempDir) && !dir.create(tempDir, recursive=TRUE) )
+        stop("Error creating temporary directory.")
+    
+    files = file.path(tempDir, sprintf("frame%.3d.png", frames, ".png"))
+    
+    ## store image frames into individual files
+    for (i in frames)
+      writePNG(getFrame(x, i, 'render'), files[i])
+    
+    dependencies = htmlDependency(
+      name = basename(tempDir),
+      version = "0",
+      src = list(tempDir)
+    )
+    
+    filePath = file.path(sprintf("%s-%s", dependencies$name, dependencies$version), basename(files))
+    
+    ## set libdir unless run in shiny
+
+    if ( !isNamespaceLoaded("shiny") || is.null(shiny::getDefaultReactiveDomain()))
+      filePath = file.path("lib", filePath)
+    
+    data = filePath
+  }
+  
+  # widget options
+  opts = list(
+    data = data,
+    width = d[1L],
+    height = d[2L]
+  )
+  
+  # create widget
+  do.call(createWidget, c(list(
+    name = 'displayWidget',
+    package = 'EBImage',
+    x = opts,
+    sizingPolicy = sizingPolicy(padding = 0, browser.fill = TRUE),
+    dependencies = dependencies),
+    dotsArgs)
+  )
+}
+
+## Shiny bindings for displayWidget
+
+displayOutput <- function(outputId, width = '100%', height = '500px'){
+  htmlwidgets::shinyWidgetOutput(outputId, 'displayWidget', width, height, package = 'EBImage')
+}
+
+renderDisplay <- function(expr, env = parent.frame(), quoted = FALSE) {
+  if (!quoted) { expr <- substitute(expr) } # force quoted
+  htmlwidgets::shinyRenderWidget(expr, displayOutput, env, quoted = TRUE)
+}
+
diff --git a/R/drawCircle.R b/R/drawCircle.R
new file mode 100644
index 0000000..a7d4130
--- /dev/null
+++ b/R/drawCircle.R
@@ -0,0 +1,28 @@
+drawCircle <- function(img, x, y, radius, col, fill=FALSE, z=1) {
+  validImage(img)
+  if ( any(is.na(img)) ) stop("'img' shouldn't contain any NAs")
+
+  toScalarInteger = function(x) suppressWarnings(as.integer(x)[1L])
+  
+  ## check whether parameters are OK
+  r = toScalarInteger(radius)
+  if ( !isTRUE(r>0L) ) stop("'radius' must be a positive integer")
+  
+  z = toScalarInteger(z)
+  if ( !isTRUE(z>0L) || isTRUE(z>numberOfFrames(img, 'render')) ) stop("'z' must be a positive integer lower than the number of image frames")
+  
+  xy = c(toScalarInteger(x), toScalarInteger(y))
+  if ( any(is.na(xy)) ) stop("'x', 'y' must be numeric scalars")
+  
+  fill = suppressWarnings(as.integer(isTRUE(as.logical(fill)[1L])))
+  
+  if (colorMode(img)==Color) {
+    rgb = as.numeric(col2rgb(col)/255)
+    if (length(rgb)!=3 || any(is.na(rgb))) stop("In Color mode, 'col' must be a valid color")
+  } else {
+    rgb = as.numeric(c(col, 0, 0))
+    if (length(rgb)!=3 || any(is.na(rgb))) stop("In Grayscale mode, 'col' must be a scalar value")
+  }
+  
+  invisible(.Call(C_drawCircle, castImage(img), c(xy-1L, z-1L, r), rgb, fill))
+}
diff --git a/R/equalize.R b/R/equalize.R
new file mode 100644
index 0000000..5e4badf
--- /dev/null
+++ b/R/equalize.R
@@ -0,0 +1,42 @@
+
+equalize <- function(x, range = c(0, 1), levels = 256){
+  validImage(x)
+  r = range(x)
+  if ( r[1L] == r[2L] ) {
+    warning("Image histogram is single-valued and cannot be equalized.")
+  }
+  else {
+    if ( !is.numeric(range) || length(range) != 2L ) stop("'range' must be a numeric vector of length 2.")
+    levels = as.integer(levels)
+    if ( is.na(levels) || levels < 1L ) stop("Levels must be at least equal 1.")
+    breaks = seq(range[1L], range[2L], length.out = levels + 1L)
+    x = castImage(x)  # converts logical to numeric
+    d = dim(x)
+    n = d[1L] * d[2L]
+    
+    # equalize each frame separately
+    .equalize = function(y) {
+      h = hist.default(y, breaks = breaks, plot = FALSE)
+      cdf = cumsum(h$counts)
+      cdf_min = min(cdf[cdf>0])
+      
+      equalized = ( (cdf - cdf_min) / (prod(dim(y)) - cdf_min) * (range[2L] - range[1L]) ) + range[1L]
+      bins = round( (y - range[1L]) / (range[2L] - range[1L]) * (levels-1L) ) + 1L
+      
+      res = equalized[bins]
+    }
+    
+    res = if ( (ld = length(d)) > 2L ) apply(x, 3L:ld, .equalize)
+    else .equalize(x)
+    
+    # setup output image
+    dim(res) = d
+    imageData(x) = res
+  }
+  x
+}
+
+clahe = function(x, nx = 8, ny = nx, bins = 256, limit = 2, keep.range = FALSE) {
+  validImage(x)
+  .Call(C_clahe, castImage(x), as.integer(nx), as.integer(ny), as.integer(bins), as.double(limit), isTRUE(keep.range))
+}
diff --git a/R/filter2.R b/R/filter2.R
new file mode 100644
index 0000000..49ff65c
--- /dev/null
+++ b/R/filter2.R
@@ -0,0 +1,125 @@
+# 2D convolution-based linear filter for images and matrix data
+
+# Copyright (c) 2007-2015, Andrzej Oleś, Gregoire Pau, Oleg Sklyar
+
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation; either version 2.1
+# of the License, or (at your option) any later version.
+
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+# See the GNU Lesser General Public License for more details.
+# LGPL license wording: http://www.gnu.org/licenses/lgpl.html
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+filter2 = function(x, filter, boundary = c("circular", "replicate")) {
+  if ( is.numeric(boundary) ) {
+    val = boundary
+    boundary = "linear"
+  }
+  else
+    boundary = match.arg(boundary)
+  
+  validObject(x)
+  validObject(filter)
+  
+  d = dx = dim(x)
+  df = dim(filter)
+  dnames = dimnames(x)
+  
+  ## do not enforce odd filter dimensions in the special case when filter size
+  ## equals image size
+  if ( any(dx[1:2]!=df) || boundary!="circular" ) {
+    if (any(df%%2==0)) stop("dimensions of 'filter' matrix must be odd")
+    if (any(dx[1:2]<df)) stop("'filter' dimensions cannot exceed dimensions of 'x'")
+  }
+  
+  cf = df%/%2
+  of = df%%2
+  
+  res = x
+  
+  switch(boundary,
+         ## default mode just wraps around edges
+         circular = {
+           x = imageData(x)
+         },
+         ## pad with a given value
+         linear = {
+           dx[1:2] = dx[1:2] + cf[1:2]
+           
+           if ( length(dx)>2 && length(val)==prod(dx[-(1:2)]) ) {
+             # Higher dim array with matching linear boundry values
+             xpad = array(rep(val, each=prod(dx[1:2])), dim = dx)
+           } else {
+             if ( length(val)>1 ) {
+               warning('The boundary value length does not match the number of frames, only the first element of boundary will be used')
+             }
+             xpad = array(val[1], dx)
+           }
+           # The do.call and arguments as a list of dimensions is to take are of arrays of unknown dimension
+           x = do.call("[<-", c(quote(xpad), lapply(d, function(x) enquote(1:x)), quote(x)) )
+         },
+         replicate = {
+           x = imageData(x)
+           
+           dx[1:2] = dx[1:2] + df[1:2] - 1L
+           
+           rep.dim <- function(x, dim, index, times) {
+             xs <- asub(x, idx=index, dims=dim, drop=FALSE)
+             ds <- dim(xs)
+             ds[dim] <- times
+             if ( dim==2L ) {
+               xs <- split(xs, ceiling(seq_along(xs)/ds[1L]))
+               xs <- sapply(xs, FUN=rep, times=times)
+             } else {
+               xs <- rep(xs, each=times)
+             }
+             array(xs, dim=ds)
+           }
+           
+           # Add left and right colums
+           lc <- rep.dim(x, 2L, 1L, cf[2L])
+           rc <- rep.dim(x, 2L, d[2L], cf[2L])
+           x <- abind::abind(x, rc, lc, along=2L)
+           # Add top and bottom rows
+           tr <- rep.dim(x, 1L, 1L, cf[1L])
+           br <- rep.dim(x, 1L, d[1L], cf[1L])
+           x <- abind::abind(x, br, tr, along=1L)
+         }
+  )
+  
+  ## create fft filter matrix
+  wf = matrix(0.0, nrow=dx[1L], ncol=dx[2L])
+  
+  wf[c(if (cf[1L]>0L) (dx[1L]-cf[1L]+1L):dx[1L] else NULL, 1L:(cf[1L]+of[1L])), 
+     c(if (cf[2L]>0L) (dx[2L]-cf[2L]+1L):dx[2L] else NULL, 1L:(cf[2L]+of[2L]))] = filter
+  
+  wf = fftw2d(wf)
+  
+  pdx = prod(dx[1:2])
+  
+  .filter = function(xx) Re(fftw2d(fftw2d(xx)*wf, inverse=1)/pdx)
+  
+  ## convert to a frame-based 3D array
+  if ( length(dx) > 2L ) {    
+    y = apply(x, 3:length(dx), .filter)
+    dim(y) = dx
+  }
+  else {
+    y = .filter(x)
+  }
+  
+  if ( boundary!="circular" ) {
+    y = asub(y, list(1:d[1L], 1:d[2L]), 1:2)
+  }
+  
+  dimnames(y) = dnames
+  
+  imageData(res) <- y
+  res
+}
diff --git a/R/floodFill.R b/R/floodFill.R
new file mode 100644
index 0000000..6d54f5f
--- /dev/null
+++ b/R/floodFill.R
@@ -0,0 +1,67 @@
+# Flood fill for images and matrices
+
+# Copyright (c) 2005 Oleg Sklyar
+
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation; either version 2.1
+# of the License, or (at your option) any later version.
+
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+# See the GNU Lesser General Public License for more details.
+# LGPL license wording: http://www.gnu.org/licenses/lgpl.html
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+floodFill = function(x, pts, col, tolerance=0) {
+  validImage(x)
+  nf = numberOfFrames(x, 'render')
+  nc = numberOfChannels(x)
+  
+  ## make sure that `pt` and `col` are lists of length matching the number of frames
+  format_as_list = function(x, nf) {
+    if ( is.list(x) )
+      if ( length(x)==nf )
+        return(x)
+      else if ( nf > 1L)
+        stop(sprintf("length of '%s' must match the number of 'render' frames"), deparse(substitute(x)))
+    rep(list(x), nf) 
+  }
+  pts = format_as_list(pts, nf)
+  col = format_as_list(col, nf)
+  
+  for (i in seq_len(nf) ) {
+    pti = pts[[i]]
+    if ( is.list(pti) )
+      if ( is.data.frame(pti) ) 
+        pti = as.matrix(pti)
+      else 
+        pti = unlist(pti, use.names=FALSE)
+    storage.mode(pti) = "integer"
+    if ( !is.matrix(pti) || dim(pti)[2L]!=2L )
+      pti = matrix(pti, nrow=length(pti)/2, ncol=2L, byrow=TRUE)
+    np = dim(pti)[1L]
+    if ( any( pti<1L ) || any( pti>rep(dim(x)[1:2], each=np) ) )
+      stop("coordinates of starting point(s) 'pts' must be inside image boundaries")
+    pts[[i]] = pti
+    
+    cli = col[[i]]
+    cli = if ( is.character(cli) )
+      unlist(lapply(cli, function(col) rep_len(col2rgb(col, alpha=TRUE)/255, nc)))
+    else 
+      rep(cli, each=nc)
+    storage.mode(cli) = storage.mode(x) 
+    cli = matrix(rep_len(cli, np * nc), nrow=np, ncol=nc, byrow=TRUE)
+    col[[i]] = cli
+  }
+  
+  return( .Call(C_floodFill, x, pts, col, as.numeric(tolerance)))
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+fillHull = function(x) {
+  validImage(x)
+  return(.Call(C_fillHull, x))
+}
diff --git a/R/gblur.R b/R/gblur.R
new file mode 100644
index 0000000..93d12e1
--- /dev/null
+++ b/R/gblur.R
@@ -0,0 +1,3 @@
+gblur <- function(x, sigma, radius=2*ceiling(3*sigma)+1, ...) {
+  filter2(x, filter = makeBrush(size=radius, shape="Gaussian", sigma=sigma), ...)
+}
diff --git a/R/localCurvature.R b/R/localCurvature.R
new file mode 100644
index 0000000..a07c35d
--- /dev/null
+++ b/R/localCurvature.R
@@ -0,0 +1,25 @@
+localCurvature = function(x, h=100, maxk=10000) {
+ # check that x is of proper type
+ if (!is.matrix(x) & !is.data.frame(x)) stop("'x' should be a data frame or a matrix")
+ # if data frame convert to matrix to standardize processing
+ if (is.data.frame(x)) x = as.matrix(x)
+ # check that x has proper dimensions
+ if (length(dim(x)) != 2 | dim(x)[2] !=2) stop("'x' should have dimensions of N x 2")
+
+ # compute arc length
+ px = c(0, cumsum(sqrt(rowSums(diff(x)^2))))
+
+ # smooth and calculate derivatives
+ poly = lp(px, deg=2, h=h)
+ derivatives = apply(x, 2, function(v) {
+  d1 = locfit(v ~ poly, deriv=1, maxk=maxk)
+  d2 = locfit(v ~ poly, deriv=c(1,1), maxk=maxk)
+  list(predict(d1, newdata=data.frame(px=px)),
+  predict(d2, newdata=data.frame(px=px)))
+ })
+ 
+ # calculate signed curvature
+ curvature = function(d) (d[[1]][[1]]*d[[2]][[2]] - d[[2]][[1]]*d[[1]][[2]]) / (d[[1]][[1]]^2+d[[2]][[1]]^2)^1.5
+
+ return(list(contour=x, curvature=curvature(derivatives), length=max(px, na.rm=TRUE)))
+}
diff --git a/R/medianFilter.R b/R/medianFilter.R
new file mode 100644
index 0000000..aa9ab31
--- /dev/null
+++ b/R/medianFilter.R
@@ -0,0 +1,9 @@
+medianFilter = function(x, size, cacheSize=512L) {
+  size = as.integer(size)
+  if (size < 1L) stop("filter radius must be >= 1")
+  if (min(dim(x)[1:2]) < 2L * size + 1L) stop("filter radius must not exceed half of the x or y dimension")
+  if (any(is.na(x))) stop("'x' shouldn't contain any NAs")
+  validImage(x)
+  
+  .Call(C_medianFilter, castImage(x), size, as.integer(cacheSize))
+}
diff --git a/R/morphology.R b/R/morphology.R
new file mode 100644
index 0000000..825ea0f
--- /dev/null
+++ b/R/morphology.R
@@ -0,0 +1,121 @@
+thresh = function (x, w=5, h=5, offset=0.01) {
+  validImage(x)
+  if (w<1 || h<1) 
+    stop ("filter width 'w' and height 'h' must be larger than 1")
+  
+  d = dim(x)[1:2]
+  d = (d-1)/2
+  if (w > d[1L])
+    stop ("filter width exceeds picture width")
+  if (h > d[2L])
+    stop ("filter height exceeds picture height")
+  
+  return (.Call(C_thresh, x, as.numeric(c(w, h, offset))))
+}
+
+distmap = function (x, metric=c('euclidean', 'manhattan')) {
+  validImage(x)
+  if (any(is.na(x))) stop("'x' shouldn't contain any NAs")
+  metric = switch(match.arg(metric), euclidean=0L, manhattan=1L) 
+  return (.Call(C_distmap, x, metric))
+}
+
+makeBrush = function(size, shape=c('box', 'disc', 'diamond', 'Gaussian', 'line'), step=TRUE, sigma=0.3, angle=45) {
+  if(! (is.numeric(size) && (length(size)==1L) && (size>=1)) ) stop("'size' must be an odd integer.")
+  shape = match.arg(arg = tolower(shape), choices = c('box', 'disc', 'diamond', 'gaussian', 'line'))
+
+  if(size %% 2 == 0){
+    size = size + 1
+    warning(paste("'size' was rounded to the next odd number: ", size))
+  }
+  
+  if (shape=='box') z = matrix(1L, size, size)
+  else if (shape == 'line') {
+    angle = angle %% 180
+    angle.radians = angle * pi / 180;
+    tg = tan(angle.radians)
+    sizeh = (size-1)/2
+    if ( angle < 45 || angle > 135) {
+      z.x = sizeh
+      z.y = round(sizeh*tg)
+    }
+    else {
+      z.y = sizeh
+      z.x = round(sizeh/tg)
+    }
+    z = array(0L, dim=2*c(z.x, z.y)+1);
+    for (i in -sizeh:sizeh) {
+      if ( angle < 45 || angle > 135) {
+        ## scan horizontally
+        i.x = i
+        i.y = round(i*tg)
+      }
+      else {
+        ## scan vertically
+        i.y = i
+        i.x = round(i/tg) 
+      }
+      z[i.x+z.x+1, i.y+z.y+1] = 1L
+    }
+  }
+  else if (shape=='gaussian') {
+    x = seq(-(size-1)/2, (size-1)/2, length=size)
+    x = matrix(x, size, size)
+    z = exp(- (x^2 + t(x)^2) / (2*sigma^2))
+    z = z / sum(z)
+  } else {
+    ## pixel center coordinates
+    x = 1:size -((size+1)/2)
+    
+    ## for each pixel, compute the distance from its center to the origin, using L1 norm ('diamond') or L2 norm ('disc')
+    if (shape=='disc') {
+      z = outer(x, x, FUN=function(X,Y) (X*X+Y*Y))
+      mz = (size/2)^2
+      z = (mz - z)/mz
+      z = sqrt(ifelse(z>0, z, 0))
+    } else {
+      z = outer(x, x, FUN=function(X,Y) (abs(X)+abs(Y)))
+      mz = (size/2)
+      z = (mz - z)/mz
+      z = ifelse(z>0, z, 0)
+    }
+
+    if (step) z = ifelse(z>0, 1L, 0L)
+  }
+  z
+}
+
+dilate = function (x, kern=makeBrush(5, shape='diamond')) {
+  validImage(x)
+  return (.Call(C_morphology, x, kern, 0L))
+}
+
+erode = function (x, kern=makeBrush(5, shape='diamond')) {
+  validImage(x)
+  return (.Call(C_morphology, x, kern, 1L))
+}
+
+opening = function (x, kern=makeBrush(5, shape='diamond')) {
+  validImage(x)
+  return (.Call(C_morphology, x, kern, 2L))
+}
+
+closing = function (x, kern=makeBrush(5, shape='diamond')) {
+  validImage(x)
+  return (.Call(C_morphology, x, kern, 3L))
+}
+
+whiteTopHat = function (x, kern=makeBrush(5, shape='diamond')) {
+  validImage(x)
+  return (.Call(C_morphology, x, kern, 4L))
+}
+
+blackTopHat = function (x, kern=makeBrush(5, shape='diamond')) {
+  validImage(x)
+  return (.Call(C_morphology, x, kern, 5L))
+}
+
+selfComplementaryTopHat = function (x, kern=makeBrush(5, shape='diamond')) {
+  validImage(x)
+  return (.Call(C_morphology, x, kern, 6L))
+}
diff --git a/R/normalize.R b/R/normalize.R
new file mode 100755
index 0000000..7d6eb75
--- /dev/null
+++ b/R/normalize.R
@@ -0,0 +1,30 @@
+normalizeImage = function(object, separate = TRUE, ft = c(0, 1), inputRange) {
+  validImage(object)
+  
+  if ( missing(inputRange) ) {
+    inputRange = NULL;
+  }
+  
+  if ( !is.null(inputRange) ) {
+    inputRange <- as.numeric(inputRange)
+    if ( diff(inputRange) == 0 ) stop("clipping range is 0")
+    separate = FALSE
+  }
+   
+  if ( !is.null(ft)) {
+    ft <- as.numeric (ft)
+    if ( diff(ft) == 0 ) stop("normalization range is 0")
+  }
+  
+  if ( is.null(ft) && is.null(inputRange) )
+    stop("please specify either normalization range or clipping range")
+  
+  .Call(C_normalize, castImage(object), isTRUE(separate), ft, inputRange)
+}
+
+## general method for the superclass of 'Image' and 'matrix'
+setMethod("normalize", signature("array"), normalizeImage)
+
+## explicit methods for subclasses of 'array'
+setMethod("normalize", signature("matrix"), normalizeImage)
+setMethod("normalize", signature("Image"), normalizeImage)
diff --git a/R/objects.R b/R/objects.R
new file mode 100644
index 0000000..cdb1c30
--- /dev/null
+++ b/R/objects.R
@@ -0,0 +1,82 @@
+# Copyright (c) 2005-2007 Oleg Sklyar
+
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation; either version 2.1
+# of the License, or (at your option) any later version.
+
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+# See the GNU Lesser General Public License for more details.
+# LGPL license wording: http://www.gnu.org/licenses/lgpl.html
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+paintObjects = function (x, tgt, opac=c(1, 1), col=c('red', NA), thick=FALSE, closed=FALSE) {
+  checkCompatibleImages(x, tgt, 'render')
+  if (colorMode(x)!=Grayscale) stop("'", deparse(substitute(x), width.cutoff = 500L, nlines = 1L), "' must be in 'Grayscale' color mode")
+  
+  if ((l=length(col))>2L) col[1:2] else col = switch(l+1L, c(NA, NA), c(col, NA), col)
+  if ((l=length(opac))>2L) opac[1:2] else opac = switch(l+1L, c(1, 1), c(opac, 1), opac)
+  
+  zcol = is.na(col)
+  col[zcol] = 'white'
+  opac[zcol] = 0
+  
+  opac = as.numeric(opac)
+  if (any(opac < 0) || any(opac > 1)) stop("all opacity values must be in the range [0,1]" )
+  
+  ## behavior at image borders
+  if ( isTRUE(closed) ) {
+    col[3L] = col[1L] 
+    opac[3L] = opac[1L]
+  }
+  else {
+    col[3L] = col[2L] 
+    opac[3L] = opac[2L]
+  }
+    
+  .Call(C_paintObjects, castImage(x), castImage(tgt), opac, Image(col, colormode = colorMode(tgt)), isTRUE(thick))
+}
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+stackObjects = function (x, ref, combine=TRUE, bg.col='black', ext) {
+  ## check arguments
+  if (colorMode(x) != Grayscale) stop("'x' must be an image in 'Grayscale' color mode or an array")
+  checkCompatibleImages(x, ref, 'render')
+  nz = numberOfFrames(x, 'total')
+  
+  ## uses 'computeFeatures.moment' to get centers and theta
+  hf = lapply(getFrames(x), computeFeatures.moment)
+
+  if (missing(ext)) {
+    ext = unlist(sapply(hf, function(h) h[, 'm.majoraxis']))/2
+    ext = quantile(ext, 0.98, na.rm=TRUE)
+  }
+  xy = lapply(hf, function(h) h[,c('m.cx', 'm.cy'), drop=FALSE])
+  if (nz==1L) xy = xy[[1L]]
+  
+  bg.img = Image(bg.col, colormode=colorMode(ref))
+  
+  res = .Call(C_stackObjects, castImage(x), castImage(ref), castImage(bg.img), xy, as.numeric(ext))
+  if (!combine || !is.list(res)) res
+  else combine(res)
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+rmObjects = function (x, index, reenumerate=TRUE) {
+  validImage(x)
+  if ( storage.mode(x)!="integer" ) stop("storage mode of object mask should be integer")
+  if (!is.list(index)) index = list(index)
+  index = lapply (index, as.integer)
+  .Call(C_rmObjects, x, index, isTRUE(reenumerate))
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+reenumerate = function(x) {
+  validImage(x)
+  if ( storage.mode(x)!="integer" ) stop("storage mode of object mask should be integer")
+  .Call(C_rmObjects, x, NULL, TRUE)
+}
diff --git a/R/otsu.R b/R/otsu.R
new file mode 100644
index 0000000..02a3e76
--- /dev/null
+++ b/R/otsu.R
@@ -0,0 +1,30 @@
+
+otsu <- function(x, range = c(0, 1), levels = 256){
+  validImage(x)
+  if ( colorMode(x) != Grayscale ) stop("Only thresholding of Grayscale images is supported.")
+  if ( !is.numeric(range) || length(range) != 2 ) stop("'range' must be a numeric vector of length 2.")
+  levels = as.integer(levels)
+  if ( is.na(levels) || levels < 1 ) stop("Levels must be at least equal 1.")
+  breaks = seq(range[1], range[2], length.out = levels+1)
+  x = castImage(x)  # converts logical to numeric
+  
+# prepare 3D array for the 'apply' function
+  dim(x) = c(dim(x)[seq_len(2)], numberOfFrames(x, 'total'))
+  
+# threshold each frame separately
+  apply(x, 3, function(y) {
+    h = hist.default(y, breaks = breaks, plot = FALSE)
+    counts = as.double(h$counts)
+    mids = as.double(h$mids)
+    len = length(counts)
+    w1 = cumsum(counts)
+    w2 = w1[len] + counts - w1
+    cm = counts * mids
+    m1 = cumsum(cm)
+    m2 = m1[len] + cm - m1
+    var = w1 * w2 * (m2/w2 - m1/w1)^2
+    # find the left- and right-most maximum and return the threshold value in between
+    maxi = which(var == max(var, na.rm = TRUE))
+    (mids[maxi[1]] + mids[maxi[length(maxi)]] ) /2
+  })
+}
diff --git a/R/segment.R b/R/segment.R
new file mode 100644
index 0000000..44747c0
--- /dev/null
+++ b/R/segment.R
@@ -0,0 +1,78 @@
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+watershed = function (x, tolerance=1, ext=1) {
+  validImage(x)
+  tolerance = as.numeric(tolerance)
+  if (tolerance<0) stop( "'tolerance' must be non-negative" )
+  ext = as.integer(ext)
+  if (ext<1) stop( "'ext' must be a positive integer" )
+  .Call(C_watershed, castImage(x), tolerance, ext)
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+propagate = function (x, seeds, mask=NULL, lambda=1e-4) {
+  validImage(x)
+  checkCompatibleImages(x, seeds)
+  
+  if (!is.integer(seeds))
+    storage.mode(seeds) = 'integer'
+
+  if (!is.null(mask)) {
+    checkCompatibleImages(x, mask)
+    if (!is.integer(mask))
+      storage.mode(mask) = 'integer'
+  }
+
+  lambda = as.numeric(lambda)
+  if (lambda<0.0) stop("'lambda' must be positive" )
+
+  return(.Call(C_propagate, castImage(x), seeds, mask, lambda))
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ocontour = function(x) {
+  validImage(x)
+  if(!is.integer(x)) storage.mode(x) = 'integer'
+  y = .Call(C_ocontour, x)
+  names(y) = seq_along(y)
+  y = y[sapply(y, length)>0] # remove missing objects
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+bwlabel = function(x) {
+  validImage(x)
+  .Call(C_bwlabel, x)
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+colorLabels = function(x, normalize = TRUE){
+  len = length( (d = dim(x)) )
+  
+  res <- x
+  
+  # linearize image data for convenient processing
+  dim(res) = c(prod(d[1:2]), if(len>2) prod(d[3:len]) else 1)
+  
+  f = function(y, m) {
+    idx <- y > 0
+    y[idx] <- sample(m)[y[idx]]
+    y
+  }
+  
+  tmp = apply(res, 2, function(y) {
+    m = max(y)
+    replicate(3, f(y, m))
+  })
+  
+  # restore proper dimensions
+  dim(tmp) = c(d[1:2], 3, if(len>2) d[3:len] else NULL)
+  
+  if ( is.Image(x) ) {
+    imageData(res) <- tmp
+    colorMode(res) <- Color
+  }
+  else {
+    res = new("Image", .Data = tmp, colormode = Color)
+  }
+  
+  if (normalize) normalize(res) else res
+}
diff --git a/R/spatial.R b/R/spatial.R
new file mode 100644
index 0000000..16385ec
--- /dev/null
+++ b/R/spatial.R
@@ -0,0 +1,122 @@
+flip <- function (x) {
+  validImage(x)
+  asub(x, seq.int(dim(x)[2],1), 2)
+}
+
+flop <- function (x) {
+  validImage(x)
+  asub(x, seq.int(dim(x)[1],1), 1)
+}
+
+## performs an affine transform on a set of images
+affine <- function (x, m, filter = c("bilinear", "none"), output.dim, bg.col = "black", antialias = TRUE) {
+  ## check arguments
+  validImage(x)
+  if ( !is.matrix(m) || dim(m)!=c(3L, 2L) ) stop("'m' must be a 3x2 matrix")
+  if ( any(is.na(m)) ) stop("'m' shouldn't contain any NAs")
+  
+  filter <- switch(match.arg(filter), none=0L, bilinear=1L)
+  
+  # dimensions of output image
+  d = dim(x)  
+  if (!missing(output.dim)) {
+    if( length(output.dim)!=2L || !is.numeric(output.dim) ) stop("'output.dim' must be a numeric vector of length 2")
+    d[1:2] = as.integer(round(output.dim))
+  }
+  
+  ## inverse of the transformation matrix
+  ## this is needed because in C we iterate over (x',y') for which we calculate (x,y)
+  m <- solve(cbind(m, c(0, 0, 1)))
+  
+  ## image background
+  nf = numberOfFrames(x, "render")
+  
+  bg <- Image(rep_len(bg.col, nf), c(1L, 1L, d[-c(1,2)]), colorMode(x))
+  
+  .Call(C_affine, castImage(x), d, castImage(bg), m, filter, isTRUE(antialias))
+}
+
+# images are represented in a cooridinate system with the y-axis oriented
+# downwards; therefore we perform "counter-clockwise" rotation, which is
+# equivalent to clockwise rotation in a regular coordinate system
+rotate = function(x, angle, filter = "bilinear", output.dim, output.origin, ...) {
+  ## check arguments
+  if ( length(angle)!=1L || !is.numeric(angle) ) stop("'angle' must be a number")
+  if ( !missing(output.dim) )
+    if ( length(output.dim)!=2L || !is.numeric(output.dim) ) stop("'output.dim' must be a numeric vector of length 2")
+
+  ## allow lossless rotation
+  if ( (angle %% 90) == 0 ) filter = "none"
+  
+  angle = angle * pi / 180
+  d  = dim(x)[1:2]
+  dx = d[1]
+  dy = d[2]
+  cos = cos(angle)
+  sin = sin(angle)
+  
+  ## if no origin is specified, rotate about center of output image
+  if ( missing(output.origin) ) {
+    ## calculate new bounding box size
+    newdim = c(
+      dx * abs(cos) + dy * abs(sin),
+      dx * abs(sin) + dy * abs(cos)
+    )
+    
+    ## offset needed to stay within the bounding box
+    offset = c(
+      dx * max(0, -cos) + dy * max(0,  sin),
+      dx * max(0, -sin) + dy * max(0, -cos)
+    )
+    
+    if ( missing(output.dim) )
+      output.dim = newdim
+    else
+      offset = offset + (output.dim - newdim) / 2
+    
+  } else {
+    if ( length(output.origin)!=2L || !is.numeric(output.origin) )
+      stop("'output.origin' must be a numeric vector of length 2")
+    offset = c(
+      output.origin[1L] * (1 - cos) + output.origin[2L] * sin,
+      output.origin[2L] * (1 - cos) - output.origin[1L] * sin
+    )
+  }
+  
+  m <- matrix(c(cos, -sin, offset[1], 
+                sin,  cos, offset[2]), 3L, 2L) 
+  
+  affine(x = x, m = m, filter = filter, output.dim = output.dim, ...)
+}
+
+translate <- function(x, v, filter = "none", ...) {
+  ## check arguments
+  if ( length(v)!=2L || !is.numeric(v) ) stop("'v' must be a numeric vector of length 2")
+  
+  m <- matrix(c(1, 0, v[1],
+                0, 1, v[2]), 3L, 2L)
+  
+  affine(x = x, m = m, filter = filter, ...)
+}
+
+resize <- function(x, w, h, output.dim = c(w, h), output.origin = c(0, 0), antialias = FALSE, ...) {
+  ## check arguments
+  if ( missing(h) && missing(w) ) stop("either 'w' or 'h' must be specified")
+  if ( length(output.origin)!=2L || !is.numeric(output.origin) ) stop("'output.origin' must be a numeric vector of length 2")
+  
+  d = dim(x)[1:2]
+  if ( missing(w) ) w <- round(h*d[1]/d[2])
+  if ( missing(h) ) h <- round(w*d[2]/d[1])
+  if ( missing(output.dim) ) output.dim <- c(w, h)
+  ratio <- c(w, h)/d
+  
+  m <- matrix(c(ratio[1], 0, (1-ratio[1]) * output.origin[1],
+                0, ratio[2], (1-ratio[2]) * output.origin[2]), 3L, 2L)
+  
+  affine(x = x, m = m, output.dim = output.dim, antialias = antialias, ...)
+}
+
+## transpose spatial dimensions
+transpose <- function(x) {
+    .Call(C_transpose, x)
+}
diff --git a/R/tile.R b/R/tile.R
new file mode 100644
index 0000000..76b5b6f
--- /dev/null
+++ b/R/tile.R
@@ -0,0 +1,36 @@
+# Copyright (c) 2005-2007 Oleg Sklyar
+
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation; either version 2.1
+# of the License, or (at your option) any later version.
+
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+# See the GNU Lesser General Public License for more details.
+# LGPL license wording: http://www.gnu.org/licenses/lgpl.html
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+tile = function (x, nx=10, lwd=1, fg.col="#E4AF2B", bg.col="gray") {
+  if (is.list(x)) lapply(x, tile, nx=nx, lwd=lwd, fg.col=fg.col, bg.col=bg.col)
+  else {
+    validImage(x)
+    if ( nx < 1 || lwd < 0 || lwd > 100 ) stop( "wrong range of arguments, see help for range details" )
+    
+    hdr = Image(c(fg.col,bg.col), colormode = colorMode(x))
+    
+    .Call(C_tile, x, hdr, as.integer(c(nx, lwd)))
+  }
+}
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+untile = function (x, nim, lwd=1) {
+  validImage(x)
+  nim = as.integer(nim)
+  lwd = as.integer(lwd)
+  if (length(nim)<2) stop("'nim' must contain two values for the number of images in x and y directions")
+  if (lwd<0) stop("wrong line width")
+  .Call(C_untile, x, nim, lwd)
+}
diff --git a/R/tools.R b/R/tools.R
new file mode 100644
index 0000000..525e87e
--- /dev/null
+++ b/R/tools.R
@@ -0,0 +1,47 @@
+# Package tools
+
+# Copyright (c) 2006 Oleg Sklyar
+
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation; either version 2.1
+# of the License, or (at your option) any later version.
+
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+# See the GNU Lesser General Public License for more details.
+# LGPL license wording: http://www.gnu.org/licenses/lgpl.html
+
+## checks whether 'x' is a suitable image
+validImage = function(x) {
+  z = validImageObject(x)
+  if (isTRUE(z))
+    TRUE
+  else 
+    stop(z)
+}
+
+## if required changes the storage.mode of 'x' to 'double' 
+castImage = function(x) {
+  if (!is.double(x)) storage.mode(x) = 'double'
+  x
+}
+
+## clip pixel data to the specified range
+clipImage = function(x, range = c(0, 1)) {
+  normalize(x, ft = NULL, inputRange = range)
+}
+
+## check if x (indexing image) and ref (image) are compatible
+checkCompatibleImages=function(x, ref, type='total') {
+  xn = deparse(substitute(x), width.cutoff = 500L, nlines = 1)
+  refn = deparse(substitute(ref), width.cutoff = 500L, nlines = 1)
+  validImage(x)
+  if (!missing(ref)) {
+    validImage(ref)
+    if ( numberOfFrames(x, type) != numberOfFrames(ref, type) ) stop(sprintf("'%s' and '%s' must have the same number of '%s' frames", xn, refn, type))
+    if ( any(dim(x)[1:2] != dim(ref)[1:2]) ) stop(sprintf("'%s' and '%s' must have the same spatial 2D dimensions", xn, refn, type))
+  }
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c3b462b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+## Installation
+
+The package is distributed as part of the [Bioconductor](http://bioconductor.org/packages/EBImage) project.
+To install this package, start R and enter:
+
+```
+source("https://bioconductor.org/biocLite.R")
+biocLite("EBImage")
+```
+
+## Documentation
+
+For an overview of the package functionality, see the [package vignette](http://bioconductor.org/packages/devel/bioc/vignettes/EBImage/inst/doc/EBImage-introduction.html).
+
+Once you install the package, you can access individual man pages by a call to, e.g., `?Image`.
+
+## FAQ
+
+See [my answers on Stack Overflow](http://stackoverflow.com/search?q=user%3A2792099+%09is%3Aanswer+EBimage).
+
+## Bug reports and feature requests
+
+Bug reports, feature requests, or any other issues with the package should be reported at https://github.com/aoles/EBImage/issues.
diff --git a/build/vignette.rds b/build/vignette.rds
new file mode 100644
index 0000000..f27deaa
Binary files /dev/null and b/build/vignette.rds differ
diff --git a/debian/README.Debian b/debian/README.Debian
deleted file mode 100644
index 4163c91..0000000
--- a/debian/README.Debian
+++ /dev/null
@@ -1,13 +0,0 @@
-Bioconductor qvalue for Debian
-──────────────────────────────
-
-Debian ftpmaster has requested to remove the "binary without source"
-manual.pdf.  You can find it here online:
-
-    http://www.bioconductor.org/packages/release/bioc/vignettes/qvalue/inst/doc/manual.pdf
-
-besides other PDF documentation at the qvalue homepage
-
-    http://www.bioconductor.org/packages/release/bioc/html/qvalue.html
-
- -- Andreas Tille <tille at debian.org>  Thu, 07 Jun 2012 17:40:29 +0200
diff --git a/debian/README.test b/debian/README.test
deleted file mode 100644
index b265efe..0000000
--- a/debian/README.test
+++ /dev/null
@@ -1,5 +0,0 @@
-Notes on how this package can be tested.
-========================================
-
-This package can be tested by loading it into R with the command
-'library(EBImage)' in order to confirm its integrity.
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index a96192e..0000000
--- a/debian/changelog
+++ /dev/null
@@ -1,13 +0,0 @@
-r-bioc-ebimage (4.6.0-1) UNRELEASED; urgency=low
-
-  [ Steffen Moeller ]
-  * Initial release.
-  TODO: r-cran-tiff: http://cran.r-project.org/web/packages/tiff/index.html
-        r-cran-jpeg: http://cran.r-project.org/web/packages/jpeg/index.html
-        r-cran-png:  http://cran.r-project.org/web/packages/png/index.html
-        r-cran-locfit: svn://anonscm.debian.org/debian-med/trunk/packages/R/r-cran-locfit/trunk/ (non-free, if distributable at all)
-
-  [ Andreas Tille ]
-  * Moved packaging from SVN to Git
-
- -- Andreas Tille <tille at debian.org>  Wed, 08 Nov 2017 13:48:23 +0100
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index ec63514..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-9
diff --git a/debian/control b/debian/control
deleted file mode 100644
index 18d3128..0000000
--- a/debian/control
+++ /dev/null
@@ -1,30 +0,0 @@
-Source: r-bioc-ebimage
-Maintainer: Debian Med Packaging Team <debian-med-packaging at lists.alioth.debian.org>
-Uploaders: Steffen Moeller <moeller at debian.org>,
-           Andreas Tille <tille at debian.org>
-Section: gnu-r
-Priority: optional
-Build-Depends: debhelper (>= 9),
-               cdbs,
-               r-base-dev (>= 3.0),
-               r-bioc-biocgenerics,
-               r-cran-abind,
-               r-cran-tiff,
-               r-cran-jpeg,
-               r-cran-png,
-               r-cran-locfit
-Standards-Version: 3.9.5
-Vcs-Browser: https://anonscm.debian.org/cgit/debian-med/r-bioc-ebimage.git
-Vcs-Git: https://anonscm.debian.org/git/debian-med/r-bioc-ebimage.git
-Homepage: http://www.bioconductor.org/packages/release/bioc/html/ebimage.html
-
-Package: r-bioc-ebimage
-Architecture: any
-Depends: ${R:Depends},
-         ${shlib:Depends}
-Description: GNU R toolbox for image processing
- EBImage is an R package which provides general purpose functionality for
- the reading, writing, processing and analysis of images. Furthermore,
- in the context of microscopy based cellular assays, EBImage offers
- tools to transform the images, segment cells and extract quantitative
- cellular descriptors.
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index 725d554..0000000
--- a/debian/copyright
+++ /dev/null
@@ -1,31 +0,0 @@
-Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: EBImage
-Upstream-Contact: Andrzej Oles <andrzej.oles at embl.de>
-Source: http://www.bioconductor.org/packages/release/bioc/html/EBImage.html
-
-Files: *
-Copyright: © 2006-2014 Gregoire Pau, Andrzej Oles, Mike Smith, Oleg Sklyar, Wolfgang Huber
-License: LGPL
-
-Files: debian/*
-Copyright: 2014 Steffen Moeller <moeller at debian.org>
-           2014 Andreas Tille <tille at debian.org>
-License: LGPL
-
-License: LGPL
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- . 
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU General Public License for more details.
- .
- You should have received a copy of the GNU Lesser General Public License
- along with this program.  If not, see <http://www.gnu.org/licenses/>.
- .
- On a Debian GNU/Linux system, the GNU Lesser GPL license version 3 is included
- in the file ‘/usr/share/common-licenses/LGPL’.
-
diff --git a/debian/docs b/debian/docs
deleted file mode 100644
index f3ab9b5..0000000
--- a/debian/docs
+++ /dev/null
@@ -1,3 +0,0 @@
-debian/README.test
-CHANGES
-tests
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index 22c97f0..0000000
--- a/debian/rules
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/make -f
-
-export DEB_BUILD_HARDENING=1
-export DEB_BUILD_MAINT_OPTIONS = hardening=+all
-debRreposname=bioc
-include /usr/share/R/debian/r-cran.mk
-
-install/$(package)::
-	chmod 644 debian/$(package)/usr/lib/R/site-library/$(cranName)/doc/*
-
-clean::
-	find . -name "*.pdf" -o -name "*.tex" -o -name "*-eps" -o -name "*.log" -o -name "*.ps" -o -name "*.txt" | xargs -r chmod -x
-
-get-orig-source:
-	uscan --verbose --force-download --repack --compression xz
diff --git a/debian/source/format b/debian/source/format
deleted file mode 100644
index 163aaf8..0000000
--- a/debian/source/format
+++ /dev/null
@@ -1 +0,0 @@
-3.0 (quilt)
diff --git a/debian/tests/control b/debian/tests/control
deleted file mode 100644
index 25377fc..0000000
--- a/debian/tests/control
+++ /dev/null
@@ -1,3 +0,0 @@
-Tests: run-unit-test
-Depends: @, r-cran-runit
-Restrictions: allow-stderr
diff --git a/debian/tests/run-unit-test b/debian/tests/run-unit-test
deleted file mode 100644
index 41ef8c3..0000000
--- a/debian/tests/run-unit-test
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh -e
-
-oname=EBImage
-pkg=r-bioc-`echo $oname | tr [A-Z] [a-z]`
-if [ "$ADTTMP" = "" ] ; then
-  ADTTMP=`mktemp -d /tmp/${pkg}-test.XXXXXX`
-fi
-cd $ADTTMP
-cp /usr/share/doc/${pkg}/tests/* $ADTTMP
-find . -name "*.gz" -exec gzip \{\} \;
-cat test.R | R --vanilla &>test.Rout
-diff -u test.Rout.save test.Rout
-rm -f $ADTTMP/*
diff --git a/debian/upstream/metadata b/debian/upstream/metadata
deleted file mode 100644
index 3da110f..0000000
--- a/debian/upstream/metadata
+++ /dev/null
@@ -1,15 +0,0 @@
-Contact: Andrzej Oles <andrzej.oles at embl.de> 
-Name: EBImage
-Reference:
- article: Pau20100201
- author: Grégoire Pau and Florian Fuchs and Oleg Sklyar and Michael Boutros and Wolfgang Huber
- title: EBImage.an R package for image processing with applications to cellular phenotypes
- journal: Bioinformatics
- volume: 26
- number: 7
- pages: 979-981
- doi: 10.1093/bioinformatics/btq046
- PMID: 20338898
- year: 2010
- URL: http://bioinformatics.oxfordjournals.org/content/26/7/979.full
- eprint: http://bioinformatics.oxfordjournals.org/content/26/7/979.full.pdf+html
diff --git a/debian/watch b/debian/watch
deleted file mode 100644
index 21a65d6..0000000
--- a/debian/watch
+++ /dev/null
@@ -1,4 +0,0 @@
-version=3
-opts=downloadurlmangle=s/\.\./packages\/release\/bioc/ \
- http://www.bioconductor.org/packages/release/bioc/html/EBImage.html \
- .*/EBImage_([\d\.]+)\.tar\.gz
diff --git a/inst/CITATION b/inst/CITATION
new file mode 100644
index 0000000..40c6acb
--- /dev/null
+++ b/inst/CITATION
@@ -0,0 +1,23 @@
+citEntry(entry="article",
+  title = "EBImage---an R package for image processing with applications to cellular phenotypes",
+  author = personList( 
+            as.person("Gregoire Pau"),
+		        as.person("Florian Fuchs"),
+            as.person("Oleg Sklyar"),
+            as.person("Michael Boutros"),
+		        as.person("Wolfgang Huber") ),
+  journal = "Bioinformatics",
+  volume = "26",
+  number = "7",
+  pages = "979--981",
+  year = "2010",
+  doi = "10.1093/bioinformatics/btq046",
+  textVersion = paste(
+    paste("Gregoire Pau, Florian Fuchs, Oleg Sklyar, Michael Boutros, and Wolfgang Huber (2010):",
+          "EBImage - an R package for image processing with applications to cellular phenotypes.",
+          "Bioinformatics"),
+    "26(7)",
+    "pp. 979-981",
+    "10.1093/bioinformatics/btq046",
+    sep = ", ")
+)
diff --git a/inst/NEWS.Rd b/inst/NEWS.Rd
new file mode 100644
index 0000000..cda63a2
--- /dev/null
+++ b/inst/NEWS.Rd
@@ -0,0 +1,348 @@
+\name{EBImage-NEWS}
+\title{News for Package 'EBImage'}
+
+\section{Changes in version 4.20.0}{
+  \subsection{NEW FEATURES}{
+    \itemize{
+      \item 'abind()' method for combining Image arrays
+      \item 'floodFill()' has been vectorized over its arguments 'pt' and 'col' allowing to specify multiple start points and different fill colors
+    }
+  }
+  \subsection{SIGNIFICANT USER-VISIBLE CHANGES}{
+    \itemize{
+      \item 'display()' "browser" mode has been updated to use the htmlwidgets infrastructure
+      \item 'filter2()' does not require filter dimensions to be odd numbers when filter size equals image size
+    }
+  }
+  \subsection{BUG FIXES}{
+    \itemize{
+      \item fixed issues with 'normalize()' (https://github.com/aoles/EBImage/issues/25)
+      \item various improvements to the 'clahe()' function
+    }
+  }
+}
+
+\section{Changes in version 4.18.0}{
+  \subsection{NEW FEATURES}{
+    \itemize{
+      \item new arguments to 'display()' enabling control over layout and appearance of image grid in "raster" mode: 'nx' (number of frames in a row), 'drawGrid' (draw lines between frames), 'spacing' (separation between frames) and 'margin' (outer margin around the image)
+      \item new function 'clahe()' for improving local contrast in images by performing Contrast Limited Adaptive Histogram Equalization
+      \item re-introduced 'output.origin' argument to 'rotate()'
+    }
+  }
+  \subsection{SIGNIFICANT USER-VISIBLE CHANGES}{
+    \itemize{
+      \item object masks returned by `bwlabel()`, `propagate()`, and `watershed()`, as well as the result of thresh() are now of storage mode integer rather than double
+      \item binary kernels constructed by 'makeBrush()' have storage mode integer (previously double)
+      \item 'rmObjects()' and 'reenumerate()' now require input of storage mode integer
+      \item 'untile()' and morphology operations preserve data storage mode
+      \item modified boundary behaviour of 'thresh()' to reduce artifacts at image borders and to match the output of a corresponding call to 'filter2()'
+      \item added the ability for different boundary values for different frames in 'filter2()' linear mode (https://github.com/aoles/EBImage/pull/11) 
+      \item removed defunct '...GreyScale' family morphological functions
+    }
+  }
+  \subsection{PERFORMANCE IMPROVEMENTS}{
+    \itemize{
+      \item significantly improved performance of 'transpose()', 'getFrame()' and 'getFrames()' by using C implementation
+      \item numerous small improvements to execution time and memory consumption across the whole package, mostly by avoiding storage mode conversion and object duplication in C
+    }
+  }
+  \subsection{BUG FIXES}{
+    \itemize{
+      \item proper origin handling in 'resize()'
+      \item import 'methods::slot' (fixes https://github.com/rstudio/blogdown/issues/17)
+      \item fixed a bug in 'filter2()' (https://github.com/aoles/EBImage/issues/8)
+      \item proper check of filter size in 'thresh()' and rectified behavior when filter dimensions are equal to image dimensions
+      \item correct computation of 'selfComplementaryTopHat()'
+      \item address PROTECT errors reported by Tomas Kalibera's 'maacheck' tool (https://stat.ethz.ch/pipermail/bioc-devel/2017-April/010771.html)
+      \item fixed class retention in 'colorLabels()', 'colormap()', 'rgbImage()', 'stackObjects()', 'tile() and untile()'
+    }
+  }
+}
+
+\section{Changes in version 4.16.0}{
+  \subsection{SIGNIFICANT USER-VISIBLE CHANGES}{
+    \itemize{
+      \item made defunct deprecated '...GreyScale' family morphological functions; use common functions 'dilate', 'erode', 'opening', 'closing', 'whiteTopHat', 'blackTopHat' and 'selfComplementaryTopHat' for filtering both binary and grayscale images
+      \item removed defunct 'getNumberOfFrames' function 
+    }
+  }
+  \subsection{PERFORMANCE IMPROVEMENTS}{
+    \itemize{
+      \item 'readImage': use 'vapply' instead of 'abind' to reduce memory footprint
+    }
+  }
+}
+
+\section{Changes in version 4.14.0}{
+  \subsection{NEW FEATURES}{
+    \itemize{
+      \item 'boundary' argument to 'filter2()' for specifying behaviour at image boundaries
+      \item the 'hist' method now returns a (list of) "histogram"" object(s) (http://stackoverflow.com/q/33831331/2792099 and http://stackoverflow.com/a/35838861/2792099)
+      \item 'colormap()' function for mapping a greyscale image to color using a color palette
+    }
+  }
+  \subsection{PERFORMANCE IMPROVEMENTS}{
+    \itemize{
+      \item 'as.raster.Image': increased performance by not transposing the image twice and improved support for Color images with less than 3 channels
+    }
+  }
+  \subsection{BUG FIXES}{
+    \itemize{
+      \item fixed the 'log' method for Image objects (https://support.bioconductor.org/p/74236/)
+      \item 'affine': fixed handling of images containing an alpha channel (https://support.bioconductor.org/p/74876/)
+      \item retain NA's in morphological operations: 'dilate', 'erode', 'opening', 'closing', 'whiteTopHat', 'blackTopHat', 'selfComplementaryTopHat' (https://support.bioconductor.org/p/77295/)
+      \item fix to potential unsafe code in C function 'affine()' (thanks Tomas Kalibera)
+      \item medianFilter.c: use proper rounding rather than truncation during float to int coercion 
+    }
+  }
+}
+
+\section{Changes in version 4.12.0}{
+  \subsection{NEW FEATURES}{
+    \itemize{
+      \item 'antialias' argument to the function 'affine' allowing to switch off bilinear filtering around image borders
+    }
+  }
+  \subsection{SIGNIFICANT USER-VISIBLE CHANGES}{
+    \itemize{
+      \item deprecated '...GreyScale' morphological functions; use common functions 'dilate', 'erode', 'opening', 'closing', 'whiteTopHat', 'blackTopHat' and 'selfComplementaryTopHat' for filtering both binary and grayscale images
+      \item 'resize' doesn't perform bilinear filtering at image borders anymore in order to prevent the blending of image edges with the background when the image is upscaled; to switch on bilinear sampling at image borders use the function argument 'antialias = TRUE'
+      \item 'floodFill' and 'fillHull' preserve storage mode
+    }
+  }
+  \subsection{PERFORMANCE IMPROVEMENTS}{
+    \itemize{
+      \item all morphological operations use the efficient Urbach-Wilkinson algorithm (up to 3x faster compared to the previous implementation)
+      \item 'rotate': perform lossless 90/180/270 degree rotations by disabling bilinear filtering
+    }
+  }
+  \subsection{BUG FIXES}{
+    \itemize{
+      \item reimplemented the Urbach-Wilkinson algorithm used to perform grayscale morphological transformations
+      \item improved pixel-level accuracy of spatial linear transformations: 'affine', 'resize', 'rotate' and 'translate'
+      \item 'display(..., method = "raster")': displaying of single-channel color images
+      \item 'drawCircle': corrected x-y offset
+      \item 'equalize': in case of a single-valued histogram issue a warning and return its argument
+      \item 'hist': accept images with 'colorMode = Color' containing less than three color channels
+      \item 'image': corrected handling of image frames
+      \item 'medianFilter': filter size check
+      \item 'normalize': normalization of a flat image when the argument 'separate' is set to 'FALSE'
+      \item 'reenumerate': corrected handling of images without background
+      \item 'stackObjects': corrected handling of blank images without any objects
+      \item 'tile': reset dimnames
+    }
+  }
+}
+
+\section{Changes in version 4.10.1}{
+\subsection{SIGNIFICANT USER-VISIBLE CHANGES}{
+    \itemize{
+      \item 'rgbImage' and 'combine' use dimnames if present
+      \item 'makeBrush(..., shape = "line")': the line is drawn symmetrically around the center of the kernel, and the argument 'size' sets the maximum x-y dimension of kernel rather than the line length
+    }
+  }
+  \subsection{BUG FIXES}{
+    \itemize{
+      \item 'drawCircle' and 'stackObjects': corrected handling of color channels
+      \item 'ocontour': doubled the size of the temporary buffer to accommodate longer contours and fixed indexing issue
+    }
+  }
+}
+
+\section{Changes in version 4.10.0}{
+  \subsection{NEW FEATURES}{
+    \itemize{
+      \item 'paintObjects' allows control over behavior at image edges
+      \item 'getFrames' function returning a list of Image objects
+      \item default 'display' method can be set by \code{options("EBImage.display")}
+      \item 'short' argument to the Image 'print' method
+      \item 'equalize' function performing histogram equalization
+    }
+  }
+  \subsection{SIGNIFICANT USER-VISIBLE CHANGES}{
+    \itemize{
+      \item 'translate' moves the image in the opposite direction as before
+      \item 'rotate' returns an image centered in a recalculated bounding box 
+      \item 'as.Image' coerces subclasses to Image objects
+      \item 'getFrame' returns individual channels of a Color image as Grayscale frames
+      \item  'display' method defaults to 'raster' unless used interactively
+      \item 'combine' allows to combine images of the same color mode only
+    }
+  }
+  \subsection{PERFORMANCE IMPROVEMENTS}{
+    \itemize{
+      \item 'filter2': calculate FFT using methods from the 'fftwtools' package
+      \item 'as.nativeRaster': now implemented in C
+      \item 'medianFilter': double to integer conversion moved to C code
+      \item 'hist': fixed binning issues and improved performance
+    }
+  }
+  \subsection{BUG FIXES}{
+    \itemize{
+      \item 'medianFilter': fixed multi-channel image handling; preserve original object class
+      \item 'as.nativeRaster': allow for an arbitrary number of color channels
+      \item 'display': set missing color channels to blank
+    }
+  }
+}
+
+\section{Changes in version 4.8.3}{
+  \subsection{BUG FIXES}{
+    \itemize{
+      \item 'paintObjects': the body of an object with ID=1 was not highlighted when option \code{thick} was set
+    }
+  }
+}
+
+\section{Changes in version 4.8.2}{
+  \subsection{BUG FIXES}{
+    \itemize{
+      \item Clip images in 'as.nativeRaster'
+    }
+  }
+}
+
+\section{Changes in version 4.8.1}{
+  \subsection{SIGNIFICANT USER-VISIBLE CHANGES}{
+    \itemize{
+      \item New sample PNG images
+    }
+  \subsection{BUG FIXES}{
+    \itemize{
+      \item Workaround to broken 'Math2' S4GroupGenerics in R 3.1.2
+    }
+  }
+  }
+}
+
+\section{Changes in version 4.8.0}{
+  \subsection{NEW FEATURES}{
+    \itemize{
+      \item 'otsu' thresholding method (contributed by Philip A. Marais, University of Pretoria, South Africa)
+      \item Support for dimnames in Image objects
+      \item 'bg.col' argument to 'affine' transformations
+      \item 'reenumerate' argument to 'rmObjects'
+      \item 'names' argument to 'readImage'
+      \item 'as.array' method for Image objects
+      \item 'as.nativeRaster' function
+    }
+  }
+  \subsection{SIGNIFICANT USER-VISIBLE CHANGES}{
+    \itemize{
+      \item Performance improvements to 'Image', 'selectChannel', 'combine ' and 'reenumerate'
+      \item Use a more efficient 'nativeRaster' representation in 'displayRaster'
+      \item Cleaner output of the 'show-Image' method; print true object class name and dimorder (if set)
+      \item 'readImage' sets Image dimnames to corresponding file names
+      \item 'filter2' and 'affine' return object of the same class as input
+      \item Renamed 'getNumberOfFrames' to 'numberOfFrames'
+    }
+  }
+  \subsection{BUG FIXES}{
+    \itemize{
+      \item Handling of dimensions of character arrays
+      \item Drawing of grid lines in 'displayRaster'
+      \item Passing of '...' arguments in 'readImage'
+    }
+  }
+}
+
+\section{Changes in version 4.6.0}{
+  \subsection{NEW FEATURES}{
+    \itemize{
+      \item 'toRGB' function for convenient grayscale to RGB conversion
+      \item 'luminance' mode in 'channel' for luminance-preserving RGB to grayscale conversion
+    }
+  }
+  \subsection{SIGNIFICANT USER-VISIBLE CHANGES}{
+    \itemize{
+      \item Performance improvements to: 'Image', 'is.Image', 'readImage', 'writeImage', 'show', 'normalize', 
+      'getFrame', 'selectChannel', 'rgbImage', 'colorLabels', 'flip'/'flop'
+      \item Reduced memory footprint of 'readImage'
+      \item When called on an 'Image' object, 'as.Image' returns its argument rather than the Grayscale-coerced copy  
+      \item 'displayRaster' sets 'par$usr' coordinates to image pixel coordinates easing up plot annotation
+    }
+  }
+  \subsection{BUG FIXES}{
+    \itemize{
+      \item 'getFrame', 'getNumberOfFrames' and 'colorLabels' support multi-dimensional images
+      \item Proper handling of multi-dimensional character arrays by the 'Image' constructor
+      \item Fixed 'getFrame' and 'combine' in case of single-channel Color Images
+      \item Fixed color mode check in 'validImageObject'
+      \item Proper 'fg.col' and 'bg.col' color handling in 'tile'
+      \item Updates to documentation
+    }
+  }
+}
+
+\section{Changes in version 4.4.0}{
+  \subsection{NEW FEATURES}{
+    \itemize{
+      \item New 'colorLabels' function for color-coding labels of object masks by a random permutation (Bernd Fisher)
+      \item Additional 'inputRange' argument to 'normalize' allowing for presetting a limited input intensity range
+      \item Additional 'thick' argument to 'paintObjects' controlling the thickness of boundary contours
+    }
+  }
+  \subsection{SIGNIFICANT USER-VISIBLE CHANGES}{
+    \itemize{
+      \item 'normalize' and 'combine' use the generics from BiocGenerics
+      \item Removed the 'along' argument from 'combine'
+      \item Re-introduced calculation of 's.radius.sd' (standard deviation of the mean radius) in cell features
+    }
+  }
+  \subsection{BUG FIXES}{
+    \itemize{
+      \item XY dimensions equal 1 were dropped by 'getFrame'
+    }
+  }
+}
+
+\section{Changes in version 4.2.0}{
+  \subsection{NEW FEATURES}{
+    \itemize{
+      \item 'localCurvature' function for computing local curvature along a line (J. Barry)
+    }
+  }
+  \subsection{SIGNIFICANT USER-VISIBLE CHANGES}{
+    \itemize{
+      \item The range of pixel coordinates displayed in the JavaScript viewer is now (1,1):(w,h) rather than (0,0):(w-1,h-1) and matches the indices of the corresponding Image array
+    }
+  }
+  \subsection{BUG FIXES}{
+    \itemize{
+      \item 'erode'/'dilate': fixed a bug introduced in the previous version (4.0.0)
+      \item 'resize': new image width was calculated incorrectly when only height was provided (reported by B. Fischer)
+      \item 'medianFilter': incorrect [0:1] <-> integer range conversion (thanks to K. Johnson)
+    }
+  }
+}
+
+\section{Changes in version 4.0.0}{
+  \subsection{NEW FEATURES}{
+    \itemize{
+      \item 'transpose' function for transposing an image by swapping its spatial dimensions
+      \item Grayscale functions for computing the self-complementary top-hat (I. Kats)
+      \item Median filter based on Perreault's constant time median filter (J. Barry)
+    }
+  }
+  \subsection{SIGNIFICANT USER-VISIBLE CHANGES}{
+    \itemize{
+      \item Removed all dependencies towards GTK+ and ImageMagick
+      \item Replaced the former GTK+ based 'display' function by a new one displaying images using either a JavaScript image viewer, or R's built-in raster graphics
+      \item 'readImage' and 'writeImage' now rely on 'jpeg', 'png' and 'tiff' packages and do not depend on ImageMagick any more
+      \item Added support for images containing an alpha channel; both grayscale and color images with an alpha channel are stored as an Image with 'colormode = Color'
+      \item Re-factored the functions, not using ImageMagick any longer: 'translate', 'affine', 'rotate', 'resize'
+      \item Deprecated: 'blur', 'equalize', 'drawtext', 'drawfont', 'getFeatures', 'hullFeatures', 'zernikeMoments', 'edgeProfile', 'edgeFeatures', 'haralickFeatures', 'haralickMatrix', 'moments', 'smoments', 'rmoments', 'cmoments', 'animate', 
+      \item Improved 'getFrame': better performance by reassigning array dimension only when needed
+      \item Modified 'as.raster'
+      \item 'inst/images/lena.gif' is now 'inst/images/lena.png'
+      \item Overhauled the testing procedure in 'tests/test.R'
+      \item Added 'NEWS.Rd'
+    }
+  }
+  \subsection{BUG FIXES}{
+    \itemize{
+      \item 'erode'/'dilate': incorrect range of loop indices caused memory reads from outside the kernel vector
+    }
+  }
+}
diff --git a/inst/doc/EBImage-introduction.R b/inst/doc/EBImage-introduction.R
new file mode 100644
index 0000000..600c8ae
--- /dev/null
+++ b/inst/doc/EBImage-introduction.R
@@ -0,0 +1,348 @@
+## ----setup, echo=FALSE-----------------------------------------------------
+library(knitr)
+.dpi = 100
+set.seed(0)
+opts_chunk$set(comment=NA, fig.align="center", dpi=.dpi)
+knit_hooks$set(crop=NULL)
+.output = output()
+switch(.output,
+        html = opts_chunk$set(fig.retina=1),
+        latex = opts_chunk$set(out.width=".5\\textwidth")
+)
+
+.dev = switch(.output, html="svg", latex="pdf")
+options(EBImage.display = "raster")
+
+## ----installation, eval=FALSE----------------------------------------------
+#  source("http://bioconductor.org/biocLite.R")
+#  biocLite("EBImage")
+
+## ----library, message=FALSE------------------------------------------------
+library("EBImage")
+
+## ----readImage-------------------------------------------------------------
+f = system.file("images", "sample.png", package="EBImage")
+img = readImage(f)
+
+## ----display---------------------------------------------------------------
+display(img, method="browser")
+
+## ----display-raster, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2----
+display(img, method="raster")
+text(x = 20, y = 20, label = "Parrots", adj = c(0,1), col = "orange", cex = 2)
+
+## ----dev-print, eval=FALSE-------------------------------------------------
+#  filename = "parrots.jpg"
+#  dev.print(jpeg, filename = filename , width = dim(img)[1], height = dim(img)[2])
+
+## ----dev-print-pre, echo=FALSE, fig.show='hide', fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2----
+display(img, method="raster")
+text(x = 20, y = 20, label = "Parrots", adj = c(0,1), col = "orange", cex = 2)
+filename = "parrots.jpg"
+dev.print(jpeg, filename = filename , width = dim(img)[1], height = dim(img)[2])
+
+## ----filesize--------------------------------------------------------------
+file.info(filename)$size
+
+## ----dev-print3, echo=FALSE, sfig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2----
+invisible(file.remove(filename))
+
+## ----readImageColor-pre, echo=FALSE----------------------------------------
+imgcol = readImage(system.file("images", "sample-color.png", package="EBImage"))
+
+## ----readImageColor, eval=FALSE--------------------------------------------
+#  imgcol = readImage(system.file("images", "sample-color.png", package="EBImage"))
+#  display(imgcol)
+
+## ----readImageColor-post, echo=FALSE, fig.width=dim(imgcol)[1L]/.dpi, fig.height=dim(imgcol)[2L]/.dpi, dpi=.dpi/2----
+display(imgcol)
+
+## ----readImageMulti-pre, include=FALSE-------------------------------------
+nuc = readImage(system.file("images", "nuclei.tif", package="EBImage"))
+
+## ----readImageMulti, eval=FALSE--------------------------------------------
+#  nuc = readImage(system.file("images", "nuclei.tif", package="EBImage"))
+#  display(nuc, method = "raster", all = TRUE)
+
+## ----readImageMulti-post, echo=FALSE, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi----
+display(nuc, method = "raster", all = TRUE)
+
+## ----displayFrame, echo=FALSE, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi/2----
+display(nuc, method = "raster", frame = 2)
+
+## ----writeImage, eval=FALSE------------------------------------------------
+#  writeImage(imgcol, "sample.jpeg", quality = 85)
+
+## ----str-------------------------------------------------------------------
+str(img)
+
+## ----dim-------------------------------------------------------------------
+dim(img)
+
+## ----imageData-------------------------------------------------------------
+imageData(img)[1:3, 1:6]
+
+## ----as.array--------------------------------------------------------------
+is.Image( as.array(img) )
+
+## ----hist, fig.width=6, fig.height=6, dev=.dev-----------------------------
+hist(img)
+range(img)
+
+## ----show------------------------------------------------------------------
+img
+
+## ----print-----------------------------------------------------------------
+print(img, short=TRUE)
+
+## ----printcol--------------------------------------------------------------
+print(imgcol, short=TRUE)
+
+## ----numberOfFrames--------------------------------------------------------
+numberOfFrames(imgcol, type = "render")
+numberOfFrames(imgcol, type = "total")
+
+## ----nuc-------------------------------------------------------------------
+nuc
+
+## ----colorMode, fig.width=dim(imgcol)[1L]/.dpi, fig.height=dim(imgcol)[2L]/.dpi, dpi=.dpi----
+colorMode(imgcol) = Grayscale
+display(imgcol, all=TRUE)
+
+## ----Image-character, fig.width=7/.dpi, fig.height=7/.dpi, dpi=10*.dpi-----
+colorMat = matrix(rep(c("red","green", "#0000ff"), 25), 5, 5)
+colorImg = Image(colorMat)
+colorImg
+display(colorImg, interpolate=FALSE)
+
+## ----negative, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2----
+img_neg = max(img) - img
+display( img_neg )
+
+## ----arithmetic, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2----
+img_comb = combine(
+  img,
+  img + 0.3,
+  img * 2,
+  img ^ 0.5
+)
+
+display(img_comb, all=TRUE)
+
+## ----cropthreshold-pre, echo=FALSE-----------------------------------------
+img_crop = img[366:749, 58:441]
+img_thresh = img_crop > .5
+
+## ----cropthreshold, eval=FALSE---------------------------------------------
+#  img_crop = img[366:749, 58:441]
+#  img_thresh = img_crop > .5
+#  display(img_thresh)
+
+## ----cropthreshold-post, echo=FALSE, fig.width=dim(img_thresh)[1L]/.dpi, fig.height=dim(img_thresh)[2L]/.dpi, dpi=.dpi/2----
+display(img_thresh)
+
+## ----img_thresh------------------------------------------------------------
+img_thresh
+
+## ----transpose, fig.width=dim(img)[2L]/.dpi, fig.height=dim(img)[1L]/.dpi, dpi=.dpi/2----
+img_t = transpose(img)
+display( img_t )
+
+## ----translate, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2----
+img_translate = translate(img, c(100,-50))
+display(img_translate)
+
+## ----rotate-pre, echo=FALSE------------------------------------------------
+img_rotate = rotate(img, 30, bg.col = "white")
+
+## ----rotate, eval=FALSE----------------------------------------------------
+#  img_rotate = rotate(img, 30, bg.col = "white")
+#  display(img_rotate)
+
+## ----rotate-post, echo=FALSE, fig.width=dim(img_rotate)[1L]/.dpi, fig.height=dim(img_rotate)[2L]/.dpi, dpi=.dpi/2----
+display(img_rotate)
+
+## ----resize-pre, echo=FALSE------------------------------------------------
+img_resize = resize(img, w=256, h=256)
+
+## ----resize, eval=FALSE----------------------------------------------------
+#  img_resize = resize(img, w=256, h=256)
+#  display(img_resize )
+
+## ----resize-post, echo=FALSE, fig.width=dim(img_resize)[1L]/.dpi, fig.height=dim(img_resize)[2L]/.dpi, dpi=.dpi/2----
+display(img_resize)
+
+## ----flipflop, fig.width=2*dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2----
+img_flip = flip(img)
+img_flop = flop(img)
+
+display(combine(img_flip, img_flop), all=TRUE)
+
+## ----affine, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2----
+m =  matrix(c(1, -.5, 128, 0, 1, 0), nrow=3, ncol=2)
+img_affine = affine(img, m)
+display( img_affine )
+
+## ----makeBrush, fig.width=6, fig.height=6, dev=.dev------------------------
+w = makeBrush(size = 31, shape = 'gaussian', sigma = 5)
+plot(w[(nrow(w)+1)/2, ], ylab = "w", xlab = "", cex = 0.7)
+
+## ----lopass, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2----
+img_flo = filter2(img, w)
+display(img_flo)
+
+## ----gblur, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi----
+nuc_gblur = gblur(nuc, sigma = 5)
+display(nuc_gblur, all=TRUE )
+
+## ----highpass, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2----
+fhi = matrix(1, nrow = 3, ncol = 3)
+fhi[2, 2] = -8
+img_fhi = filter2(img, fhi)
+display(img_fhi)
+
+## ----medianFilter, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2----
+l = length(img)
+n = l/10
+pixels = sample(l, n)
+img_noisy = img
+img_noisy[pixels] = runif(n, min=0, max=1)
+display(img_noisy)
+img_median = medianFilter(img_noisy, 1)
+display(img_median)
+
+## ----logo-pre, echo=FALSE--------------------------------------------------
+shapes = readImage(system.file('images', 'shapes.png', package='EBImage'))
+logo = shapes[110:512,1:130]
+
+## ----logo, eval=FALSE------------------------------------------------------
+#  shapes = readImage(system.file('images', 'shapes.png', package='EBImage'))
+#  logo = shapes[110:512,1:130]
+#  display(logo)
+
+## ----logo-post, echo=FALSE, fig.width=dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi----
+display(logo)
+
+## ----kern, fig.width=7/.dpi, fig.height=7/.dpi, dpi=10*.dpi----------------
+kern = makeBrush(5, shape='diamond')
+display(kern, interpolate=FALSE)
+
+## ----morph, fig.width=2*dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi----
+logo_erode= erode(logo, kern)
+logo_dilate = dilate(logo, kern)
+
+display(combine(logo_erode, logo_dilate), all=TRUE)
+
+## ----otsu, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi----
+threshold = otsu(nuc)
+threshold
+nuc_th = combine( mapply(function(frame, th) frame > th, getFrames(nuc), threshold, SIMPLIFY=FALSE) )
+display(nuc_th, all=TRUE)
+
+## ----filter2thresh, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi----
+disc = makeBrush(31, "disc")
+disc = disc / sum(disc)
+offset = 0.05
+nuc_bg = filter2( nuc, disc )
+nuc_th = nuc > nuc_bg + offset
+display(nuc_th, all=TRUE)
+
+## ----thresh, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi----
+display( thresh(nuc, w=15, h=15, offset=0.05), all=TRUE )
+
+## ----bwlabel---------------------------------------------------------------
+logo_label = bwlabel(logo)
+table(logo_label)
+
+## ----max_logolabel---------------------------------------------------------
+max(logo_label)
+
+## ----displaybw, fig.width=dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi----
+display( normalize(logo_label) )
+
+## ----colorCode, fig.width=dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi----
+display( colorLabels(logo_label) )
+
+## ----watershed, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi----
+nmask = watershed( distmap(nuc_th), 2 )
+display(colorLabels(nmask), all=TRUE)
+
+## ----voronoiExample, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi----
+voronoiExamp = propagate(seeds = nmask, x = nmask, lambda = 100)
+voronoiPaint = colorLabels (voronoiExamp)
+display(voronoiPaint)
+
+## ----rmObjects, fig.width=2*dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi----
+objects = list(
+    seq.int(from = 2, to = max(logo_label), by = 2),
+    seq.int(from = 1, to = max(logo_label), by = 2)
+    )
+logos = combine(logo_label, logo_label)
+z = rmObjects(logos, objects, reenumerate=FALSE)
+display(z, all=TRUE)
+
+## ----uniqueIDs-------------------------------------------------------------
+showIds = function(image) lapply(getFrames(image), function(frame) unique(as.vector(frame)))
+
+showIds(z)
+
+## ----reenumeratedIDs-------------------------------------------------------
+showIds( reenumerate(z) )
+
+## ----fillHull, fig.width=dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi----
+filled_logo = fillHull(logo)
+display(filled_logo)
+
+## ----floodFill-logo, fig.width=dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi----
+rgblogo = toRGB(logo)
+points = rbind(c(50, 50), c(100, 50), c(150, 50))
+colors = c("red", "green", "blue")
+rgblogo = floodFill(rgblogo, points, colors)
+display( rgblogo )
+
+## ----floodFill-img, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2----
+display(floodFill(img, rbind(c(200, 300), c(444, 222)), col=0.2, tolerance=0.2))
+
+## ----paintObjects, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2----
+d1 = dim(img)[1:2]
+overlay = Image(dim=d1)
+d2 = dim(logo_label)-1
+
+offset = (d1-d2) %/% 2
+
+overlay[offset[1]:(offset[1]+d2[1]), offset[2]:(offset[2]+d2[2])] = logo_label
+
+img_logo = paintObjects(overlay, toRGB(img), col=c("red", "yellow"), opac=c(1, 0.3), thick=TRUE)
+
+display( img_logo )
+
+## ----load, fig.height=dim(nuc)[2L]/.dpi, fig.width=dim(nuc)[1L]/.dpi, warning=FALSE, dpi=.dpi----
+nuc = readImage(system.file('images', 'nuclei.tif', package='EBImage'))
+cel = readImage(system.file('images', 'cells.tif', package='EBImage'))
+
+cells = rgbImage(green=1.5*cel, blue=nuc)
+display(cells, all = TRUE)
+
+## ----nmask, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi----
+nmask = thresh(nuc, w=10, h=10, offset=0.05)
+nmask = opening(nmask, makeBrush(5, shape='disc'))
+nmask = fillHull(nmask)
+nmask = bwlabel(nmask)
+
+display(nmask, all=TRUE)
+
+## ----ctmask, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi----
+ctmask = opening(cel>0.1, makeBrush(5, shape='disc'))
+cmask = propagate(cel, seeds=nmask, mask=ctmask)
+
+display(ctmask, all=TRUE)
+
+## ----res, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi----
+segmented = paintObjects(cmask, cells, col='#ff00ff')
+segmented = paintObjects(nmask, segmented, col='#ffff00')
+
+display(segmented, all=TRUE)
+
+## ----sessionInfo-----------------------------------------------------------
+sessionInfo()
+
diff --git a/inst/doc/EBImage-introduction.Rmd b/inst/doc/EBImage-introduction.Rmd
new file mode 100644
index 0000000..b25c820
--- /dev/null
+++ b/inst/doc/EBImage-introduction.Rmd
@@ -0,0 +1,729 @@
+---
+title: "Introduction to *EBImage*"
+author: Andrzej Oleś, Gregoire Pau, Oleg Sklyar, Wolfgang Huber
+email: andrzej.oles at embl.de
+package: EBImage
+abstract: >
+  `r Biocpkg("EBImage")` provides general purpose functionality for image processing and analysis. In the context of (high-throughput) microscopy-based cellular assays, EBImage offers tools to segment cells and extract quantitative cellular descriptors. This allows the automation of such tasks using the R programming language and facilitates the use of other tools in the R environment for signal processing, statistical modeling, machine learning and visualization with image data.
+output: 
+  BiocStyle::html_document2:
+    toc_float: true
+graphics: yes
+vignette: >
+  %\VignetteIndexEntry{Introduction to EBImage}
+  %\VignetteKeywords{image processing, visualization}
+  %\VignettePackage{EBImage}
+  %\VignetteEngine{knitr::rmarkdown}
+  %\VignetteEncoding{UTF-8}  
+---
+
+```{r setup, echo=FALSE}
+library(knitr)
+.dpi = 100
+set.seed(0)
+opts_chunk$set(comment=NA, fig.align="center", dpi=.dpi)
+knit_hooks$set(crop=NULL)
+.output = output()
+switch(.output,
+        html = opts_chunk$set(fig.retina=1),
+        latex = opts_chunk$set(out.width=".5\\textwidth")
+)
+
+.dev = switch(.output, html="svg", latex="pdf")
+options(EBImage.display = "raster")
+```
+
+<p>![](logo.png){width=128 style="padding: 0px; margin: auto;"}</p>
+
+# Getting started
+
+`r Biocpkg("EBImage")` is an R package distributed as part of the [Bioconductor](http://bioconductor.org) project. To install the package, start R and enter:
+
+```{r installation, eval=FALSE}
+source("http://bioconductor.org/biocLite.R")
+biocLite("EBImage")
+```
+
+Once `r Rpackage("EBImage")` is installed, it can be loaded by the following command.
+
+```{r library, message=FALSE}
+library("EBImage")
+```
+
+
+# Reading, displaying and writing images
+
+Basic `r Rpackage("EBImage")` functionality includes reading, writing, and displaying of images. Images are read using the function `readImage`, which takes as input a file name or an URL. To start off, let us load a sample picture distributed with the package.
+
+```{r readImage}
+f = system.file("images", "sample.png", package="EBImage")
+img = readImage(f)
+```
+
+`r Rpackage("EBImage")` currently supports three image file formats: `jpeg`, `png` and `tiff`. This list is complemented by the `r Githubpkg("aoles/RBioFormats")` package providing support for a much wider range of file formats including proprietary microscopy image data and metadata.
+
+The image which we just loaded can be visualized by the function `display`.
+
+```{r display}
+display(img, method="browser")
+```
+
+When called from an interactive R session, `display` opens the image in a JavaScript viewer in your web browser. Using the mouse or keyboard shortcuts, you can zoom in and out of the image, pan, and cycle through multiple image frames. Alternatively, the image can be displayed using R's build-in plotting facilities by calling `display` with the argument `method = "raster"`. The image is then drawn on the current device.  This allows to easily combine image data with other plotting functi [...]
+
+```{r display-raster, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+display(img, method="raster")
+text(x = 20, y = 20, label = "Parrots", adj = c(0,1), col = "orange", cex = 2)
+```
+
+The graphics displayed in an R device can be saved using `r Rpackage("base")` R functions `dev.print` or `dev.copy`. For example, lets save our annotated image as a JPEG file and verify its size on disk.
+
+```{r dev-print, eval=FALSE}
+filename = "parrots.jpg"
+dev.print(jpeg, filename = filename , width = dim(img)[1], height = dim(img)[2])
+```{r dev-print-pre, echo=FALSE, fig.show='hide', fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+display(img, method="raster")
+text(x = 20, y = 20, label = "Parrots", adj = c(0,1), col = "orange", cex = 2)
+filename = "parrots.jpg"
+dev.print(jpeg, filename = filename , width = dim(img)[1], height = dim(img)[2])
+```{r filesize}
+file.info(filename)$size
+```{r dev-print3, echo=FALSE, sfig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+invisible(file.remove(filename))
+```
+
+If R is not running interactively, e.g. for code in a package vignette, `"raster"` becomes the default method in `display`.
+The default behavior of `display` can be overridden globally be setting the `"options("EBImage.display")` to either `"browser"` or `"raster"`. This is useful, for example, to preview images inside RStudio.
+
+It is also possible to read and view color images,
+
+```{r readImageColor-pre, echo=FALSE}
+imgcol = readImage(system.file("images", "sample-color.png", package="EBImage"))
+```{r readImageColor, eval=FALSE}
+imgcol = readImage(system.file("images", "sample-color.png", package="EBImage"))
+display(imgcol)
+```{r readImageColor-post, echo=FALSE, fig.width=dim(imgcol)[1L]/.dpi, fig.height=dim(imgcol)[2L]/.dpi, dpi=.dpi/2}
+display(imgcol)
+```
+
+or images containing several frames. If an image consists of multiple frames, they can be displayed all at once in a grid
+arrangement by specifying the function argument `all = TRUE`,
+
+```{r readImageMulti-pre, include=FALSE}
+nuc = readImage(system.file("images", "nuclei.tif", package="EBImage"))
+```{r readImageMulti, eval=FALSE}
+nuc = readImage(system.file("images", "nuclei.tif", package="EBImage"))
+display(nuc, method = "raster", all = TRUE)
+```{r readImageMulti-post, echo=FALSE, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+display(nuc, method = "raster", all = TRUE)
+```
+
+or we can just view a single frame, for example, the second one.
+
+```{r displayFrame, echo=FALSE, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi/2}
+display(nuc, method = "raster", frame = 2)
+```
+
+Images can be saved to files using the `writeImage` function.  The image that we loaded was a `r toupper(strsplit(basename(f), split=".", fixed=TRUE)[[1L]][2L])` file; suppose now that we want to save this image as a JPEG
+file.  The JPEG format allows to set a quality value between 1 and 100 for its
+compression algorithm.  The default value of the `quality` argument of
+`writeImage` is 100, here we use a smaller value, leading to smaller file size
+at the cost of some reduction in image quality.
+
+```{r writeImage, eval=FALSE}
+writeImage(imgcol, "sample.jpeg", quality = 85)
+```
+
+Similarly, we could have saved the image as a TIFF file and set which 
+compression algorithm we want to use. For a complete list of available parameters see `?writeImage`.
+
+
+# Image data representation
+
+`r Rpackage("EBImage")` uses a package-specific class `Image` to store and process images. It extends the R base class `array`, and all `r Rpackage("EBImage")` functions can also be called directly on matrices and arrays. You can find out more about this class by typing `?Image`.  Let us peek into the internal structure of an `Image` object.
+
+```{r str}
+str(img)
+```
+
+The `.Data` slot contains a numeric array of pixel intensities. We see that in this case the array is two-dimensional, with `r dim(img)[1L]` times `r dim(img)[2L]` elements, and corresponds to the pixel width and height of the image. These dimensions can be accessed using the `dim` function, just like for regular arrays.
+
+```{r dim}
+dim(img)
+```
+
+Image data can be accessed as a plain R `array` using the `imageData` accessor,
+
+```{r imageData}
+imageData(img)[1:3, 1:6]
+```
+
+and the `as.array` method can be used to coerce an `Image` to an `array`.
+
+```{r as.array}
+is.Image( as.array(img) )
+```
+
+The distribution of pixel intensities can be plotted in a histogram, and their range inspected using the `range` function.
+
+```{r hist, fig.width=6, fig.height=6, dev=.dev}
+hist(img)
+range(img)
+```
+
+A useful summary of `Image` objects is also provided by the `show`
+method, which is invoked if we simply type the object's name.
+
+```{r show}
+img
+```
+
+For a more compact representation without the preview of the intensities array use the `print` method with the argument `short` set to `TRUE`.
+
+```{r print}
+print(img, short=TRUE)
+```
+
+Let's now have a closer look a our color image.
+
+```{r printcol}
+print(imgcol, short=TRUE)
+```
+
+It differs from its grayscale counterpart `img` by the property `colorMode` and the number of dimensions. 
+The `colorMode` slot turns out to be convenient when dealing with stacks of images.  If
+it is set to `Grayscale`, then the third and all higher dimensions of the
+array are considered as separate image frames corresponding, for instance, to
+different z-positions, time points, replicates, etc.  On the other hand, if
+`colorMode` is `Color`, then the third dimension is assumed to hold
+different color channels, and only the fourth and higher dimensions---if present---are
+used for multiple image frames. `imgcol` contains three color channels, which
+correspond to the red, green and blue intensities of the photograph.  However, this does
+not necessarily need to be the case, and the number of color channels is arbitrary.
+
+The "frames.total" and "frames.render" fields shown by the object summary correspond to the total number of frames contained in the image, and to the number of rendered frames. These numbers can be accessed using the function `numberOfFrames` by specifying the `type` argument.
+
+```{r numberOfFrames}
+numberOfFrames(imgcol, type = "render")
+numberOfFrames(imgcol, type = "total")
+```
+
+Image frames can be extracted using `getFrame` and `getFrames`. `getFrame` returns the i-th frame contained in the image y. If `type` is `"total"`, the function is unaware of the color mode and returns an xy-plane. For `type="render"` the function returns the i-th image as shown by the display function. While `getFrame` returns just a single frame, `getFrames` retrieves a list of frames which can serve as input to `lapply`-family functions. See the "Global thresholding" section for an il [...]
+
+Finally, if we look at our cell data,
+
+```{r nuc}
+nuc
+```
+we see that it contains 4 total frames that correspond to the 4 separate greyscale images, as indicated by "frames.render".
+
+
+# Color management
+
+As described in the previous section, the class `Image` extends the base class `array` and uses
+`colorMode` to store how the color information of the multi-dimensional data should be handled. 
+The function `colorMode` can be used to access and change this property,
+modifying the rendering mode of an image. For example, if we take a `Color` image and change its 
+mode to `Grayscale`, then the image won't display as a single color image anymore but rather as three separate 
+grayscale frames corresponding to the red, green and blue channels. The function `colorMode` does not change
+the actual content of the image but only changes the way the image is rendered by `r Rpackage("EBImage")`.
+
+```{r colorMode, fig.width=dim(imgcol)[1L]/.dpi, fig.height=dim(imgcol)[2L]/.dpi, dpi=.dpi}
+colorMode(imgcol) = Grayscale
+display(imgcol, all=TRUE)
+```
+
+Color space conversions between `Grayscale` and `Color` images are performed using the function `channel`.
+It has a flexible interface which allows to convert either way between the modes, and can be used 
+to extract color channels. Unlike `colorMode`, `channel` changes the pixel intensity values of the image.
+
+`Color` to `Grayscale` conversion modes include taking a uniform average across the RGB channels,
+and a weighted luminance preserving conversion mode better suited for display purposes.
+
+The `asred`, `asgreen` and `asblue` modes convert a grayscale image or array into a color image of the specified hue. 
+
+The convenience function `toRGB` promotes a grayscale image to RGB color space by replicating it across the red, green and blue channels, 
+which is equivalent to calling `channel` with mode set to `rgb`. When displayed, this image doesn't look different from its grayscale origin, which is expected because the information between the color channels is the same. To combine three grayscale images into a single rgb image use the function `rgbImage`.
+
+The function `Image` can be used to construct a color image from a character vector or array of named R colors (as listed by `colors()`) and/or hexadecimal strings of the form "\#rrggbb" or "\#rrggbbaa".
+
+```{r Image-character, fig.width=7/.dpi, fig.height=7/.dpi, dpi=10*.dpi}
+colorMat = matrix(rep(c("red","green", "#0000ff"), 25), 5, 5)
+colorImg = Image(colorMat)
+colorImg
+display(colorImg, interpolate=FALSE)
+```
+
+
+# Manipulating images
+
+Being numeric arrays, images can be conveniently manipulated by any of R's arithmetic operators. For example, we can produce a negative image by simply subtracting the image from its maximum value.
+
+```{r negative, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+img_neg = max(img) - img
+display( img_neg )
+```
+
+We can also increase the brightness of an image through addition, adjust the contrast through multiplication, and apply gamma correction through exponentiation.
+
+```{r arithmetic, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+img_comb = combine(
+  img,
+  img + 0.3,
+  img * 2,
+  img ^ 0.5
+)
+
+display(img_comb, all=TRUE)
+```
+
+In the example above we have used `combine` to merge individual images into a single multi-frame image object.
+
+Furthermore, we can crop and threshold images with standard matrix operations.
+```{r cropthreshold-pre, echo=FALSE}
+img_crop = img[366:749, 58:441]
+img_thresh = img_crop > .5
+```{r cropthreshold, eval=FALSE}
+img_crop = img[366:749, 58:441]
+img_thresh = img_crop > .5
+display(img_thresh)
+```{r cropthreshold-post, echo=FALSE, fig.width=dim(img_thresh)[1L]/.dpi, fig.height=dim(img_thresh)[2L]/.dpi, dpi=.dpi/2}
+display(img_thresh)
+```
+
+The thresholding operation returns an `Image` object with binarized pixels values. 
+The R data type used to store such an image is `logical`.
+```{r img_thresh}
+img_thresh
+```
+
+For image transposition, use `transpose` rather than R's `r Rpackage("base")` function `t`. This is because the former one works also on color and multiframe images by swapping its spatial dimensions.
+
+```{r transpose, fig.width=dim(img)[2L]/.dpi, fig.height=dim(img)[1L]/.dpi, dpi=.dpi/2}
+img_t = transpose(img)
+display( img_t )
+```
+
+
+# Spatial transformations
+
+We just saw one type of spatial transformation, transposition, but there are many more, for example translation, rotation, reflection and scaling. `translate` moves the image plane by the specified two-dimensional vector in such a way that pixels that end up outside the image region are cropped, and pixels that enter into the image region are set to background.
+```{r translate, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+img_translate = translate(img, c(100,-50))
+display(img_translate)
+```
+
+The background color can be set using the argument `bg.col` common to all relevant spatial transformation functions. The default sets the value of  background pixels to zero which corresponds to black. Let us demonstrate the use of this argument with `rotate` which rotates the image clockwise by the given angle.
+```{r rotate-pre, echo=FALSE}
+img_rotate = rotate(img, 30, bg.col = "white")
+```{r rotate, eval=FALSE}
+img_rotate = rotate(img, 30, bg.col = "white")
+display(img_rotate)
+```{r rotate-post, echo=FALSE, fig.width=dim(img_rotate)[1L]/.dpi, fig.height=dim(img_rotate)[2L]/.dpi, dpi=.dpi/2}
+display(img_rotate)
+```
+
+To scale an image to desired dimensions use `resize`. If you provide only one of either width or height, the other dimension is automatically computed keeping the original aspect ratio. 
+```{r resize-pre, echo=FALSE}
+img_resize = resize(img, w=256, h=256)
+```{r resize, eval=FALSE}
+img_resize = resize(img, w=256, h=256)
+display(img_resize )
+```{r resize-post, echo=FALSE, fig.width=dim(img_resize)[1L]/.dpi, fig.height=dim(img_resize)[2L]/.dpi, dpi=.dpi/2}
+display(img_resize)
+```
+
+The functions `flip` and  `flop` reflect the image around the image horizontal and vertical axis, respectively.
+
+```{r flipflop, fig.width=2*dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+img_flip = flip(img)
+img_flop = flop(img)
+
+display(combine(img_flip, img_flop), all=TRUE)
+```
+
+Spatial linear transformations are implemented using the general `affine` transformation. It maps image pixel coordinates `px` using a 3x2 transformation matrix `m` in the following way: `cbind(px, 1) %*% m`. For example, horizontal sheer mapping can be applied by
+
+```{r affine, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+m =  matrix(c(1, -.5, 128, 0, 1, 0), nrow=3, ncol=2)
+img_affine = affine(img, m)
+display( img_affine )
+```
+
+
+# Filtering
+
+## Linear filters
+
+A common preprocessing step involves cleaning up the images by removing
+local artifacts or noise through smoothing.  An intuitive approach is to
+define a window of a selected size around each pixel and average the values within that
+neighborhood. After applying this procedure to all pixels, the new, smoothed image is obtained.
+Mathematically, this can be expressed as
+$$
+f'(x,y) = \frac{1}{N} \sum_{s=-a}^{a}\sum_{t=-a}^{a} f(x+s, y+t),
+$$
+where $f(x,y)$ is the value of the pixel at position $(x, y)$, and $a$ determines the
+window size, which is $2a+1$ in each direction.  $N=(2a+1)^2$ is the number of pixels
+averaged over, and $f'$ is the new, smoothed image.
+
+More generally, we can replace the moving average by a weighted average, using a weight
+function $w$, which typically has the highest value at the window midpoint ($s=t=0$) and then
+decreases towards the edges. 
+$$
+(w * f)(x,y) = \sum_{s=-\infty}^{+\infty} \sum_{t=-\infty}^{+\infty} w(s,t)\, f(x+s, y+s)
+$$
+For notational convenience, we let the summations range from $-\infty$ to $+\infty$, even if in practice the sums are finite 
+and $w$ has only a finite number of non-zero values. In fact, we can think of the weight function $w$ as another image,
+and this operation is also called the *convolution* of the images $f$ and $w$, indicated by the the symbol $*$.
+Convolution is a linear operation in the sense that $w*(c_1f_1+c_2f_2)=c_1w*f_1 + c_2w*f_2$
+for any two images $f_1$, $f_2$ and numbers $c_1$, $c_2$.
+
+
+In `r Biocpkg("EBImage")`, the 2-dimensional convolution is implemented by the function `filter2`, and the auxiliary 
+function `makeBrush` can be used to generate the weight function. 
+In fact, `filter2` does not directly perform the summation indicated in the equation above.
+Instead, it uses the Fast Fourier Transformation in a way that is mathematically equivalent 
+but computationally more efficient.
+
+```{r makeBrush, fig.width=6, fig.height=6, dev=.dev}
+w = makeBrush(size = 31, shape = 'gaussian', sigma = 5)
+plot(w[(nrow(w)+1)/2, ], ylab = "w", xlab = "", cex = 0.7)
+```{r lopass, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+img_flo = filter2(img, w)
+display(img_flo)
+```
+
+Here we have used a Gaussian filter of width 5 given by `sigma`. 
+Other available filter shapes include `"box"` (default), `"disc"`, `"diamond"` and `"line"`, for some of which the kernel can be binary; see `?makeBrush` for details.
+
+If the filtered image contains multiple frames, the filter is applied to each frame separately. For convenience, images can be also smoothed using the wrapper function `gblur` which performs Gaussian smoothing with the filter size automatically adjusted to `sigma`. 
+
+```{r gblur, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+nuc_gblur = gblur(nuc, sigma = 5)
+display(nuc_gblur, all=TRUE )
+```
+
+In signal processing the operation of smoothing an image is referred to as low-pass filtering. High-pass filtering is the opposite operation which allows to detect edges and sharpen images. This can be done, for instance, using a Laplacian filter.
+
+```{r highpass, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+fhi = matrix(1, nrow = 3, ncol = 3)
+fhi[2, 2] = -8
+img_fhi = filter2(img, fhi)
+display(img_fhi)
+```
+
+## Median filter
+
+Another approach to perform noise reduction is to apply a median filter, which is a non-linear technique as opposed to the low pass convolution filter described in the previous section. Median filtering is particularly effective in the case of speckle noise, and has the advantage of removing noise while preserving edges. 
+
+The local median filter works by scanning the image pixel by pixel, replacing each pixel by the median on of its neighbors inside a window of specified size. This filtering technique is provided in `r Rpackage("EBImage")` by the function `medianFilter`. We demonstrate its use by first corrupting the image with uniform noise, and reconstructing the original image by median filtering.
+
+```{r medianFilter, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+l = length(img)
+n = l/10
+pixels = sample(l, n)
+img_noisy = img
+img_noisy[pixels] = runif(n, min=0, max=1)
+display(img_noisy)
+img_median = medianFilter(img_noisy, 1)
+display(img_median)
+```
+
+## Morphological operations
+
+Binary images are images which contain only two sets of pixels, with values, say 0 and 1, representing the background and foreground pixels. Such images are subject to several non-linear morphological operations: erosion, dilation, opening, and closing. These operations work by overlaying a mask, called the structuring element, over the binary image in the following way:
+
+* erosion: For every foreground pixel, put the mask around it, and if any pixel covered by the mask is from the background, set the pixel to background.
+
+* dilation: For every background pixel, put the mask around it, and if any pixel covered by the mask is from the foreground, set the pixel to foreground.
+
+```{r logo-pre, echo=FALSE}
+shapes = readImage(system.file('images', 'shapes.png', package='EBImage'))
+logo = shapes[110:512,1:130]
+```{r logo, eval=FALSE}
+shapes = readImage(system.file('images', 'shapes.png', package='EBImage'))
+logo = shapes[110:512,1:130]
+display(logo)
+```{r logo-post, echo=FALSE, fig.width=dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi}
+display(logo)
+```
+
+```{r kern, fig.width=7/.dpi, fig.height=7/.dpi, dpi=10*.dpi}
+kern = makeBrush(5, shape='diamond')
+display(kern, interpolate=FALSE)
+```
+
+```{r morph, fig.width=2*dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi}
+logo_erode= erode(logo, kern)
+logo_dilate = dilate(logo, kern)
+
+display(combine(logo_erode, logo_dilate), all=TRUE)
+```
+
+Opening and closing are combinations of the two operations above: opening performs erosion followed by dilation, while closing does the opposite, i.e, performs dilation followed by erosion. Opening is useful for morphological noise removal, as it removes small objects from the background, and closing can be used to fill small holes in the foreground. These operations are implemented by `opening` and `closing`.
+
+
+# Thresholding
+
+## Global thresholding
+
+In the "Manipulating images" section we have already demonstrated how to set a global threshold on an image.
+There we used an arbitrary cutoff value.
+For images whose distribution of pixel intensities follows a bi-modal histogram a more systematic approach involves using the Otsu's method. Otsu's method is a technique to automatically perform clustering-based image thresholding. Assuming a bi-modal intensity distribution, the algorithm separates image pixels into foreground and background. The optimal threshold value is determined by minimizing the combined intra-class variance.
+
+Otsu's threshold can be calculated using the function `otsu`. When called on a multi-frame image, the threshold is calculated for each frame separately resulting in a output vector of length equal to the total number of frames in the image.
+
+```{r otsu, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+threshold = otsu(nuc)
+threshold
+nuc_th = combine( mapply(function(frame, th) frame > th, getFrames(nuc), threshold, SIMPLIFY=FALSE) )
+display(nuc_th, all=TRUE)
+```
+
+Note the use of `getFrames` to split the image into a list of individual frames, and `combine` to merge the results back together.
+
+## Adaptive thresholding
+
+The idea of adaptive thresholding is that, compared to straightforward thresholding from 
+the previous section, the threshold is allowed to be different in different
+regions of the image. In this way, one can anticipate spatial dependencies of the
+underlying background signal caused, for instance, by uneven illumination or by stray
+signal from nearby bright objects.
+
+Adaptive thresholding works by comparing each pixel's intensity to the background determined from a 
+local neighbourhood. This can be achieved by comparing the image to its smoothed version, where the filtering
+window is bigger than the typical size of objects we want to capture.
+
+```{r filter2thresh, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+disc = makeBrush(31, "disc")
+disc = disc / sum(disc)
+offset = 0.05
+nuc_bg = filter2( nuc, disc )
+nuc_th = nuc > nuc_bg + offset
+display(nuc_th, all=TRUE)
+```
+
+This technique assumes that the objects are relatively sparsely
+distributed in the image, so that the signal distribution in the neighborhood is dominated
+by background. While for the nuclei in our images this assumption makes sense, for other
+situations you may need to make different assumptions. The adaptive thresholding 
+using a linear filter with a rectangular box is provided by `thresh`,
+which uses a faster implementation compared to directly using `filter2`.
+
+```{r thresh, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+display( thresh(nuc, w=15, h=15, offset=0.05), all=TRUE )
+```
+
+
+# Image segmentation
+
+Image segmentation performs partitioning of an image, and is typically used to identify objects in an image. Non-touching connected objects can be segmented using the function `bwlabel`, while `watershed` and `propagate` use more sophisticated algorithms able to separate objects which touch each other.
+
+`bwlabel` finds every connected set of pixels other than the background, and relabels these sets with a unique increasing integer. It can be called on a thresholded binary image in order to extract objects.
+
+```{r bwlabel}
+logo_label = bwlabel(logo)
+table(logo_label)
+```
+
+The pixel values of the `logo_label` image range from 0 corresponding to background to the number of objects it contains, which is given by
+
+```{r max_logolabel}
+max(logo_label)
+```
+
+To display the image we normalize it to the (0,1) range expected by the display function. This results in different objects being rendered with a different shade of gray.
+
+```{r displaybw, fig.width=dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi}
+display( normalize(logo_label) )
+```
+
+The horizontal grayscale gradient which can be observed reflects to the way `bwlabel` scans the image and labels the connected sets: from left to right and from top to bottom. Another way of visualizing the segmentation is to use the `colorLabels` function, which color codes the objects by a random permutation of unique colors.
+
+```{r colorCode, fig.width=dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi}
+display( colorLabels(logo_label) )
+```
+
+## Watershed 
+
+Some of the nuclei in `nuc` are quite close to each other and get merged into one big object when thresholded, as seen in `nuc_th`.
+`bwlabel` would incorrectly identify them as a single object. The watershed transformation allows to overcome this issue.
+The `watershed` algorithm treats a grayscale image as a topographic relief, or heightmap. Objects that stand out of the background
+are identified and separated by flooding an inverted source image. In case of a binary image its distance map  can serve as the input heightmap. The distance map, which contains for each pixel the distance to the nearest background pixel, can be obtained by `distmap`.
+
+```{r watershed, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+nmask = watershed( distmap(nuc_th), 2 )
+display(colorLabels(nmask), all=TRUE)
+```
+
+## Voronoi tesselation
+
+Voronoi tessellation is useful when we have a set of seed points (or regions) and want to
+partition the space that lies between these seeds in such a way that each point in the
+space is assigned to its closest seed. This function is implemented in `r Rpackage("EBImage")` by the function `propagate`.
+Let us illustrate the concept of Voronoi tessalation on a basic example. We use the nuclei mask `nmask` as seeds and
+partition the space between them.
+
+```{r voronoiExample, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+voronoiExamp = propagate(seeds = nmask, x = nmask, lambda = 100)
+voronoiPaint = colorLabels (voronoiExamp)
+display(voronoiPaint)
+```
+
+The basic definition of Voronoi tessellation, which we have given above, allows
+for two generalizations:
+
+* By default, the space that we partition is the full, rectangular image area, but indeed we could 
+    restrict ourselves to any arbitrary subspace. This is akin to finding the shortest distance from each point to the next seed
+    not in a simple flat landscape, but in a landscape that is interspersed by lakes and rivers (which you cannot cross), 
+    so that all paths need to remain on the land. `propagate` allows for this generalization through its 
+    `mask` argument. 
+
+* By default, we think of the space as flat -- but in fact it could have hills and canyons, so that 
+    the distance between two points in the landscape not only depends on their x- and y-positions but also on 
+    the ascents and descents, up and down in z-direction, 
+    that lie in between. You can specify such a landscape to `propagate` through its `x` argument.  
+      
+Mathematically, we can say that instead of the simple default case (a flat rectangle image with an Euclidean metric), we perform the Voronoi segmentation on a Riemann manifold, which
+can have an arbitrary shape and an arbitrary metric.
+Let us use the notation $x$ and $y$ for the column and row coordinates
+of the image, and $z$ for the elevation of the landscape. For two neighboring points, defined
+by coordinates $(x, y, z)$ and $(x+dx, y+dy, z+dz)$, the distance between them is given by
+$$
+ds = \sqrt{ \frac{2}{\lambda+1} \left[ \lambda \left( dx^2 + dy^2 \right) + dz^2 \right] }.
+$$
+For $\lambda=1$, this reduces to $ds = ( dx^2 + dy^2 + dz^2)^{1/2}$.
+Distances between points further apart are obtained by summing $ds$ along the shortest path between them. 
+The parameter $\lambda\ge0$ has been introduced as a convenient control of the relative weighting between sideways movement
+(along the $x$ and $y$ axes) and vertical movement.  Intuitively, if you imagine yourself
+as a hiker in such a landscape, by choosing $\lambda$ you can specify how much you are prepared to climb up
+and down to overcome a mountain, versus sideways walking around it.
+When $\lambda$ is large, the expression becomes equivalent to $ds = \sqrt{dx^2 + dy^2}$, i. e., 
+the importance of $dz$ becomes negligible. This is what we did when we used `lambda = 100` in our `propagate` example.
+
+A more advanced application of `propagate` to the segmentation of cell bodies is presented in the "Cell segmentation example" section.
+
+
+# Object manipulation
+
+## Object removal
+
+`r Rpackage("EBImage")` defines an object mask as a set of pixels with the same unique integer value. Typically, images containing object masks are the result of segmentation functions such as `bwalabel`, `watershed`, or `propagate`. Objects can be removed from such images by `rmObject`, which deletes objects from the mask simply by setting their pixel values to 0. By default, after object removal all the remaining objects are relabeled so that the highest object ID corresponds to the nu [...]
+```{r rmObjects, fig.width=2*dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi}
+objects = list(
+    seq.int(from = 2, to = max(logo_label), by = 2),
+    seq.int(from = 1, to = max(logo_label), by = 2)
+    )
+logos = combine(logo_label, logo_label)
+z = rmObjects(logos, objects, reenumerate=FALSE)
+display(z, all=TRUE)
+```
+
+In the example above we demonstrate how the object removal function can be applied to a multi-frame image by providing a list of object indicies to be removed from each frame. Additionally we have set `reenumerate` to `FALSE` keeping the original object IDs.
+
+```{r uniqueIDs}
+showIds = function(image) lapply(getFrames(image), function(frame) unique(as.vector(frame)))
+
+showIds(z)
+```
+
+Recall that 0 stands for the background. If at some stage we decide to relabel the objects, we can use for this the standalone function `reenumarate`.
+
+```{r reenumeratedIDs}
+showIds( reenumerate(z) )
+```
+
+## Filling holes and regions
+
+Holes in object masks can be filled using the function `fillHull`.
+
+```{r fillHull, fig.width=dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi}
+filled_logo = fillHull(logo)
+display(filled_logo)
+```
+
+`floodFill` fills a region of an image with a specified color. The filling starts at the given point, and the filling region is expanded to a connected area in which the absolute difference in pixel intensities remains below `tolerance`. The color specification uses R color names for `Color` images, and numeric values for `Grayscale` images.
+
+```{r floodFill-logo, fig.width=dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi}
+rgblogo = toRGB(logo)
+points = rbind(c(50, 50), c(100, 50), c(150, 50))
+colors = c("red", "green", "blue")
+rgblogo = floodFill(rgblogo, points, colors)
+display( rgblogo )
+```{r floodFill-img, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+display(floodFill(img, rbind(c(200, 300), c(444, 222)), col=0.2, tolerance=0.2))
+```
+
+## Highlighting objects
+
+Given an image containing object masks,
+the function `paintObjects` can be used to highlight the objects from the mask in the target image provided in the `tgt` argument. 
+Objects can be outlined and filled with colors of given opacities specified in the `col` and `opac` arguments, respectively. If the color specification is missing or equals `NA` it is not painted.
+
+
+```{r paintObjects, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+d1 = dim(img)[1:2]
+overlay = Image(dim=d1)
+d2 = dim(logo_label)-1
+
+offset = (d1-d2) %/% 2
+
+overlay[offset[1]:(offset[1]+d2[1]), offset[2]:(offset[2]+d2[2])] = logo_label
+
+img_logo = paintObjects(overlay, toRGB(img), col=c("red", "yellow"), opac=c(1, 0.3), thick=TRUE)
+
+display( img_logo )
+```
+
+In the example above we have created a new mask `overlay` matching the size of our target image `img`, and copied the mask containing the "EBImage" logo into that overlay mask. The output of `paintObjects` retains the color mode of its target image, therefore in order to have the logo highlighted in color it was necessary to convert `img` to an RGB image first, otherwise the result would be a grayscale image. The `thick` argument controls the object contour drawing: if set to `FALSE`, on [...]
+
+
+# Cell segmentation example
+
+We conclude our vignette by applying the functions described before to the task of segmenting cells. Our goal is to computationally identify and qualitatively characterize the cells in the sample fluorescent microscopy images. Even though this by itself may seem a modest goal, this approach can be applied to collections containing thousands of images, an that need no longer to be an modest aim!
+
+We start by loading the images of nuclei and cell bodies. To visualize the cells we overlay these images as the green and the blue channel of a false-color image.
+
+```{r load, fig.height=dim(nuc)[2L]/.dpi, fig.width=dim(nuc)[1L]/.dpi, warning=FALSE, dpi=.dpi}
+nuc = readImage(system.file('images', 'nuclei.tif', package='EBImage'))
+cel = readImage(system.file('images', 'cells.tif', package='EBImage'))
+
+cells = rgbImage(green=1.5*cel, blue=nuc)
+display(cells, all = TRUE)
+```
+
+First, we segment the nuclei using `thresh`, `fillHull`, `bwlabel`
+and `opening`.
+
+```{r nmask, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+nmask = thresh(nuc, w=10, h=10, offset=0.05)
+nmask = opening(nmask, makeBrush(5, shape='disc'))
+nmask = fillHull(nmask)
+nmask = bwlabel(nmask)
+
+display(nmask, all=TRUE)
+```
+
+Next, we use the segmented nuclei as seeds in the Voronoi segmentation of the cytoplasm.
+
+```{r ctmask, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+ctmask = opening(cel>0.1, makeBrush(5, shape='disc'))
+cmask = propagate(cel, seeds=nmask, mask=ctmask)
+
+display(ctmask, all=TRUE)
+```
+
+To visualize our segmentation on the we use `paintObject`.
+
+```{r res, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+segmented = paintObjects(cmask, cells, col='#ff00ff')
+segmented = paintObjects(nmask, segmented, col='#ffff00')
+
+display(segmented, all=TRUE)
+```
+
+# Session Info
+
+```{r sessionInfo}
+sessionInfo()
+```
diff --git a/inst/doc/EBImage-introduction.html b/inst/doc/EBImage-introduction.html
new file mode 100644
index 0000000..8899aa2
--- /dev/null
+++ b/inst/doc/EBImage-introduction.html
@@ -0,0 +1,782 @@
+<!DOCTYPE html>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+
+<meta charset="utf-8" />
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="generator" content="pandoc" />
+
+
+<meta name="author" content="Andrzej Oleś, Gregoire Pau, Oleg Sklyar, Wolfgang Huber" />
+
+<meta name="date" content="2017-10-30" />
+
+<title>Introduction to EBImage</title>
+
+<script src="data:application/x-javascript;base64,LyohIGpRdWVyeSB2MS4xMS4zIHwgKGMpIDIwMDUsIDIwMTUgalF1ZXJ5IEZvdW5kYXRpb24sIEluYy4gfCBqcXVlcnkub3JnL2xpY2Vuc2UgKi8KIWZ1bmN0aW9uKGEsYil7Im9iamVjdCI9PXR5cGVvZiBtb2R1bGUmJiJvYmplY3QiPT10eXBlb2YgbW9kdWxlLmV4cG9ydHM/bW9kdWxlLmV4cG9ydHM9YS5kb2N1bWVudD9iKGEsITApOmZ1bmN0aW9uKGEpe2lmKCFhLmRvY3VtZW50KXRocm93IG5ldyBFcnJvcigialF1ZXJ5IHJlcXVpcmVzIGEgd2luZG93IHdpdGggYSBkb2N1bWVudCIpO3JldHVybiBiKGEpfTpiKGEpfSgidW5kZWZpbmVkIiE9dHlwZW9mIHdpbmRvdz93aW5kb3c6dG [...]
+<meta name="viewport" content="width=device-width, initial-scale=1" />
+<link href="data:text/css;charset=utf-8,html%7Bfont%2Dfamily%3Asans%2Dserif%3B%2Dwebkit%2Dtext%2Dsize%2Dadjust%3A100%25%3B%2Dms%2Dtext%2Dsize%2Dadjust%3A100%25%7Dbody%7Bmargin%3A0%7Darticle%2Caside%2Cdetails%2Cfigcaption%2Cfigure%2Cfooter%2Cheader%2Chgroup%2Cmain%2Cmenu%2Cnav%2Csection%2Csummary%7Bdisplay%3Ablock%7Daudio%2Ccanvas%2Cprogress%2Cvideo%7Bdisplay%3Ainline%2Dblock%3Bvertical%2Dalign%3Abaseline%7Daudio%3Anot%28%5Bcontrols%5D%29%7Bdisplay%3Anone%3Bheight%3A0%7D%5Bhidden%5D%2Ctem [...]
+<script src="data:application/x-javascript;base64,LyohCiAqIEJvb3RzdHJhcCB2My4zLjUgKGh0dHA6Ly9nZXRib290c3RyYXAuY29tKQogKiBDb3B5cmlnaHQgMjAxMS0yMDE1IFR3aXR0ZXIsIEluYy4KICogTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBsaWNlbnNlCiAqLwppZigidW5kZWZpbmVkIj09dHlwZW9mIGpRdWVyeSl0aHJvdyBuZXcgRXJyb3IoIkJvb3RzdHJhcCdzIEphdmFTY3JpcHQgcmVxdWlyZXMgalF1ZXJ5Iik7K2Z1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1hLmZuLmpxdWVyeS5zcGxpdCgiICIpWzBdLnNwbGl0KCIuIik7aWYoYlswXTwyJiZiWzFdPDl8fDE9PWJbMF0mJjk9PWJbMV0mJmJbMl08MSl0aHJvdy [...]
+<script src="data:application/x-javascript;base64,LyoqCiogQHByZXNlcnZlIEhUTUw1IFNoaXYgMy43LjIgfCBAYWZhcmthcyBAamRhbHRvbiBAam9uX25lYWwgQHJlbSB8IE1JVC9HUEwyIExpY2Vuc2VkCiovCi8vIE9ubHkgcnVuIHRoaXMgY29kZSBpbiBJRSA4CmlmICghIXdpbmRvdy5uYXZpZ2F0b3IudXNlckFnZW50Lm1hdGNoKCJNU0lFIDgiKSkgewohZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEsYil7dmFyIGM9YS5jcmVhdGVFbGVtZW50KCJwIiksZD1hLmdldEVsZW1lbnRzQnlUYWdOYW1lKCJoZWFkIilbMF18fGEuZG9jdW1lbnRFbGVtZW50O3JldHVybiBjLmlubmVySFRNTD0ieDxzdHlsZT4iK2IrIjwvc3R5bGU+IixkLm [...]
+<script src="data:application/x-javascript;base64,LyohIFJlc3BvbmQuanMgdjEuNC4yOiBtaW4vbWF4LXdpZHRoIG1lZGlhIHF1ZXJ5IHBvbHlmaWxsICogQ29weXJpZ2h0IDIwMTMgU2NvdHQgSmVobAogKiBMaWNlbnNlZCB1bmRlciBodHRwczovL2dpdGh1Yi5jb20vc2NvdHRqZWhsL1Jlc3BvbmQvYmxvYi9tYXN0ZXIvTElDRU5TRS1NSVQKICogICovCgovLyBPbmx5IHJ1biB0aGlzIGNvZGUgaW4gSUUgOAppZiAoISF3aW5kb3cubmF2aWdhdG9yLnVzZXJBZ2VudC5tYXRjaCgiTVNJRSA4IikpIHsKIWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjthLm1hdGNoTWVkaWE9YS5tYXRjaE1lZGlhfHxmdW5jdGlvbihhKXt2YXIgYixjPWEuZG [...]
+<script src="data:application/x-javascript;base64,LyohIGpRdWVyeSBVSSAtIHYxLjExLjQgLSAyMDE2LTAxLTA1CiogaHR0cDovL2pxdWVyeXVpLmNvbQoqIEluY2x1ZGVzOiBjb3JlLmpzLCB3aWRnZXQuanMsIG1vdXNlLmpzLCBwb3NpdGlvbi5qcywgZHJhZ2dhYmxlLmpzLCBkcm9wcGFibGUuanMsIHJlc2l6YWJsZS5qcywgc2VsZWN0YWJsZS5qcywgc29ydGFibGUuanMsIGFjY29yZGlvbi5qcywgYXV0b2NvbXBsZXRlLmpzLCBidXR0b24uanMsIGRpYWxvZy5qcywgbWVudS5qcywgcHJvZ3Jlc3NiYXIuanMsIHNlbGVjdG1lbnUuanMsIHNsaWRlci5qcywgc3Bpbm5lci5qcywgdGFicy5qcywgdG9vbHRpcC5qcywgZWZmZWN0LmpzLC [...]
+<link href="data:text/css;charset=utf-8,%0A%0A%2Etocify%20%7B%0Awidth%3A%2020%25%3B%0Amax%2Dheight%3A%2090%25%3B%0Aoverflow%3A%20auto%3B%0Amargin%2Dleft%3A%202%25%3B%0Aposition%3A%20fixed%3B%0Aborder%3A%201px%20solid%20%23ccc%3B%0Awebkit%2Dborder%2Dradius%3A%206px%3B%0Amoz%2Dborder%2Dradius%3A%206px%3B%0Aborder%2Dradius%3A%206px%3B%0A%7D%0A%0A%2Etocify%20ul%2C%20%2Etocify%20li%20%7B%0Alist%2Dstyle%3A%20none%3B%0Amargin%3A%200%3B%0Apadding%3A%200%3B%0Aborder%3A%20none%3B%0Aline%2Dheight%3 [...]
+<script src="data:application/x-javascript;base64,LyoganF1ZXJ5IFRvY2lmeSAtIHYxLjkuMSAtIDIwMTMtMTAtMjIKICogaHR0cDovL3d3dy5ncmVnZnJhbmtvLmNvbS9qcXVlcnkudG9jaWZ5LmpzLwogKiBDb3B5cmlnaHQgKGMpIDIwMTMgR3JlZyBGcmFua287IExpY2Vuc2VkIE1JVCAqLwoKLy8gSW1tZWRpYXRlbHktSW52b2tlZCBGdW5jdGlvbiBFeHByZXNzaW9uIChJSUZFKSBbQmVuIEFsbWFuIEJsb2cgUG9zdF0oaHR0cDovL2JlbmFsbWFuLmNvbS9uZXdzLzIwMTAvMTEvaW1tZWRpYXRlbHktaW52b2tlZC1mdW5jdGlvbi1leHByZXNzaW9uLykgdGhhdCBjYWxscyBhbm90aGVyIElJRkUgdGhhdCBjb250YWlucyBhbGwgb2YgdG [...]
+<script src="data:application/x-javascript;base64,CgovKioKICogalF1ZXJ5IFBsdWdpbjogU3RpY2t5IFRhYnMKICoKICogQGF1dGhvciBBaWRhbiBMaXN0ZXIgPGFpZGFuQHBocC5uZXQ+CiAqIGFkYXB0ZWQgYnkgUnViZW4gQXJzbGFuIHRvIGFjdGl2YXRlIHBhcmVudCB0YWJzIHRvbwogKiBodHRwOi8vd3d3LmFpZGFubGlzdGVyLmNvbS8yMDE0LzAzL3BlcnNpc3RpbmctdGhlLXRhYi1zdGF0ZS1pbi1ib290c3RyYXAvCiAqLwooZnVuY3Rpb24oJCkgewogICJ1c2Ugc3RyaWN0IjsKICAkLmZuLnJtYXJrZG93blN0aWNreVRhYnMgPSBmdW5jdGlvbigpIHsKICAgIHZhciBjb250ZXh0ID0gdGhpczsKICAgIC8vIFNob3cgdGhlIHRhYi [...]
+<link href="data:text/css;charset=utf-8,pre%20%2Eoperator%2C%0Apre%20%2Eparen%20%7B%0Acolor%3A%20rgb%28104%2C%20118%2C%20135%29%0A%7D%0Apre%20%2Eliteral%20%7B%0Acolor%3A%20%23990073%0A%7D%0Apre%20%2Enumber%20%7B%0Acolor%3A%20%23099%3B%0A%7D%0Apre%20%2Ecomment%20%7B%0Acolor%3A%20%23998%3B%0Afont%2Dstyle%3A%20italic%0A%7D%0Apre%20%2Ekeyword%20%7B%0Acolor%3A%20%23900%3B%0Afont%2Dweight%3A%20bold%0A%7D%0Apre%20%2Eidentifier%20%7B%0Acolor%3A%20rgb%280%2C%200%2C%200%29%3B%0A%7D%0Apre%20%2Estri [...]
+<script src="data:application/x-javascript;base64,dmFyIGhsanM9bmV3IGZ1bmN0aW9uKCl7ZnVuY3Rpb24gbShwKXtyZXR1cm4gcC5yZXBsYWNlKC8mL2dtLCImYW1wOyIpLnJlcGxhY2UoLzwvZ20sIiZsdDsiKX1mdW5jdGlvbiBmKHIscSxwKXtyZXR1cm4gUmVnRXhwKHEsIm0iKyhyLmNJPyJpIjoiIikrKHA/ImciOiIiKSl9ZnVuY3Rpb24gYihyKXtmb3IodmFyIHA9MDtwPHIuY2hpbGROb2Rlcy5sZW5ndGg7cCsrKXt2YXIgcT1yLmNoaWxkTm9kZXNbcF07aWYocS5ub2RlTmFtZT09IkNPREUiKXtyZXR1cm4gcX1pZighKHEubm9kZVR5cGU9PTMmJnEubm9kZVZhbHVlLm1hdGNoKC9ccysvKSkpe2JyZWFrfX19ZnVuY3Rpb24gaCh0LH [...]
+<script src="data:application/x-javascript;base64,KGZ1bmN0aW9uKCkgewogIC8vIElmIHdpbmRvdy5IVE1MV2lkZ2V0cyBpcyBhbHJlYWR5IGRlZmluZWQsIHRoZW4gdXNlIGl0OyBvdGhlcndpc2UgY3JlYXRlIGEKICAvLyBuZXcgb2JqZWN0LiBUaGlzIGFsbG93cyBwcmVjZWRpbmcgY29kZSB0byBzZXQgb3B0aW9ucyB0aGF0IGFmZmVjdCB0aGUKICAvLyBpbml0aWFsaXphdGlvbiBwcm9jZXNzICh0aG91Z2ggbm9uZSBjdXJyZW50bHkgZXhpc3QpLgogIHdpbmRvdy5IVE1MV2lkZ2V0cyA9IHdpbmRvdy5IVE1MV2lkZ2V0cyB8fCB7fTsKCiAgLy8gU2VlIGlmIHdlJ3JlIHJ1bm5pbmcgaW4gYSB2aWV3ZXIgcGFuZS4gSWYgbm90LCB3ZS [...]
+<link href="data:text/css;charset=utf-8,%2EdisplayWidget%20%7B%0Amargin%3A%200%3B%0Apadding%3A%200%3B%0Aborder%3A%200%3B%0Abackground%2Dcolor%3A%20%23dddddd%3B%0Aoverflow%3A%20hidden%3B%0A%7D%0A%2Eebimage%20%7B%0Adisplay%3A%20block%3B%0Amargin%3A%200%3B%0Apadding%3A%200%3B%0Aborder%3A%200%3B%0Aoverflow%3A%20hidden%3B%0A%7D%0A%2Eebimage%2Dtoolbar%20%7B%0Aoverflow%3A%20inherit%3B%0Amin%2Dwidth%3A%20128px%3B%0A%7D%0A%2Eebimage%2Dbuttons%2Dnav%2C%20%2Eebimage%2Dbuttons%2Dzoom%20%7B%0Afloat%3 [...]
+<script src="data:application/x-javascript;base64,LyoKCUVCSW1hZ2UgSmF2YVNjcmlwdCBJbWFnZSBWaWV3ZXIKCUNvcHlyaWdodCAoYykgMjAxMiBBbmRyemVqIE9sZXMKKi8KCmZ1bmN0aW9uIFZpZXdlcihwYXJlbnQpewogIHZhciB2aWV3ZXIgPSB0aGlzOwoJdmFyIGRhdGEgPSBudWxsOwogIHZhciBudW1iZXJPZkZyYW1lcyA9IDA7CgogIHZhciBvcmlnaW5hbFdpZHRoIAk9IDA7CiAgdmFyIG9yaWdpbmFsSGVpZ2h0CT0gMDsKCgl2YXIgY3VycmVudEZyYW1lID0gMTsKCXZhciB6b29tTGV2ZWwgPSBudWxsLCBtaW5ab29tTGV2ZWwgPSAtMTIsIG1heFpvb21MZXZlbCA9IDY7IC8vICd6b29tTGV2ZWwgPT0gMCcgaXMgMTAwJSwgJ3 [...]
+<script src="data:application/x-javascript;base64,SFRNTFdpZGdldHMud2lkZ2V0KHsKCiAgbmFtZTogJ2Rpc3BsYXlXaWRnZXQnLAoKICB0eXBlOiAnb3V0cHV0JywKCiAgZmFjdG9yeTogZnVuY3Rpb24oZWwsIHdpZHRoLCBoZWlnaHQpIHsKCiAgICB2YXIgdmlld2VyID0gbmV3IFZpZXdlcihlbC5pZCk7CgogICAgcmV0dXJuIHsKCiAgICAgIHJlbmRlclZhbHVlOiBmdW5jdGlvbih4KSB7CiAgICAgICAgdmlld2VyLnJlc2V0KHguZGF0YSwgeC53aWR0aCwgeC5oZWlnaHQpOwogICAgICB9LAoKICAgICAgcmVzaXplOiBmdW5jdGlvbih3aWR0aCwgaGVpZ2h0KSB7CiAgICAgICAgdmlld2VyLnJlc2V0Q2FudmFzKCk7CiAgICAgIH0KCi [...]
+
+<style type="text/css">code{white-space: pre;}</style>
+<style type="text/css">
+
+</style>
+<script type="text/javascript">
+if (window.hljs && document.readyState && document.readyState === "complete") {
+   window.setTimeout(function() {
+      hljs.initHighlighting();
+   }, 0);
+}
+</script>
+
+
+<style type="text/css">
+  p.abstract{
+    text-align: center;
+    font-weight: bold;
+  }
+  div.abstract{
+    margin: auto;
+    width: 90%;
+  }
+</style>
+
+<style type="text/css">
+h1 {
+  font-size: 34px;
+}
+h1.title {
+  font-size: 38px;
+}
+h2 {
+  font-size: 30px;
+}
+h3 {
+  font-size: 24px;
+}
+h4 {
+  font-size: 18px;
+}
+h5 {
+  font-size: 16px;
+}
+h6 {
+  font-size: 12px;
+}
+.table th:not([align]) {
+  text-align: left;
+}
+</style>
+
+<link href="data:text/css;charset=utf-8,body%20%7B%0Amargin%3A%200px%20auto%3B%0Amax%2Dwidth%3A%201134px%3B%0Afont%2Dfamily%3A%20sans%2Dserif%3B%0Afont%2Dsize%3A%2010pt%3B%0A%7D%0A%0Adiv%23TOC%20ul%20%7B%0Apadding%3A%200px%200px%200px%2045px%3B%0Alist%2Dstyle%3A%20none%3B%0Abackground%2Dimage%3A%20none%3B%0Abackground%2Drepeat%3A%20none%3B%0Abackground%2Dposition%3A%200%3B%0Afont%2Dsize%3A%2010pt%3B%0Afont%2Dfamily%3A%20Helvetica%2C%20Arial%2C%20sans%2Dserif%3B%0A%7D%0Adiv%23TOC%20%3E%20 [...]
+
+</head>
+
+<body>
+
+<style type="text/css">
+.main-container {
+  max-width: 828px;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+img {
+  max-width:100%;
+  height: auto;
+}
+.tabbed-pane {
+  padding-top: 12px;
+}
+button.code-folding-btn:focus {
+  outline: none;
+}
+</style>
+
+
+
+<div class="container-fluid main-container">
+
+<!-- tabsets -->
+<script>
+$(document).ready(function () {
+  window.buildTabsets("TOC");
+});
+</script>
+
+<!-- code folding -->
+
+
+
+
+<script>
+$(document).ready(function ()  {
+
+    // move toc-ignore selectors from section div to header
+    $('div.section.toc-ignore')
+        .removeClass('toc-ignore')
+        .children('h1,h2,h3,h4,h5').addClass('toc-ignore');
+
+    // establish options
+    var options = {
+      selectors: "h1,h2,h3",
+      theme: "bootstrap3",
+      context: '.toc-content',
+      hashGenerator: function (text) {
+        return text.replace(/[.\\/?&!#<>]/g, '').replace(/\s/g, '_').toLowerCase();
+      },
+      ignoreSelector: ".toc-ignore",
+      scrollTo: 0
+    };
+    options.showAndHide = true;
+    options.smoothScroll = true;
+
+    // tocify
+    var toc = $("#TOC").tocify(options).data("toc-tocify");
+});
+</script>
+
+<style type="text/css">
+
+#TOC {
+  margin: 25px 0px 20px 0px;
+}
+ at media (max-width: 768px) {
+#TOC {
+  position: relative;
+  width: 100%;
+}
+}
+
+
+
+
+div.main-container {
+  max-width: 1200px;
+}
+
+div.tocify {
+  width: 20%;
+  max-width: 246px;
+  max-height: 85%;
+}
+
+ at media (min-width: 768px) and (max-width: 991px) {
+  div.tocify {
+    width: 25%;
+  }
+}
+
+ at media (max-width: 767px) {
+  div.tocify {
+    width: 100%;
+    max-width: none;
+  }
+}
+
+.tocify ul, .tocify li {
+  line-height: 20px;
+}
+
+.tocify-subheader .tocify-item {
+  font-size: 0.90em;
+  padding-left: 25px;
+  text-indent: 0;
+}
+
+.tocify .list-group-item {
+  border-radius: 0px;
+}
+
+
+</style>
+
+<!-- setup 3col/9col grid for toc_float and main content  -->
+<div class="row-fluid">
+<div class="col-xs-12 col-sm-4 col-md-3">
+<div id="TOC" class="tocify">
+</div>
+</div>
+
+<div class="toc-content col-xs-12 col-sm-8 col-md-9">
+
+
+
+
+<div class="fluid-row" id="header">
+
+
+
+<h1 class="title toc-ignore">Introduction to <em>EBImage</em></h1>
+<p class="author-name">Andrzej Oleś, Gregoire Pau, Oleg Sklyar, Wolfgang Huber</p>
+<h4 class="date"><em>30 October 2017</em></h4>
+<h4 class="abstract">Abstract</h4>
+<p><em><a href="http://bioconductor.org/packages/EBImage">EBImage</a></em> provides general purpose functionality for image processing and analysis. In the context of (high-throughput) microscopy-based cellular assays, EBImage offers tools to segment cells and extract quantitative cellular descriptors. This allows the automation of such tasks using the R programming language and facilitates the use of other tools in the R environment for signal processing, statistical modeling, machine l [...]
+<h4 class="package">Package</h4>
+<p>EBImage 4.20.0</p>
+
+</div>
+
+
+<p>
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAFACAYAAADNkKWqAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAG/wAABv8B+tgJkgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAD1oSURBVHja7Z0JmCRFmf6r7+r7vqu7+j6r72Om756ee4aBGRhgYMBVFA9E/S8oKi4gghcuuB6AIuoqsgiiiwIKiBdyi6jgeuyK97Egq7j6Fw8gN97K7qGnpqoyIjMyMyLr6+d5H32Y7qrKyMhfRcT3fe8XMgwj5KXYTwPTXqYzmC5iuprpVqaHmX7D9AyTQSKRAqdnVp/xh1ef+atXGXDGKhMaPOeRR9CLMZ3LdD/TczQRSCRSEj23ygiwIqY1ANnPItO7mR6jG0sikWzosVWGLGoDQPazxHQv3TwSiSRRYMqSsgBkP+NMt9GN [...]
+</p>
+<div id="getting-started" class="section level1">
+<h1><span class="header-section-number">1</span> Getting started</h1>
+<p><em><a href="http://bioconductor.org/packages/EBImage">EBImage</a></em> is an R package distributed as part of the <a href="http://bioconductor.org">Bioconductor</a> project. To install the package, start R and enter:</p>
+<pre class="r"><code>source("http://bioconductor.org/biocLite.R")
+biocLite("EBImage")</code></pre>
+<p>Once <em>EBImage</em> is installed, it can be loaded by the following command.</p>
+<pre class="r"><code>library("EBImage")</code></pre>
+</div>
+<div id="reading-displaying-and-writing-images" class="section level1">
+<h1><span class="header-section-number">2</span> Reading, displaying and writing images</h1>
+<p>Basic <em>EBImage</em> functionality includes reading, writing, and displaying of images. Images are read using the function <code>readImage</code>, which takes as input a file name or an URL. To start off, let us load a sample picture distributed with the package.</p>
+<pre class="r"><code>f = system.file("images", "sample.png", package="EBImage")
+img = readImage(f)</code></pre>
+<p><em>EBImage</em> currently supports three image file formats: <code>jpeg</code>, <code>png</code> and <code>tiff</code>. This list is complemented by the <em><a href="https://github.com/aoles/RBioFormats">RBioFormats</a></em> package providing support for a much wider range of file formats including proprietary microscopy image data and metadata.</p>
+<p>The image which we just loaded can be visualized by the function <code>display</code>.</p>
+<pre class="r"><code>display(img, method="browser")</code></pre>
+<div id="htmlwidget-e5435f92e833e5f1a9a1" style="width:800px;height:500px;" class="displayWidget html-widget"></div>
+<script type="application/json" data-for="htmlwidget-e5435f92e833e5f1a9a1">{"x":{"data":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAwAAAAIACAAAAAAQnVTmAAAgAElEQVR4nEy8zdIkSbIldH7MI+s+FIIIgrAAYcEMMAKzGBY8AQ8PdIXrOSzUoi696KruzPwywt1M9fyp8v+iI8wTOCiYABh93/Zf/8/7pTICEJRjFhinxtQRGwCAgoKaCkD3vwlFKQUGPGBhBiqHBdtUIdAoZRt4RuAAMF4XnDoIpBmzIKwIhFmYxVMqhEoDKc2iOAWLahzGUOoWcNkIVOAOO4CBl4CAAiLrIXA6hIYCUQBgGQ9z5hWIvMzTAYAiTwZ6T1DYmAivTgtoAPSTPF9QnpLvE9WpCAUfFCFEBOfLAwdMYDRiMIehQk6NAi/BYpyIacgy+xlDB [...]
+<p>When called from an interactive R session, <code>display</code> opens the image in a JavaScript viewer in your web browser. Using the mouse or keyboard shortcuts, you can zoom in and out of the image, pan, and cycle through multiple image frames. Alternatively, the image can be displayed using R’s build-in plotting facilities by calling <code>display</code> with the argument <code>method = "raster"</code>. The image is then drawn on the current device. This allows to easily  [...]
+<pre class="r"><code>display(img, method="raster")
+text(x = 20, y = 20, label = "Parrots", adj = c(0,1), col = "orange", cex = 2)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYAAAAEACAIAAAA1FTYwAAAACXBIWXMAAAewAAAHsAHUgoNiAAAgAElEQVR4nIS9a4xmx3kmVlXnfNf+unt6em49MxTFkXkRSYkKJUbWJdbNFiAhWcNWvA68iyAOjGCBLLJIYCDIBgmyQBAECyQwkGyw+RXEWSz8Y3cTBV5n12trZV0s2pYVSqRkihyJ5Aw5wxn2dE9fvv5u51Tlx8P3mafqNJXzo/H1951Tp+q9PO+l3qryv/3bvx1CcM4550IIKSXnXEopxuicizHGGBeLxWKxOD4+Xi6X/Am3pZS897gTH9ACWosx8ga27Jzz3usH3MDP+LeqKjSCn7QFfEA32rYNIbRty+/xDX7FiHjh8eKlVVV5u6qqCiHgpfgejYQQvPcxRnzAlxw46cZ2+CIdFFrAI+wGx5hSYpsFQfQeJQVuaNsWL [...]
+<p>The graphics displayed in an R device can be saved using <em>base</em> R functions <code>dev.print</code> or <code>dev.copy</code>. For example, lets save our annotated image as a JPEG file and verify its size on disk.</p>
+<pre class="r"><code>filename = "parrots.jpg"
+dev.print(jpeg, filename = filename , width = dim(img)[1], height = dim(img)[2])</code></pre>
+<pre><code>png 
+  2 </code></pre>
+<pre class="r"><code>file.info(filename)$size</code></pre>
+<pre><code>[1] 37850</code></pre>
+<p>If R is not running interactively, e.g. for code in a package vignette, <code>"raster"</code> becomes the default method in <code>display</code>. The default behavior of <code>display</code> can be overridden globally be setting the <code>"options("EBImage.display")</code> to either <code>"browser"</code> or <code>"raster"</code>. This is useful, for example, to preview images inside RStudio.</p>
+<p>It is also possible to read and view color images,</p>
+<pre class="r"><code>imgcol = readImage(system.file("images", "sample-color.png", package="EBImage"))
+display(imgcol)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYAAAAEACAIAAAA1FTYwAAAACXBIWXMAAAewAAAHsAHUgoNiAAAgAElEQVR4nEy9bZIcSa4DCIAekVVSvzng3mNtz70fM9PKdAL7gx7VT2Om1pRKWZkRdBIEQAb/r//z/6AEQrCoIAgRthuQvW2//7z//vP+9//799+fncA2AAQIgggEEBOEeb4IRKQNIETCICTgADEkxghIJiEDgmACMhIVqgBQAgCQ87cCkjgm2DaBbkNJJ4aTkN0tKQ4AUg6MIHQCIAnJBIQiF4TF5xeqLml+KKtEAhBLJCWkjSIpIJJAArFTUuIgYp3PQsYhCQIBBDIEHSO0k3g+O0CJDAmQoAyIJEECCMgCLAUg4rkUSBSGZNz9EbUTOASTJEg6TMIk8x4YAmYRKAexJVJIElYcMEWFtEOeWCjQgIgquaOqyGKVkJAUC [...]
+<p>or images containing several frames. If an image consists of multiple frames, they can be displayed all at once in a grid arrangement by specifying the function argument <code>all = TRUE</code>,</p>
+<pre class="r"><code>nuc = readImage(system.file("images", "nuclei.tif", package="EBImage"))
+display(nuc, method = "raster", all = TRUE)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAf0AAAH9CAMAAAAnElW6AAADAFBMVEUAAAABAQECAgIDAwMEBAQFBQUGBgYHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZ [...]
+<p>or we can just view a single frame, for example, the second one.</p>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAP4AAAD+CAMAAAAtSEhjAAADAFBMVEUAAAABAQECAgIDAwMEBAQFBQUGBgYHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZ [...]
+<p>Images can be saved to files using the <code>writeImage</code> function. The image that we loaded was a PNG file; suppose now that we want to save this image as a JPEG file. The JPEG format allows to set a quality value between 1 and 100 for its compression algorithm. The default value of the <code>quality</code> argument of <code>writeImage</code> is 100, here we use a smaller value, leading to smaller file size at the cost of some reduction in image quality.</p>
+<pre class="r"><code>writeImage(imgcol, "sample.jpeg", quality = 85)</code></pre>
+<p>Similarly, we could have saved the image as a TIFF file and set which compression algorithm we want to use. For a complete list of available parameters see <code>?writeImage</code>.</p>
+</div>
+<div id="image-data-representation" class="section level1">
+<h1><span class="header-section-number">3</span> Image data representation</h1>
+<p><em>EBImage</em> uses a package-specific class <code>Image</code> to store and process images. It extends the R base class <code>array</code>, and all <em>EBImage</em> functions can also be called directly on matrices and arrays. You can find out more about this class by typing <code>?Image</code>. Let us peek into the internal structure of an <code>Image</code> object.</p>
+<pre class="r"><code>str(img)</code></pre>
+<pre><code>Formal class 'Image' [package "EBImage"] with 2 slots
+  ..@ .Data    : num [1:768, 1:512] 0.447 0.451 0.463 0.455 0.463 ...
+  ..@ colormode: int 0</code></pre>
+<p>The <code>.Data</code> slot contains a numeric array of pixel intensities. We see that in this case the array is two-dimensional, with 768 times 512 elements, and corresponds to the pixel width and height of the image. These dimensions can be accessed using the <code>dim</code> function, just like for regular arrays.</p>
+<pre class="r"><code>dim(img)</code></pre>
+<pre><code>[1] 768 512</code></pre>
+<p>Image data can be accessed as a plain R <code>array</code> using the <code>imageData</code> accessor,</p>
+<pre class="r"><code>imageData(img)[1:3, 1:6]</code></pre>
+<pre><code>          [,1]      [,2]      [,3]      [,4]      [,5]      [,6]
+[1,] 0.4470588 0.4627451 0.4784314 0.4980392 0.5137255 0.5294118
+[2,] 0.4509804 0.4627451 0.4784314 0.4823529 0.5058824 0.5215686
+[3,] 0.4627451 0.4666667 0.4823529 0.4980392 0.5137255 0.5137255</code></pre>
+<p>and the <code>as.array</code> method can be used to coerce an <code>Image</code> to an <code>array</code>.</p>
+<pre class="r"><code>is.Image( as.array(img) )</code></pre>
+<pre><code>[1] FALSE</code></pre>
+<p>The distribution of pixel intensities can be plotted in a histogram, and their range inspected using the <code>range</code> function.</p>
+<pre class="r"><code>hist(img)</code></pre>
+<p><img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iNDMycHQiIGhlaWdodD0iNDMycHQiIHZpZXdCb3g9IjAgMCA0MzIgNDMyIiB2ZXJzaW9uPSIxLjEiPgo8ZGVmcz4KPGc+CjxzeW1ib2wgb3ZlcmZsb3c9InZpc2libGUiIGlkPSJnbHlwaDAtMCI+CjxwYXRoIHN0eWxlPSJzdHJva2U6bm9uZTsiIGQ9IiIvPgo8L3N5bWJvbD4KPHN5bWJvbCBvdmVyZmxvdz0idmlzaWJsZSIgaWQ9ImdseXBoMC0xIj4KPHBhdGggc3R5bGU9I [...]
+<pre class="r"><code>range(img)</code></pre>
+<pre><code>[1] 0 1</code></pre>
+<p>A useful summary of <code>Image</code> objects is also provided by the <code>show</code> method, which is invoked if we simply type the object’s name.</p>
+<pre class="r"><code>img</code></pre>
+<pre><code>Image 
+  colorMode    : Grayscale 
+  storage.mode : double 
+  dim          : 768 512 
+  frames.total : 1 
+  frames.render: 1 
+
+imageData(object)[1:5,1:6]
+          [,1]      [,2]      [,3]      [,4]      [,5]      [,6]
+[1,] 0.4470588 0.4627451 0.4784314 0.4980392 0.5137255 0.5294118
+[2,] 0.4509804 0.4627451 0.4784314 0.4823529 0.5058824 0.5215686
+[3,] 0.4627451 0.4666667 0.4823529 0.4980392 0.5137255 0.5137255
+[4,] 0.4549020 0.4666667 0.4862745 0.4980392 0.5176471 0.5411765
+[5,] 0.4627451 0.4627451 0.4823529 0.4980392 0.5137255 0.5411765</code></pre>
+<p>For a more compact representation without the preview of the intensities array use the <code>print</code> method with the argument <code>short</code> set to <code>TRUE</code>.</p>
+<pre class="r"><code>print(img, short=TRUE)</code></pre>
+<pre><code>Image 
+  colorMode    : Grayscale 
+  storage.mode : double 
+  dim          : 768 512 
+  frames.total : 1 
+  frames.render: 1 </code></pre>
+<p>Let’s now have a closer look a our color image.</p>
+<pre class="r"><code>print(imgcol, short=TRUE)</code></pre>
+<pre><code>Image 
+  colorMode    : Color 
+  storage.mode : double 
+  dim          : 768 512 3 
+  frames.total : 3 
+  frames.render: 1 </code></pre>
+<p>It differs from its grayscale counterpart <code>img</code> by the property <code>colorMode</code> and the number of dimensions. The <code>colorMode</code> slot turns out to be convenient when dealing with stacks of images. If it is set to <code>Grayscale</code>, then the third and all higher dimensions of the array are considered as separate image frames corresponding, for instance, to different z-positions, time points, replicates, etc. On the other hand, if <code>colorMode</code> is [...]
+<p>The “frames.total” and “frames.render” fields shown by the object summary correspond to the total number of frames contained in the image, and to the number of rendered frames. These numbers can be accessed using the function <code>numberOfFrames</code> by specifying the <code>type</code> argument.</p>
+<pre class="r"><code>numberOfFrames(imgcol, type = "render")</code></pre>
+<pre><code>[1] 1</code></pre>
+<pre class="r"><code>numberOfFrames(imgcol, type = "total")</code></pre>
+<pre><code>[1] 3</code></pre>
+<p>Image frames can be extracted using <code>getFrame</code> and <code>getFrames</code>. <code>getFrame</code> returns the i-th frame contained in the image y. If <code>type</code> is <code>"total"</code>, the function is unaware of the color mode and returns an xy-plane. For <code>type="render"</code> the function returns the i-th image as shown by the display function. While <code>getFrame</code> returns just a single frame, <code>getFrames</code> retrieves a list o [...]
+<p>Finally, if we look at our cell data,</p>
+<pre class="r"><code>nuc</code></pre>
+<pre><code>Image 
+  colorMode    : Grayscale 
+  storage.mode : double 
+  dim          : 510 510 4 
+  frames.total : 4 
+  frames.render: 4 
+
+imageData(object)[1:5,1:6,1]
+           [,1]       [,2]       [,3]       [,4]       [,5]       [,6]
+[1,] 0.06274510 0.07450980 0.07058824 0.08235294 0.10588235 0.09803922
+[2,] 0.06274510 0.05882353 0.07843137 0.09019608 0.09019608 0.10588235
+[3,] 0.06666667 0.06666667 0.08235294 0.07843137 0.09411765 0.09411765
+[4,] 0.06666667 0.06666667 0.07058824 0.08627451 0.08627451 0.09803922
+[5,] 0.05882353 0.06666667 0.07058824 0.08235294 0.09411765 0.10588235</code></pre>
+<p>we see that it contains 4 total frames that correspond to the 4 separate greyscale images, as indicated by “frames.render”.</p>
+</div>
+<div id="color-management" class="section level1">
+<h1><span class="header-section-number">4</span> Color management</h1>
+<p>As described in the previous section, the class <code>Image</code> extends the base class <code>array</code> and uses <code>colorMode</code> to store how the color information of the multi-dimensional data should be handled. The function <code>colorMode</code> can be used to access and change this property, modifying the rendering mode of an image. For example, if we take a <code>Color</code> image and change its mode to <code>Grayscale</code>, then the image won’t display as a single [...]
+<pre class="r"><code>colorMode(imgcol) = Grayscale
+display(imgcol, all=TRUE)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAwAAAAIACAMAAAACKPsIAAADAFBMVEUAAAABAQECAgIDAwMEBAQFBQUGBgYHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZ [...]
+<p>Color space conversions between <code>Grayscale</code> and <code>Color</code> images are performed using the function <code>channel</code>. It has a flexible interface which allows to convert either way between the modes, and can be used to extract color channels. Unlike <code>colorMode</code>, <code>channel</code> changes the pixel intensity values of the image.</p>
+<p><code>Color</code> to <code>Grayscale</code> conversion modes include taking a uniform average across the RGB channels, and a weighted luminance preserving conversion mode better suited for display purposes.</p>
+<p>The <code>asred</code>, <code>asgreen</code> and <code>asblue</code> modes convert a grayscale image or array into a color image of the specified hue.</p>
+<p>The convenience function <code>toRGB</code> promotes a grayscale image to RGB color space by replicating it across the red, green and blue channels, which is equivalent to calling <code>channel</code> with mode set to <code>rgb</code>. When displayed, this image doesn’t look different from its grayscale origin, which is expected because the information between the color channels is the same. To combine three grayscale images into a single rgb image use the function <code>rgbImage</code>.</p>
+<p>The function <code>Image</code> can be used to construct a color image from a character vector or array of named R colors (as listed by <code>colors()</code>) and/or hexadecimal strings of the form “#rrggbb” or “#rrggbbaa”.</p>
+<pre class="r"><code>colorMat = matrix(rep(c("red","green", "#0000ff"), 25), 5, 5)
+colorImg = Image(colorMat)
+colorImg</code></pre>
+<pre><code>Image 
+  colorMode    : Color 
+  storage.mode : double 
+  dim          : 5 5 3 
+  frames.total : 3 
+  frames.render: 1 
+
+imageData(object)[1:5,1:5,1]
+     [,1] [,2] [,3] [,4] [,5]
+[1,]    1    0    0    1    0
+[2,]    0    1    0    0    1
+[3,]    0    0    1    0    0
+[4,]    1    0    0    1    0
+[5,]    0    1    0    0    1</code></pre>
+<pre class="r"><code>display(colorImg, interpolate=FALSE)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAABGCAMAAABG8BK2AAAACVBMVEUAAP8A/wD/AABlqVCRAAAACXBIWXMAAJnKAACZygHjkaQiAAAARElEQVRYhe3MIQ4AIBADQeD/j0bXXAiqYlZvZp1oRysaTwwGU8n8rnliMJhO5n2dTgwG08m8r+OJwWAqmd81TwwGU8lcSD4T6bS3wlcAAAAASUVORK5CYII=" style="display: block; margin: auto;" /></p>
+</div>
+<div id="manipulating-images" class="section level1">
+<h1><span class="header-section-number">5</span> Manipulating images</h1>
+<p>Being numeric arrays, images can be conveniently manipulated by any of R’s arithmetic operators. For example, we can produce a negative image by simply subtracting the image from its maximum value.</p>
+<pre class="r"><code>img_neg = max(img) - img
+display( img_neg )</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYAAAAEACAMAAACNqVFVAAACzVBMVEUAAAABAQECAgIDAwMEBAQFBQUGBgYHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZ [...]
+<p>We can also increase the brightness of an image through addition, adjust the contrast through multiplication, and apply gamma correction through exponentiation.</p>
+<pre class="r"><code>img_comb = combine(
+  img,
+  img + 0.3,
+  img * 2,
+  img ^ 0.5
+)
+
+display(img_comb, all=TRUE)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYAAAAEACAMAAACNqVFVAAACuFBMVEUYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZGRlZWVmZmZnZ2doaGhpaWlqampra2tsbGxtbW1ubm5vb29wcHBxcXFycnJzc3N0dHR1dXV2dnZ3d3d4eHh5eXl6enp7e3t8f [...]
+<p>In the example above we have used <code>combine</code> to merge individual images into a single multi-frame image object.</p>
+<p>Furthermore, we can crop and threshold images with standard matrix operations.</p>
+<pre class="r"><code>img_crop = img[366:749, 58:441]
+img_thresh = img_crop > .5
+display(img_thresh)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAMAAABlApw1AAAAD1BMVEUAAAA/Pz9/f3+/v7////93N0HWAAAACXBIWXMAAAewAAAHsAHUgoNiAAAJzklEQVR4nOVdi5LrIAgV9P+/eQU0UaPGJiaSWWbuo9tty5E3ojXmUUJ3h/BZ5kYI7vBvV3NP9HEB3BMBrGae6QaA1awz3ZCADgFcl4AS/i87UhUWzPRxARiwHxfARR1azXVCl/yQIgEYc0WH1FgA0QUAqgRwxQ+pEkBdBF2xqEhDE6p4UuiKRZkGmYoS9Z3TanaPVMQCVpG2EukTQKlEwuGXAOTrbZG9TCtEazNhoZRbEC/ZsgJlPjTSLgJrDz/SL4BsvbeffYl/A7sSbTpSs4KVPPZp80R28zJHK3ht/cEgek4SEsvsGaBHYHMjPYToN/gH64k49iocP58eECBSCuIBW [...]
+<p>The thresholding operation returns an <code>Image</code> object with binarized pixels values. The R data type used to store such an image is <code>logical</code>.</p>
+<pre class="r"><code>img_thresh</code></pre>
+<pre><code>Image 
+  colorMode    : Grayscale 
+  storage.mode : logical 
+  dim          : 384 384 
+  frames.total : 1 
+  frames.render: 1 
+
+imageData(object)[1:5,1:6]
+      [,1]  [,2]  [,3]  [,4]  [,5]  [,6]
+[1,] FALSE FALSE FALSE FALSE FALSE FALSE
+[2,] FALSE FALSE FALSE FALSE FALSE FALSE
+[3,] FALSE FALSE FALSE FALSE FALSE FALSE
+[4,] FALSE FALSE FALSE FALSE FALSE FALSE
+[5,] FALSE FALSE FALSE FALSE FALSE FALSE</code></pre>
+<p>For image transposition, use <code>transpose</code> rather than R’s <em>base</em> function <code>t</code>. This is because the former one works also on color and multiframe images by swapping its spatial dimensions.</p>
+<pre class="r"><code>img_t = transpose(img)
+display( img_t )</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAGACAMAAAB1HEyMAAACzVBMVEURERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZGRlZWVmZmZnZ2doaGhpaWlqampra2tsbGxtbW1ubm5vb29wcHBxcXFycnJzc3N0dHR1d [...]
+</div>
+<div id="spatial-transformations" class="section level1">
+<h1><span class="header-section-number">6</span> Spatial transformations</h1>
+<p>We just saw one type of spatial transformation, transposition, but there are many more, for example translation, rotation, reflection and scaling. <code>translate</code> moves the image plane by the specified two-dimensional vector in such a way that pixels that end up outside the image region are cropped, and pixels that enter into the image region are set to background.</p>
+<pre class="r"><code>img_translate = translate(img, c(100,-50))
+display(img_translate)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYAAAAEACAMAAACNqVFVAAACzVBMVEUAAAASEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZGRlZWVmZmZnZ2doaGhpaWlqampra2tsbGxtbW1ubm5vb29wcHBxcXFycnJzc3N0dHR1d [...]
+<p>The background color can be set using the argument <code>bg.col</code> common to all relevant spatial transformation functions. The default sets the value of background pixels to zero which corresponds to black. Let us demonstrate the use of this argument with <code>rotate</code> which rotates the image clockwise by the given angle.</p>
+<pre class="r"><code>img_rotate = rotate(img, 30, bg.col = "white")
+display(img_rotate)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcwAAAGdCAMAAACo17PmAAACwVBMVEUTExMWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZGRlZWVmZmZnZ2doaGhpaWlqampra2tsbGxtbW1ubm5vb29wcHBxcXFycnJzc3N0dHR1dXV2dnZ3d3d4eHh5e [...]
+<p>To scale an image to desired dimensions use <code>resize</code>. If you provide only one of either width or height, the other dimension is automatically computed keeping the original aspect ratio.</p>
+<pre class="r"><code>img_resize = resize(img, w=256, h=256)
+display(img_resize )</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAACslBMVEUZGRkaGhobGxsdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZGRlZWVmZmZnZ2doaGhpaWlqampra2tsbGxtbW1ubm5vb29wcHBxcXFycnJzc3N0dHR1dXV2dnZ3d3d4eHh5eXl6enp7e3t8fHx9fX1+f [...]
+<p>The functions <code>flip</code> and <code>flop</code> reflect the image around the image horizontal and vertical axis, respectively.</p>
+<pre class="r"><code>img_flip = flip(img)
+img_flop = flop(img)
+
+display(combine(img_flip, img_flop), all=TRUE)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAwAAAAEACAMAAAAzwOGVAAACzVBMVEURERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZGRlZWVmZmZnZ2doaGhpaWlqampra2tsbGxtbW1ubm5vb29wcHBxcXFycnJzc3N0dHR1d [...]
+<p>Spatial linear transformations are implemented using the general <code>affine</code> transformation. It maps image pixel coordinates <code>px</code> using a 3x2 transformation matrix <code>m</code> in the following way: <code>cbind(px, 1) %*% m</code>. For example, horizontal sheer mapping can be applied by</p>
+<pre class="r"><code>m =  matrix(c(1, -.5, 128, 0, 1, 0), nrow=3, ncol=2)
+img_affine = affine(img, m)
+display( img_affine )</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYAAAAEACAMAAACNqVFVAAAC7lBMVEUAAAAHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZGRlZWVmZmZnZ2doaGhpaWlqa [...]
+</div>
+<div id="filtering" class="section level1">
+<h1><span class="header-section-number">7</span> Filtering</h1>
+<div id="linear-filters" class="section level2">
+<h2><span class="header-section-number">7.1</span> Linear filters</h2>
+<p>A common preprocessing step involves cleaning up the images by removing local artifacts or noise through smoothing. An intuitive approach is to define a window of a selected size around each pixel and average the values within that neighborhood. After applying this procedure to all pixels, the new, smoothed image is obtained. Mathematically, this can be expressed as <span class="math display">\[
+f'(x,y) = \frac{1}{N} \sum_{s=-a}^{a}\sum_{t=-a}^{a} f(x+s, y+t),
+\]</span> where <span class="math inline">\(f(x,y)\)</span> is the value of the pixel at position <span class="math inline">\((x, y)\)</span>, and <span class="math inline">\(a\)</span> determines the window size, which is <span class="math inline">\(2a+1\)</span> in each direction. <span class="math inline">\(N=(2a+1)^2\)</span> is the number of pixels averaged over, and <span class="math inline">\(f'\)</span> is the new, smoothed image.</p>
+<p>More generally, we can replace the moving average by a weighted average, using a weight function <span class="math inline">\(w\)</span>, which typically has the highest value at the window midpoint (<span class="math inline">\(s=t=0\)</span>) and then decreases towards the edges. <span class="math display">\[
+(w * f)(x,y) = \sum_{s=-\infty}^{+\infty} \sum_{t=-\infty}^{+\infty} w(s,t)\, f(x+s, y+s)
+\]</span> For notational convenience, we let the summations range from <span class="math inline">\(-\infty\)</span> to <span class="math inline">\(+\infty\)</span>, even if in practice the sums are finite and <span class="math inline">\(w\)</span> has only a finite number of non-zero values. In fact, we can think of the weight function <span class="math inline">\(w\)</span> as another image, and this operation is also called the <em>convolution</em> of the images <span class="math inline [...]
+<p>In <em><a href="http://bioconductor.org/packages/EBImage">EBImage</a></em>, the 2-dimensional convolution is implemented by the function <code>filter2</code>, and the auxiliary function <code>makeBrush</code> can be used to generate the weight function. In fact, <code>filter2</code> does not directly perform the summation indicated in the equation above. Instead, it uses the Fast Fourier Transformation in a way that is mathematically equivalent but computationally more efficient.</p>
+<pre class="r"><code>w = makeBrush(size = 31, shape = 'gaussian', sigma = 5)
+plot(w[(nrow(w)+1)/2, ], ylab = "w", xlab = "", cex = 0.7)</code></pre>
+<p><img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iNDMycHQiIGhlaWdodD0iNDMycHQiIHZpZXdCb3g9IjAgMCA0MzIgNDMyIiB2ZXJzaW9uPSIxLjEiPgo8ZGVmcz4KPGc+CjxzeW1ib2wgb3ZlcmZsb3c9InZpc2libGUiIGlkPSJnbHlwaDAtMCI+CjxwYXRoIHN0eWxlPSJzdHJva2U6bm9uZTsiIGQ9IiIvPgo8L3N5bWJvbD4KPHN5bWJvbCBvdmVyZmxvdz0idmlzaWJsZSIgaWQ9ImdseXBoMC0xIj4KPHBhdGggc3R5bGU9I [...]
+<pre class="r"><code>img_flo = filter2(img, w)
+display(img_flo)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYAAAAEACAMAAACNqVFVAAACjlBMVEUlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZGRlZWVmZmZnZ2doaGhpaWlqampra2tsbGxtbW1ubm5vb29wcHBxcXFycnJzc3N0dHR1dXV2dnZ3d3d4eHh5eXl6enp7e3t8fHx9fX1+fn5/f3+AgICBgYGCgoKDg4OEhISFhYWGhoaHh4eIiIiJi [...]
+<p>Here we have used a Gaussian filter of width 5 given by <code>sigma</code>. Other available filter shapes include <code>"box"</code> (default), <code>"disc"</code>, <code>"diamond"</code> and <code>"line"</code>, for some of which the kernel can be binary; see <code>?makeBrush</code> for details.</p>
+<p>If the filtered image contains multiple frames, the filter is applied to each frame separately. For convenience, images can be also smoothed using the wrapper function <code>gblur</code> which performs Gaussian smoothing with the filter size automatically adjusted to <code>sigma</code>.</p>
+<pre class="r"><code>nuc_gblur = gblur(nuc, sigma = 5)
+display(nuc_gblur, all=TRUE )</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAf0AAAH9CAMAAAAnElW6AAAC/VBMVEUBAQECAgIDAwMEBAQFBQUGBgYHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZGRlZ [...]
+<p>In signal processing the operation of smoothing an image is referred to as low-pass filtering. High-pass filtering is the opposite operation which allows to detect edges and sharpen images. This can be done, for instance, using a Laplacian filter.</p>
+<pre class="r"><code>fhi = matrix(1, nrow = 3, ncol = 3)
+fhi[2, 2] = -8
+img_fhi = filter2(img, fhi)
+display(img_fhi)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYAAAAEACAMAAACNqVFVAAAC0FBMVEUAAAABAQECAgIDAwMEBAQFBQUGBgYHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZ [...]
+</div>
+<div id="median-filter" class="section level2">
+<h2><span class="header-section-number">7.2</span> Median filter</h2>
+<p>Another approach to perform noise reduction is to apply a median filter, which is a non-linear technique as opposed to the low pass convolution filter described in the previous section. Median filtering is particularly effective in the case of speckle noise, and has the advantage of removing noise while preserving edges.</p>
+<p>The local median filter works by scanning the image pixel by pixel, replacing each pixel by the median on of its neighbors inside a window of specified size. This filtering technique is provided in <em>EBImage</em> by the function <code>medianFilter</code>. We demonstrate its use by first corrupting the image with uniform noise, and reconstructing the original image by median filtering.</p>
+<pre class="r"><code>l = length(img)
+n = l/10
+pixels = sample(l, n)
+img_noisy = img
+img_noisy[pixels] = runif(n, min=0, max=1)
+display(img_noisy)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYAAAAEACAMAAACNqVFVAAAC0FBMVEUPDw8RERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZGRlZWVmZmZnZ2doaGhpaWlqampra2tsbGxtbW1ubm5vb29wcHBxcXFycnJzc3N0d [...]
+<pre class="r"><code>img_median = medianFilter(img_noisy, 1)
+display(img_median)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYAAAAEACAMAAACNqVFVAAAC0FBMVEUQEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZGRlZWVmZmZnZ2doaGhpaWlqampra2tsbGxtbW1ubm5vb29wcHBxcXFycnJzc3N0d [...]
+</div>
+<div id="morphological-operations" class="section level2">
+<h2><span class="header-section-number">7.3</span> Morphological operations</h2>
+<p>Binary images are images which contain only two sets of pixels, with values, say 0 and 1, representing the background and foreground pixels. Such images are subject to several non-linear morphological operations: erosion, dilation, opening, and closing. These operations work by overlaying a mask, called the structuring element, over the binary image in the following way:</p>
+<ul>
+<li><p>erosion: For every foreground pixel, put the mask around it, and if any pixel covered by the mask is from the background, set the pixel to background.</p></li>
+<li><p>dilation: For every background pixel, put the mask around it, and if any pixel covered by the mask is from the foreground, set the pixel to foreground.</p></li>
+</ul>
+<pre class="r"><code>shapes = readImage(system.file('images', 'shapes.png', package='EBImage'))
+logo = shapes[110:512,1:130]
+display(logo)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZMAAACCCAMAAAC0P9XZAAAABlBMVEUAAAD///+l2Z/dAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAE0UlEQVR4nO2Z227bQAxErf//6aJA+xCLc5MsaQPPeau4Q85yojhGX69SSimllFJKKaWUUkoppZRSSimllFJKKaWUUsp3s/1FHfnxr7nFP5JGYuz3sjmEAnfW9bf7nVyRCdp2M/G4JpN5383E46pMpoU3E4/LMjFCuemKv46zmZBGatjld/utfCwT51dTM7Gw9mgKZCjNxEJmkgiayUf4aCaqWzOxaCYL8tFMRLdm4tFM1qOZrEczWY9msh7xnprJ5TST9Vg9E3Y4aBT+IGwYpTAn+NPPCnizc7OiUXCqHEziQOpwguTcnuj3+OPS6fhPhbsGf7H0PJGGAxziVkwgemVSclN3DfO+8EXxe [...]
+<pre class="r"><code>kern = makeBrush(5, shape='diamond')
+display(kern, interpolate=FALSE)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAABGCAMAAABG8BK2AAAABlBMVEUAAAD///+l2Z/dAAAACXBIWXMAAJnKAACZygHjkaQiAAAARUlEQVRYhe3UIQ4AIAwEQfr/T6MxlyJISjKreyO7VqiO0mUMg8EMYaodBoP5gekPI4rBYEYys/4NBoN5yST0YojBYGYwG7aOCfU/O7o9AAAAAElFTkSuQmCC" style="display: block; margin: auto;" /></p>
+<pre class="r"><code>logo_erode= erode(logo, kern)
+logo_dilate = dilate(logo, kern)
+
+display(combine(logo_erode, logo_dilate), all=TRUE)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyYAAACCCAMAAACEjCxlAAAACVBMVEUAAACEhIT///8goa4iAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAKD0lEQVR4nO1d7ZLbOAxL9/0f+uamnXZjk/iQZctZA//OIEiICjbZTK99vYKb4dev1Q6C4PZITIKAIjEJAorEJAgoEpMgoEhMgoAiMQkCisQkCCgSkyCgSEyCgCIxCQKKxCQIKBKTIKBITIKAIjEJAorEJAgoEpMgoEhMgoAiMQkCisQkCCgSkyCgSEyCgCIxCQKKxCQIKBKTIKBITIKAQovJ1/9gJW//Vbf4A6cRGRsEF0CKyZcGTyDPmnHMIDgCJSZiSv69oO1Y4VnzThsEQ1gZkyoAiYmOw6vJklWsjQn+FSY3iGFv17+OUnrmmW6KxTGRbnLuiX8Oji2XPAXSk45zZ6yOyXbnQ [...]
+<p>Opening and closing are combinations of the two operations above: opening performs erosion followed by dilation, while closing does the opposite, i.e, performs dilation followed by erosion. Opening is useful for morphological noise removal, as it removes small objects from the background, and closing can be used to fill small holes in the foreground. These operations are implemented by <code>opening</code> and <code>closing</code>.</p>
+</div>
+</div>
+<div id="thresholding" class="section level1">
+<h1><span class="header-section-number">8</span> Thresholding</h1>
+<div id="global-thresholding" class="section level2">
+<h2><span class="header-section-number">8.1</span> Global thresholding</h2>
+<p>In the “Manipulating images” section we have already demonstrated how to set a global threshold on an image. There we used an arbitrary cutoff value. For images whose distribution of pixel intensities follows a bi-modal histogram a more systematic approach involves using the Otsu’s method. Otsu’s method is a technique to automatically perform clustering-based image thresholding. Assuming a bi-modal intensity distribution, the algorithm separates image pixels into foreground and backgr [...]
+<p>Otsu’s threshold can be calculated using the function <code>otsu</code>. When called on a multi-frame image, the threshold is calculated for each frame separately resulting in a output vector of length equal to the total number of frames in the image.</p>
+<pre class="r"><code>threshold = otsu(nuc)
+threshold</code></pre>
+<pre><code>[1] 0.3535156 0.4082031 0.3808594 0.4121094</code></pre>
+<pre class="r"><code>nuc_th = combine( mapply(function(frame, th) frame > th, getFrames(nuc), threshold, SIMPLIFY=FALSE) )
+display(nuc_th, all=TRUE)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAf0AAAH9CAMAAAAnElW6AAADAFBMVEUAAAABAQECAgIDAwMEBAQFBQUGBgYHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZ [...]
+<p>Note the use of <code>getFrames</code> to split the image into a list of individual frames, and <code>combine</code> to merge the results back together.</p>
+</div>
+<div id="adaptive-thresholding" class="section level2">
+<h2><span class="header-section-number">8.2</span> Adaptive thresholding</h2>
+<p>The idea of adaptive thresholding is that, compared to straightforward thresholding from the previous section, the threshold is allowed to be different in different regions of the image. In this way, one can anticipate spatial dependencies of the underlying background signal caused, for instance, by uneven illumination or by stray signal from nearby bright objects.</p>
+<p>Adaptive thresholding works by comparing each pixel’s intensity to the background determined from a local neighbourhood. This can be achieved by comparing the image to its smoothed version, where the filtering window is bigger than the typical size of objects we want to capture.</p>
+<pre class="r"><code>disc = makeBrush(31, "disc")
+disc = disc / sum(disc)
+offset = 0.05
+nuc_bg = filter2( nuc, disc )
+nuc_th = nuc > nuc_bg + offset
+display(nuc_th, all=TRUE)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAf0AAAH9CAMAAAAnElW6AAADAFBMVEUAAAABAQECAgIDAwMEBAQFBQUGBgYHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZ [...]
+<p>This technique assumes that the objects are relatively sparsely distributed in the image, so that the signal distribution in the neighborhood is dominated by background. While for the nuclei in our images this assumption makes sense, for other situations you may need to make different assumptions. The adaptive thresholding using a linear filter with a rectangular box is provided by <code>thresh</code>, which uses a faster implementation compared to directly using <code>filter2</code>.</p>
+<pre class="r"><code>display( thresh(nuc, w=15, h=15, offset=0.05), all=TRUE )</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAf0AAAH9CAMAAAAnElW6AAADAFBMVEUAAAABAQECAgIDAwMEBAQFBQUGBgYHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZ [...]
+</div>
+</div>
+<div id="image-segmentation" class="section level1">
+<h1><span class="header-section-number">9</span> Image segmentation</h1>
+<p>Image segmentation performs partitioning of an image, and is typically used to identify objects in an image. Non-touching connected objects can be segmented using the function <code>bwlabel</code>, while <code>watershed</code> and <code>propagate</code> use more sophisticated algorithms able to separate objects which touch each other.</p>
+<p><code>bwlabel</code> finds every connected set of pixels other than the background, and relabels these sets with a unique increasing integer. It can be called on a thresholded binary image in order to extract objects.</p>
+<pre class="r"><code>logo_label = bwlabel(logo)
+table(logo_label)</code></pre>
+<pre><code>logo_label
+    0     1     2     3     4     5     6     7 
+42217  1375  2012   934  1957  1135  1697  1063 </code></pre>
+<p>The pixel values of the <code>logo_label</code> image range from 0 corresponding to background to the number of objects it contains, which is given by</p>
+<pre class="r"><code>max(logo_label)</code></pre>
+<pre><code>[1] 7</code></pre>
+<p>To display the image we normalize it to the (0,1) range expected by the display function. This results in different objects being rendered with a different shade of gray.</p>
+<pre class="r"><code>display( normalize(logo_label) )</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZMAAACCCAMAAAC0P9XZAAAAGFBMVEUAAAAkJCRJSUltbW2SkpK2trbb29v////S/FSRAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAEjElEQVR4nO3c7XbiOgyF4QAF7v+OZ01nWowtbcm2/JHFfv81Jk2r57ihlDPHwRhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYY++wuf7Mekn50LdavSfAT3W76R+zVxVN6wtVOu9Yta/x3d85GmGgqNPE1xkRWoYmvUSYSCk18DTNxoEz6Fk9Xr4l2XEQhiaswk1KlvBhNXIHpvx6QfgwGb6LQxJVpkofmTpOQQk0sFJq4osmGhZocNImIJvtFk/2iyX7RZL9qTfDYaRIRTfZrd5Ovf1WvZd3/57vo8dBTznj+5LwCaqBJcWq1yVdaxdpb9zx8TcChmTzzH [...]
+<p>The horizontal grayscale gradient which can be observed reflects to the way <code>bwlabel</code> scans the image and labels the connected sets: from left to right and from top to bottom. Another way of visualizing the segmentation is to use the <code>colorLabels</code> function, which color codes the objects by a random permutation of unique colors.</p>
+<pre class="r"><code>display( colorLabels(logo_label) )</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZMAAACCCAMAAAC0P9XZAAAAGFBMVEUAAAAkSf9JJEltkiSS29u2bbbb/5L/tm049EhsAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAEi0lEQVR4nO3ca1fjOgyF4fRG//8/njXMQF1b2pJt+ZLV/X4jbgjoOSal9MxxMMYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGPrvL36yHpB/divVbEvxE97v+EXt18ZSecLPTrnXPGv/dnbMRJpoKTXyNMZFVaOJrlImEQhNfw0wcKJO+xdPVa6IdF1FI4irMpFQpL0YTV2D6rwekH4PBmyg0cWWa5KG50ySkUBMLhSauaLJhoSYHTSKiyX7RZL9osl802a9aEzx2mkREk/3a3eTxr+q1rOf/fBc9rnrKGV8/Oa+AGmhSnFpt8kirWHvrmYevCTg0k688x [...]
+<div id="watershed" class="section level2">
+<h2><span class="header-section-number">9.1</span> Watershed</h2>
+<p>Some of the nuclei in <code>nuc</code> are quite close to each other and get merged into one big object when thresholded, as seen in <code>nuc_th</code>. <code>bwlabel</code> would incorrectly identify them as a single object. The watershed transformation allows to overcome this issue. The <code>watershed</code> algorithm treats a grayscale image as a topographic relief, or heightmap. Objects that stand out of the background are identified and separated by flooding an inverted source  [...]
+<pre class="r"><code>nmask = watershed( distmap(nuc_th), 2 )
+display(colorLabels(nmask), all=TRUE)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAf0AAAH9CAIAAACfrjLfAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAgAElEQVR4nOydd3zTRhvHH3k7ey+yyWBvSCBsyqZQ9ihQKLuMFgoFCqWUXaBQyiqUVfaGsvcKK4S9Q0JIyN7b8fb7h/M6ji3JsiyvRN+P/5DuTqeHYP90eu655xCwEkKOxnB8g1Cr4gc0l2SmGOm+7ILvECceVm2HjNq9U+rr7GRWmy5ymYxSu2jw6DagwZAJETgNGAhzbPdtbmG9+X6d8bvKfrxRXJxM2hJ24XXtQknrcfAuiXgnYuZd9dOl8l1LFbv0teSHHq4/9HRVne6LLvzleLa+nWDRa/oPAOCr4GzftJp8Lwji8/sohUxecvVF6Y1XAABMhn3nhvZdGwOAQiLLWHCAGnOxWbL8sc42hQUZz56evXlju7GN8XKv1 [...]
+</div>
+<div id="voronoi-tesselation" class="section level2">
+<h2><span class="header-section-number">9.2</span> Voronoi tesselation</h2>
+<p>Voronoi tessellation is useful when we have a set of seed points (or regions) and want to partition the space that lies between these seeds in such a way that each point in the space is assigned to its closest seed. This function is implemented in <em>EBImage</em> by the function <code>propagate</code>. Let us illustrate the concept of Voronoi tessalation on a basic example. We use the nuclei mask <code>nmask</code> as seeds and partition the space between them.</p>
+<pre class="r"><code>voronoiExamp = propagate(seeds = nmask, x = nmask, lambda = 100)
+voronoiPaint = colorLabels (voronoiExamp)
+display(voronoiPaint)</code></pre>
+<pre><code>Only the first frame of the image stack is displayed.
+To display all frames use 'all = TRUE'.</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAf0AAAH9CAIAAACfrjLfAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAgAElEQVR4nOydd3wb533/v3eHvQESJMA9tPf0lCV5xtvxdpbTrLaJ0zRtmqS/tE3sjKZp06ZpkzSr2UljO07ikWXLU5ZsS7Y29xAl7oG9ceP5/XEkBJEgiHGHG3jeL/0BgsDdV6L4fj74Ps89R3zk/4ZhgfNX3QdqoZ71/nf0fwt5JT0SnPmz34hdj4AQe7XaHzmkrkIAfs4ee5h9TpJTX3uw5YaDrZKcWliePHPm1OSE1FXIkbZzTxjS66SuQqaQUhcgPdo2R+NL76v74dulLqTqeBe142Hq+sqfF0sfU+Vg78+jbXdi+1ceAdX/QfKSv6P2rfgy1UgfgykZjdQFiEId5ymwybMI3v70kH/mA08KXhVGJD5IXvJpzdX84 [...]
+<p>The basic definition of Voronoi tessellation, which we have given above, allows for two generalizations:</p>
+<ul>
+<li><p>By default, the space that we partition is the full, rectangular image area, but indeed we could restrict ourselves to any arbitrary subspace. This is akin to finding the shortest distance from each point to the next seed not in a simple flat landscape, but in a landscape that is interspersed by lakes and rivers (which you cannot cross), so that all paths need to remain on the land. <code>propagate</code> allows for this generalization through its <code>mask</code> argument.</p></li>
+<li><p>By default, we think of the space as flat – but in fact it could have hills and canyons, so that the distance between two points in the landscape not only depends on their x- and y-positions but also on the ascents and descents, up and down in z-direction, that lie in between. You can specify such a landscape to <code>propagate</code> through its <code>x</code> argument.</p></li>
+</ul>
+<p>Mathematically, we can say that instead of the simple default case (a flat rectangle image with an Euclidean metric), we perform the Voronoi segmentation on a Riemann manifold, which can have an arbitrary shape and an arbitrary metric. Let us use the notation <span class="math inline">\(x\)</span> and <span class="math inline">\(y\)</span> for the column and row coordinates of the image, and <span class="math inline">\(z\)</span> for the elevation of the landscape. For two neighboring [...]
+ds = \sqrt{ \frac{2}{\lambda+1} \left[ \lambda \left( dx^2 + dy^2 \right) + dz^2 \right] }.
+\]</span> For <span class="math inline">\(\lambda=1\)</span>, this reduces to <span class="math inline">\(ds = ( dx^2 + dy^2 + dz^2)^{1/2}\)</span>. Distances between points further apart are obtained by summing <span class="math inline">\(ds\)</span> along the shortest path between them. The parameter <span class="math inline">\(\lambda\ge0\)</span> has been introduced as a convenient control of the relative weighting between sideways movement (along the <span class="math inline">\(x\)< [...]
+<p>A more advanced application of <code>propagate</code> to the segmentation of cell bodies is presented in the “Cell segmentation example” section.</p>
+</div>
+</div>
+<div id="object-manipulation" class="section level1">
+<h1><span class="header-section-number">10</span> Object manipulation</h1>
+<div id="object-removal" class="section level2">
+<h2><span class="header-section-number">10.1</span> Object removal</h2>
+<p><em>EBImage</em> defines an object mask as a set of pixels with the same unique integer value. Typically, images containing object masks are the result of segmentation functions such as <code>bwalabel</code>, <code>watershed</code>, or <code>propagate</code>. Objects can be removed from such images by <code>rmObject</code>, which deletes objects from the mask simply by setting their pixel values to 0. By default, after object removal all the remaining objects are relabeled so that the [...]
+<pre class="r"><code>objects = list(
+    seq.int(from = 2, to = max(logo_label), by = 2),
+    seq.int(from = 1, to = max(logo_label), by = 2)
+    )
+logos = combine(logo_label, logo_label)
+z = rmObjects(logos, objects, reenumerate=FALSE)
+display(z, all=TRUE)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyYAAACCCAMAAACEjCxlAAAACVBMVEUAAACEhIT///8goa4iAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAGiklEQVR4nO2d0XYjKQwFM/7/j96zD7vj2AghuBLddNWjmdaVgDpxnMnMzw9cjD9/dncAcHnQBMAFTQBc0ATABU0AXNAEwAVNAFzQBMAFTQBc0ATABU0AXNAEwAVNAFzQBMAFTQBc0ATABU0AXNAEwAVNAFzQBMAFTQBc0ATABU0AXNAEwAVNAFzQBMAFTQBc0OSuvP7F+yOyrJSy9yFJk++NfL2REvk0XiMkZYnK3ocsTcpO8LGgSSH7NHngZktBk0J2avLA7RaCJoXs1eR5+62jdH+ffmybNXnehsvYqImq6n2o0cR6/ZFbLgJNCinWhLe5Mtx9RBMd5ZrgiYjSbXz6kdVr8vhvB [...]
+<p>In the example above we demonstrate how the object removal function can be applied to a multi-frame image by providing a list of object indicies to be removed from each frame. Additionally we have set <code>reenumerate</code> to <code>FALSE</code> keeping the original object IDs.</p>
+<pre class="r"><code>showIds = function(image) lapply(getFrames(image), function(frame) unique(as.vector(frame)))
+
+showIds(z)</code></pre>
+<pre><code>[[1]]
+[1] 0 1 3 5 7
+
+[[2]]
+[1] 0 2 4 6</code></pre>
+<p>Recall that 0 stands for the background. If at some stage we decide to relabel the objects, we can use for this the standalone function <code>reenumarate</code>.</p>
+<pre class="r"><code>showIds( reenumerate(z) )</code></pre>
+<pre><code>[[1]]
+[1] 0 1 2 3 4
+
+[[2]]
+[1] 0 1 2 3</code></pre>
+</div>
+<div id="filling-holes-and-regions" class="section level2">
+<h2><span class="header-section-number">10.2</span> Filling holes and regions</h2>
+<p>Holes in object masks can be filled using the function <code>fillHull</code>.</p>
+<pre class="r"><code>filled_logo = fillHull(logo)
+display(filled_logo)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZMAAACCCAMAAAC0P9XZAAAABlBMVEUAAAD///+l2Z/dAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAEkElEQVR4nO2Z0W4bQQwD7f//6SJAX2yvqOGu76AgnLdaK4oSGzRJH48QQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEMLf5vlD9+TlT2uJ/zhCzdi/y5NgNtBZ12/3O7kik+rayYRxTSbreycTxlWZrA6eTBiXZQJCuWnFX8d1mSxOnkgQyWQe6I6iwQolmSDaTJqGZPJ9rszkQy2ZIJLJQJLJPJLJPJLJPJLJPJLJPOw7JZPLSSbzmJ6JemwImX8RjKXeO+AEPv3Mbuf+bJZR04bP9juf0HJ2J8+9Ki9KSpCegVrj27GO7ooSW6p3XWqp+mdJSNIzOObobrQFnLLCFiK+Cy1V/ygJTTrQc [...]
+<p><code>floodFill</code> fills a region of an image with a specified color. The filling starts at the given point, and the filling region is expanded to a connected area in which the absolute difference in pixel intensities remains below <code>tolerance</code>. The color specification uses R color names for <code>Color</code> images, and numeric values for <code>Grayscale</code> images.</p>
+<pre class="r"><code>rgblogo = toRGB(logo)
+points = rbind(c(50, 50), c(100, 50), c(150, 50))
+colors = c("red", "green", "blue")
+rgblogo = floodFill(rgblogo, points, colors)
+display( rgblogo )</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZMAAACCCAMAAAC0P9XZAAAAD1BMVEUAAAAAAP8A/wD/AAD///9seLuhAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAES0lEQVR4nO2c2Y7jMAwEc/j/v3mxGCzGkchm06ZkedP1FlGXu0bOiXk8hBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQ3837L1GX/aNXV3/tgBM9n/4j8cubYT/gFeOt9WwYf3X3ZIQTz4qccIxxYluRE45RTiwpcsIxzAkhZdIl3o6zTrx2U4qUUJQ56a30i8kJBUj/t8P+MQg+lCInFKGTFpS7nJRQ6iSSIicUcrIgpU4eclKBnKyHnKyHnKyHnKxH1gmOXU4qkJP1WN3J9kO6Znel+u66G0QjyBUQA510Q9NOUBhMUH3HODWgwxudXCGk1Ak+JtCJcU3oUtkY+GBhf [...]
+<pre class="r"><code>display(floodFill(img, rbind(c(200, 300), c(444, 222)), col=0.2, tolerance=0.2))</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYAAAAEACAMAAACNqVFVAAACzVBMVEURERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZGRlZWVmZmZnZ2doaGhpaWlqampra2tsbGxtbW1ubm5vb29wcHBxcXFycnJzc3N0dHR1d [...]
+</div>
+<div id="highlighting-objects" class="section level2">
+<h2><span class="header-section-number">10.3</span> Highlighting objects</h2>
+<p>Given an image containing object masks, the function <code>paintObjects</code> can be used to highlight the objects from the mask in the target image provided in the <code>tgt</code> argument. Objects can be outlined and filled with colors of given opacities specified in the <code>col</code> and <code>opac</code> arguments, respectively. If the color specification is missing or equals <code>NA</code> it is not painted.</p>
+<pre class="r"><code>d1 = dim(img)[1:2]
+overlay = Image(dim=d1)
+d2 = dim(logo_label)-1
+
+offset = (d1-d2) %/% 2
+
+overlay[offset[1]:(offset[1]+d2[1]), offset[2]:(offset[2]+d2[2])] = logo_label
+
+img_logo = paintObjects(overlay, toRGB(img), col=c("red", "yellow"), opac=c(1, 0.3), thick=TRUE)
+
+display( img_logo )</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYAAAAEACAIAAAA1FTYwAAAACXBIWXMAAAewAAAHsAHUgoNiAAAgAElEQVR4nIR9249lR3X+qtr73LrP6cv03LpnjMHIBmwTEMgKwVbAjkAKUhQFlJc850/IH5THKFIU5SV5CkIYgqMgBAwhlm1imBnPjMczPdOX092nz9lVv4fP6/NXtdv57YfW6XP2rl21aq1vXasq/N3f/V2M0czMLMaYczaznHNKycxSSimlxWKxWCyOj4/Pz8/5E27LOYcQcCc+oAW0llLiDWzZzEII+gE38DP+bZoGjeAnbQEf0I2u62KMXdfxe3yDXzEiXni8emnTNMGvpmlijHgpvkcjMcYQQkoJH/AlB066sR2+SAeFFvAIu8Ex5pzZZkUQvUdJgRu6rkPLSh/9rI/gFZhEfq56gqnEoNgTkJS04q/8PqU0GAx0g [...]
+<p>In the example above we have created a new mask <code>overlay</code> matching the size of our target image <code>img</code>, and copied the mask containing the “EBImage” logo into that overlay mask. The output of <code>paintObjects</code> retains the color mode of its target image, therefore in order to have the logo highlighted in color it was necessary to convert <code>img</code> to an RGB image first, otherwise the result would be a grayscale image. The <code>thick</code> argument  [...]
+</div>
+</div>
+<div id="cell-segmentation-example" class="section level1">
+<h1><span class="header-section-number">11</span> Cell segmentation example</h1>
+<p>We conclude our vignette by applying the functions described before to the task of segmenting cells. Our goal is to computationally identify and qualitatively characterize the cells in the sample fluorescent microscopy images. Even though this by itself may seem a modest goal, this approach can be applied to collections containing thousands of images, an that need no longer to be an modest aim!</p>
+<p>We start by loading the images of nuclei and cell bodies. To visualize the cells we overlay these images as the green and the blue channel of a false-color image.</p>
+<pre class="r"><code>nuc = readImage(system.file('images', 'nuclei.tif', package='EBImage'))
+cel = readImage(system.file('images', 'cells.tif', package='EBImage'))
+
+cells = rgbImage(green=1.5*cel, blue=nuc)
+display(cells, all = TRUE)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAf0AAAH9CAIAAACfrjLfAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAgAElEQVR4nGS92a8lSXLe+TNzjzjLXfJmZmVmLV1bN7vRXERKA3A0iyBgMJh5EjCYl5k/ct71KIDAPIgEhqS6RYliV3d1rVm53fVsEeFuNg/uHudU86KQefPUWSLcbfnss8/8CJ9cgWCOg0B01InOmfOes4ClYU4POO7sYA9fGRsnlxc64kShhwWIk8EhC4OjoGDQCQo9KGQIAmAKkMEVB1GCIEof6JSlshRCZLVE3yM/YVrAe4xCUmyL3bF6Q/+K6we2I1tjdAYjgzsO5gAO7lCupNynQLlfIQu9IAGFLtIpZ4FVz2rFv73AX3BzQeh58QnygnRJv2DITG959CsubnjVoz9n+xHpCTvn/MA+o99zuWX5DZsv2b/l9cB3E [...]
+<p>First, we segment the nuclei using <code>thresh</code>, <code>fillHull</code>, <code>bwlabel</code> and <code>opening</code>.</p>
+<pre class="r"><code>nmask = thresh(nuc, w=10, h=10, offset=0.05)
+nmask = opening(nmask, makeBrush(5, shape='disc'))
+nmask = fillHull(nmask)
+nmask = bwlabel(nmask)
+
+display(nmask, all=TRUE)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAf0AAAH9CAMAAAAnElW6AAADAFBMVEUAAAABAQECAgIDAwMEBAQFBQUGBgYHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZ [...]
+<p>Next, we use the segmented nuclei as seeds in the Voronoi segmentation of the cytoplasm.</p>
+<pre class="r"><code>ctmask = opening(cel>0.1, makeBrush(5, shape='disc'))
+cmask = propagate(cel, seeds=nmask, mask=ctmask)
+
+display(ctmask, all=TRUE)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAf0AAAH9CAMAAAAnElW6AAADAFBMVEUAAAABAQECAgIDAwMEBAQFBQUGBgYHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZ [...]
+<p>To visualize our segmentation on the we use <code>paintObject</code>.</p>
+<pre class="r"><code>segmented = paintObjects(cmask, cells, col='#ff00ff')
+segmented = paintObjects(nmask, segmented, col='#ffff00')
+
+display(segmented, all=TRUE)</code></pre>
+<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAf0AAAH9CAIAAACfrjLfAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAgAElEQVR4nHS9eZxdRZn//36qzrn39t7Zd5IQkUURN2AYNgUZRxRBcXfEccSdLQSIISSdTggQtrDJoqKDIIiIiIwLKgoijojAKGFfsu/d6e70cpdzqur7R1Wdvpn5/fqVV3Jz+/a951Q99Xk+z+f5VLWwXzcI1uFAIHEod0L9uHp7/Yn9/kwZKhbrKAEO5xiDKmc/+7UbSzdh/A86xJEIJSiDOAw4MELdoUCBhVRQUAIFBrQAWAVgwCkciEILoihpUvXQGz865Z2fRSe0VFCTMRPJyie9Pf/tX2eQK+zoe9/x2h9egNJO9gw/9vxDx085iYajbjHgHA6sA3DgHPgr8fcp4O9XMEJJEI2CNCFVr259+oDDjqGlheM6cNMY6 [...]
+</div>
+<div id="session-info" class="section level1">
+<h1><span class="header-section-number">12</span> Session Info</h1>
+<pre class="r"><code>sessionInfo()</code></pre>
+<pre><code>R version 3.4.2 (2017-09-28)
+Platform: x86_64-pc-linux-gnu (64-bit)
+Running under: Ubuntu 16.04.3 LTS
+
+Matrix products: default
+BLAS: /home/biocbuild/bbs-3.6-bioc/R/lib/libRblas.so
+LAPACK: /home/biocbuild/bbs-3.6-bioc/R/lib/libRlapack.so
+
+locale:
+ [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
+ [3] LC_TIME=en_US.UTF-8        LC_COLLATE=C              
+ [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
+ [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
+ [9] LC_ADDRESS=C               LC_TELEPHONE=C            
+[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
+
+attached base packages:
+[1] stats     graphics  grDevices utils     datasets  methods   base     
+
+other attached packages:
+[1] EBImage_4.20.0  knitr_1.17      BiocStyle_2.6.0
+
+loaded via a namespace (and not attached):
+ [1] Rcpp_0.12.13        magrittr_1.5        BiocGenerics_0.24.0
+ [4] lattice_0.20-35     jpeg_0.1-8          stringr_1.2.0      
+ [7] tools_3.4.2         parallel_3.4.2      grid_3.4.2         
+[10] png_0.1-7           htmltools_0.3.6     yaml_2.1.14        
+[13] abind_1.4-5         rprojroot_1.2       digest_0.6.12      
+[16] bookdown_0.5        htmlwidgets_0.9     bitops_1.0-6       
+[19] fftwtools_0.9-8     RCurl_1.95-4.8      evaluate_0.10.1    
+[22] rmarkdown_1.6       tiff_0.1-5          stringi_1.1.5      
+[25] compiler_3.4.2      backports_1.1.1     locfit_1.5-9.1     
+[28] jsonlite_1.5       </code></pre>
+</div>
+
+
+
+</div>
+</div>
+
+</div>
+
+<script>
+
+// add bootstrap table styles to pandoc tables
+function bootstrapStylePandocTables() {
+  $('tr.header').parent('thead').parent('table').addClass('table table-condensed');
+}
+$(document).ready(function () {
+  bootstrapStylePandocTables();
+});
+
+
+</script>
+
+<script type="text/x-mathjax-config">
+  MathJax.Hub.Config({
+    "HTML-CSS": {
+      styles: {
+        ".MathJax_Display": {
+           "text-align": "center",
+           padding: "0px 150px 0px 65px",
+           margin: "0px 0px 0.5em"
+        },
+      }
+    }
+  });
+</script>
+<!-- dynamically load mathjax for compatibility with self-contained -->
+<script>
+  (function () {
+    var script = document.createElement("script");
+    script.type = "text/javascript";
+    script.src  = "https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
+    document.getElementsByTagName("head")[0].appendChild(script);
+  })();
+</script>
+
+</body>
+</html>
diff --git a/inst/htmlwidgets/displayWidget.js b/inst/htmlwidgets/displayWidget.js
new file mode 100644
index 0000000..fa5d09d
--- /dev/null
+++ b/inst/htmlwidgets/displayWidget.js
@@ -0,0 +1,23 @@
+HTMLWidgets.widget({
+
+  name: 'displayWidget',
+
+  type: 'output',
+
+  factory: function(el, width, height) {
+
+    var viewer = new Viewer(el.id);
+
+    return {
+
+      renderValue: function(x) {
+        viewer.reset(x.data, x.width, x.height);
+      },
+
+      resize: function(width, height) {
+        viewer.resetCanvas();
+      }
+
+    };
+  }
+});
diff --git a/inst/htmlwidgets/displayWidget.yaml b/inst/htmlwidgets/displayWidget.yaml
new file mode 100644
index 0000000..089a384
--- /dev/null
+++ b/inst/htmlwidgets/displayWidget.yaml
@@ -0,0 +1,6 @@
+dependencies:
+  - name: viewer
+    version: 1.0.0
+    src: htmlwidgets/lib/viewer
+    script: viewer.js
+    stylesheet: display.css
diff --git a/inst/htmlwidgets/lib/viewer/display.css b/inst/htmlwidgets/lib/viewer/display.css
new file mode 100755
index 0000000..944346b
--- /dev/null
+++ b/inst/htmlwidgets/lib/viewer/display.css
@@ -0,0 +1,106 @@
+.displayWidget {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background-color: #dddddd;
+ overflow: hidden;
+}
+.ebimage {
+ display: block;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ overflow: hidden;
+}
+.ebimage-toolbar {
+ overflow: inherit;
+ min-width: 128px;
+}
+.ebimage-buttons-nav, .ebimage-buttons-zoom {
+ float: left;
+ width: 50%;
+ min-width: 128px;
+}
+.ebimage-buttons-zoom {
+ text-align: right;
+ margin-right: -1px;
+}
+.ebimage-buttons-nav {
+ font-size: 20px;
+}
+.ebimage-buttons-zoom {
+ font-size: 16px;
+}
+.ebimage-button {
+ font-family: Arial,sans-serif;
+ font-size: inherit;
+ font-weight: bold;
+ width: 32px;
+ height: 32px;
+ padding: 0;
+ vertical-align: top;
+}
+.btn-org {
+ font-size: 12px;
+}
+.btn-fit {
+ line-height: 0px;
+}
+.ebimage-canvas {
+ position: relative;
+ z-index: 1;
+ background-color: #222222;
+}
+.ebimage-image {
+ max-width: none;
+ position: absolute;
+ z-index: 2;
+ cursor: crosshair;
+ image-rendering:optimizeSpeed;             /* Legal fallback                 */
+ image-rendering:-moz-crisp-edges;          /* Firefox                        */
+ image-rendering:-o-crisp-edges;            /* Opera                          */
+ image-rendering:-webkit-optimize-contrast; /* Chrome (and eventually Safari) */
+ image-rendering:optimize-contrast;         /* CSS3 Proposed                  */
+ -ms-interpolation-mode:nearest-neighbor;   /* IE8+                           */
+}
+.ebimage-status {
+ font-size: 14px;
+ font-family: "Lucida Console", Monaco, monospace;
+ margin-right: 10px;
+ padding: 1px;
+ float: left;
+}
+.ebimage-help {
+ position: relative;
+ margin: auto;
+ top: 50px;
+ width: 360px;
+ border-radius: 10px;
+ overflow: auto;
+ border: 1px solid #eeeeee;
+ z-index: 3;
+ display: none;
+}
+.ebimage-help-key {
+ font-family: "Lucida Console", Monaco, monospace;
+ font-weight: bold;
+}
+.ebimage-help-topic {
+ font-size: 14px;
+ font-weight: bold;
+ padding-top: 5px;
+}
+.ebimage-help-close {
+ text-align: center;
+ font-weight: bold;
+ padding: 5px;
+}
+.ebimage-help-table {
+ background-color: white;
+ opacity: 0.8;
+ border-radius: 8px;
+ padding-left: 5px;
+ padding-right: 5px;
+ font-size: 12px;
+ font-family: Tahoma, Geneva, sans-serif;
+}
diff --git a/inst/htmlwidgets/lib/viewer/viewer.js b/inst/htmlwidgets/lib/viewer/viewer.js
new file mode 100644
index 0000000..20d23cd
--- /dev/null
+++ b/inst/htmlwidgets/lib/viewer/viewer.js
@@ -0,0 +1,611 @@
+/*
+	EBImage JavaScript Image Viewer
+	Copyright (c) 2012 Andrzej Oles
+*/
+
+function Viewer(parent){
+  var viewer = this;
+	var data = null;
+  var numberOfFrames = 0;
+
+  var originalWidth 	= 0;
+  var originalHeight	= 0;
+
+	var currentFrame = 1;
+	var zoomLevel = null, minZoomLevel = -12, maxZoomLevel = 6; // 'zoomLevel == 0' is 100%, 'zoomLevel == null' is autofit
+	var previousMousePosition = null;
+	var baseMouseSpeed = currentMouseSpeed = minMouseSpeed = 2;
+
+	// viever DOM elements
+	var body = document.getElementById(parent);
+	var canvas = null;
+	var image = null;
+	var toolbar = null;
+	var statusbar = null;
+	var help = null;
+	var buttons = [];
+	var status = [];
+	
+	var navEnabled = {
+	  first: "<img src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ij4NCiAgPHBvbHlnb24gcG9pbnRzPSIxNiw0IDksOCAxNiwxMiIgc3R5bGU9ImZpbGw6YmxhY2s7Ii8+DQogIDxwb2x5Z29uIHBvaW50cz0iOSw0IDIsOCA5LDEyIiBzdHlsZT0iZmlsbDpibGFjazsiLz4NCiAgPHBvbHlnb24gcG9pbnRzPSIyLDQgMSw0IDEsMTIgMiwxMiIgc3R5bGU9ImZpbGw6YmxhY2s7Ii8+DQo8L3N2Zz4='>",
+	  prev: "<img src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ij4NCiAgPHBvbHlnb24gcG9pbnRzPSIxMSw0IDQsOCAxMSwxMiIgc3R5bGU9ImZpbGw6YmxhY2s7Ii8+DQo8L3N2Zz4='>",
+	  next: "<img src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ij4NCiAgPHBvbHlnb24gcG9pbnRzPSI1LDQgMTIsOCA1LDEyIiBzdHlsZT0iZmlsbDpibGFjazsiLz4NCjwvc3ZnPg=='>",
+	  last: "<img src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ij4NCiAgPHBvbHlnb24gcG9pbnRzPSIwLDQgNyw4IDAsMTIiIHN0eWxlPSJmaWxsOmJsYWNrOyIvPg0KICA8cG9seWdvbiBwb2ludHM9IjcsNCAxNCw4IDcsMTIiIHN0eWxlPSJmaWxsOmJsYWNrOyIvPg0KICA8cG9seWdvbiBwb2ludHM9IjE0LDQgMTUsNCAxNSwxMiAxNCwxMiIgc3R5bGU9ImZpbGw6YmxhY2s7Ii8+DQo8L3N2Zz4='>"
+	};
+	var navDisabled = {
+	  first:
+	"<img src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ij4NCiAgPHBvbHlnb24gcG9pbnRzPSIxNiw0IDksOCAxNiwxMiIgc3R5bGU9ImZpbGw6IzgwODA4MDsiLz4NCiAgPHBvbHlnb24gcG9pbnRzPSI5LDQgMiw4IDksMTIiIHN0eWxlPSJmaWxsOiM4MDgwODA7Ii8+DQogIDxwb2x5Z29uIHBvaW50cz0iMiw0IDEsNCAxLDEyIDIsMTIiIHN0eWxlPSJmaWxsOiM4MDgwODA7Ii8+DQo8L3N2Zz4='>",
+	  prev: "<img src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ij4NCiAgPHBvbHlnb24gcG9pbnRzPSIxMSw0IDQsOCAxMSwxMiIgc3R5bGU9ImZpbGw6IzgwODA4MDsiLz4NCjwvc3ZnPg=='>",
+	  next: "<img src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ij4NCiAgPHBvbHlnb24gcG9pbnRzPSI1LDQgMTIsOCA1LDEyIiBzdHlsZT0iZmlsbDojODA4MDgwOyIvPg0KPC9zdmc+'>",
+	  last: "<img src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ij4NCiAgPHBvbHlnb24gcG9pbnRzPSIwLDQgNyw4IDAsMTIiIHN0eWxlPSJmaWxsOiM4MDgwODA7Ii8+DQogIDxwb2x5Z29uIHBvaW50cz0iNyw0IDE0LDggNywxMiIgc3R5bGU9ImZpbGw6IzgwODA4MDsiLz4NCiAgPHBvbHlnb24gcG9pbnRzPSIxNCw0IDE1LDQgMTUsMTIgMTQsMTIiIHN0eWxlPSJmaWxsOiM4MDgwODA7Ii8+DQo8L3N2Zz4='>"
+	};
+	
+/* Internal functions */
+
+	this.setImagePosition = function(x, y) {
+		image.style.left=(Math.round(x)+'px');
+		image.style.top=(Math.round(y)+'px');
+	}
+	this.getImagePosition = function() {
+		return [stripUnits(image.style.left,'px'), stripUnits(image.style.top,'px')];
+	}
+
+	//return zoom factor given a zoom level
+	this.getZoomFactor = function(zoomLevel) {
+		if(typeof zoomLevel=='undefined')
+			zoomLevel=zoomLevel;
+		if(zoomLevel==null)
+			return image.width/originalWidth;
+		else if(zoomLevel>=0)
+			// increase by doubling the size
+			return Math.pow(2, zoomLevel);
+		else
+			// decrease each time by 1/3
+			return Math.floor(100 * Math.pow(2, Math.ceil(zoomLevel/2) ) * Math.pow(3/2, zoomLevel%2) ) /100;
+	}
+
+	this.zoom = function(dir, x, y) { // direction = 1 (zoom in) or -1 (zoom out)
+		if(zoomLevel==null){
+			zoomLevel = 0;
+			var currentZoomFactor = image.width/originalWidth;
+
+			// find from above
+			if(currentZoomFactor > 1)
+				while(viewer.getZoomFactor(zoomLevel)<currentZoomFactor) zoomLevel++;
+			else
+				while(viewer.getZoomFactor(zoomLevel-1)>=currentZoomFactor) zoomLevel--;
+			if ( (viewer.getZoomFactor(zoomLevel)!=currentZoomFactor) && (dir==1) )
+				zoomLevel--;
+		}
+		viewer.zoomTo(zoomLevel+dir, x, y);
+	}
+
+	this.zoomTo = function(newZoomLevel, x, y) {
+		// valid range?
+		if( newZoomLevel<minZoomLevel || newZoomLevel>maxZoomLevel )
+			return false;
+		// within the canvas?
+		if( x<0 || y<0 || x>=canvas.clientWidth || y>=canvas.clientHeight )
+			return false;
+
+		var zoomFactor = viewer.getZoomFactor(newZoomLevel);
+		var size = [originalWidth * zoomFactor, originalHeight * zoomFactor];
+		var position = viewer.getImagePosition();
+
+		position[0]-=((x-position[0])*((size[0]/image.width)-1));
+		position[1]-=((y-position[1])*((size[1]/image.height)-1));
+
+		viewer.updateImage(size[0], size[1], position[0], position[1], newZoomLevel);
+
+		// button locking
+		buttons['in'].disable(newZoomLevel==maxZoomLevel);
+		buttons['out'].disable(newZoomLevel==minZoomLevel);
+		buttons['org'].disable(viewer.getZoomFactor()==1);
+		buttons['fit'].disable(false).style.color='';
+
+		return true;
+	}
+
+	this.resetZoom = function() {
+		zoomLevel = null;
+
+		// scale image down when its dimensions exceed canvas size
+		var canvasSize = [canvas.clientWidth, canvas.clientHeight];
+		var downscale = ( canvasSize[0]>originalWidth && canvasSize[1]>originalHeight ) ? false : true;
+		var imageSize = downscale ? viewer.fitToCanvas() : [originalWidth, originalHeight];
+
+		viewer.updateImage(imageSize[0], imageSize[1], 0, 0, zoomLevel);
+
+		// lock buttons
+		buttons['fit'].disable(true).style.color='#FF0000';
+		buttons['org'].disable(!downscale);
+	}
+
+	this.updateImage = function(w, h, x, y, newZoomLevel) {
+		// set image size
+		image.width = Math.round(w);
+		image.height = Math.round(h);
+
+		// set image position
+		var position = viewer.centerImage(w, h, x ,y);
+		viewer.setImagePosition(position[0], position[1]);
+
+		zoomLevel = newZoomLevel;
+		baseMouseSpeed = currentMouseSpeed = (viewer.getZoomFactor() > minMouseSpeed) ? Math.round(viewer.getZoomFactor()) : minMouseSpeed;
+		viewer.updateStatusField("Zoom", Math.round( 100 * viewer.getZoomFactor() )+'%');
+	}
+
+	this.centerImage = function(w, h, x, y) {
+		var canvasSize = [canvas.clientWidth, canvas.clientHeight];
+
+		if(w<=canvasSize[0])
+			x = Math.round((canvasSize[0] - w)/2);
+		if(h<=canvasSize[1])
+			y = Math.round((canvasSize[1] - h)/2);
+
+		if(w>canvasSize[0]) {
+			if(x>0)
+				x=0;
+			else if((x+w)<canvasSize[0])
+				x=canvasSize[0]-w;
+		}
+
+		if(h>canvasSize[1]) {
+			if(y>0)
+				y=0;
+			else if((y+h)<canvasSize[1])
+				y=canvasSize[1]-h;
+		}
+
+		return [x,y];
+	}
+
+	this.fitToCanvas = function() {
+		var canvasSize = [canvas.clientWidth, canvas.clientHeight];
+
+		var newWidth = canvasSize[0];
+		var newHeight = Math.round((newWidth*originalHeight)/originalWidth);
+		if(newHeight>(canvasSize[1])) {
+			newHeight = canvasSize[1];
+			newWidth = Math.round((newHeight*originalWidth)/originalHeight);
+		}
+		return [newWidth,newHeight];
+	}
+
+	this.moveImage = function(x, y) {
+		var position = viewer.getImagePosition();
+		position = viewer.centerImage(image.width, image.height, position[0]+x, position[1]+y);
+		viewer.setImagePosition(position[0], position[1]);
+	}
+
+	this.resetCanvas = function(){
+		// recalculate canvas size
+		var windowSize = [body.clientWidth, body.clientHeight];//getWindowSize();
+		var newCanvasSize = [windowSize[0], windowSize[1] - (toolbar.offsetHeight+statusbar.offsetHeight)];
+		// set new canvas size
+		canvas.style.width = (Math.round(newCanvasSize[0])+'px');
+		canvas.style.height = (Math.round(newCanvasSize[1])+'px');
+
+		// redraw image on canvas
+		if(zoomLevel == null)
+			viewer.autofitImage();
+		else
+			viewer.zoomTo(zoomLevel, canvas.clientWidth/2, canvas.clientHeight/2);
+	}
+
+	this.setFrame = function(frame) {
+		if(typeof frame=='undefined')
+			frame = 1;
+		if( frame<1 || frame>numberOfFrames )
+			return false;
+
+		image.src = data[(frame-1)];
+
+		currentFrame = frame;
+		viewer.updateStatusField("Frame", currentFrame+'/'+numberOfFrames);
+
+		// button locking
+		buttons['first'].disable(currentFrame==1);
+		buttons['prev'].disable(currentFrame==1);
+		buttons['next'].disable(currentFrame==numberOfFrames);
+		buttons['last'].disable(currentFrame==numberOfFrames);
+
+		return true;
+	}
+
+	this.updateStatusField = function(name, value) {
+		status[name].innerHTML = name+': '+value+' ';
+	}
+
+	this.clearStatusField = function(name) {
+		status[name].innerHTML = '';
+	}
+
+	this.getPixelPosition = function(event){
+		var mousePos = getMouseXY(event);
+		var imagePos = getObjectXY(image);
+		var zoomFactor = viewer.getZoomFactor();
+		return [Math.floor((mousePos[0]-imagePos[0])/zoomFactor) + 1, Math.floor((mousePos[1]-imagePos[1])/zoomFactor) + 1];
+	}
+
+	this.updatePixelPosition = function(event) {
+		event = preProcessEvent(event);
+
+		var pixelPos = viewer.getPixelPosition(event);
+		(pixelPos[0]<1 || pixelPos[1]<1 || pixelPos[0]>originalWidth || pixelPos[1]>originalHeight) ? viewer.clearStatusField("Position") : viewer.updateStatusField("Position", '('+pixelPos+')');
+	}
+
+	this.clearPixelPosition = function(event) {
+		event = preProcessEvent(event);
+
+		viewer.clearStatusField('Position');
+	}
+
+/* Helper functions */
+
+	getWindowSize = function() {
+		var winW = 630, winH = 460;
+		if (body && body.offsetWidth) {
+			winW = body.clientWidth;
+			winH = body.clientHeight;
+		}
+		return [winW,winH];
+	}
+
+	getObjectXY = function(object) {
+		var left = 0, top = 0;
+		if (object.offsetParent)
+			do {
+				left += object.offsetLeft;
+				top += object.offsetTop;
+			}
+			while (object = object.offsetParent);
+		return [left, top];
+	}
+
+	getMouseXY = function(event) {
+		var posX = 0, posY = 0;
+		if (!event) event = window.event;	//firefox
+		if (event.pageX || event.pageY) {
+			posX = event.pageX;
+			posY = event.pageY;
+		}
+		else if (event.clientX || event.clientY) {	//IE
+			posX = event.clientX + document.body.scrollLeft
+				+ document.documentElement.scrollLeft;
+			posY = event.clientY + document.body.scrollTop
+				+ document.documentElement.scrollTop;
+		}
+		return [posX,posY];
+	}
+
+	stripUnits = function(string, units) {
+		if(typeof string=='number')
+			return string;
+		var result = string.indexOf(units);
+		return parseInt(string.substring(0, (result!=-1) ? result : string.length));
+	}
+
+/* User actions */
+
+	// frame navigation
+	this.firstFrame = function() {
+		viewer.setFrame(1);
+	}
+	this.prevFrame = function() {
+		viewer.setFrame(currentFrame-1);
+	}
+	this.nextFrame = function() {
+		viewer.setFrame(currentFrame+1);
+	}
+	this.lastFrame = function() {
+		viewer.setFrame(numberOfFrames);
+	}
+	// zooming
+	this.zoomIn = function() {
+		viewer.zoom(+1, canvas.clientWidth/2, canvas.clientHeight/2);
+	}
+	this.zoomOut = function() {
+		viewer.zoom(-1, canvas.clientWidth/2, canvas.clientHeight/2);
+	}
+	this.originalSize = function() {
+		viewer.zoomTo(0, canvas.clientWidth/2, canvas.clientHeight/2);
+	}
+	this.autofitImage = function() {
+		viewer.resetZoom();
+	}
+	// help
+	this.showHelp = function() {
+		help.style.display = 'block';
+	}
+	this.hideHelp = function() {
+		help.style.display = 'none';
+	}
+
+/* Mouse and keyboard actions */
+
+	setUpMouseWheelAction = function(action) {
+		if (body.addEventListener) {
+    	// IE9, Chrome, Safari, Opera
+    	body.addEventListener("mousewheel", action, false);
+  	  // Firefox
+    	body.addEventListener("DOMMouseScroll", action, false);
+    }
+    // IE 6/7/8
+    else body.attachEvent("onmousewheel", action);
+	}
+
+	preProcessEvent = function(e) {
+		if (!e) //For IE
+			e = window.event, e.returnValue = false;
+		else if (e.preventDefault)
+			e.preventDefault();
+
+		return e;
+	}
+
+	this.onmousewheel = function(event) {
+		event = preProcessEvent(event);
+
+		var direction = 0; // up is +, down is -
+
+		if (event.wheelDelta) 	// IE & Chrome
+			direction = (event.wheelDelta > 1) ? 1 : -1;
+		else if (event.detail) // FF & Opera
+			direction = (event.detail < 1) ? 1 : -1;
+
+		var mousePos = getMouseXY(event);
+		var canvasPos = getObjectXY(canvas);
+		viewer.zoom(direction, mousePos[0]-canvasPos[0], mousePos[1]-canvasPos[1]);
+
+		return false;
+	}
+
+	this.grabImage =  function(event) {
+		event = preProcessEvent(event);
+
+		// set mouse cursor
+		var imageSize = [image.width, image.height], canvasSize = [canvas.clientWidth, canvas.clientHeight];
+		var cursor = 'crosshair';
+		if(imageSize[0]>canvasSize[0] && imageSize[1]>canvasSize[1])
+			cursor = 'move';
+		else if(imageSize[0]>canvasSize[0])
+			cursor = 'e-resize';
+		else if(imageSize[1]>canvasSize[1])
+			cursor = 'n-resize';
+
+		image.style.cursor = cursor;
+		previousMousePosition = getMouseXY(event);
+		image.onmousemove = viewer.dragImage;
+	}
+
+	this.releaseImage = function(event) {
+		event = preProcessEvent(event);
+
+		image.style.cursor = 'crosshair';
+		image.onmousemove = null;
+	}
+
+	this.dragImage = function(event) {
+		event = preProcessEvent(event);
+
+		var mousePosition = getMouseXY(event);
+		viewer.moveImage(mousePosition[0]-previousMousePosition[0], mousePosition[1]-previousMousePosition[1]);
+		previousMousePosition = mousePosition;
+	}
+
+	this.onkeydown = function(event) {
+
+		event = preProcessEvent(event);
+		var keyCode = event.which || event.keyCode;
+
+		var shift = [0, 0];
+
+		switch(keyCode){
+
+		// browsing
+			// next frame
+			case 33: // PageUp
+			case 190: // . >
+				viewer.nextFrame();
+				break;
+			// previous frame
+			case 34: // PageDown
+			case 188: // , <
+				viewer.prevFrame();
+				break;
+			// last frame
+			case 35: // End
+			case 191: // / ?
+				viewer.lastFrame();
+				break;
+			// first frame
+			case 36: // Home
+			case 77: // m
+				viewer.firstFrame();
+				break;
+		// zooming
+			// zoom in
+			case 88: // x
+			case 43: // + / Numpad +
+			case 61:
+			case 107:
+			case 187:
+				viewer.zoomIn();
+				break;
+			// zoom out
+			case 90: // z
+			case 45: // - / Numpad -
+			case 109:
+			case 173:
+			case 189:
+				viewer.zoomOut();
+				break;
+			// reset zoom to 100%
+			case 82: // r
+			case 8: // Backspace
+				viewer.originalSize();
+				break;
+			// fit-in
+			case 32: // Space
+			case 13: // Enter
+				viewer.autofitImage();
+				break;
+		// moving
+			case 37: // Left arrow
+				shift[0] += currentMouseSpeed;
+				break;
+			case 38: // Up arrow
+				shift[1] += currentMouseSpeed;
+				break;
+			case 39: // Right arrow
+				shift[0] -= currentMouseSpeed;
+				break;
+			case 40: // Down arrow
+				shift[1] -= currentMouseSpeed;
+				break;
+		// help
+			// show
+			case 72: // h
+				viewer.showHelp();
+				break;
+			// hide
+			case 27: // Esc
+			case 81: // q
+				viewer.hideHelp();
+				break;
+		// debug
+			case 68: // d
+				break;
+		}
+
+		if( keyCode>=37 && keyCode<=40){ // when moving
+			viewer.moveImage(shift[0], shift[1]);
+			currentMouseSpeed += baseMouseSpeed;
+		}
+	}
+
+	this.onkeyup = function(event) {
+		currentMouseSpeed = baseMouseSpeed;
+	}
+
+/* DOM elements creation */
+
+	createElement = function(type, className, parent) {
+		var element = document.createElement(type);
+		element.className = "ebimage-" + className;
+		parent.appendChild(element);
+		return element;
+	}
+
+	createButton = function(name, value, title, onclick, group) {
+		var button = createElement('button', "button", group);
+		button.className += " btn-" + name;
+		button.innerHTML = value;
+		button.title = title;
+		button.onclick = onclick;
+		button.disable = function(disable){this.disabled=disable; this.blur(); return this;};
+		return (buttons[name] = button);
+	}
+
+  createNavButton = function(name, title, onclick) {
+		var button = createElement('button', "button", navbuttons);
+		button.className += " btn-" + name;
+		button.title = title;
+		button.onclick = onclick;
+		button.disable = function(disable) {
+		  this.disabled = disable;
+		  this.innerHTML = disable ? navDisabled[name] : navEnabled[name];
+		  this.blur();
+		  return this;
+		};
+		return (buttons[name] = button);
+	}
+
+	createStatusField = function(name) {
+	  var statusField = createElement('div', 'status ebimage', statusbar)
+	  statusField.className += " sts-" + name;
+		return (status[name] = statusField);
+	}
+
+/* Set image */
+  this.reset = function(img, width, height) {
+    if (!(img instanceof Array)) {
+        data = [img];
+      } else {
+        data = img;
+      }
+
+    numberOfFrames = data.length;
+
+    originalWidth 	= width;
+  	originalHeight	= height;
+
+  	// set up image
+		viewer.setFrame();
+		image.onload = viewer.resetCanvas();
+
+		viewer.updateStatusField("Image", originalWidth+'x'+originalHeight);
+  }
+
+/* Viewer initialization */
+	this.init = function() {
+
+		// create toolbar
+		toolbar = createElement('div', 'toolbar ebimage', body);
+		// create button containers
+		navbuttons = createElement('div', 'buttons-nav ebimage', toolbar);
+		zoombuttons = createElement('div', 'buttons-zoom ebimage', toolbar);
+
+		// create navigation buttons
+		createNavButton('first','First frame [HOME]/[m] ',viewer.firstFrame);
+		createNavButton('prev','Previous frame [PAGE DOWN]/[<]',viewer.prevFrame);
+		createNavButton('next','Next frame [PAGE UP]/[>]',viewer.nextFrame);
+		createNavButton('last','Last frame [END]/[?]',viewer.lastFrame);
+
+		// create zoom buttons
+		createButton('in','+','Zoom in [+]/[x]', viewer.zoomIn, zoombuttons);
+		createButton('out','−','Zoom out [-]/[z]',viewer.zoomOut, zoombuttons);
+		createButton('org','1:1','Original size [BACKSPACE]/[r]',viewer.originalSize, zoombuttons);
+		createButton('fit','∗<br/> ','Fit image [SPACE]/[ENTER]',viewer.autofitImage, zoombuttons);
+
+		// create canvas
+		canvas = createElement('div', 'canvas ebimage', body)
+
+		//create statusbar
+		statusbar = createElement('div', 'statusbar ebimage', body);
+
+		// create statusbar elements
+		createStatusField("Image"), createStatusField("Frame"), createStatusField("Zoom"), createStatusField("Position");
+
+		// create help
+		help = createElement('div', 'help ebimage', canvas)
+		help.innerHTML = '<table class="ebimage-help-table"><tr><td colspan="3" class="ebimage-help-topic">Browsing</td></tr><tr><td colspan="3">Use toolbar buttons or the following keys to change between the frames:</td></tr><tr><td>Next frame</td><td class="ebimage-help-key">PAGE UP</td><td class="ebimage-help-key">&gt</td></tr><tr><td>Previous frame</td><td class="ebimage-help-key">PAGE DOWN</td><td class="ebimage-help-key">&lt</td></tr><tr><td>First frame</td><td class="ebimage-help-key">H [...]
+
+		// create image
+		image = createElement('img', 'image ebimage', canvas);
+
+		// set up mouse and keyboard actions
+		// use mousewheel for zooming
+    setUpMouseWheelAction(viewer.onmousewheel);
+
+		// grab and pan image on click
+		image.onmousedown = viewer.grabImage;
+		image.onmouseup = image.onmouseout = viewer.releaseImage;
+		image.onmousemove = null;
+		// read keystrokes, http://jsfiddle.net/erCEq/173/
+		body.tabIndex = 0;
+		body.style.outline = "none";
+		body.onmouseover = function() { this.focus(); };
+    body.onmouseout = function() { this.blur(); };
+    body.onfocus = function() { this.onkeydown = viewer.onkeydown; };
+		// read current pixel position
+		canvas.onmousemove = viewer.updatePixelPosition;
+		canvas.onmouseout = viewer.clearPixelPosition;
+
+		// reset view on window resize
+		//window.onresize = resetCanvas;
+	}
+
+	this.onload = viewer.init();
+}
diff --git a/inst/images/cells.tif b/inst/images/cells.tif
new file mode 100644
index 0000000..23b3c41
Binary files /dev/null and b/inst/images/cells.tif differ
diff --git a/inst/images/nuclei.tif b/inst/images/nuclei.tif
new file mode 100644
index 0000000..29a5514
Binary files /dev/null and b/inst/images/nuclei.tif differ
diff --git a/inst/images/sample-color.png b/inst/images/sample-color.png
new file mode 100644
index 0000000..99257c6
Binary files /dev/null and b/inst/images/sample-color.png differ
diff --git a/inst/images/sample.png b/inst/images/sample.png
new file mode 100644
index 0000000..dacd7e5
Binary files /dev/null and b/inst/images/sample.png differ
diff --git a/inst/images/shapes.png b/inst/images/shapes.png
new file mode 100644
index 0000000..b3e1557
Binary files /dev/null and b/inst/images/shapes.png differ
diff --git a/inst/scripts/figDistanceMapDemo.R b/inst/scripts/figDistanceMapDemo.R
new file mode 100644
index 0000000..52a6ae0
--- /dev/null
+++ b/inst/scripts/figDistanceMapDemo.R
@@ -0,0 +1,27 @@
+## This scripts makes two figures for the explanation
+##   of the distance map transformation that we use in talks
+## WH 5.4.2007
+##
+library("EBImage")
+library("RColorBrewer")
+library("lattice")
+
+h = matrix(0, nrow=500, ncol=500)
+
+x=140; y=140; r=80
+h[ ((col(h)-x)^2+(row(h)-y)^2) < r^2 ] = 1
+
+x=250; y=290; r=150
+h[ ((col(h)-x)^2+(row(h)-y)^2) < r^2 ] = 1
+
+dm = distMap(Image(h, dim=dim(h)))
+
+png("tmp/distmap1.png", width=500, heigth=500)
+par(mai=rep(0,4))
+image(h, col=c("white", "black"))
+dev.off()
+
+png("tmp/distmap2.png", width=512, heigth=660)
+print(levelplot(matrix(dm, nrow=nrow(h), ncol=ncol(h)),
+   col.regions=colorRampPalette(c("white", brewer.pal(9, "YlOrRd")))(256)))
+dev.off()
diff --git a/inst/scripts/readWriteTest.R b/inst/scripts/readWriteTest.R
new file mode 100644
index 0000000..ca908a1
--- /dev/null
+++ b/inst/scripts/readWriteTest.R
@@ -0,0 +1,49 @@
+## cat readWriteTest.R | R --vanilla &>readWriteTest.Rout.save
+
+library("EBImage")
+
+## Fetch test images frome the on-line respository
+repositoryURL  = "http://www-huber.embl.de/EBImage/ExampleImages"
+
+testImages = c(	
+  "lena.jpg",		# JPEG Greyscale
+  "lena-color.jpg",	# JPEG Color
+  "dices-grey.png",	# PNG GA
+  "dices.png",		# PNG RGBA
+  "photo_16b.png",	# PNG RGB 16bps
+  "dices-grey.tif",	# TIFF GA 8bps
+  "photo_8b.tif",	# TIFF RGB 8bps
+  "lena-original.tif",	# TIFF RGB 8bps
+  "dices.tif",		# TIFF RGBA 8bps
+  "nuc.tif",		# TIFF G 16bps
+  "photo_16b.tif"	# TIFF RGB 8bps multiple pages
+)
+
+names(testImages) = rep(repositoryURL, length(testImages))
+
+for (i in seq_along(testImages))
+  if (!file.exists(testImages[i]))
+    system(paste("wget ", names(testImages)[i], "/", testImages[i], sep=""))
+
+## Append package test images
+
+packageTestImages = list.files(system.file("images", package="EBImage"), full.names=TRUE)
+names(packageTestImages) =  list.files(system.file("images", package="EBImage"))
+names(testImages) = testImages
+testImages = c(testImages, packageTestImages)
+
+## Actual tests
+
+tempdir = file.path(tempdir(),"copies")
+dir.create(tempdir)
+
+for (i in seq_along(testImages)) {
+  original = readImage(testImages[i])
+  filename = names(testImages)[i]
+  tempfile = file.path(tempdir, filename)
+  writeImage(original, tempfile)
+  copy = readImage(tempfile)
+  cat(filename, rep(" ", 40-nchar(filename)), if(identical(original, copy)) "PASS" else "FAIL", "\n", sep="")	
+}
+
+unlink(tempdir, recursive=TRUE)
\ No newline at end of file
diff --git a/inst/scripts/readWriteTest.Rout.save b/inst/scripts/readWriteTest.Rout.save
new file mode 100644
index 0000000..8d245ce
--- /dev/null
+++ b/inst/scripts/readWriteTest.Rout.save
@@ -0,0 +1,114 @@
+
+R version 2.15.1 (2012-06-22) -- "Roasted Marshmallows"
+Copyright (C) 2012 The R Foundation for Statistical Computing
+ISBN 3-900051-07-0
+Platform: x86_64-unknown-linux-gnu (64-bit)
+
+R is free software and comes with ABSOLUTELY NO WARRANTY.
+You are welcome to redistribute it under certain conditions.
+Type 'license()' or 'licence()' for distribution details.
+
+  Natural language support but running in an English locale
+
+R is a collaborative project with many contributors.
+Type 'contributors()' for more information and
+'citation()' on how to cite R or R packages in publications.
+
+Type 'demo()' for some demos, 'help()' for on-line help, or
+'help.start()' for an HTML browser interface to help.
+Type 'q()' to quit R.
+
+> ## cat readWriteTest.R | R --vanilla &>readWriteTest.Rout.save
+> 
+> library("EBImage")
+> 
+> ## Fetch test images frome the on-line respository
+> repositoryURL  = "http://www-huber.embl.de/EBImage/ExampleImages"
+> 
+> testImages = c(	
++   "lena.jpg",		# JPEG Greyscale
++   "lena-color.jpg",	# JPEG Color
++   "dices-grey.png",	# PNG GA
++   "dices.png",		# PNG RGBA
++   "photo_16b.png",	# PNG RGB 16bps
++   "dices-grey.tif",	# TIFF GA 8bps
++   "photo_8b.tif",	# TIFF RGB 8bps
++   "lena-original.tif",	# TIFF RGB 8bps
++   "dices.tif",		# TIFF RGBA 8bps
++   "nuc.tif",		# TIFF G 16bps
++   "photo_16b.tif"	# TIFF RGB 8bps multiple pages
++ )
+> 
+> names(testImages) = rep(repositoryURL, length(testImages))
+> 
+> for (i in seq_along(testImages))
++   if (!file.exists(testImages[i]))
++     system(paste("wget ", names(testImages)[i], "/", testImages[i], sep=""))
+> 
+> ## Append package test images
+> 
+> packageTestImages = list.files(system.file("images", package="EBImage"), full.names=TRUE)
+> names(packageTestImages) =  list.files(system.file("images", package="EBImage"))
+> names(testImages) = testImages
+> testImages = c(testImages, packageTestImages)
+> 
+> ## Actual tests
+> 
+> tempdir = file.path(tempdir(),"copies")
+> dir.create(tempdir)
+> 
+> for (i in seq_along(testImages)) {
++   original = readImage(testImages[i])
++   filename = names(testImages)[i]
++   tempfile = file.path(tempdir, filename)
++   writeImage(original, tempfile)
++   copy = readImage(tempfile)
++   cat(filename, rep(" ", 40-nchar(filename)), if(identical(original, copy)) "PASS" else "FAIL", "\n", sep="")	
++ }
+lena.jpg                                FAIL
+lena-color.jpg                          FAIL
+dices-grey.png                          PASS
+dices.png                               PASS
+photo_16b.png                           FAIL
+image 800 x 600 x 0, tiles 0 x 0, bps = 8, spp = 2 (output 2), config = 1, colormap = no
+image 800 x 600 x 0, tiles 0 x 0, bps = 8, spp = 2 (output 2), config = 1, colormap = no
+dices-grey.tif                          PASS
+image 768 x 1152 x 0, tiles 0 x 0, bps = 8, spp = 3 (output 3), config = 1, colormap = no
+image 768 x 1152 x 0, tiles 0 x 0, bps = 8, spp = 3 (output 3), config = 1, colormap = no
+photo_8b.tif                            PASS
+image 512 x 512 x 0, tiles 0 x 0, bps = 8, spp = 3 (output 3), config = 1, colormap = no
+image 512 x 512 x 0, tiles 0 x 0, bps = 8, spp = 3 (output 3), config = 1, colormap = no
+lena-original.tif                       PASS
+image 800 x 600 x 0, tiles 0 x 0, bps = 8, spp = 4 (output 4), config = 1, colormap = no
+image 800 x 600 x 0, tiles 0 x 0, bps = 8, spp = 4 (output 4), config = 1, colormap = no
+dices.tif                               PASS
+image 2048 x 2048 x 0, tiles 0 x 0, bps = 16, spp = 1 (output 1), config = 1, colormap = no
+image 2048 x 2048 x 0, tiles 0 x 0, bps = 16, spp = 1 (output 1), config = 1, colormap = no
+nuc.tif                                 PASS
+image 768 x 1152 x 0, tiles 0 x 0, bps = 16, spp = 3 (output 3), config = 1, colormap = no
+image 768 x 1152 x 0, tiles 0 x 0, bps = 16, spp = 3 (output 3), config = 1, colormap = no
+photo_16b.tif                           PASS
+image 510 x 510 x 0, tiles 0 x 0, bps = 8, spp = 1 (output 1), config = 1, colormap = no
+image 510 x 510 x 0, tiles 0 x 0, bps = 8, spp = 1 (output 1), config = 1, colormap = no
+image 510 x 510 x 0, tiles 0 x 0, bps = 8, spp = 1 (output 1), config = 1, colormap = no
+image 510 x 510 x 0, tiles 0 x 0, bps = 8, spp = 1 (output 1), config = 1, colormap = no
+image 510 x 510 x 0, tiles 0 x 0, bps = 8, spp = 1 (output 1), config = 1, colormap = no
+image 510 x 510 x 0, tiles 0 x 0, bps = 8, spp = 1 (output 1), config = 1, colormap = no
+image 510 x 510 x 0, tiles 0 x 0, bps = 8, spp = 1 (output 1), config = 1, colormap = no
+image 510 x 510 x 0, tiles 0 x 0, bps = 8, spp = 1 (output 1), config = 1, colormap = no
+cells.tif                               PASS
+lena-color.png                          PASS
+lena.png                                PASS
+image 510 x 510 x 0, tiles 0 x 0, bps = 8, spp = 1 (output 1), config = 1, colormap = no
+image 510 x 510 x 0, tiles 0 x 0, bps = 8, spp = 1 (output 1), config = 1, colormap = no
+image 510 x 510 x 0, tiles 0 x 0, bps = 8, spp = 1 (output 1), config = 1, colormap = no
+image 510 x 510 x 0, tiles 0 x 0, bps = 8, spp = 1 (output 1), config = 1, colormap = no
+image 510 x 510 x 0, tiles 0 x 0, bps = 8, spp = 1 (output 1), config = 1, colormap = no
+image 510 x 510 x 0, tiles 0 x 0, bps = 8, spp = 1 (output 1), config = 1, colormap = no
+image 510 x 510 x 0, tiles 0 x 0, bps = 8, spp = 1 (output 1), config = 1, colormap = no
+image 510 x 510 x 0, tiles 0 x 0, bps = 8, spp = 1 (output 1), config = 1, colormap = no
+nuclei.tif                              PASS
+shapes.png                              PASS
+> 
+> unlink(tempdir, recursive=TRUE)
+> 
diff --git a/inst/viewer/display.css b/inst/viewer/display.css
new file mode 100755
index 0000000..3b0b962
--- /dev/null
+++ b/inst/viewer/display.css
@@ -0,0 +1,97 @@
+   body {
+    margin: 0px;
+    padding: 0px;
+    border: 0px; 
+    background-color:#dddddd;
+    overflow: hidden;
+   }
+   img {
+    image-rendering:optimizeSpeed;             /* Legal fallback                 */
+    image-rendering:-moz-crisp-edges;          /* Firefox                        */
+    image-rendering:-o-crisp-edges;            /* Opera                          */
+    image-rendering:-webkit-optimize-contrast; /* Chrome (and eventually Safari) */
+    image-rendering:optimize-contrast;         /* CSS3 Proposed                  */
+    -ms-interpolation-mode:nearest-neighbor;   /* IE8+                           */
+   }
+   div {
+    display:block;
+    margin:0px;
+    padding:0px;
+    border:0px;
+    overflow:hidden;
+   }
+   button {
+    font-size:20px;
+    font-family:Arial,sans-serif;
+    width:32px;
+    height:32px;
+    line-height:100%;
+    vertical-align:top;
+   }
+   .buttons{
+    float:left;
+    width:50%;
+    min-width:128px;
+   }
+   #toolbar{
+    overflow: inherit;
+    min-width:128px;
+   }
+   #zoombuttons{
+    text-align:right;
+    margin-right:-1px;
+   }
+   #button_org{
+    font-size:14px;
+   }
+   #canvas{
+    position:relative;
+    z-index:1;
+    background-color:#222222;
+   }
+   #ebimage{
+    position:absolute;
+    z-index:2;
+    cursor:crosshair;
+   }
+   #statusbar, .status{	
+    font-size:14px;
+    font-family:"Lucida Console", Monaco, monospace;
+    margin-right:10px;
+    padding:1px;
+    float:left;
+   }
+   #help{
+    position:relative;
+    margin:auto;
+    top:50px;
+    width:360px;
+    border-radius:10px;
+    overflow:auto;
+    border:1px solid #eeeeee;
+    z-index:3;
+    display:none;
+   }
+   .key{
+    font-family:"Lucida Console", Monaco, monospace;
+    font-weight:bold;
+   }
+   .topic{
+    font-size:14px;
+    font-weight:bold;
+    padding-top:5px;
+   }
+   .close{
+    text-align:center;
+    font-weight:bold;
+    padding:5px;
+   }
+   table{
+    background-color:white;
+    opacity:0.8;
+    border-radius:8px;
+    padding-left:5px;
+    padding-right:5px;
+    font-size:12px;
+    font-family:Tahoma, Geneva, sans-serif;
+   }
diff --git a/inst/viewer/display.template b/inst/viewer/display.template
new file mode 100644
index 0000000..4f5cd93
--- /dev/null
+++ b/inst/viewer/display.template
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <title>TITLE</title>
+  <link rel="stylesheet" type="text/css" href="./display.css">
+  <script type="text/javascript" src="./viewer.js"></script>
+ </head>
+ <body onload="new Viewer('IMAGE', FRAMES, WIDTH, HEIGHT);"></body>
+</html>
diff --git a/inst/viewer/viewer.js b/inst/viewer/viewer.js
new file mode 100644
index 0000000..1de5c18
--- /dev/null
+++ b/inst/viewer/viewer.js
@@ -0,0 +1,569 @@
+/*
+	EBImage JavaScript Image Viewer
+	Copyright (c) 2012 Andrzej Oles 
+*/
+
+function Viewer(image, frames, width, height){
+	if (arguments.callee.viewer)
+		return arguments.callee.viewer;
+	var viewer = arguments.callee.viewer = this;
+
+	var imageName		= image;
+	var numberOfFrames	= frames;
+	var originalWidth 	= width;
+	var originalHeight	= height;
+
+	var currentFrame = 1;
+	var zoomLevel = null, minZoomLevel = -12, maxZoomLevel = 6; // 'zoomLevel == 0' is 100%, 'zoomLevel == null' is autofit
+	var previousMousePosition = null;
+	var baseMouseSpeed = currentMouseSpeed = minMouseSpeed = 2;
+
+	// viever DOM elements
+	var body = document.body; 
+	var canvas = null, image = null, toolbar = null, statusbar = null, help = null;
+	var buttons = [], status = [];
+
+
+/* Internal functions */
+
+	setImagePosition = function(x, y) {
+		image.style.left=(Math.round(x)+'px');
+		image.style.top=(Math.round(y)+'px');
+	}
+	getImagePosition = function() {
+		return [stripUnits(image.style.left,'px'), stripUnits(image.style.top,'px')];
+	}
+
+	//return zoom factor given a zoom level
+	getZoomFactor = function(zoomLevel) {
+		if(typeof zoomLevel=='undefined')
+			zoomLevel=zoomLevel;
+		if(zoomLevel==null)
+			return image.width/originalWidth;
+		else if(zoomLevel>=0)
+			// increase by doubling the size
+			return Math.pow(2, zoomLevel);
+		else
+			// decrease each time by 1/3 
+			return Math.floor(100 * Math.pow(2, Math.ceil(zoomLevel/2) ) * Math.pow(3/2, zoomLevel%2) ) /100;
+	}
+
+	zoom = function(dir, x, y) { // direction = 1 (zoom in) or -1 (zoom out)
+		if(zoomLevel==null){
+			zoomLevel = 0;
+			var currentZoomFactor = image.width/originalWidth;
+		
+			// find from above
+			if(currentZoomFactor > 1)
+				while(getZoomFactor(zoomLevel)<currentZoomFactor) zoomLevel++;
+			else
+				while(getZoomFactor(zoomLevel-1)>=currentZoomFactor) zoomLevel--;
+			if ( (getZoomFactor(zoomLevel)!=currentZoomFactor) && (dir==1) )
+				zoomLevel--;
+		}	
+		zoomTo(zoomLevel+dir, x, y);
+	}
+
+	zoomTo = function(newZoomLevel, x, y) {
+		// valid range?
+		if( newZoomLevel<minZoomLevel || newZoomLevel>maxZoomLevel )
+			return false;
+		// within the canvas?
+		if( x<0 || y<0 || x>=canvas.clientWidth || y>=canvas.clientHeight )
+			return false;
+		
+		var zoomFactor = getZoomFactor(newZoomLevel);
+		var size = [originalWidth * zoomFactor, originalHeight * zoomFactor];
+		var position = getImagePosition();
+		
+		position[0]-=((x-position[0])*((size[0]/image.width)-1)), position[1]-=((y-position[1])*((size[1]/image.height)-1));
+		
+		updateImage(size[0], size[1], position[0], position[1], newZoomLevel);
+		
+		// button locking		
+		buttons['in'].disable(newZoomLevel==maxZoomLevel);
+		buttons['out'].disable(newZoomLevel==minZoomLevel);
+		buttons['org'].disable(getZoomFactor()==1);
+		buttons['fit'].disable(false).style.color='';
+		
+		return true;
+	}
+
+	resetZoom = function() {
+		zoomLevel = null;
+		
+		// scale image down when its dimensions exceed canvas size
+		var canvasSize = [canvas.clientWidth, canvas.clientHeight];
+		var downscale = ( canvasSize[0]>originalWidth && canvasSize[1]>originalHeight ) ? false : true;
+		var imageSize = downscale ? fitToCanvas() : [originalWidth, originalHeight];
+
+		updateImage(imageSize[0], imageSize[1], 0, 0, zoomLevel);
+
+		// lock buttons
+		buttons['fit'].disable(true).style.color='#FF0000';
+		buttons['org'].disable(!downscale);
+	}
+
+	updateImage = function(w, h, x, y, newZoomLevel) {
+		// set image size
+		image.width = Math.round(w);
+		image.height = Math.round(h);
+
+		// set image position
+		var position = centerImage(w, h, x ,y);
+		setImagePosition(position[0], position[1]);
+
+		zoomLevel = newZoomLevel;
+		baseMouseSpeed = currentMouseSpeed = (getZoomFactor() > minMouseSpeed) ? Math.round(getZoomFactor()) : minMouseSpeed;
+		updateStatusField("Zoom", Math.round( 100 * getZoomFactor() )+'%');
+	}
+
+	centerImage = function(w, h, x, y) { 
+		var canvasSize = [canvas.clientWidth, canvas.clientHeight];
+
+		if(w<=canvasSize[0])
+			x = Math.round((canvasSize[0] - w)/2);
+		if(h<=canvasSize[1])
+			y = Math.round((canvasSize[1] - h)/2);
+
+		if(w>canvasSize[0]) {
+			if(x>0)
+				x=0;
+			else if((x+w)<canvasSize[0])
+				x=canvasSize[0]-w;
+		}
+
+		if(h>canvasSize[1]) {
+			if(y>0)
+				y=0;
+			else if((y+h)<canvasSize[1])
+				y=canvasSize[1]-h;
+		}
+
+		return [x,y];
+	}
+
+	fitToCanvas = function() {
+		var canvasSize = [canvas.clientWidth, canvas.clientHeight];
+		
+		var newWidth = canvasSize[0];
+		var newHeight = Math.round((newWidth*height)/originalWidth);
+		if(newHeight>(canvasSize[1])) {
+			newHeight = canvasSize[1];
+			newWidth = Math.round((newHeight*width)/originalHeight); 
+		}
+		return [newWidth,newHeight];
+	}
+
+	moveImage = function(x, y) {
+		var position = getImagePosition();
+		position = centerImage(image.width, image.height, position[0]+x, position[1]+y);
+		setImagePosition(position[0], position[1]);
+	}
+
+	resetCanvas = function(){
+		// recalculate canvas size
+		var windowSize = getWindowSize();
+		var newCanvasSize = [windowSize[0], windowSize[1] - (toolbar.offsetHeight+statusbar.offsetHeight)];
+		// set new canvas size
+		canvas.style.width = (Math.round(newCanvasSize[0])+'px');
+		canvas.style.height = (Math.round(newCanvasSize[1])+'px');
+
+		// redraw image on canvas
+		if(zoomLevel == null)
+			viewer.autofitImage();
+		else
+			zoomTo(zoomLevel, canvas.clientWidth/2, canvas.clientHeight/2);
+	}
+
+	setFrame = function(frame) {
+		if(typeof frame=='undefined')
+			frame = 1;
+		if( frame<1 || frame>numberOfFrames )
+			return false;
+
+		// determine filename
+		var framename = imageName;
+		if(numberOfFrames>1){
+			filename = imageName.split('.');
+			extension = filename.pop();
+			framename = filename.join('.')+'-'+(frame-1)+'.'+extension;
+		}
+		image.src = framename;
+
+		currentFrame = frame;
+		updateStatusField("Frame", currentFrame+'/'+numberOfFrames);
+		
+		// button locking
+		buttons['first'].disable(currentFrame==1);
+		buttons['prev'].disable(currentFrame==1);
+		buttons['next'].disable(currentFrame==numberOfFrames);
+		buttons['last'].disable(currentFrame==numberOfFrames);
+		
+		return true;
+	}
+
+	updateStatusField = function(name, value) {
+		status[name].innerHTML = name+': '+value+' ';
+	}
+
+	clearStatusField = function(name) {
+		status[name].innerHTML = '';
+	}
+
+	getPixelPosition = function(event){
+		var mousePos = getMouseXY(event);
+		var imagePos = getObjectXY(image);
+		var zoomFactor = getZoomFactor();
+		return [Math.floor((mousePos[0]-imagePos[0])/zoomFactor) + 1, Math.floor((mousePos[1]-imagePos[1])/zoomFactor) + 1];
+	}
+
+	updatePixelPosition = function(event) {
+		event = preProcessEvent(event);
+
+		var pixelPos = getPixelPosition(event);
+		(pixelPos[0]<1 || pixelPos[1]<1 || pixelPos[0]>originalWidth || pixelPos[1]>originalHeight) ? clearStatusField("Position") : updateStatusField("Position", '('+pixelPos+')');
+	}
+
+	clearPixelPosition = function(event) {
+		event = preProcessEvent(event);
+
+		clearStatusField('Position');
+	}
+
+/* Helper functions */
+
+	getWindowSize = function() {
+		var winW = 630, winH = 460;
+		if (document.body && document.body.offsetWidth) {
+			winW = document.body.offsetWidth;
+			winH = document.body.offsetHeight;
+		}
+		if (document.compatMode=='CSS1Compat' && document.documentElement && document.documentElement.offsetWidth ) {
+			winW = document.documentElement.offsetWidth;
+			winH = document.documentElement.offsetHeight;
+		}
+		if (window.innerWidth && window.innerHeight) {
+			winW = window.innerWidth;
+			winH = window.innerHeight;
+		}
+		return [winW,winH];
+	}
+
+	getObjectXY = function(object) {
+		var left = 0, top = 0;
+		if (object.offsetParent)
+			do {
+				left += object.offsetLeft;
+				top += object.offsetTop;
+			}
+			while (object = object.offsetParent);	
+		return [left, top];
+	}
+
+	getMouseXY = function(event) {
+		var posX = 0, posY = 0;
+		if (!event) event = window.event;	//firefox
+		if (event.pageX || event.pageY) {
+			posX = event.pageX;
+			posY = event.pageY;
+		}
+		else if (event.clientX || event.clientY) {	//IE
+			posX = event.clientX + document.body.scrollLeft
+				+ document.documentElement.scrollLeft;
+			posY = event.clientY + document.body.scrollTop
+				+ document.documentElement.scrollTop;
+		}
+		return [posX,posY];
+	}
+
+	stripUnits = function(string, units) {
+		if(typeof string=='number')
+			return string;
+		var result = string.indexOf(units);
+		return parseInt(string.substring(0, (result!=-1) ? result : string.length));
+	}
+
+/* User actions */
+
+	// frame navigation
+	viewer.firstFrame = function() {
+		setFrame(1);
+	}
+	viewer.prevFrame = function() {
+		setFrame(currentFrame-1);
+	}
+	viewer.nextFrame = function() {
+		setFrame(currentFrame+1);
+	}
+	viewer.lastFrame = function() {
+		setFrame(numberOfFrames);
+	}
+	// zooming
+	viewer.zoomIn = function() {
+		zoom(+1, canvas.clientWidth/2, canvas.clientHeight/2);
+	}
+	viewer.zoomOut = function() {
+		zoom(-1, canvas.clientWidth/2, canvas.clientHeight/2);
+	}
+	viewer.originalSize = function() {
+		zoomTo(0, canvas.clientWidth/2, canvas.clientHeight/2);
+	}
+	viewer.autofitImage = function() {
+		resetZoom();
+	}
+	// help
+	viewer.showHelp = function() {
+		help.style.display = 'block';
+	}
+	viewer.hideHelp = function() {
+		help.style.display = 'none';
+	}
+
+/* Mouse and keyboard actions */
+
+	setUpMouseWheelAction = function(action) {
+			if (window.addEventListener) //For firefox
+				window.addEventListener('DOMMouseScroll', action, false);
+			//For IE			
+			document.onmousewheel = action;
+	}
+
+	preProcessEvent = function(e) {
+		if (!e) //For IE
+			e = window.event, e.returnValue = false;
+		else if (e.preventDefault)
+			e.preventDefault();
+
+		return e;
+	}
+
+	viewer.onmousewheel = function(event) {
+		event = preProcessEvent(event);
+
+		var direction = 0; // up is +, down is -
+		
+		if (event.wheelDelta) 	// IE & Chrome
+			direction = (event.wheelDelta > 1) ? 1 : -1;
+		else if (event.detail) // FF & Opera
+			direction = (event.detail < 1) ? 1 : -1;
+
+		var mousePos = getMouseXY(event);
+		var canvasPos = getObjectXY(canvas);
+		zoom(direction, mousePos[0]-canvasPos[0], mousePos[1]-canvasPos[1]);
+	}
+	
+	viewer.grabImage =  function(event) {
+		event = preProcessEvent(event);
+	
+		// set mouse cursor	
+		var imageSize = [image.width, image.height], canvasSize = [canvas.clientWidth, canvas.clientHeight];
+		var cursor = 'crosshair';
+		if(imageSize[0]>canvasSize[0] && imageSize[1]>canvasSize[1])
+			cursor = 'move';
+		else if(imageSize[0]>canvasSize[0])
+			cursor = 'e-resize';
+		else if(imageSize[1]>canvasSize[1])
+			cursor = 'n-resize';
+		
+		image.style.cursor = cursor;
+		previousMousePosition = getMouseXY(event);
+		image.onmousemove = viewer.dragImage;
+	}	
+
+	viewer.releaseImage = function(event) {
+		event = preProcessEvent(event);
+
+		image.style.cursor = 'crosshair';
+		image.onmousemove = null;
+	}
+
+	viewer.dragImage = function(event) {
+		event = preProcessEvent(event);
+		
+		var mousePosition = getMouseXY(event);
+		moveImage(mousePosition[0]-previousMousePosition[0], mousePosition[1]-previousMousePosition[1]);
+		previousMousePosition = mousePosition;
+	}
+
+	viewer.onkeydown = function(event) {
+		event = preProcessEvent(event);
+		var keyCode = event.which || event.keyCode;
+
+		var shift = [0, 0];
+	
+		switch(keyCode){
+			
+		// browsing
+			// next frame
+			case 33: // PageUp
+			case 190: // . >
+				viewer.nextFrame();
+				break;
+			// previous frame
+			case 34: // PageDown
+			case 188: // , <
+				viewer.prevFrame();
+				break;
+			// last frame
+			case 35: // End
+			case 191: // / ?
+				viewer.lastFrame();
+				break;
+			// first frame
+			case 36: // Home
+			case 77: // m
+				viewer.firstFrame();
+				break;		
+		// zooming
+			// zoom in
+			case 88: // x
+			case 43: // + / Numpad +
+			case 61:
+			case 107:
+			case 187:
+				viewer.zoomIn();
+				break;
+			// zoom out
+			case 90: // z
+			case 45: // - / Numpad -
+			case 109: 
+			case 173:
+			case 189: 
+				viewer.zoomOut();
+				break;
+			// reset zoom to 100%
+			case 82: // r
+			case 8: // Backspace
+				viewer.originalSize();
+				break;
+			// fit-in
+			case 32: // Space
+			case 13: // Enter
+				viewer.autofitImage();
+				break;
+		// moving 
+			case 37: // Left arrow
+				shift[0] += currentMouseSpeed;
+				break;
+			case 38: // Up arrow
+				shift[1] += currentMouseSpeed;
+				break;
+			case 39: // Right arrow
+				shift[0] -= currentMouseSpeed;
+				break;
+			case 40: // Down arrow
+				shift[1] -= currentMouseSpeed;
+				break;
+		// help
+			// show
+			case 72: // h
+				viewer.showHelp();
+				break;
+			// hide
+			case 27: // Esc
+			case 81: // q
+				viewer.hideHelp();
+				break;
+		// debug
+			case 68: // d
+				break;
+		}
+
+		if( keyCode>=37 && keyCode<=40){ // when moving
+			moveImage(shift[0], shift[1]);
+			currentMouseSpeed += baseMouseSpeed;
+		}
+	}
+
+	viewer.onkeyup = function(event) {
+		currentMouseSpeed = baseMouseSpeed;
+	}
+
+/* DOM elements creation */
+
+	createElement = function(type, id, className, parent) {
+		var element = document.createElement(type);
+		element.id = id;
+		if(className!=null) element.className = className;
+		parent.appendChild(element);
+		return element;
+	}
+
+	createButton = function(name, value, title, onclick, group) {
+		var button = createElement('button', 'button_'+name, null, group)
+		button.innerHTML = value;
+		button.title = title;
+		button.onclick = onclick;
+		button.disable = function(disable){this.disabled=disable; this.blur(); return this;};
+
+		return (buttons[name] = button);
+	}
+
+	createStatusField = function(name) {
+		return (status[name] = createElement('div', name, 'status', statusbar));
+	}
+
+/* Viewer initialization */
+
+	init = function() {
+		
+		// create toolbar
+		toolbar = createElement('div', 'toolbar', null, body);
+		// create button containers
+		navbuttons = createElement('div', 'navbuttons', 'buttons', toolbar), zoombuttons = createElement('div', 'zoombuttons', 'buttons', toolbar);
+		
+		// create navigation buttons
+		createButton('first','«<br/> ','First frame [HOME]/[m] ',viewer.firstFrame,navbuttons);
+		createButton('prev','<','Previous frame [PAGE DOWN]/[<]',viewer.prevFrame,navbuttons);
+		createButton('next','>','Next frame [PAGE UP]/[>]',viewer.nextFrame,navbuttons);
+		createButton('last','»<br/> ','Last frame [END]/[?]',viewer.lastFrame,navbuttons);
+		
+		// create zoom buttons
+		createButton('in','+','Zoom in [+]/[x]',viewer.zoomIn,zoombuttons);
+		createButton('out','−','Zoom out [-]/[z]',viewer.zoomOut,zoombuttons);
+		createButton('org','1:1','Original size [BACKSPACE]/[r]',viewer.originalSize,zoombuttons);
+		createButton('fit','∗<br/> ','Fit image [SPACE]/[ENTER]',viewer.autofitImage,zoombuttons);
+		
+		// create canvas
+		canvas = createElement('div', 'canvas', null, body)
+		
+		//create statusbar
+		statusbar = createElement('div', 'statusbar', null, body);
+		
+		// create statusbar elements
+		createStatusField("Image"), createStatusField("Frame"), createStatusField("Zoom"), createStatusField("Position");
+		
+		// create help
+		help = createElement('div', 'help', null, canvas)
+		help.innerHTML = '<table><tr><td colspan="3" class="topic">Browsing</td></tr><tr><td colspan="3">Use toolbar buttons or the following keys to change between the frames:</td></tr><tr><td>Next frame</td><td class="key">PAGE UP</td><td class="key">&gt</td></tr><tr><td>Previous frame</td><td class="key">PAGE DOWN</td><td class="key">&lt</td></tr><tr><td>First frame</td><td class="key">HOME</td><td class="key">M</td></tr><tr><td>Last frame</td><td class="key">END</td><td class="key">?</td>< [...]
+		
+		// create image	
+		image = createElement('img', 'ebimage', null, canvas);
+		
+		// set up image
+		setFrame();
+		image.onload = resetCanvas;
+		
+		updateStatusField("Image", originalWidth+'x'+originalHeight);
+		
+		// set up mouse and keyboard actions
+		// use mousewheel for zooming
+		setUpMouseWheelAction(viewer.onmousewheel);
+		// grab and pan image on click
+		image.onmousedown = viewer.grabImage;
+		image.onmouseup = image.onmouseout = viewer.releaseImage;
+		image.onmousemove = null;
+		// read keystrokes
+		document.onkeydown = viewer.onkeydown;
+		document.onkeyup = viewer.onkeyup;
+		// read current pixel position
+		canvas.onmousemove = updatePixelPosition;
+		canvas.onmouseout = clearPixelPosition;
+		
+		// reset view on window resize
+		window.onresize = resetCanvas;
+	}
+	
+	viewer.onload = init();
+}
diff --git a/man/EBImage-defunct.Rd b/man/EBImage-defunct.Rd
new file mode 100644
index 0000000..26d4691
--- /dev/null
+++ b/man/EBImage-defunct.Rd
@@ -0,0 +1,63 @@
+\name{EBImage-defunct}
+
+\alias{EBImage-defunct}
+\alias{animate}
+\alias{blur}
+\alias{drawtext}
+\alias{drawfont}
+\alias{getFeatures}
+\alias{getNumberOfFrames}
+\alias{hullFeatures}
+\alias{zernikeMoments}
+\alias{edgeProfile}
+\alias{edgeFeatures}
+\alias{haralickFeatures}
+\alias{haralickMatrix}
+\alias{moments}
+\alias{rmoments}
+\alias{smoments}
+\alias{cmoments}
+\alias{dilateGreyScale}
+\alias{erodeGreyScale}
+\alias{openingGreyScale}
+\alias{closingGreyScale}
+\alias{whiteTopHatGreyScale}
+\alias{blackTopHatGreyScale}
+\alias{selfcomplementaryTopHatGreyScale}
+
+\title{Defunct functions in package \sQuote{EBImage}}
+
+\description{These functions are defunct and no longer available.}
+
+\details{
+  The following functions are defunct and no longer available; use
+  the replacement indicated below.
+  
+  \itemize{
+    \item{\code{animate}: \code{\link{display}}}
+    \item{\code{blur}: \code{\link{gblur}}}
+    \item{\code{drawtext}: see package vignette for documentation on how to add text labels to images}
+    \item{\code{drawfont}: see package vignette for documentation on how to add text labels to images}
+    \item{\code{getFeatures}: \code{\link{computeFeatures}}}
+    \item{\code{getNumberOfFrames}: \code{\link{numberOfFrames}}}
+    \item{\code{hullFeatures}: \code{\link{computeFeatures.shape}}}
+    \item{\code{zernikeMoments}: \code{\link{computeFeatures}}}
+    \item{\code{edgeProfile}: \code{\link{computeFeatures}}}
+    \item{\code{edgeFeatures}: \code{\link{computeFeatures.shape}}}
+    \item{\code{haralickFeatures}: \code{\link{computeFeatures}}}
+    \item{\code{haralickMatrix}: \code{\link{computeFeatures}}}
+    \item{\code{moments}: \code{\link{computeFeatures.moment}}}
+    \item{\code{cmoments}: \code{\link{computeFeatures.moment}}}
+    \item{\code{rmoments}: \code{\link{computeFeatures.moment}}}
+    \item{\code{smoments}: \code{\link{computeFeatures.moment}}}
+    \item{\code{dilateGreyScale}: \code{\link{dilate}}}
+    \item{\code{erodeGreyScale}: \code{\link{erode}}}
+    \item{\code{openingGreyScale}: \code{\link{opening}}}
+    \item{\code{closingGreyScale}: \code{\link{closing}}}
+    \item{\code{whiteTopHatGreyScale}: \code{\link{whiteTopHat}}}
+    \item{\code{blackTopHatGreyScale}: \code{\link{blackTopHat}}}
+    \item{\code{selfcomplementaryTopHatGreyScale}: \code{\link{selfComplementaryTopHat}}}
+  }
+}
+
+\keyword{package}
diff --git a/man/EBImage.Rd b/man/EBImage.Rd
new file mode 100644
index 0000000..b64fff8
--- /dev/null
+++ b/man/EBImage.Rd
@@ -0,0 +1,144 @@
+\name{EBImage}
+
+\alias{EBImage}
+
+\docType{package}
+
+\title{Package overview}
+
+\description{
+  \code{EBImage} is an image processing and analysis package for R. Its primary
+  goal is to enable automated analysis of large sets of images such as those
+  obtained in high throughput automated microscopy.
+
+  \code{EBImage} relies on the \code{Image} object to store and process images
+  but also works on multi-dimensional arrays.
+}
+
+\section{Package content}{
+
+  Image methods
+  \itemize{
+    \item \code{Image}
+    \item \code{as.Image}, \code{is.Image}, \code{as.raster}
+    \item \code{colorMode}, \code{imageData}
+    \item \code{getFrame}, \code{numberOfFrames}
+  }
+
+  Image I/O, display
+  \itemize{
+    \item \code{readImage}, \code{writeImage}
+    \item \code{display}
+    \item \code{image}
+  }
+
+  Spatial transforms
+  \itemize{
+    \item \code{resize}, \code{flip}, \code{flop}, \code{transpose}
+    \item \code{rotate}, \code{translate}, \code{affine}
+  }
+
+  Image segmentation, objects manipulation
+  \itemize{
+    \item \code{thresh}, \code{bwlabel}, \code{otsu}
+    \item \code{watershed}, \code{propagate}
+    \item \code{ocontour}
+    \item \code{paintObjects}, \code{rmObjects}, \code{reenumerate}
+  }
+
+  Image enhancement, filtering
+  \itemize{
+    \item \code{normalize}
+    \item \code{filter2}, \code{gblur}, \code{medianFilter}
+  }
+
+  Morphological operations
+  \itemize{
+    \item \code{makeBrush}
+    \item \code{erode}, \code{dilate}, \code{opening}, \code{closing}
+    \item \code{whiteTopHat}, \code{blackTopHat}, \code{selfComplementaryTopHat}
+    \item \code{distmap}
+    \item \code{floodFill}, \code{fillHull}
+  }
+
+  Color space manipulation
+  \itemize{  
+    \item \code{rgbImage}, \code{channel}, \code{toRGB}
+  }
+
+  Image stacking, combining, tiling
+  \itemize{ 
+    \item \code{stackObjects}
+    \item \code{combine}
+    \item \code{tile}, \code{untile}
+  }
+
+  Drawing on images
+  \itemize{
+    \item \code{drawCircle}
+  }
+
+  Features extraction
+  \itemize{
+    \item \code{computeFeatures}
+    \item \code{computeFeatures.basic}, \code{computeFeatures.moment}, \code{computeFeatures.shape}, \code{computeFeatures.haralick}
+    \item \code{standardExpandRef}
+  }
+
+  Defunct
+  \itemize{
+    \item \code{blur}, \code{equalize}
+    \item \code{drawtext}, \code{drawfont}
+    \item \code{getFeatures}, \code{hullFeatures}, \code{zernikeMoments}
+    \item \code{edgeProfile}, \code{edgeFeatures}, 
+    \item \code{haralickFeatures}, \code{haralickMatrix}
+    \item \code{moments}, \code{cmoments}, \code{smoments}, \code{rmoments}
+  }
+}
+
+\section{Authors}{
+
+  Oleg Sklyar, \email{osklyar at ebi.ac.uk}, Copyright 2005-2007
+
+  Gregoire Pau, \email{gpau at ebi.ac.uk}
+
+  Wolfgang Huber, \email{huber at ebi.ac.uk}
+
+  Andrzej Oles, \email{andrzej.oles at embl.de}
+
+  Mike Smith, \email{msmith at ebi.ac.uk}
+
+  \preformatted{
+  European Bioinformatics Institute
+  European Molecular Biology Laboratory
+  Wellcome Trust Genome Campus
+  Hinxton 
+  Cambridge CB10 1SD
+  UK
+  }
+
+  The code of \code{\link{propagate}} is based on the \code{CellProfiler} 
+  with permission granted to distribute this particular part under LGPL, the 
+  corresponding copyright (Jones, Carpenter) applies.
+
+  The source code is released under \code{LGPL} (see the \code{LICENSE} 
+  file in the package root for the complete license wording).
+
+  This library is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version. This library is distributed
+  in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+  See the GNU Lesser General Public License for more details. For LGPL license 
+  wording see \url{http://www.gnu.org/licenses/lgpl.html}
+}
+
+\examples{
+  example(readImage)
+  example(display)
+  example(rotate)
+  example(propagate)
+}
+
+\keyword{package}
diff --git a/man/Image.Rd b/man/Image.Rd
new file mode 100644
index 0000000..b3c10bb
--- /dev/null
+++ b/man/Image.Rd
@@ -0,0 +1,153 @@
+\name{Image}
+
+\alias{Image}
+\alias{Image-class}
+\alias{Grayscale}
+\alias{Color}
+\alias{is.Image}
+\alias{as.Image}
+\alias{as.array.Image}
+\alias{as.raster.Image}
+\alias{print.Image}
+\alias{median.Image}
+\alias{quantile.Image}
+\alias{colorMode}
+\alias{colorMode<-}
+\alias{getFrame}
+\alias{getFrames}
+\alias{numberOfFrames}
+\alias{imageData}
+\alias{imageData<-}
+\alias{colormode}
+\alias{Ops,Image,Image-method}
+\alias{Ops,numeric,Image-method}
+\alias{Ops,Image,numeric-method}
+\alias{Math2,Image-method}
+\alias{[,Image,ANY,ANY,ANY-method}
+\alias{[,Image-method}
+\alias{show,Image-method}
+\alias{image,Image-method}
+\alias{hist,Image-method}
+\alias{log,Image-method}
+
+\title{Image class}
+
+\description{
+  \code{EBImage} uses the \code{Image} class to store and process
+  images. Images are stored as multi-dimensional arrays containing the pixel
+  intensities. \code{Image} extends the base class \code{array} and
+  uses the \code{colormode} slot to store how the color information of
+  the multi-dimensional data is handled.
+
+  The \code{colormode} slot can be either \code{Grayscale} or \code{Color}.
+  In either mode, the first two dimensions of the underlying array are understood to be the spatial dimensions of the image.
+  In the \code{Grayscale} mode the remaining dimensions contain other image frames.
+  In the \code{Color} mode, the third dimension contains color channels of the image, while higher dimensions contain  image frames.
+  The number of channels is not limited and can be any number >= 1; these can be, for instance, the red, green, blue and, possibly, alpha channel.
+  Note that grayscale images containing an alpha channel are stored with \code{colormode=Color}.
+  
+  All methods from the \code{EBImage} package work either with \code{Image} objects or
+  multi-dimensional arrays. In the latter case, the color mode is assumed to be \code{Grayscale}.
+}
+
+\usage{
+Image(data, dim, colormode)
+as.Image(x)
+is.Image(x)
+
+\method{as.array}{Image}(x, \ldots)
+\method{as.raster}{Image}(x, max = 1, i = 1L, \ldots)
+
+colorMode(y)
+colorMode(y) <- value
+
+imageData(y)
+imageData(y) <- value
+
+getFrame(y, i, type = c('total', 'render'))
+getFrames(y, i, type = c('total', 'render'))
+numberOfFrames(y, type = c('total', 'render'))
+}
+
+\arguments{
+  \item{data}{A vector or array containing the pixel intensities of an image. If missing, the default 1x1
+  zero-filled array is used.}
+
+  \item{dim}{A vector containing the final dimensions of an \code{Image} object. If missing, equals to
+  \code{dim(data)}.}
+
+  \item{colormode}{A numeric or a character string containing the color mode which can be
+  either \code{Grayscale} or \code{Color}. If missing, equals to \code{Grayscale}.}
+
+  \item{x}{An R object.}
+
+  \item{y}{An \code{Image} object or an array.}
+  
+  \item{max}{Number giving the maximum of the color values range.}
+
+  \item{i}{Number(s) of frame(s). A single number in case of \code{getFrame}, or a vector of frame numbers for \code{getFrames}. If missing all frames are returned.}
+
+  \item{value}{For \code{colorMode}, a numeric or a character string containing the color mode which
+    can be either \code{Grayscale} or \code{Color}. For \code{imageData}, an \code{Image} object or an array.}
+
+  \item{type}{A character string containing \code{total} or \code{render}. Default is \code{total}.}
+  
+  \item{\dots}{further arguments passed to or from other methods.}
+}
+
+\value{
+  \code{Image} and \code{as.Image} return a new \code{Image} object.
+
+  \code{is.Image} returns TRUE if \code{x} is an \code{Image} object and FALSE otherwise.
+
+  \code{as.raster} coerces an Image object to its raster representation. For stacked images the \code{i}-th frame is returned (by default the first one).
+
+  \code{colorMode} returns the color mode of \code{y} and \code{colorMode<-} changes the color mode
+  of \code{y}.
+
+  \code{imageData} returns the array contained in an \code{Image} object.
+}
+
+\details{
+  Depending on \code{type}, \code{numberOfFrames} returns the total number of frames contained
+  in the object \code{y} or the number of rendered frames. The total number of frames is independent
+  of the color mode and equals to the product of all the dimensions except the two first ones. The
+  number of rendered frames is equal to the total number of frames in the \code{Grayscale} color mode, or
+  to the product of all the dimensions except the three first ones in the \code{Color} color mode.
+
+  \code{getFrame} returns the i-th frame contained in the image \code{y}. If \code{type} is \code{total}, the
+  function is unaware of the color mode and returns an xy-plane. For \code{type=render}, the function returns the
+  i-th image as shown by the \code{display} function.
+}
+
+\seealso{
+  \code{\link{readImage}}, \code{\link{writeImage}}, \code{\link{display}}
+}
+
+\author{
+  Oleg Sklyar, \email{osklyar at ebi.ac.uk}, 2005-2007
+}
+
+\examples{
+  s1 = exp(12i*pi*seq(-1, 1, length=300)^2)
+  y = Image(outer(Im(s1), Re(s1)))
+  display(normalize(y))
+
+  x = Image(rnorm(300*300*3),dim=c(300,300,3), colormode='Color')
+  display(x)
+
+  w = matrix(seq(0, 1, len=300), nc=300, nr=300)
+  m = abind::abind(w, t(w), along=3)
+  z = Image(m, colormode='Color')
+  display(normalize(z))
+
+  y = Image(matrix(c('red', 'violet', '#ff51a5', 'yellow'), nrow=10, ncol=10))
+  display(y, interpolate=FALSE)
+
+  ## colorMode example
+  x = readImage(system.file('images', 'nuclei.tif', package='EBImage'))
+  x = x[,,1:3]
+  display(x, title='Cell nuclei')
+  colorMode(x) = Color
+  display(x, title='Cell nuclei in RGB')
+}
diff --git a/man/abind.Rd b/man/abind.Rd
new file mode 100644
index 0000000..f472def
--- /dev/null
+++ b/man/abind.Rd
@@ -0,0 +1,62 @@
+\name{abind}
+
+\docType{methods}
+
+\alias{abind}
+\alias{abind-methods}
+\alias{abind,ANY-method}
+\alias{abind,Image-method}
+\alias{abind,list-method}
+
+\title{Combine Image Arrays}
+
+\description{
+  Methods for function \code{\link[abind]{abind}} from package \pkg{abind} useful for combining \code{Image} arrays.
+}
+
+\section{Usage}{
+\code{abind(...)}
+}
+
+\section{Arguments}{
+  \describe{
+    \item{\code{\ldots}}{Arguments to \code{\link[abind]{abind}}}
+  }
+}
+
+\section{Methods}{
+\describe{
+\item{\code{signature(... = "Image")}}{
+ This method is defined primarily for the sake of preserving the class of the combined \code{Image} objects. Unlike the original \code{\link[abind]{abind}} function, if \code{dimnames} for all combined objects are \code{NULL} it does not introduce a list of empty \code{dimnames} for each dimension.
+}
+\item{\code{signature(... = "ANY")}}{
+  Dispatches to the original \code{\link[abind]{abind}} function.
+}
+}}
+
+\value{
+  An \code{Image} object or an array, containing the combined data arrays of the input objects.
+}
+
+\seealso{
+   \code{\link{combine}} provides a more convenient interface to merging images into an image sequence. Use \code{\link{tile}} to lay out images next to each other in a regular grid.
+}
+
+\author{
+  Andrzej Oleś, \email{andrzej.oles at embl.de}, 2017
+}
+
+\examples{
+  f = system.file("images", "sample-color.png", package="EBImage")
+  x = readImage(f)
+  
+  ## combine images horizontally
+  y = abind(x, x, along=1)
+  display(y)
+  
+  ## stack images one on top of the other
+  z = abind(x, x, along=2)
+  display(z)
+}
+
+\keyword{methods}
diff --git a/man/bwlabel.Rd b/man/bwlabel.Rd
new file mode 100644
index 0000000..06a9495
--- /dev/null
+++ b/man/bwlabel.Rd
@@ -0,0 +1,67 @@
+\name{bwlabel}
+
+\alias{bwlabel}
+
+\title{Binary segmentation}
+
+\description{
+  Labels connected (connected sets) objects in a binary image.
+}
+
+\usage{
+bwlabel(x)
+}
+
+\arguments{
+  \item{x}{An \code{Image} object or an array. \code{x} is considered as
+    a binary image, whose pixels of value 0 are considered as background
+    ones and other pixels as foreground ones.}
+}
+
+\value{
+  A \code{Grayscale} \code{Image} object or an array, containing the
+  labelled version of \code{x}.
+}
+
+\details{
+  All pixels for each connected set of foreground (non-zero) pixels
+  in \code{x} are set to an unique increasing integer, starting from 1.
+  Hence, \code{max(x)} gives the number of connected objects in \code{x}.
+}
+
+\author{
+  Gregoire Pau, 2009
+}
+
+\seealso{
+  \code{\link{computeFeatures}}, \code{\link{propagate}}, \code{\link{watershed}}, \code{\link{paintObjects}}, \code{\link{colorLabels}}
+}
+
+\examples{
+  ## simple example
+  x = readImage(system.file('images', 'shapes.png', package='EBImage'))
+  x = x[110:512,1:130]
+  display(x, title='Binary')
+  y = bwlabel(x)
+  display(normalize(y), title='Segmented')
+
+  ## read nuclei images
+  x = readImage(system.file('images', 'nuclei.tif', package='EBImage'))
+  display(x)
+
+  ## computes binary mask
+  y = thresh(x, 10, 10, 0.05)
+  y = opening(y, makeBrush(5, shape='disc'))
+  display(y, title='Cell nuclei binary mask')
+
+  ## bwlabel
+  z = bwlabel(y)
+  display(normalize(z), title='Cell nuclei')
+  nbnuclei = apply(z, 3, max)
+  cat('Number of nuclei=', paste(nbnuclei, collapse=','),'\n')
+
+  ## paint nuclei in color
+  cols = c('black', sample(rainbow(max(z))))
+  zrainbow = Image(cols[1+z], dim=dim(z))
+  display(zrainbow, title='Cell nuclei (recolored)')
+}
diff --git a/man/channel.Rd b/man/channel.Rd
new file mode 100644
index 0000000..8bcafbc
--- /dev/null
+++ b/man/channel.Rd
@@ -0,0 +1,85 @@
+\name{channel}
+
+\alias{channel}
+\alias{rgbImage}
+\alias{toRGB}
+
+\concept{color conversion}
+\concept{color mode}
+
+\title{Color and image color mode conversions}
+
+\description{
+  \code{channel} handles color space conversions between image modes.
+  \code{rgbImage} combines \code{Grayscale} images into a \code{Color} one.
+  \code{toRGB} is a wrapper function for convenient grayscale to RGB color space conversion; the call \code{toRGB(x)} returns the result of \code{channel(x, 'rgb')}.
+}
+
+\usage{ 
+channel(x, mode)
+rgbImage(red, green, blue)
+toRGB(x)
+}
+
+\arguments{
+  \item{x}{An \code{Image} object or an array.}
+  
+  \item{mode}{ A character value specifying the target mode for
+    conversion. See Details.}
+  
+  \item{red, green, blue}{\code{Image} objects in \code{Grayscale}
+    color mode or arrays of the same dimension. If missing, a
+    black image will be used.} 
+}
+
+\value{
+  An \code{Image} object or an array.
+}
+
+\details{
+  Conversion modes:
+  \describe{
+    \item{\code{rgb}}{Converts a \code{Grayscale} image or an array
+    into a \code{Color} image, replicating RGB channels.}
+
+  \item{\code{gray, grey}}{Converts a \code{Color} image into a
+    \code{Grayscale} image, using uniform 1/3 RGB weights.}
+    
+  \item{\code{luminance}}{Luminance-preserving \code{Color} to \code{Grayscale} conversion
+  using CIE 1931 luminance weights: 0.2126 * R + 0.7152 * G + 0.0722 * B.}
+
+  \item{\code{red, green, blue}}{Extracts the \code{red}, \code{green} or
+    \code{blue} channel from a \code{Color} image. Returns a
+    \code{Grayscale} image.}
+
+  \item{\code{asred, asgreen, asblue}}{Converts a \code{Grayscale}
+    image or an array into a \code{Color} image of the specified hue.}
+}
+
+NOTE: \code{channel} changes the pixel intensities, unlike \code{colorMode}
+which just changes the way that EBImage renders an image.
+}
+
+\seealso{
+  \code{\link{colorMode}}
+} 
+
+\examples{
+ x = readImage(system.file("images", "shapes.png", package="EBImage"))
+ display(x)
+ y = channel(x, 'asgreen')
+ display(y)
+
+ ## rgbImage
+ x = readImage(system.file('images', 'nuclei.tif', package='EBImage'))
+ y = readImage(system.file('images', 'cells.tif', package='EBImage'))
+ display(x, title='Cell nuclei')
+ display(y, title='Cell bodies')
+
+ cells = rgbImage(green=1.5*y, blue=x)
+ display(cells, title='Cells')
+}
+
+\author{
+  Oleg Sklyar, \email{osklyar at ebi.ac.uk}
+}
diff --git a/man/clahe.Rd b/man/clahe.Rd
new file mode 100644
index 0000000..2309351
--- /dev/null
+++ b/man/clahe.Rd
@@ -0,0 +1,72 @@
+\name{clahe}
+
+\alias{clahe}
+
+\title{Contrast Limited Adaptive Histogram Equalization}
+
+\description{
+  Improve contrast locally by performing adaptive histogram equalization.
+}
+
+\usage{
+clahe(x, nx = 8, ny = nx, bins = 256, limit = 2, keep.range = FALSE) 
+}
+
+\arguments{
+  \item{x}{an \code{Image} object or an array.}
+  \item{nx}{integer, number of contextual regions in the X direction (min 2, max 256)}
+  \item{ny}{integer, number of contextual regions in the Y direction (min 2, max 256)}
+  \item{bins}{integer, number of greybins for histogram ("dynamic range"). Smaller values (eg. 128) speed up processing while still producing good quality output.}
+  \item{limit}{double, normalized clip limit (higher values give more contrast).  A clip limit smaller than 0 results in standard (non-contrast limited) AHE.}
+  \item{keep.range}{logical, retain image minimum and maximum values rather then use the full available range}
+}
+
+\value{
+  An \code{Image} object or an array, containing the filtered version
+  of \code{x}.
+}
+
+\details{
+  Adaptive histogram equalization (AHE) is a contrast enhancement 
+  technique which overcomes the limitations of standard histogram equalization.
+  Unlike ordinary histogram equalization the adaptive method redistributes the lightness values 
+  of the image based on several histograms, each corresponding to a distinct section of the image.
+  It is therefore useful for improving the local contrast and enhancing the definitions of edges in each region of an image.
+  However, AHE has a tendency to overamplify noise in relatively homogeneous 
+  regions of an image. Contrast limited adaptive histogram equalization (CLAHE) prevents this by limiting the amplification.
+  
+  The function is based on the implementation by Karel Zuiderveld [1].
+  This implementation assumes that the X- and Y image dimensions are an integer
+  multiple of the X- and Y sizes of the contextual regions.
+  The input image \code{x} should contain pixel values in the range from 0 to 1, 
+  inclusive; values lower than 0 or higher than 1 are clipped before applying 
+  the filter. Internal processing is performed in 16-bit precision.
+  If the image contains multiple channels or frames, 
+  the filter is applied to each one of them separately.
+}
+
+\note{
+  The interpolation step of the original implementation by Karel Zuiderveld [1] 
+  was modified to use double precision arithmetic in order to make the filter 
+  rotationally invariant for even-sized contextual regions, and the result is 
+  properly rounded rather than truncated towards 0 in order to avoid a 
+  systematic shift of pixel values.
+}
+
+\seealso{
+  \code{\link{equalize}}
+}
+
+\author{
+  Andrzej Oleś, \email{andrzej.oles at embl.de}, 2017
+}
+
+\examples{
+  x = readImage(system.file("images", "sample-color.png", package="EBImage"))
+  y = clahe(x)  
+  display(y)
+}
+
+\references{
+  [1] K. Zuiderveld: Contrast Limited Adaptive Histogram Equalization. In: P. Heckbert: Graphics Gems IV, Academic Press 1994
+}
diff --git a/man/colorLabels.Rd b/man/colorLabels.Rd
new file mode 100644
index 0000000..2b275f5
--- /dev/null
+++ b/man/colorLabels.Rd
@@ -0,0 +1,42 @@
+\name{colorLabels}
+
+\alias{colorLabels}
+
+\title{Color Code Labels}
+
+\description{
+  Color codes the labels of object masks by a random permutation.
+}
+
+\usage{
+colorLabels(x, normalize = TRUE)
+}
+
+\arguments{
+  \item{x}{an \code{Image} object in \code{Grayscale} color mode or an array containing object masks. Object masks are sets of pixels with the same unique integer value}
+  \item{normalize}{if TRUE normalizes the resulting color image}
+}
+
+\value{
+  An \code{Image} object containing color coded objects of \code{x}.
+}
+
+\details{
+Performs color coding of object masks, which are typically obtained using the \code{bwlabel} function. Each label from \code{x} is assigned an unique color. The colors are distributed among the labels using a random permutation. If \code{normalize} is set to \code{TRUE} the intensity values of the resulting image are mapped to the [0,1] range.
+}
+
+\author{
+  Bernd Fischer, Andrzej Oles, 2013-2014
+}
+
+\seealso{
+  \code{\link{bwlabel}},  \code{\link{normalize}}
+}
+
+\examples{
+  x = readImage(system.file('images', 'shapes.png', package='EBImage'))
+  x = x[110:512,1:130]
+  y = bwlabel(x)
+  z = colorLabels(y)
+  display(z, title='Colored segmentation')
+}
diff --git a/man/colormap.Rd b/man/colormap.Rd
new file mode 100644
index 0000000..b694865
--- /dev/null
+++ b/man/colormap.Rd
@@ -0,0 +1,53 @@
+\name{colormap}
+
+\alias{colormap}
+
+\title{Map a Greyscale Image to Color}
+
+\description{
+  Maps a greyscale image to color using a color palette.
+}
+
+\usage{
+colormap(x, palette = heat.colors(256L))
+}
+
+\arguments{
+  \item{x}{an \code{Image} object of color mode \code{Grayscale}, or an array}
+
+  \item{palette}{character vector containing the color palette}
+}
+
+\details{
+  The \code{colormap} function first linearly maps the pixel intensity values
+  of \code{x} to the integer range \code{1:length(palette)}. It then 
+  uses these values as indices to the provided color palette to create
+  a color version of the original image.
+  
+  The default palette contains 256 colors, which is the typical number of
+  different shades in a 8bit grayscale image.
+}
+
+\value{
+  An \code{Image} object of color mode \code{Color}, containing the color-mapped version
+  of \code{x}.
+}
+
+\author{
+  Andrzej Oleś, \email{andrzej.oles at embl.de}, 2016
+}
+
+\examples{
+  x = readImage(system.file("images", "sample.png", package="EBImage"))
+
+  ## posterize an image using the topo.colors palette
+  y = colormap(x, topo.colors(8))
+
+  display(y, method="raster")
+
+  ## mimic MatLab's 'jet.colors' colormap
+  jet.colors = colorRampPalette(c("#00007F", "blue", "#007FFF", "cyan", "#7FFF7F", "yellow", "#FF7F00", "red", "#7F0000"))
+  y = colormap(x, jet.colors(256))
+
+  display(y, method="raster")
+}
diff --git a/man/combine.Rd b/man/combine.Rd
new file mode 100644
index 0000000..4138211
--- /dev/null
+++ b/man/combine.Rd
@@ -0,0 +1,62 @@
+\name{combine}
+
+\alias{combine}
+\alias{combine,array,array-method}
+\alias{combine,matrix,matrix-method}
+\alias{combine,Image,Image-method}
+\alias{combine,list,missing-method}
+\alias{combine,ANY,NULL-method}
+\alias{combine,NULL,ANY-method}
+\alias{combine,NULL,NULL-method}
+
+\title{Combine images}
+
+\description{
+  Merges images to create image sequences.
+}
+
+\usage{
+combine(x, y, ...)
+}
+
+
+\arguments{
+   \item{x}{An \code{Image} object, an array, or a list containing \code{Image} objects and arrays.}
+   \item{y}{An \code{Image} object or an array.}
+   \item{...}{\code{Image} objects or arrays.}
+}
+
+\value{
+  An \code{Image} object or an array.
+}
+
+\details{
+  The function \code{combine} uses \code{\link[abind]{abind}} to merge multi-dimensional
+  arrays along the dimension depending on the
+  color mode of \code{x}. If \code{x} is a \code{Grayscale} image or an array,
+  image objects are combined along the third dimension, whereas when
+  \code{x} is a \code{Color} image they are combined along the fourth dimension, leaving room on the third dimension for color
+  channels.
+}
+
+\seealso{
+  The method \code{\link{abind}} provides a more flexible interface which allows to specify the dimension along which to combine the images.
+}
+
+\author{
+  Gregoire Pau, Andrzej Oles, 2013
+}
+
+\examples{
+  ## combination of color images
+  img = readImage(system.file("images", "sample-color.png", package="EBImage"))[257:768,,]
+  x = combine(img, flip(img), flop(img))
+  display(x, all=TRUE)
+
+  ## Blurred images
+  x = resize(img, 128, 128)
+  xt = list()
+  for (t in seq(0.1, 5, len=9)) xt=c(xt, list(gblur(x, s=t)))
+  xt = combine(xt)
+  display(xt, title='Blurred images', all=TRUE)
+}
diff --git a/man/computeFeatures.Rd b/man/computeFeatures.Rd
new file mode 100644
index 0000000..ad1df74
--- /dev/null
+++ b/man/computeFeatures.Rd
@@ -0,0 +1,159 @@
+\name{computeFeatures}
+
+\alias{computeFeatures}
+\alias{computeFeatures.basic}
+\alias{computeFeatures.shape}
+\alias{computeFeatures.moment}
+\alias{computeFeatures.haralick}
+\alias{standardExpandRef}
+\alias{gblob}
+  
+\title{Compute object features}
+
+\description{
+  Computes morphological and texture features from image objects.
+}
+
+\usage{
+computeFeatures(x, ref, methods.noref=c("computeFeatures.moment", "computeFeatures.shape"),
+  methods.ref=c("computeFeatures.basic", "computeFeatures.moment", "computeFeatures.haralick"),
+  xname="x", refnames, properties=FALSE, expandRef=standardExpandRef, ...)
+  
+computeFeatures.basic(x, ref, properties=FALSE, basic.quantiles=c(0.01, 0.05, 0.5, 0.95, 0.99), xs, ...)
+computeFeatures.shape(x, properties=FALSE, xs, ...)
+computeFeatures.moment(x, ref, properties=FALSE, xs, ...)
+computeFeatures.haralick(x, ref , properties=FALSE, haralick.nbins=32, haralick.scales=c(1, 2), xs, ...)
+
+standardExpandRef(ref, refnames, filter = gblob())
+}
+
+\arguments{
+  \item{x}{An \code{Image} object or an array containing labelled objects.
+    Labelled objects are pixel sets with the same unique integer value.}
+  \item{ref}{A matrix or a list of matrices, containing the
+    intensity values of the reference objects.}
+  \item{methods.noref}{A character vector containing the function names
+    to be called to compute features without reference intensities. Default is
+    \code{computeFeatures.moment} and \code{computeFeatures.shape}.}
+ \item{methods.ref}{A character vector containing the function names
+   to be called to compute features with reference intensities. Default is
+   \code{computeFeatures.basic}, \code{computeFeatures.moment} and
+   \code{computeFeatures.haralick}.}
+   \item{xname}{A character string naming the object layer. Default is
+     \code{x}.}
+   \item{refnames}{A character vector naming the reference intensity
+     layers. Default are the names of \code{ref}, if present. If not,
+     reference intensity layers are named using lower-case letters.}
+   \item{properties}{A logical. If \code{FALSE}, the default, the
+    function returns the feature matrix. If \code{TRUE}, the function
+    returns feature properties.}
+  \item{expandRef}{A function used to expand the reference
+    images. Default is \code{standardExpandRef}. See Details.}
+  \item{basic.quantiles}{A numerical vector indicating the quantiles to
+    compute.}
+  \item{haralick.nbins}{An integer indicating the number of bins using
+    to compute the Haralick matrix. See Details.}
+  \item{haralick.scales}{A integer vector indicating the number of
+    scales to use to compute the Haralick features.}
+  \item{xs}{An optional temporary object created by
+    \code{computeFeatures} used for performance considerations.}
+  \item{filter}{The filter applied to reference images using \code{\link{filter2}} in order to add granulometry.}
+  \item{...}{Optional arguments passed to the feature computation functions.}
+}
+
+\value{
+  If \code{properties} if \code{FALSE} (by default), \code{computeFeatures}
+  returns a matrix of n cells times p features, where p depends of
+  the options given to the function. Returns \code{NULL} if no object is
+  present.
+
+  If \code{properties} if \code{TRUE}, \code{computeFeatures}
+  returns a matrix of p features times 2 properties (translation and
+  rotation invariance). Feature properties are useful to filter out
+  features that may not be needed for specific tasks, e.g. cell
+  position when doing cell classification.
+}
+
+\details{
+  Features are named x.y.f, where x is the object layer, y the reference
+  image layer and f the feature name. Examples include \code{cell.dna.mean},
+  indicating mean DNA intensity computed in the cell or
+  \code{nucleus.tubulin.cx}, indicating the x center of mass of tubulin
+  computed in the nucleus region.
+
+  The function \code{computeFeatures} computes sets of
+  features. Features are organized in 4 sets, each computed by a
+  different function. The function \code{computeFeatures.basic}
+  computes spatial-independent statistics on pixel intensities:
+  \itemize{
+    \item b.mean: mean intensity
+    \item b.sd: standard deviation intensity
+    \item b.mad: mad intensity
+    \item b.q*: quantile intensity
+  }
+  
+  The function \code{computeFeatures.shape} computes features that
+  quantify object shape:
+  \itemize{
+    \item s.area: area size (in pixels)
+    \item s.perimeter: perimeter (in pixels)
+    \item s.radius.mean: mean radius (in pixels)
+    \item s.radius.sd: standard deviation of the mean radius (in pixels)
+    \item s.radius.max: max radius (in pixels)
+    \item s.radius.min: min radius (in pixels)
+  }
+  
+  The function \code{computeFeatures.moment} computes features
+  related to object image moments, which can be computed with or without
+  reference intensities:
+  \itemize{
+    \item m.cx: center of mass x (in pixels)
+    \item m.cy: center of mass y (in pixels)
+    \item m.majoraxis: elliptical fit major axis (in pixels)
+    \item m.eccentricity: elliptical eccentricity defined by
+    sqrt(1-minoraxis^2/majoraxis^2). Circle eccentricity is 0 and straight
+    line eccentricity is 1.
+    \item m.theta: object angle (in radians)
+  }
+
+  The function \code{computeFeatures.haralick} computes features
+  that quantify pixel texture. Features are named according to
+  Haralick's original paper.
+}
+
+
+\references{
+  R. M. Haralick, K Shanmugam and Its'Hak Deinstein (1979). \emph{Textural Features for Image 
+  Classification}. IEEE Transactions on Systems, Man and Cybernetics.
+}
+
+\seealso{
+  \code{\link{bwlabel}}, \code{\link{propagate}}
+}
+
+\author{
+  Gregoire Pau, \email{gregoire.pau at embl.de}, 2011
+}
+
+\examples{
+  ## load and segment nucleus
+  y = readImage(system.file("images", "nuclei.tif", package="EBImage"))[,,1]
+  x = thresh(y, 10, 10, 0.05)
+  x = opening(x, makeBrush(5, shape='disc'))
+  x = bwlabel(x)
+  display(y, title="Cell nuclei")
+  display(x, title="Segmented nuclei")
+
+  ## compute shape features
+  fts = computeFeatures.shape(x)
+  fts
+
+  ## compute features
+  ft = computeFeatures(x, y, xname="nucleus")
+  cat("median features are:\n")
+  apply(ft, 2, median)
+
+  ## compute feature properties
+  ftp = computeFeatures(x, y, properties=TRUE, xname="nucleus")
+  ftp
+}
diff --git a/man/display-shiny.Rd b/man/display-shiny.Rd
new file mode 100644
index 0000000..beeb269
--- /dev/null
+++ b/man/display-shiny.Rd
@@ -0,0 +1,85 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/displayWidget.R
+\name{display-shiny}
+\alias{display-shiny}
+\alias{displayOutput}
+\alias{renderDisplay}
+\title{Shiny Bindings for display}
+\usage{
+displayOutput(outputId, width = "100\%", height = "500px")
+
+renderDisplay(expr, env = parent.frame(), quoted = FALSE)
+}
+\arguments{
+\item{outputId}{output variable to read from}
+
+\item{width, height}{Must be a valid CSS unit (like \code{'100\%'},
+\code{'400px'}, \code{'auto'}) or a number, which will be coerced to a
+string and have \code{'px'} appended.}
+
+\item{expr}{An expression that generates the image viewer (typicall through a call to \code{display})}
+
+\item{env}{The environment in which to evaluate \code{expr}.}
+
+\item{quoted}{Is \code{expr} a quoted expression (with \code{quote()})? This
+is useful if you want to save an expression in a variable.}
+}
+\description{
+Output and render functions for using the interactive image viewer within Shiny
+applications and interactive R Markdown documents.
+}
+
+\seealso{
+  \code{\link{display}}
+}
+
+\examples{
+# Only run this example in interactive R sessions
+if (interactive()) {
+  options(device.ask.default = FALSE)
+
+  require("shiny")
+  
+  ui <- fluidPage(
+  
+    # Application title
+    titlePanel("Image display"),
+  
+    # Sidebar with a select input for the image
+    sidebarLayout(
+      sidebarPanel(
+        selectInput("image", "Sample image:", list.files(system.file("images", package="EBImage")))
+      ),
+  
+      # Show a plot of the generated distribution
+      mainPanel(
+        tabsetPanel(
+          tabPanel("Static raster", plotOutput("raster")),
+          tabPanel("Interactive browser", displayOutput("widget"))
+        )
+      )
+    )
+  
+  )
+  
+  server <- function(input, output) {
+  
+    img <- reactive({
+      f = system.file("images", input$image, package="EBImage")
+      readImage(f)
+    })
+  
+    output$widget <- renderDisplay({
+      display(img())
+    })
+  
+    output$raster <- renderPlot({
+      plot(img(), all=TRUE)
+    })
+  
+  }
+  
+  # Run the application
+  shinyApp(ui = ui, server = server)
+}
+}
diff --git a/man/display.Rd b/man/display.Rd
new file mode 100644
index 0000000..ecf0cf2
--- /dev/null
+++ b/man/display.Rd
@@ -0,0 +1,93 @@
+\name{display}
+
+\alias{display}
+\alias{plot.Image}
+
+\concept{display}
+
+\title{Image Display}
+
+\description{
+  Display images in an interactive JavaScript viewer or using R's built-in graphics capabilities.
+}
+
+\usage{
+display(x, method, ...)
+
+\method{plot}{Image}(x, ...)
+}
+
+\arguments{
+  \item{x}{an \code{Image} object or an array.}
+  \item{method}{the way of displaying images. Defaults to \code{"browser"} when R is used interactively, and to \code{"raster"} otherwise. The default behavior can be overridden by setting \code{options("EBImage.display")}. See Details.}
+  \item{\dots}{arguments to be passed to the specialized display functions; for details see the sections on individual display methods.}
+}
+
+\details{
+The default \code{method} used for displaying images depends on whether called from and interactive R session. If \code{interactive()} is \code{TRUE} images are displayed with the \code{"browser"} method, otherwise the \code{"raster"} method is used. This dynamic behavior can be overridden by setting \code{options("EBImage.display")} to either \code{"browser"} or \code{"raster"}.
+
+\code{plot.Image} S3 method is a wrapper for \code{display(..., method="raster")}
+}
+
+\section{"browser" method}{
+The \code{"browser"} method runs an interactive JavaScript image viewer. A list of available features along with corresponding mouse and keyboard actions is shown by pressing 'h'. This method takes the following additional arguments.
+\describe{
+  \item{\code{embed}}{logical(1), include images in the document as data URIs. Defaults to \code{TRUE} in non-interactive context (e.g. static R Markdown documents), otherwise to \code{FALSE}.}
+  
+  \item{\code{tempDir}}{character(1), file path for storing any temporary image files. Defaults to \code{tempfile("")}}
+  
+  \item{\code{\dots}}{arguments passed to \code{\link{createWidget}}, such as fixed \code{width} and \code{height} (in CSS units), \code{elementId}, or \code{preRenderHook}.}
+  }
+}
+
+\section{"raster" method}{
+The \code{"raster"} method displays images as R raster graphics. The user coordinates of the plotting region are set to the image pixel coordinates with the origin \code{(0, 0)} in the upper left corner.
+
+By default only the first frame of an image stack is shown; a different \code{frame} can also be specified. When \code{all=TRUE} the whole image stack is rendered and the frames are automatically positioned next to each other in a grid. The grid layout can be modified through \code{nx} and \code{spacing} and \code{margin}.
+
+This method provides to following additional arguments to \code{display}.
+  \describe{
+    \item{\code{interpolate}}{a logical vector (or scalar) indicating whether to apply linear interpolation to the image when drawing.}
+    \item{\code{frame}}{a numeric indicating the frame number to display; only effective when \code{all = FALSE}.}
+    \item{\code{all}}{logical, defaulting to \code{FALSE}. If set to \code{TRUE}, all frames of a stacked image are displayed arranged in a grid, otherwise (default) just a single frame specified in \code{frame} is displayed. The grid layout can be controlled by \code{nx}, \code{spacing} and \code{margin}.}
+    \item{\code{drawGrid}}{a logical indicating whether to draw grid lines between individual frames. Defaults to \code{TRUE} unless \code{spacing} is non-zero. Line color, type and width can be specified through graphical parameters \code{col}, \code{lty} and \code{lwd}, respectively; see \code{\link{par}} for details.}
+    \item{\code{nx}}{integer. Specifies the number images in a row. Negative numbers are interpreted as the number of images in a column, e.g. use \code{-1} to display a single row containing all the frames.}
+    \item{\code{spacing}}{numeric. Specifies the separation between frames as a fraction of frame dimensions (positive numbers <1) or in pixels (numbers >=1). It can be either a single number or a vector of length 2, in which case its elements correspond to the horizontal and vertical spacing, respectively.}
+    \item{\code{margin}}{numeric. Specifies the outer margins around the image, or a grid of images. Similarly as for \code{spacing}, different horizontal and vertical margins can be defined by providing a vector.}
+    \item{\code{\dots}}{graphical parameters passed to \code{\link{par}}}
+  }
+}
+
+\author{
+  Andrzej Oles, \email{andrzej.oles at embl.de}, 2012-2017
+}
+
+\seealso{
+  \code{\link{display-shiny}}
+}
+
+\examples{
+## Display a single image
+x = readImage(system.file("images", "sample-color.png", package="EBImage"))[257:768,,]
+display(x)
+
+## Display a thresholded sequence ...
+y = readImage(system.file("images", "sample.png", package="EBImage"))[366:749, 58:441]
+z = lapply(seq(from=0.5, to=5, length=6),
+  function(s) gblur(y, s, boundary="replicate")
+)
+z = combine(z)
+
+## ... using the interactive viewer ...
+display(z)
+
+## ... or using R's build-in raster device
+display(z, method = "raster", all = TRUE)
+
+## Display the last frame 
+display(z, method = "raster", frame = numberOfFrames(z, type = "render"))
+
+## Customize grid appearance
+display(z, method = "raster", all = TRUE,
+        nx = 2, spacing = 0.05, margin = 20, bg = "black")
+}
diff --git a/man/distmap.Rd b/man/distmap.Rd
new file mode 100644
index 0000000..423cc65
--- /dev/null
+++ b/man/distmap.Rd
@@ -0,0 +1,49 @@
+\name{distmap}
+
+\alias{distmap}
+
+\title{Distance map transform}
+
+\description{
+  Computes the distance map transform of a binary image.
+  The distance map is a matrix which contains for each pixel
+  the distance to its nearest background pixel.
+}
+
+\usage{
+distmap(x, metric=c('euclidean', 'manhattan'))
+}
+
+\arguments{
+  \item{x}{An \code{Image} object or an array.
+    \code{x} is considered as a binary image, whose pixels of value 0
+    are considered as background ones and other pixels as
+    foreground ones.}
+
+  \item{metric}{A character indicating which metric to use, L1 distance (\code{manhattan}) 
+  or L2 distance (\code{euclidean}). Default is \code{euclidean}.} 
+}
+
+\value{
+  An \code{Image} object or an array, with pixels
+  containing the distances to the nearest background points. 
+}
+
+\details{
+  A fast algorithm of complexity O(M*N*log(max(M,N))), where (M,N) are the
+  dimensions of \code{x}, is used to compute the distance map.
+}
+
+\references{M. N. Kolountzakis, K. N. Kutulakos. Fast Computation of the Euclidean
+  Distance Map for Binary Images, Infor. Proc. Letters 43 (1992).}
+
+\author{
+  Gregoire Pau, \email{gpau at ebi.ac.uk}, 2008
+}
+
+\examples{
+  x = readImage(system.file("images", "shapes.png", package="EBImage"))
+  display(x)
+  dx = distmap(x)
+  display(dx/10, title='Distance map of x')
+}
diff --git a/man/drawCircle.Rd b/man/drawCircle.Rd
new file mode 100644
index 0000000..ea9c27f
--- /dev/null
+++ b/man/drawCircle.Rd
@@ -0,0 +1,49 @@
+\name{drawCircle}
+
+\alias{drawCircle}
+
+\title{Draw a circle on an image.}
+
+\description{
+  Draw a circle on an image.
+}
+
+\usage{
+drawCircle(img, x, y, radius, col, fill=FALSE, z=1)
+}
+
+\arguments{
+\item{img}{An \code{Image} object or an array.}  
+\item{x, y, radius}{numerics indicating the center and the radius
+    of the circle.}
+  
+\item{col}{A numeric or a character string specifying the color
+    of the circle.}
+  
+\item{fill}{A logical indicating whether the circle should be filled.
+    Default is \code{FALSE}.}
+  
+\item{z}{A numeric indicating on which frame of the image the circle
+    should be drawn. Default is 1.}
+}
+
+\value{
+  An \code{Image} object or an array, containing the transformed version
+  of \code{img}.
+}
+
+\author{
+  Gregoire Pau, 2010
+}
+
+\examples{
+  ## Simple white circle
+  x = matrix(0, nrow=300, ncol=300)
+  y = drawCircle(x, 100, 200, 47, col=1)
+  display(y)
+  
+  ## Simple filled yellow circle
+  x = channel(y, 'rgb')
+  y = drawCircle(x, 200, 140, 57, col='yellow', fill=TRUE)
+  display(y)
+}
diff --git a/man/equalize.Rd b/man/equalize.Rd
new file mode 100644
index 0000000..0bc0cef
--- /dev/null
+++ b/man/equalize.Rd
@@ -0,0 +1,56 @@
+\name{equalize}
+
+\alias{equalize}
+
+\concept{equalization}
+
+\title{Histogram Equalization}
+
+\description{
+  Equalize the image histogram to a specified range and number of levels.
+}
+
+\usage{
+equalize(x, range = c(0, 1), levels = 256)
+}
+
+\arguments{
+  \item{x}{an \code{Image} object or an array}
+
+  \item{range}{numeric vector of length 2, the output range of the equalized histogram}
+
+  \item{levels}{number of grayscale levels (Grayscale images) or intensity levels of each channel (Color images)}
+}
+
+\value{
+  An \code{Image} object or an array, containing the transformed version
+  of \code{x}.
+}
+
+\details{
+ Histogram equalization is an adaptive image contrast adjustment method. It flattens the image histogram by performing linearization of the cumulative distribution function of pixel intensities.
+
+ Individual channels of \code{Color} images and frames of image stacks are equalized separately.
+}
+
+\seealso{
+  \code{\link{clahe}}
+}
+
+\author{
+  Andrzej Oles, \email{andrzej.oles at embl.de}, 2014
+}
+
+\examples{
+  x = readImage(system.file('images', 'cells.tif', package='EBImage'))
+  hist(x)
+  y = equalize(x)
+  hist(y)
+  display(y, title='Equalized Grayscale Image')
+
+  x = readImage(system.file('images', 'sample-color.png', package='EBImage'))
+  hist(x)
+  y = equalize(x)
+  hist(y)
+  display(y, title='Equalized Grayscale Image')
+}
diff --git a/man/fillHull.Rd b/man/fillHull.Rd
new file mode 100644
index 0000000..e971934
--- /dev/null
+++ b/man/fillHull.Rd
@@ -0,0 +1,46 @@
+\name{fillHull}
+
+\alias{fillHull}
+
+\title{Fill holes in objects}
+
+\description{
+  Fill holes in objects.
+}
+
+\usage{
+fillHull(x)
+}
+
+\arguments{
+  \item{x}{An \code{Image} object or an array.}
+}
+
+\value{
+  An \code{Image} object or an array, containing the transformed version
+  of \code{x}.
+}
+
+\details{
+  \code{fillHull} fills holes in the objects defined in \code{x}, where
+   objects are sets of pixels with the same unique integer value.
+}
+
+\seealso{
+  \code{\link{bwlabel}}
+}
+
+\author{
+  Gregoire Pau, Oleg Sklyar; 2007
+}
+
+\examples{
+  x = readImage(system.file('images', 'nuclei.tif', package='EBImage'))
+  display(x)
+
+  y = thresh(x, 10, 10, 0.05)
+  display(y, title='Cell nuclei')
+
+  y = fillHull(y)
+  display(y, title='Cell nuclei without holes')
+}
diff --git a/man/filter2.Rd b/man/filter2.Rd
new file mode 100644
index 0000000..90303e0
--- /dev/null
+++ b/man/filter2.Rd
@@ -0,0 +1,75 @@
+\name{filter2}
+
+\alias{filter2}
+
+\title{2D Convolution Filter}
+
+\description{
+  Filters an image using the fast 2D FFT convolution product.
+}
+
+\usage{
+filter2(x, filter, boundary = c("circular", "replicate"))
+}
+
+\arguments{
+  \item{x}{An \code{Image} object or an array.}
+
+  \item{filter}{An \code{Image} object or an array, with odd spatial
+    dimensions. Must contain only one frame.}
+    
+  \item{boundary}{Behaviour at image borders. The default is to wrap the image around borders. For other modes see details.}
+}
+
+\value{
+  An \code{Image} object or an array, containing the filtered version
+  of \code{x}.
+}
+
+\details{
+  Linear filtering is useful to perform low-pass filtering (to blur
+  images, remove noise...) and high-pass filtering (to detect
+  edges, sharpen images). The function \code{makeBrush} is useful to
+  generate filters.
+
+  The default \code{"circular"} behaviour at boundaries is to wrap the image around borders.
+  In the \code{"replicate"} mode pixels outside the bounds of the image are assumed to equal the nearest border pixel value.
+  Numeric values of \code{boundary} yield linear convolution by padding the image with the given value(s).
+  
+  If \code{x} contains multiple frames, the filter is applied separately to each frame.
+  
+}
+
+\seealso{
+  \code{\link{makeBrush}}, \code{\link{convolve}}, \code{\link{fft}}, \code{\link{blur}}
+}
+
+\author{
+  Andrzej Oleś, Gregoire Pau
+}
+
+\examples{
+  x = readImage(system.file("images", "sample-color.png", package="EBImage"))
+  display(x, title='Sample')
+
+  ## Low-pass disc-shaped filter
+  f = makeBrush(21, shape='disc', step=FALSE)
+  display(f, title='Disc filter')
+  f = f/sum(f)
+  y = filter2(x, f)
+  display(y, title='Filtered image')
+
+  ## Low-pass filter with linear padded boundary
+  y = filter2(x, f, boundary=c(0,.5,1))
+  display(y, title='Filtered image with linear padded boundary')
+
+  ## High-pass Laplacian filter
+  la = matrix(1, nc=3, nr=3)
+  la[2,2] = -8
+  y = filter2(x, la)
+  display(y, title='Filtered image')
+  
+  ## High-pass Laplacian filter with replicated boundary
+  y = filter2(x, la, boundary='replicate')
+  display(y, title='Filtered image with replicated boundary')
+}
diff --git a/man/floodFill.Rd b/man/floodFill.Rd
new file mode 100644
index 0000000..84f4fd4
--- /dev/null
+++ b/man/floodFill.Rd
@@ -0,0 +1,72 @@
+\name{floodFill}
+
+\alias{floodFill}
+
+\title{Region filling}
+
+\description{
+  Fill regions in images.
+}
+
+\usage{
+floodFill(x, pts, col, tolerance=0)
+}
+
+\arguments{
+  \item{x}{An \code{Image} object or an array.}
+
+  \item{pts}{Coordinates of the start filling points given as either of the following: a vector of the form \code{c(x1, y1, x2, y2, ...)}, a list of points, a matrix or data frame where rows represent points and columns are the x and y coordinates. For image stacks different points for each frame can be specified by providing them in a list of length matching the number of 'render' frames.}
+
+  \item{col}{Fill color. This argument should be a numeric for Grayscale images and an R color for Color images. Values are recycled such that their length matches the number of points in \code{pts}. Can be a list of length matching the number of 'render' frames similarly as \code{pts}.}
+
+  \item{tolerance}{Color tolerance used during the fill.}
+}
+
+\value{
+  An \code{Image} object or an array, containing the transformed version
+  of \code{x}.
+}
+
+\details{
+  Flood fill is performed using the fast scan line algorithm. Filling
+  starts at \code{pts} and grows in connected areas where the absolute
+  difference of the pixels intensities (or colors) remains below
+  \code{tolerance}.
+}
+
+\author{
+  Gregoire Pau, Oleg Sklyar; 2007
+}
+
+\examples{
+  x = readImage(system.file("images", "shapes.png", package="EBImage"))
+
+  ## fill a shape with 50% shade of gray
+  y = floodFill(x, c(67, 146), 0.5)
+  display(y)
+  
+  ## fill with color
+  y = toRGB(y)
+  y = floodFill(y, c(48, 78), 'orange')	
+  display(y)
+  
+  ## fill multiple shapes with different colors
+  y = y[110:512,1:130,]
+  points = rbind(c(50, 50), c(100, 50), c(150, 50))
+  colors = c("red", "green", "blue")
+  y = floodFill(y, points, colors)
+  display(y)
+  
+  ## area fill
+  x = readImage(system.file("images", "sample.png", package="EBImage"))
+  y = floodFill(x, rbind(c(200, 400), c(200, 325)), 1, tolerance=0.1)
+  display(y)
+  
+  ## application to image stacks
+  f = system.file("images", "nuclei.tif", package="EBImage")
+  x = readImage(f)[1:250,1:250,]
+  x = opening(thresh(x, 12, 12), makeBrush(5, shape='disc'))
+  xy = lapply(getFrames(bwlabel(x)), function(x) computeFeatures.moment(x)[,1:2])
+  y = floodFill(toRGB(x), xy, c("red", "green", "blue"))
+  display(y)
+}
diff --git a/man/gblur.Rd b/man/gblur.Rd
new file mode 100644
index 0000000..aa48b23
--- /dev/null
+++ b/man/gblur.Rd
@@ -0,0 +1,49 @@
+\name{gblur}
+
+\alias{gblur}
+
+\title{Low-pass Gaussian filter	}
+
+\description{
+  Filters an image with a low-pass Gaussian filter.	
+}
+
+\usage{
+gblur(x, sigma, radius = 2 * ceiling(3 * sigma) + 1, ...)
+}
+
+\arguments{
+  \item{x}{An \code{Image} object or an array.}
+
+  \item{sigma}{A numeric denoting the standard deviation of the Gaussian filter
+    used for blurring.}
+
+  \item{radius}{The radius of the filter in pixels. Default is \code{2*ceiling(3*sigma)+1)}.}
+  
+  \item{...}{Arguments passed to \code{\link{filter2}}.}
+}
+
+\value{
+  An \code{Image} object or an array, containing the filtered version
+  of \code{x}.
+}
+
+\details{
+The Gaussian filter is created with the function \code{makeBrush}.	
+}
+
+\seealso{
+  \code{\link{filter2}},  \code{\link{makeBrush}}
+}
+
+\author{
+  Oleg Sklyar, \email{osklyar at ebi.ac.uk}, 2005-2007
+}
+
+\examples{
+  x = readImage(system.file("images", "sample.png", package="EBImage"))
+  display(x)
+
+  y = gblur(x, sigma=8)
+  display(y, title='gblur(x, sigma=8)')
+}
diff --git a/man/io.Rd b/man/io.Rd
new file mode 100644
index 0000000..c96d9f0
--- /dev/null
+++ b/man/io.Rd
@@ -0,0 +1,81 @@
+\name{io}
+
+\alias{readImage}
+\alias{writeImage}
+
+\concept{image IO}
+
+\title{Image I/O}
+
+\description{
+  Read images from files and URLs, and write images to files.
+}
+
+\usage{
+readImage(files, type, all = TRUE, names = sub("\\\\.[^.]*$", "", basename(files)), \dots)
+writeImage(x, files, type, quality = 100, bits.per.sample, compression = "none", \dots)
+}
+
+\arguments{
+  \item{files}{a character vector of file names or URLs.}
+  \item{type}{image type (optional). Supported values are: \code{jpeg}, \code{png}, and \code{tiff}. If missing, file format is automatically determined by file name extension.}
+  \item{all}{logical: when the file contains more than one image should all frames be read, or only the first one?}
+  \item{names}{a character vector used for frame names. Should have the same length as files.}
+  \item{x}{an \code{Image} object or an array.} 
+  \item{bits.per.sample}{a numeric scalar specifying the number of bits per sample (only for \code{tiff} files). Supported values are 8 and 16.}
+  \item{compression}{the desired compression algorithm (only for \code{tiff} files). For a list of supported values consult the documentation of the \code{\link{writeTIFF}} function from the \pkg{tiff} package.}
+  \item{quality}{a numeric ranging from 1 to 100 (default) controlling the quality of the JPEG output.}
+  \item{\dots}{arguments passed to the corresponding functions from the \pkg{jpeg}, \pkg{png}, and \pkg{tiff} packages.}
+}
+
+\value{
+  \code{readImage} returns a new \code{Image} object.
+
+  \code{writeImage} returns an invisible vector of file names.
+}
+
+\details{
+\code{readImage} loads all images from the \code{files} vector and returns them stacked into a single \code{Image} object containing an array of doubles ranging from 0 (black) to 1 (white). All images need to be of the same \code{type} and have the same dimensions and color mode. If \code{type} is missing, the appropriate file format is determined from file name extension. Color mode is determined automatically based on the number of channels. When the function fails to read an image it  [...]
+
+  \code{writeImage} writes images into files specified by \code{files}, were the number of \code{files} needs to be equal 1 or the number of frames. Given an image containing multiple frames and a single file name either the whole stack is written into a single TIFF file, or each frame is saved to an individual JPEG/PNG file (for \code{files = "image.*"} frames are saved into \code{image-X.*} files, where \code{X} equals the frame number less one; for an image containing \code{n} frames  [...]
+
+  When writing JPEG files the compression quality can be specified using \code{quality}. Valid values range from 100 (highest quality) to 1 (lowest quality). For TIFF files additional information about the desired number of bits per sample (\code{bits.per.sample}) and the compression algorithm (\code{compression}) can be provided. For a complete list of supported values please consult the documentation of the \pkg{tiff} package.
+}
+
+\note{
+  Image formats have a limited dynamic range (e.g. JPEG: 8 bit, TIFF: 16 bit) and \code{writeImage} may cause some loss of accuracy. In specific, writing 16 bit image data to formats other than TIFF will strip the 8 LSB. When writing TIFF files a dynamic range check is performed and an appropriate value of \code{bits.per.sample} is set automatically.
+}
+
+\seealso{
+  \code{\link{Image}}, \code{\link{display}}, \code{\link{readJPEG}}/\code{\link{writeJPEG}},  \code{\link{readPNG}}/\code{\link{writePNG}}, \code{\link{readTIFF}}/\code{\link{writeTIFF}}
+}
+
+\author{
+  Andrzej Oles, \email{andrzej.oles at embl.de}, 2012
+}
+
+\examples{
+  ## Read and display an image
+  f = system.file("images", "sample-color.png", package="EBImage")
+  x = readImage(f)
+  display(x)
+
+  ## Read and display a multi-frame TIFF
+  y = readImage(system.file("images", "nuclei.tif", package="EBImage"))
+  display(y)
+
+  ## Read an image directly from a remote location by specifying its URL
+  try({
+    im = readImage("http://www-huber.embl.de/EBImage/ExampleImages/berlin.tif")
+    display(im, title = "Berlin Impressions")
+  })
+
+  ## Convert a PNG file into JPEG
+  tempfile = tempfile("", , ".jpeg")
+  writeImage(x, tempfile, quality = 85)
+  cat("Converted '", f, "' into '", tempfile, "'.\n", sep="")
+
+  ## Save a frame sequence
+  files = writeImage(y, tempfile("", , ".jpeg"), quality = 85)
+  cat("Files created: ", files, sep="\n")
+}
diff --git a/man/localCurvature.Rd b/man/localCurvature.Rd
new file mode 100644
index 0000000..d67a2a4
--- /dev/null
+++ b/man/localCurvature.Rd
@@ -0,0 +1,70 @@
+\name{localCurvature}
+
+\alias{localCurvature}
+
+\title{Local Curvature}
+
+\description{
+  Computes signed curvature along a line.
+}
+
+\usage{
+localCurvature(x, h, maxk)
+}
+
+\arguments{
+  \item{x}{A data frame or matrix of dimensions N x 2 containing the coordinates 
+    of the line, where N is the number of points. The points should be ordered according 
+    to their position on the line. The columns should contain the x and y coordinates. 
+    The curvature calculation is unaffected by any permutation of the columns.
+    Directly accepts a list element from \code{ocontour}.}
+  \item{h}{Specifies the length of the smoothing window. See \code{locfit::lp} for more details.}
+  \item{maxk}{See \code{locfit::locfit.raw} for details.}
+}
+
+\value{
+  Returns a \code{list} containing the contour coordinates \code{x}, the signed curvature at each point \code{curvature} 
+  and the arc length of the contour \code{length}.
+}
+
+\details{
+ \code{localCurvature} fits a local non-parametric smoothing line (polynomial of degree 2) 
+ at each point along the line segment, and computes the curvature locally using numerical derivatives.
+}
+
+\author{
+  Joseph Barry, Wolfgang Huber, 2013
+}
+
+\seealso{
+  \code{\link{ocontour}}
+}
+
+\examples{
+  ## curvature goes as the inverse of the radius of a circle
+  range=seq(3.5,33.5,by=2)
+  plotRange=seq(0.5,33.5,length=100)
+  circleRes=array(dim=length(range))
+  names(circleRes)=range
+  for (i in  seq_along(1:length(range))) {
+   y=as.Image(makeBrush('disc', size=2*range[i]))
+   y=ocontour(y)[[1]]
+   circleRes[i]=abs(mean(localCurvature(x=y,h=range[i])$curvature, na.rm=TRUE))
+  }
+  plot(range, circleRes, ylim=c(0,max(circleRes, na.rm=TRUE)), xlab='Circle Radius', ylab='Curvature', type='p', xlim=range(plotRange))
+  points(plotRange, 1/plotRange, type='l')
+
+  ## Calculate curvature
+  x = readImage(system.file("images", "shapes.png", package="EBImage"))[25:74, 60:109]
+  x = resize(x, 200)
+  y = gblur(x, 3) > .3
+  display(y)
+  
+  contours = ocontour(bwlabel(y))
+  c = localCurvature(x=contours[[1]], h=11)
+  i = c$curvature >= 0
+  pos = neg = array(0, dim(x))
+  pos[c$contour[i,]+1]  = c$curvature[i]
+  neg[c$contour[!i,]+1] = -c$curvature[!i]
+  display(10*(rgbImage(pos, , neg)), title = "Image curvature")
+}
diff --git a/man/medianFilter.Rd b/man/medianFilter.Rd
new file mode 100644
index 0000000..a1a9708
--- /dev/null
+++ b/man/medianFilter.Rd
@@ -0,0 +1,64 @@
+\name{medianFilter}
+
+\alias{medianFilter}
+
+\title{2D constant time median filtering}
+
+\description{
+  Process an image using Perreault's modern constant-time median filtering algorithm [1, 2].
+}
+
+\usage{
+medianFilter(x, size, cacheSize=512)
+}
+
+\arguments{
+  \item{x}{an \code{Image} object or an array.}
+  \item{size}{integer, median filter radius.}
+  \item{cacheSize}{integer, the L2 cache size of the system CPU in kB.}
+}
+
+\value{
+  An \code{Image} object or an array, containing the filtered version
+  of \code{x}.
+}
+
+\details{
+  Median filtering is useful as a smoothing technique, e.g. in the removal
+  of speckling noise.
+  
+  For a filter of radius \code{size}, the median kernel is a \code{2*size+1}
+  times \code{2*size+1} square.
+  
+  The input image \code{x} should contain pixel values in the range from 0 to 1, 
+  inclusive; values lower than 0 or higher than 1 are clipped before applying 
+  the filter. Internal processing is performed using 16-bit precision. The 
+  behavior at image boundaries is such as the source image has been padded with
+  pixels whose values equal the nearest border pixel value.
+  
+  If the image contains multiple channels or frames, 
+  the filter is applied to each one of them separately.
+}
+
+\seealso{
+  \code{\link{gblur}}
+}
+
+\author{
+  Joseph Barry, \email{joseph.barry at embl.de}, 2012
+
+  Andrzej Oleś, \email{andrzej.oles at embl.de}, 2016
+}
+
+\examples{
+  x = readImage(system.file("images", "nuclei.tif", package="EBImage"))
+  display(x, title='Nuclei')
+  y = medianFilter(x, 5)  
+  display(y, title='Filtered nuclei')
+}
+
+\references{
+  [1] S. Perreault and P. Hebert, "Median Filtering in Constant Time", IEEE Trans Image Process 16(9), 2389-2394, 2007
+  
+  [2] \url{http://nomis80.org/ctmf.html}
+}
diff --git a/man/morphology.Rd b/man/morphology.Rd
new file mode 100644
index 0000000..af43e1e
--- /dev/null
+++ b/man/morphology.Rd
@@ -0,0 +1,112 @@
+\name{morphology}
+
+\alias{dilate}
+\alias{closing}
+\alias{erode}
+\alias{opening}
+\alias{whiteTopHat}
+\alias{blackTopHat}
+\alias{selfComplementaryTopHat}
+\alias{makeBrush}
+
+\title{Perform morphological operations on images}
+
+\description{
+  Functions to perform morphological operations on binary and grayscale images.
+}
+
+\usage{
+dilate(x, kern)
+erode(x, kern)
+opening(x, kern)
+closing(x, kern)
+whiteTopHat(x, kern)
+blackTopHat(x, kern)
+selfComplementaryTopHat(x, kern)
+
+makeBrush(size, shape=c('box', 'disc', 'diamond', 'Gaussian', 'line'), step=TRUE, sigma=0.3, angle=45)
+}
+
+\arguments{
+  \item{x}{An \code{Image} object or an array.}
+  
+  \item{kern}{An \code{Image} object or an array, containing the
+    structuring element. \code{kern} is considered as a binary image, with
+    pixels of value 0 being the background and pixels with values other than 0 being the foreground.}
+  
+  \item{size}{A numeric containing the size of the brush in pixels. This should be an odd number; even numbers are rounded to the next odd one, i.e., \code{size = 4} has the same effect as \code{size = 5}. Default is 5}
+  
+  \item{shape}{A character vector indicating the shape of the brush. Can
+    be \code{box}, \code{disc}, \code{diamond}, \code{Gaussian} or \code{line}. Default is
+    \code{box}.}
+  
+  \item{step}{a logical indicating if the brush is binary. Default is
+    \code{TRUE}. This argument is relevant only for the \code{disc} and
+    \code{diamond} shapes.}
+
+  \item{sigma}{An optional numeric containing the standard deviation of
+    the \code{Gaussian} shape. Default is 0.3.}
+
+  \item{angle}{An optional numeric containing the angle at which the line should be drawn. The angle is
+    one between the top of the image and the line.}
+}
+
+\details{
+  \code{dilate} applies the mask \code{kern} by positioning its center over every pixel of the
+  image \code{x}, the output value of the pixel is the maximum value of \code{x}
+  covered by the mask. In case of binary images this is equivalent of putting the mask over every background pixel, and setting it to foreground if any of the pixels covered by the mask is from the foreground.
+
+  \code{erode} applies the mask \code{kern} by positioning its center over every pixel of the
+  image \code{x}, the output value of the pixel is the minimum value of \code{x}
+  covered by the mask. In case of binary images this is equivalent of putting the mask over every foreground pixel, and setting it to background if any of the pixels covered by the mask is from the background.
+
+  \code{opening} is an erosion followed by a dilation and \code{closing} is a dilation followed by an erosion.
+
+  \code{whiteTopHat} returns the difference between the original image \code{x} and its opening by the structuring element \code{kern}.
+  
+  \code{blackTopHat} subtracts the original image \code{x} from its closing by the structuring element \code{kern}.
+
+  \code{selfComplementaryTopHat} is the sum of the \code{whiteTopHat} and the \code{blackTopHat}, simplified
+  the difference between the \code{closing} and the \code{opening} of the image.
+
+  \code{makeBrush} generates brushes of various sizes and shapes that can be used
+  as structuring elements.
+
+  \subsection{Processing Pixels at Image Borders (Padding Behavior)}{
+    Morphological functions position the center of the structuring element over each pixel in the input image. For pixels close to the edge of an image, parts of the neighborhood defined by the structuring element may extend past the border of the image. In such a case, a value is assigned to these undefined pixels, as if the image was padded with additional rows and columns. The value of these padding pixels varies for dilation and erosion operations. For dilation, pixels beyond the ima [...]
+}
+
+\value{
+  \code{dilate}, \code{erode}, \code{opening}, \code{whiteTopHat}, \code{blackTopHat} and
+  \code{selfComplementaryTopHat} return the transformed \code{Image} object
+  or array \code{x}, after the corresponding morphological operation.
+    
+  \code{makeBrush} generates a 2D matrix containing the desired brush.
+}
+
+\note{
+  Morphological operations are implemented using the efficient Urbach-Wilkinson algorithm [1]. Its required computing time is independent of both the image content and the number of gray levels used.
+}
+
+\author{
+  Ilia Kats <\email{ilia-kats at gmx.net}> (2012), Andrzej Oles <\email{andrzej.oles at embl.de}> (2015)
+}
+
+\examples{	
+  x = readImage(system.file("images", "shapes.png", package="EBImage"))
+  kern = makeBrush(5, shape='diamond')  
+  
+  display(x)
+  display(kern, title='Structuring element')
+  display(erode(x, kern), title='Erosion of x')
+  display(dilate(x, kern), title='Dilatation of x')
+
+  ## makeBrush
+  display(makeBrush(99, shape='diamond'))
+  display(makeBrush(99, shape='disc', step=FALSE))
+  display(2000*makeBrush(99, shape='Gaussian', sigma=10))
+}
+
+\references{
+  [1] E. R. Urbach and M.H.F. Wilkinson, "Efficient 2-D grayscale morphological transformations with arbitrary flat structuring elements", IEEE Trans Image Process 17(1), 1-8, 2008
+}
diff --git a/man/normalize.Rd b/man/normalize.Rd
new file mode 100644
index 0000000..21a1d72
--- /dev/null
+++ b/man/normalize.Rd
@@ -0,0 +1,58 @@
+\name{normalize}
+
+\alias{normalize}
+\alias{normalize,array-method}
+\alias{normalize,matrix-method}
+\alias{normalize,Image-method}
+
+\concept{normalization}
+
+\title{Intensity values linear scaling}
+
+\description{
+  Linearly scale the intensity values of an image to a specified range.
+}
+
+\usage{
+\S4method{normalize}{Image}(object, separate=TRUE, ft=c(0,1), inputRange)%
+
+\S4method{normalize}{array}(object, separate=TRUE, ft=c(0,1), inputRange)
+}
+
+\arguments{
+  \item{object}{an \code{Image} object or an array}
+
+  \item{separate}{if \code{TRUE}, normalizes each frame separately}
+
+  \item{ft}{a numeric vector of 2 values, target minimum and maximum 
+    intensity values after normalization}
+    
+  \item{inputRange}{a numeric vector of 2 values, sets the range of the input
+    intensity values; values exceeding this range are clipped}
+}
+
+\value{
+  An \code{Image} object or an array, containing the transformed version
+  of \code{object}.
+}
+
+\details{
+  \code{normalize} performs linear interpolation of the intensity values of an image to the specified range \code{ft}. If \code{inputRange} is not set the whole dynamic range of the image is used as input. By specifying \code{inputRange} the input intensity range of the image can be limited to [min, max]. Values exceeding this range are clipped, i.e. intensities lower/higher than \code{min}/\code{max} are set to \code{min}/\code{max}.
+}
+
+\author{
+  Oleg Sklyar, \email{osklyar at ebi.ac.uk}, 2006-2007
+  Andrzej Oles, \email{andrzej.oles at embl.de}, 2013
+}
+
+\examples{
+  x = readImage(system.file('images', 'shapes.png', package='EBImage'))
+  x = x[110:512,1:130]
+  y = bwlabel(x)
+  display(x, title='Original')
+
+  print(range(y))
+  y = normalize(y)
+  print(range(y))
+  display(y, title='Segmented')
+}
diff --git a/man/ocontour.Rd b/man/ocontour.Rd
new file mode 100644
index 0000000..5fb68a0
--- /dev/null
+++ b/man/ocontour.Rd
@@ -0,0 +1,37 @@
+\name{ocontour}
+
+\alias{ocontour}
+\title{Oriented contours}
+
+\description{
+  Computes the oriented contour of objects.
+}
+
+\usage{
+ocontour(x)
+}
+
+\arguments{
+  \item{x}{An \code{Image} object or an array, containing objects.
+    Only integer values are considered. Pixels of value 0 constitute
+    the background. Each object is a set of pixels with the same unique
+    integer value. Objects are assumed connected.
+  }
+}
+
+\value{
+  A list of matrices, containing the coordinates of object oriented contours.
+}
+
+\author{
+  Gregoire Pau, \email{gpau at ebi.ac.uk}, 2008
+}
+
+\examples{
+  x = readImage(system.file("images", "shapes.png", package="EBImage"))
+  x = x[1:120,50:120]
+  display(x)
+  oc = ocontour(x)
+  plot(oc[[1]], type='l')
+  points(oc[[1]], col=2)
+}
diff --git a/man/otsu.Rd b/man/otsu.Rd
new file mode 100644
index 0000000..261f2f8
--- /dev/null
+++ b/man/otsu.Rd
@@ -0,0 +1,57 @@
+\name{otsu}
+
+\alias{otsu}
+
+\title{Calculate Otsu's threshold}
+
+\description{
+Returns a threshold value based on Otsu's method, which can be then used to reduce the grayscale image to a binary image.
+}
+
+\usage{
+otsu(x, range = c(0, 1), levels = 256)
+}
+
+\arguments{
+  \item{x}{A \code{Grayscale} \code{Image} object or an array.}
+  
+  \item{range}{Numeric vector of length 2 specifying the histogram range used for thresholding.}
+  
+  \item{levels}{Number of grayscale levels.}
+}
+
+\value{
+A vector of length equal to the total number of frames in \code{x}. Each vector element contains the Otsu's threshold value calculated for the corresponding image frame.
+}
+
+\details{
+Otsu's thresholding method [1] is useful to automatically perform clustering-based image thresholding. The algorithm assumes that the distribution of image pixel intensities follows a bi-modal histogram, and separates those pixels into two classes (e.g. foreground and background). 
+The optimal threshold value is determined by minimizing the combined intra-class variance.
+
+The threshold value is calculated for each image frame separately resulting in a output vector of length equal to the total number of frames in the image.
+
+The default number of \code{levels} corresponds to the number of gray levels of an 8bit image. It is recommended to adjust this value according to the bit depth of the processed data, i.e. set \code{levels} to 2^16 = 65536 when working with 16bit images.
+}
+
+\seealso{
+  \code{\link{thresh}}
+}
+
+\references{
+   [1] Nobuyuki Otsu, "A threshold selection method from gray-level histograms". IEEE Trans. Sys., Man., Cyber. 9 (1): 62-66. doi:10.1109/TSMC.1979.4310076 (1979)
+}
+
+\author{
+  Philip A. Marais \email{philipmarais at gmail.com}, Andrzej Oles \email{andrzej.oles at embl.de}, 2014
+}
+
+\examples{
+  x = readImage(system.file("images", "sample.png", package="EBImage"))
+  display(x)
+  
+  ## threshold using Otsu's method
+  y = x > otsu(x)
+  display(y)
+}
+
+\keyword{manip}
diff --git a/man/paintObjects.Rd b/man/paintObjects.Rd
new file mode 100644
index 0000000..b61caaa
--- /dev/null
+++ b/man/paintObjects.Rd
@@ -0,0 +1,72 @@
+\name{paintObjects}
+
+\alias{paintObjects}
+
+\title{Mark objects in images}
+
+\description{
+  Higlight objects in images by outlining and/or painting them.
+}
+
+\usage{
+paintObjects(x, tgt, opac=c(1, 1), col=c('red', NA), thick=FALSE, closed=FALSE)
+}
+
+\arguments{
+   \item{x}{An \code{Image} object in \code{Grayscale} color mode or an
+    array containing object masks. Object masks are sets of pixels
+    with the same unique integer value.}
+    
+  \item{tgt}{An \code{Image} object or an array, containing the
+    intensity values of the objects.}
+
+  \item{opac}{A numeric vector of two opacity values for drawing object
+    boundaries and object bodies. Opacity ranges from 0 to 1, with 0
+    being fully transparent and 1 fully opaque.}
+
+  \item{col}{A character vector of two R colors for drawing object
+    boundaries and object bodies. By default, object boundaries are
+    painted in \code{red} while object bodies are not painted.}
+    
+  \item{thick}{A logical indicating whether to use thick boundary contours. Default is \code{FALSE}.}
+  
+  \item{closed}{A logical indicating whether object contours should be closed along image edges or remain open.}
+}
+
+\value{
+  An \code{Image} object or an array, containing the painted version of \code{tgt}.
+}
+
+\seealso{
+  \code{\link{bwlabel}}, \code{\link{watershed}}, \code{\link{computeFeatures}}, \code{\link{colorLabels}}
+}
+
+\examples{
+  ## load images
+  nuc = readImage(system.file('images', 'nuclei.tif', package='EBImage'))
+  cel = readImage(system.file('images', 'cells.tif', package='EBImage'))
+  img = rgbImage(green=cel, blue=nuc)
+  display(img, title='Cells')
+
+  ## segment nuclei
+  nmask = thresh(nuc, 10, 10, 0.05)
+  nmask = opening(nmask, makeBrush(5, shape='disc'))
+  nmask = fillHull(nmask)
+  nmask = bwlabel(nmask)
+  display(normalize(nmask), title='Cell nuclei mask')
+
+  ## segment cells, using propagate and nuclei as 'seeds'
+  ctmask = opening(cel>0.1, makeBrush(5, shape='disc'))
+  cmask = propagate(cel, nmask, ctmask)
+  display(normalize(cmask), title='Cell mask')
+
+  ## using paintObjects to highlight objects
+  res = paintObjects(cmask, img, col='#ff00ff')
+  res = paintObjects(nmask, res, col='#ffff00')
+  display(res, title='Segmented cells')
+}
+
+\author{
+  Oleg Sklyar, \email{osklyar at ebi.ac.uk}, 2006-2007
+  Andrzej Oles, \email{andrzej.oles at embl.de}, 2015
+}
diff --git a/man/propagate.Rd b/man/propagate.Rd
new file mode 100644
index 0000000..ba03782
--- /dev/null
+++ b/man/propagate.Rd
@@ -0,0 +1,123 @@
+\name{propagate}
+
+\alias{propagate}
+
+\concept{object detection}
+
+\title{Voronoi-based segmentation on image manifolds}
+
+\description{
+Find boundaries between adjacent regions in an image, where seeds
+have been already identified in the individual regions to be
+segmented. The method finds the Voronoi region of each seed on
+a manifold with a metric controlled by local image properties.
+The method is motivated by the problem of finding the borders of
+cells in microscopy images, given a labelling of the nuclei
+in the images.
+
+Algorithm and implementation are from Jones et al. [1].
+}
+
+\usage{
+propagate(x, seeds, mask=NULL, lambda=1e-4)
+}
+
+\arguments{
+   \item{x}{An \code{Image} object or an array, containing the image to segment.}
+
+   \item{seeds}{An \code{Image} object or an array, containing the seeding objects of
+   the already identified regions.}	
+
+   \item{mask}{An optional \code{Image} object or an array, containing
+   the binary image mask of the regions that can be segmented. If missing, 
+   the whole image is segmented.}
+
+   \item{lambda}{A numeric value. The regularization parameter used in the
+   metric, determining the trade-off between the Euclidean distance in the
+   image plane and the contribution of the gradient of \code{x}. See details.}
+}
+
+\value{
+  An \code{Image} object or an array, containing the labelled objects.
+}
+
+\details{
+  The method operates by computing a discretized approximation of the
+  Voronoi regions for given seed points on a Riemann manifold with a
+  metric controlled by local image features.
+
+  Under this metric, the infinitesimal distance d between points
+  v and v+dv is defined by: 
+  \preformatted{d^2 = ( (t(dv)*g)^2 + lambda*t(dv)*dv )/(lambda + 1) },
+  where g is the gradient of image \code{x} at point v.
+
+  \code{lambda} controls the weight of the Euclidean distance term. 
+  When \code{lambda} tends to infinity, d tends to the Euclidean
+  distance. When \code{lambda} tends to 0, d tends to the intensity
+  gradient of the image.
+
+  The gradient is
+  computed on a neighborhood of 3x3 pixels.
+
+  Segmentation of the Voronoi regions in the vicinity of flat areas
+  (having a null gradient) with small values of \code{lambda} can
+  suffer from artifacts coming from the metric approximation.
+}
+
+\seealso{ \code{\link{bwlabel}}, \code{\link{watershed}}
+}
+
+\examples{
+  ## a paraboloid mountain in a plane
+  n = 400
+  x = (n/4)^2 - matrix(
+	(rep(1:n, times=n) - n/2)^2 + (rep(1:n, each=n) - n/2)^2,
+	nrow=n, ncol=n)
+  x = normalize(x)
+
+  ## 4 seeds
+  seeds = array(0, dim=c(n,n))
+  seeds[51:55, 301:305] = 1
+  seeds[301:305, 101:105] = 2
+  seeds[201:205, 141:145] = 3
+  seeds[331:335, 351:355] = 4
+
+  lambda = 10^seq(-8, -1, by=1)
+  segmented = Image(dim=c(dim(x), length(lambda)))
+
+  for(i in seq_along(lambda)) {
+    prop = propagate(x, seeds, lambda=lambda[i])
+    prop = prop/max(prop)
+    segmented[,,i] = prop
+  }
+
+  display(x, title='Image')
+  display(seeds/max(seeds), title='Seeds')
+  display(segmented, title="Voronoi regions", all=TRUE)
+  
+}
+
+\author{
+  The original CellProfiler code is from Anne Carpenter <carpenter at wi.mit.edu>,
+  Thouis Jones <thouis at csail.mit.edu>, In Han Kang <inthek at mit.edu>.
+  Responsible for this implementation: Greg Pau.
+}
+
+\section{License}{
+  The implementation is based on CellProfiler C++ source code [2, 3].
+  An LGPL license was granted by Thouis Jones to use this part of
+  CellProfiler's code for the \code{propagate} function.
+}
+
+\references{
+   [1] T. Jones, A. Carpenter and P. Golland,
+    "Voronoi-Based Segmentation of Cells on Image Manifolds",
+    CVBIA05 (535-543), 2005
+
+   [2] A. Carpenter, T.R. Jones, M.R. Lamprecht, C. Clarke, I.H. Kang,
+    O. Friman, D. Guertin, J.H. Chang, R.A. Lindquist, J. Moffat,
+    P. Golland and D.M. Sabatini, "CellProfiler: image analysis software
+    for identifying and quantifying cell phenotypes", Genome Biology 2006, 7:R100
+
+   [3] CellProfiler: http://www.cellprofiler.org
+}
diff --git a/man/rmObjects.Rd b/man/rmObjects.Rd
new file mode 100644
index 0000000..fc00857
--- /dev/null
+++ b/man/rmObjects.Rd
@@ -0,0 +1,78 @@
+\name{rmObjects}
+
+\alias{rmObjects}
+\alias{reenumerate}
+
+\concept{delete objects}
+
+\title{Object removal and re-indexation}
+
+\description{
+  The \code{rmObjects} functions deletes objects from an image
+  by setting their pixel intensity values to 0.
+  \code{reenumerate} re-enumerates all objects in an image from 0 (background)
+  to the actual number of objects.
+}
+
+\usage{
+  rmObjects(x, index, reenumerate = TRUE)
+
+  reenumerate(x)
+}
+
+\arguments{
+ \item{x}{An \code{Image} object in \code{Grayscale} color mode or an
+    array containing object masks. Object masks are sets of pixels
+    with the same unique integer value.}
+
+  \item{index}{A numeric vector (or a list of vectors if \code{x} contains multiple frames) 
+    containing the indexes of objects to remove in the frame.}
+    
+  \item{reenumerate}{Logical, should all the objects in the image be re-indexed afterwards (default).}
+}
+
+\value{
+ An \code{Image} object or an array, containing the new objects.
+}
+
+\seealso{ 
+  \code{\link{bwlabel}}, \code{\link{watershed}}
+}
+
+\examples{
+  ## make objects
+  x = readImage(system.file('images', 'shapes.png', package='EBImage'))
+  x = x[110:512,1:130]
+  y = bwlabel(x)
+  
+  ## number of objects found
+  max(y)
+  
+  display(normalize(y), title='Objects')
+  
+  ## remove every second letter
+  objects = list(
+    seq.int(from = 2, to = max(y), by = 2),
+    seq.int(from = 1, to = max(y), by = 2)
+    )
+  z = rmObjects(combine(y, y), objects)
+  
+  display(normalize(z), title='Object removal')
+  
+  ## the number of objects left in each image
+  apply(z, 3, max)
+  
+  ## perform object removal without re-enumerating
+  z = rmObjects(y, objects, reenumerate = FALSE)
+  
+  ## labels of objects left
+  unique(as.vector(z))[-1L]
+  
+  ## re-index objects
+  z = reenumerate(z)
+  unique(as.vector(z))[-1L]
+}
+
+\author{
+  Oleg Sklyar, \email{osklyar at ebi.ac.uk}, 2006-2007
+}
diff --git a/man/spatial.Rd b/man/spatial.Rd
new file mode 100644
index 0000000..ab8bfa3
--- /dev/null
+++ b/man/spatial.Rd
@@ -0,0 +1,102 @@
+\name{resize}
+
+\alias{flip}
+\alias{flop}
+\alias{resize}
+\alias{rotate}
+\alias{translate}
+\alias{affine}
+
+\concept{transformation}
+\concept{rotation}
+\concept{resize}
+\concept{mirror}
+
+\title{Spatial linear transformations}
+
+\description{
+  The following functions perform all spatial linear transforms: reflection, rotation,
+  translation, resizing, and general affine transform.
+}
+
+\usage{
+flip(x)
+flop(x)
+rotate(x, angle, filter = "bilinear", output.dim, output.origin, ...)
+translate(x, v, filter = "none", ...)
+resize(x, w, h, output.dim = c(w, h), output.origin = c(0, 0), antialias = FALSE, ...)
+
+affine(x, m, filter = c("bilinear", "none"), output.dim, bg.col = "black", antialias = TRUE)
+}
+
+\arguments{
+  \item{x}{An \code{Image} object or an array.}
+  \item{angle}{A numeric specifying the image rotation angle in degrees.}
+  \item{v}{A vector of 2 numbers denoting the translation vector in pixels.}
+  \item{w, h}{Width and height of the resized image. One of these arguments
+    can be missing to enable proportional resizing.}  
+  \item{filter}{A character string indicating the interpolating sampling filter.
+                Valid values are 'none' or 'bilinear'. See Details.}
+  \item{output.dim}{A vector of 2 numbers indicating the dimension of the output image. 
+                    For \code{affine} and \code{translate} the default is \code{dim(x)}, for \code{resize} it equals \code{c(w, h)}, and for \code{rotate} it defaults to the bounding box size of the rotated image.}
+  \item{output.origin}{A vector of 2 numbers indicating the output coordinates of the origin in pixels.}
+  
+  \item{m}{A 3x2 matrix describing the affine transformation. See Details.}
+  \item{bg.col}{Color used to fill the background pixels, defaults to "black". In the case of multi-frame images the value is recycled, and individual background for each frame can be specified by providing a vector.}
+  \item{antialias}{If \code{TRUE}, perform bilinear sampling at image edges using \code{bg.col}.}
+  \item{...}{Arguments to be passed to \code{affine}, such as \code{filter}, \code{output.dim}, \code{bg.col} or \code{antialias}.}  
+}
+ 
+\value{
+  An \code{Image} object or an array, containing the transformed version
+  of \code{x}.
+}
+
+\details{
+  \code{flip} mirrors \code{x} around the image horizontal axis (vertical reflection).
+  
+  \code{flop} mirrors \code{x} around the image vertical axis (horizontal reflection).
+
+  \code{rotate} rotates the image clockwise by the given angle around the 
+  origin specified in \code{output.origin}. If no \code{output.origin} is 
+  provided, the result will be centered in a recalculated bounding box unless
+  \code{output.dim} is provided.
+ 
+  \code{resize} scales the image \code{x} to the desired dimensions.
+  The transformation origin can be specified in \code{output.origin}. 
+  For example, zooming about the \code{output.origin} can be achieved by setting
+  \code{output.dim} to a value different from \code{c(w, h)}.
+
+  \code{affine} returns the affine transformation of \code{x}, where
+  pixels coordinates, denoted by the matrix \code{px}, are
+  transformed to \code{cbind(px, 1)\%*\%m}. 
+
+  All spatial transformations except \code{flip} and \code{flop} are based on the 
+  general \code{affine} transformation. Spatial interpolation can be either
+  \code{none}, also called nearest neighbor, where the resulting pixel value equals to
+  the closest pixel value, or \code{bilinear}, where the new 
+  pixel value is computed by bilinear approximation of the 4 neighboring pixels. The
+  \code{bilinear} filter gives smoother results.
+}
+
+\seealso{ 
+  \code{\link{transpose}}
+}
+
+\author{
+  Gregoire Pau, 2012
+}
+
+\examples{
+  x <- readImage(system.file("images", "sample.png", package="EBImage"))
+  display(x)
+
+  display( flip(x) )
+  display( flop(x) ) 
+  display( resize(x, 128) )
+  display( rotate(x, 30) )
+  display( translate(x, c(120, -20)) )
+
+  m <- matrix(c(0.6, 0.2, 0, -0.2, 0.3, 300), nrow=3)
+  display( affine(x, m) )
+}
diff --git a/man/stackObjects.Rd b/man/stackObjects.Rd
new file mode 100644
index 0000000..b902d0f
--- /dev/null
+++ b/man/stackObjects.Rd
@@ -0,0 +1,91 @@
+\name{stackObjects}
+
+\alias{stackObjects}
+
+\concept{image manipulation}
+
+\title{Places detected objects into an image stack}
+
+\description{
+  Places detected objects into an image stack.
+}
+
+\usage{
+stackObjects(x, ref, combine=TRUE, bg.col='black', ext)
+}
+
+\arguments{
+  \item{x}{An \code{Image} object or an array containing object masks.
+    Object masks are sets of pixels with the same unique integer value.}
+    
+  \item{ref}{An \code{Image} object or an array, containing the
+    intensity values of the objects.}
+
+  \item{combine}{If \code{x} contains multiple images, specifies if the resulting
+    list of image stacks with individual objects should be combined
+    using \code{combine} into a single image stack.}
+
+  \item{bg.col}{Background pixel color.}
+
+  \item{ext}{A numeric controlling the size of the output image. 
+   If missing, \code{ext} is estimated from data. See details.}
+}
+
+\value{
+  An \code{Image} object containing the stacked objects contained in
+  \code{x}. If \code{x} contains multiple images and if \code{combine}
+  is \code{TRUE}, \code{stackObjects} returns a list of \code{Image}
+  objects.  
+}
+
+\details{
+  \code{stackObjects} creates a set of \code{n} images of size
+  (\code{2*ext+1}, \code{2*ext+1}), where \code{n} is the number of objects
+  in \code{x}, and places each object of \code{x} in this set.
+  
+  If not specified, \code{ext} is estimated using the 98\% quantile of
+  m.majoraxis/2, where \code{m.majoraxis} is the semi-major axis
+  descriptor extracted from \code{computeFeatures.moment}, taken over
+  all the objects of the image \code{x}.
+}
+
+\seealso{ \code{\link{combine}}, \code{\link{tile}}, \code{\link{computeFeatures.moment}} 
+}
+
+\examples{
+  ## simple example
+  x = readImage(system.file('images', 'shapes.png', package='EBImage'))
+  x = x[110:512,1:130]
+  y = bwlabel(x)
+  display(normalize(y), title='Objects')
+  z = stackObjects(y, normalize(y))
+  display(z, title='Stacked objects')
+
+  ## load images
+  nuc = readImage(system.file('images', 'nuclei.tif', package='EBImage'))
+  cel = readImage(system.file('images', 'cells.tif', package='EBImage'))
+  img = rgbImage(green=cel, blue=nuc)
+  display(img, title='Cells')
+
+  ## segment nuclei
+  nmask = thresh(nuc, 10, 10, 0.05)
+  nmask = opening(nmask, makeBrush(5, shape='disc'))
+  nmask = fillHull(bwlabel(nmask))
+
+  ## segment cells, using propagate and nuclei as 'seeds'
+  ctmask = opening(cel>0.1, makeBrush(5, shape='disc'))
+  cmask = propagate(cel, nmask, ctmask)
+
+  ## using paintObjects to highlight objects
+  res = paintObjects(cmask, img, col='#ff00ff')
+  res = paintObjects(nmask, res, col='#ffff00')
+  display(res, title='Segmented cells')
+
+  ## stacked cells
+  st = stackObjects(cmask, img)
+  display(st, title='Stacked objects')
+}
+
+\author{
+  Oleg Sklyar, \email{osklyar at ebi.ac.uk}, 2006-2007
+}
diff --git a/man/thresh.Rd b/man/thresh.Rd
new file mode 100644
index 0000000..e78450e
--- /dev/null
+++ b/man/thresh.Rd
@@ -0,0 +1,56 @@
+\name{thresh}
+
+\alias{thresh}
+
+\title{Adaptive thresholding}
+
+\description{
+  Thresholds an image using a moving rectangular window.
+}
+
+\usage{
+thresh(x, w=5, h=5, offset=0.01)
+}
+
+\arguments{
+  \item{x}{An \code{Image} object or an array.}
+  
+  \item{w, h}{Half width and height of the moving rectangular window.} 
+  
+  \item{offset}{Thresholding offset from the averaged value.}
+}
+
+\value{
+  An \code{Image} object or an array, containing the transformed version
+  of \code{x}.
+}
+
+\details{
+  This function returns the binary image resulting from the comparison
+  between an image and its filtered version with a rectangular window.
+  It is equivalent of doing 
+  \code{\{
+  f = matrix(1, nc=2*w+1, nr=2*h+1); 
+  f = f/sum(f); 
+  x > (filter2(x, f, boundary="replicate") + offset)
+  \}} 
+  but faster. The function \code{filter2} provides hence more 
+  flexibility than \code{thresh}.
+}
+
+\seealso{
+  \code{filter2}
+}
+
+\author{
+  Oleg Sklyar, \email{osklyar at ebi.ac.uk},  2005-2007
+}
+
+\examples{
+  x = readImage(system.file('images', 'nuclei.tif', package='EBImage'))
+  display(x)
+  y = thresh(x, 10, 10, 0.05)
+  display(y)
+}
+
+\keyword{manip}
diff --git a/man/tile.Rd b/man/tile.Rd
new file mode 100644
index 0000000..f96d425
--- /dev/null
+++ b/man/tile.Rd
@@ -0,0 +1,67 @@
+\name{tile}
+
+\alias{tile}
+\alias{untile}
+
+\title{Tiling/untiling images}
+
+\description{
+  Given a sequence of frames, \code{tile} generates a single image with frames tiled.
+  \code{untile} is the inverse function and divides an image into a sequence of images.
+}
+
+\usage{
+tile(x, nx=10, lwd=1, fg.col="#E4AF2B", bg.col="gray")
+untile(x, nim, lwd=1)
+}
+
+\arguments{
+  \item{x}{An \code{Image} object, an array or a list of these objects.} 
+
+  \item{nx}{ The number of tiled images in a row. }
+
+  \item{lwd}{ The width of the grid lines between tiled images, can be 0. }
+
+  \item{fg.col}{The color of the grid lines.}
+
+  \item{bg.col}{The color of the background for extra tiles.}
+
+  \item{nim}{A numeric vector of 2 elements for the number of images in both directions.}
+}
+
+\value{
+ An \code{Image} object or an array, containing the tiled/untiled version
+ of \code{x}.
+}
+
+\details{
+  After object segmentation, \code{tile} is a useful addition to \code{stackObjects}
+  to have an overview of the segmented objects.
+}
+
+\seealso{ \code{\link{stackObjects}}
+}
+
+\examples{
+  ## make a set of blurred images
+  img = readImage(system.file("images", "sample-color.png", package="EBImage"))[257:768,,]
+  x = resize(img, 128, 128)
+  xt = list()
+  for (t in seq(0.1, 5, len=9)) xt=c(xt, list(gblur(x, s=t)))
+  xt = combine(xt)
+  display(xt, title='Blurred images')
+
+  ## tile
+  xt = tile(xt, 3)
+  display(xt, title='Tiles')
+
+  ## untile
+  xu = untile(img, c(3, 3))
+  display(xu, title='Blocks')
+}
+
+\author{
+  Oleg Sklyar, \email{osklyar at ebi.ac.uk}, 2006-2007
+}
+
+\keyword{manip}
diff --git a/man/transpose.Rd b/man/transpose.Rd
new file mode 100644
index 0000000..95e54ec
--- /dev/null
+++ b/man/transpose.Rd
@@ -0,0 +1,49 @@
+\name{transpose}
+
+\alias{transpose}
+
+\title{Image Transposition}
+
+\description{
+  Transposes an image by swapping its spatial dimensions.
+}
+
+\usage{
+transpose(x)
+}
+
+\arguments{
+  \item{x}{an \code{Image} object or an array.}
+}
+
+\value{
+  A transformed version of \code{x} with its first two dimensions transposed.
+}
+
+\details{
+The transposition of an image is performed by swapping the X and Y indices of its array representation.
+}
+
+\note{
+The function is implemented using an efficient cash-oblivious algorithm which is typically faster than R's \code{aperm} and \code{t} functions.
+}
+
+\author{
+  Andrzej Oles, \email{andrzej.oles at embl.de}, 2012-2017
+}
+
+\seealso{
+  \code{\link{flip}}, \code{\link{flop}}, \code{\link{rotate}}
+}
+
+\examples{
+  x = readImage(system.file("images", "sample-color.png", package="EBImage"))
+  y = transpose(x)
+  
+  display(x, title='Original')
+  display(y, title='Transposed')
+
+  ## performing the transposition of an image twice should result in the original image
+  z = transpose(y)
+  identical(x, z)
+}
diff --git a/man/watershed.Rd b/man/watershed.Rd
new file mode 100644
index 0000000..59b07ed
--- /dev/null
+++ b/man/watershed.Rd
@@ -0,0 +1,68 @@
+\name{watershed}
+
+\alias{watershed}
+
+\concept{watershed transformation}
+\concept{watershed segmentation}
+
+\title{Watershed transformation and watershed based object detection}
+
+\description{
+  Watershed transformation and watershed based object detection.
+}
+
+\usage{
+watershed(x, tolerance=1, ext=1)
+}
+
+\arguments{
+
+  \item{x}{An \code{Image} object or an array.}
+
+  \item{tolerance}{ The minimum height of the object in the units of image
+    intensity between its highest point (seed) and the point where it
+    contacts another object (checked for every contact pixel). If the
+    height is smaller than the tolerance, the object will be combined with
+    one of its neighbors, which is the highest. Tolerance should be chosen
+    according to the range of \code{x}. Default value is 1, which 
+    is a reasonable value if \code{x} comes from \code{distmap}.}
+
+  \item{ext}{Radius of the neighborhood in pixels for the detection
+    of neighboring objects. Higher value smoothes out small objects. }
+}
+
+\value{
+  An \code{Grayscale} \code{Image} object or an array, containing the
+  labelled version of \code{x}.
+}
+
+\details{
+  The algorithm identifies and separates objects that stand out of the
+  background (zero). It inverts the image and uses water to fill
+  the resulting valleys (pixels with high intensity in the source image)
+  until another object or background is met.
+  The deepest valleys become indexed first, starting from 1.
+
+  The function \code{bwlabel} is a simpler, faster alternative to 
+  segment connected objects from binary images.
+}
+
+\seealso{ 
+  \code{\link{bwlabel}},  \code{\link{propagate}} 
+}
+
+\examples{
+  x = readImage(system.file('images', 'shapes.png', package='EBImage'))
+  x = x[110:512,1:130]
+  display(x, title='Binary')
+  y = distmap(x)
+  display(normalize(y), title='Distance map')
+  w = watershed(y)
+  display(normalize(w), title='Watershed')
+}
+
+\author{
+  Oleg Sklyar, \email{osklyar at ebi.ac.uk}, 2007
+}
+
+\keyword{manip}
diff --git a/src/EBImage.c b/src/EBImage.c
new file mode 100644
index 0000000..a978604
--- /dev/null
+++ b/src/EBImage.c
@@ -0,0 +1,72 @@
+/* -------------------------------------------------------------------------
+Package initialization
+Copyright (c) 2006 Oleg Sklyar
+See: ../LICENSE for license, LGPL
+------------------------------------------------------------------------- */
+#include "tools.h"
+#include "distmap.h"
+#include "morphology.h"
+#include "spatial.h"
+#include "propagate.h"
+#include "normalize.h"
+#include "watershed.h"
+#include "thresh.h"
+#include "floodFill.h"
+#include "medianFilter.h"
+#include "haralick.h"
+#include "drawCircle.h"
+#include "objects.h"
+#include "ocontour.h"
+#include "tile.h"
+#include "nativeRaster.h"
+#include "getFrames.h"
+#include "transpose.h"
+#include "clahe.h"
+
+#include <R.h>
+#include <Rdefines.h>
+#include <R_ext/Rdynload.h>
+#include <R_ext/Error.h>
+
+#define CALLDEF(name, n)  {#name, (DL_FUNC) &name, n}
+
+SEXP Image_colormode;
+
+static R_CallMethodDef CallEntries[] = {
+    CALLDEF(transpose, 1),
+    CALLDEF(numberOfFrames, 2),
+    CALLDEF(validImageObject, 1),
+    CALLDEF(morphology, 3),
+    CALLDEF(thresh, 2),
+    CALLDEF(floodFill, 4),
+    CALLDEF(fillHull, 1),
+    CALLDEF(bwlabel, 1),
+    CALLDEF(normalize, 4),
+    CALLDEF(distmap, 2),
+    CALLDEF(watershed, 3),
+    CALLDEF(propagate, 4),
+    CALLDEF(paintObjects, 5),
+    CALLDEF(rmObjects, 3),
+    CALLDEF(tile, 3),
+    CALLDEF(untile, 3),
+    CALLDEF(stackObjects, 5),
+    CALLDEF(ocontour, 1),
+    CALLDEF(haralickMatrix, 3),
+    CALLDEF(haralickFeatures, 1),
+    CALLDEF(drawCircle, 4),
+    CALLDEF(affine, 6),
+    CALLDEF(medianFilter, 3),
+    CALLDEF(nativeRaster, 1),
+    CALLDEF(getFrame, 3),
+    CALLDEF(getFrames, 3),
+    CALLDEF(clahe, 6),
+    /* add above all R-lib functions from common.h */
+    {NULL, NULL, 0}
+};
+
+void R_init_EBImage (DllInfo *dll) {
+    R_registerRoutines (dll, NULL, CallEntries, NULL, NULL);
+    R_useDynamicSymbols (dll, FALSE);
+    
+    Image_colormode = install("colormode");
+}
diff --git a/src/clahe.c b/src/clahe.c
new file mode 100644
index 0000000..11dc682
--- /dev/null
+++ b/src/clahe.c
@@ -0,0 +1,391 @@
+#include "clahe.h"
+#include "tools.h"
+
+/******************** R entry point by Andrzej Oles, 2017 ********************/ 
+
+SEXP clahe (SEXP x, SEXP _uiNrX, SEXP _uiNrY, SEXP _uiNrBins, SEXP _fCliplimit, SEXP _keepRange) {
+  int nx, ny, nz, i, j;
+  unsigned int uiNrX, uiNrY, uiNrBins;
+  float fCliplimit;
+  int keepRange;
+  double *src, *tgt;
+  SEXP res;
+  
+  kz_pixel_t min = 0, max = uiNR_OF_GREY-1;
+  kz_pixel_t *img;
+  
+  double maxPixelValue = uiNR_OF_GREY-1;
+  
+  PROTECT( res = allocVector(REALSXP, XLENGTH(x)) );
+  DUPLICATE_ATTRIB(res, x);
+  
+  nx = INTEGER(GET_DIM(x))[0];
+  ny = INTEGER(GET_DIM(x))[1];
+  nz = getNumberOfFrames(x, 0);
+  
+  uiNrX = INTEGER(_uiNrX)[0];
+  uiNrY = INTEGER(_uiNrY)[0];
+  uiNrBins = INTEGER(_uiNrBins)[0];
+  fCliplimit = REAL(_fCliplimit)[0];
+  keepRange = LOGICAL(_keepRange)[0];
+  
+  img = R_Calloc(nx*ny, kz_pixel_t);
+  
+  // process channels separately
+  for(j = 0; j < nz; j++) {
+    src = &(REAL(x)[j*nx*ny]);
+    tgt = &(REAL(res)[j*nx*ny]);
+    
+    if (keepRange) {
+      min = uiNR_OF_GREY-1;
+      max = 0;
+    }
+    
+    // convert frame to CLAHE-compatible format
+    for (i = 0; i < nx*ny; i++) {
+      double el = src[i];
+      
+      // clip
+      if (el < 0.0) el = 0;
+      else if (el > 1.0) el = 1.0;
+      // convert to int
+      kz_pixel_t nel = (kz_pixel_t) round(el * maxPixelValue);
+      
+      if (keepRange) {
+        if (nel < min) min = nel;
+        if (nel > max) max = nel;
+      }
+      
+      img[i] = nel;
+    }
+    
+    int val = CLAHE (img, (unsigned int) nx, (unsigned int) ny,
+                     min, max, uiNrX, uiNrY, uiNrBins, fCliplimit);
+    
+    // translate internal error codes
+    switch (val) {
+    case -1:
+      error("# of regions x-direction too large");
+      break;
+    case -2:
+      error("# of regions y-direction too large");
+      break;
+    case -3:
+      error("x-resolution no multiple of 'nx'");
+      break;
+    case -4:
+      error("y-resolution no multiple of 'ny'");
+      break;
+    case -5:
+      error("maximum too large");
+      break;
+    case -6:
+      error("minimum equal or larger than maximum");
+      break;
+    case -7:
+      error("at least 4 contextual regions required");
+      break;
+    case -8:
+      error("not enough memory! (try reducing 'bins')");
+      break;
+    }
+    
+    // convert back to [0:1] range
+    for (i = 0; i < nx*ny; i++) {
+      tgt[i] = (double) img[i] / maxPixelValue;
+    }
+  }
+  
+  R_Free(img);
+  
+  UNPROTECT(1);
+  
+  return res;
+}
+
+
+/*
+ * ANSI C code from the article
+ * "Contrast Limited Adaptive Histogram Equalization"
+ * by Karel Zuiderveld, karel at cv.ruu.nl
+ * in "Graphics Gems IV", Academic Press, 1994
+ *
+ *
+ *  These functions implement Contrast Limited Adaptive Histogram Equalization.
+ *  The main routine (CLAHE) expects an input image that is stored contiguously in
+ *  memory;  the CLAHE output image overwrites the original input image and has the
+ *  same minimum and maximum values (which must be provided by the user).
+ *  This implementation assumes that the X- and Y image resolutions are an integer
+ *  multiple of the X- and Y sizes of the contextual regions. A check on various other
+ *  error conditions is performed.
+ *
+ *  #define the symbol BYTE_IMAGE to make this implementation suitable for
+ *  8-bit images. The maximum number of contextual regions can be redefined
+ *  by changing uiMAX_REG_X and/or uiMAX_REG_Y; the use of more than 256
+ *  contextual regions is not recommended.
+ *
+ *  The code is ANSI-C and is also C++ compliant.
+ *
+ *  Author: Karel Zuiderveld, Computer Vision Research Group,
+ *	     Utrecht, The Netherlands (karel at cv.ruu.nl)
+ */
+
+/*********************** Local prototypes ************************/
+static void ClipHistogram (unsigned long*, unsigned int, unsigned long);
+static void MakeHistogram (kz_pixel_t*, unsigned int, unsigned int, unsigned int,
+		unsigned long*, unsigned int, kz_pixel_t*);
+static void MapHistogram (unsigned long*, kz_pixel_t, kz_pixel_t,
+	       unsigned int, unsigned long);
+static void MakeLut (kz_pixel_t*, kz_pixel_t, kz_pixel_t, unsigned int);
+static void Interpolate (kz_pixel_t*, int, unsigned long*, unsigned long*,
+	unsigned long*, unsigned long*, unsigned int, unsigned int, kz_pixel_t*);
+
+/**************	 Start of actual code **************/
+#include <stdlib.h>			 /* To get prototypes of malloc() and free() */
+
+const unsigned int uiMAX_REG_X = 256;	  /* max. # contextual regions in x-direction */
+const unsigned int uiMAX_REG_Y = 256;	  /* max. # contextual regions in y-direction */
+
+
+
+/************************** main function CLAHE ******************/
+int CLAHE (kz_pixel_t* pImage, unsigned int uiXRes, unsigned int uiYRes,
+	 kz_pixel_t Min, kz_pixel_t Max, unsigned int uiNrX, unsigned int uiNrY,
+	      unsigned int uiNrBins, float fCliplimit)
+/*   pImage - Pointer to the input/output image
+ *   uiXRes - Image resolution in the X direction
+ *   uiYRes - Image resolution in the Y direction
+ *   Min - Minimum greyvalue of input image (also becomes minimum of output image)
+ *   Max - Maximum greyvalue of input image (also becomes maximum of output image)
+ *   uiNrX - Number of contextial regions in the X direction (min 2, max uiMAX_REG_X)
+ *   uiNrY - Number of contextial regions in the Y direction (min 2, max uiMAX_REG_Y)
+ *   uiNrBins - Number of greybins for histogram ("dynamic range")
+ *   float fCliplimit - Normalized cliplimit (higher values give more contrast)
+ * The number of "effective" greylevels in the output image is set by uiNrBins; selecting
+ * a small value (eg. 128) speeds up processing and still produce an output image of
+ * good quality. The output image will have the same minimum and maximum value as the input
+ * image. A clip limit smaller than 1 results in standard (non-contrast limited) AHE.
+ */
+{
+    unsigned int uiX, uiY;		  /* counters */
+    unsigned int uiXSize, uiYSize, uiSubX, uiSubY; /* size of context. reg. and subimages */
+    unsigned int uiXL, uiXR, uiYU, uiYB;  /* auxiliary variables interpolation routine */
+    unsigned long ulClipLimit, ulNrPixels;/* clip limit and region pixel count */
+    kz_pixel_t* pImPointer;		   /* pointer to image */
+    kz_pixel_t aLUT[uiNR_OF_GREY];	    /* lookup table used for scaling of input image */
+    unsigned long* pulHist, *pulMapArray; /* pointer to histogram and mappings*/
+    unsigned long* pulLU, *pulLB, *pulRU, *pulRB; /* auxiliary pointers interpolation */
+
+    if (uiNrX > uiMAX_REG_X) return -1;	   /* # of regions x-direction too large */
+    if (uiNrY > uiMAX_REG_Y) return -2;	   /* # of regions y-direction too large */
+    if (uiXRes % uiNrX) return -3;	  /* x-resolution no multiple of uiNrX */
+    if (uiYRes % uiNrY) return -4;	  /* y-resolution no multiple of uiNrY */
+    if (Max >= uiNR_OF_GREY) return -5;	   /* maximum too large */
+    if (Min >= Max) return -6;		  /* minimum equal or larger than maximum */
+    if (uiNrX < 2 || uiNrY < 2) return -7;/* at least 4 contextual regions required */
+    if (fCliplimit == 1.0) return 0;	  /* is OK, immediately returns original image. */
+    if (uiNrBins == 0) uiNrBins = 128;	  /* default value when not specified */
+
+    pulMapArray=(unsigned long *)malloc(sizeof(unsigned long)*uiNrX*uiNrY*uiNrBins);
+    if (pulMapArray == 0) return -8;	  /* Not enough memory! (try reducing uiNrBins) */
+
+    uiXSize = uiXRes/uiNrX; uiYSize = uiYRes/uiNrY;  /* Actual size of contextual regions */
+    ulNrPixels = (unsigned long)uiXSize * (unsigned long)uiYSize;
+
+    if(fCliplimit > 0.0) {		  /* Calculate actual cliplimit	 */
+       ulClipLimit = (unsigned long) (fCliplimit * (uiXSize * uiYSize) / uiNrBins);
+       ulClipLimit = (ulClipLimit < 1UL) ? 1UL : ulClipLimit;
+    }
+    else ulClipLimit = 1UL<<14;		  /* Large value, do not clip (AHE) */
+    MakeLut(aLUT, Min, Max, uiNrBins);	  /* Make lookup table for mapping of greyvalues */
+    /* Calculate greylevel mappings for each contextual region */
+    for (uiY = 0, pImPointer = pImage; uiY < uiNrY; uiY++) {
+	for (uiX = 0; uiX < uiNrX; uiX++, pImPointer += uiXSize) {
+	    pulHist = &pulMapArray[uiNrBins * (uiY * uiNrX + uiX)];
+	    MakeHistogram(pImPointer,uiXRes,uiXSize,uiYSize,pulHist,uiNrBins,aLUT);
+	    ClipHistogram(pulHist, uiNrBins, ulClipLimit);
+	    MapHistogram(pulHist, Min, Max, uiNrBins, ulNrPixels);
+	}
+	pImPointer += (uiYSize - 1) * uiXRes;		  /* skip lines, set pointer */
+    }
+
+    /* Interpolate greylevel mappings to get CLAHE image */
+    for (pImPointer = pImage, uiY = 0; uiY <= uiNrY; uiY++) {
+	if (uiY == 0) {					  /* special case: top row */
+	    uiSubY = uiYSize >> 1;  uiYU = 0; uiYB = 0;
+	}
+	else {
+	    if (uiY == uiNrY) {				  /* special case: bottom row */
+		uiSubY = uiYSize+1 >> 1;	uiYU = uiNrY-1;	 uiYB = uiYU;
+	    }
+	    else {					  /* default values */
+		uiSubY = uiYSize; uiYU = uiY - 1; uiYB = uiYU + 1;
+	    }
+	}
+	for (uiX = 0; uiX <= uiNrX; uiX++) {
+	    if (uiX == 0) {				  /* special case: left column */
+		uiSubX = uiXSize >> 1; uiXL = 0; uiXR = 0;
+	    }
+	    else {
+		if (uiX == uiNrX) {			  /* special case: right column */
+		    uiSubX = uiXSize+1 >> 1;  uiXL = uiNrX - 1; uiXR = uiXL;
+		}
+		else {					  /* default values */
+		    uiSubX = uiXSize; uiXL = uiX - 1; uiXR = uiXL + 1;
+		}
+	    }
+
+	    pulLU = &pulMapArray[uiNrBins * (uiYU * uiNrX + uiXL)];
+	    pulRU = &pulMapArray[uiNrBins * (uiYU * uiNrX + uiXR)];
+	    pulLB = &pulMapArray[uiNrBins * (uiYB * uiNrX + uiXL)];
+	    pulRB = &pulMapArray[uiNrBins * (uiYB * uiNrX + uiXR)];
+	    Interpolate(pImPointer,uiXRes,pulLU,pulRU,pulLB,pulRB,uiSubX,uiSubY,aLUT);
+	    pImPointer += uiSubX;			  /* set pointer on next matrix */
+	}
+	pImPointer += (uiSubY - 1) * uiXRes;
+    }
+    free(pulMapArray);					  /* free space for histograms */
+    return 0;						  /* return status OK */
+}
+void ClipHistogram (unsigned long* pulHistogram, unsigned int
+		     uiNrGreylevels, unsigned long ulClipLimit)
+/* This function performs clipping of the histogram and redistribution of bins.
+ * The histogram is clipped and the number of excess pixels is counted. Afterwards
+ * the excess pixels are equally redistributed across the whole histogram (providing
+ * the bin count is smaller than the cliplimit).
+ */
+{
+    unsigned long* pulBinPointer, *pulEndPointer, *pulHisto;
+    unsigned long ulNrExcess, ulUpper, ulBinIncr, ulStepSize, i;
+    long lBinExcess;
+
+    ulNrExcess = 0;  pulBinPointer = pulHistogram;
+    for (i = 0; i < uiNrGreylevels; i++) { /* calculate total number of excess pixels */
+	lBinExcess = (long) pulBinPointer[i] - (long) ulClipLimit;
+	if (lBinExcess > 0) ulNrExcess += lBinExcess;	  /* excess in current bin */
+    };
+
+    /* Second part: clip histogram and redistribute excess pixels in each bin */
+    ulBinIncr = ulNrExcess / uiNrGreylevels;		  /* average binincrement */
+    ulUpper =  ulClipLimit - ulBinIncr;	 /* Bins larger than ulUpper set to cliplimit */
+
+    for (i = 0; i < uiNrGreylevels; i++) {
+      if (pulHistogram[i] > ulClipLimit) pulHistogram[i] = ulClipLimit; /* clip bin */
+      else {
+	  if (pulHistogram[i] > ulUpper) {		/* high bin count */
+	      ulNrExcess -= pulHistogram[i] - ulUpper; pulHistogram[i]=ulClipLimit;
+	  }
+	  else {					/* low bin count */
+	      ulNrExcess -= ulBinIncr; pulHistogram[i] += ulBinIncr;
+	  }
+       }
+    }
+
+    while (ulNrExcess) {   /* Redistribute remaining excess  */
+	pulEndPointer = &pulHistogram[uiNrGreylevels]; pulHisto = pulHistogram;
+
+	while (ulNrExcess && pulHisto < pulEndPointer) {
+	    ulStepSize = uiNrGreylevels / ulNrExcess;
+	    if (ulStepSize < 1) ulStepSize = 1;		  /* stepsize at least 1 */
+	    for (pulBinPointer=pulHisto; pulBinPointer < pulEndPointer && ulNrExcess;
+		 pulBinPointer += ulStepSize) {
+		if (*pulBinPointer < ulClipLimit) {
+		    (*pulBinPointer)++;	 ulNrExcess--;	  /* reduce excess */
+		}
+	    }
+	    pulHisto++;		  /* restart redistributing on other bin location */
+	}
+    }
+}
+void MakeHistogram (kz_pixel_t* pImage, unsigned int uiXRes,
+		unsigned int uiSizeX, unsigned int uiSizeY,
+		unsigned long* pulHistogram,
+		unsigned int uiNrGreylevels, kz_pixel_t* pLookupTable)
+/* This function classifies the greylevels present in the array image into
+ * a greylevel histogram. The pLookupTable specifies the relationship
+ * between the greyvalue of the pixel (typically between 0 and 4095) and
+ * the corresponding bin in the histogram (usually containing only 128 bins).
+ */
+{
+    kz_pixel_t* pImagePointer;
+    unsigned int i;
+
+    for (i = 0; i < uiNrGreylevels; i++) pulHistogram[i] = 0L; /* clear histogram */
+
+    for (i = 0; i < uiSizeY; i++) {
+		pImagePointer = &pImage[uiSizeX];
+		while (pImage < pImagePointer) pulHistogram[pLookupTable[*pImage++]]++;
+		pImagePointer += uiXRes;
+		pImage = &pImagePointer[-(int)uiSizeX];	/* go to bdeginning of next row */
+    }
+}
+
+void MapHistogram (unsigned long* pulHistogram, kz_pixel_t Min, kz_pixel_t Max,
+	       unsigned int uiNrGreylevels, unsigned long ulNrOfPixels)
+/* This function calculates the equalized lookup table (mapping) by
+ * cumulating the input histogram. Note: lookup table is rescaled in range [Min..Max].
+ */
+{
+    unsigned int i;  unsigned long ulSum = 0;
+    const float fScale = ((float)(Max - Min)) / ulNrOfPixels;
+    const unsigned long ulMin = (unsigned long) Min;
+
+    for (i = 0; i < uiNrGreylevels; i++) {
+		ulSum += pulHistogram[i]; pulHistogram[i]=(unsigned long)(ulMin+ulSum*fScale);
+		if (pulHistogram[i] > Max) pulHistogram[i] = Max;
+    }
+}
+
+void MakeLut (kz_pixel_t * pLUT, kz_pixel_t Min, kz_pixel_t Max, unsigned int uiNrBins)
+/* To speed up histogram clipping, the input image [Min,Max] is scaled down to
+ * [0,uiNrBins-1]. This function calculates the LUT.
+ */
+{
+    int i;
+    const kz_pixel_t BinSize = (kz_pixel_t) (1 + (Max - Min) / uiNrBins);
+
+    for (i = Min; i <= Max; i++)  pLUT[i] = (i - Min) / BinSize;
+}
+
+void Interpolate (kz_pixel_t * pImage, int uiXRes, unsigned long * pulMapLU,
+     unsigned long * pulMapRU, unsigned long * pulMapLB,  unsigned long * pulMapRB,
+     unsigned int uiXSize, unsigned int uiYSize, kz_pixel_t * pLUT)
+/* pImage      - pointer to input/output image
+ * uiXRes      - resolution of image in x-direction
+ * pulMap*     - mappings of greylevels from histograms
+ * uiXSize     - uiXSize of image submatrix
+ * uiYSize     - uiYSize of image submatrix
+ * pLUT	       - lookup table containing mapping greyvalues to bins
+ * This function calculates the new greylevel assignments of pixels within a submatrix
+ * of the image with size uiXSize and uiYSize. This is done by a bilinear interpolation
+ * between four different mappings in order to eliminate boundary artifacts.
+ * It uses a division; since division is often an expensive operation, I added code to
+ * perform a logical shift instead when feasible.
+ *
+ * modification by Andrzej Oleś
+ * Use double arithmetic and stratify interpolation between odd and even region
+ * sizes in order to make the filter rotationally invariant. Use proper rounding to 
+ * avoid systematic shift by truncation towards 0.
+ */
+{
+    const unsigned int uiIncr = uiXRes-uiXSize;
+    kz_pixel_t GreyValue; 
+    
+    double dNum = uiXSize*uiYSize;
+    double dXCoef, dYCoef, dXInvCoef, dYInvCoef;
+    double dXCoef0 = (uiXSize % 2) ? 0.0 : 0.5;
+    double dYCoef0 = (uiYSize % 2) ? 0.0 : 0.5;
+    
+    for (dYCoef = dYCoef0, dYInvCoef = uiYSize - dYCoef;
+         dYCoef < uiYSize;
+         dYCoef++, dYInvCoef--,pImage+=uiIncr) {
+        for (dXCoef = dXCoef0, dXInvCoef = uiXSize - dXCoef;
+             dXCoef < uiXSize;
+             dXCoef++, dXInvCoef--) {
+            GreyValue = pLUT[*pImage];
+            *pImage++ = (kz_pixel_t ) round((dYInvCoef * (dXInvCoef*pulMapLU[GreyValue] + dXCoef * pulMapRU[GreyValue])
+                                                 + dYCoef * (dXInvCoef * pulMapLB[GreyValue] + dXCoef * pulMapRB[GreyValue])) / dNum);
+        }
+    }
+    
+}
diff --git a/src/clahe.h b/src/clahe.h
new file mode 100644
index 0000000..e2b4f60
--- /dev/null
+++ b/src/clahe.h
@@ -0,0 +1,30 @@
+#ifndef EBIMAGE_CLAHE_H
+#define EBIMAGE_CLAHE_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SEXP clahe (SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);
+
+#ifdef BYTE_IMAGE
+typedef unsigned char kz_pixel_t;	 /* for 8 bit-per-pixel images */
+#define uiNR_OF_GREY (256)
+#else
+typedef unsigned short kz_pixel_t;	 /* for 16 bit-per-pixel images (default) */
+# define uiNR_OF_GREY (65536)
+#endif
+
+/******** Prototype of CLAHE function. Put this in a separate include file. *****/
+int CLAHE(kz_pixel_t* pImage, unsigned int uiXRes, unsigned int uiYRes, kz_pixel_t Min,
+	  kz_pixel_t Max, unsigned int uiNrX, unsigned int uiNrY,
+	  unsigned int uiNrBins, float fCliplimit);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/src/distmap.cpp b/src/distmap.cpp
new file mode 100644
index 0000000..e36512b
--- /dev/null
+++ b/src/distmap.cpp
@@ -0,0 +1,146 @@
+// Fast exact L2/L1 distance map algorithm 
+// Complexity: O(N^2*log(N)), where N is the edge of a square image
+// Reference: Kolountzakis, Kutulakos: Fast Computation of the Euclidean Distance Map for Binary Images, Infor. Proc. Letters (1992)
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <limits.h>
+
+#include <R.h>
+#include <Rdefines.h>
+
+#include "distmap.h"
+#include "tools.h"
+
+// Globals
+int width,height,metric,*vj;
+numeric *d;
+
+template <typename type> void distmap_onesided(type *, int);
+template <typename type> void _distmap(type *);
+
+// Find all minimal distances of points (*,j) given their closest neighbours given in vj
+// Complexity: O(N*log(N))
+void find_ndist(int x1, int x2, int z1 ,int z2, int j) {
+  static int k;
+  static double dk,dmin;
+  int x0,z0;
+  
+  x0=(x1+x2)/2;
+  
+  // find (z0,vj[z0]), coordinates of the nearest neighbour of (x0,j), in the range z1<=k<=z2 from the pixels (k,vj[k])
+  dmin=R_PosInf;
+  z0=0;
+  for (k=z1;k<z2+1;k++) {
+    if (vj[k]==INT_MAX) dk=R_PosInf;
+    else {
+      if (metric) dk=fabs(x0-k+0.0)+fabs(j-vj[k]+0.0);
+      else dk=(x0-k+0.0)*(x0-k+0.0)+(j-vj[k]+0.0)*(j-vj[k]+0.0);
+    }
+    if (dk<dmin) {
+      z0=k;
+      dmin=dk;
+    }
+  }
+  if (dmin==R_PosInf) z0=(z1+z2)/2;
+  if (dmin<d[j+x0*width]) d[j+x0*width]=dmin;
+  
+  // child calls, with constrained ranges
+  if (x1<=(x0-1)) find_ndist(x1,x0-1,z1,z0,j);
+  if ((x0+1)<=x2) find_ndist(x0+1,x2,z0,z2,j);
+}
+
+// Compute minimal distances in one direction
+template <typename type> void distmap_onesided(type *src, int right2left) {
+  int i,j,k;
+  
+  // initialize vj
+  for (i=0;i<height;i++) vj[i]=-1;
+  
+  for (j=0;j<width;j++) {
+    // compute vj, knowing v(j-1)
+    for (i=0;i<height;i++) {
+      if (vj[i]<j) {
+        k=j;
+        if (right2left)	while (k<width) if (src[k+i*width]!=0) k++; else break;
+        else while (k<width) if (src[width-1-k+i*width]!=0) k++; else break;
+        if (k==width) vj[i]=INT_MAX;
+        else vj[i]=k;
+      }
+    }
+    
+    if (right2left) find_ndist(0,height-1,0,height-1,j);
+    else {
+      for (i=0;i<height;i++) if (vj[i]!=INT_MAX) vj[i]=width-1-vj[i];
+      find_ndist(0,height-1,0,height-1,width-1-j);
+      for (i=0;i<height;i++) if (vj[i]!=INT_MAX) vj[i]=width-1-vj[i];
+    }
+    
+    // check for user interruption
+    R_CheckUserInterrupt();
+  }
+}
+
+template <typename type> void _distmap(type *src) {
+  distmap_onesided<type>(src, 1);
+  distmap_onesided<type>(src, 0);
+}
+
+// Compute Euclidean (L2)/Manhattan (L1) distance map of matrix _a 
+// Input: numeric matrix _a, of size width*height, where 0 is background and everything else is foreground. _a shouldn't contain any NAs
+// Input: integer _metric. If 0, will compute Euclidean distance and Manhattan distance otherwise
+// Output: distance matrix of same size as _a
+SEXP distmap(SEXP _a, SEXP _metric) {
+  SEXP res;
+  int i,nprotect=0,nz;
+  
+  // check validity
+  validImage(_a,0);
+  
+  // initialize width, height, dim
+  width=INTEGER(GET_DIM(_a))[0];
+  height=INTEGER(GET_DIM(_a))[1];
+  nz=getNumberOfFrames(_a,0);
+  
+  // initialize vj, where (i,vj[i]) are the coordinates of the closest background pixel to a(i,j) with vj[i]>=j
+  vj=(int *)R_Calloc(height,int);
+  
+  // initialize d, the output distance matrix
+  PROTECT(res = allocVector(REALSXP, XLENGTH(_a)) );
+  nprotect++;
+  DUPLICATE_ATTRIB(res, _a);
+  
+  d=REAL(res);
+  for (i=0;i<height*width*nz;i++) d[i]=R_PosInf;
+  
+  // initialize dist, the distance type
+  metric=INTEGER(_metric)[0];
+  
+  // do the job
+  int sizexy = height*width;
+  int offset = 0;
+  
+  for (i=0; i<nz; i++, offset+=sizexy) {
+    d = &(REAL(res)[offset]);
+    
+    switch (TYPEOF(_a)) {
+    case LGLSXP:
+    case INTSXP:
+      _distmap<int>( &(INTEGER(_a)[offset]) );
+      break;
+    case REALSXP:
+      _distmap<double>( &(REAL(_a)[offset]) );
+      break;
+    }
+  }
+  
+  // final square root for Euclidean distance
+  d=REAL(res);
+  if (metric==0) for (i=0;i<height*width*nz;i++) d[i]=sqrt(d[i]);
+  
+  R_Free(vj);
+  
+  UNPROTECT (nprotect);
+  return res;
+}
diff --git a/src/distmap.h b/src/distmap.h
new file mode 100644
index 0000000..c7983cb
--- /dev/null
+++ b/src/distmap.h
@@ -0,0 +1,17 @@
+#ifndef EBIMAGE_DISTMAP_H
+#define EBIMAGE_DISTMAP_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SEXP distmap (SEXP, SEXP);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/src/drawCircle.c b/src/drawCircle.c
new file mode 100644
index 0000000..1bf68a4
--- /dev/null
+++ b/src/drawCircle.c
@@ -0,0 +1,87 @@
+#include "drawCircle.h"
+#include "tools.h"
+#include <R_ext/Error.h>
+
+#define SET_PIXEL(a, width, height, x, y, color) if (((x)>=0) & ((x)<(width)) & ((y)>=0) & ((y)<(height))) a[(x) + (y)*(width)] = (color);
+void rasterCircle(double *a, int width, int height, int x0, int y0, int radius, double color, int fill) {
+  int f = 1 - radius;
+  int ddF_x = 1;
+  int ddF_y = -2 * radius;
+  int x = 0;
+  int y = radius;
+  int i;
+  
+  if (fill) {
+    for (i=x0-radius; i<=x0+radius ;i++) SET_PIXEL(a, width, height, i, y0, color);
+    for (i=y0-radius; i<=y0+radius ;i++) SET_PIXEL(a, width, height, x0, i, color);
+  } else {
+    SET_PIXEL(a, width, height, x0, y0 + radius, color);
+    SET_PIXEL(a, width, height, x0, y0 - radius, color);
+    SET_PIXEL(a, width, height, x0 + radius, y0, color);
+    SET_PIXEL(a, width, height, x0 - radius, y0, color);
+  }
+  
+  while(x < y) {
+    if(f >= 0) {
+      y--;
+      ddF_y += 2;
+      f += ddF_y;
+    }
+    x++;
+    ddF_x += 2;
+    f += ddF_x;
+    if (fill) {
+      for (i=x0-x; i<=x0+x ;i++) SET_PIXEL(a, width, height, i, y0+y, color);
+      for (i=x0-x; i<=x0+x ;i++) SET_PIXEL(a, width, height, i, y0-y, color);
+      for (i=x0-y; i<=x0+y ;i++) SET_PIXEL(a, width, height, i, y0+x, color);
+      for (i=x0-y; i<=x0+y ;i++) SET_PIXEL(a, width, height, i, y0-x, color);
+    }
+    else {
+      SET_PIXEL(a, width, height, x0 + x, y0 + y, color);
+      SET_PIXEL(a, width, height, x0 - x, y0 + y, color);
+      SET_PIXEL(a, width, height, x0 + x, y0 - y, color);
+      SET_PIXEL(a, width, height, x0 - x, y0 - y, color);
+      SET_PIXEL(a, width, height, x0 + y, y0 + x, color);
+      SET_PIXEL(a, width, height, x0 - y, y0 + x, color);
+      SET_PIXEL(a, width, height, x0 + y, y0 - x, color);
+      SET_PIXEL(a, width, height, x0 - y, y0 - x, color);
+    }
+  }
+}
+
+// draw a circle on the 2D image _a using (x, y, z, radius) and color (red, green, blue)
+// if colormode = Grayscale, only the first channel is used
+SEXP drawCircle(SEXP _a, SEXP _xyzr, SEXP _rgb, SEXP _fill) {
+  SEXP _res;
+  int nprotect = 0;
+  int width, height;
+  int x, y, z, radius;
+  ColorStrides strides;
+  double *res;
+  int fill;
+
+  // check image validity and copy _a
+  validImage(_a, 0);
+  PROTECT(_res=Rf_duplicate(_a));
+  nprotect++;
+
+  width = INTEGER(GET_DIM(_res))[0];
+  height = INTEGER(GET_DIM(_res))[1];
+
+  // get strides
+  x = INTEGER(_xyzr)[0];
+  y = INTEGER(_xyzr)[1];
+  z = INTEGER(_xyzr)[2];
+  radius = INTEGER(_xyzr)[3];
+  fill = INTEGER(_fill)[0];
+  getColorStrides(_res, z, &strides);
+  res = REAL(_res);
+
+  // draw circle
+  if (strides.r!=-1) rasterCircle(&res[strides.r], width, height, x, y, radius, REAL(_rgb)[0], fill);
+  if (strides.g!=-1) rasterCircle(&res[strides.g], width, height, x, y, radius, REAL(_rgb)[1], fill);
+  if (strides.b!=-1) rasterCircle(&res[strides.b], width, height, x, y, radius, REAL(_rgb)[2], fill);
+  
+  UNPROTECT (nprotect);
+  return _res;
+}
diff --git a/src/drawCircle.h b/src/drawCircle.h
new file mode 100644
index 0000000..f886534
--- /dev/null
+++ b/src/drawCircle.h
@@ -0,0 +1,18 @@
+#ifndef EBIMAGE_DRAWCIRCLE_H
+#define EBIMAGE_DRAWCIRCLE_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+  
+  SEXP drawCircle (SEXP, SEXP, SEXP, SEXP);
+  
+#ifdef __cplusplus
+};
+#endif
+
+#endif
+
diff --git a/src/floodFill.cpp b/src/floodFill.cpp
new file mode 100644
index 0000000..af90913
--- /dev/null
+++ b/src/floodFill.cpp
@@ -0,0 +1,396 @@
+#include "floodFill.h"
+#include "tools.h"
+
+/* -------------------------------------------------------------------------
+Flood fill for images and flood-fill-based hull filling for objects
+Copyright (c) 2007 Gregoire Pau; templated code by Oleg Sklyar
+
+See: ../LICENSE for license, LGPL
+------------------------------------------------------------------------- */
+
+#include <R_ext/Error.h>
+#include <stack>
+
+/* -------------------------------------------------------------------------- */
+struct XYPoint {
+  XYPoint() {}
+  XYPoint(int xx, int yy): x(xx), y(yy) {}
+  int x, y;
+};
+
+template <class T> void _floodFill(T*, XYPoint, XYPoint, T, double tol = 1e-3);
+template <class T> void _fillHullT(T *, const XYPoint &);
+template <class T> void _bwlabel(T *, int *, XYPoint);
+
+/* -------------------------------------------------------------------------- */
+SEXP
+floodFill(SEXP x, SEXP _pts, SEXP _col, SEXP _tol) {
+  int i, p, c, nf, np, nc, *dim, *pts;
+  double tol = REAL(_tol)[0];
+  XYPoint pt;
+  SEXP res, points, colors;
+  // check image validity
+  validImage(x, 0);
+  nf = getNumberOfFrames(x, 1);
+  nc = getNumberOfChannels(x, COLOR_MODE(x));
+  dim = INTEGER(GET_DIM(x));
+  XYPoint size(dim[0], dim[1]);
+  if (size.x <= 0 || size.y <= 0) error("image must have positive dimensions");
+  if (LENGTH(_pts) != nf) error("length of points list must match the number of 'render' frames");
+  if (LENGTH(_col) != nf) error("length of color list must match the number of 'render' frames");
+  
+  // initialize result
+  PROTECT(res = Rf_duplicate(x));
+  
+  // iterate over images
+  for (i=0; i<nf; i++) {
+    points = VECTOR_ELT(_pts, i);
+    colors = VECTOR_ELT(_col, i);
+    np = INTEGER(GET_DIM(points))[0];
+    pts = INTEGER(points);
+    // iterate over points
+    for (p=0; p<np; p++) {
+      pt.x = pts[p]-1;
+      pt.y = pts[np+p]-1;
+      // iterate over channels
+      for (c=0; c<nc; c++) {
+        switch (TYPEOF(res)) {
+        case LGLSXP:
+        case INTSXP:
+          _floodFill<int>(&(INTEGER(res)[(i*nc+c)*size.x*size.y]), size, pt, INTEGER(colors)[c*np+p], tol);
+          break;
+        case REALSXP:
+          _floodFill<double>(&(REAL(res)[(i*nc+c)*size.x*size.y]), size, pt, REAL(colors)[c*np+p], tol);
+          break;
+        }
+      }
+    }
+  }
+  
+  UNPROTECT(1);
+  return res;
+}
+
+/* -------------------------------------------------------------------------- */
+SEXP
+fillHull(SEXP x) {
+  SEXP res;
+  int nz;
+
+  // check image validity
+  validImage(x,0);
+  nz = getNumberOfFrames(x, 0);
+
+  int *dim=INTEGER(GET_DIM(x));
+  XYPoint size(dim[0], dim[1]);
+
+  // return itself if nothing to do
+  if (size.x <= 0 || size.y <= 0 || nz < 1) return x;
+ 
+  PROTECT(res = Rf_duplicate(x));
+  
+  // apply fillHull over frames
+  for (int i=0; i<nz; i++) {
+    switch (TYPEOF(res)) {
+    case LGLSXP:
+    case INTSXP:
+      _fillHullT<int>(&(INTEGER(res)[i*size.x*size.y]), size);
+      break;
+    case REALSXP:
+      _fillHullT<double>(&(REAL(res)[i*size.x*size.y]), size);
+      break;
+    }
+  }
+  
+  UNPROTECT(1);
+  return res;
+}
+
+/* -------------------------------------------------------------------------- */
+SEXP
+bwlabel(SEXP x) {
+  int i, nz, sizexy, offset, *dim, *tgt;
+  SEXP res;
+
+  // check image validity
+  validImage(x,0);
+  nz = getNumberOfFrames(x, 0);
+  dim = INTEGER(GET_DIM(x));
+  XYPoint size(dim[0], dim[1]);
+  if (size.x <= 0 || size.y <= 0) error("image must have positive dimensions");
+  
+  // store results as integers
+  PROTECT( res = allocVector(INTSXP, XLENGTH(x)) );
+  DUPLICATE_ATTRIB(res, x);
+  
+  sizexy = size.x*size.y;
+  offset = 0;
+  
+  for (i=0; i<nz; i++, offset+=sizexy) {
+    tgt = &(INTEGER(res)[offset]);
+  
+    switch (TYPEOF(x)) {
+      case LGLSXP:
+      case INTSXP:
+        _bwlabel<int>( &(INTEGER(x)[offset]), tgt, size);
+        break;
+      case REALSXP:
+        _bwlabel<double>( &(REAL(x)[offset]), tgt, size);
+        break;
+    }
+  }
+
+  UNPROTECT(1);
+  return res;
+}
+
+template <class T> void _bwlabel(T *src, int *res, XYPoint size) {
+  XYPoint pt;
+  int pos = 0;
+  int idx = 1;
+  
+  // assuming binary images:
+  // 0 is background and everything else (background) is converted here to -1
+  for (int i=0; i<size.x*size.y; i++) {
+    res[i] = (src[i]==0.0) ? 0 : -1;
+  }
+  
+  // do the job over images
+  // every pixel with -1 is filled with an increasing index, starting from 1
+  for (int ky=0; ky<size.y ; ky++) {
+    for (int kx=0; kx<size.x ; kx++, pos++) {
+      if (res[pos]==-1) {
+        pt.x = kx;
+        pt.y = ky;
+        _floodFill<int>(res, size, pt, idx, 0);
+        idx++;
+      }
+    }
+  }
+}
+
+/* -------------------------------------------------------------------------- */
+/* stack that checks the size and returns a value on pop in one line */
+template <class T> class PopCheckStack {
+  public:
+    void push(T t) {
+      vstack.push(t);
+    }
+    bool pop(T &t) {
+      if (vstack.empty()) return false;
+      t = vstack.top();
+      vstack.pop();
+      return true;
+    }
+  protected:
+    std::stack<T> vstack;
+};
+
+/* stack of x-y points */
+typedef PopCheckStack<XYPoint> XYStack;
+
+/* -------------------------------------------------------------------------- */
+/** 
+  Floodfill 
+   
+  Fill the region of matrix m (of size [width,height]) with color rc, starting 
+  with seed starting pixel at position x,y. Fast stacked scanline algorithm.
+
+  gregoire.pau at ebi.ac.uk ; 10/2007
+   
+  Templated version by Oleg Sklyar
+*/
+
+template <class T>void 
+_floodFill(T *m, XYPoint size, XYPoint xy, T rc, double tol) {
+  XYStack s, offsets;
+  XYPoint pt = xy;
+  bool spanLeft,spanRight,offset=false;
+  /* set the target color tc */
+  T tc = m[pt.x+pt.y*size.x];
+
+  /* FIXME: the offset workaround with another stack is ONLY used when
+   * the reset color (rc) is the same as target color (tc). In this case
+   * we reset to an offset color from rc first, keep coordinates of all
+   * reset points and reset them to what we need at the end of the loop.
+   * This does not affect the speed when the color is different as the 
+   * stack is not used then.
+   */
+  T resetc = rc;
+  if (fabs(tc-rc) <= tol) {
+    offset=true;
+    resetc = (T)(rc+tol+1);
+  }
+    
+  // pushes the seed starting pixel
+  s.push(pt);
+    
+  while(s.pop(pt)) {    
+    // climbs up along the column x as far as possible
+    while(pt.y>=0 && fabs(m[pt.x+pt.y*size.x]-tc) <= tol) pt.y--;
+    pt.y++;
+    spanLeft=false;
+    spanRight=false;
+    /* to enable users to terminate this function */
+    R_CheckUserInterrupt();
+
+    // processes the column x
+    while(pt.y<size.y && fabs(m[pt.x+pt.y*size.x]-tc) <= tol) {
+      m[pt.x+pt.y*size.x]=resetc;
+      if (offset) offsets.push(pt);
+      if(!spanLeft && pt.x>0 && fabs(m[pt.x-1+pt.y*size.x]-tc) <= tol) {
+    	  s.push(XYPoint(pt.x-1,pt.y));
+    	  spanLeft=true;
+    	}
+      else if(spanLeft && pt.x>0 && fabs(m[pt.x-1+pt.y*size.x]-tc) > tol) spanLeft=false;
+      if(!spanRight && pt.x<size.x-1 && fabs(m[pt.x+1+pt.y*size.x]-tc) <= tol) {
+    	  s.push(XYPoint(pt.x+1,pt.y));
+    	  spanRight=true;
+    	}
+      else if(spanRight && pt.x<size.x-1 && fabs(m[pt.x+1+pt.y*size.x]-tc) > tol) spanRight=false;
+      pt.y++;
+    }
+  }
+  while(offsets.pop(pt)) m[pt.x+pt.y*size.x]=rc;
+}
+
+/* -------------------------------------------------------------------------- */
+struct Box {
+  Box(): t(0), l(0), r(0), b(0) {}
+  Box(int tt, int ll, int rr, int bb): t(tt), l(ll), r(rr), b(bb) {}
+  int t, l, r, b;
+  void expand(int px=1) {
+    t -= px;
+    l -= px;
+    r += px;
+    b += px;
+  }
+};
+
+/* -------------------------------------------------------------------------- */
+/** 
+  Extended floodfill version: to be used in fillHull only
+
+  Fills image canvas (of width size.x * size.y):
+  - under box 
+  - with color rc
+  - with points of m (of width size.x * size.y) which are NOT of color ntc
+  - with a starting at top-left corner
+  
+*/
+/* -------------------------------------------------------------------------- */
+/* Templated version by Oleg Sklyar */
+template <class T> void 
+_fillAroundObjectHullT(T **m, T **canvas, const Box &box, int &rc) {
+  XYStack s;
+  XYPoint pt;
+  bool spanLeft,spanRight;
+
+  pt.x = box.l;
+  pt.y = box.t;
+    
+  // pushes the starting pixel
+  s.push(pt);
+    
+  while(s.pop(pt)) {    
+    // climbs up along the column x as far as possible
+    while(pt.y>=box.t && (int)m[pt.x][pt.y]!=rc && (int)canvas[pt.x][pt.y]!=rc) pt.y--;
+    pt.y++;
+    spanLeft=false;
+    spanRight=false;
+    // processes the column x
+    while(pt.y<=box.b && (int)m[pt.x][pt.y]!=rc) {
+      R_CheckUserInterrupt();
+      canvas[pt.x][pt.y]=rc;
+      if(!spanLeft && pt.x>box.l && (int)m[pt.x-1][pt.y]!=rc && 
+                                    (int)canvas[pt.x-1][pt.y]!=rc) {
+    	  s.push(XYPoint(pt.x-1,pt.y));
+    	  spanLeft=true;
+    	} else 
+      if(spanLeft && pt.x>box.l && ((int)m[pt.x-1][pt.y]==rc || (int)canvas[pt.x-1][pt.y]==rc)) spanLeft=false;
+      if(!spanRight && pt.x<box.r && (int)m[pt.x+1][pt.y]!=rc && 
+                                     (int)canvas[pt.x+1][pt.y]!=rc) {
+    	  s.push(XYPoint(pt.x+1,pt.y));
+    	  spanRight=true;
+    	} else 
+    	if(spanRight && pt.x<box.r && ((int)m[pt.x+1][pt.y]==rc || (int)canvas[pt.x+1][pt.y]==rc)) spanRight=false;
+      pt.y++;
+    }
+  }
+}
+
+/* -------------------------------------------------------------------------- */
+/* Templated version by Oleg Sklyar: T assumed to be int or double (at least)
+   dereferancible to those! */
+
+template <class T> void
+_fillHullT(T *_m, const XYPoint &srcsize) {
+  int nobj = 0, i, x, y;
+  XYPoint size = srcsize;
+
+  // computes maximum number of different objects
+  for (i=0; i < srcsize.x*srcsize.y; i++)
+    if ((int)_m[i] > nobj) nobj = (int)_m[i];
+
+  // nothing to do if no objects
+  if (nobj < 1) return;
+
+  // extend m by 2 pixels, copy content of _m inside, the frame - 0;
+  // initialize temporary canvas with 0 
+  size.x += 2;
+  size.y += 2;
+
+  typedef T* pT;
+  T ** m = new pT[size.x];
+  T ** canvas = new pT[size.x];
+  for (x=0; x < size.x; x++) {
+    m[x] = new T[size.y];
+    canvas[x] = new T[size.y];
+    for (y=0; y < size.y; y++) {
+      canvas[x][y] = (T)0;
+      if (x==0 || x==size.x-1 || y==0 || y==size.y-1) m[x][y] = (T)0;
+      else m[x][y] = _m[x-1 + (y-1)*srcsize.x];
+    }
+  }
+
+  // allocate and compute bounding boxes for all objects (the one for 0 never used)
+  Box * bbox = new Box[nobj+1];
+
+  for (i=1; i <= nobj; i++) {
+    bbox[i].l = size.x-2;
+    bbox[i].t = size.y-2;
+  }
+
+  for (x=1; x < size.x-1; x++)
+    for (y=1; y < size.y-1; y++) {
+      if ( (i=(int)m[x][y]) == 0) continue;
+      if (x < bbox[i].l) bbox[i].l = x;
+      else if (bbox[i].r < x) bbox[i].r = x;
+      if (y < bbox[i].t) bbox[i].t = y;
+      else if (bbox[i].b < y) bbox[i].b = y;
+    }
+
+  // reverse filling
+  for (i=1; i <= nobj; i++) {
+    Box box = bbox[i];
+    box.expand(1);
+    _fillAroundObjectHullT<T>(m, canvas, box, i);
+    // fill back the original matrix!
+  	for (x=box.l+1; x <= box.r-1; x++)
+      for (y=box.t+1; y <= box.b-1; y++) {
+	      // if ((int)_m[x-1+(y-1)*srcsize.x] > 0) continue;
+	      if ((int)m[x][y] != 0 || (int)canvas[x][y]==i) continue;
+        // this should never happen, but just in case
+	      if (x-1<0 || x-1>=srcsize.x || y-1<0 || y-1>=srcsize.y) continue;
+ 	      _m[x-1+(y-1)*srcsize.x] = (T)i;
+	    }
+  }
+  // cleanup
+  for (x=0; x < size.x; x++) {
+    delete[] m[x];
+    delete[] canvas[x];
+  }
+  delete[] m;
+  delete[] canvas;
+  delete[] bbox;
+}
diff --git a/src/floodFill.h b/src/floodFill.h
new file mode 100644
index 0000000..b724fdc
--- /dev/null
+++ b/src/floodFill.h
@@ -0,0 +1,19 @@
+#ifndef EBIMAGE_FLOODFILL_H
+#define EBIMAGE_FLOODFILL_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SEXP floodFill(SEXP, SEXP, SEXP, SEXP);
+SEXP fillHull(SEXP);
+SEXP bwlabel(SEXP);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/src/getFrames.c b/src/getFrames.c
new file mode 100644
index 0000000..1b86474
--- /dev/null
+++ b/src/getFrames.c
@@ -0,0 +1,107 @@
+#include "getFrames.h"
+#include "tools.h"
+
+/* -------------------------------------------------------------------------
+Extract frames from image stack
+
+Copyright (c) 2017 Andrzej Oles
+See: ../LICENSE for license, LGPL
+------------------------------------------------------------------------- */
+
+#include <R_ext/Error.h>
+
+/*----------------------------------------------------------------------- */
+SEXP getFrames (SEXP x, SEXP i, SEXP _type) {
+  int n, nx, ny, nc, nd, d, type, mode;
+  int j, nprotect, isimage;
+  int* ids;
+  SEXP res, frame, dm, names, dnames;
+  
+  nprotect = 0;
+  isimage = isImage(x);
+  
+  ids  = INTEGER(i);
+  n    = length(i);
+  type = INTEGER(_type)[0];
+  mode = COLOR_MODE(x);
+  
+  nx = INTEGER(GET_DIM(x))[0];
+  ny = INTEGER(GET_DIM(x))[1];
+  
+  if ( type==0 ) {  
+    nc = 1; 
+    mode = MODE_GRAYSCALE;
+  } else {
+    nc = getNumberOfChannels(x, mode);
+  }
+  
+  /* allocate memory for frame list */
+  PROTECT ( res = allocVector(VECSXP, n) );
+  nprotect++;
+  
+  /* frame length */
+  d = nx * ny * nc;
+  
+  /* set frame dimensions */
+  nd = ( mode==MODE_COLOR && length(GET_DIM(x))>2 ) ? 3 : 2;
+  
+  PROTECT ( dm = allocVector( INTSXP, nd) );
+  nprotect++;
+  
+  INTEGER(dm)[0] = nx;
+  INTEGER(dm)[1] = ny;
+  if ( nd == 3 )
+    INTEGER(dm)[2] = nc;
+  
+  /* set dimnames */
+  if ( GET_DIMNAMES(x) != R_NilValue ) {
+    PROTECT ( dnames = allocVector(VECSXP, nd) );
+    nprotect++;   
+    
+    for (j=0; j<nd; j++)
+      SET_VECTOR_ELT(dnames, j, VECTOR_ELT(GET_DIMNAMES(x), j)); 
+    
+    if ( GET_NAMES(GET_DIMNAMES(x)) != R_NilValue ) {
+      PROTECT ( names = allocVector(STRSXP, nd) );
+      nprotect++;
+      
+      for (j=0; j<nd; j++)
+        SET_STRING_ELT(names, j, STRING_ELT(GET_NAMES(GET_DIMNAMES(x)), j));
+      
+      SET_NAMES(dnames, names);
+    }
+  } else {
+    dnames = R_NilValue;
+  }
+  
+  for (j=0; j<n; j++) {
+    PROTECT(frame = allocVector(TYPEOF(x), d));
+    nprotect++;
+    
+    DUPLICATE_ATTRIB(frame, x);
+    SET_DIM(frame, dm);
+    SET_DIMNAMES(frame, dnames);
+    
+    if (isimage) frame = SET_SLOT( frame, Image_colormode, ScalarInteger(mode) );
+    
+    // copy pixel data
+    switch( TYPEOF(x) ) {
+    case LGLSXP:
+    case INTSXP:
+      memcpy( INTEGER(frame), &(INTEGER(x)[d*(ids[j]-1)]), d * sizeof(int));
+      break;
+    case REALSXP:
+      memcpy( REAL(frame), &(REAL(x)[d*(ids[j]-1)]), d * sizeof(double));
+      break;
+    }
+    
+    SET_VECTOR_ELT(res, j, frame);    
+  }
+  
+  UNPROTECT (nprotect);
+  return res;
+}
+
+SEXP getFrame (SEXP x, SEXP i, SEXP type) {
+  return VECTOR_ELT(getFrames(x, i, type), 0);
+}
diff --git a/src/getFrames.h b/src/getFrames.h
new file mode 100644
index 0000000..15bde53
--- /dev/null
+++ b/src/getFrames.h
@@ -0,0 +1,18 @@
+#ifndef EBIMAGE_GETFRAMES_H
+#define EBIMAGE_GETFRAMES_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SEXP getFrame (SEXP, SEXP, SEXP);
+SEXP getFrames (SEXP, SEXP, SEXP);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/src/haralick.c b/src/haralick.c
new file mode 100644
index 0000000..bd02bc8
--- /dev/null
+++ b/src/haralick.c
@@ -0,0 +1,259 @@
+#include "haralick.h"
+
+/* -------------------------------------------------------------------------
+Calculating Haralick image features
+Copyright (c) 2007 Oleg Sklyar, Mike Smith
+See: ../LICENSE for license, LGPL
+------------------------------------------------------------------------- */
+
+#include "tools.h"
+#include <R_ext/Error.h>
+
+# define  IND(I,X,Y) (X) + (Y)*(nc) + (I)*(nc)*(nc)
+
+/* calculates haralick cooccurrence matrix ---------------------------------- */
+SEXP
+haralickMatrix(SEXP obj, SEXP ref, SEXP cgrades) {
+  SEXP res, cm, dm;
+  int nx, ny, nz, nprotect, im, x, y, nobj, index, i, nc, colthis, colthat, no_objects, * ncomp;
+  double * data, * refdata, * cmdata;
+
+  if ( !validImage(obj,1) || !validImage(ref,1) ) return R_NilValue;
+
+  nx = INTEGER ( GET_DIM(obj) )[0];
+  ny = INTEGER ( GET_DIM(obj) )[1];
+  nz = getNumberOfFrames(obj,0);
+  nprotect = 0;
+
+  if ( INTEGER(GET_DIM(ref))[0] != nx || INTEGER(GET_DIM(ref))[1] != ny ||
+       getNumberOfFrames(ref,0) != nz )
+    error( "'ref' image has different size than 'obj'" );
+
+  nc = INTEGER(cgrades)[0];
+  if ( nc < 2 ) error( "the number of color grades must be larger than 1" );
+
+  PROTECT ( res = allocVector(VECSXP, nz) );
+  nprotect++;
+
+  for ( im = 0; im < nz; im++ ) {
+    /* get image data */
+    data = &( REAL(obj)[ im * nx * ny ] );
+    refdata = &( REAL(ref)[ im * nx * ny ] );
+    /* get number of objects -- max index */
+    nobj = 0;
+    for ( index = 0; index < nx * ny; index++ )
+      if ( data[index] > nobj ) nobj = floor( data[index] );
+    if ( nobj < 1 ) {
+      no_objects = 1;
+      nobj = 0; /* if no objects, create a 0-row matrix */
+    }
+    else no_objects = 0;
+    /* create features matrix */
+    SET_VECTOR_ELT( res, im, (cm = allocVector(REALSXP, nobj * nc * nc)) );
+    /* initialize feature matrix with 0 */
+    cmdata = REAL( cm );
+    for ( index = 0; index < nobj * nc * nc; index++ ) cmdata [index] = 0.0;
+    /* set dimensions of the feature matrix */
+    PROTECT( dm = allocVector(INTSXP, 3) );
+    nprotect++;
+    INTEGER( dm )[0] = nc;
+    INTEGER( dm )[1] = nc;
+    INTEGER( dm )[2] = nobj;
+    SET_DIM( cm, dm );
+    UNPROTECT( 1 ); nprotect--; // dm
+
+    /* return empty matrix (go to next image) with 1 line if error (no objects) */
+    if ( no_objects ) continue;
+
+    /* number of comparisons for each object */
+    ncomp = (int *) R_alloc (nobj, sizeof(int) );
+    for ( index = 0; index < nobj; index++ )  ncomp[index] = 0;
+    
+    /* go through pixels and collect primary descriptors */
+    /* reason to skip lines: we compare from this to: right, botoom, right-botoom, left-bottom */
+    for ( x = 1; x < nx - 1; x++ )      // skip leftmost and rightmost cols
+      for ( y = 0; y < ny - 1; y++ ) {  // skip bottom row
+        index = floor( data[x + y * nx] ); /* index of the object, R-style = 1-based */
+        if ( index < 1 ) continue;
+        /* all indexes were 1, 2, 3, but C has 0-based indexes!!! */
+        index--;
+        colthis = floor(refdata[x + y * nx] * (nc - 1));
+        /* we compare from this to: right, botoom, right-botoom, left-bottom */
+        if ( data[x+1 + y * nx] - 1 == index ) {     // 1. right
+          colthat = floor(refdata[x+1 + y * nx] * (nc - 1));
+          cmdata[IND(index,colthis,colthat)] += 1.0;
+          cmdata[IND(index,colthat,colthis)] += 1.0;
+          ncomp[index] += 2;
+        }
+        if ( data[x + (y+1) * nx] - 1 == index ) {   // 2. bottom
+          colthat = floor(refdata[x + (y+1) * nx] * (nc - 1));
+          cmdata[IND(index,colthis,colthat)] += 1.0;
+          cmdata[IND(index,colthat,colthis)] += 1.0;
+          ncomp[index] += 2;
+        }
+        if ( data[x+1 + (y+1) * nx] - 1 == index ) { // 3. right-bottom
+          colthat = floor(refdata[x+1 + (y+1) * nx] * (nc - 1));
+          cmdata[IND(index,colthis,colthat)] += 1.0;
+          cmdata[IND(index,colthat,colthis)] += 1.0;
+          ncomp[index] += 2;
+        }
+        if ( data[x-1 + (y+1) * nx] - 1 == index ) { // 4. left-bottom
+          colthat = floor(refdata[x-1 + (y+1) * nx] * (nc - 1));
+          cmdata[IND(index,colthis,colthat)] += 1.0;
+          cmdata[IND(index,colthat,colthis)] += 1.0;
+          ncomp[index] += 2;
+        }
+      }
+    for ( index = 0; index < nobj; index++ )
+      for ( i = 0; i < nc * nc; i++ ) 
+	    if ( ncomp[index] > 0 ) cmdata[i + index * nc * nc] /= ncomp[index];
+  }
+
+  UNPROTECT( nprotect );
+  if ( nz == 1 ) return VECTOR_ELT(res, 0 );
+  return res;
+}
+
+/* -------------------------------------------------------------------------- */
+# define  SMALL 1e-7 //stops log2() breaking if the data is 0
+
+/* given an array consisting of layers of square co-occurrence matrices,
+   one layer per object, this function returns a matrix of selected haralick
+   features (rows - objects, columns - features) -----------------------------*/
+SEXP 
+haralickFeatures ( SEXP cm ) {
+  SEXP res, dm;
+  int nprotect, nc, nobj, index, i, j, n, nonZeros, no_objects, nf=13;
+  double mu, tmp;
+  double *p, *f;   // p for co-occurrence matrix -- probability; f for features
+  double *px;      // partial probability density: rows summed, = py as matrix symmetrical
+  double * Pxpy, * Pxmy;
+  double HXY1, HXY2, entpx;  //used in calculating IMC1 and IMC2
+  /* offsets in the res matrix for each feature, calculated from nobj */
+  enum { ASM, CON, COR, VAR, IDM, SAV, SVA, SEN, ENT, DVA, DEN, IMC1, IMC2 };
+
+  if ( cm == R_NilValue ) return R_NilValue;
+  
+  nprotect = 0;
+  /* n = number of colors, nc, determined by the size of the haralick matrix */
+  nc = INTEGER(GET_DIM(cm))[0];
+  if ( nc != INTEGER(GET_DIM(cm))[1] || nc < 2 ) 
+    error( "Haralick matrix is not square or too small" );
+  /* return NULL if no objects */
+  nobj = INTEGER(GET_DIM(cm))[2];
+
+  if ( nobj < 1 ) return R_NilValue;
+  /* after all the checks we hope we can collect the results, alloc mem */
+  PROTECT( res = allocVector(REALSXP, nobj * nf) );
+  nprotect++;
+  f = REAL(res);
+  for ( i = 0; i < nobj * nf; i++ ) f[i] = 0.0;
+  PROTECT( dm = allocVector(INTSXP, 2) );
+  nprotect++;
+  INTEGER(dm)[0] = nobj;
+  INTEGER(dm)[1] = nf;
+  SET_DIM( res, dm );
+  if ( nobj == 1) {
+    /* check if all zeros (co-occurance matrix for 0 objects ), 
+       return empty matrix if yes */
+    no_objects = 1;
+    p = REAL(cm); /* for the first object */
+    for ( i = 0; i < nc && no_objects; i++ )
+      for ( j = 0; j < nc && no_objects; j++ )
+        if ( (tmp = p[i + j * nc]) > 0 ) no_objects = 0;
+    if ( no_objects ) {
+      UNPROTECT( nprotect );
+      return res;
+    }
+  }
+  /* temp vars */
+  px   = (double *) R_alloc (nc, sizeof(double) );
+  Pxpy = (double *) R_alloc(2 * nc + 10, sizeof(double) ); // +10 is to be safe with limits
+  Pxmy = (double *) R_alloc(2 * nc + 10, sizeof(double) ); // +10 is to be safe with limits
+  /* GO through objects and calculate features */
+  for ( index = 0; index < nobj; index++ ) {
+    p = &( REAL(cm)[index * nc * nc] );
+    /* total number of non zeros to get mu,
+       angular second moment (ASM),
+       inverse diff mom      (IDM),
+       entropy               (ENT) */
+    /* calculate Pxpy and Pxmy */
+      //  indexing (adding 1 to i and j) is based on Boland and netpbm,
+      //  makes no sense to me
+    nonZeros = 0;
+    for ( i = 0; i < 2 * nc + 10; i++ ) Pxpy[i] = Pxmy[i] = 0;
+    for ( i = 0; i < nc; i++ )
+      for ( px[i]=0.0, j = 0; j < nc; j++ )
+        if ( (tmp = p[i + j * nc]) > 0 ) {
+          f[index + ASM * nobj] += tmp * tmp;
+          f[index + IDM * nobj] += tmp / (double)(1 + (i-j)*(i-j));
+          f[index + ENT * nobj] -= (tmp >= SMALL) ? (tmp * log10(tmp)) : (tmp * log10(SMALL));
+          nonZeros++;
+          Pxpy[i + j + 2] += tmp;
+          Pxmy[abs(i - j)] += tmp;
+          px[i] += tmp;
+        }
+    /* no sense to do anything if no non zero elements */
+    if ( nonZeros < 1 ) continue; 
+    /* contrast              (CON) */
+    for ( n = 1; n < nc; n++ ) { // was from 0, but n^2 at n=0 is 0, so nonsense
+      tmp = 0.0;
+      for ( i = 0; i < nc; i++ )
+        for ( j = 0; j < nc; j++ )
+          if ( abs(i - j) == n ) tmp += p[i + j * nc];
+      f[index + CON * nobj] += n * n * tmp;
+    }
+    /* correlation           (COR) */
+//  the calculation of mu and sd is based on Boland and netpbm, 
+//  but it does not make any sense to me
+    /* reset px and recalculate it */
+    /* the code assumes mux=muy, sdx=sdy */
+    for ( mu=0, tmp=0,  i = 0; i < nc; i++ ) {
+      mu += i * px[i];     // why is it this way, no idea
+      tmp += i * i * px[i]; // sum of squares, why it is i^2 -- no idea
+    }
+    if ( tmp - mu * mu > 0 ) { // tmp - mu * mu = sd^2
+      for ( i = 0; i < nc; i++ )
+        for ( j = 0; j < nc; j++ ) f[index + COR * nobj] += i * j * p[i + j * nc];
+      f[index + COR * nobj] = ( f[index + COR * nobj] - mu * mu ) / ( tmp - mu * mu );
+    }
+    /* variance             (VAR) */    
+//  the calculation of mu is based on Boland and netpbm,
+//  but it and indexing do not make any sense to me
+    for ( mu=0,  i = 0; i < nc; i++ )
+      for ( j = 0; j < nc; j++ ) mu += i * p[i + j * nc];
+    for ( i = 0; i < nc; i++ )
+      for ( j = 0; j < nc; j++ )
+        f[index + VAR * nobj] += (i + 1 - mu) * (i + 1 - mu) * p[i + j * nc];
+    /* sum average          (SAV)
+       sum entropy          (SEN) */
+    for ( i = 2; i <= 2 * nc; i++ ) {
+      f[index + SAV * nobj] += i * Pxpy[i];
+      f[index + SEN * nobj] -= (Pxpy[i] >= SMALL) ? (Pxpy[i] * log10(Pxpy[i])) : (Pxpy[i] * log10(SMALL));
+    }
+    /* sum variance         (SVA) */
+    for ( i = 2; i <= 2 * nc; i++ )
+      f[index + SVA * nobj] += (i - f[index + SEN * nobj]) * (i - f[index + SEN * nobj]) * Pxpy[i];
+    /* difference Variance  (DVA)
+       difference entropy   (DEN) */
+    for ( i = 0; i < nc - 1; i++ ) { // top: Nc - 1
+      f[index + DVA * nobj] += i * i * Pxmy[i];
+      f[index + DEN * nobj] -= (Pxmy[i] >= SMALL) ? (Pxmy[i] * log10(Pxmy[i])) : (Pxmy[i] * log10(SMALL));
+    }
+    /* Info Measure of Correlation 1 and 2 */
+    for ( HXY1=0, HXY2=0, entpx=0,   i = 0; i < nc; i++ ) {
+      entpx -= (px[i] >= SMALL) ? (px[i] * log10(px[i])) : (px[i] * log10(SMALL));
+      for( j = 0; j < nc; j++ ) {
+        tmp = px[i] * px[j];
+        HXY1 -= (tmp >= SMALL) ? (p[i + j * nc] * log10(tmp)) : (p[i + j * nc] * log10(SMALL));
+        HXY2 -= (tmp >= SMALL) ? (tmp * log10(tmp)) : (tmp * log10(SMALL));
+      }
+    }
+//    FIXME: why is the following negative (if fabs removed)?
+    f[index + IMC1 * nobj] = (entpx != 0) ? ( fabs(f[index + ENT * nobj] - HXY1) / entpx) : 0.0;
+    tmp = 1.0 - exp( -2.0 * (HXY2 - f[index + ENT * nobj]));
+    f[index + IMC2 * nobj] = ( tmp >= 0 ) ? sqrt(tmp) : 0.0;
+  }
+  UNPROTECT( nprotect );
+  return res;
+}        
diff --git a/src/haralick.h b/src/haralick.h
new file mode 100644
index 0000000..1cf2a00
--- /dev/null
+++ b/src/haralick.h
@@ -0,0 +1,18 @@
+#ifndef EBIMAGE_HARALICK_H
+#define EBIMAGE_HARALICK_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+  SEXP haralickMatrix(SEXP, SEXP, SEXP);
+  SEXP haralickFeatures(SEXP);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/src/medianFilter.c b/src/medianFilter.c
new file mode 100644
index 0000000..71da247
--- /dev/null
+++ b/src/medianFilter.c
@@ -0,0 +1,506 @@
+/* medianFilter.c - Constant-time median filtering of 16-bit images
+ *  (for inclusion in the Bioconductor Package EBImage)
+ *
+ *  The original ctmf algorithm here has minor modifications,
+ *  which continue to be covered by the GNU General Public License.
+ *
+ * Contact:
+ *  Joseph Barry
+ *  Huber Group
+ *  EMBL Heidelberg
+ *  Meyerhofstr. 1
+ *  69115 Germany
+ *
+ *  joseph.barry at embl.de
+*/
+
+/* R/Bioconductor includes */
+#include <Rinternals.h>
+#include "tools.h"
+
+/* parameters helpful in extending algorithm to 16-bit*/
+#define SQRT_BUCKET_SIZE 256
+#define MSB 8
+
+/*
+ * ctmf.c - Constant-time median filtering
+ * Copyright (C) 2006  Simon Perreault
+ *
+ * Reference: S. Perreault and P. Hébert, "Median Filtering in Constant Time",
+ * IEEE Transactions on Image Processing, September 2007.
+ *
+ * This program has been obtained from http://nomis80.org/ctmf.html. No patent
+ * covers this program, although it is subject to the following license:
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Contact:
+ *  Laboratoire de vision et systèmes numériques
+ *  Pavillon Adrien-Pouliot
+ *  Université Laval
+ *  Sainte-Foy, Québec, Canada
+   G1K 7P4
+ *
+ *  perreaul at gel.ulaval.ca
+ */
+
+/* Standard C includes */
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Type declarations */
+#ifdef _MSC_VER
+#include <basetsd.h>
+typedef UINT8 uint8_t;
+typedef UINT16 uint16_t;
+typedef UINT32 uint32_t;
+#pragma warning( disable: 4799 )
+#else
+#include <stdint.h>
+#endif
+
+/* Intrinsic declarations */
+#if defined(__SSE2__) || defined(__MMX__)
+#if defined(__SSE2__)
+#include <emmintrin.h>
+#elif defined(__MMX__)
+#include <mmintrin.h>
+#endif
+#if defined(__GNUC__)
+#include <mm_malloc.h>
+#elif defined(_MSC_VER)
+#include <malloc.h>
+#endif
+#elif defined(__ALTIVEC__)
+#include <altivec.h>
+#endif
+
+
+/* Compiler peculiarities */
+#if defined(__GNUC__)
+#include <stdint.h>
+#define inline __inline__
+#define align(x) __attribute__ ((aligned (x)))
+#elif defined(_MSC_VER)
+#define inline __inline
+#define align(x) __declspec(align(x))
+#else
+#define inline
+#define align(x)
+#endif
+
+#ifndef MIN
+#define MIN(a,b) ((a) > (b) ? (b) : (a))
+#endif
+
+#ifndef MAX
+#define MAX(a,b) ((a) < (b) ? (b) : (a))
+#endif
+
+/**
+ * This structure represents a two-tier histogram. The first tier (known as the
+ * "coarse" level) is 8 bit wide and the second tier (known as the "fine" level)
+ * is 16 bit wide. Pixels inserted in the fine level also get inserted into the
+ * coarse bucket designated by the MSBs of the fine bucket value.
+ *
+ * The structure is aligned on 16 bytes, which is a prerequisite for SIMD
+ * instructions. Each bucket is 16 bit wide, which means that extra care must be
+ * taken to prevent overflow.
+ */
+typedef struct align(16)
+{
+    uint16_t coarse[SQRT_BUCKET_SIZE];
+    uint16_t fine[SQRT_BUCKET_SIZE][SQRT_BUCKET_SIZE];
+} Histogram;
+
+/**
+ * HOP is short for Histogram OPeration. This macro makes an operation \a op on
+ * histogram \a h for pixel value \a x. It takes care of handling both levels.
+ */
+
+#define HOP(h,x,op) \
+    h.coarse[x>>MSB] op; \
+    *((uint16_t*) h.fine + x) op;
+
+/* note that the hex numerical value needs to be changed depending on the MSB (e.g. 2^8-1 is here 0xFF) */
+#define COP(c,j,x,op) \
+    h_coarse[ SQRT_BUCKET_SIZE*(n*c+j) + (x>>MSB) ] op; \
+    h_fine[ SQRT_BUCKET_SIZE * (n*(SQRT_BUCKET_SIZE*c+(x>>MSB)) + j) + (x & 0xFF) ] op;
+
+/**
+ * Adds histograms \a x and \a y and stores the result in \a y. Makes use of
+ * SSE2, MMX or Altivec, if available.
+ */
+
+#if defined(__SSE2__)
+static inline void histogram_add( const uint16_t x[SQRT_BUCKET_SIZE], uint16_t y[SQRT_BUCKET_SIZE] )
+{   
+    int i;
+    for (i=0;i<SQRT_BUCKET_SIZE;i+=8) *(__m128i*) &y[i] = _mm_add_epi16( *(__m128i*) &y[i], *(__m128i*) &x[i] );
+}
+#elif defined(__MMX__)
+static inline void histogram_add( const uint16_t x[SQRT_BUCKET_SIZE], uint16_t y[SQRT_BUCKET_SIZE] )
+{
+    int i;
+    for (i=0;i<SQRT_BUCKET_SIZE;i+=4) *(__m64*) &y[i]  = _mm_add_pi16( *(__m64*) &y[i],  *(__m64*) &x[i]  );
+}
+#elif defined(__ALTIVEC__)
+static inline void histogram_add( const uint16_t x[SQRT_BUCKET_SIZE], uint16_t y[SQRT_BUCKET_SIZE] )
+{
+    int i;
+    for (i=0;i<SQRT_BUCKET_SIZE;i+=8) *(vector unsigned short*) &y[i] = vec_add( *(vector unsigned short*) &y[i], *(vector unsigned short*) &x[i] );
+}
+#else
+static inline void histogram_add( const uint16_t x[SQRT_BUCKET_SIZE], uint16_t y[SQRT_BUCKET_SIZE] )
+{
+    int i;
+    for ( i = 0; i < SQRT_BUCKET_SIZE; ++i ) {
+        y[i] += x[i];
+    }
+}
+#endif
+
+/**
+ * Subtracts histogram \a x from \a y and stores the result in \a y. Makes use
+ * of SSE2, MMX or Altivec, if available.
+ */
+#if defined(__SSE2__)
+static inline void histogram_sub( const uint16_t x[SQRT_BUCKET_SIZE], uint16_t y[SQRT_BUCKET_SIZE] )
+{
+    int i;
+    for (i=0;i<SQRT_BUCKET_SIZE;i+=8) *(__m128i*) &y[i] = _mm_sub_epi16( *(__m128i*) &y[i], *(__m128i*) &x[i] );
+}
+#elif defined(__MMX__)
+static inline void histogram_sub( const uint16_t x[SQRT_BUCKET_SIZE], uint16_t y[SQRT_BUCKET_SIZE] )
+{
+    int i;
+    for (i=0;i<SQRT_BUCKET_SIZE;i+=4) *(__m64*) &y[i]  = _mm_sub_pi16( *(__m64*) &y[i],  *(__m64*) &x[i]  );
+}
+#elif defined(__ALTIVEC__)
+static inline void histogram_sub( const uint16_t x[SQRT_BUCKET_SIZE], uint16_t y[SQRT_BUCKET_SIZE] )
+{
+    int i;
+    for (i=0;i<SQRT_BUCKET_SIZE;i+=8) *(vector unsigned short*) &y[i] = vec_sub( *(vector unsigned short*) &y[i], *(vector unsigned short*) &x[i] );
+}
+#else
+static inline void histogram_sub( const uint16_t x[SQRT_BUCKET_SIZE], uint16_t y[SQRT_BUCKET_SIZE] )
+{
+    int i;
+    for ( i = 0; i < SQRT_BUCKET_SIZE; ++i ) {
+        y[i] -= x[i];
+    }
+}
+#endif
+static inline void histogram_muladd( const uint16_t a, const uint16_t x[SQRT_BUCKET_SIZE],
+        uint16_t y[SQRT_BUCKET_SIZE] )
+{
+    int i;
+    for ( i = 0; i < SQRT_BUCKET_SIZE; ++i ) {
+        y[i] += a * x[i];
+    }
+}
+
+static void ctmf_helper(
+        const uint16_t* const src, uint16_t* const dst,
+        const int width, const int height,
+        const int src_step, const int dst_step,
+        const int r, const int cn,
+        const int pad_left, const int pad_right
+        )
+{
+    const int m = height, n = width;
+    int i, j, k, c;
+    const unsigned short int *p, *q;
+
+    Histogram H[4];
+    uint16_t *h_coarse, *h_fine, luc[4][SQRT_BUCKET_SIZE];
+    assert( src );
+    assert( dst );
+    assert( r >= 0 );
+    assert( width >= 2*r+1 );
+    assert( height >= 2*r+1 );
+    assert( src_step != 0 );
+    assert( dst_step != 0 );
+
+    /* SSE2 and MMX need aligned memory, provided by _mm_malloc(). */
+#if defined(__SSE2__) || defined(__MMX__)
+    h_coarse = (uint16_t*) _mm_malloc(  1 * SQRT_BUCKET_SIZE * n * cn * sizeof(uint16_t), 16 );
+    h_fine   = (uint16_t*) _mm_malloc( SQRT_BUCKET_SIZE * SQRT_BUCKET_SIZE * n * cn * sizeof(uint16_t), 16 );
+    memset( h_coarse, 0,  1 * SQRT_BUCKET_SIZE * n * cn * sizeof(uint16_t) );
+    memset( h_fine,   0, SQRT_BUCKET_SIZE * SQRT_BUCKET_SIZE * n * cn * sizeof(uint16_t) );
+#else
+    h_coarse = (uint16_t*) calloc(  1 * SQRT_BUCKET_SIZE * n * cn, sizeof(uint16_t) );
+    h_fine   = (uint16_t*) calloc( SQRT_BUCKET_SIZE * SQRT_BUCKET_SIZE * n * cn, sizeof(uint16_t) );
+#endif
+
+    /* First row initialization */
+    for ( j = 0; j < n; ++j ) {
+        for ( c = 0; c < cn; ++c ) {
+            COP( c, j, src[cn*j+c], += r+1 );
+        }
+    }
+    for ( i = 0; i < r; ++i ) {
+        for ( j = 0; j < n; ++j ) {
+            for ( c = 0; c < cn; ++c ) {
+                COP( c, j, src[src_step*i+cn*j+c], ++ );
+            }
+        }
+    }
+
+    for ( i = 0; i < m; ++i ) {
+
+        /* Update column histograms for entire row. */
+        p = src + src_step * MAX( 0, i-r-1 );
+        q = p + cn * n;
+        for ( j = 0; p != q; ++j ) {
+            for ( c = 0; c < cn; ++c, ++p ) {
+                COP( c, j, *p, -- );
+            }
+        }
+
+        p = src + src_step * MIN( m-1, i+r );
+        q = p + cn * n;
+        for ( j = 0; p != q; ++j ) {
+            for ( c = 0; c < cn; ++c, ++p ) {
+                COP( c, j, *p, ++ );
+            }
+        }
+
+        /* First column initialization */
+        memset( H, 0, cn*sizeof(H[0]) );
+        memset( luc, 0, cn*sizeof(luc[0]) );
+        if ( pad_left ) {
+            for ( c = 0; c < cn; ++c ) {
+                histogram_muladd( r, &h_coarse[SQRT_BUCKET_SIZE*n*c], H[c].coarse );
+            }
+        }
+        for ( j = 0; j < (pad_left ? r : 2*r); ++j ) {
+            for ( c = 0; c < cn; ++c ) {
+                histogram_add( &h_coarse[SQRT_BUCKET_SIZE*(n*c+j)], H[c].coarse );
+            }
+        }
+        for ( c = 0; c < cn; ++c ) {
+            for ( k = 0; k < SQRT_BUCKET_SIZE; ++k ) {
+                histogram_muladd( 2*r+1, &h_fine[SQRT_BUCKET_SIZE*n*(SQRT_BUCKET_SIZE*c+k)], &H[c].fine[k][0] );
+            }
+        }
+
+        for ( j = pad_left ? 0 : r; j < (pad_right ? n : n-r); ++j ) {
+            for ( c = 0; c < cn; ++c ) {
+                const uint16_t t = 2*r*r + 2*r;
+                uint16_t sum = 0, *segment;
+                int b;
+
+                histogram_add( &h_coarse[SQRT_BUCKET_SIZE*(n*c + MIN(j+r,n-1))], H[c].coarse );
+
+                /* Find median at coarse level */
+                for ( k = 0; k < SQRT_BUCKET_SIZE ; ++k ) {
+                    sum += H[c].coarse[k];
+                    if ( sum > t ) {
+                        sum -= H[c].coarse[k];
+                        break;
+                    }
+                }
+                assert( k < (uint16_t)SQRT_BUCKET_SIZE );
+
+                /* Update corresponding histogram segment */
+                if ( luc[c][k] <= j-r ) {
+                    memset( &H[c].fine[k], 0, SQRT_BUCKET_SIZE * sizeof(uint16_t) );
+                    for ( luc[c][k] = j-r; luc[c][k] < MIN(j+r+1,n); ++luc[c][k] ) {
+                        histogram_add( &h_fine[SQRT_BUCKET_SIZE*(n*(SQRT_BUCKET_SIZE*c+k)+luc[c][k])], H[c].fine[k] );
+                    }
+                    if ( luc[c][k] < j+r+1 ) {
+                        histogram_muladd( j+r+1 - n, &h_fine[SQRT_BUCKET_SIZE*(n*(SQRT_BUCKET_SIZE*c+k)+(n-1))], &H[c].fine[k][0] );
+                        luc[c][k] = j+r+1;
+                    }
+                }
+                else {
+                    for ( ; luc[c][k] < j+r+1; ++luc[c][k] ) {
+                        histogram_sub( &h_fine[SQRT_BUCKET_SIZE*(n*(SQRT_BUCKET_SIZE*c+k)+MAX(luc[c][k]-2*r-1,0))], H[c].fine[k] );
+                        histogram_add( &h_fine[SQRT_BUCKET_SIZE*(n*(SQRT_BUCKET_SIZE*c+k)+MIN(luc[c][k],n-1))], H[c].fine[k] );
+                    }
+                }
+
+                histogram_sub( &h_coarse[SQRT_BUCKET_SIZE*(n*c+MAX(j-r,0))], H[c].coarse );
+
+                /* Find median in segment */
+                segment = H[c].fine[k];
+                for ( b = 0; b < SQRT_BUCKET_SIZE ; ++b ) {
+                    sum += segment[b];
+                    if ( sum > t ) {
+                        dst[dst_step*i+cn*j+c] = SQRT_BUCKET_SIZE*k + b;
+                        break;
+                    }
+                }
+                assert( b < (uint16_t)SQRT_BUCKET_SIZE );
+            }
+        }
+    }
+
+#if defined(__SSE2__) || defined(__MMX__)
+    _mm_empty();
+    _mm_free(h_coarse);
+   _mm_free(h_fine);
+#else
+    free(h_coarse);
+    free(h_fine);
+#endif
+}
+
+/**
+ * \brief Constant-time median filtering
+ *
+ * This function does a median filtering of an 16-bit image. The source image is
+ * processed as if it was padded with zeros. The median kernel is square with
+ * odd dimensions. Images of arbitrary size may be processed.
+ *
+ * To process multi-channel images, you must call this function multiple times,
+ * changing the source and destination adresses and steps such that each channel
+ * is processed as an independent single-channel image.
+ *
+ * Processing images of arbitrary bit depth is not supported.
+ *
+ * The computing time is O(1) per pixel, independent of the radius of the
+ * filter. The algorithm's initialization is O(r*width), but it is negligible.
+ * Memory usage is simple: it will be as big as the cache size, or smaller if
+ * the image is small. For efficiency, the histograms' bins are 16-bit wide.
+ * This may become too small and lead to overflow as \a r increases.
+ *
+ * \param src           Source image data.
+ * \param dst           Destination image data. Must be preallocated.
+ * \param width         Image width, in pixels.
+ * \param height        Image height, in pixels.
+ * \param src_step      Distance between adjacent pixels on the same column in
+ *                      the source image, in bytes.
+ * \param dst_step      Distance between adjacent pixels on the same column in
+ *                      the destination image, in bytes.
+ * \param r             Median filter radius. The kernel will be a 2*r+1 by
+ *                      2*r+1 square.
+ * \param cn            Number of channels. For example, a grayscale image would
+ *                      have cn=1 while an RGB image would have cn=3.
+ * \param memsize       Maximum amount of memory to use, in bytes. Set this to
+ *                      the size of the L2 cache, then vary it slightly and
+ *                      measure the processing time to find the optimal value.
+ *                      For example, a 512 kB L2 cache would have
+ *                      memsize=512*1024 initially.
+ */
+void ctmf(
+        const uint16_t* const src, uint16_t* const dst,
+        const int width, const int height,
+        const int src_step, const int dst_step,
+        const int r, const int cn, const long unsigned int memsize
+        )
+{
+    /*
+     * Processing the image in vertical stripes is an optimization made
+     * necessary by the limited size of the CPU cache. Each histogram is 544
+     * bytes big and therefore I can fit a limited number of them in the cache.
+     * That number may sometimes be smaller than the image width, which would be
+     * the number of histograms I would need without stripes.
+     *
+     * I need to keep histograms in the cache so that they are available
+     * quickly when processing a new row. Each row needs access to the previous
+     * row's histograms. If there are too many histograms to fit in the cache,
+     * thrashing to RAM happens.
+     *
+     * To solve this problem, I figure out the maximum number of histograms
+     * that can fit in cache. From this is determined the number of stripes in
+     * an image. The formulas below make the stripes all the same size and use
+     * as few stripes as possible.
+     *
+     * Note that each stripe causes an overlap on the neighboring stripes, as
+     * when mowing the lawn. That overlap is proportional to r. When the overlap
+     * is a significant size in comparison with the stripe size, then we are not
+     * O(1) anymore, but O(r). In fact, we have been O(r) all along, but the
+     * initialization term was neglected, as it has been (and rightly so) in B.
+     * Weiss, "Fast Median and Bilateral Filtering", SIGGRAPH, 2006. Processing
+     * by stripes only makes that initialization term bigger.
+     *
+     * Also, note that the leftmost and rightmost stripes don't need overlap.
+     * A flag is passed to ctmf_helper() so that it treats these cases as if the
+     * image was zero-padded.
+     */
+    int stripes = (int) ceil( (double) (width - 2*r) / (memsize / sizeof(Histogram) - 2*r) );
+    int stripe_size = (int) ceil( (double) ( width + stripes*2*r - 2*r ) / stripes );
+
+    int i;
+
+    for ( i = 0; i < width; i += stripe_size - 2*r ) {
+        int stripe = stripe_size;
+    /* Make sure that the filter kernel fits into one stripe. */
+
+    if ( i + stripe_size - 2*r >= width || width - (i + stripe_size - 2*r) < 2*r+1 ) {
+            stripe = width - i;
+        }
+
+        ctmf_helper( src + cn*i, dst + cn*i, stripe, height, src_step, dst_step, r, cn,
+                i == 0, stripe == width - i );
+
+        if ( stripe == width - i ) {
+            break;
+        }
+
+    }
+}
+
+//updated by Andrzej Oles, 2015
+
+SEXP medianFilter (SEXP _in, SEXP _r, SEXP _memsize) {
+    const double max_uint16 = 65535.0;
+    int x, y, z, r, memsize, i, j, imagesize, framesize;
+    SEXP res;
+    
+    x = INTEGER(GET_DIM(_in))[0];
+    y = INTEGER(GET_DIM(_in))[1];
+    framesize = x*y;
+    z = getNumberOfFrames(_in, 0);
+    imagesize = length(_in);
+    r = INTEGER(_r)[0];
+    memsize = INTEGER(_memsize)[0] * 1024;
+    
+    uint16_t *px, *py;
+    px = R_Calloc(imagesize, uint16_t);
+    py = R_Calloc(imagesize, uint16_t);
+    
+    double *src = REAL(_in);
+    for (i = 0; i < imagesize; i++) {
+        double el = src[i];
+        // clip
+        if (el < 0.0) el = 0;
+        else if (el > 1.0) el = 1.0;
+        // convert to int
+        px[i] = (uint16_t) round(el * max_uint16);
+    }
+    
+    // process channels separately
+    for(j = 0; j < z; j++)
+        ctmf(&px[framesize*j], &py[framesize*j], x, y, x, x, r, 1, memsize);
+    
+    PROTECT( res = allocVector(REALSXP, imagesize) );
+    DUPLICATE_ATTRIB(res, _in);
+    
+    // convert back to [0:1] range
+    double *tgt = REAL(res);
+    for (i = 0; i < imagesize; i++)
+        tgt[i] = (double) py[i] / max_uint16;
+    
+    R_Free(px);
+    R_Free(py);
+    
+    UNPROTECT(1);
+    
+    return res;
+}
diff --git a/src/medianFilter.h b/src/medianFilter.h
new file mode 100644
index 0000000..3aa6f2a
--- /dev/null
+++ b/src/medianFilter.h
@@ -0,0 +1,17 @@
+#ifndef EBIMAGE_MEDIANFILTER_H
+#define EBIMAGE_MEDIANFILTER_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SEXP medianFilter (SEXP, SEXP, SEXP);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/src/morphology.cpp b/src/morphology.cpp
new file mode 100644
index 0000000..611e782
--- /dev/null
+++ b/src/morphology.cpp
@@ -0,0 +1,383 @@
+/* -------------------------------------------------------------------------
+ Morphological filters for greyscale images
+ Copyright (c) 2012-2015 Ilia Kats, Andrzej Oles
+ See: ../LICENSE for license, LGPL
+ ------------------------------------------------------------------------- */
+
+#include "morphology.h"
+#include "tools.h"
+
+#define DILATE 0
+#define ERODE 1
+#define OPENING 2
+#define CLOSING 3
+#define TOPHAT_WHITE  4
+#define TOPHAT_BLACK 5
+#define TOPHAT_SELFCOMPLEMENTARY 6
+
+#define BUF_LENGTH 10
+
+#define CHECK_BUFFER(pointer, occupied, buffer, type) \
+if (occupied == buffer) {                             \
+    buffer += BUF_LENGTH;                             \
+    pointer = R_Realloc(pointer, buffer, type);       \
+}
+
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
+
+typedef struct {
+    int yOffset;
+    int xOffset1;
+    int xOffset2;
+    int n;
+} chord;
+
+typedef struct {
+    chord *C;
+    int CLength;
+    int minYoffset;
+    int maxYoffset;
+    int minXoffset;
+    int maxXoffset;
+    int maxN;
+} chordSet;
+
+/* use custom templates rather than std::numeric_limits to avoid dependency on C++11 due to lowest() */
+#define MIN_VALUE true
+#define MAX_VALUE false
+
+template <typename type> const type limits(const bool);
+
+template <> const int limits(const bool min) {
+    return min ? INT_MIN : INT_MAX;
+}
+
+template <> const double limits(const bool min) {
+    return min ? -DBL_MAX : DBL_MAX;
+}
+
+template <typename type> chordSet buildChordSet(type *, PointXY);
+template <typename type> type*** allocate_lookup_table(chordSet *, int);
+template <typename type> void free_lookup_table(type ***, chordSet *);
+template <typename type> void compute_lookup_table_for_line_dilate(type ***, type *, int, int, chordSet *, PointXY);
+template <typename type> void compute_lookup_table_for_line_erode(type ***, type *, int, int, chordSet *, PointXY);  
+template <typename type> void dilate_line(type ***, type *, type *, chordSet *, int, int);
+template <typename type> void erode_line(type ***, type *, type *, chordSet *, int, int);
+template <typename type> void erode_dilate(type *, type *, PointXY, int, int, chordSet *, type ***);
+template <typename type> void opening_closing(type *, type *, PointXY, int, int, chordSet *, type ***);
+template <typename type> void tophat(type *, type *, PointXY, int, int, chordSet *, type ***);
+template <typename type> void _morphology(type *, type *, PointXY, int, SEXP kernel, int what);
+
+
+template <typename type> chordSet buildChordSet(type * kern, PointXY ksize) {
+    PointXY korigin;
+    korigin.x = (int) ceil((float)ksize.x / 2) - 1; // -1 due to 0-based indices
+    korigin.y = (int) ceil((float)ksize.y / 2) - 1;
+    
+    chordSet set = (chordSet) {C: NULL, CLength: 0, minYoffset: korigin.y, maxYoffset: -korigin.y, minXoffset: korigin.x, maxXoffset: -korigin.x, maxN: 0};
+    
+    int CBufLength = 0;
+    set.C = R_Calloc(BUF_LENGTH, chord);
+    CBufLength = BUF_LENGTH;
+    for (int i = 0; i < ksize.y; ++i) {
+        type prevValue = 0;
+        int beginChord = 0;
+        for (int j = 0; j <= ksize.x; ++j) {
+            type value = (j < ksize.x ? kern[INDEX_FROM_XY(j, i, ksize.x)] : 0);
+            if (value == 0 && prevValue != 0) {
+                chord c;
+                c.yOffset = i - korigin.y;
+                c.xOffset1 = beginChord - korigin.x;
+                c.n = 0;
+                int length = j - beginChord;
+                if (length > 1) c.n = (int) floor(log2(length-1));
+                c.xOffset2 = j - korigin.x - (int) pow(2, c.n);
+                int xEnd = j - korigin.x - 1;
+                
+                set.C[set.CLength++] = c;
+                CHECK_BUFFER(set.C, set.CLength, CBufLength, chord);
+                
+                if (c.yOffset < set.minYoffset)
+                    set.minYoffset = c.yOffset;
+                else if (c.yOffset > set.maxYoffset)
+                    set.maxYoffset = c.yOffset;
+                if (c.xOffset1 < set.minXoffset)
+                    set.minXoffset = c.xOffset1;
+                if (xEnd > set.maxXoffset)
+                    set.maxXoffset = xEnd;
+                if (c.n > set.maxN)
+                    set.maxN = c.n;
+            } else if (value != 0 && prevValue == 0) {
+                beginChord = j;
+            }
+            prevValue = value;
+        }
+    }
+    
+    return set;
+}
+
+template <typename type> type*** allocate_lookup_table(chordSet *set, int width) {
+    type ***T;
+    T = R_Calloc(set->maxYoffset - set->minYoffset + 1, type**); // + 1 for offset of 0
+    T = T - set->minYoffset;
+    
+    int Txlength = width - set->minXoffset + set->maxXoffset + 1;
+    for (int i = set->minYoffset; i <= set->maxYoffset; ++i) {
+        T[i] = R_Calloc(set->maxN + 1, type*);
+        for (int j = 0, d = 1; j <= set->maxN; ++j, d *= 2) {
+            T[i][j] = R_Calloc(Txlength - d, type);
+            T[i][j] = T[i][j] - set->minXoffset;
+        }
+    }
+    return T;
+}
+
+template <typename type> void free_lookup_table(type ***T, chordSet *set) {
+    for (int i = set->minYoffset; i <= set->maxYoffset; ++i) {
+        for (int j = 0; j < set->maxN; j++) {
+            type *first = T[i][j] + set->minXoffset;
+            R_Free(first);
+        }
+        R_Free(T[i]);
+    }
+    type ***first = T + set->minYoffset;
+    R_Free(first);
+}
+
+template <typename type> void compute_lookup_table_for_line_dilate(type ***T, type *image, int yOff, int line, chordSet *set, PointXY size) {
+    const type MIN_VAL = limits<type>(MIN_VALUE);
+    
+    int y = line + yOff;
+    
+    if (y < 0 || y >= size.y) {
+        for (int i = set->minXoffset; i < size.x + set->maxXoffset; ++i) {
+            T[yOff][0][i] = MIN_VAL;
+        }
+    }
+    else {
+        int maxX = MIN(size.x, size.x + set->maxXoffset);
+        int i = set->minXoffset;
+        
+        for (i; i < 0; ++i) {
+            T[yOff][0][i] = MIN_VAL;
+        }
+        for (i; i < maxX; ++i) {
+            type val = image[INDEX_FROM_XY(i, y, size.x)];
+            T[yOff][0][i] = ISNA(val) ? MIN_VAL : val;
+        }
+        for (i; i < size.x + set->maxXoffset; ++i) {
+            T[yOff][0][i] = MIN_VAL;
+        }
+    }
+    
+    for (int i = 1, d = 1; i <= set->maxN; ++i, d *= 2) {
+        for (int j = set->minXoffset; j <= size.x + set->maxXoffset - 2 * d; ++j) {
+            T[yOff][i][j] = MAX(T[yOff][i - 1][j], T[yOff][i - 1][j + d]);
+        }
+    }
+}
+
+template <typename type> void compute_lookup_table_for_line_erode(type ***T, type *image, int yOff, int line, chordSet *set, PointXY size) {
+    const type MAX_VAL = limits<type>(MAX_VALUE);
+    
+    int y = line + yOff;
+    
+    if (y < 0 || y >= size.y) {
+        for (int i = set->minXoffset; i < size.x + set->maxXoffset; ++i) {
+            T[yOff][0][i] = MAX_VAL;
+        }
+    }
+    else {
+        int maxX = MIN(size.x, size.x + set->maxXoffset);
+        int i = set->minXoffset;
+        
+        for (i; i < 0; ++i) {
+            T[yOff][0][i] = MAX_VAL;
+        }
+        for (i; i < maxX; ++i) {
+            type val = image[INDEX_FROM_XY(i, y, size.x)];
+            T[yOff][0][i] = ISNA(val) ? MAX_VAL : val;
+        }
+        for (i; i < size.x + set->maxXoffset; ++i) {
+            T[yOff][0][i] = MAX_VAL;
+        }
+    }
+    
+    for (int i = 1, d = 1; i <= set->maxN; ++i, d *= 2) {
+        for (int j = set->minXoffset; j <= size.x + set->maxXoffset - 2 * d; ++j) {
+            T[yOff][i][j] = MIN(T[yOff][i - 1][j], T[yOff][i - 1][j + d]);
+        }
+    }
+}
+
+template <typename type> void dilate_line(type ***T, type *input, type *output, chordSet *set, int line, int width) {
+    for (int i = 0; i < width; ++i) {
+        int index = INDEX_FROM_XY(i, line, width);
+        if (ISNA(input[index])) {
+            output[index] = input[index];
+        }
+        else {
+            for (int j = 0; j < set->CLength; ++j) {
+                type v = MAX(T[set->C[j].yOffset][set->C[j].n][i + set->C[j].xOffset1], T[set->C[j].yOffset][set->C[j].n][i + set->C[j].xOffset2]);
+                output[index] = MAX(output[index], v);
+            }
+        }
+    }
+}
+
+template <typename type> void erode_line(type ***T, type *input, type *output, chordSet *set, int line, int width) {
+    for (int i = 0; i < width; ++i) {
+        int index = INDEX_FROM_XY(i, line, width);
+        if (ISNA(input[index])) {
+            output[index] = input[index];
+        }
+        else {
+            for (int j = 0; j < set->CLength; ++j) {
+                type v = MIN(T[set->C[j].yOffset][set->C[j].n][i + set->C[j].xOffset1], T[set->C[j].yOffset][set->C[j].n][i + set->C[j].xOffset2]);
+                output[index] = MIN(output[index], v);
+            }
+        }
+    }
+}
+
+template <typename type> void erode_dilate (type *x, type *res, PointXY size, int nz, int what, chordSet *set, type ***T) {
+    type *tgt, *src;
+    
+    void (*compute_lookup_table_for_line)(type ***, type *, int, int, chordSet *, PointXY);
+    void (*process_line)(type ***, type *, type *, chordSet *, int, int);
+    
+    if (what == ERODE) {
+        process_line = &erode_line<type>;
+        compute_lookup_table_for_line = &compute_lookup_table_for_line_erode<type>;
+    }
+    else {
+        process_line = &dilate_line<type>;
+        compute_lookup_table_for_line = &compute_lookup_table_for_line_dilate<type>;
+    }
+    
+    for (int i = 0; i < nz; i++ ) {
+        tgt = &( res[i * size.x * size.y] );
+        src = &( x[i * size.x * size.y] );
+        for (int j = 0; j < size.x * size.y; ++j) {
+            tgt[j] = what;
+        }
+        for (int j = set->minYoffset; j <= set->maxYoffset; ++j) {
+            compute_lookup_table_for_line(T, src, j, 0, set, size);
+        }
+        process_line(T, src, tgt, set, 0, size.x);
+        for (int j = 1; j < size.y; ++j) {
+            type **first = T[set->minYoffset];
+            for (int k = set->minYoffset; k < set->maxYoffset; ++k) {
+                T[k] = T[k + 1];
+            }
+            T[set->maxYoffset] = first;
+            compute_lookup_table_for_line(T, src, set->maxYoffset, j, set, size);
+            process_line(T, src, tgt, set, j, size.x);
+        }
+    }
+}
+
+template <typename type> void opening_closing(type *x, type *res, PointXY size, int nz, int what, chordSet *set, type ***T) {
+    type * tmp = R_Calloc(size.x * size.y * nz, type);
+    
+    erode_dilate<type>(x, tmp, size, nz, (what-1) % 2, set, T);
+    erode_dilate<type>(tmp, res, size, nz, what % 2, set, T);
+    
+    R_Free(tmp);
+}
+
+template <typename type> void tophat(type *x, type *res, PointXY size, int nz, int what, chordSet *set, type ***T) {
+    int n = size.x * size.y * nz;
+    
+    switch(what) {
+    case TOPHAT_WHITE:
+        opening_closing<type>(x, res, size, nz, OPENING, set, T);
+        for (int i = 0; i < n; ++i) {
+            res[i] = x[i] - res[i];
+        }
+        break;
+    case TOPHAT_BLACK:
+        opening_closing<type>(x, res, size, nz, CLOSING, set, T);
+        for (int i = 0; i < n; ++i) {
+            res[i] = res[i] - x[i];
+        }
+        break;
+    case TOPHAT_SELFCOMPLEMENTARY:
+        type * tmp = R_Calloc(n, type);
+        opening_closing<type>(x, res, size, nz, OPENING, set, T);
+        opening_closing<type>(x, tmp, size, nz, CLOSING, set, T);
+        for (int i = 0; i < n; ++i) {
+            res[i] = res[i] + tmp[i];
+        }
+        R_Free(tmp);
+        break;
+    }
+}
+
+template <typename type> void _morphology(type *x, type *res, PointXY size, int nz, SEXP kernel, int what) {
+    PointXY ksize;
+    ksize.x = INTEGER ( GET_DIM(kernel) )[0];
+    ksize.y = INTEGER ( GET_DIM(kernel) )[1];
+    
+    chordSet set;
+    switch (TYPEOF(kernel)) {
+    case LGLSXP:
+    case INTSXP:
+        set = buildChordSet<int>(INTEGER(kernel), ksize);
+        break;
+    case REALSXP:
+        set = buildChordSet<double>(REAL(kernel), ksize);
+        break;
+    }
+    
+    type ***T = allocate_lookup_table<type>(&set, size.x);
+    
+    switch( what ) {
+    case DILATE:
+    case ERODE:
+        erode_dilate<type>(x, res, size, nz, what, &set, T);
+        break;
+    case OPENING:
+    case CLOSING:
+        opening_closing<type>(x, res, size, nz, what, &set, T);
+        break;
+    case TOPHAT_WHITE:
+    case TOPHAT_BLACK:
+    case TOPHAT_SELFCOMPLEMENTARY:
+        tophat<type>(x, res, size, nz, what, &set, T);
+        break;
+    }
+    
+    free_lookup_table<type>(T, &set);
+    R_Free(set.C);
+}
+
+SEXP morphology (SEXP x, SEXP kernel, SEXP what) {
+    validImage(x,0);
+    validImage(kernel,0);
+    
+    SEXP res = PROTECT( allocVector(TYPEOF(x), XLENGTH(x)) );
+    DUPLICATE_ATTRIB(res, x);
+    
+    int operation = INTEGER(what)[0];
+    
+    PointXY size;
+    size.x = INTEGER ( GET_DIM(x) )[0];
+    size.y = INTEGER ( GET_DIM(x) )[1];
+    int nz = getNumberOfFrames(x,0);
+    
+    switch (TYPEOF(x)) {
+    case LGLSXP:
+    case INTSXP:
+        _morphology<int>(INTEGER(x), INTEGER(res), size, nz, kernel, operation);
+        break;
+    case REALSXP:
+        _morphology<double>(REAL(x), REAL(res), size, nz, kernel, operation);
+        break;
+    }
+    
+    UNPROTECT(1);
+    return res;
+}
diff --git a/src/morphology.h b/src/morphology.h
new file mode 100644
index 0000000..805f4be
--- /dev/null
+++ b/src/morphology.h
@@ -0,0 +1,17 @@
+#ifndef EBIMAGE_MORPH_H
+#define EBIMAGE_MORPH_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+  
+SEXP morphology (SEXP, SEXP, SEXP);
+  
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/src/nativeRaster.c b/src/nativeRaster.c
new file mode 100644
index 0000000..9566cd2
--- /dev/null
+++ b/src/nativeRaster.c
@@ -0,0 +1,72 @@
+#include "normalize.h"
+#include "tools.h"
+
+#include <stdint.h>
+#include <R_ext/Error.h>
+
+/* -------------------------------------------------------------------------
+as.nativeRaster C implementation
+
+Copyright (c) 2015 Andrzej Oles
+------------------------------------------------------------------------- */
+
+SEXP nativeRaster (SEXP _in) {
+  int nprotect = 0, x, y, c, i, j;
+  uint32_t *out;
+  SEXP res, nch;
+  
+  x = INTEGER (GET_DIM(_in))[0];
+  y = INTEGER (GET_DIM(_in))[1];
+  c = COLOR_MODE(_in);
+  
+  // pointers to color channels
+  double *rgba[4] = {NULL, NULL, NULL, NULL};
+  
+  if (c != MODE_COLOR)
+    rgba[0] = rgba[1] = rgba[2] = REAL(_in);
+  else {
+    int nc = getNumberOfChannels(_in, c);
+    if (nc > 4) nc = 4;
+    for(int i = 0; i < nc; i++)
+      rgba[i] = &( REAL(_in)[ i * x * y ] );
+  }
+  
+  res = PROTECT( allocMatrix(INTSXP, y, x) );
+  nprotect++;
+  
+  // initialize
+  out = INTEGER(res);
+  memset (out, 0, x*y*sizeof(uint32_t) );
+  
+  for(j=0; j<4; j++) {
+    double *data = rgba[j];
+    int mul = (int) pow(2, j*8);
+    
+    for (i=0; i<x*y; i++) {
+      int nel = 0; //zero fill empty channels      
+      
+      if (data!=NULL) {        
+        double el = data[i];
+        // clip
+        if (el < 0.0) el = 0;
+        else if (el > 1.0) el = 1.0;
+        // cast to integer representation
+        nel = (int) round(el * 255);
+      }
+      // full opaque if alpha channel missing
+      else if (j==3) 
+        nel = 255;
+      
+      out[i] += nel * mul;
+    }
+  }
+  
+  //set class
+  setAttrib(res, R_ClassSymbol, mkString("nativeRaster"));
+  nch = PROTECT( ScalarInteger(4) ); nprotect++;
+  setAttrib(res, install("channels"), nch);
+  
+  UNPROTECT (nprotect);
+  
+  return res;
+}
diff --git a/src/nativeRaster.h b/src/nativeRaster.h
new file mode 100644
index 0000000..dcd7013
--- /dev/null
+++ b/src/nativeRaster.h
@@ -0,0 +1,17 @@
+#ifndef EBIMAGE_NATIVERASTER_H
+#define EBIMAGE_NATIVERASTER_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SEXP nativeRaster (SEXP);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/src/normalize.c b/src/normalize.c
new file mode 100644
index 0000000..4acad71
--- /dev/null
+++ b/src/normalize.c
@@ -0,0 +1,100 @@
+#include "normalize.h"
+#include "tools.h"
+
+/* -------------------------------------------------------------------------
+Frame-based image normalization
+
+Copyright (c) 2007 Oleg Sklyar
+See: ../LICENSE for license, LGPL
+------------------------------------------------------------------------- */
+
+#include <R_ext/Error.h>
+
+/*----------------------------------------------------------------------- */
+
+double min, max, diff;
+
+void range (double *src, double *tgt, int n, int frame) {
+  int i;
+  char *msg;
+  min = DBL_MAX;
+  max = -DBL_MAX;
+  for ( i = 0; i < n; i++ ) {
+    if ( src[i] < min ) min = src[i];
+    if ( src[i] > max ) max = src[i];
+  }
+  diff = max - min;
+  if ( diff == 0 ) {
+    memcpy(tgt, src, n * sizeof(double));
+    msg = "cannot be normalized as its diff(range) is 0";
+    if ( frame==-1 )
+      warning("image %s", msg);
+    else
+      warning("frame %d %s", frame, msg);
+  }
+}
+
+SEXP
+normalize (SEXP x, SEXP separate, SEXP outrange, SEXP inrange) {
+  int nprotect, nx, ny, nz, im, i, sep;
+  double *src, *tgt, from, to, val;
+  SEXP res;
+
+  nprotect = 0;
+
+  sep = LOGICAL(separate)[0];
+  
+  nx = INTEGER (GET_DIM(x))[0];
+  ny = INTEGER (GET_DIM(x))[1];
+  nz = getNumberOfFrames(x,0);
+  
+  PROTECT( res = allocVector(REALSXP, XLENGTH(x)) );
+  nprotect++;
+  DUPLICATE_ATTRIB(res, x);
+  
+  src = REAL(x);
+  tgt = REAL(res);
+  
+  from = to = 0;
+  
+  // clip and set min/max values to input range 
+  if ( inrange != R_NilValue ) {
+    min  = REAL(inrange)[0];
+    max  = REAL(inrange)[1];
+    diff = max - min;
+    for ( i = 0; i < nx * ny * nz; i++ ) {
+      val = src[i];
+      if ( val < min ) val = min;
+      if ( val > max ) val = max;
+      tgt[i] = val;
+    }
+  }
+  // calculate global range
+  else if ( !sep ) {
+    range(src, tgt, nx*ny*nz, -1);
+  }
+
+  // normalize only if normalization range is valid
+  if ( outrange != R_NilValue && (diff!=0 || sep) ) {
+    from = REAL(outrange)[0];
+    to   = REAL(outrange)[1];
+  
+    for ( im = 0; im < nz; im++ ) {
+      tgt = &( REAL(res)[ im * nx * ny ] );
+      if ( inrange==R_NilValue ) 
+        src = &( REAL(x)[ im * nx * ny ] );
+      else // use pre-initialized tgt as src
+        src = tgt;
+      
+      if ( sep ) 
+        range(src, tgt, nx * ny, im+1);
+      if ( diff != 0 ) {
+        for ( i = 0; i < nx * ny; i++ )
+          tgt[i] = ( src[i] - min ) / diff * (to - from) + from;
+      }
+    }
+  }
+  
+  UNPROTECT (nprotect);
+  return res;
+}
diff --git a/src/normalize.h b/src/normalize.h
new file mode 100644
index 0000000..4ff8a33
--- /dev/null
+++ b/src/normalize.h
@@ -0,0 +1,17 @@
+#ifndef EBIMAGE_NORMALIZE_H
+#define EBIMAGE_NORMALIZE_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SEXP normalize (SEXP, SEXP, SEXP, SEXP);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/src/objects.c b/src/objects.c
new file mode 100644
index 0000000..ef7663a
--- /dev/null
+++ b/src/objects.c
@@ -0,0 +1,296 @@
+#include "objects.h"
+
+/* -------------------------------------------------------------------------
+Counting objects determined in segmentations like watershed
+Copyright (c) 2006 Oleg Sklyar
+See: ../LICENSE for license, LGPL
+------------------------------------------------------------------------- */
+
+#include "tools.h"
+#include <R_ext/Error.h>
+#include <stdio.h>
+
+/*----------------------------------------------------------------------- */
+/* paints features on the target image with given colors and opacs    */
+SEXP
+paintObjects (SEXP x, SEXP ref, SEXP _opac, SEXP _col, SEXP _thick) {
+    SEXP res;
+    int nx, ny, nz, im, index, thick;
+    int i, j;
+    double *opac, *col;
+    double *obj, *src, *tgt, dp, val;
+    ColorStrides src_strides, tgt_strides;
+
+    validImage(x,0);
+    validImage(ref,0);
+
+    nx = INTEGER(GET_DIM(x))[0];
+    ny = INTEGER(GET_DIM(x))[1];
+    nz = getNumberOfFrames(x, 0);
+
+    opac = REAL(_opac);
+    col = REAL(_col);
+    thick = LOGICAL(_thick)[0];
+
+    PROTECT( res = allocVector(REALSXP, XLENGTH(ref)) );
+    DUPLICATE_ATTRIB(res, ref);
+    
+    src = REAL(ref);
+    tgt = REAL(res);
+    
+    for (im = 0; im < nz; im++) {
+      obj = &( REAL(x)[ im * nx * ny ] );
+      getColorStrides(ref, im, &src_strides);
+      getColorStrides(res, im, &tgt_strides);
+      
+      for ( j = 0; j < ny; j++ ) {
+        for ( i = 0; i < nx; i++ ) {      
+          val = obj[j*nx + i];
+          
+          if (thick) {
+            /* object border */
+            if (  ( i > 0       && obj[j*nx + i-1] != val ) ||
+                  ( i < nx - 1  && obj[j*nx + i+1] != val ) ||
+                  ( j > 0       && obj[(j-1)*nx + i] != val ) ||
+                  ( j < ny - 1  && obj[(j+1)*nx + i] != val ) ) 
+              index = 0;
+            else {
+              /* background */
+              if ( val <= 0 )
+                index = -1;
+              /* if image edge index=2, if object body index=1 */
+              else
+                index = (i==0 || i==nx-1 || j==0 || j==ny-1 || val < 1) ? 2 : 1;
+            }
+          }
+          
+          else {
+            /* background */
+            if ( val <= 0 )
+              index = -1;
+            else {
+              /* object border */
+              if (  ( i > 0       && obj[j*nx + i-1] != val ) ||
+                    ( i < nx - 1  && obj[j*nx + i+1] != val ) ||
+                    ( j > 0       && obj[(j-1)*nx + i] != val ) ||
+                    ( j < ny - 1  && obj[(j+1)*nx + i] != val ) ) 
+                index = 0;
+              /* if image edge index=2, if object body index=1 */
+              else
+                index = (i==0 || i==nx-1 || j==0 || j==ny-1 || val < 1) ? 2 : 1;
+            }
+          }
+          
+          // duplicate pixels
+          if ( index == -1 ) {
+            if ( src_strides.r!=-1 )
+              tgt[tgt_strides.r+j*nx + i] = src[src_strides.r+j*nx + i];
+            if ( src_strides.g!=-1 )
+              tgt[tgt_strides.g+j*nx + i] = src[src_strides.g+j*nx + i];
+            if ( src_strides.b!=-1 ) 
+              tgt[tgt_strides.b+j*nx + i] = src[src_strides.b+j*nx + i];
+          }
+          else {
+            if ( src_strides.r!=-1 )
+              tgt[tgt_strides.r+j*nx + i] = src[src_strides.r+j*nx + i]*(1-opac[index]) + col[index]*opac[index];
+            if ( src_strides.g!=-1 )
+              tgt[tgt_strides.g+j*nx + i] = src[src_strides.g+j*nx + i]*(1-opac[index]) + col[index+3]*opac[index];
+            if ( src_strides.b!=-1 )
+              tgt[tgt_strides.b+j*nx + i] = src[src_strides.b+j*nx + i]*(1-opac[index]) + col[index+6]*opac[index];	
+          }
+          
+        }
+      }
+    }
+
+    UNPROTECT (1);
+    return res;
+}
+
+
+/*----------------------------------------------------------------------- */
+SEXP
+rmObjects (SEXP x, SEXP _index, SEXP _reenum) {
+    SEXP res, index;
+    int nx, ny, nz, sizexy, i, j, im, nobj, * indexes, idx, val, reenum;
+    int * src, * tgt;
+
+    validImage(x,0);
+
+    nx = INTEGER ( GET_DIM(x) )[0];
+    ny = INTEGER ( GET_DIM(x) )[1];
+    nz = getNumberOfFrames(x,0);
+    
+    reenum = LOGICAL(_reenum)[0];
+    
+    PROTECT( res = allocVector(INTSXP, XLENGTH(x)) );
+    DUPLICATE_ATTRIB(res, x);
+  
+    sizexy = nx * ny;
+  
+    for ( im = 0; im < nz; im++ ) {
+        /* get image data */
+        src = &( INTEGER(x)[ im * sizexy ] );
+        tgt = &( INTEGER(res)[ im * sizexy ] );
+        
+        /* get number of objects -- max index */
+        nobj = 0;
+        for ( i = 0; i < sizexy; i++ )
+            if ( src[i] > nobj ) nobj = src[i]; // NA_integer_ is -2147483648
+            
+        indexes = (int *) R_Calloc((nobj + 1), int);
+        
+        /* reset indices of removed objects */
+        if ( _index!=R_NilValue ) {
+          index = VECTOR_ELT (_index, im);
+          
+          for ( i = 0; i <= nobj; i++ )
+            indexes[i] = i;
+          
+          for ( i = 0; i < LENGTH(index); i++ ) {
+            idx = INTEGER(index)[i];
+            if (idx > 0 && idx <= nobj)
+              indexes[idx] = 0;
+          }
+        }
+        else {
+          for ( i = 0; i < sizexy; i++ ) {
+            val = src[i];
+            if ( val > 0 )
+              indexes[val] = val;
+          }
+        }
+        
+        /* reenumerate object indices */
+        if ( reenum ) {
+          j = 1;
+          for ( i = 1; i <= nobj; i++ ) {
+              if ( indexes[i] > 0 ) {
+                  indexes[i] = j;
+                  j++;
+              }
+          }
+        }
+        
+        /* reset image */
+        for ( i = 0; i < sizexy; i++ ) {
+            val = src[i];
+            tgt[i] = ( val > 0 ) ? indexes[val] : val; // support NA's
+        }
+        
+        Free (indexes);
+    }
+
+    UNPROTECT (1);
+    return res;
+}
+
+
+/*----------------------------------------------------------------------- */
+SEXP
+stackObjects (SEXP obj, SEXP ref, SEXP _bgcol, SEXP xy_list, SEXP extension) {
+  SEXP res, dim, xys, img;
+  int nx, ny, nz, nc, nprotect, im, x, y, i, j, pxi, nobj, index;
+  double *dobj, *dref, *xy, xx, yy,  *bgcol;
+  double * dst;
+  int ext = floor(REAL(extension)[0]);
+  int snx = 2 * ext + 1;
+  int sny = 2 * ext + 1;
+  int mode = COLOR_MODE(ref);
+  int nbChannels = getNumberOfChannels(ref, mode);
+  int stride, shift;
+ 
+  nx = INTEGER ( GET_DIM(obj) )[0];
+  ny = INTEGER ( GET_DIM(obj) )[1];
+  nz = getNumberOfFrames(obj, 0);
+  bgcol = REAL(_bgcol);
+  nprotect = 0;
+
+  // allow only up to 3 color channels
+  if (nbChannels>3) nbChannels = 3;
+
+  if (nz > 1) {
+    PROTECT(res = allocVector(VECSXP, nz));
+    nprotect++;
+  }
+  
+  for (im = 0; im < nz; im++) {
+    // set dobj, dref and strides
+    dobj = &(REAL(obj)[im * nx * ny]);
+    dref = REAL(ref);
+
+    // get number of objects = max index
+    nobj = 0;
+    for (i = 0; i < nx * ny; i++) if (dobj[i] > nobj) nobj = dobj[i];
+
+    if (nobj>0) {
+      // create stack
+      PROTECT(img = allocVector(REALSXP, nobj * snx * sny * nbChannels));
+      nprotect++;
+      DUPLICATE_ATTRIB(img, ref);
+      
+      dst = REAL(img);
+      
+      // bg color initialization
+      for (j=0; j<nobj; j++) {
+        for(nc=0; nc<nbChannels; nc++) {
+          shift = j*nbChannels*snx*sny + nc*snx*sny;
+          for (i=0; i<snx*sny; i++) dst[i+shift] = bgcol[nc];
+        }
+      }
+      
+      if (mode==MODE_GRAYSCALE) {
+      	PROTECT (dim = allocVector( INTSXP, 3 ));
+      	nprotect++;
+      	INTEGER (dim)[0] = snx;
+      	INTEGER (dim)[1] = sny;
+      	INTEGER (dim)[2] = nobj;
+      }
+      else if (mode==MODE_COLOR) {
+      	PROTECT (dim = allocVector( INTSXP, 4));
+      	nprotect++;
+      	INTEGER (dim)[0] = snx;
+      	INTEGER (dim)[1] = sny;
+      	INTEGER (dim)[2] = nbChannels;
+      	INTEGER (dim)[3] = nobj;
+      }
+      SET_DIM (img, dim);
+      
+      // get xy
+      if (nz == 1) xys = xy_list;
+      else xys = VECTOR_ELT(xy_list, im);
+      if (xys == R_NilValue || INTEGER(GET_DIM(xys))[0] != nobj || INTEGER(GET_DIM(xys))[1] < 2) continue;
+      xy = REAL(xys);
+
+      // copy ref
+      for (x = 0; x < nx; x++) {
+      	for (y = 0; y < ny; y++) {
+      	  index = dobj[x + y * nx] - 1; // background index 0 is not kept
+      	  if (index < 0) continue;
+      	 
+      	  // target frame x, y coordinates
+      	  xx = x - floor(xy[index]) + ext + 1 ;
+      	  yy = y - floor(xy[index + nobj]) + ext + 1;
+      	  
+      	  if ( xx < 0 || xx >= snx || yy < 0 || yy >= sny ) continue;
+      	  else {
+      	    pxi = xx + yy * snx;
+            for(nc=0; nc<nbChannels; nc++) {
+              stride = im*nbChannels*nx*ny+nc*nx*ny;
+              shift = index*nbChannels*snx*sny + nc*snx*sny;
+              dst[pxi + shift] = dref[x + y * nx + stride];
+            }  
+      	  }
+      	}
+      }
+    } // nobj>0
+    else {
+      img = R_NilValue;
+    }
+    if (nz == 1) res = img;
+    else SET_VECTOR_ELT(res, im, img);
+  } // im
+
+  UNPROTECT( nprotect );
+  return res;
+}
diff --git a/src/objects.h b/src/objects.h
new file mode 100644
index 0000000..3e1a981
--- /dev/null
+++ b/src/objects.h
@@ -0,0 +1,19 @@
+#ifndef EBIMAGE_OBJECTS_H
+#define EBIMAGE_OBJECTS_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SEXP paintObjects (SEXP, SEXP, SEXP, SEXP, SEXP);
+SEXP rmObjects (SEXP, SEXP, SEXP);
+SEXP stackObjects (SEXP, SEXP, SEXP, SEXP, SEXP);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/src/ocontour.c b/src/ocontour.c
new file mode 100644
index 0000000..f86373b
--- /dev/null
+++ b/src/ocontour.c
@@ -0,0 +1,92 @@
+#include <stdio.h>
+#include <math.h>
+#include <R.h>
+#include <Rdefines.h>
+#include "ocontour.h"
+
+#define MAX_NB_POINTS 65535
+
+static int rotr[8]={-1,-1,-1,0,1,1,1,0};
+static int rotc[8]={-1,0,1,1,1,0,-1,-1};
+static int dir [9]={5,4,3,6,-1,2,7,0,1};
+
+SEXP ocontour(SEXP _image) {
+  int *image, width, height;
+  int i, j, k, direction, nbCells;
+  int r, c, ocr, occ, ndirection, nr, nc;
+  int nprotect=0, nboc, *octemp;
+  SEXP _res, _oc;
+  
+  // Transfer variables
+  height=INTEGER(GET_DIM(_image))[0];
+  width=INTEGER(GET_DIM(_image))[1];
+  image=INTEGER(_image);
+  
+  // Compute number of objects
+  nbCells=0;
+  for (i=0; i<width*height; i++) {
+    if (image[i]>nbCells) nbCells=image[i];
+  }
+ 
+  // Output result
+  _res = PROTECT(allocVector(VECSXP, nbCells));
+  nprotect++;
+
+  // Temporary vector to store the current oriented contour
+  octemp=(int *)R_Calloc(MAX_NB_POINTS*2+1, int);
+  //objs = (int *)R_Calloc(nbCells, int);
+
+  // For each object, except the 0-th one (background)
+  for (k=1; k<=nbCells; k++) {
+    nboc=0;
+
+    // Find min (r,c) starting point for object k
+    i=0;
+    while (image[i]!=k && i<width*height) i++;
+    if (i!=width*height) {
+      r=i%height;
+      c=i/height;
+
+      // Starting points of the oriented coutour
+      ocr=r;
+      occ=c;
+
+      // Turn around the edges of the object
+      direction=0;
+      do {
+      	// Stores (r,c) in the oriented contour matrix
+      	octemp[nboc]=r;
+      	octemp[nboc+MAX_NB_POINTS]=c;
+      	if (nboc<MAX_NB_POINTS) nboc++;
+        
+      	// Change direction
+      	for (j=0; j<8; j++) {
+      	  ndirection=(j+direction)%8;
+      	  nr=r+rotr[ndirection];
+      	  nc=c+rotc[ndirection];
+      	  if (nr>=0 && nc>=0 && nr<height && nc<width) {
+      	    if (image[nr+nc*height]==k) break;
+      	  }
+      	}
+      	if (j!=8) {
+      	  direction=dir[(nr-r+1)+3*(nc-c+1)];
+      	  r=nr;
+      	  c=nc;
+      	}
+      } while (r!=ocr || c!=occ);
+    }
+    // Copy octemp in an element of _res
+    _oc = PROTECT(allocMatrix(INTSXP, nboc, 2));
+    nprotect++;
+    SET_VECTOR_ELT(_res, k-1, _oc);
+    memcpy(INTEGER(_oc), octemp, nboc*sizeof(int));
+    memcpy(&(INTEGER(_oc)[nboc]), &(octemp[MAX_NB_POINTS]), nboc*sizeof(int));
+    
+  } // k
+
+  // Free oct
+  R_Free(octemp);
+
+  UNPROTECT (nprotect);
+  return(_res);
+}
diff --git a/src/ocontour.h b/src/ocontour.h
new file mode 100644
index 0000000..9f050ef
--- /dev/null
+++ b/src/ocontour.h
@@ -0,0 +1,17 @@
+#ifndef EBIMAGE_OCONTOUR_H
+#define EBIMAGE_OCONTOUR_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SEXP ocontour (SEXP);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/src/propagate.cpp b/src/propagate.cpp
new file mode 100644
index 0000000..779c733
--- /dev/null
+++ b/src/propagate.cpp
@@ -0,0 +1,216 @@
+/* -------------------------------------------------------------------------
+Implementation of the Voronoi-based segmentation on image manifolds [1]
+
+The code below is based on the 'IdentifySecPropagateSubfunction.cpp'
+module (revision 4730) of CellProfiler [2]. CellProfiler is released
+under the terms of GPL, however the LGPL license was granted by T. Jones
+on Feb 7, 07 to use the code in the above file for this project.
+
+[1] T. Jones, A. Carpenter and P. Golland,
+    "Voronoi-Based Segmentation of Cells on Image Manifolds"
+    CVBIA05 (535-543), 2005
+
+[2] CellProfiler: http://www.cellprofiler.org
+
+Copyright (C) of the original CellProfiler code:
+ - Anne Carpenter <carpenter at wi.mit.edu>
+ - Thouis Jones <thouis at csail.mit.edu>
+ - In Han Kang <inthek at mit.edu>
+
+Copyright (C) of the implementation below:
+ - Oleg Sklyar <osklyar at ebi.ac.uk>
+ - Wolfgang Huber <huber at ebi.ac.uk>
+
+See: ../LICENSE for license, LGPL.
+
+------------------------------------------------------------------------- */
+
+#include <math.h>
+#include <queue>
+#include <vector>
+#include <iostream>
+#include "propagate.h"
+#include <R_ext/Error.h>
+#include "tools.h"
+using namespace std;
+
+#define IJ(i,j) ((j)*m+(i))
+
+class Pixel { 
+public:
+  double distance;
+  unsigned int i, j;
+  int label;
+  Pixel (double ds, unsigned int ini, unsigned int inj, int l) : 
+    distance(ds), i(ini), j(inj), label(l) {}
+};
+
+struct Pixel_compare { 
+ bool operator() (const Pixel& a, const Pixel& b) const 
+ { return a.distance > b.distance; }
+};
+
+typedef priority_queue<Pixel, vector<Pixel>, Pixel_compare> PixelQueue;
+
+static double
+clamped_fetch(double *image, 
+              int i, int j,
+              int m, int n)
+{
+  if (i < 0) i = 0;
+  if (i >= m) i = m-1;
+  if (j < 0) j = 0;
+  if (j >= n) j = n-1;
+
+  return (image[IJ(i,j)]);
+}
+
+static double
+Difference(double *image,
+           int i1,  int j1,
+           int i2,  int j2,
+           unsigned int m, unsigned int n,
+           double lambda)
+{
+  int delta_i, delta_j;
+  double pixel_diff = 0.0;
+
+  /* At some point, the width over which differences are calculated should probably be user controlled. */
+  for (delta_j = -1; delta_j <= 1; delta_j++) {
+    for (delta_i = -1; delta_i <= 1; delta_i++) {
+      pixel_diff += fabs(clamped_fetch(image, i1 + delta_i, j1 + delta_j, m, n) - 
+                         clamped_fetch(image, i2 + delta_i, j2 + delta_j, m, n));
+    }
+  } 
+  double dEucl = (double(i1)-i2)*(double(i1)-i2) + (double(j1)-j2)*(double(j1)-j2);
+  return (sqrt((pixel_diff*pixel_diff + lambda*dEucl)/(1.0 + lambda)));
+  //return (sqrt(pixel_diff*pixel_diff + (fabs((double) i1 - i2) + fabs((double) j1 - j2)) * lambda * lambda));
+}
+
+static void
+push_neighbors_on_queue(PixelQueue &pq, double dist,
+                        double *image,
+                        unsigned int i, unsigned int j,
+                        unsigned int m, unsigned int n,
+                        double lambda, int label,
+                        int *labels_out)
+{
+  /* TODO: Check if the neighbor is already labelled. If so, skip pushing. 
+   */    
+    
+  /* 4-connected */
+  if (i > 0) {
+    if ( 0 == labels_out[IJ(i-1,j)] ) // if the neighbor was not labeled, do pushing
+      pq.push(Pixel(dist + Difference(image, i, j, i-1, j, m, n, lambda), i-1, j, label));
+  }                                                                   
+  if (j > 0) {                                                        
+    if ( 0 == labels_out[IJ(i,j-1)] )   
+      pq.push(Pixel(dist + Difference(image, i, j, i, j-1, m, n, lambda), i, j-1, label));
+  }                                                                   
+  if (i < (m-1)) {
+    if ( 0 == labels_out[IJ(i+1,j)] ) 
+      pq.push(Pixel(dist + Difference(image, i, j, i+1, j, m, n, lambda), i+1, j, label));
+  }                                                                   
+  if (j < (n-1)) {              
+    if ( 0 == labels_out[IJ(i,j+1)] )   
+      pq.push(Pixel(dist + Difference(image, i, j, i, j+1, m, n, lambda), i, j+1, label));
+  } 
+
+  /* 8-connected */
+  if ((i > 0) && (j > 0)) {
+    if ( 0 == labels_out[IJ(i-1,j-1)] )   
+      pq.push(Pixel(dist + Difference(image, i, j, i-1, j-1, m, n, lambda), i-1, j-1, label));
+  }                                                                       
+  if ((i < (m-1)) && (j > 0)) {                                           
+    if ( 0 == labels_out[IJ(i+1,j-1)] )   
+      pq.push(Pixel(dist + Difference(image, i, j, i+1, j-1, m, n, lambda), i+1, j-1, label));
+  }                                                                       
+  if ((i > 0) && (j < (n-1))) {                                           
+    if ( 0 == labels_out[IJ(i-1,j+1)] )   
+      pq.push(Pixel(dist + Difference(image, i, j, i-1, j+1, m, n, lambda), i-1, j+1, label));
+  }                                                                       
+  if ((i < (m-1)) && (j < (n-1))) {
+    if ( 0 == labels_out[IJ(i+1,j+1)] )   
+      pq.push(Pixel(dist + Difference(image, i, j, i+1, j+1, m, n, lambda), i+1, j+1, label));
+  }
+  
+}
+
+static void _propagate(int *labels_in, double *im_in,
+                      int *mask_in, int *labels_out,
+                      double *dists,
+                      unsigned int m, unsigned int n,
+                      double lambda)
+{
+  /* TODO: Initialization of nuclei labels can be simplified by labeling
+   *       the nuclei region first, then make the queue prepared for 
+   *       propagation
+   */
+  unsigned int i, j;
+  PixelQueue pixel_queue;
+
+  /* initialize dist to Inf, read labels_in and wrtite out to labels_out */
+  for (j = 0; j < n; j++) {
+    for (i = 0; i < m; i++) {
+      dists[IJ(i,j)] = R_PosInf;            
+      labels_out[IJ(i,j)] = labels_in[IJ(i,j)];
+    }
+  }
+  /* if the pixel is already labeled (i.e, labeled in labels_in) and within a mask, 
+   * then set dist to 0 and push its neighbors for propagation */
+  for (j = 0; j < n; j++) {
+    for (i = 0; i < m; i++) {        
+      int label = labels_in[IJ(i,j)];
+      if ((label > 0) && (mask_in[IJ(i,j)])) {
+        dists[IJ(i,j)] = 0.0;
+        push_neighbors_on_queue(pixel_queue, 0.0, im_in, i, j, m, n, lambda, label, labels_out);
+      }
+    }
+  }
+
+  while (! pixel_queue.empty()) {
+    Pixel p = pixel_queue.top();
+    pixel_queue.pop();
+    if (! mask_in[IJ(p.i, p.j)]) continue;
+    if ((dists[IJ(p.i, p.j)] > p.distance) && (mask_in[IJ(p.i,p.j)])) {
+      dists[IJ(p.i, p.j)] = p.distance;
+      labels_out[IJ(p.i, p.j)] = p.label;
+      push_neighbors_on_queue(pixel_queue, p.distance, im_in, p.i, p.j, m, n, lambda, p.label, labels_out);
+    }
+  }
+}
+
+// R entry point
+SEXP propagate(SEXP _x, SEXP _seeds, SEXP _mask, SEXP _lambda) {
+  SEXP res;
+  int nx = INTEGER(GET_DIM(_x))[0];
+  int ny = INTEGER(GET_DIM(_x))[1];
+  int nz = getNumberOfFrames(_x, 0);
+  double lambda = REAL(_lambda)[0];
+  int nprotect = 0;
+
+  PROTECT( res = allocVector(INTSXP, XLENGTH(_x)) );
+  nprotect++;
+  DUPLICATE_ATTRIB(res, _x);
+
+  double *dists = (double *)R_Calloc(nx*ny, double);
+  int *mask;
+  if (_mask == R_NilValue) {
+    mask = (int *) R_Calloc(nx*ny, int);
+    for (int i=0; i<nx*ny; mask[i++]=1);
+  }
+  
+  for (int im=0; im<nz; im++) {
+    double *x = &( REAL(_x)[im*nx*ny]);
+    int *tgt = &( INTEGER(res)[im*nx*ny]);
+    int *seeds = &( INTEGER(_seeds)[im*nx*ny]);
+    if (_mask != R_NilValue) mask = &( INTEGER(_mask)[im*nx*ny]);
+
+    _propagate(seeds, x, mask, tgt, dists, nx, ny, lambda); 
+  }
+  
+  if (_mask == R_NilValue) R_Free(mask);
+  R_Free(dists);
+  UNPROTECT( nprotect );
+  return res;
+}
diff --git a/src/propagate.h b/src/propagate.h
new file mode 100644
index 0000000..28eb57e
--- /dev/null
+++ b/src/propagate.h
@@ -0,0 +1,17 @@
+#ifndef EBIMAGE_PROPAGATE_H
+#define EBIMAGE_PROPAGATE_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SEXP propagate (SEXP, SEXP, SEXP, SEXP);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/src/spatial.c b/src/spatial.c
new file mode 100644
index 0000000..bec4db1
--- /dev/null
+++ b/src/spatial.c
@@ -0,0 +1,123 @@
+#include <R.h>
+#include <Rdefines.h>
+#include "spatial.h"
+#include "tools.h"
+
+#define PEEKPIXEL(x, y, w, h, a, bg) ((x)<0 || (x)>=w || (y)<0 || (y)>=h) ? (bg) : a[INDEX_FROM_XY(x, y, w)]
+
+SEXP affine(SEXP _a, SEXP _d, SEXP _b, SEXP _m, SEXP _filter, SEXP _antialias) {
+  SEXP res;
+  int width, height, nz, framesize;
+  int owidth, oheight;
+  int filter, antialias;
+  double *a, *b, *m, *tgt;
+  
+  // check image validity
+  validImage(_a, 0); 
+  
+  // initialize width, height, nz
+  width = INTEGER(GET_DIM(_a))[0];
+  height = INTEGER(GET_DIM(_a))[1];
+  framesize = width * height;
+  nz = getNumberOfFrames(_a, 0);
+  
+  const int minx = -1, miny = -1, maxx = width-1, maxy = height-1;
+  
+  // initialize a, m, filter
+  a = REAL(_a);
+  b = REAL(_b);
+  m = REAL(_m);
+  
+  filter = INTEGER(_filter)[0];
+  antialias = LOGICAL(_antialias)[0];
+  
+  res = PROTECT( allocArray(REALSXP, _d) );
+  DUPLICATE_ATTRIB(res, _a);
+  SET_DIM(res, _d); //restore proper dimensions overwritten in the previous line
+  
+  owidth = INTEGER(_d)[0];
+  oheight = INTEGER(_d)[1];
+  tgt = REAL(res);
+  
+  // apply transform
+  for (int z=0, i=0; z<nz; z++, a += framesize) {
+    double bg = b[z];
+    for (int y=0; y<oheight; y++) { 
+      for (int x=0; x<owidth; x++) {
+        double val;
+        double tx = m[0]*(x+.5) + m[1]*(y+.5) + m[2];
+        double ty = m[3]*(x+.5) + m[4]*(y+.5) + m[5];
+        
+        // bilinear filter?
+        if (filter==1) {
+          tx -= .5;
+          ty -= .5;
+          int ftx = floor(tx);
+          int fty = floor(ty);
+          
+          if ( ftx>=minx && fty>=miny && ftx<=maxx && fty<=maxy) {
+            double dx = tx-ftx;
+            double dy = ty-fty;
+            
+            if (antialias==1 || (ftx>minx && fty>miny && ftx<maxx && fty<maxy)) {
+              double pa = PEEKPIXEL(ftx, fty, width, height, a, bg);
+              double pb = PEEKPIXEL(ftx+1, fty, width, height, a, bg);
+              double pc = PEEKPIXEL(ftx, fty+1, width, height, a, bg);
+              double pd = PEEKPIXEL(ftx+1, fty+1, width, height, a, bg);
+              val = (1-dy)*(pa*(1-dx) + pb*dx) + dy*(pc*(1-dx) + pd*dx);
+            }
+            else {
+              if (ftx==minx) {
+                // upper left corner
+                if (fty==miny) {
+                  val = a[INDEX_FROM_XY(ftx+1, fty+1, width)];
+                }
+                // lower left corner
+                else if (fty==maxy) {
+                  val = a[INDEX_FROM_XY(ftx+1, fty, width)];
+                }
+                // left border
+                else {
+                  val = (1-dy) * a[INDEX_FROM_XY(ftx+1, fty, width)] + dy * a[INDEX_FROM_XY(ftx+1, fty+1, width)];
+                }
+              }
+              else if (ftx==maxx) {
+                // upper right corner
+                if (fty==miny) {
+                  val = a[INDEX_FROM_XY(ftx, fty+1, width)];
+                }
+                // lower right corner
+                else if (fty==maxy) {
+                  val = a[INDEX_FROM_XY(ftx, fty, width)];
+                }
+                // right border
+                else {
+                  val = (1-dy) * a[INDEX_FROM_XY(ftx, fty, width)] + dy * a[INDEX_FROM_XY(ftx, fty+1, width)];
+                }
+              } else {
+                // top border
+                if (fty==miny) {
+                  val = (1-dx) * a[INDEX_FROM_XY(ftx, fty+1, width)] + dx * a[INDEX_FROM_XY(ftx+1, fty+1, width)];
+                }
+                // bootom border
+                else {
+                  val = (1-dx) * a[INDEX_FROM_XY(ftx, fty, width)] + dx * a[INDEX_FROM_XY(ftx+1, fty, width)];
+                }
+              }
+            }
+          }
+          else val = bg;
+        }
+        else {
+          int ftx = floor(tx);
+          int fty = floor(ty);
+          val = PEEKPIXEL(ftx, fty, width, height, a, bg);
+        }
+        tgt[i++] = val;
+      }
+    }
+  }
+  
+  UNPROTECT(1);
+  return res;
+}
diff --git a/src/spatial.h b/src/spatial.h
new file mode 100644
index 0000000..17fb2fa
--- /dev/null
+++ b/src/spatial.h
@@ -0,0 +1,17 @@
+#ifndef EBIMAGE_SPATIAL_H
+#define EBIMAGE_SPATIAL_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SEXP affine (SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/src/thresh.cpp b/src/thresh.cpp
new file mode 100644
index 0000000..2b8930a
--- /dev/null
+++ b/src/thresh.cpp
@@ -0,0 +1,127 @@
+#include "thresh.h"
+#include "tools.h"
+
+/* -------------------------------------------------------------------------
+Adaptive thresholding
+Uses mean value estimated on a square frame, to speed up calculations
+the sum is not calculated for the full frame, but updated on the sum of
+a columns added and removed after the frame is replaced
+
+Copyright (c) 2006 Oleg Sklyar
+See: ../LICENSE for license, LGPL
+------------------------------------------------------------------------- */
+
+#include <R_ext/Error.h>
+
+#define BG 0
+#define FG 1
+
+template <class T> void _thresh(T *, int *, int, int, int, int, double);
+
+/*----------------------------------------------------------------------- */
+SEXP
+thresh (SEXP x, SEXP param) {
+    int nx, ny, nz, dx, dy, i, sizexy, shift, * tgt;
+    double offset;
+    SEXP res;
+    
+    validImage(x,0);
+
+    nx = INTEGER( GET_DIM(x) )[0];
+    ny = INTEGER( GET_DIM(x) )[1];
+    nz = getNumberOfFrames(x,0);
+
+    dx = (int)( REAL(param)[0] );
+    dy = (int)( REAL(param)[1] );
+    offset = REAL(param)[2];
+
+    PROTECT( res = allocVector(INTSXP, XLENGTH(x)) );
+    DUPLICATE_ATTRIB(res, x);
+    
+    sizexy = nx * ny;
+    shift = 0;
+    
+    for ( i = 0; i < nz; i++, shift+=sizexy) {
+        tgt = &(INTEGER(res)[shift]);
+        
+        switch (TYPEOF(x)) {
+          case LGLSXP:
+          case INTSXP:
+            _thresh<int>( &(INTEGER(x)[shift]), tgt, nx, ny, dx, dy, offset);
+            break;
+          case REALSXP:
+            _thresh<double>( &(REAL(x)[shift]), tgt, nx, ny, dx, dy, offset);
+            break;
+        }
+    }
+
+    UNPROTECT (1);
+    return res;
+}
+
+
+template <class T> void _thresh(T *src, int *tgt, int nx, int ny, int dx, int dy, double offset) {
+    int xi, yi, u, k, l, ou, nu, ov, nv, i;
+    double mean, nFramePix;
+    T sum, * colsums;
+    
+    nFramePix = (2 * dx + 1) * (2 * dy + 1);
+    
+    /* allocate vector of column sums */
+    colsums = (T *) malloc ( nx * sizeof(T) );
+    
+    for ( yi = 0; yi < ny; yi++ ) {
+        if ( yi == 0 ) {
+            /* initialize column sums */
+            for ( k = 0; k < nx; k++ ) {
+                colsums[k] = dy * src[k];
+                
+                for ( l = 0; l <= dy; l++ )
+                    colsums[k] += src[k + l * nx];
+            }
+        }
+        else {
+            /* update column sums */
+            ov = yi - dy - 1;
+            nv = yi + dy;
+            
+            if (ov < 0) ov = 0;
+            else if (nv >= ny) nv = ny - 1;
+            
+            for ( k = 0; k < nx; k++ )
+                colsums[k] += src[k + nv * nx] - src[k + ov * nx];
+          }
+          
+          for ( xi = 0; xi < nx; xi++ ) {
+              if ( xi == 0 ) {
+                  /* first position in a row -- collect new sum */
+                  sum = 0.0;
+                  for ( k = xi - dx; k <= xi + dx; k++ ) {
+                      u = k;
+                      if (u < 0) u = 0;
+                      else if (u >= nx) u = nx - 1;
+                      
+                      sum += colsums[u];
+                  }
+              }
+              else {
+                  /* frame moved in the row, modify sum */
+                  ou = xi - dx - 1;
+                  nu = xi + dx;
+                  
+                  if (ou < 0) ou = 0;
+                  else if (nu >= nx) nu = nx - 1;
+                  
+                  sum += colsums[nu] - colsums[ou];
+              }
+              
+              /* calculate threshold and update tgt data */
+              mean = sum / nFramePix + offset;
+              
+              /* thresh current pixel */
+              tgt [xi + yi * nx] = ( src [xi + yi * nx] < mean ) ? BG : FG;
+        }
+    }
+    
+    free(colsums);
+}
diff --git a/src/thresh.h b/src/thresh.h
new file mode 100644
index 0000000..4fefa7c
--- /dev/null
+++ b/src/thresh.h
@@ -0,0 +1,17 @@
+#ifndef EBIMAGE_THRESH_H
+#define EBIMAGE_THRESH_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SEXP thresh (SEXP, SEXP);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/src/tile.cpp b/src/tile.cpp
new file mode 100644
index 0000000..72d5ce4
--- /dev/null
+++ b/src/tile.cpp
@@ -0,0 +1,229 @@
+/* -------------------------------------------------------------------------
+Counting objects determined in segmentations like watershed
+Copyright (c) 2006 Oleg Sklyar
+See: ../LICENSE for license, LGPL
+------------------------------------------------------------------------- */
+
+#include "tile.h"
+#include "tools.h"
+#include <R_ext/Error.h>
+#include <stdio.h>
+
+using namespace std;
+
+typedef struct {
+  int dx, dy;
+  int nx, ny;
+  int xoff, yoff;
+} Dims;
+
+template <class T> void _untile(T *, T *, ColorStrides, ColorStrides, Dims);
+
+/*----------------------------------------------------------------------- */
+SEXP tile (SEXP obj, SEXP _hdr, SEXP params) {
+  SEXP res, dm, ims;
+  int mode =  COLOR_MODE(obj);
+  int ndy, ndx = INTEGER(params)[0];
+  int lwd = INTEGER(params)[1];
+  int nc = getNumberOfChannels(obj, mode);
+  int nprotect, nx, ny, nz, nxr, nyr, i, j, index, x, y;
+  double *hdr, *tgt;
+  double *dsrc = NULL;
+  int *isrc = NULL;
+  
+  ColorStrides src_strides, tgt_strides;
+  
+  nx = INTEGER ( GET_DIM(obj) )[0];
+  ny = INTEGER ( GET_DIM(obj) )[1];
+  nz = getNumberOfFrames(obj, 1);
+  nprotect = 0;
+  
+  if ( nz < 1 ) error("no images in stack to tile");
+  /* get FG and BG colors from supplied header */
+  hdr = REAL(_hdr);
+  
+  /* calculate size of the resulting image */
+  ndy = ceil( nz / (double) ndx ); // number of tiles in y-dir
+  nxr = lwd + (nx + lwd) * ndx;
+  nyr = lwd + (ny + lwd) * ndy;
+  
+  /* allocate memory for the image */
+  PROTECT( res = allocVector(REALSXP, nc * nxr * nyr) );
+  nprotect++;
+  DUPLICATE_ATTRIB(res, obj);
+  
+  // set dimensions
+  if (mode!=MODE_COLOR) {
+    PROTECT ( dm = allocVector( INTSXP, 2) );
+    nprotect++;
+  } else {
+    PROTECT ( dm = allocVector( INTSXP, 3) );
+    nprotect++;
+    INTEGER (dm)[2] = nc;
+  }
+  INTEGER (dm)[0] = nxr;
+  INTEGER (dm)[1] = nyr;
+  
+  SET_DIM(res, dm);
+  SET_DIMNAMES (res, R_NilValue);
+  
+  tgt = REAL(res);
+  
+  getColorStrides(res, 0, &tgt_strides);
+  
+  /* loop through image stack and copy them to res */  
+  for ( index = 0; index < ndx * ndy; index++ ) {
+    y = lwd + (index / ndx) * (ny + lwd);
+    x = lwd + (index % ndx) * (nx + lwd);
+    i = x + y * nxr;
+    
+    /*  copy line by line */
+    if (index < nz) {
+      getColorStrides(obj, index, &src_strides);
+      
+      switch (TYPEOF(obj)) {
+      case LGLSXP:
+      case INTSXP:
+        isrc = INTEGER(obj);
+        for (j = 0; j < nx*ny; j+=nx, i+=nxr ) {
+          for (int k = 0; k < nx; k++) {
+            if (tgt_strides.r!=-1) tgt[tgt_strides.r + i + k] = (double) isrc[j+src_strides.r + k];
+            if (tgt_strides.g!=-1) tgt[tgt_strides.g + i + k] = (double) isrc[j+src_strides.g + k];
+            if (tgt_strides.b!=-1) tgt[tgt_strides.b + i + k] = (double) isrc[j+src_strides.b + k];
+          }
+        } 
+        break;
+      case REALSXP:
+        dsrc = REAL(obj);
+        for (j = 0; j < nx*ny; j+=nx, i+=nxr ) {
+          if (src_strides.r!=-1) memcpy( &(tgt[i+tgt_strides.r]), &(dsrc[j+src_strides.r]), nx * sizeof(double));
+          if (src_strides.g!=-1) memcpy( &(tgt[i+tgt_strides.g]), &(dsrc[j+src_strides.g]), nx * sizeof(double));
+          if (src_strides.b!=-1) memcpy( &(tgt[i+tgt_strides.b]), &(dsrc[j+src_strides.b]), nx * sizeof(double));
+        }
+        break;
+      }
+      
+    }
+    /* reset remaining empty tiles to BG */
+    else {
+      for ( j = 0; j < ny; j++, i+=nxr ) {
+        for (int k = 0; k < nx; k++){
+          if (tgt_strides.r!=-1) tgt[tgt_strides.r + i + k] = hdr[1];
+          if (tgt_strides.g!=-1) tgt[tgt_strides.g + i + k] = hdr[3];
+          if (tgt_strides.b!=-1) tgt[tgt_strides.b + i + k] = hdr[5];
+        }
+      }
+    }
+  }
+  
+  /* draw grid if required */  
+  if ( lwd > 0 ) {
+    /* vertical stripes */
+    for (i = 0; i <= ndx; i++ ) {
+      for ( x = i * (nx + lwd); x < lwd + i * (nx + lwd); x++ ) {
+        for ( y = 0; y < nyr; y++ ) {
+          if (tgt_strides.r!=-1) tgt[tgt_strides.r + x + y * nxr] = hdr[0];
+          if (tgt_strides.g!=-1) tgt[tgt_strides.g + x + y * nxr] = hdr[2];
+          if (tgt_strides.b!=-1) tgt[tgt_strides.b + x + y * nxr] = hdr[4];
+        }        
+      }
+    }
+    /* horizontal stripes */
+    for (j = 0; j <= ndy; j++ ) {
+      for ( y = j * (ny + lwd); y < lwd + j * (ny + lwd); y++ ) {
+        for ( x = 0; x < nxr; x++ ) {
+          if (tgt_strides.r!=-1) tgt[tgt_strides.r + x + y * nxr] = hdr[0];
+          if (tgt_strides.g!=-1) tgt[tgt_strides.g + x + y * nxr] = hdr[2];
+          if (tgt_strides.b!=-1) tgt[tgt_strides.b + x + y * nxr] = hdr[4];
+        }
+      }
+    }
+  }
+  
+  UNPROTECT( nprotect );
+  return res;
+}
+
+/*----------------------------------------------------------------------- */
+
+SEXP
+untile(SEXP img, SEXP nim, SEXP linewd) {
+  int mode = COLOR_MODE(img);
+  int nimx = INTEGER(nim)[0];
+  int nimy = INTEGER(nim)[1];
+
+  int lwd  = INTEGER(linewd)[0];
+  int *sdim = INTEGER(GET_DIM(img));
+  int nx = (sdim[0]-(nimx+1)*lwd) / nimx;
+  int ny = (sdim[1]-(nimy+1)*lwd) / nimy;
+  int nz = getNumberOfFrames(img,1) * nimx * nimy;
+  int nc = getNumberOfChannels(img, mode);
+  int nprotect=0, i, j, im, y, iim;
+  SEXP res, dim, dat;
+  double *src, *tgt;
+
+  ColorStrides src_strides, res_strides;
+  
+  Dims d = (Dims) {dx: sdim[0], dy: sdim[1], nx: nx, ny: ny, xoff: 0, yoff: 0};
+
+  if (nx<1 || ny<1 || nz <1 || ((nx*ny*nz*nc)>(1024*1024*1024))) {
+    if (nc==1) Rprintf("size of the resulting image will be (nx=%d,ny=%d,nz=%d)\n",nx,ny,nz);
+    else Rprintf("size of the resulting image will be (nx=%d,ny=%d,nc=%d,nz=%d)\n",nx,ny,nc,nz);
+    error("invalid nx, ny or nz values: negative or too large values");
+  }
+
+  PROTECT(res = allocVector(TYPEOF(img), nc*nx*ny*nz)); 
+  nprotect++;
+  DUPLICATE_ATTRIB(res, img);
+  
+  // set dimensions
+  if (mode!=MODE_COLOR) {
+    PROTECT(dim = allocVector(INTSXP, 3)); nprotect++;
+    INTEGER(dim)[0] = nx;
+    INTEGER(dim)[1] = ny;
+    INTEGER(dim)[2] = nz;
+  } else {
+    PROTECT(dim = allocVector(INTSXP, 4)); nprotect++;
+    INTEGER(dim)[0] = nx;
+    INTEGER(dim)[1] = ny;
+    INTEGER(dim)[2] = nc;
+    INTEGER(dim)[3] = nz;
+  }
+  SET_DIM(res, dim);
+  SET_DIMNAMES (res, R_NilValue);
+  
+  for (im=0; im<nz; im++) {
+    iim = im / (nimx*nimy);
+
+    getColorStrides(img, iim, &src_strides);
+    getColorStrides(res, im,  &res_strides);
+   
+    i = im % nimx;
+    j = (im-iim*nimx*nimy) / nimx;
+
+    d.xoff = i*nx+lwd*(i+1);                                                                                      
+    d.yoff = j*ny+lwd*(j+1);                                                                                      
+ 
+    switch (TYPEOF(img)) {
+    case LGLSXP:
+    case INTSXP:
+      _untile<int>(INTEGER(img), INTEGER(res), src_strides, res_strides, d);
+      break;
+    case REALSXP:
+      _untile<double>(REAL(img), REAL(res), src_strides, res_strides, d);
+      break;
+    }
+    
+  }
+  
+  UNPROTECT(nprotect);
+  return res;
+}
+
+template <class T> void _untile(T *src, T *tgt, ColorStrides src_strides, ColorStrides tgt_strides, Dims d) { 
+  for (int y=0; y<d.ny; y++) {
+    if (src_strides.r!=-1) memcpy(&(tgt[tgt_strides.r + y*d.nx]), &(src[src_strides.r + (d.yoff + y)*d.dx + d.xoff]), d.nx*sizeof(T));
+    if (src_strides.g!=-1) memcpy(&(tgt[tgt_strides.g + y*d.nx]), &(src[src_strides.g + (d.yoff + y)*d.dx + d.xoff]), d.nx*sizeof(T));
+    if (src_strides.b!=-1) memcpy(&(tgt[tgt_strides.b + y*d.nx]), &(src[src_strides.b + (d.yoff + y)*d.dx + d.xoff]), d.nx*sizeof(T));
+  }
+}
diff --git a/src/tile.h b/src/tile.h
new file mode 100644
index 0000000..794dbe5
--- /dev/null
+++ b/src/tile.h
@@ -0,0 +1,18 @@
+#ifndef EBIMAGE_TILE_H
+#define EBIMAGE_TILE_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SEXP tile (SEXP, SEXP, SEXP);
+SEXP untile (SEXP, SEXP, SEXP);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/src/tools.c b/src/tools.c
new file mode 100644
index 0000000..a312f7c
--- /dev/null
+++ b/src/tools.c
@@ -0,0 +1,85 @@
+#include <R.h>
+#include <Rdefines.h>
+#include "tools.h"
+
+SEXP numberOfFrames(SEXP x, SEXP type) {
+  SEXP res = PROTECT( ScalarInteger(getNumberOfFrames(x, INTEGER(type)[0])) );
+  UNPROTECT(1);
+  return res;
+}
+
+char * _validImageObject (SEXP x) {
+  int colorMode;
+  
+  // check Nil
+  if (x==R_NilValue) return("object is NULL");
+  else {
+    // check array
+    if(!isArray(x)) return("object must be an array");
+    
+    // check dim
+    if (LENGTH(GET_DIM(x))<2) return("object must have at least two dimensions");
+    if (INTEGER(GET_DIM(x))[0]<1 || INTEGER(GET_DIM(x))[1]<1) return("spatial dimensions of object must be higher than zero"); 
+    if (getNumberOfFrames(x,0)<1) return("object must contain at least one frame");
+    
+    // check colormode
+    colorMode=COLOR_MODE(x);
+    if (colorMode!=0 && colorMode!=2) return("invalid colormode");
+  }
+  return(NULL);
+}
+
+// test=0 will make validImage fail if x is not an image
+// test=1 will return 0 if x is not an Image
+int validImage (SEXP x,int test) {
+  char *msg = _validImageObject(x);
+  
+  if (test==0 && msg!=NULL) error(msg);
+  if (msg!=NULL) return(0);
+  else return(1);
+}
+
+SEXP validImageObject (SEXP x) {
+  SEXP res;
+  char *msg = _validImageObject(x);
+  res = (msg==NULL) ? PROTECT( ScalarLogical(1) ) : PROTECT( mkString(msg) );
+  UNPROTECT (1);
+  return(res);
+}
+
+// If type=0, returns the total number of frames
+// If type=1, returns the number of frames to be rendered, according to the colorMode
+int getNumberOfFrames(SEXP x, int type) {
+  int k;
+  int kp = (type==1 && COLOR_MODE(x)==MODE_COLOR) ? 3 : 2;
+  int n = 1;
+  int p = LENGTH(GET_DIM(x));
+  if (p > kp) {
+    for (k = kp; k < p; k++) n*=INTEGER(GET_DIM(x))[k];
+  }
+  
+  return(n);
+}
+
+int getNumberOfChannels(SEXP x, int colormode) {
+  if (colormode!=MODE_COLOR)
+    return(1);
+  else
+    return(LENGTH(GET_DIM(x))<3 ? 1 : INTEGER(GET_DIM(x))[2]);
+}
+
+void getColorStrides(SEXP x, int index, ColorStrides *strides) {
+  int xysize, nch;
+  
+  xysize = INTEGER(GET_DIM(x))[0] * INTEGER(GET_DIM(x))[1];
+  nch = getNumberOfChannels(x, COLOR_MODE(x));
+  
+  (*strides).r = index * nch * xysize;
+  (*strides).g = (nch>1) ? (*strides).r + xysize : -1;
+  (*strides).b = (nch>2) ? (*strides).g + xysize : -1;
+}
+
+int isImage(SEXP x) {
+  static const char *valid[] = { "Image", ""};
+  return R_check_class_etc(x, valid) + 1;
+}
diff --git a/src/tools.h b/src/tools.h
new file mode 100644
index 0000000..c453adb
--- /dev/null
+++ b/src/tools.h
@@ -0,0 +1,46 @@
+#ifndef EBIMAGE_TOOLS_H
+#define EBIMAGE_TOOLS_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MODE_GRAYSCALE  0
+#define MODE_COLOR      2
+
+#define COLOR_MODE(x) ( R_has_slot(x, Image_colormode) ? INTEGER(GET_SLOT(x, Image_colormode))[0] : MODE_GRAYSCALE )
+
+#define INDEX_FROM_XY(x, y, xsize) ((x) + (y) * (xsize))
+#define POINT_FROM_INDEX(pt, index, xsize) pt.x = index % xsize; pt.y = index / xsize;
+
+#define DISTANCE_XY(pt1, pt2) sqrt( (long double) ( (pt1.x - pt2.x) * (pt1.x - pt2.x) + (pt1.y - pt2.y) * (pt1.y - pt2.y) ) )
+
+extern SEXP Image_colormode;
+
+typedef struct {
+    int x, y;
+} PointXY;
+
+typedef struct {
+  int r, g, b;
+} ColorStrides;
+
+typedef double numeric;
+
+SEXP numberOfFrames (SEXP, SEXP);
+SEXP validImageObject (SEXP x);
+int validImage (SEXP x,int test);
+int getNumberOfFrames (SEXP x, int type);
+int getNumberOfChannels (SEXP x, int colormode);
+void getColorStrides(SEXP x, int index, ColorStrides *strides);
+
+int isImage (SEXP x);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/transpose.cpp b/src/transpose.cpp
new file mode 100644
index 0000000..5e3a65f
--- /dev/null
+++ b/src/transpose.cpp
@@ -0,0 +1,99 @@
+#include "transpose.h"
+#include "tools.h"
+
+/* -------------------------------------------------------------------------
+ Cache oblivious transposition of spatial dimensions
+ 
+ Copyright (c) 2017 Andrzej Oles
+ See: ../LICENSE for license, LGPL
+ ------------------------------------------------------------------------- */
+
+#define BLOCKSIZE 16
+
+template <typename type> void _transpose(type *, type *, int, int, int, int, PointXY);
+
+template <typename type> void _transpose(type * src, type * tgt, int rb, int re, int cb, int ce, PointXY size) {
+  int r = re - rb, c = ce - cb;
+  if (r <= BLOCKSIZE && c <= BLOCKSIZE) {
+    for (int i = rb; i < re; i++) {
+      for (int j = cb; j < ce; j++) {
+        tgt[j + i*size.y] = src[i + j*size.x];
+      }
+    }
+  } else if (r >= c) {
+    _transpose<type>(src, tgt, rb, rb + (r / 2), cb, ce, size);
+    _transpose<type>(src, tgt, rb + (r / 2), re, cb, ce, size);
+  } else {
+    _transpose<type>(src, tgt, rb, re, cb, cb + (c / 2), size);
+    _transpose<type>(src, tgt, rb, re, cb + (c / 2), ce, size);
+  }
+}
+
+SEXP transpose (SEXP x) {
+  validImage(x, 0);
+  
+  int nprotect = 0;
+  
+  SEXP res = PROTECT( allocVector(TYPEOF(x), XLENGTH(x)) );
+  nprotect++;
+  DUPLICATE_ATTRIB(res, x);
+  
+  // swap spatial dimensions
+  SEXP dim = PROTECT( duplicate(GET_DIM(x)) );
+  nprotect++;
+  int * dm = INTEGER(dim);
+  
+  int tmp;
+  tmp = dm[0];
+  dm[0] = dm[1];
+  dm[1] = tmp;
+  
+  SET_DIM(res, dim);
+  
+  // swap dimnames
+  if ( GET_DIMNAMES(x) != R_NilValue ) {
+    SEXP dnames = PROTECT( duplicate(GET_DIMNAMES(x)) );
+    nprotect++;
+    SEXP v = PROTECT( VECTOR_ELT(dnames, 0) );
+    nprotect++;
+    SET_VECTOR_ELT(dnames, 0, VECTOR_ELT(dnames, 1)); 
+    SET_VECTOR_ELT(dnames, 1, v); 
+    
+    if ( GET_NAMES(dnames) != R_NilValue ) {
+      SEXP names = PROTECT( duplicate(GET_NAMES(dnames)) );
+      nprotect++;
+      SEXP s = PROTECT( STRING_ELT(names, 0) );
+      nprotect++;
+      SET_STRING_ELT(names, 0, STRING_ELT(names, 1)); 
+      SET_STRING_ELT(names, 1, s); 
+      
+      SET_NAMES(dnames, names);
+    }
+    
+    SET_DIMNAMES(res, dnames);
+  } 
+  
+  // transpose
+  PointXY size;
+  size.x = INTEGER ( GET_DIM(x) )[0];
+  size.y = INTEGER ( GET_DIM(x) )[1];
+  int nz = getNumberOfFrames(x, 0);
+  
+  int sizexy = size.x * size.y;
+  int offset = 0;
+  
+  for (int i = 0; i < nz; i++, offset+=sizexy) {
+    switch (TYPEOF(x)) {
+    case LGLSXP:
+    case INTSXP:
+      _transpose<int>( &(INTEGER(x)[offset]), &(INTEGER(res)[offset]), 0, size.x, 0, size.y, size);
+      break;
+    case REALSXP:
+      _transpose<double>( &(REAL(x)[offset]), &(REAL(res)[offset]), 0, size.x, 0, size.y, size);
+      break;
+    }
+  }
+  
+  UNPROTECT (nprotect);
+  return res;
+}
diff --git a/src/transpose.h b/src/transpose.h
new file mode 100644
index 0000000..dfd0997
--- /dev/null
+++ b/src/transpose.h
@@ -0,0 +1,17 @@
+#ifndef EBIMAGE_TRANSPOSE_H
+#define EBIMAGE_TRANSPOSE_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SEXP transpose (SEXP);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/src/watershed.cpp b/src/watershed.cpp
new file mode 100644
index 0000000..4f4b2ab
--- /dev/null
+++ b/src/watershed.cpp
@@ -0,0 +1,209 @@
+#include "watershed.h"
+
+/* -------------------------------------------------------------------------
+Watershed transform for Image
+Copyright (c) 2006 Oleg Sklyar
+See: ../LICENSE for license, LGPL
+------------------------------------------------------------------------- */
+
+#include "tools.h"
+#include <R_ext/Error.h>
+
+/* list of STL, C++ */
+#include <list>
+
+#define BG 0.0
+
+struct TheSeed {
+    int index, seed;
+};
+
+typedef std::list<int>     IntList;
+typedef std::list<TheSeed> SeedList;
+
+double check_multiple( double *, double *, int &, IntList &, SeedList &, double &, int &, int & );
+
+/*----------------------------------------------------------------------- */
+SEXP
+watershed (SEXP x, SEXP _tolerance, SEXP _ext) {
+    SEXP res;
+    int im, i, j, nx, ny, nz, ext;
+    double tolerance;
+
+    nx = INTEGER ( GET_DIM(x) )[0];
+    ny = INTEGER ( GET_DIM(x) )[1];
+    nz = getNumberOfFrames(x,0);
+    tolerance = REAL( _tolerance )[0];
+    ext = INTEGER( _ext )[0];
+
+    PROTECT( res = allocVector(INTSXP, XLENGTH(x)) );
+    DUPLICATE_ATTRIB(res, x);
+    
+    int * index = new int[ nx * ny ];
+    double * frame = new double[ nx * ny ];
+
+    for ( im = 0; im < nz; im++ ) {
+
+        double * src = &( REAL(x)[ im * nx * ny ] );
+        int * tgt = &( INTEGER(res)[ im * nx * ny ] );
+
+        /* generate pixel index and negate the image -- filling wells */
+        for ( i = 0; i < nx * ny; i++ ) {
+        	  frame[ i ] = -src[ i ];
+        	  index[ i ] = i;
+        }
+        /* from R includes R_ext/Utils.h */
+        /* will resort frame as well */
+        rsort_with_index( frame, index, nx * ny );
+        /* reassign frame as it was reset above but keep new index */
+        for ( i = 0; i < nx * ny; i++ )
+            frame[ i ] = ( src[i]==0 ? 0 : -src[i] ); // avoid turning +0 into -0
+
+        SeedList seeds;  /* indexes of all seed starting points, i.e. lowest values */
+
+        IntList  equals; /* queue of all pixels on the same gray level */
+        IntList  nb;     /* seed values of assigned neighbours */
+        int ind, indxy, nbseed, x, y, topseed = 0;
+        IntList::iterator it;
+        TheSeed newseed;
+        PointXY pt;
+        bool isin;
+        /* loop through the sorted index */
+        for ( i = 0; i < nx * ny && src[ index[i] ] > BG; ) {
+            /* pool a queue of equally lowest values */
+            ind = index[ i ];
+            equals.push_back( ind );
+            for ( i = i + 1; i < nx * ny; ) {
+                if ( src[ index[i] ] != src[ ind ] ) break;
+                equals.push_back( index[i] );
+                i++;
+            }
+            while ( !equals.empty() ) {
+                /* first check through all the pixels if we can assign them to
+                 * existing objects, count checked and reset counter on each assigned
+                 * -- exit when counter equals queue length */
+                for ( j = 0; j < (int) equals.size(); ) {
+		  if ((j%1000)==0) R_CheckUserInterrupt();
+                    ind = equals.front();
+                    equals.pop_front();
+                    /* check neighbours:
+                     * - if exists one, assign
+                     * - if two or more check what should be combined and assign to the steepest
+                     * - if none, push back */
+                    /* reset j to 0 every time we assign another pixel to restart the loop */
+                    nb.clear();
+                    POINT_FROM_INDEX(pt, ind, nx)
+                    /* determine which neighbour we have, push them to nb */
+                    for ( x = pt.x - ext; x <= pt.x + ext; x++ )
+                        for ( y = pt.y - ext; y <= pt.y + ext; y++ ) {
+                            if ( x < 0 || y < 0 || x >= nx || y >= ny || (x == pt.x && y == pt.y) ) continue;
+                            indxy = x + y * nx;
+                            nbseed = (int) frame[ indxy ];
+                            if ( nbseed < 1 ) continue;
+                            isin = false;
+                            for ( it = nb.begin(); it != nb.end() && !isin; it++ )
+                                if ( nbseed == *it ) isin = true;
+                            if ( !isin ) nb.push_back( nbseed );
+                        }
+                    if ( nb.size() == 0 ) {
+                        /* push the pixel back and continue with the next one */
+                        equals.push_back( ind );
+                        j++;
+                        continue;
+                    }
+                    frame[ ind ] = check_multiple(frame, src, ind, nb, seeds, tolerance, nx, ny );
+                    /* we assigned the pixel, reset j to restart neighbours detection */
+                    j = 0;
+                }
+                /* now we have assigned all that we could */
+                if ( !equals.empty() ) {
+                    /* create a new seed for one pixel only and go back to assigning neighbours */
+                    topseed++;
+                    newseed.index = equals.front();
+                    newseed.seed = topseed;
+                    equals.pop_front();
+                    frame[ newseed.index ] = topseed;
+                    seeds.push_back( newseed );
+                }
+            } // assigning equals
+        } // sorted index
+
+        /* now we need to reassign indexes while some seeds could be removed */
+        double * finseed = new double[ topseed ];
+        for ( i = 0; i < topseed; i++ )
+            finseed[ i ] = 0;
+        i = 0;
+        while ( !seeds.empty() ) {
+            newseed = seeds.front();
+            seeds.pop_front();
+            finseed[ newseed.seed - 1 ] = i + 1;
+            i++;
+        }
+        for ( i = 0; i < nx * ny; i++ ) {
+            j = (int) frame[ i ];
+            tgt[ i ] = ( 0 < j && j <= topseed ) ? finseed[ j - 1 ] : 0;
+        }
+        delete[] finseed;
+
+    } // loop through images
+
+    delete[] index;
+    delete[] frame;
+
+    UNPROTECT (1);
+    return res;
+}
+
+bool
+get_seed( SeedList & seeds, int & seed, SeedList::iterator & sit ) {
+    for ( sit = seeds.begin(); sit != seeds.end(); sit++ )
+        if ( (*sit).seed == seed ) return true;
+    return false;
+}
+
+double
+check_multiple( double * tgt, double * src, int & ind, IntList & nb, SeedList & seeds, double & tolerance, int & nx, int & ny ) {
+    if ( nb.size() == 1 ) return nb.front();
+    if ( nb.size() <  1 ) return 0.0; // dumb protection
+
+    double diff, maxdiff = 0.0, res = 0.0;
+    int i;
+    IntList::iterator  it;
+    SeedList::iterator sit;
+    PointXY ptsit, pt;
+    POINT_FROM_INDEX(pt, ind, nx)
+    double distx, dist = DBL_MAX;
+
+    /* maxdiff */
+    for ( it = nb.begin(); it != nb.end(); it++ ) {
+        if ( !get_seed( seeds, *it, sit ) ) continue;
+        diff = fabs( src[ ind ] - src[ (*sit).index ] );
+        if ( diff > maxdiff ) {
+            maxdiff = diff;
+            /* assign result to the steepest until and if it not assigned to closest over the tolerance */
+            if ( dist == DBL_MAX )
+                res = *it;
+        }
+        /* we assign to the closest centre which is above tolerance, if none than to maxdiff */
+        if ( diff >= tolerance ) {
+            POINT_FROM_INDEX(ptsit, (*sit).index, nx)
+            distx = DISTANCE_XY(pt, ptsit);
+            if ( distx < dist ) {
+                dist =  distx;
+                res = * it;
+            }
+        }
+
+    }
+    /* assign all that need assignment to res, which has maxdiff */
+    for ( it = nb.begin(); it != nb.end(); it++ ) {
+        if ( *it == res ) continue;
+        if ( !get_seed( seeds, *it, sit ) ) continue;
+        if ( fabs( src[ ind ] - src[ (*sit).index ] ) >= tolerance ) continue;
+        for ( i = 0; i < nx * ny; i++ )
+            if ( tgt[ i ] == *it )
+                tgt[ i ] = res;
+        seeds.erase( sit );
+    }
+    return res;
+}
diff --git a/src/watershed.h b/src/watershed.h
new file mode 100644
index 0000000..c43f463
--- /dev/null
+++ b/src/watershed.h
@@ -0,0 +1,17 @@
+#ifndef EBIMAGE_WATERSHED_H
+#define EBIMAGE_WATERSHED_H
+
+#include <R.h>
+#include <Rdefines.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SEXP watershed(SEXP, SEXP, SEXP);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/tests/test.R b/tests/test.R
new file mode 100644
index 0000000..fb44424
--- /dev/null
+++ b/tests/test.R
@@ -0,0 +1,259 @@
+## cat tests/test.R | R --vanilla &>tests/test.Rout.save
+library("EBImage")
+
+set.seed(0) # make random color permutations in 'colorLabels' reproducible
+.digits = floor(-log10(.Machine$double.eps^.5) - 1)
+
+## returns a hashcode given an object
+hash = function (x) .Call(digest:::digest_impl, serialize(x, connection=NULL, ascii=FALSE, xdr=FALSE), 7L, -1L, 14L, 0L, 0L, PACKAGE="digest")
+
+hash.old <- function(x) {
+  if (is.list(x)) hash(sapply(x,hash))
+  else {
+    xd <- as.numeric(x)
+    xd <- xd[!is.nan(xd)]
+    if (is.matrix(xd)) sum(xd*(1:length(xd))) + 0.7*hash(dim(xd))
+    else sum(xd*(1:length(xd))) - 0.1
+  }
+}
+
+## try to evaluate fun(x,...) 
+check <- function(fun, x, ..., capture.output=FALSE, suppressWarnings=FALSE, suppressMessages=FALSE, expectError=FALSE, round=FALSE, debug=FALSE) {
+  passed <- TRUE
+
+  cat(sprintf("checking \'%s\' %s ", fun, paste(rep(".", 35L-nchar(fun)), collapse = "")))
+  
+  expr = quote(do.call(fun,c(list(x),list(...))))
+  if ( isTRUE(capture.output) ) expr = call("capture.output", expr)
+  if ( isTRUE(suppressWarnings) ) expr = call("suppressWarnings", expr)
+  if ( isTRUE(suppressMessages) ) expr = call("suppressMessages", expr)
+  
+  y <- try(eval(expr), silent=TRUE)
+  
+  if ( (class(y)=="try-error" && !isTRUE(expectError) ) || ( is.Image(y) && !validObject(y)) ) {
+    y <- NULL
+    passed <- FALSE
+  }
+
+  if (passed) {
+    if ( isTRUE(round) && class(y)!="try-error")
+      y = if ( is.list(y) ) lapply(y, round, digits=.digits) else round(y, digits = .digits)
+    cat("PASS (", hash(y), ") ", hash.old(y), "\n", sep="") 
+  }
+  else cat("FAIL\n")
+  
+  if ( isTRUE(debug) ) {
+    if (is.Image(y)) show(y)
+    if (is.array(y)) display(y, all=TRUE, interpolate=FALSE) 
+    else if (is.character(y)) cat(y, sep="\n")
+  }
+  
+  y
+}
+
+checkIO <- function(name) {
+  cat("checking IO for \'", name, "\' ... ", sep="")
+  x = get(name)
+  y = FALSE
+  if ( !is.null(x) ) {
+    y <- try({
+      xx <- readImage(writeImage(x, tempfile("", fileext = ".tif")))
+      dimnames(xx) <- dimnames(x)
+      identical(x, xx)
+      }, silent=TRUE)
+  }
+  if ( isTRUE(y) ) cat("PASS\n") else cat("FAIL\n")
+  invisible(y)
+}
+
+testIOFunctions <- function(...) invisible(lapply(list(...), function(y) checkIO(y)))
+
+testEBImageFunctions <- function(x) {
+  cat("new test (hash=", hash(x), ")\n", sep="")
+  
+  z <- check("show", x, capture.output=TRUE)
+  z <- check("print", x, short=TRUE, capture.output=TRUE)
+  if ( typeof(x)=="logical" )
+    z <- check("hist", EBImage:::castImage(x), breaks = c(0, .5, 1))
+  else
+    z <- check("hist", x)
+  
+  ## pixel arithmetic
+  z <- check(">", x, 0.5)
+  z <- check("+", x, x)
+  z <- check("/", x, 2)
+  z <- check("*", 2L, x)
+  z <- check("median", x)
+  z <- check("quantile", x)
+
+  ## image methods
+  z <- check("Image", x, colormode="Color")
+  z <- check("as.Image", x)
+  z <- check("is.Image", x)
+  z <- check("imageData", x)
+  z <- check("imageData<-", x, z)
+  z <- check("as.raster", x)
+  z <- check("colorMode<-", x, Grayscale, suppressWarnings=TRUE)
+  y <- check("numberOfFrames", x, type="render")
+  z <- if ( y==1L ) check("getFrames", x, 1L, "render") else check("getFrames", x)
+  z <- check("display", x, method = "browser", tempDir = file.path(dirname(tempdir()), "EBImage_tmp"))
+  if ( y>2L ) {
+    z <- check("display", x, method = "raster", all = TRUE)
+    z <- check("image", x, i = 3L)
+  }
+  else {
+    z <- if (y==1L) check("display", x, method = "raster") else check("display", x, method = "raster", frame = 2L, suppressMessages=TRUE)
+    z <- check("image", x, suppressMessages=TRUE)
+  }
+  
+  ## drawCircle
+  d <- dim(x)
+  c.x <- ceiling(d[1L]/2)
+  c.y <- ceiling(d[2L]/2)
+  radius <- max(c.x-1, 1)
+  nf <- numberOfFrames(x, "render")
+  fill <- nf > 1
+  col <- if ( colorMode(x)==Color ) "yellow" else 1
+  z <- check("drawCircle", x, c.x, c.x, radius, col, fill, nf)
+  
+  ## subset
+  sub <- list(x, 1:min(10,d[1L]), 1:min(7,d[2L]))
+  if (length(d)>2) sub <- c(sub, rep(TRUE, length(d)-2))
+  z <- do.call("check", c("[", sub))
+
+  ## spatial transform
+  z <- check("resize", x, 137, 22)
+  z <- check("rotate", x, 20)
+  z <- check("flip", x)
+  z <- check("flop", x)
+  z <- check("translate", x, c(-7, 5), bg.col=1)
+  z <- check("affine", x, matrix(c(-7, 5, 0.1, -0.2, 0.3, 1), ncol=2L))
+  z <- check("transpose", x)
+
+  ## segmentation
+  fd = pmin(5, (d[1:2]-1)/2)
+  z <- check("thresh", x, w=fd[1L], h=fd[2L])
+  y <- check("channel", x, "luminance")
+  z <- check("otsu", y)
+  y <- suppressWarnings(normalize(y, separate=FALSE))
+  y <- check("bwlabel", y > 0.5)
+  z <- check("colorLabels", y, suppressWarnings=TRUE)
+  z <- check("stackObjects", y, x)
+  z <- check("stackObjects", Image(dim=dim(y)), x)
+  cls <- if ( colorMode(x)==Color ) TRUE else FALSE
+  z <- check("paintObjects", y, x, col=c("#ff00ff", "#ffff00"), opac=c(1.0, 0.5), thick=cls, closed=cls)  
+  z <- check("rmObjects", y, as.list(seq_len(numberOfFrames(y))), cls)
+  z <- check("reenumerate", z)
+  z <- check("reenumerate", y)
+  
+  ## features
+  x1 <- getFrame(x, 1)
+  x2 <- list(x=x1, y=2*x1)
+  y1 <- getFrame(y, 1)
+  expandRef <- if ( min(dim(x1)) > 31L ) function(ref, refnames) standardExpandRef(ref, refnames, gblob(n=31L)) else NULL
+  z <- check("computeFeatures", y1, x2, expandRef = expandRef, round = !is.null(expandRef))
+  z <- check("computeFeatures", y1, x2, expandRef = expandRef, properties = TRUE)
+  
+  ## curvature
+  y <- check("ocontour", x>0.5)
+  if (length(y) > 0L ) z <- check("localCurvature", y[[1L]], round=TRUE)
+
+  ## filtering
+  z <- check("normalize", x, suppressWarnings=TRUE)
+  z <- check("normalize", x, inputRange=c(0.1, 0.9), suppressWarnings=TRUE)
+  z <- check("gblur", x, sigma=1, expectError=min(d)<7, round=TRUE)
+  y <- if (cls) "replicate" else if (nf>1) 0.5 else "circular"
+  z <- check("filter2", x, array(1, dim=c(5, 5)), y, round=TRUE)
+  z <- check("medianFilter", x, 2)
+  z <- check("equalize", x, suppressWarnings=TRUE)
+
+  ## morphological operations
+  y <- x > 0.5
+  z <- check("erode", y)
+  z <- check("dilate", y, makeBrush(5, 'disc'))
+  z <- check("opening", y, makeBrush(7, 'line'))
+  z <- check("closing", y, makeBrush(4, 'line', angle=0), suppressWarnings=TRUE)
+  z <- check("whiteTopHat", x, makeBrush(3, 'box'))
+  z <- check("blackTopHat", x, makeBrush(5, 'disc'))
+  z <- check("selfComplementaryTopHat", x)
+  z <- check("distmap", y)
+  z <- check("watershed", z)
+  z <- check('floodFill', x, c(5, 5), 0.5)
+  z <- check('fillHull', y)
+
+  ## propagate
+  y <- thresh(x, w=fd[1L], h=fd[2L], offset=0.02)
+  y <- fillHull(y)
+  y <- bwlabel(y)
+  z <- check("propagate", x, y, x>0.5)
+  
+  ## colorspace
+  z <- check("toRGB", x)
+  z <- check("rgbImage", x, x>0.5)
+  z <- check("colormap", channel(x, "luminance"), heat.colors(16L))
+
+  ## image stacking, combining, tiling
+  y <- check("combine", list(NULL, x, x, NULL, NULL))
+  z <- check("combine", x, y, y)
+  y <- check("tile", z, nx=3)
+  z <- check("untile", y, c(3, 2))
+  y <- check("abind", list(x, x), along=length(d)+1L)
+  cat("\n")
+}
+
+
+## check error handling
+try.readImage <- function(...) tryCatch(suppressWarnings(readImage(...)), error = function(e) NULL)
+mock <- try.readImage(system.file("images", package="EBImage"), type="png")
+mock <- try.readImage("http://www.huber.embl.de/EBImage/missing.file ", type="png")
+
+## single greyscale and color images
+sample <- try.readImage(system.file("images","sample.png", package="EBImage"))
+sample.color <- try.readImage(system.file("images","sample-color.png", package="EBImage"))
+## multi-frame image stack
+f = system.file("images","nuclei.tif", package="EBImage")
+nuclei = try.readImage(c(f, f))
+## test reading from URL
+logo <- try.readImage("http://www.huber.embl.de/EBImage/logo.png")
+
+## test: IO operations
+testIOFunctions("sample", "sample.color", "nuclei", "logo")
+
+## test: black image
+testEBImageFunctions(Image(0, c(8, 8)))
+
+## test: white image
+testEBImageFunctions(Image(1L, c(5, 5)))
+
+## test: 2D Grayscale 64x48
+x <- nuclei[50:113,208:255,2]
+testEBImageFunctions(as.array(x))
+
+## test: 2D Color 32x48x1
+x <- sample[1:32, 1:48]
+testEBImageFunctions(Image(as.vector(x), dim(x), Color))
+
+## test: 3D Color 65x17x3
+x <- sample.color[1:65, 1:17,]
+testEBImageFunctions(x)
+
+## test: 3D Grayscale logical 32x32x2
+x <- sample[32:63, 32:63]
+x <- x > otsu(x)
+x <- combine(x, x)
+testEBImageFunctions(x)
+
+## test: 4D Color 33x16x3x2
+x <- sample.color[1:33, 1:16,]
+x <- combine(x, x)
+testEBImageFunctions(x)
+
+## test: 4D Grayscale 16x33x2x3
+colorMode(x) <- Grayscale
+imageData(x) <- aperm(x, c(2L, 1L, 4L, 3L))
+testEBImageFunctions(x)
+
+## test: Image subclass
+ImageSubclass <- setClass ("ImageSubclass", contains = "Image", slots = c(foo = "character"))
+x <- ImageSubclass(x, foo="bar")
+testEBImageFunctions(x)
diff --git a/tests/test.Rout.save b/tests/test.Rout.save
new file mode 100644
index 0000000..9b86a8d
--- /dev/null
+++ b/tests/test.Rout.save
@@ -0,0 +1,977 @@
+
+R version 3.4.2 (2017-09-28) -- "Short Summer"
+Copyright (C) 2017 The R Foundation for Statistical Computing
+Platform: x86_64-pc-linux-gnu (64-bit)
+
+R is free software and comes with ABSOLUTELY NO WARRANTY.
+You are welcome to redistribute it under certain conditions.
+Type 'license()' or 'licence()' for distribution details.
+
+  Natural language support but running in an English locale
+
+R is a collaborative project with many contributors.
+Type 'contributors()' for more information and
+'citation()' on how to cite R or R packages in publications.
+
+Type 'demo()' for some demos, 'help()' for on-line help, or
+'help.start()' for an HTML browser interface to help.
+Type 'q()' to quit R.
+
+> ## cat tests/test.R | R --vanilla &>tests/test.Rout.save
+> library("EBImage")
+> 
+> set.seed(0) # make random color permutations in 'colorLabels' reproducible
+> .digits = floor(-log10(.Machine$double.eps^.5) - 1)
+> 
+> ## returns a hashcode given an object
+> hash = function (x) .Call(digest:::digest_impl, serialize(x, connection=NULL, ascii=FALSE, xdr=FALSE), 7L, -1L, 14L, 0L, 0L, PACKAGE="digest")
+> 
+> hash.old <- function(x) {
++   if (is.list(x)) hash(sapply(x,hash))
++   else {
++     xd <- as.numeric(x)
++     xd <- xd[!is.nan(xd)]
++     if (is.matrix(xd)) sum(xd*(1:length(xd))) + 0.7*hash(dim(xd))
++     else sum(xd*(1:length(xd))) - 0.1
++   }
++ }
+> 
+> ## try to evaluate fun(x,...) 
+> check <- function(fun, x, ..., capture.output=FALSE, suppressWarnings=FALSE, suppressMessages=FALSE, expectError=FALSE, round=FALSE, debug=FALSE) {
++   passed <- TRUE
++ 
++   cat(sprintf("checking \'%s\' %s ", fun, paste(rep(".", 35L-nchar(fun)), collapse = "")))
++   
++   expr = quote(do.call(fun,c(list(x),list(...))))
++   if ( isTRUE(capture.output) ) expr = call("capture.output", expr)
++   if ( isTRUE(suppressWarnings) ) expr = call("suppressWarnings", expr)
++   if ( isTRUE(suppressMessages) ) expr = call("suppressMessages", expr)
++   
++   y <- try(eval(expr), silent=TRUE)
++   
++   if ( (class(y)=="try-error" && !isTRUE(expectError) ) || ( is.Image(y) && !validObject(y)) ) {
++     y <- NULL
++     passed <- FALSE
++   }
++ 
++   if (passed) {
++     if ( isTRUE(round) && class(y)!="try-error")
++       y = if ( is.list(y) ) lapply(y, round, digits=.digits) else round(y, digits = .digits)
++     cat("PASS (", hash(y), ") ", hash.old(y), "\n", sep="") 
++   }
++   else cat("FAIL\n")
++   
++   if ( isTRUE(debug) ) {
++     if (is.Image(y)) show(y)
++     if (is.array(y)) display(y, all=TRUE, interpolate=FALSE) 
++     else if (is.character(y)) cat(y, sep="\n")
++   }
++   
++   y
++ }
+> 
+> checkIO <- function(name) {
++   cat("checking IO for \'", name, "\' ... ", sep="")
++   x = get(name)
++   y = FALSE
++   if ( !is.null(x) ) {
++     y <- try({
++       xx <- readImage(writeImage(x, tempfile("", fileext = ".tif")))
++       dimnames(xx) <- dimnames(x)
++       identical(x, xx)
++       }, silent=TRUE)
++   }
++   if ( isTRUE(y) ) cat("PASS\n") else cat("FAIL\n")
++   invisible(y)
++ }
+> 
+> testIOFunctions <- function(...) invisible(lapply(list(...), function(y) checkIO(y)))
+> 
+> testEBImageFunctions <- function(x) {
++   cat("new test (hash=", hash(x), ")\n", sep="")
++   
++   z <- check("show", x, capture.output=TRUE)
++   z <- check("print", x, short=TRUE, capture.output=TRUE)
++   if ( typeof(x)=="logical" )
++     z <- check("hist", EBImage:::castImage(x), breaks = c(0, .5, 1))
++   else
++     z <- check("hist", x)
++   
++   ## pixel arithmetic
++   z <- check(">", x, 0.5)
++   z <- check("+", x, x)
++   z <- check("/", x, 2)
++   z <- check("*", 2L, x)
++   z <- check("median", x)
++   z <- check("quantile", x)
++ 
++   ## image methods
++   z <- check("Image", x, colormode="Color")
++   z <- check("as.Image", x)
++   z <- check("is.Image", x)
++   z <- check("imageData", x)
++   z <- check("imageData<-", x, z)
++   z <- check("as.raster", x)
++   z <- check("colorMode<-", x, Grayscale, suppressWarnings=TRUE)
++   y <- check("numberOfFrames", x, type="render")
++   z <- if ( y==1L ) check("getFrames", x, 1L, "render") else check("getFrames", x)
++   z <- check("display", x, method = "browser", tempDir = file.path(dirname(tempdir()), "EBImage_tmp"))
++   if ( y>2L ) {
++     z <- check("display", x, method = "raster", all = TRUE)
++     z <- check("image", x, i = 3L)
++   }
++   else {
++     z <- if (y==1L) check("display", x, method = "raster") else check("display", x, method = "raster", frame = 2L, suppressMessages=TRUE)
++     z <- check("image", x, suppressMessages=TRUE)
++   }
++   
++   ## drawCircle
++   d <- dim(x)
++   c.x <- ceiling(d[1L]/2)
++   c.y <- ceiling(d[2L]/2)
++   radius <- max(c.x-1, 1)
++   nf <- numberOfFrames(x, "render")
++   fill <- nf > 1
++   col <- if ( colorMode(x)==Color ) "yellow" else 1
++   z <- check("drawCircle", x, c.x, c.x, radius, col, fill, nf)
++   
++   ## subset
++   sub <- list(x, 1:min(10,d[1L]), 1:min(7,d[2L]))
++   if (length(d)>2) sub <- c(sub, rep(TRUE, length(d)-2))
++   z <- do.call("check", c("[", sub))
++ 
++   ## spatial transform
++   z <- check("resize", x, 137, 22)
++   z <- check("rotate", x, 20)
++   z <- check("flip", x)
++   z <- check("flop", x)
++   z <- check("translate", x, c(-7, 5), bg.col=1)
++   z <- check("affine", x, matrix(c(-7, 5, 0.1, -0.2, 0.3, 1), ncol=2L))
++   z <- check("transpose", x)
++ 
++   ## segmentation
++   fd = pmin(5, (d[1:2]-1)/2)
++   z <- check("thresh", x, w=fd[1L], h=fd[2L])
++   y <- check("channel", x, "luminance")
++   z <- check("otsu", y)
++   y <- suppressWarnings(normalize(y, separate=FALSE))
++   y <- check("bwlabel", y > 0.5)
++   z <- check("colorLabels", y, suppressWarnings=TRUE)
++   z <- check("stackObjects", y, x)
++   z <- check("stackObjects", Image(dim=dim(y)), x)
++   cls <- if ( colorMode(x)==Color ) TRUE else FALSE
++   z <- check("paintObjects", y, x, col=c("#ff00ff", "#ffff00"), opac=c(1.0, 0.5), thick=cls, closed=cls)  
++   z <- check("rmObjects", y, as.list(seq_len(numberOfFrames(y))), cls)
++   z <- check("reenumerate", z)
++   z <- check("reenumerate", y)
++   
++   ## features
++   x1 <- getFrame(x, 1)
++   x2 <- list(x=x1, y=2*x1)
++   y1 <- getFrame(y, 1)
++   expandRef <- if ( min(dim(x1)) > 31L ) function(ref, refnames) standardExpandRef(ref, refnames, gblob(n=31L)) else NULL
++   z <- check("computeFeatures", y1, x2, expandRef = expandRef, round = !is.null(expandRef))
++   z <- check("computeFeatures", y1, x2, expandRef = expandRef, properties = TRUE)
++   
++   ## curvature
++   y <- check("ocontour", x>0.5)
++   if (length(y) > 0L ) z <- check("localCurvature", y[[1L]], round=TRUE)
++ 
++   ## filtering
++   z <- check("normalize", x, suppressWarnings=TRUE)
++   z <- check("normalize", x, inputRange=c(0.1, 0.9), suppressWarnings=TRUE)
++   z <- check("gblur", x, sigma=1, expectError=min(d)<7, round=TRUE)
++   y <- if (cls) "replicate" else if (nf>1) 0.5 else "circular"
++   z <- check("filter2", x, array(1, dim=c(5, 5)), y, round=TRUE)
++   z <- check("medianFilter", x, 2)
++   z <- check("equalize", x, suppressWarnings=TRUE)
++ 
++   ## morphological operations
++   y <- x > 0.5
++   z <- check("erode", y)
++   z <- check("dilate", y, makeBrush(5, 'disc'))
++   z <- check("opening", y, makeBrush(7, 'line'))
++   z <- check("closing", y, makeBrush(4, 'line', angle=0), suppressWarnings=TRUE)
++   z <- check("whiteTopHat", x, makeBrush(3, 'box'))
++   z <- check("blackTopHat", x, makeBrush(5, 'disc'))
++   z <- check("selfComplementaryTopHat", x)
++   z <- check("distmap", y)
++   z <- check("watershed", z)
++   z <- check('floodFill', x, c(5, 5), 0.5)
++   z <- check('fillHull', y)
++ 
++   ## propagate
++   y <- thresh(x, w=fd[1L], h=fd[2L], offset=0.02)
++   y <- fillHull(y)
++   y <- bwlabel(y)
++   z <- check("propagate", x, y, x>0.5)
++   
++   ## colorspace
++   z <- check("toRGB", x)
++   z <- check("rgbImage", x, x>0.5)
++   z <- check("colormap", channel(x, "luminance"), heat.colors(16L))
++ 
++   ## image stacking, combining, tiling
++   y <- check("combine", list(NULL, x, x, NULL, NULL))
++   z <- check("combine", x, y, y)
++   y <- check("tile", z, nx=3)
++   z <- check("untile", y, c(3, 2))
++   y <- check("abind", list(x, x), along=length(d)+1L)
++   cat("\n")
++ }
+> 
+> 
+> ## check error handling
+> try.readImage <- function(...) tryCatch(suppressWarnings(readImage(...)), error = function(e) NULL)
+> mock <- try.readImage(system.file("images", package="EBImage"), type="png")
+> mock <- try.readImage("http://www.huber.embl.de/EBImage/missing.file ", type="png")
+> 
+> ## single greyscale and color images
+> sample <- try.readImage(system.file("images","sample.png", package="EBImage"))
+> sample.color <- try.readImage(system.file("images","sample-color.png", package="EBImage"))
+> ## multi-frame image stack
+> f = system.file("images","nuclei.tif", package="EBImage")
+> nuclei = try.readImage(c(f, f))
+> ## test reading from URL
+> logo <- try.readImage("http://www.huber.embl.de/EBImage/logo.png")
+> 
+> ## test: IO operations
+> testIOFunctions("sample", "sample.color", "nuclei", "logo")
+checking IO for 'sample' ... PASS
+checking IO for 'sample.color' ... PASS
+checking IO for 'nuclei' ... PASS
+checking IO for 'logo' ... PASS
+> 
+> ## test: black image
+> testEBImageFunctions(Image(0, c(8, 8)))
+new test (hash=bb3d006dd4423285)
+checking 'show' ............................... PASS (c60852b2f4b4b9e3) NA
+checking 'print' .............................. PASS (01ec99e74b854ba0) NA
+checking 'hist' ............................... PASS (8c46196952e29db3) 34846b0bcea9feb4
+checking '>' .................................. PASS (1b5285e59ce84fa0) -0.1
+checking '+' .................................. PASS (bb3d006dd4423285) -0.1
+checking '/' .................................. PASS (bb3d006dd4423285) -0.1
+checking '*' .................................. PASS (bb3d006dd4423285) -0.1
+checking 'median' ............................. PASS (f5699b21f198d2c3) -0.1
+checking 'quantile' ........................... PASS (003bcd6206f48eaa) -0.1
+checking 'Image' .............................. PASS (99ce97a369121647) -0.1
+checking 'as.Image' ........................... PASS (bb3d006dd4423285) -0.1
+checking 'is.Image' ........................... PASS (29e1e263d8769b6f) 0.9
+checking 'imageData' .......................... PASS (b51b95b0ba6f4dce) -0.1
+checking 'imageData<-' ........................ PASS (bb3d006dd4423285) -0.1
+checking 'as.raster' .......................... PASS (1675cda1d5c380fa) NA
+checking 'colorMode<-' ........................ PASS (bb3d006dd4423285) -0.1
+checking 'numberOfFrames' ..................... PASS (96180b4a6b132e65) 0.9
+checking 'getFrames' .......................... PASS (d6919177c682b8b7) 14ff3acc3c64b912
+checking 'display' ............................ PASS (8c4f07b8386609ca) 9a746e3b1818a41e
+checking 'display' ............................ PASS (57f54e3531a18f4e) -0.1
+checking 'image' .............................. PASS (57f54e3531a18f4e) -0.1
+checking 'drawCircle' ......................... PASS (f78ee95d7e176d93) 447.9
+checking '[' .................................. PASS (7e14c53ed4d31621) -0.1
+checking 'resize' ............................. PASS (a1c9da3458e151e8) -0.1
+checking 'rotate' ............................. PASS (6ace606563c0af26) -0.1
+checking 'flip' ............................... PASS (bb3d006dd4423285) -0.1
+checking 'flop' ............................... PASS (bb3d006dd4423285) -0.1
+checking 'translate' .......................... PASS (574e9cac8770a1fd) 1932.9
+checking 'affine' ............................. PASS (bb3d006dd4423285) -0.1
+checking 'transpose' .......................... PASS (bb3d006dd4423285) -0.1
+checking 'thresh' ............................. PASS (15bf2ee33450c598) -0.1
+checking 'channel' ............................ PASS (bb3d006dd4423285) -0.1
+checking 'otsu' ............................... PASS (cbe8a6c6aa2e8245) -0.09804688
+checking 'bwlabel' ............................ PASS (15bf2ee33450c598) -0.1
+checking 'colorLabels' ........................ PASS (5cb6a857d91ed0ea) -0.1
+checking 'stackObjects' ....................... PASS (57f54e3531a18f4e) -0.1
+checking 'stackObjects' ....................... PASS (57f54e3531a18f4e) -0.1
+checking 'paintObjects' ....................... PASS (bb3d006dd4423285) -0.1
+checking 'rmObjects' .......................... PASS (15bf2ee33450c598) -0.1
+checking 'reenumerate' ........................ PASS (15bf2ee33450c598) -0.1
+checking 'reenumerate' ........................ PASS (15bf2ee33450c598) -0.1
+checking 'computeFeatures' .................... PASS (57f54e3531a18f4e) -0.1
+checking 'computeFeatures' .................... PASS (6dc4c63dde7e7828) 32d95b1456f80f68
+checking 'ocontour' ........................... PASS (236bd4454cc8e13c) 236bd4454cc8e13c
+checking 'normalize' .......................... PASS (bb3d006dd4423285) -0.1
+checking 'normalize' .......................... PASS (bb3d006dd4423285) -0.1
+checking 'gblur' .............................. PASS (bb3d006dd4423285) -0.1
+checking 'filter2' ............................ PASS (bb3d006dd4423285) -0.1
+checking 'medianFilter' ....................... PASS (bb3d006dd4423285) -0.1
+checking 'equalize' ........................... PASS (bb3d006dd4423285) -0.1
+checking 'erode' .............................. PASS (1b5285e59ce84fa0) -0.1
+checking 'dilate' ............................. PASS (1b5285e59ce84fa0) -0.1
+checking 'opening' ............................ PASS (1b5285e59ce84fa0) -0.1
+checking 'closing' ............................ PASS (1b5285e59ce84fa0) -0.1
+checking 'whiteTopHat' ........................ PASS (bb3d006dd4423285) -0.1
+checking 'blackTopHat' ........................ PASS (bb3d006dd4423285) -0.1
+checking 'selfComplementaryTopHat' ............ PASS (bb3d006dd4423285) -0.1
+checking 'distmap' ............................ PASS (bb3d006dd4423285) -0.1
+checking 'watershed' .......................... PASS (15bf2ee33450c598) -0.1
+checking 'floodFill' .......................... PASS (02f684c8d407c0f3) 1039.9
+checking 'fillHull' ........................... PASS (1b5285e59ce84fa0) -0.1
+checking 'propagate' .......................... PASS (15bf2ee33450c598) -0.1
+checking 'toRGB' .............................. PASS (5cb6a857d91ed0ea) -0.1
+checking 'rgbImage' ........................... PASS (5cb6a857d91ed0ea) -0.1
+checking 'colormap' ........................... PASS (4fc03a2c05330d87) 2079.9
+checking 'combine' ............................ PASS (7587cd4863b3f3d0) -0.1
+checking 'combine' ............................ PASS (8cc517353533c670) -0.1
+checking 'tile' ............................... PASS (9c28fe28dabc9dc0) 42140.88
+checking 'untile' ............................. PASS (f57349ddce21e8e1) 16809.31
+checking 'abind' .............................. PASS (7587cd4863b3f3d0) -0.1
+
+Warning messages:
+1: In hash.old(y) : NAs introduced by coercion
+2: In hash.old(y) : NAs introduced by coercion
+3: In hash.old(y) : NAs introduced by coercion
+> 
+> ## test: white image
+> testEBImageFunctions(Image(1L, c(5, 5)))
+new test (hash=fd2903042f6577db)
+checking 'show' ............................... PASS (d5cb02fa53f44540) NA
+checking 'print' .............................. PASS (19e6cb20d3a1bbed) NA
+checking 'hist' ............................... PASS (92c01a74babbd9d2) 885b2aa14f6149d7
+checking '>' .................................. PASS (57132767bf0cf5c4) 324.9
+checking '+' .................................. PASS (78a44d6226d9747a) 649.9
+checking '/' .................................. PASS (35e25cdc915f220c) 162.4
+checking '*' .................................. PASS (78a44d6226d9747a) 649.9
+checking 'median' ............................. PASS (96180b4a6b132e65) 0.9
+checking 'quantile' ........................... PASS (e438e836956ab833) 14.9
+checking 'Image' .............................. PASS (25cb6efefa0c888d) 324.9
+checking 'as.Image' ........................... PASS (fd2903042f6577db) 324.9
+checking 'is.Image' ........................... PASS (29e1e263d8769b6f) 0.9
+checking 'imageData' .......................... PASS (8657532cd462ea81) 324.9
+checking 'imageData<-' ........................ PASS (fd2903042f6577db) 324.9
+checking 'as.raster' .......................... PASS (f789919d8c93fb15) NA
+checking 'colorMode<-' ........................ PASS (fd2903042f6577db) 324.9
+checking 'numberOfFrames' ..................... PASS (96180b4a6b132e65) 0.9
+checking 'getFrames' .......................... PASS (5fc2af241f80dfe3) 792ca128c76659ed
+checking 'display' ............................ PASS (19741a401daf92e2) e4e805547bbe7024
+checking 'display' ............................ PASS (57f54e3531a18f4e) -0.1
+checking 'image' .............................. PASS (57f54e3531a18f4e) -0.1
+checking 'drawCircle' ......................... PASS (545d29ae11f62e79) 324.9
+checking '[' .................................. PASS (fd2903042f6577db) 324.9
+checking 'resize' ............................. PASS (066442c79b5dbc5d) 4543605
+checking 'rotate' ............................. PASS (6fef9b3e4085e72c) 483.6843
+checking 'flip' ............................... PASS (fd2903042f6577db) 324.9
+checking 'flop' ............................... PASS (fd2903042f6577db) 324.9
+checking 'translate' .......................... PASS (545d29ae11f62e79) 324.9
+checking 'affine' ............................. PASS (dc78180c379f20d2) 39.9
+checking 'transpose' .......................... PASS (fd2903042f6577db) 324.9
+checking 'thresh' ............................. PASS (9a58bf6b1ef39788) -0.1
+checking 'channel' ............................ PASS (fd2903042f6577db) 324.9
+checking 'otsu' ............................... PASS (f397fccddb916350) 0.8980469
+checking 'bwlabel' ............................ PASS (fd2903042f6577db) 324.9
+checking 'colorLabels' ........................ PASS (5a10b69b9bb71739) 2849.9
+checking 'stackObjects' ....................... PASS (8f1472c017e2814c) 324.9
+checking 'stackObjects' ....................... PASS (57f54e3531a18f4e) -0.1
+checking 'paintObjects' ....................... PASS (51f47541d96a6ecf) 270.7333
+checking 'rmObjects' .......................... PASS (9a58bf6b1ef39788) -0.1
+checking 'reenumerate' ........................ PASS (9a58bf6b1ef39788) -0.1
+checking 'reenumerate' ........................ PASS (fd2903042f6577db) 324.9
+checking 'computeFeatures' .................... PASS (559dfc74a97494a3) 950932.9
+checking 'computeFeatures' .................... PASS (6dc4c63dde7e7828) 32d95b1456f80f68
+checking 'ocontour' ........................... PASS (2663ed016907f460) 2cd2b9ad99f98fd3
+checking 'localCurvature' ..................... PASS (80236128c43c5c64) aa70763bde359b48
+checking 'normalize' .......................... PASS (545d29ae11f62e79) 324.9
+checking 'normalize' .......................... PASS (545d29ae11f62e79) 324.9
+checking 'gblur' .............................. PASS (19cc14ba61b7caea) NA
+checking 'filter2' ............................ PASS (a98d049f987b512a) 8124.9
+checking 'medianFilter' ....................... PASS (545d29ae11f62e79) 324.9
+checking 'equalize' ........................... PASS (fd2903042f6577db) 324.9
+checking 'erode' .............................. PASS (57132767bf0cf5c4) 324.9
+checking 'dilate' ............................. PASS (57132767bf0cf5c4) 324.9
+checking 'opening' ............................ PASS (57132767bf0cf5c4) 324.9
+checking 'closing' ............................ PASS (57132767bf0cf5c4) 324.9
+checking 'whiteTopHat' ........................ PASS (9a58bf6b1ef39788) -0.1
+checking 'blackTopHat' ........................ PASS (9a58bf6b1ef39788) -0.1
+checking 'selfComplementaryTopHat' ............ PASS (78a44d6226d9747a) 649.9
+checking 'distmap' ............................ PASS (2690b089a32348c7) Inf
+checking 'watershed' .......................... PASS (fd2903042f6577db) 324.9
+checking 'floodFill' .......................... PASS (9a58bf6b1ef39788) -0.1
+checking 'fillHull' ........................... PASS (57132767bf0cf5c4) 324.9
+checking 'propagate' .......................... PASS (9a58bf6b1ef39788) -0.1
+checking 'toRGB' .............................. PASS (c8f79be3016a4f30) 2849.9
+checking 'rgbImage' ........................... PASS (04e5727b08bf45cb) 1274.9
+checking 'colormap' ........................... PASS (465b64f5769d9429) 2652.253
+checking 'combine' ............................ PASS (4d32f0cd424c46ed) 1274.9
+checking 'combine' ............................ PASS (d235aa9dfa5f7f28) 7874.9
+checking 'tile' ............................... PASS (d78128e72e9c8cfb) 24420.64
+checking 'untile' ............................. PASS (2f982bd94f9d06ba) 10445.49
+checking 'abind' .............................. PASS (4d32f0cd424c46ed) 1274.9
+
+Warning messages:
+1: In hash.old(y) : NAs introduced by coercion
+2: In hash.old(y) : NAs introduced by coercion
+3: In hash.old(y) : NAs introduced by coercion
+4: In hash.old(y) : NAs introduced by coercion
+> 
+> ## test: 2D Grayscale 64x48
+> x <- nuclei[50:113,208:255,2]
+> testEBImageFunctions(as.array(x))
+new test (hash=ea1a665d75817fbb)
+checking 'show' ............................... PASS (642a853c4a9fb245) NA
+checking 'print' .............................. PASS (642a853c4a9fb245) NA
+checking 'hist' ............................... PASS (758d77d39292aceb) 22261eb2334cd65e
+checking '>' .................................. PASS (b630618a382dd149) 1217568
+checking '+' .................................. PASS (78bcec8b0be50e22) 2940144
+checking '/' .................................. PASS (eaac0e8ec321e267) 735035.9
+checking '*' .................................. PASS (78bcec8b0be50e22) 2940144
+checking 'median' ............................. PASS (0ba48ebb83d20dcf) 0.04509804
+checking 'quantile' ........................... PASS (922eedf2ceb73c21) 7.331373
+checking 'Image' .............................. PASS (930c159d012125dd) 1470072
+checking 'as.Image' ........................... PASS (d2785eea34628f36) 1470072
+checking 'is.Image' ........................... PASS (7a36fa498bbfcb30) -0.1
+checking 'imageData' .......................... PASS (ea1a665d75817fbb) 1470072
+checking 'imageData<-' ........................ PASS (ea1a665d75817fbb) 1470072
+checking 'as.raster' .......................... PASS (92e268a98a202d0d) NA
+checking 'colorMode<-' ........................ PASS (2f347dbc7d4df203) NA
+checking 'numberOfFrames' ..................... PASS (96180b4a6b132e65) 0.9
+checking 'getFrames' .......................... PASS (c8a1bed0f342ba9c) f9df249b9d461561
+checking 'display' ............................ PASS (e5abc9317013ef0f) 4597ff5701547c38
+checking 'display' ............................ PASS (57f54e3531a18f4e) -0.1
+checking 'image' .............................. PASS (57f54e3531a18f4e) -0.1
+checking 'drawCircle' ......................... PASS (d3ac30a887c990f0) 1607165
+checking '[' .................................. PASS (9bb7b574fc489848) 48.94314
+checking 'resize' ............................. PASS (00fab9ec40eb5e2f) 1419476
+checking 'rotate' ............................. PASS (447c32314cc4e9d7) 2493977
+checking 'flip' ............................... PASS (a8c542a26b0874e8) 1195977
+checking 'flop' ............................... PASS (7e68dec7614712d2) 1462119
+checking 'translate' .......................... PASS (21641bbeafdcbc39) 2133961
+checking 'affine' ............................. PASS (1ed86f465543429e) 54171.17
+checking 'transpose' .......................... PASS (f39a2b54df5d5df8) 1522055
+checking 'thresh' ............................. PASS (aed13a82eaa0d6f6) 1262811
+checking 'channel' ............................ PASS (ea1a665d75817fbb) 1470072
+checking 'otsu' ............................... PASS (0c822739ccd9b674) 0.3160156
+checking 'bwlabel' ............................ PASS (d2978ac5475fd985) 1199387
+checking 'colorLabels' ........................ PASS (0505b0b4e09d05ed) 10021713
+checking 'stackObjects' ....................... PASS (f8eb9b4047e677fb) 718602.9
+checking 'stackObjects' ....................... PASS (57f54e3531a18f4e) -0.1
+checking 'paintObjects' ....................... PASS (f064eb914151d29d) 1447733
+checking 'rmObjects' .......................... PASS (8f36486b7880f33a) -0.1
+checking 'reenumerate' ........................ PASS (8f36486b7880f33a) -0.1
+checking 'reenumerate' ........................ PASS (d2978ac5475fd985) 1199387
+checking 'computeFeatures' .................... PASS (9dca8bdb6fc6383c) 3634215
+checking 'computeFeatures' .................... PASS (3a766122214a21a8) cf1b0ac94ace207a
+checking 'ocontour' ........................... PASS (ddb1a61382610e29) 0dab270d98fdbdd8
+checking 'localCurvature' ..................... PASS (48edb77f99eb5e0a) a351f1682141f4de
+checking 'normalize' .......................... PASS (dd024957ed9be970) 1431381
+checking 'normalize' .......................... PASS (327914cc2a95292d) 1266798
+checking 'gblur' .............................. PASS (ba634c0d6cb869f5) 1466231
+checking 'filter2' ............................ PASS (b1783e1383b0d429) 36592176
+checking 'medianFilter' ....................... PASS (f73f9cb355632e37) 1454712
+checking 'equalize' ........................... PASS (3229c589865d1dd0) 2642794
+checking 'erode' .............................. PASS (a808a82152309cd0) 759774.9
+checking 'dilate' ............................. PASS (6ad0a88f208f30d0) 1797576
+checking 'opening' ............................ PASS (14899fd17ddf0b81) 1152484
+checking 'closing' ............................ PASS (8f3f27e6caed70de) 1232737
+checking 'whiteTopHat' ........................ PASS (0461dfe452491caf) 18730.15
+checking 'blackTopHat' ........................ PASS (8f53c6a02fcc449b) 27716.23
+checking 'selfComplementaryTopHat' ............ PASS (53e3b78d71635465) 2929436
+checking 'distmap' ............................ PASS (66da40cd47ec8762) 3826969
+checking 'watershed' .......................... PASS (fe4e99c1bb5f9562) 2762113
+checking 'floodFill' .......................... PASS (e508f01acf50e00d) 1470197
+checking 'fillHull' ........................... PASS (b630618a382dd149) 1217568
+checking 'propagate' .......................... PASS (9e56846cb0e2b112) 3464201
+checking 'toRGB' .............................. PASS (7b33402954c29951) 12381911
+checking 'rgbImage' ........................... PASS (c44ead1803e628d9) 4859544
+checking 'colormap' ........................... PASS (fbcba322d3f5c951) 11126039
+checking 'combine' ............................ PASS (3f46a10efaaab318) 5597376
+checking 'combine' ............................ PASS (c2857e1a820b6b96) 33922678
+checking 'tile' ............................... PASS (78eab32eac869231) 78718054
+checking 'untile' ............................. PASS (59f8bb36c4481a79) 72597772
+checking 'abind' .............................. PASS (3f46a10efaaab318) 5597376
+
+Warning messages:
+1: In hash.old(y) : NAs introduced by coercion
+2: In hash.old(y) : NAs introduced by coercion
+3: In hash.old(y) : NAs introduced by coercion
+4: In hash.old(y) : NAs introduced by coercion
+> 
+> ## test: 2D Color 32x48x1
+> x <- sample[1:32, 1:48]
+> testEBImageFunctions(Image(as.vector(x), dim(x), Color))
+new test (hash=42e4a51e3404811e)
+checking 'show' ............................... PASS (c341f3b9fc1ff684) NA
+checking 'print' .............................. PASS (419cec4864e97478) NA
+checking 'hist' ............................... PASS (91566cadc223f9a3) 05bf2b59f2a77bb3
+checking '>' .................................. PASS (c843a337bba9a51b) 988518.9
+checking '+' .................................. PASS (5b9c39c8dde0f4ba) 1531522
+checking '/' .................................. PASS (c8eb6e9ad11ccbd8) 382880.4
+checking '*' .................................. PASS (5b9c39c8dde0f4ba) 1531522
+checking 'median' ............................. PASS (1855a565fb986239) 0.5039216
+checking 'quantile' ........................... PASS (3b22f40de65ddba9) 10.10392
+checking 'Image' .............................. PASS (42e4a51e3404811e) 765760.9
+checking 'as.Image' ........................... PASS (42e4a51e3404811e) 765760.9
+checking 'is.Image' ........................... PASS (29e1e263d8769b6f) 0.9
+checking 'imageData' .......................... PASS (e5ac272d081a94e8) 765760.9
+checking 'imageData<-' ........................ PASS (42e4a51e3404811e) 765760.9
+checking 'as.raster' .......................... PASS (b2474ac70f11c65b) NA
+checking 'colorMode<-' ........................ PASS (659e2fc3253b48a5) 765760.9
+checking 'numberOfFrames' ..................... PASS (96180b4a6b132e65) 0.9
+checking 'getFrames' .......................... PASS (0db71a429380e045) 2ce69e3965ee85bb
+checking 'display' ............................ PASS (e02c3fc0d11fe3dd) ece292adeae37a84
+checking 'display' ............................ PASS (57f54e3531a18f4e) -0.1
+checking 'image' .............................. PASS (57f54e3531a18f4e) -0.1
+checking 'drawCircle' ......................... PASS (db43b8c94523e893) 782100.7
+checking '[' .................................. PASS (9e53ad55777f433d) 1285.273
+checking 'resize' ............................. PASS (3e691caece9a23f3) 2941770
+checking 'rotate' ............................. PASS (8445c36b57d60c5d) 1247487
+checking 'flip' ............................... PASS (a292f4134eba03ae) 661525.2
+checking 'flop' ............................... PASS (edb212fe7ec01fae) 768451.1
+checking 'translate' .......................... PASS (158cc0e28f649d32) 820434.5
+checking 'affine' ............................. PASS (3aca89b692c9110c) 22238.15
+checking 'transpose' .......................... PASS (32f4e3c321435048) 652051.4
+checking 'thresh' ............................. PASS (bf6cf9e7f1b50aa0) 382924.9
+checking 'channel' ............................ PASS (288c8c2a23d7d309) 162800.7
+checking 'otsu' ............................... PASS (2634e72eb12ab864) 0.03085937
+checking 'bwlabel' ............................ PASS (a53dfbd74180c15c) 832970.9
+checking 'colorLabels' ........................ PASS (9aea6bf69b9535b1) 5455321
+checking 'stackObjects' ....................... PASS (202a964735f173b1) 596493.9
+checking 'stackObjects' ....................... PASS (57f54e3531a18f4e) -0.1
+checking 'paintObjects' ....................... PASS (d729a9cd58722f9f) 919069.9
+checking 'rmObjects' .......................... PASS (315db6e1904a02f2) 565.9
+checking 'reenumerate' ........................ PASS (315db6e1904a02f2) 565.9
+checking 'reenumerate' ........................ PASS (a53dfbd74180c15c) 832970.9
+checking 'computeFeatures' .................... PASS (607eae59e3423025) 3988895
+checking 'computeFeatures' .................... PASS (3a766122214a21a8) cf1b0ac94ace207a
+checking 'ocontour' ........................... PASS (da840dfc876b827d) 90e46e616d6ca694
+checking 'localCurvature' ..................... PASS (744209c192ca349a) 97da7b3a7c2ec717
+checking 'normalize' .......................... PASS (fd0fb40ffb018169) 747801.4
+checking 'normalize' .......................... PASS (4b1f8e1c8e2f7bf3) 809649.2
+checking 'gblur' .............................. PASS (850f1053006670c6) 761679.3
+checking 'filter2' ............................ PASS (c6d3b7ddd80521f9) 19130329
+checking 'medianFilter' ....................... PASS (07a157a471910361) 765141.6
+checking 'equalize' ........................... PASS (0e526b3729fc6369) 705546.8
+checking 'erode' .............................. PASS (36413a22e579f0bc) 908526.9
+checking 'dilate' ............................. PASS (144e48361a76b7c4) 1072658
+checking 'opening' ............................ PASS (b7c1eac6357c6e80) 985798.9
+checking 'closing' ............................ PASS (2e4462eb216d9848) 988727.9
+checking 'whiteTopHat' ........................ PASS (9db3096e14961ab7) 3572.998
+checking 'blackTopHat' ........................ PASS (bf87d7286ea29ad9) 7139.473
+checking 'selfComplementaryTopHat' ............ PASS (b77a4b378692f404) 1533178
+checking 'distmap' ............................ PASS (f6300a4f340ae60a) 12740449
+checking 'watershed' .......................... PASS (8a9f971d0fd151b0) 988518.9
+checking 'floodFill' .......................... PASS (58b50ac20366b30b) 765755.4
+checking 'fillHull' ........................... PASS (c843a337bba9a51b) 988518.9
+checking 'propagate' .......................... PASS (c9ccd5b308eb7ef1) 26630579
+checking 'toRGB' .............................. PASS (42e4a51e3404811e) 765760.9
+checking 'rgbImage' ........................... PASS (3e4ef74fd42ad9d7) 3520680
+checking 'colormap' ........................... PASS (d2139029833173d7) 1816912
+checking 'combine' ............................ PASS (475166958acc7172) 2960568
+checking 'combine' ............................ PASS (dcd2456fbb97967f) 18119267
+checking 'tile' ............................... PASS (da0d33a63c42b563) 33061491
+checking 'untile' ............................. PASS (3268a8970f502c72) 27788327
+checking 'abind' .............................. PASS (6fd94295b094e0dd) 2960568
+
+Warning messages:
+1: In hash.old(y) : NAs introduced by coercion
+2: In hash.old(y) : NAs introduced by coercion
+3: In hash.old(y) : NAs introduced by coercion
+> 
+> ## test: 3D Color 65x17x3
+> x <- sample.color[1:65, 1:17,]
+> testEBImageFunctions(x)
+new test (hash=de81d0d989193946)
+checking 'show' ............................... PASS (2e8e7d2423e3ccfe) NA
+checking 'print' .............................. PASS (6ab730d47def3609) NA
+checking 'hist' ............................... PASS (6a57fc1cd042ba00) 65b5b051743defa8
+checking '>' .................................. PASS (53e61ab2bda88b20) 1058553
+checking '+' .................................. PASS (bfc8079a28d79b4c) 4072301
+checking '/' .................................. PASS (b55c46146fdfe46d) 1018075
+checking '*' .................................. PASS (bfc8079a28d79b4c) 4072301
+checking 'median' ............................. PASS (1bc9c6651e4507c8) 0.2333333
+checking 'quantile' ........................... PASS (686379af319f166a) 7.280392
+checking 'Image' .............................. PASS (de81d0d989193946) 2036151
+checking 'as.Image' ........................... PASS (de81d0d989193946) 2036151
+checking 'is.Image' ........................... PASS (29e1e263d8769b6f) 0.9
+checking 'imageData' .......................... PASS (cc315d22f9180202) 2036151
+checking 'imageData<-' ........................ PASS (de81d0d989193946) 2036151
+checking 'as.raster' .......................... PASS (9f57220e155de221) NA
+checking 'colorMode<-' ........................ PASS (908b90694e179a06) 2036151
+checking 'numberOfFrames' ..................... PASS (96180b4a6b132e65) 0.9
+checking 'getFrames' .......................... PASS (1d27d730987b7a26) 8e44d4ba87d4aa02
+checking 'display' ............................ PASS (0a11225efa5072ae) df8baf2e444e61aa
+checking 'display' ............................ PASS (57f54e3531a18f4e) -0.1
+checking 'image' .............................. PASS (57f54e3531a18f4e) -0.1
+checking 'drawCircle' ......................... PASS (7dca62c4ac89e623) 2049936
+checking '[' .................................. PASS (c393e7deb8110d2e) 9950.096
+checking 'resize' ............................. PASS (75a7216227f8dbfa) 15155777
+checking 'rotate' ............................. PASS (cba5275a49d1c025) 4525312
+checking 'flip' ............................... PASS (ce5674ff635ed106) 1977928
+checking 'flop' ............................... PASS (c9a027c754ade888) 2048664
+checking 'translate' .......................... PASS (98b2270654265063) 2998417
+checking 'affine' ............................. PASS (091470a40c4fba44) 226067.6
+checking 'transpose' .......................... PASS (2a083c731f2328d6) 1907377
+checking 'thresh' ............................. PASS (aeb34a5300761161) 954376.9
+checking 'channel' ............................ PASS (628995dc542023b6) 257578.5
+checking 'otsu' ............................... PASS (a7933cc16292d271) 0.3316406
+checking 'bwlabel' ............................ PASS (f8bd68974c926eec) 230329.9
+checking 'colorLabels' ........................ PASS (80dc35afde7d79d4) 1920855
+checking 'stackObjects' ....................... PASS (6f9fa1b0c36f14c3) 712799.6
+checking 'stackObjects' ....................... PASS (57f54e3531a18f4e) -0.1
+checking 'paintObjects' ....................... PASS (1812b3a1be6f2a21) 2090128
+checking 'rmObjects' .......................... PASS (cf00ced5df055ef3) -0.1
+checking 'reenumerate' ........................ PASS (cf00ced5df055ef3) -0.1
+checking 'reenumerate' ........................ PASS (f8bd68974c926eec) 230329.9
+checking 'computeFeatures' .................... PASS (f17bea67c6db8efa) 746878.2
+checking 'computeFeatures' .................... PASS (6dc4c63dde7e7828) 32d95b1456f80f68
+checking 'ocontour' ........................... PASS (62edd8045dda1043) 91bbaa639ef2cb55
+checking 'localCurvature' ..................... PASS (9ddeedd40b4e5c7b) a2c60afaa6d43988
+checking 'normalize' .......................... PASS (0b70467017a902ca) 1788995
+checking 'normalize' .......................... PASS (95a2f0d3d09f9593) 1858155
+checking 'gblur' .............................. PASS (220352a03dba8eda) 2029979
+checking 'filter2' ............................ PASS (4ca91f0d9cb2a1e9) 50878763
+checking 'medianFilter' ....................... PASS (afb4863b9d632271) 2034604
+checking 'equalize' ........................... PASS (6d80d436cf8278ec) 2942981
+checking 'erode' .............................. PASS (86fbc1458f72ebfe) 830816.9
+checking 'dilate' ............................. PASS (fffa10444677a5de) 1339263
+checking 'opening' ............................ PASS (2502c3f605bf79f2) 1013295
+checking 'closing' ............................ PASS (16dfffda0eb72d6d) 1069405
+checking 'whiteTopHat' ........................ PASS (ab977dc4ab166626) 16585.63
+checking 'blackTopHat' ........................ PASS (e1f6d9987bffb784) 22039.05
+checking 'selfComplementaryTopHat' ............ PASS (37e1c11a042ce219) 4071763
+checking 'distmap' ............................ PASS (046048efe2665db2) 6038778
+checking 'watershed' .......................... PASS (5810b16b06c7a236) 1058553
+checking 'floodFill' .......................... PASS (87ccae676293e02d) 2036854
+checking 'fillHull' ........................... PASS (53e61ab2bda88b20) 1058553
+checking 'propagate' .......................... PASS (50fd5efe0e56a631) 7755487
+checking 'toRGB' .............................. PASS (de81d0d989193946) 2036151
+checking 'rgbImage' ........................... PASS (cab54e5b08f3aae8) 7617867
+checking 'colormap' ........................... PASS (1844757fe6d6692b) 1657338
+checking 'combine' ............................ PASS (07952012d019c893) 8350133
+checking 'combine' ............................ PASS (8cb162a59398a20b) 52959073
+checking 'tile' ............................... PASS (498539964fb48dd4) 107188639
+checking 'untile' ............................. PASS (9fb04cc414930cc6) 97994583
+checking 'abind' .............................. PASS (07952012d019c893) 8350133
+
+Warning messages:
+1: In hash.old(y) : NAs introduced by coercion
+2: In hash.old(y) : NAs introduced by coercion
+3: In hash.old(y) : NAs introduced by coercion
+> 
+> ## test: 3D Grayscale logical 32x32x2
+> x <- sample[32:63, 32:63]
+> x <- x > otsu(x)
+> x <- combine(x, x)
+> testEBImageFunctions(x)
+new test (hash=45775cc98ccde7d0)
+checking 'show' ............................... PASS (17bd08c197d327bf) NA
+checking 'print' .............................. PASS (86a9e2802088f094) NA
+checking 'hist' ............................... PASS (b67b7652db932932) e392832c9ac4ef1a
+checking '>' .................................. PASS (45775cc98ccde7d0) 1055560
+checking '+' .................................. PASS (a808c74d4b8f4b1a) 2111120
+checking '/' .................................. PASS (050916ceba989d59) 527779.9
+checking '*' .................................. PASS (a808c74d4b8f4b1a) 2111120
+checking 'median' ............................. PASS (f5699b21f198d2c3) -0.1
+checking 'quantile' ........................... PASS (abb46cc6fe827b1e) 8.9
+checking 'Image' .............................. PASS (5644736cfb4b41de) 1055560
+checking 'as.Image' ........................... PASS (45775cc98ccde7d0) 1055560
+checking 'is.Image' ........................... PASS (29e1e263d8769b6f) 0.9
+checking 'imageData' .......................... PASS (a8595646a000f9e9) 1055560
+checking 'imageData<-' ........................ PASS (45775cc98ccde7d0) 1055560
+checking 'as.raster' .......................... PASS (35debdd5bd091cf6) NA
+checking 'colorMode<-' ........................ PASS (45775cc98ccde7d0) 1055560
+checking 'numberOfFrames' ..................... PASS (0cd27cfa5ab0714e) 1.9
+checking 'getFrames' .......................... PASS (8c50f91fce805fce) df388a91608fa45d
+checking 'display' ............................ PASS (a69f9d95f70d6b4f) 2ed798e15d730f30
+checking 'display' ............................ PASS (57f54e3531a18f4e) -0.1
+checking 'image' .............................. PASS (57f54e3531a18f4e) -0.1
+checking 'drawCircle' ......................... PASS (a54d00e6ec30cdaa) 1656514
+checking '[' .................................. PASS (43978c7896f345b7) 1889.9
+checking 'resize' ............................. PASS (b189246d26913306) 9108608
+checking 'rotate' ............................. PASS (a4a84e73999cd629) 1600126
+checking 'flip' ............................... PASS (3705f945c5050495) 714439.9
+checking 'flop' ............................... PASS (91665fdcc5d39ce2) 1064092
+checking 'translate' .......................... PASS (6c5b0b16cf2595ee) 1105982
+checking 'affine' ............................. PASS (4a6b1cf59de37216) 133467.8
+checking 'transpose' .......................... PASS (9be0ad64c90eabc7) 758083.9
+checking 'thresh' ............................. PASS (cf8b639ee5c91bd5) 824497.9
+checking 'channel' ............................ PASS (45775cc98ccde7d0) 1055560
+checking 'otsu' ............................... PASS (9966633498a5e2d8) 1.4
+checking 'bwlabel' ............................ PASS (a00294e8900f15eb) 1597368
+checking 'colorLabels' ........................ PASS (6dc0e9def8039903) 3525827
+checking 'stackObjects' ....................... PASS (8ddf2c694201cbf1) 8889538
+checking 'stackObjects' ....................... PASS (57f54e3531a18f4e) -0.1
+checking 'paintObjects' ....................... PASS (f4107fd743a22ecf) 834266.6
+checking 'rmObjects' .......................... PASS (07bc253bd936adb5) 1303423
+checking 'reenumerate' ........................ PASS (ff441c512995c200) 1244828
+checking 'reenumerate' ........................ PASS (a00294e8900f15eb) 1597368
+checking 'computeFeatures' .................... PASS (1d847743be56e5a7) 416164140
+checking 'computeFeatures' .................... PASS (3a766122214a21a8) cf1b0ac94ace207a
+checking 'ocontour' ........................... PASS (cdb0d0c07c1e0775) b86c6546ddbffb2d
+checking 'localCurvature' ..................... PASS (4c13330f1d7fcbd6) 4805cb6e9d47da01
+checking 'normalize' .......................... PASS (a34cd51357995e04) 1055560
+checking 'normalize' .......................... PASS (a34cd51357995e04) 1055560
+checking 'gblur' .............................. PASS (f499c36c42da96ae) 1037581
+checking 'filter2' ............................ PASS (e5a3d2e924e050fe) 26149148
+checking 'medianFilter' ....................... PASS (ff54de12f77d64e5) 1030730
+checking 'equalize' ........................... PASS (a34cd51357995e04) 1055560
+checking 'erode' .............................. PASS (87a98696219eeb40) 617311.9
+checking 'dilate' ............................. PASS (49a5f7a8063bdcc8) 1591594
+checking 'opening' ............................ PASS (557cd0af78bc3bbe) 861145.9
+checking 'closing' ............................ PASS (04eefe7f21e9b36a) 1188258
+checking 'whiteTopHat' ........................ PASS (0a2dbcdbb9e396a8) 148131.9
+checking 'blackTopHat' ........................ PASS (af7e8fad58a0f587) 202411.9
+checking 'selfComplementaryTopHat' ............ PASS (729954d933215c8d) 2151192
+checking 'distmap' ............................ PASS (8ba0b0fb6770e8a3) 3856630
+checking 'watershed' .......................... PASS (0bd6a31bc6197067) 1443978
+checking 'floodFill' .......................... PASS (7a8a06c4be73e522) 2057384
+checking 'fillHull' ........................... PASS (d3904950b8acdb73) 1096352
+checking 'propagate' .......................... PASS (6c610fe4376714f6) 1613920
+checking 'toRGB' .............................. PASS (57b9a77f5a12af4f) 8499672
+checking 'rgbImage' ........................... PASS (82e9189e21600e37) 4777616
+checking 'colormap' ........................... PASS (244a6e10e928fb5e) 10283526
+checking 'combine' ............................ PASS (fbb690c6993fed59) 3888784
+checking 'combine' ............................ PASS (db4c289138cad595) 23054440
+checking 'tile' ............................... PASS (b5d61ba1e08ccf53) 48846817
+checking 'untile' ............................. PASS (f740856c06d69a15) 41221063
+checking 'abind' .............................. PASS (dca1271130f49d13) 3888784
+
+Warning messages:
+1: In hash.old(y) : NAs introduced by coercion
+2: In hash.old(y) : NAs introduced by coercion
+3: In hash.old(y) : NAs introduced by coercion
+> 
+> ## test: 4D Color 33x16x3x2
+> x <- sample.color[1:33, 1:16,]
+> x <- combine(x, x)
+> testEBImageFunctions(x)
+new test (hash=7b41d26d4354fed1)
+checking 'show' ............................... PASS (0e20a4fb07853778) NA
+checking 'print' .............................. PASS (0b3e2b71e556d7a8) NA
+checking 'hist' ............................... PASS (7266d20001b63e05) 48ff490a50be36d5
+checking '>' .................................. PASS (5a6494dd882e20ae) 2013322
+checking '+' .................................. PASS (78d490385e4c68e5) 4709417
+checking '/' .................................. PASS (98087f12b7f61f2d) 1177354
+checking '*' .................................. PASS (78d490385e4c68e5) 4709417
+checking 'median' ............................. PASS (fdd3c9a35d83db2f) 0.3823529
+checking 'quantile' ........................... PASS (e26e9f3ec19dd1e9) 8.139216
+checking 'Image' .............................. PASS (7b41d26d4354fed1) 2354709
+checking 'as.Image' ........................... PASS (7b41d26d4354fed1) 2354709
+checking 'is.Image' ........................... PASS (29e1e263d8769b6f) 0.9
+checking 'imageData' .......................... PASS (242db37f6093cb73) 2354709
+checking 'imageData<-' ........................ PASS (7b41d26d4354fed1) 2354709
+checking 'as.raster' .......................... PASS (b47cd18dd737d179) NA
+checking 'colorMode<-' ........................ PASS (739b8cc774a94061) 2354709
+checking 'numberOfFrames' ..................... PASS (0cd27cfa5ab0714e) 1.9
+checking 'getFrames' .......................... PASS (dfada18bf7486f49) 6bccd3af078885b1
+checking 'display' ............................ PASS (713161e915733541) bcf32094ce1d3dd4
+checking 'display' ............................ PASS (57f54e3531a18f4e) -0.1
+checking 'image' .............................. PASS (57f54e3531a18f4e) -0.1
+checking 'drawCircle' ......................... PASS (516ff89688aa090a) 2650006
+checking '[' .................................. PASS (b7ab984ac4c76be7) 40701.82
+checking 'resize' ............................. PASS (19f045e2a12c3dcd) 76725662
+checking 'rotate' ............................. PASS (5ca7d358dc5f6cb8) 4140744
+checking 'flip' ............................... PASS (ec438fa29adb6692) 2309692
+checking 'flop' ............................... PASS (189be924d5127b6d) 2358658
+checking 'translate' .......................... PASS (6b8b9dffcab202c5) 3393923
+checking 'affine' ............................. PASS (8490fa9d387d7f22) 301692.6
+checking 'transpose' .......................... PASS (f415a850f96da517) 2303263
+checking 'thresh' ............................. PASS (96c1db8a6b3c5f8f) 1549362
+checking 'channel' ............................ PASS (d0059f4eaa729cb0) 287206.2
+checking 'otsu' ............................... PASS (18794185be6d9049) 1.370703
+checking 'bwlabel' ............................ PASS (cf48a1553a7ca557) 360413.9
+checking 'colorLabels' ........................ PASS (87aec3caedd0f2b2) 3039066
+checking 'stackObjects' ....................... PASS (6c9d66add9231c0b) 2472601
+checking 'stackObjects' ....................... PASS (57f54e3531a18f4e) -0.1
+checking 'paintObjects' ....................... PASS (a94d303375c85afc) 2558300
+checking 'rmObjects' .......................... PASS (3c7c32b7c14bf399) 261782.9
+checking 'reenumerate' ........................ PASS (3c7c32b7c14bf399) 261782.9
+checking 'reenumerate' ........................ PASS (cf48a1553a7ca557) 360413.9
+checking 'computeFeatures' .................... PASS (43ed64a6574660f3) 749353.1
+checking 'computeFeatures' .................... PASS (6dc4c63dde7e7828) 32d95b1456f80f68
+checking 'ocontour' ........................... PASS (0cc37fb8be6a9150) a8ef8364446f767a
+checking 'localCurvature' ..................... PASS (91ac6bafe7f8c008) feedc85342708fe9
+checking 'normalize' .......................... PASS (6f82aae3b180c153) 2693244
+checking 'normalize' .......................... PASS (27441d4baba7c727) 2315924
+checking 'gblur' .............................. PASS (6317c51db4b2e332) 2349701
+checking 'filter2' ............................ PASS (ff5a03c8be20359c) 58841710
+checking 'medianFilter' ....................... PASS (08a067834027edb5) 2352447
+checking 'equalize' ........................... PASS (02d6a459ef536ba0) 2616480
+checking 'erode' .............................. PASS (c065e093042fa687) 1547948
+checking 'dilate' ............................. PASS (3ad67b8d06ebb303) 2604996
+checking 'opening' ............................ PASS (789077961fab1b84) 1919386
+checking 'closing' ............................ PASS (930601017e06bf69) 2038196
+checking 'whiteTopHat' ........................ PASS (248102e12cde390f) 12437.36
+checking 'blackTopHat' ........................ PASS (2769c6f876a888b2) 19780.97
+checking 'selfComplementaryTopHat' ............ PASS (535e331642e8affe) 4711722
+checking 'distmap' ............................ PASS (e9f6f8a5899a40fd) 10836360
+checking 'watershed' .......................... PASS (355196749445c942) 2013322
+checking 'floodFill' .......................... PASS (a23cce6e67e088ae) 2355706
+checking 'fillHull' ........................... PASS (5a6494dd882e20ae) 2013322
+checking 'propagate' .......................... PASS (6a54d058269b4352) 11618104
+checking 'toRGB' .............................. PASS (7b41d26d4354fed1) 2354709
+checking 'rgbImage' ........................... PASS (05d03bf83df83dbf) 12071186
+checking 'colormap' ........................... PASS (40f159a4fec5a5e4) 2273276
+checking 'combine' ............................ PASS (943e696dd062479b) 9516503
+checking 'combine' ............................ PASS (5b2b6f0a51b05e18) 59844403
+checking 'tile' ............................... PASS (853d179912a1e6b9) 114300809
+checking 'untile' ............................. PASS (92a5de5886618db5) 105862114
+checking 'abind' .............................. PASS (26faa8168a99c898) 9516503
+
+Warning messages:
+1: In hash.old(y) : NAs introduced by coercion
+2: In hash.old(y) : NAs introduced by coercion
+3: In hash.old(y) : NAs introduced by coercion
+> 
+> ## test: 4D Grayscale 16x33x2x3
+> colorMode(x) <- Grayscale
+> imageData(x) <- aperm(x, c(2L, 1L, 4L, 3L))
+> testEBImageFunctions(x)
+new test (hash=ea218ca35d898b4f)
+checking 'show' ............................... PASS (07d544b61ec16bf1) NA
+checking 'print' .............................. PASS (185b486b9fcdb3b0) NA
+checking 'hist' ............................... PASS (06d171c8f173fbfa) c3d0c3b2d4df7f61
+checking '>' .................................. PASS (cdeb2538f3cc7adf) 1541186
+checking '+' .................................. PASS (9d2cb7572d822536) 4466272
+checking '/' .................................. PASS (93c6c46f0b0e7df5) 1116568
+checking '*' .................................. PASS (9d2cb7572d822536) 4466272
+checking 'median' ............................. PASS (fdd3c9a35d83db2f) 0.3823529
+checking 'quantile' ........................... PASS (e26e9f3ec19dd1e9) 8.139216
+checking 'Image' .............................. PASS (b2c89c8bd2d8cfbb) 2233136
+checking 'as.Image' ........................... PASS (ea218ca35d898b4f) 2233136
+checking 'is.Image' ........................... PASS (29e1e263d8769b6f) 0.9
+checking 'imageData' .......................... PASS (85da79da2cd4332f) 2233136
+checking 'imageData<-' ........................ PASS (ea218ca35d898b4f) 2233136
+checking 'as.raster' .......................... PASS (f7837c7682313ed7) NA
+checking 'colorMode<-' ........................ PASS (ea218ca35d898b4f) 2233136
+checking 'numberOfFrames' ..................... PASS (ce8815ddd92a2bbb) 5.9
+checking 'getFrames' .......................... PASS (e193a8c072f9729f) feb32c3d20c02193
+checking 'display' ............................ PASS (c99fb2dbf3f4f099) 8222a6379bd22cd5
+checking 'display' ............................ PASS (57f54e3531a18f4e) -0.1
+checking 'image' .............................. PASS (57f54e3531a18f4e) -0.1
+checking 'drawCircle' ......................... PASS (2eeddc22da537d19) 2502013
+checking '[' .................................. PASS (ca2402ebe50888e9) 41200.75
+checking 'resize' ............................. PASS (c2412da2d423bb4c) 72772706
+checking 'rotate' ............................. PASS (e88e2e93ce61d959) 3978266
+checking 'flip' ............................... PASS (735ec4743ab3f0c4) 2296325
+checking 'flop' ............................... PASS (039d65e48af245d2) 2231772
+checking 'translate' .......................... PASS (6dc22f8c9da472fe) 3827517
+checking 'affine' ............................. PASS (987d2bf7d6243ac5) 219714.6
+checking 'transpose' .......................... PASS (70d09e0401597e51) 2284582
+checking 'thresh' ............................. PASS (99ef268285d31156) 1426674
+checking 'channel' ............................ PASS (ea218ca35d898b4f) 2233136
+checking 'otsu' ............................... PASS (73c8c3c1a7c3a7e0) 9.425391
+checking 'bwlabel' ............................ PASS (48a9facb579b630f) 2016822
+checking 'colorLabels' ........................ PASS (2f81d6c90e88b2d4) 14827904
+checking 'stackObjects' ....................... PASS (28c1ffc9196cbeda) 2575847
+checking 'stackObjects' ....................... PASS (57f54e3531a18f4e) -0.1
+checking 'paintObjects' ....................... PASS (ba568e37e29116fa) 2379341
+checking 'rmObjects' .......................... PASS (150895efa1acc9af) 1934575
+checking 'reenumerate' ........................ PASS (150895efa1acc9af) 1934575
+checking 'reenumerate' ........................ PASS (48a9facb579b630f) 2016822
+checking 'computeFeatures' .................... PASS (5f168ac8bf99f53d) 734993.9
+checking 'computeFeatures' .................... PASS (6dc4c63dde7e7828) 32d95b1456f80f68
+checking 'ocontour' ........................... PASS (67c938cc8c8b0a94) e0b0122d0f8dbec3
+checking 'localCurvature' ..................... PASS (14e7e43044a6edce) dbbde661d27cf4d5
+checking 'normalize' .......................... PASS (c6700c53a770f4dc) 2526618
+checking 'normalize' .......................... PASS (321165d27d376355) 2163958
+checking 'gblur' .............................. PASS (167add1590f90b31) 2237219
+checking 'filter2' ............................ PASS (288a7b9238221d38) 56904871
+checking 'medianFilter' ....................... PASS (12259cca79cae7e1) 2231438
+checking 'equalize' ........................... PASS (8a3ab1c34c6d6b46) 2447139
+checking 'erode' .............................. PASS (76903cc870b48afc) 1121204
+checking 'dilate' ............................. PASS (9a465193b3c535f1) 2104588
+checking 'opening' ............................ PASS (f27ee35968015aff) 1451050
+checking 'closing' ............................ PASS (fc6578a91384556e) 1569448
+checking 'whiteTopHat' ........................ PASS (2545dff4d9a67f18) 12004.54
+checking 'blackTopHat' ........................ PASS (0bc09ee83c3b7e6b) 20296.87
+checking 'selfComplementaryTopHat' ............ PASS (7271dd2d24a8b8fa) 4469404
+checking 'distmap' ............................ PASS (fd74d28ff7a40bc8) 7464344
+checking 'watershed' .......................... PASS (aec9e7c80b500f8a) 1541186
+checking 'floodFill' .......................... PASS (c8f6a63ecc5faeb2) 2234521
+checking 'fillHull' ........................... PASS (cdeb2538f3cc7adf) 1541186
+checking 'propagate' .......................... PASS (2fa32c86f0afbbe9) 8948876
+checking 'toRGB' .............................. PASS (7ac5c5945e1aa8b4) 20279146
+checking 'rgbImage' ........................... PASS (6c13f1c0649a1b02) 10796552
+checking 'colormap' ........................... PASS (fffb8d91ccca5f12) 22585273
+checking 'combine' ............................ PASS (61a6f7f5c6357973) 8992851
+checking 'combine' ............................ PASS (e3af2b87e81b577e) 56431472
+checking 'tile' ............................... PASS (22c39d5bf0a57b26) 72664167
+checking 'untile' ............................. PASS (f3dd10e639f40c64) 60048605
+checking 'abind' .............................. PASS (f28d70bcf3781d08) 9273358
+
+Warning messages:
+1: In hash.old(y) : NAs introduced by coercion
+2: In hash.old(y) : NAs introduced by coercion
+3: In hash.old(y) : NAs introduced by coercion
+> 
+> ## test: Image subclass
+> ImageSubclass <- setClass ("ImageSubclass", contains = "Image", slots = c(foo = "character"))
+> x <- ImageSubclass(x, foo="bar")
+> testEBImageFunctions(x)
+new test (hash=2cd1b3ca941d0306)
+checking 'show' ............................... PASS (ce75d078eccda68c) NA
+checking 'print' .............................. PASS (e01f0947e67df522) NA
+checking 'hist' ............................... PASS (06d171c8f173fbfa) c3d0c3b2d4df7f61
+checking '>' .................................. PASS (8eb52b594f8cd22b) 1541186
+checking '+' .................................. PASS (d0da7a47899efbc4) 4466272
+checking '/' .................................. PASS (8bfccf8b88e728b0) 1116568
+checking '*' .................................. PASS (d0da7a47899efbc4) 4466272
+checking 'median' ............................. PASS (fdd3c9a35d83db2f) 0.3823529
+checking 'quantile' ........................... PASS (e26e9f3ec19dd1e9) 8.139216
+checking 'Image' .............................. PASS (b2c89c8bd2d8cfbb) 2233136
+checking 'as.Image' ........................... PASS (ea218ca35d898b4f) 2233136
+checking 'is.Image' ........................... PASS (29e1e263d8769b6f) 0.9
+checking 'imageData' .......................... PASS (85da79da2cd4332f) 2233136
+checking 'imageData<-' ........................ PASS (2cd1b3ca941d0306) 2233136
+checking 'as.raster' .......................... PASS (f7837c7682313ed7) NA
+checking 'colorMode<-' ........................ PASS (2cd1b3ca941d0306) 2233136
+checking 'numberOfFrames' ..................... PASS (ce8815ddd92a2bbb) 5.9
+checking 'getFrames' .......................... PASS (57ddf8e1c5653fa1) 578ceb264bf8c00f
+checking 'display' ............................ PASS (c99fb2dbf3f4f099) 8222a6379bd22cd5
+checking 'display' ............................ PASS (57f54e3531a18f4e) -0.1
+checking 'image' .............................. PASS (57f54e3531a18f4e) -0.1
+checking 'drawCircle' ......................... PASS (2a6138d3f6c7cf2b) 2502013
+checking '[' .................................. PASS (21b006dc9b230591) 41200.75
+checking 'resize' ............................. PASS (d4439ccaddffedbf) 72772706
+checking 'rotate' ............................. PASS (49bab3d36db066a3) 3978266
+checking 'flip' ............................... PASS (de3315d317526bf8) 2296325
+checking 'flop' ............................... PASS (1746b768e1919758) 2231772
+checking 'translate' .......................... PASS (eb6575b71bd228cf) 3827517
+checking 'affine' ............................. PASS (6f88949e9ca0500f) 219714.6
+checking 'transpose' .......................... PASS (cae5d3f85d9a6547) 2284582
+checking 'thresh' ............................. PASS (6907f0b8232e5a87) 1426674
+checking 'channel' ............................ PASS (2cd1b3ca941d0306) 2233136
+checking 'otsu' ............................... PASS (73c8c3c1a7c3a7e0) 9.425391
+checking 'bwlabel' ............................ PASS (0cd3636f1086f354) 2016822
+checking 'colorLabels' ........................ PASS (3d06f02ad8d875a7) 16357947
+checking 'stackObjects' ....................... PASS (3dfb061836ca6d36) 2575847
+checking 'stackObjects' ....................... PASS (57f54e3531a18f4e) -0.1
+checking 'paintObjects' ....................... PASS (d0e505d3ebc1fb67) 2379341
+checking 'rmObjects' .......................... PASS (72c36fcb451899a7) 1934575
+checking 'reenumerate' ........................ PASS (72c36fcb451899a7) 1934575
+checking 'reenumerate' ........................ PASS (0cd3636f1086f354) 2016822
+checking 'computeFeatures' .................... PASS (5f168ac8bf99f53d) 734993.9
+checking 'computeFeatures' .................... PASS (6dc4c63dde7e7828) 32d95b1456f80f68
+checking 'ocontour' ........................... PASS (67c938cc8c8b0a94) e0b0122d0f8dbec3
+checking 'localCurvature' ..................... PASS (14e7e43044a6edce) dbbde661d27cf4d5
+checking 'normalize' .......................... PASS (c864fc347d3e1eec) 2526618
+checking 'normalize' .......................... PASS (b924aa1ced88d0b0) 2163958
+checking 'gblur' .............................. PASS (41001e94fa243311) 2237219
+checking 'filter2' ............................ PASS (c4dde73f4cd08626) 56904871
+checking 'medianFilter' ....................... PASS (f336aae43926dd3a) 2231438
+checking 'equalize' ........................... PASS (8aff651b4bfe1e2d) 2447139
+checking 'erode' .............................. PASS (af16c2acdfe1ce54) 1121204
+checking 'dilate' ............................. PASS (6cf3e21751061042) 2104588
+checking 'opening' ............................ PASS (e32b483b92a01e61) 1451050
+checking 'closing' ............................ PASS (39b38ac520b6b72d) 1569448
+checking 'whiteTopHat' ........................ PASS (2dfa04a48724ec36) 12004.54
+checking 'blackTopHat' ........................ PASS (70ff24813042529a) 20296.87
+checking 'selfComplementaryTopHat' ............ PASS (4e93f72a43638575) 4469404
+checking 'distmap' ............................ PASS (fb1ca8ba680de2b3) 7464344
+checking 'watershed' .......................... PASS (29d5027b84535806) 1541186
+checking 'floodFill' .......................... PASS (54aa4548fd305300) 2234521
+checking 'fillHull' ........................... PASS (8eb52b594f8cd22b) 1541186
+checking 'propagate' .......................... PASS (0d73b4f196a75014) 8948876
+checking 'toRGB' .............................. PASS (3e30f901f32391f1) 20279146
+checking 'rgbImage' ........................... PASS (7265edee83ee6475) 10796552
+checking 'colormap' ........................... PASS (2d850cd3a218b6b9) 22585273
+checking 'combine' ............................ PASS (8750feb29c15297c) 8992851
+checking 'combine' ............................ PASS (c97b83093836307a) 56431472
+checking 'tile' ............................... PASS (2144c4a0e85d648a) 72664167
+checking 'untile' ............................. PASS (340bd33c3fc91099) 60048605
+checking 'abind' .............................. PASS (0fe154b843d8fc58) 9273358
+
+Warning messages:
+1: In hash.old(y) : NAs introduced by coercion
+2: In hash.old(y) : NAs introduced by coercion
+3: In hash.old(y) : NAs introduced by coercion
+> 
diff --git a/vignettes/EBImage-introduction.Rmd b/vignettes/EBImage-introduction.Rmd
new file mode 100644
index 0000000..b25c820
--- /dev/null
+++ b/vignettes/EBImage-introduction.Rmd
@@ -0,0 +1,729 @@
+---
+title: "Introduction to *EBImage*"
+author: Andrzej Oleś, Gregoire Pau, Oleg Sklyar, Wolfgang Huber
+email: andrzej.oles at embl.de
+package: EBImage
+abstract: >
+  `r Biocpkg("EBImage")` provides general purpose functionality for image processing and analysis. In the context of (high-throughput) microscopy-based cellular assays, EBImage offers tools to segment cells and extract quantitative cellular descriptors. This allows the automation of such tasks using the R programming language and facilitates the use of other tools in the R environment for signal processing, statistical modeling, machine learning and visualization with image data.
+output: 
+  BiocStyle::html_document2:
+    toc_float: true
+graphics: yes
+vignette: >
+  %\VignetteIndexEntry{Introduction to EBImage}
+  %\VignetteKeywords{image processing, visualization}
+  %\VignettePackage{EBImage}
+  %\VignetteEngine{knitr::rmarkdown}
+  %\VignetteEncoding{UTF-8}  
+---
+
+```{r setup, echo=FALSE}
+library(knitr)
+.dpi = 100
+set.seed(0)
+opts_chunk$set(comment=NA, fig.align="center", dpi=.dpi)
+knit_hooks$set(crop=NULL)
+.output = output()
+switch(.output,
+        html = opts_chunk$set(fig.retina=1),
+        latex = opts_chunk$set(out.width=".5\\textwidth")
+)
+
+.dev = switch(.output, html="svg", latex="pdf")
+options(EBImage.display = "raster")
+```
+
+<p>![](logo.png){width=128 style="padding: 0px; margin: auto;"}</p>
+
+# Getting started
+
+`r Biocpkg("EBImage")` is an R package distributed as part of the [Bioconductor](http://bioconductor.org) project. To install the package, start R and enter:
+
+```{r installation, eval=FALSE}
+source("http://bioconductor.org/biocLite.R")
+biocLite("EBImage")
+```
+
+Once `r Rpackage("EBImage")` is installed, it can be loaded by the following command.
+
+```{r library, message=FALSE}
+library("EBImage")
+```
+
+
+# Reading, displaying and writing images
+
+Basic `r Rpackage("EBImage")` functionality includes reading, writing, and displaying of images. Images are read using the function `readImage`, which takes as input a file name or an URL. To start off, let us load a sample picture distributed with the package.
+
+```{r readImage}
+f = system.file("images", "sample.png", package="EBImage")
+img = readImage(f)
+```
+
+`r Rpackage("EBImage")` currently supports three image file formats: `jpeg`, `png` and `tiff`. This list is complemented by the `r Githubpkg("aoles/RBioFormats")` package providing support for a much wider range of file formats including proprietary microscopy image data and metadata.
+
+The image which we just loaded can be visualized by the function `display`.
+
+```{r display}
+display(img, method="browser")
+```
+
+When called from an interactive R session, `display` opens the image in a JavaScript viewer in your web browser. Using the mouse or keyboard shortcuts, you can zoom in and out of the image, pan, and cycle through multiple image frames. Alternatively, the image can be displayed using R's build-in plotting facilities by calling `display` with the argument `method = "raster"`. The image is then drawn on the current device.  This allows to easily combine image data with other plotting functi [...]
+
+```{r display-raster, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+display(img, method="raster")
+text(x = 20, y = 20, label = "Parrots", adj = c(0,1), col = "orange", cex = 2)
+```
+
+The graphics displayed in an R device can be saved using `r Rpackage("base")` R functions `dev.print` or `dev.copy`. For example, lets save our annotated image as a JPEG file and verify its size on disk.
+
+```{r dev-print, eval=FALSE}
+filename = "parrots.jpg"
+dev.print(jpeg, filename = filename , width = dim(img)[1], height = dim(img)[2])
+```{r dev-print-pre, echo=FALSE, fig.show='hide', fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+display(img, method="raster")
+text(x = 20, y = 20, label = "Parrots", adj = c(0,1), col = "orange", cex = 2)
+filename = "parrots.jpg"
+dev.print(jpeg, filename = filename , width = dim(img)[1], height = dim(img)[2])
+```{r filesize}
+file.info(filename)$size
+```{r dev-print3, echo=FALSE, sfig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+invisible(file.remove(filename))
+```
+
+If R is not running interactively, e.g. for code in a package vignette, `"raster"` becomes the default method in `display`.
+The default behavior of `display` can be overridden globally be setting the `"options("EBImage.display")` to either `"browser"` or `"raster"`. This is useful, for example, to preview images inside RStudio.
+
+It is also possible to read and view color images,
+
+```{r readImageColor-pre, echo=FALSE}
+imgcol = readImage(system.file("images", "sample-color.png", package="EBImage"))
+```{r readImageColor, eval=FALSE}
+imgcol = readImage(system.file("images", "sample-color.png", package="EBImage"))
+display(imgcol)
+```{r readImageColor-post, echo=FALSE, fig.width=dim(imgcol)[1L]/.dpi, fig.height=dim(imgcol)[2L]/.dpi, dpi=.dpi/2}
+display(imgcol)
+```
+
+or images containing several frames. If an image consists of multiple frames, they can be displayed all at once in a grid
+arrangement by specifying the function argument `all = TRUE`,
+
+```{r readImageMulti-pre, include=FALSE}
+nuc = readImage(system.file("images", "nuclei.tif", package="EBImage"))
+```{r readImageMulti, eval=FALSE}
+nuc = readImage(system.file("images", "nuclei.tif", package="EBImage"))
+display(nuc, method = "raster", all = TRUE)
+```{r readImageMulti-post, echo=FALSE, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+display(nuc, method = "raster", all = TRUE)
+```
+
+or we can just view a single frame, for example, the second one.
+
+```{r displayFrame, echo=FALSE, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi/2}
+display(nuc, method = "raster", frame = 2)
+```
+
+Images can be saved to files using the `writeImage` function.  The image that we loaded was a `r toupper(strsplit(basename(f), split=".", fixed=TRUE)[[1L]][2L])` file; suppose now that we want to save this image as a JPEG
+file.  The JPEG format allows to set a quality value between 1 and 100 for its
+compression algorithm.  The default value of the `quality` argument of
+`writeImage` is 100, here we use a smaller value, leading to smaller file size
+at the cost of some reduction in image quality.
+
+```{r writeImage, eval=FALSE}
+writeImage(imgcol, "sample.jpeg", quality = 85)
+```
+
+Similarly, we could have saved the image as a TIFF file and set which 
+compression algorithm we want to use. For a complete list of available parameters see `?writeImage`.
+
+
+# Image data representation
+
+`r Rpackage("EBImage")` uses a package-specific class `Image` to store and process images. It extends the R base class `array`, and all `r Rpackage("EBImage")` functions can also be called directly on matrices and arrays. You can find out more about this class by typing `?Image`.  Let us peek into the internal structure of an `Image` object.
+
+```{r str}
+str(img)
+```
+
+The `.Data` slot contains a numeric array of pixel intensities. We see that in this case the array is two-dimensional, with `r dim(img)[1L]` times `r dim(img)[2L]` elements, and corresponds to the pixel width and height of the image. These dimensions can be accessed using the `dim` function, just like for regular arrays.
+
+```{r dim}
+dim(img)
+```
+
+Image data can be accessed as a plain R `array` using the `imageData` accessor,
+
+```{r imageData}
+imageData(img)[1:3, 1:6]
+```
+
+and the `as.array` method can be used to coerce an `Image` to an `array`.
+
+```{r as.array}
+is.Image( as.array(img) )
+```
+
+The distribution of pixel intensities can be plotted in a histogram, and their range inspected using the `range` function.
+
+```{r hist, fig.width=6, fig.height=6, dev=.dev}
+hist(img)
+range(img)
+```
+
+A useful summary of `Image` objects is also provided by the `show`
+method, which is invoked if we simply type the object's name.
+
+```{r show}
+img
+```
+
+For a more compact representation without the preview of the intensities array use the `print` method with the argument `short` set to `TRUE`.
+
+```{r print}
+print(img, short=TRUE)
+```
+
+Let's now have a closer look a our color image.
+
+```{r printcol}
+print(imgcol, short=TRUE)
+```
+
+It differs from its grayscale counterpart `img` by the property `colorMode` and the number of dimensions. 
+The `colorMode` slot turns out to be convenient when dealing with stacks of images.  If
+it is set to `Grayscale`, then the third and all higher dimensions of the
+array are considered as separate image frames corresponding, for instance, to
+different z-positions, time points, replicates, etc.  On the other hand, if
+`colorMode` is `Color`, then the third dimension is assumed to hold
+different color channels, and only the fourth and higher dimensions---if present---are
+used for multiple image frames. `imgcol` contains three color channels, which
+correspond to the red, green and blue intensities of the photograph.  However, this does
+not necessarily need to be the case, and the number of color channels is arbitrary.
+
+The "frames.total" and "frames.render" fields shown by the object summary correspond to the total number of frames contained in the image, and to the number of rendered frames. These numbers can be accessed using the function `numberOfFrames` by specifying the `type` argument.
+
+```{r numberOfFrames}
+numberOfFrames(imgcol, type = "render")
+numberOfFrames(imgcol, type = "total")
+```
+
+Image frames can be extracted using `getFrame` and `getFrames`. `getFrame` returns the i-th frame contained in the image y. If `type` is `"total"`, the function is unaware of the color mode and returns an xy-plane. For `type="render"` the function returns the i-th image as shown by the display function. While `getFrame` returns just a single frame, `getFrames` retrieves a list of frames which can serve as input to `lapply`-family functions. See the "Global thresholding" section for an il [...]
+
+Finally, if we look at our cell data,
+
+```{r nuc}
+nuc
+```
+we see that it contains 4 total frames that correspond to the 4 separate greyscale images, as indicated by "frames.render".
+
+
+# Color management
+
+As described in the previous section, the class `Image` extends the base class `array` and uses
+`colorMode` to store how the color information of the multi-dimensional data should be handled. 
+The function `colorMode` can be used to access and change this property,
+modifying the rendering mode of an image. For example, if we take a `Color` image and change its 
+mode to `Grayscale`, then the image won't display as a single color image anymore but rather as three separate 
+grayscale frames corresponding to the red, green and blue channels. The function `colorMode` does not change
+the actual content of the image but only changes the way the image is rendered by `r Rpackage("EBImage")`.
+
+```{r colorMode, fig.width=dim(imgcol)[1L]/.dpi, fig.height=dim(imgcol)[2L]/.dpi, dpi=.dpi}
+colorMode(imgcol) = Grayscale
+display(imgcol, all=TRUE)
+```
+
+Color space conversions between `Grayscale` and `Color` images are performed using the function `channel`.
+It has a flexible interface which allows to convert either way between the modes, and can be used 
+to extract color channels. Unlike `colorMode`, `channel` changes the pixel intensity values of the image.
+
+`Color` to `Grayscale` conversion modes include taking a uniform average across the RGB channels,
+and a weighted luminance preserving conversion mode better suited for display purposes.
+
+The `asred`, `asgreen` and `asblue` modes convert a grayscale image or array into a color image of the specified hue. 
+
+The convenience function `toRGB` promotes a grayscale image to RGB color space by replicating it across the red, green and blue channels, 
+which is equivalent to calling `channel` with mode set to `rgb`. When displayed, this image doesn't look different from its grayscale origin, which is expected because the information between the color channels is the same. To combine three grayscale images into a single rgb image use the function `rgbImage`.
+
+The function `Image` can be used to construct a color image from a character vector or array of named R colors (as listed by `colors()`) and/or hexadecimal strings of the form "\#rrggbb" or "\#rrggbbaa".
+
+```{r Image-character, fig.width=7/.dpi, fig.height=7/.dpi, dpi=10*.dpi}
+colorMat = matrix(rep(c("red","green", "#0000ff"), 25), 5, 5)
+colorImg = Image(colorMat)
+colorImg
+display(colorImg, interpolate=FALSE)
+```
+
+
+# Manipulating images
+
+Being numeric arrays, images can be conveniently manipulated by any of R's arithmetic operators. For example, we can produce a negative image by simply subtracting the image from its maximum value.
+
+```{r negative, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+img_neg = max(img) - img
+display( img_neg )
+```
+
+We can also increase the brightness of an image through addition, adjust the contrast through multiplication, and apply gamma correction through exponentiation.
+
+```{r arithmetic, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+img_comb = combine(
+  img,
+  img + 0.3,
+  img * 2,
+  img ^ 0.5
+)
+
+display(img_comb, all=TRUE)
+```
+
+In the example above we have used `combine` to merge individual images into a single multi-frame image object.
+
+Furthermore, we can crop and threshold images with standard matrix operations.
+```{r cropthreshold-pre, echo=FALSE}
+img_crop = img[366:749, 58:441]
+img_thresh = img_crop > .5
+```{r cropthreshold, eval=FALSE}
+img_crop = img[366:749, 58:441]
+img_thresh = img_crop > .5
+display(img_thresh)
+```{r cropthreshold-post, echo=FALSE, fig.width=dim(img_thresh)[1L]/.dpi, fig.height=dim(img_thresh)[2L]/.dpi, dpi=.dpi/2}
+display(img_thresh)
+```
+
+The thresholding operation returns an `Image` object with binarized pixels values. 
+The R data type used to store such an image is `logical`.
+```{r img_thresh}
+img_thresh
+```
+
+For image transposition, use `transpose` rather than R's `r Rpackage("base")` function `t`. This is because the former one works also on color and multiframe images by swapping its spatial dimensions.
+
+```{r transpose, fig.width=dim(img)[2L]/.dpi, fig.height=dim(img)[1L]/.dpi, dpi=.dpi/2}
+img_t = transpose(img)
+display( img_t )
+```
+
+
+# Spatial transformations
+
+We just saw one type of spatial transformation, transposition, but there are many more, for example translation, rotation, reflection and scaling. `translate` moves the image plane by the specified two-dimensional vector in such a way that pixels that end up outside the image region are cropped, and pixels that enter into the image region are set to background.
+```{r translate, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+img_translate = translate(img, c(100,-50))
+display(img_translate)
+```
+
+The background color can be set using the argument `bg.col` common to all relevant spatial transformation functions. The default sets the value of  background pixels to zero which corresponds to black. Let us demonstrate the use of this argument with `rotate` which rotates the image clockwise by the given angle.
+```{r rotate-pre, echo=FALSE}
+img_rotate = rotate(img, 30, bg.col = "white")
+```{r rotate, eval=FALSE}
+img_rotate = rotate(img, 30, bg.col = "white")
+display(img_rotate)
+```{r rotate-post, echo=FALSE, fig.width=dim(img_rotate)[1L]/.dpi, fig.height=dim(img_rotate)[2L]/.dpi, dpi=.dpi/2}
+display(img_rotate)
+```
+
+To scale an image to desired dimensions use `resize`. If you provide only one of either width or height, the other dimension is automatically computed keeping the original aspect ratio. 
+```{r resize-pre, echo=FALSE}
+img_resize = resize(img, w=256, h=256)
+```{r resize, eval=FALSE}
+img_resize = resize(img, w=256, h=256)
+display(img_resize )
+```{r resize-post, echo=FALSE, fig.width=dim(img_resize)[1L]/.dpi, fig.height=dim(img_resize)[2L]/.dpi, dpi=.dpi/2}
+display(img_resize)
+```
+
+The functions `flip` and  `flop` reflect the image around the image horizontal and vertical axis, respectively.
+
+```{r flipflop, fig.width=2*dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+img_flip = flip(img)
+img_flop = flop(img)
+
+display(combine(img_flip, img_flop), all=TRUE)
+```
+
+Spatial linear transformations are implemented using the general `affine` transformation. It maps image pixel coordinates `px` using a 3x2 transformation matrix `m` in the following way: `cbind(px, 1) %*% m`. For example, horizontal sheer mapping can be applied by
+
+```{r affine, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+m =  matrix(c(1, -.5, 128, 0, 1, 0), nrow=3, ncol=2)
+img_affine = affine(img, m)
+display( img_affine )
+```
+
+
+# Filtering
+
+## Linear filters
+
+A common preprocessing step involves cleaning up the images by removing
+local artifacts or noise through smoothing.  An intuitive approach is to
+define a window of a selected size around each pixel and average the values within that
+neighborhood. After applying this procedure to all pixels, the new, smoothed image is obtained.
+Mathematically, this can be expressed as
+$$
+f'(x,y) = \frac{1}{N} \sum_{s=-a}^{a}\sum_{t=-a}^{a} f(x+s, y+t),
+$$
+where $f(x,y)$ is the value of the pixel at position $(x, y)$, and $a$ determines the
+window size, which is $2a+1$ in each direction.  $N=(2a+1)^2$ is the number of pixels
+averaged over, and $f'$ is the new, smoothed image.
+
+More generally, we can replace the moving average by a weighted average, using a weight
+function $w$, which typically has the highest value at the window midpoint ($s=t=0$) and then
+decreases towards the edges. 
+$$
+(w * f)(x,y) = \sum_{s=-\infty}^{+\infty} \sum_{t=-\infty}^{+\infty} w(s,t)\, f(x+s, y+s)
+$$
+For notational convenience, we let the summations range from $-\infty$ to $+\infty$, even if in practice the sums are finite 
+and $w$ has only a finite number of non-zero values. In fact, we can think of the weight function $w$ as another image,
+and this operation is also called the *convolution* of the images $f$ and $w$, indicated by the the symbol $*$.
+Convolution is a linear operation in the sense that $w*(c_1f_1+c_2f_2)=c_1w*f_1 + c_2w*f_2$
+for any two images $f_1$, $f_2$ and numbers $c_1$, $c_2$.
+
+
+In `r Biocpkg("EBImage")`, the 2-dimensional convolution is implemented by the function `filter2`, and the auxiliary 
+function `makeBrush` can be used to generate the weight function. 
+In fact, `filter2` does not directly perform the summation indicated in the equation above.
+Instead, it uses the Fast Fourier Transformation in a way that is mathematically equivalent 
+but computationally more efficient.
+
+```{r makeBrush, fig.width=6, fig.height=6, dev=.dev}
+w = makeBrush(size = 31, shape = 'gaussian', sigma = 5)
+plot(w[(nrow(w)+1)/2, ], ylab = "w", xlab = "", cex = 0.7)
+```{r lopass, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+img_flo = filter2(img, w)
+display(img_flo)
+```
+
+Here we have used a Gaussian filter of width 5 given by `sigma`. 
+Other available filter shapes include `"box"` (default), `"disc"`, `"diamond"` and `"line"`, for some of which the kernel can be binary; see `?makeBrush` for details.
+
+If the filtered image contains multiple frames, the filter is applied to each frame separately. For convenience, images can be also smoothed using the wrapper function `gblur` which performs Gaussian smoothing with the filter size automatically adjusted to `sigma`. 
+
+```{r gblur, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+nuc_gblur = gblur(nuc, sigma = 5)
+display(nuc_gblur, all=TRUE )
+```
+
+In signal processing the operation of smoothing an image is referred to as low-pass filtering. High-pass filtering is the opposite operation which allows to detect edges and sharpen images. This can be done, for instance, using a Laplacian filter.
+
+```{r highpass, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+fhi = matrix(1, nrow = 3, ncol = 3)
+fhi[2, 2] = -8
+img_fhi = filter2(img, fhi)
+display(img_fhi)
+```
+
+## Median filter
+
+Another approach to perform noise reduction is to apply a median filter, which is a non-linear technique as opposed to the low pass convolution filter described in the previous section. Median filtering is particularly effective in the case of speckle noise, and has the advantage of removing noise while preserving edges. 
+
+The local median filter works by scanning the image pixel by pixel, replacing each pixel by the median on of its neighbors inside a window of specified size. This filtering technique is provided in `r Rpackage("EBImage")` by the function `medianFilter`. We demonstrate its use by first corrupting the image with uniform noise, and reconstructing the original image by median filtering.
+
+```{r medianFilter, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+l = length(img)
+n = l/10
+pixels = sample(l, n)
+img_noisy = img
+img_noisy[pixels] = runif(n, min=0, max=1)
+display(img_noisy)
+img_median = medianFilter(img_noisy, 1)
+display(img_median)
+```
+
+## Morphological operations
+
+Binary images are images which contain only two sets of pixels, with values, say 0 and 1, representing the background and foreground pixels. Such images are subject to several non-linear morphological operations: erosion, dilation, opening, and closing. These operations work by overlaying a mask, called the structuring element, over the binary image in the following way:
+
+* erosion: For every foreground pixel, put the mask around it, and if any pixel covered by the mask is from the background, set the pixel to background.
+
+* dilation: For every background pixel, put the mask around it, and if any pixel covered by the mask is from the foreground, set the pixel to foreground.
+
+```{r logo-pre, echo=FALSE}
+shapes = readImage(system.file('images', 'shapes.png', package='EBImage'))
+logo = shapes[110:512,1:130]
+```{r logo, eval=FALSE}
+shapes = readImage(system.file('images', 'shapes.png', package='EBImage'))
+logo = shapes[110:512,1:130]
+display(logo)
+```{r logo-post, echo=FALSE, fig.width=dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi}
+display(logo)
+```
+
+```{r kern, fig.width=7/.dpi, fig.height=7/.dpi, dpi=10*.dpi}
+kern = makeBrush(5, shape='diamond')
+display(kern, interpolate=FALSE)
+```
+
+```{r morph, fig.width=2*dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi}
+logo_erode= erode(logo, kern)
+logo_dilate = dilate(logo, kern)
+
+display(combine(logo_erode, logo_dilate), all=TRUE)
+```
+
+Opening and closing are combinations of the two operations above: opening performs erosion followed by dilation, while closing does the opposite, i.e, performs dilation followed by erosion. Opening is useful for morphological noise removal, as it removes small objects from the background, and closing can be used to fill small holes in the foreground. These operations are implemented by `opening` and `closing`.
+
+
+# Thresholding
+
+## Global thresholding
+
+In the "Manipulating images" section we have already demonstrated how to set a global threshold on an image.
+There we used an arbitrary cutoff value.
+For images whose distribution of pixel intensities follows a bi-modal histogram a more systematic approach involves using the Otsu's method. Otsu's method is a technique to automatically perform clustering-based image thresholding. Assuming a bi-modal intensity distribution, the algorithm separates image pixels into foreground and background. The optimal threshold value is determined by minimizing the combined intra-class variance.
+
+Otsu's threshold can be calculated using the function `otsu`. When called on a multi-frame image, the threshold is calculated for each frame separately resulting in a output vector of length equal to the total number of frames in the image.
+
+```{r otsu, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+threshold = otsu(nuc)
+threshold
+nuc_th = combine( mapply(function(frame, th) frame > th, getFrames(nuc), threshold, SIMPLIFY=FALSE) )
+display(nuc_th, all=TRUE)
+```
+
+Note the use of `getFrames` to split the image into a list of individual frames, and `combine` to merge the results back together.
+
+## Adaptive thresholding
+
+The idea of adaptive thresholding is that, compared to straightforward thresholding from 
+the previous section, the threshold is allowed to be different in different
+regions of the image. In this way, one can anticipate spatial dependencies of the
+underlying background signal caused, for instance, by uneven illumination or by stray
+signal from nearby bright objects.
+
+Adaptive thresholding works by comparing each pixel's intensity to the background determined from a 
+local neighbourhood. This can be achieved by comparing the image to its smoothed version, where the filtering
+window is bigger than the typical size of objects we want to capture.
+
+```{r filter2thresh, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+disc = makeBrush(31, "disc")
+disc = disc / sum(disc)
+offset = 0.05
+nuc_bg = filter2( nuc, disc )
+nuc_th = nuc > nuc_bg + offset
+display(nuc_th, all=TRUE)
+```
+
+This technique assumes that the objects are relatively sparsely
+distributed in the image, so that the signal distribution in the neighborhood is dominated
+by background. While for the nuclei in our images this assumption makes sense, for other
+situations you may need to make different assumptions. The adaptive thresholding 
+using a linear filter with a rectangular box is provided by `thresh`,
+which uses a faster implementation compared to directly using `filter2`.
+
+```{r thresh, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+display( thresh(nuc, w=15, h=15, offset=0.05), all=TRUE )
+```
+
+
+# Image segmentation
+
+Image segmentation performs partitioning of an image, and is typically used to identify objects in an image. Non-touching connected objects can be segmented using the function `bwlabel`, while `watershed` and `propagate` use more sophisticated algorithms able to separate objects which touch each other.
+
+`bwlabel` finds every connected set of pixels other than the background, and relabels these sets with a unique increasing integer. It can be called on a thresholded binary image in order to extract objects.
+
+```{r bwlabel}
+logo_label = bwlabel(logo)
+table(logo_label)
+```
+
+The pixel values of the `logo_label` image range from 0 corresponding to background to the number of objects it contains, which is given by
+
+```{r max_logolabel}
+max(logo_label)
+```
+
+To display the image we normalize it to the (0,1) range expected by the display function. This results in different objects being rendered with a different shade of gray.
+
+```{r displaybw, fig.width=dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi}
+display( normalize(logo_label) )
+```
+
+The horizontal grayscale gradient which can be observed reflects to the way `bwlabel` scans the image and labels the connected sets: from left to right and from top to bottom. Another way of visualizing the segmentation is to use the `colorLabels` function, which color codes the objects by a random permutation of unique colors.
+
+```{r colorCode, fig.width=dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi}
+display( colorLabels(logo_label) )
+```
+
+## Watershed 
+
+Some of the nuclei in `nuc` are quite close to each other and get merged into one big object when thresholded, as seen in `nuc_th`.
+`bwlabel` would incorrectly identify them as a single object. The watershed transformation allows to overcome this issue.
+The `watershed` algorithm treats a grayscale image as a topographic relief, or heightmap. Objects that stand out of the background
+are identified and separated by flooding an inverted source image. In case of a binary image its distance map  can serve as the input heightmap. The distance map, which contains for each pixel the distance to the nearest background pixel, can be obtained by `distmap`.
+
+```{r watershed, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+nmask = watershed( distmap(nuc_th), 2 )
+display(colorLabels(nmask), all=TRUE)
+```
+
+## Voronoi tesselation
+
+Voronoi tessellation is useful when we have a set of seed points (or regions) and want to
+partition the space that lies between these seeds in such a way that each point in the
+space is assigned to its closest seed. This function is implemented in `r Rpackage("EBImage")` by the function `propagate`.
+Let us illustrate the concept of Voronoi tessalation on a basic example. We use the nuclei mask `nmask` as seeds and
+partition the space between them.
+
+```{r voronoiExample, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+voronoiExamp = propagate(seeds = nmask, x = nmask, lambda = 100)
+voronoiPaint = colorLabels (voronoiExamp)
+display(voronoiPaint)
+```
+
+The basic definition of Voronoi tessellation, which we have given above, allows
+for two generalizations:
+
+* By default, the space that we partition is the full, rectangular image area, but indeed we could 
+    restrict ourselves to any arbitrary subspace. This is akin to finding the shortest distance from each point to the next seed
+    not in a simple flat landscape, but in a landscape that is interspersed by lakes and rivers (which you cannot cross), 
+    so that all paths need to remain on the land. `propagate` allows for this generalization through its 
+    `mask` argument. 
+
+* By default, we think of the space as flat -- but in fact it could have hills and canyons, so that 
+    the distance between two points in the landscape not only depends on their x- and y-positions but also on 
+    the ascents and descents, up and down in z-direction, 
+    that lie in between. You can specify such a landscape to `propagate` through its `x` argument.  
+      
+Mathematically, we can say that instead of the simple default case (a flat rectangle image with an Euclidean metric), we perform the Voronoi segmentation on a Riemann manifold, which
+can have an arbitrary shape and an arbitrary metric.
+Let us use the notation $x$ and $y$ for the column and row coordinates
+of the image, and $z$ for the elevation of the landscape. For two neighboring points, defined
+by coordinates $(x, y, z)$ and $(x+dx, y+dy, z+dz)$, the distance between them is given by
+$$
+ds = \sqrt{ \frac{2}{\lambda+1} \left[ \lambda \left( dx^2 + dy^2 \right) + dz^2 \right] }.
+$$
+For $\lambda=1$, this reduces to $ds = ( dx^2 + dy^2 + dz^2)^{1/2}$.
+Distances between points further apart are obtained by summing $ds$ along the shortest path between them. 
+The parameter $\lambda\ge0$ has been introduced as a convenient control of the relative weighting between sideways movement
+(along the $x$ and $y$ axes) and vertical movement.  Intuitively, if you imagine yourself
+as a hiker in such a landscape, by choosing $\lambda$ you can specify how much you are prepared to climb up
+and down to overcome a mountain, versus sideways walking around it.
+When $\lambda$ is large, the expression becomes equivalent to $ds = \sqrt{dx^2 + dy^2}$, i. e., 
+the importance of $dz$ becomes negligible. This is what we did when we used `lambda = 100` in our `propagate` example.
+
+A more advanced application of `propagate` to the segmentation of cell bodies is presented in the "Cell segmentation example" section.
+
+
+# Object manipulation
+
+## Object removal
+
+`r Rpackage("EBImage")` defines an object mask as a set of pixels with the same unique integer value. Typically, images containing object masks are the result of segmentation functions such as `bwalabel`, `watershed`, or `propagate`. Objects can be removed from such images by `rmObject`, which deletes objects from the mask simply by setting their pixel values to 0. By default, after object removal all the remaining objects are relabeled so that the highest object ID corresponds to the nu [...]
+```{r rmObjects, fig.width=2*dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi}
+objects = list(
+    seq.int(from = 2, to = max(logo_label), by = 2),
+    seq.int(from = 1, to = max(logo_label), by = 2)
+    )
+logos = combine(logo_label, logo_label)
+z = rmObjects(logos, objects, reenumerate=FALSE)
+display(z, all=TRUE)
+```
+
+In the example above we demonstrate how the object removal function can be applied to a multi-frame image by providing a list of object indicies to be removed from each frame. Additionally we have set `reenumerate` to `FALSE` keeping the original object IDs.
+
+```{r uniqueIDs}
+showIds = function(image) lapply(getFrames(image), function(frame) unique(as.vector(frame)))
+
+showIds(z)
+```
+
+Recall that 0 stands for the background. If at some stage we decide to relabel the objects, we can use for this the standalone function `reenumarate`.
+
+```{r reenumeratedIDs}
+showIds( reenumerate(z) )
+```
+
+## Filling holes and regions
+
+Holes in object masks can be filled using the function `fillHull`.
+
+```{r fillHull, fig.width=dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi}
+filled_logo = fillHull(logo)
+display(filled_logo)
+```
+
+`floodFill` fills a region of an image with a specified color. The filling starts at the given point, and the filling region is expanded to a connected area in which the absolute difference in pixel intensities remains below `tolerance`. The color specification uses R color names for `Color` images, and numeric values for `Grayscale` images.
+
+```{r floodFill-logo, fig.width=dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi}
+rgblogo = toRGB(logo)
+points = rbind(c(50, 50), c(100, 50), c(150, 50))
+colors = c("red", "green", "blue")
+rgblogo = floodFill(rgblogo, points, colors)
+display( rgblogo )
+```{r floodFill-img, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+display(floodFill(img, rbind(c(200, 300), c(444, 222)), col=0.2, tolerance=0.2))
+```
+
+## Highlighting objects
+
+Given an image containing object masks,
+the function `paintObjects` can be used to highlight the objects from the mask in the target image provided in the `tgt` argument. 
+Objects can be outlined and filled with colors of given opacities specified in the `col` and `opac` arguments, respectively. If the color specification is missing or equals `NA` it is not painted.
+
+
+```{r paintObjects, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2}
+d1 = dim(img)[1:2]
+overlay = Image(dim=d1)
+d2 = dim(logo_label)-1
+
+offset = (d1-d2) %/% 2
+
+overlay[offset[1]:(offset[1]+d2[1]), offset[2]:(offset[2]+d2[2])] = logo_label
+
+img_logo = paintObjects(overlay, toRGB(img), col=c("red", "yellow"), opac=c(1, 0.3), thick=TRUE)
+
+display( img_logo )
+```
+
+In the example above we have created a new mask `overlay` matching the size of our target image `img`, and copied the mask containing the "EBImage" logo into that overlay mask. The output of `paintObjects` retains the color mode of its target image, therefore in order to have the logo highlighted in color it was necessary to convert `img` to an RGB image first, otherwise the result would be a grayscale image. The `thick` argument controls the object contour drawing: if set to `FALSE`, on [...]
+
+
+# Cell segmentation example
+
+We conclude our vignette by applying the functions described before to the task of segmenting cells. Our goal is to computationally identify and qualitatively characterize the cells in the sample fluorescent microscopy images. Even though this by itself may seem a modest goal, this approach can be applied to collections containing thousands of images, an that need no longer to be an modest aim!
+
+We start by loading the images of nuclei and cell bodies. To visualize the cells we overlay these images as the green and the blue channel of a false-color image.
+
+```{r load, fig.height=dim(nuc)[2L]/.dpi, fig.width=dim(nuc)[1L]/.dpi, warning=FALSE, dpi=.dpi}
+nuc = readImage(system.file('images', 'nuclei.tif', package='EBImage'))
+cel = readImage(system.file('images', 'cells.tif', package='EBImage'))
+
+cells = rgbImage(green=1.5*cel, blue=nuc)
+display(cells, all = TRUE)
+```
+
+First, we segment the nuclei using `thresh`, `fillHull`, `bwlabel`
+and `opening`.
+
+```{r nmask, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+nmask = thresh(nuc, w=10, h=10, offset=0.05)
+nmask = opening(nmask, makeBrush(5, shape='disc'))
+nmask = fillHull(nmask)
+nmask = bwlabel(nmask)
+
+display(nmask, all=TRUE)
+```
+
+Next, we use the segmented nuclei as seeds in the Voronoi segmentation of the cytoplasm.
+
+```{r ctmask, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+ctmask = opening(cel>0.1, makeBrush(5, shape='disc'))
+cmask = propagate(cel, seeds=nmask, mask=ctmask)
+
+display(ctmask, all=TRUE)
+```
+
+To visualize our segmentation on the we use `paintObject`.
+
+```{r res, fig.width=dim(nuc)[1L]/.dpi, fig.height=dim(nuc)[2L]/.dpi, dpi=.dpi}
+segmented = paintObjects(cmask, cells, col='#ff00ff')
+segmented = paintObjects(nmask, segmented, col='#ffff00')
+
+display(segmented, all=TRUE)
+```
+
+# Session Info
+
+```{r sessionInfo}
+sessionInfo()
+```
diff --git a/vignettes/logo.png b/vignettes/logo.png
new file mode 100644
index 0000000..b230de2
Binary files /dev/null and b/vignettes/logo.png differ

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/r-bioc-ebimage.git



More information about the debian-med-commit mailing list