001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.image.processing.transform; 031 032import java.util.ArrayList; 033import java.util.List; 034 035import org.openimaj.citation.annotation.Reference; 036import org.openimaj.citation.annotation.ReferenceType; 037import org.openimaj.image.FImage; 038import org.openimaj.image.Image; 039import org.openimaj.image.processing.convolution.FGaussianConvolve; 040import org.openimaj.image.processing.convolution.FImageConvolveSeparable; 041import org.openimaj.image.processor.SinglebandImageProcessor; 042import org.openimaj.math.geometry.point.Point2d; 043import org.openimaj.math.geometry.transforms.TransformUtilities; 044 045/** 046 * Utility methods to simulate affine transformations defined by a rotation and 047 * tilt, or series of rotations and tilts. 048 * 049 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 050 * 051 * @param <I> 052 * Concrete subclass of {@link Image} 053 * @param <P> 054 * Pixel type 055 */ 056@Reference( 057 type = ReferenceType.Article, 058 author = { "Morel, Jean-Michel", "Yu, Guoshen" }, 059 title = "{ASIFT: A New Framework for Fully Affine Invariant Image Comparison}", 060 year = "2009", 061 journal = "SIAM J. Img. Sci.", 062 publisher = "Society for Industrial and Applied Mathematics") 063public abstract class AffineSimulation<I extends Image<P, I> & SinglebandImageProcessor.Processable<Float, FImage, I>, P> 064{ 065 protected static final float PI = 3.141592654f; 066 protected static final float InitialAntiAliasingSigma = 1.6f; 067 068 private AffineSimulation() { 069 } 070 071 /** 072 * Compute the position of a point in an image given the position in the 073 * transformed image and the transform parameters. 074 * 075 * @param pt 076 * the point in the transformed image 077 * @param width 078 * the width of the untransformed image 079 * @param height 080 * the height of the untransformed image 081 * @param theta 082 * the rotation 083 * @param t 084 * the tilt 085 * @return the point mapped to the untransformed image 086 */ 087 public static Point2d transformToOriginal(Point2d pt, int width, int height, float theta, float t) { 088 if (t == 1) 089 return pt; 090 091 return internalTransformToOriginal(pt, width, height, theta, t); 092 } 093 094 /** 095 * Compute the position of a point in an image given the position in the 096 * transformed image and the transform parameters. 097 * 098 * @param pt 099 * 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}