package gr.iti.mklab.visual.aggregation;
import gr.iti.mklab.visual.utilities.Result;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import com.aliasi.util.BoundedPriorityQueue;
/**
* All methods which aggregate a set of local image descriptors should extend this abstract class.
*
* @author Eleftherios Spyromitros-Xioufis
*
*/
public abstract class AbstractFeatureAggregator {
/**
* The codebook (centroids) used to aggregate the vectors. Each centroid is stored in a different row.
*/
protected double[][] codebook;
/**
* The number of centroids in the codebook.
*/
protected int numCentroids;
/**
* The length of the generated vectors.
*/
protected int vectorLength;
/**
* Should compute and return the length of the generated vector.
*
* @return
*/
public abstract int getVectorLength();
/**
* The dimensionality of the local descriptors ( should be equal to the dimensionality of each centroid).
*/
protected int descriptorLength;
public int getNumCentroids() {
return numCentroids;
}
public void setNumCentroids(int numCentroids) {
this.numCentroids = numCentroids;
}
public int getDescriptorLength() {
return descriptorLength;
}
public void setDescriptorLength(int descriptorLength) {
this.descriptorLength = descriptorLength;
}
/**
* This method performs some general checks before calling the aggregateInternal method which is
* implemented by each aggregator.
*
* @param descriptors
* a set of local descriptors
* @return a vector which aggregates the local descriptors
* @throws Exception
*/
public double[] aggregate(double[][] descriptors) throws Exception {
if (descriptors.length > 0) {
if (descriptors[0].length != descriptorLength) {
throw new Exception("Descriptor length is incompatible with codebook centroid length!");
}
}
return aggregateInternal(descriptors);
}
/**
* This method should be overridden by all aggregators.
*
* @param descriptors
* @return
*/
protected abstract double[] aggregateInternal(ArrayList<double[]> descriptors) throws Exception;
/**
* This method performs some general checks before calling the aggregateInternal method which is
* implemented by each aggregator.
*
* @param descriptors
* a set of local descriptors
* @return a vector which aggregates the local descriptors
* @throws Exception
*/
public double[] aggregate(ArrayList<double[]> descriptors) throws Exception {
if (descriptors.size() > 0) {
if (descriptors.get(0).length != descriptorLength) {
throw new Exception("Descriptor length is incompatible with codebook centroid length!");
}
}
return aggregateInternal(descriptors);
}
/**
* This method should be overridden by all aggregators.
*
* @param descriptors
* @return
*/
protected abstract double[] aggregateInternal(double[][] descriptors) throws Exception;
protected AbstractFeatureAggregator() {
}
/**
* The constructor.
*
* @param codebook
*/
protected AbstractFeatureAggregator(double[][] codebook) {
this.codebook = codebook;
this.numCentroids = codebook.length;
this.descriptorLength = codebook[0].length;
}
/**
* Returns the index of the centroid which is closer to the given descriptor.
*
* @param descriptor
* @return
*/
protected int computeNearestCentroid(double[] descriptor) {
int centroidIndex = -1;
double minDistance = Double.MAX_VALUE;
for (int i = 0; i < numCentroids; i++) {
double distance = 0;
for (int j = 0; j < descriptorLength; j++) {
distance += (codebook[i][j] - descriptor[j]) * (codebook[i][j] - descriptor[j]);
// when distance becomes greater than minDistance
// break the inner loop and check the next centroid!!!
if (distance >= minDistance) {
break;
}
}
if (distance < minDistance) {
minDistance = distance;
centroidIndex = i;
}
}
return centroidIndex;
}
/**
* Returns a double array which has the nearest centroid's index as the first element and the distance
* from this centroid as the second element.
*
* @param descriptor
* @return
*/
protected double[] computeNearestCentroidIndexAndDistance(double[] descriptor) {
int centroidIndex = -1;
double minDistance = Double.MAX_VALUE;
for (int i = 0; i < numCentroids; i++) {
double distance = 0;
for (int j = 0; j < descriptorLength; j++) {
distance += (codebook[i][j] - descriptor[j]) * (codebook[i][j] - descriptor[j]);
// when distance becomes greater than minDistance
// break the inner loop and check the next centroid!!!
if (distance >= minDistance) {
break;
}
}
if (distance < minDistance) {
minDistance = distance;
centroidIndex = i;
}
}
return new double[] { centroidIndex, minDistance };
}
/**
* Returns the indices of the k centroids which are closer to the given descriptor. Can be used for soft
* quantization. Fast implementation with a bounded priority queue. TO DO: early stopping!
*
* @param descriptor
* @param k
* @return
*/
protected int[] computeKNearestCentroids(double[] descriptor, int k) {
BoundedPriorityQueue<Result> bpq = new BoundedPriorityQueue<Result>(new Result(), k);
double minDistance = Double.MAX_VALUE;
for (int i = 0; i < numCentroids; i++) {
double distance = 0;
boolean skip = false;
for (int j = 0; j < descriptorLength; j++) {
distance += (codebook[i][j] - descriptor[j]) * (codebook[i][j] - descriptor[j]);
if (distance > minDistance) {
skip = true;
break;
}
}
if (skip) {
continue;
}
bpq.offer(new Result(i, distance));
if (i >= k) {
minDistance = bpq.last().getDistance();
}
}
int[] nn = new int[k];
for (int i = 0; i < k; i++) {
nn[i] = bpq.poll().getId();
}
return nn;
}
/**
* Reads a quantizer (codebook) from the given file and returns it in a 2-dimensional double array.
*
* @param filename
* name of the file containing the quantizer
* @param numCentroids
* number of centroids of the quantizer
* @param centroidLength
* length of each centroid
* @return the quantizer as a 2-dimensional double array
* @throws IOException
*/
public static double[][] readQuantizer(String filename, int numCentroids, int centroidLength)
throws IOException {
double[][] quantizer = new double[numCentroids][centroidLength];
// load the quantizer
BufferedReader in = new BufferedReader(new FileReader(filename));
String line;
int counter = 0;
while ((line = in.readLine()) != null) {
// skip header lines
if (!line.contains(",")) { // not a csv data line
continue;
}
String[] centerStrings = line.split(",");
for (int i = 0; i < centerStrings.length; i++) {
quantizer[counter][i] = Double.parseDouble(centerStrings[i]);
}
counter++;
}
in.close();
return quantizer;
}
/**
* Reads multiple quantizers (codebooks) from the given files and returns them in a 3-dimensional double
* array.
*
* @param filenames
* names of the files containing the quantizers
* @param numCentroids
* numbers of centroids of each quantizer
* @param centroidLength
* length of each centroid
* @return the quantizers as a 3-dimensional double array
* @throws IOException
*/
public static double[][][] readQuantizers(String[] filenames, int[] numCentroids, int centroidLength)
throws IOException {
int numQuantizers = filenames.length;
double[][][] quantizers = new double[numQuantizers][][];
for (int i = 0; i < numQuantizers; i++) {
quantizers[i] = AbstractFeatureAggregator.readQuantizer(filenames[i], numCentroids[i],
centroidLength);
}
return quantizers;
}
}