package edu.uniklu.itec.mosaix.engine;
import net.semanticmetadata.lire.builders.DocumentBuilder;
import net.semanticmetadata.lire.searchers.ImageSearchHits;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
/*
* This file is part of the Caliph and Emir project: http://www.SemanticMetadata.net.
*
* Caliph & Emir 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.
*
* Caliph & Emir 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 Caliph & Emir; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Copyright statement:
* --------------------
* (c) 2002-2007 by Mathias Lux (mathias@juggle.at), Lukas Esterle & Manuel Warum.
* http://www.juggle.at, http://www.SemanticMetadata.net
*/
/**
* The workhorse and brain of the whole application,
* that makes use of strategies and generic sub systems.
*
* @author Manuel Warum
* @author Mathias Lux, mathias@juggle.at
* @version 0.25
*/
public final class Engine {
private HashMap<String, Integer> file2occurence;
private final LinkedList<WeightingStrategy> strategies_;
private final LinkedList<EngineObserver> observer_;
private WeightingDataFactory weightingDataFactory_;
private static boolean outweightImageReuse = false;
public Engine() {
weightingDataFactory_ = new SimpleWeightingDataFactory();
strategies_ = new LinkedList<WeightingStrategy>();
observer_ = new LinkedList<EngineObserver>();
file2occurence = new HashMap<String, Integer>(1000);
// Logging.log(this, "Engine instantiated.");
}
/**
* Adds a strategy to the weighting strategy collection.
*
* @param strategy the non-<code>null</code> strategy instance.
*/
public void addStrategy(final WeightingStrategy strategy) {
assert strategy != null;
strategies_.add(strategy);
// Logging.log(this, "Strategy added: " + strategy.getClass().getName());
}
/**
* Removes the strategy from the weighting strategies
* collection.
*
* @param strategy the strategy to remove.
*/
public void removeStrategy(final WeightingStrategy strategy) {
strategies_.remove(strategy);
// Logging.log(this, "Strategy removed: " + strategy.getClass().getName());
}
public List<WeightingStrategy> getStrategies() {
return strategies_;
}
/**
* Gets all engine observers registered to the
* engine.
*
* @return a list of engine observers.
*/
public List<EngineObserver> getObservers() {
return observer_;
}
/**
* Adds an engine observer to this engine instance.
*
* @param observer the observer to add.
*/
public void addObserver(EngineObserver observer) {
assert observer != null;
observer_.add(observer);
// Logging.log(this, "Observer added: " + observer.getClass().getName());
}
/**
* Removes an engine observer from this engine instance.
*
* @param observer the observer to remove.
*/
public void removeObserver(EngineObserver observer) {
observer_.remove(observer);
// Logging.log(this, "Observer removed: " + observer.getClass().getName());
}
/**
* <p>Evaluates the search results provided by LIRE and
* returns the best available match.</p>
* <p>This method takes two aspects into account: First,
* it uses the relevancy factor as provided by LIRE;
* second, it uses implementation instances of the
* <code>WeightingStrategy</code> interface added to this
* interface.</p>
*
* @param original a non-<code>null</code> image instance.
* @param hits a non-<code>null</code> LIRE search result.
* @param scalePercentage value from 1-100d
* @return the best match as determined by the relevancy
* and the relevancy weighting.
* @throws IOException if the image could not be loaded.
* @see edu.uniklu.itec.mosaix.engine.WeightingStrategy
*/
public BufferedImage findBestMatch(final BufferedImage original, final ImageSearchHits hits, double scalePercentage, IndexReader reader) throws IOException {
assert original != null;
assert hits != null;
//BufferedImage bestImage = null;
WeightingData bestHit = null;
float bestRating = Float.NEGATIVE_INFINITY;
for (int i = 0; i < hits.length(); i++) {
Document doc = reader.document(hits.documentID(i));
String file = doc.getField(DocumentBuilder.FIELD_NAME_IDENTIFIER).stringValue();
// BufferedImage repl = ImageIO.read(new File(file));
WeightingData data = weightingDataFactory_.newInstance(doc);
data.setRelevancy((float) hits.score(i));
data.setSlice(original);
data.setId(file);
data.setScalePercentage(scalePercentage);
// data.setReplacement(repl);
float weight = getWeightedRelevancy(data);
if (outweightImageReuse) {
if (file2occurence.containsKey(file))
weight *= 1f / (((float) file2occurence.get(file)) + 1f);
}
// Logging.log(this, "Rated " + file + " with " + Float.toString(weight));
if (bestRating < weight) {
bestRating = weight;
bestHit = data;
}
}
// Logging.log(this, "Enforcing Garbage Collection.");
// System.gc(); // suppose, it's badly needed now
for (EngineObserver observer : observer_)
observer.notifyState(bestHit, EngineObserver.USED);
// Logging.log(this, "Evaluation complete");
if (outweightImageReuse) {
if (file2occurence.containsKey(bestHit.getId()))
file2occurence.put(bestHit.getId(), file2occurence.get(bestHit.getId()) + 1);
else
file2occurence.put(bestHit.getId(), 1);
}
return bestHit.getReplacement();
}
/**
* Evaluates all weighting strategies for the
* specified evaluation data.
*
* @param data the non-<code>null</code> data to evaluate.
* @return the evaluated weighting to apply to the relevancy.
*/
private float getWeighting(WeightingData data) {
assert data != null;
// Logging.log(this, "Weighting slice with " + strategies_.size() + " strategies.");
float weight = 1.0f;
for (WeightingStrategy strategy : strategies_)
weight *= strategy.getFactor(data);
return weight;
}
/**
* Gets the weighted relevancy for the specified
* weighting data instance.
*
* @param data the non-<code>null</code> data to evaluate.
* @return the weighted relevancy within <code>0.0f</code>
* (worst case) and <code>1.0f</code> (best case).
*/
private float getWeightedRelevancy(WeightingData data) {
assert data != null;
return data.getRelevancy() * getWeighting(data);
}
/**
* Resets all strategies' state data to their initial
* level.
*/
public void reset() {
// Logging.log(this, "Resetting state.");
for (WeightingStrategy s : strategies_)
s.reset();
}
/**
* Switches the option if duplicate tile images (using the same images for a
* tile more than once) should be avoided. It will still happen, but not so
* often any more.
*
* @param avoid
*/
public static void setAvoidDuplicateTileImages(boolean avoid) {
outweightImageReuse = avoid;
}
}