Java tutorial
/* * This file is part of the LIRE project: http://www.semanticmetadata.net/lire * LIRE is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * LIRE is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with LIRE; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * We kindly ask you to refer the any or one of the following publications in * any publication mentioning or employing Lire: * * Lux Mathias, Savvas A. Chatzichristofis. Lire: Lucene Image Retrieval * An Extensible Java CBIR Library. In proceedings of the 16th ACM International * Conference on Multimedia, pp. 1085-1088, Vancouver, Canada, 2008 * URL: http://doi.acm.org/10.1145/1459359.1459577 * * Lux Mathias. Content Based Image Retrieval with LIRE. In proceedings of the * 19th ACM International Conference on Multimedia, pp. 735-738, Scottsdale, * Arizona, USA, 2011 * URL: http://dl.acm.org/citation.cfm?id=2072432 * * Mathias Lux, Oge Marques. Visual Information Retrieval using Java and LIRE * Morgan & Claypool, 2013 * URL: http://www.morganclaypool.com/doi/abs/10.2200/S00468ED1V01Y201301ICR025 * * Copyright statement: * ==================== * (c) 2002-2013 by Mathias Lux (mathias@juggle.at) * http://www.semanticmetadata.net/lire, http://www.lire-project.net * * Updated: 11.07.13 10:51 */ package net.semanticmetadata.lire.impl.docbuilder; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.HashMap; import java.util.logging.Logger; import net.semanticmetadata.lire.AbstractDocumentBuilder; import net.semanticmetadata.lire.imageanalysis.AutoColorCorrelogram; import net.semanticmetadata.lire.imageanalysis.BinaryPatternsPyramid; import net.semanticmetadata.lire.imageanalysis.CEDD; import net.semanticmetadata.lire.imageanalysis.ColorLayout; import net.semanticmetadata.lire.imageanalysis.EdgeHistogram; import net.semanticmetadata.lire.imageanalysis.FCTH; import net.semanticmetadata.lire.imageanalysis.Gabor; import net.semanticmetadata.lire.imageanalysis.JCD; import net.semanticmetadata.lire.imageanalysis.JpegCoefficientHistogram; import net.semanticmetadata.lire.imageanalysis.LireFeature; import net.semanticmetadata.lire.imageanalysis.LocalBinaryPatterns; import net.semanticmetadata.lire.imageanalysis.LuminanceLayout; import net.semanticmetadata.lire.imageanalysis.OpponentHistogram; import net.semanticmetadata.lire.imageanalysis.PHOG; import net.semanticmetadata.lire.imageanalysis.RotationInvariantLocalBinaryPatterns; import net.semanticmetadata.lire.imageanalysis.ScalableColor; import net.semanticmetadata.lire.imageanalysis.SimpleColorHistogram; import net.semanticmetadata.lire.imageanalysis.Tamura; import net.semanticmetadata.lire.imageanalysis.joint.JointHistogram; import net.semanticmetadata.lire.indexing.hashing.BitSampling; import net.semanticmetadata.lire.indexing.hashing.LocalitySensitiveHashing; import net.semanticmetadata.lire.indexing.parallel.ImageInfo; import net.semanticmetadata.lire.utils.DocumentUtils; import net.semanticmetadata.lire.utils.ImageUtils; import net.semanticmetadata.lire.utils.SerializationUtils; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.StoredField; import org.apache.lucene.document.TextField; /** * This class allows to create a DocumentBuilder based on a class implementing LireFeature. * Date: 28.05.2008 * Time: 14:32:15 * * @author Mathias Lux, mathias@juggle.at * @author sangupta, sandy.pec@gmail.com (fixed null pointer exception) */ public class GenericDocumentBuilder extends AbstractDocumentBuilder { enum HashingMode { BitSampling, LSH } private boolean hashingEnabled = false; private Logger logger = Logger.getLogger(getClass().getName()); public static final int MAX_IMAGE_DIMENSION = 1024; Class<? extends LireFeature> descriptorClass; String fieldName; final static Mode DEFAULT_MODE = Mode.Fast; Mode currentMode = DEFAULT_MODE; // private LireFeature lireFeature; protected static HashingMode hashingMode = HashingMode.BitSampling; public static HashMap<Class, String> fieldForClass = new HashMap<Class, String>(); public static HashMap<String, Class> classForField = new HashMap<String, Class>(); public static final String HASH_FIELD_SUFFIX = "_hash"; static { // Let's try to read the hash functions right here and we don't have to care about it right now. try { BitSampling.readHashFunctions(); // LocalitySensitiveHashing.readHashFunctions(); } catch (IOException e) { System.err.println( "Could not read hashes from file when first creating a GenericDocumentBuilder instance."); e.printStackTrace(); } // Setting up the class 2 field relation: fieldForClass.put(AutoColorCorrelogram.class, FIELD_NAME_AUTOCOLORCORRELOGRAM); fieldForClass.put(BinaryPatternsPyramid.class, FIELD_NAME_BINARY_PATTERNS_PYRAMID); fieldForClass.put(CEDD.class, FIELD_NAME_CEDD); fieldForClass.put(SimpleColorHistogram.class, FIELD_NAME_COLORHISTOGRAM); fieldForClass.put(ColorLayout.class, FIELD_NAME_COLORLAYOUT); fieldForClass.put(EdgeHistogram.class, FIELD_NAME_EDGEHISTOGRAM); fieldForClass.put(FCTH.class, FIELD_NAME_FCTH); fieldForClass.put(Gabor.class, FIELD_NAME_GABOR); fieldForClass.put(JCD.class, FIELD_NAME_JCD); fieldForClass.put(JointHistogram.class, FIELD_NAME_JOINT_HISTOGRAM); fieldForClass.put(JpegCoefficientHistogram.class, FIELD_NAME_JPEGCOEFFS); fieldForClass.put(LocalBinaryPatterns.class, FIELD_NAME_LOCAL_BINARY_PATTERNS); fieldForClass.put(LuminanceLayout.class, FIELD_NAME_LUMINANCE_LAYOUT); fieldForClass.put(OpponentHistogram.class, FIELD_NAME_OPPONENT_HISTOGRAM); fieldForClass.put(PHOG.class, FIELD_NAME_PHOG); fieldForClass.put(RotationInvariantLocalBinaryPatterns.class, FIELD_NAME_ROTATION_INVARIANT_LOCAL_BINARY_PATTERNS); fieldForClass.put(ScalableColor.class, FIELD_NAME_SCALABLECOLOR); fieldForClass.put(Tamura.class, FIELD_NAME_TAMURA); // Setting up the field 2 class relation: classForField.put(FIELD_NAME_AUTOCOLORCORRELOGRAM, AutoColorCorrelogram.class); classForField.put(FIELD_NAME_BINARY_PATTERNS_PYRAMID, BinaryPatternsPyramid.class); classForField.put(FIELD_NAME_CEDD, CEDD.class); classForField.put(FIELD_NAME_COLORHISTOGRAM, SimpleColorHistogram.class); classForField.put(FIELD_NAME_COLORLAYOUT, ColorLayout.class); classForField.put(FIELD_NAME_EDGEHISTOGRAM, EdgeHistogram.class); classForField.put(FIELD_NAME_FCTH, FCTH.class); classForField.put(FIELD_NAME_GABOR, Gabor.class); classForField.put(FIELD_NAME_JCD, JCD.class); classForField.put(FIELD_NAME_JOINT_HISTOGRAM, JointHistogram.class); classForField.put(FIELD_NAME_JPEGCOEFFS, JpegCoefficientHistogram.class); classForField.put(FIELD_NAME_LOCAL_BINARY_PATTERNS, LocalBinaryPatterns.class); classForField.put(FIELD_NAME_LUMINANCE_LAYOUT, LuminanceLayout.class); classForField.put(FIELD_NAME_OPPONENT_HISTOGRAM, OpponentHistogram.class); classForField.put(FIELD_NAME_PHOG, PHOG.class); classForField.put(FIELD_NAME_ROTATION_INVARIANT_LOCAL_BINARY_PATTERNS, RotationInvariantLocalBinaryPatterns.class); classForField.put(FIELD_NAME_SCALABLECOLOR, ScalableColor.class); classForField.put(FIELD_NAME_TAMURA, Tamura.class); } // Decide between byte array version (fast) or string version (slow) public enum Mode { Fast, Slow } /** * Creating a new DocumentBuilder based on a class based on the interface {@link net.semanticmetadata.lire.imageanalysis.LireFeature} * * @param descriptorClass has to implement {@link net.semanticmetadata.lire.imageanalysis.LireFeature} * @param fieldName the field hashFunctionsFileName in the index. */ public GenericDocumentBuilder(Class<? extends LireFeature> descriptorClass, String fieldName) { this.descriptorClass = descriptorClass; this.fieldName = fieldName; } /** * Creating a new DocumentBuilder based on a class based on the interface * {@link net.semanticmetadata.lire.imageanalysis.LireFeature} * * @param descriptorClass has to implement {@link net.semanticmetadata.lire.imageanalysis.LireFeature} */ public GenericDocumentBuilder(Class<? extends LireFeature> descriptorClass) { this.descriptorClass = descriptorClass; this.fieldName = fieldForClass.get(descriptorClass); if (fieldName == null) { try { fieldName = descriptorClass.newInstance().getFieldName(); } catch (Exception e) { e.printStackTrace(); } } } /** * Creating a new DocumentBuilder based on a class based on the interface * {@link net.semanticmetadata.lire.imageanalysis.LireFeature} * * @param descriptorClass has to implement {@link net.semanticmetadata.lire.imageanalysis.LireFeature} * @param hashing set to true is you want to create an additional field for hashes based on BitSampling. */ public GenericDocumentBuilder(Class<? extends LireFeature> descriptorClass, boolean hashing) { this.descriptorClass = descriptorClass; this.fieldName = fieldForClass.get(descriptorClass); if (fieldName == null) { try { fieldName = descriptorClass.newInstance().getFieldName(); } catch (Exception e) { e.printStackTrace(); } } hashingEnabled = hashing; } /** * Creating a new DocumentBuilder based on a class based on the interface {@link net.semanticmetadata.lire.imageanalysis.LireFeature} * * @param descriptorClass has to implement {@link net.semanticmetadata.lire.imageanalysis.LireFeature} * @param fieldName The hashFunctionsFileName of the field, where the feature vector is stored. * @param hashing set to true is you want to create an additional field for hashes based on BitSampling. */ public GenericDocumentBuilder(Class<? extends LireFeature> descriptorClass, String fieldName, boolean hashing) { this.descriptorClass = descriptorClass; this.fieldName = fieldName; hashingEnabled = hashing; } /** * Creating a new DocumentBuilder based on a class based on the interface {@link net.semanticmetadata.lire.imageanalysis.LireFeature} * * @param descriptorClass has to implement {@link net.semanticmetadata.lire.imageanalysis.LireFeature} * @param fieldName the field hashFunctionsFileName in the index. * @param mode the mode the GenericDocumentBuilder should work in, byte[] (== Mode.Fast) or string (==Mode.Slow) storage in Lucene. */ public GenericDocumentBuilder(Class<? extends LireFeature> descriptorClass, String fieldName, Mode mode) { this.descriptorClass = descriptorClass; this.fieldName = fieldName; this.currentMode = mode; } public Field[] createDescriptorFields(BufferedImage image) { Field[] result; if (hashingEnabled) result = new Field[2]; else result = new Field[1]; String featureString = ""; assert (image != null); BufferedImage bimg = image; // Scaling image is especially with the correlogram features very important! // All images are scaled to guarantee a certain upper limit for indexing. if (Math.max(image.getHeight(), image.getWidth()) > MAX_IMAGE_DIMENSION) { bimg = ImageUtils.scaleImage(image, MAX_IMAGE_DIMENSION); } Document doc = null; try { logger.finer("Starting extraction from image [" + descriptorClass.getName() + "]."); LireFeature lireFeature = null; lireFeature = descriptorClass.newInstance(); lireFeature.extract(bimg); // featureString = vd.getStringRepresentation(); logger.fine("Extraction finished [" + descriptorClass.getName() + "]."); // TODO: Stored field is compressed and upon search decompression takes a lot of time (> 50% with a small index with 50k images). Find something else ... result[0] = new StoredField(fieldName, lireFeature.getByteArrayRepresentation()); // if BitSampling is an issue we add a field with the given hashFunctionsFileName and the suffix "hash": if (hashingEnabled) { // TODO: check eventually if there is a more compressed string version of the integers. i.e. the hex string if (lireFeature.getDoubleHistogram().length <= 3100) { int[] hashes; if (hashingMode == HashingMode.BitSampling) { hashes = BitSampling.generateHashes(lireFeature.getDoubleHistogram()); } else { hashes = LocalitySensitiveHashing.generateHashes(lireFeature.getDoubleHistogram()); } result[1] = new TextField(fieldName + HASH_FIELD_SUFFIX, SerializationUtils.arrayToString(hashes), Field.Store.YES); } else System.err.println("Could not create hashes, feature vector too long: " + lireFeature.getDoubleHistogram().length + " (" + lireFeature.getClass().getName() + ")"); } } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return result; } /** * Creates a fully fledged Document to be added to a Lucene index. * * @param image the image to index. Cannot be NULL. * @param identifier an id for the image, for instance the filename or an URL. Can be NULL. * @return */ public Document createDocument(BufferedImage image, ImageInfo imageInfo) { assert (image != null); // sangupta: create a new document else code below // will throw a NPE Document doc = new Document(); Field[] fields = createDescriptorFields(image); for (int i = 0; i < fields.length; i++) { doc.add(fields[i]); } DocumentUtils.appendImageInfoFields(doc, imageInfo); return doc; } }