From c3e0b5c315ed9d68c9e54cf66cd0ace3870720e9 Mon Sep 17 00:00:00 2001 From: Christian Tischer Date: Tue, 26 Nov 2024 18:02:58 +0100 Subject: [PATCH] Choose appropriate screen shot output datatype, #1191 --- .../context/ScreenShotMakerCommand.java | 16 ++- .../context/ScreenShotStackMakerCommand.java | 28 +++-- .../embl/mobie/lib/bdv/ScreenShotMaker.java | 108 +++++++++++++----- 3 files changed, 106 insertions(+), 46 deletions(-) diff --git a/src/main/java/org/embl/mobie/command/context/ScreenShotMakerCommand.java b/src/main/java/org/embl/mobie/command/context/ScreenShotMakerCommand.java index fbfaccbe..d9aadf40 100644 --- a/src/main/java/org/embl/mobie/command/context/ScreenShotMakerCommand.java +++ b/src/main/java/org/embl/mobie/command/context/ScreenShotMakerCommand.java @@ -39,8 +39,6 @@ import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import sc.fiji.bdvpg.bdv.BdvHandleHelper; -import sc.fiji.bdvpg.bdv.navigate.ViewerTransformAdjuster; -import sc.fiji.bdvpg.bdv.navigate.ViewerTransformChanger; import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; import java.util.ArrayList; @@ -60,8 +58,8 @@ public class ScreenShotMakerCommand extends DynamicCommand implements BdvPlaygro persist = false, callback = "showNumPixels", min = "0.0", - style="format:#.00", - stepSize = "0.01") + style="format:#.000", + stepSize = "0.001") public Double targetSamplingInXY = 1D; @Parameter(label="Pixel unit", persist = false, choices = {"micrometer"} ) @@ -95,10 +93,16 @@ public void initialize() { // init screenshot sampling // - final MutableModuleItem< Double > targetSamplingItem = // + final MutableModuleItem< Double > targetSamplingItem = getInfo().getMutableInput("targetSamplingInXY", Double.class); + targetSamplingItem.setValue( this, getTargetSampling() ); + } + + protected double getTargetSampling() + { double viewerVoxelSpacing = BdvHandleHelper.getViewerVoxelSpacing( bdvHandle ); - targetSamplingItem.setValue( this, 2 * viewerVoxelSpacing ); + double targetSampling = 2 * viewerVoxelSpacing; + return targetSampling; } // callback diff --git a/src/main/java/org/embl/mobie/command/context/ScreenShotStackMakerCommand.java b/src/main/java/org/embl/mobie/command/context/ScreenShotStackMakerCommand.java index 5bb79ebc..02b96daf 100644 --- a/src/main/java/org/embl/mobie/command/context/ScreenShotStackMakerCommand.java +++ b/src/main/java/org/embl/mobie/command/context/ScreenShotStackMakerCommand.java @@ -38,6 +38,7 @@ import org.embl.mobie.MoBIE; import org.embl.mobie.command.CommandConstants; import org.embl.mobie.lib.bdv.ScreenShotMaker; +import org.scijava.module.MutableModuleItem; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import sc.fiji.bdvpg.bdv.BdvHandleHelper; @@ -51,14 +52,13 @@ public class ScreenShotStackMakerCommand extends ScreenShotMakerCommand static { net.imagej.patcher.LegacyInjector.preinit(); } @Parameter(label="Slice distance (in above units)", - persist = true, + persist = false, min = "0.0", - style="format:#.00", - stepSize = "0.01") - public Double physicalSliceDistance = 1D; + style="format:#.000", + stepSize = "0.001") + public Double targetSamplingInZ = 1D; - @Parameter(label="Number of slices ab" + - "ove & below current", + @Parameter(label="Number of slices above & below current", description = "For example, entering 5 here will result in:\n5 above + 1 current + 5 below = 11 slices in total.", persist = false) public Integer numSlices = 5; @@ -84,7 +84,7 @@ public void run() System.out.println( screenToPhysicalScale ); // move viewer to starting point - viewerTransform.translate( 0, 0, - numSlices * physicalSliceDistance * screenToPhysicalScale ); + viewerTransform.translate( 0, 0, - numSlices * targetSamplingInZ * screenToPhysicalScale ); bdvHandle.getViewerPanel().state().setViewerTransform( viewerTransform ); bdvHandle.getViewerPanel().requestRepaint(); @@ -97,7 +97,7 @@ public void run() for ( int sliceIndex = 0; sliceIndex < numSlices; sliceIndex++ ) { // adapt viewer transform - viewerTransform.translate( 0, 0, physicalSliceDistance * screenToPhysicalScale ); + viewerTransform.translate( 0, 0, targetSamplingInZ * screenToPhysicalScale ); bdvHandle.getViewerPanel().state().setViewerTransform( viewerTransform ); bdvHandle.getViewerPanel().requestRepaint(); @@ -131,7 +131,7 @@ public void run() bdvHandle.getViewerPanel().state().setViewerTransform( initialViewerTransform ); bdvHandle.getViewerPanel().requestRepaint(); - calibration.pixelDepth = physicalSliceDistance; + calibration.pixelDepth = targetSamplingInZ; ImagePlus rgbImage = new ImagePlus( "RGB stack", rgbStack ); rgbImage.setDimensions( 1, rgbStack.size(), 1 ); @@ -143,4 +143,14 @@ public void run() compositeImage.setCalibration( calibration ); compositeImage.show(); } + + @Override + public void initialize() + { + super.initialize(); + + final MutableModuleItem< Double > targetSamplingItem = + getInfo().getMutableInput("targetSamplingInZ", Double.class); + targetSamplingItem.setValue( this, getTargetSampling() ); + } } diff --git a/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java b/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java index 8a1592d4..e64870a7 100644 --- a/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java +++ b/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java @@ -43,10 +43,17 @@ import ij.process.LUT; import net.imglib2.*; import net.imglib2.Cursor; +import net.imglib2.ops.parse.token.Real; import net.imglib2.roi.geom.real.WritableBox; import net.imglib2.type.Type; import net.imglib2.type.logic.BitType; +import net.imglib2.type.numeric.NumericType; +import net.imglib2.type.numeric.integer.ByteType; +import net.imglib2.type.numeric.integer.ShortType; +import net.imglib2.type.numeric.integer.UnsignedByteType; +import net.imglib2.type.numeric.integer.UnsignedShortType; import net.imglib2.type.numeric.real.FloatType; +import net.imglib2.util.Util; import org.embl.mobie.lib.util.MoBIEHelper; import org.embl.mobie.lib.util.ThreadHelper; import org.embl.mobie.lib.annotation.Annotation; @@ -65,6 +72,7 @@ import org.embl.mobie.lib.source.SourceHelper; import sc.fiji.bdvpg.bdv.BdvHandleHelper; import sc.fiji.bdvpg.services.SourceAndConverterServices; +import sc.fiji.bdvpg.sourceandconverter.SourceAndConverterHelper; import java.awt.*; import java.util.ArrayList; @@ -72,6 +80,7 @@ import java.util.List; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import static sc.fiji.bdvpg.bdv.BdvHandleHelper.getLevel; import static sc.fiji.bdvpg.bdv.BdvHandleHelper.getViewerVoxelSpacing; @@ -135,7 +144,7 @@ public void run( List< SourceAndConverter< ? > > sacs, double targetVoxelSpacing IJ.log( "Fetching data from " + sacs.size() + " image(s)..." ); - final ArrayList< RandomAccessibleInterval< FloatType > > floatCaptures = new ArrayList<>(); + final ArrayList< RandomAccessibleInterval< ? extends RealType< ? > > > realCaptures = new ArrayList<>(); final ArrayList< RandomAccessibleInterval< BitType > > maskCaptures = new ArrayList<>(); final ArrayList< RandomAccessibleInterval< ARGBType > > argbCaptures = new ArrayList<>(); @@ -152,13 +161,42 @@ public void run( List< SourceAndConverter< ? > > sacs, double targetVoxelSpacing // IJ.log( ThreadHelper.getNumIoThreads() + " threads working on blocks of " + Arrays.toString( blockSize ) ); final long currentTimeMillis = System.currentTimeMillis(); + + List< ? > types = sacs.stream() + .map( sac -> Util.getTypeFromInterval( sac.getSpimSource().getSource( 0, 0 ) ) ) + .collect( Collectors.toList() ); + + boolean allByte = sacs.stream() + .map( sac -> Util.getTypeFromInterval( sac.getSpimSource().getSource( 0, 0 ) ) ) + .allMatch( t -> t instanceof UnsignedByteType ); + + boolean allShort = sacs.stream() + .map( sac -> Util.getTypeFromInterval( sac.getSpimSource().getSource( 0, 0 ) ) ) + .allMatch( t -> t instanceof UnsignedShortType ); + for ( SourceAndConverter< ? > sac : sacs ) { - final RandomAccessibleInterval< FloatType > floatCapture - = ArrayImgs.floats( screenshotDimensions[ 0 ], screenshotDimensions[ 1 ] ); - final RandomAccessibleInterval< BitType > maskCapture + RandomAccessibleInterval< ? extends RealType< ? > > realRAI; + + if ( allByte ) + { + // ImageJ 8-bit + realRAI = ArrayImgs.unsignedBytes( screenshotDimensions[ 0 ], screenshotDimensions[ 1 ] ); + } + else if ( allShort ) + { + // ImageJ 16-bit + realRAI = ArrayImgs.unsignedShorts( screenshotDimensions[ 0 ], screenshotDimensions[ 1 ] ); + } + else + { + // ImageJ 32-bit + realRAI = ArrayImgs.floats( screenshotDimensions[ 0 ], screenshotDimensions[ 1 ] ); + } + + final RandomAccessibleInterval< BitType > maskRAI = ArrayImgs.bits( screenshotDimensions[ 0 ], screenshotDimensions[ 1 ] ); - final RandomAccessibleInterval< ARGBType > argbCapture + final RandomAccessibleInterval< ARGBType > argbRAI = ArrayImgs.argbs( screenshotDimensions[ 0 ], screenshotDimensions[ 1 ] ); Source< ? > source = sac.getSpimSource(); @@ -188,15 +226,15 @@ public void run( List< SourceAndConverter< ? > > sacs, double targetVoxelSpacing //RandomAccessibleInterval< ? > sourceInterval = source.getSource( currentTimepoint, level ); // to collect raw data - final IntervalView< FloatType > floatCrop = Views.interval( floatCapture, interval ); - final Cursor< FloatType > floatCursor = Views.iterable( floatCrop ).localizingCursor(); - final RandomAccess< FloatType > floatAccess = floatCrop.randomAccess(); + final IntervalView< ? extends RealType< ? > > realCrop = Views.interval( realRAI, interval ); + final Cursor< ? extends RealType< ? > > targetCursor = Views.iterable( realCrop ).localizingCursor(); + final RandomAccess< ? extends RealType< ? > > targetAccess = realCrop.randomAccess(); // to collect masks - final RandomAccess< BitType > maskAccess = Views.interval( maskCapture, interval ).randomAccess(); + final RandomAccess< BitType > maskAccess = Views.interval( maskRAI, interval ).randomAccess(); // to collect colored data - final RandomAccess< ARGBType > argbAccess = Views.interval( argbCapture, interval ).randomAccess(); + final RandomAccess< ARGBType > argbAccess = Views.interval( argbRAI, interval ).randomAccess(); final double[] canvasPosition = new double[ 3 ]; final double[] sourceRealPosition = new double[ 3 ]; @@ -204,14 +242,14 @@ public void run( List< SourceAndConverter< ? > > sacs, double targetVoxelSpacing final ARGBType argbType = new ARGBType(); // iterate through the target image in pixel units - while ( floatCursor.hasNext() ) + while ( targetCursor.hasNext() ) { // set the positions - floatCursor.fwd(); - floatCursor.localize( canvasPosition ); - floatAccess.setPosition( floatCursor ); - maskAccess.setPosition( floatCursor ); - argbAccess.setPosition( floatCursor ); + targetCursor.fwd(); + targetCursor.localize( canvasPosition ); + targetAccess.setPosition( targetCursor ); + maskAccess.setPosition( targetCursor ); + argbAccess.setPosition( targetCursor ); targetCanvasToSourceTransform.apply( canvasPosition, sourceRealPosition ); sourceAccess.setPosition( sourceRealPosition ); @@ -220,7 +258,7 @@ public void run( List< SourceAndConverter< ? > > sacs, double targetVoxelSpacing if ( sourceMask.test( new RealPoint( sourceRealPosition ) ) ) { maskAccess.get().set( true ); - setFloatPixelValue( sourceAccess, floatAccess ); + setPixelValue( sourceAccess, targetAccess ); setArgbPixelValue( converter, sourceAccess, argbAccess, argbType ); } else @@ -251,9 +289,9 @@ public void run( List< SourceAndConverter< ? > > sacs, double targetVoxelSpacing ThreadHelper.waitUntilFinished( futures ); - floatCaptures.add( floatCapture ); - maskCaptures.add( maskCapture ); - argbCaptures.add( argbCapture ); + realCaptures.add( realRAI ); + maskCaptures.add( maskRAI ); + argbCaptures.add( argbRAI ); displayRanges.add( displayRange ); } @@ -262,13 +300,17 @@ public void run( List< SourceAndConverter< ? > > sacs, double targetVoxelSpacing final double[] voxelSpacing = new double[ 3 ]; Arrays.fill( voxelSpacing, targetVoxelSpacing ); - if ( ! floatCaptures.isEmpty() ) + if ( ! realCaptures.isEmpty() ) { rgbImagePlus = createRGBImagePlus( voxelUnit, argbCaptures, voxelSpacing, sacs ); + + // TODO: not could return multiple images here, one per sac, + // this would also help with the datatype + // one has to think about the pros and cons of having them in one image... compositeImagePlus = createCompositeImagePlus( voxelSpacing, voxelUnit, - floatCaptures, + realCaptures, maskCaptures, displayRanges ); } @@ -291,12 +333,15 @@ private void setArgbPixelValue( Converter converter, RealRandomAccess< ? > acces argbCaptureAccess.get().set( argbType.get() ); } - private void setFloatPixelValue( RealRandomAccess< ? extends Type< ? > > access, RandomAccess< FloatType > floatCaptureAccess ) + private void setPixelValue( + RealRandomAccess< ? extends Type< ? > > sourceAccess, + RandomAccess< ? extends RealType< ? > > targetAccess ) { - final Type< ? > type = access.get(); + final Type< ? > type = sourceAccess.get(); if ( type instanceof RealType ) { - floatCaptureAccess.get().setReal( ( ( RealType ) type ).getRealDouble() ); + double realDouble = ( ( RealType ) type ).getRealDouble(); + targetAccess.get().setReal( realDouble ); } else if ( type instanceof AnnotationType ) { @@ -304,7 +349,7 @@ else if ( type instanceof AnnotationType ) { final Annotation annotation = ( Annotation ) ( ( AnnotationType< ? > ) type ).getAnnotation(); if ( annotation != null ) - floatCaptureAccess.get().setReal( annotation.label() ); + targetAccess.get().setReal( annotation.label() ); } catch ( Exception e ) { @@ -417,19 +462,20 @@ private ImagePlus asImagePlus( RandomAccessibleInterval< ARGBType > argbCapture, public static CompositeImage createCompositeImagePlus( double[] voxelSpacing, String voxelUnit, - ArrayList< RandomAccessibleInterval< FloatType > > floatCaptures, - ArrayList< RandomAccessibleInterval< BitType > > maskCaptures, + ArrayList< RandomAccessibleInterval< ? extends RealType< ? > > > realRAIs, + ArrayList< RandomAccessibleInterval< BitType > > maskRAIs, ArrayList< double[] > displayRanges ) { - final ImagePlus imp = ImageJFunctions.wrap( Views.stack( floatCaptures ), "Floats" ); - final ImagePlus mask = ImageJFunctions.wrap( Views.stack( maskCaptures ), "Masks" ); + + final ImagePlus imp = ImageJFunctions.wrap( Views.stack( (ArrayList) realRAIs ), "Floats" ); + final ImagePlus mask = ImageJFunctions.wrap( Views.stack( maskRAIs ), "Masks" ); // duplicate: otherwise it is virtual and cannot be modified final ImagePlus dup = new Duplicator().run( imp ); IJ.run( dup, "Properties...", - "channels="+floatCaptures.size() + "channels="+realRAIs.size() +" slices=1 frames=1 unit=" + voxelUnit +" pixel_width=" + voxelSpacing[ 0 ] +" pixel_height=" + voxelSpacing[ 1 ]