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

Implemect QCMPFileHeader as IFileHeader.

Also moved the getExpectedDataSize() from IImageDecompressor to
IFileHeader.
parent dcc9e20a
Branches
No related tags found
No related merge requests found
Showing
with 199 additions and 183 deletions
......@@ -421,7 +421,7 @@ public class QuantizationCacheManager {
return;
}
final StringBuilder reportBuilder = new StringBuilder();
final long expectedFileSize = header.getExpectedFileSize();
final long expectedFileSize = header.getExpectedDataSize();
if (expectedFileSize == fileSize) {
reportBuilder.append("\u001B[32mCache file is VALID ").append(fileSize).append(" bytes\u001B[0m\n");
} else {
......@@ -429,7 +429,7 @@ public class QuantizationCacheManager {
.append(fileSize).append(" bytes instead of expected ")
.append(expectedFileSize).append(" bytes.\n");
}
header.report(reportBuilder);
header.report(reportBuilder, path);
if (verbose) {
......
......@@ -8,14 +8,6 @@ import java.io.DataInputStream;
import java.io.DataOutputStream;
public interface IImageDecompressor extends IListenable {
/**
* Get correct size of data block.
*
* @param header QCMPFile header with information about compressed file.
* @return Expected size of data.
*/
long getExpectedDataSize(final QCMPFileHeader header);
/**
* Decompress the image planes to decompress stream.
*
......
......@@ -137,7 +137,7 @@ public class ImageCompressor extends CompressorDecompressorBase {
final DataOutputStream compressStream = new DataOutputStream(new BufferedOutputStream(fos, 8192))) {
final QCMPFileHeader header = createHeader();
header.writeHeader(compressStream);
header.writeToStream(compressStream);
planeDataSizes = imageCompressor.compress(compressStream);
......
......@@ -46,10 +46,7 @@ public class ImageDecompressor extends CompressorDecompressorBase {
*/
private QCMPFileHeader readQCMPFileHeader(final DataInputStream inputStream) throws IOException {
final QCMPFileHeader header = new QCMPFileHeader();
if (!header.readHeader(inputStream)) {
// Invalid QCMPFile header.
return null;
}
header.readFromStream(inputStream);
return header;
}
......@@ -102,89 +99,15 @@ public class ImageDecompressor extends CompressorDecompressorBase {
return "";
}
if (header == null) {
logBuilder.append("Input file is not valid QCMPFile\n");
if (!header.validateHeader()) {
logBuilder.append("Input file is not valid QCMP file\n");
validFile = false;
} else {
final boolean validHeader = header.validateHeader();
logBuilder.append("Header is:\t\t").append(validHeader ? "valid" : "invalid").append('\n');
logBuilder.append("Magic value:\t\t").append(header.getMagicValue()).append('\n');
logBuilder.append("Quantization type\t");
switch (header.getQuantizationType()) {
case Scalar:
logBuilder.append("Scalar\n");
break;
case Vector1D:
logBuilder.append("Vector1D\n");
break;
case Vector2D:
logBuilder.append("Vector2D\n");
break;
case Vector3D:
logBuilder.append("Vector3D\n");
break;
case Invalid:
logBuilder.append("INVALID\n");
break;
}
logBuilder.append("Bits per pixel:\t\t").append(header.getBitsPerCodebookIndex()).append('\n');
logBuilder.append("Codebook:\t\t").append(header.isCodebookPerPlane() ? "one per plane\n" : "one for " +
"all\n");
final int codebookSize = (int) Math.pow(2, header.getBitsPerCodebookIndex());
logBuilder.append("Codebook size:\t\t").append(codebookSize).append('\n');
logBuilder.append("Image size X:\t\t").append(header.getImageSizeX()).append('\n');
logBuilder.append("Image size Y:\t\t").append(header.getImageSizeY()).append('\n');
logBuilder.append("Image size Z:\t\t").append(header.getImageSizeZ()).append('\n');
logBuilder.append("Vector size X:\t\t").append(header.getVectorSizeX()).append('\n');
logBuilder.append("Vector size Y:\t\t").append(header.getVectorSizeY()).append('\n');
logBuilder.append("Vector size Z:\t\t").append(header.getVectorSizeZ()).append('\n');
final long headerSize = header.getHeaderSize();
final long fileSize = new File(options.getInputDataInfo().getFilePath()).length();
final long dataSize = fileSize - header.getHeaderSize();
final IImageDecompressor decompressor = getImageDecompressor(header.getQuantizationType());
if (decompressor != null) {
final long expectedDataSize = decompressor.getExpectedDataSize(header);
validFile = (dataSize == expectedDataSize);
logBuilder.append("File size:\t\t").append(fileSize).append(" B");
final long KB = (fileSize / 1000);
if (KB > 0) {
logBuilder.append(" (").append(KB).append(" KB)");
final long MB = (KB / 1000);
if (MB > 0) {
logBuilder.append(" (").append(MB).append(" MB)");
}
}
logBuilder.append('\n');
logBuilder.append("Header size:\t\t").append(headerSize).append(" Bytes\n");
logBuilder.append("Data size:\t\t").append(dataSize).append(" Bytes ")
.append(dataSize == expectedDataSize ? "(correct)\n" : "(INVALID)\n");
final long pixelCount = header.getImageDims().multiplyTogether();
final long uncompressedSize = 2 * pixelCount; // We assert 16 bit (2 byte) pixel.
final double compressionRatio = (double) fileSize / (double) uncompressedSize;
logBuilder.append(String.format("Compression ratio:\t%.4f\n", compressionRatio));
final double BPP = ((double) fileSize * 8.0) / (double) pixelCount;
logBuilder.append(String.format("Bits Per Pixel (BPP):\t%.4f\n", BPP));
}
header.report(logBuilder, options.getInputDataInfo().getFilePath());
}
logBuilder.append("\n=== Input file is ").append(validFile ? "VALID" : "INVALID").append(" ===\n");
if (header != null && options.isVerbose()) {
if (validFile && options.isVerbose()) {
final String prefix = header.getQuantizationType() != QuantizationType.Vector3D ? "Plane" : "Voxel layer";
final long[] planeDataSizes = header.getPlaneDataSizes();
long planeIndex = 0;
......@@ -244,7 +167,7 @@ public class ImageDecompressor extends CompressorDecompressorBase {
private boolean checkInputFileSize(final QCMPFileHeader header, final IImageDecompressor imageDecompressor) {
final long fileSize = new File(options.getInputDataInfo().getFilePath()).length();
final long dataSize = fileSize - header.getHeaderSize();
final long expectedDataSize = imageDecompressor.getExpectedDataSize(header);
final long expectedDataSize = header.getExpectedDataSize();
if (dataSize != expectedDataSize) {
reportStatusToListeners("Invalid file size.");
return false;
......
......@@ -40,25 +40,6 @@ public class SQImageDecompressor extends CompressorDecompressorBase implements I
return new SQCodebook(quantizationValues, symbolFrequencies);
}
@Override
public long getExpectedDataSize(final QCMPFileHeader header) {
// Quantization value count.
final int codebookSize = (int) Math.pow(2, header.getBitsPerCodebookIndex());
// Total codebook size in bytes. Also symbol frequencies for Huffman.
final long codebookDataSize = ((2 * codebookSize) + (LONG_BYTES * codebookSize)) *
(header.isCodebookPerPlane() ? header.getImageSizeZ() : 1);
// Indices are encoded using huffman. Plane data size is written in the header.
final long[] planeDataSizes = header.getPlaneDataSizes();
long totalPlaneDataSize = 0;
for (final long planeDataSize : planeDataSizes) {
totalPlaneDataSize += planeDataSize;
}
return (codebookDataSize + totalPlaneDataSize);
}
@Override
public void decompress(final DataInputStream compressedStream,
final DataOutputStream decompressStream,
......
......@@ -90,28 +90,7 @@ public class VQImageDecompressor extends CompressorDecompressorBase implements I
}
return reconstructedChunk;
}
@Override
public long getExpectedDataSize(final QCMPFileHeader header) {
// Vector count in codebook
final int codebookSize = (int) Math.pow(2, header.getBitsPerCodebookIndex());
// Single vector size in bytes.
final int vectorDataSize = 2 * header.getVectorSizeX() * header.getVectorSizeY() * header.getVectorSizeZ();
// Total codebook size in bytes.
final long codebookDataSize = ((codebookSize * vectorDataSize) + (codebookSize * LONG_BYTES)) *
(header.isCodebookPerPlane() ? header.getImageSizeZ() : 1);
// Indices are encoded using huffman. Plane data size is written in the header.
final long[] planeDataSizes = header.getPlaneDataSizes();
long totalPlaneDataSize = 0;
for (final long planeDataSize : planeDataSizes) {
totalPlaneDataSize += planeDataSize;
}
return (codebookDataSize + totalPlaneDataSize);
}
@Override
public void decompress(final DataInputStream compressedStream,
final DataOutputStream decompressStream,
......
......@@ -97,7 +97,7 @@ public class CacheFileHeaderV1 implements IFileHeader {
}
@Override
public long getExpectedFileSize() {
public long getExpectedDataSize() {
long expectedFileSize = 20 + trainFileNameSize; // Base header size
expectedFileSize += (codebookSize * 8); // Frequency values
switch (quantizationType) {
......@@ -116,7 +116,7 @@ public class CacheFileHeaderV1 implements IFileHeader {
}
@Override
public void report(final StringBuilder sb) {
public void report(final StringBuilder sb, final String inputFile) {
sb.append("HeaderVersion: ").append(VERSION).append('\n');
sb.append("Magic: ").append(magicValue).append('\n');
......
......@@ -15,7 +15,7 @@ public interface IFileHeader {
void readFromStream(final DataInputStream stream) throws IOException;
void report(final StringBuilder builder);
void report(final StringBuilder builder, final String inputFile);
long getExpectedFileSize();
long getExpectedDataSize();
}
......@@ -3,16 +3,23 @@ package cz.it4i.qcmp.fileformat;
import cz.it4i.qcmp.U16;
import cz.it4i.qcmp.compression.VQImageCompressor;
import cz.it4i.qcmp.data.V3i;
import cz.it4i.qcmp.io.RawDataIO;
import cz.it4i.qcmp.utilities.Utils;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
public class QCMPFileHeader implements Cloneable {
public static final int BASE_QCMP_HEADER_SIZE = 23;
public static final String QCMP_MAGIC_VALUE = "QCMPFILE";
public class QCMPFileHeader implements IFileHeader, Cloneable {
//region Constants
private static final int VERSION = 1;
private static final int BASE_QCMP_HEADER_SIZE = 23;
private static final String MAGIC_VALUE = "QCMPFILE";
//endregion
private String magicValue = QCMP_MAGIC_VALUE;
//region Header fields
private String magicValue = MAGIC_VALUE;
private QuantizationType quantizationType;
private byte bitsPerCodebookIndex;
private boolean codebookPerPlane;
......@@ -26,15 +33,18 @@ public class QCMPFileHeader implements Cloneable {
private int vectorSizeZ;
private long[] planeDataSizes;
//endregion
//region IFileHeader implementation
/**
* Validate that all header values are in their valid range.
*
* @return True if this is valid QCMPFILE header.
*/
@Override
public boolean validateHeader() {
if (!magicValue.equals(QCMP_MAGIC_VALUE))
if (!magicValue.equals(MAGIC_VALUE))
return false;
if (bitsPerCodebookIndex == 0)
......@@ -51,27 +61,12 @@ public class QCMPFileHeader implements Cloneable {
return false;
if (!U16.isInRange(vectorSizeY))
return false;
if (!U16.isInRange(vectorSizeZ))
return false;
return true;
return U16.isInRange(vectorSizeZ);
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public QCMPFileHeader copyOf() {
try {
return (QCMPFileHeader) this.clone();
} catch (final CloneNotSupportedException e) {
return null;
}
}
public void writeHeader(final DataOutputStream outputStream) throws IOException {
outputStream.writeBytes(QCMP_MAGIC_VALUE);
public void writeToStream(final DataOutputStream outputStream) throws IOException {
outputStream.writeBytes(MAGIC_VALUE);
outputStream.writeByte(quantizationType.getValue());
outputStream.writeByte(bitsPerCodebookIndex);
......@@ -95,26 +90,18 @@ public class QCMPFileHeader implements Cloneable {
}
}
public boolean readHeader(final DataInputStream inputStream) throws IOException {
@Override
public void readFromStream(final DataInputStream inputStream) throws IOException {
if (inputStream.available() < BASE_QCMP_HEADER_SIZE) {
return false;
throw new IOException("Provided file is not QCMP file. The file is too small.");
}
final byte[] magicBuffer = new byte[QCMP_MAGIC_VALUE.length()];
final byte[] magicValueBuffer = new byte[MAGIC_VALUE.length()];
RawDataIO.readFullBuffer(inputStream, magicValueBuffer);
int toRead = QCMP_MAGIC_VALUE.length();
while (toRead > 0) {
final int read = inputStream.read(magicBuffer, QCMP_MAGIC_VALUE.length() - toRead, toRead);
if (read < 0) {
// Invalid magic value.
return false;
}
toRead -= read;
}
magicValue = new String(magicBuffer);
if (!magicValue.equals(QCMP_MAGIC_VALUE)) {
return false;
magicValue = new String(magicValueBuffer);
if (!magicValue.equals(MAGIC_VALUE)) {
throw new IOException("Provided file is not QCMP file. Magic value is invalid.");
}
quantizationType = QuantizationType.fromByte(inputStream.readByte());
......@@ -139,10 +126,153 @@ public class QCMPFileHeader implements Cloneable {
final long readValue = inputStream.readInt();
planeDataSizes[i] = (readValue & 0x00000000FFFFFFFFL);
}
}
return true;
@Override
public int getHeaderVersion() {
return VERSION;
}
@Override
public void report(final StringBuilder builder, final String inputFile) {
if (!validateHeader()) {
builder.append("Header is:\t\t invalid\n");
return;
}
builder.append("HeaderVersion\t\t: ").append(VERSION).append('\n');
builder.append("Magic value\t\t: ").append(magicValue).append('\n');
builder.append("Quantization type\t: ");
switch (quantizationType) {
case Scalar:
builder.append("Scalar\n");
break;
case Vector1D:
builder.append("Vector1D\n");
break;
case Vector2D:
builder.append("Vector2D\n");
break;
case Vector3D:
builder.append("Vector3D\n");
break;
case Invalid:
builder.append("INVALID\n");
break;
}
builder.append("Bits per pixel\t\t: ").append(bitsPerCodebookIndex).append('\n');
builder.append("Codebook\t\t: ").append(codebookPerPlane ? "one per plane\n" : "one for all\n");
final int codebookSize = (int) Math.pow(2, bitsPerCodebookIndex);
builder.append("Codebook size\t\t: ").append(codebookSize).append('\n');
builder.append("Image stack size\t: ")
.append(imageSizeX).append('x')
.append(imageSizeY).append('x')
.append(imageSizeZ).append('\n');
builder.append("Quantization vector\t: ")
.append(vectorSizeX).append('x')
.append(vectorSizeY).append('x')
.append(vectorSizeZ).append('\n');
final long headerSize = getHeaderSize();
final long fileSize = new File(inputFile).length();
final long dataSize = fileSize - headerSize;
final long expectedDataSize = getExpectedDataSize();
final boolean correctFileSize = (dataSize == expectedDataSize);
builder.append("File size\t\t: ");
Utils.prettyPrintFileSize(builder, fileSize).append('\n');
builder.append("Header size\t\t: ").append(headerSize).append(" Bytes\n");
builder.append("Data size\t\t: ");
Utils.prettyPrintFileSize(builder, dataSize).append(correctFileSize ? "(correct)\n" : "(INVALID)\n");
final long pixelCount = imageSizeX * imageSizeY * imageSizeZ;
final long uncompressedSize = 2 * pixelCount; // We assert 16 bit (2 byte) pixel.
final double compressionRatio = (double) fileSize / (double) uncompressedSize;
builder.append(String.format("Compression ratio\t: %.4f\n", compressionRatio));
final double BPP = ((double) fileSize * 8.0) / (double) pixelCount;
builder.append(String.format("Bits Per Pixel (BPP)\t: %.4f\n", BPP));
builder.append("\n=== Input file is ").append(correctFileSize ? "VALID" : "INVALID").append(" ===\n");
}
private long calculateDataSizeForSq() {
final int LONG_BYTES = 8;
// Quantization value count.
final int codebookSize = (int) Math.pow(2, bitsPerCodebookIndex);
// Total codebook size in bytes. Also symbol frequencies for Huffman.
final long codebookDataSize = ((2 * codebookSize) + (LONG_BYTES * codebookSize)) * (codebookPerPlane ? imageSizeZ : 1);
// Indices are encoded using huffman. Plane data size is written in the header.
long totalPlaneDataSize = 0;
for (final long planeDataSize : planeDataSizes) {
totalPlaneDataSize += planeDataSize;
}
return (codebookDataSize + totalPlaneDataSize);
}
private long calculateDataSizeForVq() {
final int LONG_BYTES = 8;
// Vector count in codebook
final int codebookSize = (int) Math.pow(2, bitsPerCodebookIndex);
// Single vector size in bytes.
final int vectorDataSize = 2 * vectorSizeX * vectorSizeY * vectorSizeZ;
// Total codebook size in bytes.
final long codebookDataSize = ((codebookSize * vectorDataSize) + (codebookSize * LONG_BYTES)) * (codebookPerPlane ? imageSizeZ : 1);
// Indices are encoded using huffman. Plane data size is written in the header.
long totalPlaneDataSize = 0;
for (final long planeDataSize : planeDataSizes) {
totalPlaneDataSize += planeDataSize;
}
return (codebookDataSize + totalPlaneDataSize);
}
@Override
public long getExpectedDataSize() {
switch (quantizationType) {
case Scalar:
return calculateDataSizeForSq();
case Vector1D:
case Vector2D:
case Vector3D:
return calculateDataSizeForVq();
}
return -1;
}
@Override
public String getMagicValue() {
return magicValue;
}
//endregion
//region Cloneable implementation
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public QCMPFileHeader copyOf() {
try {
return (QCMPFileHeader) this.clone();
} catch (final CloneNotSupportedException e) {
return null;
}
}
//endregion
//region Getters and Setters
public QuantizationType getQuantizationType() {
return quantizationType;
}
......@@ -219,10 +349,6 @@ public class QCMPFileHeader implements Cloneable {
this.vectorSizeZ = vectorSizeZ;
}
public String getMagicValue() {
return magicValue;
}
public void setImageDimension(final V3i imageDims) {
imageSizeX = imageDims.getX();
imageSizeY = imageDims.getY();
......@@ -250,4 +376,5 @@ public class QCMPFileHeader implements Cloneable {
return BASE_QCMP_HEADER_SIZE + (chunkCount * 4);
}
//endregion
}
\ No newline at end of file
......@@ -192,4 +192,18 @@ public class Utils {
}
return result;
}
public static StringBuilder prettyPrintFileSize(final StringBuilder builder, final long fileSize) {
builder.append(fileSize).append(" B");
final long KB = (fileSize / 1000);
if (KB > 0) {
builder.append(" (").append(KB).append(" KB)");
final long MB = (KB / 1000);
if (MB > 0) {
builder.append(" (").append(MB).append(" MB)");
}
}
return builder;
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment