From 368cc7c0749ac523597d19985598f662d7520d35 Mon Sep 17 00:00:00 2001
From: Vojtech Moravec <vojtech.moravec.st@vsb.cz>
Date: Fri, 25 Sep 2020 13:06:13 +0200
Subject: [PATCH] Enable usage of multiple decompressors.

This contains functionality to map mipmap level to codebook size. Also -compress-from options is added to set the minimal mipmap level to compress.

Eg. -comress-from 1, means that level 0 is not compressed and levels > 1 are.
---
 .../ViewerCompressionOptions.java             |  22 ++++
 src/main/java/bdv/BigDataViewer.java          | 118 +++++++++---------
 .../bdv/img/remote/RemoteImageLoader.java     |  22 ++--
 .../RemoteVolatileShortArrayLoader.java       |  53 ++++++--
 .../bdv/spimdata/XmlIoSpimDataMinimal.java    |  85 ++++++-------
 5 files changed, 173 insertions(+), 127 deletions(-)
 create mode 100644 src/main/java/azgracompress/ViewerCompressionOptions.java

diff --git a/src/main/java/azgracompress/ViewerCompressionOptions.java b/src/main/java/azgracompress/ViewerCompressionOptions.java
new file mode 100644
index 00000000..c74cd3c1
--- /dev/null
+++ b/src/main/java/azgracompress/ViewerCompressionOptions.java
@@ -0,0 +1,22 @@
+package azgracompress;
+
+public class ViewerCompressionOptions {
+    private boolean enabled = false;
+    private int compressFromMipmapLevel = 0;
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(final boolean enable) {
+        this.enabled = enable;
+    }
+
+    public int getCompressFromMipmapLevel() {
+        return compressFromMipmapLevel;
+    }
+
+    public void setCompressFromMipmapLevel(final int compressFrom) {
+        this.compressFromMipmapLevel = compressFrom;
+    }
+}
diff --git a/src/main/java/bdv/BigDataViewer.java b/src/main/java/bdv/BigDataViewer.java
index 88583bc6..e1f6a331 100644
--- a/src/main/java/bdv/BigDataViewer.java
+++ b/src/main/java/bdv/BigDataViewer.java
@@ -29,73 +29,55 @@
  */
 package bdv;
 
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.swing.ActionMap;
-import javax.swing.JFileChooser;
-import javax.swing.JMenu;
-import javax.swing.JMenuBar;
-import javax.swing.JMenuItem;
-import javax.swing.filechooser.FileFilter;
-
-import azgracompress.cache.ICacheFile;
-import net.imglib2.Volatile;
-import net.imglib2.converter.Converter;
-import net.imglib2.display.ColorConverter;
-import net.imglib2.display.RealARGBColorConverter;
-import net.imglib2.display.ScaledARGBConverter;
-import net.imglib2.type.numeric.ARGBType;
-import net.imglib2.type.numeric.NumericType;
-import net.imglib2.type.numeric.RealType;
-import net.imglib2.type.volatiles.VolatileARGBType;
-
-import org.jdom2.Document;
-import org.jdom2.Element;
-import org.jdom2.JDOMException;
-import org.jdom2.input.SAXBuilder;
-import org.jdom2.output.Format;
-import org.jdom2.output.XMLOutputter;
-import org.scijava.ui.behaviour.io.InputTriggerConfig;
-import org.scijava.ui.behaviour.io.yaml.YamlConfigIO;
-
+import azgracompress.ViewerCompressionOptions;
+import azgracompress.utilities.ColorConsole;
 import bdv.cache.CacheControl;
 import bdv.export.ProgressWriter;
 import bdv.export.ProgressWriterConsole;
 import bdv.spimdata.SpimDataMinimal;
 import bdv.spimdata.WrapBasicImgLoader;
 import bdv.spimdata.XmlIoSpimDataMinimal;
-import bdv.tools.HelpDialog;
-import bdv.tools.InitializeViewerState;
-import bdv.tools.RecordMaxProjectionDialog;
-import bdv.tools.RecordMovieDialog;
-import bdv.tools.VisibilityAndGroupingDialog;
+import bdv.tools.*;
 import bdv.tools.bookmarks.Bookmarks;
 import bdv.tools.bookmarks.BookmarksEditor;
-import bdv.tools.brightness.BrightnessDialog;
-import bdv.tools.brightness.ConverterSetup;
-import bdv.tools.brightness.MinMaxGroup;
-import bdv.tools.brightness.RealARGBColorConverterSetup;
-import bdv.tools.brightness.SetupAssignments;
+import bdv.tools.brightness.*;
 import bdv.tools.crop.CropDialog;
 import bdv.tools.transformation.ManualTransformation;
 import bdv.tools.transformation.ManualTransformationEditor;
 import bdv.tools.transformation.TransformedSource;
