diff --git a/src/main/java/azgracompress/compression/CompressorDecompressorBase.java b/src/main/java/azgracompress/compression/CompressorDecompressorBase.java index 0616503e0dd3733b1efade1ec2654586a46ff8b6..c6669f1fb5578612d8f1250846ed45de16415bb7 100644 --- a/src/main/java/azgracompress/compression/CompressorDecompressorBase.java +++ b/src/main/java/azgracompress/compression/CompressorDecompressorBase.java @@ -102,14 +102,12 @@ public abstract class CompressorDecompressorBase { return huffman; } - protected int[] getPlaneIndicesForCompression() { - - final InputData ifi = options.getInputDataInfo(); - if (ifi.isPlaneIndexSet()) { - return new int[]{ifi.getPlaneIndex()}; - } else if (ifi.isPlaneRangeSet()) { - final int from = ifi.getPlaneRange().getFrom(); - final int count = ifi.getPlaneRange().getTo() - from; + protected int[] getPlaneIndicesForCompression(final InputData inputData) { + if (inputData.isPlaneIndexSet()) { + return new int[]{inputData.getPlaneIndex()}; + } else if (inputData.isPlaneRangeSet()) { + final int from = inputData.getPlaneRange().getFrom(); + final int count = inputData.getPlaneRange().getTo() - from; int[] indices = new int[count + 1]; for (int i = 0; i <= count; i++) { @@ -117,7 +115,7 @@ public abstract class CompressorDecompressorBase { } return indices; } else { - return generateAllPlaneIndices(ifi.getDimensions().getZ()); + return generateAllPlaneIndices(inputData.getDimensions().getZ()); } } diff --git a/src/main/java/azgracompress/compression/SQImageCompressor.java b/src/main/java/azgracompress/compression/SQImageCompressor.java index 7604543cbaea1a9911ed98d0c54931969513bc30..31fd8d6648318e367d85e9be987f8cafc4652e0e 100644 --- a/src/main/java/azgracompress/compression/SQImageCompressor.java +++ b/src/main/java/azgracompress/compression/SQImageCompressor.java @@ -146,7 +146,7 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm reportStatusToListeners("Middle plane codebook with huffman coder created in: " + stopwatch.getElapsedTimeString()); } - final int[] planeIndices = getPlaneIndicesForCompression(); + final int[] planeIndices = getPlaneIndicesForCompression(options.getInputDataInfo()); long[] planeDataSizes = new long[planeIndices.length]; int planeCounter = 0; for (final int planeIndex : planeIndices) { @@ -201,7 +201,7 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm } } else if (inputDataInfo.isPlaneRangeSet()) { reportStatusToListeners("Loading plane range data."); - final int[] planes = getPlaneIndicesForCompression(); + final int[] planes = getPlaneIndicesForCompression(options.getInputDataInfo()); try { trainData = planeLoader.loadPlanesU16Data(planes); } catch (IOException e) { diff --git a/src/main/java/azgracompress/compression/VQImageCompressor.java b/src/main/java/azgracompress/compression/VQImageCompressor.java index c6ea153b0b1764f0bbd6a4c9f3d5aaa9cb87c701..e1da06671feef320095861a70fe840c0994ef093 100644 --- a/src/main/java/azgracompress/compression/VQImageCompressor.java +++ b/src/main/java/azgracompress/compression/VQImageCompressor.java @@ -117,7 +117,7 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm return compressVoxels(compressStream, false, options.getInputDataInfo()); } assert (options.getQuantizationVector().getZ() == 1); - return compress1D2DVectors(compressStream, false); + return compress1D2DVectors(compressStream, false, options.getInputDataInfo()); } @Override @@ -126,52 +126,54 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm return compressVoxels(compressStream, true, inputData); } assert (options.getQuantizationVector().getZ() == 1); - return compress1D2DVectors(compressStream, true); + return compress1D2DVectors(compressStream, true, inputData); } @NotNull - private long[] compress1D2DVectors(final DataOutputStream compressStream, final boolean streamMode) throws ImageCompressionException { + private long[] compress1D2DVectors(final DataOutputStream compressStream, + final boolean streamMode, + final InputData inputData) 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()); try { - planeLoader = PlaneLoaderFactory.getPlaneLoaderForInputFile(inputDataInfo); + planeLoader = PlaneLoaderFactory.getPlaneLoaderForInputFile(inputData); } catch (Exception e) { throw new ImageCompressionException("Unable to create plane reader. " + e.getMessage()); } - VectorQuantizer quantizer = null; - Huffman huffman = null; - - if (options.getCodebookType() == CompressionOptions.CodebookType.Global) { - reportStatusToListeners("Loading codebook from cache file."); - quantizer = loadQuantizerFromCache(); - huffman = createHuffmanCoder(huffmanSymbols, quantizer.getFrequencies()); - reportStatusToListeners("Cached quantizer with huffman coder created."); - if (!streamMode) - writeQuantizerToCompressStream(quantizer, compressStream); - } else if (options.getCodebookType() == CompressionOptions.CodebookType.MiddlePlane) { - stopwatch.restart(); - reportStatusToListeners("Training vector quantizer from middle plane."); - final int[][] refPlaneVectors = planeLoader.loadVectorsFromPlaneRange(options, Utils.singlePlaneRange(getMiddlePlaneIndex())); - quantizer = trainVectorQuantizerFromPlaneVectors(refPlaneVectors); - huffman = createHuffmanCoder(huffmanSymbols, quantizer.getFrequencies()); - if (!streamMode) - writeQuantizerToCompressStream(quantizer, compressStream); - stopwatch.stop(); - reportStatusToListeners("Middle plane codebook created in: " + stopwatch.getElapsedTimeString()); + + VectorQuantizer quantizer = cachedQuantizer; + Huffman huffman = cachedHuffman; + assert (!streamMode || ((quantizer != null) && (huffman != null))); + + if (!streamMode) { + if (options.getCodebookType() == CompressionOptions.CodebookType.Global) { + reportStatusToListeners("Loading codebook from cache file."); + quantizer = loadQuantizerFromCache(); + huffman = createHuffmanCoder(huffmanSymbols, quantizer.getFrequencies()); + reportStatusToListeners("Cached quantizer with huffman coder created."); + } else if (options.getCodebookType() == CompressionOptions.CodebookType.MiddlePlane) { + stopwatch.restart(); + reportStatusToListeners("Training vector quantizer from middle plane."); + final int[][] refPlaneVectors = planeLoader.loadVectorsFromPlaneRange(options, + Utils.singlePlaneRange(getMiddlePlaneIndex())); + quantizer = trainVectorQuantizerFromPlaneVectors(refPlaneVectors); + huffman = createHuffmanCoder(huffmanSymbols, quantizer.getFrequencies()); + stopwatch.stop(); + reportStatusToListeners("Middle plane codebook created in: " + stopwatch.getElapsedTimeString()); + } + writeQuantizerToCompressStream(quantizer, compressStream); } - final int[] planeIndices = getPlaneIndicesForCompression(); + final int[] planeIndices = getPlaneIndicesForCompression(inputData); if (streamMode) { try { - final V3i imageDims = options.getInputDataInfo().getDimensions(); // Image dimensions - compressStream.writeShort(imageDims.getX()); - compressStream.writeShort(imageDims.getY()); - compressStream.writeShort(imageDims.getZ()); + compressStream.writeShort(inputData.getDimensions().getX()); + compressStream.writeShort(inputData.getDimensions().getY()); + compressStream.writeShort(inputData.getDimensions().getZ()); // Write voxel layer in stream mode. compressStream.writeShort(planeIndices.length); @@ -189,18 +191,15 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm final int[][] planeVectors = planeLoader.loadVectorsFromPlaneRange(options, Utils.singlePlaneRange(planeIndex)); - if (!hasGeneralQuantizer) { + if (!streamMode && !hasGeneralQuantizer) { reportStatusToListeners(String.format("Training vector quantizer from plane %d.", planeIndex)); quantizer = trainVectorQuantizerFromPlaneVectors(planeVectors); huffman = createHuffmanCoder(huffmanSymbols, quantizer.getFrequencies()); - if (!streamMode) - writeQuantizerToCompressStream(quantizer, compressStream); + writeQuantizerToCompressStream(quantizer, compressStream); } - assert (quantizer != null); - // Use BestBinFirst KDTree for codebook lookup. - // final int[] indices = quantizer.quantizeIntoIndicesUsingKDTree(planeVectors, options.getWorkerCount()); + // final int[] indices = quantizer.quantizeIntoIndicesUsingKDTree(planeVectors, options.getWorkerCount()); // Use BruteForce for codebook lookup. final int[] indices = quantizer.quantizeIntoIndices(planeVectors, options.getWorkerCount()); @@ -276,7 +275,6 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm return (datasetPlaneCount / voxelDepth); } - // TODO(Moravec): Remove dependencies on instance variables to enable multi-thread usage. public long[] compressVoxels(final DataOutputStream compressStream, final boolean streamMode, final InputData inputData) throws ImageCompressionException { @@ -325,10 +323,7 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm final int toZ = (voxelLayerIndex == voxelLayerCount - 1) ? inputData.getDimensions().getZ() : (voxelLayerDepth + (voxelLayerIndex * voxelLayerDepth)); - - if (toZ < fromZ) { - System.err.println("@Wrong range"); - } + assert (toZ >= fromZ); final Range<Integer> voxelLayerRange = new Range<>(fromZ, toZ); diff --git a/src/main/java/azgracompress/compression/VQImageDecompressor.java b/src/main/java/azgracompress/compression/VQImageDecompressor.java index 4cb77dcb3a22cad2eecf33d36d80e03807746c7e..dd5ae32c94582f7adf89778442c9dbb491e8ccc5 100644 --- a/src/main/java/azgracompress/compression/VQImageDecompressor.java +++ b/src/main/java/azgracompress/compression/VQImageDecompressor.java @@ -1,7 +1,6 @@ package azgracompress.compression; import azgracompress.cache.ICacheFile; -import azgracompress.cache.SQCacheFile; import azgracompress.cache.VQCacheFile; import azgracompress.compression.exception.ImageDecompressionException; import azgracompress.data.*; @@ -194,6 +193,49 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I } } + @SuppressWarnings("DuplicatedCode") + public void decompressStreamModelImpl(DataInputStream compressedStream, + QCMPFileHeader header, + DecompressCallback callback) throws ImageDecompressionException { + + assert (cachedCodebook != null && cachedHuffman != null); + assert (header.getVectorSizeZ() == 1); + final int planeCountForDecompression = header.getImageSizeZ(); + final long planeVectorCount = calculatePlaneVectorCount(header); + final V2i qVector = new V2i(header.getVectorSizeX(), header.getVectorSizeY()); + final int vectorSize = qVector.multiplyTogether(); + + + for (int planeIndex = 0; planeIndex < planeCountForDecompression; planeIndex++) { + + final int planeDataSize = (int) header.getPlaneDataSizes()[planeIndex]; + try (InBitStream inBitStream = new InBitStream(compressedStream, + header.getBitsPerCodebookIndex(), + planeDataSize)) { + inBitStream.readToBuffer(); + inBitStream.setAllowReadFromUnderlyingStream(false); + + int[][] decompressedVectors = new int[(int) planeVectorCount][vectorSize]; + int huffmanIndex; + for (int vecIndex = 0; vecIndex < planeVectorCount; vecIndex++) { + huffmanIndex = decodeHuffmanSymbol(cachedHuffman, inBitStream); + System.arraycopy(cachedCodebook.getVectors()[huffmanIndex], 0, decompressedVectors[vecIndex], 0, vectorSize); + } + + + final Block decompressedPlane = reconstructImageFromQuantizedVectors(decompressedVectors, qVector, header.getImageDims()); + + callback.process(decompressedPlane, planeIndex); + } catch (Exception ex) { + throw new ImageDecompressionException("VQImageDecompressor::decompressToBuffer() - Unable to read indices from " + + "InBitStream.", + ex); + } + reportProgressToListeners(planeIndex, planeCountForDecompression, + "Decompressed plane %d.", planeIndex); + } + } + @Override public void decompressToBuffer(DataInputStream compressedStream, @@ -370,21 +412,34 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I @Override public short[] decompressStreamMode(final DataInputStream compressedStream, final QCMPFileHeader header) throws ImageDecompressionException { - // TODO(Moravec): Implement missing quantization type. - assert (header.getQuantizationType() == QuantizationType.Vector3D); + final short[] buffer = new short[(int) header.getImageDims().multiplyTogether()]; - final V3i voxelDim = new V3i(header.getVectorSizeX(), header.getVectorSizeY(), header.getVectorSizeZ()); + if (header.getQuantizationType() == QuantizationType.Vector3D) { + final V3i voxelDim = new V3i(header.getVectorSizeX(), header.getVectorSizeY(), header.getVectorSizeZ()); - decompressVoxelsStreamModeImpl(compressedStream, header, (voxel, voxelData, planeOffset) -> { - final ImageU16Dataset currentVoxelLayer = voxel.reconstructFromVoxelsToDataset(voxelDim, voxelData); - int offset = planeOffset * (voxelDim.getX() * voxelDim.getY()); + decompressVoxelsStreamModeImpl(compressedStream, header, (voxel, voxelData, planeOffset) -> { + final ImageU16Dataset currentVoxelLayer = voxel.reconstructFromVoxelsToDataset(voxelDim, voxelData); + int offset = planeOffset * (voxelDim.getX() * voxelDim.getY()); - for (int layer = 0; layer < voxel.getDims().getZ(); layer++) { - final short[] voxelLayerData = currentVoxelLayer.getPlaneData(layer); - System.arraycopy(voxelLayerData, 0, buffer, offset, voxelLayerData.length); - offset += voxelLayerData.length; - } - }); + for (int layer = 0; layer < voxel.getDims().getZ(); layer++) { + final short[] voxelLayerData = currentVoxelLayer.getPlaneData(layer); + System.arraycopy(voxelLayerData, 0, buffer, offset, voxelLayerData.length); + offset += voxelLayerData.length; + } + }); + return buffer; + } else { + final int planePixelCount = header.getImageDims().toV2i().multiplyTogether(); + decompressStreamModelImpl(compressedStream, header, (imageBlock, planeIndex) -> { + final int offset = planePixelCount * planeIndex; + final int[] data = imageBlock.getData(); + for (int i = 0; i < planePixelCount; i++) { + buffer[offset + i] = (short) data[i]; + } + }); + } return buffer; + + } }