Repository: mobie/mobie-viewer-fiji (language: python)

Issue:
Segment selection colouring mode
In the old MoBIE, there was a context menu option to change the segment selection colouring mode e.g. to show selected segments in yellow only, to dim the non-selected segments etc.

This still needs to be migrated to MoBIE2.


Discussion / hints:

Fix patch:
diff --git a/pom.xml b/pom.xml
index 2d4181044..0793bd69e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -81,8 +81,10 @@
 		<bigdataviewer-core.version>10.2.1-SNAPSHOT</bigdataviewer-core.version>
 		<bigdataviewer-vistools.version>1.0.0-beta-28</bigdataviewer-vistools.version>
 
+
 		<!-- BIOP -->
-		<bigdataviewer-playground.version>0.2.14</bigdataviewer-playground.version>
+		<bigdataviewer-playground.version>0.3.4</bigdataviewer-playground.version>
+		<bigdataviewer-playground-display.version>0.1.7-SNAPSHOT</bigdataviewer-playground-display.version>
 
 		<!-- EMBL CBA -->
 		<imagej-utils.version>0.6.4-SNAPSHOT</imagej-utils.version>
@@ -125,6 +127,11 @@
 			<artifactId>bigdataviewer-playground</artifactId>
 			<version>${bigdataviewer-playground.version}</version>
 		</dependency>
+		<dependency>
+			<groupId>sc.fiji.bigdataviewer</groupId>
+			<artifactId>bigdataviewer-playground-display</artifactId>
+			<version>${bigdataviewer-playground-display.version}</version>
+		</dependency>
 		<dependency>
 			<groupId>net.imglib2</groupId>
 			<artifactId>imglib2</artifactId>
