View Javadoc

1   /**
2    * Copyright (c) 2011, The University of Southampton and the individual contributors.
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without modification,
6    * are permitted provided that the following conditions are met:
7    *
8    *   * 	Redistributions of source code must retain the above copyright notice,
9    * 	this list of conditions and the following disclaimer.
10   *
11   *   *	Redistributions in binary form must reproduce the above copyright notice,
12   * 	this list of conditions and the following disclaimer in the documentation
13   * 	and/or other materials provided with the distribution.
14   *
15   *   *	Neither the name of the University of Southampton nor the names of its
16   * 	contributors may be used to endorse or promote products derived from this
17   * 	software without specific prior written permission.
18   *
19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  package org.openimaj.image.processing.transform;
31  
32  import java.util.ArrayList;
33  import java.util.List;
34  
35  import org.openimaj.citation.annotation.Reference;
36  import org.openimaj.citation.annotation.ReferenceType;
37  import org.openimaj.image.FImage;
38  import org.openimaj.image.Image;
39  import org.openimaj.image.processing.convolution.FGaussianConvolve;
40  import org.openimaj.image.processing.convolution.FImageConvolveSeparable;
41  import org.openimaj.image.processor.SinglebandImageProcessor;
42  import org.openimaj.math.geometry.point.Point2d;
43  import org.openimaj.math.geometry.transforms.TransformUtilities;
44  
45  /**
46   * Utility methods to simulate affine transformations defined by a rotation and
47   * tilt, or series of rotations and tilts.
48   * 
49   * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
50   * 
51   * @param <I>
52   *            Concrete subclass of {@link Image}
53   * @param <P>
54   *            Pixel type
55   */
56  @Reference(
57  		type = ReferenceType.Article,
58  		author = { "Morel, Jean-Michel", "Yu, Guoshen" },
59  		title = "{ASIFT: A New Framework for Fully Affine Invariant Image Comparison}",
60  		year = "2009",
61  		journal = "SIAM J. Img. Sci.",
62  		publisher = "Society for Industrial and Applied Mathematics")
63  public abstract class AffineSimulation<I extends Image<P, I> & SinglebandImageProcessor.Processable<Float, FImage, I>, P>
64  {
65  	protected static final float PI = 3.141592654f;
66  	protected static final float InitialAntiAliasingSigma = 1.6f;
67  
68  	private AffineSimulation() {
69  	}
70  
71  	/**
72  	 * Compute the position of a point in an image given the position in the
73  	 * transformed image and the transform parameters.
74  	 * 
75  	 * @param pt
76  	 *            the point in the transformed image
77  	 * @param width
78  	 *            the width of the untransformed image
79  	 * @param height
80  	 *            the height of the untransformed image
81  	 * @param theta
82  	 *            the rotation
83  	 * @param t
84  	 *            the tilt
85  	 * @return the point mapped to the untransformed image
86  	 */
87  	public static Point2d transformToOriginal(Point2d pt, int width, int height, float theta, float t) {
88  		if (t == 1)
89  			return pt;
90  
91  		return internalTransformToOriginal(pt, width, height, theta, t);
92  	}
93  
94  	/**
95  	 * Compute the position of a point in an image given the position in the
96  	 * transformed image and the transform parameters.
97  	 * 
98  	 * @param pt
99  	 *            the point in the transformed image
100 	 * @param original
101 	 *            the original untransformed image
102 	 * @param theta
103 	 *            the rotation
104 	 * @param t
105 	 *            the tilt
106 	 * @return the point mapped to the untransformed image
107 	 */
108 	public static Point2d transformToOriginal(Point2d pt, Image<?, ?> original, float theta, float t) {
109 		if (t == 1)
110 			return pt;
111 
112 		return internalTransformToOriginal(pt, original.getWidth(), original.getHeight(), theta, t);
113 	}
114 
115 	/**
116 	 * Compute the position of a point in an image given the position in the
117 	 * transformed image and the transform parameters.
118 	 * 
119 	 * @param pt
120 	 *            the point in the transformed image
121 	 * @param width
122 	 *            the width of the untransformed image
123 	 * @param height
124 	 *            the height of the untransformed image
125 	 * @param params
126 	 *            the simulation parameters
127 	 * @return the point mapped to the untransformed image
128 	 */
129 	public static Point2d transformToOriginal(Point2d pt, int width, int height, AffineParams params) {
130 		return transformToOriginal(pt, width, height, params.theta, params.tilt);
131 	}
132 
133 	/**
134 	 * Compute the position of a point in an image given the position in the
135 	 * transformed image and the transform parameters.
136 	 * 
137 	 * @param pt
138 	 *            the point in the transformed image
139 	 * @param original
140 	 *            the original untransformed image
141 	 * @param params
142 	 *            the simulation parameters
143 	 * @return the point mapped to the untransformed image
144 	 */
145 	public static Point2d transformToOriginal(Point2d pt, Image<?, ?> original, AffineParams params) {
146 		return transformToOriginal(pt, original.getWidth(), original.getHeight(), params.theta, params.tilt);
147 	}
148 
149 	protected static Point2d internalTransformToOriginal(Point2d pt, int width, int height, float Rtheta, float t1)
150 	{
151 		float x_ori, y_ori;
152 		Rtheta = Rtheta * PI / 180;
153 
154 		if (Rtheta <= PI / 2) {
155 			x_ori = 0;
156 			y_ori = (float) ((width) * Math.sin(Rtheta) / t1);
157 		} else {
158 			x_ori = (float) (-(width) * Math.cos(Rtheta) / 1);
159 			y_ori = (float) (((width) * Math.sin(Rtheta) + (height) * Math.sin(Rtheta - PI / 2)) / t1);
160 		}
161 
162 		final float sin_Rtheta = (float) Math.sin(Rtheta);
163 		final float cos_Rtheta = (float) Math.cos(Rtheta);
164 
165 		final Point2d ptout = pt.copy();
166 
167 		/*
168 		 * project the coordinates of im1 to original image before tilt-rotation
169 		 * transform; get the coordinates with respect to the 'origin' of the
170 		 * original image before transform
171 		 */
172 		ptout.setX(pt.getX() - x_ori);
173 		ptout.setY(pt.getY() - y_ori);
174 
175 		/* Invert tilt */
176 		ptout.setX(ptout.getX() * 1);
177 		ptout.setY(ptout.getY() * t1);
178 
179 		/*
180 		 * Invert rotation (Note that the y direction (vertical) is inverse to
181 		 * the usual concention. Hence Rtheta instead of -Rtheta to inverse the
182 		 * rotation.)
183 		 */
184 		final float tx = cos_Rtheta * ptout.getX() - sin_Rtheta * ptout.getY();
185 		final float ty = sin_Rtheta * ptout.getX() + cos_Rtheta * ptout.getY();
186 
187 		ptout.setX(tx);
188 		ptout.setY(ty);
189 
190 		return ptout;
191 	}
192 
193 	/**
194 	 * Transform the coordinates of the given points from a transformed image to
195 	 * the original space.
196 	 * 
197 	 * @param <Q>
198 	 *            Type of interest point list
199 	 * @param <T>
200 	 *            Type of interest point
201 	 * @param <I>
202 	 *            Type of {@link Image}
203 	 * @param points
204 	 *            the points
205 	 * @param original
206 	 *            the original untransformed image
207 	 * @param theta
208 	 *            the rotation
209 	 * @param tilt
210 	 *            the tilt
211 	 */
212 	public static <Q extends List<T>, T extends Point2d, I extends Image<?, I>> void transformToOriginal(Q points,
213 			I original, float theta, float tilt)
214 	{
215 		final List<T> keys_to_remove = new ArrayList<T>();
216 		float x_ori, y_ori;
217 
218 		if (theta <= PI / 2) {
219 			x_ori = 0;
220 			y_ori = (float) ((original.getWidth()) * Math.sin(theta) / tilt);
221 		} else {
222 			x_ori = (float) (-(original.getWidth()) * Math.cos(theta) / 1);
223 			y_ori = (float) (((original.getWidth()) * Math.sin(theta) + (original.getHeight())
224 					* Math.sin(theta - PI / 2)) / tilt);
225 		}
226 
227 		final float sin_Rtheta = (float) Math.sin(theta);
228 		final float cos_Rtheta = (float) Math.cos(theta);
229 
230 		for (final T k : points) {
231 			/*
232 			 * project the coordinates of im1 to original image before
233 			 * tilt-rotation transform
234 			 */
235 			/*
236 			 * Get the coordinates with respect to the 'origin' of the original
237 			 * image before transform
238 			 */
239 			k.setX(k.getX() - x_ori);
240 			k.setY(k.getY() - y_ori);
241 			/* Invert tilt */
242 			k.setX(k.getX() * 1);
243 			k.setY(k.getY() * tilt);
244 			/*
245 			 * Invert rotation (Note that the y direction (vertical) is inverse
246 			 * to the usual concention. Hence Rtheta instead of -Rtheta to
247 			 * inverse the rotation.)
248 			 */
249 			final float tx = cos_Rtheta * k.getX() - sin_Rtheta * k.getY();
250 			final float ty = sin_Rtheta * k.getX() + cos_Rtheta * k.getY();
251 
252 			k.setX(tx);
253 			k.setY(ty);
254 
255 			if (tx <= 0 || ty <= 0 || tx >= original.getWidth() || ty >= original.getHeight()) {
256 				keys_to_remove.add(k);
257 			}
258 		}
259 		points.removeAll(keys_to_remove);
260 	}
261 
262 	/**
263 	 * Compute the transformed images based on the given number of tilts.
264 	 * 
265 	 * @param image
266 	 *            the image to transform.
267 	 * @param numTilts
268 	 *            the number of tilts to simulate.
269 	 * @return the transformed images
270 	 * @throws IllegalArgumentException
271 	 *             if the number of tilts is < 1
272 	 */
273 	public static <I extends Image<P, I> & SinglebandImageProcessor.Processable<Float, FImage, I>, P>
274 			List<I> transformImage(I image, int numTilts)
275 	{
276 		if (numTilts < 1) {
277 			throw new IllegalArgumentException("Number of tilts num_tilt should be equal or larger than 1.");
278 		}
279 
280 		final List<I> transformed = new ArrayList<I>();
281 		int num_rot1 = 0;
282 
283 		final int num_rot_t2 = 10;
284 		final float t_min = 1;
285 		final float t_k = (float) Math.sqrt(2);
286 
287 		for (int tt = 1; tt <= numTilts; tt++) {
288 			final float t = t_min * (float) Math.pow(t_k, tt - 1);
289 
290 			if (t == 1) {
291 				transformed.add(image.clone());
292 			} else {
293 				num_rot1 = Math.round(num_rot_t2 * t / 2);
294 
295 				if (num_rot1 % 2 == 1) {
296 					num_rot1 = num_rot1 + 1;
297 				}
298 				num_rot1 = num_rot1 / 2;
299 
300 				final float delta_theta = PI / num_rot1;
301 
302 				for (int rr = 1; rr <= num_rot1; rr++) {
303 					final float theta = delta_theta * (rr - 1);
304 
305 					transformed.add(transformImage(image, theta, t));
306 				}
307 			}
308 		}
309 
310 		return transformed;
311 	}
312 
313 	/**
314 	 * Compute a single transformed image for a given rotation and tilt.
315 	 * 
316 	 * @param image
317 	 *            the image
318 	 * @param theta
319 	 *            the rotation angle
320 	 * @param t
321 	 *            the tilt amount
322 	 * @return the transformed image
323 	 */
324 	public static <I extends Image<P, I> & SinglebandImageProcessor.Processable<Float, FImage, I>, P> I transformImage(
325 			I image, float theta, float t)
326 	{
327 		final float t1 = 1;
328 		final float t2 = 1 / t;
329 
330 		// Perform rotation
331 		final I image_rotated = ProjectionProcessor.project(image, TransformUtilities.rotationMatrix(-theta));
332 
333 		// Perform anti-aliasing filtering by convolving with a Gaussian in the
334 		// vertical direction
335 		final float sigma_aa = InitialAntiAliasingSigma * t / 2;
336 		image_rotated.processInplace(new FImageConvolveSeparable(null, FGaussianConvolve.makeKernel(sigma_aa)));
337 
338 		// Squash the image in the x and y direction by t1 and t2 to mimic tilt
339 		return ProjectionProcessor.project(image_rotated, TransformUtilities.scaleMatrix(t1, t2));
340 	}
341 
342 	/**
343 	 * Compute a single transformed image for a given rotation and tilt.
344 	 * 
345 	 * @param image
346 	 *            the image
347 	 * @param params
348 	 *            the simulation parameters
349 	 * @return the transformed image
350 	 */
351 	public static <I extends Image<P, I> & SinglebandImageProcessor.Processable<Float, FImage, I>, P> I transformImage(
352 			I image, AffineParams params)
353 	{
354 		return transformImage(image, params.theta, params.tilt);
355 	}
356 
357 }