package gr.iti.mklab.visual.vectorization;
import gr.iti.mklab.visual.aggregation.AbstractFeatureAggregator;
import gr.iti.mklab.visual.aggregation.VladAggregatorMultipleVocabularies;
import gr.iti.mklab.visual.dimreduction.PCA;
import gr.iti.mklab.visual.extraction.AbstractFeatureExtractor;
import gr.iti.mklab.visual.extraction.ColorSURFExtractor;
import gr.iti.mklab.visual.extraction.RootSIFTExtractor;
import gr.iti.mklab.visual.extraction.SIFTExtractor;
import gr.iti.mklab.visual.extraction.SURFExtractor;
import java.awt.image.BufferedImage;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* This class implements multi-threaded image vectorization.
*
* @author Eleftherios Spyromitros-Xioufis
*
*/
public class ImageVectorizer {
private ExecutorService vectorizationExecutor;
private CompletionService<ImageVectorizationResult> pool;
/** The current number of tasks whose termination is pending. **/
private int numPendingTasks;
/** The target length of the extracted vector. **/
private int targetVectorLength;
/** The initial length of the vector. **/
private int initialVectorLength;
public int getInitialVectorLength() {
return initialVectorLength;
}
/**
* Image will be scaled at this maximum number of pixels before vectorization.
*/
private int maxImageSizeInPixels = 1024 * 768;
public void setMaxImageSizeInPixels(int maxImageSizeInPixels) {
this.maxImageSizeInPixels = maxImageSizeInPixels;
}
/**
* The maximum allowable number of pending tasks, used to limit the memory usage.
*/
private final int maxNumPendingTasks;
/**
* Constructor of the multi-threaded vectorization class.
*
* @param featureType
* the features to be extracted (surf/sift/rootsift/csurf)
* @param codebooksFiles
* a String array with full paths to the codebook files
* @param numCentroids
* an int array with the number of centroids in each codebook
* @param projectionLength
* the length at which the vectors are projected
* @param PCAFileName
* the file containing the PCA projection matrix
* @param whitening
* whether whitening should be applied jointly with PCA projection
* @param numThreads
* the number of vectorization threads to use
* @throws Exception
*/
public ImageVectorizer(String featureType, String[] codebookFiles, int[] numCentroids,
int projectionLength, String PCAFileName, boolean whitening, int numThreads) throws Exception {
int featureLength;
AbstractFeatureExtractor fe = null;
if (featureType.equals("surf")) {
featureLength = AbstractFeatureExtractor.SURFLength;
fe = new SURFExtractor();
} else if (featureType.equals("sift")) {
featureLength = AbstractFeatureExtractor.SIFTLength;
fe = new SIFTExtractor();
} else if (featureType.equals("rootsift")) {
featureLength = AbstractFeatureExtractor.SIFTLength;
fe = new RootSIFTExtractor();
} else if (featureType.equals("csurf")) {
featureLength = 3 * AbstractFeatureExtractor.SURFLength;
fe = new ColorSURFExtractor();
} else {
throw new Exception("Wrong feature type;");
}
ImageVectorization.setFeatureExtractor(fe);
int numCodebooks = codebookFiles.length;
// initialize the VLAD object
double[][][] codebooks = new double[numCodebooks][][];
for (int i = 0; i < numCodebooks; i++) {
codebooks[i] = AbstractFeatureAggregator.readQuantizer(codebookFiles[i], numCentroids[i],
featureLength);
initialVectorLength += codebooks[i].length * featureLength;
}
targetVectorLength = projectionLength;
VladAggregatorMultipleVocabularies vladmvoc = new VladAggregatorMultipleVocabularies(codebooks);
ImageVectorization.setVladAggregator(vladmvoc);
// initialize the PCA object
PCA PCA = null;
if (PCAFileName != null && projectionLength < initialVectorLength) {
// initialize the PCA object
PCA = new PCA(projectionLength, 1, initialVectorLength, whitening);
PCA.loadPCAFromFile(PCAFileName);
}
ImageVectorization.setPcaProjector(PCA);
vectorizationExecutor = Executors.newFixedThreadPool(numThreads);
pool = new ExecutorCompletionService<ImageVectorizationResult>(vectorizationExecutor);
numPendingTasks = 0;
maxNumPendingTasks = numThreads * 10;
}
/**
* Submits a new image vectorization task for an image that is stored in the disk and has not yet been
* read into a BufferedImage object.
*
* @param imageFolder
* The folder where the image resides.
* @param imageName
* The name of the image.
*/
public void submitImageVectorizationTask(String imageFolder, String imageName) {
Callable<ImageVectorizationResult> call = new ImageVectorization(imageFolder, imageName,
targetVectorLength, maxImageSizeInPixels);
pool.submit(call);
numPendingTasks++;
}
/**
* This methods submits an image vectorization task for an image that has already been read into a
* BufferedImage object.
*
* @param imageName
* The name of the image.
* @param im
* The BufferedImage object of the image.
*/
public void submitImageVectorizationTask(String imageName, BufferedImage im) {
Callable<ImageVectorizationResult> call = new ImageVectorization(imageName, im, targetVectorLength,
maxImageSizeInPixels);
pool.submit(call);
numPendingTasks++;
}
/**
* Takes and returns a vectorization result from the pool.
*
* @return the vectorization result, or null in no results are ready
* @throws Exception
* for a failed vectorization task
*/
public ImageVectorizationResult getImageVectorizationResult() throws Exception {
Future<ImageVectorizationResult> future = pool.poll();
if (future == null) {
return null;
} else {
try {
ImageVectorizationResult imvr = future.get();
return imvr;
} catch (Exception e) {
throw e;
} finally {
// in any case (Exception or not) the numPendingTask should be reduced
numPendingTasks--;
}
}
}
/**
* Gets an image vectorization result from the pool, waiting if necessary.
*
* @return the vectorization result
* @throws Exception
* for a failed vectorization task
*/
public ImageVectorizationResult getImageVectorizationResultWait() throws Exception {
try {
ImageVectorizationResult imvr = pool.take().get();
return imvr;
} catch (Exception e) {
throw e;
} finally {
// in any case (Exception or not) the numPendingTask should be reduced
numPendingTasks--;
}
}
/**
* Returns true if the number of pending tasks is smaller than the maximum allowable number.
*
* @return
*/
public boolean canAcceptMoreTasks() {
if (numPendingTasks < maxNumPendingTasks) {
return true;
} else {
return false;
}
}
/**
* Shuts the vectorization executor down, waiting for up to 10 seconds for the remaining tasks to
* complete. See http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html
*
*/
public void shutDown() {
vectorizationExecutor.shutdown(); // Disable new tasks from being submitted
try {
// Wait a while for existing tasks to terminate
if (!vectorizationExecutor.awaitTermination(10, TimeUnit.SECONDS)) {
vectorizationExecutor.shutdownNow(); // Cancel currently executing tasks
// Wait a while for tasks to respond to being cancelled
if (!vectorizationExecutor.awaitTermination(10, TimeUnit.SECONDS))
System.err.println("Pool did not terminate");
}
} catch (InterruptedException ie) {
// (Re-)Cancel if current thread also interrupted
vectorizationExecutor.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
}
}
}