diff --git a/.gitignore b/.gitignore
index cbacafce2e63531cc4d06db5118df7c7c53cfeba..d358561c28d0de740515edbafc6966af56b99dc4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
 **/.settings/
 **/target/
 *.orig
+*.iml
 .classpath
 .project
 .settings
diff --git a/pom.xml b/pom.xml
index 384754f74c3c3047f24276975b7796d153f11806..9f783f4507ed2da6398e304761dc6d6a4a857078 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,13 +5,13 @@
 	<parent>
 		<groupId>org.scijava</groupId>
 		<artifactId>pom-scijava</artifactId>
-		<version>26.0.0</version>
+		<version>28.0.0</version>
 		<relativePath />
 	</parent>
 
 	<groupId>sc.fiji</groupId>
 	<artifactId>bigdataviewer_fiji</artifactId>
-	<version>6.0.1-SNAPSHOT</version>
+	<version>6.2.1-SNAPSHOT</version>
 
 	<name>BigDataViewer Fiji</name>
 	<description>Fiji plugins for starting BigDataViewer and exporting data.</description>
@@ -98,6 +98,12 @@
 		<license.licenseName>gpl_v3</license.licenseName>
 		<license.copyrightOwners>BigDataViewer developers.</license.copyrightOwners>
 
+		<imglib2.version>5.9.0</imglib2.version>
+		<imglib2-ij.version>2.0.0-beta-46</imglib2-ij.version>
+		<bigdataviewer-core.version>9.0.3</bigdataviewer-core.version>
+		<spim_data.version>2.2.4</spim_data.version>
+		<ui-behaviour.version>2.0.1</ui-behaviour.version>
+
 		<!-- NB: Deploy releases to the SciJava Maven repository. -->
 		<releaseProfiles>deploy-to-scijava</releaseProfiles>
 	</properties>
diff --git a/src/main/java/bdv/ij/ExportImagePlusAsN5PlugIn.java b/src/main/java/bdv/ij/ExportImagePlusAsN5PlugIn.java
new file mode 100644
index 0000000000000000000000000000000000000000..5fc5e97253ad9b2b4d71f677fb5968fb3cd22b0c
--- /dev/null
+++ b/src/main/java/bdv/ij/ExportImagePlusAsN5PlugIn.java
@@ -0,0 +1,565 @@
+package bdv.ij;
+
+import bdv.export.ExportMipmapInfo;
+import bdv.export.ExportScalePyramid.AfterEachPlane;
+import bdv.export.ExportScalePyramid.LoopbackHeuristic;
+import bdv.export.ProgressWriter;
+import bdv.export.ProposeMipmaps;
+import bdv.export.SubTaskProgressWriter;
+import bdv.export.n5.WriteSequenceToN5;
+import bdv.ij.util.PluginHelper;
+import bdv.ij.util.ProgressWriterIJ;
+import bdv.img.imagestack.ImageStackImageLoader;
+import bdv.img.n5.N5ImageLoader;
+import bdv.img.virtualstack.VirtualStackImageLoader;
+import bdv.spimdata.SequenceDescriptionMinimal;
+import bdv.spimdata.SpimDataMinimal;
+import bdv.spimdata.XmlIoSpimDataMinimal;
+import fiji.util.gui.GenericDialogPlus;
+import ij.IJ;
+import ij.ImageJ;
+import ij.ImagePlus;
+import ij.WindowManager;
+import java.awt.Checkbox;
+import java.awt.TextField;
+import java.awt.event.ItemEvent;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import mpicbg.spim.data.SpimDataException;
+import mpicbg.spim.data.generic.sequence.BasicViewSetup;
+import mpicbg.spim.data.generic.sequence.TypedBasicImgLoader;
+import mpicbg.spim.data.registration.ViewRegistration;
+import mpicbg.spim.data.registration.ViewRegistrations;
+import mpicbg.spim.data.sequence.Channel;
+import mpicbg.spim.data.sequence.FinalVoxelDimensions;
+import mpicbg.spim.data.sequence.TimePoint;
+import mpicbg.spim.data.sequence.TimePoints;
+import net.imglib2.FinalDimensions;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.realtransform.AffineTransform3D;
+import net.imglib2.util.Intervals;
+import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
+import org.janelia.saalfeldlab.n5.Bzip2Compression;
+import org.janelia.saalfeldlab.n5.Compression;
+import org.janelia.saalfeldlab.n5.GzipCompression;
+import org.janelia.saalfeldlab.n5.Lz4Compression;
+import org.janelia.saalfeldlab.n5.RawCompression;
+import org.janelia.saalfeldlab.n5.XzCompression;
+import org.scijava.command.Command;
+import org.scijava.plugin.Plugin;
+
+/**
+ * ImageJ plugin to export the current image to xml/n5.
+ *
+ * @author Tobias Pietzsch
+ */
+@Plugin(type = Command.class,
+	menuPath = "Plugins>BigDataViewer>Export Current Image as XML/N5")
+public class ExportImagePlusAsN5PlugIn implements Command
+{
+	public static void main( final String[] args )
+	{
+		new ImageJ();
+		final ImagePlus imp = IJ.openImage( "/Users/pietzsch/workspace/data/confocal-series.tif" );
+		imp.show();
+		new ExportImagePlusAsN5PlugIn().run();
+	}
+
+	@Override
+	public void run()
+	{
+		if ( ij.Prefs.setIJMenuBar )
+			System.setProperty( "apple.laf.useScreenMenuBar", "true" );
+
+		// get the current image
+		final ImagePlus imp = WindowManager.getCurrentImage();
+
+		// make sure there is one
+		if ( imp == null )
+		{
+			IJ.showMessage( "Please open an image first." );
+			return;
+		}
+
+		// check the image type
+		switch ( imp.getType() )
+		{
+		case ImagePlus.GRAY8:
+		case ImagePlus.GRAY16:
+		case ImagePlus.GRAY32:
+			break;
+		default:
+			IJ.showMessage( "Only 8, 16, 32-bit images are supported currently!" );
+			return;
+		}
+
+		// check the image dimensionality
+		if ( imp.getNDimensions() < 2 )
+		{
+			IJ.showMessage( "Image must be at least 2-dimensional!" );
+			return;
+		}
+
+		// get calibration and image size
+		final double pw = imp.getCalibration().pixelWidth;
+		final double ph = imp.getCalibration().pixelHeight;
+		final double pd = imp.getCalibration().pixelDepth;
+		String punit = imp.getCalibration().getUnit();
+		if ( punit == null || punit.isEmpty() )
+			punit = "px";
+		final FinalVoxelDimensions voxelSize = new FinalVoxelDimensions( punit, pw, ph, pd );
+		final int w = imp.getWidth();
+		final int h = imp.getHeight();
+		final int d = imp.getNSlices();
+		final FinalDimensions size = new FinalDimensions( w, h, d );
+
+		// propose reasonable mipmap settings
+		final int maxNumElements = 64 * 64 * 64;
+		final ExportMipmapInfo autoMipmapSettings = ProposeMipmaps.proposeMipmaps(
+				new BasicViewSetup( 0, "", size, voxelSize ),
+				maxNumElements );
+
+		// show dialog to get output paths, resolutions, subdivisions, min-max option
+		final Parameters params = getParameters( autoMipmapSettings );
+		if ( params == null )
+			return;
+
+		final ProgressWriter progressWriter = new ProgressWriterIJ();
+		progressWriter.out().println( "starting export..." );
+
+		// create ImgLoader wrapping the image
+		final TypedBasicImgLoader< ? > imgLoader;
+		final Runnable clearCache;
+		final boolean isVirtual = imp.getStack() != null && imp.getStack().isVirtual();
+		if ( isVirtual )
+		{
+			final VirtualStackImageLoader< ?, ?, ? > il;
+			switch ( imp.getType() )
+			{
+			case ImagePlus.GRAY8:
+				il = VirtualStackImageLoader.createUnsignedByteInstance( imp );
+				break;
+			case ImagePlus.GRAY16:
+				il = VirtualStackImageLoader.createUnsignedShortInstance( imp );
+				break;
+			case ImagePlus.GRAY32:
+			default:
+				il = VirtualStackImageLoader.createFloatInstance( imp );
+				break;
+			}
+			imgLoader = il;
+			clearCache = il.getCacheControl()::clearCache;
+		}
+		else
+		{
+			switch ( imp.getType() )
+			{
+			case ImagePlus.GRAY8:
+				imgLoader = ImageStackImageLoader.createUnsignedByteInstance( imp );
+				break;
+			case ImagePlus.GRAY16:
+				imgLoader = ImageStackImageLoader.createUnsignedShortInstance( imp );
+				break;
+			case ImagePlus.GRAY32:
+			default:
+				imgLoader = ImageStackImageLoader.createFloatInstance( imp );
+				break;
+			}
+			clearCache = () -> {};
+		}
+
+		final int numTimepoints = imp.getNFrames();
+		final int numSetups = imp.getNChannels();
+
+		// create SourceTransform from the images calibration
+		final AffineTransform3D sourceTransform = new AffineTransform3D();
+		sourceTransform.set( pw, 0, 0, 0, 0, ph, 0, 0, 0, 0, pd, 0 );
+
+		// write n5
+		final HashMap< Integer, BasicViewSetup > setups = new HashMap<>( numSetups );
+		for ( int s = 0; s < numSetups; ++s )
+		{
+			final BasicViewSetup setup = new BasicViewSetup( s, String.format( "channel %d", s + 1 ), size, voxelSize );
+			setup.setAttribute( new Channel( s + 1 ) );
+			setups.put( s, setup );
+		}
+		final ArrayList< TimePoint > timepoints = new ArrayList<>( numTimepoints );
+		for ( int t = 0; t < numTimepoints; ++t )
+			timepoints.add( new TimePoint( t ) );
+		final SequenceDescriptionMinimal seq = new SequenceDescriptionMinimal( new TimePoints( timepoints ), setups, imgLoader, null );
+
+		Map< Integer, ExportMipmapInfo > perSetupExportMipmapInfo;
+		perSetupExportMipmapInfo = new HashMap<>();
+		final ExportMipmapInfo mipmapInfo = new ExportMipmapInfo( params.resolutions, params.subdivisions );
+		for ( final BasicViewSetup setup : seq.getViewSetupsOrdered() )
+			perSetupExportMipmapInfo.put( setup.getId(), mipmapInfo );
+
+		// LoopBackHeuristic:
+		// - If saving more than 8x on pixel reads use the loopback image over
+		//   original image
+		// - For virtual stacks also consider the cache size that would be
+		//   required for all original planes contributing to a "plane of
+		//   blocks" at the current level. If this is more than 1/4 of
+		//   available memory, use the loopback image.
+		final long planeSizeInBytes = imp.getWidth() * imp.getHeight() * imp.getBytesPerPixel();
+		final long ijMaxMemory = IJ.maxMemory();
+		final int numCellCreatorThreads = Math.max( 1, PluginHelper.numThreads() - 1 );
+		final LoopbackHeuristic loopbackHeuristic = new LoopbackHeuristic()
+		{
+			@Override
+			public boolean decide( final RandomAccessibleInterval< ? > originalImg, final int[] factorsToOriginalImg, final int previousLevel, final int[] factorsToPreviousLevel, final int[] chunkSize )
+			{
+				if ( previousLevel < 0 )
+					return false;
+
+				if ( Intervals.numElements( factorsToOriginalImg ) / Intervals.numElements( factorsToPreviousLevel ) >= 8 )
+					return true;
+
+				if ( isVirtual )
+				{
+					final long requiredCacheSize = planeSizeInBytes * factorsToOriginalImg[ 2 ] * chunkSize[ 2 ];
+					if ( requiredCacheSize > ijMaxMemory / 4 )
+						return true;
+				}
+
+				return false;
+			}
+		};
+
+		final AfterEachPlane afterEachPlane = new AfterEachPlane()
+		{
+			@Override
+			public void afterEachPlane( final boolean usedLoopBack )
+			{
+				if ( !usedLoopBack && isVirtual )
+				{
+					final long free = Runtime.getRuntime().freeMemory();
+					final long total = Runtime.getRuntime().totalMemory();
+					final long max = Runtime.getRuntime().maxMemory();
+					final long actuallyFree = max - total + free;
+
+					if ( actuallyFree < max / 2 )
+						clearCache.run();
+				}
+			}
+
+		};
+
+		try
+		{
+			WriteSequenceToN5.writeN5File( seq, perSetupExportMipmapInfo,
+					params.compression, params.n5File,
+					loopbackHeuristic, afterEachPlane, numCellCreatorThreads,
+					new SubTaskProgressWriter( progressWriter, 0, 0.95 ) );
+
+			// write xml sequence description
+			final N5ImageLoader n5Loader = new N5ImageLoader( params.n5File, null );
+			final SequenceDescriptionMinimal seqh5 = new SequenceDescriptionMinimal( seq, n5Loader );
+
+			final ArrayList< ViewRegistration > registrations = new ArrayList<>();
+			for ( int t = 0; t < numTimepoints; ++t )
+				for ( int s = 0; s < numSetups; ++s )
+					registrations.add( new ViewRegistration( t, s, sourceTransform ) );
+
+			final File basePath = params.seqFile.getParentFile();
+			final SpimDataMinimal spimData = new SpimDataMinimal( basePath, seqh5, new ViewRegistrations( registrations ) );
+
+			new XmlIoSpimDataMinimal().save( spimData, params.seqFile.getAbsolutePath() );
+			progressWriter.setProgress( 1.0 );
+		}
+		catch ( final SpimDataException | IOException e )
+		{
+			throw new RuntimeException( e );
+		}
+		progressWriter.out().println( "done" );
+	}
+
+	protected static class Parameters
+	{
+		final boolean setMipmapManual;
+
+		final int[][] resolutions;
+
+		final int[][] subdivisions;
+
+		final File seqFile;
+
+		final File n5File;
+
+		final Compression compression;
+
+		public Parameters(
+				final boolean setMipmapManual, final int[][] resolutions, final int[][] subdivisions,
+				final File seqFile, final File n5File,
+				final Compression compression )
+		{
+			this.setMipmapManual = setMipmapManual;
+			this.resolutions = resolutions;
+			this.subdivisions = subdivisions;
+			this.seqFile = seqFile;
+			this.n5File = n5File;
+			this.compression = compression;
+		}
+	}
+
+	static boolean lastSetMipmapManual = false;
+
+	static String lastSubsampling = "";
+
+	static String lastChunkSizes = "";
+
+	static int lastCompressionChoice = 0;
+
+	static boolean lastCompressionDefaultSettings = true;
+
+	static String lastExportPath = "./export.xml";
+
+	protected Parameters getParameters( final ExportMipmapInfo autoMipmapSettings  )
+	{
+		while ( true )
+		{
+			final GenericDialogPlus gd = new GenericDialogPlus( "Export for BigDataViewer as XML/N5" );
+
+			gd.addCheckbox( "manual_mipmap_setup", lastSetMipmapManual );
+			final Checkbox cManualMipmap = ( Checkbox ) gd.getCheckboxes().lastElement();
+			gd.addStringField( "Subsampling_factors", lastSubsampling, 25 );
+			final TextField tfSubsampling = ( TextField ) gd.getStringFields().lastElement();
+			gd.addStringField( "N5_chunk_sizes", lastChunkSizes, 25 );
+			final TextField tfChunkSizes = ( TextField ) gd.getStringFields().lastElement();
+
+			gd.addMessage( "" );
+			final String[] compressionChoices = new String[] { "raw (no compression)", "bzip", "gzip", "lz4", "xz" };
+			gd.addChoice( "compression", compressionChoices, compressionChoices[ lastCompressionChoice ] );
+			gd.addCheckbox( "default settings", lastCompressionDefaultSettings );
+
+			gd.addMessage( "" );
+			PluginHelper.addSaveAsFileField( gd, "Export_path", lastExportPath, 25 );
+
+			final String autoSubsampling = ProposeMipmaps.getArrayString( autoMipmapSettings.getExportResolutions() );
+			final String autoChunkSizes = ProposeMipmaps.getArrayString( autoMipmapSettings.getSubdivisions() );
+			gd.addDialogListener( ( dialog, e ) -> {
+				gd.getNextBoolean();
+				gd.getNextString();
+				gd.getNextString();
+				gd.getNextChoiceIndex();
+				gd.getNextBoolean();
+				gd.getNextString();
+				if ( e instanceof ItemEvent && e.getID() == ItemEvent.ITEM_STATE_CHANGED && e.getSource() == cManualMipmap )
+				{
+					final boolean useManual = cManualMipmap.getState();
+					tfSubsampling.setEnabled( useManual );
+					tfChunkSizes.setEnabled( useManual );
+					if ( !useManual )
+					{
+						tfSubsampling.setText( autoSubsampling );
+						tfChunkSizes.setText( autoChunkSizes );
+					}
+				}
+				return true;
+			} );
+
+			tfSubsampling.setEnabled( lastSetMipmapManual );
+			tfChunkSizes.setEnabled( lastSetMipmapManual );
+			if ( !lastSetMipmapManual )
+			{
+				tfSubsampling.setText( autoSubsampling );
+				tfChunkSizes.setText( autoChunkSizes );
+			}
+
+			gd.showDialog();
+			if ( gd.wasCanceled() )
+				return null;
+
+			lastSetMipmapManual = gd.getNextBoolean();
+			lastSubsampling = gd.getNextString();
+			lastChunkSizes = gd.getNextString();
+			lastCompressionChoice = gd.getNextChoiceIndex();
+			lastCompressionDefaultSettings = gd.getNextBoolean();
+			lastExportPath = gd.getNextString();
+
+			// parse mipmap resolutions and cell sizes
+			final int[][] resolutions = PluginHelper.parseResolutionsString( lastSubsampling );
+			final int[][] subdivisions = PluginHelper.parseResolutionsString( lastChunkSizes );
+			if ( resolutions.length == 0 )
+			{
+				IJ.showMessage( "Cannot parse subsampling factors " + lastSubsampling );
+				continue;
+			}
+			if ( subdivisions.length == 0 )
+			{
+				IJ.showMessage( "Cannot parse n5 chunk sizes " + lastChunkSizes );
+				continue;
+			}
+			else if ( resolutions.length != subdivisions.length )
+			{
+				IJ.showMessage( "subsampling factors and n5 chunk sizes must have the same number of elements" );
+				continue;
+			}
+
+			String seqFilename = lastExportPath;
+			if ( !seqFilename.endsWith( ".xml" ) )
+				seqFilename += ".xml";
+			final File seqFile = new File( seqFilename );
+			final File parent = seqFile.getParentFile();
+			if ( parent == null || !parent.exists() || !parent.isDirectory() )
+			{
+				IJ.showMessage( "Invalid export filename " + seqFilename );
+				continue;
+			}
+			final String n5Filename = seqFilename.substring( 0, seqFilename.length() - 4 ) + ".n5";
+			final File n5File = new File( n5Filename );
+
+			final Compression compression;
+			switch ( lastCompressionChoice )
+			{
+			default:
+			case 0: // raw (no compression)
+				compression = new RawCompression();
+				break;
+			case 1: // bzip
+				compression = lastCompressionDefaultSettings
+						? new Bzip2Compression()
+						: getBzip2Settings();
+				break;
+			case 2: // gzip
+				compression = lastCompressionDefaultSettings
+						? new GzipCompression()
+						: getGzipSettings();
+				break;
+			case 3:// lz4
+				compression = lastCompressionDefaultSettings
+						? new Lz4Compression()
+						: getLz4Settings();
+				break;
+			case 4:// xz" };
+				compression = lastCompressionDefaultSettings
+						? new XzCompression()
+						: getXzSettings();
+				break;
+			}
+			if ( compression == null )
+				return null;
+
+			return new Parameters( lastSetMipmapManual, resolutions, subdivisions, seqFile, n5File, compression );
+		}
+	}
+
+	static int lastBzip2BlockSize = BZip2CompressorOutputStream.MAX_BLOCKSIZE;
+
+	protected Bzip2Compression getBzip2Settings()
+	{
+		while ( true )
+		{
+			final GenericDialogPlus gd = new GenericDialogPlus( "Bzip2 compression settings" );
+			gd.addNumericField(
+					String.format( "block size (%d-%d)",
+							BZip2CompressorOutputStream.MIN_BLOCKSIZE,
+							BZip2CompressorOutputStream.MAX_BLOCKSIZE ),
+					lastBzip2BlockSize, 0 );
+			gd.addMessage( "as 100k units" );
+
+			gd.showDialog();
+			if ( gd.wasCanceled() )
+				return null;
+
+			lastBzip2BlockSize = ( int ) gd.getNextNumber();
+			if ( lastBzip2BlockSize < BZip2CompressorOutputStream.MIN_BLOCKSIZE || lastBzip2BlockSize > BZip2CompressorOutputStream.MAX_BLOCKSIZE )
+			{
+				IJ.showMessage(
+						String.format( "Block size must be in range [%d, %d]",
+								BZip2CompressorOutputStream.MIN_BLOCKSIZE,
+								BZip2CompressorOutputStream.MAX_BLOCKSIZE ) );
+				continue;
+			}
+			return new Bzip2Compression( lastBzip2BlockSize );
+		}
+	}
+
+	static int lastGzipLevel = 6;
+
+	static boolean lastGzipUseZlib = false;
+
+	protected GzipCompression getGzipSettings()
+	{
+		while ( true )
+		{
+			final GenericDialogPlus gd = new GenericDialogPlus( "Gzip compression settings" );
+			gd.addNumericField( "level (0-9)", lastGzipLevel, 0 );
+			gd.addCheckbox( "use Zlib", lastGzipUseZlib );
+
+			gd.showDialog();
+			if ( gd.wasCanceled() )
+				return null;
+
+			lastGzipLevel = ( int ) gd.getNextNumber();
+			lastGzipUseZlib = gd.getNextBoolean();
+			if ( lastGzipLevel < 0 || lastGzipLevel > 9 )
+			{
+				IJ.showMessage( "Level must be in range [0, 9]" );
+				continue;
+			}
+			return new GzipCompression( lastGzipLevel, lastGzipUseZlib );
+		}
+	}
+
+	static int lastLz4BlockSize = 1 << 16;
+
+	protected Lz4Compression getLz4Settings()
+	{
+		final int COMPRESSION_LEVEL_BASE = 10;
+		final int MIN_BLOCK_SIZE = 64;
+		final int MAX_BLOCK_SIZE = 1 << (COMPRESSION_LEVEL_BASE + 0x0F);
+
+		while ( true )
+		{
+			final GenericDialogPlus gd = new GenericDialogPlus( "LZ4 compression settings" );
+			gd.addNumericField(
+					String.format( "block size (%d-%d)",
+							MIN_BLOCK_SIZE,
+							MAX_BLOCK_SIZE ),
+					lastLz4BlockSize, 0, 8, null );
+
+			gd.showDialog();
+			if ( gd.wasCanceled() )
+				return null;
+
+			lastLz4BlockSize = ( int ) gd.getNextNumber();
+			if ( lastLz4BlockSize < MIN_BLOCK_SIZE || lastLz4BlockSize > MAX_BLOCK_SIZE )
+			{
+				IJ.showMessage( String.format( "Block size must be in range [%d, %d]",
+						MIN_BLOCK_SIZE,
+						MAX_BLOCK_SIZE ) );
+				continue;
+			}
+			return new Lz4Compression( lastLz4BlockSize );
+		}
+	}
+
+	static int lastXzLevel = 6;
+
+	protected XzCompression getXzSettings()
+	{
+		while ( true )
+		{
+			final GenericDialogPlus gd = new GenericDialogPlus( "XZ compression settings" );
+			gd.addNumericField( "level (0-9)", lastXzLevel, 0 );
+			gd.addMessage( "LZMA2 preset level" );
+
+			gd.showDialog();
+			if ( gd.wasCanceled() )
+				return null;
+
+			lastXzLevel = ( int ) gd.getNextNumber();
+			if ( lastXzLevel < 0 || lastXzLevel > 9 )
+			{
+				IJ.showMessage( "Level must be in range [0, 9]" );
+				continue;
+			}
+			return new XzCompression( lastXzLevel );
+		}
+	}
+
+}
diff --git a/src/main/java/bdv/ij/ExportImagePlusPlugIn.java b/src/main/java/bdv/ij/ExportImagePlusPlugIn.java
index 1a3c90b3accd850c4726df23aa990ea73cfab0e1..0312a164f4ff7107343bf8ebc2f6312bbda7dc5f 100644
--- a/src/main/java/bdv/ij/ExportImagePlusPlugIn.java
+++ b/src/main/java/bdv/ij/ExportImagePlusPlugIn.java
@@ -14,16 +14,17 @@ import net.imglib2.FinalDimensions;
 import net.imglib2.RandomAccessibleInterval;
 import net.imglib2.realtransform.AffineTransform3D;
 
