From 846e48f97c11fdc6255bf319ad098d8ab3b6985c Mon Sep 17 00:00:00 2001 From: Vojtech Moravec <vojtech.moravec.st@vsb.cz> Date: Fri, 11 Sep 2020 10:07:05 +0200 Subject: [PATCH] Add new compression options. Also created subclass of Option which allow us to report QCMP options at the end of help report. --- src/main/java/bdv/server/BigDataServer.java | 257 ++++++++++---------- src/main/java/bdv/util/OptionWithOrder.java | 39 +++ 2 files changed, 170 insertions(+), 126 deletions(-) create mode 100644 src/main/java/bdv/util/OptionWithOrder.java diff --git a/src/main/java/bdv/server/BigDataServer.java b/src/main/java/bdv/server/BigDataServer.java index e4d4809..5983fbf 100644 --- a/src/main/java/bdv/server/BigDataServer.java +++ b/src/main/java/bdv/server/BigDataServer.java @@ -1,27 +1,23 @@ package bdv.server; -import compression.U16; -import compression.quantization.QuantizationValueCache; -import compression.quantization.scalar.LloydMaxU16ScalarQuantization; -import compression.quantization.scalar.ScalarQuantizer; -import compression.utilities.Utils; +import azgracompress.cli.CliConstants; +import azgracompress.cli.ParseUtils; +import azgracompress.compression.CompressionOptions; +import azgracompress.data.V2i; +import azgracompress.data.V3i; +import azgracompress.fileformat.QuantizationType; +import bdv.util.OptionWithOrder; import mpicbg.spim.data.SpimDataException; - import org.apache.commons.cli.*; import org.apache.commons.lang.StringUtils; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.ConnectorStatistics; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.*; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.handler.StatisticsHandler; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.thread.QueuedThreadPool; -import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; @@ -33,6 +29,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; /** * Serve XML/HDF5 datasets over HTTP. @@ -61,7 +58,7 @@ import java.util.Map.Entry; public class BigDataServer { private static final org.eclipse.jetty.util.log.Logger LOG = Log.getLogger(BigDataServer.class); - private static ScalarQuantizer quantizer; + // private static ScalarQuantizer quantizer; static Parameters getDefaultParameters() { final int port = 8080; @@ -73,8 +70,12 @@ public class BigDataServer { } final String thumbnailDirectory = null; final boolean enableManagerContext = false; - return new Parameters(port, hostname, new HashMap<String, String>(), thumbnailDirectory, enableManagerContext, - new CustomCompressionParameters("", "", 8, false, false, -1)); + return new Parameters(port, + hostname, + new HashMap<String, String>(), + thumbnailDirectory, + enableManagerContext, + new CompressionOptions()); } public static void main(final String[] args) throws Exception { @@ -97,36 +98,6 @@ public class BigDataServer { server.setConnectors(new Connector[]{connector}); final String baseURL = "http://" + server.getURI().getHost() + ":" + params.getPort(); - - final CustomCompressionParameters compParams = params.getCompressionParams(); - if (compParams.shouldCompressData() || compParams.renderDifference()) { - //TODO(Moravec): Replace LloydMaxU16ScalarQuantization with some ICompressor. - - QuantizationValueCache quantizationCache = new QuantizationValueCache("D:\\biology\\bdv_cache"); - final int quantizationValueCount = (int) Math.pow(2, compParams.getBitTarget()); - - final String trainFilename = new File(compParams.getTrainFile()).getName(); - if (quantizationCache.areQuantizationValueCached(trainFilename, quantizationValueCount)) { - LOG.info("Found cached quantization values..."); - final int[] centroids = quantizationCache.readCachedValues(trainFilename, quantizationValueCount); - assert (centroids.length == quantizationValueCount) : "Cache is corrupted"; - quantizer = new ScalarQuantizer(U16.Min, U16.Max, centroids); - LOG.info("Initialized quantizer..."); - } else { - LOG.info("Calculating quantization values..."); - - LloydMaxU16ScalarQuantization lloydMax = new LloydMaxU16ScalarQuantization( - Utils.convertU16ByteArrayToIntArray(Utils.readFileBytes(compParams.getTrainFile())), - (int) Math.pow(2, compParams.getBitTarget())); - - lloydMax.train(); - quantizationCache.saveQuantizationValue(trainFilename, lloydMax.getCentroids()); - LOG.info("Saving quantization values..."); - quantizer = new ScalarQuantizer(U16.Min, U16.Max, lloydMax.getCentroids()); - } - } - - // Handler initialization final HandlerCollection handlers = new HandlerCollection(); @@ -172,13 +143,13 @@ public class BigDataServer { private final String thumbnailDirectory; - private final CustomCompressionParameters compressionParam; + private final CompressionOptions compressionParam; private final boolean enableManagerContext; Parameters(final int port, final String hostname, final Map<String, String> datasetNameToXml, final String thumbnailDirectory, final boolean enableManagerContext, - final CustomCompressionParameters customCompressionParameters) { + final CompressionOptions customCompressionParameters) { this.port = port; this.hostname = hostname; this.datasetNameToXml = datasetNameToXml; @@ -212,7 +183,7 @@ public class BigDataServer { return enableManagerContext; } - public CustomCompressionParameters getCompressionParams() { + public CompressionOptions getCompressionParams() { return compressionParam; } @@ -221,12 +192,6 @@ public class BigDataServer { @SuppressWarnings("static-access") static private Parameters processOptions(final String[] args, final Parameters defaultParameters) throws IOException { - final String BIT_TARGET = "bits"; - final String ENABLE_COMPRESSION = "compress"; - final String ENABLE_COMPRESSION_DIFF = "diff"; - final String DUMP_FILE = "dump"; - final String TRAIN_FILE = "train"; - final String DIFF_THRESHOLD = "diffthreshold"; // create Options object final Options options = new Options(); @@ -234,70 +199,63 @@ public class BigDataServer { final String description = "Serves one or more XML/HDF5 datasets for remote access over HTTP.\n" + - "Provide (NAME XML) pairs on the command line or in a dataset file, where NAME is the name under which the dataset should be made accessible and XML is the path to the XML file of the dataset."; + "Provide (NAME XML) pairs on the command line or in a dataset file, where NAME is the name under which the " + + "dataset should be made accessible and XML is the path to the XML file of the dataset.\n" + + "If -qcmp option is specified, these options are enabled:\u001b[35m-sq,-vq,-b,-cbc\u001b[0m\n"; options.addOption(OptionBuilder - .withDescription("Hostname of the server.\n(default: " + defaultParameters.getHostname() + ")") - .hasArg() - .withArgName("HOSTNAME") - .create("s")); + .withDescription("Hostname of the server.\n(default: " + defaultParameters.getHostname() + ")") + .hasArg() + .withArgName("HOSTNAME") + .create("s")); options.addOption(OptionBuilder - .withDescription("Listening port.\n(default: " + defaultParameters.getPort() + ")") - .hasArg() - .withArgName("PORT") - .create("p")); + .withDescription("Listening port.\n(default: " + defaultParameters.getPort() + ")") + .hasArg() + .withArgName("PORT") + .create("p")); // -d or multiple {name name.xml} pairs options.addOption(OptionBuilder - .withDescription("Dataset file: A plain text file specifying one dataset per line. Each line is formatted as \"NAME <TAB> XML\".") - .hasArg() - .withArgName("FILE") - .create("d")); + .withDescription( + "Dataset file: A plain text file specifying one dataset per line. Each line is formatted as " + + "\"NAME <TAB> XML\".") + .hasArg() + .withArgName("FILE") + .create("d")); options.addOption(OptionBuilder - .withDescription("Directory to store thumbnails. (new temporary directory by default.)") - .hasArg() - .withArgName("DIRECTORY") - .create("t")); + .withDescription("Directory to store thumbnails. (new temporary directory by default.)") + .hasArg() + .withArgName("DIRECTORY") + .create("t")); + final String ENABLE_COMPRESSION = "qcmp"; - options.addOption(OptionBuilder - .withDescription("File in which to store request data dump") - .hasArg() - .withArgName("DUMP") - .create(DUMP_FILE)); + final Option test = OptionBuilder + .withDescription("Enable QCMP compression") + .create(ENABLE_COMPRESSION); - options.addOption(OptionBuilder - .withDescription("Enable request compression") - .create(ENABLE_COMPRESSION)); - options.addOption(OptionBuilder - .withDescription("Compression train file") - .hasArg() - .withArgName("TRAINFILE") - .create(TRAIN_FILE)); + int optionOrder = 0; + options.addOption(new OptionWithOrder(OptionBuilder + .withDescription("Enable QCMP compression") + .create(ENABLE_COMPRESSION), ++optionOrder)); - options.addOption(OptionBuilder - .withDescription("Compression bit target") - .hasArg() - .withArgName("BITS") - .create(BIT_TARGET)); - options.addOption(OptionBuilder - .withDescription("Send compression difference") - .create(ENABLE_COMPRESSION_DIFF)); + OptionGroup qcmpOptionGroup = new OptionGroup(); + qcmpOptionGroup.setRequired(false); + qcmpOptionGroup.addOption(new OptionWithOrder(CliConstants.createCBCMethod(), ++optionOrder)); + qcmpOptionGroup.addOption(new OptionWithOrder(CliConstants.createSQOption(), ++optionOrder)); + qcmpOptionGroup.addOption(new OptionWithOrder(CliConstants.createVQOption(), ++optionOrder)); + qcmpOptionGroup.addOption(new OptionWithOrder(CliConstants.createBitsOption(), ++optionOrder)); + options.addOptionGroup(qcmpOptionGroup); - options.addOption(OptionBuilder - .withDescription("Render difference above this threshold") - .hasArg() - .withArgName(DIFF_THRESHOLD) - .create(DIFF_THRESHOLD)); if (Constants.ENABLE_EXPERIMENTAL_FEATURES) { options.addOption(OptionBuilder - .withDescription("enable statistics and manager context. EXPERIMENTAL!") - .create("m")); + .withDescription("enable statistics and manager context. EXPERIMENTAL!") + .create("m")); } try { @@ -317,29 +275,55 @@ public class BigDataServer { final HashMap<String, String> datasets = new HashMap<String, String>(defaultParameters.getDatasets()); - // Custom compression parameters - //cmd.hasOption() - - final String dumpFile = cmd.getOptionValue(DUMP_FILE, ""); - final boolean enableCompression = cmd.hasOption(ENABLE_COMPRESSION); - final boolean enableCompressionDiff = cmd.hasOption(ENABLE_COMPRESSION_DIFF); - final String trainFile = cmd.getOptionValue(TRAIN_FILE, ""); - final int bitTarget = Integer.parseInt(cmd.getOptionValue(BIT_TARGET, "8")); - final int diffThreshold = Integer.parseInt(cmd.getOptionValue(DIFF_THRESHOLD, "-1")); - if (diffThreshold > -1) { - LOG.info("Diff threshold is set to: " + diffThreshold); - } - if ((enableCompression || enableCompressionDiff) && (trainFile.isEmpty())) { - throw new MissingArgumentException(String.format("!!! %s must be specified when %s or %s is specified !!!", - TRAIN_FILE, ENABLE_COMPRESSION, ENABLE_COMPRESSION_DIFF)); - } + final boolean enableQcmpCompression = cmd.hasOption(ENABLE_COMPRESSION); + CompressionOptions compressionOptions = new CompressionOptions(); + if (enableQcmpCompression) { + compressionOptions.setQuantizationType(QuantizationType.Invalid); + if (cmd.hasOption(CliConstants.SCALAR_QUANTIZATION_LONG)) + compressionOptions.setQuantizationType(QuantizationType.Scalar); + else if (cmd.hasOption(CliConstants.VECTOR_QUANTIZATION_LONG)) { + final String vqValue = cmd.getOptionValue(CliConstants.VECTOR_QUANTIZATION_LONG); + Optional<V2i> maybeV2 = ParseUtils.tryParseV2i(vqValue, 'x'); + if (maybeV2.isPresent()) { + compressionOptions.setQuantizationType(QuantizationType.Vector2D); + compressionOptions.setQuantizationVector(new V3i(maybeV2.get().getX(), maybeV2.get().getY(), 1)); + } else { + Optional<V3i> maybeV3 = ParseUtils.tryParseV3i(vqValue, 'x'); + if (maybeV3.isPresent()) { + compressionOptions.setQuantizationType(QuantizationType.Vector3D); + compressionOptions.setQuantizationVector(maybeV3.get()); + } + } + } + if (compressionOptions.getQuantizationType() == QuantizationType.Invalid) { + throw new ParseException("Invalid quantization type."); + } - final CustomCompressionParameters customCompParams = new CustomCompressionParameters(dumpFile, trainFile, bitTarget, - enableCompression, enableCompressionDiff, diffThreshold); + compressionOptions.setCodebookType(CompressionOptions.CodebookType.Global); + compressionOptions.setCodebookCacheFolder(cmd.getOptionValue(CliConstants.CODEBOOK_CACHE_FOLDER_LONG)); + compressionOptions.setBitsPerCodebookIndex(Integer.parseInt(cmd.getOptionValue(CliConstants.BITS_LONG))); + + StringBuilder compressionReport = new StringBuilder(); + compressionReport.append("\u001b[33m"); + compressionReport.append("Quantization type: "); + switch (compressionOptions.getQuantizationType()) { + case Scalar: + compressionReport.append("Scalar\n"); + break; + case Vector1D: + compressionReport.append(String.format("Vector1D %s\n", compressionOptions.getQuantizationVector().toString())); + break; + case Vector2D: + compressionReport.append(String.format("Vector2D %s\n", compressionOptions.getQuantizationVector().toString())); + break; + } + compressionReport.append("Bits per codebook index: ").append(compressionOptions.getBitsPerCodebookIndex()).append('\n'); + compressionReport.append("Codebook cache folder: ").append(compressionOptions.getCodebookCacheFolder()).append('\n'); + compressionReport.append("\u001b[0m"); - LOG.info("Compression is " + (enableCompression ? "Matched" : "Not matched")); - LOG.info("Compression-Diff is " + (enableCompressionDiff ? "Matched" : "Not matched")); + System.out.println(compressionReport.toString()); + } boolean enableManagerContext = false; if (Constants.ENABLE_EXPERIMENTAL_FEATURES) { @@ -388,18 +372,37 @@ public class BigDataServer { if (datasets.isEmpty()) throw new IllegalArgumentException("Dataset list is empty."); - return new Parameters(port, serverName, datasets, thumbnailDirectory, enableManagerContext, - customCompParams); + return new Parameters(port, + serverName, + datasets, + thumbnailDirectory, + enableManagerContext, + enableQcmpCompression ? compressionOptions : null); } catch (final ParseException | IllegalArgumentException e) { LOG.warn(e.getMessage()); System.out.println(); final HelpFormatter formatter = new HelpFormatter(); + formatter.setOptionComparator((x, y) -> { + if (x instanceof OptionWithOrder && y instanceof OptionWithOrder) { + return ((OptionWithOrder) x).compareTo((OptionWithOrder) y); + } else if (x instanceof OptionWithOrder) { + return 1; + } else if (y instanceof OptionWithOrder) { + return -1; + } else { + Option opt1 = (Option) x; + Option opt2 = (Option) y; + return opt1.getOpt().compareToIgnoreCase(opt2.getOpt()); + } + }); formatter.printHelp(cmdLineSyntax, description, options, null); } return null; } - private static void tryAddDataset(final HashMap<String, String> datasetNameToXML, final String name, final String xmlpath) throws IllegalArgumentException { + private static void tryAddDataset(final HashMap<String, String> datasetNameToXML, + final String name, + final String xmlpath) throws IllegalArgumentException { for (final String reserved : Constants.RESERVED_CONTEXT_NAMES) if (name.equals(reserved)) throw new IllegalArgumentException("Cannot use dataset name: \"" + name + "\" (reserved for internal use)."); @@ -421,11 +424,13 @@ public class BigDataServer { return thumbnails.toFile().getAbsolutePath(); } catch (final IOException e) { LOG.warn(e.getMessage()); - LOG.warn("Could not create thumbnails directory \"" + thumbnailDirectoryName + "\".\n Trying to create temporary directory."); + LOG.warn("Could not create thumbnails directory \"" + thumbnailDirectoryName + "\".\n Trying to create temporary " + + "directory."); } } else { if (!Files.isDirectory(thumbnails)) - LOG.warn("Thumbnails directory \"" + thumbnailDirectoryName + "\" is not a directory.\n Trying to create temporary directory."); + LOG.warn("Thumbnails directory \"" + thumbnailDirectoryName + "\" is not a directory.\n Trying to create temporary " + + "directory."); else return thumbnails.toFile().getAbsolutePath(); } @@ -447,8 +452,8 @@ public class BigDataServer { final String xmlpath = entry.getValue(); final String context = "/" + name; final CellHandler ctx = new CellHandler(baseURL + context + "/", xmlpath, name, - thumbnailsDirectoryName, - params.getCompressionParams(), quantizer); + thumbnailsDirectoryName, + params.getCompressionParams()); ctx.setContextPath(context); handlers.addHandler(ctx); diff --git a/src/main/java/bdv/util/OptionWithOrder.java b/src/main/java/bdv/util/OptionWithOrder.java new file mode 100644 index 0000000..1a5323c --- /dev/null +++ b/src/main/java/bdv/util/OptionWithOrder.java @@ -0,0 +1,39 @@ +package bdv.util; + +import org.apache.commons.cli.Option; +import org.jetbrains.annotations.NotNull; + +public class OptionWithOrder extends Option implements Comparable<OptionWithOrder> { + + private int order = 0; + + public OptionWithOrder(final Option option, final int order) { + super(option.getOpt(), option.getLongOpt(), option.hasArg(), option.getDescription()); + this.order = order; + } + + public OptionWithOrder(String opt, String description) throws IllegalArgumentException { + super(opt, description); + } + + public OptionWithOrder(String opt, boolean hasArg, String description) throws IllegalArgumentException { + super(opt, hasArg, description); + } + + public OptionWithOrder(String opt, String longOpt, boolean hasArg, String description) throws IllegalArgumentException { + super(opt, longOpt, hasArg, description); + } + + public int getOrder() { + return order; + } + + public void setOrder(int order) { + this.order = order; + } + + @Override + public int compareTo(@NotNull OptionWithOrder other) { + return Integer.compare(order, other.order); + } +} -- GitLab