-import bdv.viewer.NavigationActions;
-import bdv.viewer.SourceAndConverter;
-import bdv.viewer.ViewerFrame;
-import bdv.viewer.ViewerOptions;
-import bdv.viewer.ViewerPanel;
+import bdv.viewer.*;
 import mpicbg.spim.data.SpimDataException;
 import mpicbg.spim.data.generic.AbstractSpimData;
 import mpicbg.spim.data.generic.sequence.AbstractSequenceDescription;
 import mpicbg.spim.data.generic.sequence.BasicViewSetup;
 import mpicbg.spim.data.sequence.Angle;
 import mpicbg.spim.data.sequence.Channel;
+import net.imglib2.Volatile;
+import net.imglib2.converter.Converter;
+import net.imglib2.display.ColorConverter;
+import net.imglib2.display.RealARGBColorConverter;
+import net.imglib2.display.ScaledARGBConverter;
+import net.imglib2.type.numeric.ARGBType;
+import net.imglib2.type.numeric.NumericType;
+import net.imglib2.type.numeric.RealType;
+import net.imglib2.type.volatiles.VolatileARGBType;
+import org.jdom2.Document;
+import org.jdom2.Element;
+import org.jdom2.JDOMException;
+import org.jdom2.input.SAXBuilder;
+import org.jdom2.output.Format;
+import org.jdom2.output.XMLOutputter;
+import org.scijava.ui.behaviour.io.InputTriggerConfig;
+import org.scijava.ui.behaviour.io.yaml.YamlConfigIO;
+
+import javax.swing.*;
+import javax.swing.filechooser.FileFilter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
 public class BigDataViewer {
     protected final ViewerFrame viewerFrame;
@@ -465,10 +447,10 @@ public class BigDataViewer {
                                      final String windowTitle,
                                      final ProgressWriter progressWriter,
                                      final ViewerOptions options,
-                                     final boolean allowCompression) throws SpimDataException {
+                                     final ViewerCompressionOptions ops) throws SpimDataException {
 
         final XmlIoSpimDataMinimal xmlIoSpimDataMinimal = new XmlIoSpimDataMinimal();
-        xmlIoSpimDataMinimal.setAllowCompression(allowCompression);
+        xmlIoSpimDataMinimal.setViewerCompressionOptions(ops);
         final SpimDataMinimal spimData = xmlIoSpimDataMinimal.load(xmlFilename);
         final BigDataViewer bdv = open(spimData, windowTitle, progressWriter, options);
         if (!bdv.tryLoadSettings(xmlFilename))
@@ -635,19 +617,37 @@ public class BigDataViewer {
         viewer.requestRepaint();
     }
 
-    public static void main(final String[] args) {
-
-        // Default
-        String fn = "http://127.0.0.1:8080/drosophila32";
-
 
+    public static void main(final String[] args) {
         if (args.length < 1) {
             System.err.println("Provide path.");
             return;
         }
+        final String fn = args[0];
+
+        final ViewerCompressionOptions ops = new ViewerCompressionOptions();
+        if (args.length > 1) {
+            for (int i = 1; i < args.length; i++) {
+
+                switch (args[i]) {
+                    case "-qcmp":
+                        ops.setEnabled(true);
+                        break;
+                    case "-compress-from":
+                        if (args.length <= i + 1) {
+                            System.err.println("Missing -compress-from integer parameter.");
+                            return;
+                        }
+                        ops.setCompressFromMipmapLevel(Integer.parseInt(args[++i]));
+                        break;
+                }
+
+            }
+        }
 
-        fn = args[0];
-        final boolean allowCompression = (args.length > 1) && (args[1].equals("-qcmp"));
+        ColorConsole.printf(ColorConsole.Color.Green, "Compression:\t" + (ops.isEnabled() ? "ON" : "OFF"));
+        if (ops.isEnabled())
+            ColorConsole.printf(ColorConsole.Color.Green, "CompressFrom:\t" + ops.getCompressFromMipmapLevel());
 
         try {
             System.setProperty("apple.laf.useScreenMenuBar", "true");
@@ -656,7 +656,7 @@ public class BigDataViewer {
                                            "Server test",
                                            new ProgressWriterConsole(),
                                            ViewerOptions.options(),
-                                           allowCompression);
+                                           ops);
 
         } catch (final Exception e) {
             e.printStackTrace();
diff --git a/src/main/java/bdv/img/remote/RemoteImageLoader.java b/src/main/java/bdv/img/remote/RemoteImageLoader.java
index e2fb6499..949c7cf3 100644
--- a/src/main/java/bdv/img/remote/RemoteImageLoader.java
+++ b/src/main/java/bdv/img/remote/RemoteImageLoader.java
@@ -29,6 +29,7 @@
  */
 package bdv.img.remote;
 
+import azgracompress.ViewerCompressionOptions;
 import azgracompress.cache.ICacheFile;
 import azgracompress.cache.QuantizationCacheManager;
 import azgracompress.compression.ImageDecompressor;
@@ -77,7 +78,7 @@ public class RemoteImageLoader implements ViewerImgLoader {
     /**
      * Flag whether we allow the server to send us compressed data.
      */
-    private boolean allowCompression;
+    private ViewerCompressionOptions viewerCompressionOptions;
 
 
     /**
@@ -124,19 +125,15 @@ public class RemoteImageLoader implements ViewerImgLoader {
                 for (final int setupId : metadata.perSetupMipmapInfo.keySet())
                     setupImgLoaders.put(setupId, new SetupImgLoader(setupId));
 
-                if (allowCompression) {
+                if (viewerCompressionOptions.isEnabled()) {
                     setupCompression();
                 }
             }
         }
     }
 
-    public boolean shouldAllowCompression() {
-        return allowCompression;
-    }
-
-    public void setAllowCompression(final boolean compressionEnabled) {
-        this.allowCompression = compressionEnabled;
+    public void setViewerCompressionOptions(final ViewerCompressionOptions ops) {
+        this.viewerCompressionOptions = ops;
     }
 
     private void setupCompression() throws IOException {
@@ -166,10 +163,11 @@ public class RemoteImageLoader implements ViewerImgLoader {
         ColorConsole.fprintf(ColorConsole.Target.stdout, ColorConsole.Color.Yellow, "Received %d cache files.", cacheFiles.size());
 
 
-        final ICacheFile cachedCodebook = cacheFiles.get(cacheFiles.size() - 1);
-        // TODO(Moravec): Provide all decompressors.
-        shortLoader.setDataDecompressor(new ImageDecompressor(cachedCodebook));
-        System.out.println("\u001b[33mRemoteImageLoader::setupCompression() - instantiated image decompressor in shortLoader.\u001b[0m");
+        final ImageDecompressor[] decompressors = new ImageDecompressor[cacheFiles.size()];
+        for (int i = 0; i < cacheFiles.size(); i++) {
+            decompressors[i] = new ImageDecompressor(cacheFiles.get(i));
+        }
+        shortLoader.setDataDecompressors(decompressors, metadata.maxNumLevels, viewerCompressionOptions.getCompressFromMipmapLevel());
     }
 
 
diff --git a/src/main/java/bdv/img/remote/RemoteVolatileShortArrayLoader.java b/src/main/java/bdv/img/remote/RemoteVolatileShortArrayLoader.java
index 0e85c9ce..7a85c86b 100644
--- a/src/main/java/bdv/img/remote/RemoteVolatileShortArrayLoader.java
+++ b/src/main/java/bdv/img/remote/RemoteVolatileShortArrayLoader.java
@@ -29,7 +29,9 @@
  */
 package bdv.img.remote;
 
+import azgracompress.compression.CompressorDecompressorBase;
 import azgracompress.compression.ImageDecompressor;
+import azgracompress.utilities.ColorConsole;
 import bdv.img.cache.CacheArrayLoader;
 import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray;
 
@@ -38,12 +40,17 @@ import java.io.InputStream;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
 
 public class RemoteVolatileShortArrayLoader implements CacheArrayLoader<VolatileShortArray> {
     private final RemoteImageLoader imgLoader;
 
     private boolean requestCompressedData = false;
-    private ImageDecompressor decompressor;
+    private HashMap<Integer, ImageDecompressor> decompressors;
+    private ImageDecompressor lowestResDecompressor;
+    private int compressFromMipmapLevel = 0;
 
 
     public RemoteVolatileShortArrayLoader(final RemoteImageLoader imgLoader) {
@@ -70,10 +77,9 @@ public class RemoteVolatileShortArrayLoader implements CacheArrayLoader<Volatile
                                         final int setup,
                                         final int level,
                                         final int[] dimensions,
-                                        final long[] min) throws InterruptedException {
+                                        final long[] min) {
 
-
-        if (requestCompressedData) {
+        if (requestCompressedData && level >= compressFromMipmapLevel) {
             return loadArrayFromCompressedDataStream(timepoint, setup, level, dimensions, min);
         }
 
@@ -104,13 +110,13 @@ public class RemoteVolatileShortArrayLoader implements CacheArrayLoader<Volatile
 
     public VolatileShortArray loadArrayFromCompressedDataStream(final int timepoint,
                                                                 final int setup,
-                                                                final int level,
+                                                                final int mipmapLevel,
                                                                 final int[] dimensions,
                                                                 final long[] min) {
 
         short[] data = null;
         try {
-            final URL url = new URL(constructRequestUrl("cell_qcmp", timepoint, setup, level, dimensions, min));
+            final URL url = new URL(constructRequestUrl("cell_qcmp", timepoint, setup, mipmapLevel, dimensions, min));
 
             final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
             connection.setRequestMethod("GET");
@@ -119,7 +125,7 @@ public class RemoteVolatileShortArrayLoader implements CacheArrayLoader<Volatile
             final int contentLength = connection.getContentLength();
 
             final InputStream urlStream = connection.getInputStream();
-            data = decompressor.decompressStream(urlStream, contentLength);
+            data = getDecompressorForMipmapLevel(mipmapLevel).decompressStream(urlStream, contentLength);
 
             urlStream.close();
         } catch (final Exception e) {
@@ -134,8 +140,35 @@ public class RemoteVolatileShortArrayLoader implements CacheArrayLoader<Volatile
         return 2;
     }
 
-    public void setDataDecompressor(final ImageDecompressor imageDecompressor) {
-        this.decompressor = imageDecompressor;
-        requestCompressedData = true;
+    private ImageDecompressor getDecompressorForMipmapLevel(final int mipmapLevel) {
+        assert (decompressors != null && !decompressors.isEmpty());
+        if (decompressors.containsKey(mipmapLevel)) {
+            return decompressors.get(mipmapLevel);
+        }
+        return lowestResDecompressor;
+    }
+
+    public void setDataDecompressors(final ImageDecompressor[] imageDecompressors,
+                                     final int levelCount,
+                                     final int compressFromMipmapLevel) {
+        Arrays.sort(imageDecompressors, Comparator.comparingInt(CompressorDecompressorBase::getBitsPerCodebookIndex));
+
+
+        final int numberOfDecompressionLevels = Math.min((levelCount - compressFromMipmapLevel), imageDecompressors.length);
+        decompressors = new HashMap<>(numberOfDecompressionLevels);
+
+        for (int mipmapLevel = 0; mipmapLevel < numberOfDecompressionLevels; mipmapLevel++) {
+            final ImageDecompressor decompressor = imageDecompressors[(imageDecompressors.length - 1) - mipmapLevel];
+            final int cbSize = (int) Math.pow(2, decompressor.getBitsPerCodebookIndex());
+            final int actualKey = mipmapLevel + compressFromMipmapLevel;
+            decompressors.put(actualKey, decompressor);
+
+            ColorConsole.fprintf(ColorConsole.Target.stdout, ColorConsole.Color.Yellow,
+                                 "Created decompressor for mipmap level %d with codebook of size %d.",
+                                 actualKey, cbSize);
+            lowestResDecompressor = decompressor;
+        }
+        this.compressFromMipmapLevel = compressFromMipmapLevel;
+        requestCompressedData = !decompressors.isEmpty();
     }
 }
diff --git a/src/main/java/bdv/spimdata/XmlIoSpimDataMinimal.java b/src/main/java/bdv/spimdata/XmlIoSpimDataMinimal.java
index 8737c521..2988d6d2 100644
--- a/src/main/java/bdv/spimdata/XmlIoSpimDataMinimal.java
+++ b/src/main/java/bdv/spimdata/XmlIoSpimDataMinimal.java
@@ -29,11 +29,9 @@
  */
 package bdv.spimdata;
 
-import static mpicbg.spim.data.XmlKeys.SPIMDATA_TAG;
-
-import java.io.File;
-
+import azgracompress.ViewerCompressionOptions;
 import bdv.img.remote.RemoteImageLoader;
+import bdv.spimdata.legacy.XmlIoSpimDataMinimalLegacy;
 import mpicbg.spim.data.SpimDataException;
 import mpicbg.spim.data.SpimDataIOException;
 import mpicbg.spim.data.generic.XmlIoAbstractSpimData;
@@ -43,58 +41,53 @@ import mpicbg.spim.data.generic.sequence.XmlIoBasicViewSetups;
 import mpicbg.spim.data.registration.XmlIoViewRegistrations;
 import mpicbg.spim.data.sequence.XmlIoMissingViews;
 import mpicbg.spim.data.sequence.XmlIoTimePoints;
-
 import org.jdom2.Document;
 import org.jdom2.Element;
 import org.jdom2.input.SAXBuilder;
 
-import bdv.spimdata.legacy.XmlIoSpimDataMinimalLegacy;
+import java.io.File;
+
+import static mpicbg.spim.data.XmlKeys.SPIMDATA_TAG;
 
-public class XmlIoSpimDataMinimal extends XmlIoAbstractSpimData< SequenceDescriptionMinimal, SpimDataMinimal >
-{
-    private boolean allowCompression = false;
+public class XmlIoSpimDataMinimal extends XmlIoAbstractSpimData<SequenceDescriptionMinimal, SpimDataMinimal> {
+    private ViewerCompressionOptions compressionOptions;
 
-	public XmlIoSpimDataMinimal()
-	{
-		super( SpimDataMinimal.class,
-				new XmlIoAbstractSequenceDescription<>(
-						SequenceDescriptionMinimal.class,
-						new XmlIoTimePoints(),
-						new XmlIoBasicViewSetups<>( BasicViewSetup.class ),
-						new XmlIoMissingViews() ),
-				new XmlIoViewRegistrations() );
-	}
+    public XmlIoSpimDataMinimal() {
+        super(SpimDataMinimal.class,
+              new XmlIoAbstractSequenceDescription<>(
+                      SequenceDescriptionMinimal.class,
+                      new XmlIoTimePoints(),
+                      new XmlIoBasicViewSetups<>(BasicViewSetup.class),
+                      new XmlIoMissingViews()),
+              new XmlIoViewRegistrations());
+    }
 
-    public void setAllowCompression(final boolean allowCompression) {
-        this.allowCompression = allowCompression;
+    public void setViewerCompressionOptions(final ViewerCompressionOptions ops) {
+        this.compressionOptions = ops;
     }
 
-	@Override
-	public SpimDataMinimal load( final String xmlFilename ) throws SpimDataException
-		{
-			final SAXBuilder sax = new SAXBuilder();
-			Document doc;
-			try
-			{
-				doc = sax.build( xmlFilename );
-			}
-			catch ( final Exception e )
-			{
-				throw new SpimDataIOException( e );
-			}
-			final Element root = doc.getRootElement();
+    @Override
+    public SpimDataMinimal load(final String xmlFilename) throws SpimDataException {
+        final SAXBuilder sax = new SAXBuilder();
+        final Document doc;
+        try {
+            doc = sax.build(xmlFilename);
+        } catch (final Exception e) {
+            throw new SpimDataIOException(e);
+        }
+        final Element root = doc.getRootElement();
 
-			if ( root.getName().equals( "SequenceDescription" ) )
-				return XmlIoSpimDataMinimalLegacy.fromXml( root, new File( xmlFilename ) );
+        if (root.getName().equals("SequenceDescription"))
+            return XmlIoSpimDataMinimalLegacy.fromXml(root, new File(xmlFilename));
 
-			if ( root.getName() != SPIMDATA_TAG )
-				throw new RuntimeException( "expected <" + SPIMDATA_TAG + "> root element. wrong file?" );
+        if (root.getName() != SPIMDATA_TAG)
+            throw new RuntimeException("expected <" + SPIMDATA_TAG + "> root element. wrong file?");
 
-            SpimDataMinimal spimDataMinimal = fromXml(root, new File(xmlFilename));
-            if (spimDataMinimal.getSequenceDescription().getImgLoader() instanceof RemoteImageLoader) {
-                final RemoteImageLoader remoteImageLoader = (RemoteImageLoader) spimDataMinimal.getSequenceDescription().getImgLoader();
-                remoteImageLoader.setAllowCompression(allowCompression);
-            }
-			return spimDataMinimal;
-		}
+        final SpimDataMinimal spimDataMinimal = fromXml(root, new File(xmlFilename));
+        if (spimDataMinimal.getSequenceDescription().getImgLoader() instanceof RemoteImageLoader) {
+            final RemoteImageLoader remoteImageLoader = (RemoteImageLoader) spimDataMinimal.getSequenceDescription().getImgLoader();
+            remoteImageLoader.setViewerCompressionOptions(compressionOptions);
+        }
+        return spimDataMinimal;
+    }
 }
-- 
GitLab