+import net.imglib2.util.Intervals;
 import org.scijava.command.Command;
 import org.scijava.plugin.Plugin;
 
 import bdv.export.ExportMipmapInfo;
+import bdv.export.ExportScalePyramid.AfterEachPlane;
+import bdv.export.ExportScalePyramid.LoopbackHeuristic;
 import bdv.export.ProgressWriter;
 import bdv.export.ProposeMipmaps;
 import bdv.export.SubTaskProgressWriter;
 import bdv.export.WriteSequenceToHdf5;
-import bdv.export.WriteSequenceToHdf5.AfterEachPlane;
-import bdv.export.WriteSequenceToHdf5.LoopbackHeuristic;
 import bdv.ij.export.imgloader.ImagePlusImgLoader;
 import bdv.ij.export.imgloader.ImagePlusImgLoader.MinMaxOption;
 import bdv.ij.util.PluginHelper;
@@ -103,7 +104,7 @@ public class ExportImagePlusPlugIn implements Command
 		final int w = imp.getWidth();
 		final int h = imp.getHeight();
 		final int d = imp.getNSlices();
-		final FinalDimensions size = new FinalDimensions( new int[] { w, h, d } );
+		final FinalDimensions size = new FinalDimensions( w, h, d );
 
 		// propose reasonable mipmap settings
 		final ExportMipmapInfo autoMipmapSettings = ProposeMipmaps.proposeMipmaps( new BasicViewSetup( 0, "", size, voxelSize ) );