diff --git a/src/main/java/de/embl/cba/mobie/MoBIE.java b/src/main/java/de/embl/cba/mobie/MoBIE.java
index 6f9a0ac44..40e61897b 100644
--- a/src/main/java/de/embl/cba/mobie/MoBIE.java
+++ b/src/main/java/de/embl/cba/mobie/MoBIE.java
@@ -477,7 +477,7 @@ public void loadPrimarySegmentsTables( SegmentationSourceDisplay segmentationDis
 
 	public void closeSourceAndConverter( SourceAndConverter< ? > sourceAndConverter )
 	{
-		SourceAndConverterServices.getSourceAndConverterDisplayService().removeFromAllBdvs( sourceAndConverter );
+		SourceAndConverterServices.getBdvDisplayService().removeFromAllBdvs( sourceAndConverter );
 		String sourceName = sourceAndConverter.getSpimSource().getName();
 		final ImgLoader imgLoader = sourceNameToImgLoader.get( sourceName );
 		if ( imgLoader instanceof N5ImageLoader )
diff --git a/src/main/java/de/embl/cba/mobie/bdv/ImageSliceView.java b/src/main/java/de/embl/cba/mobie/bdv/ImageSliceView.java
index 1edca3b36..1a5cf8dce 100644
--- a/src/main/java/de/embl/cba/mobie/bdv/ImageSliceView.java
+++ b/src/main/java/de/embl/cba/mobie/bdv/ImageSliceView.java
@@ -2,6 +2,7 @@
 
 import bdv.tools.brightness.ConverterSetup;
 import bdv.util.BdvHandle;
+import bdv.util.projector.mixed.BlendingMode;
 import bdv.viewer.SourceAndConverter;
 import de.embl.cba.bdv.utils.lut.GlasbeyARGBLut;
 import de.embl.cba.mobie.MoBIE;
@@ -17,7 +18,6 @@
 import net.imglib2.converter.Converter;
 import net.imglib2.type.numeric.ARGBType;
 import net.imglib2.type.numeric.RealType;
-import sc.fiji.bdvpg.bdv.projector.BlendingMode;
 import sc.fiji.bdvpg.scijava.services.SourceAndConverterBdvDisplayService;
 import sc.fiji.bdvpg.scijava.services.SourceAndConverterService;
 import sc.fiji.bdvpg.services.SourceAndConverterServices;
@@ -45,7 +45,7 @@ public ImageSliceView( MoBIE moBIE, ImageSourceDisplay imageDisplay, BdvHandle b
 		this.bdvHandle = bdvHandle;
 		this.sourceAndConverterSupplier = sourceAndConverterSupplier;
 
-		displayService = SourceAndConverterServices.getSourceAndConverterDisplayService();
+		displayService = SourceAndConverterServices.getBdvDisplayService();
 		sacService = ( SourceAndConverterService ) SourceAndConverterServices.getSourceAndConverterService();
 
 		show();
diff --git a/src/main/java/de/embl/cba/mobie/bdv/ScreenShotMaker.java b/src/main/java/de/embl/cba/mobie/bdv/ScreenShotMaker.java
new file mode 100644
index 000000000..d2fcc3658
--- /dev/null
+++ b/src/main/java/de/embl/cba/mobie/bdv/ScreenShotMaker.java
@@ -0,0 +1,453 @@
+/*-
+ * #%L
+ * BigDataViewer-Playground
+ * %%
+ * Copyright (C) 2019 - 2021 Nicolas Chiaruttini, EPFL - Robert Haase, MPI CBG - Christian Tischer, EMBL
+ * %%
+ * 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
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+package de.embl.cba.mobie.bdv;
+
+import bdv.util.BdvHandle;
+import bdv.util.projector.mixed.AccumulateMixedProjectorARGB;
+import bdv.util.projector.mixed.BlendingMode;
+import bdv.viewer.Interpolation;
+import bdv.viewer.Source;
+import bdv.viewer.SourceAndConverter;
+import ij.CompositeImage;
+import ij.IJ;
+import ij.ImagePlus;
+import ij.plugin.Duplicator;
+import ij.process.LUT;
+import net.imglib2.Cursor;
+import net.imglib2.RandomAccess;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.RealRandomAccess;
+import net.imglib2.algorithm.util.Grids;
+import net.imglib2.converter.Converter;
+import net.imglib2.img.array.ArrayImgs;
+import net.imglib2.img.display.imagej.ImageJFunctions;
+import net.imglib2.realtransform.AffineTransform3D;
+import net.imglib2.type.numeric.ARGBType;
+import net.imglib2.type.numeric.RealType;
+import net.imglib2.type.numeric.integer.UnsignedShortType;
+import net.imglib2.util.Intervals;
+import net.imglib2.view.IntervalView;
+import net.imglib2.view.Views;
+import sc.fiji.bdvpg.bdv.BdvHandleHelper;
+import sc.fiji.bdvpg.scijava.services.SourceAndConverterBdvDisplayService;
+import sc.fiji.bdvpg.services.SourceAndConverterServices;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
+
+import static sc.fiji.bdvpg.bdv.BdvHandleHelper.getLevel;
+import static sc.fiji.bdvpg.bdv.BdvHandleHelper.getViewerVoxelSpacing;
+import static sc.fiji.bdvpg.bdv.BdvHandleHelper.isSourceIntersectingCurrentView;
+
+/**
+ * BigDataViewer Playground Action --
+ * ScreenShotMaker
+ * Author: @haesleinhuepf, @tischi
+ *         December 2019
+ */
+
+public class ScreenShotMaker
+{
+
+    private final BdvHandle bdvHandle;
+    private double physicalPixelSpacingInXY = 1;
+    private String physicalUnit = "Pixels";
+    private boolean sourceInteractionWithViewerPlaneOnly2D = false; // TODO: maybe remove in the future
+    ImagePlus screenShot = null;
+    private CompositeImage rawImageData = null;
+    private final SourceAndConverterBdvDisplayService displayService;
+    //private final ISourceAndConverterService sacService;
+    private long captureWidth;
+    private long captureHeight;
+
+    public ScreenShotMaker( BdvHandle bdvHandle) {
+        this.bdvHandle = bdvHandle;
+        this.displayService = SourceAndConverterServices.getBdvDisplayService();
+        //this.sacService = SourceAndConverterServices.getSourceAndConverterService();
+    }
+
+    public void setPhysicalPixelSpacingInXY(double spacing, String unit) {
+        this.screenShot = null;
+        this.physicalPixelSpacingInXY = spacing;
+        this.physicalUnit = unit;
+    }
+
+    public void setSourceInteractionWithViewerPlaneOnly2D(boolean sourceInteractionWithViewerPlaneOnly2D) {
+        this.screenShot = null;
+        this.sourceInteractionWithViewerPlaneOnly2D = sourceInteractionWithViewerPlaneOnly2D;
+    }
+
+    private void process() {
+        if (screenShot != null) {
+            return;
+        }
+        createScreenShot();
+    }
+
+    public ImagePlus getRgbScreenShot() {
+        process();
+        return screenShot;
+    }
+
+    public CompositeImage getRawScreenShot()
+    {
+        process();
+        return rawImageData;
+    }
+
+    private void createScreenShot()
+    {
+        final AffineTransform3D viewerTransform = new AffineTransform3D();
+        bdvHandle.getViewerPanel().state().getViewerTransform( viewerTransform );
+
+        final double viewerVoxelSpacing = getViewerVoxelSpacing( bdvHandle );
+        double dxy = physicalPixelSpacingInXY / viewerVoxelSpacing;
+
+        final int w = bdvHandle.getViewerPanel().getWidth();
+        final int h = bdvHandle.getViewerPanel().getHeight();
+
+        captureWidth = ( long ) Math.ceil( w / dxy );
+        captureHeight = ( long ) Math.ceil( h / dxy );
+
+        final ArrayList< RandomAccessibleInterval< UnsignedShortType > > rawCaptures = new ArrayList<>();
+        final ArrayList< RandomAccessibleInterval< ARGBType > > argbCaptures = new ArrayList<>();
+        final ArrayList< ARGBType > colors = new ArrayList<>();
+
+        final ArrayList< double[] > displayRanges = new ArrayList<>();
+
+        final List< SourceAndConverter <?> > visibleSacs = getVisibleSacs( bdvHandle );
+        if ( visibleSacs.size() == 0 ) return;
+
+        final int t = bdvHandle.getViewerPanel().state().getCurrentTimepoint();
+
+        for ( SourceAndConverter sac : visibleSacs )
+        {
+            if ( ! isSourceIntersectingCurrentView( bdvHandle, sac.getSpimSource(), sourceInteractionWithViewerPlaneOnly2D ) ) continue;
+
+            final RandomAccessibleInterval< UnsignedShortType > rawCapture
+                    = ArrayImgs.unsignedShorts( captureWidth, captureHeight );
+            final RandomAccessibleInterval< ARGBType > argbCapture
+                    = ArrayImgs.argbs( captureWidth, captureHeight );
+
+            Source< ? > source = sac.getSpimSource();
+            final Converter converter = sac.getConverter();
+
+            final int level = getLevel( source, physicalPixelSpacingInXY );
+            final AffineTransform3D sourceTransform =
+                    BdvHandleHelper.getSourceTransform( source, t, level );
+
+            AffineTransform3D viewerToSourceTransform = new AffineTransform3D();
+            viewerToSourceTransform.preConcatenate( viewerTransform.inverse() );
+            viewerToSourceTransform.preConcatenate( sourceTransform.inverse() );
+
+            // TODO: Once we have a logic for segmentation images, make this choice depend on this
+            boolean interpolate = true;
+
+            Grids.collectAllContainedIntervals(
+                    Intervals.dimensionsAsLongArray( argbCapture ),
+                    new int[]{100, 100}).parallelStream().forEach( interval ->
+            {
+                RealRandomAccess< ? extends RealType< ? > > realTypeAccess =
+                        getRealTypeRealRandomAccess( t, source, level, interpolate );
+                RealRandomAccess< ? > access =
+                        getRealRandomAccess( t, source, level, interpolate );
+
+                // to collect raw data
+                final IntervalView< UnsignedShortType > rawCrop = Views.interval( rawCapture, interval );
+                final Cursor< UnsignedShortType > rawCaptureCursor = Views.iterable( rawCrop ).localizingCursor();
+                final RandomAccess< UnsignedShortType > rawCaptureAccess = rawCrop.randomAccess();
+
+                // to collect coloured data
+                final IntervalView< ARGBType > argbCrop = Views.interval( argbCapture, interval );
+                final RandomAccess< ARGBType > argbCaptureAccess = argbCrop.randomAccess();
+
+                final double[] canvasPosition = new double[ 3 ];
+                final double[] sourceRealPosition = new double[ 3 ];
+
+                final ARGBType argbType = new ARGBType();
+
+                while ( rawCaptureCursor.hasNext() )
+                {
+                    rawCaptureCursor.fwd();
+                    rawCaptureCursor.localize( canvasPosition );
+                    rawCaptureAccess.setPosition( rawCaptureCursor );
+                    argbCaptureAccess.setPosition( rawCaptureCursor );
+
+                    // canvasPosition is the position on the canvas, in calibrated units
+                    // dxy is the step size that is needed to get the desired resolution in the
+                    // output image
+                    canvasPosition[ 0 ] *= dxy;
+                    canvasPosition[ 1 ] *= dxy;
+
+                    viewerToSourceTransform.apply( canvasPosition, sourceRealPosition );
+
+                    setRawCapturePixelValue( realTypeAccess, rawCaptureAccess, sourceRealPosition );
+                    setArgbCapturePixelValue( converter, access, argbCaptureAccess, sourceRealPosition, argbType );
+                }
+            });
+
+            rawCaptures.add( rawCapture );
+            argbCaptures.add( argbCapture );
+            // colors.add( getSourceColor( bdv, sourceIndex ) ); Not used, show GrayScale
+            displayRanges.add( BdvHandleHelper.getDisplayRange( displayService.getConverterSetup( sac ) ) );
+        }
+
+        final double[] voxelSpacing = new double[ 3 ];
+        for ( int d = 0; d < 2; d++ )
+            voxelSpacing[ d ] = physicalPixelSpacingInXY;
+
+        voxelSpacing[ 2 ] = viewerVoxelSpacing; // TODO: What to put here?
+
+        if ( rawCaptures.size() > 0 )
+        {
+            final BlendingMode[] blendingModes = AccumulateMixedProjectorARGB.getBlendingModes( visibleSacs );
+            screenShot = createImagePlus( physicalUnit, argbCaptures, voxelSpacing, blendingModes, null );
+            rawImageData  = createCompositeImage(
+                    voxelSpacing, physicalUnit, rawCaptures, colors, displayRanges );
+        }
+    }
+
+    private List< SourceAndConverter<?> > getVisibleSacs( BdvHandle bdv )
+    {
+        final SourceAndConverterBdvDisplayService displayService = SourceAndConverterServices.getBdvDisplayService();
+
+        final List< SourceAndConverter<?> > sacs = displayService.getSourceAndConverterOf( bdvHandle );
+        List< SourceAndConverter<?> > visibleSacs = new ArrayList<>(  );
+        for ( SourceAndConverter sac : sacs )
+        {
+            // TODO: this does not evaluate to true for all visible sources
+            if ( displayService.isVisible( sac, bdv ) )
+            {
+                if (sac.getSpimSource().getSource(0,0)!=null) // TODO improve this hack that allows to discard overlays source from screenshot
+                    visibleSacs.add( sac );
+            }
+        }
+
+        return visibleSacs;
+    }
+
+    private void setArgbCapturePixelValue( Converter converter, RealRandomAccess< ? > access, RandomAccess< ARGBType > argbCaptureAccess, double[] sourceRealPosition, ARGBType argbType )
+    {
+        access.setPosition( sourceRealPosition );
+        final Object pixelValue = access.get();
+        if ( pixelValue instanceof ARGBType )
+            argbType.set( ( ARGBType ) pixelValue );
+        else
+            converter.convert( pixelValue, argbType );
+
+        final int sourceARGBIndex = argbType.get();
+        final int captureARGBIndex = argbCaptureAccess.get().get();
+
+        // here the projection happens
+        int a = ARGBType.alpha( sourceARGBIndex ) + ARGBType.alpha( captureARGBIndex );
+        int r = ARGBType.red( sourceARGBIndex ) + ARGBType.red( captureARGBIndex );
+        int g = ARGBType.green( sourceARGBIndex )+ ARGBType.green( captureARGBIndex );
+        int b = ARGBType.blue( sourceARGBIndex )+ ARGBType.blue( captureARGBIndex );
+
+        if ( a > 255 )
+            a = 255;
+        if ( r > 255 )
+            r = 255;
+        if ( g > 255 )
+            g = 255;
+        if ( b > 255 )
+            b = 255;
+
+        argbCaptureAccess.get().set( ARGBType.rgba( r, g, b, a ) );
+    }
+
+    private void setRawCapturePixelValue( RealRandomAccess< ? extends RealType< ? > > realTypeAccess, RandomAccess< UnsignedShortType > realCaptureAccess, double[] sourceRealPosition )
+    {
+        realTypeAccess.setPosition( sourceRealPosition );
+        final RealType< ? > realType = realTypeAccess.get();
+        realCaptureAccess.get().setReal( realType.getRealDouble() );
+    }
+
+    private RealRandomAccess< ? > getRealRandomAccess( int t, Source< ? > source, int level, boolean interpolate )
+    {
+        RealRandomAccess< ? > access;
+        if ( interpolate )
+            access = source.getInterpolatedSource( t, level, Interpolation.NLINEAR ).realRandomAccess();
+        else
+            access = source.getInterpolatedSource( t, level, Interpolation.NEARESTNEIGHBOR ).realRandomAccess();
+        return access;
+    }
+
+    private ImagePlus createImagePlus(
+            String physicalUnit,
+            ArrayList< RandomAccessibleInterval< ARGBType > > argbCaptures,
+            double[] voxelSpacing,
+            BlendingMode[] projectionModes,
+            String projector )
+    {
+        final RandomAccessibleInterval< ARGBType > argbCapture = ArrayImgs.argbs( captureWidth, captureHeight );
+
+        projectUsingMixedProjector( argbCaptures, argbCapture, projectionModes );
+
+//        switch ( projector )
+//        {
+//            case Projection.MIXED_PROJECTOR:
+//                projectUsingMixedProjector( argbCaptures, argbCapture, projectionModes );
+//                break;
+//            case Projection.SUM_PROJECTOR:
+//                projectUsingSumProjector( argbCaptures, argbCapture );
+//                break;
+//            case Projection.AVERAGE_PROJECTOR:
+//                projectUsingAverageProjector( argbCaptures, argbCapture );
+//                break;
+//            default:
+//                break;
+//        }
+
+        return asImagePlus( argbCapture, physicalUnit, voxelSpacing );
+    }
+
+    private void projectUsingMixedProjector( ArrayList< RandomAccessibleInterval< ARGBType > > argbCaptures, RandomAccessibleInterval< ARGBType > argbCapture, BlendingMode[] blendingModes )
+    {
+        final Cursor< ARGBType > argbCursor = Views.iterable( argbCapture ).localizingCursor();
+        final int numVisibleSources = argbCaptures.size();
+        Cursor< ARGBType >[] cursors = getCursors( argbCaptures, numVisibleSources );
+
+        while ( argbCursor.hasNext() )
+        {
+            argbCursor.fwd();
+            for ( int i = 0; i < numVisibleSources; i++ )
+                cursors[ i ].fwd();
+
+            final int argbIndex = AccumulateMixedProjectorARGB.getArgbIndex( cursors, blendingModes );
+            argbCursor.get().set( argbIndex );
+        }
+    }
+
+//    private void projectUsingSumProjector( ArrayList< RandomAccessibleInterval< ARGBType > > argbCaptures, RandomAccessibleInterval< ARGBType > argbCapture )
+//    {
+//        final Cursor< ARGBType > argbCursor = Views.iterable( argbCapture ).localizingCursor();
+//        final int numVisibleSources = argbCaptures.size();
+//
+//        Cursor< ARGBType >[] cursors = getCursors( argbCaptures, numVisibleSources );
+//
+//        while ( argbCursor.hasNext() )
+//        {
+//            argbCursor.fwd();
+//            for ( int i = 0; i < numVisibleSources; i++ )
+//                cursors[ i ].fwd();
+//
+//            final int argbIndex = AccumulateSumProjectorARGB.getArgbIndex( cursors );
+//            argbCursor.get().set( argbIndex );
+//        }
+//    }
+//
+//    private void projectUsingAverageProjector( ArrayList< RandomAccessibleInterval< ARGBType > > argbCaptures, RandomAccessibleInterval< ARGBType > argbCapture )
+//    {
+//        final Cursor< ARGBType > argbCursor = Views.iterable( argbCapture ).localizingCursor();
+//        final int numVisibleSources = argbCaptures.size();
+//
+//        Cursor< ARGBType >[] cursors = getCursors( argbCaptures, numVisibleSources );
+//
+//        while ( argbCursor.hasNext() )
+//        {
+//            argbCursor.fwd();
+//            for ( int i = 0; i < numVisibleSources; i++ )
+//                cursors[ i ].fwd();
+//
+//            final int argbIndex = AccumulateAverageProjectorARGB.getArgbIndex( cursors );
+//            argbCursor.get().set( argbIndex );
+//        }
+//    }
+//
+    private Cursor< ARGBType >[] getCursors( ArrayList< RandomAccessibleInterval< ARGBType > > argbCaptures, int numVisibleSources )
+    {
+        Cursor< ARGBType >[] cursors = new Cursor[ numVisibleSources ];
+        for ( int i = 0; i < numVisibleSources; i++ )
+            cursors[ i ] = Views.iterable( argbCaptures.get( i ) ).cursor();
+        return cursors;
+    }
+
+    private ImagePlus asImagePlus( RandomAccessibleInterval< ARGBType > argbCapture, String physicalUnit, double[] voxelSpacing )
+    {
+        final ImagePlus rgbImage = ImageJFunctions.wrap( argbCapture, "View Capture RGB" );
+
+        IJ.run( rgbImage,
+                "Properties...",
+                "channels=" + 1
+                        +" slices=1 frames=1 unit=" + physicalUnit
+                        +" pixel_width=" + voxelSpacing[ 0 ]
+                        +" pixel_height=" + voxelSpacing[ 1 ]
+                        +" voxel_depth=" + voxelSpacing[ 2 ] );
+        return rgbImage;
+    }
+
+    public static CompositeImage createCompositeImage(
+            double[] voxelSpacing,
+            String voxelUnit,
+            ArrayList< RandomAccessibleInterval< UnsignedShortType > > rais,
+            ArrayList< ARGBType > colors,
+            ArrayList< double[] > displayRanges )
+    {
+        final RandomAccessibleInterval< UnsignedShortType > stack = Views.stack( rais );
+
+        final ImagePlus imp = ImageJFunctions.wrap( stack, "View Capture Raw" );
+
+        // duplicate: otherwise it is virtual and cannot be modified
+        final ImagePlus dup = new Duplicator().run( imp );
+
+        IJ.run( dup,
+                "Properties...",
+                "channels="+rais.size()
+                        +" slices=1 frames=1 unit=" + voxelUnit
+                        +" pixel_width=" + voxelSpacing[ 0 ]
+                        +" pixel_height=" + voxelSpacing[ 1 ]
+                        +" voxel_depth=" + voxelSpacing[ 2 ] );
+
+        final CompositeImage compositeImage = new CompositeImage( dup );
+
+        for ( int channel = 1; channel <= compositeImage.getNChannels(); ++channel )
+        {
+            // TODO: Maybe put different LUTs there?
+            final LUT lut = compositeImage.createLutFromColor( Color.WHITE );
+            compositeImage.setC( channel );
+            compositeImage.setChannelLut( lut );
+            final double[] range = displayRanges.get( channel - 1 );
+            compositeImage.setDisplayRange( range[ 0 ], range[ 1 ] );
+        }
+
+        compositeImage.setTitle( "View Capture Raw" );
+        return compositeImage;
+    }
+
+    public static RealRandomAccess< ? extends RealType< ? > >
+    getRealTypeRealRandomAccess( int t, Source< ? > source, int level, boolean interpolate )
+    {
+        if ( interpolate )
+            return (RealRandomAccess<? extends RealType<?>>) source.getInterpolatedSource(t, level, Interpolation.NLINEAR).realRandomAccess();
+        else
+            return (RealRandomAccess<? extends RealType<?>>) source.getInterpolatedSource(t, level, Interpolation.NEARESTNEIGHBOR).realRandomAccess();
+    }
+}
diff --git a/src/main/java/de/embl/cba/mobie/bdv/SegmentationImageSliceView.java b/src/main/java/de/embl/cba/mobie/bdv/SegmentationImageSliceView.java
index f7deb2874..c055efc22 100644
--- a/src/main/java/de/embl/cba/mobie/bdv/SegmentationImageSliceView.java
+++ b/src/main/java/de/embl/cba/mobie/bdv/SegmentationImageSliceView.java
@@ -40,7 +40,7 @@ public SegmentationImageSliceView( MoBIE moBIE, SegmentationSourceDisplay segmen
 		this.bdvHandle = bdvHandle;
 		this.sourceAndConverterSupplier = sourceAndConverterSupplier;
 
-		displayService = SourceAndConverterServices.getSourceAndConverterDisplayService();
+		displayService = SourceAndConverterServices.getBdvDisplayService();
 		show();
 	}
 
@@ -164,7 +164,7 @@ public void removeSourceDisplay( SourceDisplay sourceDisplay )
 	{
 		for ( SourceAndConverter< ? > sourceAndConverter : sourceDisplay.sourceAndConverters )
 		{
-			SourceAndConverterServices.getSourceAndConverterDisplayService().removeFromAllBdvs( sourceAndConverter );
+			SourceAndConverterServices.getBdvDisplayService().removeFromAllBdvs( sourceAndConverter );
 		}
 	}
 }
diff --git a/src/main/java/de/embl/cba/mobie/bdv/SliceViewer.java b/src/main/java/de/embl/cba/mobie/bdv/SliceViewer.java
index 9579ae4a0..104ffe13d 100644
--- a/src/main/java/de/embl/cba/mobie/bdv/SliceViewer.java
+++ b/src/main/java/de/embl/cba/mobie/bdv/SliceViewer.java
@@ -1,19 +1,20 @@
 package de.embl.cba.mobie.bdv;
 
 import bdv.util.BdvHandle;
+import bdv.util.projector.mixed.SourceAndConverterBlendingModeChangerCommand;
 import bdv.viewer.SourceAndConverter;
 import de.embl.cba.mobie.color.NonSelectedSegmentsOpacityAdjusterCommand;
+import de.embl.cba.mobie.color.SelectedSegmentsColorConfiguratorCommand;
 import de.embl.cba.mobie.segment.BdvSegmentSelector;
 import de.embl.cba.mobie.color.RandomColorSeedChangerCommand;
 import de.embl.cba.mobie.view.ViewerManager;
 import org.scijava.ui.behaviour.ClickBehaviour;
 import org.scijava.ui.behaviour.io.InputTriggerConfig;
 import org.scijava.ui.behaviour.util.Behaviours;
-import sc.fiji.bdvpg.bdv.MinimalBdvCreator;
-import sc.fiji.bdvpg.bdv.projector.Projector;
+import sc.fiji.bdvpg.bdv.supplier.IBdvSupplier;
+import sc.fiji.bdvpg.bdv.supplier.mobie.MobieBdvSupplier;
+import sc.fiji.bdvpg.bdv.supplier.mobie.MobieSerializableBdvOptions;
 import sc.fiji.bdvpg.behaviour.SourceAndConverterContextMenuClickBehaviour;
-import sc.fiji.bdvpg.scijava.command.bdv.ScreenShotMakerCommand;
-import sc.fiji.bdvpg.scijava.command.source.SourceAndConverterBlendingModeChangerCommand;
 import sc.fiji.bdvpg.scijava.services.SourceAndConverterBdvDisplayService;
 import sc.fiji.bdvpg.scijava.services.SourceAndConverterService;
 import sc.fiji.bdvpg.services.SourceAndConverterServices;
@@ -45,7 +46,7 @@ public SliceViewer( boolean is2D, ViewerManager viewerManager, int timepoints )
 		this.timepoints = timepoints;
 
 		sacService = ( SourceAndConverterService ) SourceAndConverterServices.getSourceAndConverterService();
-		sacDisplayService = SourceAndConverterServices.getSourceAndConverterDisplayService();
+		sacDisplayService = SourceAndConverterServices.getBdvDisplayService();
 
 		bdvHandle = createBdv( timepoints );
 		sacDisplayService.registerBdvHandle( bdvHandle );
@@ -94,11 +95,12 @@ private void installContextMenuAndKeyboardShortCuts( )
 		final Set< String > actionsKeys = sacService.getActionsKeys();
 
 		final String[] actions = {
-				sacService.getCommandName( ScreenShotMakerCommand.class ),
+				//sacService.getCommandName( ScreenShotMakerCommand.class ),
 				sacService.getCommandName( ViewerTransformLogger.class ),
 				sacService.getCommandName( SourceAndConverterBlendingModeChangerCommand.class ),
 				sacService.getCommandName( RandomColorSeedChangerCommand.class ),
 				sacService.getCommandName( NonSelectedSegmentsOpacityAdjusterCommand.class ),
+				sacService.getCommandName( SelectedSegmentsColorConfiguratorCommand.class ),
 				UNDO_SEGMENT_SELECTIONS,
 				LOAD_ADDITIONAL_VIEWS,
 				SAVE_CURRENT_SETTINGS_AS_VIEW
@@ -145,9 +147,15 @@ public SourceAndConverterContextMenuClickBehaviour getContextMenu()
 
 	private BdvHandle createBdv( int numTimepoints )
 	{
-		// create Bdv
-		final MinimalBdvCreator bdvCreator = new MinimalBdvCreator( "MoBIE", is2D, Projector.MIXED_PROJECTOR, true, numTimepoints );
-		final BdvHandle bdvHandle = bdvCreator.get();
+		final MobieSerializableBdvOptions sOptions = new MobieSerializableBdvOptions();
+		sOptions.is2D = is2D;
+		sOptions.numTimePoints = numTimepoints;
+
+		IBdvSupplier bdvSupplier = new MobieBdvSupplier( sOptions );
+
+		SourceAndConverterServices.getBdvDisplayService().setDefaultBdvSupplier(bdvSupplier);
+
+		BdvHandle bdvHandle = SourceAndConverterServices.getBdvDisplayService().getNewBdv();
 
 		return bdvHandle;
 	}
diff --git a/src/main/java/de/embl/cba/mobie/bdv/SourcesAtMousePositionSupplier.java b/src/main/java/de/embl/cba/mobie/bdv/SourcesAtMousePositionSupplier.java
index 12be003c8..2d1bbf200 100644
--- a/src/main/java/de/embl/cba/mobie/bdv/SourcesAtMousePositionSupplier.java
+++ b/src/main/java/de/embl/cba/mobie/bdv/SourcesAtMousePositionSupplier.java
@@ -26,10 +26,10 @@ public SourcesAtMousePositionSupplier( BdvHandle bdvHandle, boolean is2D )
 	{
 		final BdvMousePositionProvider positionProvider = new BdvMousePositionProvider( bdvHandle );
 
-		final List< SourceAndConverter< ? > > sourceAndConverters = SourceAndConverterServices.getSourceAndConverterDisplayService().getSourceAndConverterOf( bdvHandle )
+		final List< SourceAndConverter< ? > > sourceAndConverters = SourceAndConverterServices.getBdvDisplayService().getSourceAndConverterOf( bdvHandle )
 				.stream()
 				.filter( sac -> SourceAndConverterHelper.isPositionWithinSourceInterval( sac, positionProvider.getPosition(), positionProvider.getTimePoint(), is2D ) )
-				.filter( sac -> SourceAndConverterServices.getSourceAndConverterDisplayService().isVisible( sac, bdvHandle ) )
+				.filter( sac -> SourceAndConverterServices.getBdvDisplayService().isVisible( sac, bdvHandle ) )
 				.collect( Collectors.toList() );
 
 		return sourceAndConverters;
diff --git a/src/main/java/de/embl/cba/mobie/color/MoBIEColoringModel.java b/src/main/java/de/embl/cba/mobie/color/MoBIEColoringModel.java
index 2ec1505d2..0a0782bd8 100644
--- a/src/main/java/de/embl/cba/mobie/color/MoBIEColoringModel.java
+++ b/src/main/java/de/embl/cba/mobie/color/MoBIEColoringModel.java
@@ -40,15 +40,12 @@ public class MoBIEColoringModel< T > extends AbstractColoringModel< T >
 	private ColoringModel< T > coloringModel;
 	private SelectionModel< T > selectionModel;
 
-	private SelectionColoringMode selectionColoringMode;
 	private ARGBType selectionColor;
 	private double opacityNotSelected;
 
 	public static final ARGBType YELLOW = new ARGBType( ARGBType.rgba( 255, 255, 0, 255 ) );
 	public static final ARGBType TRANSPARENT = new ARGBType( ARGBType.rgba( 0, 0, 0, 0 ) );
 
-	private List< SelectionColoringMode > selectionColoringModes;
-
 	public enum SelectionColoringMode
 	{
 		SelectionColor,
@@ -70,10 +67,8 @@ public MoBIEColoringModel( String lut )
 
 	private void init()
 	{
-		this.selectionColoringModes = Arrays.asList( SelectionColoringMode.values() );
-		this.selectionColor = YELLOW;
+		this.selectionColor = null;
 		this.opacityNotSelected = 0.15;
-		this.selectionColoringMode = SelectionColoringMode.DimNotSelected;
 	}
 
 	public void setSelectionModel( SelectionModel< T > selectionModel )
@@ -94,27 +89,13 @@ public void convert( T input, ARGBType output )
 
 		final boolean isSelected = selectionModel.isSelected( input );
 
-		switch ( selectionColoringMode )
+		if ( ! isSelected )
 		{
-			case DimNotSelected:
-				if ( ! isSelected )
-					dim( output, opacityNotSelected );
-				break;
-
-			case SelectionColor:
-				if ( isSelected )
-					output.set( selectionColor );
-				break;
-
-			case SelectionColorAndDimNotSelected:
-				if ( isSelected )
-					output.set( selectionColor );
-				else
-					dim( output, opacityNotSelected );
-				break;
-
-			default:
-				break;
+			dim( output, opacityNotSelected );
+		}
+		else
+		{
+			if ( selectionColor != null ) output.set( selectionColor );
 		}
 	}
 
@@ -122,9 +103,9 @@ public void convert( T input, ARGBType output )
 	 * Implements dimming via alpha
 	 *
 	 * @param output
-	 * @param alpha
+	 * @param opacity
 	 */
-	private void dim( ARGBType output, double alpha )
+	private void dim( ARGBType output, double opacity )
 	{
 		final int colorIndex = output.get();
 
@@ -133,33 +114,10 @@ private void dim( ARGBType output, double alpha )
 						ARGBType.red( colorIndex ),
 						ARGBType.green( colorIndex ),
 						ARGBType.blue( colorIndex ),
-						alpha * 255 )
+						opacity * 255 )
 		);
 	}
 
-	public void setSelectionColoringMode( SelectionColoringMode selectionColoringMode )
-	{
-		this.selectionColoringMode = selectionColoringMode;
-		notifyColoringListeners();
-	}
-
-	public void setSelectionColoringMode( SelectionColoringMode selectionColoringMode, double brightnessNotSelected )
-	{
-		this.selectionColoringMode = selectionColoringMode;
-
-		// ensure value between 0 and 1
-		brightnessNotSelected = Math.min( 1.0, brightnessNotSelected );
-		brightnessNotSelected = Math.max( 0.0, brightnessNotSelected );
-		this.opacityNotSelected = brightnessNotSelected;
-
-		notifyColoringListeners();
-	}
-
-	public SelectionColoringMode getSelectionColoringMode()
-	{
-		return selectionColoringMode;
-	}
-
 	public void setSelectionColor( ARGBType selectionColor )
 	{
 		this.selectionColor = selectionColor;
diff --git a/src/main/java/de/embl/cba/mobie/color/SelectedSegmentsColorConfiguratorCommand.java b/src/main/java/de/embl/cba/mobie/color/SelectedSegmentsColorConfiguratorCommand.java
new file mode 100644
index 000000000..73320ac03
--- /dev/null
+++ b/src/main/java/de/embl/cba/mobie/color/SelectedSegmentsColorConfiguratorCommand.java
@@ -0,0 +1,58 @@
+package de.embl.cba.mobie.color;
+
+import bdv.util.BdvHandle;
+import bdv.viewer.SourceAndConverter;
+import net.imglib2.type.numeric.ARGBType;
+import org.scijava.plugin.Parameter;
+import org.scijava.plugin.Plugin;
+import org.scijava.util.ColorRGB;
+import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults;
+import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand;
+
+@Plugin(type = BdvPlaygroundActionCommand.class, menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Display>Configure coloring of selected segments")
+public class SelectedSegmentsColorConfiguratorCommand implements BdvPlaygroundActionCommand
+{
+	public static final String DEFAULT_COLOR = "Default color";
+	public static final String SELECTED_COLOR = "Selected color";
+
+	@Parameter
+	BdvHandle bdvh;
+
+	@Parameter
+	SourceAndConverter[] sourceAndConverters;
+
+	@Parameter ( label = "Coloring mode", choices = { DEFAULT_COLOR, SELECTED_COLOR } )
+	String coloringMode = DEFAULT_COLOR;
+
+	@Parameter ( label = "Selected color")
+	ColorRGB color = new ColorRGB(255,255,255);
+
+	@Override
+	public void run()
+	{
+		if ( coloringMode.equals( DEFAULT_COLOR ) )
+		{
+			setSelectedSegmentsColor( bdvh, sourceAndConverters, null );
+		}
+		else
+		{
+			final ARGBType argbType = new ARGBType( ARGBType.rgba( color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha() ) );
+			setSelectedSegmentsColor( bdvh, sourceAndConverters, argbType );
+		}
+	}
+
+	public static void setSelectedSegmentsColor( BdvHandle bdvh, SourceAndConverter[] sourceAndConverters, ARGBType argbType )
+	{
+		for ( SourceAndConverter sourceAndConverter : sourceAndConverters )
+		{
+			if ( sourceAndConverter.getConverter() instanceof LabelConverter< ? > )
+			{
+				final LabelConverter< ? > converter = ( LabelConverter< ? > ) sourceAndConverter.getConverter();
+
+				converter.getColoringModel().setSelectionColor( argbType );
+			}
+		}
+
+		bdvh.getViewerPanel().requestRepaint();
+	}
+}
diff --git a/src/main/java/de/embl/cba/mobie/color/opacity/AdjustableOpacityColorConverter.java b/src/main/java/de/embl/cba/mobie/color/opacity/AdjustableOpacityColorConverter.java
index 0a657d3ef..69d4ac038 100644
--- a/src/main/java/de/embl/cba/mobie/color/opacity/AdjustableOpacityColorConverter.java
+++ b/src/main/java/de/embl/cba/mobie/color/opacity/AdjustableOpacityColorConverter.java
@@ -47,8 +47,16 @@ public AdjustableOpacityColorConverter( Converter< RealType, ARGBType > converte
 	@Override
 	public void convert( RealType realType, ARGBType output )
 	{
-		converter.convert( realType, output );
-		output.mul( opacity );
+		if ( realType.getRealDouble() == 0 )
+		{
+			// ...for the Accumulate projector
+			output.set( new ARGBType( ARGBType.rgba( 0, 0, 0, 0 ) ) );
+		}
+		else
+		{
+			converter.convert( realType, output );
+			output.mul( opacity );
+		}
 	}
 
 	@Override
diff --git a/src/main/java/de/embl/cba/mobie/color/opacity/VolatileAdjustableOpacityColorConverter.java b/src/main/java/de/embl/cba/mobie/color/opacity/VolatileAdjustableOpacityColorConverter.java
index 1fd12b03d..50dbab86b 100644
--- a/src/main/java/de/embl/cba/mobie/color/opacity/VolatileAdjustableOpacityColorConverter.java
+++ b/src/main/java/de/embl/cba/mobie/color/opacity/VolatileAdjustableOpacityColorConverter.java
@@ -50,8 +50,16 @@ public VolatileAdjustableOpacityColorConverter( Converter< V, ARGBType > convert
 	@Override
 	public void convert( V realTypeVolatile, ARGBType output )
 	{
-		converter.convert( realTypeVolatile, output );
-		output.mul( opacity );
+		if ( realTypeVolatile.isValid() && realTypeVolatile.get().getRealDouble() == 0 )
+		{
+			// ...for the Accumulate projector to know where the source ends
+			output.set( new ARGBType( ARGBType.rgba( 0, 0, 0, 0 ) ) );
+		}
+		else
+		{
+			converter.convert( realTypeVolatile, output );
+			output.mul( opacity );
+		}
 	}
 
 	@Override
diff --git a/src/main/java/de/embl/cba/mobie/command/ScreenShotMakerCommand.java b/src/main/java/de/embl/cba/mobie/command/ScreenShotMakerCommand.java
new file mode 100644
index 000000000..24b627348
--- /dev/null
+++ b/src/main/java/de/embl/cba/mobie/command/ScreenShotMakerCommand.java
@@ -0,0 +1,72 @@
+/*-
+ * #%L
+ * BigDataViewer-Playground
+ * %%
+ * Copyright (C) 2019 - 2021 Nicolas Chiaruttini, EPFL - Robert Haase, MPI CBG - Christian Tischer, EMBL
+ * %%
+ * 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
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+package de.embl.cba.mobie.command;
+
+import bdv.util.BdvHandle;
+import de.embl.cba.mobie.bdv.ScreenShotMaker;
+import org.scijava.plugin.Parameter;
+import org.scijava.plugin.Plugin;
+import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults;
+import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand;
+
+/**
+ * ScreenShotMakerCommand
+ * <p>
+ * <p>
+ * <p>
+ * Author: @haesleinhuepf
+ * 12 2019
+ */
+
+@Plugin(type = BdvPlaygroundActionCommand.class, menuPath = ScijavaBdvDefaults.RootMenu+"BDV>BDV - Screenshot",
+        description = "Creates a screenshot of a BDV view, the resolution can be chosen to upscale or downscale" +
+                " the image compared to the original window. A single RGB image resulting from the projection" +
+                " of all sources is displayed. Raw image data can also be exported in grayscale.")
+public class ScreenShotMakerCommand implements BdvPlaygroundActionCommand {
+
+    @Parameter
+    BdvHandle bdvh;
+
+    @Parameter(label="Target Pixel Size (in XY)")
+    public double targetPixelSizeInXY = 1;
+
+    @Parameter(label="Pixel Size Unit")
+    public String targetPixelUnit = "Pixels";
+
+    @Parameter(label="Show Raw Data")
+    public boolean showRawData = false;
+
+    @Override
+    public void run() {
+        ScreenShotMaker screenShotMaker = new ScreenShotMaker(bdvh);
+        screenShotMaker.setPhysicalPixelSpacingInXY(targetPixelSizeInXY, targetPixelUnit);
+        screenShotMaker.getRgbScreenShot().show();
+        if(showRawData) screenShotMaker.getRawScreenShot().show();
+    }
+}
diff --git a/src/main/java/de/embl/cba/mobie/display/ImageSourceDisplay.java b/src/main/java/de/embl/cba/mobie/display/ImageSourceDisplay.java
index 2d1421e5b..6279af279 100644
--- a/src/main/java/de/embl/cba/mobie/display/ImageSourceDisplay.java
+++ b/src/main/java/de/embl/cba/mobie/display/ImageSourceDisplay.java
@@ -1,17 +1,17 @@
 package de.embl.cba.mobie.display;
 
 import bdv.tools.brightness.ConverterSetup;
+import bdv.util.projector.mixed.BlendingMode;
 import bdv.viewer.SourceAndConverter;
 import de.embl.cba.mobie.bdv.ImageSliceView;
 import de.embl.cba.mobie.color.opacity.AdjustableOpacityColorConverter;
 import net.imglib2.display.ColorConverter;
-import sc.fiji.bdvpg.bdv.projector.BlendingMode;
 import sc.fiji.bdvpg.services.SourceAndConverterServices;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import static sc.fiji.bdvpg.bdv.projector.BlendingMode.BLENDING_MODE;
+import static bdv.util.projector.mixed.BlendingMode.BLENDING_MODE;
 
 public class ImageSourceDisplay extends SourceDisplay
 {
@@ -39,8 +39,7 @@ public BlendingMode getBlendingMode()
 		return blendingMode;
 	}
 
-	public ImageSourceDisplay( String name, double opacity, List< String > sources, String color,
-							   double[] contrastLimits, BlendingMode blendingMode, boolean showImagesIn3d ) {
+	public ImageSourceDisplay( String name, double opacity, List< String > sources, String color, double[] contrastLimits, BlendingMode blendingMode, boolean showImagesIn3d ) {
 		this.name = name;
 		this.opacity = opacity;
 		this.sources = sources;
@@ -65,7 +64,7 @@ public ImageSourceDisplay( ImageSourceDisplay imageDisplay )
 		}
 
 		final SourceAndConverter< ? > sourceAndConverter = imageDisplay.sourceAndConverters.get( 0 );
-		final ConverterSetup converterSetup = SourceAndConverterServices.getSourceAndConverterDisplayService().getConverterSetup( sourceAndConverter );
+		final ConverterSetup converterSetup = SourceAndConverterServices.getBdvDisplayService().getConverterSetup( sourceAndConverter );
 
 		if( sourceAndConverter.getConverter() instanceof AdjustableOpacityColorConverter )
 		{
diff --git a/src/main/java/de/embl/cba/mobie/grid/GridOverlaySourceDisplay.java b/src/main/java/de/embl/cba/mobie/grid/GridOverlaySourceDisplay.java
index 560c73d85..fd17016c5 100644
--- a/src/main/java/de/embl/cba/mobie/grid/GridOverlaySourceDisplay.java
+++ b/src/main/java/de/embl/cba/mobie/grid/GridOverlaySourceDisplay.java
@@ -89,7 +89,7 @@ private void showGridImage( BdvHandle bdvHandle, String name, List< DefaultAnnot
 	{
 		final TableRowsIntervalImage< DefaultAnnotatedIntervalTableRow > intervalImage = new TableRowsIntervalImage<>( tableRows, coloringModel, name );
 		SourceAndConverter< IntType > sourceAndConverter = intervalImage.getSourceAndConverter();
-		SourceAndConverterServices.getSourceAndConverterDisplayService().show( bdvHandle, sourceAndConverter );
+		SourceAndConverterServices.getBdvDisplayService().show( bdvHandle, sourceAndConverter );
 		sourceAndConverters.add( sourceAndConverter );
 	}
 
@@ -139,7 +139,7 @@ public void close()
 	{
 		for ( SourceAndConverter< ? > sourceAndConverter : sourceAndConverters )
 		{
-			SourceAndConverterServices.getSourceAndConverterDisplayService().removeFromAllBdvs( sourceAndConverter );
+			SourceAndConverterServices.getBdvDisplayService().removeFromAllBdvs( sourceAndConverter );
 		}
 
 		tableViewer.close();
diff --git a/src/main/java/de/embl/cba/mobie/playground/PlaygroundHelpers.java b/src/main/java/de/embl/cba/mobie/playground/PlaygroundHelpers.java
index c125bb98c..284344633 100644
--- a/src/main/java/de/embl/cba/mobie/playground/PlaygroundHelpers.java
+++ b/src/main/java/de/embl/cba/mobie/playground/PlaygroundHelpers.java
@@ -10,6 +10,6 @@ public class PlaygroundHelpers
 {
 	public static BdvHandle getBdvHandle( List< SourceAndConverter< ? > > sourceAndConverters )
 	{
-		return SourceAndConverterServices.getSourceAndConverterDisplayService().getDisplaysOf( sourceAndConverters.get( 0 ) ).iterator().next();
+		return SourceAndConverterServices.getBdvDisplayService().getDisplaysOf( sourceAndConverters.get( 0 ) ).iterator().next();
 	}
 }
diff --git a/src/main/java/de/embl/cba/mobie/table/TableViewer.java b/src/main/java/de/embl/cba/mobie/table/TableViewer.java
index 3be9a7404..5add2b627 100644
--- a/src/main/java/de/embl/cba/mobie/table/TableViewer.java
+++ b/src/main/java/de/embl/cba/mobie/table/TableViewer.java
@@ -602,7 +602,6 @@ public void continueAnnotation( String columnName )
 			columnNameToColoringModel.put( columnName, categoricalColoringModel );
 		}
 
-		coloringModel.setSelectionColoringMode( MoBIEColoringModel.SelectionColoringMode.DimNotSelected );
 		coloringModel.setColoringModel( columnNameToColoringModel.get( columnName ) );
 		final RowSorter< ? extends TableModel > rowSorter = jTable.getRowSorter();
 
diff --git a/src/main/java/de/embl/cba/mobie/transform/CroppedSource.java b/src/main/java/de/embl/cba/mobie/transform/CroppedSource.java
index ad1eca6bd..1471e4773 100644
--- a/src/main/java/de/embl/cba/mobie/transform/CroppedSource.java
+++ b/src/main/java/de/embl/cba/mobie/transform/CroppedSource.java
@@ -33,6 +33,7 @@
 import bdv.viewer.Source;
 import mpicbg.spim.data.sequence.VoxelDimensions;
 import net.imglib2.FinalInterval;
+import net.imglib2.FinalRealInterval;
 import net.imglib2.Interval;
 import net.imglib2.RandomAccessibleInterval;
 import net.imglib2.RealInterval;
@@ -78,16 +79,27 @@ private void initCropIntervals( Source< T > source, RealInterval crop )
             source.getSourceTransform( 0, level, transform3D );
             final AffineTransform3D inverse = transform3D.inverse();
             final Interval voxelInterval = Intervals.smallestContainingInterval( inverse.estimateBounds( crop ) );
+            final FinalInterval containedVoxelInterval = intersectWithSourceInterval( source, crop, level, voxelInterval );
+            final FinalRealInterval realInterval = transform3D.estimateBounds( voxelInterval );
 
-            // If the interval is outside the bounds of the RAI then there is nothing to show.
-            // Moreover the fetcher threads throw errors when trying to access pixels outside the RAI.
-            // Thus let's limit the interval to where there actually is data.
-            final RandomAccessibleInterval< T > rai = source.getSource( 0, level );
-            final FinalInterval intersect = Intervals.intersect( rai, voxelInterval );
-            levelToVoxelInterval.put( level, intersect );
+            levelToVoxelInterval.put( level, containedVoxelInterval );
         }
     }
 
+    private FinalInterval intersectWithSourceInterval( Source< T > source, RealInterval crop, int level, Interval voxelInterval )
+    {
+        // If the interval is outside the bounds of the RAI then there is nothing to show.
+        // Moreover the fetcher threads throw errors when trying to access pixels outside the RAI.
+        // Thus let's limit the interval to where there actually is data.
+        final RandomAccessibleInterval< T > rai = source.getSource( 0, level );
+        final FinalInterval intersect = Intervals.intersect( rai, voxelInterval );
+        if ( Intervals.numElements( intersect ) <= 0 )
+        {
+            throw new RuntimeException( "The crop interval " + crop + " is not within the image source " + source.getName() );
+        }
+        return intersect;
+    }
+
     public Source< ? > getWrappedSource() {
         return source;
     }
diff --git a/src/main/java/de/embl/cba/mobie/transform/TransformerHelper.java b/src/main/java/de/embl/cba/mobie/transform/TransformerHelper.java
index 8fafd0c2b..962c380d8 100644
--- a/src/main/java/de/embl/cba/mobie/transform/TransformerHelper.java
+++ b/src/main/java/de/embl/cba/mobie/transform/TransformerHelper.java
@@ -13,6 +13,7 @@ public class TransformerHelper
 			for ( SourceTransformer sourceTransformer : sourceTransformers )
 			{
 				sourceAndConverters = sourceTransformer.transform( sourceAndConverters );
+				//break;
 			}
 		}
 
diff --git a/src/main/java/de/embl/cba/mobie/ui/UserInterfaceHelper.java b/src/main/java/de/embl/cba/mobie/ui/UserInterfaceHelper.java
index 95abcd3e0..dad31f529 100644
--- a/src/main/java/de/embl/cba/mobie/ui/UserInterfaceHelper.java
+++ b/src/main/java/de/embl/cba/mobie/ui/UserInterfaceHelper.java
@@ -303,7 +303,7 @@ public JPanel createImageDisplaySettingsPanel( ImageSourceDisplay display )
 		// make the panel color listen to color changes of the sources
 		for ( SourceAndConverter< ? > sourceAndConverter : display.sourceAndConverters )
 		{
-			SourceAndConverterServices.getSourceAndConverterDisplayService().getConverterSetup( sourceAndConverter ).setupChangeListeners().add( setup -> {
+			SourceAndConverterServices.getBdvDisplayService().getConverterSetup( sourceAndConverter ).setupChangeListeners().add( setup -> {
 				// color changed listener
 				setPanelColor( panel, setup.getColor() );
 			} );
@@ -610,7 +610,7 @@ public void actionPerformed( ActionEvent e )
 			{
 				for ( SourceAndConverter< ? > sourceAndConverter : sourceAndConverters )
 				{
-					SourceAndConverterServices.getSourceAndConverterDisplayService().setVisible( sourceAndConverter, checkBox.isSelected() );
+					SourceAndConverterServices.getBdvDisplayService().setVisible( sourceAndConverter, checkBox.isSelected() );
 				}
 			}
 		} );
@@ -736,7 +736,7 @@ public static JButton createImageDisplayBrightnessButton( ImageSourceDisplay ima
 			final ArrayList< ConverterSetup > converterSetups = new ArrayList<>();
 			for ( SourceAndConverter< ? > sourceAndConverter : imageDisplay.sourceAndConverters )
 			{
-				converterSetups.add( SourceAndConverterServices.getSourceAndConverterDisplayService().getConverterSetup( sourceAndConverter ) );
+				converterSetups.add( SourceAndConverterServices.getBdvDisplayService().getConverterSetup( sourceAndConverter ) );
 			}
 
 			UserInterfaceHelper.showBrightnessDialog(


Test patch:
diff --git a/src/test/java/projects/OpenRemoteYeastCLEM.java b/src/test/java/projects/OpenRemoteYeastCLEM.java
index ba23e889e..e113e6ff3 100644
--- a/src/test/java/projects/OpenRemoteYeastCLEM.java
+++ b/src/test/java/projects/OpenRemoteYeastCLEM.java
@@ -14,6 +14,6 @@ public static void main( String[] args ) throws IOException
 		final ImageJ imageJ = new ImageJ();
 		imageJ.ui().showUI();
 
-		new MoBIE("https://github.com/mobie/yeast-clem-datasets", MoBIESettings.settings().gitProjectBranch( "spec-v2" ).imageDataFormat( ImageDataFormat.BdvN5S3 ) );
+		new MoBIE("https://github.com/mobie/yeast-clem-datasets", MoBIESettings.settings().gitProjectBranch( "spec-v2-blending-modes" ).imageDataFormat( ImageDataFormat.BdvN5S3 ) );
 	}
 }