# Using ImageTransformation[] with a lookup table

I need to transform images by ImageTransformation in a time critical application. Therefore I precalculate a lookup table lut of the transformation function for the true pixel range {{0,width},{0,height}}. However, when I use

ImageTransformation[img, lut[[#[[1]],#[[2]]]]&, DataRange->Full]


I get the error message

ImageTransformation::imgtrnsfun: The function lu should map a pixel position of form {x, y} to {x', y'}. >>

Is it possible to provide a lookup table to ImageTransformation?

-

If the problem is that the transformation function is slow to compute, a simple way to create and use a look-up table is to memoize the function:

(* create an example image *)
image = RandomImage[1, {30, 20}, ColorSpace -> "RGB"] ~ ImageResize ~ Scaled[10]

(* define the transformation function with memoization *)
mem : func[{x_, y_}] := mem = {x + 0.01 Abs@BesselJ[1, 10 x], y^2}


The first time you use the transformation, it will be slow:

AbsoluteTiming[ImageTransformation[image, func]]


But next time you use it, it will be much faster, as the values have been remembered (you could say your look-up table is stored in the DownValues of func)

AbsoluteTiming[ImageTransformation[image, func]]


More speed

The nice thing about ImageTransformation is that it will interpolate between pixels so that you can sample the image at non-integer pixel positions. If you can tolerate losing the interpolation feature (i.e. so that each pixel in the output image is a direct copy of a pixel from the input image), you can get some more speed by manipulating the image data directly.

(Updated with faster code)

The code below creates a fast image transformation function for a specific size of image. Briefly, the procedure is:

1. Create an Image expression in which the pixel values are just the integers from 1 to n (effectively labelling each pixel position with a unique identifier)
2. Run the standard ImageTranformation on that image. The option Padding -> "Reflected" is used to ensure that the resulting image consists only of pixels in the input image. "Periodic" would also work.
3. The result of the image transformation is flattened and rounded - this is the look-up table.
4. Create a compiled function to flatten the input image data, apply the look-up table using Part, and partition the result.
5. Sandwich the compiled function between Image and ImageData

The output is a function which can be applied directly to an image.

makeFastTransformation[func_, {cols_, rows_}] := Module[{lut, cfunc},
lut = ImageData @ ImageTransformation[Image @ Partition[Range[rows*cols], cols],
func, Resampling -> "Nearest", Padding -> "Reflected"];
cfunc = With[{lut = Round @ Flatten @ lut}, Compile[{{data, _Real, 3}},
Partition[Flatten[data, 1][[lut]], cols]]];
Composition[Image, cfunc, ImageData]]


The resulting function is very fast

trans = makeFastTransformation[func, ImageDimensions[image]];

AbsoluteTiming[trans @ image]


To measure the time properly we need to do the transformation several times. It came out at about about 1.4 ms on my PC, about 80 times faster than ImageTransformation with the memoized function.

AbsoluteTiming[Do[trans @ image, {1000}]]
(* {1.3593228, Null} *)

-

ImageTransformation works with functions, not tables. It should be straightforward to define a function that carries out the same transformation as the table, but you will need to be aware that the #[[1]] and #[[2]] arguments go from 0 to 1 (across the image) so you will need to design the function to handle this input range. For example, you might want a 256x256 element table. Then you would take the #[[1]] as input to the function, multiply by 255, Round to the nearest integer, and add 1 (i.e., Round[255 #[[1]]+1]) and the same for the second parameter. Then use these to index into your table. Your function/table will need to have appropriate outputs, that is, each element of the table will need to be a pair of numbers in the range (0,1).

For example, in an answer to this this question, I created a function using the #[[1]] and #[[2]] arguments, to that may help as you are designing your function to imitate the action of a table.

-

I think you have to make sure that your transformation function always handles input cleanly. Here's a test you can do to see what goes into your function. (And I think you can use real coordinates if you use the DataRange option.)

i = ImageResize[ExampleData[{"TestImage", "Mandrill"}], {20, 20}];


The function:

f[pt_] := (Print[pt]; {pt[[1]], pt[[2]]});


Call it:

ImageTransformation[i, f, DataRange -> Full]


The output:

{1034.,17475.}
{0.5,19.5}
{1.5,19.5}
{2.5,19.5}
{3.5,19.5}
{4.5,19.5}
{5.5,19.5}
{6.5,19.5}
{7.5,19.5}
{8.5,19.5}
...


Apart from the hard-to-explain first line (‽), you can see that none of these coordinate pairs are going to be very useful as look-up table pointers. So, as bill's answer points out, make sure your function handles inputs cleanly.

-