From ddeb3b334ad6be5e99b7be03151a17e6288d22c1 Mon Sep 17 00:00:00 2001
From: Vojtech Moravec <vojtech.moravec.st@vsb.cz>
Date: Fri, 25 Sep 2020 09:20:48 +0200
Subject: [PATCH] Support multiple codebooks of different size.

Load all available codebooks for given file.
When client requests qcmp_init servers sends him all the codebooks.
---
 src/main/java/bdv/server/CellHandler.java | 71 +++++++++++++++--------
 1 file changed, 46 insertions(+), 25 deletions(-)

diff --git a/src/main/java/bdv/server/CellHandler.java b/src/main/java/bdv/server/CellHandler.java
index c41fccc..efa97a3 100644
--- a/src/main/java/bdv/server/CellHandler.java
+++ b/src/main/java/bdv/server/CellHandler.java
@@ -47,6 +47,8 @@ import java.io.*;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Stack;
 
 public class CellHandler extends ContextHandler {
@@ -101,9 +103,11 @@ public class CellHandler extends ContextHandler {
      * Compression stuff.
      */
     private final CompressionOptions compressionParams;
-    private ImageCompressor compressor = null;
+
+    private ArrayList<ICacheFile> cachedCodebooks = null;
+    private HashMap<Integer, ImageCompressor> compressors = null;
+
     private Stack<MemoryOutputStream> cachedBuffers = null;
-    private ICacheFile cachedCodebook = null;
     private final int INITIAL_BUFFER_SIZE = 2048;
     private long accumulation = 0;
     private long uncompressedAccumulation = 0;
@@ -152,16 +156,27 @@ public class CellHandler extends ContextHandler {
             return;
         this.compressionParams.setInputDataInfo(new FileInputData(this.baseFilename));
         final QuantizationCacheManager qcm = new QuantizationCacheManager(compressionParams.getCodebookCacheFolder());
-        this.cachedCodebook = qcm.loadCacheFile(compressionParams);
-        if (cachedCodebook == null) {
-            LOG.warn("CellHandler: Didn't find cached codebook for " + this.baseFilename);
+
+        cachedCodebooks = qcm.loadAvailableCacheFiles(compressionParams);
+        if (cachedCodebooks.isEmpty()) {
+            LOG.warn("Didn't find any cached codebook for " + this.baseFilename);
             return;
         }
-        LOG.info(String.format("CellHandler: Loaded cached codebook file. '%s' for %s", cachedCodebook, this.baseFilename));
+        LOG.info(String.format("Found %d codebooks for %s.", cachedCodebooks.size(), this.baseFilename));
+        compressors = new HashMap<>(cachedCodebooks.size());
 
-        final int initialCompressionCacheSize = 10;
+        for (final ICacheFile cacheFile : cachedCodebooks) {
+            LOG.info(String.format("  Loaded codebook of size %d. '%s'", cacheFile.getHeader().getCodebookSize(), cacheFile));
+
+            final int bitsPerCodebookIndex = cacheFile.getHeader().getBitsPerCodebookIndex();
+            final CompressionOptions compressorOptions = compressionParams.createClone();
+            assert (compressorOptions != compressionParams);
 
-        compressor = new ImageCompressor(compressionParams, cachedCodebook);
+            compressorOptions.setBitsPerCodebookIndex(bitsPerCodebookIndex);
+            compressors.put(bitsPerCodebookIndex, new ImageCompressor(compressorOptions, cacheFile));
+        }
+
+        final int initialCompressionCacheSize = 10;
         cachedBuffers = new Stack<>();
         for (int i = 0; i < initialCompressionCacheSize; i++) {
             cachedBuffers.push(new MemoryOutputStream(INITIAL_BUFFER_SIZE));
@@ -264,11 +279,12 @@ public class CellHandler extends ContextHandler {
             final int[] cellDims = new int[]{Integer.parseInt(parts[5]), Integer.parseInt(parts[6]), Integer.parseInt(parts[7])};
 
             final short[] data = getCachedVolatileCellData(parts, cellDims);
-            assert (compressor != null);
+            assert (compressors != null && !compressors.isEmpty());
 
             final FlatBufferInputData inputData = createInputDataObject(data, cellDims);
             final MemoryOutputStream cellCompressionStream = getCachedCompressionBuffer();
-            final int compressedContentLength = compressor.streamCompressChunk(cellCompressionStream, inputData);
+            // TODO(Moravec): Choose compressor based on `level`.
+            final int compressedContentLength = compressors.get(8).streamCompressChunk(cellCompressionStream, inputData);
 
             response.setContentLength(compressedContentLength);
             try (final OutputStream responseStream = response.getOutputStream()) {
@@ -304,23 +320,28 @@ public class CellHandler extends ContextHandler {
         } else if (parts[0].equals("init")) {
             respondWithString(baseRequest, response, "application/json", metadataJson);
         } else if (parts[0].equals("init_qcmp")) {
-            if (compressor == null) {
-                LOG.info("QCMP initialization request was refused, QCMP compression is not enabled.");
-                respondWithString(baseRequest, response,
-                                  "text/plain", "QCMP Compression wasn't enabled on BigDataViewer server.",
-                                  HttpServletResponse.SC_BAD_REQUEST);
-                return;
-            }
-
-            try (final DataOutputStream dos = new DataOutputStream(response.getOutputStream())) {
-                cachedCodebook.writeToStream(dos);
-            }
-            response.getOutputStream().close();
+            respondWithCompressionInfo(baseRequest, response);
+        }
+    }
 
-            response.setContentType("application/octet-stream");
-            response.setStatus(HttpServletResponse.SC_OK);
-            baseRequest.setHandled(true);
+    private void respondWithCompressionInfo(final Request baseRequest, final HttpServletResponse response) throws IOException {
+        if (cachedCodebooks == null || cachedCodebooks.isEmpty()) {
+            LOG.info("QCMP initialization request was refused, QCMP compression is not enabled.");
+            respondWithString(baseRequest, response,
+                              "text/plain", "QCMP Compression wasn't enabled on BigDataViewer server.",
+                              HttpServletResponse.SC_BAD_REQUEST);
+            return;
         }
+
+        try (final DataOutputStream dos = new DataOutputStream(response.getOutputStream())) {
+            dos.writeByte(cachedCodebooks.size());
+            for (final ICacheFile cacheFile : cachedCodebooks) {
+                cacheFile.writeToStream(dos);
+            }
+        } // Stream gets closed here.
+        response.setContentType("application/octet-stream");
+        response.setStatus(HttpServletResponse.SC_OK);
+        baseRequest.setHandled(true);
     }
 
     private void provideThumbnail(final Request baseRequest, final HttpServletResponse response) throws IOException {
-- 
GitLab