@@ -177,7 +178,7 @@ public class ExportImagePlusPlugIn implements Command
 				if ( previousLevel < 0 )
 					return false;
 
-				if ( WriteSequenceToHdf5.numElements( factorsToOriginalImg ) / WriteSequenceToHdf5.numElements( factorsToPreviousLevel ) >= 8 )
+				if ( Intervals.numElements( factorsToOriginalImg ) / Intervals.numElements( factorsToPreviousLevel ) >= 8 )
 					return true;
 
 				if ( isVirtual )
diff --git a/src/main/java/bdv/ij/ExportSpimFusionPlugIn.java b/src/main/java/bdv/ij/ExportSpimFusionPlugIn.java
index 73c9e3a64e212e67ef87ca6df2db021a859a90cb..84066e29e0d087be8403df6602695360fe473c05 100644
--- a/src/main/java/bdv/ij/ExportSpimFusionPlugIn.java
+++ b/src/main/java/bdv/ij/ExportSpimFusionPlugIn.java
@@ -68,6 +68,7 @@ import mpicbg.spim.io.SPIMConfiguration;
 import mpicbg.spim.io.TextFileAccess;
 import spimopener.SPIMExperiment;
 
+@Deprecated
 @Plugin(type = Command.class,
 	menuPath = "Plugins>BigDataViewer>Deprecated>Export Fused Sequence as XML/HDF5")
 public class ExportSpimFusionPlugIn implements Command
diff --git a/src/main/java/bdv/ij/ExportSpimSequencePlugIn.java b/src/main/java/bdv/ij/ExportSpimSequencePlugIn.java
index 44bbdf36a75ef6d92fe446551506b6a898f4854c..d7378cb3a133aef2667e174b4cf6d9da61591232 100644
--- a/src/main/java/bdv/ij/ExportSpimSequencePlugIn.java
+++ b/src/main/java/bdv/ij/ExportSpimSequencePlugIn.java
@@ -45,6 +45,7 @@ import mpicbg.spim.io.SPIMConfiguration;
 import mpicbg.spim.io.TextFileAccess;
 import spimopener.SPIMExperiment;
 
+@Deprecated
 @Plugin(type = Command.class,
 	menuPath = "Plugins>BigDataViewer>Deprecated>Export Spim Sequence as XML/HDF5")
 public class ExportSpimSequencePlugIn implements Command
diff --git a/src/main/java/bdv/ij/OpenImagePlusPlugIn.java b/src/main/java/bdv/ij/OpenImagePlusPlugIn.java
index 132a981cb9851e965df599b1e254910e5ee05a30..d84afa91d8556557a907028b5509a80107a98589 100644
--- a/src/main/java/bdv/ij/OpenImagePlusPlugIn.java
+++ b/src/main/java/bdv/ij/OpenImagePlusPlugIn.java
@@ -1,9 +1,13 @@
 package bdv.ij;
 
+import bdv.viewer.ConverterSetups;
+import bdv.viewer.SynchronizedViewerState;
+import bdv.viewer.ViewerState;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.HashMap;
 
+import java.util.List;
 import net.imglib2.FinalDimensions;
 import net.imglib2.realtransform.AffineTransform3D;
 import net.imglib2.type.numeric.ARGBType;
@@ -21,11 +25,9 @@ import bdv.spimdata.SequenceDescriptionMinimal;
 import bdv.spimdata.SpimDataMinimal;
 import bdv.spimdata.WrapBasicImgLoader;
 import bdv.tools.brightness.ConverterSetup;
-import bdv.tools.brightness.SetupAssignments;
 import bdv.viewer.DisplayMode;
 import bdv.viewer.SourceAndConverter;
 import bdv.viewer.ViewerOptions;
