From 7e6ccedfc8dfc1aa103184edfa4acd74433d1b60 Mon Sep 17 00:00:00 2001 From: Vojtech Moravec <vojtech.moravec.st@vsb.cz> Date: Tue, 21 Jan 2020 17:11:19 +0100 Subject: [PATCH] Use special exception types for compression and decompression. --- .../compression/IImageCompressor.java | 6 +- .../compression/IImageDecompressor.java | 4 +- .../ImageCompressionException.java | 24 +++++++ .../compression/ImageCompressor.java | 6 +- .../ImageDecompressionException.java | 24 +++++++ .../compression/ImageDecompressor.java | 9 +-- .../compression/SQImageCompressor.java | 47 +++++++++---- .../compression/SQImageDecompressor.java | 46 +++++++++---- .../compression/VQImageCompressor.java | 48 ++++++++----- .../compression/VQImageDecompressor.java | 67 ++++++++++++------- .../java/azgracompress/io/InBitStream.java | 7 +- .../quantization/vector/LBGResult.java | 3 + .../vector/LBGVectorQuantizer.java | 7 +- 13 files changed, 212 insertions(+), 86 deletions(-) create mode 100644 src/main/java/azgracompress/compression/ImageCompressionException.java create mode 100644 src/main/java/azgracompress/compression/ImageDecompressionException.java diff --git a/src/main/java/azgracompress/compression/IImageCompressor.java b/src/main/java/azgracompress/compression/IImageCompressor.java index 7752a28..d0165c9 100644 --- a/src/main/java/azgracompress/compression/IImageCompressor.java +++ b/src/main/java/azgracompress/compression/IImageCompressor.java @@ -4,12 +4,10 @@ import java.io.DataOutputStream; public interface IImageCompressor { - // TODO(Moravec): Replace default Exception with better Exception type. - /** * Compress the image planes. * @param compressStream Compressed data stream. - * @throws Exception when compression fails. + * @throws ImageCompressionException when compression fails. */ - void compress(DataOutputStream compressStream) throws Exception; + void compress(DataOutputStream compressStream) throws ImageCompressionException; } diff --git a/src/main/java/azgracompress/compression/IImageDecompressor.java b/src/main/java/azgracompress/compression/IImageDecompressor.java index a9516bd..e21bc41 100644 --- a/src/main/java/azgracompress/compression/IImageDecompressor.java +++ b/src/main/java/azgracompress/compression/IImageDecompressor.java @@ -20,10 +20,10 @@ public interface IImageDecompressor { * @param compressedStream Input stream of compressed data. * @param decompressStream Output stream for decompressed data. * @param header QCMPFile information. - * @throws Exception when decompression fails. + * @throws ImageDecompressionException when decompression fails. */ void decompress(DataInputStream compressedStream, DataOutputStream decompressStream, - final QCMPFileHeader header) throws Exception; + final QCMPFileHeader header) throws ImageDecompressionException; } diff --git a/src/main/java/azgracompress/compression/ImageCompressionException.java b/src/main/java/azgracompress/compression/ImageCompressionException.java new file mode 100644 index 0000000..7fe5e34 --- /dev/null +++ b/src/main/java/azgracompress/compression/ImageCompressionException.java @@ -0,0 +1,24 @@ +package azgracompress.compression; + +public class ImageCompressionException extends Exception { + private final Exception innerException; + + public ImageCompressionException(final String message, final Exception innerException) { + super(message); + this.innerException = innerException; + } + + public ImageCompressionException(final String message) { + super(message); + this.innerException = null; + } + + @Override + public String getMessage() { + String msg = super.getMessage(); + if (msg != null && innerException != null) { + msg += "\nInner exception:\n" + innerException.getMessage(); + } + return msg; + } +} diff --git a/src/main/java/azgracompress/compression/ImageCompressor.java b/src/main/java/azgracompress/compression/ImageCompressor.java index 80f35e2..866c41d 100644 --- a/src/main/java/azgracompress/compression/ImageCompressor.java +++ b/src/main/java/azgracompress/compression/ImageCompressor.java @@ -62,10 +62,12 @@ public class ImageCompressor extends CompressorDecompressorBase { reportCompressionRatio(header, compressStream.size()); } - } catch (Exception ex) { + } catch (ImageCompressionException ex) { System.err.println(ex.getMessage()); return false; - + } catch (Exception e) { + e.printStackTrace(); + return false; } return true; } diff --git a/src/main/java/azgracompress/compression/ImageDecompressionException.java b/src/main/java/azgracompress/compression/ImageDecompressionException.java new file mode 100644 index 0000000..5ed88cc --- /dev/null +++ b/src/main/java/azgracompress/compression/ImageDecompressionException.java @@ -0,0 +1,24 @@ +package azgracompress.compression; + +public class ImageDecompressionException extends Exception { + private final Exception innerException; + + public ImageDecompressionException(final String message, final Exception innerException) { + super(message); + this.innerException = innerException; + } + + public ImageDecompressionException(final String message) { + super(message); + this.innerException = null; + } + + @Override + public String getMessage() { + String msg = super.getMessage(); + if (msg != null && innerException != null) { + msg += "\nInner exception:\n" + innerException.getMessage(); + } + return msg; + } +} diff --git a/src/main/java/azgracompress/compression/ImageDecompressor.java b/src/main/java/azgracompress/compression/ImageDecompressor.java index 30993f7..badbbfa 100644 --- a/src/main/java/azgracompress/compression/ImageDecompressor.java +++ b/src/main/java/azgracompress/compression/ImageDecompressor.java @@ -62,8 +62,7 @@ public class ImageDecompressor extends CompressorDecompressorBase { try (FileInputStream fileInputStream = new FileInputStream(options.getInputFile()); DataInputStream dataInputStream = new DataInputStream(fileInputStream)) { header = readQCMPFileHeader(dataInputStream); - } - catch (IOException ioEx){ + } catch (IOException ioEx) { ioEx.printStackTrace(); return ""; } @@ -154,9 +153,11 @@ public class ImageDecompressor extends CompressorDecompressorBase { try (FileOutputStream fos = new FileOutputStream(options.getOutputFile(), false); DataOutputStream decompressStream = new DataOutputStream(fos)) { + imageDecompressor.decompress(dataInputStream, decompressStream, header); - } catch (Exception ex) { - ex.printStackTrace(); + + } catch (ImageDecompressionException ex) { + System.err.println(ex.getMessage()); return false; } diff --git a/src/main/java/azgracompress/compression/SQImageCompressor.java b/src/main/java/azgracompress/compression/SQImageCompressor.java index 2181a7a..314dbfd 100644 --- a/src/main/java/azgracompress/compression/SQImageCompressor.java +++ b/src/main/java/azgracompress/compression/SQImageCompressor.java @@ -35,13 +35,17 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm * * @param quantizer Quantizer used for compression of the image. * @param compressStream Compressed data stream. - * @throws IOException when writing to the stream fails. + * @throws ImageCompressionException when writing to the stream fails. */ private void writeCodebookToOutputStream(final ScalarQuantizer quantizer, - DataOutputStream compressStream) throws IOException { + DataOutputStream compressStream) throws ImageCompressionException { final int[] centroids = quantizer.getCentroids(); - for (final int quantizationValue : centroids) { - compressStream.writeShort(quantizationValue); + try { + for (final int quantizationValue : centroids) { + compressStream.writeShort(quantizationValue); + } + } catch (IOException ioEx) { + throw new ImageCompressionException("Unable to write codebook to compress stream.", ioEx); } if (options.isVerbose()) { Log("Wrote quantization values to compressed stream."); @@ -52,21 +56,29 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm * Compress the image file specified by parsed CLI options using scalar quantization. * * @param compressStream Stream to which compressed data will be written. - * @throws Exception When compress process fails. + * @throws ImageCompressionException When compress process fails. */ - public void compress(DataOutputStream compressStream) throws Exception { + public void compress(DataOutputStream compressStream) throws ImageCompressionException { ScalarQuantizer quantizer = null; Stopwatch stopwatch = new Stopwatch(); if (options.hasReferencePlaneIndex()) { stopwatch.restart(); - final ImageU16 referencePlane = RawDataIO.loadImageU16(options.getInputFile(), - options.getImageDimension(), - options.getReferencePlaneIndex()); + ImageU16 referencePlane = null; + try { + referencePlane = RawDataIO.loadImageU16(options.getInputFile(), + options.getImageDimension(), + options.getReferencePlaneIndex()); + } catch (Exception ex) { + throw new ImageCompressionException("Unable to load reference plane data.", ex); + } + Log(String.format("Training scalar quantizer from reference plane %d.", options.getReferencePlaneIndex())); quantizer = trainScalarQuantizerFromData(referencePlane.getData()); stopwatch.stop(); + writeCodebookToOutputStream(quantizer, compressStream); + Log("Reference codebook created in: " + stopwatch.getElapsedTimeString()); } @@ -74,9 +86,16 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm for (final int planeIndex : planeIndices) { stopwatch.restart(); Log(String.format("Loading plane %d.", planeIndex)); - final ImageU16 plane = RawDataIO.loadImageU16(options.getInputFile(), - options.getImageDimension(), - planeIndex); + + ImageU16 plane = null; + + try { + plane = RawDataIO.loadImageU16(options.getInputFile(), + options.getImageDimension(), + planeIndex); + } catch (Exception ex) { + throw new ImageCompressionException("Unable to load plane data.", ex); + } if (!options.hasReferencePlaneIndex()) { Log(String.format("Training scalar quantizer from plane %d.", planeIndex)); @@ -91,8 +110,8 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm try (OutBitStream outBitStream = new OutBitStream(compressStream, options.getBitsPerPixel(), 2048)) { outBitStream.write(indices); - } catch (IOException ioEx) { - ioEx.printStackTrace(); + } catch (Exception ex) { + throw new ImageCompressionException("Unable to write indices to OutBitStream.", ex); } stopwatch.stop(); Log("Plane time: " + stopwatch.getElapsedTimeString()); diff --git a/src/main/java/azgracompress/compression/SQImageDecompressor.java b/src/main/java/azgracompress/compression/SQImageDecompressor.java index 0c6cbea..0eb6dbc 100644 --- a/src/main/java/azgracompress/compression/SQImageDecompressor.java +++ b/src/main/java/azgracompress/compression/SQImageDecompressor.java @@ -15,10 +15,15 @@ public class SQImageDecompressor extends CompressorDecompressorBase implements I super(options); } - private int[] readScalarQuantizationValues(DataInputStream compressedStream, final int n) throws IOException { + private int[] readScalarQuantizationValues(DataInputStream compressedStream, + final int n) throws ImageDecompressionException { int[] quantizationValues = new int[n]; - for (int i = 0; i < n; i++) { - quantizationValues[i] = compressedStream.readUnsignedShort(); + try { + for (int i = 0; i < n; i++) { + quantizationValues[i] = compressedStream.readUnsignedShort(); + } + } catch (IOException ioEx) { + throw new ImageDecompressionException("Unable to read quantization values from compressed stream.", ioEx); } return quantizationValues; } @@ -44,7 +49,7 @@ public class SQImageDecompressor extends CompressorDecompressorBase implements I @Override public void decompress(DataInputStream compressedStream, DataOutputStream decompressStream, - QCMPFileHeader header) throws Exception { + QCMPFileHeader header) throws ImageDecompressionException { final int codebookSize = (int) Math.pow(2, header.getBitsPerPixel()); final int planeCountForDecompression = header.getImageSizeZ(); @@ -68,18 +73,31 @@ public class SQImageDecompressor extends CompressorDecompressorBase implements I assert (quantizationValues != null); Log(String.format("Decompressing plane %d...", planeIndex)); - InBitStream inBitStream = new InBitStream(compressedStream, header.getBitsPerPixel(), planeIndicesDataSize); - inBitStream.readToBuffer(); - inBitStream.setAllowReadFromUnderlyingStream(false); - final int[] indices = inBitStream.readNValues(planePixelCount); - - int[] decompressedValues = new int[planePixelCount]; - for (int i = 0; i < planePixelCount; i++) { - decompressedValues[i] = quantizationValues[indices[i]]; + byte[] decompressedPlaneData = null; + try (InBitStream inBitStream = new InBitStream(compressedStream, + header.getBitsPerPixel(), + planeIndicesDataSize)) { + inBitStream.readToBuffer(); + inBitStream.setAllowReadFromUnderlyingStream(false); + final int[] indices = inBitStream.readNValues(planePixelCount); + + int[] decompressedValues = new int[planePixelCount]; + for (int i = 0; i < planePixelCount; i++) { + decompressedValues[i] = quantizationValues[indices[i]]; + } + decompressedPlaneData = + TypeConverter.unsignedShortArrayToByteArray(decompressedValues, false); + + + } catch (Exception ex) { + throw new ImageDecompressionException("Unable to read indices from InBitStream.", ex); + } + try { + decompressStream.write(decompressedPlaneData); + } catch (IOException e) { + throw new ImageDecompressionException("Unable to write decompressed data to decompress stream.", e); } - final byte[] decompressedPlaneData = TypeConverter.unsignedShortArrayToByteArray(decompressedValues, false); - decompressStream.write(decompressedPlaneData); stopwatch.stop(); Log(String.format("Decompressed plane %d in %s.", planeIndex, stopwatch.getElapsedTimeString())); } diff --git a/src/main/java/azgracompress/compression/VQImageCompressor.java b/src/main/java/azgracompress/compression/VQImageCompressor.java index 312cb18..cffc856 100644 --- a/src/main/java/azgracompress/compression/VQImageCompressor.java +++ b/src/main/java/azgracompress/compression/VQImageCompressor.java @@ -56,16 +56,20 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm * * @param quantizer Quantizer with the codebook. * @param compressStream Stream with compressed data. - * @throws IOException When unable to write quantizer. + * @throws ImageCompressionException When unable to write quantizer. */ private void writeQuantizerToCompressStream(final VectorQuantizer quantizer, - DataOutputStream compressStream) throws IOException { + DataOutputStream compressStream) throws ImageCompressionException { final CodebookEntry[] codebook = quantizer.getCodebook(); - for (final CodebookEntry entry : codebook) { - final int[] entryVector = entry.getVector(); - for (final int vecVal : entryVector) { - compressStream.writeShort(vecVal); + try { + for (final CodebookEntry entry : codebook) { + final int[] entryVector = entry.getVector(); + for (final int vecVal : entryVector) { + compressStream.writeShort(vecVal); + } } + } catch (IOException ioEx) { + throw new ImageCompressionException("Unable to write codebook to compress stream.", ioEx); } if (options.isVerbose()) { Log("Wrote quantization vectors to compressed stream."); @@ -76,16 +80,22 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm * Compress the image file specified by parsed CLI options using vector quantization. * * @param compressStream Stream to which compressed data will be written. - * @throws Exception When compress process fails. + * @throws ImageCompressionException When compress process fails. */ - public void compress(DataOutputStream compressStream) throws Exception { + public void compress(DataOutputStream compressStream) throws ImageCompressionException { VectorQuantizer quantizer = null; Stopwatch stopwatch = new Stopwatch(); if (options.hasReferencePlaneIndex()) { stopwatch.restart(); - final ImageU16 referencePlane = RawDataIO.loadImageU16(options.getInputFile(), - options.getImageDimension(), - options.getReferencePlaneIndex()); + + ImageU16 referencePlane = null; + try { + referencePlane = RawDataIO.loadImageU16(options.getInputFile(), + options.getImageDimension(), + options.getReferencePlaneIndex()); + } catch (Exception ex) { + throw new ImageCompressionException("Unable to load reference plane data.", ex); + } Log(String.format("Training vector quantizer from reference plane %d.", options.getReferencePlaneIndex())); final int[][] refPlaneVectors = getPlaneVectors(referencePlane); @@ -101,9 +111,15 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm for (final int planeIndex : planeIndices) { stopwatch.restart(); Log(String.format("Loading plane %d.", planeIndex)); - final ImageU16 plane = RawDataIO.loadImageU16(options.getInputFile(), - options.getImageDimension(), - planeIndex); + + ImageU16 plane = null; + try { + plane = RawDataIO.loadImageU16(options.getInputFile(), + options.getImageDimension(), + planeIndex); + } catch (Exception ex) { + throw new ImageCompressionException("Unable to load plane data.", ex); + } final int[][] planeVectors = getPlaneVectors(plane); @@ -121,8 +137,8 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm try (OutBitStream outBitStream = new OutBitStream(compressStream, options.getBitsPerPixel(), 2048)) { outBitStream.write(indices); - } catch (IOException ioEx) { - ioEx.printStackTrace(); + } catch (Exception ex) { + throw new ImageCompressionException("Unable to write indices to OutBitStream.", ex); } stopwatch.stop(); Log("Plane time: " + stopwatch.getElapsedTimeString()); diff --git a/src/main/java/azgracompress/compression/VQImageDecompressor.java b/src/main/java/azgracompress/compression/VQImageDecompressor.java index 2e27c31..1f77f77 100644 --- a/src/main/java/azgracompress/compression/VQImageDecompressor.java +++ b/src/main/java/azgracompress/compression/VQImageDecompressor.java @@ -30,13 +30,17 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I private int[][] readCodebookVectors(DataInputStream compressedStream, final int codebookSize, - final int vectorSize) throws IOException { + final int vectorSize) throws ImageDecompressionException { int[][] codebook = new int[codebookSize][vectorSize]; - for (int codebookIndex = 0; codebookIndex < codebookSize; codebookIndex++) { - for (int vecIndex = 0; vecIndex < vectorSize; vecIndex++) { - codebook[codebookIndex][vecIndex] = compressedStream.readUnsignedShort(); + try { + for (int codebookIndex = 0; codebookIndex < codebookSize; codebookIndex++) { + for (int vecIndex = 0; vecIndex < vectorSize; vecIndex++) { + codebook[codebookIndex][vecIndex] = compressedStream.readUnsignedShort(); + } } + } catch (IOException ioEx) { + throw new ImageDecompressionException("Unable to read quantization values from compressed stream.", ioEx); } return codebook; } @@ -87,7 +91,7 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I @Override public void decompress(DataInputStream compressedStream, DataOutputStream decompressStream, - QCMPFileHeader header) throws Exception { + QCMPFileHeader header) throws ImageDecompressionException { final int codebookSize = (int) Math.pow(2, header.getBitsPerPixel()); assert (header.getVectorSizeZ() == 1); final int vectorSize = header.getVectorSizeX() * header.getVectorSizeY() * header.getVectorSizeZ(); @@ -114,28 +118,43 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I assert (quantizationVectors != null); Log(String.format("Decompressing plane %d...", planeIndex)); - InBitStream inBitStream = new InBitStream(compressedStream, header.getBitsPerPixel(), (int) planeDataSize); - inBitStream.readToBuffer(); - inBitStream.setAllowReadFromUnderlyingStream(false); - final int[] indices = inBitStream.readNValues((int) planeVectorCount); - - int[][] decompressedVectors = new int[(int) planeVectorCount][vectorSize]; - for (int vecIndex = 0; vecIndex < planeVectorCount; vecIndex++) { - System.arraycopy(quantizationVectors[indices[vecIndex]], - 0, - decompressedVectors[vecIndex], - 0, - vectorSize); + + byte[] decompressedPlaneData = null; + + try (InBitStream inBitStream = new InBitStream(compressedStream, + header.getBitsPerPixel(), + (int) planeDataSize)) { + inBitStream.readToBuffer(); + inBitStream.setAllowReadFromUnderlyingStream(false); + final int[] indices = inBitStream.readNValues((int) planeVectorCount); + + int[][] decompressedVectors = new int[(int) planeVectorCount][vectorSize]; + for (int vecIndex = 0; vecIndex < planeVectorCount; vecIndex++) { + + System.arraycopy(quantizationVectors[indices[vecIndex]], + 0, + decompressedVectors[vecIndex], + 0, + vectorSize); + } + + + final ImageU16 decompressedPlane = reconstructImageFromQuantizedVectors(decompressedVectors, + qVector, + header.getImageDims()); + decompressedPlaneData = + TypeConverter.unsignedShortArrayToByteArray(decompressedPlane.getData(), false); + } catch (Exception ex) { + throw new ImageDecompressionException("Unable to read indices from InBitStream.", ex); } - final ImageU16 decompressedPlane = reconstructImageFromQuantizedVectors(decompressedVectors, - qVector, - header.getImageDims()); - final byte[] decompressedPlaneData = TypeConverter.unsignedShortArrayToByteArray( - decompressedPlane.getData(), - false); - decompressStream.write(decompressedPlaneData); + try { + decompressStream.write(decompressedPlaneData); + } catch (IOException e) { + throw new ImageDecompressionException("Unable to write decompressed data to decompress stream.", e); + } + stopwatch.stop(); Log(String.format("Decompressed plane %d in %s.", planeIndex, stopwatch.getElapsedTimeString())); } diff --git a/src/main/java/azgracompress/io/InBitStream.java b/src/main/java/azgracompress/io/InBitStream.java index a104d85..c64c120 100644 --- a/src/main/java/azgracompress/io/InBitStream.java +++ b/src/main/java/azgracompress/io/InBitStream.java @@ -3,7 +3,7 @@ package azgracompress.io; import java.io.IOException; import java.io.InputStream; -public class InBitStream { +public class InBitStream implements AutoCloseable { private InputStream inputStream; private byte[] buffer; @@ -90,4 +90,9 @@ public class InBitStream { } + @Override + public void close() throws Exception { + bitBufferSize = 0; + bytesAvailable = 0; + } } diff --git a/src/main/java/azgracompress/quantization/vector/LBGResult.java b/src/main/java/azgracompress/quantization/vector/LBGResult.java index 57c290b..6b75114 100644 --- a/src/main/java/azgracompress/quantization/vector/LBGResult.java +++ b/src/main/java/azgracompress/quantization/vector/LBGResult.java @@ -1,6 +1,9 @@ package azgracompress.quantization.vector; +import azgracompress.quantization.QTrainIteration; + public class LBGResult { + private final CodebookEntry[] codebook; private final double averageMse; private final double psnr; diff --git a/src/main/java/azgracompress/quantization/vector/LBGVectorQuantizer.java b/src/main/java/azgracompress/quantization/vector/LBGVectorQuantizer.java index d4391c2..13d0099 100644 --- a/src/main/java/azgracompress/quantization/vector/LBGVectorQuantizer.java +++ b/src/main/java/azgracompress/quantization/vector/LBGVectorQuantizer.java @@ -28,7 +28,6 @@ public class LBGVectorQuantizer { return findOptimalCodebook(true); } - // TODO(Moravec): Maybe return QTrainIteration somehow? public LBGResult findOptimalCodebook(boolean verbose) { ArrayList<LearningCodebookEntry> codebook = initializeCodebook(verbose); if (verbose) { @@ -118,8 +117,6 @@ public class LBGVectorQuantizer { // Create perturbation vector. - // TODO(Moravec): Make sure that when we are splitting entry we don't end up creating two same entries. - // The problem happens when we try to split Vector full of zeroes. // Split each entry in codebook with fixed perturbation vector. for (final LearningCodebookEntry entryToSplit : codebook) { double[] prtV; @@ -142,8 +139,8 @@ public class LBGVectorQuantizer { newCodebook.add(entryToSplit); ArrayList<Integer> rndEntryValues = new ArrayList<>(prtV.length); - for (int j = 0; j < prtV.length; j++) { - final int value = (int) Math.floor(prtV[j]); + for (final double v : prtV) { + final int value = (int) Math.floor(v); assert (value >= 0) : "value is too low!"; assert (value <= U16.Max) : "value is too big!"; rndEntryValues.add(value); -- GitLab