[lua-torch-image] 01/01: Imported Upstream version 0~20160730-g797fcb1

Zhou Mo cdluminate-guest at moszumanska.debian.org
Thu Aug 25 14:13:41 UTC 2016


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

cdluminate-guest pushed a commit to branch master
in repository lua-torch-image.

commit bafd9bacf118845a0771981f4fd8b72fb9660c77
Author: Zhou Mo <cdluminate at gmail.com>
Date:   Thu Aug 25 12:34:05 2016 +0000

    Imported Upstream version 0~20160730-g797fcb1
---
 .gitignore                  |    1 +
 .travis.yml                 |   62 ++
 CMakeLists.txt              |   90 ++
 COPYRIGHT.txt               |   36 +
 README.md                   |   43 +
 assets/P2.pgm               |   11 +
 assets/P4.pbm               |    3 +
 assets/P5.pgm               |    7 +
 assets/P6.ppm               |  Bin 0 -> 313 bytes
 assets/corrupt-ihdr.png     |  Bin 0 -> 275 bytes
 assets/fabio.jpg            |  Bin 0 -> 17958 bytes
 assets/fabio.png            |  Bin 0 -> 65067 bytes
 assets/foobar.png           |  Bin 0 -> 202 bytes
 assets/grace_hopper_512.jpg |  Bin 0 -> 65544 bytes
 assets/grace_hopper_512.png |  Bin 0 -> 431614 bytes
 assets/gray16-1x2.png       |  Bin 0 -> 75 bytes
 assets/gray3x1.png          |  Bin 0 -> 73 bytes
 assets/rectangle.png        |  Bin 0 -> 113 bytes
 assets/rgb16-2x1.png        |  Bin 0 -> 79 bytes
 assets/rgb2x1.png           |  Bin 0 -> 76 bytes
 doc/colorspace.md           |   73 ++
 doc/drawing.md              |   44 +
 doc/gui.md                  |   53 +
 doc/index.md                |   35 +
 doc/paramtransform.md       |   73 ++
 doc/saveload.md             |   63 ++
 doc/simpletransform.md      |  130 +++
 doc/tensorconstruct.md      |   91 ++
 font.c                      |  287 ++++++
 generic/image.c             | 2296 ++++++++++++++++++++++++++++++++++++++++++
 generic/jpeg.c              |  527 ++++++++++
 generic/png.c               |  400 ++++++++
 generic/ppm.c               |  183 ++++
 image-1.1.alpha-0.rockspec  |   32 +
 image.c                     |   52 +
 init.lua                    | 2323 +++++++++++++++++++++++++++++++++++++++++++
 jpeg.c                      |   68 ++
 mkdocs.yml                  |   14 +
 png.c                       |   87 ++
 ppm.c                       |   70 ++
 test/test.lua               |  687 +++++++++++++
 test/test_rotate.lua        |   75 ++
 test/test_warp.lua          |  139 +++
 win.ui                      |   40 +
 44 files changed, 8095 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..378eac2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+build
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..060ae0a
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,62 @@
+language: c
+compiler:
+  - gcc
+  - clang
+cache:
+  directories:
+  - $HOME/OpenBlasInstall
+sudo: false
+env:
+  - TORCH_LUA_VERSION=LUAJIT21
+  - TORCH_LUA_VERSION=LUA51
+  - TORCH_LUA_VERSION=LUA52
+addons:
+  apt:
+    packages:
+    - cmake
+    - gfortran
+    - gcc-multilib
+    - gfortran-multilib
+    - liblapack-dev
+    - build-essential
+    - gcc 
+    - g++ 
+    - curl
+    - cmake 
+    - libreadline-dev 
+    - git-core 
+    - libqt4-core 
+    - libqt4-gui
+    - libqt4-dev 
+    - libjpeg-dev 
+    - libpng-dev 
+    - ncurses-dev
+    - imagemagick 
+    - libzmq3-dev 
+    - gfortran 
+    - unzip 
+    - gnuplot
+    - gnuplot-x11 
+    - libgraphicsmagick1-dev
+    - imagemagick 
+before_script: 
+- export ROOT_TRAVIS_DIR=$(pwd)
+- export INSTALL_PREFIX=~/torch/install
+-  ls $HOME/OpenBlasInstall/lib || (cd /tmp/ && git clone https://github.com/xianyi/OpenBLAS.git -b master && cd OpenBLAS && (make NO_AFFINITY=1 -j$(getconf _NPROCESSORS_ONLN) 2>/dev/null >/dev/null) && make PREFIX=$HOME/OpenBlasInstall install)
+- git clone https://github.com/torch/distro.git ~/torch --recursive
+- cd ~/torch && git submodule update --init --recursive
+- mkdir build && cd build
+- export CMAKE_LIBRARY_PATH=$HOME/OpenBlasInstall/include:$HOME/OpenBlasInstall/lib:$CMAKE_LIBRARY_PATH
+- cmake .. -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" -DCMAKE_BUILD_TYPE=Release -DWITH_${TORCH_LUA_VERSION}=ON
+- make && make install
+- cd $ROOT_TRAVIS_DIR
+- export LD_LIBRARY_PATH=${INSTALL_PREFIX}/lib:$LD_LIBRARY_PATH
+script: 
+- ${INSTALL_PREFIX}/bin/luarocks make 
+- export PATH=${INSTALL_PREFIX}/bin:$PATH
+- export TESTLUA=$(which luajit lua | head -n 1)
+- ${TESTLUA} -limage -e "print('image loaded succesfully')"
+- cd test
+- ${INSTALL_PREFIX}/bin/luarocks install graphicsmagick
+- ${TESTLUA} ./test_rotate.lua
+- ${TESTLUA} -limage -e "t=image.test(); if t.errors[1] then os.exit(1) end"
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..d19b863
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,90 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 2.6 FATAL_ERROR)
+CMAKE_POLICY(VERSION 2.6)
+
+FIND_PACKAGE(Torch REQUIRED)
+FIND_PACKAGE(JPEG)
+FIND_PACKAGE(PNG)
+
+# OpenMP support?
+SET(WITH_OPENMP ON CACHE BOOL "OpenMP support if available?")
+IF (APPLE AND CMAKE_COMPILER_IS_GNUCC)
+  EXEC_PROGRAM (uname ARGS -v  OUTPUT_VARIABLE DARWIN_VERSION)
+  STRING (REGEX MATCH "[0-9]+" DARWIN_VERSION ${DARWIN_VERSION})
+  MESSAGE (STATUS "MAC OS Darwin Version: ${DARWIN_VERSION}")
+  IF (DARWIN_VERSION GREATER 9)
+    SET(APPLE_OPENMP_SUCKS 1)
+  ENDIF (DARWIN_VERSION GREATER 9)
+  EXECUTE_PROCESS (COMMAND ${CMAKE_C_COMPILER} -dumpversion
+    OUTPUT_VARIABLE GCC_VERSION)
+  IF (APPLE_OPENMP_SUCKS AND GCC_VERSION VERSION_LESS 4.6.2)
+    MESSAGE(STATUS "Warning: Disabling OpenMP (unstable with this version of GCC)")
+    MESSAGE(STATUS " Install GCC >= 4.6.2 or change your OS to enable OpenMP")
+    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unknown-pragmas")
+    SET(WITH_OPENMP OFF CACHE BOOL "OpenMP support if available?" FORCE)
+  ENDIF ()
+ENDIF ()
+
+IF (WITH_OPENMP)
+  FIND_PACKAGE(OpenMP)
+  IF(OPENMP_FOUND)
+    MESSAGE(STATUS "Compiling with OpenMP support")
+    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
+    SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
+  ENDIF(OPENMP_FOUND)
+ENDIF (WITH_OPENMP)
+
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99")
+
+SET(src ppm.c)
+ADD_TORCH_PACKAGE(ppm "${src}" "${luasrc}" "Image Processing")
+TARGET_LINK_LIBRARIES(ppm luaT TH)
+IF(LUALIB)
+  TARGET_LINK_LIBRARIES(ppm ${LUALIB})
+ENDIF()
+
+if (JPEG_FOUND)
+    SET(src jpeg.c)
+    include_directories (${JPEG_INCLUDE_DIR})
+    SET(CMAKE_REQUIRED_INCLUDES "${JPEG_INCLUDE_DIR}")
+    SET(CMAKE_REQUIRED_LIBRARIES "${JPEG_LIBRARY}")
+    INCLUDE(CheckSymbolExists)
+    CHECK_SYMBOL_EXISTS(jpeg_mem_src "stddef.h;stdio.h;jpeglib.h" HAVE_JPEG_MEM_SRC)
+    IF (HAVE_JPEG_MEM_SRC)
+      SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_JPEG_MEM_SRC")
+    ENDIF (HAVE_JPEG_MEM_SRC)
+    CHECK_SYMBOL_EXISTS(jpeg_mem_dest "stddef.h;stdio.h;jpeglib.h" HAVE_JPEG_MEM_DEST)
+    IF (HAVE_JPEG_MEM_DEST)
+      SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_JPEG_MEM_DEST")
+    ENDIF (HAVE_JPEG_MEM_DEST)
+    ADD_TORCH_PACKAGE(jpeg "${src}" "${luasrc}" "Image Processing")
+    TARGET_LINK_LIBRARIES(jpeg luaT TH ${JPEG_LIBRARIES})
+    IF(LUALIB)
+        TARGET_LINK_LIBRARIES(jpeg ${LUALIB})
+    ENDIF()
+else (JPEG_FOUND)
+    message ("WARNING: Could not find JPEG libraries, JPEG wrapper will not be installed")
+endif (JPEG_FOUND)
+
+if (PNG_FOUND)
+    SET(src png.c)
+    include_directories (${PNG_INCLUDE_DIR})
+    ADD_TORCH_PACKAGE(png "${src}" "${luasrc}" "Image Processing")
+    TARGET_LINK_LIBRARIES(png luaT TH ${PNG_LIBRARIES})
+    IF(LUALIB)
+        TARGET_LINK_LIBRARIES(png ${LUALIB})
+    ENDIF()
+else (PNG_FOUND)
+    message ("WARNING: Could not find PNG libraries, PNG wrapper will not be installed")
+endif (PNG_FOUND)
+
+SET(src image.c)
+SET(luasrc init.lua win.ui test/test.lua)
+
+ADD_TORCH_PACKAGE(image "${src}" "${luasrc}" "Image Processing")
+TARGET_LINK_LIBRARIES(image luaT TH)
+IF(LUALIB)
+  TARGET_LINK_LIBRARIES(image ${LUALIB})
+ENDIF()
+INSTALL(DIRECTORY "assets" DESTINATION "${Torch_INSTALL_LUA_PATH_SUBDIR}/image")
+INSTALL(FILES "README.md" DESTINATION "${Torch_INSTALL_LUA_PATH_SUBDIR}/image")
diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt
new file mode 100644
index 0000000..c9cc784
--- /dev/null
+++ b/COPYRIGHT.txt
@@ -0,0 +1,36 @@
+Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert)
+Copyright (c) 2012-2014 Deepmind Technologies (Koray Kavukcuoglu)
+Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu)
+Copyright (c) 2011-2013 NYU (Clement Farabet)
+Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert, Leon Bottou, Iain Melvin, Jason Weston)
+Copyright (c) 2006      Idiap Research Institute (Samy Bengio)
+Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert, Samy Bengio, Johnny Mariethoz)
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+3. Neither the names of Deepmind Technologies, NYU, NEC Laboratories America 
+   and IDIAP Research Institute nor the names of its contributors may be 
+   used to endorse or promote products derived from this software without 
+   specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..97d505b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,43 @@
+# image Package Reference Manual #
+
+[![Build Status](https://travis-ci.org/torch/image.svg)](https://travis-ci.org/torch/image) 
+
+__image__ is the [Torch7 distribution](http://torch.ch/) package for processing 
+images. It contains a wide variety of functions divided into the following categories:
+
+  * [Saving and loading](doc/saveload.md) images as JPEG, PNG, PPM and PGM;
+  * [Simple transformations](doc/simpletransform.md) like translation, scaling and rotation;
+  * [Parameterized transformations](doc/paramtransform.md) like convolutions and warping;
+  * [Simple Drawing Routines](doc/drawing.md) like drawing text or a rectangle on an image;
+  * [Graphical user interfaces](doc/gui.md) like display and window;
+  * [Color Space Conversions](doc/colorspace.md) from and to RGB, YUV, Lab, and HSL;
+  * [Tensor Constructors](doc/tensorconstruct.md) for creating Lenna, Fabio and Gaussian and Laplacian kernels;
+
+Note that unless speficied otherwise, this package deals with images of size 
+`nChannel x height x width`.
+
+## Install
+
+The easiest way to install this package it by following the [intructions](http://torch.ch/docs/getting-started.html) 
+to install [Torch7](http://www.torch.ch), which includes __image__. 
+Otherwise, to update or manually re-install it:
+
+```bash
+$ luarocks install image
+```
+
+You can test your install with:
+
+```bash
+$ luajit -limage -e "image.test()"
+```
+
+## Usage
+
+```lua
+> require 'image'
+> l = image.lena()
+> image.display(l)
+> f = image.fabio()
+> image.display(f)
+```
diff --git a/assets/P2.pgm b/assets/P2.pgm
new file mode 100644
index 0000000..0e76d7d
--- /dev/null
+++ b/assets/P2.pgm
@@ -0,0 +1,11 @@
+P2
+# feep.ascii.pgm
+24 7
+15
+0 0  0  0  0  0  0  0  0 0  0  0  0  0  0  0  0 0  0  0  0  0  0  0
+0 3  3  3  3  0  0  7  7 7  7  0  0 11 11 11 11 0  0 15 15 15 15  0
+0 3  0  0  0  0  0  7  0 0  0  0  0 11  0  0  0 0  0 15  0  0 15  0
+0 3  3  3  0  0  0  7  7 7  0  0  0 11 11 11  0 0  0 15 15 15 15  0
+0 3  0  0  0  0  0  7  0 0  0  0  0 11  0  0  0 0  0 15  0  0  0  0
+0 3  0  0  0  0  0  7  7 7  7  0  0 11 11 11 11 0  0 15  0  0  0  0
+0 0  0  0  0  0  0  0  0 0  0  0  0  0  0  0  0 0  0  0  0  0  0  0
\ No newline at end of file
diff --git a/assets/P4.pbm b/assets/P4.pbm
new file mode 100644
index 0000000..0cc3736
--- /dev/null
+++ b/assets/P4.pbm
@@ -0,0 +1,3 @@
+P4
+1 1
+�
\ No newline at end of file
diff --git a/assets/P5.pgm b/assets/P5.pgm
new file mode 100644
index 0000000..b4ea2fb
--- /dev/null
+++ b/assets/P5.pgm
@@ -0,0 +1,7 @@
+P5
+100 1
+255
+������������������������������������������������������������������������������������666666666666666P5
+100 1
+255
+������������������������������������������������������������������������������������666666666666666
\ No newline at end of file
diff --git a/assets/P6.ppm b/assets/P6.ppm
new file mode 100644
index 0000000..68b997c
Binary files /dev/null and b/assets/P6.ppm differ
diff --git a/assets/corrupt-ihdr.png b/assets/corrupt-ihdr.png
new file mode 100644
index 0000000..ca53ac9
Binary files /dev/null and b/assets/corrupt-ihdr.png differ
diff --git a/assets/fabio.jpg b/assets/fabio.jpg
new file mode 100644
index 0000000..895b2bc
Binary files /dev/null and b/assets/fabio.jpg differ
diff --git a/assets/fabio.png b/assets/fabio.png
new file mode 100644
index 0000000..0a7b14c
Binary files /dev/null and b/assets/fabio.png differ
diff --git a/assets/foobar.png b/assets/foobar.png
new file mode 100644
index 0000000..f40f68c
Binary files /dev/null and b/assets/foobar.png differ
diff --git a/assets/grace_hopper_512.jpg b/assets/grace_hopper_512.jpg
new file mode 100644
index 0000000..6026020
Binary files /dev/null and b/assets/grace_hopper_512.jpg differ
diff --git a/assets/grace_hopper_512.png b/assets/grace_hopper_512.png
new file mode 100644
index 0000000..f0cb7cd
Binary files /dev/null and b/assets/grace_hopper_512.png differ
diff --git a/assets/gray16-1x2.png b/assets/gray16-1x2.png
new file mode 100644
index 0000000..9b3cb5e
Binary files /dev/null and b/assets/gray16-1x2.png differ
diff --git a/assets/gray3x1.png b/assets/gray3x1.png
new file mode 100644
index 0000000..ce89719
Binary files /dev/null and b/assets/gray3x1.png differ
diff --git a/assets/rectangle.png b/assets/rectangle.png
new file mode 100644
index 0000000..aa4720e
Binary files /dev/null and b/assets/rectangle.png differ
diff --git a/assets/rgb16-2x1.png b/assets/rgb16-2x1.png
new file mode 100644
index 0000000..3aab682
Binary files /dev/null and b/assets/rgb16-2x1.png differ
diff --git a/assets/rgb2x1.png b/assets/rgb2x1.png
new file mode 100644
index 0000000..60a0e9f
Binary files /dev/null and b/assets/rgb2x1.png differ
diff --git a/doc/colorspace.md b/doc/colorspace.md
new file mode 100644
index 0000000..12c37d6
--- /dev/null
+++ b/doc/colorspace.md
@@ -0,0 +1,73 @@
+<a name="image.colorspace"></a>
+## Color Space Conversions ##
+This section includes functions for performing conversions between 
+different color spaces.
+
+<a name="image.rgb2lab"></a>
+### [res] image.rgb2lab([dst,] src) ###
+Converts a `src` RGB image to [Lab](https://en.wikipedia.org/wiki/Lab_color_space). 
+If `dst` is provided, it is used to store the output
+image. Otherwise, returns a new `res` Tensor.
+
+<a name="image.lab2rgb"></a>
+### [res] image.lab2rgb([dst,] src) ###
+Converts a `src` [Lab](https://en.wikipedia.org/wiki/Lab_color_space) image to RGB. 
+If `dst` is provided, it is used to store the output
+image. Otherwise, returns a new `res` Tensor.
+
+<a name="image.rgb2yuv"></a>
+### [res] image.rgb2yuv([dst,] src) ###
+Converts a RGB image to YUV. If `dst` is provided, it is used to store the output
+image. Otherwise, returns a new `res` Tensor.
+
+<a name="image.yuv2rgb"></a>
+### [res] image.yuv2rgb([dst,] src) ###
+Converts a YUV image to RGB. If `dst` is provided, it is used to store the output
+image. Otherwise, returns a new `res` Tensor.
+
+<a name="image.rgb2y"></a>
+### [res] image.rgb2y([dst,] src) ###
+Converts a RGB image to Y (discard U and V). 
+If `dst` is provided, it is used to store the output
+image. Otherwise, returns a new `res` Tensor.
+
+<a name="image.rgb2hsl"></a>
+### [res] image.rgb2hsl([dst,] src) ###
+Converts a RGB image to [HSL](https://en.wikipedia.org/wiki/HSL_and_HSV). 
+If `dst` is provided, it is used to store the output
+image. Otherwise, returns a new `res` Tensor.
+
+<a name="image.hsl2rgb"></a>
+### [res] image.hsl2rgb([dst,] src) ###
+Converts a HSL image to RGB. 
+If `dst` is provided, it is used to store the output
+image. Otherwise, returns a new `res` Tensor.
+
+<a name="image.rgb2hsv"></a>
+### [res] image.rgb2hsv([dst,] src) ###
+Converts a RGB image to [HSV](https://en.wikipedia.org/wiki/HSL_and_HSV). 
+If `dst` is provided, it is used to store the output
+image. Otherwise, returns a new `res` Tensor.
+
+<a name="image.hsv2rgb"></a>
+### [res] image.hsv2rgb([dst,] src) ###
+Converts a HSV image to RGB. 
+If `dst` is provided, it is used to store the output
+image. Otherwise, returns a new `res` Tensor.
+
+<a name="image.rgb2nrgb"></a>
+### [res] image.rgb2nrgb([dst,] src) ###
+Converts an RGB image to normalized-RGB. 
+
+<a name="image.y2jet"></a>
+### [res] image.y2jet([dst,] src) ###
+Converts a L-levels (1 to L) greyscale image into a L-levels jet heat-map.
+If `dst` is provided, it is used to store the output image. Otherwise, returns a new `res` Tensor.
+
+This is particulary helpful for understanding the magnitude of the values of a matrix, or easily spot peaks in scalar field (like probability densities over a 2D area).
+For example, you can run it as
+
+```lua
+image.display{image=image.y2jet(torch.linspace(1,10,10)), zoom=50}
+```
+
diff --git a/doc/drawing.md b/doc/drawing.md
new file mode 100644
index 0000000..90aca0b
--- /dev/null
+++ b/doc/drawing.md
@@ -0,0 +1,44 @@
+<a name="image.drawing"></a>
+## Simple Drawing Routines ##
+This section includes simple routines to draw on images.
+
+<a name="image.drawText"></a>
+### [res] image.drawText(src, text, x, y, [options]) ###
+Draws text onto a 3-channel Tensor (C x H x W) at the x-offset `x` and y-offset `y`.
+
+The `options` table can be passed in to set color, background color, in-place etc.
+
+Options:
+* `color` - [table] The text color. A table of 3 numbers `{R, G, B}`, each number scaled between 0 and 255. For example, `red` is `{255, 0, 0}`
+* bg - [table] The background color where text is drawn. Same format as color.
+* size - [number] Size of the text to be drawn. `Default value = 1`.
+* wrap - [boolean] If the text goes out of bounds, wrap it with a newline automatically. `default value = true`
+* inplace - [boolean] If true, draws directly on the input tensor and returns it. `default value = false`
+
+Example:
+
+```lua
+image.drawText(image.lena(), "hello\nworld", 10, 10)
+image.drawText(image.lena(), "hello\nworld", 10, 20,{color = {0, 255, 0}, size = 5})
+image.drawText(image.lena(), "hello\nworld", 10, 20,{color = {0, 255, 0}, bg = {255, 0, 0}, size = 5})
+```
+
+<a name="image.drawRect"></a>
+### [res] image.drawRect(src, x1, y1, x2, y2, [options]) ###
+Draws a rectangle onto a 3-channel Tensor (C x H x W).  The top-left corner of
+the rectangle is `x1, y1`, and the bottom-right corner is `x2, y2`.
+
+The `options` table can be passed in to set color, in-place etc.
+
+Options:
+* `color` - [table] The rectangle color. A table of 3 numbers `{R, G, B}`, each
+    number scaled between 0 and 255. For example, `red` is `{255, 0, 0}`
+* `lineWidth` - [number] The width of the rectangle line, in pixels
+* `inplace` - [boolean] If true, draws directly on the input tensor and returns
+    it. `default value = false`
+
+Example:
+
+```lua
+image.drawRect(image.lena(), 200, 200, 370, 400, {lineWidth = 5, color = {0, 255, 0}})
+```
diff --git a/doc/gui.md b/doc/gui.md
new file mode 100644
index 0000000..3213af3
--- /dev/null
+++ b/doc/gui.md
@@ -0,0 +1,53 @@
+<a name="image.grapicalinter"></a>
+## Graphical User Interfaces ##
+The following functions, except for [image.toDisplayTensor](#image.toDisplayTensor), 
+require package [qtlua](https://github.com/torch/qtlua) and can only be 
+accessed via the `qlua` Lua interpreter (as opposed to the 
+[th](https://github.com/torch/trepl) or luajit interpreter).
+
+<a name="image.toDisplayTensor"></a>
+### [res] image.toDisplayTensor(input, [...]) ###
+Optional arguments `[...]` expand to `padding`, `nrow`, `scaleeach`, `min`, `max`, `symmetric`, `saturate`.
+Returns a single `res` Tensor that contains a grid of all in the images in `input`.
+The latter can either be a table of image Tensors of size `height x width` (greyscale) or 
+`nChannel x height x width` (color), 
+or a single Tensor of size `batchSize x nChannel x height x width` or `nChannel x height x width` 
+where `nChannel=[3,1]`, `batchSize x height x width` or `height x width`.
+
+When `scaleeach=false` (the default), all detected images 
+are compressed with successive calls to [image.minmax](simpletransform.md#image.minmax):
+```lua
+image.minmax{tensor=input[i], min=min, max=max, symm=symmetric, saturate=saturate}
+```
+`padding` specifies the number of padding pixels between images. The default is 0.
+`nrow` specifies the number of images per row. The default is 6.
+
+Note that arguments can also be specified as key-value arguments (in a table).
+
+<a name="image.display"></a>
+### [res] image.display(input, [...]) ###
+Optional arguments `[...]` expand to `zoom`, `min`, `max`, `legend`, `win`, 
+`x`, `y`, `scaleeach`, `gui`, `offscreen`, `padding`, `symm`, `nrow`.
+Displays `input` image(s) with optional saturation and zooming. 
+The `input`, which is either a Tensor of size `HxW`, `KxHxW` or `Kx3xHxW`, or list,
+is first prepared for display by passing it through [image.toDisplayTensor](#image.toDisplayTensor):
+```lua
+input = image.toDisplayTensor{
+   input=input, padding=padding, nrow=nrow, saturate=saturate, 
+   scaleeach=scaleeach, min=min, max=max, symmetric=symm
+}
+```
+The resulting `input` will be displayed using [qtlua](https://github.com/torch/qtlua).
+The displayed image will be zoomed by a factor of `zoom`. The default is 1.
+If `gui=true` (the default), the graphical user inteface (GUI) 
+is an interactive window that provides the user with the ability to zoom in or out. 
+This can be turned off for a faster display. `legend` is a legend to be displayed,
+which has a default value of `image.display`. `win` is an optional qt window descriptor.
+If `x` and `y` are given, they are used to offset the image. Both default to 0.
+When `offscreen=true`, rendering (to generate images) is performed offscreen.
+
+<a name="image.window"></a>
+### [window, painter] image.window([...]) ###
+Creates a window context for images. 
+Optional arguments `[...]` expand to `hook_resize`, `hook_mousepress`, `hook_mousedoublepress`.
+These have a default value of `nil`, but may correspond to commensurate qt objects.
diff --git a/doc/index.md b/doc/index.md
new file mode 100644
index 0000000..5e50c39
--- /dev/null
+++ b/doc/index.md
@@ -0,0 +1,35 @@
+# image Package Reference Manual #
+
+__image__ is the [Torch7 distribution](http://torch.ch/) package for processing 
+images. It contains a wide variety of functions divided into the following categories:
+
+  * [Saving and loading](saveload.md) images as JPEG, PNG, PPM and PGM;
+  * [Simple transformations](simpletransform.md) like translation, scaling and rotation;
+  * [Parameterized transformations](paramtransform.md) like convolutions and warping;
+  * [Simple Drawing Routines](doc/drawing.md) like drawing text or a rectangle on an image;
+  * [Graphical user interfaces](gui.md) like display and window;
+  * [Color Space Conversions](colorspace.md) from and to RGB, YUV, Lab, and HSL;
+  * [Tensor Constructors](tensorconstruct.md) for creating Lenna, Fabio and Gaussian and Laplacian kernels;
+
+Note that unless speficied otherwise, this package deals with images of size 
+`nChannel x height x width`.
+
+## Install
+
+The easiest way to install this package it by following the [intructions](http://torch.ch/docs/getting-started.html) 
+to install [Torch7](www.torch.ch), which includes __image__. 
+Otherwise, to update or manually re-install it:
+
+```bash
+$ luarocks install image
+```
+
+## Usage
+
+```lua
+> require 'image'
+> l = image.lena()
+> image.display(l)
+> f = image.fabio()
+> image.display(f)
+```
diff --git a/doc/paramtransform.md b/doc/paramtransform.md
new file mode 100644
index 0000000..839c754
--- /dev/null
+++ b/doc/paramtransform.md
@@ -0,0 +1,73 @@
+<a name="image.paramtrans"></a>
+## Parameterized transformations ##
+This section includes functions for performing transformations on 
+images requiring parameter Tensors like a warp `field` or a convolution
+`kernel`.
+
+<a name="image.warp"></a>
+### [res] image.warp([dst,]src,field,[mode,offset,clamp_mode,pad_val]) ###
+Warps image `src` (of size`KxHxW`) 
+according to flow field `field`. The latter has size `2xHxW` where the 
+first dimension is for the `(y,x)` flow field. String `mode` can 
+take on values [lanczos](https://en.wikipedia.org/wiki/Lanczos_resampling), 
+[bicubic](https://en.wikipedia.org/wiki/Bicubic_interpolation),
+[bilinear](https://en.wikipedia.org/wiki/Bilinear_interpolation) (the default), 
+or *simple*. When `offset` is true (the default), `(x,y)` is added to the flow field.
+The `clamp_mode` variable specifies how to handle the interpolation of samples off the input image.
+Permitted values are strings *clamp* (the default) or *pad*.
+When `clamp_mode` equals `pad`, the user can specify the padding value with `pad_val` (default = 0). Note: setting this value when `clamp_mode` equals `clamp` will result in an error.
+If `dst` is specified, it is used to store the result of the warp.
+Otherwise, returns a new `res` Tensor.
+
+<a name="image.convolve"></a>
+### [res] image.convolve([dst,] src, kernel, [mode]) ###
+Convolves Tensor `kernel` over image `src`. Valid string values for argument 
+`mode` are :
+ * *full* : the `src` image is effectively zero-padded such that the `res` of the convolution has the same size as `src`;
+ * *valid* (the default) : the `res` image will have `math.ceil(kernel/2)` less columns and rows on each side;
+ * *same* : performs a *full* convolution, but crops out the portion fitting the output size of *valid*;
+Note that this function internally uses 
+[torch.conv2](https://github.com/torch/torch7/blob/master/doc/maths.md#torch.conv.dok).
+If `dst` is provided, it is used to store the output image. 
+Otherwise, returns a new `res` Tensor.
+
+<a name="image.lcn"></a>
+### [res] image.lcn(src, [kernel]) ###
+Local contrast normalization (LCN) on a given `src` image using kernel `kernel`.
+If `kernel` is not given, then a default `9x9` Gaussian is used 
+(see [image.gaussian](tensorconstruct.md#image.gaussian)).
+
+To prevent border effects, the image is first global contrast normalized
+(GCN) by substracting the global mean and dividing by the global 
+standard deviation.
+
+Then the image is locally contrast normalized using the following equation:
+```lua
+res = (src - lm(src)) / sqrt( lm(src) - lm(src*src) )
+```
+where `lm(x)` is the local mean of each pixel in the image (i.e. 
+`image.convolve(x,kernel)`) and  `sqrt(x)` is the element-wise 
+square root of `x`. In other words, LCN performs 
+local substractive and divisive normalization. 
+
+Note that this implementation is different than the LCN Layer defined on page 3 of 
+[What is the Best Multi-Stage Architecture for Object Recognition?](http://yann.lecun.com/exdb/publis/pdf/jarrett-iccv-09.pdf).
+
+<a name="image.erode"></a>
+### [res] image.erode(src, [kernel, pad]) ###
+Performs a [morphological erosion](https://en.wikipedia.org/wiki/Erosion_(morphology)) 
+on binary (zeros and ones) image `src` using odd 
+dimensioned morphological binary kernel `kernel`. 
+The default is a kernel consisting of ones of size `3x3`. Number 
+`pad` is the value to assume outside the image boundary when performing 
+the convolution. The default is 1.
+
+<a name="image.dilate"></a>
+### [res] image.dilate(src, [kernel, pad]) ###
+Performs a [morphological dilation](https://en.wikipedia.org/wiki/Dilation_(morphology)) 
+on binary (zeros and ones) image `src` using odd 
+dimensioned morphological binary kernel `kernel`. 
+The default is a kernel consisting of ones of size `3x3`. Number 
+`pad` is the value to assume outside the image boundary when performing 
+the convolution. The default is 0.
+
diff --git a/doc/saveload.md b/doc/saveload.md
new file mode 100644
index 0000000..d90fc20
--- /dev/null
+++ b/doc/saveload.md
@@ -0,0 +1,63 @@
+<a name="image.saveload"></a>
+## Saving and Loading ##
+This sections includes functions for saving and loading different types 
+of images to and from disk.
+
+<a name="image.load"></a>
+### [res] image.load(filename, [depth, tensortype]) ###
+Loads an image located at path `filename` having `depth` channels (1 or 3)
+into a [Tensor](https://github.com/torch/torch7/blob/master/doc/tensor.md#tensor)
+of type `tensortype` (*float*, *double* or *byte*). The last two arguments 
+are optional.
+
+The image format is determined from the `filename`'s 
+extension suffix. Supported formats are 
+[JPEG](https://en.wikipedia.org/wiki/JPEG), 
+[PNG](https://en.wikipedia.org/wiki/Portable_Network_Graphics), 
+[PPM and PGM](https://en.wikipedia.org/wiki/Netpbm_format).
+ 
+The returned `res` Tensor has size `nChannel x height x width` where `nChannel` is 
+1 (greyscale) or 3 (usually [RGB](https://en.wikipedia.org/wiki/RGB_color_model) 
+or [YUV](https://en.wikipedia.org/wiki/YUV).
+
+Usage:
+```lua
+--To load as byte tensor for rgb imagefile
+local img = image.load(imagefile,3,'byte')
+
+--To load as byte tensor for gray imagefile
+local img = image.load(imagefile,1,'byte')
+
+```
+
+<a name="image.save"></a>
+### image.save(filename, tensor) ###
+Saves Tensor `tensor` to disk at path `filename`. The format to which 
+the image is saved is extrapolated from the `filename`'s extension suffix.
+The `tensor` should be of size `nChannel x height x width`.
+To save with a minimal loss, the tensor values should lie in the range [0, 1] since the tensor is clamped between 0 and 1 before being saved to the disk.
+
+<a name="image.decompressJPG"></a>
+### [res] image.decompressJPG(tensor, [depth, tensortype]) ###
+Decompresses an image from a ByteTensor in memory having `depth` channels (1 or 3)
+into a [Tensor](https://github.com/torch/torch7/blob/master/doc/tensor.md#tensor)
+of type `tensortype` (*float*, *double* or *byte*). The last two arguments
+are optional.
+
+Usage:
+```lua
+local fin = torch.DiskFile(imfile, 'r')
+fin:binary()
+fin:seekEnd()
+local file_size_bytes = fin:position() - 1
+fin:seek(1)
+local img_binary = torch.ByteTensor(file_size_bytes)
+fin:readByte(img_binary:storage())
+fin:close()
+-- Then when you're ready to decompress the ByteTensor:
+im = image.decompressJPG(img_binary)
+```
+
+<a name="image.compressJPG"></a>
+### [res] image.compressJPG(tensor, [quality]) ###
+Compresses an image to a ByteTensor in memory.  Optional quality is between 1 and 100 and adjusts compression quality.
diff --git a/doc/simpletransform.md b/doc/simpletransform.md
new file mode 100644
index 0000000..52b5341
--- /dev/null
+++ b/doc/simpletransform.md
@@ -0,0 +1,130 @@
+<a name="image.simpletrans"></a>
+## Simple Transformations ##
+This section includes simple but very common image transformations 
+like cropping, translation, scaling and rotation. 
+
+<a name="image.crop"></a>
+### [res] image.crop([dst,] src, x1, y1, [x2, y2]) ###
+Crops image `src` at coordinate `(x1, y1)` up to coordinate 
+`(x2, y2)`. The coordinate indexing is zero-based and `(x2, y2)` is non-inclusive.
+If `dst` is provided, it is used to store the output
+image. Otherwise, returns a new `res` Tensor. 
+
+```lua
+-- The indexing starts with 0 and 2 is non-inclusive coordinate.
+> require('image')
+> image.crop(torch.Tensor(3, 2, 2), 0, 0 , 2, 2) -- crop is a correct crop and the result is 3x2x2 tensor. 
+(1,.,.) = 
+  0  0
+  0  0
+
+(2,.,.) = 
+  0  0
+  0  0
+
+(3,.,.) = 
+  0  0
+  0  0
+[torch.DoubleTensor of size 3x2x2]
+```
+
+### [res] image.crop([dst,] src, format, width, height) ###
+Crops a `width x height` section of source image `src`. The argument
+`format` is a string specifying where to crop: it can be "c", "tl", "tr",
+"bl" or "br" for center, top left, top right, bottom left and bottom right,
+respectively.  If `dst` is provided, it is used to store the output
+image. Otherwise, returns a new `res` Tensor.
+
+<a name="image.translate"></a>
+### [res] image.translate([dst,] src, x, y) ###
+Translates image `src` by `x` pixels horizontally and `y` pixels 
+vertically. If `dst` is provided, it is used to store the output
+image. Otherwise, returns a new `res` Tensor.
+
+<a name="image.scale"></a>
+### [res] image.scale(src, width, height, [mode]) ###
+Rescale the height and width of image `src` to have 
+width `width` and height `height`.  Variable `mode` specifies 
+type of interpolation to be used. Valid values include 
+[bilinear](https://en.wikipedia.org/wiki/Bilinear_interpolation)
+(the default), [bicubic](https://en.wikipedia.org/wiki/Bicubic_interpolation),
+or *simple* interpolation. Returns a new `res` Tensor.
+
+### [res] image.scale(src, size, [mode]) ###
+Rescale the height and width of image `src`.  Variable `size` is a number
+or a string specifying the size of the result image. When `size` is a
+number, it specifies the maximum height or width of the output. When it is
+a string like `WxH` or `MAX` or `^MIN`, `*SC` or `*SCn/SCd` it specifies
+the `height x width`, maximum height or width of the output, minimum height
+or width of the output, scaling factor (number), or fractional scaling
+factor (int/int), respectively.
+
+### [res] image.scale(dst, src, [mode]) ###
+Rescale the height and width of image `src` to fit the dimensions of 
+Tensor `dst`. 
+
+<a name="image.rotate"></a>
+### [res] image.rotate([dst,], src, theta, [mode]) ###
+Rotates image `src` by `theta` radians. 
+If `dst` is specified it is used to store the results of the rotation.
+Variable `mode` specifies type of interpolation to be used. Valid values include 
+*simple* (the default) or *bilinear* interpolation.
+
+<a name="image.polar"></a>
+### [res] image.polar([dst,], src, [interpolation], [mode]) ###
+Converts image `src` to polar coordinates. In the polar image, angular information is in the vertical direction and radius information in the horizontal direction.
+If `dst` is specified it is used to store the polar image. If `dst` is not specified, its size is automatically determined. Variable `interpolation` specifies type of interpolation to be used. Valid values include *simple* (the default) or *bilinear* interpolation. Variable `mode` determines whether the *full* image is converted to the polar space (implying empty regions in the polar image), or whether only the *valid* central part of the polar transform is returned (the default).
+
+<a name="image.logpolar"></a>
+### [res] image.logpolar([dst,], src, [interpolation], [mode]) ###
+Converts image `src` to log-polar coordinates. In the log-polar image, angular information is in the vertical direction and log-radius information in the horizontal direction.
+If `dst` is specified it is used to store the polar image. If `dst` is not specified, its size is automatically determined. Variable `interpolation` specifies type of interpolation to be used. Valid values include *simple* (the default) or *bilinear* interpolation. Variable `mode` determines whether the *full* image is converted to the log-polar space (implying empty regions in the log-polar image), or whether only the *valid* central part of the log-polar transform is returned (the default). 
+
+<a name="image.hflip"></a>
+### [res] image.hflip([dst,] src) ###
+Flips image `src` horizontally (left<->right). If `dst` is provided, it is used to
+store the output image. Otherwise, returns a new `res` Tensor.
+
+<a name="image.vflip"></a>
+### [res] image.vflip([dst,], src) ###
+Flips image `src` vertically (upsize<->down). If `dst` is provided, it is used to
+store the output image. Otherwise, returns a new `res` Tensor.
+
+<a name="image.flip"></a>
+### [res] image.flip([dst,] src, flip_dim) ###
+Flips image `src` along the specified dimension. If `dst` is provided, it is used to
+store the output image. Otherwise, returns a new `res` Tensor.
+
+<a name="image.minmax"></a>
+### [res] image.minmax{tensor, [min, max, ...]} ###
+Compresses image `tensor` between `min` and `max`. 
+When omitted, `min` and `max` are infered from 
+`tensor:min()` and `tensor:max()`, respectively.
+The `tensor` is normalized using `min` and `max` by performing :
+```lua
+tensor:add(-min):div(max-min)
+```
+Other optional arguments (`...`) include `symm`, `inplace`, `saturate`, and `tensorOut`.
+When `symm=true` and `min` and `max` are both omitted, 
+`max = min*2` in the above equation. This results in a symmetric dynamic 
+range that is particularly useful for drawing filters. The default is `false`.
+When `inplace=true`, the result of the compression is stored in `tensor`. 
+The default is `false`.
+When `saturate=true`, the result of the compression is passed through
+a function that clips the values between 0 and 1 
+(i.e. anything below 0 is set to 0, anything above 1 is set to 1).
+When provided, Tensor `tensorOut` is used to store results. 
+Note that arguments should be provided as key-value pairs (in a table).
+
+<a name="image.gaussianpyramid"></a>
+### [res] image.gaussianpyramid([dst,] src, scales) ###
+Constructs a [Gaussian pyramid](https://en.wikipedia.org/wiki/Gaussian_pyramid)
+of scales `scales` from a 2D or 3D `src` image or size 
+`[nChannel x] width x height`. Each Tensor at index `i` 
+in the returned list of Tensors has size  `[nChannel x] width*scales[i] x height*scales[i]`.
+
+If list `dst` is provided, with or without Tensors, it is used to store the output images. 
+Otherwise, returns a new `res` list of Tensors.
+
+Internally, this function makes use of functions [image.gaussian](tensorconstruct.md#image.gaussian),
+[image.scale](#image.scale) and [image.convolve](paramtransform.md#image.convolve).
diff --git a/doc/tensorconstruct.md b/doc/tensorconstruct.md
new file mode 100644
index 0000000..18b909e
--- /dev/null
+++ b/doc/tensorconstruct.md
@@ -0,0 +1,91 @@
+<a name="image.tensorconst"></a>
+## Tensor Constructors ##
+The following functions construct Tensors like Gaussian or 
+Laplacian kernels, or images like Lenna and Fabio.
+
+<a name="image.lena"></a>
+### [res] image.lena() ###
+Returns the classic `Lenna.jpg` image as a `3 x 512 x 512` Tensor.
+
+<a name="image.fabio"></a>
+### [res] image.fabio() ###
+Returns the `fabio.jpg` image as a `257 x 271` Tensor.
+
+<a name="image.gaussian"></a>
+### [res] image.gaussian([size, sigma, amplitude, normalize, [...]]) ###
+Returns a 2D [Gaussian](https://en.wikipedia.org/wiki/Gaussian_function) 
+kernel of size `height x width`. When used as a Gaussian smoothing operator in a 2D 
+convolution, this kernel is used to `blur` images and remove detail and noise 
+(ref.: [Gaussian Smoothing](http://homepages.inf.ed.ac.uk/rbf/HIPR2/gsmooth.htm)).
+Optional arguments `[...]` expand to 
+`width`, `height`, `sigma_horz`, `sigma_vert`, `mean_horz`, `mean_vert` and `tensor`.
+
+The default value of `height` and `width` is `size`, where the latter 
+has a default value of 3. The amplitude of the Gaussian (its maximum value) 
+is `amplitude`. The default is 1. 
+When `normalize=true`, the kernel is normalized to have a sum of 1.
+This overrides the `amplitude` argument. The default is `false`.
+The default value of the horizontal and vertical standard deviation 
+`sigma_horz` and `sigma_vert` of the Gaussian kernel is `sigma`, where 
+the latter has a default value of 0.25. The default values for the 
+corresponding means `mean_horz` and `mean_vert` are 0.5. Both the 
+standard deviations and means are relative to kernels of unit width and height
+where the top-left corner is the origin. In other works, a mean of 0.5 is 
+the center of the kernel size, while a standard deviation of 0.25 is a quarter
+of it. When `tensor` is provided (a 2D Tensor), the `height`, `width` and `size` are ignored.
+It is used to store the returned gaussian kernel.
+
+Note that arguments can also be specified as key-value arguments (in a table).
+
+<a name="image.gaussian1D"></a>
+### [res] image.gaussian1D([size, sigma, amplitude, normalize, mean, tensor]) ###
+Returns a 1D Gaussian kernel of size `size`, mean `mean` and standard 
+deviation `sigma`. 
+Respectively, these arguments have default values of 3, 0.25 and 0.5. 
+The amplitude of the Gaussian (its maximum value) 
+is `amplitude`. The default is 1. 
+When `normalize=true`, the kernel is normalized to have a sum of 1.
+This overrides the `amplitude` argument. The default is `false`. Both the 
+standard deviation and mean are relative to a kernel of unit size. 
+In other works, a mean of 0.5 is the center of the kernel size, 
+while a standard deviation of 0.25 is a quarter of it. 
+When `tensor` is provided (a 1D Tensor), the `size` is ignored.
+It is used to store the returned gaussian kernel.
+
+Note that arguments can also be specified as key-value arguments (in a table).
+
+<a name="image.laplacian"></a>
+### [res] image.laplacian([size, sigma, amplitude, normalize, [...]]) ###
+Returns a 2D [Laplacian](https://en.wikipedia.org/wiki/Blob_detection#The_Laplacian_of_Gaussian) 
+kernel of size `height x width`. 
+When used in a 2D convolution, the Laplacian of an image highlights 
+regions of rapid intensity change and is therefore often used for edge detection 
+(ref.: [Laplacian/Laplacian of Gaussian](http://homepages.inf.ed.ac.uk/rbf/HIPR2/log.htm)).
+Optional arguments `[...]` expand to 
+`width`, `height`, `sigma_horz`, `sigma_vert`, `mean_horz`, `mean_vert`.
+
+The default value of `height` and `width` is `size`, where the latter 
+has a default value of 3. The amplitude of the Laplacian (its maximum value) 
+is `amplitude`. The default is 1. 
+When `normalize=true`, the kernel is normalized to have a sum of 1.
+This overrides the `amplitude` argument. The default is `false`.
+The default value of the horizontal and vertical standard deviation 
+`sigma_horz` and `sigma_vert` of the Laplacian kernel is `sigma`, where 
+the latter has a default value of 0.25. The default values for the 
+corresponding means `mean_horz` and `mean_vert` are 0.5. Both the 
+standard deviations and means are relative to kernels of unit width and height
+where the top-left corner is the origin. In other works, a mean of 0.5 is 
+the center of the kernel size, while a standard deviation of 0.25 is a quarter
+of it.
+
+<a name="image.colormap"></a>
+### [res] image.colormap(nColor) ###
+Creates an optimally-spaced RGB color mapping of `nColor` colors. 
+Note that the mapping is obtained by generating the colors around 
+the HSV wheel, varying the Hue component.
+The returned `res` Tensor has size `nColor x 3`. 
+
+<a name="image.jetColormap"></a>
+### [res] image.jetColormap(nColor) ###
+Creates a jet (blue to red) RGB color mapping of `nColor` colors.
+The returned `res` Tensor has size `nColor x 3`. 
diff --git a/font.c b/font.c
new file mode 100644
index 0000000..1fda9e1
--- /dev/null
+++ b/font.c
@@ -0,0 +1,287 @@
+/*
+Software License Agreement (BSD License)
+
+Copyright (c) 2012 Adafruit Industries.  All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice,
+  this list of conditions and the following disclaimer.
+- Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+
+// Borrowed from https://github.com/adafruit/Adafruit-GFX-Library
+// Standard ASCII 5x7 font
+static const unsigned char image_ada_font[] = {
+	0x00, 0x00, 0x00, 0x00, 0x00,
+	0x3E, 0x5B, 0x4F, 0x5B, 0x3E,
+	0x3E, 0x6B, 0x4F, 0x6B, 0x3E,
+	0x1C, 0x3E, 0x7C, 0x3E, 0x1C,
+	0x18, 0x3C, 0x7E, 0x3C, 0x18,
+	0x1C, 0x57, 0x7D, 0x57, 0x1C,
+	0x1C, 0x5E, 0x7F, 0x5E, 0x1C,
+	0x00, 0x18, 0x3C, 0x18, 0x00,
+	0xFF, 0xE7, 0xC3, 0xE7, 0xFF,
+	0x00, 0x18, 0x24, 0x18, 0x00,
+	0xFF, 0xE7, 0xDB, 0xE7, 0xFF,
+	0x30, 0x48, 0x3A, 0x06, 0x0E,
+	0x26, 0x29, 0x79, 0x29, 0x26,
+	0x40, 0x7F, 0x05, 0x05, 0x07,
+	0x40, 0x7F, 0x05, 0x25, 0x3F,
+	0x5A, 0x3C, 0xE7, 0x3C, 0x5A,
+	0x7F, 0x3E, 0x1C, 0x1C, 0x08,
+	0x08, 0x1C, 0x1C, 0x3E, 0x7F,
+	0x14, 0x22, 0x7F, 0x22, 0x14,
+	0x5F, 0x5F, 0x00, 0x5F, 0x5F,
+	0x06, 0x09, 0x7F, 0x01, 0x7F,
+	0x00, 0x66, 0x89, 0x95, 0x6A,
+	0x60, 0x60, 0x60, 0x60, 0x60,
+	0x94, 0xA2, 0xFF, 0xA2, 0x94,
+	0x08, 0x04, 0x7E, 0x04, 0x08,
+	0x10, 0x20, 0x7E, 0x20, 0x10,
+	0x08, 0x08, 0x2A, 0x1C, 0x08,
+	0x08, 0x1C, 0x2A, 0x08, 0x08,
+	0x1E, 0x10, 0x10, 0x10, 0x10,
+	0x0C, 0x1E, 0x0C, 0x1E, 0x0C,
+	0x30, 0x38, 0x3E, 0x38, 0x30,
+	0x06, 0x0E, 0x3E, 0x0E, 0x06,
+	0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x5F, 0x00, 0x00,
+	0x00, 0x07, 0x00, 0x07, 0x00,
+	0x14, 0x7F, 0x14, 0x7F, 0x14,
+	0x24, 0x2A, 0x7F, 0x2A, 0x12,
+	0x23, 0x13, 0x08, 0x64, 0x62,
+	0x36, 0x49, 0x56, 0x20, 0x50,
+	0x00, 0x08, 0x07, 0x03, 0x00,
+	0x00, 0x1C, 0x22, 0x41, 0x00,
+	0x00, 0x41, 0x22, 0x1C, 0x00,
+	0x2A, 0x1C, 0x7F, 0x1C, 0x2A,
+	0x08, 0x08, 0x3E, 0x08, 0x08,
+	0x00, 0x80, 0x70, 0x30, 0x00,
+	0x08, 0x08, 0x08, 0x08, 0x08,
+	0x00, 0x00, 0x60, 0x60, 0x00,
+	0x20, 0x10, 0x08, 0x04, 0x02,
+	0x3E, 0x51, 0x49, 0x45, 0x3E,
+	0x00, 0x42, 0x7F, 0x40, 0x00,
+	0x72, 0x49, 0x49, 0x49, 0x46,
+	0x21, 0x41, 0x49, 0x4D, 0x33,
+	0x18, 0x14, 0x12, 0x7F, 0x10,
+	0x27, 0x45, 0x45, 0x45, 0x39,
+	0x3C, 0x4A, 0x49, 0x49, 0x31,
+	0x41, 0x21, 0x11, 0x09, 0x07,
+	0x36, 0x49, 0x49, 0x49, 0x36,
+	0x46, 0x49, 0x49, 0x29, 0x1E,
+	0x00, 0x00, 0x14, 0x00, 0x00,
+	0x00, 0x40, 0x34, 0x00, 0x00,
+	0x00, 0x08, 0x14, 0x22, 0x41,
+	0x14, 0x14, 0x14, 0x14, 0x14,
+	0x00, 0x41, 0x22, 0x14, 0x08,
+	0x02, 0x01, 0x59, 0x09, 0x06,
+	0x3E, 0x41, 0x5D, 0x59, 0x4E,
+	0x7C, 0x12, 0x11, 0x12, 0x7C,
+	0x7F, 0x49, 0x49, 0x49, 0x36,
+	0x3E, 0x41, 0x41, 0x41, 0x22,
+	0x7F, 0x41, 0x41, 0x41, 0x3E,
+	0x7F, 0x49, 0x49, 0x49, 0x41,
+	0x7F, 0x09, 0x09, 0x09, 0x01,
+	0x3E, 0x41, 0x41, 0x51, 0x73,
+	0x7F, 0x08, 0x08, 0x08, 0x7F,
+	0x00, 0x41, 0x7F, 0x41, 0x00,
+	0x20, 0x40, 0x41, 0x3F, 0x01,
+	0x7F, 0x08, 0x14, 0x22, 0x41,
+	0x7F, 0x40, 0x40, 0x40, 0x40,
+	0x7F, 0x02, 0x1C, 0x02, 0x7F,
+	0x7F, 0x04, 0x08, 0x10, 0x7F,
+	0x3E, 0x41, 0x41, 0x41, 0x3E,
+	0x7F, 0x09, 0x09, 0x09, 0x06,
+	0x3E, 0x41, 0x51, 0x21, 0x5E,
+	0x7F, 0x09, 0x19, 0x29, 0x46,
+	0x26, 0x49, 0x49, 0x49, 0x32,
+	0x03, 0x01, 0x7F, 0x01, 0x03,
+	0x3F, 0x40, 0x40, 0x40, 0x3F,
+	0x1F, 0x20, 0x40, 0x20, 0x1F,
+	0x3F, 0x40, 0x38, 0x40, 0x3F,
+	0x63, 0x14, 0x08, 0x14, 0x63,
+	0x03, 0x04, 0x78, 0x04, 0x03,
+	0x61, 0x59, 0x49, 0x4D, 0x43,
+	0x00, 0x7F, 0x41, 0x41, 0x41,
+	0x02, 0x04, 0x08, 0x10, 0x20,
+	0x00, 0x41, 0x41, 0x41, 0x7F,
+	0x04, 0x02, 0x01, 0x02, 0x04,
+	0x40, 0x40, 0x40, 0x40, 0x40,
+	0x00, 0x03, 0x07, 0x08, 0x00,
+	0x20, 0x54, 0x54, 0x78, 0x40,
+	0x7F, 0x28, 0x44, 0x44, 0x38,
+	0x38, 0x44, 0x44, 0x44, 0x28,
+	0x38, 0x44, 0x44, 0x28, 0x7F,
+	0x38, 0x54, 0x54, 0x54, 0x18,
+	0x00, 0x08, 0x7E, 0x09, 0x02,
+	0x18, 0xA4, 0xA4, 0x9C, 0x78,
+	0x7F, 0x08, 0x04, 0x04, 0x78,
+	0x00, 0x44, 0x7D, 0x40, 0x00,
+	0x20, 0x40, 0x40, 0x3D, 0x00,
+	0x7F, 0x10, 0x28, 0x44, 0x00,
+	0x00, 0x41, 0x7F, 0x40, 0x00,
+	0x7C, 0x04, 0x78, 0x04, 0x78,
+	0x7C, 0x08, 0x04, 0x04, 0x78,
+	0x38, 0x44, 0x44, 0x44, 0x38,
+	0xFC, 0x18, 0x24, 0x24, 0x18,
+	0x18, 0x24, 0x24, 0x18, 0xFC,
+	0x7C, 0x08, 0x04, 0x04, 0x08,
+	0x48, 0x54, 0x54, 0x54, 0x24,
+	0x04, 0x04, 0x3F, 0x44, 0x24,
+	0x3C, 0x40, 0x40, 0x20, 0x7C,
+	0x1C, 0x20, 0x40, 0x20, 0x1C,
+	0x3C, 0x40, 0x30, 0x40, 0x3C,
+	0x44, 0x28, 0x10, 0x28, 0x44,
+	0x4C, 0x90, 0x90, 0x90, 0x7C,
+	0x44, 0x64, 0x54, 0x4C, 0x44,
+	0x00, 0x08, 0x36, 0x41, 0x00,
+	0x00, 0x00, 0x77, 0x00, 0x00,
+	0x00, 0x41, 0x36, 0x08, 0x00,
+	0x02, 0x01, 0x02, 0x04, 0x02,
+	0x3C, 0x26, 0x23, 0x26, 0x3C,
+	0x1E, 0xA1, 0xA1, 0x61, 0x12,
+	0x3A, 0x40, 0x40, 0x20, 0x7A,
+	0x38, 0x54, 0x54, 0x55, 0x59,
+	0x21, 0x55, 0x55, 0x79, 0x41,
+	0x22, 0x54, 0x54, 0x78, 0x42, // a-umlaut
+	0x21, 0x55, 0x54, 0x78, 0x40,
+	0x20, 0x54, 0x55, 0x79, 0x40,
+	0x0C, 0x1E, 0x52, 0x72, 0x12,
+	0x39, 0x55, 0x55, 0x55, 0x59,
+	0x39, 0x54, 0x54, 0x54, 0x59,
+	0x39, 0x55, 0x54, 0x54, 0x58,
+	0x00, 0x00, 0x45, 0x7C, 0x41,
+	0x00, 0x02, 0x45, 0x7D, 0x42,
+	0x00, 0x01, 0x45, 0x7C, 0x40,
+	0x7D, 0x12, 0x11, 0x12, 0x7D, // A-umlaut
+	0xF0, 0x28, 0x25, 0x28, 0xF0,
+	0x7C, 0x54, 0x55, 0x45, 0x00,
+	0x20, 0x54, 0x54, 0x7C, 0x54,
+	0x7C, 0x0A, 0x09, 0x7F, 0x49,
+	0x32, 0x49, 0x49, 0x49, 0x32,
+	0x3A, 0x44, 0x44, 0x44, 0x3A, // o-umlaut
+	0x32, 0x4A, 0x48, 0x48, 0x30,
+	0x3A, 0x41, 0x41, 0x21, 0x7A,
+	0x3A, 0x42, 0x40, 0x20, 0x78,
+	0x00, 0x9D, 0xA0, 0xA0, 0x7D,
+	0x3D, 0x42, 0x42, 0x42, 0x3D, // O-umlaut
+	0x3D, 0x40, 0x40, 0x40, 0x3D,
+	0x3C, 0x24, 0xFF, 0x24, 0x24,
+	0x48, 0x7E, 0x49, 0x43, 0x66,
+	0x2B, 0x2F, 0xFC, 0x2F, 0x2B,
+	0xFF, 0x09, 0x29, 0xF6, 0x20,
+	0xC0, 0x88, 0x7E, 0x09, 0x03,
+	0x20, 0x54, 0x54, 0x79, 0x41,
+	0x00, 0x00, 0x44, 0x7D, 0x41,
+	0x30, 0x48, 0x48, 0x4A, 0x32,
+	0x38, 0x40, 0x40, 0x22, 0x7A,
+	0x00, 0x7A, 0x0A, 0x0A, 0x72,
+	0x7D, 0x0D, 0x19, 0x31, 0x7D,
+	0x26, 0x29, 0x29, 0x2F, 0x28,
+	0x26, 0x29, 0x29, 0x29, 0x26,
+	0x30, 0x48, 0x4D, 0x40, 0x20,
+	0x38, 0x08, 0x08, 0x08, 0x08,
+	0x08, 0x08, 0x08, 0x08, 0x38,
+	0x2F, 0x10, 0xC8, 0xAC, 0xBA,
+	0x2F, 0x10, 0x28, 0x34, 0xFA,
+	0x00, 0x00, 0x7B, 0x00, 0x00,
+	0x08, 0x14, 0x2A, 0x14, 0x22,
+	0x22, 0x14, 0x2A, 0x14, 0x08,
+	0x55, 0x00, 0x55, 0x00, 0x55, // #176 (25% block) missing in old code
+	0xAA, 0x55, 0xAA, 0x55, 0xAA, // 50% block
+	0xFF, 0x55, 0xFF, 0x55, 0xFF, // 75% block
+	0x00, 0x00, 0x00, 0xFF, 0x00,
+	0x10, 0x10, 0x10, 0xFF, 0x00,
+	0x14, 0x14, 0x14, 0xFF, 0x00,
+	0x10, 0x10, 0xFF, 0x00, 0xFF,
+	0x10, 0x10, 0xF0, 0x10, 0xF0,
+	0x14, 0x14, 0x14, 0xFC, 0x00,
+	0x14, 0x14, 0xF7, 0x00, 0xFF,
+	0x00, 0x00, 0xFF, 0x00, 0xFF,
+	0x14, 0x14, 0xF4, 0x04, 0xFC,
+	0x14, 0x14, 0x17, 0x10, 0x1F,
+	0x10, 0x10, 0x1F, 0x10, 0x1F,
+	0x14, 0x14, 0x14, 0x1F, 0x00,
+	0x10, 0x10, 0x10, 0xF0, 0x00,
+	0x00, 0x00, 0x00, 0x1F, 0x10,
+	0x10, 0x10, 0x10, 0x1F, 0x10,
+	0x10, 0x10, 0x10, 0xF0, 0x10,
+	0x00, 0x00, 0x00, 0xFF, 0x10,
+	0x10, 0x10, 0x10, 0x10, 0x10,
+	0x10, 0x10, 0x10, 0xFF, 0x10,
+	0x00, 0x00, 0x00, 0xFF, 0x14,
+	0x00, 0x00, 0xFF, 0x00, 0xFF,
+	0x00, 0x00, 0x1F, 0x10, 0x17,
+	0x00, 0x00, 0xFC, 0x04, 0xF4,
+	0x14, 0x14, 0x17, 0x10, 0x17,
+	0x14, 0x14, 0xF4, 0x04, 0xF4,
+	0x00, 0x00, 0xFF, 0x00, 0xF7,
+	0x14, 0x14, 0x14, 0x14, 0x14,
+	0x14, 0x14, 0xF7, 0x00, 0xF7,
+	0x14, 0x14, 0x14, 0x17, 0x14,
+	0x10, 0x10, 0x1F, 0x10, 0x1F,
+	0x14, 0x14, 0x14, 0xF4, 0x14,
+	0x10, 0x10, 0xF0, 0x10, 0xF0,
+	0x00, 0x00, 0x1F, 0x10, 0x1F,
+	0x00, 0x00, 0x00, 0x1F, 0x14,
+	0x00, 0x00, 0x00, 0xFC, 0x14,
+	0x00, 0x00, 0xF0, 0x10, 0xF0,
+	0x10, 0x10, 0xFF, 0x10, 0xFF,
+	0x14, 0x14, 0x14, 0xFF, 0x14,
+	0x10, 0x10, 0x10, 0x1F, 0x00,
+	0x00, 0x00, 0x00, 0xF0, 0x10,
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+	0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
+	0xFF, 0xFF, 0xFF, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0xFF, 0xFF,
+	0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
+	0x38, 0x44, 0x44, 0x38, 0x44,
+	0xFC, 0x4A, 0x4A, 0x4A, 0x34, // sharp-s or beta
+	0x7E, 0x02, 0x02, 0x06, 0x06,
+	0x02, 0x7E, 0x02, 0x7E, 0x02,
+	0x63, 0x55, 0x49, 0x41, 0x63,
+	0x38, 0x44, 0x44, 0x3C, 0x04,
+	0x40, 0x7E, 0x20, 0x1E, 0x20,
+	0x06, 0x02, 0x7E, 0x02, 0x02,
+	0x99, 0xA5, 0xE7, 0xA5, 0x99,
+	0x1C, 0x2A, 0x49, 0x2A, 0x1C,
+	0x4C, 0x72, 0x01, 0x72, 0x4C,
+	0x30, 0x4A, 0x4D, 0x4D, 0x30,
+	0x30, 0x48, 0x78, 0x48, 0x30,
+	0xBC, 0x62, 0x5A, 0x46, 0x3D,
+	0x3E, 0x49, 0x49, 0x49, 0x00,
+	0x7E, 0x01, 0x01, 0x01, 0x7E,
+	0x2A, 0x2A, 0x2A, 0x2A, 0x2A,
+	0x44, 0x44, 0x5F, 0x44, 0x44,
+	0x40, 0x51, 0x4A, 0x44, 0x40,
+	0x40, 0x44, 0x4A, 0x51, 0x40,
+	0x00, 0x00, 0xFF, 0x01, 0x03,
+	0xE0, 0x80, 0xFF, 0x00, 0x00,
+	0x08, 0x08, 0x6B, 0x6B, 0x08,
+	0x36, 0x12, 0x36, 0x24, 0x36,
+	0x06, 0x0F, 0x09, 0x0F, 0x06,
+	0x00, 0x00, 0x18, 0x18, 0x00,
+	0x00, 0x00, 0x10, 0x10, 0x00,
+	0x30, 0x40, 0xFF, 0x01, 0x01,
+	0x00, 0x1F, 0x01, 0x01, 0x1E,
+	0x00, 0x19, 0x1D, 0x17, 0x12,
+	0x00, 0x3C, 0x3C, 0x3C, 0x3C,
+	0x00, 0x00, 0x00, 0x00, 0x00  // #255 NBSP
+};
diff --git a/generic/image.c b/generic/image.c
new file mode 100755
index 0000000..f30fcad
--- /dev/null
+++ b/generic/image.c
@@ -0,0 +1,2296 @@
+#ifndef TH_GENERIC_FILE
+#define TH_GENERIC_FILE "generic/image.c"
+#else
+
+#undef MAX
+#define MAX(a,b) ( ((a)>(b)) ? (a) : (b) )
+
+#undef MIN
+#define MIN(a,b) ( ((a)<(b)) ? (a) : (b) )
+
+#undef TAPI
+#define TAPI __declspec(dllimport)
+
+#ifndef M_PI
+#define M_PI    3.14159265358979323846
+#endif
+
+#undef temp_t
+#if defined(TH_REAL_IS_FLOAT) || defined(TH_REAL_IS_DOUBLE)
+#define temp_t real
+#else
+#define temp_t float
+#endif
+
+
+static inline real image_(FromIntermediate)(temp_t x) {
+#ifdef TH_REAL_IS_BYTE
+  x += 0.5;
+  if( x <= 0 ) return 0;
+  if( x >= 255 ) return 255;
+#endif
+  return x;
+}
+
+
+static void image_(Main_op_validate)( lua_State *L,  THTensor *Tsrc, THTensor *Tdst){
+
+  long src_depth = 1;
+  long dst_depth = 1;
+
+  luaL_argcheck(L, Tsrc->nDimension==2 || Tsrc->nDimension==3, 1, "rotate: src not 2 or 3 dimensional");
+  luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "rotate: dst not 2 or 3 dimensional");
+
+  if(Tdst->nDimension == 3) dst_depth =  Tdst->size[0];
+  if(Tsrc->nDimension == 3) src_depth =  Tsrc->size[0];
+
+  if( (Tdst->nDimension==3 && ( src_depth!=dst_depth)) ||
+      (Tdst->nDimension!=Tsrc->nDimension) )
+    luaL_error(L, "image.scale: src and dst depths do not match");
+
+  if( Tdst->nDimension==3 && ( src_depth!=dst_depth) )
+    luaL_error(L, "image.scale: src and dst depths do not match");
+}
+
+static long image_(Main_op_stride)( THTensor *T,int i){
+  if (T->nDimension == 2) {
+    if (i == 0) return 0;
+    else return T->stride[i-1];
+  }
+  return T->stride[i];
+}
+
+static long image_(Main_op_depth)( THTensor *T){
+  if(T->nDimension == 3) return T->size[0]; /* rgb or rgba */
+  return 1; /* greyscale */
+}
+
+static void image_(Main_scaleLinear_rowcol)(THTensor *Tsrc,
+                                            THTensor *Tdst,
+                                            long src_start,
+                                            long dst_start,
+                                            long src_stride,
+                                            long dst_stride,
+                                            long src_len,
+                                            long dst_len ) {
+
+  real *src= THTensor_(data)(Tsrc);
+  real *dst= THTensor_(data)(Tdst);
+
+  if ( dst_len > src_len ){
+    long di;
+    float si_f;
+    long si_i;
+    float scale = (float)(src_len - 1) / (dst_len - 1);
+
+    if ( src_len == 1 ) {
+      for( di = 0; di < dst_len - 1; di++ ) {
+        long dst_pos = dst_start + di*dst_stride;
+        dst[dst_pos] = src[ src_start ];
+      }
+    } else {
+      for( di = 0; di < dst_len - 1; di++ ) {
+        long dst_pos = dst_start + di*dst_stride;
+        si_f = di * scale; si_i = (long)si_f; si_f -= si_i;
+
+        dst[dst_pos] = image_(FromIntermediate)(
+            (1 - si_f) * src[ src_start + si_i * src_stride ] +
+            si_f * src[ src_start + (si_i + 1) * src_stride ]);
+      }
+    }
+
+    dst[ dst_start + (dst_len - 1) * dst_stride ] =
+      src[ src_start + (src_len - 1) * src_stride ];
+  }
+  else if ( dst_len < src_len ) {
+    long di;
+    long si0_i = 0; float si0_f = 0;
+    long si1_i; float si1_f;
+    long si;
+    float scale = (float)src_len / dst_len;
+    float acc, n;
+
+    for( di = 0; di < dst_len; di++ )
+      {
+        si1_f = (di + 1) * scale; si1_i = (long)si1_f; si1_f -= si1_i;
+        acc = (1 - si0_f) * src[ src_start + si0_i * src_stride ];
+        n = 1 - si0_f;
+        for( si = si0_i + 1; si < si1_i; si++ )
+          {
+            acc += src[ src_start + si * src_stride ];
+            n += 1;
+          }
+        if( si1_i < src_len )
+          {
+            acc += si1_f * src[ src_start + si1_i*src_stride ];
+            n += si1_f;
+          }
+        dst[ dst_start + di*dst_stride ] = image_(FromIntermediate)(acc / n);
+        si0_i = si1_i; si0_f = si1_f;
+      }
+  }
+  else {
+    long i;
+    for( i = 0; i < dst_len; i++ )
+      dst[ dst_start + i*dst_stride ] = src[ src_start + i*src_stride ];
+  }
+}
+
+
+static inline temp_t image_(Main_cubicInterpolate)(temp_t p0,
+                                                   temp_t p1,
+                                                   temp_t p2,
+                                                   temp_t p3,
+                                                   temp_t x) {
+  temp_t a0 = p1;
+  temp_t a1 = p2 - p0;
+  temp_t a2 = 2 * p0 - 5 * p1 + 4 * p2 - p3;
+  temp_t a3 = 3 * (p1 - p2) + p3 - p0;
+  return a0 + 0.5 * x * (a1 + x * (a2 + x * a3));
+}
+
+
+static void image_(Main_scaleCubic_rowcol)(THTensor *Tsrc,
+                                           THTensor *Tdst,
+                                           long src_start,
+                                           long dst_start,
+                                           long src_stride,
+                                           long dst_stride,
+                                           long src_len,
+                                           long dst_len ) {
+
+  real *src= THTensor_(data)(Tsrc);
+  real *dst= THTensor_(data)(Tdst);
+
+  if ( dst_len == src_len ){
+    long i;
+    for( i = 0; i < dst_len; i++ )
+      dst[ dst_start + i*dst_stride ] = src[ src_start + i*src_stride ];
+  } else if ( src_len == 1 ) {
+     long i;
+     for( i = 0; i < dst_len - 1; i++ ) {
+       long dst_pos = dst_start + i*dst_stride;
+       dst[dst_pos] = src[ src_start ];
+     }
+  } else {
+    long di;
+    float si_f;
+    long si_i;
+    float scale;
+    if (dst_len == 1)
+      scale = (float)(src_len - 1);
+    else
+      scale = (float)(src_len - 1) / (dst_len - 1);
+
+    for( di = 0; di < dst_len - 1; di++ ) {
+      long dst_pos = dst_start + di*dst_stride;
+      si_f = di * scale; si_i = (long)si_f; si_f -= si_i;
+
+      temp_t p0;
+      temp_t p1 = src[ src_start + si_i * src_stride ];
+      temp_t p2 = src[ src_start + (si_i + 1) * src_stride ];
+      temp_t p3;
+      if (si_i > 0) {
+        p0 = src[ src_start + (si_i - 1) * src_stride ];
+      } else {
+        p0 = 2 * p1 - p2;
+      }
+      if (si_i + 2 < src_len) {
+        p3 = src[ src_start + (si_i + 2) * src_stride ];
+      } else {
+        p3 = 2 * p2 - p1;
+      }
+
+      temp_t value = image_(Main_cubicInterpolate)(p0, p1, p2, p3, si_f);
+      dst[dst_pos] = image_(FromIntermediate)(value);
+    }
+
+    dst[ dst_start + (dst_len - 1) * dst_stride ] =
+      src[ src_start + (src_len - 1) * src_stride ];
+  }
+}
+
+static int image_(Main_scaleBilinear)(lua_State *L) {
+
+  THTensor *Tsrc = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *Tdst = luaT_checkudata(L, 2, torch_Tensor);
+  THTensor *Ttmp;
+  long dst_stride0, dst_stride1, dst_stride2, dst_width, dst_height;
+  long src_stride0, src_stride1, src_stride2, src_width, src_height;
+  long tmp_stride0, tmp_stride1, tmp_stride2, tmp_width, tmp_height;
+  long i, j, k;
+
+  image_(Main_op_validate)(L, Tsrc,Tdst);
+
+  int ndims;
+  if (Tdst->nDimension == 3) ndims = 3;
+  else ndims = 2;
+
+  Ttmp = THTensor_(newWithSize2d)(Tsrc->size[ndims-2], Tdst->size[ndims-1]);
+
+  dst_stride0= image_(Main_op_stride)(Tdst,0);
+  dst_stride1= image_(Main_op_stride)(Tdst,1);
+  dst_stride2= image_(Main_op_stride)(Tdst,2);
+  src_stride0= image_(Main_op_stride)(Tsrc,0);
+  src_stride1= image_(Main_op_stride)(Tsrc,1);
+  src_stride2= image_(Main_op_stride)(Tsrc,2);
+  tmp_stride0= image_(Main_op_stride)(Ttmp,0);
+  tmp_stride1= image_(Main_op_stride)(Ttmp,1);
+  tmp_stride2= image_(Main_op_stride)(Ttmp,2);
+  dst_width=   Tdst->size[ndims-1];
+  dst_height=  Tdst->size[ndims-2];
+  src_width=   Tsrc->size[ndims-1];
+  src_height=  Tsrc->size[ndims-2];
+  tmp_width=   Ttmp->size[1];
+  tmp_height=  Ttmp->size[0];
+
+  for(k=0;k<image_(Main_op_depth)(Tsrc);k++) {
+    /* compress/expand rows first */
+    for(j = 0; j < src_height; j++) {
+      image_(Main_scaleLinear_rowcol)(Tsrc,
+                                      Ttmp,
+                                      0*src_stride2+j*src_stride1+k*src_stride0,
+                                      0*tmp_stride2+j*tmp_stride1+k*tmp_stride0,
+                                      src_stride2,
+                                      tmp_stride2,
+                                      src_width,
+                                      tmp_width );
+
+    }
+
+    /* then columns */
+    for(i = 0; i < dst_width; i++) {
+      image_(Main_scaleLinear_rowcol)(Ttmp,
+                                      Tdst,
+                                      i*tmp_stride2+0*tmp_stride1+k*tmp_stride0,
+                                      i*dst_stride2+0*dst_stride1+k*dst_stride0,
+                                      tmp_stride1,
+                                      dst_stride1,
+                                      tmp_height,
+                                      dst_height );
+    }
+  }
+  THTensor_(free)(Ttmp);
+  return 0;
+}
+
+static int image_(Main_scaleBicubic)(lua_State *L) {
+
+  THTensor *Tsrc = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *Tdst = luaT_checkudata(L, 2, torch_Tensor);
+  THTensor *Ttmp;
+  long dst_stride0, dst_stride1, dst_stride2, dst_width, dst_height;
+  long src_stride0, src_stride1, src_stride2, src_width, src_height;
+  long tmp_stride0, tmp_stride1, tmp_stride2, tmp_width, tmp_height;
+  long i, j, k;
+
+  image_(Main_op_validate)(L, Tsrc,Tdst);
+
+  int ndims;
+  if (Tdst->nDimension == 3) ndims = 3;
+  else ndims = 2;
+
+  Ttmp = THTensor_(newWithSize2d)(Tsrc->size[ndims-2], Tdst->size[ndims-1]);
+
+  dst_stride0= image_(Main_op_stride)(Tdst,0);
+  dst_stride1= image_(Main_op_stride)(Tdst,1);
+  dst_stride2= image_(Main_op_stride)(Tdst,2);
+  src_stride0= image_(Main_op_stride)(Tsrc,0);
+  src_stride1= image_(Main_op_stride)(Tsrc,1);
+  src_stride2= image_(Main_op_stride)(Tsrc,2);
+  tmp_stride0= image_(Main_op_stride)(Ttmp,0);
+  tmp_stride1= image_(Main_op_stride)(Ttmp,1);
+  tmp_stride2= image_(Main_op_stride)(Ttmp,2);
+  dst_width=   Tdst->size[ndims-1];
+  dst_height=  Tdst->size[ndims-2];
+  src_width=   Tsrc->size[ndims-1];
+  src_height=  Tsrc->size[ndims-2];
+  tmp_width=   Ttmp->size[1];
+  tmp_height=  Ttmp->size[0];
+
+  for(k=0;k<image_(Main_op_depth)(Tsrc);k++) {
+    /* compress/expand rows first */
+    for(j = 0; j < src_height; j++) {
+      image_(Main_scaleCubic_rowcol)(Tsrc,
+                                     Ttmp,
+                                     0*src_stride2+j*src_stride1+k*src_stride0,
+                                     0*tmp_stride2+j*tmp_stride1+k*tmp_stride0,
+                                     src_stride2,
+                                     tmp_stride2,
+                                     src_width,
+                                     tmp_width );
+    }
+
+    /* then columns */
+    for(i = 0; i < dst_width; i++) {
+      image_(Main_scaleCubic_rowcol)(Ttmp,
+                                     Tdst,
+                                     i*tmp_stride2+0*tmp_stride1+k*tmp_stride0,
+                                     i*dst_stride2+0*dst_stride1+k*dst_stride0,
+                                     tmp_stride1,
+                                     dst_stride1,
+                                     tmp_height,
+                                     dst_height );
+    }
+  }
+  THTensor_(free)(Ttmp);
+  return 0;
+}
+
+static int image_(Main_scaleSimple)(lua_State *L)
+{
+  THTensor *Tsrc = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *Tdst = luaT_checkudata(L, 2, torch_Tensor);
+  real *src, *dst;
+  long dst_stride0, dst_stride1, dst_stride2, dst_width, dst_height, dst_depth;
+  long src_stride0, src_stride1, src_stride2, src_width, src_height, src_depth;
+  long i, j, k;
+  float scx, scy;
+
+  luaL_argcheck(L, Tsrc->nDimension==2 || Tsrc->nDimension==3, 1, "image.scale: src not 2 or 3 dimensional");
+  luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "image.scale: dst not 2 or 3 dimensional");
+
+  src= THTensor_(data)(Tsrc);
+  dst= THTensor_(data)(Tdst);
+
+  dst_stride0 = 0;
+  dst_stride1 = Tdst->stride[Tdst->nDimension-2];
+  dst_stride2 = Tdst->stride[Tdst->nDimension-1];
+  dst_depth =  0;
+  dst_height = Tdst->size[Tdst->nDimension-2];
+  dst_width = Tdst->size[Tdst->nDimension-1];
+  if(Tdst->nDimension == 3) {
+    dst_stride0 = Tdst->stride[0];
+    dst_depth = Tdst->size[0];
+  }
+
+  src_stride0 = 0;
+  src_stride1 = Tsrc->stride[Tsrc->nDimension-2];
+  src_stride2 = Tsrc->stride[Tsrc->nDimension-1];
+  src_depth =  0;
+  src_height = Tsrc->size[Tsrc->nDimension-2];
+  src_width = Tsrc->size[Tsrc->nDimension-1];
+  if(Tsrc->nDimension == 3) {
+    src_stride0 = Tsrc->stride[0];
+    src_depth = Tsrc->size[0];
+  }
+
+  if( (Tdst->nDimension==3 && ( src_depth!=dst_depth)) ||
+      (Tdst->nDimension!=Tsrc->nDimension) ) {
+    printf("image.scale:%d,%d,%ld,%ld\n",Tsrc->nDimension,Tdst->nDimension,src_depth,dst_depth);
+    luaL_error(L, "image.scale: src and dst depths do not match");
+  }
+
+  if( Tdst->nDimension==3 && ( src_depth!=dst_depth) )
+    luaL_error(L, "image.scale: src and dst depths do not match");
+
+  /* printf("%d,%d -> %d,%d\n",src_width,src_height,dst_width,dst_height); */
+  scx=((float)src_width)/((float)dst_width);
+  scy=((float)src_height)/((float)dst_height);
+
+#pragma omp parallel for private(j, i, k)
+  for(j = 0; j < dst_height; j++) {
+    for(i = 0; i < dst_width; i++) {
+      float val = 0.0;
+      long ii=(long) (((float)i)*scx);
+      long jj=(long) (((float)j)*scy);
+      if(ii>src_width-1) ii=src_width-1;
+      if(jj>src_height-1) jj=src_height-1;
+
+      if(Tsrc->nDimension==2)
+        {
+          val=src[ii*src_stride2+jj*src_stride1];
+          dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val);
+        }
+      else
+        {
+          for(k=0;k<src_depth;k++)
+            {
+              val=src[ii*src_stride2+jj*src_stride1+k*src_stride0];
+              dst[i*dst_stride2+j*dst_stride1+k*dst_stride0] = image_(FromIntermediate)(val);
+            }
+        }
+    }
+  }
+  return 0;
+}
+
+static int image_(Main_rotate)(lua_State *L)
+{
+  THTensor *Tsrc = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *Tdst = luaT_checkudata(L, 2, torch_Tensor);
+  float theta = luaL_checknumber(L, 3);
+  float cos_theta, sin_theta;
+  real *src, *dst;
+  long dst_stride0, dst_stride1, dst_stride2, dst_width, dst_height, dst_depth;
+  long src_stride0, src_stride1, src_stride2, src_width, src_height, src_depth;
+  long i, j, k;
+  float xc, yc;
+  float id,jd;
+  long ii,jj;
+
+  luaL_argcheck(L, Tsrc->nDimension==2 || Tsrc->nDimension==3, 1, "rotate: src not 2 or 3 dimensional");
+  luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "rotate: dst not 2 or 3 dimensional");
+
+  src= THTensor_(data)(Tsrc);
+  dst= THTensor_(data)(Tdst);
+
+  if (dst == src) {
+    luaL_error(L, "image.rotate: in-place rotate not supported");
+  }
+
+  dst_stride0 = 0;
+  dst_stride1 = Tdst->stride[Tdst->nDimension-2];
+  dst_stride2 = Tdst->stride[Tdst->nDimension-1];
+  dst_depth =  0;
+  dst_height = Tdst->size[Tdst->nDimension-2];
+  dst_width = Tdst->size[Tdst->nDimension-1];
+  if(Tdst->nDimension == 3) {
+    dst_stride0 = Tdst->stride[0];
+    dst_depth = Tdst->size[0];
+  }
+
+  src_stride0 = 0;
+  src_stride1 = Tsrc->stride[Tsrc->nDimension-2];
+  src_stride2 = Tsrc->stride[Tsrc->nDimension-1];
+  src_depth =  0;
+  src_height = Tsrc->size[Tsrc->nDimension-2];
+  src_width = Tsrc->size[Tsrc->nDimension-1];
+  if(Tsrc->nDimension == 3) {
+    src_stride0 = Tsrc->stride[0];
+    src_depth = Tsrc->size[0];
+  }
+
+  if( Tsrc->nDimension==3 && Tdst->nDimension==3 && ( src_depth!=dst_depth) )
+    luaL_error(L, "image.rotate: src and dst depths do not match");
+
+  if( (Tsrc->nDimension!=Tdst->nDimension) )
+    luaL_error(L, "image.rotate: src and dst depths do not match");
+
+  xc = (src_width-1)/2.0;
+  yc = (src_height-1)/2.0;
+
+  sin_theta = sin(theta);
+  cos_theta = cos(theta);
+
+  for(j = 0; j < dst_height; j++) {
+    jd=j;
+    for(i = 0; i < dst_width; i++) {
+      float val = -1;
+      id= i;
+
+      ii = (long) round(cos_theta*(id-xc) - sin_theta*(jd-yc) + xc);
+      jj = (long) round(cos_theta*(jd-yc) + sin_theta*(id-xc) + yc);
+
+      /* rotated corners are blank */
+      if(ii>src_width-1) val=0;
+      if(jj>src_height-1) val=0;
+      if(ii<0) val=0;
+      if(jj<0) val=0;
+
+      if(Tsrc->nDimension==2)
+        {
+          if(val==-1)
+            val=src[ii*src_stride2+jj*src_stride1];
+          dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val);
+        }
+      else
+        {
+          int do_copy=0; if(val==-1) do_copy=1;
+          for(k=0;k<src_depth;k++)
+            {
+              if(do_copy)
+                val=src[ii*src_stride2+jj*src_stride1+k*src_stride0];
+              dst[i*dst_stride2+j*dst_stride1+k*dst_stride0] = image_(FromIntermediate)(val);
+            }
+        }
+    }
+  }
+  return 0;
+}
+static int image_(Main_rotateBilinear)(lua_State *L)
+{
+  THTensor *Tsrc = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *Tdst = luaT_checkudata(L, 2, torch_Tensor);
+  float theta = luaL_checknumber(L, 3);
+  real *src, *dst;
+  long dst_stride0, dst_stride1, dst_stride2, dst_width, dst_height, dst_depth;
+  long src_stride0, src_stride1, src_stride2, src_width, src_height, src_depth;
+  long i, j, k;
+  float xc, yc;
+  float id,jd;
+  long ii_0, ii_1, jj_0, jj_1;
+
+  luaL_argcheck(L, Tsrc->nDimension==2 || Tsrc->nDimension==3, 1, "rotate: src not 2 or 3 dimensional");
+  luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "rotate: dst not 2 or 3 dimensional");
+
+  src= THTensor_(data)(Tsrc);
+  dst= THTensor_(data)(Tdst);
+
+  if (dst == src) {
+    luaL_error(L, "image.rotate: in-place rotate not supported");
+  }
+
+  dst_stride0 = 0;
+  dst_stride1 = Tdst->stride[Tdst->nDimension-2];
+  dst_stride2 = Tdst->stride[Tdst->nDimension-1];
+  dst_depth =  0;
+  dst_height = Tdst->size[Tdst->nDimension-2];
+  dst_width = Tdst->size[Tdst->nDimension-1];
+  if(Tdst->nDimension == 3) {
+    dst_stride0 = Tdst->stride[0];
+    dst_depth = Tdst->size[0];
+  }
+
+  src_stride0 = 0;
+  src_stride1 = Tsrc->stride[Tsrc->nDimension-2];
+  src_stride2 = Tsrc->stride[Tsrc->nDimension-1];
+  src_depth =  0;
+  src_height = Tsrc->size[Tsrc->nDimension-2];
+  src_width = Tsrc->size[Tsrc->nDimension-1];
+  if(Tsrc->nDimension == 3) {
+    src_stride0 = Tsrc->stride[0];
+    src_depth = Tsrc->size[0];
+  }
+
+  if( Tsrc->nDimension==3 && Tdst->nDimension==3 && ( src_depth!=dst_depth) )
+    luaL_error(L, "image.rotate: src and dst depths do not match");
+
+  if( (Tsrc->nDimension!=Tdst->nDimension) )
+    luaL_error(L, "image.rotate: src and dst depths do not match");
+
+  xc = (src_width-1)/2.0;
+  yc = (src_height-1)/2.0;
+
+  for(j = 0; j < dst_height; j++) {
+    jd=j;
+    for(i = 0; i < dst_width; i++) {
+      float val = -1;
+      temp_t ri, rj, wi, wj;
+      id= i;
+      ri = cos(theta)*(id-xc)-sin(theta)*(jd-yc);
+      rj = cos(theta)*(jd-yc)+sin(theta)*(id-xc);
+
+      ii_0 = (long)floor(ri+xc);
+      ii_1 = ii_0 + 1;
+      jj_0 = (long)floor(rj+yc);
+      jj_1 = jj_0 + 1;
+      wi = ri+xc-ii_0;
+      wj = rj+yc-jj_0;
+
+      /* default to the closest value when interpolating on image boundaries (either image pixel or 0) */
+      if(ii_1==src_width && wi<0.5) ii_1 = ii_0;
+      else if(ii_1>=src_width) val=0;
+      if(jj_1==src_height && wj<0.5) jj_1 = jj_0;
+      else if(jj_1>=src_height) val=0;
+      if(ii_0==-1 && wi>0.5) ii_0 = ii_1;
+      else if(ii_0<0) val=0;
+      if(jj_0==-1 && wj>0.5) jj_0 = jj_1;
+      else if(jj_0<0) val=0;
+
+      if(Tsrc->nDimension==2) {
+        if(val==-1)
+          val = (1.0 - wi) * (1.0 - wj) * src[ii_0*src_stride2+jj_0*src_stride1]
+            + wi * (1.0 - wj) * src[ii_1*src_stride2+jj_0*src_stride1]
+            + (1.0 - wi) * wj * src[ii_0*src_stride2+jj_1*src_stride1]
+            + wi * wj * src[ii_1*src_stride2+jj_1*src_stride1];
+        dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val);
+      } else {
+        int do_copy=0; if(val==-1) do_copy=1;
+        for(k=0;k<src_depth;k++) {
+          if(do_copy) {
+            val = (1.0 - wi) * (1.0 - wj) * src[ii_0*src_stride2+jj_0*src_stride1+k*src_stride0]
+              + wi * (1.0 - wj) * src[ii_1*src_stride2+jj_0*src_stride1+k*src_stride0]
+              + (1.0 - wi) * wj * src[ii_0*src_stride2+jj_1*src_stride1+k*src_stride0]
+              + wi * wj * src[ii_1*src_stride2+jj_1*src_stride1+k*src_stride0];
+          }
+          dst[i*dst_stride2+j*dst_stride1+k*dst_stride0] = image_(FromIntermediate)(val);
+        }
+      }
+    }
+  }
+  return 0;
+}
+
+static int image_(Main_polar)(lua_State *L)
+{
+    THTensor *Tsrc = luaT_checkudata(L, 1, torch_Tensor);
+    THTensor *Tdst = luaT_checkudata(L, 2, torch_Tensor);
+    float doFull = luaL_checknumber(L, 3);
+    real *src, *dst;
+    long dst_stride0, dst_stride1, dst_stride2, dst_width, dst_height, dst_depth;
+    long src_stride0, src_stride1, src_stride2, src_width, src_height, src_depth;
+    long i, j, k;
+    float id, jd, a, r, m, midY, midX;
+    long ii,jj;
+
+    luaL_argcheck(L, Tsrc->nDimension==2 || Tsrc->nDimension==3, 1, "polar: src not 2 or 3 dimensional");
+    luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "polar: dst not 2 or 3 dimensional");
+
+    src= THTensor_(data)(Tsrc);
+    dst= THTensor_(data)(Tdst);
+
+    dst_stride0 = 0;
+    dst_stride1 = Tdst->stride[Tdst->nDimension-2];
+    dst_stride2 = Tdst->stride[Tdst->nDimension-1];
+    dst_depth =  0;
+    dst_height = Tdst->size[Tdst->nDimension-2];
+    dst_width = Tdst->size[Tdst->nDimension-1];
+    if(Tdst->nDimension == 3) {
+        dst_stride0 = Tdst->stride[0];
+        dst_depth = Tdst->size[0];
+    }
+
+    src_stride0 = 0;
+    src_stride1 = Tsrc->stride[Tsrc->nDimension-2];
+    src_stride2 = Tsrc->stride[Tsrc->nDimension-1];
+    src_depth =  0;
+    src_height = Tsrc->size[Tsrc->nDimension-2];
+    src_width = Tsrc->size[Tsrc->nDimension-1];
+    if(Tsrc->nDimension == 3) {
+        src_stride0 = Tsrc->stride[0];
+        src_depth = Tsrc->size[0];
+    }
+
+    if( Tsrc->nDimension==3 && Tdst->nDimension==3 && ( src_depth!=dst_depth) ) {
+        luaL_error(L, "image.polar: src and dst depths do not match"); }
+
+    if( (Tsrc->nDimension!=Tdst->nDimension) ) {
+        luaL_error(L, "image.polar: src and dst depths do not match"); }
+
+    // compute maximum distance
+    midY = (float) src_height / 2.0;
+    midX = (float) src_width  / 2.0;
+    if(doFull == 1) {
+      m = sqrt((float) src_width * (float) src_width + (float) src_height * (float) src_height) / 2.0;
+    }
+    else {
+      m = (src_width < src_height) ? midX : midY;
+    }
+
+    // loop to fill polar image
+    for(j = 0; j < dst_height; j++) {               // orientation loop
+        jd = (float) j;
+        a = (2 * M_PI * jd) / (float) dst_height;   // current angle
+        for(i = 0; i < dst_width; i++) {            // radius loop
+            float val = -1;
+            id = (float) i;
+            r = (m * id) / (float) dst_width;       // current distance
+
+            jj = (long) floor( r * cos(a) + midY);  // y-location in source image
+            ii = (long) floor(-r * sin(a) + midX);  // x-location in source image
+
+            if(ii>src_width-1) val=0;
+            if(jj>src_height-1) val=0;
+            if(ii<0) val=0;
+            if(jj<0) val=0;
+
+            if(Tsrc->nDimension==2)
+            {
+                if(val==-1)
+                    val=src[ii*src_stride2+jj*src_stride1];
+                dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val);
+            }
+            else
+            {
+                int do_copy=0; if(val==-1) do_copy=1;
+                for(k=0;k<src_depth;k++)
+                {
+                    if(do_copy)
+                        val=src[ii*src_stride2+jj*src_stride1+k*src_stride0];
+                    dst[i*dst_stride2+j*dst_stride1+k*dst_stride0] = image_(FromIntermediate)(val);
+                }
+            }
+        }
+    }
+    return 0;
+}
+static int image_(Main_polarBilinear)(lua_State *L)
+{
+    THTensor *Tsrc = luaT_checkudata(L, 1, torch_Tensor);
+    THTensor *Tdst = luaT_checkudata(L, 2, torch_Tensor);
+    float doFull = luaL_checknumber(L, 3);
+    real *src, *dst;
+    long dst_stride0, dst_stride1, dst_stride2, dst_width, dst_height, dst_depth;
+    long src_stride0, src_stride1, src_stride2, src_width, src_height, src_depth;
+    long i, j, k;
+    float id, jd, a, r, m, midY, midX;
+    long ii_0, ii_1, jj_0, jj_1;
+
+    luaL_argcheck(L, Tsrc->nDimension==2 || Tsrc->nDimension==3, 1, "polar: src not 2 or 3 dimensional");
+    luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "polar: dst not 2 or 3 dimensional");
+
+    src= THTensor_(data)(Tsrc);
+    dst= THTensor_(data)(Tdst);
+
+    dst_stride0 = 0;
+    dst_stride1 = Tdst->stride[Tdst->nDimension-2];
+    dst_stride2 = Tdst->stride[Tdst->nDimension-1];
+    dst_depth =  0;
+    dst_height = Tdst->size[Tdst->nDimension-2];
+    dst_width = Tdst->size[Tdst->nDimension-1];
+    if(Tdst->nDimension == 3) {
+        dst_stride0 = Tdst->stride[0];
+        dst_depth = Tdst->size[0];
+    }
+
+    src_stride0 = 0;
+    src_stride1 = Tsrc->stride[Tsrc->nDimension-2];
+    src_stride2 = Tsrc->stride[Tsrc->nDimension-1];
+    src_depth =  0;
+    src_height = Tsrc->size[Tsrc->nDimension-2];
+    src_width = Tsrc->size[Tsrc->nDimension-1];
+    if(Tsrc->nDimension == 3) {
+        src_stride0 = Tsrc->stride[0];
+        src_depth = Tsrc->size[0];
+    }
+
+    if( Tsrc->nDimension==3 && Tdst->nDimension==3 && ( src_depth!=dst_depth) ) {
+        luaL_error(L, "image.polar: src and dst depths do not match"); }
+
+    if( (Tsrc->nDimension!=Tdst->nDimension) ) {
+        luaL_error(L, "image.polar: src and dst depths do not match"); }
+
+    // compute maximum distance
+    midY = (float) src_height / 2.0;
+    midX = (float) src_width  / 2.0;
+    if(doFull == 1) {
+      m = sqrt((float) src_width * (float) src_width + (float) src_height * (float) src_height) / 2.0;
+    }
+    else {
+      m = (src_width < src_height) ? midX : midY;
+    }
+
+    // loop to fill polar image
+    for(j = 0; j < dst_height; j++) {                 // orientation loop
+        jd = (float) j;
+        a = (2 * M_PI * jd) / (float) dst_height;     // current angle
+        for(i = 0; i < dst_width; i++) {              // radius loop
+            float val = -1;
+            temp_t ri, rj, wi, wj;
+            id = (float) i;
+            r = (m * id) / (float) dst_width;         // current distance
+
+            rj =  r * cos(a) + midY;                  // y-location in source image
+            ri = -r * sin(a) + midX;                  // x-location in source image
+
+            ii_0=(long)floor(ri);
+            ii_1=ii_0 + 1;
+            jj_0=(long)floor(rj);
+            jj_1=jj_0 + 1;
+            wi = ri - ii_0;
+            wj = rj - jj_0;
+
+            // switch to nearest interpolation when bilinear is impossible
+            if(ii_1>src_width-1 || jj_1>src_height-1 || ii_0<0 || jj_0<0) {
+                if(ii_0>src_width-1) val=0;
+                if(jj_0>src_height-1) val=0;
+                if(ii_0<0) val=0;
+                if(jj_0<0) val=0;
+
+                if(Tsrc->nDimension==2)
+                {
+                    if(val==-1)
+                        val=src[ii_0*src_stride2+jj_0*src_stride1];
+                    dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val);
+                }
+                else
+                {
+                    int do_copy=0; if(val==-1) do_copy=1;
+                    for(k=0;k<src_depth;k++)
+                    {
+                        if(do_copy)
+                            val=src[ii_0*src_stride2+jj_0*src_stride1+k*src_stride0];
+                        dst[i*dst_stride2+j*dst_stride1+k*dst_stride0] = image_(FromIntermediate)(val);
+                    }
+                }
+            }
+
+            // bilinear interpolation
+            else {
+                if(Tsrc->nDimension==2) {
+                    if(val==-1)
+                        val = (1.0 - wi) * (1.0 - wj) * src[ii_0*src_stride2+jj_0*src_stride1]
+                        + wi * (1.0 - wj) * src[ii_1*src_stride2+jj_0*src_stride1]
+                        + (1.0 - wi) * wj * src[ii_0*src_stride2+jj_1*src_stride1]
+                        + wi * wj * src[ii_1*src_stride2+jj_1*src_stride1];
+                    dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val);
+                } else {
+                    int do_copy=0; if(val==-1) do_copy=1;
+                    for(k=0;k<src_depth;k++) {
+                        if(do_copy) {
+                            val = (1.0 - wi) * (1.0 - wj) * src[ii_0*src_stride2+jj_0*src_stride1+k*src_stride0]
+                            + wi * (1.0 - wj) * src[ii_1*src_stride2+jj_0*src_stride1+k*src_stride0]
+                            + (1.0 - wi) * wj * src[ii_0*src_stride2+jj_1*src_stride1+k*src_stride0]
+                            + wi * wj * src[ii_1*src_stride2+jj_1*src_stride1+k*src_stride0];
+                        }
+                        dst[i*dst_stride2+j*dst_stride1+k*dst_stride0] = image_(FromIntermediate)(val);
+                    }
+                }
+            }
+        }
+    }
+    return 0;
+}
+
+static int image_(Main_logPolar)(lua_State *L)
+{
+    THTensor *Tsrc = luaT_checkudata(L, 1, torch_Tensor);
+    THTensor *Tdst = luaT_checkudata(L, 2, torch_Tensor);
+    float doFull = luaL_checknumber(L, 3);
+    real *src, *dst;
+    long dst_stride0, dst_stride1, dst_stride2, dst_width, dst_height, dst_depth;
+    long src_stride0, src_stride1, src_stride2, src_width, src_height, src_depth;
+    long i, j, k;
+    float id, jd, a, r, m, midY, midX, fw;
+    long ii,jj;
+
+    luaL_argcheck(L, Tsrc->nDimension==2 || Tsrc->nDimension==3, 1, "polar: src not 2 or 3 dimensional");
+    luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "polar: dst not 2 or 3 dimensional");
+
+    src= THTensor_(data)(Tsrc);
+    dst= THTensor_(data)(Tdst);
+
+    dst_stride0 = 0;
+    dst_stride1 = Tdst->stride[Tdst->nDimension-2];
+    dst_stride2 = Tdst->stride[Tdst->nDimension-1];
+    dst_depth =  0;
+    dst_height = Tdst->size[Tdst->nDimension-2];
+    dst_width = Tdst->size[Tdst->nDimension-1];
+    if(Tdst->nDimension == 3) {
+        dst_stride0 = Tdst->stride[0];
+        dst_depth = Tdst->size[0];
+    }
+
+    src_stride0 = 0;
+    src_stride1 = Tsrc->stride[Tsrc->nDimension-2];
+    src_stride2 = Tsrc->stride[Tsrc->nDimension-1];
+    src_depth =  0;
+    src_height = Tsrc->size[Tsrc->nDimension-2];
+    src_width = Tsrc->size[Tsrc->nDimension-1];
+    if(Tsrc->nDimension == 3) {
+        src_stride0 = Tsrc->stride[0];
+        src_depth = Tsrc->size[0];
+    }
+
+    if( Tsrc->nDimension==3 && Tdst->nDimension==3 && ( src_depth!=dst_depth) ) {
+        luaL_error(L, "image.polar: src and dst depths do not match"); }
+
+    if( (Tsrc->nDimension!=Tdst->nDimension) ) {
+        luaL_error(L, "image.polar: src and dst depths do not match"); }
+
+    // compute maximum distance
+    midY = (float) src_height / 2.0;
+    midX = (float) src_width  / 2.0;
+    if(doFull == 1) {
+        m = sqrt((float) src_width * (float) src_width + (float) src_height * (float) src_height) / 2.0;
+    }
+    else {
+        m = (src_width < src_height) ? midX : midY;
+    }
+
+    // loop to fill polar image
+    fw = log(m) / (float) dst_width;
+    for(j = 0; j < dst_height; j++) {               // orientation loop
+        jd = (float) j;
+        a = (2 * M_PI * jd) / (float) dst_height;   // current angle
+        for(i = 0; i < dst_width; i++) {            // radius loop
+            float val = -1;
+            id = (float) i;
+
+            r = exp(id * fw);
+
+            jj = (long) floor( r * cos(a) + midY);  // y-location in source image
+            ii = (long) floor(-r * sin(a) + midX);  // x-location in source image
+
+            if(ii>src_width-1) val=0;
+            if(jj>src_height-1) val=0;
+            if(ii<0) val=0;
+            if(jj<0) val=0;
+
+            if(Tsrc->nDimension==2)
+            {
+                if(val==-1)
+                    val=src[ii*src_stride2+jj*src_stride1];
+                dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val);
+            }
+            else
+            {
+                int do_copy=0; if(val==-1) do_copy=1;
+                for(k=0;k<src_depth;k++)
+                {
+                    if(do_copy)
+                        val=src[ii*src_stride2+jj*src_stride1+k*src_stride0];
+                    dst[i*dst_stride2+j*dst_stride1+k*dst_stride0] = image_(FromIntermediate)(val);
+                }
+            }
+        }
+    }
+    return 0;
+}
+static int image_(Main_logPolarBilinear)(lua_State *L)
+{
+    THTensor *Tsrc = luaT_checkudata(L, 1, torch_Tensor);
+    THTensor *Tdst = luaT_checkudata(L, 2, torch_Tensor);
+    float doFull = luaL_checknumber(L, 3);
+    real *src, *dst;
+    long dst_stride0, dst_stride1, dst_stride2, dst_width, dst_height, dst_depth;
+    long src_stride0, src_stride1, src_stride2, src_width, src_height, src_depth;
+    long i, j, k;
+    float id, jd, a, r, m, midY, midX, fw;
+    long ii_0, ii_1, jj_0, jj_1;
+
+    luaL_argcheck(L, Tsrc->nDimension==2 || Tsrc->nDimension==3, 1, "polar: src not 2 or 3 dimensional");
+    luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "polar: dst not 2 or 3 dimensional");
+
+    src= THTensor_(data)(Tsrc);
+    dst= THTensor_(data)(Tdst);
+
+    dst_stride0 = 0;
+    dst_stride1 = Tdst->stride[Tdst->nDimension-2];
+    dst_stride2 = Tdst->stride[Tdst->nDimension-1];
+    dst_depth =  0;
+    dst_height = Tdst->size[Tdst->nDimension-2];
+    dst_width = Tdst->size[Tdst->nDimension-1];
+    if(Tdst->nDimension == 3) {
+        dst_stride0 = Tdst->stride[0];
+        dst_depth = Tdst->size[0];
+    }
+
+    src_stride0 = 0;
+    src_stride1 = Tsrc->stride[Tsrc->nDimension-2];
+    src_stride2 = Tsrc->stride[Tsrc->nDimension-1];
+    src_depth =  0;
+    src_height = Tsrc->size[Tsrc->nDimension-2];
+    src_width = Tsrc->size[Tsrc->nDimension-1];
+    if(Tsrc->nDimension == 3) {
+        src_stride0 = Tsrc->stride[0];
+        src_depth = Tsrc->size[0];
+    }
+
+    if( Tsrc->nDimension==3 && Tdst->nDimension==3 && ( src_depth!=dst_depth) ) {
+        luaL_error(L, "image.polar: src and dst depths do not match"); }
+
+    if( (Tsrc->nDimension!=Tdst->nDimension) ) {
+        luaL_error(L, "image.polar: src and dst depths do not match"); }
+
+    // compute maximum distance
+    midY = (float) src_height / 2.0;
+    midX = (float) src_width  / 2.0;
+    if(doFull == 1) {
+        m = sqrt((float) src_width * (float) src_width + (float) src_height * (float) src_height) / 2.0;
+    }
+    else {
+        m = (src_width < src_height) ? midX : midY;
+    }
+
+    // loop to fill polar image
+    fw = log(m) / (float) dst_width;
+    for(j = 0; j < dst_height; j++) {                 // orientation loop
+        jd = (float) j;
+        a = (2 * M_PI * jd) / (float) dst_height;     // current angle
+        for(i = 0; i < dst_width; i++) {              // radius loop
+            float val = -1;
+            float ri, rj, wi, wj;
+            id = (float) i;
+
+            r = exp(id * fw);
+
+            rj =  r * cos(a) + midY;                  // y-location in source image
+            ri = -r * sin(a) + midX;                  // x-location in source image
+
+            ii_0=(long)floor(ri);
+            ii_1=ii_0 + 1;
+            jj_0=(long)floor(rj);
+            jj_1=jj_0 + 1;
+            wi = ri - ii_0;
+            wj = rj - jj_0;
+
+            // switch to nearest interpolation when bilinear is impossible
+            if(ii_1>src_width-1 || jj_1>src_height-1 || ii_0<0 || jj_0<0) {
+                if(ii_0>src_width-1) val=0;
+                if(jj_0>src_height-1) val=0;
+                if(ii_0<0) val=0;
+                if(jj_0<0) val=0;
+
+                if(Tsrc->nDimension==2)
+                {
+                    if(val==-1)
+                        val=src[ii_0*src_stride2+jj_0*src_stride1];
+                    dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val);
+                }
+                else
+                {
+                    int do_copy=0; if(val==-1) do_copy=1;
+                    for(k=0;k<src_depth;k++)
+                    {
+                        if(do_copy)
+                            val=src[ii_0*src_stride2+jj_0*src_stride1+k*src_stride0];
+                        dst[i*dst_stride2+j*dst_stride1+k*dst_stride0] = image_(FromIntermediate)(val);
+                    }
+                }
+            }
+
+            // bilinear interpolation
+            else {
+                if(Tsrc->nDimension==2) {
+                    if(val==-1)
+                        val = (1.0 - wi) * (1.0 - wj) * src[ii_0*src_stride2+jj_0*src_stride1]
+                        + wi * (1.0 - wj) * src[ii_1*src_stride2+jj_0*src_stride1]
+                        + (1.0 - wi) * wj * src[ii_0*src_stride2+jj_1*src_stride1]
+                        + wi * wj * src[ii_1*src_stride2+jj_1*src_stride1];
+                    dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val);
+                } else {
+                    int do_copy=0; if(val==-1) do_copy=1;
+                    for(k=0;k<src_depth;k++) {
+                        if(do_copy) {
+                            val = (1.0 - wi) * (1.0 - wj) * src[ii_0*src_stride2+jj_0*src_stride1+k*src_stride0]
+                            + wi * (1.0 - wj) * src[ii_1*src_stride2+jj_0*src_stride1+k*src_stride0]
+                            + (1.0 - wi) * wj * src[ii_0*src_stride2+jj_1*src_stride1+k*src_stride0]
+                            + wi * wj * src[ii_1*src_stride2+jj_1*src_stride1+k*src_stride0];
+                        }
+                        dst[i*dst_stride2+j*dst_stride1+k*dst_stride0] = image_(FromIntermediate)(val);
+                    }
+                }
+            }
+        }
+    }
+    return 0;
+}
+
+
+static int image_(Main_cropNoScale)(lua_State *L)
+{
+  THTensor *Tsrc = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *Tdst = luaT_checkudata(L, 2, torch_Tensor);
+  long startx = luaL_checklong(L, 3);
+  long starty = luaL_checklong(L, 4);
+  real *src, *dst;
+  long dst_stride0, dst_stride1, dst_stride2, dst_width, dst_height, dst_depth;
+  long src_stride0, src_stride1, src_stride2, src_width, src_height, src_depth;
+  long i, j, k;
+
+  luaL_argcheck(L, Tsrc->nDimension==2 || Tsrc->nDimension==3, 1, "rotate: src not 2 or 3 dimensional");
+  luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "rotate: dst not 2 or 3 dimensional");
+
+  src= THTensor_(data)(Tsrc);
+  dst= THTensor_(data)(Tdst);
+
+  dst_stride0 = 0;
+  dst_stride1 = Tdst->stride[Tdst->nDimension-2];
+  dst_stride2 = Tdst->stride[Tdst->nDimension-1];
+  dst_depth =  0;
+  dst_height = Tdst->size[Tdst->nDimension-2];
+  dst_width = Tdst->size[Tdst->nDimension-1];
+  if(Tdst->nDimension == 3) {
+    dst_stride0 = Tdst->stride[0];
+    dst_depth = Tdst->size[0];
+  }
+
+  src_stride0 = 0;
+  src_stride1 = Tsrc->stride[Tsrc->nDimension-2];
+  src_stride2 = Tsrc->stride[Tsrc->nDimension-1];
+  src_depth =  0;
+  src_height = Tsrc->size[Tsrc->nDimension-2];
+  src_width = Tsrc->size[Tsrc->nDimension-1];
+  if(Tsrc->nDimension == 3) {
+    src_stride0 = Tsrc->stride[0];
+    src_depth = Tsrc->size[0];
+  }
+
+  if( startx<0 || starty<0 || (startx+dst_width>src_width) || (starty+dst_height>src_height))
+    luaL_error(L, "image.crop: crop goes outside bounds of src");
+
+  if( Tdst->nDimension==3 && ( src_depth!=dst_depth) )
+    luaL_error(L, "image.crop: src and dst depths do not match");
+
+  for(j = 0; j < dst_height; j++) {
+    for(i = 0; i < dst_width; i++) {
+      float val = 0.0;
+
+      long ii=i+startx;
+      long jj=j+starty;
+
+      if(Tsrc->nDimension==2)
+        {
+          val=src[ii*src_stride2+jj*src_stride1];
+          dst[i*dst_stride2+j*dst_stride1] = image_(FromIntermediate)(val);
+        }
+      else
+        {
+          for(k=0;k<src_depth;k++)
+            {
+              val=src[ii*src_stride2+jj*src_stride1+k*src_stride0];
+              dst[i*dst_stride2+j*dst_stride1+k*dst_stride0] = image_(FromIntermediate)(val);
+            }
+        }
+    }
+  }
+  return 0;
+}
+
+static int image_(Main_translate)(lua_State *L)
+{
+  THTensor *Tsrc = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *Tdst = luaT_checkudata(L, 2, torch_Tensor);
+  long shiftx = luaL_checklong(L, 3);
+  long shifty = luaL_checklong(L, 4);
+  real *src, *dst;
+  long dst_stride0, dst_stride1, dst_stride2, dst_width, dst_height, dst_depth;
+  long src_stride0, src_stride1, src_stride2, src_width, src_height, src_depth;
+  long i, j, k;
+
+  luaL_argcheck(L, Tsrc->nDimension==2 || Tsrc->nDimension==3, 1, "rotate: src not 2 or 3 dimensional");
+  luaL_argcheck(L, Tdst->nDimension==2 || Tdst->nDimension==3, 2, "rotate: dst not 2 or 3 dimensional");
+
+  src= THTensor_(data)(Tsrc);
+  dst= THTensor_(data)(Tdst);
+
+  dst_stride0 = 1;
+  dst_stride1 = Tdst->stride[Tdst->nDimension-2];
+  dst_stride2 = Tdst->stride[Tdst->nDimension-1];
+  dst_depth =  1;
+  dst_height = Tdst->size[Tdst->nDimension-2];
+  dst_width = Tdst->size[Tdst->nDimension-1];
+  if(Tdst->nDimension == 3) {
+    dst_stride0 = Tdst->stride[0];
+    dst_depth = Tdst->size[0];
+  }
+
+  src_stride0 = 1;
+  src_stride1 = Tsrc->stride[Tsrc->nDimension-2];
+  src_stride2 = Tsrc->stride[Tsrc->nDimension-1];
+  src_depth =  1;
+  src_height = Tsrc->size[Tsrc->nDimension-2];
+  src_width = Tsrc->size[Tsrc->nDimension-1];
+  if(Tsrc->nDimension == 3) {
+    src_stride0 = Tsrc->stride[0];
+    src_depth = Tsrc->size[0];
+  }
+
+  if( Tdst->nDimension==3 && ( src_depth!=dst_depth) )
+    luaL_error(L, "image.translate: src and dst depths do not match");
+
+  for(j = 0; j < src_height; j++) {
+    for(i = 0; i < src_width; i++) {
+      long ii=i+shiftx;
+      long jj=j+shifty;
+
+      // Check it's within destination bounds, else crop
+      if(ii<dst_width && jj<dst_height && ii>=0 && jj>=0) {
+        for(k=0;k<src_depth;k++) {
+          dst[ii*dst_stride2+jj*dst_stride1+k*dst_stride0] = src[i*src_stride2+j*src_stride1+k*src_stride0];
+        }
+      }
+    }
+  }
+  return 0;
+}
+
+static int image_(Main_saturate)(lua_State *L) {
+#ifdef TH_REAL_IS_BYTE
+  // Noop since necessarily constrained to [0, 255].
+#else
+  THTensor *input = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *output = input;
+
+  TH_TENSOR_APPLY2(real, output, real, input,                       \
+                   *output_data = (*input_data < 0) ? 0 : (*input_data > 1) ? 1 : *input_data;)
+#endif
+  return 1;
+}
+
+/*
+ * Converts an RGB color value to HSL. Conversion formula
+ * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
+ * Assumes r, g, and b are contained in the set [0, 1] and
+ * returns h, s, and l in the set [0, 1].
+ */
+int image_(Main_rgb2hsl)(lua_State *L) {
+  THTensor *rgb = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *hsl = luaT_checkudata(L, 2, torch_Tensor);
+
+  int y,x;
+  temp_t r, g, b, h, s, l;
+  for (y=0; y<rgb->size[1]; y++) {
+    for (x=0; x<rgb->size[2]; x++) {
+      // get Rgb
+      r = THTensor_(get3d)(rgb, 0, y, x);
+      g = THTensor_(get3d)(rgb, 1, y, x);
+      b = THTensor_(get3d)(rgb, 2, y, x);
+#ifdef TH_REAL_IS_BYTE
+      r /= 255;
+      g /= 255;
+      b /= 255;
+#endif
+
+      temp_t mx = max(max(r, g), b);
+      temp_t mn = min(min(r, g), b);
+      if(mx == mn) {
+        h = 0; // achromatic
+        s = 0;
+        l = mx;
+      } else {
+        temp_t d = mx - mn;
+        if (mx == r) {
+          h = (g - b) / d + (g < b ? 6 : 0);
+        } else if (mx == g) {
+          h = (b - r) / d + 2;
+        } else {
+          h = (r - g) / d + 4;
+        }
+        h /= 6;
+        l = (mx + mn) / 2;
+        s = l > 0.5 ? d / (2 - mx - mn) : d / (mx + mn);
+      }
+
+      // set hsl
+#ifdef TH_REAL_IS_BYTE
+      h *= 255;
+      s *= 255;
+      l *= 255;
+#endif
+      THTensor_(set3d)(hsl, 0, y, x, image_(FromIntermediate)(h));
+      THTensor_(set3d)(hsl, 1, y, x, image_(FromIntermediate)(s));
+      THTensor_(set3d)(hsl, 2, y, x, image_(FromIntermediate)(l));
+    }
+  }
+  return 0;
+}
+
+// helper
+static inline temp_t image_(hue2rgb)(temp_t p, temp_t q, temp_t t) {
+  if (t < 0.) t += 1;
+  if (t > 1.) t -= 1;
+  if (t < 1./6)
+    return p + (q - p) * 6. * t;
+  else if (t < 1./2)
+    return q;
+  else if (t < 2./3)
+    return p + (q - p) * (2./3 - t) * 6.;
+  else
+    return p;
+}
+
+/*
+ * Converts an HSL color value to RGB. Conversion formula
+ * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
+ * Assumes h, s, and l are contained in the set [0, 1] and
+ * returns r, g, and b in the set [0, 1].
+ */
+int image_(Main_hsl2rgb)(lua_State *L) {
+  THTensor *hsl = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *rgb = luaT_checkudata(L, 2, torch_Tensor);
+
+  int y,x;
+  temp_t r, g, b, h, s, l;
+  for (y=0; y<hsl->size[1]; y++) {
+    for (x=0; x<hsl->size[2]; x++) {
+      // get hsl
+      h = THTensor_(get3d)(hsl, 0, y, x);
+      s = THTensor_(get3d)(hsl, 1, y, x);
+      l = THTensor_(get3d)(hsl, 2, y, x);
+#ifdef TH_REAL_IS_BYTE
+      h /= 255;
+      s /= 255;
+      l /= 255;
+#endif
+
+      if(s == 0) {
+        // achromatic
+        r = l;
+        g = l;
+        b = l;
+      } else {
+        temp_t q = (l < 0.5) ? (l * (1 + s)) : (l + s - l * s);
+        temp_t p = 2 * l - q;
+        temp_t hr = h + 1./3;
+        temp_t hg = h;
+        temp_t hb = h - 1./3;
+        r = image_(hue2rgb)(p, q, hr);
+        g = image_(hue2rgb)(p, q, hg);
+        b = image_(hue2rgb)(p, q, hb);
+      }
+
+      // set rgb
+#ifdef TH_REAL_IS_BYTE
+      r *= 255;
+      g *= 255;
+      b *= 255;
+#endif
+      THTensor_(set3d)(rgb, 0, y, x, image_(FromIntermediate)(r));
+      THTensor_(set3d)(rgb, 1, y, x, image_(FromIntermediate)(g));
+      THTensor_(set3d)(rgb, 2, y, x, image_(FromIntermediate)(b));
+    }
+  }
+  return 0;
+}
+
+/*
+ * Converts an RGB color value to HSV. Conversion formula
+ * adapted from http://en.wikipedia.org/wiki/HSV_color_space.
+ * Assumes r, g, and b are contained in the set [0, 1] and
+ * returns h, s, and v in the set [0, 1].
+ */
+int image_(Main_rgb2hsv)(lua_State *L) {
+  THTensor *rgb = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *hsv = luaT_checkudata(L, 2, torch_Tensor);
+
+  int y, x;
+  temp_t r, g, b, h, s, v;
+  for (y=0; y<rgb->size[1]; y++) {
+    for (x=0; x<rgb->size[2]; x++) {
+      // get Rgb
+      r = THTensor_(get3d)(rgb, 0, y, x);
+      g = THTensor_(get3d)(rgb, 1, y, x);
+      b = THTensor_(get3d)(rgb, 2, y, x);
+#ifdef TH_REAL_IS_BYTE
+      r /= 255;
+      g /= 255;
+      b /= 255;
+#endif
+
+      temp_t mx = max(max(r, g), b);
+      temp_t mn = min(min(r, g), b);
+      if(mx == mn) {
+        // achromatic
+        h = 0;
+        s = 0;
+        v = mx;
+      } else {
+        temp_t d = mx - mn;
+        if (mx == r) {
+          h = (g - b) / d + (g < b ? 6 : 0);
+        } else if (mx == g) {
+          h = (b - r) / d + 2;
+        } else {
+          h = (r - g) / d + 4;
+        }
+        h /= 6;
+        s = d / mx;
+        v = mx;
+      }
+
+      // set hsv
+#ifdef TH_REAL_IS_BYTE
+      h *= 255;
+      s *= 255;
+      v *= 255;
+#endif
+      THTensor_(set3d)(hsv, 0, y, x, image_(FromIntermediate)(h));
+      THTensor_(set3d)(hsv, 1, y, x, image_(FromIntermediate)(s));
+      THTensor_(set3d)(hsv, 2, y, x, image_(FromIntermediate)(v));
+    }
+  }
+  return 0;
+}
+
+/*
+ * Converts an HSV color value to RGB. Conversion formula
+ * adapted from http://en.wikipedia.org/wiki/HSV_color_space.
+ * Assumes h, s, and l are contained in the set [0, 1] and
+ * returns r, g, and b in the set [0, 1].
+ */
+int image_(Main_hsv2rgb)(lua_State *L) {
+  THTensor *hsv = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *rgb = luaT_checkudata(L, 2, torch_Tensor);
+
+  int y, x;
+  temp_t r, g, b, h, s, v;
+  for (y=0; y<hsv->size[1]; y++) {
+    for (x=0; x<hsv->size[2]; x++) {
+      // get hsv
+      h = THTensor_(get3d)(hsv, 0, y, x);
+      s = THTensor_(get3d)(hsv, 1, y, x);
+      v = THTensor_(get3d)(hsv, 2, y, x);
+#ifdef TH_REAL_IS_BYTE
+      h /= 255;
+      s /= 255;
+      v /= 255;
+#endif
+
+      int i = floor(h*6.);
+      temp_t f = h*6-i;
+      temp_t p = v*(1-s);
+      temp_t q = v*(1-f*s);
+      temp_t t = v*(1-(1-f)*s);
+
+      switch (i % 6) {
+      case 0: r = v, g = t, b = p; break;
+      case 1: r = q, g = v, b = p; break;
+      case 2: r = p, g = v, b = t; break;
+      case 3: r = p, g = q, b = v; break;
+      case 4: r = t, g = p, b = v; break;
+      case 5: r = v, g = p, b = q; break;
+      default: r=0; g = 0, b = 0; break;
+      }
+
+      // set rgb
+#ifdef TH_REAL_IS_BYTE
+      r *= 255;
+      g *= 255;
+      b *= 255;
+#endif
+      THTensor_(set3d)(rgb, 0, y, x, image_(FromIntermediate)(r));
+      THTensor_(set3d)(rgb, 1, y, x, image_(FromIntermediate)(g));
+      THTensor_(set3d)(rgb, 2, y, x, image_(FromIntermediate)(b));
+    }
+  }
+  return 0;
+}
+
+#ifndef TH_REAL_IS_BYTE
+/*
+ * Convert an sRGB color channel to a linear sRGB color channel.
+ */
+static inline real image_(gamma_expand_sRGB)(real nonlinear)
+{
+  return (nonlinear <= 0.04045) ? (nonlinear / 12.92)
+                                : (pow((nonlinear+0.055)/1.055, 2.4));
+}
+
+/*
+ * Convert a linear sRGB color channel to a sRGB color channel.
+ */
+static inline real image_(gamma_compress_sRGB)(real linear)
+{
+  return (linear <= 0.0031308) ? (12.92 * linear)
+                               : (1.055 * pow(linear, 1.0/2.4) - 0.055);
+}
+
+/*
+ * Converts an sRGB color value to LAB.
+ * Based on http://www.brucelindbloom.com/index.html?Equations.html.
+ * Assumes r, g, and b are contained in the set [0, 1].
+ * LAB output is NOT restricted to [0, 1]!
+ */
+int image_(Main_rgb2lab)(lua_State *L) {
+  THTensor *rgb = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *lab = luaT_checkudata(L, 2, torch_Tensor);
+
+  // CIE Standard
+  double epsilon = 216.0/24389.0;
+  double k = 24389.0/27.0;
+  // D65 white point
+  double xn = 0.950456;
+  double zn = 1.088754;
+
+  int y,x;
+  real r,g,b,l,a,_b;
+  for (y=0; y<rgb->size[1]; y++) {
+    for (x=0; x<rgb->size[2]; x++) {
+      // get RGB
+      r = image_(gamma_expand_sRGB)(THTensor_(get3d)(rgb, 0, y, x));
+      g = image_(gamma_expand_sRGB)(THTensor_(get3d)(rgb, 1, y, x));
+      b = image_(gamma_expand_sRGB)(THTensor_(get3d)(rgb, 2, y, x));
+
+      // sRGB to XYZ
+      double X = 0.412453 * r + 0.357580 * g + 0.180423 * b;
+      double Y = 0.212671 * r + 0.715160 * g + 0.072169 * b;
+      double Z = 0.019334 * r + 0.119193 * g + 0.950227 * b;
+
+      // normalize for D65 white point
+      X /= xn;
+      Z /= zn;
+
+      // XYZ normalized to CIE Lab
+      double fx = X > epsilon ? pow(X, 1/3.0) : (k * X + 16)/116;
+      double fy = Y > epsilon ? pow(Y, 1/3.0) : (k * Y + 16)/116;
+      double fz = Z > epsilon ? pow(Z, 1/3.0) : (k * Z + 16)/116;
+      l = 116 * fy - 16;
+      a = 500 * (fx - fy);
+      _b = 200 * (fy - fz);
+
+      // set lab
+      THTensor_(set3d)(lab, 0, y, x, l);
+      THTensor_(set3d)(lab, 1, y, x, a);
+      THTensor_(set3d)(lab, 2, y, x, _b);
+    }
+  }
+  return 0;
+}
+
+/*
+ * Converts an LAB color value to sRGB.
+ * Based on http://www.brucelindbloom.com/index.html?Equations.html.
+ * returns r, g, and b in the set [0, 1].
+ */
+int image_(Main_lab2rgb)(lua_State *L) {
+  THTensor *lab = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *rgb = luaT_checkudata(L, 2, torch_Tensor);
+
+  int y,x;
+  real r,g,b,l,a,_b;
+
+  // CIE Standard
+  double epsilon = 216.0/24389.0;
+  double k = 24389.0/27.0;
+  // D65 white point
+  double xn = 0.950456;
+  double zn = 1.088754;
+
+  for (y=0; y<lab->size[1]; y++) {
+    for (x=0; x<lab->size[2]; x++) {
+      // get lab
+      l = THTensor_(get3d)(lab, 0, y, x);
+      a = THTensor_(get3d)(lab, 1, y, x);
+      _b = THTensor_(get3d)(lab, 2, y, x);
+
+      // LAB to XYZ
+      double fy = (l + 16) / 116;
+      double fz = fy - _b / 200;
+      double fx = (a / 500) + fy;
+      double X = pow(fx, 3);
+      if (X <= epsilon)
+        X = (116 * fx - 16) / k;
+      double Y = l > (k * epsilon) ? pow((l + 16) / 116, 3) : l/k;
+      double Z = pow(fz, 3);
+      if (Z <= epsilon)
+        Z = (116 * fz - 16) / k;
+
+      X *= xn;
+      Z *= zn;
+
+      // XYZ to sRGB
+      r =  3.2404542 * X - 1.5371385 * Y - 0.4985314 * Z;
+      g = -0.9692660 * X + 1.8760108 * Y + 0.0415560 * Z;
+      b =  0.0556434 * X - 0.2040259 * Y + 1.0572252 * Z;
+
+      // set rgb
+      THTensor_(set3d)(rgb, 0, y, x, image_(gamma_compress_sRGB(r)));
+      THTensor_(set3d)(rgb, 1, y, x, image_(gamma_compress_sRGB(g)));
+      THTensor_(set3d)(rgb, 2, y, x, image_(gamma_compress_sRGB(b)));
+    }
+  }
+  return 0;
+}
+#else
+int image_(Main_rgb2lab)(lua_State *L) {
+  return luaL_error(L, "image.rgb2lab: not supported for torch.ByteTensor");
+}
+
+int image_(Main_lab2rgb)(lua_State *L) {
+  return luaL_error(L, "image.lab2rgb: not supported for torch.ByteTensor");
+}
+#endif // TH_REAL_IS_BYTE
+
+/* Vertically flip an image */
+int image_(Main_vflip)(lua_State *L) {
+  THTensor *dst = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *src = luaT_checkudata(L, 2, torch_Tensor);
+
+  int width = dst->size[2];
+  int height = dst->size[1];
+  int channels = dst->size[0];
+  long *is = src->stride;
+  long *os = dst->stride;
+
+  // get raw pointers
+  real *dst_data = THTensor_(data)(dst);
+  real *src_data = THTensor_(data)(src);
+
+  long k, x, y;
+  if (dst_data != src_data) {
+      /* not in-place.
+       * this branch could be removed by first duplicating the src into dst then doing inplace */
+#pragma omp parallel for private(k, x, y)
+      for(k=0; k<channels; k++) {
+          for (y=0; y<height; y++) {
+            for (x=0; x<width; x++) {
+                dst_data[ k*os[0] + (height-1-y)*os[1] + x*os[2] ] = src_data[ k*is[0] + y*is[1] + x*is[2] ];
+            }
+          }
+      }
+  } else {
+      /* in-place  */
+      real swap, * src_px,  * dst_px;
+      long half_height = height >> 1;
+      for(k=0; k<channels; k++) {
+          for (y=0; y < half_height; y++) {
+            for (x=0; x<width; x++) {
+                src_px = src_data + k*is[0] + y*is[1] + x*is[2];
+                dst_px =  dst_data + k*is[0] + (height-1-y)*is[1] + x*is[2];
+                swap = *dst_px;
+                *dst_px = *src_px;
+                *src_px = swap;
+            }
+          }
+      }
+  }
+
+  return 0;
+}
+
+
+/* Horizontally flip an image */
+int image_(Main_hflip)(lua_State *L) {
+  THTensor *dst = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *src = luaT_checkudata(L, 2, torch_Tensor);
+
+  int width = dst->size[2];
+  int height = dst->size[1];
+  int channels = dst->size[0];
+  long *is = src->stride;
+  long *os = dst->stride;
+
+  // get raw pointers
+  real *dst_data = THTensor_(data)(dst);
+  real *src_data = THTensor_(data)(src);
+
+  long k, x, y;
+  if (dst_data != src_data) {
+      /* not in-place.
+       * this branch could be removed by first duplicating the src into dst then doing inplace */
+#pragma omp parallel for private(k, x, y)
+      for(k=0; k<channels; k++) {
+          for (y=0; y<height; y++) {
+              for (x=0; x<width; x++) {
+                  dst_data[ k*os[0] + y*os[1] + (width-x-1)*os[2] ] = src_data[ k*is[0] + y*is[1] + x*is[2] ];
+              }
+          }
+      }
+  } else {
+      /* in-place  */
+      real swap, * src_px,  * dst_px;
+      long half_width = width >> 1;
+      for(k=0; k<channels; k++) {
+          for (y=0; y < height; y++) {
+            for (x=0; x<half_width; x++) {
+                src_px = src_data + k*is[0] + y*is[1] + x*is[2];
+                dst_px =  dst_data + k*is[0] + y*is[1] + (width-x-1)*is[2];
+                swap = *dst_px;
+                *dst_px = *src_px;
+                *src_px = swap;
+            }
+          }
+      }
+  }
+
+  return 0;
+}
+
+/* flip an image along a specified dimension */
+int image_(Main_flip)(lua_State *L) {
+  THTensor *dst = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *src = luaT_checkudata(L, 2, torch_Tensor);
+  long flip_dim = luaL_checklong(L, 3);
+
+  if ((dst->nDimension != 5) || (src->nDimension != 5)) {
+    luaL_error(L, "image.flip: expected 5 dimensions for src and dst");
+  }
+
+  if (flip_dim < 1 || flip_dim > dst->nDimension || flip_dim > 5) {
+    luaL_error(L, "image.flip: flip_dim out of bounds");
+  }
+  flip_dim--;  //  Make it zero indexed
+
+  // get raw pointers
+  real *dst_data = THTensor_(data)(dst);
+  real *src_data = THTensor_(data)(src);
+  if (dst_data == src_data) {
+    luaL_error(L, "image.flip: in-place flip not supported");
+  }
+
+  long size0 = dst->size[0];
+  long size1 = dst->size[1];
+  long size2 = dst->size[2];
+  long size3 = dst->size[3];
+  long size4 = dst->size[4];
+
+  if (src->size[0] != size0 || src->size[1] != size1 ||
+      src->size[2] != size2 || src->size[3] != size3 ||
+      src->size[4] != size4) {
+    luaL_error(L, "image.flip: src and dst are not the same size");
+  }
+
+  long *is = src->stride;
+  long *os = dst->stride;
+
+  long x, y, z, d, t, isrc, idst = 0;
+  for (t = 0; t < size0; t++) {
+    for (d = 0; d < size1; d++) {
+      for (z = 0; z < size2; z++) {
+        for (y = 0; y < size3; y++) {
+          for (x = 0; x < size4; x++) {
+            isrc = t*is[0] + d*is[1] + z*is[2] + y*is[3] + x*is[4];
+            // The big switch statement here looks ugly, however on my machine
+            // gcc compiles it to a skip list, so it should be fast.
+            switch (flip_dim) {
+              case 0:
+                idst = (size0 - t - 1)*os[0] + d*os[1] + z*os[2] + y*os[3] + x*os[4];
+                break;
+              case 1:
+                idst = t*os[0] + (size1 - d - 1)*os[1] + z*os[2] + y*os[3] + x*os[4];
+                break;
+              case 2:
+                idst = t*os[0] + d*os[1] + (size2 - z - 1)*os[2] + y*os[3] + x*os[4];
+                break;
+              case 3:
+                idst = t*os[0] + d*os[1] + z*os[2] + (size3 - y - 1)*os[3] + x*os[4];
+                break;
+              case 4:
+                idst = t*os[0] + d*os[1] + z*os[2] + y*os[3] + (size4 - x - 1)*os[4];
+                break;
+            }
+            dst_data[ idst ] = src_data[  isrc ];
+          }
+        }
+      }
+    }
+  }
+
+  return 0;
+}
+
+static inline void image_(Main_bicubicInterpolate)(
+  real* src, long* is, long* size, temp_t ix, temp_t iy,
+  real* dst, long *os,
+  real pad_value, int bounds_check)
+{
+  int i, j, k;
+  temp_t arr[4], p[4];
+
+  // Calculate fractional and integer components
+  long x_pix = floor(ix);
+  long y_pix = floor(iy);
+  temp_t dx = ix - x_pix;
+  temp_t dy = iy - y_pix;
+
+  for (k=0; k<size[0]; k++) {
+    #pragma unroll
+    for (i = 0; i < 4; i++) {
+      long v = y_pix + i - 1;
+      real* data = &src[k * is[0] + v * is[1]];
+
+      #pragma unroll
+      for (j = 0; j < 4; j++) {
+        long u = x_pix + j - 1;
+        if (bounds_check && (v < 0 || v >= size[1] || u < 0 || u >= size[2])) {
+          p[j] = pad_value;
+        } else {
+          p[j] = data[u * is[2]];
+        }
+      }
+
+      arr[i] = image_(Main_cubicInterpolate)(p[0], p[1], p[2], p[3], dx);
+    }
+
+    temp_t value = image_(Main_cubicInterpolate)(arr[0], arr[1], arr[2], arr[3], dy);
+    dst[k * os[0]] = image_(FromIntermediate)(value);
+  }
+}
+
+/*
+ * Warps an image, according to an (x,y) flow field. The flow
+ * field is in the space of the destination image, each vector
+ * ponts to a source pixel in the original image.
+ */
+int image_(Main_warp)(lua_State *L) {
+  THTensor *dst = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *src = luaT_checkudata(L, 2, torch_Tensor);
+  THTensor *flowfield = luaT_checkudata(L, 3, torch_Tensor);
+  int mode = lua_tointeger(L, 4);
+  int offset_mode = lua_toboolean(L, 5);
+  int clamp_mode = lua_tointeger(L, 6);
+  real pad_value = (real)lua_tonumber(L, 7);
+
+  // dims
+  int width = dst->size[2];
+  int height = dst->size[1];
+  int src_width = src->size[2];
+  int src_height = src->size[1];
+  int channels = dst->size[0];
+  long *is = src->stride;
+  long *os = dst->stride;
+  long *fs = flowfield->stride;
+
+  // get raw pointers
+  real *dst_data = THTensor_(data)(dst);
+  real *src_data = THTensor_(data)(src);
+  real *flow_data = THTensor_(data)(flowfield);
+
+  // resample
+  long k,x,y,v,u,i,j;
+#pragma omp parallel for private(k, x, y, v, u, i, j)
+  for (y=0; y<height; y++) {
+    for (x=0; x<width; x++) {
+      // subpixel position:
+      float flow_y = flow_data[ 0*fs[0] + y*fs[1] + x*fs[2] ];
+      float flow_x = flow_data[ 1*fs[0] + y*fs[1] + x*fs[2] ];
+      float iy = offset_mode*y + flow_y;
+      float ix = offset_mode*x + flow_x;
+
+      // borders
+      int off_image = 0;
+      if (iy < 0 || iy > src_height - 1 ||
+          ix < 0 || ix > src_width - 1) {
+        off_image = 1;
+      }
+
+      if (off_image == 1 && clamp_mode == 1) {
+        // We're off the image and we're clamping the input image to 0
+        for (k=0; k<channels; k++) {
+          dst_data[ k*os[0] + y*os[1] + x*os[2] ] = pad_value;
+        }
+      } else {
+        ix = MAX(ix,0); ix = MIN(ix,src_width-1);
+        iy = MAX(iy,0); iy = MIN(iy,src_height-1);
+
+        // bilinear?
+        switch (mode) {
+        case 1:  // Bilinear interpolation
+          {
+            // 4 nearest neighbors:
+            long ix_nw = floor(ix);
+            long iy_nw = floor(iy);
+            long ix_ne = ix_nw + 1;
+            long iy_ne = iy_nw;
+            long ix_sw = ix_nw;
+            long iy_sw = iy_nw + 1;
+            long ix_se = ix_nw + 1;
+            long iy_se = iy_nw + 1;
+
+            // get surfaces to each neighbor:
+            temp_t nw = (ix_se-ix)*(iy_se-iy);
+            temp_t ne = (ix-ix_sw)*(iy_sw-iy);
+            temp_t sw = (ix_ne-ix)*(iy-iy_ne);
+            temp_t se = (ix-ix_nw)*(iy-iy_nw);
+
+            // weighted sum of neighbors:
+            for (k=0; k<channels; k++) {
+              dst_data[ k*os[0] + y*os[1] + x*os[2] ] = image_(FromIntermediate)(
+                  src_data[ k*is[0] +               iy_nw*is[1] +              ix_nw*is[2] ] * nw
+                + src_data[ k*is[0] +               iy_ne*is[1] + MIN(ix_ne,src_width-1)*is[2] ] * ne
+                + src_data[ k*is[0] + MIN(iy_sw,src_height-1)*is[1] +              ix_sw*is[2] ] * sw
+                + src_data[ k*is[0] + MIN(iy_se,src_height-1)*is[1] + MIN(ix_se,src_width-1)*is[2] ] * se);
+            }
+          }
+          break;
+        case 0:  // Simple (i.e., nearest neighbor)
+          {
+            // 1 nearest neighbor:
+            long ix_n = floor(ix+0.5);
+            long iy_n = floor(iy+0.5);
+
+            // weighted sum of neighbors:
+            for (k=0; k<channels; k++) {
+              dst_data[ k*os[0] + y*os[1] + x*os[2] ] = src_data[ k*is[0] + iy_n*is[1] + ix_n*is[2] ];
+            }
+          }
+          break;
+        case 2:  // Bicubic
+          {
+            // We only need to do bounds checking if ix or iy are near the edge
+            int edge = !(iy >= 1 && iy < src_height - 2 && ix >= 1 && ix < src_width - 2);
+
+            real* dst = dst_data + y*os[1] + x*os[2];
+            if (edge) {
+              image_(Main_bicubicInterpolate)(src_data, is, src->size, ix, iy, dst, os, pad_value, 1);
+            } else {
+              image_(Main_bicubicInterpolate)(src_data, is, src->size, ix, iy, dst, os, pad_value, 0);
+            }
+          }
+          break;
+        case 3:  // Lanczos
+          {
+            // Note: Lanczos can be made fast if the resampling period is
+            // constant... and therefore the Lu, Lv can be cached and reused.
+            // However, unfortunately warp makes no assumptions about resampling
+            // and so we need to perform the O(k^2) convolution on each pixel AND
+            // we have to re-calculate the kernel for every pixel.
+            // See wikipedia for more info.
+            // It is however an extremely good approximation to to full sinc
+            // interpolation (IIR) filter.
+            // Another note is that the version here has been optimized using
+            // pretty aggressive code flow and explicit inlining.  It might not
+            // be very readable (contact me, Jonathan Tompson, if it is not)
+
+            // Calculate fractional and integer components
+            long x_pix = floor(ix);
+            long y_pix = floor(iy);
+
+            // Precalculate the L(x) function evaluations in the u and v direction
+            #define rad (3)  // This is a tunable parameter: 2 to 3 is OK
+            float Lu[2 * rad];  // L(x) for u direction
+            float Lv[2 * rad];  // L(x) for v direction
+            for (u=x_pix-rad+1, i=0; u<=x_pix+rad; u++, i++) {
+              float du = ix - (float)u;  // Lanczos kernel x value
+              du = du < 0 ? -du : du;  // prefer not to used std absf
+              if (du < 0.000001f) {  // TODO: Is there a real eps standard?
+                Lu[i] = 1;
+              } else if (du > (float)rad) {
+                Lu[i] = 0;
+              } else {
+                Lu[i] = ((float)rad * sin((float)M_PI * du) *
+                  sin((float)M_PI * du / (float)rad)) /
+                  ((float)(M_PI * M_PI) * du * du);
+              }
+            }
+            for (v=y_pix-rad+1, i=0; v<=y_pix+rad; v++, i++) {
+              float dv = iy - (float)v;  // Lanczos kernel x value
+              dv = dv < 0 ? -dv : dv;  // prefer not to used std absf
+              if (dv < 0.000001f) {  // TODO: Is there a real eps standard?
+                Lv[i] = 1;
+              } else if (dv > (float)rad) {
+                Lv[i] = 0;
+              } else {
+                Lv[i] = ((float)rad * sin((float)M_PI * dv) *
+                  sin((float)M_PI * dv / (float)rad)) /
+                  ((float)(M_PI * M_PI) * dv * dv);
+              }
+            }
+            float sum_weights = 0;
+            for (u=0; u<2*rad; u++) {
+              for (v=0; v<2*rad; v++) {
+                sum_weights += (Lu[u] * Lv[v]);
+              }
+            }
+
+            for (k=0; k<channels; k++) {
+              temp_t result = 0;
+              for (u=x_pix-rad+1, i=0; u<=x_pix+rad; u++, i++) {
+                long curu = MAX(MIN((long)(src_width-1), u), 0);
+                for (v=y_pix-rad+1, j=0; v<=y_pix+rad; v++, j++) {
+                  long curv = MAX(MIN((long)(src_height-1), v), 0);
+                  temp_t Suv = src_data[k * is[0] + curv * is[1] + curu * is[2]];
+
+                  temp_t weight = Lu[i] * Lv[j];
+                  result += (Suv * weight);
+                }
+              }
+              // Normalize by the sum of the weights
+              result = result / (float)sum_weights;
+
+              // Again,  I assume that since the image is stored as reals we
+              // don't have to worry about clamping to min and max int (to
+              // prevent over or underflow)
+              dst_data[ k*os[0] + y*os[1] + x*os[2] ] = image_(FromIntermediate)(result);
+            }
+          }
+          break;
+        }  // end switch (mode)
+      }  // end else
+    }
+  }
+
+  // done
+  return 0;
+}
+
+
+int image_(Main_gaussian)(lua_State *L) {
+  THTensor *dst = luaT_checkudata(L, 1, torch_Tensor);
+  long width = dst->size[1];
+  long height = dst->size[0];
+  long *os = dst->stride;
+
+  real *dst_data = THTensor_(data)(dst);
+
+  temp_t amplitude = (temp_t)lua_tonumber(L, 2);
+  int normalize = (int)lua_toboolean(L, 3);
+  temp_t sigma_u = (temp_t)lua_tonumber(L, 4);
+  temp_t sigma_v = (temp_t)lua_tonumber(L, 5);
+  temp_t mean_u = (temp_t)lua_tonumber(L, 6) * width + 0.5;
+  temp_t mean_v = (temp_t)lua_tonumber(L, 7) * height + 0.5;
+
+  // Precalculate 1/(sigma*size) for speed (for some stupid reason the pragma
+  // omp declaration prevents gcc from optimizing the inside loop on my macine:
+  // verified by checking the assembly output)
+  temp_t over_sigmau = 1.0 / (sigma_u * width);
+  temp_t over_sigmav = 1.0 / (sigma_v * height);
+
+  long v, u;
+  temp_t du, dv;
+#pragma omp parallel for private(v, u, du, dv)
+  for (v = 0; v < height; v++) {
+    for (u = 0; u < width; u++) {
+      du = (u + 1 - mean_u) * over_sigmau;
+      dv = (v + 1 - mean_v) * over_sigmav;
+      temp_t value = amplitude * exp(-0.5 * (du*du + dv*dv));
+      dst_data[ v*os[0] + u*os[1] ] = image_(FromIntermediate)(value);
+    }
+  }
+
+  if (normalize) {
+    temp_t sum = 0;
+    // We could parallelize this, but it's more trouble than it's worth
+    for(v = 0; v < height; v++) {
+      for(u = 0; u < width; u++) {
+        sum += dst_data[ v*os[0] + u*os[1] ];
+      }
+    }
+    temp_t one_over_sum = 1.0 / sum;
+#pragma omp parallel for private(v, u)
+    for(v = 0; v < height; v++) {
+      for(u = 0; u < width; u++) {
+        dst_data[ v*os[0] + u*os[1] ] *= one_over_sum;
+      }
+    }
+  }
+  return 0;
+}
+
+/*
+ * Borrowed from github.com/clementfarabet/lua---imgraph
+ * with Clément's permission for implementing y2jet()
+ */
+int image_(Main_colorize)(lua_State *L) {
+  // get args
+  THTensor *output = (THTensor *)luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *input = (THTensor *)luaT_checkudata(L, 2, torch_Tensor);
+  THTensor *colormap = (THTensor *)luaT_checkudata(L, 3, torch_Tensor);
+
+  // dims
+  long height = input->size[0];
+  long width = input->size[1];
+
+  // generate color map if not given
+  int noColorMap = THTensor_(nElement)(colormap) == 0;
+  if (noColorMap) {
+    THTensor_(resize2d)(colormap, width*height, 3);
+    THTensor_(fill)(colormap, -1);
+  }
+
+  // colormap channels
+  int channels = colormap->size[1];
+
+  // generate output
+  THTensor_(resize3d)(output, channels, height, width);
+  int x,y,k;
+  for (y = 0; y < height; y++) {
+    for (x = 0; x < width; x++) {
+      int id = THTensor_(get2d)(input, y, x);
+      if (noColorMap) {
+        for (k = 0; k < channels; k++) {
+          temp_t value = (float)rand() / (float)RAND_MAX;
+#ifdef TH_REAL_IS_BYTE
+          value *= 255;
+#endif
+          THTensor_(set2d)(colormap, id, k, image_(FromIntermediate)(value));
+        }
+      }
+      for (k = 0; k < channels; k++) {
+        real color = THTensor_(get2d)(colormap, id, k);
+        THTensor_(set3d)(output, k, y, x, color);
+      }
+    }
+  }
+
+  // return nothing
+  return 0;
+}
+
+int image_(Main_rgb2y)(lua_State *L) {
+  THTensor *rgb = luaT_checkudata(L, 1, torch_Tensor);
+  THTensor *yim = luaT_checkudata(L, 2, torch_Tensor);
+
+  luaL_argcheck(L, rgb->nDimension == 3, 1, "image.rgb2y: src not 3D");
+  luaL_argcheck(L, yim->nDimension == 2, 2, "image.rgb2y: dst not 2D");
+  luaL_argcheck(L, rgb->size[1] == yim->size[0], 2,
+                "image.rgb2y: src and dst not of same height");
+  luaL_argcheck(L, rgb->size[2] == yim->size[1], 2,
+                "image.rgb2y: src and dst not of same width");
+
+  int y, x;
+  temp_t r, g, b, yc;
+  const int height = rgb->size[1];
+  const int width = rgb->size[2];
+  for (y=0; y<height; y++) {
+    for (x=0; x<width; x++) {
+      // get Rgb
+      r = THTensor_(get3d)(rgb, 0, y, x);
+      g = THTensor_(get3d)(rgb, 1, y, x);
+      b = THTensor_(get3d)(rgb, 2, y, x);
+
+      yc = 0.299 * r + 0.587 * g + 0.114 * b;
+      THTensor_(set2d)(yim, y, x, image_(FromIntermediate)(yc));
+    }
+  }
+  return 0;
+}
+
+static inline void image_(drawPixel)(THTensor *output, int y, int x,
+                                     int cr, int cg, int cb) {
+#ifdef TH_REAL_IS_BYTE
+  THTensor_(set3d)(output, 0, y, x, cr);
+  THTensor_(set3d)(output, 1, y, x, cg);
+  THTensor_(set3d)(output, 2, y, x, cb);
+#else
+  THTensor_(set3d)(output, 0, y, x, cr / 255);
+  THTensor_(set3d)(output, 1, y, x, cg / 255);
+  THTensor_(set3d)(output, 2, y, x, cb / 255);
+#endif
+}
+static inline void image_(drawChar)(THTensor *output, int x, int y, unsigned char c, int size,
+                                    int cr, int cg, int cb,
+                                    int bg_cr, int bg_cg, int bg_cb) {
+  long channels = output->size[0];
+  long height = output->size[1];
+  long width  = output->size[2];
+
+  /* out of bounds condition, return without drawing */
+  if((x >= width)            || // Clip right
+     (y >= height)           || // Clip bottom
+     ((x + 6 * size - 1) < 0) || // Clip left
+     ((y + 8 * size - 1) < 0))   // Clip top
+    return;
+
+  for(char i = 0; i < 6; i++ ) {
+    unsigned char line;
+    if (i < 5) {
+      line = *(const unsigned char *)(image_ada_font+(c*5) + i);
+    } else {
+      line = 0x0;
+    }
+    for(char j = 0; j < 8; j++, line >>= 1) {
+      if(line & 0x1) {
+        if (size == 1) {
+          image_(drawPixel)(output, y+j, x+i, cr, cg, cb);
+        }
+        else {
+          for (int ii = x+(i*size); ii < x+(i*size) + size; ii++) {
+            for (int jj = y+(j*size); jj < y+(j*size) + size; jj++) {
+              image_(drawPixel)(output, jj, ii, cr, cg, cb);
+            }
+          }
+        }
+      } else if (bg_cr != -1 && bg_cg != -1 && bg_cb != -1) {
+        if (size == 1) {
+          image_(drawPixel)(output, y+j, x+i, bg_cr, bg_cg, bg_cb);
+        } else {
+          for (int ii = x+(i*size); ii < x+(i*size) + size; ii++) {
+            for (int jj = y+(j*size); jj < y+(j*size) + size; jj++) {
+              image_(drawPixel)(output, jj, ii, bg_cr, bg_cg, bg_cb);
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+int image_(Main_drawtext)(lua_State *L) {
+  // get args
+  THTensor *output = (THTensor *)luaT_checkudata(L, 1, torch_Tensor);
+  const char* text = lua_tostring(L, 2);
+  long x = luaL_checklong(L, 3);
+  long y = luaL_checklong(L, 4);
+  int size = luaL_checkint(L, 5);
+  int cr = luaL_checkint(L, 6);
+  int cg = luaL_checkint(L, 7);
+  int cb = luaL_checkint(L, 8);
+  int bg_cr = luaL_checkint(L, 9);
+  int bg_cg = luaL_checkint(L, 10);
+  int bg_cb = luaL_checkint(L, 11);
+  int wrap = luaL_checkint(L, 12);
+
+  long len = strlen(text);
+
+  // dims
+  long channels = output->size[0];
+  long height = output->size[1];
+  long width  = output->size[2];
+
+  long cursor_y = y;
+  long cursor_x = x;
+
+  for (long cnt = 0; cnt < len; cnt++) {
+    unsigned char c = text[cnt];
+    if(c == '\n') {
+      cursor_y += size*8;
+      cursor_x  = x;
+    } else if(c == '\r') {
+      // skip em
+    } else {
+      if(wrap && ((cursor_x + size * 6) >= width)) { // Heading off edge?
+        cursor_x  = 0;            // Reset x to zero
+        cursor_y += size * 8; // Advance y one line
+      }
+      image_(drawChar)(output, cursor_x, cursor_y, c, size,
+                       cr, cg, cb,
+                       bg_cr, bg_cg, bg_cb);
+      cursor_x += size * 6;
+    }
+  }
+
+  return 0;
+}
+
+int image_(Main_drawRect)(lua_State *L) {
+  THTensor *output = (THTensor *)luaT_checkudata(L, 1, torch_Tensor);
+  long x1long = luaL_checklong(L, 2);
+  long y1long = luaL_checklong(L, 3);
+  long x2long = luaL_checklong(L, 4);
+  long y2long = luaL_checklong(L, 5);
+  int lineWidth = luaL_checkint(L, 6);
+  int cr = luaL_checkint(L, 7);
+  int cg = luaL_checkint(L, 8);
+  int cb = luaL_checkint(L, 9);
+
+  int offset = lineWidth / 2;
+  int x1 = (int) MAX(0, x1long - offset - 1);
+  int y1 = (int) MAX(0, y1long - offset - 1);
+  int x2 = (int) MIN(output->size[2] - 1, x2long - offset - 1);
+  int y2 = (int) MIN(output->size[1] - 1, y2long - offset - 1);
+
+  int w = x2 - x1 + 1;
+  int h = y2 - y1 + 1;
+  for (int y = y1; y < y2 + lineWidth; y++) {
+    for (int x = x1; x < x1 + lineWidth; x++) {
+      image_(drawPixel)(output, y, x, cr, cg, cb);
+    }
+    for (int x = x2; x < x2 + lineWidth; x++) {
+      image_(drawPixel)(output, y, x, cr, cg, cb);
+    }
+  }
+  for (int x = x1; x < x2 + lineWidth; x++) {
+    for (int y = y1; y < y1 + lineWidth; y++) {
+      image_(drawPixel)(output, y, x, cr, cg, cb);
+    }
+    for (int y = y2; y < y2 + lineWidth; y++) {
+      image_(drawPixel)(output, y, x, cr, cg, cb);
+    }
+  }
+
+  return 0;
+}
+
+
+static const struct luaL_Reg image_(Main__) [] = {
+  {"scaleSimple", image_(Main_scaleSimple)},
+  {"scaleBilinear", image_(Main_scaleBilinear)},
+  {"scaleBicubic", image_(Main_scaleBicubic)},
+  {"rotate", image_(Main_rotate)},
+  {"rotateBilinear", image_(Main_rotateBilinear)},
+  {"polar", image_(Main_polar)},
+  {"polarBilinear", image_(Main_polarBilinear)},
+  {"logPolar", image_(Main_logPolar)},
+  {"logPolarBilinear", image_(Main_logPolarBilinear)},
+  {"translate", image_(Main_translate)},
+  {"cropNoScale", image_(Main_cropNoScale)},
+  {"warp", image_(Main_warp)},
+  {"saturate", image_(Main_saturate)},
+  {"rgb2y",   image_(Main_rgb2y)},
+  {"rgb2hsv", image_(Main_rgb2hsv)},
+  {"rgb2hsl", image_(Main_rgb2hsl)},
+  {"hsv2rgb", image_(Main_hsv2rgb)},
+  {"hsl2rgb", image_(Main_hsl2rgb)},
+  {"rgb2lab", image_(Main_rgb2lab)},
+  {"lab2rgb", image_(Main_lab2rgb)},
+  {"gaussian", image_(Main_gaussian)},
+  {"vflip", image_(Main_vflip)},
+  {"hflip", image_(Main_hflip)},
+  {"flip", image_(Main_flip)},
+  {"colorize", image_(Main_colorize)},
+  {"text", image_(Main_drawtext)},
+  {"drawRect", image_(Main_drawRect)},
+  {NULL, NULL}
+};
+
+void image_(Main_init)(lua_State *L)
+{
+  luaT_pushmetatable(L, torch_Tensor);
+  luaT_registeratname(L, image_(Main__), "image");
+}
+
+#endif // TH_GENERIC_FILE
diff --git a/generic/jpeg.c b/generic/jpeg.c
new file mode 100755
index 0000000..1fd1e70
--- /dev/null
+++ b/generic/jpeg.c
@@ -0,0 +1,527 @@
+#ifndef TH_GENERIC_FILE
+#define TH_GENERIC_FILE "generic/jpeg.c"
+#else
+
+/******************** JPEG DECOMPRESSION SAMPLE INTERFACE *******************/
+
+/* This half of the example shows how to read data from the JPEG decompressor.
+ * It's a bit more refined than the above, in that we show:
+ *   (a) how to modify the JPEG library's standard error-reporting behavior;
+ *   (b) how to allocate workspace using the library's memory manager.
+ *
+ * Just to make this example a little different from the first one, we'll
+ * assume that we do not intend to put the whole image into an in-memory
+ * buffer, but to send it line-by-line someplace else.  We need a one-
+ * scanline-high JSAMPLE array as a work buffer, and we will let the JPEG
+ * memory manager allocate it for us.  This approach is actually quite useful
+ * because we don't need to remember to deallocate the buffer separately: it
+ * will go away automatically when the JPEG object is cleaned up.
+ */
+
+
+/*
+ * ERROR HANDLING:
+ *
+ * The JPEG library's standard error handler (jerror.c) is divided into
+ * several "methods" which you can override individually.  This lets you
+ * adjust the behavior without duplicating a lot of code, which you might
+ * have to update with each future release.
+ *
+ * Our example here shows how to override the "error_exit" method so that
+ * control is returned to the library's caller when a fatal error occurs,
+ * rather than calling exit() as the standard error_exit method does.
+ *
+ * We use C's setjmp/longjmp facility to return control.  This means that the
+ * routine which calls the JPEG library must first execute a setjmp() call to
+ * establish the return point.  We want the replacement error_exit to do a
+ * longjmp().  But we need to make the setjmp buffer accessible to the
+ * error_exit routine.  To do this, we make a private extension of the
+ * standard JPEG error handler object.  (If we were using C++, we'd say we
+ * were making a subclass of the regular error handler.)
+ *
+ * Here's the extended error handler struct:
+ */
+
+#ifndef _LIBJPEG_ERROR_STRUCTS_
+#define _LIBJPEG_ERROR_STRUCTS_
+struct my_error_mgr {
+  struct jpeg_error_mgr pub;	/* "public" fields */
+
+  jmp_buf setjmp_buffer;	/* for return to caller */
+
+  char msg[JMSG_LENGTH_MAX]; /* last error message */
+};
+
+typedef struct my_error_mgr * my_error_ptr;
+#endif
+
+/*
+ * Here's the routine that will replace the standard error_exit method:
+ */
+
+METHODDEF(void)
+libjpeg_(Main_error) (j_common_ptr cinfo)
+{
+  /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
+  my_error_ptr myerr = (my_error_ptr) cinfo->err;
+
+  /* See below. */
+  (*cinfo->err->output_message) (cinfo);
+
+  /* Return control to the setjmp point */
+  longjmp(myerr->setjmp_buffer, 1);
+}
+
+/*
+ * Here's the routine that will replace the standard output_message method:
+ */
+
+METHODDEF(void)
+libjpeg_(Main_output_message) (j_common_ptr cinfo)
+{
+  my_error_ptr myerr = (my_error_ptr) cinfo->err;
+
+  (*cinfo->err->format_message) (cinfo, myerr->msg);
+}
+
+
+/*
+ * Sample routine for JPEG decompression.  We assume that the source file name
+ * is passed in.  We want to return 1 on success, 0 on error.
+ */
+
+
+static int libjpeg_(Main_size)(lua_State *L)
+{
+  /* This struct contains the JPEG decompression parameters and pointers to
+   * working space (which is allocated as needed by the JPEG library).
+   */
+  struct jpeg_decompress_struct cinfo;
+  /* We use our private extension JPEG error handler.
+   * Note that this struct must live as long as the main JPEG parameter
+   * struct, to avoid dangling-pointer problems.
+   */
+  struct my_error_mgr jerr;
+  /* More stuff */
+  FILE * infile;		/* source file */
+
+  const char *filename = luaL_checkstring(L, 1);
+
+  /* In this example we want to open the input file before doing anything else,
+   * so that the setjmp() error recovery below can assume the file is open.
+   * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
+   * requires it in order to read binary files.
+   */
+
+  if ((infile = fopen(filename, "rb")) == NULL)
+  {
+    luaL_error(L, "cannot open file <%s> for reading", filename);
+  }
+
+  /* Step 1: allocate and initialize JPEG decompression object */
+
+  /* We set up the normal JPEG error routines, then override error_exit. */
+  cinfo.err = jpeg_std_error(&jerr.pub);
+  jerr.pub.error_exit = libjpeg_(Main_error);
+  jerr.pub.output_message = libjpeg_(Main_output_message);
+  /* Establish the setjmp return context for my_error_exit to use. */
+  if (setjmp(jerr.setjmp_buffer)) {
+    /* If we get here, the JPEG code has signaled an error.
+     * We need to clean up the JPEG object, close the input file, and return.
+     */
+    jpeg_destroy_decompress(&cinfo);
+    fclose(infile);
+    luaL_error(L, jerr.msg);
+  }
+
+  /* Now we can initialize the JPEG decompression object. */
+  jpeg_create_decompress(&cinfo);
+
+  /* Step 2: specify data source (eg, a file) */
+
+  jpeg_stdio_src(&cinfo, infile);
+
+  /* Step 3: read file parameters with jpeg_read_header() */
+
+  jpeg_read_header(&cinfo, TRUE);
+  /* We can ignore the return value from jpeg_read_header since
+   *   (a) suspension is not possible with the stdio data source, and
+   *   (b) we passed TRUE to reject a tables-only JPEG file as an error.
+   * See libjpeg.doc for more info.
+   */
+
+  /* Step 4: set parameters for decompression */
+
+  /* In this example, we don't need to change any of the defaults set by
+   * jpeg_read_header(), so we do nothing here.
+   */
+
+  /* Step 5: Start decompressor */
+
+  (void) jpeg_start_decompress(&cinfo);
+  /* We can ignore the return value since suspension is not possible
+   * with the stdio data source.
+   */
+
+  lua_pushnumber(L, cinfo.output_components);
+  lua_pushnumber(L, cinfo.output_height);
+  lua_pushnumber(L, cinfo.output_width);
+
+  /* Step 8: Release JPEG decompression object */
+
+  /* This is an important step since it will release a good deal of memory. */
+  jpeg_destroy_decompress(&cinfo);
+
+  /* After finish_decompress, we can close the input file.
+   * Here we postpone it until after no more JPEG errors are possible,
+   * so as to simplify the setjmp error logic above.  (Actually, I don't
+   * think that jpeg_destroy can do an error exit, but why assume anything...)
+   */
+  fclose(infile);
+
+  /* At this point you may want to check to see whether any corrupt-data
+   * warnings occurred (test whether jerr.pub.num_warnings is nonzero).
+   */
+
+  /* And we're done! */
+  return 3;
+}
+
+static int libjpeg_(Main_load)(lua_State *L)
+{
+  const int load_from_file = luaL_checkint(L, 1);
+
+#if !defined(HAVE_JPEG_MEM_SRC)
+  if (load_from_file != 1) {
+    luaL_error(L, JPEG_MEM_SRC_ERR_MSG);
+  }
+#endif
+
+  /* This struct contains the JPEG decompression parameters and pointers to
+   * working space (which is allocated as needed by the JPEG library).
+   */
+  struct jpeg_decompress_struct cinfo;
+  /* We use our private extension JPEG error handler.
+   * Note that this struct must live as long as the main JPEG parameter
+   * struct, to avoid dangling-pointer problems.
+   */
+  struct my_error_mgr jerr;
+  /* More stuff */
+  FILE * infile;		    /* source file (if loading from file) */
+  unsigned char * inmem;    /* source memory (if loading from memory) */
+  unsigned long inmem_size; /* source memory size (bytes) */
+  JSAMPARRAY buffer;		/* Output row buffer */
+  /* int row_stride;		/1* physical row width in output buffer *1/ */
+  int i, k;
+
+  THTensor *tensor = NULL;
+
+  if (load_from_file == 1) {
+    const char *filename = luaL_checkstring(L, 2);
+
+    /* In this example we want to open the input file before doing anything else,
+     * so that the setjmp() error recovery below can assume the file is open.
+     * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
+     * requires it in order to read binary files.
+     */
+
+    if ((infile = fopen(filename, "rb")) == NULL)
+    {
+      luaL_error(L, "cannot open file <%s> for reading", filename);
+    }
+  } else {
+    /* We're loading from a ByteTensor */
+    THByteTensor *src = luaT_checkudata(L, 2, "torch.ByteTensor");
+    inmem = THByteTensor_data(src);
+    inmem_size = src->size[0];
+    infile = NULL;
+  }
+
+  /* Step 1: allocate and initialize JPEG decompression object */
+
+  /* We set up the normal JPEG error routines, then override error_exit. */
+  cinfo.err = jpeg_std_error(&jerr.pub);
+  jerr.pub.error_exit = libjpeg_(Main_error);
+  jerr.pub.output_message = libjpeg_(Main_output_message);
+  /* Establish the setjmp return context for my_error_exit to use. */
+  if (setjmp(jerr.setjmp_buffer)) {
+    /* If we get here, the JPEG code has signaled an error.
+     * We need to clean up the JPEG object, close the input file, and return.
+     */
+    jpeg_destroy_decompress(&cinfo);
+    if (infile) {
+      fclose(infile);
+    }
+    luaL_error(L, jerr.msg);
+  }
+  /* Now we can initialize the JPEG decompression object. */
+  jpeg_create_decompress(&cinfo);
+
+  /* Step 2: specify data source (eg, a file) */
+  if (load_from_file == 1) {
+    jpeg_stdio_src(&cinfo, infile);
+  } else {
+    jpeg_mem_src(&cinfo, inmem, inmem_size);
+  }
+
+  /* Step 3: read file parameters with jpeg_read_header() */
+
+  (void) jpeg_read_header(&cinfo, TRUE);
+  /* We can ignore the return value from jpeg_read_header since
+   *   (a) suspension is not possible with the stdio data source, and
+   *   (b) we passed TRUE to reject a tables-only JPEG file as an error.
+   * See libjpeg.doc for more info.
+   */
+
+  /* Step 4: set parameters for decompression */
+
+  /* In this example, we don't need to change any of the defaults set by
+   * jpeg_read_header(), so we do nothing here.
+   */
+
+  /* Step 5: Start decompressor */
+
+  (void) jpeg_start_decompress(&cinfo);
+  /* We can ignore the return value since suspension is not possible
+   * with the stdio data source.
+   */
+
+  /* We may need to do some setup of our own at this point before reading
+   * the data.  After jpeg_start_decompress() we have the correct scaled
+   * output image dimensions available, as well as the output colormap
+   * if we asked for color quantization.
+   * In this example, we need to make an output work buffer of the right size.
+   */
+
+  /* Make a one-row-high sample array that will go away when done with image */
+  const unsigned int chans = cinfo.output_components;
+  const unsigned int height = cinfo.output_height;
+  const unsigned int width = cinfo.output_width;
+  tensor = THTensor_(newWithSize3d)(chans, height, width);
+  real *tdata = THTensor_(data)(tensor);
+  buffer = (*cinfo.mem->alloc_sarray)
+    ((j_common_ptr) &cinfo, JPOOL_IMAGE, chans * width, 1);
+
+  /* Step 6: while (scan lines remain to be read) */
+  /*           jpeg_read_scanlines(...); */
+
+  /* Here we use the library's state variable cinfo.output_scanline as the
+   * loop counter, so that we don't have to keep track ourselves.
+   */
+  while (cinfo.output_scanline < height) {
+    /* jpeg_read_scanlines expects an array of pointers to scanlines.
+     * Here the array is only one element long, but you could ask for
+     * more than one scanline at a time if that's more convenient.
+     */
+    (void) jpeg_read_scanlines(&cinfo, buffer, 1);
+    const unsigned int j = cinfo.output_scanline-1;
+
+    if (chans == 3) { /* special-case for speed */
+      real *td1 = tdata + 0 * (height * width) + j * width;
+      real *td2 = tdata + 1 * (height * width) + j * width;
+      real *td3 = tdata + 2 * (height * width) + j * width;
+      const unsigned char *buf = buffer[0];
+      for(i = 0; i < width; i++) {
+        *td1++ = (real)buf[chans * i + 0];
+        *td2++ = (real)buf[chans * i + 1];
+        *td3++ = (real)buf[chans * i + 2];
+      }
+    } else if (chans == 1) { /* special-case for speed */
+      real *td = tdata + j * width;
+      for(i = 0; i < width; i++) {
+        *td++ = (real)buffer[0][i];
+      }
+    } else { /* general case */
+      for(k = 0; k < chans; k++) {
+        const unsigned int k_ = k;
+        real *td = tdata + k_ * (height * width) + j * width;
+        for(i = 0; i < width; i++) {
+          *td++ = (real)buffer[0][chans * i + k_];
+        }
+      }
+    }
+  }
+  /* Step 7: Finish decompression */
+
+  (void) jpeg_finish_decompress(&cinfo);
+  /* We can ignore the return value since suspension is not possible
+   * with the stdio data source.
+   */
+
+  /* Step 8: Release JPEG decompression object */
+
+  /* This is an important step since it will release a good deal of memory. */
+  jpeg_destroy_decompress(&cinfo);
+
+  /* After finish_decompress, we can close the input file.
+   * Here we postpone it until after no more JPEG errors are possible,
+   * so as to simplify the setjmp error logic above.  (Actually, I don't
+   * think that jpeg_destroy can do an error exit, but why assume anything...)
+   */
+  if (infile) {
+    fclose(infile);
+  }
+
+  /* At this point you may want to check to see whether any corrupt-data
+   * warnings occurred (test whether jerr.pub.num_warnings is nonzero).
+   */
+
+  /* And we're done! */
+  luaT_pushudata(L, tensor, torch_Tensor);
+  return 1;
+}
+
+/*
+ * save function
+ *
+ */
+int libjpeg_(Main_save)(lua_State *L) {
+  const int save_to_file = luaL_checkint(L, 3);
+
+#if !defined(HAVE_JPEG_MEM_DEST)
+  if (save_to_file != 1) {
+    luaL_error(L, JPEG_MEM_DEST_ERR_MSG);
+  }
+#endif
+
+  unsigned char *inmem = NULL;  /* destination memory (if saving to memory) */
+  unsigned long inmem_size = 0;  /* destination memory size (bytes) */
+
+  /* get args */
+  const char *filename = luaL_checkstring(L, 1);
+  THTensor *tensor = luaT_checkudata(L, 2, torch_Tensor);
+  THTensor *tensorc = THTensor_(newContiguous)(tensor);
+  real *tensor_data = THTensor_(data)(tensorc);
+
+  THByteTensor* tensor_dest = NULL;
+  if (save_to_file == 0) {
+    tensor_dest = luaT_checkudata(L, 5, "torch.ByteTensor");
+  }
+
+  int quality = luaL_checkint(L, 4);
+  if (quality < 0 || quality > 100) {
+    luaL_error(L, "quality should be between 0 and 100");
+  }
+
+  /* jpeg struct */
+  struct jpeg_compress_struct cinfo;
+  struct jpeg_error_mgr jerr;
+
+  /* pointer to raw image */
+  unsigned char *raw_image = NULL;
+
+  /* dimensions of the image we want to write */
+  int width=0, height=0, bytes_per_pixel=0;
+  int color_space=0;
+  if (tensorc->nDimension == 3) {
+    bytes_per_pixel = tensorc->size[0];
+    height = tensorc->size[1];
+    width = tensorc->size[2];
+    if (bytes_per_pixel == 3) {
+      color_space = JCS_RGB;
+    } else if (bytes_per_pixel == 1) {
+      color_space = JCS_GRAYSCALE;
+    } else {
+      luaL_error(L, "tensor should have 1 or 3 channels (gray or RGB)");
+    }
+  } else if (tensorc->nDimension == 2) {
+    bytes_per_pixel = 1;
+    height = tensorc->size[0];
+    width = tensorc->size[1];
+    color_space = JCS_GRAYSCALE;
+  } else {
+    luaL_error(L, "supports only 1 or 3 dimension tensors");
+  }
+
+  /* alloc raw image data */
+  raw_image = (unsigned char *)malloc((sizeof (unsigned char))*width*height*bytes_per_pixel);
+
+  /* convert tensor to raw bytes */
+  int x,y,k;
+  for (k=0; k<bytes_per_pixel; k++) {
+    for (y=0; y<height; y++) {
+      for (x=0; x<width; x++) {
+        raw_image[(y*width+x)*bytes_per_pixel+k] = *tensor_data++;
+      }
+    }
+  }
+
+  /* this is a pointer to one row of image data */
+  JSAMPROW row_pointer[1];
+  FILE *outfile = NULL;
+  if (save_to_file == 1) {
+    outfile = fopen( filename, "wb" );
+    if ( !outfile ) {
+      luaL_error(L, "Error opening output jpeg file %s\n!", filename );
+    }
+  }
+
+  cinfo.err = jpeg_std_error( &jerr );
+  jpeg_create_compress(&cinfo);
+
+  /* specify data source (eg, a file) */
+  if (save_to_file == 1) {
+    jpeg_stdio_dest(&cinfo, outfile);
+  } else {
+    jpeg_mem_dest(&cinfo, &inmem, &inmem_size);
+  }
+
+  /* Setting the parameters of the output file here */
+  cinfo.image_width = width;
+  cinfo.image_height = height;
+  cinfo.input_components = bytes_per_pixel;
+  cinfo.in_color_space = color_space;
+
+  /* default compression parameters, we shouldn't be worried about these */
+  jpeg_set_defaults( &cinfo );
+  jpeg_set_quality(&cinfo, quality, (boolean)0);
+
+  /* Now do the compression .. */
+  jpeg_start_compress( &cinfo, TRUE );
+
+  /* like reading a file, this time write one row at a time */
+  while( cinfo.next_scanline < cinfo.image_height ) {
+    row_pointer[0] = &raw_image[ cinfo.next_scanline * cinfo.image_width *  cinfo.input_components];
+    jpeg_write_scanlines( &cinfo, row_pointer, 1 );
+  }
+
+  /* similar to read file, clean up after we're done compressing */
+  jpeg_finish_compress( &cinfo );
+  jpeg_destroy_compress( &cinfo );
+
+  if (outfile != NULL) {
+    fclose( outfile );
+  }
+
+  if (save_to_file == 0) {
+
+    THByteTensor_resize1d(tensor_dest, inmem_size);  /* will fail if it's not a Byte Tensor */
+    unsigned char* tensor_dest_data = THByteTensor_data(tensor_dest);
+    memcpy(tensor_dest_data, inmem, inmem_size);
+    free(inmem);
+  }
+
+  /* some cleanup */
+  free(raw_image);
+  THTensor_(free)(tensorc);
+
+  /* success code is 1! */
+  return 1;
+}
+
+static const luaL_Reg libjpeg_(Main__)[] =
+{
+  {"size", libjpeg_(Main_size)},
+  {"load", libjpeg_(Main_load)},
+  {"save", libjpeg_(Main_save)},
+  {NULL, NULL}
+};
+
+DLL_EXPORT int libjpeg_(Main_init)(lua_State *L)
+{
+  luaT_pushmetatable(L, torch_Tensor);
+  luaT_registeratname(L, libjpeg_(Main__), "libjpeg");
+  return 1;
+}
+
+#endif
diff --git a/generic/png.c b/generic/png.c
new file mode 100755
index 0000000..5613236
--- /dev/null
+++ b/generic/png.c
@@ -0,0 +1,400 @@
+#ifndef TH_GENERIC_FILE
+#define TH_GENERIC_FILE "generic/png.c"
+#else
+
+/*
+ * Copyright 2002-2010 Guillaume Cottenceau.
+ *
+ * This software may be freely redistributed under the terms
+ * of the X11 license.
+ *
+ * Clement: modified for Torch7.
+ */
+
+static int libpng_(Main_load)(lua_State *L)
+{
+
+  png_byte header[8];    // 8 is the maximum size that can be checked
+
+  int width, height, bit_depth;
+  png_byte color_type;
+  
+  png_structp png_ptr;
+  png_infop info_ptr;
+  png_bytep * row_pointers;
+  size_t fread_ret;
+  FILE* fp;
+  libpng_inmem_buffer inmem = {0};    /* source memory (if loading from memory) */
+  libpng_errmsg errmsg;
+
+  const int load_from_file = luaL_checkint(L, 1);
+
+  if (load_from_file == 1){
+    const char *file_name = luaL_checkstring(L, 2);
+   /* open file and test for it being a png */
+    fp = fopen(file_name, "rb");
+    if (!fp)
+      luaL_error(L, "[read_png_file] File %s could not be opened for reading", file_name);
+    fread_ret = fread(header, 1, 8, fp);
+    if (fread_ret != 8)
+      luaL_error(L, "[read_png_file] File %s error reading header", file_name);
+    if (png_sig_cmp(header, 0, 8))
+      luaL_error(L, "[read_png_file] File %s is not recognized as a PNG file", file_name);
+  } else {
+    /* We're loading from a ByteTensor */
+    THByteTensor *src = luaT_checkudata(L, 2, "torch.ByteTensor");
+    inmem.buffer = THByteTensor_data(src);
+    inmem.length = src->size[0];
+    inmem.offset = 8;
+    fp = NULL;
+    if (png_sig_cmp(inmem.buffer, 0, 8))
+      luaL_error(L, "[read_png_byte_tensor] ByteTensor is not recognized as a PNG file");
+  }
+  /* initialize stuff */
+  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+
+  if (!png_ptr)
+    luaL_error(L, "[read_png] png_create_read_struct failed");
+
+  png_set_error_fn(png_ptr, &errmsg, libpng_error_fn, NULL);
+
+  info_ptr = png_create_info_struct(png_ptr);
+  if (!info_ptr) {
+    png_destroy_read_struct(&png_ptr, NULL, NULL);
+    if (fp) {
+      fclose(fp);
+    }
+    luaL_error(L, "[read_png] png_create_info_struct failed");
+  }
+
+  if (setjmp(png_jmpbuf(png_ptr))) {
+    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+    if (fp) {
+      fclose(fp);
+    }
+    luaL_error(L, "[read_png] Error during init_io: %s", errmsg.str);
+  }
+
+  if (load_from_file == 1) {
+    png_init_io(png_ptr, fp);
+  } else {
+    /* set the read callback */
+    png_set_read_fn(png_ptr,(png_voidp)&inmem, libpng_userReadData);
+  }
+  png_set_sig_bytes(png_ptr, 8);
+  png_read_info(png_ptr, info_ptr);
+
+  width      = png_get_image_width(png_ptr, info_ptr);
+  height     = png_get_image_height(png_ptr, info_ptr);
+  color_type = png_get_color_type(png_ptr, info_ptr);
+  bit_depth  = png_get_bit_depth(png_ptr, info_ptr);
+
+  /* get depth */
+  int depth = 0;
+  if (color_type == PNG_COLOR_TYPE_RGBA) {
+    depth = 4;
+  } else if (color_type == PNG_COLOR_TYPE_RGB) {
+    depth = 3;
+  } else if (color_type == PNG_COLOR_TYPE_GRAY) {
+    if (bit_depth < 8) {
+      png_set_expand_gray_1_2_4_to_8(png_ptr);
+    }
+    depth = 1;
+  } else if (color_type == PNG_COLOR_TYPE_GA) {
+    depth = 2;
+  } else if (color_type == PNG_COLOR_TYPE_PALETTE) {
+    depth = 3;
+    png_set_expand(png_ptr);
+  } else {
+    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+    if (fp) {
+      fclose(fp);
+    }
+    luaL_error(L, "[read_png_file] Unknown color space");
+  }
+
+  if (bit_depth < 8) {
+    png_set_strip_16(png_ptr);
+  }
+
+  png_read_update_info(png_ptr, info_ptr);
+
+  /* read file */
+  if (setjmp(png_jmpbuf(png_ptr))) {
+    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+    if (fp) {
+      fclose(fp);
+    }
+    luaL_error(L, "[read_png_file] Error during read_image: %s", errmsg.str);
+  }
+
+  /* alloc tensor */
+  THTensor *tensor = THTensor_(newWithSize3d)(depth, height, width);
+  real *tensor_data = THTensor_(data)(tensor);
+
+  /* alloc data in lib format */
+  row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * height);
+  int y;
+  for (y=0; y<height; y++)
+    row_pointers[y] = (png_byte*) malloc(png_get_rowbytes(png_ptr,info_ptr));
+
+  /* read image in */
+  png_read_image(png_ptr, row_pointers);
+
+  /* convert image to dest tensor */
+  int x,k;
+  if ((bit_depth == 16) && (sizeof(real) > 1)) {
+    for (k=0; k<depth; k++) {
+      for (y=0; y<height; y++) {
+	png_byte* row = row_pointers[y];
+	for (x=0; x<width; x++) {
+	  // PNG is big-endian
+	  int val = ((int)row[(x*depth+k)*2] << 8) + row[(x*depth+k)*2+1];
+	  *tensor_data++ = (real)val;
+	}
+      }
+    }
+  } else {
+    int stride = 1;
+    if (bit_depth == 16) {
+      /* PNG has 16 bit color depth, but the tensor type is byte. */
+      stride = 2;
+    }
+    for (k=0; k<depth; k++) {
+      for (y=0; y<height; y++) {
+	png_byte* row = row_pointers[y];
+	for (x=0; x<width; x++) {
+	  *tensor_data++ = (real)row[(x*depth+k)*stride];
+	  //png_byte val = row[x*depth+k];
+	  //THTensor_(set3d)(tensor, k, y, x, (real)val);
+	}
+      }
+    }
+  }
+
+
+  /* cleanup heap allocation */
+  for (y=0; y<height; y++)
+    free(row_pointers[y]);
+  free(row_pointers);
+
+  /* cleanup png structs */
+  png_read_end(png_ptr, NULL);
+  png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+
+  /* done with file */
+  if (fp) {
+    fclose(fp);
+  }
+
+  /* return tensor */
+  luaT_pushudata(L, tensor, torch_Tensor);
+
+  if (bit_depth < 8) {
+    bit_depth = 8;
+  }
+  lua_pushnumber(L, bit_depth);
+
+  return 2;
+}
+
+static int libpng_(Main_save)(lua_State *L)
+{
+  THTensor *tensor = luaT_checkudata(L, 2, torch_Tensor);
+  const char *file_name = luaL_checkstring(L, 1);
+
+  int width=0, height=0;
+  png_byte color_type = 0;
+  png_byte bit_depth = 8;
+
+  png_structp png_ptr;
+  png_infop info_ptr;
+  png_bytep * row_pointers;
+  libpng_errmsg errmsg;
+
+  /* get dims and contiguous tensor */
+  THTensor *tensorc = THTensor_(newContiguous)(tensor);
+  real *tensor_data = THTensor_(data)(tensorc);
+  long depth=0;
+  if (tensorc->nDimension == 3) {
+    depth = tensorc->size[0];
+    height = tensorc->size[1];
+    width = tensorc->size[2];
+  } else if (tensorc->nDimension == 2) {
+    depth = 1;
+    height = tensorc->size[0];
+    width = tensorc->size[1];    
+  }
+
+  /* depth check */
+  if ((depth != 1) && (depth != 3) && (depth != 4)) {
+    luaL_error(L, "[write_png_file] Depth must be 1, 3 or 4");
+  }
+  if (depth == 4) color_type = PNG_COLOR_TYPE_RGBA;
+  else if (depth == 3) color_type = PNG_COLOR_TYPE_RGB;
+  else if (depth == 1) color_type = PNG_COLOR_TYPE_GRAY;
+
+  /* create file */
+  FILE *fp = fopen(file_name, "wb");
+  if (!fp)
+    luaL_error(L, "[write_png_file] File %s could not be opened for writing", file_name);
+
+  /* initialize stuff */
+  png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+
+  if (!png_ptr)
+    luaL_error(L, "[write_png_file] png_create_write_struct failed");
+
+  png_set_error_fn(png_ptr, &errmsg, libpng_error_fn, NULL);
+
+  info_ptr = png_create_info_struct(png_ptr);
+  if (!info_ptr)
+    luaL_error(L, "[write_png_file] png_create_info_struct failed");
+
+  if (setjmp(png_jmpbuf(png_ptr)))
+    luaL_error(L, "[write_png_file] Error during init_io: %s", errmsg.str);
+
+  png_init_io(png_ptr, fp);
+
+  /* write header */
+  if (setjmp(png_jmpbuf(png_ptr)))
+    luaL_error(L, "[write_png_file] Error during writing header: %s", errmsg.str);
+
+  png_set_IHDR(png_ptr, info_ptr, width, height,
+         bit_depth, color_type, PNG_INTERLACE_NONE,
+         PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+  png_write_info(png_ptr, info_ptr);
+
+  /* convert tensor to 8bit bytes */
+  row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * height);
+  int y;
+  for (y=0; y<height; y++)
+    row_pointers[y] = (png_byte*) malloc(png_get_rowbytes(png_ptr,info_ptr));
+
+  /* convert image to dest tensor */
+  int x,k;
+  for (k=0; k<depth; k++) {
+    for (y=0; y<height; y++) {
+      png_byte* row = row_pointers[y];
+      for (x=0; x<width; x++) {
+        //row[x*depth+k] = (png_byte)THTensor_(get3d)(tensor, k, y, x);
+        row[x*depth+k] = *tensor_data++;
+      }
+    }
+  }
+
+  /* write bytes */
+  if (setjmp(png_jmpbuf(png_ptr)))
+    luaL_error(L, "[write_png_file] Error during writing bytes: %s", errmsg.str);
+
+  png_write_image(png_ptr, row_pointers);
+
+  /* end write */
+  if (setjmp(png_jmpbuf(png_ptr)))
+    luaL_error(L, "[write_png_file] Error during end of write: %s", errmsg.str);
+
+  /* cleanup png structs */
+  png_write_end(png_ptr, NULL);
+  png_destroy_write_struct(&png_ptr, &info_ptr);
+
+  /* cleanup heap allocation */
+  for (y=0; y<height; y++)
+    free(row_pointers[y]);
+  free(row_pointers);
+
+  /* cleanup */
+  fclose(fp);
+  THTensor_(free)(tensorc);
+  return 0;
+}
+
+static int libpng_(Main_size)(lua_State *L) 
+{
+  const char *filename = luaL_checkstring(L, 1);
+  png_byte header[8];    // 8 is the maximum size that can be checked
+
+  int width, height;
+  png_byte color_type;
+
+  png_structp png_ptr;
+  png_infop info_ptr;
+  libpng_errmsg errmsg;
+  size_t fread_ret;
+  /* open file and test for it being a png */
+  FILE *fp = fopen(filename, "rb");
+  if (!fp)
+    luaL_error(L, "[get_png_size] File %s could not be opened for reading", filename);
+  fread_ret = fread(header, 1, 8, fp);
+  if (fread_ret != 8)
+    luaL_error(L, "[get_png_size] File %s error reading header", filename);
+  
+  if (png_sig_cmp(header, 0, 8))
+    luaL_error(L, "[get_png_size] File %s is not recognized as a PNG file", filename);
+  
+  /* initialize stuff */
+  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+  
+  if (!png_ptr)
+    luaL_error(L, "[get_png_size] png_create_read_struct failed");
+
+  png_set_error_fn(png_ptr, &errmsg, libpng_error_fn, NULL);
+
+  info_ptr = png_create_info_struct(png_ptr);
+  if (!info_ptr)
+    luaL_error(L, "[get_png_size] png_create_info_struct failed");
+  
+  if (setjmp(png_jmpbuf(png_ptr)))
+    luaL_error(L, "[get_png_size] Error during init_io: %s", errmsg.str);
+
+  png_init_io(png_ptr, fp);
+  png_set_sig_bytes(png_ptr, 8);
+  
+  png_read_info(png_ptr, info_ptr);
+  
+  width      = png_get_image_width(png_ptr, info_ptr);
+  height     = png_get_image_height(png_ptr, info_ptr);
+  color_type = png_get_color_type(png_ptr, info_ptr);
+  png_read_update_info(png_ptr, info_ptr);
+
+  /* get depth */
+  int depth = 0;
+  if (color_type == PNG_COLOR_TYPE_RGBA)
+    depth = 4;
+  else if (color_type == PNG_COLOR_TYPE_RGB)
+    depth = 3;
+  else if (color_type == PNG_COLOR_TYPE_GRAY)
+    depth = 1;
+  else if (color_type == PNG_COLOR_TYPE_GA)
+    depth = 2;
+  else if (color_type == PNG_COLOR_TYPE_PALETTE)
+    luaL_error(L, "[get_png_size] unsupported type: PALETTE");
+  else
+    luaL_error(L, "[get_png_size] Unknown color space");
+
+  /* done with file */
+  fclose(fp);
+
+  lua_pushnumber(L, depth);
+  lua_pushnumber(L, height);
+  lua_pushnumber(L, width);
+
+  return 3;
+}
+
+static const luaL_Reg libpng_(Main__)[] =
+{
+  {"load", libpng_(Main_load)},
+  {"size", libpng_(Main_size)},
+  {"save", libpng_(Main_save)},
+  {NULL, NULL}
+};
+
+DLL_EXPORT int libpng_(Main_init)(lua_State *L)
+{
+  luaT_pushmetatable(L, torch_Tensor);
+  luaT_registeratname(L, libpng_(Main__), "libpng");
+  return 1;
+}
+
+#endif
diff --git a/generic/ppm.c b/generic/ppm.c
new file mode 100644
index 0000000..6d324e9
--- /dev/null
+++ b/generic/ppm.c
@@ -0,0 +1,183 @@
+#ifndef TH_GENERIC_FILE
+#define TH_GENERIC_FILE "generic/ppm.c"
+#else
+
+static int libppm_(Main_load)(lua_State *L)
+{
+  const char *filename = luaL_checkstring(L, 1);
+  FILE* fp = fopen ( filename, "r" );
+  if ( !fp ) {
+    luaL_error(L, "cannot open file <%s> for reading", filename);
+  }
+
+  long W,H,C;
+  char p,n;
+  int D, bps, bpc;
+
+  // magic number
+  p = (char)getc(fp);
+  if ( p != 'P' ) {
+    W = H = 0;
+    fclose(fp);
+    luaL_error(L, "corrupted file");
+  }
+
+  n = (char)getc(fp);
+
+  // Dimensions
+  W = ppm_get_long(fp);
+  H = ppm_get_long(fp);
+
+  // Max color value
+  D = ppm_get_long(fp);
+
+  // Either 8 or 16 bits per pixel
+  bps = 8;
+  if (D > 255) {
+     bps = 16;
+  }
+  bpc = bps / 8;
+
+  //printf("Loading PPM\nMAGIC: %c%c\nWidth: %ld, Height: %ld\nChannels: %d, Bits-per-pixel: %d\n", p, n, W, H, D, bps);
+
+  // load data
+  int ok = 1;
+  size_t s;
+  unsigned char *r = NULL;
+  if ( n=='6' ) {
+    C = 3;
+    s = W*H*C*bpc;
+    r = malloc(s);
+    if (fread ( r, 1, s, fp ) < s) ok = 0;
+  } else if ( n=='5' ) {
+    C = 1;
+    s = W*H*C*bpc;
+    r = malloc(s);
+    if (fread ( r, 1, s, fp ) < s) ok = 0;
+  } else if ( n=='3' ) {
+    int c,i;
+    C = 3;
+    s = W*H*C;
+    r = malloc(s);
+    for (i=0; i<s; i++) {
+      if (fscanf ( fp, "%d", &c ) != 1) { ok = 0; break; }
+      r[i] = 255*c / D;
+    }
+  } else if ( n=='2' ) {
+    int c,i;
+    C = 1;
+    s = W*H*C;
+    r = malloc(s);
+    for (i=0; i<s; i++) {
+      if (fscanf ( fp, "%d", &c ) != 1) { ok = 0; break; }
+      r[i] = 255*c / D;
+    }
+  } else {
+    W=H=C=0;
+    fclose ( fp );
+    luaL_error(L, "unsupported magic number: P%c", n);
+  }
+
+  if (!ok) {
+    fclose ( fp );
+    luaL_error(L, "corrupted file or read error");
+  }
+
+  // export tensor
+  THTensor *tensor = THTensor_(newWithSize3d)(C,H,W);
+  real *data = THTensor_(data)(tensor);
+  long i,k,j=0;
+  int val;
+  for (i=0; i<W*H; i++) {
+    for (k=0; k<C; k++) {
+       if (bpc == 1) {
+          data[k*H*W+i] = (real)r[j++];
+       } else if (bpc == 2) {
+          val = r[j] | (r[j+1] << 8);
+          j += 2;
+          data[k*H*W+i] = (real)val;
+       }
+    }
+  }
+
+  // cleanup
+  free(r);
+  fclose(fp);
+
+  // return loaded image
+  luaT_pushudata(L, tensor, torch_Tensor);
+  return 1;
+}
+
+int libppm_(Main_save)(lua_State *L) {
+  // get args
+  const char *filename = luaL_checkstring(L, 1);
+  THTensor *tensor = luaT_checkudata(L, 2, torch_Tensor);
+  THTensor *tensorc = THTensor_(newContiguous)(tensor);
+  real *data = THTensor_(data)(tensorc);
+
+  // dimensions
+  long C,H,W,N;
+  if (tensorc->nDimension == 3) {
+    C = tensorc->size[0];
+    H = tensorc->size[1];
+    W = tensorc->size[2];
+  } else if (tensorc->nDimension == 2) {
+    C = 1;
+    H = tensorc->size[0];
+    W = tensorc->size[1];
+  } else {
+    C=W=H=0;
+    luaL_error(L, "can only export tensor with geometry: HxW or 1xHxW or 3xHxW");
+  }
+  N = C*H*W;
+
+  // convert to chars
+  unsigned char *bytes = (unsigned char*)malloc(N);
+  long i,k,j=0;
+  for (i=0; i<W*H; i++) {
+    for (k=0; k<C; k++) {
+      bytes[j++] = (unsigned char)data[k*H*W+i];
+    }
+  }
+
+  // open file
+  FILE* fp = fopen(filename, "w");
+  if ( !fp ) {
+    luaL_error(L, "cannot open file <%s> for writing", filename);
+  }
+
+  // write 3 or 1 channel(s) header
+  if (C == 3) {
+    fprintf(fp, "P6\n%ld %ld\n%d\n", W, H, 255);
+  } else {
+    fprintf(fp, "P5\n%ld %ld\n%d\n", W, H, 255);
+  }
+
+  // write data
+  fwrite(bytes, 1, N, fp);
+
+  // cleanup
+  THTensor_(free)(tensorc);
+  free(bytes);
+  fclose (fp);
+
+  // return result
+  return 1;
+}
+
+static const luaL_Reg libppm_(Main__)[] =
+{
+  {"load", libppm_(Main_load)},
+  {"save", libppm_(Main_save)},
+  {NULL, NULL}
+};
+
+DLL_EXPORT int libppm_(Main_init)(lua_State *L)
+{
+  luaT_pushmetatable(L, torch_Tensor);
+  luaT_registeratname(L, libppm_(Main__), "libppm");
+  return 1;
+}
+
+#endif
diff --git a/image-1.1.alpha-0.rockspec b/image-1.1.alpha-0.rockspec
new file mode 100644
index 0000000..99e84c1
--- /dev/null
+++ b/image-1.1.alpha-0.rockspec
@@ -0,0 +1,32 @@
+package = "image"
+version = "1.1.alpha-0"
+
+source = {
+   url = "git://github.com/torch/image",
+   tag = "master"
+}
+
+description = {
+   summary = "An image library for Torch",
+   detailed = [[
+This package provides routines to load/save and manipulate images
+using Torch's Tensor data structure.
+   ]],
+   homepage = "https://github.com/torch/image",
+   license = "BSD"
+}
+
+dependencies = {
+   "torch >= 7.0",
+   "sys >= 1.0",
+   "xlua >= 1.0",
+   "dok"
+}
+
+build = {
+   type = "command",
+   build_command = [[
+cmake -E make_directory build && cd build && cmake .. -DLUALIB=$(LUALIB) -DLUA_INCDIR="$(LUA_INCDIR)" -DLUA_LIBDIR="$(LUA_LIBDIR)"  -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$(LUA_BINDIR)/.." -DCMAKE_INSTALL_PREFIX="$(PREFIX)" && $(MAKE)
+   ]],
+   install_command = "cd build && $(MAKE) install"
+}
diff --git a/image.c b/image.c
new file mode 100644
index 0000000..a6783e9
--- /dev/null
+++ b/image.c
@@ -0,0 +1,52 @@
+
+#include <TH.h>
+#include <luaT.h>
+
+#if LUA_VERSION_NUM >= 503
+#define luaL_checklong(L,n)     ((long)luaL_checkinteger(L, (n)))
+#endif
+
+
+#define torch_(NAME) TH_CONCAT_3(torch_, Real, NAME)
+#define torch_Tensor TH_CONCAT_STRING_3(torch., Real, Tensor)
+#define image_(NAME) TH_CONCAT_3(image_, Real, NAME)
+
+#ifdef max
+#undef max
+#endif
+#define max( a, b ) ( ((a) > (b)) ? (a) : (b) )
+
+#ifdef min
+#undef min
+#endif
+#define min( a, b ) ( ((a) < (b)) ? (a) : (b) )
+
+#include "font.c"
+
+#include "generic/image.c"
+#include "THGenerateAllTypes.h"
+
+DLL_EXPORT int luaopen_libimage(lua_State *L)
+{
+  image_FloatMain_init(L);
+  image_DoubleMain_init(L);
+  image_ByteMain_init(L);
+
+  lua_newtable(L);
+  lua_pushvalue(L, -1);
+  lua_setglobal(L, "image");
+
+  lua_newtable(L);
+  luaT_setfuncs(L, image_DoubleMain__, 0);
+  lua_setfield(L, -2, "double");
+
+  lua_newtable(L);
+  luaT_setfuncs(L, image_FloatMain__, 0);
+  lua_setfield(L, -2, "float");
+
+  lua_newtable(L);
+  luaT_setfuncs(L, image_ByteMain__, 0);
+  lua_setfield(L, -2, "byte");
+
+  return 1;
+}
diff --git a/init.lua b/init.lua
new file mode 100644
index 0000000..5f4b9ed
--- /dev/null
+++ b/init.lua
@@ -0,0 +1,2323 @@
+----------------------------------------------------------------------
+--
+-- Copyright (c) 2011 Ronan Collobert, Clement Farabet
+--
+-- Permission is hereby granted, free of charge, to any person obtaining
+-- a copy of this software and associated documentation files (the
+-- "Software"), to deal in the Software without restriction, including
+-- without limitation the rights to use, copy, modify, merge, publish,
+-- distribute, sublicense, and/or sell copies of the Software, and to
+-- permit persons to whom the Software is furnished to do so, subject to
+-- the following conditions:
+--
+-- The above copyright notice and this permission notice shall be
+-- included in all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+-- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+-- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+-- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+--
+----------------------------------------------------------------------
+-- description:
+--     image - an image toolBox, for Torch
+--
+-- history:
+--     July  1, 2011, 7:42PM - import from Torch5 - Clement Farabet
+----------------------------------------------------------------------
+
+require 'torch'
+require 'xlua'
+require 'dok'
+require 'libimage'
+
+local fpath = require 'sys.fpath'
+
+local startswith = function(str, prefix)
+  return string.find(str, prefix, 1, true) == 1
+end
+
+local magicJPG = string.char(0xff, 0xd8, 0xff)
+local magicPNG = string.char(0x89, 0x50, 0x4e, 0x47)
+
+----------------------------------------------------------------------
+-- include unit test function
+--
+require 'image.test'
+
+----------------------------------------------------------------------
+-- types lookups
+--
+local type2tensor = {
+   float = torch.FloatTensor(),
+   double = torch.DoubleTensor(),
+   byte = torch.ByteTensor(),
+}
+local template = function(type)
+   if type then
+      return type2tensor[type]
+   else
+      return torch.Tensor()
+   end
+end
+
+----------------------------------------------------------------------
+-- save/load in multiple formats
+--
+
+-- depth convertion helper
+local function todepth(img, depth)
+   if depth and depth == 1 then
+      if img:nDimension() == 2 then
+         -- all good
+      elseif img:size(1) == 3 or img:size(1) == 4 then
+	 img = image.rgb2y(img:narrow(1,1,3))[1]
+      elseif img:size(1) == 2 then
+         img = img:narrow(1,1,1)
+      elseif img:size(1) ~= 1 then
+         dok.error('image loaded has wrong #channels', 'image.todepth')
+      end
+   elseif depth and depth == 3 then
+      local chan = img:size(1)
+      if chan == 3 then
+         -- all good
+      elseif img:nDimension() == 2 then
+         local imgrgb = img.new(3, img:size(1), img:size(2))
+         imgrgb:select(1, 1):copy(img)
+         imgrgb:select(1, 2):copy(img)
+         imgrgb:select(1, 3):copy(img)
+         img = imgrgb
+      elseif chan == 4 then
+         img = img:narrow(1,1,3)
+      elseif chan == 1 then
+         local imgrgb = img.new(3, img:size(2), img:size(3))
+         imgrgb:select(1, 1):copy(img)
+         imgrgb:select(1, 2):copy(img)
+         imgrgb:select(1, 3):copy(img)
+         img = imgrgb
+      else
+         dok.error('image loaded has wrong #channels', 'image.todepth')
+      end
+   end
+   return img
+end
+
+local function isPNG(magicTensor)
+    local pngMagic = torch.ByteTensor({0x89,0x50,0x4e,0x47})
+    return torch.all(torch.eq(magicTensor, pngMagic))
+end
+
+local function isJPG(magicTensor)
+    -- There are many valid 4th bytes, so only check the first 3 bytes.
+    -- libjpeg should support most if not all of these:
+    -- source: http://filesignatures.net/?page=all&order=SIGNATURE&alpha=J
+    local jpgMagic = torch.ByteTensor({0xff, 0xd8, 0xff})
+    return torch.all(torch.eq(magicTensor, jpgMagic))
+end
+
+local function decompress(tensor, depth, tensortype)
+    if torch.typename(tensor) ~= 'torch.ByteTensor' then
+        dok.error('Input tensor must be a byte tensor',
+                  'image.decompress')
+    end
+    if tensor:nElement() < 4 then
+    	dok.error('Input must be either jpg or png format',
+                  'image.decompress')
+    end
+    if isJPG(tensor[{{1,3}}]) then
+        return image.decompressJPG(tensor, depth, tensortype)
+    elseif isPNG(tensor[{{1,4}}]) then
+        return image.decompressPNG(tensor, depth, tensortype)
+    else
+        dok.error('Input must be either jpg or png format',
+                  'image.decompress')
+    end
+end
+rawset(image, 'decompress', decompress)
+
+local function processPNG(img, depth, bit_depth, tensortype)
+    local MAXVAL = 255
+    if bit_depth == 16 then MAXVAL = 65535 end
+    if tensortype ~= 'byte' then
+        img:mul(1/MAXVAL)
+    end
+    img = todepth(img, depth)
+    return img
+end
+
+local function loadPNG(filename, depth, tensortype)
+   if not xlua.require 'libpng' then
+      dok.error('libpng package not found, please install libpng','image.loadPNG')
+   end
+   local load_from_file = 1
+   local a, bit_depth = template(tensortype).libpng.load(load_from_file, filename)
+   return processPNG(a, depth, bit_depth, tensortype)
+end
+rawset(image, 'loadPNG', loadPNG)
+
+local function clampImage(tensor)
+   if tensor:type() == 'torch.ByteTensor' then
+      return tensor
+   end
+   local a = torch.Tensor():resize(tensor:size()):copy(tensor)
+   a.image.saturate(a) -- bound btwn 0 and 1
+   a:mul(255)          -- remap to [0..255]
+   return a
+end
+
+local function savePNG(filename, tensor)
+   if not xlua.require 'libpng' then
+      dok.error('libpng package not found, please install libpng','image.savePNG')
+   end
+   tensor = clampImage(tensor)
+   tensor.libpng.save(filename, tensor)
+end
+rawset(image, 'savePNG', savePNG)
+
+local function decompressPNG(tensor, depth, tensortype)
+    if not xlua.require 'libpng' then
+        dok.error('libpng package not found, please install libpng',
+                  'image.decompressPNG')
+    end
+    if torch.typename(tensor) ~= 'torch.ByteTensor' then
+        dok.error('Input tensor (with compressed png) must be a byte tensor',
+                  'image.decompressPNG')
+    end
+    local load_from_file = 0
+    local a, bit_depth = template(tensortype).libpng.load(load_from_file, tensor)
+    if a == nil then
+        return nil
+    else
+        return processPNG(a, depth, bit_depth, tensortype)
+    end
+end
+rawset(image, 'decompressPNG', decompressPNG)
+
+function image.getPNGsize(filename)
+   if not xlua.require 'libpng' then
+      dok.error('libpng package not found, please install libpng','image.getPNGsize')
+   end
+   return torch.Tensor().libpng.size(filename)
+end
+
+local function processJPG(img, depth, tensortype)
+   local MAXVAL = 255
+   if tensortype ~= 'byte' then
+      img:mul(1/MAXVAL)
+   end
+   img = todepth(img, depth)
+   return img
+end
+
+local function loadJPG(filename, depth, tensortype)
+   if not xlua.require 'libjpeg' then
+      dok.error('libjpeg package not found, please install libjpeg','image.loadJPG')
+   end
+   local load_from_file = 1
+   local a = template(tensortype).libjpeg.load(load_from_file, filename)
+   if a == nil then
+      return nil
+   else
+      return processJPG(a, depth, tensortype)
+   end
+end
+rawset(image, 'loadJPG', loadJPG)
+
+local function decompressJPG(tensor, depth, tensortype)
+   if not xlua.require 'libjpeg' then
+      dok.error('libjpeg package not found, please install libjpeg',
+        'image.decompressJPG')
+   end
+   if torch.typename(tensor) ~= 'torch.ByteTensor' then
+      dok.error('Input tensor (with compressed jpeg) must be a byte tensor',
+        'image.decompressJPG')
+   end
+   local load_from_file = 0
+   local a = template(tensortype).libjpeg.load(load_from_file, tensor)
+   if a == nil then
+      return nil
+   else
+      return processJPG(a, depth, tensortype)
+   end
+end
+rawset(image, 'decompressJPG', decompressJPG)
+
+local function saveJPG(filename, tensor)
+   if not xlua.require 'libjpeg' then
+      dok.error('libjpeg package not found, please install libjpeg','image.saveJPG')
+   end
+   tensor = clampImage(tensor)
+   local save_to_file = 1
+   local quality = 75
+   tensor.libjpeg.save(filename, tensor, save_to_file, quality)
+end
+rawset(image, 'saveJPG', saveJPG)
+
+function image.getJPGsize(filename)
+   if not xlua.require 'libjpeg' then
+      dok.error('libjpeg package not found, please install libjpeg','image.getJPGsize')
+   end
+   return torch.Tensor().libjpeg.size(filename)
+end
+
+local function compressJPG(tensor, quality)
+   if not xlua.require 'libjpeg' then
+      dok.error('libjpeg package not found, please install libjpeg',
+         'image.compressJPG')
+   end
+   tensor = clampImage(tensor)
+   local b = torch.ByteTensor()
+   local save_to_file = 0
+   quality = quality or 75
+   tensor.libjpeg.save("", tensor, save_to_file, quality, b)
+   return b
+end
+rawset(image, 'compressJPG', compressJPG)
+
+local function loadPPM(filename, depth, tensortype)
+   require 'libppm'
+   local MAXVAL = 255
+   local a = template(tensortype).libppm.load(filename)
+   if tensortype ~= 'byte' then
+      a:mul(1/MAXVAL)
+   end
+   a = todepth(a, depth)
+   return a
+end
+rawset(image, 'loadPPM', loadPPM)
+
+local function savePPM(filename, tensor)
+   require 'libppm'
+   if tensor:nDimension() ~= 3 or tensor:size(1) ~= 3 then
+      dok.error('can only save 3xHxW images as PPM', 'image.savePPM')
+   end
+   tensor = clampImage(tensor)
+   tensor.libppm.save(filename, tensor)
+end
+rawset(image, 'savePPM', savePPM)
+
+local function savePGM(filename, tensor)
+   require 'libppm'
+   if tensor:nDimension() == 3 and tensor:size(1) ~= 1 then
+      dok.error('can only save 1xHxW or HxW images as PGM', 'image.savePGM')
+   end
+   tensor = clampImage(tensor)
+   tensor.libppm.save(filename, tensor)
+end
+rawset(image, 'savePGM', savePGM)
+
+local filetypes = {
+   jpg = {loader = image.loadJPG, saver = image.saveJPG},
+   png = {loader = image.loadPNG, saver = image.savePNG},
+   ppm = {loader = image.loadPPM, saver = image.savePPM},
+   -- yes, loadPPM not loadPGM
+   pgm = {loader = image.loadPPM, saver = image.savePGM}
+}
+
+filetypes['JPG']  = filetypes['jpg']
+filetypes['JPEG'] = filetypes['jpg']
+filetypes['jpeg'] = filetypes['jpg']
+filetypes['PNG']  = filetypes['png']
+filetypes['PPM']  = filetypes['ppm']
+filetypes['PGM']  = filetypes['pgm']
+rawset(image, 'supported_filetypes', filetypes)
+
+local function is_supported(suffix)
+   return filetypes[suffix] ~= nil
+end
+rawset(image, 'is_supported', is_supported)
+
+local function load(filename, depth, tensortype)
+   if not filename then
+      print(dok.usage('image.load',
+                       'loads an image into a torch.Tensor', nil,
+                       {type='string', help='path to file', req=true},
+                       {type='number', help='force destination depth: 1 | 3'},
+                       {type='string', help='type: byte | float | double'}))
+      dok.error('missing file name', 'image.load')
+   end
+
+   local ext
+
+   local f, err = io.open(filename, 'rb')
+   if not f then
+      error(err)
+   end
+   local hdr = f:read(4) or ''
+   f:close()
+
+   if startswith(hdr, magicJPG) then
+      ext = 'jpg'
+   elseif startswith(hdr, magicPNG) then
+      ext = 'png'
+   elseif hdr:match('^P[25]') then
+      ext = 'pgm'
+   elseif hdr:match('^P[36]') then
+      ext = 'ppm'
+   end
+
+   if not ext then
+      ext = string.match(filename,'%.(%a+)$')
+   end
+
+   local tensor
+   if image.is_supported(ext) then
+      tensor = filetypes[ext].loader(filename, depth, tensortype)
+   else
+      dok.error('unknown image type: ' .. ext, 'image.load')
+   end
+
+   return tensor
+end
+rawset(image, 'load', load)
+
+local function save(filename, tensor)
+   if not filename or not tensor then
+      print(dok.usage('image.save',
+                       'saves a torch.Tensor to a disk', nil,
+                       {type='string', help='path to file', req=true},
+                       {type='torch.Tensor', help='tensor to save (NxHxW, N = 1 | 3)'}))
+      dok.error('missing file name | tensor to save', 'image.save')
+   end
+   local ext = string.match(filename,'%.(%a+)$')
+   if image.is_supported(ext) then
+      tensor = filetypes[ext].saver(filename, tensor)
+   else
+      dok.error('unknown image type: ' .. ext, 'image.save')
+   end
+end
+rawset(image, 'save', save)
+
+----------------------------------------------------------------------
+-- crop
+--
+local function crop(...)
+   local dst,src,startx,starty,endx,endy
+   local format,width,height
+   local args = {...}
+   if select('#',...) == 6 then
+      dst = args[1]
+      src = args[2]
+      startx = args[3]
+      starty = args[4]
+      endx = args[5]
+      endy = args[6]
+   elseif select('#',...) == 5 then
+      if type(args[3]) == 'string' then
+         dst = args[1]
+         src = args[2]
+         format = args[3]
+         width = args[4]
+         height = args[5]
+      else
+         src = args[1]
+         startx = args[2]
+         starty = args[3]
+         endx = args[4]
+         endy = args[5]
+      end
+   elseif select('#',...) == 4 then
+      if type(args[2]) == 'string' then
+         src = args[1]
+         format = args[2]
+         width = args[3]
+         height = args[4]
+      else
+         dst = args[1]
+         src = args[2]
+         startx = args[3]
+         starty = args[4]
+      end
+   elseif select('#',...) == 3 then
+      src = args[1]
+      startx = args[2]
+      starty = args[3]
+   else
+      print(dok.usage('image.crop',
+                       'crop an image', nil,
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='number', help='start x', req=true},
+                       {type='number', help='start y', req=true},
+                       {type='number', help='end x'},
+                       {type='number', help='end y'},
+                       '',
+                       {type='torch.Tensor', help='destination', req=true},
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='number', help='start x', req=true},
+                       {type='number', help='start y', req=true},
+                       {type='number', help='end x'},
+                       {type='number', help='end y'},
+                       '',
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='string', help='format: "c" or "tl" or "tr" or "bl" or "br"', req=true},
+                       {type='number', help='width', req=true},
+                       {type='number', help='height', req=true},
+                       '',
+                       {type='torch.Tensor', help='destination', req=true},
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='string', help='format: "c" or "tl" or "tr" or "bl" or "br"', req=true},
+                       {type='number', help='width', req=true},
+                       {type='number', help='height', req=true}))
+      dok.error('incorrect arguments', 'image.crop')
+   end
+   if format then
+      local iwidth,iheight
+      if src:nDimension() == 3 then
+         iwidth,iheight = src:size(3),src:size(2)
+      else
+         iwidth,iheight = src:size(2),src:size(1)
+      end
+      local x1, y1
+      if format == 'c' then
+         x1, y1 = math.floor((iwidth-width)/2), math.floor((iheight-height)/2)
+      elseif format == 'tl' then
+         x1, y1 = 0, 0
+      elseif format == 'tr' then
+         x1, y1 = iwidth-width, 0
+      elseif format == 'bl' then
+         x1, y1 = 0, iheight-height
+      elseif format == 'br' then
+         x1, y1 = iwidth-width, iheight-height
+      else
+         error('crop format must be "c"|"tl"|"tr"|"bl"|"br"')
+      end
+      return crop(dst, src, x1, y1, x1+width, y1+height)
+   end
+   if endx==nil then
+      return src.image.cropNoScale(src,dst,startx,starty)
+   else
+      local depth=1
+      local x
+      if src:nDimension() > 2 then
+         x = src.new(src:size(1),endy-starty,endx-startx)
+      else
+         x = src.new(endy-starty,endx-startx)
+      end
+      src.image.cropNoScale(src,x,startx,starty)
+      if dst then
+         image.scale(dst, x)
+      else
+         dst = x
+      end
+   end
+   return dst
+end
+rawset(image, 'crop', crop)
+
+----------------------------------------------------------------------
+-- translate
+--
+local function translate(...)
+   local dst,src,x,y
+   local args = {...}
+   if select('#',...) == 4 then
+      dst = args[1]
+      src = args[2]
+      x = args[3]
+      y = args[4]
+   elseif select('#',...) == 3 then
+      src = args[1]
+      x = args[2]
+      y = args[3]
+   else
+      print(dok.usage('image.translate',
+                       'translate an image', nil,
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='number', help='horizontal translation', req=true},
+                       {type='number', help='vertical translation', req=true},
+                       '',
+                       {type='torch.Tensor', help='destination', req=true},
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='number', help='horizontal translation', req=true},
+                       {type='number', help='vertical translation', req=true}))
+      dok.error('incorrect arguments', 'image.translate')
+   end
+   dst = dst or src.new()
+   dst:resizeAs(src)
+   dst:zero()
+   src.image.translate(src,dst,x,y)
+   return dst
+end
+rawset(image, 'translate', translate)
+
+----------------------------------------------------------------------
+-- scale
+--
+local function scale(...)
+   local dst,src,width,height,mode,size
+   local args = {...}
+   if select('#',...) == 4 then
+      src = args[1]
+      width = args[2]
+      height = args[3]
+      mode = args[4]
+   elseif select('#',...) == 3 then
+      if type(args[3]) == 'string' then
+         if type(args[2]) == 'string' or type(args[2]) == 'number' then
+            src = args[1]
+            size = args[2]
+            mode = args[3]
+         else
+            dst = args[1]
+            src = args[2]
+            mode = args[3]
+         end
+      else
+         src = args[1]
+         width = args[2]
+         height = args[3]
+      end
+   elseif select('#',...) == 2 then
+      if type(args[2]) == 'string' or type(args[2]) == 'number' then
+         src = args[1]
+         size = args[2]
+      else
+         dst = args[1]
+         src = args[2]
+      end
+   else
+      print(dok.usage('image.scale',
+                       'rescale an image (geometry)', nil,
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='number', help='destination width', req=true},
+                       {type='number', help='destination height', req=true},
+                       {type='string', help='mode: bilinear | bicubic |simple', default='bilinear'},
+                       '',
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='string | number', help='destination size: "WxH" or "MAX" or "^MIN" or "*SC" or "*SCd/SCn" or MAX', req=true},
+                       {type='string', help='mode: bilinear | bicubic | simple', default='bilinear'},
+                       '',
+                       {type='torch.Tensor', help='destination image', req=true},
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='string', help='mode: bilinear | bicubic | simple', default='bilinear'}))
+      dok.error('incorrect arguments', 'image.scale')
+   end
+   if size then
+      local iwidth, iheight
+      if src:nDimension() == 3 then
+         iwidth, iheight = src:size(3),src:size(2)
+      else
+         iwidth, iheight = src:size(2),src:size(1)
+      end
+
+      -- MAX?
+      local imax = math.max(iwidth, iheight)
+      local omax = tonumber(size)
+      if omax then
+         height = iheight*omax/imax
+         width = iwidth*omax/imax
+      end
+
+      -- WxH?
+      if not width or not height then
+         width, height = size:match('(%d+)x(%d+)')
+      end
+
+      -- ^MIN?
+      if not width or not height then
+         local imin = math.min(iwidth, iheight)
+         local omin = tonumber(size:match('%^(%d+)'))
+         if omin then
+            height = iheight*omin/imin
+            width = iwidth*omin/imin
+         end
+      end
+
+      -- *SCn/SCd?
+      if not width or not height then
+         local scn, scd = size:match('%*(%d+)%/(%d+)')
+         if scn and scd then
+            height = iheight*scn/scd
+            width = iwidth*scn/scd
+         end
+      end
+
+      -- *SC?
+      if not width or not height then
+         local sc = tonumber(size:match('%*(.+)'))
+         if sc then
+            height = iheight*sc
+            width = iwidth*sc
+         end
+      end
+   end
+   if not dst and (not width or not height) then
+      dok.error('could not find valid dest size', 'image.scale')
+   end
+   if not dst then
+      height = math.max(height, 1)
+      width = math.max(width, 1)
+      if src:nDimension() == 3 then
+         dst = src.new(src:size(1), height, width)
+      else
+         dst = src.new(height, width)
+      end
+   end
+   mode = mode or 'bilinear'
+   if mode=='bilinear' then
+      src.image.scaleBilinear(src,dst)
+   elseif mode=='bicubic' then
+      src.image.scaleBicubic(src,dst)
+   elseif mode=='simple' then
+      src.image.scaleSimple(src,dst)
+   else
+      dok.error('mode must be one of: simple | bicubic | bilinear', 'image.scale')
+   end
+   return dst
+end
+rawset(image, 'scale', scale)
+
+----------------------------------------------------------------------
+-- rotate
+--
+local function rotate(...)
+   local dst,src,theta, mode
+   local args = {...}
+   if select('#',...) == 4 then
+      dst = args[1]
+      src = args[2]
+      theta = args[3]
+      mode = args[4]
+   elseif select('#',...) == 3 then
+      if type(args[2]) == 'number' then
+	 src = args[1]
+	 theta = args[2]
+	 mode = args[3]
+      else
+	 dst = args[1]
+	 src = args[2]
+	 theta = args[3]
+      end
+   elseif select('#',...) == 2 then
+      src = args[1]
+      theta = args[2]
+   else
+      print(dok.usage('image.rotate',
+                       'rotate an image by theta radians', nil,
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='number', help='rotation angle (in radians)', req=true},
+		       {type='string', help='mode: simple | bilinear', default='simple'},
+                       '',
+                       {type='torch.Tensor', help='destination', req=true},
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='number', help='rotation angle (in radians)', req=true},
+		       {type='string', help='mode: simple | bilinear', default='simple'}))
+      dok.error('incorrect arguments', 'image.rotate')
+   end
+   dst = dst or src.new()
+   dst:resizeAs(src)
+   mode = mode or 'simple'
+   if mode == 'simple' then
+      src.image.rotate(src,dst,theta)
+   elseif mode == 'bilinear' then
+      src.image.rotateBilinear(src,dst,theta)
+   else
+      dok.error('mode must be one of: simple | bilinear', 'image.rotate')
+   end
+   return dst
+end
+rawset(image, 'rotate', rotate)
+
+----------------------------------------------------------------------
+-- polar
+--
+local function polar(...)
+   local dst,src,interp,mode
+   local args = {...}
+   if select('#',...) == 4 then
+      dst    = args[1]
+      src    = args[2]
+      interp = args[3]
+      mode   = args[4]
+    elseif select('#',...) == 3 then
+      if type(args[2]) == 'string' then
+        src    = args[1]
+        interp = args[2]
+        mode   = args[3]
+      else
+        dst    = args[1]
+        src    = args[2]
+        interp = args[3]
+      end
+   elseif select('#',...) == 2 then
+      if type(args[2]) == 'string' then
+        src    = args[1]
+        interp = args[2]
+      else
+        dst  = args[1]
+        src  = args[2]
+      end
+   elseif select('#',...) == 1 then
+     src = args[1]
+   else
+      print(dok.usage('image.polar',
+                       'convert an image to polar coordinates', nil,
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='string', help='interpolation: simple | bilinear', default='simple'},
+                       {type='string', help='mode: valid | full', default='valid'},
+                       '',
+                       {type='torch.Tensor', help='destination', req=true},
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='string', help='interpolation: simple | bilinear', default='simple'},
+                       {type='string', help='mode: valid | full', default='valid'}))
+      dok.error('incorrect arguments', 'image.polar')
+   end
+   interp = interp or 'valid'
+   mode = mode or 'simple'
+   if dst == nil then
+      local maxDist = math.floor(math.max(src:size(2), src:size(3)))
+      dst = src.new()
+      dst:resize(src:size(1), maxDist, maxDist)
+   end
+   if interp == 'simple' then
+      if mode == 'full' then
+        src.image.polar(src,dst,1)
+      elseif mode == 'valid' then
+        src.image.polar(src,dst,0)
+      else
+        dok.error('mode must be one of: valid | full', 'image.polar')
+      end
+   elseif interp == 'bilinear' then
+      if mode == 'full' then
+        src.image.polarBilinear(src,dst,1)
+      elseif mode == 'valid' then
+        src.image.polarBilinear(src,dst,0)
+      else
+        dok.error('mode must be one of: valid | full', 'image.polar')
+      end
+   else
+      dok.error('interpolation must be one of: simple | bilinear', 'image.polar')
+   end
+   return dst
+end
+rawset(image, 'polar', polar)
+
+----------------------------------------------------------------------
+-- logpolar
+--
+local function logpolar(...)
+   local dst,src,interp,mode
+   local args = {...}
+   if select('#',...) == 4 then
+      dst    = args[1]
+      src    = args[2]
+      interp = args[3]
+      mode   = args[4]
+    elseif select('#',...) == 3 then
+      if type(args[2]) == 'string' then
+        src    = args[1]
+        interp = args[2]
+        mode   = args[3]
+      else
+        dst    = args[1]
+        src    = args[2]
+        interp = args[3]
+      end
+   elseif select('#',...) == 2 then
+      if type(args[2]) == 'string' then
+        src    = args[1]
+        interp = args[2]
+      else
+        dst  = args[1]
+        src  = args[2]
+      end
+   elseif select('#',...) == 1 then
+     src = args[1]
+   else
+      print(dok.usage('image.logpolar',
+                       'convert an image to log-polar coordinates', nil,
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='string', help='interpolation: simple | bilinear', default='simple'},
+                       {type='string', help='mode: valid | full', default='valid'},
+                       '',
+                       {type='torch.Tensor', help='destination', req=true},
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='string', help='interpolation: simple | bilinear', default='simple'},
+                       {type='string', help='mode: valid | full', default='valid'}))
+      dok.error('incorrect arguments', 'image.polar')
+   end
+   interp = interp or 'valid'
+   mode = mode or 'simple'
+   if dst == nil then
+      local maxDist = math.floor(math.max(src:size(2), src:size(3)))
+      dst = src.new()
+      dst:resize(src:size(1), maxDist, maxDist)
+   end
+   if interp == 'simple' then
+      if mode == 'full' then
+        src.image.logPolar(src,dst,1)
+      elseif mode == 'valid' then
+        src.image.logPolar(src,dst,0)
+      else
+        dok.error('mode must be one of: valid | full', 'image.logpolar')
+      end
+   elseif interp == 'bilinear' then
+      if mode == 'full' then
+        src.image.logPolarBilinear(src,dst,1)
+      elseif mode == 'valid' then
+        src.image.logPolarBilinear(src,dst,0)
+      else
+        dok.error('mode must be one of: valid | full', 'image.logpolar')
+      end
+   else
+      dok.error('interpolation must be one of: simple | bilinear', 'image.logpolar')
+   end
+   return dst
+end
+rawset(image, 'logpolar', logpolar)
+
+----------------------------------------------------------------------
+-- warp
+--
+local function warp(...)
+   local dst,src,field
+   local mode = 'bilinear'
+   local offset_mode = true
+   local clamp_mode = 'clamp'
+   local pad_value = 0
+   local args = {...}
+   local nargs = select('#',...)
+   local bad_args = false
+   if nargs == 2 then
+      src = args[1]
+      field = args[2]
+   elseif nargs >= 3 then
+      if type(args[3]) == 'string' then
+         -- No destination tensor
+         src = args[1]
+         field = args[2]
+         mode = args[3]
+         if nargs >= 4 then offset_mode = args[4] end
+         if nargs >= 5 then clamp_mode = args[5] end
+         if nargs >= 6 then
+           assert(clamp_mode == 'pad', 'pad_value can only be specified if' ..
+                                       ' clamp_mode = "pad"')
+           pad_value = args[6]
+         end
+         if nargs >= 7 then bad_args = true end
+      else
+         -- With Destination tensor
+         dst = args[1]
+         src = args[2]
+         field = args[3]
+         if nargs >= 4 then mode = args[4] end
+         if nargs >= 5 then offset_mode = args[5] end
+         if nargs >= 6 then clamp_mode = args[6] end
+         if nargs >= 7 then
+           assert(clamp_mode == 'pad', 'pad_value can only be specified if' ..
+                                       ' clamp_mode = "pad"')
+           pad_value = args[7]
+         end
+         if nargs >= 8 then bad_args = true end
+      end
+   end
+   if bad_args then
+      print(dok.usage('image.warp',
+         'warp an image, according to a flow field', nil,
+         {type='torch.Tensor', help='input image (KxHxW)', req=true},
+         {type='torch.Tensor', help='(y,x) flow field (2xHxW)', req=true},
+         {type='string', help='mode: lanczos | bicubic | bilinear | simple', default='bilinear'},
+         {type='string', help='offset mode (add (x,y) to flow field)', default=true},
+         {type='string', help='clamp mode: how to handle interp of samples off the input image (clamp | pad)', default='clamp'},
+         '',
+         {type='torch.Tensor', help='destination', req=true},
+         {type='torch.Tensor', help='input image (KxHxW)', req=true},
+         {type='torch.Tensor', help='(y,x) flow field (2xHxW)', req=true},
+         {type='string', help='mode: lanczos | bicubic | bilinear | simple', default='bilinear'},
+         {type='string', help='offset mode (add (x,y) to flow field)', default=true},
+         {type='string', help='clamp mode: how to handle interp of samples off the input image (clamp | pad)', default='clamp'},
+         {type='number', help='pad value: value to pad image. Can only be set when clamp mode equals "pad"', default=0}))
+      dok.error('incorrect arguments', 'image.warp')
+   end
+   -- This is a little messy, but convert mode string to an enum
+   if (mode == 'simple') then
+      mode = 0
+   elseif (mode == 'bilinear') then
+      mode = 1
+   elseif (mode == 'bicubic') then
+      mode = 2
+   elseif (mode == 'lanczos') then
+      mode = 3
+   else
+      dok.error('Incorrect arguments (mode is not lanczos | bicubic | bilinear | simple)!', 'image.warp')
+   end
+   if (clamp_mode == 'clamp') then
+      clamp_mode = 0
+   elseif (clamp_mode == 'pad') then
+      clamp_mode = 1
+   else
+      dok.error('Incorrect arguments (clamp_mode is not clamp | pad)!', 'image.warp')
+   end
+
+   local dim2 = false
+   if src:nDimension() == 2 then
+      dim2 = true
+      src = src:reshape(1,src:size(1),src:size(2))
+   end
+   dst = dst or src.new()
+   dst:resize(src:size(1), field:size(2), field:size(3))
+
+   src.image.warp(dst, src, field, mode, offset_mode, clamp_mode, pad_value)
+   if dim2 then
+      dst = dst[1]
+   end
+   return dst
+end
+rawset(image, 'warp', warp)
+
+----------------------------------------------------------------------
+-- hflip
+--
+local function hflip(...)
+   local dst,src
+   local args = {...}
+   if select('#',...) == 2 then
+      dst = args[1]
+      src = args[2]
+   elseif select('#',...) == 1 then
+      src = args[1]
+   else
+      print(dok.usage('image.hflip',
+                       'flips an image horizontally (left/right)', nil,
+                       {type='torch.Tensor', help='input image', req=true},
+                       '',
+                       {type='torch.Tensor', help='destination', req=true},
+                       {type='torch.Tensor', help='input image', req=true}))
+      dok.error('incorrect arguments', 'image.hflip')
+   end
+
+   if (src:dim() == 2) and (not src:isContiguous()) then
+     dok.error('2D input tensor is not contiguous', 'image.hflip')
+   end
+
+   dst = dst or src.new()
+   local original_size = src:size()
+   if src:nDimension() == 2 then
+      src = src:new():resize(1,src:size(1),src:size(2))
+   end
+   dst:resizeAs(src)
+
+   if not dst:isContiguous() then
+     dok.error('destination tensor is not contiguous', 'image.hflip')
+   end
+
+   dst.image.hflip(dst, src)
+   dst:resize(original_size)
+   return dst
+end
+rawset(image, 'hflip', hflip)
+
+----------------------------------------------------------------------
+-- vflip
+--
+local function vflip(...)
+   local dst,src
+   local args = {...}
+   if select('#',...) == 2 then
+      dst = args[1]
+      src = args[2]
+   elseif select('#',...) == 1 then
+      src = args[1]
+   else
+      print(dok.usage('image.vflip',
+                       'flips an image vertically (upside-down)', nil,
+                       {type='torch.Tensor', help='input image', req=true},
+                       '',
+                       {type='torch.Tensor', help='destination', req=true},
+                       {type='torch.Tensor', help='input image', req=true}))
+      dok.error('incorrect arguments', 'image.vflip')
+   end
+
+   if (src:dim() == 2) and (not src:isContiguous()) then
+     dok.error('2D input tensor is not contiguous', 'image.vflip')
+   end
+
+   dst = dst or src.new()
+   local original_size = src:size()
+   if src:nDimension() == 2 then
+      src = src:new():resize(1,src:size(1),src:size(2))
+   end
+   dst:resizeAs(src)
+
+   if not dst:isContiguous() then
+     dok.error('destination tensor is not contiguous', 'image.vflip')
+   end
+
+   dst.image.vflip(dst, src)
+   dst:resize(original_size)
+   return dst
+end
+rawset(image, 'vflip', vflip)
+
+----------------------------------------------------------------------
+-- flip (specify dimension, up to 5D tensor)
+--
+local function flip(...)
+   local dst,src,flip_dim
+   local args = {...}
+   if select('#',...) == 3 then
+      dst = args[1]
+      src = args[2]
+      flip_dim = args[3]
+   elseif select('#',...) == 2 then
+      src = args[1]
+      flip_dim = args[2]
+   else
+      print(dok.usage('image.flip',
+                       'flips an image along a specified dimension', nil,
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='number', help='Dimension to flip', req=true},
+                       '',
+                       {type='torch.Tensor', help='destination', req=true},
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='number', help='Dimension to flip', req=true}))
+      dok.error('incorrect arguments', 'image.flip')
+   end
+   assert(src:nDimension() <= 5, 'too many input dims (up to 5D supported)')
+   assert(flip_dim <= src:nDimension() and flip_dim >= 1, 'Bad flip dimension')
+
+   if not src:isContiguous() then
+     dok.error('input tensor is not contiguous', 'image.flip')
+   end
+
+   dst = dst or src.new()
+   local original_size = src:size()
+   local flip_dim_cpp
+   if src:nDimension() == 1 then
+      src = src:new():resize(1, 1, 1, 1, src:size(1))
+      flip_dim_cpp = flip_dim + 4
+   elseif src:nDimension() == 2 then
+      src = src:new():resize(1, 1, 1, src:size(1), src:size(2))
+      flip_dim_cpp = flip_dim + 3
+   elseif src:nDimension() == 3 then
+      src = src:new():resize(1, 1, src:size(1), src:size(2),src:size(3))
+      flip_dim_cpp = flip_dim + 2
+   elseif src:nDimension() == 4 then
+      src = src:new():resize(1, src:size(1), src:size(2), src:size(3),
+        src:size(4))
+      flip_dim_cpp = flip_dim + 1
+   else
+      flip_dim_cpp = flip_dim
+   end
+   dst:resizeAs(src)
+
+   if not dst:isContiguous() then
+     dok.error('destination tensor is not contiguous', 'image.flip')
+   end
+
+   dst.image.flip(dst, src, flip_dim_cpp)
+   dst:resize(original_size)
+   return dst
+end
+
+rawset(image, 'flip', flip)
+
+----------------------------------------------------------------------
+-- convolve(dst,src,ker,type)
+-- convolve(dst,src,ker)
+-- dst = convolve(src,ker,type)
+-- dst = convolve(src,ker)
+--
+local function convolve(...)
+   local dst,src,kernel,mode
+   local args = {...}
+   if select('#',...) == 4 then
+      dst = args[1]
+      src = args[2]
+      kernel = args[3]
+      mode = args[4]
+   elseif select('#',...) == 3 then
+      if type(args[3]) == 'string' then
+         src = args[1]
+         kernel = args[2]
+         mode = args[3]
+      else
+         dst = args[1]
+         src = args[2]
+         kernel = args[3]
+      end
+   elseif select('#',...) == 2 then
+      src = args[1]
+      kernel = args[2]
+   else
+      print(dok.usage('image.convolve',
+                       'convolves an input image with a kernel, returns the result', nil,
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='torch.Tensor', help='kernel', req=true},
+                       {type='string', help='type: full | valid | same', default='valid'},
+                       '',
+                       {type='torch.Tensor', help='destination', req=true},
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='torch.Tensor', help='kernel', req=true},
+                       {type='string', help='type: full | valid | same', default='valid'}))
+      dok.error('incorrect arguments', 'image.convolve')
+   end
+   if mode and mode ~= 'valid' and mode ~= 'full' and mode ~= 'same' then
+      dok.error('mode has to be one of: full | valid | same', 'image.convolve')
+   end
+   local md = (((mode == 'full') or (mode == 'same')) and 'F') or 'V'
+   if kernel:nDimension() == 2 and src:nDimension() == 3 then
+      local k3d = src.new(src:size(1), kernel:size(1), kernel:size(2))
+      for i = 1,src:size(1) do
+         k3d[i]:copy(kernel)
+      end
+      kernel = k3d
+   end
+   if dst then
+      torch.conv2(dst,src,kernel,md)
+   else
+      dst = torch.conv2(src,kernel,md)
+   end
+   if mode == 'same' then
+      local cx = dst:dim()
+      local cy = cx-1
+      local ofy = math.ceil(kernel:size(cy)/2)
+      local ofx = math.ceil(kernel:size(cx)/2)
+      dst = dst:narrow(cy, ofy, src:size(cy)):narrow(cx, ofx, src:size(cx))
+   end
+   return dst
+end
+rawset(image, 'convolve', convolve)
+
+----------------------------------------------------------------------
+-- compresses an image between min and max
+--
+local function minmax(args)
+   local tensor = args.tensor
+   local min = args.min
+   local max = args.max
+   local symm = args.symm or false
+   local inplace = args.inplace or false
+   local saturate = args.saturate or false
+   local tensorOut = args.tensorOut or (inplace and tensor)
+      or torch.Tensor(tensor:size()):copy(tensor)
+
+   -- resize
+   if args.tensorOut then
+      tensorOut:resize(tensor:size()):copy(tensor)
+   end
+
+   -- saturate useless if min/max inferred
+   if min == nil and max == nil then
+      saturate = false
+   end
+
+   -- rescale min
+   local fmin = 0
+   if (min == nil) then
+      if args.symm then
+         fmin = math.max(math.abs(tensorOut:min()),math.abs(tensorOut:max()))
+         min = -fmin
+      else
+         min = tensorOut:min()
+      end
+   end
+   if (min ~= 0) then tensorOut:add(-min) end
+
+   -- rescale for max
+   if (max == nil) then
+      if args.symm then
+         max = fmin*2
+      else
+         max = tensorOut:max()
+      end
+   else
+      max = max - min
+   end
+   if (max ~= 0) then tensorOut:div(max) end
+
+   -- saturate
+   if saturate then
+      tensorOut.image.saturate(tensorOut)
+   end
+
+   -- and return
+   return tensorOut
+end
+rawset(image, 'minmax', minmax)
+
+local function toDisplayTensor(...)
+   -- usage
+   local _, input, padding, nrow, scaleeach, min, max, symm, saturate = dok.unpack(
+      {...},
+      'image.toDisplayTensor',
+      'given a pack of tensors, returns a single tensor that contains a grid of all in the pack',
+      {arg='input',type='torch.Tensor | table', help='input (HxW or KxHxW or Kx3xHxW or list)',req=true},
+      {arg='padding', type='number', help='number of padding pixels between images', default=0},
+      {arg='nrow',type='number',help='number of images per row', default=6},
+      {arg='scaleeach', type='boolean', help='individual scaling for list of images', default=false},
+      {arg='min', type='number', help='lower-bound for range'},
+      {arg='max', type='number', help='upper-bound for range'},
+      {arg='symmetric',type='boolean',help='if on, images will be displayed using a symmetric dynamic range, useful for drawing filters', default=false},
+      {arg='saturate', type='boolean', help='saturate (useful when min/max are lower than actual min/max', default=true}
+   )
+
+   local packed = torch.Tensor()
+   if type(input) == 'table' then
+      -- pack images in single tensor
+      local ndims = input[1]:dim()
+      local channels = ((ndims == 2) and 1) or input[1]:size(1)
+      local height = input[1]:size(ndims-1)
+      local width = input[1]:size(ndims)
+      packed:resize(#input,channels,height,width)
+      for i,img in ipairs(input) do
+         packed[i]:copy(input[i])
+      end
+   elseif torch.isTensor(input) then
+      packed:resize(input:size()):copy(input)
+   else
+      error('Unknown or incompatbile type of input: ' .. torch.type(input))
+   end
+
+   -- scale each
+   if scaleeach and (
+         (packed:dim() == 4 and (packed:size(2) == 3 or packed:size(2) == 1))
+         or
+         (packed:dim() == 3 and (packed:size(1) ~= 1 and packed:size(1) ~= 3))
+         ) then
+      for i=1,packed:size(1) do
+         image.minmax{tensor=packed[i], inplace=true, min=min, max=max, symm=symm, saturate=saturate}
+      end
+   end
+
+   local grid = torch.Tensor()
+   if packed:dim() == 4 and (packed:size(2) == 3 or packed:size(2) == 1) then
+      -- arbitrary number of color images: lay them out on a grid
+      local nmaps = packed:size(1)
+      local xmaps = math.min(nrow, nmaps)
+      local ymaps = math.ceil(nmaps / xmaps)
+      local height = packed:size(3)+padding
+      local width = packed:size(4)+padding
+      grid:resize(packed:size(2), height*ymaps, width*xmaps):fill(packed:max())
+      local k = 1
+      for y = 1,ymaps do
+         for x = 1,xmaps do
+            if k > nmaps then break end
+            grid:narrow(2,(y-1)*height+1+padding/2,height-padding):narrow(3,(x-1)*width+1+padding/2,width-padding):copy(packed[k])
+            k = k + 1
+         end
+      end
+   elseif packed:dim() == 2  or (packed:dim() == 3 and (packed:size(1) == 1 or packed:size(1) == 3)) then
+      -- Rescale range
+      image.minmax{tensor=packed, inplace=true, min=min, max=max, symm=symm, saturate=saturate}
+      return packed
+   elseif packed:dim() == 3 then
+      -- arbitrary number of channels: lay them out on a grid
+      local nmaps = packed:size(1)
+      local xmaps = math.min(nrow, nmaps)
+      local ymaps = math.ceil(nmaps / xmaps)
+      local height = packed:size(2)+padding
+      local width = packed:size(3)+padding
+      grid:resize(height*ymaps, width*xmaps):fill(packed:max())
+      local k = 1
+      for y = 1,ymaps do
+         for x = 1,xmaps do
+            if k > nmaps then break end
+            grid:narrow(1,(y-1)*height+1+padding/2,height-padding):narrow(2,(x-1)*width+1+padding/2,width-padding):copy(packed[k])
+            k = k + 1
+         end
+      end
+   else
+      xerror('packed must be a HxW or KxHxW or Kx3xHxW tensor, or a list of tensors', 'image.toDisplayTensor')
+   end
+
+   if not scaleeach then
+      image.minmax{tensor=grid, inplace=true, min=min, max=max, symm=symm, saturate=saturate}
+   end
+   return grid
+end
+rawset(image,'toDisplayTensor',toDisplayTensor)
+
+----------------------------------------------------------------------
+-- super generic display function
+--
+local function display(...)
+   -- usage
+   local _, input, zoom, min, max, legend, w, ox, oy, scaleeach, gui, offscreen, padding, symm, nrow, saturate = dok.unpack(
+      {...},
+      'image.display',
+      'displays a single image, with optional saturation/zoom',
+      {arg='image', type='torch.Tensor | table', help='image (HxW or KxHxW or Kx3xHxW or list)', req=true},
+      {arg='zoom', type='number', help='display zoom', default=1},
+      {arg='min', type='number', help='lower-bound for range'},
+      {arg='max', type='number', help='upper-bound for range'},
+      {arg='legend', type='string', help='legend', default='image.display'},
+      {arg='win', type='qt window', help='window descriptor'},
+      {arg='x', type='number', help='x offset (only if win is given)', default=0},
+      {arg='y', type='number', help='y offset (only if win is given)', default=0},
+      {arg='scaleeach', type='boolean', help='individual scaling for list of images', default=false},
+      {arg='gui', type='boolean', help='if on, user can zoom in/out (turn off for faster display)',
+       default=true},
+      {arg='offscreen', type='boolean', help='offscreen rendering (to generate images)',
+       default=false},
+      {arg='padding', type='number', help='number of padding pixels between images', default=0},
+      {arg='symmetric',type='boolean',help='if on, images will be displayed using a symmetric dynamic range, useful for drawing filters', default=false},
+      {arg='nrow',type='number',help='number of images per row', default=6},
+      {arg='saturate', type='boolean', help='saturate (useful when min/max are lower than actual min/max', default=true}
+   )
+
+   -- dependencies
+   require 'qt'
+   require 'qttorch'
+   require 'qtwidget'
+   require 'qtuiloader'
+
+   input = image.toDisplayTensor{input=input, padding=padding, nrow=nrow, saturate=saturate,
+                                 scaleeach=scaleeach, min=min, max=max, symmetric=symm}
+
+   -- if 2 dims or 3 dims and 1/3 channels, then we treat it as a single image
+   if input:nDimension() == 2  or (input:nDimension() == 3 and (input:size(1) == 1 or input:size(1) == 3)) then
+      -- Compute width
+      local d = input:nDimension()
+      local x = input:size(d)*zoom
+      local y = input:size(d-1)*zoom
+
+      -- if gui active, then create interactive window (with zoom, clicks and so on)
+      if gui and not w and not offscreen then
+         -- create window context
+         local closure = w
+         local hook_resize, hook_mouse
+         if closure and closure.window and closure.image then
+            closure.image = input
+            closure.refresh(x,y)
+         else
+            closure = {image=input}
+            hook_resize = function(wi,he)
+                             local qtimg = qt.QImage.fromTensor(closure.image)
+                             closure.painter:image(0,0,wi,he,qtimg)
+                             collectgarbage()
+                          end
+            hook_mouse = function(x,y,button)
+                            --local size = closure.window.frame.size:totable()
+                            --size.width =
+                            --size.height =
+                            if button == 'LeftButton' then
+                            elseif button == 'RightButton' then
+                            end
+                            --closure.window.frame.size = qt.QSize(size)
+                         end
+            closure.window, closure.painter = image.window(hook_resize,hook_mouse)
+            closure.refresh = hook_resize
+         end
+         closure.window.size = qt.QSize{width=x,height=y}
+         closure.window.windowTitle = legend
+         closure.window:show()
+         hook_resize(x,y)
+         closure.isclosure = true
+         return closure
+      else
+         if offscreen then
+            w = w or qt.QtLuaPainter(x,y)
+         else
+            w = w or qtwidget.newwindow(x,y,legend)
+         end
+         if w.window and not w.window.visible then
+            -- make sure window is visible
+            w.window.visible = true
+         end
+         if w.isclosure then
+            -- window was created with gui, just update closure
+            local closure = w
+            closure.image = input
+            local size = closure.window.size:totable()
+            closure.window.windowTitle = legend
+            closure.refresh(size.width, size.height)
+         else
+            -- if no gui, create plain window, and blit
+            local qtimg = qt.QImage.fromTensor(input)
+            w:image(ox,oy,x,y,qtimg)
+         end
+      end
+   else
+      xerror('image must be a HxW or KxHxW or Kx3xHxW tensor, or a list of tensors', 'image.display')
+   end
+   -- return painter
+   return w
+end
+rawset(image, 'display', display)
+
+----------------------------------------------------------------------
+-- creates a window context for images
+--
+local function window(hook_resize, hook_mousepress, hook_mousedoublepress)
+   require 'qt'
+   require 'qttorch'
+   require 'qtwidget'
+   require 'qtuiloader'
+   local pathui = paths.concat(fpath(), 'win.ui')
+   local win = qtuiloader.load(pathui)
+   local painter = qt.QtLuaPainter(win.frame)
+   if hook_resize then
+      qt.connect(qt.QtLuaListener(win.frame),
+                 'sigResize(int,int)',
+                 hook_resize)
+   end
+   if hook_mousepress then
+      qt.connect(qt.QtLuaListener(win.frame),
+                 'sigMousePress(int,int,QByteArray,QByteArray,QByteArray)',
+                 hook_mousepress)
+   end
+   if hook_mousedoublepress then
+      qt.connect(qt.QtLuaListener(win.frame),
+                 'sigMouseDoubleClick(int,int,QByteArray,QByteArray,QByteArray)',
+                 hook_mousedoublepress)
+   end
+   local ctrl = false
+   qt.connect(qt.QtLuaListener(win),
+              'sigKeyPress(QString,QByteArray,QByteArray)',
+              function (str, s2)
+                 if s2 and s2 == 'Key_Control' then
+                    ctrl = true
+                 elseif s2 and s2 == 'Key_W' and ctrl then
+                    win:close()
+                 else
+                    ctrl = false
+                 end
+              end)
+   return win,painter
+end
+rawset(image, 'window', window)
+
+----------------------------------------------------------------------
+-- lena is always useful
+--
+local function lena()
+   local fname = 'grace_hopper_512'
+   if xlua.require 'libjpeg' then
+      lena = image.load(paths.concat(fpath(), 'assets', fname .. '.jpg'), 3)
+   elseif xlua.require 'libpng' then
+      lena = image.load(paths.concat(fpath(), 'assets', fname .. '.png'), 3)
+   else
+      dok.error('no bindings available to load images (libjpeg AND libpng missing)', 'image.lena')
+   end
+   return lena
+end
+rawset(image, 'lena', lena)
+
+
+----------------------------------------------------------------------
+-- fabio is a nice gender-balancing variation on lena
+-- See: http://www.claremontmckenna.edu/news/every-picture-tells-a-story/
+-- and first use in http://arxiv.org/abs/1202.6429
+-- along with original file on http://nuit-blanche.blogspot.co.uk/2012/03/let-there-be-only-one-fabio.html
+local function fabio()
+   local fname = 'fabio'
+   if xlua.require 'libjpeg' then
+      lena = image.load(paths.concat(fpath(), 'assets', fname .. '.jpg'), 1)
+   elseif xlua.require 'libpng' then
+      lena = image.load(paths.concat(fpath(), 'assets', fname .. '.png'), 1)
+   else
+      dok.error('no bindings available to load images (libjpeg AND libpng missing)', 'image.fabio')
+   end
+   return lena
+end
+rawset(image, 'fabio', fabio)
+
+
+----------------------------------------------------------------------
+-- image.rgb2yuv(image)
+-- converts a RGB image to YUV
+--
+function image.rgb2yuv(...)
+   -- arg check
+   local output,input
+   local args = {...}
+   if select('#',...) == 2 then
+      output = args[1]
+      input = args[2]
+   elseif select('#',...) == 1 then
+      input = args[1]
+   else
+      print(dok.usage('image.rgb2yuv',
+                      'transforms an image from RGB to YUV', nil,
+                      {type='torch.Tensor', help='input image', req=true},
+                      '',
+                      {type='torch.Tensor', help='output image', req=true},
+                      {type='torch.Tensor', help='input image', req=true}
+                      ))
+      dok.error('missing input', 'image.rgb2yuv')
+   end
+
+   -- resize
+   output = output or input.new()
+   output:resizeAs(input)
+
+   -- input chanels
+   local inputRed = input[1]
+   local inputGreen = input[2]
+   local inputBlue = input[3]
+
+   -- output chanels
+   local outputY = output[1]
+   local outputU = output[2]
+   local outputV = output[3]
+
+   -- convert
+   outputY:zero():add(0.299, inputRed):add(0.587, inputGreen):add(0.114, inputBlue)
+   outputU:zero():add(-0.14713, inputRed):add(-0.28886, inputGreen):add(0.436, inputBlue)
+   outputV:zero():add(0.615, inputRed):add(-0.51499, inputGreen):add(-0.10001, inputBlue)
+
+   -- return YUV image
+   return output
+end
+
+----------------------------------------------------------------------
+-- image.yuv2rgb(image)
+-- converts a YUV image to RGB
+--
+function image.yuv2rgb(...)
+   -- arg check
+   local output,input
+   local args = {...}
+   if select('#',...) == 2 then
+      output = args[1]
+      input = args[2]
+   elseif select('#',...) == 1 then
+      input = args[1]
+   else
+      print(dok.usage('image.yuv2rgb',
+                      'transforms an image from YUV to RGB', nil,
+                      {type='torch.Tensor', help='input image', req=true},
+                      '',
+                      {type='torch.Tensor', help='output image', req=true},
+                      {type='torch.Tensor', help='input image', req=true}
+                      ))
+      dok.error('missing input', 'image.yuv2rgb')
+   end
+
+   -- resize
+   output = output or input.new()
+   output:resizeAs(input)
+
+   -- input chanels
+   local inputY = input[1]
+   local inputU = input[2]
+   local inputV = input[3]
+
+   -- output chanels
+   local outputRed = output[1]
+   local outputGreen = output[2]
+   local outputBlue = output[3]
+
+   -- convert
+   outputRed:copy(inputY):add(1.13983, inputV)
+   outputGreen:copy(inputY):add(-0.39465, inputU):add(-0.58060, inputV)
+   outputBlue:copy(inputY):add(2.03211, inputU)
+
+   -- return RGB image
+   return output
+end
+
+----------------------------------------------------------------------
+-- image.rgb2y(image)
+-- converts a RGB image to Y (discards U/V)
+--
+function image.rgb2y(...)
+   -- arg check
+   local output,input
+   local args = {...}
+   if select('#',...) == 2 then
+      output = args[1]
+      input = args[2]
+   elseif select('#',...) == 1 then
+      input = args[1]
+   else
+      print(dok.usage('image.rgb2y',
+                      'transforms an image from RGB to Y', nil,
+                      {type='torch.Tensor', help='input image', req=true},
+                      '',
+                      {type='torch.Tensor', help='output image', req=true},
+                      {type='torch.Tensor', help='input image', req=true}
+                      ))
+      dok.error('missing input', 'image.rgb2y')
+   end
+
+   -- resize
+   output = output or input.new()
+   output:resize(1, input:size(2), input:size(3))
+
+   -- input chanels
+   local inputRed = input[1]
+   local inputGreen = input[2]
+   local inputBlue = input[3]
+
+   -- output chanels
+   local outputY = output[1]
+
+   -- convert
+   input.image.rgb2y(input, outputY)
+
+   -- return YUV image
+   return output
+end
+
+----------------------------------------------------------------------
+-- image.rgb2hsl(image)
+-- converts an RGB image to HSL
+--
+function image.rgb2hsl(...)
+   -- arg check
+   local output,input
+   local args = {...}
+   if select('#',...) == 2 then
+      output = args[1]
+      input = args[2]
+   elseif select('#',...) == 1 then
+      input = args[1]
+   else
+      print(dok.usage('image.rgb2hsl',
+                      'transforms an image from RGB to HSL', nil,
+                      {type='torch.Tensor', help='input image', req=true},
+                      '',
+                      {type='torch.Tensor', help='output image', req=true},
+                      {type='torch.Tensor', help='input image', req=true}
+                      ))
+      dok.error('missing input', 'image.rgb2hsl')
+   end
+
+   -- resize
+   output = output or input.new()
+   output:resizeAs(input)
+
+   -- compute
+   input.image.rgb2hsl(input,output)
+
+   -- return HSL image
+   return output
+end
+
+----------------------------------------------------------------------
+-- image.hsl2rgb(image)
+-- converts an HSL image to RGB
+--
+function image.hsl2rgb(...)
+   -- arg check
+   local output,input
+   local args = {...}
+   if select('#',...) == 2 then
+      output = args[1]
+      input = args[2]
+   elseif select('#',...) == 1 then
+      input = args[1]
+   else
+      print(dok.usage('image.hsl2rgb',
+                      'transforms an image from HSL to RGB', nil,
+                      {type='torch.Tensor', help='input image', req=true},
+                      '',
+                      {type='torch.Tensor', help='output image', req=true},
+                      {type='torch.Tensor', help='input image', req=true}
+                      ))
+      dok.error('missing input', 'image.hsl2rgb')
+   end
+
+   -- resize
+   output = output or input.new()
+   output:resizeAs(input)
+
+   -- compute
+   input.image.hsl2rgb(input,output)
+
+   -- return HSL image
+   return output
+end
+
+----------------------------------------------------------------------
+-- image.rgb2hsv(image)
+-- converts an RGB image to HSV
+--
+function image.rgb2hsv(...)
+   -- arg check
+   local output,input
+   local args = {...}
+   if select('#',...) == 2 then
+      output = args[1]
+      input = args[2]
+   elseif select('#',...) == 1 then
+      input = args[1]
+   else
+      print(dok.usage('image.rgb2hsv',
+                      'transforms an image from RGB to HSV', nil,
+                      {type='torch.Tensor', help='input image', req=true},
+                      '',
+                      {type='torch.Tensor', help='output image', req=true},
+                      {type='torch.Tensor', help='input image', req=true}
+                      ))
+      dok.error('missing input', 'image.rgb2hsv')
+   end
+
+   -- resize
+   output = output or input.new()
+   output:resizeAs(input)
+
+   -- compute
+   input.image.rgb2hsv(input,output)
+
+   -- return HSV image
+   return output
+end
+
+----------------------------------------------------------------------
+-- image.hsv2rgb(image)
+-- converts an HSV image to RGB
+--
+function image.hsv2rgb(...)
+   -- arg check
+   local output,input
+   local args = {...}
+   if select('#',...) == 2 then
+      output = args[1]
+      input = args[2]
+   elseif select('#',...) == 1 then
+      input = args[1]
+   else
+      print(dok.usage('image.hsv2rgb',
+                      'transforms an image from HSV to RGB', nil,
+                      {type='torch.Tensor', help='input image', req=true},
+                      '',
+                      {type='torch.Tensor', help='output image', req=true},
+                      {type='torch.Tensor', help='input image', req=true}
+                      ))
+      dok.error('missing input', 'image.hsv2rgb')
+   end
+
+   -- resize
+   output = output or input.new()
+   output:resizeAs(input)
+
+   -- compute
+   input.image.hsv2rgb(input,output)
+
+   -- return HSV image
+   return output
+end
+
+----------------------------------------------------------------------
+-- image.rgb2lab(image)
+-- converts an RGB image to LAB
+-- assumes sRGB input in the range [0, 1]
+--
+function image.rgb2lab(...)
+   -- arg check
+   local output,input
+   local args = {...}
+   if select('#',...) == 2 then
+      output = args[1]
+      input = args[2]
+   elseif select('#',...) == 1 then
+      input = args[1]
+   else
+      print(dok.usage('image.rgb2lab',
+                      'transforms an image from sRGB to LAB', nil,
+                      {type='torch.Tensor', help='input image', req=true},
+                      '',
+                      {type='torch.Tensor', help='output image', req=true},
+                      {type='torch.Tensor', help='input image', req=true}
+                      ))
+      dok.error('missing input', 'image.rgb2lab')
+   end
+
+   -- resize
+   output = output or input.new()
+   output:resizeAs(input)
+
+   -- compute
+   input.image.rgb2lab(input,output)
+
+   -- return LAB image
+   return output
+end
+
+----------------------------------------------------------------------
+-- image.lab2rgb(image)
+-- converts an LAB image to RGB (assumes sRGB)
+--
+function image.lab2rgb(...)
+   -- arg check
+   local output,input
+   local args = {...}
+   if select('#',...) == 2 then
+      output = args[1]
+      input = args[2]
+   elseif select('#',...) == 1 then
+      input = args[1]
+   else
+      print(dok.usage('image.lab2rgb',
+                      'transforms an image from LAB to RGB', nil,
+                      {type='torch.Tensor', help='input image', req=true},
+                      '',
+                      {type='torch.Tensor', help='output image', req=true},
+                      {type='torch.Tensor', help='input image', req=true}
+                      ))
+      dok.error('missing input', 'image.lab2rgb')
+   end
+
+   -- resize
+   output = output or input.new()
+   output:resizeAs(input)
+
+   -- compute
+   input.image.lab2rgb(input,output)
+
+   -- return sRGB image
+   return output
+end
+
+
+----------------------------------------------------------------------
+-- image.rgb2nrgb(image)
+-- converts an RGB image to normalized-RGB
+--
+function image.rgb2nrgb(...)
+   -- arg check
+   local output,input
+   local args = {...}
+   if select('#',...) == 2 then
+      output = args[1]
+      input = args[2]
+   elseif select('#',...) == 1 then
+      input = args[1]
+   else
+      print(dok.usage('image.rgb2nrgb',
+                      'transforms an image from RGB to normalized RGB', nil,
+                      {type='torch.Tensor', help='input image', req=true},
+                      '',
+                      {type='torch.Tensor', help='output image', req=true},
+                      {type='torch.Tensor', help='input image', req=true}
+                      ))
+      dok.error('missing input', 'image.rgb2nrgb')
+   end
+
+   -- resize
+   output = output or input.new()
+   output:resizeAs(input)
+   local sum = input.new()
+   sum:resize(input:size(2), input:size(3))
+
+   -- compute sum and normalize
+   sum:copy(input[1]):add(input[2]):add(input[3]):add(1e-6)
+   output:copy(input)
+   output[1]:cdiv(sum)
+   output[2]:cdiv(sum)
+   output[3]:cdiv(sum)
+
+   -- return HSV image
+   return output
+end
+
+----------------------------------------------------------------------
+-- image.y2jet(image)
+-- Converts a L-levels (1-L) greyscale image into a jet heat-map
+--
+function image.y2jet(...)
+
+   -- arg check
+   local output,input
+   local args = {...}
+   if select('#',...) == 2 then
+      output = args[1]
+      input = args[2]
+   elseif select('#',...) == 1 then
+      input = args[1]
+   else
+      error('Invalid input for image.y2jet()')
+   end
+
+   -- accept 3D grayscale
+   if input:dim() == 3 and input:size(1) == 1 then
+      input = input.new(input):resize(input:size(2), input:size(3))
+   end
+
+   -- accept 1D greyscale
+   if input:dim() == 1  then
+      input = input.new(input):resize(1, input:size(1))
+   end
+
+   local output = output or input.new()
+   local L = input:max()
+
+   local colorMap = image.jetColormap(L)
+   if torch.type(input) == 'torch.ByteTensor' then
+     colorMap = colorMap:mul(255):round()
+     colorMap[torch.lt(colorMap, 0)] = 0
+     colorMap[torch.gt(colorMap, 255)] = 255
+     colorMap = colorMap:byte()
+   else
+     colorMap = colorMap:typeAs(input)
+   end
+
+   input.image.colorize(output, input-1, colorMap)
+
+   return output
+end
+
+-- color, bgcolor, size, wrap, inplace
+function image.drawText(src, text, x, y, opts)
+    opts = opts or {}
+    assert(torch.isTensor(src) and src:dim() == 3 and src:size(1) == 3,
+	   "input image has to be a 3D tensor of shape 3 x H x W ")
+    local out = src
+    if not opts.inplace then
+	out = src:clone()
+    end
+    if not text or text:gsub("%s*$", "") == '' then return out end
+    x = x or 1
+    y = y or 1
+    local color = opts.color or {255, 0, 0} -- red default
+    local bgcolor = opts.bg or {-1, -1, -1} -- no bgcolor default
+    local size = opts.size or 1
+    if opts.wrap == nil then opts.wrap = true end -- to wrap long lines or not
+    src.image.text(out, text, x, y, size,
+		   color[1], color[2], color[3],
+		   bgcolor[1], bgcolor[2], bgcolor[3],
+		   opts.wrap and 1 or 0)
+    return out
+end
+
+----------------------------------------------------------------------
+--- Draw a rectangle on the image
+--
+-- color, bgcolor, size, wrap, inplace
+function image.drawRect(src, x1, y1, x2, y2, opts)
+   opts = opts or {}
+   assert(torch.isTensor(src) and src:dim() == 3 and src:size(1) == 3,
+    "input image has to be a 3D tensor of shape 3 x H x W ")
+   local out = src
+   if not opts.inplace then
+      out = src:clone()
+   end
+   if not (x1 and x2 and y1 and y2) then return out end
+   local color = opts.color or {255, 0, 0} -- red default
+   local lineWidth = opts.lineWidth or 1
+
+   src.image.drawRect(out, x1, y1, x2, y2, lineWidth, color[1], color[2], color[3])
+   return out
+end
+
+
+----------------------------------------------------------------------
+--- Returns a gaussian kernel.
+--
+function image.gaussian(...)
+   -- process args
+   local _, size, sigma, amplitude, normalize, width, height,
+      sigma_horz, sigma_vert, mean_horz, mean_vert, tensor = dok.unpack(
+      {...},
+      'image.gaussian',
+      'returns a 2D gaussian kernel',
+      {arg='size', type='number', help='kernel size (size x size)', default=3},
+      {arg='sigma', type='number', help='sigma (horizontal and vertical)', default=0.25},
+      {arg='amplitude', type='number', help='amplitute of the gaussian (max value)', default=1},
+      {arg='normalize', type='number', help='normalize kernel (exc Amplitude)', default=false},
+      {arg='width', type='number', help='kernel width', defaulta='size'},
+      {arg='height', type='number', help='kernel height', defaulta='size'},
+      {arg='sigma_horz', type='number', help='horizontal sigma', defaulta='sigma'},
+      {arg='sigma_vert', type='number', help='vertical sigma', defaulta='sigma'},
+      {arg='mean_horz', type='number', help='horizontal mean', default=0.5},
+      {arg='mean_vert', type='number', help='vertical mean', default=0.5},
+      {arg='tensor', type='torch.Tensor', help='result tensor (height/width are ignored)'}
+   )
+   if tensor then
+      assert(tensor:dim() == 2, "expecting 2D tensor")
+      assert(tensor:nElement() > 0, "expecting non-empty tensor")
+   end
+   -- generate kernel
+   local gauss = tensor or torch.Tensor(height, width)
+   gauss.image.gaussian(gauss, amplitude, normalize, sigma_horz, sigma_vert, mean_horz, mean_vert)
+
+   return gauss
+end
+
+function image.gaussian1D(...)
+   -- process args
+   local _, size, sigma, amplitude, normalize, mean, tensor
+      = dok.unpack(
+      {...},
+      'image.gaussian1D',
+      'returns a 1D gaussian kernel',
+      {arg='size', type='number', help='size the kernel', default=3},
+      {arg='sigma', type='number', help='Sigma', default=0.25},
+      {arg='amplitude', type='number', help='Amplitute of the gaussian (max value)', default=1},
+      {arg='normalize', type='number', help='Normalize kernel (exc Amplitude)', default=false},
+      {arg='mean', type='number', help='Mean', default=0.5},
+      {arg='tensor', type='torch.Tensor', help='result tensor (size is ignored)'}
+   )
+
+   -- local vars
+   if tensor then
+      assert(tensor:dim() == 1, "expecting 1D tensor")
+      assert(tensor:nElement() > 0, "expecting non-empty tensor")
+      size = tensor:size(1)
+   end
+   local center = mean * size + 0.5
+
+   -- generate kernel
+   local gauss = tensor or torch.Tensor(size)
+   for i=1,size do
+      gauss[i] = amplitude * math.exp(-(math.pow((i-center)
+                                              /(sigma*size),2)/2))
+   end
+   if normalize then
+      gauss:div(gauss:sum())
+   end
+   return gauss
+end
+
+----------------------------------------------------------------------
+--- Returns a Laplacian kernel.
+--
+function image.laplacian(...)
+   -- process args
+   local _, size, sigma, amplitude, normalize,
+   width, height, sigma_horz, sigma_vert, mean_horz, mean_vert = dok.unpack(
+      {...},
+      'image.laplacian',
+      'returns a 2D Laplacian kernel',
+      {arg='size', type='number', help='kernel size (size x size)', default=3},
+      {arg='sigma', type='number', help='sigma (horizontal and vertical)', default=0.1},
+      {arg='amplitude', type='number', help='amplitute of the Laplacian (max value)', default=1},
+      {arg='normalize', type='number', help='normalize kernel (exc Amplitude)', default=false},
+      {arg='width', type='number', help='kernel width', defaulta='size'},
+      {arg='height', type='number', help='kernel height', defaulta='size'},
+      {arg='sigma_horz', type='number', help='horizontal sigma', defaulta='sigma'},
+      {arg='sigma_vert', type='number', help='vertical sigma', defaulta='sigma'},
+      {arg='mean_horz', type='number', help='horizontal mean', default=0.5},
+      {arg='mean_vert', type='number', help='vertical mean', default=0.5}
+   )
+
+   -- local vars
+   local center_x = mean_horz * width + 0.5
+   local center_y = mean_vert * height + 0.5
+
+   -- generate kernel
+   local logauss = torch.Tensor(height,width)
+   for i=1,height do
+      for j=1,width do
+         local xsq = math.pow((i-center_x)/(sigma_horz*width),2)/2
+         local ysq = math.pow((j-center_y)/(sigma_vert*height),2)/2
+         local derivCoef = 1 - (xsq + ysq)
+         logauss[i][j] = derivCoef * amplitude * math.exp(-(xsq + ysq))
+      end
+   end
+   if normalize then
+      logauss:div(logauss:sum())
+   end
+   return logauss
+end
+
+----------------------------------------------------------------------
+--- Gaussian Pyramid
+--
+function image.gaussianpyramid(...)
+   local dst,src,scales
+   local args = {...}
+   if select('#',...) == 3 then
+      dst = args[1]
+      src = args[2]
+      scales = args[3]
+   elseif select('#',...) == 2 then
+      dst = {}
+      src = args[1]
+      scales = args[2]
+   else
+      print(dok.usage('image.gaussianpyramid',
+                       'construct a Gaussian pyramid from an image', nil,
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='table', help='list of scales', req=true},
+                       '',
+                       {type='table', help='destination (list of Tensors)', req=true},
+                       {type='torch.Tensor', help='input image', req=true},
+                       {type='table', help='list of scales', req=true}))
+      dok.error('incorrect arguments', 'image.gaussianpyramid')
+   end
+   if src:nDimension() == 2 then
+      for i = 1,#scales do
+         dst[i] = dst[i] or src.new()
+         dst[i]:resize(src:size(1)*scales[i], src:size(2)*scales[i])
+      end
+   elseif src:nDimension() == 3 then
+      for i = 1,#scales do
+         dst[i] = dst[i] or src.new()
+         dst[i]:resize(src:size(1), src:size(2)*scales[i], src:size(3)*scales[i])
+      end
+   else
+      dok.error('src image must be 2D or 3D', 'image.gaussianpyramid')
+   end
+   local k = image.gaussian{width=3, normalize=true}:typeAs(src)
+   local tmp = src
+   for i = 1,#scales do
+      if scales[i] == 1 then
+         dst[i][{}] = tmp
+      else
+         image.scale(dst[i], tmp, 'simple')
+      end
+      tmp = image.convolve(dst[i], k, 'same')
+   end
+   return dst
+end
+
+----------------------------------------------------------------------
+--- Creates an optimally-spaced RGB color mapping
+--
+function image.colormap(nbColor)
+   -- note: the best way of obtaining optimally-spaced
+   -- colors is to generate them around the HSV wheel,
+   -- by varying the Hue component
+   local map = torch.Tensor(nbColor,3)
+   local huef = 0
+   local satf = 0
+   for i = 1,nbColor do
+      -- HSL
+      local hue = math.fmod(huef,360)
+      local sat = math.fmod(satf,0.7) + 0.3
+      local light = 0.5
+      huef = huef + 39
+      satf = satf + 1/9
+      -- HSL -> RGB
+      local c = (1 - math.abs(2*light-1))*sat
+      local huep = hue/60
+      local x = c*(1-math.abs(math.fmod(huep,2)-1))
+      local redp
+      local greenp
+      local bluep
+      if huep < 1 then
+         redp = c; greenp = x; bluep = 0
+      elseif huep < 2 then
+         redp = x; greenp = c; bluep = 0
+      elseif huep < 3 then
+         redp = 0; greenp = c; bluep = x
+      elseif huep < 4 then
+         redp = 0; greenp = x; bluep = c
+      elseif huep < 5 then
+         redp = x; greenp = 0; bluep = c
+      else
+         redp = c; greenp = 0; bluep = x
+      end
+      local m = light - c/2
+      map[i][1] = redp + m
+      map[i][2] = greenp + m
+      map[i][3] = bluep + m
+   end
+   return map
+end
+
+----------------------------------------------------------------------
+--- Creates a jet color mapping - Inspired by http://www.metastine.com/?p=7
+--
+function image.jetColormap(nbColor)
+   local map = torch.Tensor(nbColor,3)
+   for i = 1,nbColor do
+      local fourValue = 4 * i / nbColor
+      map[i][1] = math.max(math.min(fourValue - 1.5, -fourValue + 4.5, 1),0)
+      map[i][2] = math.max(math.min(fourValue -  .5, -fourValue + 3.5, 1),0)
+      map[i][3] = math.max(math.min(fourValue +  .5, -fourValue + 2.5, 1),0)
+   end
+   return map
+end
+
+
+
+------------------------------------------------------------------------
+--- Local contrast normalization of an image
+--
+-- local contrast normalization on a given image tensor using kernel ker.
+-- If kernel is not given, then a default 9x9 gaussian will be used
+function image.lcn(im,ker)
+
+   ker = ker or image.gaussian({size=9,sigma=1.591/9,normalize=true})
+   local im = im:clone():type('torch.DoubleTensor')
+   if not(im:dim() == 2 or (im:dim() == 3 and im:size(1) == 1)) then
+     error('grayscale image expected')
+   end
+   if im:dim() == 3 then
+      im = im[1]
+   end
+   local mn = im:mean()
+   local sd = im:std()
+   -- print(ker)
+
+   -- 1. subtract the mean and divide by the standard deviation
+   im:add(-mn)
+   im:div(sd)
+
+   -- 2. calculate local mean and std and normalize each pixel
+
+   -- mean
+   local lmn = torch.conv2(im, ker)
+   -- variance
+   local imsq = im:clone():cmul(im)
+   local lmnsq = torch.conv2(imsq, ker)
+   local lvar = lmn:clone():cmul(lmn)
+   lvar:add(-1,lmnsq):mul(-1)
+   -- avoid numerical errors
+   lvar:apply(function(x) if x < 0 then return 0 end end)
+   -- standard deviation
+   local lstd  = lvar:sqrt()
+   lstd:apply(function (x) if x < 1 then return 1 end end)
+
+   -- apply normalization
+   local shifti = math.floor(ker:size(1)/2)+1
+   local shiftj = math.floor(ker:size(2)/2)+1
+   --print(shifti,shiftj,lstd:size(),im:size())
+   local dim = im:narrow(1,shifti,lstd:size(1)):narrow(2,shiftj,lstd:size(2)):clone()
+   dim:add(-1,lmn)
+   dim:cdiv(lstd)
+   return dim:clone()
+
+end
+
+------------------------------------------------------------------------
+--- Morphological erosion
+function image.erode(im,kern,pad)
+   if not im then
+      print(dok.usage("image.erode",
+		            "Morphological erosion for odd dimension kernels",nil,
+			    {type="torch.Tensor",help="binary image of 0 and 1",req=true},
+			    {type="torch.Tensor",help="morphological kernel of 0 and 1; default is 3x3"},
+			    {type="number",help="value to assume outside boundary; default is 1"}))
+      dok.error("missing image","image.erode")
+   end
+   -- Default kernel is 3x3
+   local kern = kern or torch.ones(3,3):typeAs(im)
+   local pad = pad or 1
+   -- Padding the image
+   local hpad = kern:size(1)/2-0.5
+   local wpad = kern:size(2)/2-0.5
+   local padded = torch.zeros(im:size(1)+2*hpad,im:size(2)+2*wpad):fill(pad):typeAs(im)
+   padded[{{hpad+1,im:size(1)+hpad},{wpad+1,im:size(2)+wpad}}]:copy(im)
+   -- Do convolution
+   local n = kern:sum()
+   local conv = padded:conv2(kern)
+   -- Do erosion
+   return conv:eq(n):typeAs(im)
+end
+
+------------------------------------------------------------------------
+--- Morphological dilation
+function image.dilate(im,kern,pad)
+   if not im then
+      print(dok.usage("image.dilate",
+		            "Morphological dilation for odd dimension kernels",nil,
+			    {type="torch.Tensor",help="binary image of 0 and 1",req=true},
+			    {type="torch.Tensor",help="morphological kernel of 0 and 1; default is 3x3"},
+			    {type="number",help="value to assume outside boundary; default is 0"}))
+      dok.error("missing image","image.dilate")
+   end
+   -- Default kernel is 3x3
+   local kern = kern or torch.ones(3,3):typeAs(im)
+   kern = image.hflip(image.vflip(kern))
+   local pad = pad or 0
+   -- Padding the image
+   local hpad = kern:size(1)/2-0.5
+   local wpad = kern:size(2)/2-0.5
+   local padded = torch.zeros(im:size(1)+2*hpad,im:size(2)+2*wpad):fill(pad):typeAs(im)
+   padded[{{hpad+1,im:size(1)+hpad},{wpad+1,im:size(2)+wpad}}]:copy(im)
+   -- Do convolution
+   local conv = padded:conv2(kern)
+   -- Do erosion
+   return conv:gt(0):typeAs(im)
+end
+
+return image
diff --git a/jpeg.c b/jpeg.c
new file mode 100644
index 0000000..ae4ce14
--- /dev/null
+++ b/jpeg.c
@@ -0,0 +1,68 @@
+
+#include <TH.h>
+#include <luaT.h>
+#include <jpeglib.h>
+#include <setjmp.h>
+
+#if LUA_VERSION_NUM >= 503
+#define luaL_checkint(L,n)      ((int)luaL_checkinteger(L, (n)))
+#endif
+
+
+#define torch_(NAME) TH_CONCAT_3(torch_, Real, NAME)
+#define torch_Tensor TH_CONCAT_STRING_3(torch., Real, Tensor)
+#define libjpeg_(NAME) TH_CONCAT_3(libjpeg_, Real, NAME)
+
+static void
+jpeg_mem_src_dummy(j_decompress_ptr c, unsigned char *ibuf, unsigned long isiz)
+{
+}
+
+static void
+jpeg_mem_dest_dummy(j_compress_ptr c, unsigned char **obuf, unsigned long *osiz)
+{
+}
+
+#define JPEG_MEM_SRC_NOT_DEF  "`jpeg_mem_src` is not defined."
+#define JPEG_MEM_DEST_NOT_DEF "`jpeg_mem_dest` is not defined."
+#define JPEG_REQUIRED_VERSION " Use libjpeg v8+, libjpeg-turbo 1.3+ or build" \
+                              " libjpeg-turbo with `--with-mem-srcdst`."
+
+#define JPEG_MEM_SRC_ERR_MSG  JPEG_MEM_SRC_NOT_DEF JPEG_REQUIRED_VERSION
+#define JPEG_MEM_DEST_ERR_MSG JPEG_MEM_DEST_NOT_DEF JPEG_REQUIRED_VERSION
+
+#if !defined(HAVE_JPEG_MEM_SRC)
+#define jpeg_mem_src jpeg_mem_src_dummy
+#endif
+
+#if !defined(HAVE_JPEG_MEM_DEST)
+#define jpeg_mem_dest jpeg_mem_dest_dummy
+#endif
+
+#include "generic/jpeg.c"
+#include "THGenerateAllTypes.h"
+
+DLL_EXPORT int luaopen_libjpeg(lua_State *L)
+{
+  libjpeg_FloatMain_init(L);
+  libjpeg_DoubleMain_init(L);
+  libjpeg_ByteMain_init(L);
+
+  lua_newtable(L);
+  lua_pushvalue(L, -1);
+  lua_setglobal(L, "libjpeg");
+
+  lua_newtable(L);
+  luaT_setfuncs(L, libjpeg_DoubleMain__, 0);
+  lua_setfield(L, -2, "double");
+
+  lua_newtable(L);
+  luaT_setfuncs(L, libjpeg_FloatMain__, 0);
+  lua_setfield(L, -2, "float");
+
+  lua_newtable(L);
+  luaT_setfuncs(L, libjpeg_ByteMain__, 0);
+  lua_setfield(L, -2, "byte");
+
+  return 1;
+}
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..97c8026
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,14 @@
+site_name: image
+theme : simplex
+repo_url : https://github.com/torch/image
+use_directory_urls : false
+markdown_extensions: [extra]
+docs_dir : doc
+pages:
+- [index.md, Image]
+- [saveload.md, Saving and Loading]
+- [simpletransform.md, Simple Transformations]
+- [paramtransform.md, Parameterized Transformations]
+- [gui.md, Graphical User Interfaces]
+- [colorspace.md, Color Space Conversions]
+- [tensorconstruct.md, Tensor Constructors]
diff --git a/png.c b/png.c
new file mode 100644
index 0000000..e69eee0
--- /dev/null
+++ b/png.c
@@ -0,0 +1,87 @@
+
+#include <TH.h>
+#include <luaT.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#if LUA_VERSION_NUM >= 503
+#define luaL_checkint(L,n)      ((int)luaL_checkinteger(L, (n)))
+#endif
+
+#define PNG_DEBUG 3
+#include <png.h>
+
+#define torch_(NAME) TH_CONCAT_3(torch_, Real, NAME)
+#define torch_Tensor TH_CONCAT_STRING_3(torch., Real, Tensor)
+#define libpng_(NAME) TH_CONCAT_3(libpng_, Real, NAME)
+
+/*
+ * Bookkeeping struct for reading png data from memory
+ */
+typedef struct {
+  unsigned char* buffer;
+  png_size_t offset;
+  png_size_t length;
+} libpng_inmem_buffer;
+
+/*
+ * Call back for reading png data from memory
+ */
+static void
+libpng_userReadData(png_structp pngPtrSrc, png_bytep dest, png_size_t length)
+{
+  libpng_inmem_buffer* src = png_get_io_ptr(pngPtrSrc);
+  assert(src->offset+length <= src->length);
+  memcpy(dest, src->buffer + src->offset, length);
+  src->offset += length;
+}
+
+/*
+ * Error message wrapper (single member struct to preserve `str` size info)
+ */
+typedef struct {
+  char str[256];
+} libpng_errmsg;
+
+/*
+ * Custom error handling function (see `png_set_error_fn`)
+ */
+static void
+libpng_error_fn(png_structp png_ptr, png_const_charp error_msg)
+{
+  libpng_errmsg *errmsg = png_get_error_ptr(png_ptr);
+  int max = sizeof(errmsg->str) - 1;
+  strncpy(errmsg->str, error_msg, max);
+  errmsg->str[max] = '\0';
+  longjmp(png_jmpbuf(png_ptr), 1);
+}
+
+#include "generic/png.c"
+#include "THGenerateAllTypes.h"
+
+DLL_EXPORT int luaopen_libpng(lua_State *L)
+{
+  libpng_FloatMain_init(L);
+  libpng_DoubleMain_init(L);
+  libpng_ByteMain_init(L);
+
+  lua_newtable(L);
+  lua_pushvalue(L, -1);
+  lua_setglobal(L, "libpng");
+
+  lua_newtable(L);
+  luaT_setfuncs(L, libpng_DoubleMain__, 0);
+  lua_setfield(L, -2, "double");
+
+  lua_newtable(L);
+  luaT_setfuncs(L, libpng_FloatMain__, 0);
+  lua_setfield(L, -2, "float");
+
+  lua_newtable(L);
+  luaT_setfuncs(L, libpng_ByteMain__, 0);
+  lua_setfield(L, -2, "byte");
+
+  return 1;
+}
diff --git a/ppm.c b/ppm.c
new file mode 100644
index 0000000..0303ffb
--- /dev/null
+++ b/ppm.c
@@ -0,0 +1,70 @@
+
+#include <TH.h>
+#include <luaT.h>
+
+#define torch_(NAME) TH_CONCAT_3(torch_, Real, NAME)
+#define torch_Tensor TH_CONCAT_STRING_3(torch., Real, Tensor)
+#define libppm_(NAME) TH_CONCAT_3(libppm_, Real, NAME)
+
+/* Get the next character in the file, skipping over comments, which
+ * start with a # and continue to the end of the line. 
+ */
+static char ppm_getc(FILE *fp)
+{
+   char ch;
+
+   ch = (char)getc(fp);
+   if (ch == '#') {
+      do {
+         ch = (char)getc(fp);
+      } while (ch != '\n' && ch != '\r');
+   }
+
+   return ch;
+}
+
+/* Get the next integer, skipping whitespace and comments. */
+static long ppm_get_long(FILE *fp)
+{
+   char ch;
+   long i = 0;
+
+   do {
+      ch = ppm_getc(fp);
+   } while (ch == ' ' || ch == ',' || ch == '\t' || ch == '\n' || ch == '\r');
+
+   do {
+      i = i * 10 + ch - '0';
+      ch = ppm_getc(fp);
+   } while (ch >= '0' && ch <= '9');
+
+   return i;
+}
+
+#include "generic/ppm.c"
+#include "THGenerateAllTypes.h"
+
+DLL_EXPORT int luaopen_libppm(lua_State *L)
+{
+  libppm_FloatMain_init(L);
+  libppm_DoubleMain_init(L);
+  libppm_ByteMain_init(L);
+
+  lua_newtable(L);
+  lua_pushvalue(L, -1);
+  lua_setglobal(L, "libppm");
+
+  lua_newtable(L);
+  luaT_setfuncs(L, libppm_DoubleMain__, 0);
+  lua_setfield(L, -2, "double");
+
+  lua_newtable(L);
+  luaT_setfuncs(L, libppm_FloatMain__, 0);
+  lua_setfield(L, -2, "float");
+
+  lua_newtable(L);
+  luaT_setfuncs(L, libppm_ByteMain__, 0);
+  lua_setfield(L, -2, "byte");
+
+  return 1;
+}
diff --git a/test/test.lua b/test/test.lua
new file mode 100644
index 0000000..80299e9
--- /dev/null
+++ b/test/test.lua
@@ -0,0 +1,687 @@
+local test = torch.TestSuite()
+local precision = 1e-4
+local precision_mean = 1e-3
+local precision_std = 1e-1
+
+
+local function getTestImagePath(name)
+  return paths.concat(sys.fpath(), 'assets', name)
+end
+
+
+local function assertByteTensorEq(actual, expected, rcond, msg)
+  rcond = rcond or 1e-5
+  tester:assertTensorEq(actual:double(), expected:double(), rcond, msg)
+end
+
+
+local function toByteTensor(x)
+  local y = torch.round(x)
+  y[torch.le(x, 0)] = 0
+  y[torch.ge(x, 255)] = 255
+  return y:byte()
+end
+
+
+local function toByteImage(x)
+  return toByteTensor(torch.mul(x, 255))
+end
+
+
+local function testFunctionOnByteTensor(f, msg)
+  local lena = image.lena():float()
+  local expected = toByteImage(f(lena))
+  local actual = f(toByteImage(lena))
+  assertByteTensorEq(actual, expected, nil, msg)
+end
+
+
+local unpack = unpack and unpack or table.unpack -- lua52 compatibility
+
+
+----------------------------------------------------------------------
+-- Flip test
+--
+function test.FlipAgainstHFlip()
+  for ndims = 1, 5 do
+    for flip_dim = 1, ndims do
+      local sz = {}
+      for i = 1, ndims do
+        sz[i] = math.random(5,10)
+      end
+
+      local input = torch.rand(unpack(sz))
+      local output = image.flip(input, flip_dim)
+
+      -- Now perform the same operation using HFLIP
+      local input_tran = input
+      if (flip_dim < ndims) then
+        -- First permute the flip dimension to X dim
+        input_tran = input:transpose(flip_dim, ndims):contiguous()
+      end
+      -- Now reshape it to 3D
+      local original_hflip_sz = input_tran:size()
+      if ndims == 1 then
+        input_tran:resize(1, original_hflip_sz[1])
+      end
+      if ndims > 3 then
+        sz1 = 1
+        for i = 1, ndims - 2 do
+          sz1 = sz1 * original_hflip_sz[i]
+        end
+        input_tran:resize(sz1, original_hflip_sz[input_tran:dim()-1],
+          original_hflip_sz[input_tran:dim()])
+      end
+
+      local output_hflip = image.hflip(input_tran)
+
+      -- Put it back to Ndim
+      output_hflip:resize(original_hflip_sz)
+
+      if (flip_dim < ndims) then
+        -- permute bacx the flip dimension
+        output_hflip = output_hflip:transpose(flip_dim, ndims):contiguous()
+      end
+
+      local err = output_hflip - output
+      tester:asserteq(err:abs():max(), 0, 'error - bad flip! (ndims='..
+        ndims..',flip_dim='..flip_dim..')')
+    end
+  end
+end
+
+----------------------------------------------------------------------
+-- Gaussian tests
+--
+-- The old gaussian function, commit: 71670e1dcfcfe040aba5403c800a0d316987c2ed
+local function naive_gaussian(...)
+   -- process args
+   local _, size, sigma, amplitude, normalize,
+   width, height, sigma_horz, sigma_vert, mean_horz, mean_vert = dok.unpack(
+      {...},
+      'image.gaussian',
+      'returns a 2D gaussian kernel',
+      {arg='size', type='number', help='kernel size (size x size)', default=3},
+      {arg='sigma', type='number', help='sigma (horizontal and vertical)', default=0.25},
+      {arg='amplitude', type='number', help='amplitute of the gaussian (max value)', default=1},
+      {arg='normalize', type='number', help='normalize kernel (exc Amplitude)', default=false},
+      {arg='width', type='number', help='kernel width', defaulta='size'},
+      {arg='height', type='number', help='kernel height', defaulta='size'},
+      {arg='sigma_horz', type='number', help='horizontal sigma', defaulta='sigma'},
+      {arg='sigma_vert', type='number', help='vertical sigma', defaulta='sigma'},
+      {arg='mean_horz', type='number', help='horizontal mean', default=0.5},
+      {arg='mean_vert', type='number', help='vertical mean', default=0.5}
+   )
+
+   -- local vars
+   local center_x = mean_horz * width + 0.5
+   local center_y = mean_vert * height + 0.5
+
+   -- generate kernel
+   local gauss = torch.Tensor(height, width)
+   for i=1,height do
+      for j=1,width do
+         gauss[i][j] = amplitude * math.exp(-(math.pow((j-center_x)
+                                                    /(sigma_horz*width),2)/2
+                                           + math.pow((i-center_y)
+                                                   /(sigma_vert*height),2)/2))
+      end
+   end
+   if normalize then
+      gauss:div(gauss:sum())
+   end
+   return gauss
+end
+
+function test.gaussian()
+   local sigma_horz = 0.1 + math.random() * 0.3;  -- [0.1, 0.4]
+   local sigma_vert = 0.1 + math.random() * 0.3;  -- [0.1, 0.4]
+   local mean_horz = 0.1 + math.random() * 0.8;  -- [0.1, 0.9]
+   local mean_vert = 0.1 + math.random() * 0.8;  -- [0.1, 0.9]
+   local width = 640
+   local height = 480
+   local amplitude = 10
+
+   for _, normalize in pairs{true, false} do
+      im1 = image.gaussian{amplitude=amplitude,
+                        normalize=normalize,
+                        width=width,
+                        height=height,
+                        sigma_horz=sigma_horz,
+                        sigma_vert=sigma_vert,
+                        mean_horz=mean_horz,
+                        mean_vert=mean_vert}
+
+      im2 = naive_gaussian{amplitude=amplitude,
+                  normalize=normalize,
+                  width=width,
+                  height=height,
+                  sigma_horz=sigma_horz,
+                  sigma_vert=sigma_vert,
+                  mean_horz=mean_horz,
+                  mean_vert=mean_vert}
+
+      tester:assertlt(im1:add(-1, im2):sum(), precision, "Incorrect gaussian")
+   end
+end
+
+
+function test.byteGaussian()
+  local expected = toByteTensor(image.gaussian{
+      amplitude = 1000,
+      tensor = torch.FloatTensor(5, 5),
+  })
+  local actual = image.gaussian{
+      amplitude = 1000,
+      tensor = torch.ByteTensor(5, 5),
+  }
+  assertByteTensorEq(actual, expected)
+end
+
+
+----------------------------------------------------------------------
+-- Gaussian pyramid test
+--
+function test.gaussianpyramid()
+  -- Char, Short and Int tensors not supported.
+  types = {
+      'torch.ByteTensor',
+      'torch.FloatTensor',
+      'torch.DoubleTensor'
+  }
+  for _, type in ipairs(types) do
+    local output = unpack(image.gaussianpyramid(torch.rand(8, 8):type(type), {0.5}))
+    tester:assert(output:type() == type, 'Type ' .. type .. ' produces a different output.')
+  end
+end
+
+----------------------------------------------------------------------
+-- Scale test
+--
+local function outerProduct(x)
+  x = torch.Tensor(x)
+  return torch.ger(x, x)
+end
+
+
+function test.bilinearUpscale()
+  local im = outerProduct{1, 2, 4, 2}
+  local expected = outerProduct{1, 1.5, 2, 3, 4, 3, 2}
+  local actual = image.scale(im, expected:size(2), expected:size(1), 'bilinear')
+  tester:assertTensorEq(actual, expected, 1e-5)
+end
+
+
+function test.bilinearDownscale()
+  local im = outerProduct{1, 2, 4, 2}
+  local expected = outerProduct{1.25, 3, 2.5}
+  local actual = image.scale(im, expected:size(2), expected:size(1), 'bilinear')
+  tester:assertTensorEq(actual, expected, 1e-5)
+end
+
+
+function test.bicubicUpscale()
+  local im = outerProduct{1, 2, 4, 2}
+  local expected = outerProduct{1, 1.4375, 2, 3.1875, 4, 3.25, 2}
+  local actual = image.scale(im, expected:size(2), expected:size(1), 'bicubic')
+  tester:assertTensorEq(actual, expected, 1e-5)
+end
+
+
+function test.bicubicDownscale()
+  local im = outerProduct{1, 2, 4, 2}
+  local expected = outerProduct{1, 3.1875, 2}
+  local actual = image.scale(im, expected:size(2), expected:size(1), 'bicubic')
+  tester:assertTensorEq(actual, expected, 1e-5)
+end
+
+
+function test.bicubicUpscale_ByteTensor()
+  local im = torch.ByteTensor{{0, 1, 32}}
+  local expected = torch.ByteTensor{{0, 0, 9, 32}}
+  local actual = image.scale(im, expected:size(2), expected:size(1), 'bicubic')
+  assertByteTensorEq(actual, expected)
+end
+
+
+function test.bilinearUpscale_ByteTensor()
+  local im = torch.ByteTensor{{1, 2},
+                              {2, 3}}
+  local expected = torch.ByteTensor{{1, 2, 2},
+                                    {2, 3, 3},
+                                    {2, 3, 3}}
+  local actual = image.scale(im, expected:size(2), expected:size(1))
+  assertByteTensorEq(actual, expected)
+end
+
+
+----------------------------------------------------------------------
+-- Scale test
+--
+local flip_tests = {}
+function flip_tests.test_transformation_largeByteImage(flip)
+    local x_real = image.fabio():double():mul(255)
+    local x_byte = x_real:clone():byte()
+
+    assert(x_byte:size(1) > 256 and x_byte:size(2) > 256, 'Tricky case only occurs for images larger than 256 px, pick another example')
+
+    local f_real, f_byte
+    f_real = image[flip](x_real)
+    f_byte = image[flip](x_byte)
+    assertByteTensorEq(f_real:byte(), f_byte, 1e-16,
+        flip .. ':  result for double and byte images do not match')
+end
+
+function flip_tests.test_inplace(flip)
+    local im = image.lena()
+    local not_inplace = image[flip](im)
+    local in_place = im:clone()
+    image[flip](in_place, in_place)
+    tester:assertTensorEq(in_place, not_inplace, 1e-16, flip .. ': result in-place does not match result not in-place')
+end
+
+for _, flip in pairs{'vflip', 'hflip'} do
+    for name, flip_test in pairs(flip_tests) do
+        test[name .. '_' .. flip] = function() return flip_test(flip) end
+    end
+end
+
+function test.test_vflip_simple()
+    local im_even = torch.Tensor{{1,2}, {3, 4}}
+    local expected_even = torch.Tensor{{3, 4}, {1, 2}}
+    local x_even = image.vflip(im_even)
+    tester:assertTensorEq(expected_even, x_even, 1e-16, 'vflip: fails on even size')
+    -- test inplace
+    image.vflip(im_even, im_even)
+    tester:assertTensorEq(expected_even, im_even, 1e-16, 'vflip: fails on even size in place')
+
+    local im_odd = torch.Tensor{{1,2}, {3, 4}, {5, 6}}
+    local expected_odd = torch.Tensor{{5,6}, {3, 4}, {1, 2}}
+    local x_odd = image.vflip(im_odd)
+    tester:assertTensorEq(expected_odd, x_odd, 1e-16, 'vflip: fails on odd size')
+    -- test inplace
+    image.vflip(im_odd, im_odd)
+    tester:assertTensorEq(expected_odd, im_odd, 1e-16, 'vflip: fails on odd size in place')
+end
+
+function test.test_hflip_simple()
+    local im_even = torch.Tensor{{1, 2}, {3, 4}}
+    local expected_even = torch.Tensor{{2, 1}, {4, 3}}
+    local x_even = image.hflip(im_even)
+    tester:assertTensorEq(expected_even, x_even, 1e-16, 'hflip: fails on even size')
+    -- test inplace
+    image.hflip(im_even, im_even)
+    tester:assertTensorEq(expected_even, im_even, 1e-16, 'hflip: fails on even size in place')
+
+    local im_odd = torch.Tensor{{1,2, 3}, {4, 5, 6}}
+    local expected_odd = torch.Tensor{{3, 2, 1}, {6, 5, 4}}
+    local x_odd = image.hflip(im_odd)
+    tester:assertTensorEq(expected_odd, x_odd, 1e-16, 'hflip: fails on odd size')
+    -- test inplace
+    image.hflip(im_odd, im_odd)
+    tester:assertTensorEq(expected_odd, im_odd, 1e-16, 'hflip: fails on odd size in place')
+end
+
+----------------------------------------------------------------------
+-- decompress jpg test
+--
+function test.CompareLoadAndDecompress()
+  -- This test breaks if someone removes lena from the repo
+  local imfile = getTestImagePath('grace_hopper_512.jpg')
+  if not paths.filep(imfile) then
+    error(imfile .. ' is missing!')
+  end
+
+  -- Load lena directly from the filename
+  local img = image.loadJPG(imfile)
+
+  -- Make sure the returned image width and height match the height and width
+  -- reported by graphicsmagick (just a sanity check)
+  local ok, gm = pcall(require, 'graphicsmagick')
+  if not ok then
+    -- skip this part of the test if graphicsmagick is not installed
+    print('\ntest.CompareLoadAndDecompress partially requires the ' ..
+          'graphicsmagick package to run. You can install it with ' ..
+          '"luarocks install graphicsmagick".')
+  else
+    local info = gm.info(imfile)
+    local w = info.width
+    local h = info.height
+    tester:assert(w == img:size(3), 'image dimension error ')
+    tester:assert(h == img:size(3), 'image dimension error ')
+  end
+
+  -- Now load the raw binary from the source file into a ByteTensor
+  local fin = torch.DiskFile(imfile, 'r')
+  fin:binary()
+  fin:seekEnd()
+  local file_size_bytes = fin:position() - 1
+  fin:seek(1)
+  local img_binary = torch.ByteTensor(file_size_bytes)
+  fin:readByte(img_binary:storage())
+  fin:close()
+
+  -- Now decompress the image from the ByteTensor
+  local img_from_tensor = image.decompressJPG(img_binary)
+
+  tester:assertlt((img_from_tensor - img):abs():max(), precision,
+    'images from load and decompress dont match! ')
+end
+
+function test.LoadInvalid()
+  -- Make sure nothing nasty happens if we try and load a "garbage" tensor
+  local file_size_bytes = 1000
+  local img_binary = torch.rand(file_size_bytes):mul(255):byte()
+
+  -- Now decompress the image from the ByteTensor
+  tester:assertError(
+    function() image.decompressJPG(img_binary) end,
+    'A non-nil was returned on an invalid input!'
+  )
+end
+
+----------------------------------------------------------------------
+-- compress jpg test
+--
+
+function test.CompressAndDecompress()
+  -- This test is unfortunately a correlated test: it will only be valid
+  -- if decompressJPG is OK.  However, since decompressJPG has it's own unit
+  -- test, this is problably fine.
+
+  local img = image.lena()
+
+  local quality = 100
+  local img_compressed = image.compressJPG(img, quality)
+  local size_100 = img_compressed:size(1)
+  local img_decompressed = image.decompressJPG(img_compressed)
+  local err = img_decompressed - img
+
+  -- Now in general we will get BIG compression artifacts (even at quality=100)
+  -- but they will be relatively small, so instead of a abs():max() test, we do
+  -- a mean and std test.
+  local mean_err = err:mean()
+  local std_err = err:std()
+  tester:assertlt(mean_err, precision_mean, 'compressJPG error is too high! ')
+  tester:assertlt(std_err, precision_std, 'compressJPG error is too high! ')
+
+  -- Also check that the quality setting scales the size of the compressed image
+  quality = 25
+  img_compressed = image.compressJPG(img, quality)
+  local size_25 = img_compressed:size(1)
+  tester:assertlt(size_25, size_100, 'compressJPG quality setting error! ')
+end
+
+----------------------------------------------------------------------
+-- Lab conversion test
+-- These tests break if someone removes lena from the repo
+
+
+local function testRoundtrip(forward, backward)
+  local expected = image.lena()
+  local actual = backward(forward(expected))
+  tester:assertTensorEq(actual, expected, 1e-4)
+end
+
+
+function test.rgb2lab()
+  testRoundtrip(image.rgb2lab, image.lab2rgb)
+end
+
+
+function test.rgb2hsv()
+  testRoundtrip(image.rgb2hsv, image.hsv2rgb)
+end
+
+
+function test.rgb2hsl()
+  testRoundtrip(image.rgb2hsl, image.hsl2rgb)
+end
+
+
+function test.rgb2y()
+  local x = torch.FloatTensor{{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}}:transpose(1, 3)
+  local actual = image.rgb2y(x)
+  local expected = torch.FloatTensor{{{0.299}, {0.587}, {0.114}}}
+  tester:assertTensorEq(actual, expected, 1e-5)
+end
+
+
+function test.y2jet()
+  local levels = torch.Tensor{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+  local expected = image.jetColormap(10)
+  local actual = image.y2jet(levels)[{{}, 1, {}}]:t()
+  tester:assertTensorEq(actual, expected, 1e-5)
+end
+
+
+function test.rgb2labByteTensor()
+  local lena = image.lena():byte()
+  tester:assertError(function () image.rgb2lab(lena) end)
+  tester:assertError(function () image.lab2rgb(lena) end)
+end
+
+
+local function testByteTensorRoundtrip(forward, backward, cond, msg)
+  local lena = toByteImage(image.lena())
+  local expected = lena
+  local actual = backward(forward(expected))  
+  assertByteTensorEq(actual, expected, cond, msg)
+end
+
+
+function test.toFromByteTensor()
+  local expected = toByteImage(image.lena():float())
+  local actual = toByteImage(expected:float():div(255))
+  assertByteTensorEq(actual, expected, nil, msg)
+end
+
+
+function test.rgb2hsvByteTensor()
+  testFunctionOnByteTensor(image.rgb2hsv, 'image.rgb2hsv error for ByteTensor')
+  testFunctionOnByteTensor(image.hsv2rgb, 'image.hsv2rgb error for ByteTensor')
+  testByteTensorRoundtrip(image.rgb2hsv, image.hsv2rgb, 3,
+                          'image.rgb2hsv roundtrip error for ByteTensor')
+end
+
+
+function test.rgb2hslByteTensor()
+  testFunctionOnByteTensor(image.rgb2hsl, 'image.hsl2rgb error for ByteTensor')
+  testFunctionOnByteTensor(image.hsl2rgb, 'image.rgb2hsl error for ByteTensor')
+  testByteTensorRoundtrip(image.rgb2hsl, image.hsl2rgb, 3,
+                          'image.rgb2hsl roundtrip error for ByteTensor')
+end
+
+
+function test.rgb2yByteTensor()
+  testFunctionOnByteTensor(image.rgb2y, 'image.rgb2y error for ByteTensor')
+end
+
+
+function test.y2jetByteTensor()
+  local levels = torch.Tensor{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+  local expected = toByteImage(image.y2jet(levels))
+  local actual = image.y2jet(levels:byte())
+  assertByteTensorEq(actual, expected, nil)
+end
+
+
+----------------------------------------------------------------------
+-- PNG test
+--
+local function toBlob(filename)
+  local f = torch.DiskFile(filename, 'r')
+  f:binary()
+  f:seekEnd()
+  local size = f:position() - 1
+  f:seek(1)
+  local blob = torch.ByteTensor(size)
+  f:readByte(blob:storage())
+  f:close()
+  return blob
+end
+
+local function checkPNG(imfile, depth, tensortype, want)
+  local img = image.load(imfile, depth, tensortype)
+  -- Tensors have to be converted to double, since assertTensorEq does not support ByteTensor
+  --print('img: ', img)
+  --print('want: ', want)
+  assertByteTensorEq(img, want, precision_mean,
+                    string.format('%s: pixel values are unexpected', imfile))
+end
+
+function test.LoadPNG()
+  -- Gray 8-bit PNG image with width = 3, height = 1
+  local gray8byte = torch.ByteTensor({{{0,127,255}}})
+  checkPNG(getTestImagePath('gray3x1.png'), 1, 'byte', gray8byte)
+
+  local gray8double = torch.DoubleTensor({{{0, 127/255, 1}}})
+  checkPNG(getTestImagePath('gray3x1.png'), 1, 'double', gray8double)
+
+  -- Gray 16-bit PNG image with width=1, height = 2
+  local gray16byte = torch.ByteTensor{{{0}, {255}}}
+  checkPNG(getTestImagePath('gray16-1x2.png'), 1, 'byte', gray16byte)
+
+  local gray16float = torch.FloatTensor{{{0}, {65534/65535}}}
+  checkPNG(getTestImagePath('gray16-1x2.png'), 1, 'float', gray16float)
+
+  -- Color 8-bit PNG image with width = 2, height = 1
+  local rgb8byte = torch.ByteTensor{{{255, 0}}, {{0, 127}}, {{63, 0}}}
+  checkPNG(getTestImagePath('rgb2x1.png'), 3, 'byte', rgb8byte)
+
+  local rgb8float = torch.FloatTensor{{{1, 0}}, {{0, 127/255}}, {{63/255, 0}}}
+  checkPNG(getTestImagePath('rgb2x1.png'), 3, 'float', rgb8float)
+
+  -- Color 16-bit PNG image with width = 2, height = 1
+  local rgb16byte = torch.ByteTensor{{{255, 0}}, {{0, 127}}, {{63, 0}}}
+  checkPNG(getTestImagePath('rgb16-2x1.png'), 3, 'byte', rgb16byte)
+
+  local rgb16float = torch.FloatTensor{{{1, 0}}, {{0, 32767/65535}}, {{16383/65535, 0}}}
+  checkPNG(getTestImagePath('rgb16-2x1.png'), 3, 'float', rgb16float)
+end
+
+function test.DecompressPNG()
+  tester:assertTensorEq(
+    image.load(getTestImagePath('rgb2x1.png')),
+    image.decompressPNG(toBlob(getTestImagePath('rgb2x1.png'))),
+    precision_mean,
+    'decompressed and loaded images should be equal'
+  )
+end
+
+function test.LoadCorruptedPNG()
+  tester:assertErrorPattern(
+    function() image.load(getTestImagePath("corrupt-ihdr.png")) end,
+    "Error during init_io",
+    "corrupted image should not be loaded or unexpected error message"
+  )
+end
+
+----------------------------------------------------------------------
+-- PPM test
+--
+function test.test_ppmload()
+    -- test.ppm is a 100x1 "French flag" like image, i.e the first pixel is blue
+    -- the 84 next pixels are white and the 15 last pixels are red.
+    -- This makes possible to implement a non regression test vs. the former
+    -- PPM loader which had for effect to skip the first 85 pixels because of
+    -- a header parser bug
+    local img = image.load(getTestImagePath("P6.ppm"))
+    local pix = img[{ {}, {1}, {1} }]
+
+    -- Check the first pixel is blue
+    local ref = torch.zeros(3, 1, 1)
+    ref[3][1][1] = 1
+    tester:assertTensorEq(pix, ref, 0, "PPM load: first pixel check failed")
+end
+
+
+function test.test_pgmaload()
+    -- ascii.ppm is a PGMA file (ascii pgm)
+    -- example comes from ehere
+    -- http://people.sc.fsu.edu/~jburkardt/data/pgma/pgma.html
+    local img = image.load(getTestImagePath("P2.pgm"), 1, 'byte')
+    local max_gray = 15 -- 4th line of ascii.pgm
+    local ascii_val = 3 -- pixel (2,2) in the file
+    local pix_val = math.floor(255 * ascii_val / max_gray)
+
+    local pix = img[1][2][2]
+
+    -- Check that Pixel(1, 2,2) == 3
+    local ref = pix_val
+    tester:asserteq(pix, ref, "PGMA load: pixel check failed")
+end
+
+function test.test_pgmload()
+    -- test.ppm is a 100x1 "French flag" like image, i.e the first pixel is blue
+    -- the 84 next pixels are white and the 15 last pixels are red.
+    -- This makes possible to implement a non regression test vs. the former
+    -- PPM loader which had for effect to skip the first 85 pixels because of
+    -- a header parser bug
+    local img = image.load(getTestImagePath("P5.pgm"))
+    local pix = img[{ {}, {1}, {1} }]
+
+    local ref = torch.zeros(1, 1, 1); ref[1][1][1] = 0.07
+    tester:assertTensorEq(pix, ref, 0.001, "PPM load: first pixel check failed")
+end
+
+function test.test_pbmload()
+  -- test.pbm is a Portable BitMap (not supported)
+  tester:assertErrorPattern(
+    function() image.loadPPM(getTestImagePath("P4.pbm")) end,
+    "unsupported magic number",
+    "PBM format should not be loaded or unexpected error message"
+  )
+end
+
+----------------------------------------------------------------------
+-- Text drawing test
+--
+function test.test_textdraw()
+  local types = {
+     ["torch.ByteTensor"]   = "byte",
+     ["torch.DoubleTensor"] = "double",
+     ["torch.FloatTensor"]  = "float"
+  }
+  for k,v in pairs(types) do
+    local img = image.drawText(
+       torch.zeros(3, 24, 24):type(k),
+       "foo\nbar", 2, 4, {color={255, 255, 255}, bg={255, 0, 0}}
+    )
+    checkPNG(getTestImagePath("foobar.png"), 3, v, img)
+  end
+end
+
+----------------------------------------------------------------------
+-- Text drawing rect
+--
+function test.test_drawRect()
+  local types = {
+     ["torch.ByteTensor"]   = "byte",
+     ["torch.DoubleTensor"] = "double",
+     ["torch.FloatTensor"]  = "float"
+  }
+  for k,v in pairs(types) do
+    local bg = torch.zeros(3, 24, 12):type(k)
+    if k == 'torch.ByteTensor' then
+      bg:fill(3)
+    else
+      bg:fill(3/255)
+    end
+    local img = image.drawRect(bg, 5, 5, 10, 20, {color={255, 0, 255}})
+    checkPNG(getTestImagePath("rectangle.png"), 3, v, img)
+  end
+end
+
+function image.test(tests, seed)
+   local defaultTensorType = torch.getdefaulttensortype()
+   torch.setdefaulttensortype('torch.DoubleTensor')
+   seed = seed or os.time()
+   print('seed: ', seed)
+   math.randomseed(seed)
+   tester = torch.Tester()
+   tester:add(test)
+   tester:run(tests)
+   torch.setdefaulttensortype(defaultTensorType)
+   return tester
+end
diff --git a/test/test_rotate.lua b/test/test_rotate.lua
new file mode 100644
index 0000000..8f7ef91
--- /dev/null
+++ b/test/test_rotate.lua
@@ -0,0 +1,75 @@
+require 'image'
+
+torch.setdefaulttensortype('torch.FloatTensor')
+torch.setnumthreads(16)
+
+local function test_rotate(src, mode)
+   torch.manualSeed(11)
+   local mean_dist = 0.0
+   for i = 1, 10 do
+      local theta = torch.uniform(0, 2 * math.pi)
+      local d1, d2, d3, d4
+      
+      -- rotate
+      if mode then
+         d1 = image.rotate(src, theta, mode)
+         d2 = src.new():resizeAs(src)
+         image.rotate(d2, src, theta, mode)
+      else
+         d1 = image.rotate(src, theta)
+         d2 = src.new():resizeAs(src)
+         image.rotate(d2, src, theta)
+      end
+
+      -- revert
+      local revert = 2 * math.pi - theta
+      if mode then
+         d3 = image.rotate(d1, revert, mode)
+         d4 = src.new():resizeAs(src)
+         image.rotate(d4, d2, revert, mode)
+      else
+         d3 = image.rotate(d1, revert)
+         d4 = src.new():resizeAs(src)
+         image.rotate(d4, d2, revert)
+      end
+      
+      -- diff
+      if src:dim() == 3 then
+         local cs = image.crop(src, src:size(2) / 4, src:size(3) / 4, src:size(2) / 4 * 3, src:size(3) / 4 * 3)
+         local c3 = image.crop(d3, src:size(2) / 4, src:size(3) / 4, src:size(2) / 4 * 3, src:size(3) / 4 * 3)
+         local c4 = image.crop(d4, src:size(2) / 4, src:size(3) / 4, src:size(2) / 4 * 3, src:size(3) / 4 * 3)
+         mean_dist = mean_dist + cs:dist(c3)
+         mean_dist = mean_dist + cs:dist(c4)
+      elseif src:dim() == 2 then
+         local cs = image.crop(src, src:size(1) / 4, src:size(2) / 4, src:size(1) / 4 * 3, src:size(2) / 4 * 3)
+         local c3 = image.crop(d3, src:size(1) / 4, src:size(2) / 4, src:size(1) / 4 * 3, src:size(2) / 4 * 3)
+         local c4 = image.crop(d4, src:size(1) / 4, src:size(2) / 4, src:size(1) / 4 * 3, src:size(2) / 4 * 3)
+         mean_dist = mean_dist + cs:dist(c3)
+         mean_dist = mean_dist + cs:dist(c4)
+      end
+      --[[
+      if i == 1 then
+         image.display(src)
+         image.display(d1)
+         image.display(d2)
+         image.display(d3)
+         image.display(d4)
+      end
+      --]]
+   end
+   if mode then
+      print("mode = " .. mode .. ", mean dist: " .. mean_dist / (10 * 2))
+   else
+      print("mode = nil, mean dist: " .. mean_dist / (10 * 2))
+   end
+end
+local src = image.scale(image.lena(), 128, 128, 'bilinear')
+print("** dim3")
+test_rotate(src, nil)
+test_rotate(src, 'simple')
+test_rotate(src, 'bilinear')
+print("** dim2")
+src = src:select(1, 1)
+test_rotate(src, nil)
+test_rotate(src, 'simple')
+test_rotate(src, 'bilinear')
diff --git a/test/test_warp.lua b/test/test_warp.lua
new file mode 100644
index 0000000..5c4a1ed
--- /dev/null
+++ b/test/test_warp.lua
@@ -0,0 +1,139 @@
+require 'image'
+torch.setdefaulttensortype('torch.FloatTensor')
+torch.setnumthreads(16)
+
+im = image.lena()
+-- Subsample lena like crazy
+im = image.scale(im, im:size()[3] / 8, im:size()[2] / 8, 'bilinear')
+
+width = im:size()[3]  -- 512 / 8
+height = im:size()[2]  -- 512 / 8
+nchan = im:size()[1]  -- 3
+upscale = 8
+width_up = width * upscale
+height_up = height * upscale
+
+-- ******************************************
+-- COMPARE RESULTS OF UPSCALE (INTERPOLATION)
+-- ******************************************
+
+-- x/y grids
+grid_y = torch.ger( torch.linspace(-1,1,height_up), torch.ones(width_up) )
+grid_x = torch.ger( torch.ones(height_up), torch.linspace(-1,1,width_up) )
+
+flow = torch.FloatTensor()
+flow:resize(2,height_up,width_up)
+flow:zero()
+
+-- Apply scale
+flow_scale = torch.FloatTensor()
+flow_scale:resize(2,height_up,width_up)
+flow_scale[1] = grid_y
+flow_scale[2] = grid_x
+flow_scale[1]:add(1):mul(0.5) -- 0 to 1
+flow_scale[2]:add(1):mul(0.5) -- 0 to 1
+flow_scale[1]:mul(height-1)
+flow_scale[2]:mul(width-1)
+flow:add(flow_scale)
+
+t0 = sys.clock()
+im_simple = image.warp(im, flow, 'simple', false)
+t1 = sys.clock()
+print("Upscale Time simple = " .. (t1 - t0))  -- Not a robust measure (should average)
+image.display{image = im_simple, zoom = 1, legend = 'upscale simple'}
+
+t0 = sys.clock()
+im_bilinear = image.warp(im, flow, 'bilinear', false)
+t1 = sys.clock()
+print("Upscale Time bilinear = " .. (t1 - t0))  -- Not a robust measure (should average)
+image.display{image = im_bilinear, zoom = 1, legend = 'upscale bilinear'}
+
+t0 = sys.clock()
+im_bicubic = image.warp(im, flow, 'bicubic', false)
+t1 = sys.clock()
+print("Upscale Time bicubic = " .. (t1 - t0))  -- Not a robust measure (should average)
+image.display{image = im_bicubic, zoom = 1, legend = 'upscale bicubic'}
+
+t0 = sys.clock()
+im_lanczos = image.warp(im, flow, 'lanczos', false)
+t1 = sys.clock()
+print("Upscale Time lanczos = " .. (t1 - t0))  -- Not a robust measure (should average)
+image.display{image = im_lanczos, zoom = 1, legend = 'upscale lanczos'}
+
+-- *********************************************
+-- NOW TRY A ROTATION AT THE STANDARD RESOLUTION
+-- *********************************************
+
+im = image.lena()
+-- Subsample lena a little bit
+im = image.scale(im, im:size()[3] / 4, im:size()[2] / 4, 'bilinear')
+
+width = im:size()[3]  -- 512 / 4
+height = im:size()[2]  -- 512 / 4
+nchan = im:size()[1]  -- 3
+
+grid_y = torch.ger( torch.linspace(-1,1,height), torch.ones(width) )
+grid_x = torch.ger( torch.ones(height), torch.linspace(-1,1,width) )
+
+flow = torch.FloatTensor()
+flow:resize(2,height,width)
+flow:zero()
+
+-- Apply uniform scale
+flow_scale = torch.FloatTensor()
+flow_scale:resize(2,height,width)
+flow_scale[1] = grid_y
+flow_scale[2] = grid_x
+flow_scale[1]:add(1):mul(0.5) -- 0 to 1
+flow_scale[2]:add(1):mul(0.5) -- 0 to 1
+flow_scale[1]:mul(height-1)
+flow_scale[2]:mul(width-1)
+flow:add(flow_scale)
+
+flow_rot = torch.FloatTensor()
+flow_rot:resize(2,height,width)
+flow_rot[1] = grid_y * ((height-1)/2) * -1
+flow_rot[2] = grid_x * ((width-1)/2) * -1
+view = flow_rot:reshape(2,height*width)
+function rmat(deg)
+  local r = deg/180*math.pi
+  return torch.FloatTensor{{math.cos(r), -math.sin(r)}, {math.sin(r), math.cos(r)}}
+end
+rot_angle = 360/7  -- a nice non-integer value
+rotmat = rmat(rot_angle)
+flow_rotr = torch.mm(rotmat, view)
+flow_rot = flow_rot - flow_rotr:reshape( 2, height, width )
+flow:add(flow_rot)
+
+t0 = sys.clock()
+im_simple = image.warp(im, flow, 'simple', false)
+t1 = sys.clock()
+print("Rotation Time simple = " .. (t1 - t0))  -- Not a robust measure (should average)
+image.display{image = im_simple, zoom = 4, legend = 'rotation simple'}
+
+t0 = sys.clock()
+im_bilinear = image.warp(im, flow, 'bilinear', false)
+t1 = sys.clock()
+print("Rotation Time bilinear = " .. (t1 - t0))  -- Not a robust measure (should average)
+image.display{image = im_bilinear, zoom = 4, legend = 'rotation bilinear'}
+
+t0 = sys.clock()
+im_bicubic = image.warp(im, flow, 'bicubic', false)
+t1 = sys.clock()
+print("Rotation Time bicubic = " .. (t1 - t0))  -- Not a robust measure (should average)
+image.display{image = im_bicubic, zoom = 4, legend = 'rotation bicubic'}
+
+t0 = sys.clock()
+im_lanczos = image.warp(im, flow, 'lanczos', false)
+t1 = sys.clock()
+print("Rotation Time lanczos = " .. (t1 - t0))  -- Not a robust measure (should average)
+image.display{image = im_lanczos, zoom = 4, legend = 'rotation lanczos'}
+
+im_lanczos = image.warp(im, flow, 'lanczos', false, 'pad')
+image.display{image = im_lanczos, zoom = 4, legend = 'rotation lanczos (default pad)'}
+
+im_lanczos = image.warp(im, flow, 'lanczos', false, 'pad', 1)
+image.display{image = im_lanczos, zoom = 4, legend = 'rotation lanczos (pad 1)'}
+
+image.display{image = im, zoom = 4, legend = 'source image'}
+
diff --git a/win.ui b/win.ui
new file mode 100644
index 0000000..a2dfdc5
--- /dev/null
+++ b/win.ui
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Display</class>
+ <widget class="QWidget" name="Display">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>640</width>
+    <height>480</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <property name="margin">
+    <number>0</number>
+   </property>
+   <item>
+    <widget class="QFrame" name="frame">
+     <property name="sizeIncrement">
+      <size>
+       <width>10</width>
+       <height>10</height>
+      </size>
+     </property>
+     <property name="frameShape">
+      <enum>QFrame::NoFrame</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/lua-torch-image.git



More information about the debian-science-commits mailing list