diff --git a/DataCompressor.iml b/DataCompressor.iml index add71798f4dc0699f9f3ae1cd18003ffe51acc6a..3b89bcd618d9e5144d76bb3f650bc05ce9f93f48 100644 --- a/DataCompressor.iml +++ b/DataCompressor.iml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<module type="JAVA_MODULE" version="4"> +<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4"> <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_9"> <output url="file://$MODULE_DIR$/target/classes" /> <output-test url="file://$MODULE_DIR$/target/test-classes" /> @@ -10,10 +10,29 @@ </content> <orderEntry type="inheritedJdk" /> <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="library" name="Maven: org.jetbrains:annotations:18.0.0" level="project" /> <orderEntry type="library" name="Maven: org.apache.commons:commons-math3:3.6.1" level="project" /> - <orderEntry type="library" name="Maven: commons-io:commons-io:1.3.2" level="project" /> <orderEntry type="library" name="Maven: commons-cli:commons-cli:1.4" level="project" /> <orderEntry type="library" name="Maven: org.jetbrains:annotations:19.0.0" level="project" /> <orderEntry type="library" name="Maven: com.google.code.gson:gson:2.8.6" level="project" /> + <orderEntry type="library" name="Maven: io.scif:scifio:0.37.3" level="project" /> + <orderEntry type="library" name="Maven: io.scif:scifio-jai-imageio:1.1.1" level="project" /> + <orderEntry type="library" name="Maven: net.imagej:imagej-common:0.28.2" level="project" /> + <orderEntry type="library" name="Maven: net.imglib2:imglib2-roi:0.7.0" level="project" /> + <orderEntry type="library" name="Maven: net.imglib2:imglib2-realtransform:2.2.1" level="project" /> + <orderEntry type="library" name="Maven: gov.nist.math:jama:1.0.3" level="project" /> + <orderEntry type="library" name="Maven: jitk:jitk-tps:3.0.1" level="project" /> + <orderEntry type="library" name="Maven: com.googlecode.efficient-java-matrix-library:ejml:0.24" level="project" /> + <orderEntry type="library" name="Maven: log4j:log4j:1.2.17" level="project" /> + <orderEntry type="library" name="Maven: net.sf.trove4j:trove4j:3.0.3" level="project" /> + <orderEntry type="library" name="Maven: org.scijava:scijava-table:0.4.0" level="project" /> + <orderEntry type="library" name="Maven: edu.ucar:udunits:4.3.18" level="project" /> + <orderEntry type="library" name="Maven: net.imglib2:imglib2:5.6.3" level="project" /> + <orderEntry type="library" name="Maven: net.imglib2:imglib2-cache:1.0.0-beta-11" level="project" /> + <orderEntry type="library" name="Maven: com.github.ben-manes.caffeine:caffeine:2.4.0" level="project" /> + <orderEntry type="library" name="Maven: org.scijava:scijava-common:2.77.0" level="project" /> + <orderEntry type="library" name="Maven: org.scijava:parsington:1.0.4" level="project" /> + <orderEntry type="library" name="Maven: org.bushe:eventbus:1.4" level="project" /> + <orderEntry type="library" name="Maven: commons-io:commons-io:2.6" level="project" /> </component> </module> \ No newline at end of file diff --git a/README.md b/README.md index bf49431a6d48dfbe18393205df04363c2a3e6114..878298021f212468f381f2f5a5d82847820d4895 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # DataCompressor usage -**Right now only RAW 16-bit files are supported. Other types can be converted to 16-bit RAW file using [FIJI](https://imagej.net/Fiji).** +**This branch support loading of 16-bit input files via SCIFIO readers. RAW files will be loaded as before, +anything else will be loaded by SCIFIO.** Help output: ``` diff --git a/pom.xml b/pom.xml index 60740bc5d43e947600887169ed75e137133c5aad..61d82b0931b75145445e0769facdf39fb55e7ccd 100644 --- a/pom.xml +++ b/pom.xml @@ -5,8 +5,14 @@ <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.scijava</groupId> + <artifactId>pom-scijava</artifactId> + <version>27.0.1</version> + <relativePath/> + </parent> -<!-- <groupId>org.scijava</groupId>--> + <!-- <groupId>org.scijava</groupId>--> <groupId>org.azgra</groupId> <artifactId>DataCompressor</artifactId> <version>1.0-SNAPSHOT</version> @@ -23,6 +29,13 @@ </plugins> </build> + <repositories> + <repository> + <id>scijava.public</id> + <url>https://maven.scijava.org/content/groups/public</url> + </repository> + </repositories> + <dependencies> <dependency> <groupId>org.apache.commons</groupId> @@ -50,5 +63,14 @@ <artifactId>gson</artifactId> <version>2.8.6</version> </dependency> + <dependency> + <groupId>io.scif</groupId> + <artifactId>scifio</artifactId> + </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + <version>2.6</version> + </dependency> </dependencies> </project> \ No newline at end of file diff --git a/src/main/java/azgracompress/DataCompressor.java b/src/main/java/azgracompress/DataCompressor.java index 0d9e0b48b34702dc238732c09593b5de2f10bc20..8203c7be7eeb6f8f0fd6a8e3f83b53dc639f02c6 100644 --- a/src/main/java/azgracompress/DataCompressor.java +++ b/src/main/java/azgracompress/DataCompressor.java @@ -30,8 +30,10 @@ public class DataCompressor { } ParsedCliOptions parsedCliOptions = new ParsedCliOptions(cmd); + // NOTE(Moravec): From this point we need to dispose of possible existing SCIFIO context. if (parsedCliOptions.parseError()) { System.err.println(parsedCliOptions.getParseError()); + ScifioWrapper.dispose(); return; } @@ -46,26 +48,27 @@ public class DataCompressor { if (!compressor.compress()) { System.err.println("Errors occurred during compression."); } - return; } + break; case Decompress: { ImageDecompressor decompressor = new ImageDecompressor(parsedCliOptions); if (!decompressor.decompress()) { System.err.println("Errors occurred during decompression."); } - return; } + break; + case Benchmark: { CompressionBenchmark.runBenchmark(parsedCliOptions); - return; } + break; case TrainCodebook: { ImageCompressor compressor = new ImageCompressor(parsedCliOptions); if (!compressor.trainAndSaveCodebook()) { System.err.println("Errors occurred during training/saving of codebook."); } - return; } + break; case CustomFunction: { // NOTE(Moravec): Custom function class here | // V @@ -74,9 +77,8 @@ public class DataCompressor { if (!customFunction.run()) { System.err.println("Errors occurred during custom function."); } - return; - } + break; case PrintHelp: { formatter.printHelp(CliConstants.MAIN_HELP, options); @@ -91,9 +93,9 @@ public class DataCompressor { System.err.println(e.getMessage()); e.printStackTrace(); } - return; } + break; } - return; + ScifioWrapper.dispose(); } } diff --git a/src/main/java/azgracompress/ScifioWrapper.java b/src/main/java/azgracompress/ScifioWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..90041087c4ae02fcea557f1a6435f03122c9a8a4 --- /dev/null +++ b/src/main/java/azgracompress/ScifioWrapper.java @@ -0,0 +1,52 @@ +package azgracompress; + +import io.scif.FormatException; +import io.scif.Reader; +import io.scif.SCIFIO; + +import java.io.IOException; + +public class ScifioWrapper { + + private static ScifioWrapper instance = null; + private SCIFIO scifioInstance = null; + + private ScifioWrapper() { + scifioInstance = new SCIFIO(); + } + + public static SCIFIO getScifio() { + if (instance == null) { + synchronized (ScifioWrapper.class) { + if (instance == null) { + instance = new ScifioWrapper(); + } + } + } + + return instance.scifioInstance; + } + + /** + * Get image file reader. + * + * @param path Path of image file. + * @return Scifio reader. + * @throws IOException + * @throws FormatException + */ + public static Reader getReader(final String path) throws IOException, FormatException { + SCIFIO scifio = getScifio(); + return scifio.initializer().initializeReader(path); + } + + public synchronized static void dispose() { + if (instance != null) { + if (instance.scifioInstance != null) { + instance.scifioInstance.context().dispose(); + } + } + } + + +} diff --git a/src/main/java/azgracompress/benchmark/Benchmark.java b/src/main/java/azgracompress/benchmark/Benchmark.java index 21af7c41cb2158b9d90d250428acd0a06978d550..e4ba0f258da6235af7c50178b3b9ea33bd506a2a 100644 --- a/src/main/java/azgracompress/benchmark/Benchmark.java +++ b/src/main/java/azgracompress/benchmark/Benchmark.java @@ -1,15 +1,6 @@ package azgracompress.benchmark; -import azgracompress.U16; import azgracompress.cli.ParsedCliOptions; -import azgracompress.compression.ImageCompressor; -import azgracompress.compression.ImageDecompressor; -import azgracompress.data.ImageU16; -import azgracompress.io.RawDataIO; -import azgracompress.utilities.Utils; - -import java.io.File; -import java.io.IOException; public class Benchmark extends BenchmarkBase { @@ -21,88 +12,89 @@ public class Benchmark extends BenchmarkBase { @Override public void startBenchmark() { - ParsedCliOptions compressOps; - ParsedCliOptions decompressOps; - try { - compressOps = (ParsedCliOptions) options.clone(); - decompressOps = (ParsedCliOptions) options.clone(); - } catch (CloneNotSupportedException e) { - e.printStackTrace(); - return; - } - - boolean dirCreated = new File(options.getOutputFilePath()).mkdirs(); - //"%d_cb%d.raw.qcmp" - final String qcmpFilePath = getFileNamePathIntoOutDir(String.format(COMPRESSED_FILE_TEMPLATE, - options.getPlaneIndex(), - codebookSize)); - compressOps.setOutputFilePath(qcmpFilePath); - ImageCompressor compressor = new ImageCompressor(compressOps); - if (!compressor.compress()) { - System.err.println("Errors occurred during compression."); - return; - } - - decompressOps.setInputFilePath(qcmpFilePath); - - - final String decompressedFile = getFileNamePathIntoOutDir(String.format(QUANTIZED_FILE_TEMPLATE, - options.getPlaneIndex(), - codebookSize)); - - decompressOps.setOutputFilePath(decompressedFile); - ImageDecompressor decompressor = new ImageDecompressor(decompressOps); - if (!decompressor.decompress()) { - System.err.println("Errors occurred during decompression."); - } - - final int[] originalData; - try { - originalData = RawDataIO.loadImageU16(options.getInputFilePath(), - options.getImageDimension(), - options.getPlaneIndex()).getData(); - } catch (IOException e) { - e.printStackTrace(); - return; - } - final int[] quantizedData; - try { - quantizedData = RawDataIO.loadImageU16(decompressedFile, - options.getImageDimension().toV2i().toV3i(), 0).getData(); - } catch (IOException e) { - e.printStackTrace(); - return; - } - - final int[] diffArray = Utils.getDifference(originalData, quantizedData); - final double mse = Utils.calculateMse(diffArray); - final double PSNR = Utils.calculatePsnr(mse, U16.Max); - System.out.println(String.format("MSE: %.4f\tPSNR: %.4f(dB)", mse, PSNR)); - - final int[] absDifferenceData = Utils.asAbsoluteValues(diffArray); - - final String diffFilePath = getFileNamePathIntoOutDir(String.format(DIFFERENCE_FILE_TEMPLATE, - options.getPlaneIndex(), - codebookSize)); - - final String absDiffFilePath = getFileNamePathIntoOutDir(String.format(ABSOLUTE_DIFFERENCE_FILE_TEMPLATE, - options.getPlaneIndex(), - codebookSize)); - - ImageU16 img = new ImageU16(rawImageDims.getX(), - rawImageDims.getY(), - absDifferenceData); - try { - // NOTE(Moravec): Use little endian so that gnuplot can read the array. - RawDataIO.writeImageU16(absDiffFilePath, img, true); - System.out.println("Saved absolute difference to: " + absDiffFilePath); - - RawDataIO.writeDataI32(diffFilePath, diffArray, true); - System.out.println("Saved difference to: " + absDiffFilePath); - } catch (Exception e) { - e.printStackTrace(); - System.err.println("Failed to save difference."); - return; - } + // TODO(Moravec): Support PlaneLoader API. +// ParsedCliOptions compressOps; +// ParsedCliOptions decompressOps; +// try { +// compressOps = (ParsedCliOptions) options.clone(); +// decompressOps = (ParsedCliOptions) options.clone(); +// } catch (CloneNotSupportedException e) { +// e.printStackTrace(); +// return; +// } +// +// boolean dirCreated = new File(options.getOutputFilePath()).mkdirs(); +// //"%d_cb%d.raw.qcmp" +// final String qcmpFilePath = getFileNamePathIntoOutDir(String.format(COMPRESSED_FILE_TEMPLATE, +// options.getPlaneIndex(), +// codebookSize)); +// compressOps.setOutputFilePath(qcmpFilePath); +// ImageCompressor compressor = new ImageCompressor(compressOps); +// if (!compressor.compress()) { +// System.err.println("Errors occurred during compression."); +// return; +// } +// +// decompressOps.setInputFileInfo(new InputFileInfo(qcmpFilePath)); +// +// +// final String decompressedFile = getFileNamePathIntoOutDir(String.format(QUANTIZED_FILE_TEMPLATE, +// options.getPlaneIndex(), +// codebookSize)); +// +// decompressOps.setOutputFilePath(decompressedFile); +// ImageDecompressor decompressor = new ImageDecompressor(decompressOps); +// if (!decompressor.decompress()) { +// System.err.println("Errors occurred during decompression."); +// } +// +// final int[] originalData; +// try { +// originalData = RawDataIO.loadImageU16(options.getInputFilePath(), +// options.getImageDimension(), +// options.getPlaneIndex()).getData(); +// } catch (IOException e) { +// e.printStackTrace(); +// return; +// } +// final int[] quantizedData; +// try { +// quantizedData = RawDataIO.loadImageU16(decompressedFile, +// options.getImageDimension().toV2i().toV3i(), 0).getData(); +// } catch (IOException e) { +// e.printStackTrace(); +// return; +// } +// +// final int[] diffArray = Utils.getDifference(originalData, quantizedData); +// final double mse = Utils.calculateMse(diffArray); +// final double PSNR = Utils.calculatePsnr(mse, U16.Max); +// System.out.println(String.format("MSE: %.4f\tPSNR: %.4f(dB)", mse, PSNR)); +// +// final int[] absDifferenceData = Utils.asAbsoluteValues(diffArray); +// +// final String diffFilePath = getFileNamePathIntoOutDir(String.format(DIFFERENCE_FILE_TEMPLATE, +// options.getPlaneIndex(), +// codebookSize)); +// +// final String absDiffFilePath = getFileNamePathIntoOutDir(String.format(ABSOLUTE_DIFFERENCE_FILE_TEMPLATE, +// options.getPlaneIndex(), +// codebookSize)); +// +// ImageU16 img = new ImageU16(rawImageDims.getX(), +// rawImageDims.getY(), +// absDifferenceData); +// try { +// // NOTE(Moravec): Use little endian so that gnuplot can read the array. +// RawDataIO.writeImageU16(absDiffFilePath, img, true); +// System.out.println("Saved absolute difference to: " + absDiffFilePath); +// +// RawDataIO.writeDataI32(diffFilePath, diffArray, true); +// System.out.println("Saved difference to: " + absDiffFilePath); +// } catch (Exception e) { +// e.printStackTrace(); +// System.err.println("Failed to save difference."); +// return; +// } } } diff --git a/src/main/java/azgracompress/benchmark/BenchmarkBase.java b/src/main/java/azgracompress/benchmark/BenchmarkBase.java index 5d272e2f7bfe7c6b77c8421bf0757bcfeed995c9..5cc054afbcf829ee35ea96bd67223996531db75f 100644 --- a/src/main/java/azgracompress/benchmark/BenchmarkBase.java +++ b/src/main/java/azgracompress/benchmark/BenchmarkBase.java @@ -1,5 +1,6 @@ package azgracompress.benchmark; +import azgracompress.cli.InputFileInfo; import azgracompress.cli.ParsedCliOptions; import azgracompress.compression.Interval; import azgracompress.data.ImageU16; @@ -39,25 +40,29 @@ abstract class BenchmarkBase { protected BenchmarkBase(final ParsedCliOptions options) { this.options = options; - this.inputFile = options.getInputFilePath(); + + final InputFileInfo ifi = options.getInputFileInfo(); + this.inputFile = ifi.getFilePath(); this.outputDirectory = options.getOutputFilePath(); - this.rawImageDims = options.getImageDimension(); + + this.rawImageDims = ifi.getDimensions(); this.useMiddlePlane = options.shouldUseMiddlePlane(); + this.codebookSize = (int) Math.pow(2, options.getBitsPerCodebookIndex()); - if (options.isPlaneIndexSet()) { - this.planes = new int[]{options.getPlaneIndex()}; - } else if (options.isPlaneRangeSet()) { - final Interval<Integer> planeRange = options.getPlaneRange(); - final int from = planeRange.getFrom(); - final int count = planeRange.getInclusiveTo() - from; + if (ifi.isPlaneIndexSet()) { + this.planes = new int[]{ifi.getPlaneIndex()}; + } else if (ifi.isPlaneRangeSet()) { + final int from = ifi.getPlaneRange().getX(); + final int to = ifi.getPlaneRange().getY(); + final int count = to - from; this.planes = new int[count + 1]; for (int i = 0; i <= count; i++) { this.planes[i] = from + i; } } else { - final int planeCount = options.getImageDimension().getZ(); + final int planeCount = ifi.getDimensions().getZ(); this.planes = new int[planeCount]; for (int i = 0; i < planeCount; i++) { this.planes[i] = i; @@ -81,32 +86,32 @@ abstract class BenchmarkBase { return file.getAbsolutePath(); } - /** - * Load u16 plane from RAW file. - * - * @param planeIndex Zero based plane index. - * @return u16 plane. - */ - protected ImageU16 loadPlane(final int planeIndex) { - try { - return RawDataIO.loadImageU16(inputFile, rawImageDims, planeIndex); - } catch (Exception ex) { - ex.printStackTrace(); - } - return null; - } - - /** - * Load U16 plane data from RAW file. - * - * @param planeIndex Zero based plane index. - * @return U16 array of image plane data. - */ - protected int[] loadPlaneData(final int planeIndex) { - ImageU16 plane = loadPlane(planeIndex); - - return (plane != null) ? plane.getData() : new int[0]; - } + // /** + // * Load u16 plane from RAW file. + // * + // * @param planeIndex Zero based plane index. + // * @return u16 plane. + // */ + // protected ImageU16 loadPlane(final int planeIndex) { + // try { + // return RawDataIO.loadImageU16(inputFile, rawImageDims, planeIndex); + // } catch (Exception ex) { + // ex.printStackTrace(); + // } + // return null; + // } + + // /** + // * Load U16 plane data from RAW file. + // * + // * @param planeIndex Zero based plane index. + // * @return U16 array of image plane data. + // */ + // protected int[] loadPlaneData(final int planeIndex) { + // ImageU16 plane = loadPlane(planeIndex); + // + // return (plane != null) ? plane.getData() : new int[0]; + // } /** diff --git a/src/main/java/azgracompress/benchmark/SQBenchmark.java b/src/main/java/azgracompress/benchmark/SQBenchmark.java index 4850b367805f606519d3670898b0c31058a3c1cd..9a61ecdfaf8cc4571cfa87d1af2641109ae9dc7b 100644 --- a/src/main/java/azgracompress/benchmark/SQBenchmark.java +++ b/src/main/java/azgracompress/benchmark/SQBenchmark.java @@ -3,6 +3,8 @@ package azgracompress.benchmark; import azgracompress.U16; import azgracompress.cache.QuantizationCacheManager; import azgracompress.cli.ParsedCliOptions; +import azgracompress.io.IPlaneLoader; +import azgracompress.io.PlaneLoaderFactory; import azgracompress.quantization.QTrainIteration; import azgracompress.quantization.scalar.LloydMaxU16ScalarQuantization; import azgracompress.quantization.scalar.SQCodebook; @@ -22,12 +24,22 @@ public class SQBenchmark extends BenchmarkBase { @Override public void startBenchmark() { + IPlaneLoader planeLoader; + try { + planeLoader = PlaneLoaderFactory.getPlaneLoaderForInputFile(options.getInputFileInfo()); + } catch (Exception e) { + e.printStackTrace(); + System.err.println("Unable to create SCIFIO reader."); + return; + } + if (planes.length < 1) { return; } boolean dirCreated = new File(this.outputDirectory).mkdirs(); System.out.println(String.format("|CODEBOOK| = %d", codebookSize)); ScalarQuantizer quantizer = null; + if (hasCacheFolder) { System.out.println("Loading codebook from cache"); QuantizationCacheManager cacheManager = new QuantizationCacheManager(cacheFolder); @@ -42,8 +54,12 @@ public class SQBenchmark extends BenchmarkBase { System.out.println("Created quantizer from cache"); } else if (useMiddlePlane) { final int middlePlaneIndex = rawImageDims.getZ() / 2; - final int[] middlePlaneData = loadPlaneData(middlePlaneIndex); - if (middlePlaneData.length == 0) { + + final int[] middlePlaneData; + try { + middlePlaneData = planeLoader.loadPlaneU16(middlePlaneIndex).getData(); + } catch (IOException e) { + e.printStackTrace(); System.err.println("Failed to load middle plane data."); return; } @@ -54,7 +70,14 @@ public class SQBenchmark extends BenchmarkBase { for (final int planeIndex : planes) { System.out.println(String.format("Loading plane %d ...", planeIndex)); // NOTE(Moravec): Actual planeIndex is zero based. - final int[] planeData = loadPlaneData(planeIndex); + final int[] planeData; + try { + planeData = planeLoader.loadPlaneU16(planeIndex).getData(); + } catch (IOException e) { + e.printStackTrace(); + System.err.println("Failed to load plane data."); + return; + } if (planeData.length == 0) { System.err.println(String.format("Failed to load plane %d data. Skipping plane.", planeIndex)); return; @@ -63,8 +86,8 @@ public class SQBenchmark extends BenchmarkBase { final String quantizedFile = String.format(QUANTIZED_FILE_TEMPLATE, planeIndex, codebookSize); final String diffFile = String.format(DIFFERENCE_FILE_TEMPLATE, planeIndex, codebookSize); final String absoluteDiffFile = String.format(ABSOLUTE_DIFFERENCE_FILE_TEMPLATE, - planeIndex, - codebookSize); + planeIndex, + codebookSize); final String trainLogFile = String.format(TRAIN_FILE_TEMPLATE, planeIndex, codebookSize); if (!hasGeneralQuantizer) { diff --git a/src/main/java/azgracompress/benchmark/VQBenchmark.java b/src/main/java/azgracompress/benchmark/VQBenchmark.java index 463a90878cf3a472e8f98a436d8e7d6d9321d4a1..e827d6ef21268f92a058973e7d3a5fca33aab556 100644 --- a/src/main/java/azgracompress/benchmark/VQBenchmark.java +++ b/src/main/java/azgracompress/benchmark/VQBenchmark.java @@ -4,6 +4,8 @@ import azgracompress.U16; import azgracompress.cache.QuantizationCacheManager; import azgracompress.cli.ParsedCliOptions; import azgracompress.data.*; +import azgracompress.io.IPlaneLoader; +import azgracompress.io.PlaneLoaderFactory; import azgracompress.quantization.vector.LBGResult; import azgracompress.quantization.vector.LBGVectorQuantizer; import azgracompress.quantization.vector.VQCodebook; @@ -11,6 +13,7 @@ import azgracompress.quantization.vector.VectorQuantizer; import azgracompress.utilities.Utils; import java.io.File; +import java.io.IOException; public class VQBenchmark extends BenchmarkBase { @@ -46,6 +49,14 @@ public class VQBenchmark extends BenchmarkBase { if (planes.length < 1) { return; } + IPlaneLoader planeLoader; + try { + planeLoader = PlaneLoaderFactory.getPlaneLoaderForInputFile(options.getInputFileInfo()); + } catch (Exception e) { + e.printStackTrace(); + System.err.println("Unable to create SCIFIO reader."); + return; + } if (qVector.getY() > 1) { System.out.println("2D qVector"); } else { @@ -65,20 +76,23 @@ public class VQBenchmark extends BenchmarkBase { } quantizer = new VectorQuantizer(codebook); System.out.println("Created quantizer from cache"); + } else if (useMiddlePlane) { final int middlePlaneIndex = rawImageDims.getZ() / 2; - final ImageU16 middlePlane = loadPlane(middlePlaneIndex); - - if (middlePlane == null) { + final ImageU16 middlePlane; + try { + middlePlane = planeLoader.loadPlaneU16(middlePlaneIndex); + } catch (IOException e) { + e.printStackTrace(); System.err.println("Failed to load middle plane data."); return; } final int[][] refPlaneData = getPlaneVectors(middlePlane, qVector); LBGVectorQuantizer vqInitializer = new LBGVectorQuantizer(refPlaneData, - codebookSize, - workerCount, - qVector.toV3i()); + codebookSize, + workerCount, + qVector.toV3i()); final LBGResult vqResult = vqInitializer.findOptimalCodebook(); quantizer = new VectorQuantizer(vqResult.getCodebook()); System.out.println("Created quantizer from middle plane."); @@ -86,9 +100,12 @@ public class VQBenchmark extends BenchmarkBase { for (final int planeIndex : planes) { System.out.println(String.format("Loading plane %d ...", planeIndex)); - final ImageU16 plane = loadPlane(planeIndex); - if (plane == null) { + final ImageU16 plane; + try { + plane = planeLoader.loadPlaneU16(planeIndex); + } catch (IOException e) { + e.printStackTrace(); System.err.println(String.format("Failed to load plane %d data. Skipping plane.", planeIndex)); return; } @@ -98,9 +115,9 @@ public class VQBenchmark extends BenchmarkBase { if (!hasGeneralQuantizer) { LBGVectorQuantizer vqInitializer = new LBGVectorQuantizer(planeData, - codebookSize, - workerCount, - qVector.toV3i()); + codebookSize, + workerCount, + qVector.toV3i()); LBGResult vqResult = vqInitializer.findOptimalCodebook(); quantizer = new VectorQuantizer(vqResult.getCodebook()); System.out.println("Created plane quantizer."); @@ -109,8 +126,8 @@ public class VQBenchmark extends BenchmarkBase { final String quantizedFile = String.format(QUANTIZED_FILE_TEMPLATE, planeIndex, codebookSize); final String diffFile = String.format(DIFFERENCE_FILE_TEMPLATE, planeIndex, codebookSize); final String absoluteDiffFile = String.format(ABSOLUTE_DIFFERENCE_FILE_TEMPLATE, - planeIndex, - codebookSize); + planeIndex, + codebookSize); final int[][] quantizedData = quantizer.quantize(planeData, workerCount); diff --git a/src/main/java/azgracompress/cli/InputFileInfo.java b/src/main/java/azgracompress/cli/InputFileInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..b8a6f073bff769397fa234a23974069e698f1658 --- /dev/null +++ b/src/main/java/azgracompress/cli/InputFileInfo.java @@ -0,0 +1,89 @@ +package azgracompress.cli; + +import azgracompress.data.V2i; +import azgracompress.data.V3i; + +/** + * Information about the input file. + */ +public class InputFileInfo { + /** + * Input file path. + */ + private final String filePath; + + private boolean isRAW = true; + + private V3i dimension; + + private boolean planeIndexSet = false; + private int planeIndex; + + private boolean planeRangeSet = false; + private V2i planeRange; + + public InputFileInfo(final String filePath) { + this.filePath = filePath; + } + + /** + * Get number of selected planes to be compressed. + * + * @return Number of planes for compression. + */ + public int getNumberOfPlanes() { + if (planeIndexSet) { + return 1; + } else if (planeRangeSet) { + return ((planeRange.getY() + 1) - planeRange.getX()); + } else { + return dimension.getZ(); + } + } + + public void setDimension(final V3i dimension) { + this.dimension = dimension; + } + + public void setPlaneIndex(final int planeIndex) { + this.planeIndexSet = true; + this.planeIndex = planeIndex; + } + + public void setPlaneRange(final V2i planeRange) { + this.planeRangeSet = true; + this.planeRange = planeRange; + } + + public String getFilePath() { + return filePath; + } + + public V3i getDimensions() { + return dimension; + } + + public boolean isPlaneIndexSet() { + return planeIndexSet; + } + + public int getPlaneIndex() { + return planeIndex; + } + + public boolean isPlaneRangeSet() { + return planeRangeSet; + } + + public V2i getPlaneRange() { + return planeRange; + } + + public boolean isRAW() { + return isRAW; + } + + public void setIsRaw(boolean RAW) { + isRAW = RAW; + } +} diff --git a/src/main/java/azgracompress/cli/ParsedCliOptions.java b/src/main/java/azgracompress/cli/ParsedCliOptions.java index 28a3bdeebfbba1f43f621fc8649a75c32c4f830a..ece73127c01c4a043fcf6352fcb9bdb2da9df2e5 100644 --- a/src/main/java/azgracompress/cli/ParsedCliOptions.java +++ b/src/main/java/azgracompress/cli/ParsedCliOptions.java @@ -1,14 +1,20 @@ package azgracompress.cli; import azgracompress.compression.CompressionOptions; +import azgracompress.ScifioWrapper; import azgracompress.compression.CompressorDecompressorBase; -import azgracompress.compression.Interval; import azgracompress.data.V2i; import azgracompress.data.V3i; +import azgracompress.fileformat.FileExtensions; import azgracompress.fileformat.QuantizationType; +import io.scif.FormatException; +import io.scif.Plane; +import io.scif.Reader; import org.apache.commons.cli.CommandLine; +import org.apache.commons.io.FilenameUtils; import java.io.File; +import java.io.IOException; import java.nio.file.Paths; public class ParsedCliOptions extends CompressionOptions implements Cloneable { @@ -38,6 +44,13 @@ public class ParsedCliOptions extends CompressionOptions implements Cloneable { parseCLI(cmdInput); } + private String removeQCMPFileExtension(final String originalPath) { + if (originalPath.toUpperCase().endsWith(CompressorDecompressorBase.EXTENSION)) { + return originalPath.substring(0, originalPath.length() - CompressorDecompressorBase.EXTENSION.length()); + } + return originalPath; + } + /** * Creates default output file path depending on the chosen program method. * @@ -45,6 +58,7 @@ public class ParsedCliOptions extends CompressionOptions implements Cloneable { * @return Default ouput file path. */ private String getDefaultOutputFilePath(final String inputPath) { + // No default output file for custom function. if (method == ProgramMethod.CustomFunction) return ""; @@ -52,29 +66,35 @@ public class ParsedCliOptions extends CompressionOptions implements Cloneable { final File outputFile = new File(Paths.get("").toAbsolutePath().toString(), inputFile.getName()); + // Default value is current directory with input file name. String defaultValue = outputFile.getAbsolutePath(); switch (method) { case Compress: { + // Add compressed file extension. defaultValue += CompressorDecompressorBase.EXTENSION; } break; case Decompress: { - if (defaultValue.toUpperCase().endsWith(CompressorDecompressorBase.EXTENSION)) { - defaultValue = defaultValue.substring(0, - defaultValue.length() - CompressorDecompressorBase.EXTENSION.length()); - } + // If it ends with QCMP file extension remove the extension. + defaultValue = removeQCMPFileExtension(defaultValue); + // Remove the old extension and add RAW extension + defaultValue = defaultValue.replace(FilenameUtils.getExtension(defaultValue), + CompressorDecompressorBase.RAW_EXTENSION_NO_DOT); + } break; case Benchmark: { defaultValue = new File(inputFile.getParent(), "benchmark").getAbsolutePath(); } break; - case PrintHelp: - break; case InspectFile: defaultValue += ".txt"; break; + case TrainCodebook: + case PrintHelp: + case CustomFunction: + break; } return defaultValue; @@ -117,68 +137,167 @@ public class ParsedCliOptions extends CompressionOptions implements Cloneable { setCodebookCacheFolder(cmd.getOptionValue(CliConstants.CODEBOOK_CACHE_FOLDER_LONG, null)); if (!parseErrorOccurred) { - setOutputFilePath(cmd.getOptionValue(CliConstants.OUTPUT_LONG, getDefaultOutputFilePath(getInputFilePath()))); + setOutputFilePath(cmd.getOptionValue(CliConstants.OUTPUT_LONG, getDefaultOutputFilePath(getInputFileInfo().getFilePath()))); + setCodebookCacheFolder(cmd.getOptionValue(CliConstants.CODEBOOK_CACHE_FOLDER_LONG, null)); } parseError = errorBuilder.toString(); } + /** * Parse input file info from command line arguments. * - * @param errorBuilder String error builder. - * @param fileInfo Input file info strings. + * @param errorBuilder String error builder. + * @param inputFileArguments Input file info strings. */ - private void parseInputFilePart(StringBuilder errorBuilder, final String[] fileInfo) { + + private void parseInputFilePart(StringBuilder errorBuilder, final String[] inputFileArguments) { + + if (inputFileArguments.length < 1) { + parseErrorOccurred = true; + errorBuilder.append("Missing input file option"); + return; + } + + setInputFileInfo(new InputFileInfo(inputFileArguments[0])); + + // Decompress and Inspect methods doesn't require additional file information. if ((method == ProgramMethod.Decompress) || (method == ProgramMethod.InspectFile)) { - if (fileInfo.length > 0) { - setInputFilePath(fileInfo[0]); - } else { - parseErrorOccurred = true; - errorBuilder.append("Missing input file for decompression"); - } + return; + } + + // Check if input file exists. + if (!new File(getInputFileInfo().getFilePath()).exists()) { + parseErrorOccurred = true; + errorBuilder.append("Input file doesn't exist.\n"); + return; + } + + final String extension = FilenameUtils.getExtension(inputFileArguments[0]).toLowerCase(); + if (FileExtensions.RAW.equals(extension)) { + parseRawFileArguments(errorBuilder, inputFileArguments); } else { - // Compression part. + // Default loading through SCIFIO. + parseSCIFIOFileArguments(errorBuilder, inputFileArguments); + } + } - // We require the file path and dimensions, like input.raw 1920x1080x5 - if (fileInfo.length < 2) { - if (method == ProgramMethod.CustomFunction) { - return; - } + + private void parseSCIFIOFileArguments(StringBuilder errorBuilder, final String[] inputFileArguments) { + + getInputFileInfo().setIsRaw(false); + Reader reader; + try { + reader = ScifioWrapper.getReader(getInputFileInfo().getFilePath()); + } catch (IOException | FormatException e) { + parseErrorOccurred = true; + errorBuilder.append("Failed to get SCIFIO reader for file.\n"); + errorBuilder.append(e.getMessage()); + return; + } + + final int imageCount = reader.getImageCount(); + if (imageCount != 1) { + parseErrorOccurred = true; + errorBuilder.append("We are currently not supporting files with multiple images.\n"); + return; + } + + final long planeCount = reader.getPlaneCount(0); + if (planeCount > (long) Integer.MAX_VALUE) { + parseErrorOccurred = true; + errorBuilder.append("Too many planes.\n"); + } + + long planeWidth, planeHeight; + try { + Plane plane = reader.openPlane(0, 0); + planeWidth = plane.getLengths()[0]; + planeHeight = plane.getLengths()[1]; + + if ((planeWidth > (long) Integer.MAX_VALUE) || + (planeHeight > (long) Integer.MAX_VALUE)) { parseErrorOccurred = true; - errorBuilder.append("Both filepath and file dimensions are required arguments\n"); - } else { - // The first string must be file path. - setInputFilePath(fileInfo[0]); + errorBuilder.append("We are currently supporting planes with maximum size of Integer.MAX_VALUE x Integer.MAX_VALUE"); + } - parseImageDims(fileInfo[1], errorBuilder); - if (fileInfo.length > 2) { + } catch (FormatException | IOException e) { + parseErrorOccurred = true; + errorBuilder.append("Unable to open first plane of the first image.\n") + .append(e.getMessage()); + return; + } - int rangeSepIndex = fileInfo[2].indexOf("-"); - if (rangeSepIndex != -1) { - final String fromIndexString = fileInfo[2].substring(0, rangeSepIndex); - final String toIndexString = fileInfo[2].substring(rangeSepIndex + 1); - final ParseResult<Integer> indexFromResult = tryParseInt(fromIndexString); - final ParseResult<Integer> indexToResult = tryParseInt(toIndexString); + getInputFileInfo().setDimension(new V3i( + (int) planeWidth, + (int) planeHeight, + (int) planeCount + )); - if (indexFromResult.isSuccess() && indexToResult.isSuccess()) { - setPlaneRange(new Interval<>(indexFromResult.getValue(), indexToResult.getValue())); - } else { - parseErrorOccurred = true; - errorBuilder.append("Plane range index is wrong. Expected format D-D, got: ").append( - fileInfo[2]).append('\n'); - } - } else { - final ParseResult<Integer> parseResult = tryParseInt(fileInfo[2]); - if (parseResult.isSuccess()) { - setPlaneIndex(parseResult.getValue()); - } else { - parseErrorOccurred = true; - errorBuilder.append("The second argument after file name must be plane index\n"); - } - } - } + if (inputFileArguments.length > 1) { + parseInputFilePlaneOptions(errorBuilder, inputFileArguments, 1); + } + } + + private void parseRawFileArguments(StringBuilder errorBuilder, String[] inputFileArguments) { + // We require the file path and dimensions, like input.raw 1920x1080x5 + // First argument is input file name. + if (inputFileArguments.length < 2) { + parseErrorOccurred = true; + errorBuilder.append("Raw file requires its dimension as additional information.") + .append("e.g.: 1920x1080x1\n"); + return; + } + + getInputFileInfo().setIsRaw(true); + parseImageDims(inputFileArguments[1], errorBuilder); + + // User specified plane index or plane range. + if (inputFileArguments.length > 2) { + parseInputFilePlaneOptions(errorBuilder, inputFileArguments, 2); + } + } + + /** + * Parse optional user specified plane index or plane range. (e.g. 5 or 5-50) + * + * @param errorBuilder String builder for the error message. + * @param inputFileArguments Input file arguments. + * @param inputFileArgumentsOffset Offset of the plane argument. + */ + private void parseInputFilePlaneOptions(StringBuilder errorBuilder, + final String[] inputFileArguments, + final int inputFileArgumentsOffset) { + int rangeSeparatorIndex = inputFileArguments[inputFileArgumentsOffset].indexOf("-"); + if (rangeSeparatorIndex != -1) { +// Here we parse the plane range option. + final String fromIndexString = + inputFileArguments[inputFileArgumentsOffset].substring(0, rangeSeparatorIndex); + final String toIndexString = + inputFileArguments[inputFileArgumentsOffset].substring(rangeSeparatorIndex + 1); + + final ParseResult<Integer> indexFromResult = tryParseInt(fromIndexString); + final ParseResult<Integer> indexToResult = tryParseInt(toIndexString); + + if (indexFromResult.isSuccess() && indexToResult.isSuccess()) { + getInputFileInfo().setPlaneRange(new V2i(indexFromResult.getValue(), indexToResult.getValue())); + } else { + parseErrorOccurred = true; + errorBuilder.append("Plane range index is wrong. Expected format D-D, got: ").append( + inputFileArguments[inputFileArgumentsOffset]).append('\n'); + } + } else { +// Here we parse single plane index option. + final ParseResult<Integer> parseResult = tryParseInt(inputFileArguments[inputFileArgumentsOffset]); + if (parseResult.isSuccess()) { + getInputFileInfo().setPlaneIndex(parseResult.getValue()); + } else { + parseErrorOccurred = true; + errorBuilder.append("Failed to parse plane index option, expected integer, got: ") + .append(inputFileArguments[inputFileArgumentsOffset]) + .append('\n'); } } } @@ -192,43 +311,42 @@ public class ParsedCliOptions extends CompressionOptions implements Cloneable { private void parseImageDims(final String dimsString, StringBuilder errorBuilder) { // We thing of 3x3x1 and 3x3 as the same thing - final int firstDelimIndex = dimsString.indexOf('x'); - if (firstDelimIndex == -1) { + final int firstDelimiterIndex = dimsString.indexOf('x'); + if (firstDelimiterIndex == -1) { parseErrorOccurred = true; errorBuilder.append("Error parsing image dimensions. We require DxDxD or DxD [=DxDx1]\n"); return; } - final String num1String = dimsString.substring(0, firstDelimIndex); - final String secondPart = dimsString.substring(firstDelimIndex + 1); + final String num1String = dimsString.substring(0, firstDelimiterIndex); + final String secondPart = dimsString.substring(firstDelimiterIndex + 1); - final int secondDelimIndex = secondPart.indexOf('x'); - if (secondDelimIndex == -1) { + final int secondDelimiterIndex = secondPart.indexOf('x'); + if (secondDelimiterIndex == -1) { final ParseResult<Integer> n1Result = tryParseInt(num1String); final ParseResult<Integer> n2Result = tryParseInt(secondPart); if (n1Result.isSuccess() && n2Result.isSuccess()) { - setImageDimension(new V3i(n1Result.getValue(), n2Result.getValue(), 1)); + getInputFileInfo().setDimension(new V3i(n1Result.getValue(), n2Result.getValue(), 1)); } else { parseErrorOccurred = true; errorBuilder.append("Failed to parse image dimensions of format DxD, got: "); errorBuilder.append(String.format("%sx%s\n", num1String, secondPart)); } } else { - final String num2String = secondPart.substring(0, secondDelimIndex); - final String num3String = secondPart.substring(secondDelimIndex + 1); + final String num2String = secondPart.substring(0, secondDelimiterIndex); + final String num3String = secondPart.substring(secondDelimiterIndex + 1); final ParseResult<Integer> n1Result = tryParseInt(num1String); final ParseResult<Integer> n2Result = tryParseInt(num2String); final ParseResult<Integer> n3Result = tryParseInt(num3String); if (n1Result.isSuccess() && n2Result.isSuccess() && n3Result.isSuccess()) { - setImageDimension(new V3i(n1Result.getValue(), n2Result.getValue(), n3Result.getValue())); + getInputFileInfo().setDimension(new V3i(n1Result.getValue(), n2Result.getValue(), n3Result.getValue())); } else { parseErrorOccurred = true; errorBuilder.append("Failed to parse image dimensions of format DxDxD, got: "); errorBuilder.append(String.format("%sx%sx%s\n", num1String, num2String, num3String)); } } - } /** @@ -267,7 +385,8 @@ public class ParsedCliOptions extends CompressionOptions implements Cloneable { /** * Parse compression type and vector dimensions for VQ. - * @param cmd Command line arguments. + * + * @param cmd Command line arguments. * @param errorBuilder String error builder. */ private void parseCompressionType(CommandLine cmd, StringBuilder errorBuilder) { @@ -326,7 +445,8 @@ public class ParsedCliOptions extends CompressionOptions implements Cloneable { /** * Parse chosen program method. - * @param cmd Command line arguments. + * + * @param cmd Command line arguments. * @param errorBuilder String error builder. */ private void parseProgramMethod(CommandLine cmd, StringBuilder errorBuilder) { @@ -352,6 +472,7 @@ public class ParsedCliOptions extends CompressionOptions implements Cloneable { /** * Try to parse int from string. + * * @param string Possible integer value. * @return Parse result. */ @@ -376,10 +497,6 @@ public class ParsedCliOptions extends CompressionOptions implements Cloneable { return parseError; } - public boolean hasCodebookCacheFolder() { - return (getCodebookCacheFolder() != null); - } - public String report() { StringBuilder sb = new StringBuilder(); @@ -425,7 +542,7 @@ public class ParsedCliOptions extends CompressionOptions implements Cloneable { } - sb.append("InputFile: ").append(getInputFilePath()).append('\n'); + sb.append("InputFile: ").append(getInputFileInfo().getFilePath()).append('\n'); sb.append("Output: ").append(getOutputFilePath()).append('\n'); sb.append("BitsPerCodebookIndex: ").append(getBitsPerCodebookIndex()).append('\n'); if (hasCodebookCacheFolder()) { @@ -433,18 +550,20 @@ public class ParsedCliOptions extends CompressionOptions implements Cloneable { } if (hasQuantizationType(method)) { - sb.append("Input image dims: ").append(getImageDimension().toString()).append('\n'); - } - if (getPlaneIndex() != null) { - sb.append("PlaneIndex: ").append(getPlaneIndex()).append('\n'); + sb.append("Input image dims: ").append(getInputFileInfo().getDimensions().toString()).append('\n'); } + if (getInputFileInfo().isPlaneIndexSet()) { + sb.append("PlaneIndex: ").append(getInputFileInfo().getPlaneIndex()).append('\n'); + } + if (shouldUseMiddlePlane()) { sb.append("Use middle plane for codebook training\n"); } - if (isPlaneRangeSet()) { - sb.append("FromPlaneIndex: ").append(getPlaneRange().getFrom()).append('\n'); - sb.append("ToPlaneIndex: ").append(getPlaneRange().getInclusiveTo()).append('\n'); + + if (getInputFileInfo().isPlaneRangeSet()) { + sb.append("FromPlaneIndex: ").append(getInputFileInfo().getPlaneRange().getX()).append('\n'); + sb.append("ToPlaneIndex: ").append(getInputFileInfo().getPlaneRange().getY()).append('\n'); } sb.append("Verbose: ").append(isVerbose()).append('\n'); diff --git a/src/main/java/azgracompress/cli/functions/EntropyCalculation.java b/src/main/java/azgracompress/cli/functions/EntropyCalculation.java index c7285b01a2a9f6e5cdf200f67fb4b6bf67cedcb7..1528a43e0db4d98abf72c56671ed7adec9b298fe 100644 --- a/src/main/java/azgracompress/cli/functions/EntropyCalculation.java +++ b/src/main/java/azgracompress/cli/functions/EntropyCalculation.java @@ -20,22 +20,24 @@ public class EntropyCalculation extends CustomFunctionBase { @Override public boolean run() { - ImageU16 plane = null; - System.out.println(String.format("Input file: %s", options.getInputFilePath())); - - for (int planeIndex = 0; planeIndex < options.getImageDimension().getZ(); planeIndex++) { - try { - plane = RawDataIO.loadImageU16(options.getInputFilePath(), - options.getImageDimension(), - planeIndex); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - final double planeEntropy = Utils.calculateEntropy(plane.getData()); - - System.out.println(String.format("%d\t%.4f", planeIndex, planeEntropy)); - } - return true; + // TODO(Moravec): Support PlaneLoader API. + return false; +// ImageU16 plane = null; +// System.out.println(String.format("Input file: %s", options.getInputFilePath())); +// +// for (int planeIndex = 0; planeIndex < options.getImageDimension().getZ(); planeIndex++) { +// try { +// plane = RawDataIO.loadImageU16(options.getInputFilePath(), +// options.getImageDimension(), +// planeIndex); +// } catch (IOException e) { +// e.printStackTrace(); +// return false; +// } +// final double planeEntropy = Utils.calculateEntropy(plane.getData()); +// +// System.out.println(String.format("%d\t%.4f", planeIndex, planeEntropy)); +// } +// return true; } } diff --git a/src/main/java/azgracompress/cli/functions/MeasurePlaneErrorFunction.java b/src/main/java/azgracompress/cli/functions/MeasurePlaneErrorFunction.java index cdc809747db281d71d29ba667c271752fc479a2d..6178a818d1ee266ab80b642bd07397a9e29510c2 100644 --- a/src/main/java/azgracompress/cli/functions/MeasurePlaneErrorFunction.java +++ b/src/main/java/azgracompress/cli/functions/MeasurePlaneErrorFunction.java @@ -1,10 +1,11 @@ package azgracompress.cli.functions; import azgracompress.cli.CustomFunctionBase; +import azgracompress.cli.InputFileInfo; import azgracompress.cli.ParsedCliOptions; import azgracompress.data.ImageU16; import azgracompress.data.V3i; -import azgracompress.io.RawDataIO; +import azgracompress.io.RawDataLoader; import azgracompress.utilities.Utils; import java.io.FileOutputStream; @@ -26,13 +27,13 @@ public class MeasurePlaneErrorFunction extends CustomFunctionBase { boolean result = true; - result &= runPlaneDifferenceForAllBits(0, "sq", "middle_frame", "D:\\biology\\tiff_data\\quantized"); - result &= runPlaneDifferenceForAllBits(0, "vq3x3", "middle_frame", "D:\\biology\\tiff_data\\quantized"); - result &= runPlaneDifferenceForAllBits(0, "vq9x1", "middle_frame", "D:\\biology\\tiff_data\\quantized"); + result &= runPlaneDifferenceForAllBits(0, "sq", "file_codebook", "D:\\biology\\tiff_data\\quantized"); + result &= runPlaneDifferenceForAllBits(0, "vq3x3", "file_codebook", "D:\\biology\\tiff_data\\quantized"); + result &= runPlaneDifferenceForAllBits(0, "vq9x1", "file_codebook", "D:\\biology\\tiff_data\\quantized"); -// result &= runPlaneDifferenceForAllBits(1, "sq", "middle_frame", "D:\\biology\\tiff_data\\quantized"); - result &= runPlaneDifferenceForAllBits(1, "vq3x3", "middle_frame", "D:\\biology\\tiff_data\\quantized"); - result &= runPlaneDifferenceForAllBits(1, "vq9x1", "middle_frame", "D:\\biology\\tiff_data\\quantized"); + result &= runPlaneDifferenceForAllBits(1, "sq", "file_codebook", "D:\\biology\\tiff_data\\quantized"); + result &= runPlaneDifferenceForAllBits(1, "vq3x3", "file_codebook", "D:\\biology\\tiff_data\\quantized"); + result &= runPlaneDifferenceForAllBits(1, "vq9x1", "file_codebook", "D:\\biology\\tiff_data\\quantized"); // result &= reportPlaneDifference( // String.format("%s\\%s\\fused_tp_10_ch_%d_16bit_%s_cb4.raw", @@ -118,13 +119,20 @@ public class MeasurePlaneErrorFunction extends CustomFunctionBase { } private boolean reportPlaneDifference(final String compressedFile, final String reportFile, final String compFile) { - final String referenceFile = compFile; final int workerCount = 8; final V3i dims = new V3i(1041, 996, 946); final int planePixelCount = dims.getX() * dims.getY(); PlaneError[] planeErrors = new PlaneError[dims.getZ()]; + InputFileInfo refFileInfo = new InputFileInfo(compFile); + refFileInfo.setDimension(dims); + InputFileInfo compFileInfo = new InputFileInfo(compressedFile); + compFileInfo.setDimension(dims); + + final RawDataLoader refPlaneloader = new RawDataLoader(refFileInfo); + final RawDataLoader compPlaneloader = new RawDataLoader(compFileInfo); + Thread[] workers = new Thread[workerCount]; final int workSize = dims.getZ() / workerCount; @@ -137,8 +145,8 @@ public class MeasurePlaneErrorFunction extends CustomFunctionBase { ImageU16 originalPlane, compressedPlane, differencePlane; for (int planeIndex = fromIndex; planeIndex < toIndex; planeIndex++) { try { - originalPlane = RawDataIO.loadImageU16(referenceFile, dims, planeIndex); - compressedPlane = RawDataIO.loadImageU16(compressedFile, dims, planeIndex); + originalPlane = refPlaneloader.loadPlaneU16(planeIndex); + compressedPlane = compPlaneloader.loadPlaneU16(planeIndex); } catch (IOException e) { e.printStackTrace(); break; @@ -183,4 +191,47 @@ public class MeasurePlaneErrorFunction extends CustomFunctionBase { System.out.println("Finished reportPlaneDifference"); return true; } + + /* + final String[] templates = new String[]{ + "D:\\biology\\benchmark\\jpeg_comp\\jpeg2000\\ch0_400_cr%d.%s", + "D:\\biology\\benchmark\\jpeg_comp\\jpeg2000\\ch1_683_cr%d.%s" + }; + final String[] referenceFiles = new String[]{ + "D:\\biology\\benchmark\\jpeg_comp\\ch0_400.raw", + "D:\\biology\\benchmark\\jpeg_comp\\ch1_683.raw" + }; + final int[] CRS = new int[]{1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30}; + try { + + for (int i = 0; i < templates.length; i++) { + final String refFile = referenceFiles[i]; + final int[] refData = loadImageData(refFile); + + for (final int cr : CRS) { + final String inputFile = String.format(templates[i], cr, "raw"); + + final int[] imageData = loadImageData(inputFile); + + final int[] diff = Utils.getDifference(refData, imageData); + final double mse = Utils.calculateMse(diff); + + final double psnr = Utils.calculatePsnr(mse, U16.Max); + + final String channel = new File(inputFile).getName().substring(0, 3); + DecimalFormat df = new DecimalFormat("#.####"); + + System.out.println(String.format("%s CR: %d\n\tMSE: %s\n\tPSNR: %s\n", + channel, cr, df.format(mse), df.format(psnr))); + } + } + + } catch (Exception e) { + e.printStackTrace(); + } finally { + ScifioWrapper.dispose(); + } + * */ + + } diff --git a/src/main/java/azgracompress/compression/CompressionOptions.java b/src/main/java/azgracompress/compression/CompressionOptions.java index ac772956d1c576ac70877c252ce6a5e9d716f6f3..48d502b72c1a268daf3bd23c9f418d2a22a9e70f 100644 --- a/src/main/java/azgracompress/compression/CompressionOptions.java +++ b/src/main/java/azgracompress/compression/CompressionOptions.java @@ -1,5 +1,6 @@ package azgracompress.compression; +import azgracompress.cli.InputFileInfo; import azgracompress.data.V2i; import azgracompress.data.V3i; import azgracompress.fileformat.QuantizationType; @@ -11,7 +12,7 @@ public class CompressionOptions { /** * Input image or compressed file. */ - private String inputFilePath; + private InputFileInfo inputFileInfo; /** * Output image or compressed file. @@ -91,12 +92,12 @@ public class CompressionOptions { return verbose; } - public String getInputFilePath() { - return inputFilePath; + public InputFileInfo getInputFileInfo() { + return inputFileInfo; } - public void setInputFilePath(String inputFilePath) { - this.inputFilePath = inputFilePath; + public void setInputFileInfo(InputFileInfo ifi) { + this.inputFileInfo = ifi; } public String getOutputFilePath() { diff --git a/src/main/java/azgracompress/compression/CompressorDecompressorBase.java b/src/main/java/azgracompress/compression/CompressorDecompressorBase.java index 896222dd24305a7b36f70e7baa27df5756d99bb1..0aab879ba9e82680abaa750efa5467d7d462b51e 100644 --- a/src/main/java/azgracompress/compression/CompressorDecompressorBase.java +++ b/src/main/java/azgracompress/compression/CompressorDecompressorBase.java @@ -1,5 +1,6 @@ package azgracompress.compression; +import azgracompress.cli.InputFileInfo; import azgracompress.cli.ParsedCliOptions; import azgracompress.compression.exception.ImageCompressionException; import azgracompress.huffman.Huffman; @@ -10,6 +11,7 @@ import java.io.DataOutputStream; public abstract class CompressorDecompressorBase { public static final int LONG_BYTES = 8; public static final String EXTENSION = ".QCMP"; + public static final String RAW_EXTENSION_NO_DOT = "raw"; protected final CompressionOptions options; private final int codebookSize; @@ -48,11 +50,14 @@ public abstract class CompressorDecompressorBase { } protected int[] getPlaneIndicesForCompression() { - if (options.isPlaneIndexSet()) { - return new int[]{options.getPlaneIndex()}; - } else if (options.isPlaneRangeSet()) { - final int from = options.getPlaneRange().getFrom(); - final int count = options.getPlaneRange().getInclusiveTo() - from; + + final InputFileInfo ifi = options.getInputFileInfo(); + if (ifi.isPlaneIndexSet()) { + return new int[]{ifi.getPlaneIndex()}; + } else if (ifi.isPlaneRangeSet()) { + final int from = ifi.getPlaneRange().getX(); + final int to = ifi.getPlaneRange().getY(); + final int count = to - from; int[] indices = new int[count + 1]; for (int i = 0; i <= count; i++) { @@ -60,7 +65,7 @@ public abstract class CompressorDecompressorBase { } return indices; } else { - return generateAllPlaneIndices(options.getImageDimension().getZ()); + return generateAllPlaneIndices(ifi.getDimensions().getZ()); } } @@ -101,7 +106,7 @@ public abstract class CompressorDecompressorBase { * @return Index of the middle plane. */ protected int getMiddlePlaneIndex() { - return (options.getImageDimension().getZ() / 2); + return (options.getInputFileInfo().getDimensions().getZ() / 2); } /** diff --git a/src/main/java/azgracompress/compression/ImageCompressor.java b/src/main/java/azgracompress/compression/ImageCompressor.java index 82676671154df6f810a8313c721c01c8e710b9d3..bff5a843fa0b7dc672777fe62c2dc0ce2a63e27f 100644 --- a/src/main/java/azgracompress/compression/ImageCompressor.java +++ b/src/main/java/azgracompress/compression/ImageCompressor.java @@ -145,9 +145,9 @@ public class ImageCompressor extends CompressorDecompressorBase { final boolean oneCodebook = options.shouldUseMiddlePlane() || options.hasCodebookCacheFolder(); header.setCodebookPerPlane(!oneCodebook); - header.setImageSizeX(options.getImageDimension().getX()); - header.setImageSizeY(options.getImageDimension().getY()); - header.setImageSizeZ(getNumberOfPlanes()); + header.setImageSizeX(options.getInputFileInfo().getDimensions().getX()); + header.setImageSizeY(options.getInputFileInfo().getDimensions().getY()); + header.setImageSizeZ(options.getInputFileInfo().getNumberOfPlanes()); header.setVectorDimension(options.getVectorDimension()); diff --git a/src/main/java/azgracompress/compression/ImageDecompressor.java b/src/main/java/azgracompress/compression/ImageDecompressor.java index ded4d46557e7069933cdf0339f9693d2e0886450..359679a07c8bd391e9d4e7fe4f5b3d94c9b3e9e6 100644 --- a/src/main/java/azgracompress/compression/ImageDecompressor.java +++ b/src/main/java/azgracompress/compression/ImageDecompressor.java @@ -61,7 +61,7 @@ public class ImageDecompressor extends CompressorDecompressorBase { boolean validFile = true; QCMPFileHeader header = null; - try (FileInputStream fileInputStream = new FileInputStream(options.getInputFilePath()); + try (FileInputStream fileInputStream = new FileInputStream(options.getInputFileInfo().getFilePath()); DataInputStream dataInputStream = new DataInputStream(fileInputStream)) { header = readQCMPFileHeader(dataInputStream); } catch (IOException ioEx) { @@ -113,10 +113,9 @@ public class ImageDecompressor extends CompressorDecompressorBase { 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 fileSize = new File(options.getInputFilePath()).length(); final long headerSize = header.getHeaderSize(); - final long dataSize = fileSize - headerSize; - + final long fileSize = new File(options.getInputFileInfo().getFilePath()).length(); + final long dataSize = fileSize - header.getHeaderSize(); final IImageDecompressor decompressor = getImageDecompressor(header); @@ -164,10 +163,9 @@ public class ImageDecompressor extends CompressorDecompressorBase { } public boolean decompress() { - final Stopwatch decompressionStopwatch = Stopwatch.startNew(); final long decompressedFileSize; - try (FileInputStream fileInputStream = new FileInputStream(options.getInputFilePath()); + try (FileInputStream fileInputStream = new FileInputStream(options.getInputFileInfo().getFilePath()); DataInputStream dataInputStream = new DataInputStream(fileInputStream)) { final QCMPFileHeader header = readQCMPFileHeader(dataInputStream); @@ -187,7 +185,7 @@ public class ImageDecompressor extends CompressorDecompressorBase { return false; } - final long fileSize = new File(options.getInputFilePath()).length(); + final long fileSize = new File(options.getInputFileInfo().getFilePath()).length(); final long dataSize = fileSize - header.getHeaderSize(); final long expectedDataSize = imageDecompressor.getExpectedDataSize(header); if (dataSize != expectedDataSize) { diff --git a/src/main/java/azgracompress/compression/SQImageCompressor.java b/src/main/java/azgracompress/compression/SQImageCompressor.java index a73f3a9542431905076deb24bd918c8b84df77fe..2ce6e59b4798d0b1fa7008e9ae0c397f4fb73c30 100644 --- a/src/main/java/azgracompress/compression/SQImageCompressor.java +++ b/src/main/java/azgracompress/compression/SQImageCompressor.java @@ -2,10 +2,13 @@ package azgracompress.compression; import azgracompress.U16; import azgracompress.cache.QuantizationCacheManager; +import azgracompress.cli.InputFileInfo; import azgracompress.cli.ParsedCliOptions; import azgracompress.compression.exception.ImageCompressionException; import azgracompress.data.ImageU16; import azgracompress.huffman.Huffman; +import azgracompress.io.IPlaneLoader; +import azgracompress.io.PlaneLoaderFactory; import azgracompress.io.RawDataIO; import azgracompress.quantization.scalar.LloydMaxU16ScalarQuantization; import azgracompress.quantization.scalar.SQCodebook; @@ -72,7 +75,8 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm private ScalarQuantizer loadQuantizerFromCache() throws ImageCompressionException { QuantizationCacheManager cacheManager = new QuantizationCacheManager(options.getCodebookCacheFolder()); - final SQCodebook codebook = cacheManager.loadSQCodebook(options.getInputFilePath(), getCodebookSize()); + final SQCodebook codebook = cacheManager.loadSQCodebook(options.getInputFileInfo().getFilePath(), + getCodebookSize()); if (codebook == null) { throw new ImageCompressionException("Failed to read quantization values from cache file."); } @@ -86,8 +90,17 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm * @throws ImageCompressionException When compress process fails. */ public long[] compress(DataOutputStream compressStream) throws ImageCompressionException { + final InputFileInfo inputFileInfo = options.getInputFileInfo(); Stopwatch stopwatch = new Stopwatch(); final boolean hasGeneralQuantizer = options.hasCodebookCacheFolder() || options.shouldUseMiddlePlane(); + + final IPlaneLoader planeLoader; + try { + planeLoader = PlaneLoaderFactory.getPlaneLoaderForInputFile(inputFileInfo); + } catch (Exception e) { + throw new ImageCompressionException("Unable to create SCIFIO reader. " + e.getMessage()); + } + ScalarQuantizer quantizer = null; Huffman huffman = null; final int[] huffmanSymbols = createHuffmanSymbols(getCodebookSize()); @@ -104,11 +117,10 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm ImageU16 middlePlane = null; final int middlePlaneIndex = getMiddlePlaneIndex(); try { - middlePlane = RawDataIO.loadImageU16(options.getInputFilePath(), - options.getImageDimension(), - getMiddlePlaneIndex()); - } catch (Exception ex) { - throw new ImageCompressionException("Unable to load plane data.", ex); + + middlePlane = planeLoader.loadPlaneU16(middlePlaneIndex); + } catch (IOException ex) { + throw new ImageCompressionException("Unable to load middle plane data.", ex); } Log(String.format("Training scalar quantizer from middle plane %d.", middlePlaneIndex)); @@ -130,10 +142,9 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm ImageU16 plane = null; try { - plane = RawDataIO.loadImageU16(options.getInputFilePath(), - options.getImageDimension(), - planeIndex); - } catch (Exception ex) { + + plane = planeLoader.loadPlaneU16(planeIndex); + } catch (IOException ex) { throw new ImageCompressionException("Unable to load plane data.", ex); } @@ -162,21 +173,27 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm } private int[] loadConfiguredPlanesData() throws ImageCompressionException { + final InputFileInfo inputFileInfo = options.getInputFileInfo(); + final IPlaneLoader planeLoader; + try { + planeLoader = PlaneLoaderFactory.getPlaneLoaderForInputFile(inputFileInfo); + } catch (Exception e) { + throw new ImageCompressionException("Unable to create SCIFIO reader. " + e.getMessage()); + } int[] trainData = null; - if (options.isPlaneIndexSet()) { + + if (inputFileInfo.isPlaneIndexSet()) { try { Log("Loading single plane data."); - trainData = RawDataIO.loadImageU16(options.getInputFilePath(), - options.getImageDimension(), - options.getPlaneIndex()).getData(); + trainData = planeLoader.loadPlaneU16(inputFileInfo.getPlaneIndex()).getData(); } catch (IOException e) { throw new ImageCompressionException("Failed to load plane data.", e); } - } else if (options.isPlaneRangeSet()) { + } else if (inputFileInfo.isPlaneRangeSet()) { Log("Loading plane range data."); final int[] planes = getPlaneIndicesForCompression(); try { - trainData = RawDataIO.loadPlanesData(options.getInputFilePath(), options.getImageDimension(), planes); + trainData = planeLoader.loadPlanesU16Data(planes); } catch (IOException e) { e.printStackTrace(); throw new ImageCompressionException("Failed to load plane range data.", e); @@ -184,7 +201,7 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm } else { Log("Loading all planes data."); try { - trainData = RawDataIO.loadAllPlanesData(options.getInputFilePath(), options.getImageDimension()); + trainData = planeLoader.loadAllPlanesU16Data(); } catch (IOException e) { throw new ImageCompressionException("Failed to load all planes data.", e); } @@ -202,13 +219,12 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm Log("Starting LloydMax training."); lloydMax.train(options.isVerbose()); final SQCodebook codebook = lloydMax.getCodebook(); - final int[] qValues = codebook.getCentroids(); Log("Finished LloydMax training."); Log(String.format("Saving cache file to %s", options.getOutputFilePath())); QuantizationCacheManager cacheManager = new QuantizationCacheManager(options.getOutputFilePath()); try { - cacheManager.saveCodebook(options.getInputFilePath(), codebook); + cacheManager.saveCodebook(options.getInputFileInfo().getFilePath(), codebook); } catch (IOException e) { throw new ImageCompressionException("Unable to write cache.", e); } diff --git a/src/main/java/azgracompress/compression/VQImageCompressor.java b/src/main/java/azgracompress/compression/VQImageCompressor.java index 7365bf2eecb11935696f3e392047d74385de3483..14f0dc91801f6e4db1ee2bf1447a35ba84c675e1 100644 --- a/src/main/java/azgracompress/compression/VQImageCompressor.java +++ b/src/main/java/azgracompress/compression/VQImageCompressor.java @@ -1,12 +1,14 @@ package azgracompress.compression; import azgracompress.cache.QuantizationCacheManager; +import azgracompress.cli.InputFileInfo; import azgracompress.cli.ParsedCliOptions; import azgracompress.compression.exception.ImageCompressionException; import azgracompress.data.Chunk2D; import azgracompress.data.ImageU16; import azgracompress.huffman.Huffman; -import azgracompress.io.RawDataIO; +import azgracompress.io.IPlaneLoader; +import azgracompress.io.PlaneLoaderFactory; import azgracompress.quantization.vector.*; import azgracompress.utilities.Stopwatch; @@ -71,11 +73,13 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm * @throws ImageCompressionException when fails to read cached codebook. */ private VectorQuantizer loadQuantizerFromCache() throws ImageCompressionException { + QuantizationCacheManager cacheManager = new QuantizationCacheManager(options.getCodebookCacheFolder()); - final VQCodebook codebook = cacheManager.loadVQCodebook(options.getInputFilePath(), - getCodebookSize(), - options.getVectorDimension().toV3i()); + + final VQCodebook codebook = cacheManager.loadVQCodebook(options.getInputFileInfo().getFilePath(), + getCodebookSize(), + options.getVectorDimension().toV3i()); if (codebook == null) { throw new ImageCompressionException("Failed to read quantization vectors from cache."); } @@ -89,12 +93,16 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm * @throws ImageCompressionException When compress process fails. */ public long[] compress(DataOutputStream compressStream) throws ImageCompressionException { - + final InputFileInfo inputFileInfo = options.getInputFileInfo(); Stopwatch stopwatch = new Stopwatch(); final boolean hasGeneralQuantizer = options.hasCodebookCacheFolder() || options.shouldUseMiddlePlane(); - - + final IPlaneLoader planeLoader; final int[] huffmanSymbols = createHuffmanSymbols(getCodebookSize()); + try { + planeLoader = PlaneLoaderFactory.getPlaneLoaderForInputFile(inputFileInfo); + } catch (Exception e) { + throw new ImageCompressionException("Unable to create SCIFIO reader. " + e.getMessage()); + } VectorQuantizer quantizer = null; Huffman huffman = null; @@ -110,11 +118,10 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm final int middlePlaneIndex = getMiddlePlaneIndex(); ImageU16 middlePlane = null; try { - middlePlane = RawDataIO.loadImageU16(options.getInputFilePath(), - options.getImageDimension(), - middlePlaneIndex); - } catch (Exception ex) { - throw new ImageCompressionException("Unable to load plane data.", ex); + + middlePlane = planeLoader.loadPlaneU16(middlePlaneIndex); + } catch (IOException ex) { + throw new ImageCompressionException("Unable to load reference plane data.", ex); } Log(String.format("Training vector quantizer from middle plane %d.", middlePlaneIndex)); @@ -136,10 +143,9 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm ImageU16 plane = null; try { - plane = RawDataIO.loadImageU16(options.getInputFilePath(), - options.getImageDimension(), - planeIndex); - } catch (Exception ex) { + + plane = planeLoader.loadPlaneU16(planeIndex); + } catch (IOException ex) { throw new ImageCompressionException("Unable to load plane data.", ex); } @@ -175,33 +181,39 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm * @return Quantization vectors of configured quantization. * @throws IOException When reading fails. */ - private int[][] loadPlaneQuantizationVectors(final int planeIndex) throws IOException { - ImageU16 refPlane = RawDataIO.loadImageU16(options.getInputFilePath(), - options.getImageDimension(), - planeIndex); + private int[][] loadPlaneQuantizationVectors(final IPlaneLoader planeLoader, + final int planeIndex) throws IOException { + ImageU16 refPlane = planeLoader.loadPlaneU16(planeIndex); return refPlane.toQuantizationVectors(options.getVectorDimension()); } private int[][] loadConfiguredPlanesData() throws ImageCompressionException { final int vectorSize = options.getVectorDimension().getX() * options.getVectorDimension().getY(); + final InputFileInfo inputFileInfo = options.getInputFileInfo(); + final IPlaneLoader planeLoader; + try { + planeLoader = PlaneLoaderFactory.getPlaneLoaderForInputFile(inputFileInfo); + } catch (Exception e) { + throw new ImageCompressionException("Unable to create SCIFIO reader. " + e.getMessage()); + } int[][] trainData = null; Stopwatch s = new Stopwatch(); s.start(); - if (options.isPlaneIndexSet()) { + if (inputFileInfo.isPlaneIndexSet()) { Log("VQ: Loading single plane data."); try { - trainData = loadPlaneQuantizationVectors(options.getPlaneIndex()); + + trainData = loadPlaneQuantizationVectors(planeLoader, inputFileInfo.getPlaneIndex()); } catch (IOException e) { throw new ImageCompressionException("Failed to load plane data.", e); } } else { - - Log((options.isPlaneRangeSet()) ? "VQ: Loading plane range data." : "VQ: Loading all planes data."); + Log(inputFileInfo.isPlaneRangeSet() ? "VQ: Loading plane range data." : "VQ: Loading all planes data."); final int[] planeIndices = getPlaneIndicesForCompression(); final int chunkCountPerPlane = Chunk2D.calculateRequiredChunkCountPerPlane( - options.getImageDimension().toV2i(), + inputFileInfo.getDimensions().toV2i(), options.getVectorDimension()); final int totalChunkCount = chunkCountPerPlane * planeIndices.length; @@ -211,14 +223,18 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm int planeCounter = 0; for (final int planeIndex : planeIndices) { try { - planeVectors = loadPlaneQuantizationVectors(planeIndex); + planeVectors = loadPlaneQuantizationVectors(planeLoader, planeIndex); assert (planeVectors.length == chunkCountPerPlane) : "Wrong chunk count per plane"; } catch (IOException e) { throw new ImageCompressionException(String.format("Failed to load plane %d image data.", planeIndex), e); } - System.arraycopy(planeVectors, 0, trainData, (planeCounter * chunkCountPerPlane), chunkCountPerPlane); + System.arraycopy(planeVectors, + 0, + trainData, + (planeCounter * chunkCountPerPlane), + chunkCountPerPlane); ++planeCounter; } } @@ -243,7 +259,7 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm Log("Saving cache file to %s", options.getOutputFilePath()); QuantizationCacheManager cacheManager = new QuantizationCacheManager(options.getOutputFilePath()); try { - cacheManager.saveCodebook(options.getInputFilePath(), lbgResult.getCodebook()); + cacheManager.saveCodebook(options.getInputFileInfo().getFilePath(), lbgResult.getCodebook()); } catch (IOException e) { throw new ImageCompressionException("Unable to write VQ cache.", e); } diff --git a/src/main/java/azgracompress/data/ImageU16.java b/src/main/java/azgracompress/data/ImageU16.java index ddd7b4e1dd34ffeb8ad2df6e89130aa836ea53ca..8c5f7a53020ebcab62013f79f6e8e7e6608d9b5a 100644 --- a/src/main/java/azgracompress/data/ImageU16.java +++ b/src/main/java/azgracompress/data/ImageU16.java @@ -8,13 +8,17 @@ public class ImageU16 { private final int height; private int[] data; - public ImageU16(int width, int height, int[] data) { + public ImageU16(final int width, final int height, final int[] data) { assert ((width * height) == data.length) : "Wrong data size in ImageU16 constructor."; this.width = width; this.height = height; this.data = data; } + public ImageU16(final V2i dims, final int[] data) { + this(dims.getX(), dims.getY(), data); + } + private int index(final int x, final int y) { assert ((x >= 0 && x < height) && (y >= 0 && y < width)) : "Index out of bounds"; return (x * width) + y; diff --git a/src/main/java/azgracompress/data/V2i.java b/src/main/java/azgracompress/data/V2i.java index 9df55d73cac1579deff4411b82a497f0b5c583ba..4e888935ec9ba8bac6cd3ddd54e661f3e8bac203 100644 --- a/src/main/java/azgracompress/data/V2i.java +++ b/src/main/java/azgracompress/data/V2i.java @@ -13,9 +13,7 @@ public class V2i { this(universalValue, universalValue); } - public int getX() { - return x; - } + public int getX() { return x; } public int getY() { return y; diff --git a/src/main/java/azgracompress/fileformat/FileExtensions.java b/src/main/java/azgracompress/fileformat/FileExtensions.java new file mode 100644 index 0000000000000000000000000000000000000000..07f171f6914e3f9a36c7671cbd2f81df1a592485 --- /dev/null +++ b/src/main/java/azgracompress/fileformat/FileExtensions.java @@ -0,0 +1,7 @@ +package azgracompress.fileformat; + +public class FileExtensions { + public static final String RAW = "raw"; + public static final String QCMP = "qcmp"; + +} diff --git a/src/main/java/azgracompress/io/IPlaneLoader.java b/src/main/java/azgracompress/io/IPlaneLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..45026b5e04b3f62f24b9403b438e9e7ce9e056a9 --- /dev/null +++ b/src/main/java/azgracompress/io/IPlaneLoader.java @@ -0,0 +1,14 @@ +package azgracompress.io; + +import azgracompress.cli.InputFileInfo; +import azgracompress.data.ImageU16; + +import java.io.IOException; + +public interface IPlaneLoader { + ImageU16 loadPlaneU16(final int plane) throws IOException; + + int[] loadPlanesU16Data(int[] planes) throws IOException; + + int[] loadAllPlanesU16Data() throws IOException; +} diff --git a/src/main/java/azgracompress/io/PlaneLoaderFactory.java b/src/main/java/azgracompress/io/PlaneLoaderFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..bb9b77b52b5e56db8f7466ab6048ee286261b168 --- /dev/null +++ b/src/main/java/azgracompress/io/PlaneLoaderFactory.java @@ -0,0 +1,21 @@ +package azgracompress.io; + +import azgracompress.cli.InputFileInfo; + +public final class PlaneLoaderFactory { + + /** + * Create concrete plane loader for the input file. + * + * @param inputFileInfo Input file information. + * @return Concrete plane loader. + * @throws Exception When fails to create plane loader. + */ + public static IPlaneLoader getPlaneLoaderForInputFile(final InputFileInfo inputFileInfo) throws Exception { + if (inputFileInfo.isRAW()) { + return new RawDataLoader(inputFileInfo); + } else { + return new SCIFIOLoader(inputFileInfo); + } + } +} diff --git a/src/main/java/azgracompress/io/RawDataIO.java b/src/main/java/azgracompress/io/RawDataIO.java index 5cf711f0271b8642282e2343d0eef24533218d9f..402df89490ae1281c60b4814598821c3f4684e18 100644 --- a/src/main/java/azgracompress/io/RawDataIO.java +++ b/src/main/java/azgracompress/io/RawDataIO.java @@ -1,119 +1,12 @@ package azgracompress.io; import azgracompress.data.ImageU16; -import azgracompress.data.V3i; import azgracompress.utilities.TypeConverter; -import java.io.*; -import java.util.Arrays; +import java.io.FileOutputStream; +import java.io.IOException; public class RawDataIO { - /** - * Load single U16 image from RAW data file. - * - * @param rawFile Path to the raw file. - * @param rawDataDimension X (Width), Y (Height) of plane and Z(Number of planes) - * @param plane Plane index. - * @return U16 image specified by the plane - */ - public static ImageU16 loadImageU16(final String rawFile, - final V3i rawDataDimension, - final int plane) throws IOException { - - byte[] buffer; - try (FileInputStream fileStream = new FileInputStream(rawFile)) { - final long planeSize = (long) rawDataDimension.getX() * (long) rawDataDimension.getY() * 2; - final long expectedFileSize = planeSize * rawDataDimension.getZ(); - final long fileSize = fileStream.getChannel().size(); - - - if (expectedFileSize != fileSize) { - throw new IOException( - "File specified by `rawFile` doesn't contains raw data for image of dimensions " + - "`rawDataDimension`"); - } - - final long planeOffset = plane * planeSize; - - buffer = new byte[(int) planeSize]; - if (fileStream.skip(planeOffset) != planeOffset) { - throw new IOException("Failed to skip."); - } - if (fileStream.read(buffer, 0, (int) planeSize) != planeSize) { - throw new IOException("Read wrong number of bytes."); - } - } - - return new ImageU16(rawDataDimension.getX(), - rawDataDimension.getY(), - TypeConverter.unsignedShortBytesToIntArray(buffer)); - } - - public static int[] loadPlanesData(final String rawFile, - final V3i rawDataDims, - int[] planes) throws IOException { - - if (planes.length < 1) - return new int[0]; - - final int planeValueCount = rawDataDims.getX() * rawDataDims.getY(); - final long planeDataSize = 2 * (long) planeValueCount; - - final long totalValueCount = (long) planeValueCount * planes.length; - int[] values = new int[(int) totalValueCount]; - - - if (totalValueCount > (long) Integer.MAX_VALUE) { - throw new IOException("Integer count is too big."); - } - - Arrays.sort(planes); - - try (FileInputStream fileStream = new FileInputStream(rawFile); - DataInputStream dis = new DataInputStream(new BufferedInputStream(fileStream, 8192))) { - - int lastIndex = 0; - int valIndex = 0; - - for (final int planeIndex : planes) { - // Skip specific number of bytes to get to the next plane. - final int requestedSkip = (planeIndex == 0) ? 0 : ((planeIndex - lastIndex) - 1) * (int) planeDataSize; - lastIndex = planeIndex; - - final int actualSkip = dis.skipBytes(requestedSkip); - if (requestedSkip != actualSkip) { - throw new IOException("Skip operation failed."); - } - - for (int i = 0; i < planeValueCount; i++) { - values[valIndex++] = dis.readUnsignedShort(); - } - - } - } - - return values; - } - - public static int[] loadAllPlanesData(final String rawFile, final V3i imageDims) throws IOException { - - final long dataSize = (long) imageDims.getX() * (long) imageDims.getY() * (long) imageDims.getZ(); - int[] values = new int[(int) dataSize]; - - if (dataSize > (long) Integer.MAX_VALUE) { - throw new IOException("RawFile size is too big."); - } - - try (FileInputStream fileStream = new FileInputStream(rawFile); - DataInputStream dis = new DataInputStream(new BufferedInputStream(fileStream, 8192))) { - - for (int i = 0; i < (int) dataSize; i++) { - values[i] = dis.readUnsignedShort(); - } - } - - return values; - } public static void writeImageU16(final String rawFile, final ImageU16 image, diff --git a/src/main/java/azgracompress/io/RawDataLoader.java b/src/main/java/azgracompress/io/RawDataLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..69e7ca33f34a4650c03fc33906ae47c3bf7342a9 --- /dev/null +++ b/src/main/java/azgracompress/io/RawDataLoader.java @@ -0,0 +1,119 @@ +package azgracompress.io; + +import azgracompress.cli.InputFileInfo; +import azgracompress.data.ImageU16; +import azgracompress.data.V3i; +import azgracompress.utilities.TypeConverter; + +import java.io.*; +import java.util.Arrays; + +public class RawDataLoader implements IPlaneLoader { + private final InputFileInfo inputFileInfo; + + public RawDataLoader(final InputFileInfo inputFileInfo) { + this.inputFileInfo = inputFileInfo; + } + + @Override + public ImageU16 loadPlaneU16(int plane) throws IOException { + byte[] buffer; + final V3i rawDataDimension = inputFileInfo.getDimensions(); + + try (FileInputStream fileStream = new FileInputStream(inputFileInfo.getFilePath())) { + final long planeSize = (long) rawDataDimension.getX() * (long) rawDataDimension.getY() * 2; + final long expectedFileSize = planeSize * rawDataDimension.getZ(); + final long fileSize = fileStream.getChannel().size(); + + + if (expectedFileSize != fileSize) { + throw new IOException( + "File specified by `rawFile` doesn't contains raw data for image of dimensions " + + "`rawDataDimension`"); + } + + final long planeOffset = plane * planeSize; + + buffer = new byte[(int) planeSize]; + if (fileStream.skip(planeOffset) != planeOffset) { + throw new IOException("Failed to skip."); + } + if (fileStream.read(buffer, 0, (int) planeSize) != planeSize) { + throw new IOException("Read wrong number of bytes."); + } + } + + return new ImageU16(rawDataDimension.getX(), + rawDataDimension.getY(), + TypeConverter.unsignedShortBytesToIntArray(buffer)); + } + + @Override + public int[] loadPlanesU16Data(int[] planes) throws IOException { + if (planes.length < 1) { + return new int[0]; + } else if (planes.length == 1) { + return loadPlaneU16(planes[0]).getData(); + } + + final int planeValueCount = inputFileInfo.getDimensions().getX() * inputFileInfo.getDimensions().getY(); + final long planeDataSize = 2 * (long) planeValueCount; + + final long totalValueCount = (long) planeValueCount * planes.length; + + if (totalValueCount > (long) Integer.MAX_VALUE) { + throw new IOException("Integer count is too big."); + } + + int[] values = new int[(int) totalValueCount]; + + Arrays.sort(planes); + + try (FileInputStream fileStream = new FileInputStream(inputFileInfo.getFilePath()); + DataInputStream dis = new DataInputStream(new BufferedInputStream(fileStream, 8192))) { + + int lastIndex = 0; + int valIndex = 0; + + for (final int planeIndex : planes) { + // Skip specific number of bytes to get to the next plane. + final int requestedSkip = (planeIndex == 0) ? 0 : ((planeIndex - lastIndex) - 1) * (int) planeDataSize; + lastIndex = planeIndex; + + final int actualSkip = dis.skipBytes(requestedSkip); + if (requestedSkip != actualSkip) { + throw new IOException("Skip operation failed."); + } + + for (int i = 0; i < planeValueCount; i++) { + values[valIndex++] = dis.readUnsignedShort(); + } + + } + } + + return values; + } + + @Override + public int[] loadAllPlanesU16Data() throws IOException { + final V3i imageDims = inputFileInfo.getDimensions(); + final long dataSize = (long) imageDims.getX() * (long) imageDims.getY() * (long) imageDims.getZ(); + + if (dataSize > (long) Integer.MAX_VALUE) { + throw new IOException("RawFile size is too big."); + } + + int[] values = new int[(int) dataSize]; + + try (FileInputStream fileStream = new FileInputStream(inputFileInfo.getFilePath()); + DataInputStream dis = new DataInputStream(new BufferedInputStream(fileStream, 8192))) { + + for (int i = 0; i < (int) dataSize; i++) { + values[i] = dis.readUnsignedShort(); + } + } + + return values; + } +} diff --git a/src/main/java/azgracompress/io/SCIFIOLoader.java b/src/main/java/azgracompress/io/SCIFIOLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..4ca3d1c36a9f058d4936da990eb4a409ce0b379e --- /dev/null +++ b/src/main/java/azgracompress/io/SCIFIOLoader.java @@ -0,0 +1,108 @@ +package azgracompress.io; + +import azgracompress.ScifioWrapper; +import azgracompress.cli.InputFileInfo; +import azgracompress.data.ImageU16; +import azgracompress.data.V3i; +import azgracompress.utilities.TypeConverter; +import io.scif.FormatException; +import io.scif.Reader; + +import java.io.IOException; +import java.util.Arrays; + +public class SCIFIOLoader implements IPlaneLoader { + + private final InputFileInfo inputFileInfo; + private final Reader reader; + + /** + * Create SCIFIO reader from input file. + * + * @param inputFileInfo Input file info. + * @throws IOException When fails to create SCIFIO reader. + * @throws FormatException When fails to create SCIFIO reader. + */ + public SCIFIOLoader(final InputFileInfo inputFileInfo) throws IOException, FormatException { + this.inputFileInfo = inputFileInfo; + this.reader = ScifioWrapper.getReader(this.inputFileInfo.getFilePath()); + } + + @Override + public ImageU16 loadPlaneU16(int plane) throws IOException { + byte[] planeBytes; + try { + planeBytes = reader.openPlane(0, plane).getBytes(); + } catch (FormatException e) { + throw new IOException("Unable to open plane with the reader. " + e.getMessage()); + } + final int[] data = TypeConverter.unsignedShortBytesToIntArray(planeBytes); + return new ImageU16(inputFileInfo.getDimensions().toV2i(), data); + } + + @SuppressWarnings("DuplicatedCode") + @Override + public int[] loadPlanesU16Data(int[] planes) throws IOException { + if (planes.length < 1) { + return new int[0]; + } else if (planes.length == 1) { + return loadPlaneU16(planes[0]).getData(); + } + + final int planeValueCount = inputFileInfo.getDimensions().getX() * inputFileInfo.getDimensions().getY(); + final long planeDataSize = 2 * (long) planeValueCount; + + final long totalValueCount = (long) planeValueCount * planes.length; + + if (totalValueCount > (long) Integer.MAX_VALUE) { + throw new IOException("Integer count is too big."); + } + + int[] values = new int[(int) totalValueCount]; + Arrays.sort(planes); + + byte[] planeBytes; + for (int i = 0; i < planes.length; i++) { + final int plane = planes[i]; + + try { + planeBytes = reader.openPlane(0, plane).getBytes(); + } catch (FormatException e) { + throw new IOException("Unable to open plane."); + } + if (planeBytes.length != planeDataSize) { + throw new IOException("Bad byte count read from plane."); + } + + TypeConverter.unsignedShortBytesToIntArray(planeBytes, values, (i * planeValueCount)); + } + return values; + } + + @Override + public int[] loadAllPlanesU16Data() throws IOException { + final V3i imageDims = inputFileInfo.getDimensions(); + final long planePixelCount = (long) imageDims.getX() * (long) imageDims.getY(); + final long dataSize = planePixelCount * (long) imageDims.getZ(); + + if (dataSize > (long) Integer.MAX_VALUE) { + throw new IOException("FileSize is too big."); + } + + int[] values = new int[(int) dataSize]; + byte[] planeBytes; + for (int plane = 0; plane < imageDims.getZ(); plane++) { + try { + planeBytes = reader.openPlane(0, plane).getBytes(); + } catch (FormatException e) { + throw new IOException("Unable to open plane."); + } + if (planeBytes.length != 2 * planePixelCount) { + throw new IOException("Bad byte count read from plane."); + } + TypeConverter.unsignedShortBytesToIntArray(planeBytes, values, (int) (plane * planePixelCount)); + } + + return values; + } +} diff --git a/src/main/java/azgracompress/utilities/TypeConverter.java b/src/main/java/azgracompress/utilities/TypeConverter.java index 09f8afe7cc506459c7e687bbb7fc9d11e914c67a..4f8ec2d16f5551907e093b0f38b183b9d4938c69 100644 --- a/src/main/java/azgracompress/utilities/TypeConverter.java +++ b/src/main/java/azgracompress/utilities/TypeConverter.java @@ -80,4 +80,20 @@ public class TypeConverter { } return buffer; } + + /** + * Convert unsigned short bytes to integers. Place integers into values array from offset position. + * + * @param bytes Unsigned short bytes. + * @param values Integer value buffer. + * @param offset Offset into integer array. + */ + public static void unsignedShortBytesToIntArray(final byte[] bytes, final int[] values, final int offset) { + assert (bytes.length % 2 == 0); + int valuesIndex = offset; + for (int i = 0; i < bytes.length; i += 2) { + final int value = (int) (((bytes[i] & 0xff) << 8) | (bytes[i + 1] & 0xff)); + values[valuesIndex++] = value; + } + } }