Skip to content
Snippets Groups Projects
Commit 8768db39 authored by Vojtech Moravec's avatar Vojtech Moravec
Browse files

Refactor Huffman code.

Huffman class was renamed to HuffmanTreeBuilder to better clarify its
purpose. The actual tree can be used with just a root Huffman node.

I have also moved the new binary io for huffman to HuffmanNode.

In the upcoming commit I will probably create HuffmanCoder class, which
will take care of encoding and decoding of symbols.
parent 1adedb13
No related branches found
No related tags found
No related merge requests found
......@@ -4,8 +4,8 @@ import cz.it4i.qcmp.cache.QuantizationCacheManager;
import cz.it4i.qcmp.cache.VQCacheFile;
import cz.it4i.qcmp.cli.CompressionOptionsCLIParser;
import cz.it4i.qcmp.cli.CustomFunctionBase;
import cz.it4i.qcmp.huffman.Huffman;
import cz.it4i.qcmp.huffman.HuffmanNode;
import cz.it4i.qcmp.huffman.HuffmanTreeBuilder;
import cz.it4i.qcmp.io.InBitStream;
import cz.it4i.qcmp.io.OutBitStream;
import cz.it4i.qcmp.quantization.vector.VQCodebook;
......@@ -36,14 +36,14 @@ public class DebugFunction extends CustomFunctionBase {
symbols[i] = i;
}
final Huffman huffman = new Huffman(symbols, codebook.getVectorFrequencies());
final HuffmanTreeBuilder huffman = new HuffmanTreeBuilder(symbols, codebook.getVectorFrequencies());
huffman.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.saveHuffmanTree(bitStream);
huffman.getRoot().writeToBinaryStream(bitStream);
} catch (final IOException e) {
e.printStackTrace();
}
......@@ -51,7 +51,7 @@ public class DebugFunction extends CustomFunctionBase {
HuffmanNode readRoot = null;
try (final InBitStream inBitStream = new InBitStream(new FileInputStream("D:\\tmp\\huffman_tree.data"), bitsPerSymbol, 256)) {
readRoot = Huffman.readHuffmanTree(inBitStream);
readRoot = HuffmanNode.readFromStream(inBitStream);
} catch (final IOException ex) {
ex.printStackTrace();
}
......
......@@ -3,7 +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.Huffman;
import cz.it4i.qcmp.huffman.HuffmanTreeBuilder;
import cz.it4i.qcmp.io.InputData;
import cz.it4i.qcmp.io.OutBitStream;
......@@ -100,8 +100,8 @@ public abstract class CompressorDecompressorBase {
return symbols;
}
protected Huffman createHuffmanCoder(final int[] symbols, final long[] frequencies) {
final Huffman huffman = new Huffman(symbols, frequencies);
protected HuffmanTreeBuilder createHuffmanCoder(final int[] symbols, final long[] frequencies) {
final HuffmanTreeBuilder huffman = new HuffmanTreeBuilder(symbols, frequencies);
huffman.buildHuffmanTree();
return huffman;
}
......@@ -157,7 +157,7 @@ public abstract class CompressorDecompressorBase {
* @throws ImageCompressionException when fails to write to compress stream.
*/
protected long writeHuffmanEncodedIndices(final DataOutputStream compressStream,
final Huffman huffman,
final HuffmanTreeBuilder huffman,
final int[] indices) throws ImageCompressionException {
try (final OutBitStream outBitStream = new OutBitStream(compressStream, options.getBitsPerCodebookIndex(), 2048)) {
for (final int index : indices) {
......
......@@ -5,7 +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.Huffman;
import cz.it4i.qcmp.huffman.HuffmanTreeBuilder;
import cz.it4i.qcmp.io.InputData;
import cz.it4i.qcmp.io.loader.IPlaneLoader;
import cz.it4i.qcmp.io.loader.PlaneLoaderFactory;
......@@ -20,7 +20,7 @@ import java.io.IOException;
public class SQImageCompressor extends CompressorDecompressorBase implements IImageCompressor {
private ScalarQuantizer cachedQuantizer;
private Huffman cachedHuffman;
private HuffmanTreeBuilder cachedHuffman;
public SQImageCompressor(final CompressionOptions options) {
super(options);
......@@ -116,7 +116,7 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm
}
ScalarQuantizer quantizer = null;
Huffman huffman = null;
HuffmanTreeBuilder huffman = null;
final int[] huffmanSymbols = createHuffmanSymbols(getCodebookSize());
if (options.getCodebookType() == CompressionOptions.CodebookType.Global) {
reportStatusToListeners("Loading codebook from cache file.");
......@@ -174,7 +174,7 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm
quantizer = trainScalarQuantizerFromData(planeData);
writeCodebookToOutputStream(quantizer, compressStream);
huffman = new Huffman(huffmanSymbols, quantizer.getCodebook().getSymbolFrequencies());
huffman = new HuffmanTreeBuilder(huffmanSymbols, quantizer.getCodebook().getSymbolFrequencies());
huffman.buildHuffmanTree();
}
......
......@@ -4,8 +4,8 @@ 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.Huffman;
import cz.it4i.qcmp.huffman.HuffmanNode;
import cz.it4i.qcmp.huffman.HuffmanTreeBuilder;
import cz.it4i.qcmp.io.InBitStream;
import cz.it4i.qcmp.quantization.scalar.SQCodebook;
import cz.it4i.qcmp.utilities.Stopwatch;
......@@ -17,7 +17,7 @@ import java.io.IOException;
public class SQImageDecompressor extends CompressorDecompressorBase implements IImageDecompressor {
private SQCodebook cachedCodebook = null;
private Huffman cachedHuffman = null;
private HuffmanTreeBuilder cachedHuffman = null;
public SQImageDecompressor(final CompressionOptions options) {
super(options);
......@@ -52,7 +52,7 @@ public class SQImageDecompressor extends CompressorDecompressorBase implements I
final int planePixelCount = header.getImageSizeX() * header.getImageSizeY();
SQCodebook codebook = null;
Huffman huffman = null;
HuffmanTreeBuilder huffman = null;
if (!header.isCodebookPerPlane()) {
// There is only one codebook.
reportStatusToListeners("Loading single codebook and huffman coder.");
......@@ -130,7 +130,7 @@ public class SQImageDecompressor extends CompressorDecompressorBase implements I
final int planePixelCount = header.getImageSizeX() * header.getImageSizeY();
SQCodebook codebook = null;
Huffman huffman = null;
HuffmanTreeBuilder huffman = null;
if (!header.isCodebookPerPlane()) {
// There is only one codebook.
codebook = readScalarQuantizationValues(compressedStream, codebookSize);
......
......@@ -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.Huffman;
import cz.it4i.qcmp.huffman.HuffmanTreeBuilder;
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 Huffman cachedHuffman = null;
private HuffmanTreeBuilder cachedHuffman = null;
private boolean useKdTree = false;
......@@ -158,7 +158,7 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm
}
VectorQuantizer quantizer = cachedQuantizer;
Huffman huffman = cachedHuffman;
HuffmanTreeBuilder huffman = cachedHuffman;
assert (!streamMode || ((quantizer != null) && (huffman != null)));
if (!streamMode) {
......@@ -283,7 +283,8 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm
final long[] voxelLayersSizes = new long[voxelLayerCount];
final VectorQuantizer quantizer = (cachedQuantizer != null) ? cachedQuantizer : loadQuantizerFromCache();
final Huffman huffman = (cachedHuffman != null) ? cachedHuffman : createHuffmanCoder(huffmanSymbols, quantizer.getFrequencies());
final HuffmanTreeBuilder huffman = (cachedHuffman != null) ? cachedHuffman : createHuffmanCoder(huffmanSymbols,
quantizer.getFrequencies());
if (!streamMode)
writeQuantizerToCompressStream(quantizer, compressStream);
......
......@@ -6,8 +6,8 @@ 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.Huffman;
import cz.it4i.qcmp.huffman.HuffmanNode;
import cz.it4i.qcmp.huffman.HuffmanTreeBuilder;
import cz.it4i.qcmp.io.InBitStream;
import cz.it4i.qcmp.quantization.vector.VQCodebook;
import cz.it4i.qcmp.utilities.Stopwatch;
......@@ -20,7 +20,7 @@ import java.io.IOException;
public class VQImageDecompressor extends CompressorDecompressorBase implements IImageDecompressor {
private VQCodebook cachedCodebook = null;
private Huffman cachedHuffman = null;
private HuffmanTreeBuilder cachedHuffman = null;
private interface DecompressCallback {
void process(final Block imageBlock, final int planeIndex) throws ImageDecompressionException;
......@@ -122,7 +122,7 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I
VQCodebook codebook = null;
Huffman huffman = null;
HuffmanTreeBuilder huffman = null;
if (!header.isCodebookPerPlane()) {
// There is only one codebook.
codebook = readCodebook(compressedStream, codebookSize, vectorSize);
......@@ -245,7 +245,7 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I
final VQCodebook codebook = readCodebook(compressedStream, codebookSize, vectorSize);
final Huffman huffman = createHuffmanCoder(huffmanSymbols, codebook.getVectorFrequencies());
final HuffmanTreeBuilder huffman = createHuffmanCoder(huffmanSymbols, codebook.getVectorFrequencies());
final int voxelLayerCount = VQImageCompressor.calculateVoxelLayerCount(header.getImageSizeZ(), header.getVectorSizeZ());
final Stopwatch stopwatch = new Stopwatch();
......@@ -379,7 +379,7 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I
});
}
private int decodeHuffmanSymbol(final Huffman huffman, final InBitStream inBitStream) throws IOException {
private int decodeHuffmanSymbol(final HuffmanTreeBuilder huffman, final InBitStream inBitStream) throws IOException {
HuffmanNode currentHuffmanNode = huffman.getRoot();
while (!currentHuffmanNode.isLeaf()) {
currentHuffmanNode = currentHuffmanNode.traverse(inBitStream.readBit());
......
package cz.it4i.qcmp.huffman;
import cz.it4i.qcmp.io.InBitStream;
import cz.it4i.qcmp.io.OutBitStream;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
public class HuffmanNode implements Comparable<HuffmanNode> {
private int symbol = -1;
private long symbolFrequency = -1;
private int bit = -1;
private boolean leaf = false;
private double probability = 0.0;
final HuffmanNode subNodeA;
final HuffmanNode subNodeB;
final HuffmanNode rightChild;
final HuffmanNode leftChild;
public HuffmanNode(final int symbol, final double probability, final long frequency) {
public HuffmanNode(final int symbol, final double probability) {
this.symbol = symbol;
this.probability = probability;
this.symbolFrequency = frequency;
subNodeA = null;
subNodeB = null;
rightChild = null;
leftChild = null;
this.leaf = true;
}
private HuffmanNode(final HuffmanNode parentA, final HuffmanNode parentB) {
subNodeA = parentA;
subNodeB = parentB;
private HuffmanNode(final HuffmanNode rightChild, final HuffmanNode leftChild) {
this.rightChild = rightChild;
this.leftChild = leftChild;
}
public static HuffmanNode constructWithSymbol(final HuffmanNode parentA, final HuffmanNode parentB, final int symbol) {
final HuffmanNode node = new HuffmanNode(parentA, parentB);
public static HuffmanNode constructWithSymbol(final HuffmanNode rightChild, final HuffmanNode leftChild, final int symbol) {
final HuffmanNode node = new HuffmanNode(rightChild, leftChild);
node.symbol = symbol;
node.leaf = (parentA == null && parentB == null);
node.leaf = (rightChild == null && leftChild == null);
return node;
}
......@@ -41,10 +43,10 @@ public class HuffmanNode implements Comparable<HuffmanNode> {
}
public HuffmanNode traverse(final boolean queryBit) {
if (subNodeA != null && subNodeA.bit == (queryBit ? 1 : 0))
return subNodeA;
if (subNodeB != null && subNodeB.bit == (queryBit ? 1 : 0))
return subNodeB;
if (rightChild != null && rightChild.bit == (queryBit ? 1 : 0))
return rightChild;
if (leftChild != null && leftChild.bit == (queryBit ? 1 : 0))
return leftChild;
assert (false) : "Corrupted huffman tree";
return null;
......@@ -63,10 +65,6 @@ public class HuffmanNode implements Comparable<HuffmanNode> {
return symbol;
}
public long getSymbolFrequency() {
return symbolFrequency;
}
public int getBit() {
return bit;
}
......@@ -79,39 +77,104 @@ public class HuffmanNode implements Comparable<HuffmanNode> {
return probability;
}
public HuffmanNode getSubNodeA() {
return subNodeA;
public HuffmanNode getRightChild() {
return rightChild;
}
public HuffmanNode getSubNodeB() {
return subNodeB;
public HuffmanNode getLeftChild() {
return leftChild;
}
private static boolean treeNodeEquality(final HuffmanNode A, final HuffmanNode B) {
if (A.leaf) {
if (!B.leaf) {
/**
* Check if two huffman nodes are value equal and if their subtrees are also equal.
*
* @param nodeA First huffman node.
* @param nodeB Second huffman code.
* @return True if nodes and their subtrees are equal.
*/
private static boolean treeNodeEquality(final HuffmanNode nodeA, final HuffmanNode nodeB) {
if (nodeA.leaf) {
if (!nodeB.leaf) {
return false;
}
return A.symbol == B.symbol;
return nodeA.symbol == nodeB.symbol;
} else {
if (B.leaf) {
if (nodeB.leaf) {
return false;
}
if (A.bit != B.bit)
if (nodeA.bit != nodeB.bit)
return false;
if ((A.subNodeA != null && B.subNodeA == null) || (A.subNodeA == null && B.subNodeA != null))
if ((nodeA.rightChild != null && nodeB.rightChild == null) || (nodeA.rightChild == null && nodeB.rightChild != null))
return false;
if ((A.subNodeB != null && B.subNodeB == null) || (A.subNodeB == null && B.subNodeB != null))
if ((nodeA.leftChild != null && nodeB.leftChild == null) || (nodeA.leftChild == null && nodeB.leftChild != null))
return false;
final boolean subTreeAResult = treeNodeEquality(A.subNodeA, B.subNodeA);
final boolean subTreeBResult = treeNodeEquality(A.subNodeB, B.subNodeB);
assert (nodeA.rightChild != null) : "Current node is not leaf and right child must be set.";
assert (nodeA.leftChild != null) : "Current node is not leaf and left child must be set.";
final boolean subTreeAResult = treeNodeEquality(nodeA.rightChild, nodeB.rightChild);
final boolean subTreeBResult = treeNodeEquality(nodeA.leftChild, nodeB.leftChild);
return (subTreeAResult && subTreeBResult);
}
}
public boolean treeEqual(final HuffmanNode opposite) {
return treeNodeEquality(this, opposite);
/**
* Check if tree starting from this node is equal to one starting from otherRoot.
*
* @param otherRoot Other tree root node.
* @return True if both trees are value equal.
*/
public boolean treeEqual(final HuffmanNode otherRoot) {
return treeNodeEquality(this, otherRoot);
}
/**
* Save current node and its children to binary stream.
*
* @param node Node to write to stream.
* @param bitStream Binary output stream.
* @throws IOException when fails to write to stream.
*/
private void writeToBinaryStreamImpl(final HuffmanNode node, final OutBitStream bitStream) throws IOException {
if (node.isLeaf()) {
bitStream.writeBit(1);
bitStream.write(node.getSymbol());
} else {
bitStream.writeBit(0);
writeToBinaryStreamImpl(node.getRightChild(), bitStream);
writeToBinaryStreamImpl(node.getLeftChild(), bitStream);
}
}
/**
* Save huffman tree from this node to the binary stream.
*
* @param bitStream Binary output stream.
* @throws IOException when fails to write to stream.
*/
public void writeToBinaryStream(final OutBitStream bitStream) throws IOException {
writeToBinaryStreamImpl(this, bitStream);
}
/**
* Read huffman tree from the binary stream.
*
* @param bitStream Binary input stream.
* @return Root of the huffman tree.
* @throws IOException when fails to read from stream.
*/
public static HuffmanNode readFromStream(final InBitStream bitStream) throws IOException {
if (bitStream.readBit()) // Leaf
{
return HuffmanNode.constructWithSymbol(null, null, bitStream.readValue());
} else {
final HuffmanNode rightChild = readFromStream(bitStream);
rightChild.setBit(1);
final HuffmanNode leftChild = readFromStream(bitStream);
leftChild.setBit(0);
return HuffmanNode.constructWithSymbol(rightChild, leftChild, -1);
}
}
}
\ No newline at end of file
package cz.it4i.qcmp.huffman;
import cz.it4i.qcmp.io.InBitStream;
import cz.it4i.qcmp.io.OutBitStream;
import java.io.IOException;
import java.util.*;
public class Huffman {
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;
public Huffman(final int[] symbols, final long[] symbolFrequencies) {
public HuffmanTreeBuilder(final int[] symbols, final long[] symbolFrequencies) {
assert (symbols.length == symbolFrequencies.length) : "Array lengths mismatch";
this.symbols = symbols;
this.symbolFrequencies = symbolFrequencies;
......@@ -57,14 +53,14 @@ public class Huffman {
currentCode.add(bit == 1);
}
if (currentNode.subNodeA != null) {
if (currentNode.rightChild != null) {
final ArrayList<Boolean> codeCopy = new ArrayList<Boolean>(currentCode);
traverseSymbolCodes(currentNode.subNodeA, codeCopy);
traverseSymbolCodes(currentNode.rightChild, codeCopy);
inLeaf = false;
}
if (currentNode.subNodeB != null) {
if (currentNode.leftChild != null) {
final ArrayList<Boolean> codeCopy = new ArrayList<Boolean>(currentCode);
traverseSymbolCodes(currentNode.subNodeB, codeCopy);
traverseSymbolCodes(currentNode.leftChild, codeCopy);
inLeaf = false;
}
......@@ -93,7 +89,7 @@ public class Huffman {
for (int sIndex = 0; sIndex < symbols.length; sIndex++) {
final double symbolProbability = (double) symbolFrequencies[sIndex] / totalFrequency;
symbolProbabilityMap.put(symbols[sIndex], symbolProbability);
queue.add(new HuffmanNode(symbols[sIndex], symbolProbability, symbolFrequencies[sIndex]));
queue.add(new HuffmanNode(symbols[sIndex], symbolProbability));
}
return queue;
......@@ -115,12 +111,7 @@ public class Huffman {
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(new Comparator<Map.Entry<Integer, Double>>() {
@Override
public int compare(final Map.Entry<Integer, Double> t0, final Map.Entry<Integer, Double> t1) {
return -(t0.getValue().compareTo(t1.getValue()));
}
});
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) {
......@@ -128,38 +119,4 @@ public class Huffman {
}
return sortedHashMap;
}
private void encodeHuffmanNode(final HuffmanNode node, final OutBitStream bitStream) throws IOException {
if (node.isLeaf()) {
bitStream.writeBit(1);
bitStream.write(node.getSymbol());
} else {
bitStream.writeBit(0);
encodeHuffmanNode(node.getSubNodeA(), bitStream);
encodeHuffmanNode(node.getSubNodeB(), bitStream);
}
}
private static HuffmanNode decodeHuffmanNode(final InBitStream bitStream) throws IOException {
if (bitStream.readBit()) // Leaf
{
return HuffmanNode.constructWithSymbol(null, null, bitStream.readValue());
} else {
final HuffmanNode nodeA = decodeHuffmanNode(bitStream);
nodeA.setBit(1);
final HuffmanNode nodeB = decodeHuffmanNode(bitStream);
nodeB.setBit(0);
return HuffmanNode.constructWithSymbol(nodeA, nodeB, -1);
}
}
public void saveHuffmanTree(final OutBitStream bitStream) throws IOException {
assert root != null : "The tree is not build.";
encodeHuffmanNode(root, bitStream);
}
public static HuffmanNode readHuffmanTree(final InBitStream bitStream) throws IOException {
return decodeHuffmanNode(bitStream);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment