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), (float) affine 205 .get(1, 0), (float) affine.get(1, 1) }; 206 return pos[dimension]; 207 } 208 } 209 210 private static final long serialVersionUID = 1L; 211 212 /** 213 * The location of the point 214 */ 215 public SIFTGeoLocation location = new SIFTGeoLocation(); 216 217 /** 218 * The descriptor 219 */ 220 public byte[] descriptor; 221 222 /** 223 * Construct with the location set to zero, and with an empty descriptor of 224 * the given length. 225 * 226 * @param len 227 * the descriptor length 228 */ 229 public SIFTGeoKeypoint(int len) { 230 descriptor = new byte[len]; 231 } 232 233 /** 234 * Construct with the given parameters 235 * 236 * @param x 237 * x-ordinate of feature 238 * 239 * @param y 240 * y-ordinate of feature 241 * 242 * @param scale 243 * scale of feature 244 * 245 * @param orientation 246 * orientation of feature 247 * 248 * @param cornerness 249 * the saliency of the interest point 250 * 251 * @param affine 252 * affine parameters 253 * @param descriptor 254 * the descriptor 255 */ 256 public SIFTGeoKeypoint(float x, float y, float orientation, float scale, float cornerness, Matrix affine, 257 byte[] descriptor) 258 { 259 this.location.x = x; 260 this.location.y = y; 261 this.location.orientation = orientation; 262 this.location.scale = scale; 263 this.location.cornerness = cornerness; 264 this.location.affine = affine; 265 this.descriptor = descriptor; 266 } 267 268 @Override 269 public void readASCII(Scanner in) throws IOException { 270 location.readASCII(in); 271 final int len = in.nextInt(); 272 273 descriptor = new byte[len]; 274 for (int i = 0; i < len; i++) 275 descriptor[i] = (byte) (in.nextInt() - 128); 276 } 277 278 @Override 279 public String asciiHeader() { 280 return ""; 281 } 282 283 @Override 284 public void readBinary(DataInput in) throws IOException { 285 location.readBinary(in); 286 287 final byte[] array = new byte[4]; 288 in.readFully(array); 289 290 final ByteBuffer buffer = ByteBuffer.wrap(array); 291 292 buffer.order(ByteOrder.LITTLE_ENDIAN); 293 final int len = buffer.getInt(); 294 295 descriptor = new byte[len]; 296 for (int i = 0; i < descriptor.length; i++) 297 descriptor[i] = (byte) (in.readUnsignedByte() - 128); 298 } 299 300 @Override 301 public byte[] binaryHeader() { 302 return new byte[0]; // legacy files are "headerless" 303 } 304 305 @Override 306 public void writeASCII(PrintWriter out) throws IOException { 307 location.writeASCII(out); 308 out.format("%d\n", descriptor.length); 309 for (int i = 0; i < descriptor.length; i++) 310 out.format("%d ", descriptor[i] + 128); 311 out.append("\n"); 312 } 313 314 @Override 315 public void writeBinary(DataOutput out) throws IOException { 316 final ByteBuffer buffer = ByteBuffer.allocate(SIFTGeoLocation.NUM_BYTES + 4 + descriptor.length); 317 buffer.order(ByteOrder.LITTLE_ENDIAN); 318 319 location.writeBinary(buffer); 320 buffer.putInt(descriptor.length); 321 322 for (int i = 0; i < descriptor.length; i++) 323 buffer.put((byte) ((descriptor[i] + 128) & 0xFF)); 324 325 out.write(buffer.array()); 326 } 327 328 @Override 329 public ByteFV getFeatureVector() { 330 return new ByteFV(descriptor); 331 } 332 333 @Override 334 public SIFTGeoLocation getLocation() { 335 return location; 336 } 337 338 @Override 339 public String toString() { 340 final StringWriter sw = new StringWriter(); 341 342 try { 343 writeASCII(new PrintWriter(sw)); 344 } catch (final IOException e) { 345 } 346 347 return sw.toString(); 348 } 349 350 /** 351 * Read a .siftgeo file. 352 * 353 * @param file 354 * the file 355 * @return the list of read {@link SIFTGeoKeypoint}s 356 * @throws IOException 357 * if an error occurs during reading 358 */ 359 public static LocalFeatureList<SIFTGeoKeypoint> read(File file) throws IOException { 360 return read(new BufferedInputStream(new FileInputStream(file))); 361 } 362 363 /** 364 * Read .siftgeo file from a stream. 365 * 366 * @param stream 367 * the stream 368 * @return the list of read {@link SIFTGeoKeypoint}s 369 * @throws IOException 370 * if an error occurs during reading 371 */ 372 public static LocalFeatureList<SIFTGeoKeypoint> read(InputStream stream) throws IOException { 373 return read(new DataInputStream(stream)); 374 } 375 376 /** 377 * Read .siftgeo file from a stream. 378 * 379 * @param stream 380 * the stream 381 * @return the list of read {@link SIFTGeoKeypoint}s 382 * @throws IOException 383 * if an error occurs during reading 384 */ 385 public static LocalFeatureList<SIFTGeoKeypoint> read(DataInputStream stream) throws IOException { 386 final MemoryLocalFeatureList<SIFTGeoKeypoint> keys = new MemoryLocalFeatureList<SIFTGeoKeypoint>(); 387 388 while (true) { 389 try { 390 final SIFTGeoKeypoint kp = new SIFTGeoKeypoint(0); 391 kp.readBinary(stream); 392 keys.add(kp); 393 } catch (final EOFException eof) { 394 // end of stream 395 return keys; 396 } 397 } 398 } 399 400 /** 401 * Write a .siftgeo file 402 * 403 * @param keys 404 * the {@link SIFTGeoKeypoint}s to write 405 * @param file 406 * the file 407 * @throws IOException 408 * if an error occurs whilst writing 409 */ 410 public static void write(List<SIFTGeoKeypoint> keys, File file) throws IOException { 411 write(keys, new FileOutputStream(file)); 412 } 413 414 /** 415 * Write a .siftgeo stream 416 * 417 * @param keys 418 * the {@link SIFTGeoKeypoint}s to write 419 * @param stream 420 * the stream 421 * @throws IOException 422 * if an error occurs whilst writing 423 */ 424 public static void write(List<SIFTGeoKeypoint> keys, OutputStream stream) throws IOException { 425 write(keys, new DataOutputStream(stream)); 426 } 427 428 /** 429 * Write a .siftgeo stream 430 * 431 * @param keys 432 * the {@link SIFTGeoKeypoint}s to write 433 * @param stream 434 * the stream 435 * @throws IOException 436 * if an error occurs whilst writing 437 */ 438 public static void write(List<SIFTGeoKeypoint> keys, DataOutputStream stream) throws IOException { 439 for (final SIFTGeoKeypoint k : keys) { 440 k.writeBinary(stream); 441 } 442 } 443}