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.feature.local.keypoints; 031 032import java.io.BufferedInputStream; 033import java.io.DataInput; 034import java.io.DataInputStream; 035import java.io.DataOutput; 036import java.io.DataOutputStream; 037import java.io.EOFException; 038import java.io.File; 039import java.io.FileInputStream; 040import java.io.FileOutputStream; 041import java.io.IOException; 042import java.io.InputStream; 043import java.io.OutputStream; 044import java.io.PrintWriter; 045import java.io.Serializable; 046import java.io.StringWriter; 047import java.nio.ByteBuffer; 048import java.nio.ByteOrder; 049import java.util.List; 050import java.util.Scanner; 051 052import org.openimaj.feature.ByteFV; 053import org.openimaj.feature.local.LocalFeature; 054import org.openimaj.feature.local.list.LocalFeatureList; 055import org.openimaj.feature.local.list.MemoryLocalFeatureList; 056import org.openimaj.image.feature.local.keypoints.SIFTGeoKeypoint.SIFTGeoLocation; 057import org.openimaj.io.IOUtils; 058import org.openimaj.io.VariableLength; 059 060import Jama.Matrix; 061 062/** 063 * Implementation of a {@link LocalFeature} based on the .siftgeo format 064 * developed by Krystian Mikolajczyk for his tools. 065 * <p> 066 * Because the .siftgeo file-format is custom, it isn't directly compatible with 067 * that read by 068 * {@link MemoryLocalFeatureList#read(java.io.BufferedInputStream, Class)} or 069 * written with {@link IOUtils}. To work-around these issues, this class 070 * implements a set of static I/O methods for reading and writing multiple 071 * features to/from a standard .siftgeo file. 072 * 073 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 074 */ 075public class SIFTGeoKeypoint implements LocalFeature<SIFTGeoLocation, ByteFV>, VariableLength, Cloneable, Serializable { 076 /** 077 * The location of a {@link SIFTGeoKeypoint}. 078 * 079 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 080 * 081 */ 082 public class SIFTGeoLocation extends KeypointLocation { 083 private static final long serialVersionUID = 1L; 084 085 // number of bytes when written as binary 086 private static final int NUM_BYTES = 36; 087 088 /** 089 * The saliency of the interest point 090 */ 091 public float cornerness; 092 093 /** 094 * affine parameters of the interest point 095 */ 096 public Matrix affine; 097 098 /** 099 * Construct with the given parameters 100 * 101 * @param x 102 * x-ordinate of feature 103 * @param y 104 * y-ordinate of feature 105 * @param scale 106 * scale of feature 107 * @param orientation 108 * orientation of feature 109 * @param cornerness 110 * the saliency of the interest point 111 * @param affine 112 * affine parameters 113 */ 114 public SIFTGeoLocation(float x, float y, float orientation, float scale, float cornerness, Matrix affine) { 115 super(x, y, scale, orientation); 116 this.cornerness = cornerness; 117 this.affine = affine; 118 } 119 120 /** 121 * Default constructor. Everything set to zero with the exception of the 122 * affine parameters which are set to the identity matrix. 123 */ 124 public SIFTGeoLocation() { 125 affine = Matrix.identity(2, 2); 126 } 127 128 @Override 129 public void writeBinary(DataOutput out) throws IOException { 130 final ByteBuffer buffer = ByteBuffer.allocate(SIFTGeoLocation.NUM_BYTES); 131 buffer.order(ByteOrder.LITTLE_ENDIAN); 132 133 writeBinary(buffer); 134 } 135 136 private void writeBinary(ByteBuffer buffer) { 137 buffer.putFloat(x); 138 buffer.putFloat(y); 139 buffer.putFloat(scale); 140 buffer.putFloat(orientation); 141 buffer.putFloat((float) affine.get(0, 0)); 142 buffer.putFloat((float) affine.get(0, 1)); 143 buffer.putFloat((float) affine.get(1, 0)); 144 buffer.putFloat((float) affine.get(1, 1)); 145 buffer.putFloat(cornerness); 146 } 147 148 @Override 149 public void writeASCII(PrintWriter out) throws IOException { 150 out.format("%4.2f %4.2f %4.2f %4.3f %4.3f %4.3f %4.3f %4.3f", x, y, scale, orientation, 151 (float) affine.get(0, 0), (float) affine.get(0, 1), (float) affine 152 .get(1, 0), (float) affine.get(1, 1)); 153 out.println(); 154 } 155 156 @Override 157 public void readBinary(DataInput in) throws IOException { 158 final byte[] array = new byte[NUM_BYTES]; 159 in.readFully(array); 160 161 final ByteBuffer buffer = ByteBuffer.wrap(array); 162 buffer.order(ByteOrder.LITTLE_ENDIAN); 163 164 readBinary(buffer); 165 } 166 167 private void readBinary(ByteBuffer buffer) { 168 x = buffer.getFloat(); 169 y = buffer.getFloat(); 170 scale = buffer.getFloat(); 171 orientation = buffer.getFloat(); 172 affine.set(0, 0, buffer.getFloat()); 173 affine.set(0, 1, buffer.getFloat()); 174 affine.set(1, 0, buffer.getFloat()); 175 affine.set(1, 1, buffer.getFloat()); 176 cornerness = buffer.getFloat(); 177 } 178 179 @Override 180 public void readASCII(Scanner in) throws IOException { 181 x = Float.parseFloat(in.next()); 182 y = Float.parseFloat(in.next()); 183 scale = Float.parseFloat(in.next()); 184 orientation = Float.parseFloat(in.next()); 185 affine.set(0, 0, Float.parseFloat(in.next())); 186 affine.set(0, 1, Float.parseFloat(in.next())); 187 affine.set(1, 0, Float.parseFloat(in.next())); 188 affine.set(1, 1, Float.parseFloat(in.next())); 189 cornerness = Float.parseFloat(in.next()); 190 } 191 192 @Override 193 public byte[] binaryHeader() { 194 return "".getBytes(); 195 } 196 197 @Override 198 public String asciiHeader() { 199 return ""; 200 } 201 202 @Override 203 public Float getOrdinate(int dimension) { 204 final float[] pos = { x, y, scale, orientation, (float) affine.get(0, 0), (float) affine.get(0, 1), 205 (float) affine 206 .get(1, 0), (float) affine.get(1, 1) }; 207 return pos[dimension]; 208 } 209 } 210 211 private static final long serialVersionUID = 1L; 212 213 /** 214 * The location of the point 215 */ 216 public SIFTGeoLocation location = new SIFTGeoLocation(); 217 218 /** 219 * The descriptor 220 */ 221 public byte[] descriptor; 222 223 /** 224 * Construct with the location set to zero, and with an empty descriptor of 225 * the given length. 226 * 227 * @param len 228 * the descriptor length 229 */ 230 public SIFTGeoKeypoint(int len) { 231 descriptor = new byte[len]; 232 } 233 234 /** 235 * Construct with the given parameters 236 * 237 * @param x 238 * x-ordinate of feature 239 * 240 * @param y 241 * y-ordinate of feature 242 * 243 * @param scale 244 * scale of feature 245 * 246 * @param orientation 247 * orientation of feature 248 * 249 * @param cornerness 250 * the saliency of the interest point 251 * 252 * @param affine 253 * affine parameters 254 * @param descriptor 255 * the descriptor 256 */ 257 public SIFTGeoKeypoint(float x, float y, float orientation, float scale, float cornerness, Matrix affine, 258 byte[] descriptor) 259 { 260 this.location.x = x; 261 this.location.y = y; 262 this.location.orientation = orientation; 263 this.location.scale = scale; 264 this.location.cornerness = cornerness; 265 this.location.affine = affine; 266 this.descriptor = descriptor; 267 } 268 269 @Override 270 public void readASCII(Scanner in) throws IOException { 271 location.readASCII(in); 272 final int len = in.nextInt(); 273 274 descriptor = new byte[len]; 275 for (int i = 0; i < len; i++) 276 descriptor[i] = (byte) (in.nextInt() - 128); 277 } 278 279 @Override 280 public String asciiHeader() { 281 return ""; 282 } 283 284 @Override 285 public void readBinary(DataInput in) throws IOException { 286 location.readBinary(in); 287 288 final byte[] array = new byte[4]; 289 in.readFully(array); 290 291 final ByteBuffer buffer = ByteBuffer.wrap(array); 292 293 buffer.order(ByteOrder.LITTLE_ENDIAN); 294 final int len = buffer.getInt(); 295 296 descriptor = new byte[len]; 297 for (int i = 0; i < descriptor.length; i++) 298 descriptor[i] = (byte) (in.readUnsignedByte() - 128); 299 } 300 301 @Override 302 public byte[] binaryHeader() { 303 return new byte[0]; // legacy files are "headerless" 304 } 305 306 @Override 307 public void writeASCII(PrintWriter out) throws IOException { 308 location.writeASCII(out); 309 out.format("%d\n", descriptor.length); 310 for (int i = 0; i < descriptor.length; i++) 311 out.format("%d ", descriptor[i] + 128); 312 out.append("\n"); 313 } 314 315 @Override 316 public void writeBinary(DataOutput out) throws IOException { 317 final ByteBuffer buffer = ByteBuffer.allocate(SIFTGeoLocation.NUM_BYTES + 4 + descriptor.length); 318 buffer.order(ByteOrder.LITTLE_ENDIAN); 319 320 location.writeBinary(buffer); 321 buffer.putInt(descriptor.length); 322 323 for (int i = 0; i < descriptor.length; i++) 324 buffer.put((byte) ((descriptor[i] + 128) & 0xFF)); 325 326 out.write(buffer.array()); 327 } 328 329 @Override 330 public ByteFV getFeatureVector() { 331 return new ByteFV(descriptor); 332 } 333 334 @Override 335 public SIFTGeoLocation getLocation() { 336 return location; 337 } 338 339 @Override 340 public String toString() { 341 final StringWriter sw = new StringWriter(); 342 343 try { 344 writeASCII(new PrintWriter(sw)); 345 } catch (final IOException e) { 346 } 347 348 return sw.toString(); 349 } 350 351 /** 352 * Read a .siftgeo file. 353 * 354 * @param file 355 * the file 356 * @return the list of read {@link SIFTGeoKeypoint}s 357 * @throws IOException 358 * if an error occurs during reading 359 */ 360 public static LocalFeatureList<SIFTGeoKeypoint> read(File file) throws IOException { 361 return read(new BufferedInputStream(new FileInputStream(file))); 362 } 363 364 /** 365 * Read .siftgeo file from a stream. 366 * 367 * @param stream 368 * the stream 369 * @return the list of read {@link SIFTGeoKeypoint}s 370 * @throws IOException 371 * if an error occurs during reading 372 */ 373 public static LocalFeatureList<SIFTGeoKeypoint> read(InputStream stream) throws IOException { 374 return read(new DataInputStream(stream)); 375 } 376 377 /** 378 * Read .siftgeo file from a stream. 379 * 380 * @param stream 381 * the stream 382 * @return the list of read {@link SIFTGeoKeypoint}s 383 * @throws IOException 384 * if an error occurs during reading 385 */ 386 public static LocalFeatureList<SIFTGeoKeypoint> read(DataInputStream stream) throws IOException { 387 final MemoryLocalFeatureList<SIFTGeoKeypoint> keys = new MemoryLocalFeatureList<SIFTGeoKeypoint>(); 388 389 while (true) { 390 try { 391 final SIFTGeoKeypoint kp = new SIFTGeoKeypoint(0); 392 kp.readBinary(stream); 393 keys.add(kp); 394 } catch (final EOFException eof) { 395 // end of stream 396 return keys; 397 } 398 } 399 } 400 401 /** 402 * Write a .siftgeo file 403 * 404 * @param keys 405 * the {@link SIFTGeoKeypoint}s to write 406 * @param file 407 * the file 408 * @throws IOException 409 * if an error occurs whilst writing 410 */ 411 public static void write(List<SIFTGeoKeypoint> keys, File file) throws IOException { 412 write(keys, new FileOutputStream(file)); 413 } 414 415 /** 416 * Write a .siftgeo stream 417 * 418 * @param keys 419 * the {@link SIFTGeoKeypoint}s to write 420 * @param stream 421 * the stream 422 * @throws IOException 423 * if an error occurs whilst writing 424 */ 425 public static void write(List<SIFTGeoKeypoint> keys, OutputStream stream) throws IOException { 426 write(keys, new DataOutputStream(stream)); 427 } 428 429 /** 430 * Write a .siftgeo stream 431 * 432 * @param keys 433 * the {@link SIFTGeoKeypoint}s to write 434 * @param stream 435 * the stream 436 * @throws IOException 437 * if an error occurs whilst writing 438 */ 439 public static void write(List<SIFTGeoKeypoint> keys, DataOutputStream stream) throws IOException { 440 for (final SIFTGeoKeypoint k : keys) { 441 k.writeBinary(stream); 442 } 443 } 444}