diff --git a/src/main/java/cz/it4i/qcmp/cli/functions/DebugFunction.java b/src/main/java/cz/it4i/qcmp/cli/functions/DebugFunction.java index ca4dcf7f96406227f58c3a41f7d924d0997be12c..a5f0fea8631131f416119677c6878d1d70f5adbc 100644 --- a/src/main/java/cz/it4i/qcmp/cli/functions/DebugFunction.java +++ b/src/main/java/cz/it4i/qcmp/cli/functions/DebugFunction.java @@ -36,14 +36,14 @@ public class DebugFunction extends CustomFunctionBase { symbols[i] = i; } - final HuffmanTreeBuilder huffman = new HuffmanTreeBuilder(symbols, codebook.getVectorFrequencies()); - huffman.buildHuffmanTree(); + final HuffmanTreeBuilder huffmanBuilder = new HuffmanTreeBuilder(symbols, codebook.getVectorFrequencies()); + huffmanBuilder.buildHuffmanTree(); final int bitsPerSymbol = (int) Utils.log2(codebook.getCodebookSize()); try (final OutBitStream bitStream = new OutBitStream(new FileOutputStream("D:\\tmp\\huffman_tree.data", false), bitsPerSymbol, 64)) { - huffman.getRoot().writeToBinaryStream(bitStream); + huffmanBuilder.createEncoder().getRoot().writeToBinaryStream(bitStream); } catch (final IOException e) { e.printStackTrace(); } @@ -56,7 +56,7 @@ public class DebugFunction extends CustomFunctionBase { ex.printStackTrace(); } - final boolean equal = huffman.getRoot().treeEqual(readRoot); + final boolean equal = huffmanBuilder.createEncoder().getRoot().treeEqual(readRoot); System.out.println(readRoot != null); diff --git a/src/main/java/cz/it4i/qcmp/compression/CompressorDecompressorBase.java b/src/main/java/cz/it4i/qcmp/compression/CompressorDecompressorBase.java index 4654ac8e280306ec577b0d584b3454d79181ce35..f41106e7bbcc8eebf1ac90ceba9899272a4e7b8e 100644 --- a/src/main/java/cz/it4i/qcmp/compression/CompressorDecompressorBase.java +++ b/src/main/java/cz/it4i/qcmp/compression/CompressorDecompressorBase.java @@ -4,6 +4,7 @@ import cz.it4i.qcmp.compression.exception.ImageCompressionException; import cz.it4i.qcmp.compression.listeners.IProgressListener; import cz.it4i.qcmp.compression.listeners.IStatusListener; import cz.it4i.qcmp.huffman.HuffmanDecoder; +import cz.it4i.qcmp.huffman.HuffmanEncoder; import cz.it4i.qcmp.huffman.HuffmanTreeBuilder; import cz.it4i.qcmp.io.InputData; import cz.it4i.qcmp.io.OutBitStream; @@ -101,16 +102,16 @@ public abstract class CompressorDecompressorBase { return symbols; } - protected HuffmanTreeBuilder createHuffmanCoder(final int[] symbols, final long[] frequencies) { + protected HuffmanEncoder createHuffmanEncoder(final int[] symbols, final long[] frequencies) { final HuffmanTreeBuilder huffman = new HuffmanTreeBuilder(symbols, frequencies); huffman.buildHuffmanTree(); - return huffman; + return huffman.createEncoder(); } protected HuffmanDecoder createHuffmanDecoder(final int[] symbols, final long[] frequencies) { final HuffmanTreeBuilder huffman = new HuffmanTreeBuilder(symbols, frequencies); huffman.buildHuffmanTree(); - return new HuffmanDecoder(huffman.getRoot()); + return huffman.createDecoder(); } protected int[] getPlaneIndicesForCompression(final InputData inputData) { @@ -158,17 +159,17 @@ public abstract class CompressorDecompressorBase { * Write huffman encoded indices to the compress stream. * * @param compressStream Compress stream. - * @param huffman Huffman encoder. + * @param huffmanEncoder Huffman encoder. * @param indices Indices to write. * @return Number of bytes written. * @throws ImageCompressionException when fails to write to compress stream. */ protected long writeHuffmanEncodedIndices(final DataOutputStream compressStream, - final HuffmanTreeBuilder huffman, + final HuffmanEncoder huffmanEncoder, final int[] indices) throws ImageCompressionException { try (final OutBitStream outBitStream = new OutBitStream(compressStream, options.getBitsPerCodebookIndex(), 2048)) { for (final int index : indices) { - outBitStream.write(huffman.getCode(index)); + outBitStream.write(huffmanEncoder.getSymbolCode(index)); } return outBitStream.getBytesWritten(); } catch (final Exception ex) { diff --git a/src/main/java/cz/it4i/qcmp/compression/SQImageCompressor.java b/src/main/java/cz/it4i/qcmp/compression/SQImageCompressor.java index e0f3451cfb2cddfe3e2a5c9de60bf8c2d6804302..06ee7b79632e8d81f8139b176c05459d4a954928 100644 --- a/src/main/java/cz/it4i/qcmp/compression/SQImageCompressor.java +++ b/src/main/java/cz/it4i/qcmp/compression/SQImageCompressor.java @@ -5,8 +5,7 @@ import cz.it4i.qcmp.cache.ICacheFile; import cz.it4i.qcmp.cache.QuantizationCacheManager; import cz.it4i.qcmp.cache.SQCacheFile; import cz.it4i.qcmp.compression.exception.ImageCompressionException; -import cz.it4i.qcmp.huffman.HuffmanDecoder; -import cz.it4i.qcmp.huffman.HuffmanTreeBuilder; +import cz.it4i.qcmp.huffman.HuffmanEncoder; import cz.it4i.qcmp.io.InputData; import cz.it4i.qcmp.io.loader.IPlaneLoader; import cz.it4i.qcmp.io.loader.PlaneLoaderFactory; @@ -21,7 +20,7 @@ import java.io.IOException; public class SQImageCompressor extends CompressorDecompressorBase implements IImageCompressor { private ScalarQuantizer cachedQuantizer; - private HuffmanDecoder cachedHuffmanDecoder; + private HuffmanEncoder cachedHuffmanEncoder; public SQImageCompressor(final CompressionOptions options) { super(options); @@ -46,7 +45,7 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm public void preloadGlobalCodebook(final ICacheFile codebookCacheFile) { final SQCodebook cachedCodebook = ((SQCacheFile) codebookCacheFile).getCodebook(); cachedQuantizer = new ScalarQuantizer(cachedCodebook); - cachedHuffmanDecoder = createHuffmanDecoder(createHuffmanSymbols(cachedCodebook.getCodebookSize()), + cachedHuffmanEncoder = createHuffmanEncoder(createHuffmanSymbols(cachedCodebook.getCodebookSize()), cachedCodebook.getSymbolFrequencies()); } @@ -118,13 +117,13 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm } ScalarQuantizer quantizer = null; - HuffmanTreeBuilder huffman = null; + HuffmanEncoder huffmanEncoder = null; final int[] huffmanSymbols = createHuffmanSymbols(getCodebookSize()); if (options.getCodebookType() == CompressionOptions.CodebookType.Global) { reportStatusToListeners("Loading codebook from cache file."); quantizer = loadQuantizerFromCache(); - huffman = createHuffmanCoder(huffmanSymbols, quantizer.getCodebook().getSymbolFrequencies()); + huffmanEncoder = createHuffmanEncoder(huffmanSymbols, quantizer.getCodebook().getSymbolFrequencies()); reportStatusToListeners("Cached quantizer with huffman coder created."); writeCodebookToOutputStream(quantizer, compressStream); @@ -140,7 +139,7 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm reportStatusToListeners(String.format("Training scalar quantizer from middle plane %d.", middlePlaneIndex)); quantizer = trainScalarQuantizerFromData(middlePlaneData); - huffman = createHuffmanCoder(huffmanSymbols, quantizer.getCodebook().getSymbolFrequencies()); + huffmanEncoder = createHuffmanEncoder(huffmanSymbols, quantizer.getCodebook().getSymbolFrequencies()); stopwatch.stop(); writeCodebookToOutputStream(quantizer, compressStream); @@ -176,16 +175,15 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm quantizer = trainScalarQuantizerFromData(planeData); writeCodebookToOutputStream(quantizer, compressStream); - huffman = new HuffmanTreeBuilder(huffmanSymbols, quantizer.getCodebook().getSymbolFrequencies()); - huffman.buildHuffmanTree(); + huffmanEncoder = createHuffmanEncoder(huffmanSymbols, quantizer.getCodebook().getSymbolFrequencies()); } assert (quantizer != null) : "Scalar Quantizer wasn't initialized."; - assert (huffman != null) : "Huffman wasn't initialized."; + assert (huffmanEncoder != null) : "Huffman wasn't initialized."; final int[] indices = quantizer.quantizeIntoIndices(planeData, 1); - planeDataSizes[planeCounter++] = writeHuffmanEncodedIndices(compressStream, huffman, indices); + planeDataSizes[planeCounter++] = writeHuffmanEncodedIndices(compressStream, huffmanEncoder, indices); stopwatch.stop(); reportProgressToListeners(planeIndex, planeIndices.length, diff --git a/src/main/java/cz/it4i/qcmp/compression/VQImageCompressor.java b/src/main/java/cz/it4i/qcmp/compression/VQImageCompressor.java index 90d3cfd7d2f1ebe8f3822ee47034e34e175bbac1..063ff1c530f264c0e41dae68a6488378a55975de 100644 --- a/src/main/java/cz/it4i/qcmp/compression/VQImageCompressor.java +++ b/src/main/java/cz/it4i/qcmp/compression/VQImageCompressor.java @@ -6,7 +6,7 @@ import cz.it4i.qcmp.cache.VQCacheFile; import cz.it4i.qcmp.compression.exception.ImageCompressionException; import cz.it4i.qcmp.data.Range; import cz.it4i.qcmp.fileformat.QuantizationType; -import cz.it4i.qcmp.huffman.HuffmanTreeBuilder; +import cz.it4i.qcmp.huffman.HuffmanEncoder; import cz.it4i.qcmp.io.InputData; import cz.it4i.qcmp.io.loader.IPlaneLoader; import cz.it4i.qcmp.io.loader.PlaneLoaderFactory; @@ -24,7 +24,7 @@ import java.io.IOException; public class VQImageCompressor extends CompressorDecompressorBase implements IImageCompressor { private VectorQuantizer cachedQuantizer = null; - private HuffmanTreeBuilder cachedHuffman = null; + private HuffmanEncoder cachedHuffmanEncoder = null; private boolean useKdTree = false; @@ -36,7 +36,8 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm public void preloadGlobalCodebook(final ICacheFile codebookCacheFile) { final VQCodebook cachedCodebook = ((VQCacheFile) codebookCacheFile).getCodebook(); cachedQuantizer = new VectorQuantizer(cachedCodebook); - cachedHuffman = createHuffmanCoder(createHuffmanSymbols(cachedCodebook.getCodebookSize()), cachedCodebook.getVectorFrequencies()); + cachedHuffmanEncoder = createHuffmanEncoder(createHuffmanSymbols(cachedCodebook.getCodebookSize()), + cachedCodebook.getVectorFrequencies()); } public boolean shouldUseKdTree() { @@ -158,14 +159,14 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm } VectorQuantizer quantizer = cachedQuantizer; - HuffmanTreeBuilder huffman = cachedHuffman; - assert (!streamMode || ((quantizer != null) && (huffman != null))); + HuffmanEncoder huffmanEncoder = cachedHuffmanEncoder; + assert (!streamMode || ((quantizer != null) && (huffmanEncoder != null))); if (!streamMode) { if (options.getCodebookType() == CompressionOptions.CodebookType.Global) { reportStatusToListeners("Loading codebook from cache file."); quantizer = loadQuantizerFromCache(); - huffman = createHuffmanCoder(huffmanSymbols, quantizer.getFrequencies()); + huffmanEncoder = createHuffmanEncoder(huffmanSymbols, quantizer.getFrequencies()); reportStatusToListeners("Cached quantizer with huffman coder created."); writeQuantizerToCompressStream(quantizer, compressStream); } else if (options.getCodebookType() == CompressionOptions.CodebookType.MiddlePlane) { @@ -174,7 +175,7 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm final int[][] refPlaneVectors = planeLoader.loadVectorsFromPlaneRange(0, options, Utils.singlePlaneRange(getMiddlePlaneIndex())); quantizer = trainVectorQuantizerFromPlaneVectors(refPlaneVectors); - huffman = createHuffmanCoder(huffmanSymbols, quantizer.getFrequencies()); + huffmanEncoder = createHuffmanEncoder(huffmanSymbols, quantizer.getFrequencies()); stopwatch.stop(); reportStatusToListeners("Middle plane codebook created in: " + stopwatch.getElapsedTimeString()); writeQuantizerToCompressStream(quantizer, compressStream); @@ -208,13 +209,13 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm if (!streamMode && !hasGeneralQuantizer) { reportStatusToListeners(String.format("Training vector quantizer from plane %d.", planeIndex)); quantizer = trainVectorQuantizerFromPlaneVectors(planeVectors); - huffman = createHuffmanCoder(huffmanSymbols, quantizer.getFrequencies()); + huffmanEncoder = createHuffmanEncoder(huffmanSymbols, quantizer.getFrequencies()); writeQuantizerToCompressStream(quantizer, compressStream); } final int[] indices = quantizeVectorsImpl(quantizer, planeVectors, options.getWorkerCount()); - planeDataSizes[planeCounter++] = writeHuffmanEncodedIndices(compressStream, huffman, indices); + planeDataSizes[planeCounter++] = writeHuffmanEncodedIndices(compressStream, huffmanEncoder, indices); stopwatch.stop(); if (options.isConsoleApplication()) { @@ -283,8 +284,9 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm final long[] voxelLayersSizes = new long[voxelLayerCount]; final VectorQuantizer quantizer = (cachedQuantizer != null) ? cachedQuantizer : loadQuantizerFromCache(); - final HuffmanTreeBuilder huffman = (cachedHuffman != null) ? cachedHuffman : createHuffmanCoder(huffmanSymbols, - quantizer.getFrequencies()); + final HuffmanEncoder huffmanEncoder = (cachedHuffmanEncoder != null) + ? cachedHuffmanEncoder + : createHuffmanEncoder(huffmanSymbols, quantizer.getFrequencies()); if (!streamMode) writeQuantizerToCompressStream(quantizer, compressStream); @@ -309,7 +311,7 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm final int[] indices = quantizeVectorsImpl(quantizer, voxelData, options.getWorkerCount()); - voxelLayersSizes[voxelLayerIndex] = writeHuffmanEncodedIndices(compressStream, huffman, indices); + voxelLayersSizes[voxelLayerIndex] = writeHuffmanEncodedIndices(compressStream, huffmanEncoder, indices); stopwatch.stop(); if (options.isConsoleApplication()) { reportStatusToListeners("%d/%d Finished voxel layer %s compression pass in %s", diff --git a/src/main/java/cz/it4i/qcmp/huffman/HuffmanDecoder.java b/src/main/java/cz/it4i/qcmp/huffman/HuffmanDecoder.java index 00ff44c52200b2d8ba66620e15ced8b2c294c50f..84c2e2e661a03e77000a9d2c96d057a724ce8f48 100644 --- a/src/main/java/cz/it4i/qcmp/huffman/HuffmanDecoder.java +++ b/src/main/java/cz/it4i/qcmp/huffman/HuffmanDecoder.java @@ -5,7 +5,7 @@ import cz.it4i.qcmp.io.InBitStream; import java.io.IOException; /** - * Simply wrapper around root huffman node to provide easy decode function. + * Simple wrapper around root huffman node to provide easy decode function. */ public class HuffmanDecoder { private final HuffmanNode root; @@ -33,4 +33,8 @@ public class HuffmanDecoder { } return currentNode.getSymbol(); } + + public HuffmanNode getRoot() { + return root; + } } diff --git a/src/main/java/cz/it4i/qcmp/huffman/HuffmanEncoder.java b/src/main/java/cz/it4i/qcmp/huffman/HuffmanEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..1d34d4ad692d80dff7f1763ce9429e12b4f1f5aa --- /dev/null +++ b/src/main/java/cz/it4i/qcmp/huffman/HuffmanEncoder.java @@ -0,0 +1,36 @@ +package cz.it4i.qcmp.huffman; + +import java.util.HashMap; + +/** + * Simple wrapper around root huffman symbol map to provide easy encode function. + */ +public class HuffmanEncoder { + private final HuffmanNode root; + private final HashMap<Integer, boolean[]> symbolCodes; + + /** + * Create huffman encoder from symbol map. + * + * @param root Huffman tree root. + * @param symbolCodes Huffman symbol map. + */ + public HuffmanEncoder(final HuffmanNode root, final HashMap<Integer, boolean[]> symbolCodes) { + this.root = root; + this.symbolCodes = symbolCodes; + } + + /** + * Get binary code for huffman symbol. + * + * @param symbol Huffman symbol. + * @return Binary code. + */ + public boolean[] getSymbolCode(final int symbol) { + return symbolCodes.get(symbol); + } + + public HuffmanNode getRoot() { + return root; + } +} diff --git a/src/main/java/cz/it4i/qcmp/huffman/HuffmanTreeBuilder.java b/src/main/java/cz/it4i/qcmp/huffman/HuffmanTreeBuilder.java index 91571ceb73e1d5411dddc4c7bbee07c920b7e6cd..25b26dcf2624892d8d3dc619193260709ebfca79 100644 --- a/src/main/java/cz/it4i/qcmp/huffman/HuffmanTreeBuilder.java +++ b/src/main/java/cz/it4i/qcmp/huffman/HuffmanTreeBuilder.java @@ -1,11 +1,12 @@ package cz.it4i.qcmp.huffman; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.PriorityQueue; public class HuffmanTreeBuilder { private HuffmanNode root = null; private HashMap<Integer, boolean[]> symbolCodes; - private HashMap<Integer, Double> symbolProbabilityMap; private final int[] symbols; private final long[] symbolFrequencies; @@ -76,7 +77,7 @@ public class HuffmanTreeBuilder { } private PriorityQueue<HuffmanNode> buildPriorityQueue() { - symbolProbabilityMap = new HashMap<>(symbols.length); + final HashMap<Integer, Double> symbolProbabilityMap = new HashMap<>(symbols.length); double totalFrequency = 0.0; for (final long symbolFrequency : symbolFrequencies) { totalFrequency += symbolFrequency; @@ -93,28 +94,18 @@ public class HuffmanTreeBuilder { return queue; } - - public boolean[] getCode(final int symbol) { - return symbolCodes.get(symbol); - } - - public HuffmanNode getRoot() { - return root; + /** + * Create huffman encoder from symbol codes. + * + * @return Huffman encoder. + */ + public HuffmanEncoder createEncoder() { + assert (root != null && symbolCodes != null) : "Huffman tree was not build yet"; + return new HuffmanEncoder(root, symbolCodes); } - public HashMap<Integer, Double> getSymbolProbabilityMap() { - return createSortedHashMap(symbolProbabilityMap); - } - - private HashMap<Integer, Double> createSortedHashMap(final HashMap<Integer, Double> map) { - final List<Map.Entry<Integer, Double>> list = new LinkedList<Map.Entry<Integer, Double>>(map.entrySet()); - //Custom Comparator - list.sort((t0, t1) -> (-(t0.getValue().compareTo(t1.getValue())))); - //copying the sorted list in HashMap to preserve the iteration order - final HashMap<Integer, Double> sortedHashMap = new LinkedHashMap<Integer, Double>(); - for (final Map.Entry<Integer, Double> entry : list) { - sortedHashMap.put(entry.getKey(), entry.getValue()); - } - return sortedHashMap; + public HuffmanDecoder createDecoder() { + assert (root != null) : "Huffman tree was not build yet"; + return new HuffmanDecoder(root); } }