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

Implement missing logic in QCMPFileHeaderV2.

I have implemeted read, write and data size calculation. The data size
calculation is now very straightforward as a `SUM` of compressed image
chunk sizes.
parent 9a659ccb
No related branches found
No related tags found
No related merge requests found
......@@ -54,6 +54,8 @@ Otherwise (codebook per plane), there are always codebook data followed by the p
## QCMP File Format V2
The second iteration of QCMP format solves the problem of the first. Hyperstack dimensions are supported and also metadata can be saved to the compressed file. We have also reserved 19 bytes for the future.
Another difference is that compressed chunk size now also contains the size of the compressed codebook. The data size can now be calculated as a `SUM` of compressed image chunk sizes.
| Offset | Size | Type | Note |
| ------------------------ | ------------ | ------------ | ------------------------------------------- |
| 0 | 8 | ASCII String | `QCMPFLV2` Magic value |
......@@ -68,7 +70,7 @@ The second iteration of QCMP format solves the problem of the first. Hyperstack
| 21 | 2 | u16 | Vector size X |
| 23 | 2 | u16 | Vector size Y |
| 25 | 2 | u16 | Vector size Z |
| 27 | 4 | u32 | MS=Metadata size |
| 27 | 4 | i32 | MS=Metadata size |
| 31 | 19 | u8 | Reserved bytes |
| 50 | MS | u8[] | Metadata |
| 50 + MS | 4*ChunkCount | u32[] | Size of compressed image chunks |
......
......@@ -81,17 +81,36 @@ public class QCMPFileHeaderV1 implements IFileHeader, Cloneable {
outputStream.writeShort(vectorSizeZ);
// NOTE(Moravec): Allocate space for plane/voxel layers data sizes. Offset: 23.
final int chunkCount = (quantizationType != QuantizationType.Vector3D)
? imageSizeZ
: VQImageCompressor.calculateVoxelLayerCount(imageSizeZ, vectorSizeZ);
allocateSpaceForChunkSizes(outputStream);
}
/**
* Allocate bytes which will be modified at the end. Size of compressed chunks will be saved here.
*
* @param outputStream Compression stream.
* @throws IOException when fails to write int.
*/
protected void allocateSpaceForChunkSizes(final DataOutputStream outputStream) throws IOException {
final int chunkCount = getChunkCount();
for (int i = 0; i < chunkCount; i++) {
outputStream.writeInt(0x0);
}
}
/**
* Get number of compressed image chunks. See FileFormat.md
*
* @return Number of compressed chunks.
*/
protected int getChunkCount() {
return (quantizationType != QuantizationType.Vector3D)
? imageSizeZ
: VQImageCompressor.calculateVoxelLayerCount(imageSizeZ, vectorSizeZ);
}
@Override
public void readFromStream(final DataInputStream inputStream) throws IOException {
// TODO(Moravec): When we have file inspector the magic value will be read there.
if (inputStream.available() < BASE_QCMP_HEADER_SIZE) {
throw new IOException("Provided file is not QCMP file. The file is too small.");
}
......@@ -117,10 +136,17 @@ public class QCMPFileHeaderV1 implements IFileHeader, Cloneable {
vectorSizeZ = inputStream.readUnsignedShort();
final int chunkCount = (quantizationType != QuantizationType.Vector3D)
? imageSizeZ
: VQImageCompressor.calculateVoxelLayerCount(imageSizeZ, vectorSizeZ);
readChunkDataSizes(inputStream);
}
/**
* Read sizes of compressed chunks.
*
* @param inputStream Decompress stream.
* @throws IOException when unable to read chunk sizes.
*/
protected void readChunkDataSizes(final DataInputStream inputStream) throws IOException {
final int chunkCount = getChunkCount();
chunkDataSizes = new long[chunkCount];
for (int i = 0; i < chunkCount; i++) {
final long readValue = inputStream.readInt();
......@@ -358,11 +384,7 @@ public class QCMPFileHeaderV1 implements IFileHeader, Cloneable {
}
public long getHeaderSize() {
final int chunkCount = (quantizationType != QuantizationType.Vector3D)
? imageSizeZ
: VQImageCompressor.calculateVoxelLayerCount(imageSizeZ, vectorSizeZ);
return BASE_QCMP_HEADER_SIZE + (chunkCount * 4);
return BASE_QCMP_HEADER_SIZE + (getChunkCount() * 4L);
}
//endregion
}
\ No newline at end of file
package cz.it4i.qcmp.fileformat;
import cz.it4i.qcmp.U16;
import cz.it4i.qcmp.compression.VQImageCompressor;
import cz.it4i.qcmp.io.RawDataIO;
import java.io.DataInputStream;
import java.io.DataOutputStream;
......@@ -12,6 +12,7 @@ public class QCMPFileHeaderV2 extends QCMPFileHeaderV1 {
private static final int VERSION = 2;
private static final int BASE_QCMP_HEADER_SIZE = 50;
public static final String MAGIC_VALUE = "QCMPFLV2";
private static final int RESERVED_BYTES_SIZE = 19;
//endregion
//region Header fields
......@@ -43,12 +44,78 @@ public class QCMPFileHeaderV2 extends QCMPFileHeaderV1 {
@Override
public void writeToStream(final DataOutputStream outputStream) throws IOException {
// TODO
outputStream.writeBytes(MAGIC_VALUE);
outputStream.writeByte(quantizationType.getValue());
outputStream.writeByte(bitsPerCodebookIndex);
outputStream.writeBoolean(codebookPerPlane);
outputStream.writeShort(imageSizeX);
outputStream.writeShort(imageSizeY);
outputStream.writeShort(imageSizeZ);
outputStream.writeShort(channelCount);
outputStream.writeShort(timepointCount);
outputStream.writeShort(vectorSizeX);
outputStream.writeShort(vectorSizeY);
outputStream.writeShort(vectorSizeZ);
outputStream.writeInt(metadataSize);
// Reserved bytes.
for (int i = 0; i < RESERVED_BYTES_SIZE; i++) {
outputStream.writeByte(0x0);
}
assert (metadata.length == metadataSize) : "Metadata size doesn't match with metadata.length";
outputStream.write(metadata);
// NOTE(Moravec): Allocate space for plane/voxel layers data sizes. Offset: 23.
allocateSpaceForChunkSizes(outputStream);
}
@Override
public void readFromStream(final DataInputStream inputStream) throws IOException {
// TODO
if (inputStream.available() < BASE_QCMP_HEADER_SIZE) {
throw new IOException("Provided file is not QCMP v2 file. The file is too small.");
}
final byte[] magicValueBuffer = new byte[MAGIC_VALUE.length()];
RawDataIO.readFullBuffer(inputStream, magicValueBuffer);
magicValue = new String(magicValueBuffer);
if (!magicValue.equals(MAGIC_VALUE)) {
throw new IOException("Provided file is not QCMP v2 file. Magic value is invalid.");
}
quantizationType = QuantizationType.fromByte(inputStream.readByte());
bitsPerCodebookIndex = inputStream.readByte();
codebookPerPlane = inputStream.readBoolean();
imageSizeX = inputStream.readUnsignedShort();
imageSizeY = inputStream.readUnsignedShort();
imageSizeZ = inputStream.readUnsignedShort();
channelCount = inputStream.readUnsignedShort();
timepointCount = inputStream.readUnsignedShort();
vectorSizeX = inputStream.readUnsignedShort();
vectorSizeY = inputStream.readUnsignedShort();
vectorSizeZ = inputStream.readUnsignedShort();
metadataSize = inputStream.readInt();
if (metadataSize < 0)
throw new IOException("Negative metadata size was read from stream.");
final int skipped = inputStream.skipBytes(RESERVED_BYTES_SIZE);
if (skipped != RESERVED_BYTES_SIZE)
throw new IOException("Unable to read QCMPFileHeaderV2. Unable to skip reserved bytes.");
// TODO(Moravec): Should we always read the full metadata string?
if (metadataSize > 0) {
metadata = new byte[metadataSize];
RawDataIO.readFullBuffer(inputStream, metadata);
}
readChunkDataSizes(inputStream);
}
@Override
......@@ -88,51 +155,29 @@ public class QCMPFileHeaderV2 extends QCMPFileHeaderV1 {
printFileSizeInfo(builder, inputFile);
}
@Override
protected long calculateDataSizeForSq() {
// TODO(Moravec): Fix this calculation. Size of the huffman tree will be added to chunk size.
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;
private long calculateDataSize() {
long dataSize = 0;
for (final long chunkSize : chunkDataSizes) {
dataSize += chunkSize;
}
return dataSize;
}
return (codebookDataSize + totalPlaneDataSize);
@Override
protected long calculateDataSizeForSq() {
return calculateDataSize();
}
@Override
protected long calculateDataSizeForVq() {
// TODO(Moravec): Fix this calculation. Size of the huffman tree will be added to chunk size.
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);
return calculateDataSize();
}
@Override
public String getMagicValue() {
return MAGIC_VALUE;
}
//endregion
//region Cloneable implementation
......@@ -151,8 +196,6 @@ public class QCMPFileHeaderV2 extends QCMPFileHeaderV1 {
//endregion
//region Getters and Setters
public int getChannelCount() {
return channelCount;
}
......@@ -186,11 +229,7 @@ public class QCMPFileHeaderV2 extends QCMPFileHeaderV1 {
}
public long getHeaderSize() {
final int chunkCount = (quantizationType != QuantizationType.Vector3D)
? imageSizeZ
: VQImageCompressor.calculateVoxelLayerCount(imageSizeZ, vectorSizeZ);
return BASE_QCMP_HEADER_SIZE + metadataSize + (chunkCount * 4L);
return BASE_QCMP_HEADER_SIZE + metadataSize + (getChunkCount() * 4L);
}
//endregion
}
\ 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