-import bdv.viewer.VisibilityAndGrouping;
 import ij.CompositeImage;
 import ij.IJ;
 import ij.ImageJ;
@@ -132,18 +134,19 @@ public class OpenImagePlusPlugIn implements Command
 					nTimepoints, cache,
 					"BigDataViewer", new ProgressWriterIJ(), ViewerOptions.options() );
 
-			final SetupAssignments sa = bdv.getSetupAssignments();
-			final VisibilityAndGrouping vg = bdv.getViewer().getVisibilityAndGrouping();
-
-			int channelOffset = 0;
-			int numActiveChannels = 0;
-			for ( ImagePlus imp : imgList )
+			final SynchronizedViewerState state = bdv.getViewer().state();
+			synchronized ( state )
 			{
-				numActiveChannels += transferChannelVisibility( channelOffset, imp,vg );
-				transferChannelSettings( channelOffset, imp, sa );
-				channelOffset += imp.getNChannels();
+				int channelOffset = 0;
+				int numActiveChannels = 0;
+				for ( ImagePlus imp : imgList )
+				{
+					numActiveChannels += transferChannelVisibility( channelOffset, imp, state );
+					transferChannelSettings( channelOffset, imp, state, bdv.getConverterSetups() );
+					channelOffset += imp.getNChannels();
+				}
+				state.setDisplayMode( numActiveChannels > 1 ? DisplayMode.FUSED : DisplayMode.SINGLE );
 			}
-			vg.setDisplayMode( numActiveChannels > 1 ? DisplayMode.FUSED : DisplayMode.SINGLE );
 		}
 	}
 
@@ -257,19 +260,20 @@ public class OpenImagePlusPlugIn implements Command
 	/**
 	 * @return number of setups that were set active.
 	 */
