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

Vector quantization decompression.

parent 99256c6b
No related branches found
No related tags found
No related merge requests found
...@@ -33,7 +33,7 @@ public class DataCompressor { ...@@ -33,7 +33,7 @@ public class DataCompressor {
return; return;
} }
// System.out.println(parsedCliOptions.report()); // System.out.println(parsedCliOptions.report());
switch (parsedCliOptions.getMethod()) { switch (parsedCliOptions.getMethod()) {
...@@ -56,7 +56,8 @@ public class DataCompressor { ...@@ -56,7 +56,8 @@ public class DataCompressor {
System.err.println("Errors occurred during decompression."); System.err.println("Errors occurred during decompression.");
System.err.println(e.getMessage()); System.err.println(e.getMessage());
e.printStackTrace(); e.printStackTrace();
} return; }
return;
} }
case PrintHelp: { case PrintHelp: {
formatter.printHelp("ijava -jar DataCompressor.jar", options); formatter.printHelp("ijava -jar DataCompressor.jar", options);
...@@ -73,7 +74,8 @@ public class DataCompressor { ...@@ -73,7 +74,8 @@ public class DataCompressor {
} }
return; return;
} }
} return; }
return;
} }
@NotNull @NotNull
......
...@@ -34,6 +34,10 @@ public class ParsedCliOptions { ...@@ -34,6 +34,10 @@ public class ParsedCliOptions {
private boolean errorOccurred; private boolean errorOccurred;
private String error; private String error;
private boolean planeRangeSet = false;
private int fromPlaneIndex;
private int toPlaneIndex;
public ParsedCliOptions(CommandLine cmdInput) { public ParsedCliOptions(CommandLine cmdInput) {
parseCLI(cmdInput); parseCLI(cmdInput);
} }
...@@ -94,12 +98,7 @@ public class ParsedCliOptions { ...@@ -94,12 +98,7 @@ public class ParsedCliOptions {
private void parseInputFilePart(StringBuilder errorBuilder, final String[] fileInfo) { private void parseInputFilePart(StringBuilder errorBuilder, final String[] fileInfo) {
if ((method == ProgramMethod.Decompress) || (method == ProgramMethod.InspectFile)) { if ((method == ProgramMethod.Decompress) || (method == ProgramMethod.InspectFile)) {
if (fileInfo.length > 0) { if (fileInfo.length > 0) {
final File input = new File(fileInfo[0]); inputFile = fileInfo[0];
if (!input.exists()) {
errorOccurred = true;
errorBuilder.append("Input file doesn't exist.\n");
}
inputFile = input.getAbsolutePath();
} else { } else {
errorOccurred = true; errorOccurred = true;
errorBuilder.append("Missing input file for decompression"); errorBuilder.append("Missing input file for decompression");
...@@ -118,13 +117,32 @@ public class ParsedCliOptions { ...@@ -118,13 +117,32 @@ public class ParsedCliOptions {
parseImageDims(fileInfo[1], errorBuilder); parseImageDims(fileInfo[1], errorBuilder);
if (fileInfo.length > 2) { if (fileInfo.length > 2) {
final var parseResult = tryParseInt(fileInfo[2]);
if (parseResult.isSuccess()) { int rangeSepIndex = fileInfo[2].indexOf("-");
planeIndexSet = true; if (rangeSepIndex != -1) {
planeIndex = parseResult.getValue(); final String fromIndexString = fileInfo[2].substring(0, rangeSepIndex);
final String toIndexString = fileInfo[2].substring(rangeSepIndex + 1);
final var indexFromResult = tryParseInt(fromIndexString);
final var indexToResult = tryParseInt(toIndexString);
if (indexFromResult.isSuccess() && indexToResult.isSuccess()) {
fromPlaneIndex = indexFromResult.getValue();
toPlaneIndex = indexToResult.getValue();
planeRangeSet = true;
} else {
errorOccurred = true;
errorBuilder.append("Plane range index is wrong. Expected format D-D, got: ").append(
fileInfo[2]).append('\n');
}
} else { } else {
errorOccurred = true; final var parseResult = tryParseInt(fileInfo[2]);
errorBuilder.append("The second argument after file name must be plane index\n"); if (parseResult.isSuccess()) {
planeIndexSet = true;
planeIndex = parseResult.getValue();
} else {
errorOccurred = true;
errorBuilder.append("The second argument after file name must be plane index\n");
}
} }
} }
} }
...@@ -342,6 +360,18 @@ public class ParsedCliOptions { ...@@ -342,6 +360,18 @@ public class ParsedCliOptions {
return error; return error;
} }
public boolean hasPlaneRangeSet() {
return planeRangeSet;
}
public int getFromPlaneIndex() {
return fromPlaneIndex;
}
public int getToPlaneIndex() {
return toPlaneIndex;
}
public String report() { public String report() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
...@@ -391,8 +421,22 @@ public class ParsedCliOptions { ...@@ -391,8 +421,22 @@ public class ParsedCliOptions {
if (refPlaneIndexSet) { if (refPlaneIndexSet) {
sb.append("ReferencePlaneIndex: ").append(referencePlaneIndex).append('\n'); sb.append("ReferencePlaneIndex: ").append(referencePlaneIndex).append('\n');
} }
if (planeRangeSet) {
sb.append("FromPlaneIndex: ").append(fromPlaneIndex).append('\n');
sb.append("ToPlaneIndex: ").append(toPlaneIndex).append('\n');
}
return sb.toString(); return sb.toString();
} }
public int getNumberOfPlanes() {
if (hasPlaneIndexSet()) {
return 1;
} else if (hasPlaneRangeSet()) {
return (toPlaneIndex - fromPlaneIndex);
} else {
return imageDimension.getZ();
}
}
} }
...@@ -14,6 +14,16 @@ public class CompressorDecompressorBase { ...@@ -14,6 +14,16 @@ public class CompressorDecompressorBase {
protected int[] getPlaneIndicesForCompression() { protected int[] getPlaneIndicesForCompression() {
if (options.hasPlaneIndexSet()) { if (options.hasPlaneIndexSet()) {
return new int[]{options.getPlaneIndex()}; return new int[]{options.getPlaneIndex()};
} else if (options.hasPlaneRangeSet()) {
final int from = options.getFromPlaneIndex();
final int to = options.getToPlaneIndex();
final int count = to - from;
int[] indices = new int[count];
for (int i = 0; i < count; i++) {
indices[i] = from + i;
}
return indices;
} else { } else {
return generateAllPlaneIndices(options.getImageDimension().getZ()); return generateAllPlaneIndices(options.getImageDimension().getZ());
} }
...@@ -33,6 +43,10 @@ public class CompressorDecompressorBase { ...@@ -33,6 +43,10 @@ public class CompressorDecompressorBase {
} }
} }
protected void DebugLog(final String message) {
System.out.println(message);
}
protected void LogError(final String message) { protected void LogError(final String message) {
if (options.isVerbose()) { if (options.isVerbose()) {
System.err.println(message); System.err.println(message);
......
...@@ -31,6 +31,8 @@ public class ImageCompressor extends CompressorDecompressorBase { ...@@ -31,6 +31,8 @@ public class ImageCompressor extends CompressorDecompressorBase {
public void compress() throws Exception { public void compress() throws Exception {
Log(String.format("Compression with BPP = %d", options.getBitsPerPixel()));
FileOutputStream fos = new FileOutputStream(options.getOutputFile(), false); FileOutputStream fos = new FileOutputStream(options.getOutputFile(), false);
DataOutputStream dataOutputStream = new DataOutputStream(new BufferedOutputStream(fos, 8192)); DataOutputStream dataOutputStream = new DataOutputStream(new BufferedOutputStream(fos, 8192));
...@@ -94,7 +96,8 @@ public class ImageCompressor extends CompressorDecompressorBase { ...@@ -94,7 +96,8 @@ public class ImageCompressor extends CompressorDecompressorBase {
options.getReferencePlaneIndex()); options.getReferencePlaneIndex());
Log("Creating codebook from reference plane..."); Log("Creating codebook from reference plane...");
quantizer = trainVectorQuantizerFromPlaneVectors(getPlaneVectors(referencePlane)); final int[][] refPlaneVectors = getPlaneVectors(referencePlane);
quantizer = trainVectorQuantizerFromPlaneVectors(refPlaneVectors);
writeCodebookToOutputStream(quantizer, compressStream); writeCodebookToOutputStream(quantizer, compressStream);
Log("Wrote reference codebook."); Log("Wrote reference codebook.");
} }
...@@ -135,7 +138,9 @@ public class ImageCompressor extends CompressorDecompressorBase { ...@@ -135,7 +138,9 @@ public class ImageCompressor extends CompressorDecompressorBase {
header.setBitsPerPixel((byte) options.getBitsPerPixel()); header.setBitsPerPixel((byte) options.getBitsPerPixel());
header.setCodebookPerPlane(!options.hasReferencePlaneIndex()); header.setCodebookPerPlane(!options.hasReferencePlaneIndex());
header.setImageDimension(options.getImageDimension()); header.setImageSizeX(options.getImageDimension().getX());
header.setImageSizeY(options.getImageDimension().getY());
header.setImageSizeZ(options.getNumberOfPlanes());
// If plane index is set then, we are compressing only one plane. // If plane index is set then, we are compressing only one plane.
if (options.hasPlaneIndexSet()) { if (options.hasPlaneIndexSet()) {
......
package compression; package compression;
import cli.ParsedCliOptions; import cli.ParsedCliOptions;
import compression.data.*;
import compression.fileformat.QCMPFileHeader; import compression.fileformat.QCMPFileHeader;
import compression.io.InBitStream; import compression.io.InBitStream;
import compression.utilities.TypeConverter; import compression.utilities.TypeConverter;
...@@ -31,24 +32,35 @@ public class ImageDecompressor extends CompressorDecompressorBase { ...@@ -31,24 +32,35 @@ public class ImageDecompressor extends CompressorDecompressorBase {
return (codebookDataSize + allPlaneIndicesDataSize); return (codebookDataSize + allPlaneIndicesDataSize);
} }
private long calculatePlaneVectorCount(final QCMPFileHeader header) {
final int vectorXCount = (int) Math.ceil((double) header.getImageSizeX() / (double) header.getVectorSizeX());
final int vectorYCount = (int) Math.ceil((double) header.getImageSizeY() / (double) header.getVectorSizeY());
// Number of vectors per plane.
return (vectorXCount * vectorYCount);
}
private long calculatePlaneDataSize(final long planeVectorCount, final int bpp) {
// Data size of single plane indices.
return (long) Math.ceil((planeVectorCount * bpp) / 8.0);
}
private long getExpectedDataSizeForVectorQuantization(final QCMPFileHeader header) { private long getExpectedDataSizeForVectorQuantization(final QCMPFileHeader header) {
// Vector count in codebook // Vector count in codebook
final int codebookSize = (int) Math.pow(2, header.getBitsPerPixel()); final int codebookSize = (int) Math.pow(2, header.getBitsPerPixel());
// Single vector size in bytes. // Single vector size in bytes.
final int vectorDataSize = 2 * header.getVectorSizeX() * header.getVectorSizeY() * header.getImageSizeZ(); assert (header.getVectorSizeZ() == 1);
final int vectorDataSize = 2 * header.getVectorSizeX() * header.getVectorSizeY() * header.getVectorSizeZ();
// Total codebook size in bytes. // Total codebook size in bytes.
final long codebookDataSize = (codebookSize * vectorDataSize) * (header.isCodebookPerPlane() ? final long codebookDataSize = (codebookSize * vectorDataSize) * (header.isCodebookPerPlane() ?
header.getImageSizeZ() : 1); header.getImageSizeZ() : 1);
final int vectorXCount = (int) Math.ceil((double) header.getImageSizeX() / (double) header.getVectorSizeX());
final int vectorYCount = (int) Math.ceil((double) header.getImageSizeY() / (double) header.getVectorSizeY());
// Number of vectors per plane. // Number of vectors per plane.
final long vectorCount = vectorXCount * vectorYCount; final long planeVectorCount = calculatePlaneVectorCount(header);
// Data size of single plane indices. // Data size of single plane indices.
final long planeDataSize = (long) Math.ceil((vectorCount * header.getBitsPerPixel()) / 8.0); final long planeDataSize = calculatePlaneDataSize(planeVectorCount, header.getBitsPerPixel());
// All planes data size. // All planes data size.
final long allPlanesDataSize = planeDataSize * header.getImageSizeZ(); final long allPlanesDataSize = planeDataSize * header.getImageSizeZ();
...@@ -64,8 +76,8 @@ public class ImageDecompressor extends CompressorDecompressorBase { ...@@ -64,8 +76,8 @@ public class ImageDecompressor extends CompressorDecompressorBase {
} }
case Vector1D: case Vector1D:
case Vector2D: case Vector2D:
return getExpectedDataSizeForVectorQuantization(header);
case Vector3D: case Vector3D:
return getExpectedDataSizeForVectorQuantization(header);
case Invalid: case Invalid:
return -1; return -1;
} }
...@@ -176,9 +188,9 @@ public class ImageDecompressor extends CompressorDecompressorBase { ...@@ -176,9 +188,9 @@ public class ImageDecompressor extends CompressorDecompressorBase {
break; break;
case Vector1D: case Vector1D:
case Vector2D: case Vector2D:
// TODO!
break;
case Vector3D: case Vector3D:
decompressUsingVectorQuantization(dataInputStream, decompressStream, header);
break;
case Invalid: case Invalid:
throw new Exception("Invalid quantization type;"); throw new Exception("Invalid quantization type;");
} }
...@@ -200,6 +212,102 @@ public class ImageDecompressor extends CompressorDecompressorBase { ...@@ -200,6 +212,102 @@ public class ImageDecompressor extends CompressorDecompressorBase {
return quantizationValues; return quantizationValues;
} }
private int[][] readCodebookVectors(DataInputStream compressedStream,
final int codebookSize,
final int vectorSize) throws IOException {
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();
}
}
return codebook;
}
private ImageU16 reconstructImageFromQuantizedVectors(final int[][] vectors,
final V2i qVector,
final V3i imageDims) {
Chunk2D reconstructedChunk = new Chunk2D(new V2i(imageDims.getX(), imageDims.getY()), new V2l(0, 0));
if (qVector.getY() > 1) {
// FIXME
// Chunk2D new Chunk2D(new V2i(width, height), new V2l(0, 0), data);
// var chunks = plane.as2dChunk().divideIntoChunks(qVector);
var chunks = reconstructedChunk.divideIntoChunks(qVector);
Chunk2D.updateChunkData(chunks, vectors);
reconstructedChunk.reconstructFromChunks(chunks);
} else {
// 1D vector
reconstructedChunk.reconstructFromVectors(vectors);
}
return reconstructedChunk.asImageU16();
}
private void decompressUsingVectorQuantization(DataInputStream compressedStream,
DataOutputStream decompressStream,
final QCMPFileHeader header) throws Exception {
final int codebookSize = (int) Math.pow(2, header.getBitsPerPixel());
assert (header.getVectorSizeZ() == 1);
final int vectorSize = header.getVectorSizeX() * header.getVectorSizeY() * header.getVectorSizeZ();
final int planeCountForDecompression = header.getImageSizeZ();
final int planePixelCount = header.getImageSizeX() * header.getImageSizeY();
final long planeVectorCount = calculatePlaneVectorCount(header);
final long planeDataSize = calculatePlaneDataSize(planeVectorCount, header.getBitsPerPixel());
final V2i qVector = new V2i(header.getVectorSizeX(), header.getVectorSizeY());
int[][] quantizationVectors = null;
if (!header.isCodebookPerPlane()) {
// There is only one codebook.
Log("Loading reference codebook...");
quantizationVectors = readCodebookVectors(compressedStream, codebookSize, vectorSize);
}
for (int planeIndex = 0; planeIndex < planeCountForDecompression; planeIndex++) {
if (header.isCodebookPerPlane()) {
Log("Loading plane codebook...");
quantizationVectors = readCodebookVectors(compressedStream, codebookSize, vectorSize);
}
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);
}
// int[] decompressedValues = new int[planePixelCount];
// for (int vecIndex = 0; vecIndex < planeVectorCount; vecIndex++) {
// System.arraycopy(quantizationVectors[indices[vecIndex]],
// 0,
// decompressedValues,
// (vecIndex * vectorSize),
// vectorSize);
// }
final ImageU16 decompressedPlane = reconstructImageFromQuantizedVectors(decompressedVectors,
qVector,
header.getImageDims());
final byte[] decompressedPlaneData = TypeConverter.shortArrayToByteArray(decompressedPlane.getData(),
false);
decompressStream.write(decompressedPlaneData);
Log(String.format("Decompressed plane %d.", planeIndex));
}
}
private void decompressUsingScalarQuantization(DataInputStream compressedStream, private void decompressUsingScalarQuantization(DataInputStream compressedStream,
DataOutputStream decompressStream, DataOutputStream decompressStream,
final QCMPFileHeader header) throws Exception { final QCMPFileHeader header) throws Exception {
......
...@@ -2,8 +2,6 @@ package compression.data; ...@@ -2,8 +2,6 @@ package compression.data;
import compression.utilities.TypeConverter; import compression.utilities.TypeConverter;
import java.util.Arrays;
public class Chunk2D { public class Chunk2D {
private final int FILL_VALUE = 0; private final int FILL_VALUE = 0;
private int[] data; private int[] data;
...@@ -247,7 +245,8 @@ public class Chunk2D { ...@@ -247,7 +245,8 @@ public class Chunk2D {
for (int i = 0; i < vectorCount; i++) { for (int i = 0; i < vectorCount; i++) {
assert (chunks[i].data.length == vectorSize); assert (chunks[i].data.length == vectorSize);
imageVectors[i] = Arrays.copyOf(chunks[i].data, vectorSize); System.arraycopy(chunks[i].data,0,imageVectors[i],0, vectorSize);
//imageVectors[i] = Arrays.copyOf(chunks[i].data, vectorSize);
} }
return imageVectors; return imageVectors;
} }
......
...@@ -141,6 +141,10 @@ public class QCMPFileHeader { ...@@ -141,6 +141,10 @@ public class QCMPFileHeader {
return imageSizeZ; return imageSizeZ;
} }
public V3i getImageDims() {
return new V3i(imageSizeX, imageSizeY, imageSizeZ);
}
public void setImageSizeZ(int imageSizeZ) { public void setImageSizeZ(int imageSizeZ) {
this.imageSizeZ = imageSizeZ; this.imageSizeZ = imageSizeZ;
} }
......
...@@ -78,6 +78,22 @@ public class TypeConverter { ...@@ -78,6 +78,22 @@ public class TypeConverter {
return buffer; return buffer;
} }
public static byte[] unsignedShortArrayToByteArray(final int[] data, final boolean littleEndian) {
byte[] buffer = new byte[data.length * 2];
int j = 0;
for (final int v : data) {
if (littleEndian) {
buffer[j++] = (byte) ((v >>> 16) & 0xFF);
buffer[j++] = (byte) ((v >>> 24) & 0xFF);
} else {
buffer[j++] = (byte) ((v >>> 8) & 0xFF);
buffer[j++] = (byte) (v & 0xFF);
}
}
return buffer;
}
public static byte[] intArrayToByteArray(final int[] data, final boolean littleEndian) { public static byte[] intArrayToByteArray(final int[] data, final boolean littleEndian) {
byte[] buffer = new byte[data.length * 4]; byte[] buffer = new byte[data.length * 4];
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment