From 9236e58520c88d78661e4c09bbc66886d2c71807 Mon Sep 17 00:00:00 2001
From: Vojtech Moravec <vojtech.moravec.st@vsb.cz>
Date: Fri, 11 Sep 2020 13:44:01 +0200
Subject: [PATCH] WIP: Decompression support in viewer.

This commit adds an option to allow compression which is propagated all
the way to RemoteImageLoader, which should handle the decompression.
Maybe we should subclass it?

Also a lot of code was automatically formatted.
---
 .gitignore                                    |    2 +-
 pom.xml                                       |    8 +-
 src/main/java/bdv/BigDataViewer.java          | 1206 ++++++++---------
 .../bdv/img/remote/RemoteImageLoader.java     |  430 +++---
 .../RemoteVolatileShortArrayLoader.java       |   45 +-
 .../img/remote/XmlIoRemoteImageLoader.java    |   57 +-
 .../bdv/spimdata/XmlIoSpimDataMinimal.java    |   18 +-
 .../legacy/XmlIoSpimDataMinimalLegacy.java    |    1 +
 8 files changed, 879 insertions(+), 888 deletions(-)

diff --git a/.gitignore b/.gitignore
index 14803ad9..8098709b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,7 +8,7 @@
 .project
 .settings
 /target
-
+classes/
 # IntelliJ
 .idea/
 *.iml
diff --git a/pom.xml b/pom.xml
index ce3fdd41..3bcf2216 100644
--- a/pom.xml
+++ b/pom.xml
@@ -201,5 +201,11 @@
 			<artifactId>junit</artifactId>
 			<scope>test</scope>
 		</dependency>
-	</dependencies>
+        <dependency>
+            <groupId>org.azgra</groupId>
+            <artifactId>DataCompressor</artifactId>
+            <version>1.0-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/src/main/java/bdv/BigDataViewer.java b/src/main/java/bdv/BigDataViewer.java
index 266aa8d8..9f2390ba 100644
--- a/src/main/java/bdv/BigDataViewer.java
+++ b/src/main/java/bdv/BigDataViewer.java
@@ -43,6 +43,7 @@ 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;
@@ -96,628 +97,589 @@ import mpicbg.spim.data.generic.sequence.BasicViewSetup;
 import mpicbg.spim.data.sequence.Angle;
 import mpicbg.spim.data.sequence.Channel;
 
