Newer
Older
package azgracompress.compression;
import azgracompress.cache.QuantizationCacheManager;
import azgracompress.io.InputData;
import azgracompress.compression.exception.ImageCompressionException;
import azgracompress.data.Chunk2D;
import azgracompress.data.ImageU16;
import azgracompress.io.loader.IPlaneLoader;
import azgracompress.io.loader.PlaneLoaderFactory;
import azgracompress.quantization.vector.*;
import azgracompress.utilities.Stopwatch;
import java.io.DataOutputStream;
import java.io.IOException;
public class VQImageCompressor extends CompressorDecompressorBase implements IImageCompressor {
public VQImageCompressor(CompressionOptions options) {
super(options);
}
/**
* Train vector quantizer from plane vectors.
*
* @param planeVectors Image vectors.
* @return Trained vector quantizer with codebook of set size.
*/
private VectorQuantizer trainVectorQuantizerFromPlaneVectors(final int[][] planeVectors) {
LBGVectorQuantizer vqInitializer = new LBGVectorQuantizer(planeVectors,
getCodebookSize(),
options.getWorkerCount(),
options.getVectorDimension().toV3i());
LBGResult vqResult = vqInitializer.findOptimalCodebook(false);
return new VectorQuantizer(vqResult.getCodebook());
}
/**
* Write the vector codebook to the compress stream.
*
* @param quantizer Quantizer with the codebook.
* @param compressStream Stream with compressed data.
* @throws ImageCompressionException When unable to write quantizer.
*/
private void writeQuantizerToCompressStream(final VectorQuantizer quantizer,
DataOutputStream compressStream) throws ImageCompressionException {
final CodebookEntry[] codebook = quantizer.getCodebookVectors();
try {
for (final CodebookEntry entry : codebook) {
final int[] entryVector = entry.getVector();
for (final int vecVal : entryVector) {
compressStream.writeShort(vecVal);
}
final long[] frequencies = quantizer.getFrequencies();
for (final long symbolFrequency : frequencies) {
compressStream.writeLong(symbolFrequency);
}
} catch (IOException ioEx) {
throw new ImageCompressionException("Unable to write codebook to compress stream.", ioEx);
if (options.isVerbose()) {
Log("Wrote quantization vectors to compressed stream.");
}
/**
* Load quantizer from cached codebook.
*
* @return Vector quantizer with cached codebook.
* @throws ImageCompressionException when fails to read cached codebook.
*/
private VectorQuantizer loadQuantizerFromCache() throws ImageCompressionException {
QuantizationCacheManager cacheManager = new QuantizationCacheManager(options.getCodebookCacheFolder());
if (!cacheManager.doesVQCacheExists(options.getInputDataInfo().getCacheFileName(),
getCodebookSize(),
options.getVectorDimension().toV3i())) {
trainAndSaveCodebook();
}
final VQCodebook codebook = cacheManager.loadVQCodebook(options.getInputDataInfo().getCacheFileName(),
getCodebookSize(),
options.getVectorDimension().toV3i());
if (codebook == null) {
throw new ImageCompressionException("Failed to read quantization vectors from cache.");
/**
* Compress the image file specified by parsed CLI options using vector quantization.
*
* @param compressStream Stream to which compressed data will be written.
* @throws ImageCompressionException When compress process fails.
public long[] compress(DataOutputStream compressStream) throws ImageCompressionException {
final InputData inputDataInfo = options.getInputDataInfo();
Stopwatch stopwatch = new Stopwatch();
final boolean hasGeneralQuantizer = options.getCodebookType() != CompressionOptions.CodebookType.Individual;
final IPlaneLoader planeLoader;
final int[] huffmanSymbols = createHuffmanSymbols(getCodebookSize());
planeLoader = PlaneLoaderFactory.getPlaneLoaderForInputFile(inputDataInfo);
} catch (Exception e) {
throw new ImageCompressionException("Unable to create SCIFIO reader. " + e.getMessage());
}
if (options.getCodebookType() == CompressionOptions.CodebookType.Global) {
Log("Loading codebook from cache file.");
quantizer = loadQuantizerFromCache();
huffman = createHuffmanCoder(huffmanSymbols, quantizer.getFrequencies());
Log("Cached quantizer with huffman coder created.");
writeQuantizerToCompressStream(quantizer, compressStream);
} else if (options.getCodebookType() == CompressionOptions.CodebookType.MiddlePlane) {
final int middlePlaneIndex = getMiddlePlaneIndex();
ImageU16 middlePlane = null;
middlePlane = planeLoader.loadPlaneU16(middlePlaneIndex);
throw new ImageCompressionException("Unable to load reference plane data.", ex);
}
Log(String.format("Training vector quantizer from middle plane %d.", middlePlaneIndex));
final int[][] refPlaneVectors = middlePlane.toQuantizationVectors(options.getVectorDimension());
quantizer = trainVectorQuantizerFromPlaneVectors(refPlaneVectors);
huffman = createHuffmanCoder(huffmanSymbols, quantizer.getFrequencies());
writeQuantizerToCompressStream(quantizer, compressStream);
Log("Middle plane codebook created in: " + stopwatch.getElapsedTimeString());
}
final int[] planeIndices = getPlaneIndicesForCompression();
long[] planeDataSizes = new long[planeIndices.length];
int planeCounter = 0;
for (final int planeIndex : planeIndices) {
Log(String.format("Loading plane %d.", planeIndex));
ImageU16 plane = null;
try {
plane = planeLoader.loadPlaneU16(planeIndex);
} catch (IOException ex) {
throw new ImageCompressionException("Unable to load plane data.", ex);
}
final int[][] planeVectors = plane.toQuantizationVectors(options.getVectorDimension());
Log(String.format("Training vector quantizer from plane %d.", planeIndex));
quantizer = trainVectorQuantizerFromPlaneVectors(planeVectors);
huffman = createHuffmanCoder(huffmanSymbols, quantizer.getFrequencies());
writeQuantizerToCompressStream(quantizer, compressStream);
Log("Wrote plane codebook.");
}
assert (quantizer != null);
final int[] indices = quantizer.quantizeIntoIndices(planeVectors, options.getWorkerCount());
planeDataSizes[planeCounter++] = writeHuffmanEncodedIndices(compressStream, huffman, indices);
stopwatch.stop();
Log("Plane time: " + stopwatch.getElapsedTimeString());
Log(String.format("Finished processing of plane %d.", planeIndex));
return planeDataSizes;
/**
* Load plane and convert the plane into quantization vectors.
*
* @param planeIndex Zero based plane index.
* @return Quantization vectors of configured quantization.
* @throws IOException When reading fails.
*/
private int[][] loadPlaneQuantizationVectors(final IPlaneLoader planeLoader,
final int planeIndex) throws IOException {
ImageU16 refPlane = planeLoader.loadPlaneU16(planeIndex);
return refPlane.toQuantizationVectors(options.getVectorDimension());
}
private int[][] loadConfiguredPlanesData() throws ImageCompressionException {
final int vectorSize = options.getVectorDimension().getX() * options.getVectorDimension().getY();
final InputData inputDataInfo = options.getInputDataInfo();
final IPlaneLoader planeLoader;
planeLoader = PlaneLoaderFactory.getPlaneLoaderForInputFile(inputDataInfo);
} catch (Exception e) {
throw new ImageCompressionException("Unable to create SCIFIO reader. " + e.getMessage());
}
int[][] trainData = null;
Stopwatch s = new Stopwatch();
s.start();
if (inputDataInfo.isPlaneIndexSet()) {
Log("VQ: Loading single plane data.");
try {
trainData = loadPlaneQuantizationVectors(planeLoader, inputDataInfo.getPlaneIndex());
throw new ImageCompressionException("Failed to load plane data.", e);
Log(inputDataInfo.isPlaneRangeSet() ? "VQ: Loading plane range data." : "VQ: Loading all planes data.");
final int[] planeIndices = getPlaneIndicesForCompression();
final int chunkCountPerPlane = Chunk2D.calculateRequiredChunkCountPerPlane(
inputDataInfo.getDimensions().toV2i(),
options.getVectorDimension());
final int totalChunkCount = chunkCountPerPlane * planeIndices.length;
trainData = new int[totalChunkCount][vectorSize];
int[][] planeVectors;
int planeCounter = 0;
for (final int planeIndex : planeIndices) {
try {
planeVectors = loadPlaneQuantizationVectors(planeLoader, planeIndex);
assert (planeVectors.length == chunkCountPerPlane) : "Wrong chunk count per plane";
} catch (IOException e) {
throw new ImageCompressionException(String.format("Failed to load plane %d image data.",
System.arraycopy(planeVectors,
0,
trainData,
(planeCounter * chunkCountPerPlane),
chunkCountPerPlane);
++planeCounter;
}
}
s.stop();
Log("Quantization vector load took: " + s.getElapsedTimeString());
return trainData;
}
@Override
public void trainAndSaveCodebook() throws ImageCompressionException {
final int[][] trainingData = loadConfiguredPlanesData();
LBGVectorQuantizer vqInitializer = new LBGVectorQuantizer(trainingData,
getCodebookSize(),
options.getWorkerCount(),
options.getVectorDimension().toV3i());
Log("Starting LBG optimization.");
LBGResult lbgResult = vqInitializer.findOptimalCodebook(options.isVerbose());
Log("Learned the optimal codebook.");
Log("Saving cache file to %s", options.getOutputFilePath());
QuantizationCacheManager cacheManager = new QuantizationCacheManager(options.getCodebookCacheFolder());
cacheManager.saveCodebook(options.getInputDataInfo().getCacheFileName(), lbgResult.getCodebook());
throw new ImageCompressionException("Unable to write VQ cache.", e);