diff --git a/src/main/java/cz/it4i/qcmp/compression/CompressorDecompressorBase.java b/src/main/java/cz/it4i/qcmp/compression/CompressorDecompressorBase.java index 82d9453091fcfd1024a1f3413ee5e6fd7811662f..4654ac8e280306ec577b0d584b3454d79181ce35 100644 --- a/src/main/java/cz/it4i/qcmp/compression/CompressorDecompressorBase.java +++ b/src/main/java/cz/it4i/qcmp/compression/CompressorDecompressorBase.java @@ -3,6 +3,7 @@ package cz.it4i.qcmp.compression; 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.HuffmanTreeBuilder; import cz.it4i.qcmp.io.InputData; import cz.it4i.qcmp.io.OutBitStream; @@ -106,6 +107,12 @@ public abstract class CompressorDecompressorBase { return huffman; } + protected HuffmanDecoder createHuffmanDecoder(final int[] symbols, final long[] frequencies) { + final HuffmanTreeBuilder huffman = new HuffmanTreeBuilder(symbols, frequencies); + huffman.buildHuffmanTree(); + return new HuffmanDecoder(huffman.getRoot()); + } + protected int[] getPlaneIndicesForCompression(final InputData inputData) { if (inputData.isPlaneIndexSet()) { return new int[]{inputData.getPlaneIndex()}; diff --git a/src/main/java/cz/it4i/qcmp/compression/SQImageCompressor.java b/src/main/java/cz/it4i/qcmp/compression/SQImageCompressor.java index 1e7c1ec7b2ac5a96f07c3280a28f109c8ec0f34e..e0f3451cfb2cddfe3e2a5c9de60bf8c2d6804302 100644 --- a/src/main/java/cz/it4i/qcmp/compression/SQImageCompressor.java +++ b/src/main/java/cz/it4i/qcmp/compression/SQImageCompressor.java @@ -5,6 +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.io.InputData; import cz.it4i.qcmp.io.loader.IPlaneLoader; @@ -20,7 +21,7 @@ import java.io.IOException; public class SQImageCompressor extends CompressorDecompressorBase implements IImageCompressor { private ScalarQuantizer cachedQuantizer; - private HuffmanTreeBuilder cachedHuffman; + private HuffmanDecoder cachedHuffmanDecoder; public SQImageCompressor(final CompressionOptions options) { super(options); @@ -45,7 +46,8 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm public void preloadGlobalCodebook(final ICacheFile codebookCacheFile) { final SQCodebook cachedCodebook = ((SQCacheFile) codebookCacheFile).getCodebook(); cachedQuantizer = new ScalarQuantizer(cachedCodebook); - cachedHuffman = createHuffmanCoder(createHuffmanSymbols(cachedCodebook.getCodebookSize()), cachedCodebook.getSymbolFrequencies()); + cachedHuffmanDecoder = createHuffmanDecoder(createHuffmanSymbols(cachedCodebook.getCodebookSize()), + cachedCodebook.getSymbolFrequencies()); } /** diff --git a/src/main/java/cz/it4i/qcmp/compression/SQImageDecompressor.java b/src/main/java/cz/it4i/qcmp/compression/SQImageDecompressor.java index e72bfbb2baa95887536e68918ca3633bb681169c..b962be46a092acc017f0409a654961c40d3d8a9e 100644 --- a/src/main/java/cz/it4i/qcmp/compression/SQImageDecompressor.java +++ b/src/main/java/cz/it4i/qcmp/compression/SQImageDecompressor.java @@ -4,8 +4,7 @@ import cz.it4i.qcmp.cache.ICacheFile; import cz.it4i.qcmp.cache.SQCacheFile; import cz.it4i.qcmp.compression.exception.ImageDecompressionException; import cz.it4i.qcmp.fileformat.QCMPFileHeader; -import cz.it4i.qcmp.huffman.HuffmanNode; -import cz.it4i.qcmp.huffman.HuffmanTreeBuilder; +import cz.it4i.qcmp.huffman.HuffmanDecoder; import cz.it4i.qcmp.io.InBitStream; import cz.it4i.qcmp.quantization.scalar.SQCodebook; import cz.it4i.qcmp.utilities.Stopwatch; @@ -17,7 +16,7 @@ import java.io.IOException; public class SQImageDecompressor extends CompressorDecompressorBase implements IImageDecompressor { private SQCodebook cachedCodebook = null; - private HuffmanTreeBuilder cachedHuffman = null; + private HuffmanDecoder cachedHuffmanDecoder = null; public SQImageDecompressor(final CompressionOptions options) { super(options); @@ -52,12 +51,12 @@ public class SQImageDecompressor extends CompressorDecompressorBase implements I final int planePixelCount = header.getImageSizeX() * header.getImageSizeY(); SQCodebook codebook = null; - HuffmanTreeBuilder huffman = null; + HuffmanDecoder huffmanDecoder = null; if (!header.isCodebookPerPlane()) { // There is only one codebook. reportStatusToListeners("Loading single codebook and huffman coder."); codebook = readScalarQuantizationValues(compressedStream, codebookSize); - huffman = createHuffmanCoder(huffmanSymbols, codebook.getSymbolFrequencies()); + huffmanDecoder = createHuffmanDecoder(huffmanSymbols, codebook.getSymbolFrequencies()); } final Stopwatch stopwatch = new Stopwatch(); @@ -66,9 +65,9 @@ public class SQImageDecompressor extends CompressorDecompressorBase implements I if (header.isCodebookPerPlane()) { reportStatusToListeners("Loading plane codebook..."); codebook = readScalarQuantizationValues(compressedStream, codebookSize); - huffman = createHuffmanCoder(huffmanSymbols, codebook.getSymbolFrequencies()); + huffmanDecoder = createHuffmanDecoder(huffmanSymbols, codebook.getSymbolFrequencies()); } - assert (codebook != null && huffman != null); + assert (codebook != null && huffmanDecoder != null); reportStatusToListeners(String.format("Decompressing plane %d...", planeIndex)); byte[] decompressedPlaneData = null; @@ -82,13 +81,8 @@ public class SQImageDecompressor extends CompressorDecompressorBase implements I final int[] decompressedValues = new int[planePixelCount]; final int[] quantizationValues = codebook.getCentroids(); for (int pixel = 0; pixel < planePixelCount; pixel++) { - HuffmanNode currentHuffmanNode = huffman.getRoot(); - boolean bit; - while (!currentHuffmanNode.isLeaf()) { - bit = inBitStream.readBit(); - currentHuffmanNode = currentHuffmanNode.traverse(bit); - } - decompressedValues[pixel] = quantizationValues[currentHuffmanNode.getSymbol()]; + final int decodedSymbol = huffmanDecoder.decodeSymbol(inBitStream); + decompressedValues[pixel] = quantizationValues[decodedSymbol]; } decompressedPlaneData = @@ -116,7 +110,8 @@ public class SQImageDecompressor extends CompressorDecompressorBase implements I final SQCacheFile codebookCache = (SQCacheFile) codebookCacheFile; cachedCodebook = codebookCache.getCodebook(); - cachedHuffman = createHuffmanCoder(createHuffmanSymbols(cachedCodebook.getCodebookSize()), cachedCodebook.getSymbolFrequencies()); + cachedHuffmanDecoder = createHuffmanDecoder(createHuffmanSymbols(cachedCodebook.getCodebookSize()), + cachedCodebook.getSymbolFrequencies()); } @Override @@ -130,20 +125,20 @@ public class SQImageDecompressor extends CompressorDecompressorBase implements I final int planePixelCount = header.getImageSizeX() * header.getImageSizeY(); SQCodebook codebook = null; - HuffmanTreeBuilder huffman = null; + HuffmanDecoder huffmanDecoder = null; if (!header.isCodebookPerPlane()) { // There is only one codebook. codebook = readScalarQuantizationValues(compressedStream, codebookSize); - huffman = createHuffmanCoder(huffmanSymbols, codebook.getSymbolFrequencies()); + huffmanDecoder = createHuffmanDecoder(huffmanSymbols, codebook.getSymbolFrequencies()); } for (int planeIndex = 0; planeIndex < planeCountForDecompression; planeIndex++) { reportProgressToListeners(planeIndex, planeCountForDecompression, "Decompressing plane %d", planeIndex); if (header.isCodebookPerPlane()) { codebook = readScalarQuantizationValues(compressedStream, codebookSize); - huffman = createHuffmanCoder(huffmanSymbols, codebook.getSymbolFrequencies()); + huffmanDecoder = createHuffmanDecoder(huffmanSymbols, codebook.getSymbolFrequencies()); } - assert (codebook != null && huffman != null); + assert (codebook != null && huffmanDecoder != null); final int planeDataSize = (int) header.getPlaneDataSizes()[planeIndex]; try (final InBitStream inBitStream = new InBitStream(compressedStream, header.getBitsPerCodebookIndex(), planeDataSize)) { @@ -153,13 +148,8 @@ public class SQImageDecompressor extends CompressorDecompressorBase implements I final int[] decompressedValues = new int[planePixelCount]; final int[] quantizationValues = codebook.getCentroids(); for (int pixel = 0; pixel < planePixelCount; pixel++) { - HuffmanNode currentHuffmanNode = huffman.getRoot(); - boolean bit; - while (!currentHuffmanNode.isLeaf()) { - bit = inBitStream.readBit(); - currentHuffmanNode = currentHuffmanNode.traverse(bit); - } - decompressedValues[pixel] = quantizationValues[currentHuffmanNode.getSymbol()]; + final int decodedSymbol = huffmanDecoder.decodeSymbol(inBitStream); + decompressedValues[pixel] = quantizationValues[decodedSymbol]; } buffer[planeIndex] = TypeConverter.intArrayToShortArray(decompressedValues); diff --git a/src/main/java/cz/it4i/qcmp/compression/VQImageDecompressor.java b/src/main/java/cz/it4i/qcmp/compression/VQImageDecompressor.java index 1da6828e3e2883ba57f761b676ea365929a005f4..6cff655804476201c0e01306a3a469a18a3c6431 100644 --- a/src/main/java/cz/it4i/qcmp/compression/VQImageDecompressor.java +++ b/src/main/java/cz/it4i/qcmp/compression/VQImageDecompressor.java @@ -6,8 +6,7 @@ import cz.it4i.qcmp.compression.exception.ImageDecompressionException; import cz.it4i.qcmp.data.*; import cz.it4i.qcmp.fileformat.QCMPFileHeader; import cz.it4i.qcmp.fileformat.QuantizationType; -import cz.it4i.qcmp.huffman.HuffmanNode; -import cz.it4i.qcmp.huffman.HuffmanTreeBuilder; +import cz.it4i.qcmp.huffman.HuffmanDecoder; import cz.it4i.qcmp.io.InBitStream; import cz.it4i.qcmp.quantization.vector.VQCodebook; import cz.it4i.qcmp.utilities.Stopwatch; @@ -20,7 +19,7 @@ import java.io.IOException; public class VQImageDecompressor extends CompressorDecompressorBase implements IImageDecompressor { private VQCodebook cachedCodebook = null; - private HuffmanTreeBuilder cachedHuffman = null; + private HuffmanDecoder cachedHuffmanDecoder = null; private interface DecompressCallback { void process(final Block imageBlock, final int planeIndex) throws ImageDecompressionException; @@ -73,7 +72,8 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I final VQCacheFile codebookCache = (VQCacheFile) codebookCacheFile; cachedCodebook = codebookCache.getCodebook(); - cachedHuffman = createHuffmanCoder(createHuffmanSymbols(cachedCodebook.getCodebookSize()), cachedCodebook.getVectorFrequencies()); + cachedHuffmanDecoder = createHuffmanDecoder(createHuffmanSymbols(cachedCodebook.getCodebookSize()), + cachedCodebook.getVectorFrequencies()); } @@ -122,19 +122,19 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I VQCodebook codebook = null; - HuffmanTreeBuilder huffman = null; + HuffmanDecoder huffmanDecoder = null; if (!header.isCodebookPerPlane()) { // There is only one codebook. codebook = readCodebook(compressedStream, codebookSize, vectorSize); - huffman = createHuffmanCoder(huffmanSymbols, codebook.getVectorFrequencies()); + huffmanDecoder = createHuffmanDecoder(huffmanSymbols, codebook.getVectorFrequencies()); } for (int planeIndex = 0; planeIndex < planeCountForDecompression; planeIndex++) { if (header.isCodebookPerPlane()) { codebook = readCodebook(compressedStream, codebookSize, vectorSize); - huffman = createHuffmanCoder(huffmanSymbols, codebook.getVectorFrequencies()); + huffmanDecoder = createHuffmanDecoder(huffmanSymbols, codebook.getVectorFrequencies()); } - assert (codebook != null && huffman != null); + assert (codebook != null && huffmanDecoder != null); final int planeDataSize = (int) header.getPlaneDataSizes()[planeIndex]; @@ -146,14 +146,8 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I final int[][] decompressedVectors = new int[(int) planeVectorCount][vectorSize]; for (int vecIndex = 0; vecIndex < planeVectorCount; vecIndex++) { - HuffmanNode currentHuffmanNode = huffman.getRoot(); - boolean bit; - while (!currentHuffmanNode.isLeaf()) { - bit = inBitStream.readBit(); - currentHuffmanNode = currentHuffmanNode.traverse(bit); - } - System.arraycopy(codebook.getVectors()[currentHuffmanNode.getSymbol()], - 0, decompressedVectors[vecIndex], 0, vectorSize); + final int decodedSymbol = huffmanDecoder.decodeSymbol(inBitStream); + System.arraycopy(codebook.getVectors()[decodedSymbol], 0, decompressedVectors[vecIndex], 0, vectorSize); } @@ -177,7 +171,7 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I final QCMPFileHeader header, final DecompressCallback callback) throws ImageDecompressionException { - assert (cachedCodebook != null && cachedHuffman != null); + assert (cachedCodebook != null && cachedHuffmanDecoder != null); assert (header.getVectorSizeZ() == 1); final int planeCountForDecompression = header.getImageSizeZ(); final long planeVectorCount = calculatePlaneVectorCount(header); @@ -197,7 +191,7 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I final int[][] decompressedVectors = new int[(int) planeVectorCount][vectorSize]; int huffmanIndex; for (int vecIndex = 0; vecIndex < planeVectorCount; vecIndex++) { - huffmanIndex = decodeHuffmanSymbol(cachedHuffman, inBitStream); + huffmanIndex = cachedHuffmanDecoder.decodeSymbol(inBitStream); System.arraycopy(cachedCodebook.getVectors()[huffmanIndex], 0, decompressedVectors[vecIndex], 0, vectorSize); } @@ -245,7 +239,7 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I final VQCodebook codebook = readCodebook(compressedStream, codebookSize, vectorSize); - final HuffmanTreeBuilder huffman = createHuffmanCoder(huffmanSymbols, codebook.getVectorFrequencies()); + final HuffmanDecoder huffmanDecoder = createHuffmanDecoder(huffmanSymbols, codebook.getVectorFrequencies()); final int voxelLayerCount = VQImageCompressor.calculateVoxelLayerCount(header.getImageSizeZ(), header.getVectorSizeZ()); final Stopwatch stopwatch = new Stopwatch(); @@ -267,7 +261,7 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I inBitStream.setAllowReadFromUnderlyingStream(false); for (int voxelIndex = 0; voxelIndex < voxelLayerVoxelCount; voxelIndex++) { - final int huffmanSymbol = decodeHuffmanSymbol(huffman, inBitStream); + final int huffmanSymbol = huffmanDecoder.decodeSymbol(inBitStream); System.arraycopy(codebook.getVectors()[huffmanSymbol], 0, decompressedVoxels[voxelIndex], 0, vectorSize); } @@ -325,7 +319,7 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I inBitStream.setAllowReadFromUnderlyingStream(false); for (int voxelIndex = 0; voxelIndex < voxelLayerVoxelCount; voxelIndex++) { - final int huffmanSymbol = decodeHuffmanSymbol(cachedHuffman, inBitStream); + final int huffmanSymbol = cachedHuffmanDecoder.decodeSymbol(inBitStream); System.arraycopy(cachedCodebook.getVectors()[huffmanSymbol], 0, decompressedVoxels[voxelIndex], 0, vectorSize); } @@ -379,15 +373,6 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I }); } - private int decodeHuffmanSymbol(final HuffmanTreeBuilder huffman, final InBitStream inBitStream) throws IOException { - HuffmanNode currentHuffmanNode = huffman.getRoot(); - while (!currentHuffmanNode.isLeaf()) { - currentHuffmanNode = currentHuffmanNode.traverse(inBitStream.readBit()); - } - return currentHuffmanNode.getSymbol(); - } - - @Override public short[] decompressStreamMode(final DataInputStream compressedStream, final QCMPFileHeader header) throws ImageDecompressionException { diff --git a/src/main/java/cz/it4i/qcmp/huffman/HuffmanDecoder.java b/src/main/java/cz/it4i/qcmp/huffman/HuffmanDecoder.java new file mode 100644 index 0000000000000000000000000000000000000000..00ff44c52200b2d8ba66620e15ced8b2c294c50f --- /dev/null +++ b/src/main/java/cz/it4i/qcmp/huffman/HuffmanDecoder.java @@ -0,0 +1,36 @@ +package cz.it4i.qcmp.huffman; + +import cz.it4i.qcmp.io.InBitStream; + +import java.io.IOException; + +/** + * Simply wrapper around root huffman node to provide easy decode function. + */ +public class HuffmanDecoder { + private final HuffmanNode root; + + /** + * Create huffman decoder from the root node. + * + * @param root Root huffman node. + */ + public HuffmanDecoder(final HuffmanNode root) { + this.root = root; + } + + /** + * Decode huffman symbol by reading binary code from stream. + * + * @param inBitStream Binary input stream. + * @return Decoded symbol. + * @throws IOException when fails to read from input stream. + */ + public int decodeSymbol(final InBitStream inBitStream) throws IOException { + HuffmanNode currentNode = root; + while (!currentNode.isLeaf()) { + currentNode = currentNode.traverse(inBitStream.readBit()); + } + return currentNode.getSymbol(); + } +} diff --git a/src/main/java/cz/it4i/qcmp/huffman/HuffmanTreeBuilder.java b/src/main/java/cz/it4i/qcmp/huffman/HuffmanTreeBuilder.java index 5daf0ae9782064ac07789aff49751d841f318535..91571ceb73e1d5411dddc4c7bbee07c920b7e6cd 100644 --- a/src/main/java/cz/it4i/qcmp/huffman/HuffmanTreeBuilder.java +++ b/src/main/java/cz/it4i/qcmp/huffman/HuffmanTreeBuilder.java @@ -23,9 +23,7 @@ public class HuffmanTreeBuilder { final HuffmanNode parentA = queue.poll(); final HuffmanNode parentB = queue.poll(); if (!(parentA.getProbability() <= parentB.getProbability())) { - System.err.println(String.format("Parent A prob: %.6f\nParent B prob: %.6f", - parentA.getProbability(), - parentB.getProbability())); + System.err.printf("Parent A prob: %.6f\nParent B prob: %.6f%n", parentA.getProbability(), parentB.getProbability()); assert (parentA.getProbability() <= parentB.getProbability()); }