diff --git a/src/main/java/cz/it4i/qcmp/cache/IQvcFile.java b/src/main/java/cz/it4i/qcmp/cache/IQvcFile.java index 7238c77afd80540091ed26bd6ba6342dde39ad26..908361a25832bbbc38b04c3cfc311938f3215823 100644 --- a/src/main/java/cz/it4i/qcmp/cache/IQvcFile.java +++ b/src/main/java/cz/it4i/qcmp/cache/IQvcFile.java @@ -1,24 +1,18 @@ package cz.it4i.qcmp.cache; import cz.it4i.qcmp.fileformat.IQvcHeader; -import cz.it4i.qcmp.fileformat.QvcHeaderV1; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -// TODO(Moravec): Rename to IQvcFile -public interface ICacheFile { +public interface IQvcFile { void writeToStream(DataOutputStream outputStream) throws IOException; void readFromStream(DataInputStream inputStream, IQvcHeader header) throws IOException; - void readFromStream(DataInputStream inputStream, QvcHeaderV1 header) throws IOException; - IQvcHeader getHeader(); void report(StringBuilder builder); - - String klass(); } diff --git a/src/main/java/cz/it4i/qcmp/cache/QvcFileReader.java b/src/main/java/cz/it4i/qcmp/cache/QvcFileReader.java new file mode 100644 index 0000000000000000000000000000000000000000..834397d074cbae8acdc11aab2d9f7e96428366ca --- /dev/null +++ b/src/main/java/cz/it4i/qcmp/cache/QvcFileReader.java @@ -0,0 +1,105 @@ +package cz.it4i.qcmp.cache; + +import cz.it4i.qcmp.fileformat.IQvcHeader; +import cz.it4i.qcmp.fileformat.QuantizationType; +import cz.it4i.qcmp.fileformat.QvcHeaderV1; +import cz.it4i.qcmp.fileformat.QvcHeaderV2; +import cz.it4i.qcmp.io.RawDataIO; + +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class QvcFileReader { + private static final int QVC_HEADER_MAGIC_VALUE_SIZE = 9; + + /** + * Read cache file from file. + * + * @param path File path. + * @return Cache file or null if reading fails. + */ + public static IQvcFile readCacheFile(final String path) { + try (final FileInputStream fis = new FileInputStream(path)) { + return readCacheFileImpl(fis); + } catch (final IOException e) { + return null; + } + } + + /** + * Make DataInputStream from InputStream. + * + * @param inputStream Some input stream. + * @return DataInputStream. + */ + private static DataInputStream asDataInputStream(final InputStream inputStream) { + if (inputStream instanceof DataInputStream) { + return (DataInputStream) inputStream; + } else { + return new DataInputStream(inputStream); + } + } + + + /** + * Create correct Qvc header version by analyzing the magic value. + * + * @param magicValue Magic value of the qvc file. + * @return Correct version of QVC header. + * @throws IOException when the magic value is unknown. + */ + private static IQvcHeader getCorrectQvcHeader(final String magicValue) throws IOException { + switch (magicValue) { + case QvcHeaderV1.MAGIC_VALUE: + return new QvcHeaderV1(); + case QvcHeaderV2.MAGIC_VALUE: + return new QvcHeaderV2(); + default: + throw new IOException("Invalid QVC file. Unknown QVC magic value: " + magicValue); + } + } + + /** + * Create correct Qvc file by analyzing the quantization type. + * + * @param quantizationType Quantization type of codebook. + * @return Correct version of QVC file. + * @throws IOException when the quantization type is unknown. + */ + private static IQvcFile getCorrectQvcFile(final QuantizationType quantizationType) throws IOException { + switch (quantizationType) { + case Scalar: + return new SqQvcFile(); + case Vector1D: + case Vector2D: + case Vector3D: + return new VqQvcFile(); + default: + throw new IOException("Invalid quantization type. Unable to create qvc file impl."); + } + } + + /** + * Read cache file by DataInputStream. + * + * @param inputStream Input stream. + * @return Cache file or null, if exception occurs. + */ + private static IQvcFile readCacheFileImpl(final InputStream inputStream) throws IOException { + final DataInputStream dis = asDataInputStream(inputStream); + + final byte[] magicValueBuffer = new byte[QVC_HEADER_MAGIC_VALUE_SIZE]; + RawDataIO.readFullBuffer(dis, magicValueBuffer); + final String magicValue = new String(magicValueBuffer); + + final IQvcHeader header = getCorrectQvcHeader(magicValue); + header.readFromStream(dis); + + final IQvcFile cacheFile = getCorrectQvcFile(header.getQuantizationType()); + cacheFile.readFromStream(dis, header); + + return cacheFile; + } +} diff --git a/src/main/java/cz/it4i/qcmp/cache/SqQvcFile.java b/src/main/java/cz/it4i/qcmp/cache/SqQvcFile.java index 1565ef34167e9e579fb338a8fd7c8732b092421e..5071ef2369755ab6c6f3ce948ad945b17f898e43 100644 --- a/src/main/java/cz/it4i/qcmp/cache/SqQvcFile.java +++ b/src/main/java/cz/it4i/qcmp/cache/SqQvcFile.java @@ -1,26 +1,28 @@ package cz.it4i.qcmp.cache; -import cz.it4i.qcmp.fileformat.QvcHeaderV1; +import cz.it4i.qcmp.fileformat.IQvcHeader; import cz.it4i.qcmp.quantization.scalar.SQCodebook; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -public class SQCacheFile implements IQvcFile { - private QvcHeaderV1 header; +public class SqQvcFile implements IQvcFile { + private IQvcHeader header; private SQCodebook codebook; - public SQCacheFile() { + public SqQvcFile() { } - public SQCacheFile(final QvcHeaderV1 header, final SQCodebook codebook) { + public SqQvcFile(final IQvcHeader header, final SQCodebook codebook) { this.header = header; this.codebook = codebook; assert (header.getCodebookSize() == codebook.getCodebookSize()); } + @Override public void writeToStream(final DataOutputStream outputStream) throws IOException { + // TODO header.writeToStream(outputStream); final int[] quantizationValues = codebook.getCentroids(); final long[] frequencies = codebook.getSymbolFrequencies(); @@ -33,13 +35,9 @@ public class SQCacheFile implements IQvcFile { } } - public void readFromStream(final DataInputStream inputStream) throws IOException { - header = new QvcHeaderV1(); - header.readFromStream(inputStream); - readFromStream(inputStream, header); - } - - public void readFromStream(final DataInputStream inputStream, final QvcHeaderV1 header) throws IOException { + @Override + public void readFromStream(final DataInputStream inputStream, final IQvcHeader header) throws IOException { + // TODO this.header = header; final int codebookSize = header.getCodebookSize(); final int[] centroids = new int[codebookSize]; @@ -54,7 +52,8 @@ public class SQCacheFile implements IQvcFile { codebook = new SQCodebook(centroids, frequencies); } - public QvcHeaderV1 getHeader() { + @Override + public IQvcHeader getHeader() { return header; } diff --git a/src/main/java/cz/it4i/qcmp/cache/VqQvcFile.java b/src/main/java/cz/it4i/qcmp/cache/VqQvcFile.java index 1eb135964c69040541b80d19e096ca50a30248d0..13104c43a9f12e76d1cbd9a0f88b3737206b3dd4 100644 --- a/src/main/java/cz/it4i/qcmp/cache/VqQvcFile.java +++ b/src/main/java/cz/it4i/qcmp/cache/VqQvcFile.java @@ -1,26 +1,28 @@ package cz.it4i.qcmp.cache; -import cz.it4i.qcmp.fileformat.QvcHeaderV1; +import cz.it4i.qcmp.fileformat.IQvcHeader; import cz.it4i.qcmp.quantization.vector.VQCodebook; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -public class VQCacheFile implements IQvcFile { - private QvcHeaderV1 header; +public class VqQvcFile implements IQvcFile { + private IQvcHeader header; private VQCodebook codebook; - public VQCacheFile() { + public VqQvcFile() { } - public VQCacheFile(final QvcHeaderV1 header, final VQCodebook codebook) { + public VqQvcFile(final IQvcHeader header, final VQCodebook codebook) { this.header = header; this.codebook = codebook; assert (header.getCodebookSize() == codebook.getCodebookSize()); } + @Override public void writeToStream(final DataOutputStream outputStream) throws IOException { + // TODO header.writeToStream(outputStream); final int[][] entries = codebook.getVectors(); @@ -36,18 +38,13 @@ public class VQCacheFile implements IQvcFile { } } - public void readFromStream(final DataInputStream inputStream) throws IOException { - header = new QvcHeaderV1(); - header.readFromStream(inputStream); - readFromStream(inputStream, header); - } - @Override - public void readFromStream(final DataInputStream inputStream, final QvcHeaderV1 header) throws IOException { + public void readFromStream(final DataInputStream inputStream, final IQvcHeader header) throws IOException { + // TODO this.header = header; final int codebookSize = header.getCodebookSize(); - final int entrySize = header.getVectorSizeX() * header.getVectorSizeY() * header.getVectorSizeZ(); + final int entrySize = header.getVectorDim().multiplyTogether(); final int[][] vectors = new int[codebookSize][entrySize]; final long[] frequencies = new long[codebookSize]; @@ -64,7 +61,8 @@ public class VQCacheFile implements IQvcFile { codebook = new VQCodebook(header.getVectorDim(), vectors, frequencies); } - public QvcHeaderV1 getHeader() { + @Override + public IQvcHeader getHeader() { return header; } diff --git a/src/main/java/cz/it4i/qcmp/fileformat/IQvcHeader.java b/src/main/java/cz/it4i/qcmp/fileformat/IQvcHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..b1a752f690a7a1b05ec7c3e245229a859a2d23db --- /dev/null +++ b/src/main/java/cz/it4i/qcmp/fileformat/IQvcHeader.java @@ -0,0 +1,13 @@ +package cz.it4i.qcmp.fileformat; + +import cz.it4i.qcmp.data.V3i; + +public interface IQvcHeader extends IFileHeader { + QuantizationType getQuantizationType(); + + int getBitsPerCodebookIndex(); + + int getCodebookSize(); + + V3i getVectorDim(); +} diff --git a/src/main/java/cz/it4i/qcmp/fileformat/QvcHeaderV1.java b/src/main/java/cz/it4i/qcmp/fileformat/QvcHeaderV1.java index cc2ab909dd57f86e4b9ad001fd9d1c658bf35059..5fc30acf6c935edfb49894853e71b2213f0d4df2 100644 --- a/src/main/java/cz/it4i/qcmp/fileformat/QvcHeaderV1.java +++ b/src/main/java/cz/it4i/qcmp/fileformat/QvcHeaderV1.java @@ -9,21 +9,22 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -public class QvcHeaderV1 implements IFileHeader { +public class QvcHeaderV1 implements IQvcHeader { //region Constants private static final int VERSION = 1; - private static final String MAGIC_VALUE = "QCMPCACHE"; + public static final String MAGIC_VALUE = "QCMPCACHE"; + public static final int BASE_HEADER_SIZE = 20; //endregion //region Header fields. - private String magicValue; - private QuantizationType quantizationType; - private int codebookSize; - private int trainFileNameSize; - private String trainFileName; - private int vectorSizeX; - private int vectorSizeY; - private int vectorSizeZ; + protected String magicValue; + protected QuantizationType quantizationType; + protected int codebookSize; + protected int trainFileNameSize; + protected String trainFileName; + protected int vectorSizeX; + protected int vectorSizeY; + protected int vectorSizeZ; //endregion //region IFileHeader implementation @@ -49,18 +50,6 @@ public class QvcHeaderV1 implements IFileHeader { */ @Override public void readFromStream(final DataInputStream inputStream) throws IOException { - final int MIN_AVAIL = 9; - if (inputStream.available() < MIN_AVAIL) { - throw new IOException("Provided file is not QCMP cache file."); - } - - final byte[] magicValueBuffer = new byte[MAGIC_VALUE.length()]; - RawDataIO.readFullBuffer(inputStream, magicValueBuffer); - - magicValue = new String(magicValueBuffer); - if (!magicValue.equals(MAGIC_VALUE)) { - throw new IOException("Invalid QCMP cache file. Wrong magic value."); - } quantizationType = QuantizationType.fromByte(inputStream.readByte()); codebookSize = inputStream.readUnsignedShort(); @@ -68,7 +57,6 @@ public class QvcHeaderV1 implements IFileHeader { final byte[] fileNameBuffer = new byte[trainFileNameSize]; RawDataIO.readFullBuffer(inputStream, fileNameBuffer); - trainFileName = new String(fileNameBuffer); vectorSizeX = inputStream.readUnsignedShort(); @@ -84,7 +72,7 @@ public class QvcHeaderV1 implements IFileHeader { */ @Override public void writeToStream(final DataOutputStream outputStream) throws IOException { - outputStream.writeBytes(MAGIC_VALUE); + outputStream.writeBytes(getMagicValue()); outputStream.writeByte(quantizationType.getValue()); outputStream.writeShort(codebookSize); @@ -98,16 +86,16 @@ public class QvcHeaderV1 implements IFileHeader { @Override public long getExpectedDataSize() { - long expectedFileSize = 20 + trainFileNameSize; // Base header size - expectedFileSize += (codebookSize * 8); // Frequency values + long expectedFileSize = BASE_HEADER_SIZE + trainFileNameSize; + expectedFileSize += codebookSize * 8L; // Frequency values switch (quantizationType) { case Scalar: - expectedFileSize += (codebookSize * 2); // Scalar quantization values + expectedFileSize += codebookSize * 2L; // Scalar quantization values break; case Vector1D: case Vector2D: case Vector3D: - expectedFileSize += ((vectorSizeX * vectorSizeY * vectorSizeZ) * codebookSize * 2); // Quantization vectors + expectedFileSize += (((long) vectorSizeX * vectorSizeY * vectorSizeZ) * codebookSize * 2L); // Quantization vectors break; case Invalid: return -1; @@ -117,8 +105,8 @@ public class QvcHeaderV1 implements IFileHeader { @Override public void report(final StringBuilder sb, final String inputFile) { - sb.append("HeaderVersion: ").append(VERSION).append('\n'); - sb.append("Magic: ").append(magicValue).append('\n'); + sb.append("HeaderVersion: ").append(getHeaderVersion()).append('\n'); + sb.append("Magic: ").append(getMagicValue()).append('\n'); sb.append("CodebookType: "); switch (quantizationType) { @@ -155,18 +143,6 @@ public class QvcHeaderV1 implements IFileHeader { this.trainFileNameSize = this.trainFileName.length(); } - public void setVectorSizeX(final int vectorSizeX) { - this.vectorSizeX = vectorSizeX; - } - - public void setVectorSizeY(final int vectorSizeY) { - this.vectorSizeY = vectorSizeY; - } - - public void setVectorSizeZ(final int vectorSizeZ) { - this.vectorSizeZ = vectorSizeZ; - } - public QuantizationType getQuantizationType() { return quantizationType; } @@ -179,10 +155,6 @@ public class QvcHeaderV1 implements IFileHeader { return (int) Utils.log2(codebookSize); } - public int getTrainFileNameSize() { - return trainFileNameSize; - } - public String getTrainFileName() { return trainFileName; } diff --git a/src/main/java/cz/it4i/qcmp/fileformat/QvcHeaderV2.java b/src/main/java/cz/it4i/qcmp/fileformat/QvcHeaderV2.java new file mode 100644 index 0000000000000000000000000000000000000000..c5d759c06717b2cfcdb1ecb02cef0bc57b120ac8 --- /dev/null +++ b/src/main/java/cz/it4i/qcmp/fileformat/QvcHeaderV2.java @@ -0,0 +1,68 @@ +package cz.it4i.qcmp.fileformat; + +import java.io.DataInputStream; +import java.io.IOException; + +public class QvcHeaderV2 extends QvcHeaderV1 { + //region Constants + private static final int VERSION = 2; + public static final String MAGIC_VALUE = "QVCFILEV2"; + public static final int RESERVED_BYTES_SIZE = 10; + public static final int BASE_HEADER_SIZE = 32; + //endregion + + //region Header fields. + int huffmanDataSize; + //endregion + + //region IFileHeader implementation + @Override + public String getMagicValue() { + return MAGIC_VALUE; + } + + @Override + public int getHeaderVersion() { + return VERSION; + } + + @Override + public boolean validateHeader() { + return (magicValue != null && magicValue.equals(MAGIC_VALUE)); + } + + @Override + public void readFromStream(final DataInputStream inputStream) throws IOException { + super.readFromStream(inputStream); + huffmanDataSize = inputStream.readUnsignedShort(); + final int skipped = inputStream.skipBytes(RESERVED_BYTES_SIZE); + if (skipped != RESERVED_BYTES_SIZE) + throw new IOException("Unable to read QvcHeaderV2. Unable to skip reserved bytes."); + } + + @Override + public long getExpectedDataSize() { + long expectedFileSize = BASE_HEADER_SIZE + trainFileNameSize + huffmanDataSize; + + switch (quantizationType) { + case Scalar: + expectedFileSize += codebookSize * 2L; // Scalar quantization values + break; + case Vector1D: + case Vector2D: + case Vector3D: + expectedFileSize += (((long) vectorSizeX * vectorSizeY * vectorSizeZ) * codebookSize * 2L); // Quantization vectors + break; + case Invalid: + return -1; + } + return expectedFileSize; + } + + @Override + public void report(final StringBuilder sb, final String inputFile) { + super.report(sb, inputFile); + sb.append("HuffmanDataSize: ").append(huffmanDataSize).append('\n'); + } + //endregion +}