diff --git a/src/main/java/cz/it4i/qcmp/compression/IImageCompressor.java b/src/main/java/cz/it4i/qcmp/compression/IImageCompressor.java index 8d8ae984143d75c68b8c36f92b9c378eddd5cf89..5b4277f7c8798a0d87817de77c06ecf8472ca8a1 100644 --- a/src/main/java/cz/it4i/qcmp/compression/IImageCompressor.java +++ b/src/main/java/cz/it4i/qcmp/compression/IImageCompressor.java @@ -37,6 +37,14 @@ public interface IImageCompressor extends IListenable { */ void trainAndSaveCodebook() throws ImageCompressionException; + /** + * Train all codebook sizes from selected frames and save learned codebooks to cache files. + * + * @throws ImageCompressionException when training or saving of any file fails. + */ + void trainAndSaveAllCodebooks() throws ImageCompressionException; + + /** * Preload compressor codebook and Huffman tree for stream compressor from provided cache file. * diff --git a/src/main/java/cz/it4i/qcmp/compression/ImageCompressor.java b/src/main/java/cz/it4i/qcmp/compression/ImageCompressor.java index 23f57921a75aaa27499509bf115be4f0ae2d775c..ac87edb777610c2db1bdbb03d9c10ea27db46193 100644 --- a/src/main/java/cz/it4i/qcmp/compression/ImageCompressor.java +++ b/src/main/java/cz/it4i/qcmp/compression/ImageCompressor.java @@ -88,6 +88,21 @@ public class ImageCompressor extends CompressorDecompressorBase { return true; } + public boolean trainAndSaveAllCodebooks() { + reportStatusToListeners("=== Training all codebooks ==="); + if (imageCompressor == null) { + return false; + } + try { + imageCompressor.trainAndSaveAllCodebooks(); + } catch (final ImageCompressionException e) { + System.err.println(e.getMessage()); + e.printStackTrace(); + return false; + } + return true; + } + public int streamCompressChunk(final OutputStream outputStream, final InputData inputData) { assert (imageCompressor != null); diff --git a/src/main/java/cz/it4i/qcmp/compression/SQImageCompressor.java b/src/main/java/cz/it4i/qcmp/compression/SQImageCompressor.java index 22a8dd9db8684256cab8570c21d0b84702805f89..381c250ef34d27868b9a1f6ea25ad608719a941c 100644 --- a/src/main/java/cz/it4i/qcmp/compression/SQImageCompressor.java +++ b/src/main/java/cz/it4i/qcmp/compression/SQImageCompressor.java @@ -246,4 +246,9 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm public long[] compressStreamChunk(final DataOutputStream compressStream, final InputData inputData) throws ImageCompressionException { throw new ImageCompressionException("Not implemented yet"); } + + @Override + public void trainAndSaveAllCodebooks() throws ImageCompressionException { + throw new ImageCompressionException("Not implemented yet"); + } } diff --git a/src/main/java/cz/it4i/qcmp/compression/VQImageCompressor.java b/src/main/java/cz/it4i/qcmp/compression/VQImageCompressor.java index 21653111257925d34b54cb7c380ce6081fbc41a8..ae130701b39f54d7fa3123dc9e4c34c644842c58 100644 --- a/src/main/java/cz/it4i/qcmp/compression/VQImageCompressor.java +++ b/src/main/java/cz/it4i/qcmp/compression/VQImageCompressor.java @@ -219,53 +219,6 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm return planeDataSizes; } - @Override - public void trainAndSaveCodebook() throws ImageCompressionException { - reportStatusToListeners("Loading image data..."); - - final IPlaneLoader planeLoader; - try { - planeLoader = PlaneLoaderFactory.getPlaneLoaderForInputFile(options.getInputDataInfo()); - } catch (final Exception e) { - throw new ImageCompressionException("Unable to create plane reader. " + e.getMessage()); - } - - final int[][] trainingData; - if (options.getInputDataInfo().isPlaneIndexSet()) { - reportStatusToListeners("VQ: Loading single plane data."); - final int planeIndex = options.getInputDataInfo().getPlaneIndex(); - trainingData = planeLoader.loadVectorsFromPlaneRange(options, new Range<>(planeIndex, planeIndex + 1)); - } else if (options.getInputDataInfo().isPlaneRangeSet()) { - reportStatusToListeners("VQ: Loading plane range data."); - trainingData = planeLoader.loadVectorsFromPlaneRange(options, options.getInputDataInfo().getPlaneRange()); - } else { - reportStatusToListeners("VQ: Loading all planes data."); - trainingData = planeLoader.loadVectorsFromPlaneRange(options, - new Range<>(0, options.getInputDataInfo().getDimensions().getZ())); - } - - - final LBGVectorQuantizer vqInitializer = new LBGVectorQuantizer(trainingData, - getCodebookSize(), - options.getWorkerCount(), - options.getQuantizationVector()); - - reportStatusToListeners("Starting LBG optimization."); - vqInitializer.setStatusListener(this::reportStatusToListeners); - final LBGResult lbgResult = vqInitializer.findOptimalCodebook(); - reportStatusToListeners("Learned the optimal codebook."); - - - final QuantizationCacheManager cacheManager = new QuantizationCacheManager(options.getCodebookCacheFolder()); - try { - final String cacheFilePath = cacheManager.saveCodebook(options.getInputDataInfo().getCacheFileName(), lbgResult.getCodebook()); - reportStatusToListeners("Saved cache file to %s", cacheFilePath); - } catch (final IOException e) { - throw new ImageCompressionException("Unable to write VQ cache.", e); - } - reportStatusToListeners("Operation completed."); - } - /** * Calculate the number of voxel layers needed for dataset of plane count. * @@ -348,4 +301,92 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm return voxelLayersSizes; } + @Override + public void trainAndSaveCodebook() throws ImageCompressionException { + reportStatusToListeners("Loading image data..."); + + final IPlaneLoader planeLoader; + try { + planeLoader = PlaneLoaderFactory.getPlaneLoaderForInputFile(options.getInputDataInfo()); + } catch (final Exception e) { + throw new ImageCompressionException("Unable to create plane reader. " + e.getMessage()); + } + + final int[][] trainingData = loadDataForCodebookTraining(planeLoader); + + + final LBGVectorQuantizer vqInitializer = new LBGVectorQuantizer(trainingData, + getCodebookSize(), + options.getWorkerCount(), + options.getQuantizationVector()); + + reportStatusToListeners("Starting LBG optimization."); + vqInitializer.setStatusListener(this::reportStatusToListeners); + final LBGResult lbgResult = vqInitializer.findOptimalCodebook(); + reportStatusToListeners("Learned the optimal codebook."); + + + final QuantizationCacheManager cacheManager = new QuantizationCacheManager(options.getCodebookCacheFolder()); + try { + final String cacheFilePath = cacheManager.saveCodebook(options.getInputDataInfo().getCacheFileName(), lbgResult.getCodebook()); + reportStatusToListeners("Saved cache file to %s", cacheFilePath); + } catch (final IOException e) { + throw new ImageCompressionException("Unable to write VQ cache.", e); + } + reportStatusToListeners("Operation completed."); + } + + @Override + public void trainAndSaveAllCodebooks() throws ImageCompressionException { + reportStatusToListeners("trainAndSaveAllCodebooks is starting with %d workers.", options.getWorkerCount()); + + reportStatusToListeners("Loading image data..."); + final IPlaneLoader planeLoader; + try { + planeLoader = PlaneLoaderFactory.getPlaneLoaderForInputFile(options.getInputDataInfo()); + } catch (final Exception e) { + throw new ImageCompressionException("Unable to create plane reader. " + e.getMessage()); + } + final int[][] trainingData = loadDataForCodebookTraining(planeLoader); + reportStatusToListeners("Data loading is finished."); + + final QuantizationCacheManager qcm = new QuantizationCacheManager(options.getCodebookCacheFolder()); + + final LBGVectorQuantizer codebookTrainer = new LBGVectorQuantizer(trainingData, + 256, + options.getWorkerCount(), + options.getQuantizationVector()); + codebookTrainer.findOptimalCodebook(vqCodebook -> { + try { + assert ((vqCodebook.getCodebookSize() == vqCodebook.getVectors().length) && + (vqCodebook.getCodebookSize() == vqCodebook.getVectorFrequencies().length)) + : "Codebook size, Vector count, Frequencies count mismatch"; + qcm.saveCodebook(options.getInputDataInfo().getCacheFileName(), vqCodebook); + } catch (final IOException e) { + System.err.println("Failed to save trained codebook."); + e.printStackTrace(); + } + reportStatusToListeners("Optimal codebook of size %d was found.", vqCodebook.getCodebookSize()); + }); + + reportStatusToListeners("Trained all codebooks."); + } + + int[][] loadDataForCodebookTraining(final IPlaneLoader planeLoader) throws ImageCompressionException { + final int[][] trainingData; + if (options.getInputDataInfo().isPlaneIndexSet()) { + reportStatusToListeners("VQ: Loading single plane data."); + final int planeIndex = options.getInputDataInfo().getPlaneIndex(); + trainingData = planeLoader.loadVectorsFromPlaneRange(options, new Range<>(planeIndex, planeIndex + 1)); + } else if (options.getInputDataInfo().isPlaneRangeSet()) { + reportStatusToListeners("VQ: Loading plane range data."); + trainingData = planeLoader.loadVectorsFromPlaneRange(options, options.getInputDataInfo().getPlaneRange()); + } else { + reportStatusToListeners("VQ: Loading all planes data."); + trainingData = planeLoader.loadVectorsFromPlaneRange(options, + new Range<>(0, options.getInputDataInfo().getDimensions().getZ())); + } + return trainingData; + } + } diff --git a/src/main/java/cz/it4i/qcmp/quantization/vector/LBGVectorQuantizer.java b/src/main/java/cz/it4i/qcmp/quantization/vector/LBGVectorQuantizer.java index 65770041ef20370eac7ec2631af8a720589c7e9b..0245ca69724f078dff224deabd9918038f05e505 100644 --- a/src/main/java/cz/it4i/qcmp/quantization/vector/LBGVectorQuantizer.java +++ b/src/main/java/cz/it4i/qcmp/quantization/vector/LBGVectorQuantizer.java @@ -28,6 +28,10 @@ public class LBGVectorQuantizer { private IStatusListener statusListener = null; private double _mse = 0.0; + public interface CodebookFoundCallback { + void process(final VQCodebook trainedCodebook); + } + public LBGVectorQuantizer(final int[][] vectors, final int codebookSize, final int workerCount, @@ -108,19 +112,23 @@ public class LBGVectorQuantizer { return new LBGResult(vectorDimensions, codebook, frequencies, mse, psnr); } + public LBGResult findOptimalCodebook() { + return findOptimalCodebook(null); + } + /** * Find the optimal codebook of vectors, used for vector quantization. * * @return Result of the search. */ - public LBGResult findOptimalCodebook() { + public LBGResult findOptimalCodebook(final CodebookFoundCallback codebookCallback) { final Stopwatch stopwatch = Stopwatch.startNew("LBG::findOptimalCodebook()"); if (uniqueVectorCount < codebookSize) { return createCodebookFromUniqueVectors(); } - final LearningCodebookEntry[] codebook = initializeCodebook(); + final LearningCodebookEntry[] codebook = initializeCodebook(codebookCallback); reportStatus("LBG::findOptimalCodebook() - Got initial codebook. Improving it..."); LBG(codebook, EPSILON * 0.1); @@ -131,7 +139,14 @@ public class LBGVectorQuantizer { psnr); stopwatch.stop(); reportStatus(stopwatch.toString()); - return new LBGResult(vectorDimensions, learningCodebookToCodebook(codebook), frequencies, finalMse, psnr); + + final LBGResult result = new LBGResult(vectorDimensions, learningCodebookToCodebook(codebook), frequencies, finalMse, psnr); + + if (codebookCallback != null) { + codebookCallback.process(result.getCodebook()); + } + + return result; } /** @@ -304,7 +319,7 @@ public class LBGVectorQuantizer { * * @return The initial codebook to be improved by LBG. */ - private LearningCodebookEntry[] initializeCodebook() { + private LearningCodebookEntry[] initializeCodebook(final CodebookFoundCallback codebookFoundCallback) { int currentCodebookSize = 1; LearningCodebookEntry[] codebook = new LearningCodebookEntry[]{createInitialEntry()}; @@ -384,11 +399,22 @@ public class LBGVectorQuantizer { currentCodebookSize *= 2; // Execute LBG Algorithm on current codebook to improve it. - LBG(codebook); + final double eps = codebookFoundCallback == null ? EPSILON : EPSILON * 0.1; + LBG(codebook, eps); final double avgMse = averageMse(codebook); reportStatus("MSE of improved divided codebook: %f", avgMse); + + if (codebookFoundCallback != null) { + + final long[] codebookFrequencies = new long[codebook.length]; + System.arraycopy(frequencies, 0, codebookFrequencies, 0, codebook.length); + + codebookFoundCallback.process(new VQCodebook(vectorDimensions, + learningCodebookToCodebook(codebook), + codebookFrequencies)); + } } return codebook; }