-public class BigDataViewer
-{
-	protected final ViewerFrame viewerFrame;
+public class BigDataViewer {
+    protected final ViewerFrame viewerFrame;
 
-	protected final ViewerPanel viewer;
+    protected final ViewerPanel viewer;
 
-	protected final SetupAssignments setupAssignments;
-
-	protected final ManualTransformation manualTransformation;
-
-	protected final Bookmarks bookmarks;
-
-	protected final BrightnessDialog brightnessDialog;
-
-	protected final CropDialog cropDialog;
-
-	protected final RecordMovieDialog movieDialog;
-
-	protected final RecordMaxProjectionDialog movieMaxProjectDialog;
-
-	protected final VisibilityAndGroupingDialog activeSourcesDialog;
-
-	protected final HelpDialog helpDialog;
-
-	protected final ManualTransformationEditor manualTransformationEditor;
-
-	protected final BookmarksEditor bookmarkEditor;
-
-	protected final JFileChooser fileChooser;
-
-	protected File proposedSettingsFile;
-
-	public void toggleManualTransformation()
-	{
-		manualTransformationEditor.toggle();
-	}
-
-	public void initSetBookmark()
-	{
-		bookmarkEditor.initSetBookmark();
-	}
-
-	public void initGoToBookmark()
-	{
-		bookmarkEditor.initGoToBookmark();
-	}
-
-	public void initGoToBookmarkRotation()
-	{
-		bookmarkEditor.initGoToBookmarkRotation();
-	}
-
-	private static String createSetupName( final BasicViewSetup setup )
-	{
-		if ( setup.hasName() )
-			return setup.getName();
-
-		String name = "";
-
-		final Angle angle = setup.getAttribute( Angle.class );
-		if ( angle != null )
-			name += ( name.isEmpty() ? "" : " " ) + "a " + angle.getName();
-
-		final Channel channel = setup.getAttribute( Channel.class );
-		if ( channel != null )
-			name += ( name.isEmpty() ? "" : " " ) + "c " + channel.getName();
-
-		return name;
-	}
-
-	/**
-	 * Create standard converter from the given {@code type} to ARGB:
-	 * <ul>
-	 * <li>For {@code RealType}s a {@link RealARGBColorConverter} is
-	 * returned.</li>
-	 * <li>For {@code ARGBType}s a {@link ScaledARGBConverter.ARGB} is
-	 * returned.</li>
-	 * <li>For {@code VolatileARGBType}s a
-	 * {@link ScaledARGBConverter.VolatileARGB} is returned.</li>
-	 * </ul>
-	 */
-	@SuppressWarnings( "unchecked" )
-	public static < T extends NumericType< T > > Converter< T, ARGBType > createConverterToARGB( final T type )
-	{
-		if ( type instanceof RealType )
-		{
-			final RealType< ? > t = ( RealType< ? > ) type;
-			final double typeMin = Math.max( 0, Math.min( t.getMinValue(), 65535 ) );
-			final double typeMax = Math.max( 0, Math.min( t.getMaxValue(), 65535 ) );
-			return ( Converter< T, ARGBType > ) RealARGBColorConverter.create( t, typeMin, typeMax );
-		}
-		else if ( type instanceof ARGBType )
-			return ( Converter< T, ARGBType > ) new ScaledARGBConverter.ARGB( 0, 255 );
-		else if ( type instanceof VolatileARGBType )
-			return ( Converter< T, ARGBType > ) new ScaledARGBConverter.VolatileARGB( 0, 255 );
-		else
-			throw new IllegalArgumentException( "ImgLoader of type " + type.getClass() + " not supported." );
-	}
-
-	/**
-	 * Create a {@code ConverterSetup} for the given {@code SourceAndConverter}.
-	 * {@link SourceAndConverter#asVolatile() Nested volatile}
-	 * {@code SourceAndConverter} are added to the {@code ConverterSetup} if
-	 * present. If {@code SourceAndConverter} does not comprise a
-	 * {@code ColorConverter}, returns {@code null}.
-	 *
-	 * @param soc
-	 *            {@code SourceAndConverter} for which to create a
-	 *            {@code ConverterSetup}
-	 * @param setupId
-	 *            setupId of the created {@code ConverterSetup}
-	 * @return a new {@code ConverterSetup} or {@code null}
-	 */
-	public static ConverterSetup createConverterSetup( final SourceAndConverter< ? > soc, final int setupId )
-	{
-		final List< ColorConverter > converters = new ArrayList<>();
-
-		final Converter< ?, ARGBType > c = soc.getConverter();
-		if ( c instanceof ColorConverter )
-			converters.add( ( ColorConverter ) c );
-
-		final SourceAndConverter< ? extends Volatile< ? > > vsoc = soc.asVolatile();
-		if ( vsoc != null )
-		{
-			final Converter< ?, ARGBType > vc = vsoc.getConverter();
-			if ( vc instanceof ColorConverter )
-				converters.add( ( ColorConverter ) vc );
-		}
-
-		if ( converters.isEmpty() )
-			return null;
-		else
-			return new RealARGBColorConverterSetup( setupId, converters );
-	}
-
-	/**
-	 * Decorate source with an extra transformation, that can be edited manually
-	 * in this viewer. {@link SourceAndConverter#asVolatile() Nested volatile}
-	 * {@code SourceAndConverter} are wrapped as well, if present.
-	 */
-	public static < T, V extends Volatile< T > > SourceAndConverter< T > wrapWithTransformedSource( final SourceAndConverter< T > soc )
-	{
-		if ( soc.asVolatile() == null )
-			return new SourceAndConverter<>( new TransformedSource<>( soc.getSpimSource() ), soc.getConverter() );
-
-		@SuppressWarnings( "unchecked" )
-		final SourceAndConverter< V > vsoc = ( SourceAndConverter< V > ) soc.asVolatile();
-		final TransformedSource< T > ts = new TransformedSource<>( soc.getSpimSource() );
-		final TransformedSource< V > vts = new TransformedSource<>( vsoc.getSpimSource(), ts );
-		return new SourceAndConverter<>( ts, soc.getConverter(), new SourceAndConverter<>( vts, vsoc.getConverter() ) );
-	}
-
-	private static < T extends NumericType< T >, V extends Volatile< T > & NumericType< V > > void initSetupNumericType(
-			final AbstractSpimData< ? > spimData,
-			final BasicViewSetup setup,
-			final List< ConverterSetup > converterSetups,
-			final List< SourceAndConverter< ? > > sources )
-	{
-		final int setupId = setup.getId();
-		final ViewerImgLoader imgLoader = ( ViewerImgLoader ) spimData.getSequenceDescription().getImgLoader();
-		@SuppressWarnings( "unchecked" )
-		final ViewerSetupImgLoader< T, V > setupImgLoader = ( ViewerSetupImgLoader< T, V > ) imgLoader.getSetupImgLoader( setupId );
-		final T type = setupImgLoader.getImageType();
-		final V volatileType = setupImgLoader.getVolatileImageType();
-
-		if ( ! ( type instanceof NumericType ) )
-			throw new IllegalArgumentException( "ImgLoader of type " + type.getClass() + " not supported." );
-
-		final String setupName = createSetupName( setup );
-
-		SourceAndConverter< V > vsoc = null;
-		if ( volatileType != null )
-		{
-			final VolatileSpimSource< V > vs = new VolatileSpimSource<>( spimData, setupId, setupName );
-			vsoc = new SourceAndConverter<>( vs, createConverterToARGB( volatileType ) );
-		}
-
-		final SpimSource< T > s = new SpimSource<>( spimData, setupId, setupName );
-		final SourceAndConverter< T > soc = new SourceAndConverter<>( s, createConverterToARGB( type ), vsoc );
-		final SourceAndConverter< T > tsoc = wrapWithTransformedSource( soc );
-		sources.add( tsoc );
-
-		final ConverterSetup converterSetup = createConverterSetup( tsoc, setupId );
-		if ( converterSetup != null )
-			converterSetups.add( converterSetup );
-	}
-
-	public static void initSetups(
-			final AbstractSpimData< ? > spimData,
-			final List< ConverterSetup > converterSetups,
-			final List< SourceAndConverter< ? > > sources )
-	{
-		for ( final BasicViewSetup setup : spimData.getSequenceDescription().getViewSetupsOrdered() )
-			initSetupNumericType( spimData, setup, converterSetups, sources );
-	}
-
-	/**
-	 *
-	 * @param converterSetups
-	 *            list of {@link ConverterSetup} that control min/max and color
-	 *            of sources.
-	 * @param sources
-	 *            list of pairs of source of some type and converter from that
-	 *            type to ARGB.
-	 * @param spimData
-	 *            may be null. The {@link AbstractSpimData} of the dataset (if
-	 *            there is one). If it exists, it is used to set up a "Crop"
-	 *            dialog.
-	 * @param numTimepoints
-	 *            the number of timepoints in the dataset.
-	 * @param cache
-	 *            handle to cache. This is used to control io timing.
-	 * @param windowTitle
-	 *            title of the viewer window.
-	 * @param progressWriter
-	 *            a {@link ProgressWriter} to which BDV may report progress
-	 *            (currently only used in the "Record Movie" dialog).
-	 * @param options
-	 *            optional parameters.
-	 */
-	public BigDataViewer(
-			final ArrayList< ConverterSetup > converterSetups,
-			final ArrayList< SourceAndConverter< ? > > sources,
-			final AbstractSpimData< ? > spimData,
-			final int numTimepoints,
-			final CacheControl cache,
-			final String windowTitle,
-			final ProgressWriter progressWriter,
-			final ViewerOptions options )
-	{
-		final InputTriggerConfig inputTriggerConfig = getInputTriggerConfig( options );
-		if ( options.values.getTransformEventHandlerFactory() instanceof BehaviourTransformEventHandlerFactory )
-			( ( BehaviourTransformEventHandlerFactory< ? > ) options.values.getTransformEventHandlerFactory() ).setConfig( inputTriggerConfig );
-
-		viewerFrame = new ViewerFrame( sources, numTimepoints, cache, options );
-		if ( windowTitle != null )
-			viewerFrame.setTitle( windowTitle );
-		viewer = viewerFrame.getViewerPanel();
-
-		for ( final ConverterSetup cs : converterSetups )
-			cs.setViewer( viewer );
-
-		manualTransformation = new ManualTransformation( viewer );
-		manualTransformationEditor = new ManualTransformationEditor( viewer, viewerFrame.getKeybindings() );
-
-		bookmarks = new Bookmarks();
-		bookmarkEditor = new BookmarksEditor( viewer, viewerFrame.getKeybindings(), bookmarks );
-
-		setupAssignments = new SetupAssignments( converterSetups, 0, 65535 );
-		if ( setupAssignments.getMinMaxGroups().size() > 0 )
-		{
-			final MinMaxGroup group = setupAssignments.getMinMaxGroups().get( 0 );
-			for ( final ConverterSetup setup : setupAssignments.getConverterSetups() )
-				setupAssignments.moveSetupToGroup( setup, group );
-		}
-
-		brightnessDialog = new BrightnessDialog( viewerFrame, setupAssignments );
-
-		if (spimData != null )
-			viewer.getSourceInfoOverlayRenderer().setTimePointsOrdered( spimData.getSequenceDescription().getTimePoints().getTimePointsOrdered() );
-
-		cropDialog = ( spimData == null ) ? null : new CropDialog( viewerFrame, viewer, spimData.getSequenceDescription() );
-
-		movieDialog = new RecordMovieDialog( viewerFrame, viewer, progressWriter );
-		// this is just to get updates of window size:
-		viewer.getDisplay().addOverlayRenderer( movieDialog );
-
-		movieMaxProjectDialog = new RecordMaxProjectionDialog( viewerFrame, viewer, progressWriter );
-		// this is just to get updates of window size:
-		viewer.getDisplay().addOverlayRenderer( movieMaxProjectDialog );
-
-		activeSourcesDialog = new VisibilityAndGroupingDialog( viewerFrame, viewer.getVisibilityAndGrouping() );
-
-		helpDialog = new HelpDialog( viewerFrame );
-
-		fileChooser = new JFileChooser();
-		fileChooser.setFileFilter( new FileFilter()
-		{
-			@Override
-			public String getDescription()
-			{
-				return "xml files";
-			}
-
-			@Override
-			public boolean accept( final File f )
-			{
-				if ( f.isDirectory() )
-					return true;
-				if ( f.isFile() )
-				{
-					final String s = f.getName();
-					final int i = s.lastIndexOf( '.' );
-					if ( i > 0 && i < s.length() - 1 )
-					{
-						final String ext = s.substring( i + 1 ).toLowerCase();
-						return ext.equals( "xml" );
-					}
-				}
-				return false;
-			}
-		} );
-
-		NavigationActions.installActionBindings( viewerFrame.getKeybindings(), viewer, inputTriggerConfig );
-		BigDataViewerActions.installActionBindings( viewerFrame.getKeybindings(), this, inputTriggerConfig );
-
-		final JMenuBar menubar = new JMenuBar();
-		JMenu menu = new JMenu( "File" );
-		menubar.add( menu );
-
-		final ActionMap actionMap = viewerFrame.getKeybindings().getConcatenatedActionMap();
-		final JMenuItem miLoadSettings = new JMenuItem( actionMap.get( BigDataViewerActions.LOAD_SETTINGS ) );
-		miLoadSettings.setText( "Load settings" );
-		menu.add( miLoadSettings );
-
-		final JMenuItem miSaveSettings = new JMenuItem( actionMap.get( BigDataViewerActions.SAVE_SETTINGS ) );
-		miSaveSettings.setText( "Save settings" );
-		menu.add( miSaveSettings );
-
-		menu = new JMenu( "Settings" );
-		menubar.add( menu );
-
-		final JMenuItem miBrightness = new JMenuItem( actionMap.get( BigDataViewerActions.BRIGHTNESS_SETTINGS ) );
-		miBrightness.setText( "Brightness & Color" );
-		menu.add( miBrightness );
-
-		final JMenuItem miVisibility = new JMenuItem( actionMap.get( BigDataViewerActions.VISIBILITY_AND_GROUPING ) );
-		miVisibility.setText( "Visibility & Grouping" );
-		menu.add( miVisibility );
-
-		menu = new JMenu( "Tools" );
-		menubar.add( menu );
-
-		if ( cropDialog != null )
-		{
-			final JMenuItem miCrop = new JMenuItem( actionMap.get( BigDataViewerActions.CROP ) );
-			miCrop.setText( "Crop" );
-			menu.add( miCrop );
-		}
-
-		final JMenuItem miMovie = new JMenuItem( actionMap.get( BigDataViewerActions.RECORD_MOVIE ) );
-		miMovie.setText( "Record Movie" );
-		menu.add( miMovie );
-
-		final JMenuItem miMaxProjectMovie = new JMenuItem( actionMap.get( BigDataViewerActions.RECORD_MAX_PROJECTION_MOVIE ) );
-		miMaxProjectMovie.setText( "Record Max-Projection Movie" );
-		menu.add( miMaxProjectMovie );
-
-		final JMenuItem miManualTransform = new JMenuItem( actionMap.get( BigDataViewerActions.MANUAL_TRANSFORM ) );
-		miManualTransform.setText( "Manual Transform" );
-		menu.add( miManualTransform );
-
-		menu = new JMenu( "Help" );
-		menubar.add( menu );
-
-		final JMenuItem miHelp = new JMenuItem( actionMap.get( BigDataViewerActions.SHOW_HELP ) );
-		miHelp.setText( "Show Help" );
-		menu.add( miHelp );
-
-		viewerFrame.setJMenuBar( menubar );
-	}
-
-	public static BigDataViewer open( final AbstractSpimData< ? > spimData, final String windowTitle, final ProgressWriter progressWriter, final ViewerOptions options )
-	{
-		if ( WrapBasicImgLoader.wrapImgLoaderIfNecessary( spimData ) )
-		{
-			System.err.println( "WARNING:\nOpening <SpimData> dataset that is not suited for interactive browsing.\nConsider resaving as HDF5 for better performance." );
-		}
-
-		final ArrayList< ConverterSetup > converterSetups = new ArrayList<>();
-		final ArrayList< SourceAndConverter< ? > > sources = new ArrayList<>();
-		initSetups( spimData, converterSetups, sources );
-
-		final AbstractSequenceDescription< ?, ?, ? > seq = spimData.getSequenceDescription();
-		final int numTimepoints = seq.getTimePoints().size();
-		final CacheControl cache = ( ( ViewerImgLoader ) seq.getImgLoader() ).getCacheControl();
-
-		final BigDataViewer bdv = new BigDataViewer( converterSetups, sources, spimData, numTimepoints, cache, windowTitle, progressWriter, options );
-
-		WrapBasicImgLoader.removeWrapperIfPresent( spimData );
-
-		bdv.viewerFrame.setVisible( true );
-		InitializeViewerState.initTransform( bdv.viewer );
-		return bdv;
-	}
-
-	public static BigDataViewer open( final String xmlFilename, final String windowTitle, final ProgressWriter progressWriter, final ViewerOptions options ) throws SpimDataException
-	{
-		final SpimDataMinimal spimData = new XmlIoSpimDataMinimal().load( xmlFilename );
-		final BigDataViewer bdv = open( spimData, windowTitle, progressWriter, options );
-		if ( !bdv.tryLoadSettings( xmlFilename ) )
-			InitializeViewerState.initBrightness( 0.001, 0.999, bdv.viewer, bdv.setupAssignments );
-		return bdv;
-	}
-
-	public static BigDataViewer open(
-			final ArrayList< ConverterSetup > converterSetups,
-			final ArrayList< SourceAndConverter< ? > > sources,
-			final int numTimepoints,
-			final CacheControl cache,
-			final String windowTitle,
-			final ProgressWriter progressWriter,
-			final ViewerOptions options )
-	{
-		final BigDataViewer bdv = new BigDataViewer( converterSetups, sources, null, numTimepoints, cache, windowTitle, progressWriter, options );
-		bdv.viewerFrame.setVisible( true );
-		InitializeViewerState.initTransform( bdv.viewer );
-		return bdv;
-	}
-
-	public ViewerPanel getViewer()
-	{
-		return viewer;
-	}
-
-	public ViewerFrame getViewerFrame()
-	{
-		return viewerFrame;
-	}
-
-	public SetupAssignments getSetupAssignments()
-	{
-		return setupAssignments;
-	}
-
-	public ManualTransformationEditor getManualTransformEditor()
-	{
-		return manualTransformationEditor;
-	}
-
-	public boolean tryLoadSettings( final String xmlFilename )
-	{
-		proposedSettingsFile = null;
-		if( xmlFilename.startsWith( "http://" ) )
-		{
-			// load settings.xml from the BigDataServer
-			final String settings = xmlFilename + "settings";
-			{
-				try
-				{
-					loadSettings( settings );
-					return true;
-				}
-				catch ( final FileNotFoundException e )
-				{}
-				catch ( final Exception e )
-				{
-					e.printStackTrace();
-				}
-			}
-		}
-		else if ( xmlFilename.endsWith( ".xml" ) )
-		{
-			final String settings = xmlFilename.substring( 0, xmlFilename.length() - ".xml".length() ) + ".settings" + ".xml";
-			proposedSettingsFile = new File( settings );
-			if ( proposedSettingsFile.isFile() )
-			{
-				try
-				{
-					loadSettings( settings );
-					return true;
-				}
-				catch ( final Exception e )
-				{
-					e.printStackTrace();
-				}
-			}
-		}
-		return false;
-	}
-
-	public void saveSettings()
-	{
-		fileChooser.setSelectedFile( proposedSettingsFile );
-		final int returnVal = fileChooser.showSaveDialog( null );
-		if ( returnVal == JFileChooser.APPROVE_OPTION )
-		{
-			proposedSettingsFile = fileChooser.getSelectedFile();
-			try
-			{
-				saveSettings( proposedSettingsFile.getCanonicalPath() );
-			}
-			catch ( final IOException e )
-			{
-				e.printStackTrace();
-			}
-		}
-	}
-
-	public void saveSettings( final String xmlFilename ) throws IOException
-	{
-		final Element root = new Element( "Settings" );
-		root.addContent( viewer.stateToXml() );
-		root.addContent( setupAssignments.toXml() );
-		root.addContent( manualTransformation.toXml() );
-		root.addContent( bookmarks.toXml() );
-		final Document doc = new Document( root );
-		final XMLOutputter xout = new XMLOutputter( Format.getPrettyFormat() );
-		xout.output( doc, new FileWriter( xmlFilename ) );
-	}
-
-	/**
-	 * If {@code options} doesn't define a {@link InputTriggerConfig}, try to
-	 * load it from files in this order:
-	 * <ol>
-	 * <li>"bdvkeyconfig.yaml" in the current directory.
-	 * <li>".bdv/bdvkeyconfig.yaml" in the user's home directory.
-	 * <li>legacy "bigdataviewer.keys.properties" in current directory (will be
-	 * also written to "bdvkeyconfig.yaml").
-	 * </ol>
-	 *
-	 * @param options
-	 * @return
-	 */
-	public static InputTriggerConfig getInputTriggerConfig( final ViewerOptions options )
-	{
-		InputTriggerConfig conf = options.values.getInputTriggerConfig();
-
-		// try "bdvkeyconfig.yaml" in current directory
-		if ( conf == null && new File( "bdvkeyconfig.yaml" ).isFile() )
-		{
-			try
-			{
-				conf = new InputTriggerConfig( YamlConfigIO.read( "bdvkeyconfig.yaml" ) );
-			}
-			catch ( final IOException e )
-			{}
-		}
-
-		// try "~/.bdv/bdvkeyconfig.yaml"
-		if ( conf == null )
-		{
-			final String fn = System.getProperty( "user.home" ) + "/.bdv/bdvkeyconfig.yaml";
-			if ( new File( fn ).isFile() )
-			{
-				try
-				{
-					conf = new InputTriggerConfig( YamlConfigIO.read( fn ) );
-				}
-				catch ( final IOException e )
-				{}
-			}
-		}
-
-		if ( conf == null )
-		{
-			conf = new InputTriggerConfig();
-		}
-
-		return conf;
-	}
-
-	public void loadSettings()
-	{
-		fileChooser.setSelectedFile( proposedSettingsFile );
-		final int returnVal = fileChooser.showOpenDialog( null );
-		if ( returnVal == JFileChooser.APPROVE_OPTION )
-		{
-			proposedSettingsFile = fileChooser.getSelectedFile();
-			try
-			{
-				loadSettings( proposedSettingsFile.getCanonicalPath() );
-			}
-			catch ( final Exception e )
-			{
-				e.printStackTrace();
-			}
-		}
-	}
-
-	public void loadSettings( final String xmlFilename ) throws IOException, JDOMException
-	{
-		final SAXBuilder sax = new SAXBuilder();
-		final Document doc = sax.build( xmlFilename );
-		final Element root = doc.getRootElement();
-		viewer.stateFromXml( root );
-		setupAssignments.restoreFromXml( root );
-		manualTransformation.restoreFromXml( root );
-		bookmarks.restoreFromXml( root );
-		activeSourcesDialog.update();
-		viewer.requestRepaint();
-	}
-
-	public static void main( final String[] args )
-	{
-//		final String fn = "http://tomancak-mac-17.mpi-cbg.de:8080/openspim/";
-//		final String fn = "/Users/Pietzsch/Desktop/openspim/datasetHDF.xml";
-//		final String fn = "/Users/pietzsch/workspace/data/111010_weber_full.xml";
-//		final String fn = "/Users/Pietzsch/Desktop/spimrec2/dataset.xml";
-//		final String fn = "/Users/pietzsch/Desktop/HisYFP-SPIM/dataset.xml";
-//		final String fn = "/Users/Pietzsch/Desktop/bdv example/drosophila 2.xml";
-//		final String fn = "/Users/pietzsch/Desktop/data/clusterValia/140219-1/valia-140219-1.xml";
-//		final String fn = "/Users/Pietzsch/Desktop/data/catmaid.xml";
-//		final String fn = "src/main/resources/openconnectome-bock11-neariso.xml";
-//		final String fn = "/home/saalfeld/catmaid.xml";
-//		final String fn = "/home/saalfeld/catmaid-fafb00-v9.xml";
-//		final String fn = "/home/saalfeld/catmaid-fafb00-sample_A_cutout_3k.xml";
-//		final String fn = "/home/saalfeld/catmaid-thorsten.xml";
-//		final String fn = "/home/saalfeld/knossos-example.xml";
-//		final String fn = "/Users/Pietzsch/Desktop/data/catmaid-confocal.xml";
-//		final String fn = "/Users/pietzsch/desktop/data/BDV130418A325/BDV130418A325_NoTempReg.xml";
-//		final String fn = "/Users/pietzsch/Desktop/data/valia2/valia.xml";
-//		final String fn = "/Users/pietzsch/workspace/data/fast fly/111010_weber/combined.xml";
-//		final String fn = "/Users/pietzsch/workspace/data/mette/mette.xml";
-//		final String fn = "/Users/tobias/Desktop/openspim.xml";
-//		final String fn = "/Users/pietzsch/Desktop/data/fibsem.xml";
-//		final String fn = "/Users/pietzsch/Desktop/data/fibsem-remote.xml";
-//		final String fn = "/Users/pietzsch/Desktop/url-valia.xml";
-//		final String fn = "/Users/pietzsch/Desktop/data/clusterValia/140219-1/valia-140219-1.xml";
-		final String fn = "/Users/pietzsch/workspace/data/111010_weber_full.xml";
-//		final String fn = "/Volumes/projects/tomancak_lightsheet/Mette/ZeissZ1SPIM/Maritigrella/021013_McH2BsGFP_CAAX-mCherry/11-use/hdf5/021013_McH2BsGFP_CAAX-mCherry-11-use.xml";
-		try
-		{
-			System.setProperty( "apple.laf.useScreenMenuBar", "true" );
-
-			final BigDataViewer bdv = open( fn, new File( fn ).getName(), new ProgressWriterConsole(), ViewerOptions.options() );
-
-//			DumpInputConfig.writeToYaml( System.getProperty( "user.home" ) + "/.bdv/bdvkeyconfig.yaml", bdv.getViewerFrame() );
-		}
-		catch ( final Exception e )
-		{
-			e.printStackTrace();
-		}
-	}
+    protected final SetupAssignments setupAssignments;
+
+    protected final ManualTransformation manualTransformation;
+
+    protected final Bookmarks bookmarks;
+
+    protected final BrightnessDialog brightnessDialog;
+
+    protected final CropDialog cropDialog;
+
+    protected final RecordMovieDialog movieDialog;
+
+    protected final RecordMaxProjectionDialog movieMaxProjectDialog;
+
+    protected final VisibilityAndGroupingDialog activeSourcesDialog;
+
+    protected final HelpDialog helpDialog;
+
+    protected final ManualTransformationEditor manualTransformationEditor;
+
+    protected final BookmarksEditor bookmarkEditor;
+
+    protected final JFileChooser fileChooser;
+
+    protected File proposedSettingsFile;
+
+    public void toggleManualTransformation() {
+        manualTransformationEditor.toggle();
+    }
+
+    public void initSetBookmark() {
+        bookmarkEditor.initSetBookmark();
+    }
+
+    public void initGoToBookmark() {
+        bookmarkEditor.initGoToBookmark();
+    }
+
+    public void initGoToBookmarkRotation() {
+        bookmarkEditor.initGoToBookmarkRotation();
+    }
+
+    private static String createSetupName(final BasicViewSetup setup) {
+        if (setup.hasName())
+            return setup.getName();
+
+        String name = "";
+
+        final Angle angle = setup.getAttribute(Angle.class);
+        if (angle != null)
+            name += (name.isEmpty() ? "" : " ") + "a " + angle.getName();
+
+        final Channel channel = setup.getAttribute(Channel.class);
+        if (channel != null)
+            name += (name.isEmpty() ? "" : " ") + "c " + channel.getName();
+
+        return name;
+    }
+
+    /**
+     * Create standard converter from the given {@code type} to ARGB:
+     * <ul>
+     * <li>For {@code RealType}s a {@link RealARGBColorConverter} is
+     * returned.</li>
+     * <li>For {@code ARGBType}s a {@link ScaledARGBConverter.ARGB} is
+     * returned.</li>
+     * <li>For {@code VolatileARGBType}s a
+     * {@link ScaledARGBConverter.VolatileARGB} is returned.</li>
+     * </ul>
+     */
+    @SuppressWarnings("unchecked")
+    public static <T extends NumericType<T>> Converter<T, ARGBType> createConverterToARGB(final T type) {
+        if (type instanceof RealType) {
+            final RealType<?> t = (RealType<?>) type;
+            final double typeMin = Math.max(0, Math.min(t.getMinValue(), 65535));
+            final double typeMax = Math.max(0, Math.min(t.getMaxValue(), 65535));
+            return (Converter<T, ARGBType>) RealARGBColorConverter.create(t, typeMin, typeMax);
+        } else if (type instanceof ARGBType)
+            return (Converter<T, ARGBType>) new ScaledARGBConverter.ARGB(0, 255);
+        else if (type instanceof VolatileARGBType)
+            return (Converter<T, ARGBType>) new ScaledARGBConverter.VolatileARGB(0, 255);
+        else
+            throw new IllegalArgumentException("ImgLoader of type " + type.getClass() + " not supported.");
+    }
+
+    /**
+     * Create a {@code ConverterSetup} for the given {@code SourceAndConverter}.
+     * {@link SourceAndConverter#asVolatile() Nested volatile}
+     * {@code SourceAndConverter} are added to the {@code ConverterSetup} if
+     * present. If {@code SourceAndConverter} does not comprise a
+     * {@code ColorConverter}, returns {@code null}.
+     *
+     * @param soc     {@code SourceAndConverter} for which to create a
+     *                {@code ConverterSetup}
+     * @param setupId setupId of the created {@code ConverterSetup}
+     * @return a new {@code ConverterSetup} or {@code null}
+     */
+    public static ConverterSetup createConverterSetup(final SourceAndConverter<?> soc, final int setupId) {
+        final List<ColorConverter> converters = new ArrayList<>();
+
+        final Converter<?, ARGBType> c = soc.getConverter();
+        if (c instanceof ColorConverter)
+            converters.add((ColorConverter) c);
+
+        final SourceAndConverter<? extends Volatile<?>> vsoc = soc.asVolatile();
+        if (vsoc != null) {
+            final Converter<?, ARGBType> vc = vsoc.getConverter();
+            if (vc instanceof ColorConverter)
+                converters.add((ColorConverter) vc);
+        }
+
+        if (converters.isEmpty())
+            return null;
+        else
+            return new RealARGBColorConverterSetup(setupId, converters);
+    }
+
+    /**
+     * Decorate source with an extra transformation, that can be edited manually
+     * in this viewer. {@link SourceAndConverter#asVolatile() Nested volatile}
+     * {@code SourceAndConverter} are wrapped as well, if present.
+     */
+    public static <T, V extends Volatile<T>> SourceAndConverter<T> wrapWithTransformedSource(final SourceAndConverter<T> soc) {
+        if (soc.asVolatile() == null)
+            return new SourceAndConverter<>(new TransformedSource<>(soc.getSpimSource()), soc.getConverter());
+
+        @SuppressWarnings("unchecked") final SourceAndConverter<V> vsoc = (SourceAndConverter<V>) soc.asVolatile();
+        final TransformedSource<T> ts = new TransformedSource<>(soc.getSpimSource());
+        final TransformedSource<V> vts = new TransformedSource<>(vsoc.getSpimSource(), ts);
+        return new SourceAndConverter<>(ts, soc.getConverter(), new SourceAndConverter<>(vts, vsoc.getConverter()));
+    }
+
+    private static <T extends NumericType<T>, V extends Volatile<T> & NumericType<V>> void initSetupNumericType(
+            final AbstractSpimData<?> spimData,
+            final BasicViewSetup setup,
+            final List<ConverterSetup> converterSetups,
+            final List<SourceAndConverter<?>> sources) {
+        final int setupId = setup.getId();
+        final ViewerImgLoader imgLoader = (ViewerImgLoader) spimData.getSequenceDescription().getImgLoader();
+        @SuppressWarnings("unchecked") final ViewerSetupImgLoader<T, V> setupImgLoader =
+                (ViewerSetupImgLoader<T, V>) imgLoader.getSetupImgLoader(
+                setupId);
+        final T type = setupImgLoader.getImageType();
+        final V volatileType = setupImgLoader.getVolatileImageType();
+
+        if (!(type instanceof NumericType))
+            throw new IllegalArgumentException("ImgLoader of type " + type.getClass() + " not supported.");
+
+        final String setupName = createSetupName(setup);
+
+        SourceAndConverter<V> vsoc = null;
+        if (volatileType != null) {
+            final VolatileSpimSource<V> vs = new VolatileSpimSource<>(spimData, setupId, setupName);
+            vsoc = new SourceAndConverter<>(vs, createConverterToARGB(volatileType));
+        }
+
+        final SpimSource<T> s = new SpimSource<>(spimData, setupId, setupName);
+        final SourceAndConverter<T> soc = new SourceAndConverter<>(s, createConverterToARGB(type), vsoc);
+        final SourceAndConverter<T> tsoc = wrapWithTransformedSource(soc);
+        sources.add(tsoc);
+
+        final ConverterSetup converterSetup = createConverterSetup(tsoc, setupId);
+        if (converterSetup != null)
+            converterSetups.add(converterSetup);
+    }
+
+    public static void initSetups(
+            final AbstractSpimData<?> spimData,
+            final List<ConverterSetup> converterSetups,
+            final List<SourceAndConverter<?>> sources) {
+        for (final BasicViewSetup setup : spimData.getSequenceDescription().getViewSetupsOrdered())
+            initSetupNumericType(spimData, setup, converterSetups, sources);
+    }
+
+    /**
+     * @param converterSetups list of {@link ConverterSetup} that control min/max and color
+     *                        of sources.
+     * @param sources         list of pairs of source of some type and converter from that
+     *                        type to ARGB.
+     * @param spimData        may be null. The {@link AbstractSpimData} of the dataset (if
+     *                        there is one). If it exists, it is used to set up a "Crop"
+     *                        dialog.
+     * @param numTimepoints   the number of timepoints in the dataset.
+     * @param cache           handle to cache. This is used to control io timing.
+     * @param windowTitle     title of the viewer window.
+     * @param progressWriter  a {@link ProgressWriter} to which BDV may report progress
+     *                        (currently only used in the "Record Movie" dialog).
+     * @param options         optional parameters.
+     */
+    public BigDataViewer(
+            final ArrayList<ConverterSetup> converterSetups,
+            final ArrayList<SourceAndConverter<?>> sources,
+            final AbstractSpimData<?> spimData,
+            final int numTimepoints,
+            final CacheControl cache,
+            final String windowTitle,
+            final ProgressWriter progressWriter,
+            final ViewerOptions options) {
+        final InputTriggerConfig inputTriggerConfig = getInputTriggerConfig(options);
+        if (options.values.getTransformEventHandlerFactory() instanceof BehaviourTransformEventHandlerFactory)
+            ((BehaviourTransformEventHandlerFactory<?>) options.values.getTransformEventHandlerFactory()).setConfig(
+                    inputTriggerConfig);
+
+        viewerFrame = new ViewerFrame(sources, numTimepoints, cache, options);
+        if (windowTitle != null)
+            viewerFrame.setTitle(windowTitle);
+        viewer = viewerFrame.getViewerPanel();
+
+        for (final ConverterSetup cs : converterSetups)
+            cs.setViewer(viewer);
+
+        manualTransformation = new ManualTransformation(viewer);
+        manualTransformationEditor = new ManualTransformationEditor(viewer, viewerFrame.getKeybindings());
+
+        bookmarks = new Bookmarks();
+        bookmarkEditor = new BookmarksEditor(viewer, viewerFrame.getKeybindings(), bookmarks);
+
+        setupAssignments = new SetupAssignments(converterSetups, 0, 65535);
+        if (setupAssignments.getMinMaxGroups().size() > 0) {
+            final MinMaxGroup group = setupAssignments.getMinMaxGroups().get(0);
+            for (final ConverterSetup setup : setupAssignments.getConverterSetups())
+                setupAssignments.moveSetupToGroup(setup, group);
+        }
+
+        brightnessDialog = new BrightnessDialog(viewerFrame, setupAssignments);
+
+        if (spimData != null)
+            viewer.getSourceInfoOverlayRenderer().setTimePointsOrdered(spimData.getSequenceDescription().getTimePoints().getTimePointsOrdered());
+
+        cropDialog = (spimData == null) ? null : new CropDialog(viewerFrame, viewer, spimData.getSequenceDescription());
+
+        movieDialog = new RecordMovieDialog(viewerFrame, viewer, progressWriter);
+        // this is just to get updates of window size:
+        viewer.getDisplay().addOverlayRenderer(movieDialog);
+
+        movieMaxProjectDialog = new RecordMaxProjectionDialog(viewerFrame, viewer, progressWriter);
+        // this is just to get updates of window size:
+        viewer.getDisplay().addOverlayRenderer(movieMaxProjectDialog);
+
+        activeSourcesDialog = new VisibilityAndGroupingDialog(viewerFrame, viewer.getVisibilityAndGrouping());
+
+        helpDialog = new HelpDialog(viewerFrame);
+
+        fileChooser = new JFileChooser();
+        fileChooser.setFileFilter(new FileFilter() {
+            @Override
+            public String getDescription() {
+                return "xml files";
+            }
+
+            @Override
+            public boolean accept(final File f) {
+                if (f.isDirectory())
+                    return true;
+                if (f.isFile()) {
+                    final String s = f.getName();
+                    final int i = s.lastIndexOf('.');
+                    if (i > 0 && i < s.length() - 1) {
+                        final String ext = s.substring(i + 1).toLowerCase();
+                        return ext.equals("xml");
+                    }
+                }
+                return false;
+            }
+        });
+
+        NavigationActions.installActionBindings(viewerFrame.getKeybindings(), viewer, inputTriggerConfig);
+        BigDataViewerActions.installActionBindings(viewerFrame.getKeybindings(), this, inputTriggerConfig);
+
+        final JMenuBar menubar = new JMenuBar();
+        JMenu menu = new JMenu("File");
+        menubar.add(menu);
+
+        final ActionMap actionMap = viewerFrame.getKeybindings().getConcatenatedActionMap();
+        final JMenuItem miLoadSettings = new JMenuItem(actionMap.get(BigDataViewerActions.LOAD_SETTINGS));
+        miLoadSettings.setText("Load settings");
+        menu.add(miLoadSettings);
+
+        final JMenuItem miSaveSettings = new JMenuItem(actionMap.get(BigDataViewerActions.SAVE_SETTINGS));
+        miSaveSettings.setText("Save settings");
+        menu.add(miSaveSettings);
+
+        menu = new JMenu("Settings");
+        menubar.add(menu);
+
+        final JMenuItem miBrightness = new JMenuItem(actionMap.get(BigDataViewerActions.BRIGHTNESS_SETTINGS));
+        miBrightness.setText("Brightness & Color");
+        menu.add(miBrightness);
+
+        final JMenuItem miVisibility = new JMenuItem(actionMap.get(BigDataViewerActions.VISIBILITY_AND_GROUPING));
+        miVisibility.setText("Visibility & Grouping");
+        menu.add(miVisibility);
+
+        menu = new JMenu("Tools");
+        menubar.add(menu);
+
+        if (cropDialog != null) {
+            final JMenuItem miCrop = new JMenuItem(actionMap.get(BigDataViewerActions.CROP));
+            miCrop.setText("Crop");
+            menu.add(miCrop);
+        }
+
+        final JMenuItem miMovie = new JMenuItem(actionMap.get(BigDataViewerActions.RECORD_MOVIE));
+        miMovie.setText("Record Movie");
+        menu.add(miMovie);
+
+        final JMenuItem miMaxProjectMovie =
+                new JMenuItem(actionMap.get(BigDataViewerActions.RECORD_MAX_PROJECTION_MOVIE));
+        miMaxProjectMovie.setText("Record Max-Projection Movie");
+        menu.add(miMaxProjectMovie);
+
+        final JMenuItem miManualTransform = new JMenuItem(actionMap.get(BigDataViewerActions.MANUAL_TRANSFORM));
+        miManualTransform.setText("Manual Transform");
+        menu.add(miManualTransform);
+
+        menu = new JMenu("Help");
+        menubar.add(menu);
+
+        final JMenuItem miHelp = new JMenuItem(actionMap.get(BigDataViewerActions.SHOW_HELP));
+        miHelp.setText("Show Help");
+        menu.add(miHelp);
+
+        viewerFrame.setJMenuBar(menubar);
+    }
+
+    public static BigDataViewer open(final AbstractSpimData<?> spimData,
+                                     final String windowTitle,
+                                     final ProgressWriter progressWriter,
+                                     final ViewerOptions options) {
+        if (WrapBasicImgLoader.wrapImgLoaderIfNecessary(spimData)) {
+            System.err.println(
+                    "WARNING:\nOpening <SpimData> dataset that is not suited for interactive browsing.\nConsider " +
+                            "resaving as HDF5 for better performance.");
+        }
+
+        final ArrayList<ConverterSetup> converterSetups = new ArrayList<>();
+        final ArrayList<SourceAndConverter<?>> sources = new ArrayList<>();
+        initSetups(spimData, converterSetups, sources);
+
+        final AbstractSequenceDescription<?, ?, ?> seq = spimData.getSequenceDescription();
+        final int numTimepoints = seq.getTimePoints().size();
+        final CacheControl cache = ((ViewerImgLoader) seq.getImgLoader()).getCacheControl();
+
+        final BigDataViewer bdv = new BigDataViewer(converterSetups,
+                                                    sources,
+                                                    spimData,
+                                                    numTimepoints,
+                                                    cache,
+                                                    windowTitle,
+                                                    progressWriter,
+                                                    options);
+
+        WrapBasicImgLoader.removeWrapperIfPresent(spimData);
+
+        bdv.viewerFrame.setVisible(true);
+        InitializeViewerState.initTransform(bdv.viewer);
+        return bdv;
+    }
+
+    public static BigDataViewer open(final String xmlFilename,
+                                     final String windowTitle,
+                                     final ProgressWriter progressWriter,
+                                     final ViewerOptions options,
+                                     final boolean allowCompression) throws SpimDataException {
+
+        final XmlIoSpimDataMinimal xmlIoSpimDataMinimal = new XmlIoSpimDataMinimal();
+        xmlIoSpimDataMinimal.setAllowCompression(allowCompression);
+        final SpimDataMinimal spimData = xmlIoSpimDataMinimal.load(xmlFilename);
+        final BigDataViewer bdv = open(spimData, windowTitle, progressWriter, options);
+        if (!bdv.tryLoadSettings(xmlFilename))
+            InitializeViewerState.initBrightness(0.001, 0.999, bdv.viewer, bdv.setupAssignments);
+        return bdv;
+    }
+
+    public static BigDataViewer open(
+            final ArrayList<ConverterSetup> converterSetups,
+            final ArrayList<SourceAndConverter<?>> sources,
+            final int numTimepoints,
+            final CacheControl cache,
+            final String windowTitle,
+            final ProgressWriter progressWriter,
+            final ViewerOptions options) {
+        final BigDataViewer bdv = new BigDataViewer(converterSetups,
+                                                    sources,
+                                                    null,
+                                                    numTimepoints,
+                                                    cache,
+                                                    windowTitle,
+                                                    progressWriter,
+                                                    options);
+        bdv.viewerFrame.setVisible(true);
+        InitializeViewerState.initTransform(bdv.viewer);
+        return bdv;
+    }
+
+    public ViewerPanel getViewer() {
+        return viewer;
+    }
+
+    public ViewerFrame getViewerFrame() {
+        return viewerFrame;
+    }
+
+    public SetupAssignments getSetupAssignments() {
+        return setupAssignments;
+    }
+
+    public ManualTransformationEditor getManualTransformEditor() {
+        return manualTransformationEditor;
+    }
+
+    public boolean tryLoadSettings(final String xmlFilename) {
+        proposedSettingsFile = null;
+        if (xmlFilename.startsWith("http://")) {
+            // load settings.xml from the BigDataServer
+            final String settings = xmlFilename + "settings";
+            {
+                try {
+                    loadSettings(settings);
+                    return true;
+                } catch (final FileNotFoundException e) {
+                } catch (final Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        } else if (xmlFilename.endsWith(".xml")) {
+            final String settings = xmlFilename.substring(0,
+                                                          xmlFilename.length() - ".xml".length()) + ".settings" +
+                    ".xml";
+            proposedSettingsFile = new File(settings);
+            if (proposedSettingsFile.isFile()) {
+                try {
+                    loadSettings(settings);
+                    return true;
+                } catch (final Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return false;
+    }
+
+    public void saveSettings() {
+        fileChooser.setSelectedFile(proposedSettingsFile);
+        final int returnVal = fileChooser.showSaveDialog(null);
+        if (returnVal == JFileChooser.APPROVE_OPTION) {
+            proposedSettingsFile = fileChooser.getSelectedFile();
+            try {
+                saveSettings(proposedSettingsFile.getCanonicalPath());
+            } catch (final IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public void saveSettings(final String xmlFilename) throws IOException {
+        final Element root = new Element("Settings");
+        root.addContent(viewer.stateToXml());
+        root.addContent(setupAssignments.toXml());
+        root.addContent(manualTransformation.toXml());
+        root.addContent(bookmarks.toXml());
+        final Document doc = new Document(root);
+        final XMLOutputter xout = new XMLOutputter(Format.getPrettyFormat());
+        xout.output(doc, new FileWriter(xmlFilename));
+    }
+
+    /**
+     * If {@code options} doesn't define a {@link InputTriggerConfig}, try to
+     * load it from files in this order:
+     * <ol>
+     * <li>"bdvkeyconfig.yaml" in the current directory.
+     * <li>".bdv/bdvkeyconfig.yaml" in the user's home directory.
+     * <li>legacy "bigdataviewer.keys.properties" in current directory (will be
+     * also written to "bdvkeyconfig.yaml").
+     * </ol>
+     *
+     * @param options
+     * @return
+     */
+    public static InputTriggerConfig getInputTriggerConfig(final ViewerOptions options) {
+        InputTriggerConfig conf = options.values.getInputTriggerConfig();
+
+        // try "bdvkeyconfig.yaml" in current directory
+        if (conf == null && new File("bdvkeyconfig.yaml").isFile()) {
+            try {
+                conf = new InputTriggerConfig(YamlConfigIO.read("bdvkeyconfig.yaml"));
+            } catch (final IOException e) {
+            }
+        }
+
+        // try "~/.bdv/bdvkeyconfig.yaml"
+        if (conf == null) {
+            final String fn = System.getProperty("user.home") + "/.bdv/bdvkeyconfig.yaml";
+            if (new File(fn).isFile()) {
+                try {
+                    conf = new InputTriggerConfig(YamlConfigIO.read(fn));
+                } catch (final IOException e) {
+                }
+            }
+        }
+
+        if (conf == null) {
+            conf = new InputTriggerConfig();
+        }
+
+        return conf;
+    }
+
+    public void loadSettings() {
+        fileChooser.setSelectedFile(proposedSettingsFile);
+        final int returnVal = fileChooser.showOpenDialog(null);
+        if (returnVal == JFileChooser.APPROVE_OPTION) {
+            proposedSettingsFile = fileChooser.getSelectedFile();
+            try {
+                loadSettings(proposedSettingsFile.getCanonicalPath());
+            } catch (final Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public void loadSettings(final String xmlFilename) throws IOException, JDOMException {
+        final SAXBuilder sax = new SAXBuilder();
+        final Document doc = sax.build(xmlFilename);
+        final Element root = doc.getRootElement();
+        viewer.stateFromXml(root);
+        setupAssignments.restoreFromXml(root);
+        manualTransformation.restoreFromXml(root);
+        bookmarks.restoreFromXml(root);
+        activeSourcesDialog.update();
+        viewer.requestRepaint();
+    }
+
+    public static void main(final String[] args) {
+        //		final String fn = "http://tomancak-mac-17.mpi-cbg.de:8080/openspim/";
+        //		final String fn = "/Users/Pietzsch/Desktop/openspim/datasetHDF.xml";
+        //		final String fn = "/Users/pietzsch/workspace/data/111010_weber_full.xml";
+        //		final String fn = "/Users/Pietzsch/Desktop/spimrec2/dataset.xml";
+        //		final String fn = "/Users/pietzsch/Desktop/HisYFP-SPIM/dataset.xml";
+        //		final String fn = "/Users/Pietzsch/Desktop/bdv example/drosophila 2.xml";
+        //		final String fn = "/Users/pietzsch/Desktop/data/clusterValia/140219-1/valia-140219-1.xml";
+        //		final String fn = "/Users/Pietzsch/Desktop/data/catmaid.xml";
+        //		final String fn = "src/main/resources/openconnectome-bock11-neariso.xml";
+        //		final String fn = "/home/saalfeld/catmaid.xml";
+        //		final String fn = "/home/saalfeld/catmaid-fafb00-v9.xml";
+        //		final String fn = "/home/saalfeld/catmaid-fafb00-sample_A_cutout_3k.xml";
+        //		final String fn = "/home/saalfeld/catmaid-thorsten.xml";
+        //		final String fn = "/home/saalfeld/knossos-example.xml";
+        //		final String fn = "/Users/Pietzsch/Desktop/data/catmaid-confocal.xml";
+        //		final String fn = "/Users/pietzsch/desktop/data/BDV130418A325/BDV130418A325_NoTempReg.xml";
+        //		final String fn = "/Users/pietzsch/Desktop/data/valia2/valia.xml";
+        //		final String fn = "/Users/pietzsch/workspace/data/fast fly/111010_weber/combined.xml";
+        //		final String fn = "/Users/pietzsch/workspace/data/mette/mette.xml";
+        //		final String fn = "/Users/tobias/Desktop/openspim.xml";
+        //		final String fn = "/Users/pietzsch/Desktop/data/fibsem.xml";
+        //		final String fn = "/Users/pietzsch/Desktop/data/fibsem-remote.xml";
+        //		final String fn = "/Users/pietzsch/Desktop/url-valia.xml";
+        //		final String fn = "/Users/pietzsch/Desktop/data/clusterValia/140219-1/valia-140219-1.xml";
+        //		final String fn = "/Users/pietzsch/workspace/data/111010_weber_full.xml";
+        //		final String fn = "/Volumes/projects/tomancak_lightsheet/Mette/ZeissZ1SPIM/Maritigrella/021013_McH2BsGFP_CAAX-mCherry/11-use/hdf5/021013_McH2BsGFP_CAAX-mCherry-11-use.xml";
+        if (args.length < 1) {
+            System.err.println("Provide path.");
+            return;
+        }
+        final String fn = args[0];
+        final boolean allowCompression = (args.length > 1) && (args[1].equals("-qcmp"));
+        try {
+            System.setProperty("apple.laf.useScreenMenuBar", "true");
+
+            final BigDataViewer bdv = open(fn,
+                                           "Server test",
+                                           new ProgressWriterConsole(),
+                                           ViewerOptions.options(),
+                                           allowCompression);
+
+            //			DumpInputConfig.writeToYaml( System.getProperty( "user.home" ) + "/.bdv/bdvkeyconfig.yaml", bdv.getViewerFrame() );
+        } 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 b485a68a..2d5247a2 100644
--- a/src/main/java/bdv/img/remote/RemoteImageLoader.java
+++ b/src/main/java/bdv/img/remote/RemoteImageLoader.java
@@ -7,13 +7,13 @@
  * %%
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
- * 
+ *
  * 1. Redistributions of source code must retain the above copyright notice,
  *    this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright notice,
  *    this list of conditions and the following disclaimer in the documentation
  *    and/or other materials provided with the distribution.
- * 
+ *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
@@ -29,13 +29,8 @@
  */
 package bdv.img.remote;
 
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.util.HashMap;
-
-import com.google.gson.GsonBuilder;
-
+import azgracompress.cache.ICacheFile;
+import azgracompress.cache.QuantizationCacheManager;
 import bdv.AbstractViewerSetupImgLoader;
 import bdv.ViewerImgLoader;
 import bdv.img.cache.VolatileCachedCellImg;
@@ -44,6 +39,7 @@ import bdv.img.hdf5.DimsAndExistence;
 import bdv.img.hdf5.MipmapInfo;
 import bdv.img.hdf5.ViewLevelId;
 import bdv.util.ConstantRandomAccessible;
+import com.google.gson.GsonBuilder;
 import mpicbg.spim.data.generic.sequence.ImgLoaderHint;
 import net.imglib2.FinalInterval;
 import net.imglib2.RandomAccessibleInterval;
@@ -57,211 +53,213 @@ import net.imglib2.type.volatiles.VolatileUnsignedShortType;
 import net.imglib2.util.IntervalIndexer;
 import net.imglib2.view.Views;
 
-public class RemoteImageLoader implements ViewerImgLoader
-{
-	protected String baseUrl;
-
-	protected RemoteImageLoaderMetaData metadata;
-
-	protected HashMap< ViewLevelId, int[] > cellsDimensions;
-
-	protected VolatileGlobalCellCache cache;
-
-	protected RemoteVolatileShortArrayLoader shortLoader;
-
-	/**
-	 * TODO
-	 */
-	protected final HashMap< Integer, SetupImgLoader > setupImgLoaders;
-
-	public RemoteImageLoader( final String baseUrl ) throws IOException
-	{
-		this( baseUrl, true );
-	}
-
-	public RemoteImageLoader( final String baseUrl, final boolean doOpen ) throws IOException
-	{
-		this.baseUrl = baseUrl;
-		setupImgLoaders = new HashMap<>();
-		if ( doOpen )
-			open();
-	}
-
-	@Override
-	public SetupImgLoader getSetupImgLoader( final int setupId )
-	{
-		tryopen();
-		return setupImgLoaders.get( setupId );
-	}
-
-	private boolean isOpen = false;
-
-	private void open() throws IOException
-	{
-		if ( ! isOpen )
-		{
-			synchronized ( this )
-			{
-				if ( isOpen )
-					return;
-				isOpen = true;
-
-				final URL url = new URL( baseUrl + "?p=init" );
-				final GsonBuilder gsonBuilder = new GsonBuilder();
-				gsonBuilder.registerTypeAdapter( AffineTransform3D.class, new AffineTransform3DJsonSerializer() );
-				metadata = gsonBuilder.create().fromJson(
-						new InputStreamReader( url.openStream() ),
-						RemoteImageLoaderMetaData.class );
-				shortLoader = new RemoteVolatileShortArrayLoader( this );
-				cache = new VolatileGlobalCellCache( metadata.maxNumLevels, 10 );
-				cellsDimensions = metadata.createCellsDimensions();
-				for ( final int setupId : metadata.perSetupMipmapInfo.keySet() )
-					setupImgLoaders.put( setupId, new SetupImgLoader( setupId ) );
-			}
-		}
-	}
-
-	private void tryopen()
-	{
-		try
-		{
-			open();
-		}
-		catch ( final IOException e )
-		{
-			throw new RuntimeException( e );
-		}
-	}
-
-	@Override
-	public VolatileGlobalCellCache getCacheControl()
-	{
-		tryopen();
-		return cache;
-	}
-
-	public MipmapInfo getMipmapInfo( final int setupId )
-	{
-		tryopen();
-		return metadata.perSetupMipmapInfo.get( setupId );
-	}
-
-	/**
-	 * Checks whether the given image data is present on the server.
-	 *
-	 * @return true, if the given image data is present.
-	 */
-	public boolean existsImageData( final ViewLevelId id )
-	{
-		return getDimsAndExistence( id ).exists();
-	}
-
-	/**
-	 * For images that are missing in the hdf5, a constant image is created. If
-	 * the dimension of the missing image is known (see
-	 * {@link #getDimsAndExistence(ViewLevelId)}) then use that. Otherwise
-	 * create a 1x1x1 image.
-	 */
-	protected < T > RandomAccessibleInterval< T > getMissingDataImage( final ViewLevelId id, final T constant )
-	{
-		final long[] d = getDimsAndExistence( id ).getDimensions();
-		return Views.interval( new ConstantRandomAccessible<>( constant, 3 ), new FinalInterval( d ) );
-	}
-
-	public DimsAndExistence getDimsAndExistence( final ViewLevelId id )
-	{
-		tryopen();
-		return metadata.dimsAndExistence.get( id );
-	}
-
-	int getCellIndex( final int timepoint, final int setup, final int level, final long[] globalPosition )
-	{
-		final int[] cellDims = cellsDimensions.get( new ViewLevelId( timepoint, setup, level ) );
-		final int[] cellSize = getMipmapInfo( setup ).getSubdivisions()[ level ];
-		final int[] cellPos = new int[] {
-				( int ) globalPosition[ 0 ] / cellSize[ 0 ],
-				( int ) globalPosition[ 1 ] / cellSize[ 1 ],
-				( int ) globalPosition[ 2 ] / cellSize[ 2 ] };
-		return IntervalIndexer.positionToIndex( cellPos, cellDims );
-	}
-
-	/**
-	 * Create a {@link VolatileCachedCellImg} backed by the cache. The
-	 * {@code type} should be either {@link UnsignedShortType} and
-	 * {@link VolatileUnsignedShortType}.
-	 */
-	protected < T extends NativeType< T > > RandomAccessibleInterval< T > prepareCachedImage(
-			final ViewLevelId id,
-			final LoadingStrategy loadingStrategy,
-			final T type )
-	{
-		tryopen();
-		if ( cache == null )
-			throw new RuntimeException( "no connection open" );
-
-//		final ViewLevelId id = new ViewLevelId( timepointId, setupId, level );
-		if ( ! existsImageData( id ) )
-		{
-			System.err.println(	String.format(
-					"image data for timepoint %d setup %d level %d could not be found.",
-					id.getTimePointId(), id.getViewSetupId(), id.getLevel() ) );
-			return getMissingDataImage( id, type );
-		}
-
-		final int timepointId = id.getTimePointId();
-		final int setupId = id.getViewSetupId();
-		final int level = id.getLevel();
-		final MipmapInfo mipmapInfo = metadata.perSetupMipmapInfo.get( setupId );
-
-		final long[] dimensions = metadata.dimsAndExistence.get( id ).getDimensions();
-		final int[] cellDimensions = mipmapInfo.getSubdivisions()[ level ];
-		final CellGrid grid = new CellGrid( dimensions, cellDimensions );
-
-		final int priority = mipmapInfo.getMaxLevel() - level;
-		final CacheHints cacheHints = new CacheHints( loadingStrategy, priority, false );
-		return cache.createImg( grid, timepointId, setupId, level, cacheHints, shortLoader, type );
-	}
-
-	public class SetupImgLoader extends AbstractViewerSetupImgLoader< UnsignedShortType, VolatileUnsignedShortType >
-	{
-		private final int setupId;
-
-		protected SetupImgLoader( final int setupId )
-		{
-			super( new UnsignedShortType(), new VolatileUnsignedShortType() );
-			this.setupId = setupId;
-		}
-
-		@Override
-		public RandomAccessibleInterval< UnsignedShortType > getImage( final int timepointId, final int level, final ImgLoaderHint... hints )
-		{
-			final ViewLevelId id = new ViewLevelId( timepointId, setupId, level );
-			return prepareCachedImage( id, LoadingStrategy.BLOCKING, type );
-		}
-
-		@Override
-		public RandomAccessibleInterval< VolatileUnsignedShortType > getVolatileImage( final int timepointId, final int level, final ImgLoaderHint... hints )
-		{
-			final ViewLevelId id = new ViewLevelId( timepointId, setupId, level );
-			return prepareCachedImage( id, LoadingStrategy.BUDGETED, volatileType );
-		}
-
-		@Override
-		public double[][] getMipmapResolutions()
-		{
-			return getMipmapInfo( setupId ).getResolutions();
-		}
-
-		@Override
-		public AffineTransform3D[] getMipmapTransforms()
-		{
-			return getMipmapInfo( setupId ).getTransforms();
-		}
-
-		@Override
-		public int numMipmapLevels()
-		{
-			return getMipmapInfo( setupId ).getNumLevels();
-		}
-	}
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.HashMap;
+
+public class RemoteImageLoader implements ViewerImgLoader {
+    protected String baseUrl;
+
+    protected RemoteImageLoaderMetaData metadata;
+
+    protected HashMap<ViewLevelId, int[]> cellsDimensions;
+
+    protected VolatileGlobalCellCache cache;
+
+    protected RemoteVolatileShortArrayLoader shortLoader;
+
+    private boolean allowCompression;
+    /**
+     * TODO
+     */
+    protected final HashMap<Integer, SetupImgLoader> setupImgLoaders;
+
+    public RemoteImageLoader(final String baseUrl) throws IOException {
+        this(baseUrl, true);
+    }
+
+    public RemoteImageLoader(final String baseUrl,
+                             final boolean doOpen) throws IOException {
+        this.baseUrl = baseUrl;
+        setupImgLoaders = new HashMap<>();
+        if (doOpen)
+            open();
+    }
+
+    @Override
+    public SetupImgLoader getSetupImgLoader(final int setupId) {
+        tryopen();
+        return setupImgLoaders.get(setupId);
+    }
+
+    private boolean isOpen = false;
+
+    private void open() throws IOException {
+        if (!isOpen) {
+            synchronized (this) {
+                if (isOpen)
+                    return;
+                isOpen = true;
+
+                if (allowCompression) {
+                    receiveCompressionInfo();
+                }
+
+                final URL url = new URL(baseUrl + "?p=init");
+                final GsonBuilder gsonBuilder = new GsonBuilder();
+                gsonBuilder.registerTypeAdapter(AffineTransform3D.class, new AffineTransform3DJsonSerializer());
+                metadata = gsonBuilder.create().fromJson(
+                        new InputStreamReader(url.openStream()),
+                        RemoteImageLoaderMetaData.class);
+                shortLoader = new RemoteVolatileShortArrayLoader(this);
+                cache = new VolatileGlobalCellCache(metadata.maxNumLevels, 10);
+                cellsDimensions = metadata.createCellsDimensions();
+                for (final int setupId : metadata.perSetupMipmapInfo.keySet())
+                    setupImgLoaders.put(setupId, new SetupImgLoader(setupId));
+            }
+        }
+    }
+
+    public boolean shouldAllowCompression() {
+        return allowCompression;
+    }
+
+    public void setAllowCompression(final boolean compressionEnabled) {
+        this.allowCompression = compressionEnabled;
+    }
+
+    private void receiveCompressionInfo() throws IOException {
+        final URL url = new URL(baseUrl + "?p=init_qcmp");
+        final ICacheFile cachedCodebook = QuantizationCacheManager.readCacheFile(url.openStream());
+        System.out.println("\u001b[33mRemoteImageLoader::receiveCompressionInfo() - received cache file. '" + cachedCodebook.toString() + "'\u001b[0m");
+    }
+
+
+    private void tryopen() {
+        try {
+            open();
+        } catch (final IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public VolatileGlobalCellCache getCacheControl() {
+        tryopen();
+        return cache;
+    }
+
+    public MipmapInfo getMipmapInfo(final int setupId) {
+        tryopen();
+        return metadata.perSetupMipmapInfo.get(setupId);
+    }
+
+    /**
+     * Checks whether the given image data is present on the server.
+     *
+     * @return true, if the given image data is present.
+     */
+    public boolean existsImageData(final ViewLevelId id) {
+        return getDimsAndExistence(id).exists();
+    }
+
+    /**
+     * For images that are missing in the hdf5, a constant image is created. If
+     * the dimension of the missing image is known (see
+     * {@link #getDimsAndExistence(ViewLevelId)}) then use that. Otherwise
+     * create a 1x1x1 image.
+     */
+    protected <T> RandomAccessibleInterval<T> getMissingDataImage(final ViewLevelId id, final T constant) {
+        final long[] d = getDimsAndExistence(id).getDimensions();
+        return Views.interval(new ConstantRandomAccessible<>(constant, 3), new FinalInterval(d));
+    }
+
+    public DimsAndExistence getDimsAndExistence(final ViewLevelId id) {
+        tryopen();
+        return metadata.dimsAndExistence.get(id);
+    }
+
+    int getCellIndex(final int timepoint, final int setup, final int level, final long[] globalPosition) {
+        final int[] cellDims = cellsDimensions.get(new ViewLevelId(timepoint, setup, level));
+        final int[] cellSize = getMipmapInfo(setup).getSubdivisions()[level];
+        final int[] cellPos = new int[]{
+                (int) globalPosition[0] / cellSize[0],
+                (int) globalPosition[1] / cellSize[1],
+                (int) globalPosition[2] / cellSize[2]};
+        return IntervalIndexer.positionToIndex(cellPos, cellDims);
+    }
+
+    /**
+     * Create a {@link VolatileCachedCellImg} backed by the cache. The
+     * {@code type} should be either {@link UnsignedShortType} and
+     * {@link VolatileUnsignedShortType}.
+     */
+    protected <T extends NativeType<T>> RandomAccessibleInterval<T> prepareCachedImage(
+            final ViewLevelId id,
+            final LoadingStrategy loadingStrategy,
+            final T type) {
+        tryopen();
+        if (cache == null)
+            throw new RuntimeException("no connection open");
+
+        //		final ViewLevelId id = new ViewLevelId( timepointId, setupId, level );
+        if (!existsImageData(id)) {
+            System.err.println(String.format(
+                    "image data for timepoint %d setup %d level %d could not be found.",
+                    id.getTimePointId(), id.getViewSetupId(), id.getLevel()));
+            return getMissingDataImage(id, type);
+        }
+
+        final int timepointId = id.getTimePointId();
+        final int setupId = id.getViewSetupId();
+        final int level = id.getLevel();
+        final MipmapInfo mipmapInfo = metadata.perSetupMipmapInfo.get(setupId);
+
+        final long[] dimensions = metadata.dimsAndExistence.get(id).getDimensions();
+        final int[] cellDimensions = mipmapInfo.getSubdivisions()[level];
+        final CellGrid grid = new CellGrid(dimensions, cellDimensions);
+
+        final int priority = mipmapInfo.getMaxLevel() - level;
+        final CacheHints cacheHints = new CacheHints(loadingStrategy, priority, false);
+        return cache.createImg(grid, timepointId, setupId, level, cacheHints, shortLoader, type);
+    }
+
+    public class SetupImgLoader extends AbstractViewerSetupImgLoader<UnsignedShortType, VolatileUnsignedShortType> {
+        private final int setupId;
+
+        protected SetupImgLoader(final int setupId) {
+            super(new UnsignedShortType(), new VolatileUnsignedShortType());
+            this.setupId = setupId;
+        }
+
+        @Override
+        public RandomAccessibleInterval<UnsignedShortType> getImage(final int timepointId, final int level, final ImgLoaderHint... hints) {
+            final ViewLevelId id = new ViewLevelId(timepointId, setupId, level);
+            return prepareCachedImage(id, LoadingStrategy.BLOCKING, type);
+        }
+
+        @Override
+        public RandomAccessibleInterval<VolatileUnsignedShortType> getVolatileImage(final int timepointId,
+                                                                                    final int level,
+                                                                                    final ImgLoaderHint... hints) {
+            final ViewLevelId id = new ViewLevelId(timepointId, setupId, level);
+            return prepareCachedImage(id, LoadingStrategy.BUDGETED, volatileType);
+        }
+
+        @Override
+        public double[][] getMipmapResolutions() {
+            return getMipmapInfo(setupId).getResolutions();
+        }
+
+        @Override
+        public AffineTransform3D[] getMipmapTransforms() {
+            return getMipmapInfo(setupId).getTransforms();
+        }
+
+        @Override
+        public int numMipmapLevels() {
+            return getMipmapInfo(setupId).getNumLevels();
+        }
+    }
 }
diff --git a/src/main/java/bdv/img/remote/RemoteVolatileShortArrayLoader.java b/src/main/java/bdv/img/remote/RemoteVolatileShortArrayLoader.java
index e6da148d..ea258212 100644
--- a/src/main/java/bdv/img/remote/RemoteVolatileShortArrayLoader.java
+++ b/src/main/java/bdv/img/remote/RemoteVolatileShortArrayLoader.java
@@ -7,13 +7,13 @@
  * %%
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
- * 
+ *
  * 1. Redistributions of source code must retain the above copyright notice,
  *    this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright notice,
  *    this list of conditions and the following disclaimer in the documentation
  *    and/or other materials provided with the distribution.
- * 
+ *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
@@ -53,24 +53,31 @@ public class RemoteVolatileShortArrayLoader implements CacheArrayLoader< Volatil
 		final short[] data = new short[ dimensions[ 0 ] * dimensions[ 1 ] * dimensions[ 2 ] ];
 		try
 		{
-			final URL url = new URL( String.format( "%s?p=cell/%d/%d/%d/%d/%d/%d/%d/%d/%d/%d",
-					imgLoader.baseUrl,
-					index,
-					timepoint,
-					setup,
-					level,
-					dimensions[ 0 ],
-					dimensions[ 1 ],
-					dimensions[ 2 ],
-					min[ 0 ],
-					min[ 1 ],
-					min[ 2 ] ) );
+			final URL url = new URL(String.format("%s?p=cell/%d/%d/%d/%d/%d/%d/%d/%d/%d/%d",
+												  imgLoader.baseUrl,
+												  index,
+												  timepoint,
+												  setup,
+												  level,
+												  dimensions[0],
+												  dimensions[1],
+												  dimensions[2],
+												  min[0],
+												  min[1],
+												  min[2]));
 			final InputStream s = url.openStream();
-			final byte[] buf = new byte[ data.length * 2 ];
-			for ( int i = 0, l = s.read( buf, 0, buf.length ); l > 0; i += l, l = s.read( buf, i, buf.length - i ) );
-			for ( int i = 0, j = 0; i < data.length; ++i, j += 2 )
-				data[ i ] = ( short ) ( ( ( buf[ j ] & 0xff ) << 8 ) | ( buf[ j + 1 ] & 0xff ) );
-			s.close();
+//            System.out.println("Request URL=" + url);
+            final byte[] buf = new byte[data.length * 2];
+
+            // NOTE(Moravec): Decompression place!
+			for (int i = 0, l = s.read(buf, 0, buf.length);
+				 l > 0;
+				 i += l, l = s.read(buf, i, buf.length - i))
+			;
+
+            for (int i = 0, j = 0; i < data.length; ++i, j += 2)
+                data[i] = (short) (((buf[j] & 0xff) << 8) | (buf[j + 1] & 0xff));
+            s.close();
 		}
 		catch ( final MalformedURLException e )
 		{
diff --git a/src/main/java/bdv/img/remote/XmlIoRemoteImageLoader.java b/src/main/java/bdv/img/remote/XmlIoRemoteImageLoader.java
index 3cf39d3a..6b9b8a03 100644
--- a/src/main/java/bdv/img/remote/XmlIoRemoteImageLoader.java
+++ b/src/main/java/bdv/img/remote/XmlIoRemoteImageLoader.java
@@ -7,13 +7,13 @@
  * %%
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
- * 
+ *
  * 1. Redistributions of source code must retain the above copyright notice,
  *    this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright notice,
  *    this list of conditions and the following disclaimer in the documentation
  *    and/or other materials provided with the distribution.
- * 
+ *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
@@ -41,31 +41,36 @@ import mpicbg.spim.data.generic.sequence.XmlIoBasicImgLoader;
 
 import org.jdom2.Element;
 
-@ImgLoaderIo( format = "bdv.remote", type = RemoteImageLoader.class )
-public class XmlIoRemoteImageLoader implements XmlIoBasicImgLoader< RemoteImageLoader >
-{
+@ImgLoaderIo(format = "bdv.remote", type = RemoteImageLoader.class)
+public class XmlIoRemoteImageLoader implements XmlIoBasicImgLoader<RemoteImageLoader> {
 
-	@Override
-	public Element toXml( final RemoteImageLoader imgLoader, final File basePath )
-	{
-		final Element elem = new Element( "ImageLoader" );
-		elem.setAttribute( IMGLOADER_FORMAT_ATTRIBUTE_NAME, "bdv.remote" );
-		elem.addContent( XmlHelpers.textElement( "baseUrl", imgLoader.baseUrl ) );
-		return elem;
-	}
+    private boolean allowCompression = false;
 
-	@Override
-	public RemoteImageLoader fromXml( final Element elem, final File basePath, final AbstractSequenceDescription< ?, ?, ? > sequenceDescription )
-	{
-		final String baseUrl = elem.getChildText( "baseUrl" );
-		try
-		{
-			return new RemoteImageLoader( baseUrl );
-		}
-		catch ( final IOException e )
-		{
-			throw new RuntimeException( e );
-		}
-	}
+    @Override
+    public Element toXml(final RemoteImageLoader imgLoader, final File basePath) {
+        final Element elem = new Element("ImageLoader");
+        elem.setAttribute(IMGLOADER_FORMAT_ATTRIBUTE_NAME, "bdv.remote");
+        elem.addContent(XmlHelpers.textElement("baseUrl", imgLoader.baseUrl));
+        return elem;
+    }
 
+    @Override
+    public RemoteImageLoader fromXml(final Element elem,
+                                     final File basePath,
+                                     final AbstractSequenceDescription<?, ?, ?> sequenceDescription) {
+        final String baseUrl = elem.getChildText("baseUrl");
+        try {
+            return new RemoteImageLoader(baseUrl, allowCompression);
+        } catch (final IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public boolean shouldAllowCompression() {
+        return allowCompression;
+    }
+
+    public void setAllowCompression(boolean allowCompression) {
+        this.allowCompression = allowCompression;
+    }
 }
diff --git a/src/main/java/bdv/spimdata/XmlIoSpimDataMinimal.java b/src/main/java/bdv/spimdata/XmlIoSpimDataMinimal.java
index c3621156..8737c521 100644
--- a/src/main/java/bdv/spimdata/XmlIoSpimDataMinimal.java
+++ b/src/main/java/bdv/spimdata/XmlIoSpimDataMinimal.java
@@ -7,13 +7,13 @@
  * %%
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
- * 
+ *
  * 1. Redistributions of source code must retain the above copyright notice,
  *    this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright notice,
  *    this list of conditions and the following disclaimer in the documentation
  *    and/or other materials provided with the distribution.
- * 
+ *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
@@ -33,6 +33,7 @@ import static mpicbg.spim.data.XmlKeys.SPIMDATA_TAG;
 
 import java.io.File;
 
+import bdv.img.remote.RemoteImageLoader;
 import mpicbg.spim.data.SpimDataException;
 import mpicbg.spim.data.SpimDataIOException;
 import mpicbg.spim.data.generic.XmlIoAbstractSpimData;
@@ -51,6 +52,8 @@ import bdv.spimdata.legacy.XmlIoSpimDataMinimalLegacy;
 
 public class XmlIoSpimDataMinimal extends XmlIoAbstractSpimData< SequenceDescriptionMinimal, SpimDataMinimal >
 {
+    private boolean allowCompression = false;
+
 	public XmlIoSpimDataMinimal()
 	{
 		super( SpimDataMinimal.class,
@@ -62,6 +65,10 @@ public class XmlIoSpimDataMinimal extends XmlIoAbstractSpimData< SequenceDescrip
 				new XmlIoViewRegistrations() );
 	}
 
+    public void setAllowCompression(final boolean allowCompression) {
+        this.allowCompression = allowCompression;
+    }
+
 	@Override
 	public SpimDataMinimal load( final String xmlFilename ) throws SpimDataException
 		{
@@ -83,6 +90,11 @@ public class XmlIoSpimDataMinimal extends XmlIoAbstractSpimData< SequenceDescrip
 			if ( root.getName() != SPIMDATA_TAG )
 				throw new RuntimeException( "expected <" + SPIMDATA_TAG + "> root element. wrong file?" );
 
-			return fromXml( root, new File( xmlFilename ) );
+            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;
 		}
 }
diff --git a/src/main/java/bdv/spimdata/legacy/XmlIoSpimDataMinimalLegacy.java b/src/main/java/bdv/spimdata/legacy/XmlIoSpimDataMinimalLegacy.java
index 7f0ffefc..a59c57b3 100644
--- a/src/main/java/bdv/spimdata/legacy/XmlIoSpimDataMinimalLegacy.java
+++ b/src/main/java/bdv/spimdata/legacy/XmlIoSpimDataMinimalLegacy.java
@@ -165,6 +165,7 @@ public class XmlIoSpimDataMinimalLegacy
 	{
 		final Element elem = sequenceDescriptionElem.getChild( "ImageLoader" );
 		final String classn = elem.getAttributeValue( "class" );
+
 		if ( classn.equals( "viewer.hdf5.Hdf5ImageLoader" ) || classn.equals( "bdv.img.hdf5.Hdf5ImageLoader" ) )
 		{
 			final String path = loadPath( elem, "hdf5", basePath ).toString();
-- 
GitLab