-	protected int transferChannelVisibility( int channelOffset, final ImagePlus imp, final VisibilityAndGrouping visibility )
+	protected int transferChannelVisibility( int channelOffset, final ImagePlus imp, final ViewerState state )
 	{
 		final int nChannels = imp.getNChannels();
 		final CompositeImage ci = imp.isComposite() ? ( CompositeImage ) imp : null;
+		final List< SourceAndConverter< ? > > sources = state.getSources();
 		if ( ci != null && ci.getCompositeMode() == IJ.COMPOSITE )
 		{
 			final boolean[] activeChannels = ci.getActiveChannels();
 			int numActiveChannels = 0;
-			for ( int i = 0; i < activeChannels.length; ++i )
+			for ( int i = 0; i < Math.min( activeChannels.length, nChannels ); ++i )
 			{
-				final int setup = channelOffset + i;
-				visibility.setSourceActive( setup, activeChannels[ i ] );
-				visibility.setCurrentSource( setup );
+				final SourceAndConverter< ? > source = sources.get( channelOffset + i );
+				state.setSourceActive( source, activeChannels[ i ] );
+				state.setCurrentSource( source );
 				numActiveChannels += activeChannels[ i ] ? 1 : 0;
 			}
 			return numActiveChannels;
@@ -278,16 +282,17 @@ public class OpenImagePlusPlugIn implements Command
 		{
 			final int activeChannel = imp.getChannel() - 1;
 			for ( int i = 0; i < nChannels; ++i )
-				visibility.setSourceActive( channelOffset + i, i == activeChannel );
-			visibility.setCurrentSource( channelOffset + activeChannel );
+				state.setSourceActive( sources.get( channelOffset + i ), i == activeChannel );
+			state.setCurrentSource( sources.get( channelOffset + activeChannel ) );
 			return 1;
 		}
 	}
 
-	protected void transferChannelSettings( int channelOffset, final ImagePlus imp, final SetupAssignments setupAssignments )
+	protected void transferChannelSettings( int channelOffset, final ImagePlus imp, final ViewerState state, final ConverterSetups converterSetups )
 	{
 		final int nChannels = imp.getNChannels();
 		final CompositeImage ci = imp.isComposite() ? ( CompositeImage ) imp : null;
+		final List< SourceAndConverter< ? > > sources = state.getSources();
 		if ( ci != null )
 		{
 			final int mode = ci.getCompositeMode();
@@ -295,7 +300,7 @@ public class OpenImagePlusPlugIn implements Command
 			for ( int c = 0; c < nChannels; ++c )
 			{
 				final LUT lut = ci.getChannelLut( c + 1 );
-				final ConverterSetup setup = setupAssignments.getConverterSetups().get( channelOffset + c );
+				final ConverterSetup setup = converterSetups.getConverterSetup( sources.get( channelOffset + c ) );
 				if ( transferColor )
 					setup.setColor( new ARGBType( lut.getRGB( 255 ) ) );
 				setup.setDisplayRange( lut.min, lut.max );
@@ -307,7 +312,7 @@ public class OpenImagePlusPlugIn implements Command
 			final double displayRangeMax = imp.getDisplayRangeMax();
 			for ( int i = 0; i < nChannels; ++i )
 			{
-				final ConverterSetup setup = setupAssignments.getConverterSetups().get( channelOffset + i );
+				final ConverterSetup setup = converterSetups.getConverterSetup( sources.get( channelOffset + i ) );
 				final LUT[] luts = imp.getLuts();
 				if ( luts.length != 0 )
 					setup.setColor( new ARGBType( luts[ 0 ].getRGB( 255 ) ) );
diff --git a/src/main/java/bdv/ij/export/FixAbsolutePathsInHdf5Partitions.java b/src/main/java/bdv/ij/export/FixAbsolutePathsInHdf5Partitions.java
index ac4e3d4a77771c4fc5b61fe96e626a984061ae29..b660a7103ada2d6bb29308f7264e70aab5aa8d66 100644
--- a/src/main/java/bdv/ij/export/FixAbsolutePathsInHdf5Partitions.java
+++ b/src/main/java/bdv/ij/export/FixAbsolutePathsInHdf5Partitions.java
@@ -29,6 +29,7 @@ import mpicbg.spim.data.generic.sequence.BasicViewSetup;
  *
  * @author Tobias Pietzsch &lt;tobias.pietzsch@gmail.com&gt;
  */
+@Deprecated
 public class FixAbsolutePathsInHdf5Partitions
 {
 	public static void fix( final String xmlFilename ) throws SpimDataException, IOException
diff --git a/src/main/java/bdv/ij/export/FusionResult.java b/src/main/java/bdv/ij/export/FusionResult.java
index 1a57fa72e76961a78342b424ed1ffd7cac0895a1..3b0259441127a9bb81bf742a83e8673a4dc4ad52 100644
--- a/src/main/java/bdv/ij/export/FusionResult.java
+++ b/src/main/java/bdv/ij/export/FusionResult.java
@@ -20,6 +20,7 @@ import mpicbg.spim.data.sequence.VoxelDimensions;
 import net.imglib2.Dimensions;
 import net.imglib2.realtransform.AffineTransform3D;
 
+@Deprecated
 public class FusionResult
 {
 	private final SequenceDescriptionMinimal desc;
diff --git a/src/main/java/bdv/ij/export/Scripting.java b/src/main/java/bdv/ij/export/Scripting.java
index e7f30ae7c10b33d04139e2f04fce84487b3211af..1491f4fe70b8f256ca62f0e018043f78a0bc8746 100644
--- a/src/main/java/bdv/ij/export/Scripting.java
+++ b/src/main/java/bdv/ij/export/Scripting.java
@@ -17,6 +17,7 @@ import mpicbg.spim.data.SpimDataException;
 import mpicbg.spim.io.ConfigurationParserException;
 import net.imglib2.realtransform.AffineTransform3D;
 
+@Deprecated
 public class Scripting
 {
 	/**
@@ -42,6 +43,7 @@ public class Scripting
 	 * @return an initialized {@link SpimRegistrationSequence} sequence.
 	 * @throws ConfigurationParserException
 	 */
+	@Deprecated
 	public static SpimRegistrationSequence createSpimRegistrationSequence(
 			final String huiskenExperimentXmlFile,
 			final String channels,
@@ -85,6 +87,7 @@ public class Scripting
 	 * @return an initialized {@link SpimRegistrationSequence} sequence.
 	 * @throws ConfigurationParserException
 	 */
+	@Deprecated
 	public static SpimRegistrationSequence createSpimRegistrationSequence(
 			final String inputDirectory,
 			final String inputFilePattern,
@@ -108,6 +111,7 @@ public class Scripting
 	 * @param cropOffsetZ
 	 * @return
 	 */
+	@Deprecated
 	public static Map< Integer, AffineTransform3D > getFusionTransforms(
 			final SpimRegistrationSequence spimseq,
 			final int scale,
@@ -130,6 +134,7 @@ public class Scripting
 	 * @param fusionTransforms
 	 * @return
 	 */
+	@Deprecated
 	public static FusionResult createFusionResult(
 			final SpimRegistrationSequence spimseq,
 			final String filepath,
@@ -159,6 +164,7 @@ public class Scripting
 	 *            is used to generate paths for the partitions.
 	 * @return list of partitions.
 	 */
+	@Deprecated
 	public static ArrayList< Partition > split(
 			final SetupAggregator aggregator,
 			final int timepointsPerPartition,
diff --git a/src/main/java/bdv/ij/export/SetupAggregator.java b/src/main/java/bdv/ij/export/SetupAggregator.java
index b21d975da3db27e19f48c296a2657a215408b110..4650098e7fe548f8ee2eaeb604fa949d8bddcaca 100644
--- a/src/main/java/bdv/ij/export/SetupAggregator.java
+++ b/src/main/java/bdv/ij/export/SetupAggregator.java
@@ -34,6 +34,7 @@ import net.imglib2.img.cell.CellImg;
  *
  * @author Tobias Pietzsch &lt;tobias.pietzsch@gmail.com&gt;
  */
+@Deprecated
 public class SetupAggregator
 {
 	/**
@@ -80,7 +81,7 @@ public class SetupAggregator
 	/**
 	 * Add a new {@link BasicViewSetup} to the aggregator.
 	 *
-	 * Adds a setup of the given source {@link SpimRegistrationSequence} to the
+	 * Adds a setup of the given source {@link AbstractSequenceDescription} to the
 	 * aggregator. A reference to the source sequence is kept and the source
 	 * {@link ViewRegistrations} are copied. In the viewer format, every image
 	 * is stored in multiple resolutions. The resolutions are described as int[]
@@ -181,6 +182,7 @@ public class SetupAggregator
 	 *            "{32,32,32}, {16,16,8}, {8,8,8}" where each "{...}"
 	 *            defines one subdivision.
 	 */
+	@Deprecated
 	public void addSetup( final SpimRegistrationSequence sequence, final int setupIndex, final String resolutionsString, final String subdivisionsString )
 	{
 		final AbstractSequenceDescription< ?, ?, ? > desc = sequence.getSequenceDescription();
@@ -222,6 +224,7 @@ public class SetupAggregator
 	 *            the set of subdivisions to store. each nested int[] array
 	 *            defines one subdivision.
 	 */
+	@Deprecated
 	public void addSetup( final SpimRegistrationSequence sequence, final int setupIndex, final int[][] resolutions, final int[][] subdivisions )
 	{
 		final AbstractSequenceDescription< ?, ?, ? > desc = sequence.getSequenceDescription();
@@ -257,6 +260,7 @@ public class SetupAggregator
 	 *            "{32,32,32}, {16,16,8}, {8,8,8}" where each "{...}"
 	 *            defines one subdivision.
 	 */
+	@Deprecated
 	public void addSetups( final SpimRegistrationSequence sequence, final String resolutionsString, final String subdivisionsString )
 	{
 		for ( int s = 0; s < sequence.getSequenceDescription().getViewSetups().size(); ++s )
@@ -286,6 +290,7 @@ public class SetupAggregator
 	 *            the set of subdivisions to store. each nested int[] array
 	 *            defines one subdivision.
 	 */
+	@Deprecated
 	public void addSetups( final SpimRegistrationSequence sequence, final int[][] resolutions, final int[][] subdivisions )
 	{
 		for ( int s = 0; s < sequence.getSequenceDescription().getViewSetups().size(); ++s )
@@ -317,6 +322,7 @@ public class SetupAggregator
 	 *            "{32,32,32}, {16,16,8}, {8,8,8}" where each "{...}"
 	 *            defines one subdivision.
 	 */
+	@Deprecated
 	public void addSetups( final FusionResult fusionResult, final String resolutionsString, final String subdivisionsString )
 	{
 		final int[][] resolutions = PluginHelper.parseResolutionsString( resolutionsString );
@@ -354,6 +360,7 @@ public class SetupAggregator
 	 *            the set of subdivisions to store. each nested int[] array
 	 *            defines one subdivision.
 	 */
+	@Deprecated
 	public void addSetups( final FusionResult fusionResult, final int[][] resolutions, final int[][] subdivisions )
 	{
 		if ( resolutions.length != subdivisions.length )
diff --git a/src/main/java/bdv/ij/export/SpimRegistrationSequence.java b/src/main/java/bdv/ij/export/SpimRegistrationSequence.java
index bb0cd223e66181c88f007131678ac62a465a0331..2f2cdfeda09a39c60d832df804877df716aca05c 100644
--- a/src/main/java/bdv/ij/export/SpimRegistrationSequence.java
+++ b/src/main/java/bdv/ij/export/SpimRegistrationSequence.java
@@ -35,6 +35,7 @@ import net.imglib2.realtransform.AffineTransform3D;
 import spim.vecmath.Point3f;
 import spimopener.SPIMExperiment;
 
+@Deprecated
 public class SpimRegistrationSequence
 {
 	private final SequenceDescriptionMinimal sequenceDescription;
diff --git a/src/main/java/bdv/ij/export/ViewSetupWrapper.java b/src/main/java/bdv/ij/export/ViewSetupWrapper.java
index ebbb167838e33d821266fcb7b13d0a25295e8f2d..cf1afb839b9f757eeef187846d4bdde747c8169b 100644
--- a/src/main/java/bdv/ij/export/ViewSetupWrapper.java
+++ b/src/main/java/bdv/ij/export/ViewSetupWrapper.java
@@ -13,6 +13,7 @@ import mpicbg.spim.data.sequence.ViewSetup;
  *
  * @author Tobias Pietzsch &lt;tobias.pietzsch@gmail.com&gt;
  */
+@Deprecated
 public class ViewSetupWrapper extends BasicViewSetup
 {
 	private final AbstractSequenceDescription< ?, ?, ? > sourceSequence;
diff --git a/src/main/java/bdv/ij/export/imgloader/FusionImageLoader.java b/src/main/java/bdv/ij/export/imgloader/FusionImageLoader.java
index 5b01a1dd587d8e59d9b3f947577100656ed6144b..9a0a4c8f35cc26a835bcc28245aae4d5a4da09e7 100644
--- a/src/main/java/bdv/ij/export/imgloader/FusionImageLoader.java
+++ b/src/main/java/bdv/ij/export/imgloader/FusionImageLoader.java
@@ -45,6 +45,7 @@ import net.imglib2.view.Views;
  *
  * @author Tobias Pietzsch &lt;tobias.pietzsch@gmail.com&gt;
  */
+@Deprecated
 public class FusionImageLoader< T extends RealType< T > > implements ImgLoader
 {
 	private final String pattern;
diff --git a/src/main/java/bdv/ij/export/imgloader/HuiskenImageLoader.java b/src/main/java/bdv/ij/export/imgloader/HuiskenImageLoader.java
index 8dc85ba386139df45dd5af513d4bde0a2cdaafc0..9e88239e468597cf661afe839966a6f7423f3789 100644
--- a/src/main/java/bdv/ij/export/imgloader/HuiskenImageLoader.java
+++ b/src/main/java/bdv/ij/export/imgloader/HuiskenImageLoader.java
@@ -25,6 +25,7 @@ import spimopener.SPIMExperiment;
  *
  * @author Tobias Pietzsch &lt;tobias.pietzsch@gmail.com&gt;
  */
+@Deprecated
 public class HuiskenImageLoader implements BasicImgLoader
 {
 	private final File expFile;
diff --git a/src/main/java/bdv/ij/export/imgloader/LegacyStackImageLoader.java b/src/main/java/bdv/ij/export/imgloader/LegacyStackImageLoader.java
index c832d531e349749484db9e5c0fc4fa7de2cc35dc..474351768bff71221fd455ce177ae9bf49b3d51f 100644
--- a/src/main/java/bdv/ij/export/imgloader/LegacyStackImageLoader.java
+++ b/src/main/java/bdv/ij/export/imgloader/LegacyStackImageLoader.java
@@ -34,6 +34,7 @@ import net.imglib2.type.numeric.integer.UnsignedShortType;
  *
  * @author Tobias Pietzsch &lt;tobias.pietzsch@gmail.com&gt;
  */
+@Deprecated
 public class LegacyStackImageLoader implements LegacyBasicImgLoader< UnsignedShortType >
 {
 	private final ImgOpener opener;
diff --git a/src/main/java/bdv/ij/export/imgloader/StackImageLoader.java b/src/main/java/bdv/ij/export/imgloader/StackImageLoader.java
index e73e7dc90c012b6e6cdef6d7aea11ace53a3a766..adb96918cd89520c6a3ac1ecb9935826f8b468e2 100644
--- a/src/main/java/bdv/ij/export/imgloader/StackImageLoader.java
+++ b/src/main/java/bdv/ij/export/imgloader/StackImageLoader.java
@@ -18,6 +18,7 @@ import net.imglib2.type.numeric.integer.UnsignedShortType;
  *
  * @author Tobias Pietzsch &lt;tobias.pietzsch@gmail.com&gt;
  */
+@Deprecated
 public class StackImageLoader extends LegacyBasicImgLoaderWrapper< UnsignedShortType, LegacyStackImageLoader >
 {
 	public StackImageLoader( final HashMap< ViewId, String > filenames, final boolean useImageJOpener )