From 7756fd0954ecd2a8c4b9919a437058d30e172cbd Mon Sep 17 00:00:00 2001 From: Christian Tischer Date: Tue, 26 Dec 2023 20:57:29 +0100 Subject: [PATCH 01/12] Add SIFT Dialog WIP --- pom.xml | 5 + .../context/BigWarpRegistrationCommand.java | 1 + .../command/context/SIFTPointsExtractor.java | 329 ++++++++++++++++++ .../context/SIFTRegistrationCommand.java | 65 ++++ .../java/org/embl/mobie/lib/MoBIEHelper.java | 25 +- .../embl/mobie/lib/bdv/ScreenShotMaker.java | 24 +- .../embl/mobie/lib/bdv/view/SliceViewer.java | 1 + .../lib/files/FileSourcesDataSetter.java | 7 +- .../lib/image/RegionAnnotationImage.java | 15 +- .../mobie/lib/image/SpotAnnotationImage.java | 3 +- ...ndomAccessibleIntervalTimelapseSource.java | 6 +- .../em_xray_alignment/OpenEmXraySlices.java | 23 ++ 12 files changed, 471 insertions(+), 33 deletions(-) create mode 100644 src/main/java/org/embl/mobie/command/context/SIFTPointsExtractor.java create mode 100644 src/main/java/org/embl/mobie/command/context/SIFTRegistrationCommand.java create mode 100644 src/test/java/projects/em_xray_alignment/OpenEmXraySlices.java diff --git a/pom.xml b/pom.xml index e03029a2d..c62706a45 100644 --- a/pom.xml +++ b/pom.xml @@ -122,6 +122,11 @@ net.imagej imagej-legacy + + mpicbg + mpicbg + + sc.fiji bigdataviewer-playground diff --git a/src/main/java/org/embl/mobie/command/context/BigWarpRegistrationCommand.java b/src/main/java/org/embl/mobie/command/context/BigWarpRegistrationCommand.java index 10157a084..0a370b881 100644 --- a/src/main/java/org/embl/mobie/command/context/BigWarpRegistrationCommand.java +++ b/src/main/java/org/embl/mobie/command/context/BigWarpRegistrationCommand.java @@ -64,6 +64,7 @@ public class BigWarpRegistrationCommand implements BdvPlaygroundActionCommand, T @Parameter BdvHandle bdvHandle; + // FIXME: Replace UI by GenericDialog, filtering out the non-image sources @Parameter(label = "Fixed Source(s)") SourceAndConverter< ? >[] fixedSources; diff --git a/src/main/java/org/embl/mobie/command/context/SIFTPointsExtractor.java b/src/main/java/org/embl/mobie/command/context/SIFTPointsExtractor.java new file mode 100644 index 000000000..c20786c2d --- /dev/null +++ b/src/main/java/org/embl/mobie/command/context/SIFTPointsExtractor.java @@ -0,0 +1,329 @@ +package org.embl.mobie.command.context; + +import bdv.viewer.SourceAndConverter; +import ij.IJ; +import ij.ImagePlus; +import ij.WindowManager; +import ij.gui.GenericDialog; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.ArrayList; +import java.util.List; + +import mpicbg.ij.FeatureTransform; +import mpicbg.ij.SIFT; +import mpicbg.ij.util.Util; +import mpicbg.imagefeatures.Feature; +import mpicbg.imagefeatures.FloatArray2DSIFT; +import mpicbg.models.AbstractModel; +import mpicbg.models.AffineModel2D; +import mpicbg.models.HomographyModel2D; +import mpicbg.models.NotEnoughDataPointsException; +import mpicbg.models.Point; +import mpicbg.models.PointMatch; +import mpicbg.models.RigidModel2D; +import mpicbg.models.SimilarityModel2D; +import mpicbg.models.TranslationModel2D; + +/** + * Extract landmark correspondences in two images as PointRoi. + * + * The plugin uses the Scale Invariant Feature Transform (SIFT) by David Lowe + * \cite{Lowe04} and the Random Sample Consensus (RANSAC) by Fishler and Bolles + * \citet{FischlerB81} with respect to a transformation model to identify + * landmark correspondences. + * + * BibTeX: + *
+ * @article{Lowe04,
+ *   author    = {David G. Lowe},
+ *   title     = {Distinctive Image Features from Scale-Invariant Keypoints},
+ *   journal   = {International Journal of Computer Vision},
+ *   year      = {2004},
+ *   volume    = {60},
+ *   number    = {2},
+ *   pages     = {91--110},
+ * }
+ * @article{FischlerB81,
+ *	 author    = {Martin A. Fischler and Robert C. Bolles},
+ *   title     = {Random sample consensus: a paradigm for model fitting with applications to image analysis and automated cartography},
+ *   journal   = {Communications of the ACM},
+ *   volume    = {24},
+ *   number    = {6},
+ *   year      = {1981},
+ *   pages     = {381--395},
+ *   publisher = {ACM Press},
+ *   address   = {New York, NY, USA},
+ *   issn      = {0001-0782},
+ *   doi       = {http://doi.acm.org/10.1145/358669.358692},
+ * }
+ * 
+ * + * @author Stephan Saalfeld <saalfeld@mpi-cbg.de> + * @version 0.4b + * + * Modified by Christian Tischer + */ +public class SIFTPointsExtractor +{ + final static private DecimalFormat decimalFormat = new DecimalFormat(); + final static private DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(); + + private ImagePlus imp1; + private ImagePlus imp2; + + final private List< Feature > fs1 = new ArrayList< Feature >(); + final private List< Feature > fs2 = new ArrayList< Feature >();; + + static private class Param + { + final public FloatArray2DSIFT.Param sift = new FloatArray2DSIFT.Param(); + + /** + * Closest/next closest neighbour distance ratio + */ + public float rod = 0.92f; + + public boolean useGeometricConsensusFilter = true; + + /** + * Maximal allowed alignment error in px + */ + public float maxEpsilon = 25.0f; + + /** + * Inlier/candidates ratio + */ + public float minInlierRatio = 0.05f; + + /** + * Minimal absolute number of inliers + */ + public int minNumInliers = 7; + + /** + * Implemeted transformation models for choice + */ + final static public String[] modelStrings = new String[]{ "Translation", "Rigid", "Similarity", "Affine", "Perspective" }; + public int modelIndex = 1; + } + + final static private Param p = new Param(); + + public SIFTPointsExtractor() + { + decimalFormatSymbols.setGroupingSeparator( ',' ); + decimalFormatSymbols.setDecimalSeparator( '.' ); + decimalFormat.setDecimalFormatSymbols( decimalFormatSymbols ); + decimalFormat.setMaximumFractionDigits( 3 ); + decimalFormat.setMinimumFractionDigits( 3 ); + } + + public void run( final List< SourceAndConverter< ? > > sourceAndConverters ) + { + // cleanup + fs1.clear(); + fs2.clear(); + + if ( sourceAndConverters == null || sourceAndConverters.size() < 2 ) + { + IJ.showMessage( "You should have at least two images shown." ); + return; + } + + final String[] titles = new String[ sourceAndConverters.size() ]; + for ( int i = 0; i < sourceAndConverters.size(); ++i ) + { + titles[ i ] = sourceAndConverters.get( i ).getSpimSource().getName(); + } + + final GenericDialog gd = new GenericDialog( "Extract SIFT Landmark Correspondences" ); + + gd.addMessage( "Image Selection:" ); + final String current = titles[ 0 ]; + gd.addChoice( "source_image", titles, current ); + gd.addChoice( "target_image", titles, current.equals( titles[ 0 ] ) ? titles[ 1 ] : titles[ 0 ] ); + + gd.addMessage( "Scale Invariant Interest Point Detector:" ); + gd.addNumericField( "initial_gaussian_blur :", p.sift.initialSigma, 2, 6, "px" ); + gd.addNumericField( "steps_per_scale_octave :", p.sift.steps, 0 ); + gd.addNumericField( "minimum_image_size :", p.sift.minOctaveSize, 0, 6, "px" ); + gd.addNumericField( "maximum_image_size :", p.sift.maxOctaveSize, 0, 6, "px" ); + + gd.addMessage( "Feature Descriptor:" ); + gd.addNumericField( "feature_descriptor_size :", p.sift.fdSize, 0 ); + gd.addNumericField( "feature_descriptor_orientation_bins :", p.sift.fdBins, 0 ); + gd.addNumericField( "closest/next_closest_ratio :", p.rod, 2 ); + + gd.addMessage( "Geometric Consensus Filter:" ); + gd.addCheckbox( "filter matches by geometric consensus", p.useGeometricConsensusFilter ); + gd.addNumericField( "maximal_alignment_error :", p.maxEpsilon, 2, 6, "px" ); + gd.addNumericField( "minimal_inlier_ratio :", p.minInlierRatio, 2 ); + gd.addNumericField( "minimal_number_of_inliers :", p.minNumInliers, 0 ); + gd.addChoice( "expected_transformation :", Param.modelStrings, Param.modelStrings[ p.modelIndex ] ); + + gd.showDialog(); + + if (gd.wasCanceled()) return; + + // FIXME Call ScreenshotMaker to create the Imps + imp1 = null; //WindowManager.getImage( ids[ gd.getNextChoiceIndex() ] ); + imp2 = null; //WindowManager.getImage( ids[ gd.getNextChoiceIndex() ] ); + + p.sift.initialSigma = ( float )gd.getNextNumber(); + p.sift.steps = ( int )gd.getNextNumber(); + p.sift.minOctaveSize = ( int )gd.getNextNumber(); + p.sift.maxOctaveSize = ( int )gd.getNextNumber(); + + p.sift.fdSize = ( int )gd.getNextNumber(); + p.sift.fdBins = ( int )gd.getNextNumber(); + p.rod = ( float )gd.getNextNumber(); + + p.useGeometricConsensusFilter = gd.getNextBoolean(); + p.maxEpsilon = ( float )gd.getNextNumber(); + p.minInlierRatio = ( float )gd.getNextNumber(); + p.minNumInliers = ( int )gd.getNextNumber(); + p.modelIndex = gd.getNextChoiceIndex(); + + exec(imp1, imp2); + } + + /** If unsure, just use default parameters by using exec(ImagePlus, ImagePlus, int) method, where only the model is specified. */ + public void exec(final ImagePlus imp1, final ImagePlus imp2, + final float initialSigma, final int steps, + final int minOctaveSize, final int maxOctaveSize, + final int fdSize, final int fdBins, + final float rod, final float maxEpsilon, + final float minInlierRatio, final int modelIndex) { + + p.sift.initialSigma = initialSigma; + p.sift.steps = steps; + p.sift.minOctaveSize = minOctaveSize; + p.sift.maxOctaveSize = maxOctaveSize; + + p.sift.fdSize = fdSize; + p.sift.fdBins = fdBins; + p.rod = rod; + + p.useGeometricConsensusFilter = true; + p.maxEpsilon = maxEpsilon; + p.minInlierRatio = minInlierRatio; + p.minNumInliers = 7; + p.modelIndex = modelIndex; + + exec( imp1, imp2 ); + } + + /** Execute with default parameters, except the model. + * @param modelIndex: 0=Translation, 1=Rigid, 2=Similarity, 3=Affine */ + public void exec(final ImagePlus imp1, final ImagePlus imp2, final int modelIndex) { + if ( modelIndex < 0 || modelIndex > 3 ) { + IJ.log("Invalid model index: " + modelIndex); + return; + } + p.modelIndex = modelIndex; + exec( imp1, imp2 ); + } + + /** Execute with default parameters (model is Rigid) */ + public void exec(final ImagePlus imp1, final ImagePlus imp2) { + + final FloatArray2DSIFT sift = new FloatArray2DSIFT( p.sift ); + final SIFT ijSIFT = new SIFT( sift ); + + long start_time = System.currentTimeMillis(); + IJ.log( "Processing SIFT ..." ); + ijSIFT.extractFeatures( imp1.getProcessor(), fs1 ); + IJ.log( " took " + ( System.currentTimeMillis() - start_time ) + "ms." ); + IJ.log( fs1.size() + " features extracted." ); + + start_time = System.currentTimeMillis(); + IJ.log( "Processing SIFT ..." ); + ijSIFT.extractFeatures( imp2.getProcessor(), fs2 ); + IJ.log( " took " + ( System.currentTimeMillis() - start_time ) + "ms." ); + IJ.log( fs2.size() + " features extracted." ); + + start_time = System.currentTimeMillis(); + IJ.log( "Identifying correspondence candidates using brute force ..." ); + final List< PointMatch > candidates = new ArrayList< PointMatch >(); + FeatureTransform.matchFeatures( fs1, fs2, candidates, p.rod ); + IJ.log( " took " + ( System.currentTimeMillis() - start_time ) + "ms." ); + + final ArrayList< Point > p1 = new ArrayList< Point >(); + final ArrayList< Point > p2 = new ArrayList< Point >(); + final List< PointMatch > inliers; + + if ( p.useGeometricConsensusFilter ) + { + IJ.log( candidates.size() + " potentially corresponding features identified." ); + + start_time = System.currentTimeMillis(); + IJ.log( "Filtering correspondence candidates by geometric consensus ..." ); + inliers = new ArrayList< PointMatch >(); + + AbstractModel< ? > model; + switch ( p.modelIndex ) + { + case 0: + model = new TranslationModel2D(); + break; + case 1: + model = new RigidModel2D(); + break; + case 2: + model = new SimilarityModel2D(); + break; + case 3: + model = new AffineModel2D(); + break; + case 4: + model = new HomographyModel2D(); + break; + default: + return; + } + + boolean modelFound; + try + { + modelFound = model.filterRansac( + candidates, + inliers, + 1000, + p.maxEpsilon, + p.minInlierRatio, + p.minNumInliers ); + } + catch ( final NotEnoughDataPointsException e ) + { + modelFound = false; + } + + IJ.log( " took " + ( System.currentTimeMillis() - start_time ) + "ms." ); + + if ( modelFound ) + { + PointMatch.apply( inliers, model ); + + IJ.log( inliers.size() + " corresponding features with an average displacement of " + decimalFormat.format( PointMatch.meanDistance( inliers ) ) + "px identified." ); + IJ.log( "Estimated transformation model: " + model ); + } + else + IJ.log( "No correspondences found." ); + } + else + { + inliers = candidates; + IJ.log( candidates.size() + " corresponding features identified." ); + } + + if ( inliers.size() > 0 ) + { + PointMatch.sourcePoints( inliers, p1 ); + PointMatch.targetPoints( inliers, p2 ); + imp1.setRoi( Util.pointsToPointRoi( p1 ) ); + imp2.setRoi( Util.pointsToPointRoi( p2 ) ); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/embl/mobie/command/context/SIFTRegistrationCommand.java b/src/main/java/org/embl/mobie/command/context/SIFTRegistrationCommand.java new file mode 100644 index 000000000..d24cc3c8e --- /dev/null +++ b/src/main/java/org/embl/mobie/command/context/SIFTRegistrationCommand.java @@ -0,0 +1,65 @@ +/*- + * #%L + * Fiji viewer for MoBIE projects + * %% + * Copyright (C) 2018 - 2023 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 org.embl.mobie.command.context; + +import bdv.util.BdvHandle; +import bdv.viewer.SourceAndConverter; +import bdv.viewer.TransformListener; +import bigwarp.BigWarp; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.realtransform.InvertibleRealTransform; +import org.embl.mobie.MoBIE; +import org.embl.mobie.command.CommandConstants; +import org.embl.mobie.lib.MoBIEHelper; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; +import sc.fiji.bdvpg.scijava.services.SourceAndConverterBdvDisplayService; +import sc.fiji.bdvpg.services.ISourceAndConverterService; +import sc.fiji.bdvpg.services.SourceAndConverterServices; + +import java.util.List; +import java.util.Map; + +@Plugin(type = BdvPlaygroundActionCommand.class, menuPath = CommandConstants.CONTEXT_MENU_ITEMS_ROOT + "Transform>Registration - SIFT") +public class SIFTRegistrationCommand implements BdvPlaygroundActionCommand +{ + static { net.imagej.patcher.LegacyInjector.preinit(); } + + @Parameter + BdvHandle bdvHandle; + + @Override + public void run() + { + SIFTPointsExtractor pointsExtractor = new SIFTPointsExtractor(); + List< SourceAndConverter< ? > > sourceAndConverters = MoBIEHelper.getVisibleSacs( bdvHandle ); + pointsExtractor.run( sourceAndConverters ); + } +} diff --git a/src/main/java/org/embl/mobie/lib/MoBIEHelper.java b/src/main/java/org/embl/mobie/lib/MoBIEHelper.java index 47135ddf7..0dfdf73cf 100644 --- a/src/main/java/org/embl/mobie/lib/MoBIEHelper.java +++ b/src/main/java/org/embl/mobie/lib/MoBIEHelper.java @@ -29,6 +29,8 @@ package org.embl.mobie.lib; import bdv.SpimSource; +import bdv.util.BdvHandle; +import bdv.viewer.SourceAndConverter; import ij.ImagePlus; import loci.plugins.in.ImagePlusReader; import loci.plugins.in.ImportProcess; @@ -46,6 +48,8 @@ import org.embl.mobie.lib.io.StorageLocation; import org.embl.mobie.lib.serialize.ImageDataSource; import org.embl.mobie.lib.source.SourceToImagePlusConverter; +import sc.fiji.bdvpg.scijava.services.SourceAndConverterBdvDisplayService; +import sc.fiji.bdvpg.services.SourceAndConverterServices; import spimdata.util.Displaysettings; import java.io.File; @@ -304,7 +308,26 @@ else if ( currentTimeMillis - lastLogMillis.get() > 6000 ) } } - public enum FileLocation + public static List< SourceAndConverter< ? > > getVisibleSacs( BdvHandle bdv ) + { + final SourceAndConverterBdvDisplayService displayService = SourceAndConverterServices.getBdvDisplayService(); + + final List< SourceAndConverter< ? > > sacs = displayService.getSourceAndConverterOf( bdv ); + 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; + } + + public enum FileLocation { Project, FileSystem 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 341e22642..13872da9a 100644 --- a/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java +++ b/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java @@ -42,6 +42,7 @@ import net.imglib2.Cursor; import net.imglib2.type.Type; import net.imglib2.type.numeric.real.FloatType; +import org.embl.mobie.lib.MoBIEHelper; import org.embl.mobie.lib.ThreadHelper; import org.embl.mobie.lib.annotation.Annotation; import org.embl.mobie.lib.bdv.blend.AccumulateAlphaBlendingProjectorARGB; @@ -52,13 +53,11 @@ import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.type.numeric.ARGBType; import net.imglib2.type.numeric.RealType; -import net.imglib2.util.Intervals; import net.imglib2.view.IntervalView; import net.imglib2.view.Views; import org.embl.mobie.lib.source.AnnotatedLabelSource; import org.embl.mobie.lib.source.AnnotationType; import sc.fiji.bdvpg.bdv.BdvHandleHelper; -import sc.fiji.bdvpg.scijava.services.SourceAndConverterBdvDisplayService; import sc.fiji.bdvpg.services.ISourceAndConverterService; import sc.fiji.bdvpg.services.SourceAndConverterServices; @@ -151,7 +150,7 @@ private void createScreenShot() final ArrayList< double[] > displayRanges = new ArrayList<>(); - final List< SourceAndConverter > visibleSacs = getVisibleSacs( bdvHandle ); + final List< SourceAndConverter > visibleSacs = MoBIEHelper.getVisibleSacs( bdvHandle ); if ( visibleSacs.size() == 0 ) return; List< SourceAndConverter< ? > > sacs = new ArrayList<>(); @@ -294,25 +293,6 @@ private void createScreenShot() } } - 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 setArgbPixelValue( Converter converter, RealRandomAccess< ? > access, RandomAccess< ARGBType > argbCaptureAccess, ARGBType argbType ) { final Object pixelValue = access.get(); diff --git a/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java b/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java index 1b126e98d..fa65e8fd6 100644 --- a/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java +++ b/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java @@ -154,6 +154,7 @@ private void installContextMenuAndKeyboardShortCuts( ) actions.add( SourceAndConverterService.getCommandName( ViewerTransformLoggerCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( SourceInfoLoggerCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( BigWarpRegistrationCommand.class ) ); + actions.add( SourceAndConverterService.getCommandName( SIFTRegistrationCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( ManualRegistrationCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( FlipCommand.class ) ); actions.add( UNDO_SEGMENT_SELECTIONS ); diff --git a/src/main/java/org/embl/mobie/lib/files/FileSourcesDataSetter.java b/src/main/java/org/embl/mobie/lib/files/FileSourcesDataSetter.java index 5c8625128..37f662dd1 100644 --- a/src/main/java/org/embl/mobie/lib/files/FileSourcesDataSetter.java +++ b/src/main/java/org/embl/mobie/lib/files/FileSourcesDataSetter.java @@ -157,8 +157,6 @@ private void addGridView( Dataset dataset, ArrayList< ImageFileSources > allSour String regionName = regionTable.getString( regionIndex, ColumnNames.REGION_ID ); regionDisplay.sources.put( regionName, new ArrayList<>() ); } - - displays.add( regionDisplay ); } // add the images of this source to the respective region @@ -236,6 +234,11 @@ else if ( sources.getGridType().equals( GridType.Transformed ) ) } } + // Add the region display last, because this currently + // does not have any voxel unit, which would cause BDV not to + // show any voxel unit. + displays.add( regionDisplay ); + // construct and add the view // // FIXME: Maybe the viewerTransform could be something else? diff --git a/src/main/java/org/embl/mobie/lib/image/RegionAnnotationImage.java b/src/main/java/org/embl/mobie/lib/image/RegionAnnotationImage.java index 9f9b1b734..2589103f0 100644 --- a/src/main/java/org/embl/mobie/lib/image/RegionAnnotationImage.java +++ b/src/main/java/org/embl/mobie/lib/image/RegionAnnotationImage.java @@ -29,6 +29,7 @@ package org.embl.mobie.lib.image; import bdv.viewer.Source; +import mpicbg.spim.data.sequence.FinalVoxelDimensions; import net.imglib2.Interval; import net.imglib2.RealLocalizable; import net.imglib2.position.FunctionRealRandomAccessible; @@ -57,14 +58,12 @@ public class RegionAnnotationImage< AR extends AnnotatedRegion > implements AnnotationImage< AR > { private final String name; - private final AnnData< AR > annData; private final Set< Integer > timepoints; private final SelectionModel< ? > selectionModel; private Source< AnnotationType< AR > > source; private SourcePair< AnnotationType< AR > > sourcePair; private RealMaskRealInterval mask; - private boolean debug = false; private List< AR > annotations; @@ -180,10 +179,18 @@ public synchronized SourcePair< AnnotationType< AR > > getSourcePair() // and then make a Map< Timepoint, regions > and modify RealRandomAccessibleIntervalTimelapseSource to consume this map final FunctionRealRandomAccessible< AnnotationType< AR > > regions = new FunctionRealRandomAccessible( 3, new LocationToAnnotatedRegionSupplier(), () -> new AnnotationType<>( annotations.get( 0 ) ) ); - // TODO it would be nice if this Source had the same voxel unit + // TODO This Source should have the same voxel unit // as the other sources, but that would mean touching one of the // annotated images which could be expensive. - source = new RealRandomAccessibleIntervalTimelapseSource<>( regions, interval, new AnnotationType<>( annotations.get( 0 ) ), new AffineTransform3D(), name, true, timepoints ); + source = new RealRandomAccessibleIntervalTimelapseSource<>( + regions, + interval, + new AnnotationType<>( annotations.get( 0 ) ), + new AffineTransform3D(), + name, + true, + timepoints, + new FinalVoxelDimensions( "", 1, 1, 1 ) ); // There is no volatile implementation (yet), because the // {@code Source} should be fast enough, diff --git a/src/main/java/org/embl/mobie/lib/image/SpotAnnotationImage.java b/src/main/java/org/embl/mobie/lib/image/SpotAnnotationImage.java index ef48bd11f..edc85f6fb 100644 --- a/src/main/java/org/embl/mobie/lib/image/SpotAnnotationImage.java +++ b/src/main/java/org/embl/mobie/lib/image/SpotAnnotationImage.java @@ -30,6 +30,7 @@ import bdv.tools.transformation.TransformedSource; import bdv.viewer.Source; +import mpicbg.spim.data.sequence.FinalVoxelDimensions; import net.imglib2.Interval; import net.imglib2.KDTree; import net.imglib2.RealLocalizable; @@ -109,7 +110,7 @@ private void createImage() final AS annotatedSpot = annData.getTable().annotation( 0 ); final FunctionRealRandomAccessible< AnnotationType< AS > > realRandomAccessible = new FunctionRealRandomAccessible( 3, new LocationToAnnotatedSpotSupplier(), () -> new AnnotationType<>( annotatedSpot ) ); //final RealRandomAccessible interpolate = Views.interpolate( new NearestNeighborSearchOnKDTree( kdTree ), new NearestNeighborSearchInterpolatorFactory() ); - source = new RealRandomAccessibleIntervalTimelapseSource( realRandomAccessible, interval, new AnnotationType<>( annotatedSpot ), new AffineTransform3D(), name, true, null ); + source = new RealRandomAccessibleIntervalTimelapseSource( realRandomAccessible, interval, new AnnotationType<>( annotatedSpot ), new AffineTransform3D(), name, true, null, new FinalVoxelDimensions( "", 1, 1, 1 ) ); } @Override diff --git a/src/main/java/org/embl/mobie/lib/source/RealRandomAccessibleIntervalTimelapseSource.java b/src/main/java/org/embl/mobie/lib/source/RealRandomAccessibleIntervalTimelapseSource.java index 087c0a335..aeed402ad 100644 --- a/src/main/java/org/embl/mobie/lib/source/RealRandomAccessibleIntervalTimelapseSource.java +++ b/src/main/java/org/embl/mobie/lib/source/RealRandomAccessibleIntervalTimelapseSource.java @@ -29,7 +29,6 @@ package org.embl.mobie.lib.source; import bdv.util.RealRandomAccessibleSource; -import mpicbg.spim.data.sequence.DefaultVoxelDimensions; import mpicbg.spim.data.sequence.FinalVoxelDimensions; import net.imglib2.Interval; import net.imglib2.RealRandomAccessible; @@ -52,9 +51,10 @@ public RealRandomAccessibleIntervalTimelapseSource( final AffineTransform3D sourceTransform, final String name, final boolean doBoundingBoxIntersectionCheck, - final Set< Integer > timePoints ) + final Set< Integer > timePoints, + FinalVoxelDimensions voxelDimensions ) { - super( accessible, type, name, new FinalVoxelDimensions( "", 1, 1, 1 ), doBoundingBoxIntersectionCheck ); + super( accessible, type, name, voxelDimensions, doBoundingBoxIntersectionCheck ); this.interval = interval; this.sourceTransform = sourceTransform; this.timePoints = timePoints; diff --git a/src/test/java/projects/em_xray_alignment/OpenEmXraySlices.java b/src/test/java/projects/em_xray_alignment/OpenEmXraySlices.java new file mode 100644 index 000000000..0bc430f81 --- /dev/null +++ b/src/test/java/projects/em_xray_alignment/OpenEmXraySlices.java @@ -0,0 +1,23 @@ +package projects.em_xray_alignment; + +import net.imagej.ImageJ; +import org.embl.mobie.MoBIE; +import org.embl.mobie.command.open.OpenMultipleImagesAndLabelsCommand; +import org.embl.mobie.io.OpenerLogging; + +import java.io.File; +import java.io.IOException; + +public class OpenEmXraySlices +{ + public static void main( String[] args ) throws IOException + { + // OpenerLogging.setLogging( true ); + final ImageJ imageJ = new ImageJ(); + imageJ.ui().showUI(); + OpenMultipleImagesAndLabelsCommand command = new OpenMultipleImagesAndLabelsCommand(); + command.image0 = new File("/Users/tischer/Desktop/em-xray/xray-slice-ds-0.tif"); + command.image1 = new File("/Users/tischer/Desktop/em-xray/em-slice-ds-0.tif"); + command.run(); + } +} From 20afbdec02a488a371f51b385504d714fc4bfea8 Mon Sep 17 00:00:00 2001 From: Christian Tischer Date: Tue, 26 Dec 2023 21:45:33 +0100 Subject: [PATCH 02/12] SIFTPointsExtractor WIP --- .../context/BigWarpRegistrationCommand.java | 64 +++++++++++-------- .../command/context/SIFTPointsExtractor.java | 54 ++++++++++++---- .../context/SIFTRegistrationCommand.java | 6 +- .../embl/mobie/lib/bdv/ScreenShotMaker.java | 2 +- 4 files changed, 82 insertions(+), 44 deletions(-) diff --git a/src/main/java/org/embl/mobie/command/context/BigWarpRegistrationCommand.java b/src/main/java/org/embl/mobie/command/context/BigWarpRegistrationCommand.java index 0a370b881..81d4d6490 100644 --- a/src/main/java/org/embl/mobie/command/context/BigWarpRegistrationCommand.java +++ b/src/main/java/org/embl/mobie/command/context/BigWarpRegistrationCommand.java @@ -37,8 +37,10 @@ import bdv.viewer.TransformListener; import bigwarp.BigWarp; import bigwarp.transforms.BigWarpTransform; +import ij.gui.GenericDialog; import ij.gui.NonBlockingGenericDialog; import org.embl.mobie.command.CommandConstants; +import org.embl.mobie.lib.MoBIEHelper; import org.embl.mobie.lib.transform.TransformHelper; import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.realtransform.InvertibleRealTransform; @@ -50,7 +52,6 @@ import sc.fiji.bdvpg.services.SourceAndConverterServices; import sc.fiji.bdvpg.sourceandconverter.register.BigWarpLauncher; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -64,36 +65,53 @@ public class BigWarpRegistrationCommand implements BdvPlaygroundActionCommand, T @Parameter BdvHandle bdvHandle; - // FIXME: Replace UI by GenericDialog, filtering out the non-image sources - @Parameter(label = "Fixed Source(s)") - SourceAndConverter< ? >[] fixedSources; - - @Parameter(label = "Moving Source(s)") - SourceAndConverter< ? >[] movingSources; - private BigWarp bigWarp; private Map< SourceAndConverter< ? >, AffineTransform3D > sacToOriginalFixedTransform; private List< SourceAndConverter< ? > > movingSacs; private List< SourceAndConverter< ? > > fixedSacs; - private ISourceAndConverterService sacService; private SourceAndConverterBdvDisplayService bdvDisplayService; @Override public void run() { - // FIXME put all of this into a new thread? + List< SourceAndConverter< ? > > sourceAndConverters = MoBIEHelper.getVisibleSacs( bdvHandle ); + + final String[] titles = sourceAndConverters.stream() + .map( sac -> sac.getSpimSource().getName() ) + .toArray( String[]::new ); + + final GenericDialog gd = new GenericDialog( "Extract SIFT Landmark Correspondences" ); + + final String current = titles[ 0 ]; + gd.addChoice( "Fixed image", titles, current ); + gd.addChoice( "Moving image", titles, current.equals( titles[ 0 ] ) ? titles[ 1 ] : titles[ 0 ] ); + + gd.showDialog(); + + if ( gd.wasCanceled() ) return; + String fixedImage = gd.getNextChoice(); + String movingImage = gd.getNextChoice(); sacService = SourceAndConverterServices.getSourceAndConverterService(); bdvDisplayService = SourceAndConverterServices.getBdvDisplayService(); - movingSacs = Arrays.stream( movingSources ).collect( Collectors.toList() ); - fixedSacs = Arrays.stream( fixedSources ).collect( Collectors.toList() ); + + movingSacs = sourceAndConverters.stream() + .filter( sac -> sac.getSpimSource().getName().equals( movingImage ) ) + .collect( Collectors.toList() ); + fixedSacs = sourceAndConverters.stream() + .filter( sac -> sac.getSpimSource().getName().equals( fixedImage ) ) + .collect( Collectors.toList() ); storeOriginalTransforms( movingSacs ); - List< ConverterSetup > converterSetups = Arrays.stream( movingSources ).map( src -> sacService.getConverterSetup(src)).collect( Collectors.toList() ); - converterSetups.addAll( Arrays.stream( fixedSources ).map( src -> sacService.getConverterSetup( src) ).collect( Collectors.toList() ) ); + List< ConverterSetup > converterSetups = movingSacs.stream() + .map( sac -> sacService.getConverterSetup(sac)) + .collect( Collectors.toList() ); + converterSetups.addAll( fixedSacs.stream() + .map( sac -> sacService.getConverterSetup(sac) ) + .collect( Collectors.toList() ) ); BigWarpLauncher bigWarpLauncher = new BigWarpLauncher( movingSacs, fixedSacs, "Big Warp", converterSetups); bigWarpLauncher.run(); @@ -108,7 +126,7 @@ public void run() bigWarp.setTransformType( TransformTypeSelectDialog.AFFINE ); bigWarp.addTransformListener( this ); - new Thread( () -> showDialog() ).start(); + new Thread( () -> showDialog( ) ).start(); } private void applyViewerTransform( AffineTransform3D normalisedViewerTransform, BigWarpViewerPanel viewerPanel ) @@ -116,7 +134,7 @@ private void applyViewerTransform( AffineTransform3D normalisedViewerTransform, viewerPanel.state().setViewerTransform( TransformHelper.createUnnormalizedViewerTransform( normalisedViewerTransform, viewerPanel ) ); } - private void showDialog() + private void showDialog( ) { final NonBlockingGenericDialog dialog = new NonBlockingGenericDialog( "Registration - BigWarp" ); dialog.addMessage( "Landmark based affine, similarity, rigid and translation transformations.\nPlease read the BigWarp help.\n" + "Press [ OK ] to close BigWarp and apply the current registration in MoBIE."); @@ -135,10 +153,8 @@ private void showDialog() private void resetMovingTransforms() { - for ( SourceAndConverter movingSac : movingSacs ) - { - ( ( TransformedSource) movingSac.getSpimSource() ).setFixedTransform( sacToOriginalFixedTransform.get( movingSac ) ); - } + movingSacs.forEach( sac -> ( ( TransformedSource< ? >) sac.getSpimSource() ) + .setFixedTransform( sacToOriginalFixedTransform.get( sac ) ) ); bdvHandle.getViewerPanel().requestRepaint(); } @@ -148,7 +164,7 @@ private void storeOriginalTransforms( List< SourceAndConverter< ? > > movingSacs for ( SourceAndConverter< ? > movingSac : movingSacs ) { final AffineTransform3D fixedTransform = new AffineTransform3D(); - ( ( TransformedSource ) movingSac.getSpimSource()).getFixedTransform( fixedTransform ); + ( ( TransformedSource< ? > ) movingSac.getSpimSource()).getFixedTransform( fixedTransform ); sacToOriginalFixedTransform.put( movingSac, fixedTransform ); } } @@ -161,17 +177,13 @@ public void transformChanged( InvertibleRealTransform transform ) { System.err.println( TransformTypeSelectDialog.TPS + "is currently not supported by MoBIE please choose any of the other transform types by selecting one of the BigWarp windows and pressing F2."); } - else - { - //setMovingTransforms(); - } } private void setMovingTransforms() { final BigWarpTransform bwTransform = bigWarp.getBwTransform(); final AffineTransform3D bwAffineTransform = bwTransform.affine3d(); - for ( SourceAndConverter< ? > movingSource : movingSources ) + for ( SourceAndConverter< ? > movingSource : movingSacs ) { final AffineTransform3D combinedTransform = sacToOriginalFixedTransform.get( movingSource ).copy(); combinedTransform.preConcatenate( bwAffineTransform.copy().inverse() ); diff --git a/src/main/java/org/embl/mobie/command/context/SIFTPointsExtractor.java b/src/main/java/org/embl/mobie/command/context/SIFTPointsExtractor.java index c20786c2d..bb61926e4 100644 --- a/src/main/java/org/embl/mobie/command/context/SIFTPointsExtractor.java +++ b/src/main/java/org/embl/mobie/command/context/SIFTPointsExtractor.java @@ -3,13 +3,14 @@ import bdv.viewer.SourceAndConverter; import ij.IJ; import ij.ImagePlus; -import ij.WindowManager; import ij.gui.GenericDialog; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; import mpicbg.ij.FeatureTransform; import mpicbg.ij.SIFT; @@ -25,6 +26,7 @@ import mpicbg.models.RigidModel2D; import mpicbg.models.SimilarityModel2D; import mpicbg.models.TranslationModel2D; +import org.embl.mobie.lib.bdv.ScreenShotMaker; /** * Extract landmark correspondences in two images as PointRoi. @@ -80,6 +82,8 @@ static private class Param { final public FloatArray2DSIFT.Param sift = new FloatArray2DSIFT.Param(); + public Double pixelSize = null; + /** * Closest/next closest neighbour distance ratio */ @@ -111,7 +115,7 @@ static private class Param final static private Param p = new Param(); - public SIFTPointsExtractor() + public SIFTPointsExtractor( ) { decimalFormatSymbols.setGroupingSeparator( ',' ); decimalFormatSymbols.setDecimalSeparator( '.' ); @@ -120,30 +124,38 @@ public SIFTPointsExtractor() decimalFormat.setMinimumFractionDigits( 3 ); } - public void run( final List< SourceAndConverter< ? > > sourceAndConverters ) + public void run( double viewerVoxelSpacing, final List< SourceAndConverter< ? > > sourceAndConverters ) { // cleanup fs1.clear(); fs2.clear(); + if( p.pixelSize == null ) + p.pixelSize = viewerVoxelSpacing; + if ( sourceAndConverters == null || sourceAndConverters.size() < 2 ) { IJ.showMessage( "You should have at least two images shown." ); return; } - final String[] titles = new String[ sourceAndConverters.size() ]; - for ( int i = 0; i < sourceAndConverters.size(); ++i ) - { - titles[ i ] = sourceAndConverters.get( i ).getSpimSource().getName(); - } + final String[] titles = sourceAndConverters.stream() + .map( sac -> sac.getSpimSource().getName() ) + .toArray( String[]::new ); final GenericDialog gd = new GenericDialog( "Extract SIFT Landmark Correspondences" ); - gd.addMessage( "Image Selection:" ); + gd.addMessage( "Images:" ); final String current = titles[ 0 ]; - gd.addChoice( "source_image", titles, current ); - gd.addChoice( "target_image", titles, current.equals( titles[ 0 ] ) ? titles[ 1 ] : titles[ 0 ] ); + gd.addChoice( "fixed_image", titles, current ); + gd.addChoice( "moving_image", titles, current.equals( titles[ 0 ] ) ? titles[ 1 ] : titles[ 0 ] ); + String voxelUnit = sourceAndConverters.get( 0 ).getSpimSource().getVoxelDimensions().unit(); + + gd.addMessage( "Scale:" ); + gd.addNumericField( "Pixel Size :", p.pixelSize, 2, 6, voxelUnit ); + + gd.addMessage( "Transformation:" ); + gd.addChoice( "expected_transformation :", Param.modelStrings, Param.modelStrings[ p.modelIndex ] ); gd.addMessage( "Scale Invariant Interest Point Detector:" ); gd.addNumericField( "initial_gaussian_blur :", p.sift.initialSigma, 2, 6, "px" ); @@ -161,16 +173,31 @@ public void run( final List< SourceAndConverter< ? > > sourceAndConverters ) gd.addNumericField( "maximal_alignment_error :", p.maxEpsilon, 2, 6, "px" ); gd.addNumericField( "minimal_inlier_ratio :", p.minInlierRatio, 2 ); gd.addNumericField( "minimal_number_of_inliers :", p.minNumInliers, 0 ); - gd.addChoice( "expected_transformation :", Param.modelStrings, Param.modelStrings[ p.modelIndex ] ); gd.showDialog(); if (gd.wasCanceled()) return; - // FIXME Call ScreenshotMaker to create the Imps + String fixedImage = gd.getNextString(); + SourceAndConverter< ? > fixedSac = sourceAndConverters.stream() + .filter( sac -> sac.getSpimSource().getName().equals( fixedImage ) ) + .findFirst().get(); + + String movingImage = gd.getNextString(); + SourceAndConverter< ? > movingSac = sourceAndConverters.stream() + .filter( sac -> sac.getSpimSource().getName().equals( movingImage ) ) + .findFirst().get(); + + // TODO Refactor ScreenshotMaker to make it useable here. + new ScreenShotMaker( ) + imp1 = null; //WindowManager.getImage( ids[ gd.getNextChoiceIndex() ] ); imp2 = null; //WindowManager.getImage( ids[ gd.getNextChoiceIndex() ] ); + p.pixelSize = gd.getNextNumber(); + + p.modelIndex = gd.getNextChoiceIndex(); + p.sift.initialSigma = ( float )gd.getNextNumber(); p.sift.steps = ( int )gd.getNextNumber(); p.sift.minOctaveSize = ( int )gd.getNextNumber(); @@ -184,7 +211,6 @@ public void run( final List< SourceAndConverter< ? > > sourceAndConverters ) p.maxEpsilon = ( float )gd.getNextNumber(); p.minInlierRatio = ( float )gd.getNextNumber(); p.minNumInliers = ( int )gd.getNextNumber(); - p.modelIndex = gd.getNextChoiceIndex(); exec(imp1, imp2); } diff --git a/src/main/java/org/embl/mobie/command/context/SIFTRegistrationCommand.java b/src/main/java/org/embl/mobie/command/context/SIFTRegistrationCommand.java index d24cc3c8e..5de94d42f 100644 --- a/src/main/java/org/embl/mobie/command/context/SIFTRegistrationCommand.java +++ b/src/main/java/org/embl/mobie/command/context/SIFTRegistrationCommand.java @@ -39,6 +39,7 @@ import org.embl.mobie.lib.MoBIEHelper; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; +import sc.fiji.bdvpg.bdv.BdvHandleHelper; import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; import sc.fiji.bdvpg.scijava.services.SourceAndConverterBdvDisplayService; import sc.fiji.bdvpg.services.ISourceAndConverterService; @@ -58,8 +59,7 @@ public class SIFTRegistrationCommand implements BdvPlaygroundActionCommand @Override public void run() { - SIFTPointsExtractor pointsExtractor = new SIFTPointsExtractor(); - List< SourceAndConverter< ? > > sourceAndConverters = MoBIEHelper.getVisibleSacs( bdvHandle ); - pointsExtractor.run( sourceAndConverters ); + SIFTPointsExtractor pointsExtractor = new SIFTPointsExtractor( ); + pointsExtractor.run( BdvHandleHelper.getViewerVoxelSpacing( bdvHandle ), MoBIEHelper.getVisibleSacs( bdvHandle ) ); } } 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 13872da9a..b0879156a 100644 --- a/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java +++ b/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java @@ -84,7 +84,7 @@ public class ScreenShotMaker private CompositeImage compositeImagePlus = null; private long[] screenshotDimensions = new long[2]; - public ScreenShotMaker( BdvHandle bdvHandle) { + public ScreenShotMaker( BdvHandle bdvHandle ) { this.bdvHandle = bdvHandle; this.sacService = SourceAndConverterServices.getSourceAndConverterService(); } From 849fecb1d81cff43dc75b51141f5d9e9c4e64423 Mon Sep 17 00:00:00 2001 From: Christian Tischer Date: Wed, 27 Dec 2023 05:18:22 +0100 Subject: [PATCH 03/12] SIFT extraction working --- .../command/context/SIFTPointsExtractor.java | 72 +++++++--- .../context/SIFTRegistrationCommand.java | 4 +- .../context/ScreenShotMakerCommand.java | 9 +- .../context/ShowRasterImagesCommand.java | 2 +- .../embl/mobie/lib/bdv/ScreenShotMaker.java | 131 +++++++++--------- 5 files changed, 121 insertions(+), 97 deletions(-) diff --git a/src/main/java/org/embl/mobie/command/context/SIFTPointsExtractor.java b/src/main/java/org/embl/mobie/command/context/SIFTPointsExtractor.java index bb61926e4..b160ed7c0 100644 --- a/src/main/java/org/embl/mobie/command/context/SIFTPointsExtractor.java +++ b/src/main/java/org/embl/mobie/command/context/SIFTPointsExtractor.java @@ -1,16 +1,18 @@ package org.embl.mobie.command.context; +import bdv.util.BdvHandle; import bdv.viewer.SourceAndConverter; +import ij.CompositeImage; import ij.IJ; import ij.ImagePlus; +import ij.ImageStack; import ij.gui.GenericDialog; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; import mpicbg.ij.FeatureTransform; import mpicbg.ij.SIFT; @@ -26,7 +28,9 @@ import mpicbg.models.RigidModel2D; import mpicbg.models.SimilarityModel2D; import mpicbg.models.TranslationModel2D; +import org.embl.mobie.lib.MoBIEHelper; import org.embl.mobie.lib.bdv.ScreenShotMaker; +import sc.fiji.bdvpg.bdv.BdvHandleHelper; /** * Extract landmark correspondences in two images as PointRoi. @@ -71,6 +75,7 @@ public class SIFTPointsExtractor { final static private DecimalFormat decimalFormat = new DecimalFormat(); final static private DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(); + private final BdvHandle bdvHandle; private ImagePlus imp1; private ImagePlus imp2; @@ -115,8 +120,10 @@ static private class Param final static private Param p = new Param(); - public SIFTPointsExtractor( ) + public SIFTPointsExtractor( BdvHandle bdvHandle ) { + this.bdvHandle = bdvHandle; + decimalFormatSymbols.setGroupingSeparator( ',' ); decimalFormatSymbols.setDecimalSeparator( '.' ); decimalFormat.setDecimalFormatSymbols( decimalFormatSymbols ); @@ -124,18 +131,19 @@ public SIFTPointsExtractor( ) decimalFormat.setMinimumFractionDigits( 3 ); } - public void run( double viewerVoxelSpacing, final List< SourceAndConverter< ? > > sourceAndConverters ) + public void run() { // cleanup fs1.clear(); fs2.clear(); if( p.pixelSize == null ) - p.pixelSize = viewerVoxelSpacing; + p.pixelSize = BdvHandleHelper.getViewerVoxelSpacing( bdvHandle ); - if ( sourceAndConverters == null || sourceAndConverters.size() < 2 ) + List< SourceAndConverter< ? > > sourceAndConverters = MoBIEHelper.getVisibleSacs( bdvHandle ); + if ( sourceAndConverters.size() < 2 ) { - IJ.showMessage( "You should have at least two images shown." ); + IJ.showMessage( "There must be at least two images visible." ); return; } @@ -178,24 +186,10 @@ public void run( double viewerVoxelSpacing, final List< SourceAndConverter< ? > if (gd.wasCanceled()) return; - String fixedImage = gd.getNextString(); - SourceAndConverter< ? > fixedSac = sourceAndConverters.stream() - .filter( sac -> sac.getSpimSource().getName().equals( fixedImage ) ) - .findFirst().get(); - - String movingImage = gd.getNextString(); - SourceAndConverter< ? > movingSac = sourceAndConverters.stream() - .filter( sac -> sac.getSpimSource().getName().equals( movingImage ) ) - .findFirst().get(); - - // TODO Refactor ScreenshotMaker to make it useable here. - new ScreenShotMaker( ) - - imp1 = null; //WindowManager.getImage( ids[ gd.getNextChoiceIndex() ] ); - imp2 = null; //WindowManager.getImage( ids[ gd.getNextChoiceIndex() ] ); + String fixedImageName = gd.getNextChoice(); + String movingImageName = gd.getNextChoice(); p.pixelSize = gd.getNextNumber(); - p.modelIndex = gd.getNextChoiceIndex(); p.sift.initialSigma = ( float )gd.getNextNumber(); @@ -212,9 +206,41 @@ public void run( double viewerVoxelSpacing, final List< SourceAndConverter< ? > p.minInlierRatio = ( float )gd.getNextNumber(); p.minNumInliers = ( int )gd.getNextNumber(); + fetchImages( sourceAndConverters, fixedImageName, movingImageName ); + exec(imp1, imp2); } + private void fetchImages( List< SourceAndConverter< ? > > sourceAndConverters, String fixedImageName, String movingImageName ) + { + SourceAndConverter< ? > fixedSac = sourceAndConverters.stream() + .filter( sac -> sac.getSpimSource().getName().equals( fixedImageName ) ) + .findFirst().get(); + + SourceAndConverter< ? > movingSac = sourceAndConverters.stream() + .filter( sac -> sac.getSpimSource().getName().equals( movingImageName ) ) + .findFirst().get(); + + ScreenShotMaker screenShotMaker = new ScreenShotMaker( bdvHandle, p.pixelSize, fixedSac.getSpimSource().getVoxelDimensions().unit() ); + screenShotMaker.run( Arrays.asList( fixedSac, movingSac ) ); + CompositeImage compositeImage = screenShotMaker.getCompositeImagePlus(); + + ImageStack stack = compositeImage.getStack(); + imp1 = new ImagePlus( "fixed", stack.getProcessor( 1 ) ); + compositeImage.setC( 2 ); + imp2 = new ImagePlus( "moving", stack.getProcessor( 2 ) ); + + // Set the display ranges + // This is important as those will be used by the SIFT for normalising the pixel values + compositeImage.setPosition( 1 ); + imp1.getProcessor().setMinAndMax( compositeImage.getDisplayRangeMin(), compositeImage.getDisplayRangeMax() ); + compositeImage.setPosition( 2 ); + imp2.getProcessor().setMinAndMax( compositeImage.getDisplayRangeMin(), compositeImage.getDisplayRangeMax() ); + + imp1.show(); + imp2.show(); + } + /** If unsure, just use default parameters by using exec(ImagePlus, ImagePlus, int) method, where only the model is specified. */ public void exec(final ImagePlus imp1, final ImagePlus imp2, final float initialSigma, final int steps, diff --git a/src/main/java/org/embl/mobie/command/context/SIFTRegistrationCommand.java b/src/main/java/org/embl/mobie/command/context/SIFTRegistrationCommand.java index 5de94d42f..a7775876c 100644 --- a/src/main/java/org/embl/mobie/command/context/SIFTRegistrationCommand.java +++ b/src/main/java/org/embl/mobie/command/context/SIFTRegistrationCommand.java @@ -59,7 +59,7 @@ public class SIFTRegistrationCommand implements BdvPlaygroundActionCommand @Override public void run() { - SIFTPointsExtractor pointsExtractor = new SIFTPointsExtractor( ); - pointsExtractor.run( BdvHandleHelper.getViewerVoxelSpacing( bdvHandle ), MoBIEHelper.getVisibleSacs( bdvHandle ) ); + SIFTPointsExtractor pointsExtractor = new SIFTPointsExtractor( bdvHandle ); + pointsExtractor.run( ); } } 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 614928d42..63f377c1c 100644 --- a/src/main/java/org/embl/mobie/command/context/ScreenShotMakerCommand.java +++ b/src/main/java/org/embl/mobie/command/context/ScreenShotMakerCommand.java @@ -29,7 +29,6 @@ package org.embl.mobie.command.context; import bdv.util.BdvHandle; -import de.embl.cba.bdv.utils.BdvUtils; import ij.IJ; import org.embl.mobie.MoBIE; import org.embl.mobie.command.CommandConstants; @@ -71,14 +70,14 @@ public class ScreenShotMakerCommand extends DynamicCommand implements BdvPlaygro @Override public void run() { - ScreenShotMaker screenShotMaker = new ScreenShotMaker( bdvh ); - screenShotMaker.setPhysicalPixelSpacingInXY( targetSamplingInXY, pixelUnit ); + ScreenShotMaker screenShotMaker = new ScreenShotMaker( bdvh, targetSamplingInXY, pixelUnit ); + screenShotMaker.run(); if( showRGB ) - screenShotMaker.getRgbScreenShot().show(); + screenShotMaker.getRGBImagePlus().show(); if( showMultiChannel ) - screenShotMaker.getRawScreenShot().show(); + screenShotMaker.getCompositeImagePlus().show(); if ( MoBIE.getInstance().getSettings().values.isOpenedFromCLI() ) MoBIE.imageJ.ui().showUI(); diff --git a/src/main/java/org/embl/mobie/command/context/ShowRasterImagesCommand.java b/src/main/java/org/embl/mobie/command/context/ShowRasterImagesCommand.java index 062ad549f..ca9990498 100644 --- a/src/main/java/org/embl/mobie/command/context/ShowRasterImagesCommand.java +++ b/src/main/java/org/embl/mobie/command/context/ShowRasterImagesCommand.java @@ -72,7 +72,7 @@ public class ShowRasterImagesCommand< T extends NumericType< T > > implements Bd public BdvHandle bdvHandle; @Parameter( label = "Source(s)" ) - public SourceAndConverter[] sourceAndConverterArray; + public SourceAndConverter< ? >[] sourceAndConverterArray; @Override public void run() 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 b0879156a..f916c2142 100644 --- a/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java +++ b/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java @@ -84,61 +84,37 @@ public class ScreenShotMaker private CompositeImage compositeImagePlus = null; private long[] screenshotDimensions = new long[2]; - public ScreenShotMaker( BdvHandle bdvHandle ) { + public ScreenShotMaker( BdvHandle bdvHandle, Double pixelSize, String pixelUnit ) { this.bdvHandle = bdvHandle; this.sacService = SourceAndConverterServices.getSourceAndConverterService(); + this.samplingXY = pixelSize; + this.physicalUnit = pixelUnit; } - public void setPhysicalPixelSpacingInXY( double spacing, String unit ) { - this.rgbImagePlus = null; - this.samplingXY = spacing; - this.physicalUnit = unit; - } - - private void process() { - if ( rgbImagePlus != null) - return; - createScreenShot(); - } - - public ImagePlus getRgbScreenShot() { - process(); + public ImagePlus getRGBImagePlus() + { return rgbImagePlus; } - public CompositeImage getRawScreenShot() + public CompositeImage getCompositeImagePlus() { - process(); return compositeImagePlus; } - public static long[] getCaptureImageSizeInPixels( BdvHandle bdvHandle, double samplingXY ) + public void run() { - final double viewerVoxelSpacing = getViewerVoxelSpacing( bdvHandle ); - - final double[] bdvWindowPhysicalSize = getBdvWindowPhysicalSize( bdvHandle, viewerVoxelSpacing ); - - final long[] capturePixelSize = new long[ 2 ]; - for ( int d = 0; d < 2; d++ ) - { - capturePixelSize[ d ] = ( long ) ( Math.ceil( bdvWindowPhysicalSize[ d ] / samplingXY ) ); - } - - return capturePixelSize; + List< SourceAndConverter< ? > > sacs = getVisibleSourceAndConverters(); + run( sacs ); } - private static double[] getBdvWindowPhysicalSize( BdvHandle bdvHandle, double viewerVoxelSpacing ) + public void run( List< SourceAndConverter< ? > > sacs ) { - final double[] bdvWindowPhysicalSize = new double[ 2 ]; - final int w = bdvHandle.getViewerPanel().getWidth(); - final int h = bdvHandle.getViewerPanel().getHeight(); - bdvWindowPhysicalSize[ 0 ] = w * viewerVoxelSpacing; - bdvWindowPhysicalSize[ 1 ] = h * viewerVoxelSpacing; - return bdvWindowPhysicalSize; - } + if ( sacs.isEmpty() ) + { + IJ.log( "No screen shot taken, as there were no images." ); + return; + } - private void createScreenShot() - { final AffineTransform3D viewerTransform = new AffineTransform3D(); bdvHandle.getViewerPanel().state().getViewerTransform( viewerTransform ); @@ -150,24 +126,9 @@ private void createScreenShot() final ArrayList< double[] > displayRanges = new ArrayList<>(); - final List< SourceAndConverter > visibleSacs = MoBIEHelper.getVisibleSacs( bdvHandle ); - if ( visibleSacs.size() == 0 ) return; - - List< SourceAndConverter< ? > > sacs = new ArrayList<>(); - for ( SourceAndConverter< ? > sac : visibleSacs ) - { - // TODO: can we determine from BDV whether a source is intersecting viewer plane? - // why do we need is2D=false ? - if ( ! isSourceIntersectingCurrentView( bdvHandle, sac.getSpimSource(), false ) ) - continue; - sacs.add( sac ); - } - - if ( sacs.isEmpty() ) return; - final int t = bdvHandle.getViewerPanel().state().getCurrentTimepoint(); - IJ.log( "Fetching data from " + sacs.size() + " sources..." ); + IJ.log( "Fetching data from " + sacs.size() + " images..." ); final long numPixels = screenshotDimensions[ 0 ] * screenshotDimensions[ 1 ]; long pixelsPerThread = numPixels / ThreadHelper.getNumIoThreads(); @@ -205,9 +166,6 @@ private void createScreenShot() final AtomicInteger pixelCount = new AtomicInteger(); final AtomicDouble fractionDone = new AtomicDouble( 0.2 ); - - IJ.log( "Fetching data from " + sac.getSpimSource().getName() ); - ArrayList< Future< ? > > futures = ThreadHelper.getFutures(); for ( Interval interval : intervals ) { @@ -268,7 +226,7 @@ private void createScreenShot() } } ) ); - } // for interval + } ThreadHelper.waitUntilFinished( futures ); @@ -286,11 +244,27 @@ private void createScreenShot() voxelSpacing[ 2 ] = getViewerVoxelSpacing( bdvHandle ); // TODO: What to put here? - if ( floatCaptures.size() > 0 ) + if ( ! floatCaptures.isEmpty() ) { - rgbImagePlus = createImagePlus( physicalUnit, argbSources, voxelSpacing, sacs ); - compositeImagePlus = createCompositeImage( voxelSpacing, physicalUnit, floatCaptures, colors, displayRanges ); + rgbImagePlus = createRGBImagePlus( physicalUnit, argbSources, voxelSpacing, sacs ); + compositeImagePlus = createCompositeImagePlus( voxelSpacing, physicalUnit, floatCaptures, colors, displayRanges ); + } + } + + private List< SourceAndConverter< ? > > getVisibleSourceAndConverters() + { + final List< SourceAndConverter > visibleSacs = MoBIEHelper.getVisibleSacs( bdvHandle ); + + List< SourceAndConverter< ? > > sacs = new ArrayList<>(); + for ( SourceAndConverter< ? > sac : visibleSacs ) + { + // TODO: can we determine from BDV whether a source is intersecting viewer plane? + // why do we need is2D=false ? + if ( ! isSourceIntersectingCurrentView( bdvHandle, sac.getSpimSource(), false ) ) + continue; + sacs.add( sac ); } + return sacs; } private void setArgbPixelValue( Converter converter, RealRandomAccess< ? > access, RandomAccess< ARGBType > argbCaptureAccess, ARGBType argbType ) @@ -339,16 +313,14 @@ else if ( type instanceof AnnotationType ) return source.getInterpolatedSource( t, level, Interpolation.NEARESTNEIGHBOR ).realRandomAccess(); } - private ImagePlus createImagePlus( + private ImagePlus createRGBImagePlus( String physicalUnit, ArrayList< RandomAccessibleInterval< ARGBType > > argbSources, double[] voxelSpacing, List< SourceAndConverter< ? > > sacs ) { final RandomAccessibleInterval< ARGBType > argbTarget = ArrayImgs.argbs( screenshotDimensions[ 0 ], screenshotDimensions[ 1 ] ); - createARGBprojection( argbSources, argbTarget, sacs ); - return asImagePlus( argbTarget, physicalUnit, voxelSpacing ); } @@ -413,6 +385,33 @@ private void createARGBprojection( ArrayList< RandomAccessibleInterval< ARGBType // } // } // + + + public static long[] getCaptureImageSizeInPixels( BdvHandle bdvHandle, double samplingXY ) + { + final double viewerVoxelSpacing = getViewerVoxelSpacing( bdvHandle ); + + final double[] bdvWindowPhysicalSize = getBdvWindowPhysicalSize( bdvHandle, viewerVoxelSpacing ); + + final long[] capturePixelSize = new long[ 2 ]; + for ( int d = 0; d < 2; d++ ) + { + capturePixelSize[ d ] = ( long ) ( Math.ceil( bdvWindowPhysicalSize[ d ] / samplingXY ) ); + } + + return capturePixelSize; + } + + private static double[] getBdvWindowPhysicalSize( BdvHandle bdvHandle, double viewerVoxelSpacing ) + { + final double[] bdvWindowPhysicalSize = new double[ 2 ]; + final int w = bdvHandle.getViewerPanel().getWidth(); + final int h = bdvHandle.getViewerPanel().getHeight(); + bdvWindowPhysicalSize[ 0 ] = w * viewerVoxelSpacing; + bdvWindowPhysicalSize[ 1 ] = h * viewerVoxelSpacing; + return bdvWindowPhysicalSize; + } + private Cursor< ARGBType >[] getCursors( ArrayList< RandomAccessibleInterval< ARGBType > > argbCaptures, int numVisibleSources ) { Cursor< ARGBType >[] cursors = new Cursor[ numVisibleSources ]; @@ -435,7 +434,7 @@ private ImagePlus asImagePlus( RandomAccessibleInterval< ARGBType > argbCapture, return rgbImage; } - public static CompositeImage createCompositeImage( + public static CompositeImage createCompositeImagePlus( double[] voxelSpacing, String voxelUnit, ArrayList< RandomAccessibleInterval< FloatType > > rais, From 4267cfaa1b5fe06e9ede8c15e224ecd699e1dad1 Mon Sep 17 00:00:00 2001 From: Christian Tischer Date: Wed, 27 Dec 2023 15:14:03 +0100 Subject: [PATCH 04/12] Improve ScreenshotMaker --- .../context/BigWarpRegistrationCommand.java | 6 +- ...ointsExtractor.java => SIFT2DAligner.java} | 140 ++++++++---------- ...onCommand.java => SIFTXYAlignCommand.java} | 42 +++--- .../context/ScreenShotMakerCommand.java | 14 +- .../embl/mobie/lib/bdv/ScreenShotMaker.java | 44 +++--- .../embl/mobie/lib/bdv/view/SliceViewer.java | 15 +- .../em_xray_alignment/OpenEmXraySlices.java | 5 +- .../OpenShearedMRIStack.java | 55 +++++++ 8 files changed, 190 insertions(+), 131 deletions(-) rename src/main/java/org/embl/mobie/command/context/{SIFTPointsExtractor.java => SIFT2DAligner.java} (76%) rename src/main/java/org/embl/mobie/command/context/{SIFTRegistrationCommand.java => SIFTXYAlignCommand.java} (64%) create mode 100644 src/test/java/projects/em_xray_alignment/OpenShearedMRIStack.java diff --git a/src/main/java/org/embl/mobie/command/context/BigWarpRegistrationCommand.java b/src/main/java/org/embl/mobie/command/context/BigWarpRegistrationCommand.java index 81d4d6490..8daa65b81 100644 --- a/src/main/java/org/embl/mobie/command/context/BigWarpRegistrationCommand.java +++ b/src/main/java/org/embl/mobie/command/context/BigWarpRegistrationCommand.java @@ -181,12 +181,12 @@ public void transformChanged( InvertibleRealTransform transform ) private void setMovingTransforms() { - final BigWarpTransform bwTransform = bigWarp.getBwTransform(); - final AffineTransform3D bwAffineTransform = bwTransform.affine3d(); + final BigWarpTransform bigWarpTransform = bigWarp.getBwTransform(); + final AffineTransform3D bigWarpAffineTransform = bigWarpTransform.affine3d(); for ( SourceAndConverter< ? > movingSource : movingSacs ) { final AffineTransform3D combinedTransform = sacToOriginalFixedTransform.get( movingSource ).copy(); - combinedTransform.preConcatenate( bwAffineTransform.copy().inverse() ); + combinedTransform.preConcatenate( bigWarpAffineTransform.copy().inverse() ); final TransformedSource< ? > transformedSource = ( TransformedSource< ? > ) movingSource.getSpimSource(); transformedSource.setFixedTransform( combinedTransform ); } diff --git a/src/main/java/org/embl/mobie/command/context/SIFTPointsExtractor.java b/src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java similarity index 76% rename from src/main/java/org/embl/mobie/command/context/SIFTPointsExtractor.java rename to src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java index b160ed7c0..57604de30 100644 --- a/src/main/java/org/embl/mobie/command/context/SIFTPointsExtractor.java +++ b/src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java @@ -19,15 +19,8 @@ import mpicbg.ij.util.Util; import mpicbg.imagefeatures.Feature; import mpicbg.imagefeatures.FloatArray2DSIFT; -import mpicbg.models.AbstractModel; -import mpicbg.models.AffineModel2D; -import mpicbg.models.HomographyModel2D; -import mpicbg.models.NotEnoughDataPointsException; -import mpicbg.models.Point; -import mpicbg.models.PointMatch; -import mpicbg.models.RigidModel2D; -import mpicbg.models.SimilarityModel2D; -import mpicbg.models.TranslationModel2D; +import mpicbg.models.*; +import net.imglib2.realtransform.AffineTransform3D; import org.embl.mobie.lib.MoBIEHelper; import org.embl.mobie.lib.bdv.ScreenShotMaker; import sc.fiji.bdvpg.bdv.BdvHandleHelper; @@ -71,7 +64,7 @@ * * Modified by Christian Tischer */ -public class SIFTPointsExtractor +public class SIFT2DAligner { final static private DecimalFormat decimalFormat = new DecimalFormat(); final static private DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(); @@ -82,6 +75,9 @@ public class SIFTPointsExtractor final private List< Feature > fs1 = new ArrayList< Feature >(); final private List< Feature > fs2 = new ArrayList< Feature >();; + private AffineTransform3D affineTransform3D; + private SourceAndConverter< ? > fixedSac; + private SourceAndConverter< ? > movingSac; static private class Param { @@ -115,12 +111,12 @@ static private class Param * Implemeted transformation models for choice */ final static public String[] modelStrings = new String[]{ "Translation", "Rigid", "Similarity", "Affine", "Perspective" }; - public int modelIndex = 1; + public int modelIndex = 3; } final static private Param p = new Param(); - public SIFTPointsExtractor( BdvHandle bdvHandle ) + public SIFT2DAligner( BdvHandle bdvHandle ) { this.bdvHandle = bdvHandle; @@ -131,7 +127,7 @@ public SIFTPointsExtractor( BdvHandle bdvHandle ) decimalFormat.setMinimumFractionDigits( 3 ); } - public void run() + public boolean run() { // cleanup fs1.clear(); @@ -144,39 +140,31 @@ public void run() if ( sourceAndConverters.size() < 2 ) { IJ.showMessage( "There must be at least two images visible." ); - return; + return false; } final String[] titles = sourceAndConverters.stream() .map( sac -> sac.getSpimSource().getName() ) .toArray( String[]::new ); - final GenericDialog gd = new GenericDialog( "Extract SIFT Landmark Correspondences" ); + final GenericDialog gd = new GenericDialog( "SIFT 2D Aligner" ); - gd.addMessage( "Images:" ); - final String current = titles[ 0 ]; - gd.addChoice( "fixed_image", titles, current ); - gd.addChoice( "moving_image", titles, current.equals( titles[ 0 ] ) ? titles[ 1 ] : titles[ 0 ] ); + gd.addChoice( "fixed_image :", titles, titles[ 0 ] ); + gd.addChoice( "moving_image :", titles, titles[ 1 ] ); String voxelUnit = sourceAndConverters.get( 0 ).getSpimSource().getVoxelDimensions().unit(); - gd.addMessage( "Scale:" ); - gd.addNumericField( "Pixel Size :", p.pixelSize, 2, 6, voxelUnit ); - - gd.addMessage( "Transformation:" ); + gd.addNumericField( "pixel_size :", p.pixelSize, 2, 6, voxelUnit ); gd.addChoice( "expected_transformation :", Param.modelStrings, Param.modelStrings[ p.modelIndex ] ); - gd.addMessage( "Scale Invariant Interest Point Detector:" ); gd.addNumericField( "initial_gaussian_blur :", p.sift.initialSigma, 2, 6, "px" ); gd.addNumericField( "steps_per_scale_octave :", p.sift.steps, 0 ); gd.addNumericField( "minimum_image_size :", p.sift.minOctaveSize, 0, 6, "px" ); gd.addNumericField( "maximum_image_size :", p.sift.maxOctaveSize, 0, 6, "px" ); - gd.addMessage( "Feature Descriptor:" ); gd.addNumericField( "feature_descriptor_size :", p.sift.fdSize, 0 ); gd.addNumericField( "feature_descriptor_orientation_bins :", p.sift.fdBins, 0 ); gd.addNumericField( "closest/next_closest_ratio :", p.rod, 2 ); - gd.addMessage( "Geometric Consensus Filter:" ); gd.addCheckbox( "filter matches by geometric consensus", p.useGeometricConsensusFilter ); gd.addNumericField( "maximal_alignment_error :", p.maxEpsilon, 2, 6, "px" ); gd.addNumericField( "minimal_inlier_ratio :", p.minInlierRatio, 2 ); @@ -184,7 +172,7 @@ public void run() gd.showDialog(); - if (gd.wasCanceled()) return; + if (gd.wasCanceled()) return false; String fixedImageName = gd.getNextChoice(); String movingImageName = gd.getNextChoice(); @@ -206,18 +194,18 @@ public void run() p.minInlierRatio = ( float )gd.getNextNumber(); p.minNumInliers = ( int )gd.getNextNumber(); - fetchImages( sourceAndConverters, fixedImageName, movingImageName ); + extractImages( sourceAndConverters, fixedImageName, movingImageName ); - exec(imp1, imp2); + return exec( imp1, imp2 ); } - private void fetchImages( List< SourceAndConverter< ? > > sourceAndConverters, String fixedImageName, String movingImageName ) + private void extractImages( List< SourceAndConverter< ? > > sourceAndConverters, String fixedImageName, String movingImageName ) { - SourceAndConverter< ? > fixedSac = sourceAndConverters.stream() + fixedSac = sourceAndConverters.stream() .filter( sac -> sac.getSpimSource().getName().equals( fixedImageName ) ) .findFirst().get(); - SourceAndConverter< ? > movingSac = sourceAndConverters.stream() + movingSac = sourceAndConverters.stream() .filter( sac -> sac.getSpimSource().getName().equals( movingImageName ) ) .findFirst().get(); @@ -227,11 +215,10 @@ private void fetchImages( List< SourceAndConverter< ? > > sourceAndConverters, S ImageStack stack = compositeImage.getStack(); imp1 = new ImagePlus( "fixed", stack.getProcessor( 1 ) ); - compositeImage.setC( 2 ); imp2 = new ImagePlus( "moving", stack.getProcessor( 2 ) ); - // Set the display ranges - // This is important as those will be used by the SIFT for normalising the pixel values + // Setting the display ranges is important + // as those will be used by the SIFT for normalising the pixel values compositeImage.setPosition( 1 ); imp1.getProcessor().setMinAndMax( compositeImage.getDisplayRangeMin(), compositeImage.getDisplayRangeMax() ); compositeImage.setPosition( 2 ); @@ -241,45 +228,13 @@ private void fetchImages( List< SourceAndConverter< ? > > sourceAndConverters, S imp2.show(); } - /** If unsure, just use default parameters by using exec(ImagePlus, ImagePlus, int) method, where only the model is specified. */ - public void exec(final ImagePlus imp1, final ImagePlus imp2, - final float initialSigma, final int steps, - final int minOctaveSize, final int maxOctaveSize, - final int fdSize, final int fdBins, - final float rod, final float maxEpsilon, - final float minInlierRatio, final int modelIndex) { - - p.sift.initialSigma = initialSigma; - p.sift.steps = steps; - p.sift.minOctaveSize = minOctaveSize; - p.sift.maxOctaveSize = maxOctaveSize; - - p.sift.fdSize = fdSize; - p.sift.fdBins = fdBins; - p.rod = rod; - - p.useGeometricConsensusFilter = true; - p.maxEpsilon = maxEpsilon; - p.minInlierRatio = minInlierRatio; - p.minNumInliers = 7; - p.modelIndex = modelIndex; - - exec( imp1, imp2 ); - } - - /** Execute with default parameters, except the model. - * @param modelIndex: 0=Translation, 1=Rigid, 2=Similarity, 3=Affine */ - public void exec(final ImagePlus imp1, final ImagePlus imp2, final int modelIndex) { - if ( modelIndex < 0 || modelIndex > 3 ) { - IJ.log("Invalid model index: " + modelIndex); - return; - } - p.modelIndex = modelIndex; - exec( imp1, imp2 ); - } - - /** Execute with default parameters (model is Rigid) */ - public void exec(final ImagePlus imp1, final ImagePlus imp2) { + /** + * Execute with default parameters (model is Rigid) + * + * @return + * boolean whether a model was found + */ + private boolean exec( final ImagePlus imp1, final ImagePlus imp2) { final FloatArray2DSIFT sift = new FloatArray2DSIFT( p.sift ); final SIFT ijSIFT = new SIFT( sift ); @@ -306,6 +261,8 @@ public void exec(final ImagePlus imp1, final ImagePlus imp2) { final ArrayList< Point > p2 = new ArrayList< Point >(); final List< PointMatch > inliers; + boolean modelFound = false; + if ( p.useGeometricConsensusFilter ) { IJ.log( candidates.size() + " potentially corresponding features identified." ); @@ -330,13 +287,14 @@ public void exec(final ImagePlus imp1, final ImagePlus imp2) { model = new AffineModel2D(); break; case 4: + // TODO: What is this? model = new HomographyModel2D(); break; default: - return; + return modelFound; } - boolean modelFound; + try { modelFound = model.filterRansac( @@ -346,6 +304,21 @@ public void exec(final ImagePlus imp1, final ImagePlus imp2) { p.maxEpsilon, p.minInlierRatio, p.minNumInliers ); + + if ( model instanceof AbstractAffineModel2D ) + { + final double[] a = new double[6]; + ( ( AbstractAffineModel2D< ? > ) model ).toArray( a ); + affineTransform3D = new AffineTransform3D(); + affineTransform3D.set( + a[0], a[2], 0, a[4] * p.pixelSize, + a[1], a[3], 0, a[5] * p.pixelSize, + 0, 0, 1, 0); + affineTransform3D = affineTransform3D.inverse(); + } + else + IJ.showMessage( "Cannot apply " + model ); + } catch ( final NotEnoughDataPointsException e ) { @@ -360,6 +333,7 @@ public void exec(final ImagePlus imp1, final ImagePlus imp2) { IJ.log( inliers.size() + " corresponding features with an average displacement of " + decimalFormat.format( PointMatch.meanDistance( inliers ) ) + "px identified." ); IJ.log( "Estimated transformation model: " + model ); + } else IJ.log( "No correspondences found." ); @@ -370,12 +344,24 @@ public void exec(final ImagePlus imp1, final ImagePlus imp2) { IJ.log( candidates.size() + " corresponding features identified." ); } - if ( inliers.size() > 0 ) + if ( ! inliers.isEmpty() ) { PointMatch.sourcePoints( inliers, p1 ); PointMatch.targetPoints( inliers, p2 ); imp1.setRoi( Util.pointsToPointRoi( p1 ) ); imp2.setRoi( Util.pointsToPointRoi( p2 ) ); } + + return modelFound; + } + + public AffineTransform3D getAffineTransform3D() + { + return affineTransform3D; + } + + public SourceAndConverter< ? > getMovingSac() + { + return movingSac; } } \ No newline at end of file diff --git a/src/main/java/org/embl/mobie/command/context/SIFTRegistrationCommand.java b/src/main/java/org/embl/mobie/command/context/SIFTXYAlignCommand.java similarity index 64% rename from src/main/java/org/embl/mobie/command/context/SIFTRegistrationCommand.java rename to src/main/java/org/embl/mobie/command/context/SIFTXYAlignCommand.java index a7775876c..d249fb286 100644 --- a/src/main/java/org/embl/mobie/command/context/SIFTRegistrationCommand.java +++ b/src/main/java/org/embl/mobie/command/context/SIFTXYAlignCommand.java @@ -28,38 +28,46 @@ */ package org.embl.mobie.command.context; +import bdv.tools.transformation.TransformedSource; import bdv.util.BdvHandle; import bdv.viewer.SourceAndConverter; -import bdv.viewer.TransformListener; -import bigwarp.BigWarp; +import ij.IJ; import net.imglib2.realtransform.AffineTransform3D; -import net.imglib2.realtransform.InvertibleRealTransform; -import org.embl.mobie.MoBIE; import org.embl.mobie.command.CommandConstants; -import org.embl.mobie.lib.MoBIEHelper; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; -import sc.fiji.bdvpg.bdv.BdvHandleHelper; import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; -import sc.fiji.bdvpg.scijava.services.SourceAndConverterBdvDisplayService; -import sc.fiji.bdvpg.services.ISourceAndConverterService; -import sc.fiji.bdvpg.services.SourceAndConverterServices; -import java.util.List; -import java.util.Map; - -@Plugin(type = BdvPlaygroundActionCommand.class, menuPath = CommandConstants.CONTEXT_MENU_ITEMS_ROOT + "Transform>Registration - SIFT") -public class SIFTRegistrationCommand implements BdvPlaygroundActionCommand +@Plugin(type = BdvPlaygroundActionCommand.class, menuPath = CommandConstants.CONTEXT_MENU_ITEMS_ROOT + "Transform>Registration - SIFT XY") +public class SIFTXYAlignCommand implements BdvPlaygroundActionCommand { static { net.imagej.patcher.LegacyInjector.preinit(); } @Parameter - BdvHandle bdvHandle; + public BdvHandle bdvHandle; @Override public void run() { - SIFTPointsExtractor pointsExtractor = new SIFTPointsExtractor( bdvHandle ); - pointsExtractor.run( ); + IJ.log("# SIFT XY Aligner" + + "\nCurrently, only aligns along the XY axis." + + "\nPress Shift+Z to align current view along Z axis to avoid surprising results."); + // start the alignment, which has its own GUI + SIFT2DAligner aligner = new SIFT2DAligner( bdvHandle ); + if( ! aligner.run() ) return; + + // apply transformation + SourceAndConverter< ? > movingSac = aligner.getMovingSac(); + if ( movingSac.getSpimSource() instanceof TransformedSource ) + { + AffineTransform3D affineTransform3D = aligner.getAffineTransform3D(); + ( ( TransformedSource< ? > ) movingSac.getSpimSource() ).setFixedTransform( affineTransform3D ); + bdvHandle.getViewerPanel().requestRepaint(); + IJ.log( "Transformed " + movingSac.getSpimSource().getName() + " with " + affineTransform3D ); + } + else + { + IJ.log("Cannot apply transformation to image of type " + movingSac.getSpimSource().getClass() ); + } } } 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 63f377c1c..4350411f0 100644 --- a/src/main/java/org/embl/mobie/command/context/ScreenShotMakerCommand.java +++ b/src/main/java/org/embl/mobie/command/context/ScreenShotMakerCommand.java @@ -54,10 +54,10 @@ public class ScreenShotMakerCommand extends DynamicCommand implements BdvPlaygro @Parameter public BdvHandle bdvh; - @Parameter(label="Sampling (in below units)", callback = "showNumPixels", min = "0.0", style="format:#.00000", stepSize = "0.01") + @Parameter(label="Sampling (in below units)", persist = false, callback = "showNumPixels", min = "0.0", style="format:#.00000", stepSize = "0.01") public Double targetSamplingInXY = 1D; - @Parameter(label="Pixel unit", choices = {"micrometer"} ) + @Parameter(label="Pixel unit", persist = false, choices = {"micrometer"} ) public String pixelUnit; @Parameter(label="Show RGB Image") @@ -86,7 +86,7 @@ public void run() @Override public void initialize() { - IJ.log( "ScreenShotMaker:" ); + IJ.log( "# ScreenShotMaker" ); // set pixel unit choices // @@ -97,8 +97,12 @@ public void initialize() { units.add( pixelUnit ); pixelUnitItem.setChoices( units ); - IJ.log( "Viewer sampling: " + BdvHandleHelper.getViewerVoxelSpacing( bdvh ) + " " + pixelUnit ); - IJ.log( "Choosing smaller sampling than the above may result in long waiting times." ); + // set screenshot sampling + // + final MutableModuleItem< Double > targetSamplingItem = // + getInfo().getMutableInput("targetSamplingInXY", Double.class); + double viewerVoxelSpacing = BdvHandleHelper.getViewerVoxelSpacing( bdvh ); + targetSamplingItem.setValue( this, 2 * viewerVoxelSpacing ); } // callback 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 f916c2142..e59a12dc8 100644 --- a/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java +++ b/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java @@ -78,16 +78,17 @@ public class ScreenShotMaker private final BdvHandle bdvHandle; private final ISourceAndConverterService sacService; - private double samplingXY = 1; + private double targetVoxelSpacing = 1; private String physicalUnit = "Pixels"; private ImagePlus rgbImagePlus = null; private CompositeImage compositeImagePlus = null; private long[] screenshotDimensions = new long[2]; + private AffineTransform3D targetCanvasToGlobalTransform; public ScreenShotMaker( BdvHandle bdvHandle, Double pixelSize, String pixelUnit ) { this.bdvHandle = bdvHandle; this.sacService = SourceAndConverterServices.getSourceAndConverterService(); - this.samplingXY = pixelSize; + this.targetVoxelSpacing = pixelSize; this.physicalUnit = pixelUnit; } @@ -118,7 +119,7 @@ public void run( List< SourceAndConverter< ? > > sacs ) final AffineTransform3D viewerTransform = new AffineTransform3D(); bdvHandle.getViewerPanel().state().getViewerTransform( viewerTransform ); - screenshotDimensions = getCaptureImageSizeInPixels( bdvHandle, samplingXY ); + screenshotDimensions = getCaptureImageSizeInPixels( bdvHandle, targetVoxelSpacing ); final ArrayList< RandomAccessibleInterval< FloatType > > floatCaptures = new ArrayList<>(); final ArrayList< RandomAccessibleInterval< ARGBType > > argbSources = new ArrayList<>(); @@ -137,13 +138,18 @@ public void run( List< SourceAndConverter< ? > > sacs ) List< Interval > intervals = Grids.collectAllContainedIntervals( screenshotDimensions, blockSize ); - final double canvasStepSize = samplingXY / getViewerVoxelSpacing( bdvHandle ); + + targetCanvasToGlobalTransform = new AffineTransform3D(); + // target canvas to viewer canvas + double targetToViewer = getViewerVoxelSpacing( bdvHandle ) / targetVoxelSpacing; + targetCanvasToGlobalTransform.scale( 1 / targetToViewer, 1 / targetToViewer, 1.0 ); + // viewer canvas to global + AffineTransform3D viewerToGlobal = viewerTransform.inverse(); + targetCanvasToGlobalTransform.preConcatenate( viewerToGlobal ); IJ.log( "Number of threads: " + ThreadHelper.getNumIoThreads() ); IJ.log( "Block per thread: " + Arrays.toString( blockSize ) ); - final long currentTimeMillis = System.currentTimeMillis(); - for ( SourceAndConverter< ? > sac : sacs ) { final RandomAccessibleInterval< FloatType > rawCapture @@ -154,13 +160,13 @@ public void run( List< SourceAndConverter< ? > > sacs ) Source< ? > source = sac.getSpimSource(); final Converter< ?, ? > converter = sac.getConverter(); - final int level = getLevel( source, samplingXY ); - final AffineTransform3D sourceTransform = - BdvHandleHelper.getSourceTransform( source, t, level ); + final int level = getLevel( source, targetVoxelSpacing ); + final AffineTransform3D sourceTransform = BdvHandleHelper.getSourceTransform( source, t, level ); - AffineTransform3D viewerToSourceTransform = new AffineTransform3D(); - viewerToSourceTransform.preConcatenate( viewerTransform.inverse() ); - viewerToSourceTransform.preConcatenate( sourceTransform.inverse() ); + // global to source + AffineTransform3D targetCanvasToSourceTransform = targetCanvasToGlobalTransform.copy(); + AffineTransform3D globalToSource = sourceTransform.inverse(); + targetCanvasToSourceTransform.preConcatenate( globalToSource ); boolean interpolate = ! ( source.getType() instanceof AnnotationType ); @@ -197,13 +203,7 @@ public void run( List< SourceAndConverter< ? > > sacs ) floatCaptureAccess.setPosition( floatCaptureCursor ); argbCaptureAccess.setPosition( floatCaptureCursor ); - // canvasPosition is in calibrated units - // canvasStepSize is the step size that is needed to get - // the desired resolution in the output image - canvasPosition[ 0 ] *= canvasStepSize; - canvasPosition[ 1 ] *= canvasStepSize; - - viewerToSourceTransform.apply( canvasPosition, sourceRealPosition ); + targetCanvasToSourceTransform.apply( canvasPosition, sourceRealPosition ); access.setPosition( sourceRealPosition ); setFloatPixelValue( access, floatCaptureAccess ); setArgbPixelValue( converter, access, argbCaptureAccess, argbType ); @@ -239,10 +239,7 @@ public void run( List< SourceAndConverter< ? > > sacs ) IJ.log( "Fetched data in " + ( System.currentTimeMillis() - currentTimeMillis ) + " ms." ); final double[] voxelSpacing = new double[ 3 ]; - for ( int d = 0; d < 2; d++ ) - voxelSpacing[ d ] = samplingXY; - - voxelSpacing[ 2 ] = getViewerVoxelSpacing( bdvHandle ); // TODO: What to put here? + Arrays.fill( voxelSpacing, targetVoxelSpacing ); if ( ! floatCaptures.isEmpty() ) { @@ -460,7 +457,6 @@ public static CompositeImage createCompositeImagePlus( 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 ); diff --git a/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java b/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java index fa65e8fd6..807dcae8f 100644 --- a/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java +++ b/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java @@ -30,8 +30,7 @@ import bdv.util.BdvHandle; import bdv.viewer.SourceAndConverter; -import net.imglib2.converter.Converter; -import net.imglib2.type.numeric.ARGBType; +import net.imglib2.realtransform.AffineTransform3D; import org.embl.mobie.DataStore; import org.embl.mobie.command.context.*; import org.embl.mobie.command.view.ViewerTransformLoggerCommand; @@ -44,7 +43,6 @@ import org.embl.mobie.lib.bdv.blend.AccumulateAlphaBlendingProjectorARGB; import org.embl.mobie.lib.bdv.blend.BlendingMode; import org.embl.mobie.lib.color.OpacityHelper; -import org.embl.mobie.lib.color.opacity.MoBIEColorConverter; import org.embl.mobie.lib.image.Image; import org.embl.mobie.lib.image.RegionAnnotationImage; import org.embl.mobie.lib.serialize.display.AbstractDisplay; @@ -154,7 +152,7 @@ private void installContextMenuAndKeyboardShortCuts( ) actions.add( SourceAndConverterService.getCommandName( ViewerTransformLoggerCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( SourceInfoLoggerCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( BigWarpRegistrationCommand.class ) ); - actions.add( SourceAndConverterService.getCommandName( SIFTRegistrationCommand.class ) ); + actions.add( SourceAndConverterService.getCommandName( SIFTXYAlignCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( ManualRegistrationCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( FlipCommand.class ) ); actions.add( UNDO_SEGMENT_SELECTIONS ); @@ -177,6 +175,15 @@ private void installContextMenuAndKeyboardShortCuts( ) behaviours.behaviour( contextMenu, "Context menu", "button3", "shift P"); behaviours.install( bdvHandle.getTriggerbindings(), "MoBIE" ); + + behaviours.behaviour( + ( ClickBehaviour ) ( x, y ) -> + new Thread( () -> { + AffineTransform3D affineTransform3D = bdvHandle.getViewerPanel().state().getViewerTransform(); + System.out.println( "Viewer transform " + affineTransform3D ); + }).start(), + "Log viewer transform", "ctrl 1" ) ; + behaviours.behaviour( ( ClickBehaviour ) ( x, y ) -> new Thread( () -> sliceViewAnnotationSelector.run() ).start(), diff --git a/src/test/java/projects/em_xray_alignment/OpenEmXraySlices.java b/src/test/java/projects/em_xray_alignment/OpenEmXraySlices.java index 0bc430f81..d2e4bf0af 100644 --- a/src/test/java/projects/em_xray_alignment/OpenEmXraySlices.java +++ b/src/test/java/projects/em_xray_alignment/OpenEmXraySlices.java @@ -2,8 +2,8 @@ import net.imagej.ImageJ; import org.embl.mobie.MoBIE; +import org.embl.mobie.command.context.SIFTXYAlignCommand; import org.embl.mobie.command.open.OpenMultipleImagesAndLabelsCommand; -import org.embl.mobie.io.OpenerLogging; import java.io.File; import java.io.IOException; @@ -19,5 +19,8 @@ public static void main( String[] args ) throws IOException command.image0 = new File("/Users/tischer/Desktop/em-xray/xray-slice-ds-0.tif"); command.image1 = new File("/Users/tischer/Desktop/em-xray/em-slice-ds-0.tif"); command.run(); + SIFTXYAlignCommand sift2DAlignCommand = new SIFTXYAlignCommand(); + sift2DAlignCommand.bdvHandle = MoBIE.getInstance().getViewManager().getSliceViewer().getBdvHandle(); + sift2DAlignCommand.run(); } } diff --git a/src/test/java/projects/em_xray_alignment/OpenShearedMRIStack.java b/src/test/java/projects/em_xray_alignment/OpenShearedMRIStack.java new file mode 100644 index 000000000..2bb7c5938 --- /dev/null +++ b/src/test/java/projects/em_xray_alignment/OpenShearedMRIStack.java @@ -0,0 +1,55 @@ +package projects.em_xray_alignment; + +import net.imagej.ImageJ; +import net.imglib2.realtransform.AffineTransform3D; +import org.embl.mobie.MoBIE; +import org.embl.mobie.command.context.SIFTXYAlignCommand; +import org.embl.mobie.command.open.OpenMultipleImagesAndLabelsCommand; + +import java.io.File; +import java.io.IOException; + +public class OpenShearedMRIStack +{ + public static void main( String[] args ) throws IOException + { + AffineTransform3D shearTransformXY = new AffineTransform3D(); + shearTransformXY.set( + 0.9967208555433267, -0.1981387047703669, 0.0, 2.9848361329209316, + 0.00626758147615312, 1.0002354640459286, -0.0, -9.235423965765829, + -0.0, 0.0, 1.0, -0.0 ); + + AffineTransform3D rotation = new AffineTransform3D(); + rotation.rotate( 0, 90.0 * Math.PI / 180.0 ); + + AffineTransform3D affineTransform3D = new AffineTransform3D(); + + affineTransform3D.preConcatenate( rotation ); + affineTransform3D.preConcatenate( shearTransformXY ); + affineTransform3D.preConcatenate( rotation.inverse() ); + + System.out.printf( "rotate " + rotation ); + System.out.printf( "final " + affineTransform3D ); + + /* + + 0.9967208555433267, -1.2132496529211612E-17, 0.1981387047703669, 2.9848361329209316, + 3.837786796583081E-19, 1.0, -1.4418014508029665E-20, -5.655066199221939E-16, + -0.00626758147615312, -1.4418014508029665E-20, 1.0002354640459286, 9.235423965765829) + + */ + +// // OpenerLogging.setLogging( true ); + final ImageJ imageJ = new ImageJ(); + imageJ.ui().showUI(); + OpenMultipleImagesAndLabelsCommand command = new OpenMultipleImagesAndLabelsCommand(); + command.image0 = new File("/Users/tischer/Desktop/em-xray/mri-stack-calibrated.tif"); + command.image1 = new File("/Users/tischer/Desktop/em-xray/mri-stack-calibrated-sheared.tif"); + command.run(); +// + +// SIFTXYAlignCommand sift2DAlignCommand = new SIFTXYAlignCommand(); +// sift2DAlignCommand.bdvHandle = MoBIE.getInstance().getViewManager().getSliceViewer().getBdvHandle(); +// sift2DAlignCommand.run(); + } +} From 3dbeda0fdc3fa862fc4f87768dd8d2b6861588d0 Mon Sep 17 00:00:00 2001 From: Christian Tischer Date: Wed, 27 Dec 2023 17:03:44 +0100 Subject: [PATCH 05/12] SIFT Registration working --- ...gnCommand.java => SIFT2DAlignCommand.java} | 20 ++++++---- .../mobie/command/context/SIFT2DAligner.java | 25 ++++++++---- .../context/ScreenShotMakerCommand.java | 15 +------- .../embl/mobie/lib/bdv/ScreenShotMaker.java | 38 ++++++++++--------- .../embl/mobie/lib/bdv/view/SliceViewer.java | 2 +- .../em_xray_alignment/OpenEmXraySlices.java | 4 +- .../OpenShearedMRIStack.java | 2 - 7 files changed, 56 insertions(+), 50 deletions(-) rename src/main/java/org/embl/mobie/command/context/{SIFTXYAlignCommand.java => SIFT2DAlignCommand.java} (79%) diff --git a/src/main/java/org/embl/mobie/command/context/SIFTXYAlignCommand.java b/src/main/java/org/embl/mobie/command/context/SIFT2DAlignCommand.java similarity index 79% rename from src/main/java/org/embl/mobie/command/context/SIFTXYAlignCommand.java rename to src/main/java/org/embl/mobie/command/context/SIFT2DAlignCommand.java index d249fb286..7b6e51a0a 100644 --- a/src/main/java/org/embl/mobie/command/context/SIFTXYAlignCommand.java +++ b/src/main/java/org/embl/mobie/command/context/SIFT2DAlignCommand.java @@ -38,8 +38,8 @@ import org.scijava.plugin.Plugin; import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; -@Plugin(type = BdvPlaygroundActionCommand.class, menuPath = CommandConstants.CONTEXT_MENU_ITEMS_ROOT + "Transform>Registration - SIFT XY") -public class SIFTXYAlignCommand implements BdvPlaygroundActionCommand +@Plugin(type = BdvPlaygroundActionCommand.class, menuPath = CommandConstants.CONTEXT_MENU_ITEMS_ROOT + "Transform>Registration - SIFT") +public class SIFT2DAlignCommand implements BdvPlaygroundActionCommand { static { net.imagej.patcher.LegacyInjector.preinit(); } @@ -49,9 +49,9 @@ public class SIFTXYAlignCommand implements BdvPlaygroundActionCommand @Override public void run() { - IJ.log("# SIFT XY Aligner" + - "\nCurrently, only aligns along the XY axis." + - "\nPress Shift+Z to align current view along Z axis to avoid surprising results."); + IJ.log("# SIFT registration" + + "\nThe registration is computed in the currently visible 2D plane" + + "\nbut then applied to the full image in 3D."); // start the alignment, which has its own GUI SIFT2DAligner aligner = new SIFT2DAligner( bdvHandle ); if( ! aligner.run() ) return; @@ -60,10 +60,14 @@ public void run() SourceAndConverter< ? > movingSac = aligner.getMovingSac(); if ( movingSac.getSpimSource() instanceof TransformedSource ) { - AffineTransform3D affineTransform3D = aligner.getAffineTransform3D(); - ( ( TransformedSource< ? > ) movingSac.getSpimSource() ).setFixedTransform( affineTransform3D ); + AffineTransform3D siftTransform3D = aligner.getSiftTransform3D(); + TransformedSource< ? > transformedSource = ( TransformedSource< ? > ) movingSac.getSpimSource(); + AffineTransform3D fixedTransform = new AffineTransform3D(); + transformedSource.getFixedTransform( fixedTransform ); + fixedTransform.preConcatenate( siftTransform3D ); + transformedSource.setFixedTransform( fixedTransform ); bdvHandle.getViewerPanel().requestRepaint(); - IJ.log( "Transformed " + movingSac.getSpimSource().getName() + " with " + affineTransform3D ); + IJ.log( "Transformed " + movingSac.getSpimSource().getName() + " with " + siftTransform3D ); } else { diff --git a/src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java b/src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java index 57604de30..1d58016fa 100644 --- a/src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java +++ b/src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java @@ -78,6 +78,7 @@ public class SIFT2DAligner private AffineTransform3D affineTransform3D; private SourceAndConverter< ? > fixedSac; private SourceAndConverter< ? > movingSac; + private ScreenShotMaker screenShotMaker; static private class Param { @@ -209,7 +210,7 @@ private void extractImages( List< SourceAndConverter< ? > > sourceAndConverters, .filter( sac -> sac.getSpimSource().getName().equals( movingImageName ) ) .findFirst().get(); - ScreenShotMaker screenShotMaker = new ScreenShotMaker( bdvHandle, p.pixelSize, fixedSac.getSpimSource().getVoxelDimensions().unit() ); + screenShotMaker = new ScreenShotMaker( bdvHandle, p.pixelSize, fixedSac.getSpimSource().getVoxelDimensions().unit() ); screenShotMaker.run( Arrays.asList( fixedSac, movingSac ) ); CompositeImage compositeImage = screenShotMaker.getCompositeImagePlus(); @@ -307,14 +308,24 @@ private boolean exec( final ImagePlus imp1, final ImagePlus imp2) { if ( model instanceof AbstractAffineModel2D ) { + affineTransform3D = new AffineTransform3D(); + + // global to target canvas + AffineTransform3D canvasToGlobalTransform = screenShotMaker.getCanvasToGlobalTransform(); + affineTransform3D.preConcatenate( canvasToGlobalTransform.inverse() ); + + // sift within canvas final double[] a = new double[6]; ( ( AbstractAffineModel2D< ? > ) model ).toArray( a ); - affineTransform3D = new AffineTransform3D(); - affineTransform3D.set( - a[0], a[2], 0, a[4] * p.pixelSize, - a[1], a[3], 0, a[5] * p.pixelSize, + AffineTransform3D canvasSiftTransform = new AffineTransform3D(); + canvasSiftTransform.set( + a[0], a[2], 0, a[4], + a[1], a[3], 0, a[5], 0, 0, 1, 0); - affineTransform3D = affineTransform3D.inverse(); + affineTransform3D.preConcatenate( canvasSiftTransform.inverse() ); + + // canvas to global + affineTransform3D.preConcatenate( canvasToGlobalTransform ); } else IJ.showMessage( "Cannot apply " + model ); @@ -355,7 +366,7 @@ private boolean exec( final ImagePlus imp1, final ImagePlus imp2) { return modelFound; } - public AffineTransform3D getAffineTransform3D() + public AffineTransform3D getSiftTransform3D() { return affineTransform3D; } 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 4350411f0..15a130a8f 100644 --- a/src/main/java/org/embl/mobie/command/context/ScreenShotMakerCommand.java +++ b/src/main/java/org/embl/mobie/command/context/ScreenShotMakerCommand.java @@ -60,24 +60,13 @@ public class ScreenShotMakerCommand extends DynamicCommand implements BdvPlaygro @Parameter(label="Pixel unit", persist = false, choices = {"micrometer"} ) public String pixelUnit; - @Parameter(label="Show RGB Image") - public boolean showRGB = true; - - @Parameter(label="Show Multi-Channel Image") - public boolean showMultiChannel = true; - - @Override public void run() { ScreenShotMaker screenShotMaker = new ScreenShotMaker( bdvh, targetSamplingInXY, pixelUnit ); screenShotMaker.run(); - - if( showRGB ) - screenShotMaker.getRGBImagePlus().show(); - - if( showMultiChannel ) - screenShotMaker.getCompositeImagePlus().show(); + screenShotMaker.getRGBImagePlus().show(); + screenShotMaker.getCompositeImagePlus().show(); if ( MoBIE.getInstance().getSettings().values.isOpenedFromCLI() ) MoBIE.imageJ.ui().showUI(); 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 e59a12dc8..44e09f4ce 100644 --- a/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java +++ b/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java @@ -83,7 +83,7 @@ public class ScreenShotMaker private ImagePlus rgbImagePlus = null; private CompositeImage compositeImagePlus = null; private long[] screenshotDimensions = new long[2]; - private AffineTransform3D targetCanvasToGlobalTransform; + private AffineTransform3D canvasToGlobalTransform; public ScreenShotMaker( BdvHandle bdvHandle, Double pixelSize, String pixelUnit ) { this.bdvHandle = bdvHandle; @@ -118,8 +118,18 @@ public void run( List< SourceAndConverter< ? > > sacs ) final AffineTransform3D viewerTransform = new AffineTransform3D(); bdvHandle.getViewerPanel().state().getViewerTransform( viewerTransform ); + final int currentTimepoint = bdvHandle.getViewerPanel().state().getCurrentTimepoint(); - screenshotDimensions = getCaptureImageSizeInPixels( bdvHandle, targetVoxelSpacing ); + canvasToGlobalTransform = new AffineTransform3D(); + // target canvas to viewer canvas... + double targetToViewer = targetVoxelSpacing / getViewerVoxelSpacing( bdvHandle ); + canvasToGlobalTransform.scale( targetToViewer, targetToViewer, 1.0 ); + // ...viewer canvas to global + AffineTransform3D viewerToGlobal = viewerTransform.inverse(); + canvasToGlobalTransform.preConcatenate( viewerToGlobal ); + IJ.log( "Canvas to global transform: " + canvasToGlobalTransform ); + + IJ.log( "Fetching data from " + sacs.size() + " images..." ); final ArrayList< RandomAccessibleInterval< FloatType > > floatCaptures = new ArrayList<>(); final ArrayList< RandomAccessibleInterval< ARGBType > > argbSources = new ArrayList<>(); @@ -127,10 +137,7 @@ public void run( List< SourceAndConverter< ? > > sacs ) final ArrayList< double[] > displayRanges = new ArrayList<>(); - final int t = bdvHandle.getViewerPanel().state().getCurrentTimepoint(); - - IJ.log( "Fetching data from " + sacs.size() + " images..." ); - + screenshotDimensions = getCaptureImageSizeInPixels( bdvHandle, targetVoxelSpacing ); final long numPixels = screenshotDimensions[ 0 ] * screenshotDimensions[ 1 ]; long pixelsPerThread = numPixels / ThreadHelper.getNumIoThreads(); int dimensionsPerThread = (int) Math.sqrt( pixelsPerThread ); @@ -139,14 +146,6 @@ public void run( List< SourceAndConverter< ? > > sacs ) screenshotDimensions, blockSize ); - targetCanvasToGlobalTransform = new AffineTransform3D(); - // target canvas to viewer canvas - double targetToViewer = getViewerVoxelSpacing( bdvHandle ) / targetVoxelSpacing; - targetCanvasToGlobalTransform.scale( 1 / targetToViewer, 1 / targetToViewer, 1.0 ); - // viewer canvas to global - AffineTransform3D viewerToGlobal = viewerTransform.inverse(); - targetCanvasToGlobalTransform.preConcatenate( viewerToGlobal ); - IJ.log( "Number of threads: " + ThreadHelper.getNumIoThreads() ); IJ.log( "Block per thread: " + Arrays.toString( blockSize ) ); final long currentTimeMillis = System.currentTimeMillis(); @@ -161,10 +160,10 @@ public void run( List< SourceAndConverter< ? > > sacs ) final Converter< ?, ? > converter = sac.getConverter(); final int level = getLevel( source, targetVoxelSpacing ); - final AffineTransform3D sourceTransform = BdvHandleHelper.getSourceTransform( source, t, level ); + final AffineTransform3D sourceTransform = BdvHandleHelper.getSourceTransform( source, currentTimepoint, level ); // global to source - AffineTransform3D targetCanvasToSourceTransform = targetCanvasToGlobalTransform.copy(); + AffineTransform3D targetCanvasToSourceTransform = canvasToGlobalTransform.copy(); AffineTransform3D globalToSource = sourceTransform.inverse(); targetCanvasToSourceTransform.preConcatenate( globalToSource ); @@ -179,7 +178,7 @@ public void run( List< SourceAndConverter< ? > > sacs ) ( ThreadHelper.ioExecutorService.submit( () -> { - RealRandomAccess< ? extends Type< ? > > access = getRealRandomAccess( ( Source< Type< ? > > ) source, t, level, interpolate ); + RealRandomAccess< ? extends Type< ? > > access = getRealRandomAccess( ( Source< Type< ? > > ) source, currentTimepoint, level, interpolate ); // to collect raw data final IntervalView< FloatType > floatCrop = Views.interval( rawCapture, interval ); @@ -248,6 +247,11 @@ public void run( List< SourceAndConverter< ? > > sacs ) } } + public AffineTransform3D getCanvasToGlobalTransform() + { + return canvasToGlobalTransform; + } + private List< SourceAndConverter< ? > > getVisibleSourceAndConverters() { final List< SourceAndConverter > visibleSacs = MoBIEHelper.getVisibleSacs( bdvHandle ); diff --git a/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java b/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java index 807dcae8f..e03979951 100644 --- a/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java +++ b/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java @@ -152,7 +152,7 @@ private void installContextMenuAndKeyboardShortCuts( ) actions.add( SourceAndConverterService.getCommandName( ViewerTransformLoggerCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( SourceInfoLoggerCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( BigWarpRegistrationCommand.class ) ); - actions.add( SourceAndConverterService.getCommandName( SIFTXYAlignCommand.class ) ); + actions.add( SourceAndConverterService.getCommandName( SIFT2DAlignCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( ManualRegistrationCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( FlipCommand.class ) ); actions.add( UNDO_SEGMENT_SELECTIONS ); diff --git a/src/test/java/projects/em_xray_alignment/OpenEmXraySlices.java b/src/test/java/projects/em_xray_alignment/OpenEmXraySlices.java index d2e4bf0af..6373cf65e 100644 --- a/src/test/java/projects/em_xray_alignment/OpenEmXraySlices.java +++ b/src/test/java/projects/em_xray_alignment/OpenEmXraySlices.java @@ -2,7 +2,7 @@ import net.imagej.ImageJ; import org.embl.mobie.MoBIE; -import org.embl.mobie.command.context.SIFTXYAlignCommand; +import org.embl.mobie.command.context.SIFT2DAlignCommand; import org.embl.mobie.command.open.OpenMultipleImagesAndLabelsCommand; import java.io.File; @@ -19,7 +19,7 @@ public static void main( String[] args ) throws IOException command.image0 = new File("/Users/tischer/Desktop/em-xray/xray-slice-ds-0.tif"); command.image1 = new File("/Users/tischer/Desktop/em-xray/em-slice-ds-0.tif"); command.run(); - SIFTXYAlignCommand sift2DAlignCommand = new SIFTXYAlignCommand(); + SIFT2DAlignCommand sift2DAlignCommand = new SIFT2DAlignCommand(); sift2DAlignCommand.bdvHandle = MoBIE.getInstance().getViewManager().getSliceViewer().getBdvHandle(); sift2DAlignCommand.run(); } diff --git a/src/test/java/projects/em_xray_alignment/OpenShearedMRIStack.java b/src/test/java/projects/em_xray_alignment/OpenShearedMRIStack.java index 2bb7c5938..7c0c1ecd7 100644 --- a/src/test/java/projects/em_xray_alignment/OpenShearedMRIStack.java +++ b/src/test/java/projects/em_xray_alignment/OpenShearedMRIStack.java @@ -2,8 +2,6 @@ import net.imagej.ImageJ; import net.imglib2.realtransform.AffineTransform3D; -import org.embl.mobie.MoBIE; -import org.embl.mobie.command.context.SIFTXYAlignCommand; import org.embl.mobie.command.open.OpenMultipleImagesAndLabelsCommand; import java.io.File; From f69f0b8b1c4f4f83277e6691821634be6b0baf63 Mon Sep 17 00:00:00 2001 From: Christian Tischer Date: Thu, 28 Dec 2023 19:04:29 +0100 Subject: [PATCH 06/12] Add option to cancel the SIFT transformation --- .../command/context/SIFT2DAlignCommand.java | 32 ++++++++++++---- .../mobie/command/context/SIFT2DAligner.java | 38 ++++++++++++------- 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/embl/mobie/command/context/SIFT2DAlignCommand.java b/src/main/java/org/embl/mobie/command/context/SIFT2DAlignCommand.java index 7b6e51a0a..9dee6f317 100644 --- a/src/main/java/org/embl/mobie/command/context/SIFT2DAlignCommand.java +++ b/src/main/java/org/embl/mobie/command/context/SIFT2DAlignCommand.java @@ -32,6 +32,8 @@ import bdv.util.BdvHandle; import bdv.viewer.SourceAndConverter; import ij.IJ; +import ij.gui.NonBlockingGenericDialog; +import ij.gui.YesNoCancelDialog; import net.imglib2.realtransform.AffineTransform3D; import org.embl.mobie.command.CommandConstants; import org.scijava.plugin.Parameter; @@ -51,23 +53,39 @@ public void run() { IJ.log("# SIFT registration" + "\nThe registration is computed in the currently visible 2D plane" + - "\nbut then applied to the full image in 3D."); + "\nand then applied to the full image in 3D."); // start the alignment, which has its own GUI SIFT2DAligner aligner = new SIFT2DAligner( bdvHandle ); - if( ! aligner.run() ) return; + if( ! aligner.showUI() ) return; // apply transformation SourceAndConverter< ? > movingSac = aligner.getMovingSac(); if ( movingSac.getSpimSource() instanceof TransformedSource ) { + // apply the transformation AffineTransform3D siftTransform3D = aligner.getSiftTransform3D(); TransformedSource< ? > transformedSource = ( TransformedSource< ? > ) movingSac.getSpimSource(); - AffineTransform3D fixedTransform = new AffineTransform3D(); - transformedSource.getFixedTransform( fixedTransform ); - fixedTransform.preConcatenate( siftTransform3D ); - transformedSource.setFixedTransform( fixedTransform ); + AffineTransform3D previousFixedTransform = new AffineTransform3D(); + transformedSource.getFixedTransform( previousFixedTransform ); + AffineTransform3D newFixedTransform = previousFixedTransform.copy(); + newFixedTransform.preConcatenate( siftTransform3D ); + transformedSource.setFixedTransform( newFixedTransform ); bdvHandle.getViewerPanel().requestRepaint(); - IJ.log( "Transformed " + movingSac.getSpimSource().getName() + " with " + siftTransform3D ); + + // ask user whether to accept the transformation + NonBlockingGenericDialog dialog = new NonBlockingGenericDialog( "SIFT Alignment" ); + dialog.addMessage( "Press OK to keep the transformation" ); + dialog.addMessage( "Press Cancel to discard the transformation" ); + dialog.showDialog(); + if ( dialog.wasOKed() ) + { + IJ.log( "Transformed " + movingSac.getSpimSource().getName() + " with " + siftTransform3D ); + } + else if ( dialog.wasCanceled() ) + { + transformedSource.setFixedTransform( previousFixedTransform ); + bdvHandle.getViewerPanel().requestRepaint(); + } } else { diff --git a/src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java b/src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java index 1d58016fa..34716d245 100644 --- a/src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java +++ b/src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java @@ -108,6 +108,11 @@ static private class Param */ public int minNumInliers = 7; + /** + * Whether to show the detected landmarks + */ + public boolean showLandmarks = false; + /** * Implemeted transformation models for choice */ @@ -128,12 +133,8 @@ public SIFT2DAligner( BdvHandle bdvHandle ) decimalFormat.setMinimumFractionDigits( 3 ); } - public boolean run() + public boolean showUI() { - // cleanup - fs1.clear(); - fs2.clear(); - if( p.pixelSize == null ) p.pixelSize = BdvHandleHelper.getViewerVoxelSpacing( bdvHandle ); @@ -171,6 +172,8 @@ public boolean run() gd.addNumericField( "minimal_inlier_ratio :", p.minInlierRatio, 2 ); gd.addNumericField( "minimal_number_of_inliers :", p.minNumInliers, 0 ); + gd.addCheckbox( "show images with detected landmarks", false ); + gd.showDialog(); if (gd.wasCanceled()) return false; @@ -195,9 +198,16 @@ public boolean run() p.minInlierRatio = ( float )gd.getNextNumber(); p.minNumInliers = ( int )gd.getNextNumber(); + p.showLandmarks = gd.getNextBoolean(); + + return run( sourceAndConverters, fixedImageName, movingImageName ); + } + + public boolean run( List< SourceAndConverter< ? > > sourceAndConverters, String fixedImageName, String movingImageName ) + { extractImages( sourceAndConverters, fixedImageName, movingImageName ); - return exec( imp1, imp2 ); + return run( imp1, imp2 ); } private void extractImages( List< SourceAndConverter< ? > > sourceAndConverters, String fixedImageName, String movingImageName ) @@ -224,18 +234,19 @@ private void extractImages( List< SourceAndConverter< ? > > sourceAndConverters, imp1.getProcessor().setMinAndMax( compositeImage.getDisplayRangeMin(), compositeImage.getDisplayRangeMax() ); compositeImage.setPosition( 2 ); imp2.getProcessor().setMinAndMax( compositeImage.getDisplayRangeMin(), compositeImage.getDisplayRangeMax() ); - - imp1.show(); - imp2.show(); } /** - * Execute with default parameters (model is Rigid) + * Execute with current parameters * * @return * boolean whether a model was found */ - private boolean exec( final ImagePlus imp1, final ImagePlus imp2) { + private boolean run( final ImagePlus imp1, final ImagePlus imp2) { + + // cleanup + fs1.clear(); + fs2.clear(); final FloatArray2DSIFT sift = new FloatArray2DSIFT( p.sift ); final SIFT ijSIFT = new SIFT( sift ); @@ -344,7 +355,6 @@ private boolean exec( final ImagePlus imp1, final ImagePlus imp2) { IJ.log( inliers.size() + " corresponding features with an average displacement of " + decimalFormat.format( PointMatch.meanDistance( inliers ) ) + "px identified." ); IJ.log( "Estimated transformation model: " + model ); - } else IJ.log( "No correspondences found." ); @@ -355,8 +365,10 @@ private boolean exec( final ImagePlus imp1, final ImagePlus imp2) { IJ.log( candidates.size() + " corresponding features identified." ); } - if ( ! inliers.isEmpty() ) + if ( ! inliers.isEmpty() && p.showLandmarks ) { + imp1.show(); + imp2.show(); PointMatch.sourcePoints( inliers, p1 ); PointMatch.targetPoints( inliers, p2 ); imp1.setRoi( Util.pointsToPointRoi( p1 ) ); From 65197bc2c054ee56825135a9197efb7fc9c4c868 Mon Sep 17 00:00:00 2001 From: Christian Tischer Date: Thu, 28 Dec 2023 20:21:27 +0100 Subject: [PATCH 07/12] Improve SIFT Dialog --- .../command/context/SIFT2DAlignCommand.java | 80 +++++++++++++------ .../mobie/command/context/SIFT2DAligner.java | 18 +++-- .../command/open/OpenHCSDatasetCommand.java | 1 - .../em_xray_alignment/OpenEMXRAY.java | 2 +- 4 files changed, 68 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/embl/mobie/command/context/SIFT2DAlignCommand.java b/src/main/java/org/embl/mobie/command/context/SIFT2DAlignCommand.java index 9dee6f317..afdea4212 100644 --- a/src/main/java/org/embl/mobie/command/context/SIFT2DAlignCommand.java +++ b/src/main/java/org/embl/mobie/command/context/SIFT2DAlignCommand.java @@ -32,24 +32,61 @@ import bdv.util.BdvHandle; import bdv.viewer.SourceAndConverter; import ij.IJ; -import ij.gui.NonBlockingGenericDialog; -import ij.gui.YesNoCancelDialog; import net.imglib2.realtransform.AffineTransform3D; import org.embl.mobie.command.CommandConstants; +import org.scijava.command.Interactive; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; +import org.scijava.widget.Button; import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; @Plugin(type = BdvPlaygroundActionCommand.class, menuPath = CommandConstants.CONTEXT_MENU_ITEMS_ROOT + "Transform>Registration - SIFT") -public class SIFT2DAlignCommand implements BdvPlaygroundActionCommand +public class SIFT2DAlignCommand implements BdvPlaygroundActionCommand, Interactive { static { net.imagej.patcher.LegacyInjector.preinit(); } @Parameter public BdvHandle bdvHandle; + @Parameter ( label = "Compute Alignment", callback = "compute") + private Button compute; + + @Parameter ( label = "Toggle Alignment", callback = "toggle") + private Button toggle; + + private AffineTransform3D previousTransform; + private AffineTransform3D newTransform; + private TransformedSource< ? > transformedSource; + private boolean isAligned; + @Override public void run() + { + // + } + + private void toggle() + { + if ( transformedSource == null ) + { + IJ.showMessage( "Please first [ Compute Alignment ]." ); + return; + } + + if ( isAligned ) + { + transformedSource.setFixedTransform( previousTransform ); + } + else + { + transformedSource.setFixedTransform( newTransform ); + } + + bdvHandle.getViewerPanel().requestRepaint(); + isAligned = ! isAligned; + } + + private void compute() { IJ.log("# SIFT registration" + "\nThe registration is computed in the currently visible 2D plane" + @@ -63,33 +100,26 @@ public void run() if ( movingSac.getSpimSource() instanceof TransformedSource ) { // apply the transformation - AffineTransform3D siftTransform3D = aligner.getSiftTransform3D(); - TransformedSource< ? > transformedSource = ( TransformedSource< ? > ) movingSac.getSpimSource(); - AffineTransform3D previousFixedTransform = new AffineTransform3D(); - transformedSource.getFixedTransform( previousFixedTransform ); - AffineTransform3D newFixedTransform = previousFixedTransform.copy(); - newFixedTransform.preConcatenate( siftTransform3D ); - transformedSource.setFixedTransform( newFixedTransform ); - bdvHandle.getViewerPanel().requestRepaint(); + AffineTransform3D siftTransform = aligner.getSiftTransform3D(); + transformedSource = ( TransformedSource< ? > ) movingSac.getSpimSource(); + previousTransform = new AffineTransform3D(); + transformedSource.getFixedTransform( previousTransform ); + newTransform = previousTransform.copy(); + newTransform.preConcatenate( siftTransform ); + transformedSource.setFixedTransform( newTransform ); + IJ.showMessage( "Transforming " + transformedSource.getName() ); + IJ.showMessage( "Previous Transform: " + previousTransform ); + IJ.showMessage( "Additional SIFT Transform: " + siftTransform ); + IJ.showMessage( "Combined Transform: " + newTransform ); - // ask user whether to accept the transformation - NonBlockingGenericDialog dialog = new NonBlockingGenericDialog( "SIFT Alignment" ); - dialog.addMessage( "Press OK to keep the transformation" ); - dialog.addMessage( "Press Cancel to discard the transformation" ); - dialog.showDialog(); - if ( dialog.wasOKed() ) - { - IJ.log( "Transformed " + movingSac.getSpimSource().getName() + " with " + siftTransform3D ); - } - else if ( dialog.wasCanceled() ) - { - transformedSource.setFixedTransform( previousFixedTransform ); - bdvHandle.getViewerPanel().requestRepaint(); - } + isAligned = true; + bdvHandle.getViewerPanel().requestRepaint(); } else { IJ.log("Cannot apply transformation to image of type " + movingSac.getSpimSource().getClass() ); } } + + } diff --git a/src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java b/src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java index 34716d245..21ac1f8af 100644 --- a/src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java +++ b/src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java @@ -79,6 +79,8 @@ public class SIFT2DAligner private SourceAndConverter< ? > fixedSac; private SourceAndConverter< ? > movingSac; private ScreenShotMaker screenShotMaker; + private static String fixedImageName; + private static String movingImageName; static private class Param { @@ -135,8 +137,9 @@ public SIFT2DAligner( BdvHandle bdvHandle ) public boolean showUI() { - if( p.pixelSize == null ) - p.pixelSize = BdvHandleHelper.getViewerVoxelSpacing( bdvHandle ); + double viewerVoxelSpacing = BdvHandleHelper.getViewerVoxelSpacing( bdvHandle ); + + p.pixelSize = 2 * viewerVoxelSpacing; List< SourceAndConverter< ? > > sourceAndConverters = MoBIEHelper.getVisibleSacs( bdvHandle ); if ( sourceAndConverters.size() < 2 ) @@ -151,8 +154,11 @@ public boolean showUI() final GenericDialog gd = new GenericDialog( "SIFT 2D Aligner" ); - gd.addChoice( "fixed_image :", titles, titles[ 0 ] ); - gd.addChoice( "moving_image :", titles, titles[ 1 ] ); + String fixedDefault = Arrays.asList( titles ).contains( fixedImageName ) ? fixedImageName : titles[ 0 ]; + gd.addChoice( "fixed_image :", titles, fixedDefault ); + + String movingDefault = Arrays.asList( titles ).contains( movingImageName ) ? movingImageName : titles[ 1 ]; + gd.addChoice( "moving_image :", titles, movingDefault ); String voxelUnit = sourceAndConverters.get( 0 ).getSpimSource().getVoxelDimensions().unit(); gd.addNumericField( "pixel_size :", p.pixelSize, 2, 6, voxelUnit ); @@ -178,8 +184,8 @@ public boolean showUI() if (gd.wasCanceled()) return false; - String fixedImageName = gd.getNextChoice(); - String movingImageName = gd.getNextChoice(); + fixedImageName = gd.getNextChoice(); + movingImageName = gd.getNextChoice(); p.pixelSize = gd.getNextNumber(); p.modelIndex = gd.getNextChoiceIndex(); diff --git a/src/main/java/org/embl/mobie/command/open/OpenHCSDatasetCommand.java b/src/main/java/org/embl/mobie/command/open/OpenHCSDatasetCommand.java index 3901671f7..1c49b3ff0 100644 --- a/src/main/java/org/embl/mobie/command/open/OpenHCSDatasetCommand.java +++ b/src/main/java/org/embl/mobie/command/open/OpenHCSDatasetCommand.java @@ -37,7 +37,6 @@ import org.scijava.plugin.Plugin; import org.scijava.widget.Button; -import java.io.File; import java.io.IOException; diff --git a/src/test/java/projects/em_xray_alignment/OpenEMXRAY.java b/src/test/java/projects/em_xray_alignment/OpenEMXRAY.java index dbca1bfb6..37b121179 100644 --- a/src/test/java/projects/em_xray_alignment/OpenEMXRAY.java +++ b/src/test/java/projects/em_xray_alignment/OpenEMXRAY.java @@ -10,7 +10,7 @@ public class OpenEMXRAY { public static void main( String[] args ) throws IOException { - OpenerLogging.setLogging( true ); +// OpenerLogging.setLogging( true ); final ImageJ imageJ = new ImageJ(); imageJ.ui().showUI(); new MoBIE("/Volumes/cba/exchange/em-xray-alignment/mobie" ); From dc8d267978575c386b2352ac50daf384f1e5310c Mon Sep 17 00:00:00 2001 From: Christian Tischer Date: Sat, 30 Dec 2023 22:05:33 +0100 Subject: [PATCH 08/12] Refactor AutomaticRegistration WIP --- .../context/AutomaticRegistrationCommand.java | 227 ++++++++++++++++++ .../context/ScreenShotMakerCommand.java | 14 +- .../embl/mobie/lib/bdv/ScreenShotMaker.java | 66 +---- .../embl/mobie/lib/bdv/view/SliceViewer.java | 2 +- .../registration}/SIFT2DAligner.java | 148 +++--------- .../registration/TurboReg2DAligner.java} | 16 +- .../em_xray_alignment/OpenEMXRAY.java | 4 +- .../em_xray_alignment/OpenEmXraySlices.java | 8 +- 8 files changed, 289 insertions(+), 196 deletions(-) create mode 100644 src/main/java/org/embl/mobie/command/context/AutomaticRegistrationCommand.java rename src/main/java/org/embl/mobie/{command/context => lib/registration}/SIFT2DAligner.java (64%) rename src/main/java/org/embl/mobie/{command/context/SIFT2DAlignCommand.java => lib/registration/TurboReg2DAligner.java} (89%) diff --git a/src/main/java/org/embl/mobie/command/context/AutomaticRegistrationCommand.java b/src/main/java/org/embl/mobie/command/context/AutomaticRegistrationCommand.java new file mode 100644 index 000000000..a73d86fa1 --- /dev/null +++ b/src/main/java/org/embl/mobie/command/context/AutomaticRegistrationCommand.java @@ -0,0 +1,227 @@ +/*- + * #%L + * Fiji viewer for MoBIE projects + * %% + * Copyright (C) 2018 - 2023 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 org.embl.mobie.command.context; + +import bdv.tools.transformation.TransformedSource; +import bdv.util.BdvHandle; +import bdv.viewer.SourceAndConverter; +import ij.CompositeImage; +import ij.IJ; +import ij.ImagePlus; +import ij.ImageStack; +import net.imglib2.realtransform.AffineTransform3D; +import org.embl.mobie.command.CommandConstants; +import org.embl.mobie.lib.MoBIEHelper; +import org.embl.mobie.lib.bdv.ScreenShotMaker; +import org.embl.mobie.lib.registration.SIFT2DAligner; +import org.scijava.Initializable; +import org.scijava.command.DynamicCommand; +import org.scijava.command.Interactive; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.widget.Button; +import sc.fiji.bdvpg.bdv.BdvHandleHelper; +import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Plugin(type = BdvPlaygroundActionCommand.class, menuPath = CommandConstants.CONTEXT_MENU_ITEMS_ROOT + "Transform>Registration - Automatic") +public class AutomaticRegistrationCommand extends DynamicCommand implements BdvPlaygroundActionCommand, Interactive, Initializable +{ + + public static final String TRANSLATION = "Translation"; + public static final String RIGID = "Rigid"; + public static final String SIMILARITY = "Similarity"; + public static final String AFFINE = "Affine"; + + static { net.imagej.patcher.LegacyInjector.preinit(); } + + @Parameter + public BdvHandle bdvHandle; + + @Parameter ( label = "Registration method", choices = {"TurboReg", "SIFT"}) + private String registrationMethod; + + @Parameter(label="Registration voxel size", persist = false, min = "0.0", style="format:#.00000") + public Double voxelSize = 1D; + + @Parameter ( label = "Transformation", choices = { TRANSLATION, RIGID, SIMILARITY, AFFINE }) + private String transformationType; + + @Parameter ( label = "Image A (fixed)", choices = {""} ) + private String imageA; + + @Parameter ( label = "Image B (transformed)", choices = {""} ) + private String imageB; + + @Parameter ( label = "Compute Alignment", callback = "compute") + private Button compute; + + @Parameter ( label = "Toggle Alignment", callback = "toggle") + private Button toggle; + + private AffineTransform3D previousTransform; + private AffineTransform3D newTransform; + private TransformedSource< ? > transformedSource; + private boolean isAligned; + private List< SourceAndConverter< ? > > sourceAndConverters; + private AffineTransform3D alignmentTransform3D; + + @Override + public void initialize() + { + sourceAndConverters = MoBIEHelper.getVisibleSacs( bdvHandle ); + + if ( sourceAndConverters.size() < 2 ) + { + IJ.showMessage( "There must be at least two images visible." ); + return; + } + + final List< String > imageNames = sourceAndConverters.stream() + .map( sac -> sac.getSpimSource().getName() ) + .collect( Collectors.toList() ); + + getInfo().getMutableInput( "imageA", String.class ) + .setChoices( imageNames ); + + getInfo().getMutableInput( "imageB", String.class ) + .setChoices( imageNames ); + + getInfo().getMutableInput("voxelSize", Double.class) + .setValue( this, 2 * BdvHandleHelper.getViewerVoxelSpacing( bdvHandle ) ); + } + + @Override + public void run() + { + // + } + + private void compute() + { + SourceAndConverter< ? > sacA = sourceAndConverters.stream() + .filter( sac -> sac.getSpimSource().getName().equals( imageA ) ) + .findFirst().get(); + + SourceAndConverter< ? > sacB = sourceAndConverters.stream() + .filter( sac -> sac.getSpimSource().getName().equals( imageB ) ) + .findFirst().get(); + + if ( ! ( sacB.getSpimSource() instanceof TransformedSource ) ) + { + IJ.log("Cannot apply transformations to image of type " + sacB.getSpimSource().getClass() ); + return; + } + + ScreenShotMaker screenShotMaker = new ScreenShotMaker( bdvHandle, sacA.getSpimSource().getVoxelDimensions().unit() ); + screenShotMaker.run( Arrays.asList( sacA, sacB ), voxelSize ); + CompositeImage compositeImage = screenShotMaker.getCompositeImagePlus(); + AffineTransform3D canvasToGlobalTransform = screenShotMaker.getCanvasToGlobalTransform(); + + ImageStack stack = compositeImage.getStack(); + ImagePlus impA = new ImagePlus( imageA + " (fixed)", stack.getProcessor( 1 ) ); + ImagePlus impB = new ImagePlus( imageB + " (moving)", stack.getProcessor( 2 ) ); + + // Setting the display ranges is important + // as those will be used by the SIFT for normalising the pixel values + compositeImage.setPosition( 1 ); + impA.getProcessor().setMinAndMax( compositeImage.getDisplayRangeMin(), compositeImage.getDisplayRangeMax() ); + compositeImage.setPosition( 2 ); + impB.getProcessor().setMinAndMax( compositeImage.getDisplayRangeMin(), compositeImage.getDisplayRangeMax() ); + + // the transformation the aligns the two images in 2D + AffineTransform3D canvasTransformation = new AffineTransform3D(); + if ( registrationMethod.equals( "SIFT" ) ) + { + SIFT2DAligner sift2DAligner = new SIFT2DAligner( bdvHandle, impA, impB, transformationType ); + if ( ! sift2DAligner.showUI() ) return; + canvasTransformation = sift2DAligner.getSIFTAlignmentTransform(); + } + else if ( registrationMethod.equals( "TurboReg" ) ) + { + // TODO + } + + // convert the transformation that aligns the images + // within the screenshot canvas to the global 3D coordinate system + AffineTransform3D globalAlignmentTransform = new AffineTransform3D(); + + // global to target canvas... + globalAlignmentTransform.preConcatenate( canvasToGlobalTransform.inverse() ); + + // ...registration within canvas... + globalAlignmentTransform.preConcatenate( canvasTransformation ); + + // ...canvas back to global + globalAlignmentTransform.preConcatenate( canvasToGlobalTransform ); + + // apply transformation + // + transformedSource = ( TransformedSource< ? > ) sacB.getSpimSource(); + previousTransform = new AffineTransform3D(); + transformedSource.getFixedTransform( previousTransform ); + newTransform = previousTransform.copy(); + newTransform.preConcatenate( globalAlignmentTransform ); + transformedSource.setFixedTransform( newTransform ); + IJ.log( "Transforming " + transformedSource.getName() ); + IJ.log( "Previous Transform: " + previousTransform ); + IJ.log( "Additional SIFT Transform: " + globalAlignmentTransform ); + IJ.log( "Combined Transform: " + newTransform ); + + isAligned = true; + bdvHandle.getViewerPanel().requestRepaint(); + } + + + private void toggle() + { + if ( transformedSource == null ) + { + IJ.showMessage( "Please first [ Compute Alignment ]." ); + return; + } + + if ( isAligned ) + { + transformedSource.setFixedTransform( previousTransform ); + } + else + { + transformedSource.setFixedTransform( newTransform ); + } + + bdvHandle.getViewerPanel().requestRepaint(); + isAligned = ! isAligned; + } + + +} 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 15a130a8f..0ce0c18d5 100644 --- a/src/main/java/org/embl/mobie/command/context/ScreenShotMakerCommand.java +++ b/src/main/java/org/embl/mobie/command/context/ScreenShotMakerCommand.java @@ -52,7 +52,7 @@ public class ScreenShotMakerCommand extends DynamicCommand implements BdvPlaygro public static final String CAPTURE_SIZE_PIXELS = "Capture size [pixels]: "; @Parameter - public BdvHandle bdvh; + public BdvHandle bdvHandle; @Parameter(label="Sampling (in below units)", persist = false, callback = "showNumPixels", min = "0.0", style="format:#.00000", stepSize = "0.01") public Double targetSamplingInXY = 1D; @@ -63,8 +63,8 @@ public class ScreenShotMakerCommand extends DynamicCommand implements BdvPlaygro @Override public void run() { - ScreenShotMaker screenShotMaker = new ScreenShotMaker( bdvh, targetSamplingInXY, pixelUnit ); - screenShotMaker.run(); + ScreenShotMaker screenShotMaker = new ScreenShotMaker( bdvHandle, pixelUnit ); + screenShotMaker.run( targetSamplingInXY ); screenShotMaker.getRGBImagePlus().show(); screenShotMaker.getCompositeImagePlus().show(); @@ -81,23 +81,23 @@ public void initialize() { // final MutableModuleItem< String > pixelUnitItem = // getInfo().getMutableInput("pixelUnit", String.class); - String pixelUnit = bdvh.getViewerPanel().state().getCurrentSource().getSpimSource().getVoxelDimensions().unit(); + String pixelUnit = bdvHandle.getViewerPanel().state().getCurrentSource().getSpimSource().getVoxelDimensions().unit(); final ArrayList< String > units = new ArrayList<>(); units.add( pixelUnit ); pixelUnitItem.setChoices( units ); - // set screenshot sampling + // init screenshot sampling // final MutableModuleItem< Double > targetSamplingItem = // getInfo().getMutableInput("targetSamplingInXY", Double.class); - double viewerVoxelSpacing = BdvHandleHelper.getViewerVoxelSpacing( bdvh ); + double viewerVoxelSpacing = BdvHandleHelper.getViewerVoxelSpacing( bdvHandle ); targetSamplingItem.setValue( this, 2 * viewerVoxelSpacing ); } // callback private void showNumPixels() { - final long[] sizeInPixels = ScreenShotMaker.getCaptureImageSizeInPixels( bdvh, targetSamplingInXY ); + final long[] sizeInPixels = ScreenShotMaker.getCaptureImageSizeInPixels( bdvHandle, targetSamplingInXY ); IJ.log( CAPTURE_SIZE_PIXELS + Arrays.toString( sizeInPixels ) ); // final MutableModuleItem< String > message = getInfo().getMutableInput("message", String.class); // message.setValue( this, CAPTURE_SIZE_PIXELS + sizeInPixels[ 0 ] + ", " + sizeInPixels[ 1 ] ); 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 44e09f4ce..a28fe3b05 100644 --- a/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java +++ b/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java @@ -58,7 +58,6 @@ import org.embl.mobie.lib.source.AnnotatedLabelSource; import org.embl.mobie.lib.source.AnnotationType; import sc.fiji.bdvpg.bdv.BdvHandleHelper; -import sc.fiji.bdvpg.services.ISourceAndConverterService; import sc.fiji.bdvpg.services.SourceAndConverterServices; import java.awt.*; @@ -77,19 +76,15 @@ public class ScreenShotMaker static { net.imagej.patcher.LegacyInjector.preinit(); } private final BdvHandle bdvHandle; - private final ISourceAndConverterService sacService; - private double targetVoxelSpacing = 1; - private String physicalUnit = "Pixels"; + private String voxelUnit = "Pixels"; private ImagePlus rgbImagePlus = null; private CompositeImage compositeImagePlus = null; private long[] screenshotDimensions = new long[2]; private AffineTransform3D canvasToGlobalTransform; - public ScreenShotMaker( BdvHandle bdvHandle, Double pixelSize, String pixelUnit ) { + public ScreenShotMaker( BdvHandle bdvHandle, String voxelUnit ) { this.bdvHandle = bdvHandle; - this.sacService = SourceAndConverterServices.getSourceAndConverterService(); - this.targetVoxelSpacing = pixelSize; - this.physicalUnit = pixelUnit; + this.voxelUnit = voxelUnit; } public ImagePlus getRGBImagePlus() @@ -102,13 +97,13 @@ public CompositeImage getCompositeImagePlus() return compositeImagePlus; } - public void run() + public void run( Double targetSamplingInXY ) { List< SourceAndConverter< ? > > sacs = getVisibleSourceAndConverters(); - run( sacs ); + run( sacs, targetSamplingInXY ); } - public void run( List< SourceAndConverter< ? > > sacs ) + public void run( List< SourceAndConverter< ? > > sacs, double targetVoxelSpacing ) { if ( sacs.isEmpty() ) { @@ -231,8 +226,7 @@ public void run( List< SourceAndConverter< ? > > sacs ) floatCaptures.add( rawCapture ); argbSources.add( argbCapture ); - // colors.add( getSourceColor( bdv, sourceIndex ) ); Not used, show GrayScale - displayRanges.add( BdvHandleHelper.getDisplayRange( sacService.getConverterSetup( sac ) ) ); + displayRanges.add( BdvHandleHelper.getDisplayRange( SourceAndConverterServices.getSourceAndConverterService().getConverterSetup( sac ) ) ); } IJ.log( "Fetched data in " + ( System.currentTimeMillis() - currentTimeMillis ) + " ms." ); @@ -242,8 +236,8 @@ public void run( List< SourceAndConverter< ? > > sacs ) if ( ! floatCaptures.isEmpty() ) { - rgbImagePlus = createRGBImagePlus( physicalUnit, argbSources, voxelSpacing, sacs ); - compositeImagePlus = createCompositeImagePlus( voxelSpacing, physicalUnit, floatCaptures, colors, displayRanges ); + rgbImagePlus = createRGBImagePlus( voxelUnit, argbSources, voxelSpacing, sacs ); + compositeImagePlus = createCompositeImagePlus( voxelSpacing, voxelUnit, floatCaptures, colors, displayRanges ); } } @@ -350,44 +344,6 @@ private void createARGBprojection( ArrayList< RandomAccessibleInterval< ARGBType } } -// 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 ); -// } -// } -// - - public static long[] getCaptureImageSizeInPixels( BdvHandle bdvHandle, double samplingXY ) { final double viewerVoxelSpacing = getViewerVoxelSpacing( bdvHandle ); @@ -413,9 +369,9 @@ private static double[] getBdvWindowPhysicalSize( BdvHandle bdvHandle, double vi return bdvWindowPhysicalSize; } - private Cursor< ARGBType >[] getCursors( ArrayList< RandomAccessibleInterval< ARGBType > > argbCaptures, int numVisibleSources ) + private static Cursor< ARGBType >[] getCursors( ArrayList< RandomAccessibleInterval< ARGBType > > argbCaptures, int numVisibleSources ) { - Cursor< ARGBType >[] cursors = new Cursor[ numVisibleSources ]; + Cursor[] cursors = new Cursor[ numVisibleSources ]; for ( int i = 0; i < numVisibleSources; i++ ) cursors[ i ] = Views.iterable( argbCaptures.get( i ) ).cursor(); return cursors; diff --git a/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java b/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java index e03979951..d3b5c6b94 100644 --- a/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java +++ b/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java @@ -152,7 +152,7 @@ private void installContextMenuAndKeyboardShortCuts( ) actions.add( SourceAndConverterService.getCommandName( ViewerTransformLoggerCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( SourceInfoLoggerCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( BigWarpRegistrationCommand.class ) ); - actions.add( SourceAndConverterService.getCommandName( SIFT2DAlignCommand.class ) ); + actions.add( SourceAndConverterService.getCommandName( AutomaticRegistrationCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( ManualRegistrationCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( FlipCommand.class ) ); actions.add( UNDO_SEGMENT_SELECTIONS ); diff --git a/src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java b/src/main/java/org/embl/mobie/lib/registration/SIFT2DAligner.java similarity index 64% rename from src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java rename to src/main/java/org/embl/mobie/lib/registration/SIFT2DAligner.java index 21ac1f8af..13d1d6f81 100644 --- a/src/main/java/org/embl/mobie/command/context/SIFT2DAligner.java +++ b/src/main/java/org/embl/mobie/lib/registration/SIFT2DAligner.java @@ -1,11 +1,8 @@ -package org.embl.mobie.command.context; +package org.embl.mobie.lib.registration; import bdv.util.BdvHandle; -import bdv.viewer.SourceAndConverter; -import ij.CompositeImage; import ij.IJ; import ij.ImagePlus; -import ij.ImageStack; import ij.gui.GenericDialog; import java.text.DecimalFormat; @@ -21,9 +18,7 @@ import mpicbg.imagefeatures.FloatArray2DSIFT; import mpicbg.models.*; import net.imglib2.realtransform.AffineTransform3D; -import org.embl.mobie.lib.MoBIEHelper; -import org.embl.mobie.lib.bdv.ScreenShotMaker; -import sc.fiji.bdvpg.bdv.BdvHandleHelper; +import org.embl.mobie.command.context.AutomaticRegistrationCommand; /** * Extract landmark correspondences in two images as PointRoi. @@ -69,18 +64,11 @@ public class SIFT2DAligner final static private DecimalFormat decimalFormat = new DecimalFormat(); final static private DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(); private final BdvHandle bdvHandle; - - private ImagePlus imp1; - private ImagePlus imp2; - + private final ImagePlus impA; + private final ImagePlus impB; final private List< Feature > fs1 = new ArrayList< Feature >(); final private List< Feature > fs2 = new ArrayList< Feature >();; - private AffineTransform3D affineTransform3D; - private SourceAndConverter< ? > fixedSac; - private SourceAndConverter< ? > movingSac; - private ScreenShotMaker screenShotMaker; - private static String fixedImageName; - private static String movingImageName; + private AffineTransform3D siftTransform; static private class Param { @@ -118,52 +106,30 @@ static private class Param /** * Implemeted transformation models for choice */ - final static public String[] modelStrings = new String[]{ "Translation", "Rigid", "Similarity", "Affine", "Perspective" }; - public int modelIndex = 3; + public String transformationType; } final static private Param p = new Param(); - public SIFT2DAligner( BdvHandle bdvHandle ) + public SIFT2DAligner( BdvHandle bdvHandle, ImagePlus impA, ImagePlus impB, String transformationType ) { this.bdvHandle = bdvHandle; + this.impA = impA; + this.impB = impB; decimalFormatSymbols.setGroupingSeparator( ',' ); decimalFormatSymbols.setDecimalSeparator( '.' ); decimalFormat.setDecimalFormatSymbols( decimalFormatSymbols ); decimalFormat.setMaximumFractionDigits( 3 ); decimalFormat.setMinimumFractionDigits( 3 ); + + p.transformationType = transformationType; } public boolean showUI() { - double viewerVoxelSpacing = BdvHandleHelper.getViewerVoxelSpacing( bdvHandle ); - - p.pixelSize = 2 * viewerVoxelSpacing; - - List< SourceAndConverter< ? > > sourceAndConverters = MoBIEHelper.getVisibleSacs( bdvHandle ); - if ( sourceAndConverters.size() < 2 ) - { - IJ.showMessage( "There must be at least two images visible." ); - return false; - } - - final String[] titles = sourceAndConverters.stream() - .map( sac -> sac.getSpimSource().getName() ) - .toArray( String[]::new ); - final GenericDialog gd = new GenericDialog( "SIFT 2D Aligner" ); - String fixedDefault = Arrays.asList( titles ).contains( fixedImageName ) ? fixedImageName : titles[ 0 ]; - gd.addChoice( "fixed_image :", titles, fixedDefault ); - - String movingDefault = Arrays.asList( titles ).contains( movingImageName ) ? movingImageName : titles[ 1 ]; - gd.addChoice( "moving_image :", titles, movingDefault ); - String voxelUnit = sourceAndConverters.get( 0 ).getSpimSource().getVoxelDimensions().unit(); - - gd.addNumericField( "pixel_size :", p.pixelSize, 2, 6, voxelUnit ); - gd.addChoice( "expected_transformation :", Param.modelStrings, Param.modelStrings[ p.modelIndex ] ); - gd.addNumericField( "initial_gaussian_blur :", p.sift.initialSigma, 2, 6, "px" ); gd.addNumericField( "steps_per_scale_octave :", p.sift.steps, 0 ); gd.addNumericField( "minimum_image_size :", p.sift.minOctaveSize, 0, 6, "px" ); @@ -178,18 +144,10 @@ public boolean showUI() gd.addNumericField( "minimal_inlier_ratio :", p.minInlierRatio, 2 ); gd.addNumericField( "minimal_number_of_inliers :", p.minNumInliers, 0 ); - gd.addCheckbox( "show images with detected landmarks", false ); - gd.showDialog(); if (gd.wasCanceled()) return false; - fixedImageName = gd.getNextChoice(); - movingImageName = gd.getNextChoice(); - - p.pixelSize = gd.getNextNumber(); - p.modelIndex = gd.getNextChoiceIndex(); - p.sift.initialSigma = ( float )gd.getNextNumber(); p.sift.steps = ( int )gd.getNextNumber(); p.sift.minOctaveSize = ( int )gd.getNextNumber(); @@ -205,50 +163,17 @@ public boolean showUI() p.minNumInliers = ( int )gd.getNextNumber(); p.showLandmarks = gd.getNextBoolean(); - - return run( sourceAndConverters, fixedImageName, movingImageName ); + + return run( impA, impB ); } - - public boolean run( List< SourceAndConverter< ? > > sourceAndConverters, String fixedImageName, String movingImageName ) - { - extractImages( sourceAndConverters, fixedImageName, movingImageName ); - - return run( imp1, imp2 ); - } - - private void extractImages( List< SourceAndConverter< ? > > sourceAndConverters, String fixedImageName, String movingImageName ) - { - fixedSac = sourceAndConverters.stream() - .filter( sac -> sac.getSpimSource().getName().equals( fixedImageName ) ) - .findFirst().get(); - - movingSac = sourceAndConverters.stream() - .filter( sac -> sac.getSpimSource().getName().equals( movingImageName ) ) - .findFirst().get(); - - screenShotMaker = new ScreenShotMaker( bdvHandle, p.pixelSize, fixedSac.getSpimSource().getVoxelDimensions().unit() ); - screenShotMaker.run( Arrays.asList( fixedSac, movingSac ) ); - CompositeImage compositeImage = screenShotMaker.getCompositeImagePlus(); - - ImageStack stack = compositeImage.getStack(); - imp1 = new ImagePlus( "fixed", stack.getProcessor( 1 ) ); - imp2 = new ImagePlus( "moving", stack.getProcessor( 2 ) ); - - // Setting the display ranges is important - // as those will be used by the SIFT for normalising the pixel values - compositeImage.setPosition( 1 ); - imp1.getProcessor().setMinAndMax( compositeImage.getDisplayRangeMin(), compositeImage.getDisplayRangeMax() ); - compositeImage.setPosition( 2 ); - imp2.getProcessor().setMinAndMax( compositeImage.getDisplayRangeMin(), compositeImage.getDisplayRangeMax() ); - } - + /** * Execute with current parameters * * @return * boolean whether a model was found */ - private boolean run( final ImagePlus imp1, final ImagePlus imp2) { + private boolean run( final ImagePlus imp1, final ImagePlus imp2 ) { // cleanup fs1.clear(); @@ -290,24 +215,24 @@ private boolean run( final ImagePlus imp1, final ImagePlus imp2) { inliers = new ArrayList< PointMatch >(); AbstractModel< ? > model; - switch ( p.modelIndex ) + switch ( p.transformationType ) { - case 0: + case AutomaticRegistrationCommand.TRANSLATION: model = new TranslationModel2D(); break; - case 1: + case AutomaticRegistrationCommand.RIGID: model = new RigidModel2D(); break; - case 2: + case AutomaticRegistrationCommand.SIMILARITY: model = new SimilarityModel2D(); break; - case 3: + case AutomaticRegistrationCommand.AFFINE: model = new AffineModel2D(); break; - case 4: - // TODO: What is this? - model = new HomographyModel2D(); - break; +// case 4: +// // TODO: What is this? +// model = new HomographyModel2D(); +// break; default: return modelFound; } @@ -325,24 +250,14 @@ private boolean run( final ImagePlus imp1, final ImagePlus imp2) { if ( model instanceof AbstractAffineModel2D ) { - affineTransform3D = new AffineTransform3D(); - - // global to target canvas - AffineTransform3D canvasToGlobalTransform = screenShotMaker.getCanvasToGlobalTransform(); - affineTransform3D.preConcatenate( canvasToGlobalTransform.inverse() ); - - // sift within canvas final double[] a = new double[6]; ( ( AbstractAffineModel2D< ? > ) model ).toArray( a ); - AffineTransform3D canvasSiftTransform = new AffineTransform3D(); - canvasSiftTransform.set( + siftTransform = new AffineTransform3D(); + siftTransform.set( a[0], a[2], 0, a[4], a[1], a[3], 0, a[5], 0, 0, 1, 0); - affineTransform3D.preConcatenate( canvasSiftTransform.inverse() ); - - // canvas to global - affineTransform3D.preConcatenate( canvasToGlobalTransform ); + siftTransform = siftTransform.inverse(); } else IJ.showMessage( "Cannot apply " + model ); @@ -384,13 +299,8 @@ private boolean run( final ImagePlus imp1, final ImagePlus imp2) { return modelFound; } - public AffineTransform3D getSiftTransform3D() - { - return affineTransform3D; - } - - public SourceAndConverter< ? > getMovingSac() + public AffineTransform3D getSIFTAlignmentTransform() { - return movingSac; + return siftTransform; } } \ No newline at end of file diff --git a/src/main/java/org/embl/mobie/command/context/SIFT2DAlignCommand.java b/src/main/java/org/embl/mobie/lib/registration/TurboReg2DAligner.java similarity index 89% rename from src/main/java/org/embl/mobie/command/context/SIFT2DAlignCommand.java rename to src/main/java/org/embl/mobie/lib/registration/TurboReg2DAligner.java index afdea4212..b42be0490 100644 --- a/src/main/java/org/embl/mobie/command/context/SIFT2DAlignCommand.java +++ b/src/main/java/org/embl/mobie/lib/registration/TurboReg2DAligner.java @@ -26,7 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package org.embl.mobie.command.context; +package org.embl.mobie.lib.registration; import bdv.tools.transformation.TransformedSource; import bdv.util.BdvHandle; @@ -41,7 +41,7 @@ import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; @Plugin(type = BdvPlaygroundActionCommand.class, menuPath = CommandConstants.CONTEXT_MENU_ITEMS_ROOT + "Transform>Registration - SIFT") -public class SIFT2DAlignCommand implements BdvPlaygroundActionCommand, Interactive +public class TurboReg2DAligner implements BdvPlaygroundActionCommand, Interactive { static { net.imagej.patcher.LegacyInjector.preinit(); } @@ -92,7 +92,7 @@ private void compute() "\nThe registration is computed in the currently visible 2D plane" + "\nand then applied to the full image in 3D."); // start the alignment, which has its own GUI - SIFT2DAligner aligner = new SIFT2DAligner( bdvHandle ); + SIFT2DAligner aligner = new SIFT2DAligner( bdvHandle, sacA, sacB, method ); if( ! aligner.showUI() ) return; // apply transformation @@ -107,10 +107,10 @@ private void compute() newTransform = previousTransform.copy(); newTransform.preConcatenate( siftTransform ); transformedSource.setFixedTransform( newTransform ); - IJ.showMessage( "Transforming " + transformedSource.getName() ); - IJ.showMessage( "Previous Transform: " + previousTransform ); - IJ.showMessage( "Additional SIFT Transform: " + siftTransform ); - IJ.showMessage( "Combined Transform: " + newTransform ); + IJ.log( "Transforming " + transformedSource.getName() ); + IJ.log( "Previous Transform: " + previousTransform ); + IJ.log( "Additional SIFT Transform: " + siftTransform ); + IJ.log( "Combined Transform: " + newTransform ); isAligned = true; bdvHandle.getViewerPanel().requestRepaint(); @@ -120,6 +120,4 @@ private void compute() IJ.log("Cannot apply transformation to image of type " + movingSac.getSpimSource().getClass() ); } } - - } diff --git a/src/test/java/projects/em_xray_alignment/OpenEMXRAY.java b/src/test/java/projects/em_xray_alignment/OpenEMXRAY.java index 37b121179..8b8a9ae01 100644 --- a/src/test/java/projects/em_xray_alignment/OpenEMXRAY.java +++ b/src/test/java/projects/em_xray_alignment/OpenEMXRAY.java @@ -2,6 +2,7 @@ import net.imagej.ImageJ; import org.embl.mobie.MoBIE; +import org.embl.mobie.MoBIESettings; import org.embl.mobie.io.OpenerLogging; import java.io.IOException; @@ -13,6 +14,7 @@ public static void main( String[] args ) throws IOException // OpenerLogging.setLogging( true ); final ImageJ imageJ = new ImageJ(); imageJ.ui().showUI(); - new MoBIE("/Volumes/cba/exchange/em-xray-alignment/mobie" ); + MoBIESettings settings = new MoBIESettings().view( "em-sift-affine--xray-u8-manual-euler" ); + new MoBIE("/Volumes/cba/exchange/em-xray-alignment/mobie", settings ); } } diff --git a/src/test/java/projects/em_xray_alignment/OpenEmXraySlices.java b/src/test/java/projects/em_xray_alignment/OpenEmXraySlices.java index 6373cf65e..a37347249 100644 --- a/src/test/java/projects/em_xray_alignment/OpenEmXraySlices.java +++ b/src/test/java/projects/em_xray_alignment/OpenEmXraySlices.java @@ -2,7 +2,7 @@ import net.imagej.ImageJ; import org.embl.mobie.MoBIE; -import org.embl.mobie.command.context.SIFT2DAlignCommand; +import org.embl.mobie.command.context.AutomaticRegistrationCommand; import org.embl.mobie.command.open.OpenMultipleImagesAndLabelsCommand; import java.io.File; @@ -19,8 +19,8 @@ public static void main( String[] args ) throws IOException command.image0 = new File("/Users/tischer/Desktop/em-xray/xray-slice-ds-0.tif"); command.image1 = new File("/Users/tischer/Desktop/em-xray/em-slice-ds-0.tif"); command.run(); - SIFT2DAlignCommand sift2DAlignCommand = new SIFT2DAlignCommand(); - sift2DAlignCommand.bdvHandle = MoBIE.getInstance().getViewManager().getSliceViewer().getBdvHandle(); - sift2DAlignCommand.run(); + AutomaticRegistrationCommand automaticRegistrationCommand = new AutomaticRegistrationCommand(); + automaticRegistrationCommand.bdvHandle = MoBIE.getInstance().getViewManager().getSliceViewer().getBdvHandle(); + automaticRegistrationCommand.run(); } } From f7337449ad7a875c75b643c8e575a60f36365456 Mon Sep 17 00:00:00 2001 From: Christian Tischer Date: Sun, 31 Dec 2023 01:30:26 +0100 Subject: [PATCH 09/12] TurboReg integration working --- pom.xml | 8 +- .../context/AutomaticRegistrationCommand.java | 55 +-- .../SIFT2DAligner.java | 26 +- .../mobie/lib/align/TurboReg2DAligner.java | 356 ++++++++++++++++++ .../lib/registration/TurboReg2DAligner.java | 123 ------ 5 files changed, 402 insertions(+), 166 deletions(-) rename src/main/java/org/embl/mobie/lib/{registration => align}/SIFT2DAligner.java (94%) create mode 100644 src/main/java/org/embl/mobie/lib/align/TurboReg2DAligner.java delete mode 100644 src/main/java/org/embl/mobie/lib/registration/TurboReg2DAligner.java diff --git a/pom.xml b/pom.xml index c62706a45..19c05e241 100644 --- a/pom.xml +++ b/pom.xml @@ -123,9 +123,15 @@ imagej-legacy
+ mpicbg mpicbg - + + + + sc.fiji + TurboReg_ + 2.0.0 sc.fiji diff --git a/src/main/java/org/embl/mobie/command/context/AutomaticRegistrationCommand.java b/src/main/java/org/embl/mobie/command/context/AutomaticRegistrationCommand.java index a73d86fa1..cb429b49f 100644 --- a/src/main/java/org/embl/mobie/command/context/AutomaticRegistrationCommand.java +++ b/src/main/java/org/embl/mobie/command/context/AutomaticRegistrationCommand.java @@ -38,8 +38,9 @@ import net.imglib2.realtransform.AffineTransform3D; import org.embl.mobie.command.CommandConstants; import org.embl.mobie.lib.MoBIEHelper; +import org.embl.mobie.lib.align.TurboReg2DAligner; import org.embl.mobie.lib.bdv.ScreenShotMaker; -import org.embl.mobie.lib.registration.SIFT2DAligner; +import org.embl.mobie.lib.align.SIFT2DAligner; import org.scijava.Initializable; import org.scijava.command.DynamicCommand; import org.scijava.command.Interactive; @@ -67,14 +68,14 @@ public class AutomaticRegistrationCommand extends DynamicCommand implements BdvP @Parameter public BdvHandle bdvHandle; - @Parameter ( label = "Registration method", choices = {"TurboReg", "SIFT"}) - private String registrationMethod; + @Parameter ( label = "Registration Method", choices = {"TurboReg", "SIFT"} ) + private String registrationMethod = "SIFT"; - @Parameter(label="Registration voxel size", persist = false, min = "0.0", style="format:#.00000") + @Parameter(label="Registration Voxel Size", persist = false, min = "0.0", style="format:#.00000") public Double voxelSize = 1D; - @Parameter ( label = "Transformation", choices = { TRANSLATION, RIGID, SIMILARITY, AFFINE }) - private String transformationType; + @Parameter ( label = "Transformation", choices = { TRANSLATION, RIGID, SIMILARITY, AFFINE } ) + private String transformationType = TRANSLATION; @Parameter ( label = "Image A (fixed)", choices = {""} ) private String imageA; @@ -82,6 +83,9 @@ public class AutomaticRegistrationCommand extends DynamicCommand implements BdvP @Parameter ( label = "Image B (transformed)", choices = {""} ) private String imageB; + @Parameter ( label = "Show Intermediates" ) + private Boolean showIntermediates = false; + @Parameter ( label = "Compute Alignment", callback = "compute") private Button compute; @@ -142,6 +146,8 @@ private void compute() return; } + // create two 2D ImagePlus that are to be aligned + // ScreenShotMaker screenShotMaker = new ScreenShotMaker( bdvHandle, sacA.getSpimSource().getVoxelDimensions().unit() ); screenShotMaker.run( Arrays.asList( sacA, sacB ), voxelSize ); CompositeImage compositeImage = screenShotMaker.getCompositeImagePlus(); @@ -151,57 +157,58 @@ private void compute() ImagePlus impA = new ImagePlus( imageA + " (fixed)", stack.getProcessor( 1 ) ); ImagePlus impB = new ImagePlus( imageB + " (moving)", stack.getProcessor( 2 ) ); - // Setting the display ranges is important + // set the display ranges // as those will be used by the SIFT for normalising the pixel values compositeImage.setPosition( 1 ); impA.getProcessor().setMinAndMax( compositeImage.getDisplayRangeMin(), compositeImage.getDisplayRangeMax() ); compositeImage.setPosition( 2 ); impB.getProcessor().setMinAndMax( compositeImage.getDisplayRangeMin(), compositeImage.getDisplayRangeMax() ); - // the transformation the aligns the two images in 2D - AffineTransform3D canvasTransformation = new AffineTransform3D(); + // compute the transformation that aligns the two images in 2D + // + AffineTransform3D localRegistration = new AffineTransform3D(); if ( registrationMethod.equals( "SIFT" ) ) { - SIFT2DAligner sift2DAligner = new SIFT2DAligner( bdvHandle, impA, impB, transformationType ); - if ( ! sift2DAligner.showUI() ) return; - canvasTransformation = sift2DAligner.getSIFTAlignmentTransform(); + SIFT2DAligner sift2DAligner = new SIFT2DAligner( impA, impB, transformationType ); + if ( ! sift2DAligner.run( showIntermediates ) ) return; + localRegistration = sift2DAligner.getAlignmentTransform(); } else if ( registrationMethod.equals( "TurboReg" ) ) { - // TODO + TurboReg2DAligner turboReg2DAligner = new TurboReg2DAligner( impA, impB, transformationType ); + turboReg2DAligner.run( showIntermediates ); + localRegistration = turboReg2DAligner.getAlignmentTransform(); } - // convert the transformation that aligns the images - // within the screenshot canvas to the global 3D coordinate system - AffineTransform3D globalAlignmentTransform = new AffineTransform3D(); + // convert the transformation that aligns the images in 2D + // to the global 3D coordinate system + AffineTransform3D globalRegistration = new AffineTransform3D(); // global to target canvas... - globalAlignmentTransform.preConcatenate( canvasToGlobalTransform.inverse() ); + globalRegistration.preConcatenate( canvasToGlobalTransform.inverse() ); // ...registration within canvas... - globalAlignmentTransform.preConcatenate( canvasTransformation ); + globalRegistration.preConcatenate( localRegistration ); // ...canvas back to global - globalAlignmentTransform.preConcatenate( canvasToGlobalTransform ); + globalRegistration.preConcatenate( canvasToGlobalTransform ); - // apply transformation + // apply the transformation to imageB // transformedSource = ( TransformedSource< ? > ) sacB.getSpimSource(); previousTransform = new AffineTransform3D(); transformedSource.getFixedTransform( previousTransform ); newTransform = previousTransform.copy(); - newTransform.preConcatenate( globalAlignmentTransform ); + newTransform.preConcatenate( globalRegistration ); transformedSource.setFixedTransform( newTransform ); IJ.log( "Transforming " + transformedSource.getName() ); IJ.log( "Previous Transform: " + previousTransform ); - IJ.log( "Additional SIFT Transform: " + globalAlignmentTransform ); + IJ.log( "Additional SIFT Transform: " + globalRegistration ); IJ.log( "Combined Transform: " + newTransform ); - isAligned = true; bdvHandle.getViewerPanel().requestRepaint(); } - private void toggle() { if ( transformedSource == null ) diff --git a/src/main/java/org/embl/mobie/lib/registration/SIFT2DAligner.java b/src/main/java/org/embl/mobie/lib/align/SIFT2DAligner.java similarity index 94% rename from src/main/java/org/embl/mobie/lib/registration/SIFT2DAligner.java rename to src/main/java/org/embl/mobie/lib/align/SIFT2DAligner.java index 13d1d6f81..1e8941f6d 100644 --- a/src/main/java/org/embl/mobie/lib/registration/SIFT2DAligner.java +++ b/src/main/java/org/embl/mobie/lib/align/SIFT2DAligner.java @@ -1,6 +1,5 @@ -package org.embl.mobie.lib.registration; +package org.embl.mobie.lib.align; -import bdv.util.BdvHandle; import ij.IJ; import ij.ImagePlus; import ij.gui.GenericDialog; @@ -8,7 +7,6 @@ import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import mpicbg.ij.FeatureTransform; @@ -63,7 +61,6 @@ public class SIFT2DAligner { final static private DecimalFormat decimalFormat = new DecimalFormat(); final static private DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(); - private final BdvHandle bdvHandle; private final ImagePlus impA; private final ImagePlus impB; final private List< Feature > fs1 = new ArrayList< Feature >(); @@ -98,11 +95,6 @@ static private class Param */ public int minNumInliers = 7; - /** - * Whether to show the detected landmarks - */ - public boolean showLandmarks = false; - /** * Implemeted transformation models for choice */ @@ -111,9 +103,8 @@ static private class Param final static private Param p = new Param(); - public SIFT2DAligner( BdvHandle bdvHandle, ImagePlus impA, ImagePlus impB, String transformationType ) + public SIFT2DAligner( ImagePlus impA, ImagePlus impB, String transformationType ) { - this.bdvHandle = bdvHandle; this.impA = impA; this.impB = impB; @@ -126,7 +117,8 @@ public SIFT2DAligner( BdvHandle bdvHandle, ImagePlus impA, ImagePlus impB, Strin p.transformationType = transformationType; } - public boolean showUI() + // TODO: make it an extra optional step to adapt the parameters + public boolean run( Boolean showIntermediates ) { final GenericDialog gd = new GenericDialog( "SIFT 2D Aligner" ); @@ -161,10 +153,8 @@ public boolean showUI() p.maxEpsilon = ( float )gd.getNextNumber(); p.minInlierRatio = ( float )gd.getNextNumber(); p.minNumInliers = ( int )gd.getNextNumber(); - - p.showLandmarks = gd.getNextBoolean(); - return run( impA, impB ); + return run( impA, impB, showIntermediates ); } /** @@ -173,7 +163,7 @@ public boolean showUI() * @return * boolean whether a model was found */ - private boolean run( final ImagePlus imp1, final ImagePlus imp2 ) { + private boolean run( final ImagePlus imp1, final ImagePlus imp2, Boolean showIntermediates ) { // cleanup fs1.clear(); @@ -286,7 +276,7 @@ private boolean run( final ImagePlus imp1, final ImagePlus imp2 ) { IJ.log( candidates.size() + " corresponding features identified." ); } - if ( ! inliers.isEmpty() && p.showLandmarks ) + if ( ! inliers.isEmpty() && showIntermediates ) { imp1.show(); imp2.show(); @@ -299,7 +289,7 @@ private boolean run( final ImagePlus imp1, final ImagePlus imp2 ) { return modelFound; } - public AffineTransform3D getSIFTAlignmentTransform() + public AffineTransform3D getAlignmentTransform() { return siftTransform; } diff --git a/src/main/java/org/embl/mobie/lib/align/TurboReg2DAligner.java b/src/main/java/org/embl/mobie/lib/align/TurboReg2DAligner.java new file mode 100644 index 000000000..5c5f23430 --- /dev/null +++ b/src/main/java/org/embl/mobie/lib/align/TurboReg2DAligner.java @@ -0,0 +1,356 @@ +package org.embl.mobie.lib.align; + +import ij.IJ; +import ij.ImagePlus; +import ij.io.FileSaver; +import mpicbg.imagefeatures.Feature; +import net.imglib2.realtransform.AffineTransform3D; +import org.embl.mobie.command.context.AutomaticRegistrationCommand; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +public class TurboReg2DAligner +{ + private final ImagePlus source; + private final ImagePlus target; + private final String transformationType; + private double[][] transformationMatrix; + + public TurboReg2DAligner( ImagePlus source, ImagePlus target, String transformationType ) + { + this.source = source; + this.target = target; + this.transformationType = transformationType; + } + + public boolean run( Boolean showIntermediates ) + { + int width = source.getWidth(); + int height = source.getHeight(); + + // Code adapted from + // https://github.com/fiji-BIG/StackReg/blob/master/src/main/java/StackReg_.java#L1021C1-L1104C20 + final FileSaver sourceFile = new FileSaver(source); + final String sourcePathAndFileName = IJ.getDirectory("temp") + "source.tif"; + sourceFile.saveAsTiff(sourcePathAndFileName); + final FileSaver targetFile = new FileSaver(target); + final String targetPathAndFileName = IJ.getDirectory("temp") + "target.tif"; + targetFile.saveAsTiff(targetPathAndFileName); + + Object turboReg; + Method method; + switch (transformationType) { + case AutomaticRegistrationCommand.TRANSLATION: { + turboReg = IJ.runPlugIn("TurboReg_", "-align" + + " -file " + sourcePathAndFileName + + " 0 0 " + (width - 1) + " " + (height - 1) + + " -file " + targetPathAndFileName + + " 0 0 " + (width - 1) + " " + (height - 1) + + " -translation" + + " " + (width / 2) + " " + (height / 2) + + " " + (width / 2) + " " + (height / 2) + + " -hideOutput" + ); + break; + } + case AutomaticRegistrationCommand.RIGID: { + turboReg = IJ.runPlugIn("TurboReg_", "-align" + + " -file " + sourcePathAndFileName + + " 0 0 " + (width - 1) + " " + (height - 1) + + " -file " + targetPathAndFileName + + " 0 0 " + (width - 1) + " " + (height - 1) + + " -rigidBody" + + " " + (width / 2) + " " + (height / 2) + + " " + (width / 2) + " " + (height / 2) + + " " + (width / 2) + " " + (height / 4) + + " " + (width / 2) + " " + (height / 4) + + " " + (width / 2) + " " + ((3 * height) / 4) + + " " + (width / 2) + " " + ((3 * height) / 4) + + " -hideOutput" + ); + break; + } + case AutomaticRegistrationCommand.SIMILARITY: { + turboReg = IJ.runPlugIn("TurboReg_", "-align" + + " -file " + sourcePathAndFileName + + " 0 0 " + (width - 1) + " " + (height - 1) + + " -file " + targetPathAndFileName + + " 0 0 " + (width - 1) + " " + (height - 1) + + " -scaledRotation" + + " " + (width / 4) + " " + (height / 2) + + " " + (width / 4) + " " + (height / 2) + + " " + ((3 * width) / 4) + " " + (height / 2) + + " " + ((3 * width) / 4) + " " + (height / 2) + + " -hideOutput" + ); + break; + } + case AutomaticRegistrationCommand.AFFINE: { + turboReg = IJ.runPlugIn("TurboReg_", "-align" + + " -file " + sourcePathAndFileName + + " 0 0 " + (width - 1) + " " + (height - 1) + + " -file " + targetPathAndFileName + + " 0 0 " + (width - 1) + " " + (height - 1) + + " -affine" + + " " + (width / 2) + " " + (height / 4) + + " " + (width / 2) + " " + (height / 4) + + " " + (width / 4) + " " + ((3 * height) / 4) + + " " + (width / 4) + " " + ((3 * height) / 4) + + " " + ((3 * width) / 4) + " " + ((3 * height) / 4) + + " " + ((3 * width) / 4) + " " + ((3 * height) / 4) + + " -hideOutput" + ); + break; + } + default: { + IJ.error("Unsupported transformation: " + transformationType); + return false; + } + } + try + { + method = turboReg.getClass().getMethod( "getSourcePoints", null ); + double[][] sourcePoints = ( ( double[][] ) method.invoke( turboReg, null ) ); + method = turboReg.getClass().getMethod( "getTargetPoints", null ); + double[][] targetPoints = ( ( double[][] ) method.invoke( turboReg, null ) ); + // If this is a licensing issue, we could probably use methods in BigWarp + // or mpicbg to compute the transformation from a set of points. + transformationMatrix = getTransformationMatrix( targetPoints, sourcePoints, transformationType ); + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + return true; + } + + // Code copied and slightly adapted from + // https://github.com/fiji-BIG/StackReg/blob/master/src/main/java/StackReg_.java#L1021C1-L1104C20 + private double[][] getTransformationMatrix(double[][] fromCoord, double[][] toCoord, String transformation) { + double[][] matrix; + matrix = new double[3][3]; + double[][] a; + double[] v; + int i; + int j; + label81: + switch (transformation) { + case AutomaticRegistrationCommand.TRANSLATION: + matrix[0][0] = 1.0; + matrix[0][1] = 0.0; + matrix[0][2] = toCoord[0][0] - fromCoord[0][0]; + matrix[1][0] = 0.0; + matrix[1][1] = 1.0; + matrix[1][2] = toCoord[0][1] - fromCoord[0][1]; + break; + case AutomaticRegistrationCommand.RIGID: + double angle = Math.atan2(fromCoord[2][0] - fromCoord[1][0], fromCoord[2][1] - fromCoord[1][1]) - Math.atan2(toCoord[2][0] - toCoord[1][0], toCoord[2][1] - toCoord[1][1]); + double c = Math.cos(angle); + double s = Math.sin(angle); + matrix[0][0] = c; + matrix[0][1] = -s; + matrix[0][2] = toCoord[0][0] - c * fromCoord[0][0] + s * fromCoord[0][1]; + matrix[1][0] = s; + matrix[1][1] = c; + matrix[1][2] = toCoord[0][1] - s * fromCoord[0][0] - c * fromCoord[0][1]; + break; + case AutomaticRegistrationCommand.SIMILARITY: + a = new double[3][3]; + v = new double[3]; + a[0][0] = fromCoord[0][0]; + a[0][1] = fromCoord[0][1]; + a[0][2] = 1.0; + a[1][0] = fromCoord[1][0]; + a[1][1] = fromCoord[1][1]; + a[1][2] = 1.0; + a[2][0] = fromCoord[0][1] - fromCoord[1][1] + fromCoord[1][0]; + a[2][1] = fromCoord[1][0] + fromCoord[1][1] - fromCoord[0][0]; + a[2][2] = 1.0; + this.invertGauss(a); + v[0] = toCoord[0][0]; + v[1] = toCoord[1][0]; + v[2] = toCoord[0][1] - toCoord[1][1] + toCoord[1][0]; + + for(i = 0; i < 3; ++i) { + matrix[0][i] = 0.0; + + for(j = 0; j < 3; ++j) { + matrix[0][i] += a[i][j] * v[j]; + } + } + + v[0] = toCoord[0][1]; + v[1] = toCoord[1][1]; + v[2] = toCoord[1][0] + toCoord[1][1] - toCoord[0][0]; + i = 0; + + while(true) { + if (i >= 3) { + break label81; + } + + matrix[1][i] = 0.0; + + for(j = 0; j < 3; ++j) { + matrix[1][i] += a[i][j] * v[j]; + } + + ++i; + } + case AutomaticRegistrationCommand.AFFINE: + a = new double[3][3]; + v = new double[3]; + a[0][0] = fromCoord[0][0]; + a[0][1] = fromCoord[0][1]; + a[0][2] = 1.0; + a[1][0] = fromCoord[1][0]; + a[1][1] = fromCoord[1][1]; + a[1][2] = 1.0; + a[2][0] = fromCoord[2][0]; + a[2][1] = fromCoord[2][1]; + a[2][2] = 1.0; + this.invertGauss(a); + v[0] = toCoord[0][0]; + v[1] = toCoord[1][0]; + v[2] = toCoord[2][0]; + + for(i = 0; i < 3; ++i) { + matrix[0][i] = 0.0; + + for(j = 0; j < 3; ++j) { + matrix[0][i] += a[i][j] * v[j]; + } + } + + v[0] = toCoord[0][1]; + v[1] = toCoord[1][1]; + v[2] = toCoord[2][1]; + i = 0; + + while(true) { + if (i >= 3) { + break label81; + } + + matrix[1][i] = 0.0; + + for(j = 0; j < 3; ++j) { + matrix[1][i] += a[i][j] * v[j]; + } + + ++i; + } + default: + IJ.error("Unexpected transformation"); + } + + matrix[2][0] = 0.0; + matrix[2][1] = 0.0; + matrix[2][2] = 1.0; + return matrix; + } + + private void invertGauss(double[][] matrix) { + int n = matrix.length; + double[][] inverse = new double[n][n]; + + int j; + double max; + double absMax; + int k; + for(j = 0; j < n; ++j) { + max = matrix[j][0]; + absMax = Math.abs(max); + + for(k = 0; k < n; ++k) { + inverse[j][k] = 0.0; + if (absMax < Math.abs(matrix[j][k])) { + max = matrix[j][k]; + absMax = Math.abs(max); + } + } + + inverse[j][j] = 1.0 / max; + + for(k = 0; k < n; ++k) { + matrix[j][k] /= max; + } + } + + for(j = 0; j < n; ++j) { + max = matrix[j][j]; + absMax = Math.abs(max); + k = j; + + int i; + for(i = j + 1; i < n; ++i) { + if (absMax < Math.abs(matrix[i][j])) { + max = matrix[i][j]; + absMax = Math.abs(max); + k = i; + } + } + + if (k != j) { + double[] partialLine = new double[n - j]; + double[] fullLine = new double[n]; + System.arraycopy(matrix[j], j, partialLine, 0, n - j); + System.arraycopy(matrix[k], j, matrix[j], j, n - j); + System.arraycopy(partialLine, 0, matrix[k], j, n - j); + System.arraycopy(inverse[j], 0, fullLine, 0, n); + System.arraycopy(inverse[k], 0, inverse[j], 0, n); + System.arraycopy(fullLine, 0, inverse[k], 0, n); + } + + for(k = 0; k <= j; ++k) { + inverse[j][k] /= max; + } + + for(k = j + 1; k < n; ++k) { + matrix[j][k] /= max; + inverse[j][k] /= max; + } + + for(i = j + 1; i < n; ++i) { + for(k = 0; k <= j; ++k) { + inverse[i][k] -= matrix[i][j] * inverse[j][k]; + } + + for(k = j + 1; k < n; ++k) { + matrix[i][k] -= matrix[i][j] * matrix[j][k]; + inverse[i][k] -= matrix[i][j] * inverse[j][k]; + } + } + } + + for(j = n - 1; 1 <= j; --j) { + for(int i = j - 1; 0 <= i; --i) { + for(k = 0; k <= j; ++k) { + inverse[i][k] -= matrix[i][j] * inverse[j][k]; + } + for(k = j + 1; k < n; ++k) { + matrix[i][k] -= matrix[i][j] * matrix[j][k]; + inverse[i][k] -= matrix[i][j] * inverse[j][k]; + } + } + } + + for(j = 0; j < n; ++j) { + System.arraycopy(inverse[j], 0, matrix[j], 0, n); + } + + } + + public AffineTransform3D getAlignmentTransform() + { + AffineTransform3D turboRegTransform = new AffineTransform3D(); + double[][] a = transformationMatrix; + turboRegTransform.set( + a[0][0], a[0][1], 0, a[0][2], + a[1][0], a[1][1], 0, a[1][2], + 0, 0, 1, 0); + return turboRegTransform; + } +} \ No newline at end of file diff --git a/src/main/java/org/embl/mobie/lib/registration/TurboReg2DAligner.java b/src/main/java/org/embl/mobie/lib/registration/TurboReg2DAligner.java deleted file mode 100644 index b42be0490..000000000 --- a/src/main/java/org/embl/mobie/lib/registration/TurboReg2DAligner.java +++ /dev/null @@ -1,123 +0,0 @@ -/*- - * #%L - * Fiji viewer for MoBIE projects - * %% - * Copyright (C) 2018 - 2023 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 org.embl.mobie.lib.registration; - -import bdv.tools.transformation.TransformedSource; -import bdv.util.BdvHandle; -import bdv.viewer.SourceAndConverter; -import ij.IJ; -import net.imglib2.realtransform.AffineTransform3D; -import org.embl.mobie.command.CommandConstants; -import org.scijava.command.Interactive; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import org.scijava.widget.Button; -import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; - -@Plugin(type = BdvPlaygroundActionCommand.class, menuPath = CommandConstants.CONTEXT_MENU_ITEMS_ROOT + "Transform>Registration - SIFT") -public class TurboReg2DAligner implements BdvPlaygroundActionCommand, Interactive -{ - static { net.imagej.patcher.LegacyInjector.preinit(); } - - @Parameter - public BdvHandle bdvHandle; - - @Parameter ( label = "Compute Alignment", callback = "compute") - private Button compute; - - @Parameter ( label = "Toggle Alignment", callback = "toggle") - private Button toggle; - - private AffineTransform3D previousTransform; - private AffineTransform3D newTransform; - private TransformedSource< ? > transformedSource; - private boolean isAligned; - - @Override - public void run() - { - // - } - - private void toggle() - { - if ( transformedSource == null ) - { - IJ.showMessage( "Please first [ Compute Alignment ]." ); - return; - } - - if ( isAligned ) - { - transformedSource.setFixedTransform( previousTransform ); - } - else - { - transformedSource.setFixedTransform( newTransform ); - } - - bdvHandle.getViewerPanel().requestRepaint(); - isAligned = ! isAligned; - } - - private void compute() - { - IJ.log("# SIFT registration" + - "\nThe registration is computed in the currently visible 2D plane" + - "\nand then applied to the full image in 3D."); - // start the alignment, which has its own GUI - SIFT2DAligner aligner = new SIFT2DAligner( bdvHandle, sacA, sacB, method ); - if( ! aligner.showUI() ) return; - - // apply transformation - SourceAndConverter< ? > movingSac = aligner.getMovingSac(); - if ( movingSac.getSpimSource() instanceof TransformedSource ) - { - // apply the transformation - AffineTransform3D siftTransform = aligner.getSiftTransform3D(); - transformedSource = ( TransformedSource< ? > ) movingSac.getSpimSource(); - previousTransform = new AffineTransform3D(); - transformedSource.getFixedTransform( previousTransform ); - newTransform = previousTransform.copy(); - newTransform.preConcatenate( siftTransform ); - transformedSource.setFixedTransform( newTransform ); - IJ.log( "Transforming " + transformedSource.getName() ); - IJ.log( "Previous Transform: " + previousTransform ); - IJ.log( "Additional SIFT Transform: " + siftTransform ); - IJ.log( "Combined Transform: " + newTransform ); - - isAligned = true; - bdvHandle.getViewerPanel().requestRepaint(); - } - else - { - IJ.log("Cannot apply transformation to image of type " + movingSac.getSpimSource().getClass() ); - } - } -} From 775601586f89de7fb00914412bd1744c374c6434 Mon Sep 17 00:00:00 2001 From: Christian Tischer Date: Mon, 1 Jan 2024 10:31:10 +0100 Subject: [PATCH 10/12] Implement Command to set a transformation --- src/main/java/org/embl/mobie/MoBIE.java | 4 +- .../context/AutomaticRegistrationCommand.java | 28 ++- .../context/SetTransformationCommand.java | 166 ++++++++++++++++++ .../command/context/SourcesInfoCommand.java | 84 +++++++++ .../open/OpenTableAdvancedCommand.java | 18 +- .../mobie/command/open/OpenTableCommand.java | 10 +- .../view/ViewerTransformLoggerCommand.java | 2 +- .../write/CreateNewMoBIEProjectCommand.java | 2 +- .../command/write/WriteOMEZARRCommand.java | 2 +- .../java/org/embl/mobie/lib/MoBIEHelper.java | 12 +- .../mobie/lib/annotation/AnnotationUI.java | 2 +- .../embl/mobie/lib/bdv/ScreenShotMaker.java | 39 +++- .../embl/mobie/lib/bdv/view/SliceViewer.java | 4 +- .../lib/create/ProjectCreatorHelper.java | 2 +- .../lib/create/ui/ProjectsCreatorPanel.java | 4 +- .../embl/mobie/lib/plot/ScatterPlotView.java | 2 +- .../mobie/lib/table/DistanceComputer.java | 15 +- .../org/embl/mobie/lib/table/TableView.java | 6 +- .../lib/transform/viewer/ViewerTransform.java | 1 - .../mobie/lib/view/AdditionalViewsLoader.java | 2 +- .../org/embl/mobie/lib/view/ViewManager.java | 4 +- .../view/save/SelectExistingViewDialog.java | 6 +- .../embl/mobie/lib/view/save/ViewSaver.java | 2 +- .../ui/BrightnessUpdateListener.java | 2 +- .../ui/ColumnColoringModelDialog.java | 2 +- .../org/embl/mobie/{lib => }/ui/MoBIELaf.java | 2 +- .../embl/mobie/{lib => }/ui/SwingHelper.java | 2 +- .../mobie/{lib => }/ui/UserInterface.java | 2 +- .../{lib => }/ui/UserInterfaceHelper.java | 17 +- .../{lib => }/ui/WindowArrangementHelper.java | 8 +- src/test/java/develop/OpenAutoMicTable.java | 4 +- .../OpenTeresaData.java | 3 +- 32 files changed, 367 insertions(+), 92 deletions(-) create mode 100644 src/main/java/org/embl/mobie/command/context/SetTransformationCommand.java create mode 100644 src/main/java/org/embl/mobie/command/context/SourcesInfoCommand.java rename src/main/java/org/embl/mobie/{lib => }/ui/BrightnessUpdateListener.java (99%) rename src/main/java/org/embl/mobie/{lib => }/ui/ColumnColoringModelDialog.java (99%) rename src/main/java/org/embl/mobie/{lib => }/ui/MoBIELaf.java (98%) rename src/main/java/org/embl/mobie/{lib => }/ui/SwingHelper.java (99%) rename src/main/java/org/embl/mobie/{lib => }/ui/UserInterface.java (99%) rename src/main/java/org/embl/mobie/{lib => }/ui/UserInterfaceHelper.java (98%) rename src/main/java/org/embl/mobie/{lib => }/ui/WindowArrangementHelper.java (93%) diff --git a/src/main/java/org/embl/mobie/MoBIE.java b/src/main/java/org/embl/mobie/MoBIE.java index b51c361b4..ae75b337c 100644 --- a/src/main/java/org/embl/mobie/MoBIE.java +++ b/src/main/java/org/embl/mobie/MoBIE.java @@ -67,8 +67,8 @@ import org.embl.mobie.lib.table.TableDataFormat; import org.embl.mobie.lib.table.saw.TableOpener; import org.embl.mobie.lib.transform.GridType; -import org.embl.mobie.lib.ui.UserInterface; -import org.embl.mobie.lib.ui.WindowArrangementHelper; +import org.embl.mobie.ui.UserInterface; +import org.embl.mobie.ui.WindowArrangementHelper; import org.embl.mobie.lib.view.ViewManager; import sc.fiji.bdvpg.PlaygroundPrefs; import sc.fiji.bdvpg.scijava.services.SourceAndConverterService; diff --git a/src/main/java/org/embl/mobie/command/context/AutomaticRegistrationCommand.java b/src/main/java/org/embl/mobie/command/context/AutomaticRegistrationCommand.java index cb429b49f..4c4484f57 100644 --- a/src/main/java/org/embl/mobie/command/context/AutomaticRegistrationCommand.java +++ b/src/main/java/org/embl/mobie/command/context/AutomaticRegistrationCommand.java @@ -35,6 +35,7 @@ import ij.IJ; import ij.ImagePlus; import ij.ImageStack; +import ij.process.ImageConverter; import net.imglib2.realtransform.AffineTransform3D; import org.embl.mobie.command.CommandConstants; import org.embl.mobie.lib.MoBIEHelper; @@ -52,12 +53,12 @@ import java.util.Arrays; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @Plugin(type = BdvPlaygroundActionCommand.class, menuPath = CommandConstants.CONTEXT_MENU_ITEMS_ROOT + "Transform>Registration - Automatic") public class AutomaticRegistrationCommand extends DynamicCommand implements BdvPlaygroundActionCommand, Interactive, Initializable { - public static final String TRANSLATION = "Translation"; public static final String RIGID = "Rigid"; public static final String SIMILARITY = "Similarity"; @@ -74,6 +75,9 @@ public class AutomaticRegistrationCommand extends DynamicCommand implements BdvP @Parameter(label="Registration Voxel Size", persist = false, min = "0.0", style="format:#.00000") public Double voxelSize = 1D; + @Parameter(label="Out of Bounds Value") + public String outOfBoundsValue = "Do not use"; + @Parameter ( label = "Transformation", choices = { TRANSLATION, RIGID, SIMILARITY, AFFINE } ) private String transformationType = TRANSLATION; @@ -149,6 +153,15 @@ private void compute() // create two 2D ImagePlus that are to be aligned // ScreenShotMaker screenShotMaker = new ScreenShotMaker( bdvHandle, sacA.getSpimSource().getVoxelDimensions().unit() ); + try + { + // useful for images with bright background + // to avoid an edge with the black background of BDV + screenShotMaker.setOutOfBoundsValue( Float.parseFloat( this.outOfBoundsValue ) ); + } catch ( Exception e ) + { + // don't set an out-of-bounds value. + } screenShotMaker.run( Arrays.asList( sacA, sacB ), voxelSize ); CompositeImage compositeImage = screenShotMaker.getCompositeImagePlus(); AffineTransform3D canvasToGlobalTransform = screenShotMaker.getCanvasToGlobalTransform(); @@ -157,12 +170,16 @@ private void compute() ImagePlus impA = new ImagePlus( imageA + " (fixed)", stack.getProcessor( 1 ) ); ImagePlus impB = new ImagePlus( imageB + " (moving)", stack.getProcessor( 2 ) ); - // set the display ranges - // as those will be used by the SIFT for normalising the pixel values + // set the display ranges and burn them in by converting to unit8 + // this is important for the intensity based registration methods compositeImage.setPosition( 1 ); impA.getProcessor().setMinAndMax( compositeImage.getDisplayRangeMin(), compositeImage.getDisplayRangeMax() ); compositeImage.setPosition( 2 ); impB.getProcessor().setMinAndMax( compositeImage.getDisplayRangeMin(), compositeImage.getDisplayRangeMax() ); + new ImageConverter( impA ).convertToGray8(); + new ImageConverter( impB ).convertToGray8(); + double min = impA.getProcessor().getMax(); + double max = impA.getProcessor().getMax(); // compute the transformation that aligns the two images in 2D // @@ -178,6 +195,11 @@ else if ( registrationMethod.equals( "TurboReg" ) ) TurboReg2DAligner turboReg2DAligner = new TurboReg2DAligner( impA, impB, transformationType ); turboReg2DAligner.run( showIntermediates ); localRegistration = turboReg2DAligner.getAlignmentTransform(); + if ( showIntermediates ) + { + impA.show(); + impB.show(); + } } // convert the transformation that aligns the images in 2D diff --git a/src/main/java/org/embl/mobie/command/context/SetTransformationCommand.java b/src/main/java/org/embl/mobie/command/context/SetTransformationCommand.java new file mode 100644 index 000000000..8052d9915 --- /dev/null +++ b/src/main/java/org/embl/mobie/command/context/SetTransformationCommand.java @@ -0,0 +1,166 @@ +/*- + * #%L + * Fiji viewer for MoBIE projects + * %% + * Copyright (C) 2018 - 2023 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 org.embl.mobie.command.context; + +import bdv.tools.transformation.TransformedSource; +import bdv.util.BdvHandle; +import bdv.viewer.SourceAndConverter; +import ij.IJ; +import net.imglib2.realtransform.AffineTransform3D; +import org.embl.mobie.command.CommandConstants; +import org.embl.mobie.lib.MoBIEHelper; +import org.embl.mobie.lib.serialize.transformation.AffineTransformation; +import org.scijava.Initializable; +import org.scijava.command.DynamicCommand; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.widget.Button; +import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Plugin(type = BdvPlaygroundActionCommand.class, menuPath = CommandConstants.CONTEXT_MENU_ITEMS_ROOT + "Transform>Registration - Set Transformation") +public class SetTransformationCommand extends DynamicCommand implements BdvPlaygroundActionCommand, Initializable +{ + static { net.imagej.patcher.LegacyInjector.preinit(); } + + @Parameter + public BdvHandle bdvHandle; + + @Parameter ( label = "Image", choices = {""} ) + private String sourceName; + + @Parameter ( label = "3D Affine" ) + private String transformation = Arrays.toString( new AffineTransform3D().getRowPackedCopy() ); + private AffineTransform3D previousTransform; + private TransformedSource< ? > transformedSource; + + public enum AdditionMode + { + Concatenate, + Replace; + } + + @Parameter ( label = "Addition Mode" ) + private AdditionMode mode = AdditionMode.Replace; + + @Parameter ( label = "Preview", callback = "apply" ) + private Button preview; + + private List< SourceAndConverter< ? > > sourceAndConverters; + + @Override + public void initialize() + { + sourceAndConverters = MoBIEHelper.getVisibleSacs( bdvHandle ); + + final List< String > imageNames = sourceAndConverters.stream() + .map( sac -> sac.getSpimSource().getName() ) + .collect( Collectors.toList() ); + + getInfo().getMutableInput( "sourceName", String.class ) + .setChoices( imageNames ); + + } + + @Override + public void run() + { + apply(); + } + + @Override + public void cancel() + { + if ( transformedSource != null ) + { + transformedSource.setFixedTransform( previousTransform ); + bdvHandle.getViewerPanel().requestRepaint(); + } + } + + private void apply() + { + double[] doubles = parseStringToDoubleArray( transformation ); + AffineTransform3D additionalTransform = new AffineTransform3D(); + additionalTransform.set( doubles ); + + if ( additionalTransform.isIdentity() ) + return; + + // TODO: the below logic will break when the + // user decides to change the source name + SourceAndConverter< ? > sourceAndConverter = sourceAndConverters.stream() + .filter( sac -> sac.getSpimSource().getName().equals( sourceName ) ) + .findFirst().get(); + + if ( sourceAndConverter.getSpimSource() instanceof TransformedSource ) + { + transformedSource = ( TransformedSource< ? > ) sourceAndConverter.getSpimSource(); + + if ( previousTransform != null ) + { + // reset transform to initial state + transformedSource.setFixedTransform( previousTransform ); + } + else + { + // remember the previous transform + // such that we can reset it + previousTransform = new AffineTransform3D(); + transformedSource.getFixedTransform( previousTransform ); + } + + if ( mode.equals( AdditionMode.Replace ) ) + { + transformedSource.setFixedTransform( additionalTransform ); + } + else if ( mode.equals( AdditionMode.Concatenate ) ) + { + AffineTransform3D newTransform = previousTransform.copy().preConcatenate( additionalTransform ); + transformedSource.setFixedTransform( newTransform ); + } + + bdvHandle.getViewerPanel().requestRepaint(); + } + else + { + IJ.error( "Cannot set the transformation of a " + sourceAndConverter.getSpimSource().getClass() ); + } + } + + public static double[] parseStringToDoubleArray(String arrayStr) { + arrayStr = arrayStr.replaceAll("[^0-9.,]", ""); + String[] items = arrayStr.split(",\\s*"); + double[] doubles = Arrays.stream(items).mapToDouble(Double::parseDouble).toArray(); + return doubles; + } +} diff --git a/src/main/java/org/embl/mobie/command/context/SourcesInfoCommand.java b/src/main/java/org/embl/mobie/command/context/SourcesInfoCommand.java new file mode 100644 index 000000000..c7088196b --- /dev/null +++ b/src/main/java/org/embl/mobie/command/context/SourcesInfoCommand.java @@ -0,0 +1,84 @@ +/*- + * #%L + * Fiji viewer for MoBIE projects + * %% + * Copyright (C) 2018 - 2023 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 org.embl.mobie.command.context; + +import bdv.tools.transformation.TransformedSource; +import bdv.util.BdvHandle; +import bdv.viewer.Source; +import bdv.viewer.SourceAndConverter; +import ij.IJ; +import net.imglib2.realtransform.AffineTransform3D; +import org.embl.mobie.MoBIE; +import org.embl.mobie.command.CommandConstants; +import org.embl.mobie.lib.MoBIEHelper; +import org.embl.mobie.lib.bdv.ScreenShotMaker; +import org.scijava.Initializable; +import org.scijava.command.DynamicCommand; +import org.scijava.module.MutableModuleItem; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import sc.fiji.bdvpg.bdv.BdvHandleHelper; +import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Plugin(type = BdvPlaygroundActionCommand.class, menuPath = CommandConstants.CONTEXT_MENU_ITEMS_ROOT + "Log Images Info") +public class SourcesInfoCommand implements BdvPlaygroundActionCommand +{ + static { net.imagej.patcher.LegacyInjector.preinit(); } + + public static final String CAPTURE_SIZE_PIXELS = "Capture size [pixels]: "; + + @Parameter + public BdvHandle bdvHandle; + + @Override + public void run() + { + List< SourceAndConverter< ? > > visibleSacs = MoBIEHelper.getVisibleSacs( bdvHandle ); + visibleSacs.forEach( sac -> + { + Source< ? > source = sac.getSpimSource(); + IJ.log( "# " + source.getName() ); + IJ.log( "Number of resolution levels: " + source.getNumMipmapLevels() ); + IJ.log( "Voxel size: " + Arrays.toString( source.getVoxelDimensions().dimensionsAsDoubleArray() ) ); + + if ( source instanceof TransformedSource ) + { + AffineTransform3D transform3D = new AffineTransform3D(); + ( ( TransformedSource ) source ).getFixedTransform( transform3D ); + IJ.log( "Additional transform: " + transform3D ); + source.getSourceTransform( 0, 0, transform3D ); + IJ.log( "Full transform: " + transform3D ); + } + }); + } +} diff --git a/src/main/java/org/embl/mobie/command/open/OpenTableAdvancedCommand.java b/src/main/java/org/embl/mobie/command/open/OpenTableAdvancedCommand.java index d3218514c..0843ca2aa 100644 --- a/src/main/java/org/embl/mobie/command/open/OpenTableAdvancedCommand.java +++ b/src/main/java/org/embl/mobie/command/open/OpenTableAdvancedCommand.java @@ -28,35 +28,23 @@ */ package org.embl.mobie.command.open; -import loci.common.DebugTools; -import org.embl.mobie.MoBIE; -import org.embl.mobie.MoBIESettings; import org.embl.mobie.command.CommandConstants; import org.embl.mobie.lib.transform.GridType; import org.scijava.command.Command; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - @Plugin(type = Command.class, menuPath = CommandConstants.MOBIE_PLUGIN_OPEN + "Open Table (Advanced)..." ) public class OpenTableAdvancedCommand extends OpenTableCommand { static { net.imagej.patcher.LegacyInjector.preinit(); } - @Parameter( label = "Grid type", choices = {"Stitched", "Transformable"} ) - public String gridTypeString = "Transformable" ; - // FIXME: SciJava supports enums now?! + @Parameter( label = "Grid type" ) + public GridType gridType = GridType.Transformed; @Override public void run() { - gridType = gridTypeString.equals( "Stitched" ) ? GridType.Stitched : GridType.Transformed; - super.run(); + super.run( gridType ); } } diff --git a/src/main/java/org/embl/mobie/command/open/OpenTableCommand.java b/src/main/java/org/embl/mobie/command/open/OpenTableCommand.java index d1644d67b..8c00ebb64 100644 --- a/src/main/java/org/embl/mobie/command/open/OpenTableCommand.java +++ b/src/main/java/org/embl/mobie/command/open/OpenTableCommand.java @@ -64,15 +64,15 @@ public class OpenTableCommand implements Command { @Parameter( label = "Remove Spatial Calibration", required = false ) public Boolean removeSpatialCalibration = false; - protected GridType gridType = GridType.Stitched; - - @Override public void run() { - DebugTools.setRootLevel( "OFF" ); + run( GridType.Stitched ); + } - final GridType gridType = this.gridType; + public void run( GridType gridType ) + { + DebugTools.setRootLevel( "OFF" ); final MoBIESettings settings = new MoBIESettings(); settings.removeSpatialCalibration( removeSpatialCalibration ); diff --git a/src/main/java/org/embl/mobie/command/view/ViewerTransformLoggerCommand.java b/src/main/java/org/embl/mobie/command/view/ViewerTransformLoggerCommand.java index 2212ed3f0..edbb74e4e 100644 --- a/src/main/java/org/embl/mobie/command/view/ViewerTransformLoggerCommand.java +++ b/src/main/java/org/embl/mobie/command/view/ViewerTransformLoggerCommand.java @@ -81,7 +81,7 @@ public void run() // print final Gson gson = JsonHelper.buildGson( false ); - Logger.log( "# Current view " ); + Logger.log( "# Current location " ); Logger.log( "To restore the view, any of below lines can be pasted into the \'location\' text field." ); Logger.log( "To share views with other people we recommend \'normalizedAffine\'." ); diff --git a/src/main/java/org/embl/mobie/command/write/CreateNewMoBIEProjectCommand.java b/src/main/java/org/embl/mobie/command/write/CreateNewMoBIEProjectCommand.java index 264804ec1..d244237fa 100644 --- a/src/main/java/org/embl/mobie/command/write/CreateNewMoBIEProjectCommand.java +++ b/src/main/java/org/embl/mobie/command/write/CreateNewMoBIEProjectCommand.java @@ -38,7 +38,7 @@ import java.io.File; import java.io.IOException; -import static org.embl.mobie.lib.ui.UserInterfaceHelper.tidyString; +import static org.embl.mobie.ui.UserInterfaceHelper.tidyString; @Plugin(type = Command.class, menuPath = CommandConstants.MOBIE_PLUGIN_ROOT + "Create>Create New MoBIE Project..." ) public class CreateNewMoBIEProjectCommand implements Command { diff --git a/src/main/java/org/embl/mobie/command/write/WriteOMEZARRCommand.java b/src/main/java/org/embl/mobie/command/write/WriteOMEZARRCommand.java index 44a5ee9b9..484dbf9c0 100644 --- a/src/main/java/org/embl/mobie/command/write/WriteOMEZARRCommand.java +++ b/src/main/java/org/embl/mobie/command/write/WriteOMEZARRCommand.java @@ -43,7 +43,7 @@ import java.io.File; -import static org.embl.mobie.lib.ui.UserInterfaceHelper.tidyString; +import static org.embl.mobie.ui.UserInterfaceHelper.tidyString; @Plugin(type = Command.class, menuPath = CommandConstants.MOBIE_PLUGIN_ROOT + "Create>Save Current Image as OME-ZARR...") public class WriteOMEZARRCommand implements Command { diff --git a/src/main/java/org/embl/mobie/lib/MoBIEHelper.java b/src/main/java/org/embl/mobie/lib/MoBIEHelper.java index 0dfdf73cf..82ceb7639 100644 --- a/src/main/java/org/embl/mobie/lib/MoBIEHelper.java +++ b/src/main/java/org/embl/mobie/lib/MoBIEHelper.java @@ -32,9 +32,6 @@ import bdv.util.BdvHandle; import bdv.viewer.SourceAndConverter; import ij.ImagePlus; -import loci.plugins.in.ImagePlusReader; -import loci.plugins.in.ImportProcess; -import loci.plugins.in.ImporterOptions; import mpicbg.spim.data.SpimDataException; import mpicbg.spim.data.generic.AbstractSpimData; import net.imglib2.Dimensions; @@ -44,9 +41,9 @@ import org.embl.mobie.io.github.GitHubUtils; import org.embl.mobie.io.toml.TOMLOpener; import org.embl.mobie.io.util.IOHelper; -import org.embl.mobie.lib.source.Metadata; import org.embl.mobie.lib.io.StorageLocation; import org.embl.mobie.lib.serialize.ImageDataSource; +import org.embl.mobie.lib.source.Metadata; import org.embl.mobie.lib.source.SourceToImagePlusConverter; import sc.fiji.bdvpg.scijava.services.SourceAndConverterBdvDisplayService; import sc.fiji.bdvpg.services.SourceAndConverterServices; @@ -56,6 +53,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -70,6 +68,12 @@ public abstract class MoBIEHelper { static { net.imagej.patcher.LegacyInjector.preinit(); } + public static > String[] enumAsStringArray(Class enumClass) { + return Arrays.stream(enumClass.getEnumConstants()) + .map(Enum::name) + .toArray(String[]::new); + } + public static int[] asInts( long[] longs) { int[] ints = new int[longs.length]; diff --git a/src/main/java/org/embl/mobie/lib/annotation/AnnotationUI.java b/src/main/java/org/embl/mobie/lib/annotation/AnnotationUI.java index df3e61661..57df923da 100644 --- a/src/main/java/org/embl/mobie/lib/annotation/AnnotationUI.java +++ b/src/main/java/org/embl/mobie/lib/annotation/AnnotationUI.java @@ -39,7 +39,7 @@ import org.embl.mobie.lib.select.SelectionModel; import org.embl.mobie.lib.table.AnnotationTableModel; import org.embl.mobie.lib.table.DefaultValues; -import org.embl.mobie.lib.ui.MoBIELaf; +import org.embl.mobie.ui.MoBIELaf; import javax.swing.*; import javax.swing.table.TableModel; 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 a28fe3b05..f643f4a10 100644 --- a/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java +++ b/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java @@ -42,6 +42,7 @@ import net.imglib2.Cursor; import net.imglib2.type.Type; import net.imglib2.type.numeric.real.FloatType; +import net.imglib2.util.Intervals; import org.embl.mobie.lib.MoBIEHelper; import org.embl.mobie.lib.ThreadHelper; import org.embl.mobie.lib.annotation.Annotation; @@ -82,6 +83,8 @@ public class ScreenShotMaker private long[] screenshotDimensions = new long[2]; private AffineTransform3D canvasToGlobalTransform; + private Float outOfBoundsValue = null; + public ScreenShotMaker( BdvHandle bdvHandle, String voxelUnit ) { this.bdvHandle = bdvHandle; this.voxelUnit = voxelUnit; @@ -97,6 +100,11 @@ public CompositeImage getCompositeImagePlus() return compositeImagePlus; } + public void setOutOfBoundsValue( Float outOfBoundsValue ) + { + this.outOfBoundsValue = outOfBoundsValue; + } + public void run( Double targetSamplingInXY ) { List< SourceAndConverter< ? > > sacs = getVisibleSourceAndConverters(); @@ -174,6 +182,7 @@ public void run( List< SourceAndConverter< ? > > sacs, double targetVoxelSpacing ThreadHelper.ioExecutorService.submit( () -> { RealRandomAccess< ? extends Type< ? > > access = getRealRandomAccess( ( Source< Type< ? > > ) source, currentTimepoint, level, interpolate ); + RandomAccessibleInterval< ? > sourceInterval = source.getSource( currentTimepoint, level ); // to collect raw data final IntervalView< FloatType > floatCrop = Views.interval( rawCapture, interval ); @@ -199,10 +208,20 @@ public void run( List< SourceAndConverter< ? > > sacs, double targetVoxelSpacing targetCanvasToSourceTransform.apply( canvasPosition, sourceRealPosition ); access.setPosition( sourceRealPosition ); - setFloatPixelValue( access, floatCaptureAccess ); - setArgbPixelValue( converter, access, argbCaptureAccess, argbType ); - pixelCount.incrementAndGet(); + boolean contains = Intervals.contains( sourceInterval, new RealPoint( sourceRealPosition ) ); + if ( ! contains && outOfBoundsValue != null ) + { + floatCaptureAccess.get().setReal( outOfBoundsValue ); + // TODO how to set the ARGB value? + } + else + { + setFloatPixelValue( access, floatCaptureAccess ); + setArgbPixelValue( converter, access, argbCaptureAccess, argbType ); + } + + pixelCount.incrementAndGet(); final double currentFractionDone = 1.0 * pixelCount.get() / numPixels; if ( currentFractionDone >= fractionDone.get() ) { @@ -274,12 +293,12 @@ private void setArgbPixelValue( Converter converter, RealRandomAccess< ? > acces argbCaptureAccess.get().set( argbType.get() ); } - private void setFloatPixelValue( RealRandomAccess< ? extends Type< ? > > access, RandomAccess< FloatType > realCaptureAccess ) + private void setFloatPixelValue( RealRandomAccess< ? extends Type< ? > > access, RandomAccess< FloatType > floatCaptureAccess ) { final Type< ? > type = access.get(); if ( type instanceof RealType ) { - realCaptureAccess.get().setReal( ( ( RealType ) type ).getRealDouble() ); + floatCaptureAccess.get().setReal( ( ( RealType ) type ).getRealDouble() ); } else if ( type instanceof AnnotationType ) { @@ -287,7 +306,7 @@ else if ( type instanceof AnnotationType ) { final Annotation annotation = ( Annotation ) ( ( AnnotationType< ? > ) type ).getAnnotation(); if ( annotation != null ) - realCaptureAccess.get().setReal( annotation.label() ); + floatCaptureAccess.get().setReal( annotation.label() ); } catch ( Exception e ) { @@ -303,9 +322,15 @@ else if ( type instanceof AnnotationType ) private RealRandomAccess< ? extends Type< ? > > getRealRandomAccess( Source< Type< ? > > source, int t, int level, boolean interpolate ) { if ( interpolate ) - return source.getInterpolatedSource( t, level, Interpolation.NLINEAR ).realRandomAccess(); + { + Interpolation interpolation = bdvHandle.getViewerPanel().state().getInterpolation(); + return source.getInterpolatedSource( t, level, interpolation ).realRandomAccess(); + } else + { + // e.g., for label masks we do not want to interpolate return source.getInterpolatedSource( t, level, Interpolation.NEARESTNEIGHBOR ).realRandomAccess(); + } } private ImagePlus createRGBImagePlus( diff --git a/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java b/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java index d3b5c6b94..55c450e7d 100644 --- a/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java +++ b/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java @@ -47,7 +47,7 @@ import org.embl.mobie.lib.image.RegionAnnotationImage; import org.embl.mobie.lib.serialize.display.AbstractDisplay; import org.embl.mobie.lib.source.SourceHelper; -import org.embl.mobie.lib.ui.WindowArrangementHelper; +import org.embl.mobie.ui.WindowArrangementHelper; import org.scijava.ui.behaviour.ClickBehaviour; import org.scijava.ui.behaviour.io.InputTriggerConfig; import org.scijava.ui.behaviour.util.Behaviours; @@ -147,6 +147,7 @@ private void installContextMenuAndKeyboardShortCuts( ) final Set< String > actionsKeys = sacService.getActionsKeys(); final ArrayList< String > actions = new ArrayList< String >(); + actions.add( SourceAndConverterService.getCommandName( SourcesInfoCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( ScreenShotMakerCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( ShowRasterImagesCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( ViewerTransformLoggerCommand.class ) ); @@ -154,6 +155,7 @@ private void installContextMenuAndKeyboardShortCuts( ) actions.add( SourceAndConverterService.getCommandName( BigWarpRegistrationCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( AutomaticRegistrationCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( ManualRegistrationCommand.class ) ); + actions.add( SourceAndConverterService.getCommandName( SetTransformationCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( FlipCommand.class ) ); actions.add( UNDO_SEGMENT_SELECTIONS ); actions.add( LOAD_ADDITIONAL_VIEWS ); diff --git a/src/main/java/org/embl/mobie/lib/create/ProjectCreatorHelper.java b/src/main/java/org/embl/mobie/lib/create/ProjectCreatorHelper.java index b6470dfcc..6e73ce825 100644 --- a/src/main/java/org/embl/mobie/lib/create/ProjectCreatorHelper.java +++ b/src/main/java/org/embl/mobie/lib/create/ProjectCreatorHelper.java @@ -60,7 +60,7 @@ import java.util.HashMap; import java.util.Map; -import static org.embl.mobie.lib.ui.UserInterfaceHelper.tidyString; +import static org.embl.mobie.ui.UserInterfaceHelper.tidyString; /** * Utility class for helper functions used during project creation diff --git a/src/main/java/org/embl/mobie/lib/create/ui/ProjectsCreatorPanel.java b/src/main/java/org/embl/mobie/lib/create/ui/ProjectsCreatorPanel.java index 328e80ac4..af368006c 100644 --- a/src/main/java/org/embl/mobie/lib/create/ui/ProjectsCreatorPanel.java +++ b/src/main/java/org/embl/mobie/lib/create/ui/ProjectsCreatorPanel.java @@ -38,8 +38,8 @@ import org.embl.mobie.lib.create.ProjectCreator; import org.embl.mobie.lib.create.ProjectCreatorHelper; import org.embl.mobie.lib.serialize.Dataset; -import org.embl.mobie.lib.ui.SwingHelper; -import org.embl.mobie.lib.ui.UserInterfaceHelper; +import org.embl.mobie.ui.SwingHelper; +import org.embl.mobie.ui.UserInterfaceHelper; import mpicbg.spim.data.SpimData; import mpicbg.spim.data.SpimDataException; import net.imglib2.realtransform.AffineTransform3D; diff --git a/src/main/java/org/embl/mobie/lib/plot/ScatterPlotView.java b/src/main/java/org/embl/mobie/lib/plot/ScatterPlotView.java index d6e25a00a..6528be0ef 100644 --- a/src/main/java/org/embl/mobie/lib/plot/ScatterPlotView.java +++ b/src/main/java/org/embl/mobie/lib/plot/ScatterPlotView.java @@ -57,7 +57,7 @@ import org.embl.mobie.lib.table.AnnotationTableModel; import org.embl.mobie.lib.transform.viewer.ViewerTransformChanger; import org.embl.mobie.lib.transform.TransformHelper; -import org.embl.mobie.lib.ui.ColumnColoringModelDialog; +import org.embl.mobie.ui.ColumnColoringModelDialog; import org.scijava.ui.behaviour.ClickBehaviour; import org.scijava.ui.behaviour.io.InputTriggerConfig; import org.scijava.ui.behaviour.util.Behaviours; diff --git a/src/main/java/org/embl/mobie/lib/table/DistanceComputer.java b/src/main/java/org/embl/mobie/lib/table/DistanceComputer.java index 64b5a8fd9..068465472 100644 --- a/src/main/java/org/embl/mobie/lib/table/DistanceComputer.java +++ b/src/main/java/org/embl/mobie/lib/table/DistanceComputer.java @@ -3,6 +3,7 @@ import ij.IJ; import ij.gui.GenericDialog; import net.imglib2.type.numeric.ARGBType; +import org.embl.mobie.lib.MoBIEHelper; import org.embl.mobie.lib.annotation.Annotation; import org.embl.mobie.lib.color.ColoringModels; import org.embl.mobie.lib.color.MobieColoringModel; @@ -20,22 +21,12 @@ enum AverageMethod { Mean, Median; - - public static String[] asArray() - { - return Arrays.stream( AverageMethod.values()).map(Enum::name).toArray(String[]::new); - } } enum DistanceMetric { Euclidian, Cosine; - - public static String[] asArray() - { - return Arrays.stream( DistanceMetric.values()).map(Enum::name).toArray(String[]::new); - } } public static < A extends Annotation > void showUI( AnnotationTableModel< A > tableModel, SelectionModel< A > selectionModel, MobieColoringModel< A > coloringModel ) @@ -51,8 +42,8 @@ public static < A extends Annotation > void showUI( AnnotationTableModel< A > ta // final GenericDialog gd = new GenericDialog( "" ); gd.addStringField( "Distance Columns RegEx", "anchor_.*" ); - gd.addChoice( "Distance Metric", DistanceMetric.asArray(), DistanceMetric.Euclidian.toString() ); - gd.addChoice( "Averaging Method", AverageMethod.asArray(), AverageMethod.Median.toString() ); + gd.addChoice( "Distance Metric", MoBIEHelper.enumAsStringArray( DistanceMetric.class ), DistanceMetric.Euclidian.toString() ); + gd.addChoice( "Averaging Method", MoBIEHelper.enumAsStringArray( AverageMethod.class ), AverageMethod.Median.toString() ); gd.addStringField( "Results Column Name", "distance" ); gd.addCheckbox( "Color by Results", true ); gd.showDialog(); diff --git a/src/main/java/org/embl/mobie/lib/table/TableView.java b/src/main/java/org/embl/mobie/lib/table/TableView.java index 9efb01c12..1a8b140db 100644 --- a/src/main/java/org/embl/mobie/lib/table/TableView.java +++ b/src/main/java/org/embl/mobie/lib/table/TableView.java @@ -47,9 +47,9 @@ import org.embl.mobie.lib.plot.ScatterPlotView; import org.embl.mobie.lib.select.SelectionListener; import org.embl.mobie.lib.select.SelectionModel; -import org.embl.mobie.lib.ui.ColumnColoringModelDialog; +import org.embl.mobie.ui.ColumnColoringModelDialog; import net.imglib2.type.numeric.ARGBType; -import org.embl.mobie.lib.ui.UserInterfaceHelper; +import org.embl.mobie.ui.UserInterfaceHelper; import javax.swing.*; import javax.swing.table.DefaultTableCellRenderer; @@ -63,7 +63,7 @@ import java.util.List; import static org.embl.mobie.lib.MoBIEHelper.FileLocation; -import static org.embl.mobie.lib.ui.UserInterfaceHelper.loadFromProjectOrFileSystemDialog; +import static org.embl.mobie.ui.UserInterfaceHelper.loadFromProjectOrFileSystemDialog; public class TableView< A extends Annotation > implements SelectionListener< A >, ColoringListener, AnnotationListener< A > { diff --git a/src/main/java/org/embl/mobie/lib/transform/viewer/ViewerTransform.java b/src/main/java/org/embl/mobie/lib/transform/viewer/ViewerTransform.java index 8c99031db..27cd0bb0b 100644 --- a/src/main/java/org/embl/mobie/lib/transform/viewer/ViewerTransform.java +++ b/src/main/java/org/embl/mobie/lib/transform/viewer/ViewerTransform.java @@ -54,7 +54,6 @@ static ViewerTransform toViewerTransform( String s ) { final Gson gson = JsonHelper.buildGson( false ); return gson.fromJson( s, ViewerTransform.class ); - } catch ( Exception gsonException ) { diff --git a/src/main/java/org/embl/mobie/lib/view/AdditionalViewsLoader.java b/src/main/java/org/embl/mobie/lib/view/AdditionalViewsLoader.java index e5cddb854..d390c14d8 100644 --- a/src/main/java/org/embl/mobie/lib/view/AdditionalViewsLoader.java +++ b/src/main/java/org/embl/mobie/lib/view/AdditionalViewsLoader.java @@ -33,7 +33,7 @@ import org.embl.mobie.lib.MoBIEHelper; import org.embl.mobie.lib.serialize.AdditionalViewsJsonParser; import org.embl.mobie.lib.serialize.View; -import org.embl.mobie.lib.ui.UserInterfaceHelper; +import org.embl.mobie.ui.UserInterfaceHelper; import java.io.IOException; import java.util.Map; diff --git a/src/main/java/org/embl/mobie/lib/view/ViewManager.java b/src/main/java/org/embl/mobie/lib/view/ViewManager.java index 3d9846746..4185191ba 100644 --- a/src/main/java/org/embl/mobie/lib/view/ViewManager.java +++ b/src/main/java/org/embl/mobie/lib/view/ViewManager.java @@ -67,8 +67,8 @@ import org.embl.mobie.lib.transform.viewer.MoBIEViewerTransformAdjuster; import org.embl.mobie.lib.transform.viewer.ViewerTransform; import org.embl.mobie.lib.transform.viewer.ViewerTransformChanger; -import org.embl.mobie.lib.ui.UserInterface; -import org.embl.mobie.lib.ui.WindowArrangementHelper; +import org.embl.mobie.ui.UserInterface; +import org.embl.mobie.ui.WindowArrangementHelper; import org.embl.mobie.lib.view.save.ViewSaver; import org.embl.mobie.lib.volume.ImageVolumeViewer; import org.embl.mobie.lib.volume.SegmentVolumeViewer; diff --git a/src/main/java/org/embl/mobie/lib/view/save/SelectExistingViewDialog.java b/src/main/java/org/embl/mobie/lib/view/save/SelectExistingViewDialog.java index 1e847e481..a21521941 100644 --- a/src/main/java/org/embl/mobie/lib/view/save/SelectExistingViewDialog.java +++ b/src/main/java/org/embl/mobie/lib/view/save/SelectExistingViewDialog.java @@ -31,9 +31,9 @@ import de.embl.cba.tables.SwingUtils; import org.embl.mobie.lib.create.ProjectCreatorHelper; import org.embl.mobie.lib.serialize.Dataset; -import org.embl.mobie.lib.ui.MoBIELaf; -import org.embl.mobie.lib.ui.SwingHelper; -import org.embl.mobie.lib.ui.UserInterfaceHelper; +import org.embl.mobie.ui.MoBIELaf; +import org.embl.mobie.ui.SwingHelper; +import org.embl.mobie.ui.UserInterfaceHelper; import org.embl.mobie.lib.view.AdditionalViews; import javax.swing.*; diff --git a/src/main/java/org/embl/mobie/lib/view/save/ViewSaver.java b/src/main/java/org/embl/mobie/lib/view/save/ViewSaver.java index 7f9670ac8..0bbb6c0cf 100644 --- a/src/main/java/org/embl/mobie/lib/view/save/ViewSaver.java +++ b/src/main/java/org/embl/mobie/lib/view/save/ViewSaver.java @@ -37,7 +37,7 @@ import org.embl.mobie.lib.serialize.AdditionalViewsJsonParser; import org.embl.mobie.lib.serialize.Dataset; import org.embl.mobie.lib.serialize.DatasetJsonParser; -import org.embl.mobie.lib.ui.UserInterfaceHelper; +import org.embl.mobie.ui.UserInterfaceHelper; import org.embl.mobie.lib.view.AdditionalViews; import org.embl.mobie.lib.serialize.View; import org.apache.commons.io.FilenameUtils; diff --git a/src/main/java/org/embl/mobie/lib/ui/BrightnessUpdateListener.java b/src/main/java/org/embl/mobie/ui/BrightnessUpdateListener.java similarity index 99% rename from src/main/java/org/embl/mobie/lib/ui/BrightnessUpdateListener.java rename to src/main/java/org/embl/mobie/ui/BrightnessUpdateListener.java index ffa47af09..d2990e061 100644 --- a/src/main/java/org/embl/mobie/lib/ui/BrightnessUpdateListener.java +++ b/src/main/java/org/embl/mobie/ui/BrightnessUpdateListener.java @@ -26,7 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package org.embl.mobie.lib.ui; +package org.embl.mobie.ui; import bdv.tools.brightness.ConverterSetup; import bdv.tools.brightness.SliderPanelDouble; diff --git a/src/main/java/org/embl/mobie/lib/ui/ColumnColoringModelDialog.java b/src/main/java/org/embl/mobie/ui/ColumnColoringModelDialog.java similarity index 99% rename from src/main/java/org/embl/mobie/lib/ui/ColumnColoringModelDialog.java rename to src/main/java/org/embl/mobie/ui/ColumnColoringModelDialog.java index 0e41ac41f..6d16d4160 100644 --- a/src/main/java/org/embl/mobie/lib/ui/ColumnColoringModelDialog.java +++ b/src/main/java/org/embl/mobie/ui/ColumnColoringModelDialog.java @@ -26,7 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package org.embl.mobie.lib.ui; +package org.embl.mobie.ui; import ij.IJ; import ij.gui.GenericDialog; diff --git a/src/main/java/org/embl/mobie/lib/ui/MoBIELaf.java b/src/main/java/org/embl/mobie/ui/MoBIELaf.java similarity index 98% rename from src/main/java/org/embl/mobie/lib/ui/MoBIELaf.java rename to src/main/java/org/embl/mobie/ui/MoBIELaf.java index 8e93cb3ee..572e3c838 100644 --- a/src/main/java/org/embl/mobie/lib/ui/MoBIELaf.java +++ b/src/main/java/org/embl/mobie/ui/MoBIELaf.java @@ -26,7 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package org.embl.mobie.lib.ui; +package org.embl.mobie.ui; import com.formdev.flatlaf.FlatLightLaf; diff --git a/src/main/java/org/embl/mobie/lib/ui/SwingHelper.java b/src/main/java/org/embl/mobie/ui/SwingHelper.java similarity index 99% rename from src/main/java/org/embl/mobie/lib/ui/SwingHelper.java rename to src/main/java/org/embl/mobie/ui/SwingHelper.java index 1b163297d..e57d46c57 100644 --- a/src/main/java/org/embl/mobie/lib/ui/SwingHelper.java +++ b/src/main/java/org/embl/mobie/ui/SwingHelper.java @@ -26,7 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package org.embl.mobie.lib.ui; +package org.embl.mobie.ui; import ij.gui.GenericDialog; diff --git a/src/main/java/org/embl/mobie/lib/ui/UserInterface.java b/src/main/java/org/embl/mobie/ui/UserInterface.java similarity index 99% rename from src/main/java/org/embl/mobie/lib/ui/UserInterface.java rename to src/main/java/org/embl/mobie/ui/UserInterface.java index 7d84b15a8..cacdcb43d 100644 --- a/src/main/java/org/embl/mobie/lib/ui/UserInterface.java +++ b/src/main/java/org/embl/mobie/ui/UserInterface.java @@ -26,7 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package org.embl.mobie.lib.ui; +package org.embl.mobie.ui; import org.embl.mobie.MoBIE; import org.embl.mobie.lib.bdv.ImageNameOverlay; diff --git a/src/main/java/org/embl/mobie/lib/ui/UserInterfaceHelper.java b/src/main/java/org/embl/mobie/ui/UserInterfaceHelper.java similarity index 98% rename from src/main/java/org/embl/mobie/lib/ui/UserInterfaceHelper.java rename to src/main/java/org/embl/mobie/ui/UserInterfaceHelper.java index 97c32fc2d..cd76d0365 100644 --- a/src/main/java/org/embl/mobie/lib/ui/UserInterfaceHelper.java +++ b/src/main/java/org/embl/mobie/ui/UserInterfaceHelper.java @@ -26,7 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package org.embl.mobie.lib.ui; +package org.embl.mobie.ui; import bdv.tools.brightness.ConverterSetup; import bdv.tools.brightness.SliderPanelDouble; @@ -95,9 +95,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; -import static org.embl.mobie.lib.ui.SwingHelper.TEXT_FIELD_HEIGHT; -import static org.embl.mobie.lib.ui.SwingHelper.selectionDialog; - public class UserInterfaceHelper { public static final String PROTOTYPE_DISPLAY_VALUE = "01234567890123456789"; @@ -211,7 +208,7 @@ public static String selectPathFromProject( String directory, String objectName } String[] fileNames = IOHelper.getFileNames( directory ); - String fileName = selectionDialog( fileNames, objectName ); + String fileName = SwingHelper.selectionDialog( fileNames, objectName ); if ( fileName != null ) { return IOHelper.combinePath( directory, fileName ); } else { @@ -237,7 +234,7 @@ public static String selectTableFileNameFromProjectDialog( Collection< String > String[] fileNames = IOHelper.getFileNames( directory ); if ( fileNames == null ) throw new RuntimeException("Could not find any files at " + directory ); - return selectionDialog( fileNames, objectName ); + return SwingHelper.selectionDialog( fileNames, objectName ); } } @@ -248,7 +245,7 @@ private static String chooseValidTableFileName( Collection< String > directories for (int i = 0; i < choices.length; i++) { choices[i] = commonFileNames.get(i); } - return selectionDialog(choices, objectName); + return SwingHelper.selectionDialog(choices, objectName); } else { return null; } @@ -784,7 +781,7 @@ private JPanel createClearAndSourceNamesOverlayPanel( MoBIE moBIE ) { final JPanel panel = SwingUtils.horizontalLayoutPanel(); - final JButton button = SwingHelper.createButton( "clear", new Dimension( 80, TEXT_FIELD_HEIGHT ) ); + final JButton button = SwingHelper.createButton( "clear", new Dimension( 80, SwingHelper.TEXT_FIELD_HEIGHT ) ); button.addActionListener( e -> { SwingUtilities.invokeLater( () -> @@ -839,8 +836,8 @@ public JPanel createMoveToLocationPanel( ViewerTransform transform ) final JPanel panel = SwingUtils.horizontalLayoutPanel(); final JButton button = SwingHelper.createButton( MOVE ); final JTextField jTextField = new JTextField( ViewerTransform.toString( transform ) ); - jTextField.setPreferredSize( new Dimension( SwingHelper.COMBOBOX_WIDTH - 3, TEXT_FIELD_HEIGHT ) ); - jTextField.setMaximumSize( new Dimension( SwingHelper.COMBOBOX_WIDTH - 3, TEXT_FIELD_HEIGHT ) ); + jTextField.setPreferredSize( new Dimension( SwingHelper.COMBOBOX_WIDTH - 3, SwingHelper.TEXT_FIELD_HEIGHT ) ); + jTextField.setMaximumSize( new Dimension( SwingHelper.COMBOBOX_WIDTH - 3, SwingHelper.TEXT_FIELD_HEIGHT ) ); button.addActionListener( e -> { ViewerTransform viewerTransform = ViewerTransform.toViewerTransform( jTextField.getText() ); diff --git a/src/main/java/org/embl/mobie/lib/ui/WindowArrangementHelper.java b/src/main/java/org/embl/mobie/ui/WindowArrangementHelper.java similarity index 93% rename from src/main/java/org/embl/mobie/lib/ui/WindowArrangementHelper.java rename to src/main/java/org/embl/mobie/ui/WindowArrangementHelper.java index 9efc15525..7608cb49e 100644 --- a/src/main/java/org/embl/mobie/lib/ui/WindowArrangementHelper.java +++ b/src/main/java/org/embl/mobie/ui/WindowArrangementHelper.java @@ -26,15 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package org.embl.mobie.lib.ui; - -import bdv.util.BdvHandle; -import de.embl.cba.bdv.utils.BdvUtils; -import ij.WindowManager; +package org.embl.mobie.ui; import java.awt.*; -import static org.embl.mobie.lib.ui.UserInterfaceHelper.*; +import static org.embl.mobie.ui.UserInterfaceHelper.*; public class WindowArrangementHelper { diff --git a/src/test/java/develop/OpenAutoMicTable.java b/src/test/java/develop/OpenAutoMicTable.java index 97fc231e2..ad39b7d69 100644 --- a/src/test/java/develop/OpenAutoMicTable.java +++ b/src/test/java/develop/OpenAutoMicTable.java @@ -2,7 +2,7 @@ import net.imagej.ImageJ; import org.embl.mobie.command.open.OpenTableAdvancedCommand; -import org.embl.mobie.command.open.OpenTableCommand; +import org.embl.mobie.lib.transform.GridType; import java.io.File; @@ -21,7 +21,7 @@ public static void main( String[] args ) //command.images = "Result.Image.Zarr"; // Result.Image.Zarr command.root = command.table.getParentFile(); command.images = "FileName_Result.Image_IMG"; // Result.Image.Zarr - command.gridTypeString = "Transformable"; // FIXME: not working with Stitched! + command.gridType = GridType.Transformed; // FIXME: not working with Stitched! command.run(); } } diff --git a/src/test/java/projects/muscle_patterning_drosophila/OpenTeresaData.java b/src/test/java/projects/muscle_patterning_drosophila/OpenTeresaData.java index 29ac39a25..686fdb2bb 100644 --- a/src/test/java/projects/muscle_patterning_drosophila/OpenTeresaData.java +++ b/src/test/java/projects/muscle_patterning_drosophila/OpenTeresaData.java @@ -2,6 +2,7 @@ import net.imagej.ImageJ; import org.embl.mobie.command.open.OpenTableAdvancedCommand; +import org.embl.mobie.lib.transform.GridType; import java.io.File; @@ -20,7 +21,7 @@ public static void main( String[] args ) //command.images = "Result.Image.Zarr"; // Result.Image.Zarr command.root = command.table.getParentFile(); command.images = "FileName_Result.Image_IMG"; // Result.Image.Zarr - command.gridTypeString = "Transformable"; // FIXME: not working with Stitched! + command.gridType = GridType.Transformed; // FIXME: not working with Stitched! command.run(); } } From 2d952a953a10a4e2eae07fb25dfec69c29ee1744 Mon Sep 17 00:00:00 2001 From: Christian Tischer Date: Fri, 5 Jan 2024 15:58:29 +0100 Subject: [PATCH 11/12] Add view description --- .../java/org/embl/mobie/MoBIESettings.java | 13 +++++ .../OpenMultipleImagesAndLabelsCommand.java | 41 +++++++++++++- .../view/ViewerTransformLoggerCommand.java | 14 ++--- .../org/embl/mobie/lib/SpimDataAdder.java | 4 +- ...sonCreator.java => DatasetSerializer.java} | 56 ++++++++++++------- .../embl/mobie/lib/create/ImagesCreator.java | 9 ++- .../embl/mobie/lib/create/ProjectCreator.java | 8 +-- .../lib/files/FileSourcesDataSetter.java | 2 +- .../org/embl/mobie/lib/hcs/HCSDataAdder.java | 2 +- .../org/embl/mobie/lib/serialize/View.java | 24 ++++++-- .../NormalizedAffineViewerTransform.java | 8 +++ .../org/embl/mobie/lib/view/ViewManager.java | 18 ++---- .../embl/mobie/lib/view/save/ViewSaver.java | 5 +- .../lib/serialize/DatasetJsonParserTest.java | 2 +- 14 files changed, 145 insertions(+), 61 deletions(-) rename src/main/java/org/embl/mobie/lib/create/{DatasetJsonCreator.java => DatasetSerializer.java} (92%) diff --git a/src/main/java/org/embl/mobie/MoBIESettings.java b/src/main/java/org/embl/mobie/MoBIESettings.java index ca841b909..692cd458e 100644 --- a/src/main/java/org/embl/mobie/MoBIESettings.java +++ b/src/main/java/org/embl/mobie/MoBIESettings.java @@ -107,12 +107,19 @@ public MoBIESettings removeSpatialCalibration( Boolean removeSpatialCalibration return this; } + public MoBIESettings appendGroovyCode( String groovyCode ) + { + this.values.groovyScript += groovyCode; + return this; + } + public MoBIESettings openedFromCLI( Boolean cli ) { this.values.openedFromCLI = cli; return this; } + public static class Values { private String[] s3AccessAndSecretKey; @@ -126,6 +133,7 @@ public static class Values private String view = View.DEFAULT; private Boolean removeSpatialCalibration = false; private Boolean openedFromCLI = false; // started from CLI + private String groovyScript = ""; public Boolean getRemoveSpatialCalibration() { @@ -186,5 +194,10 @@ public Boolean isOpenedFromCLI() { return openedFromCLI; } + + public String getGroovyScript() + { + return groovyScript; + } } } diff --git a/src/main/java/org/embl/mobie/command/open/OpenMultipleImagesAndLabelsCommand.java b/src/main/java/org/embl/mobie/command/open/OpenMultipleImagesAndLabelsCommand.java index 222ed54ee..ae2123771 100644 --- a/src/main/java/org/embl/mobie/command/open/OpenMultipleImagesAndLabelsCommand.java +++ b/src/main/java/org/embl/mobie/command/open/OpenMultipleImagesAndLabelsCommand.java @@ -93,6 +93,9 @@ public void run() final MoBIESettings settings = new MoBIESettings(); settings.removeSpatialCalibration( removeSpatialCalibration ); + String groovyCode = generateGroovyScript(); + settings.appendGroovyCode( groovyCode ); +// System.out.println( groovyCode ); try { @@ -100,7 +103,43 @@ public void run() } catch ( IOException e ) { - e.printStackTrace(); + throw new RuntimeException( e ); } } + + public String generateGroovyScript() { + StringBuilder sb = new StringBuilder(); + + sb.append("import org.embl.mobie.command.open.OpenMultipleImagesAndLabelsCommand\n"); + sb.append("import java.io.File\n\n"); + + sb.append("OpenMultipleImagesAndLabelsCommand command = new OpenMultipleImagesAndLabelsCommand()\n"); + + if (image0 != null) sb.append(String.format("command.image0 = new File(\"%s\")\n", + image0.getAbsolutePath())); + if (image1 != null) sb.append(String.format("command.image1 = new File(\"%s\")\n", + image1.getAbsolutePath())); + if (image2 != null) sb.append(String.format("command.image2 = new File(\"%s\")\n", + image2.getAbsolutePath())); + if (image3 != null) sb.append(String.format("command.image3 = new File(\"%s\")\n", + image3.getAbsolutePath())); + + if (labels0 != null) sb.append(String.format("command.labels0 = new File(\"%s\")\n", + labels0.getAbsolutePath())); + if (labels1 != null) sb.append(String.format("command.labels1 = new File(\"%s\")\n", + labels1.getAbsolutePath())); + + if (table0 != null) sb.append(String.format("command.table0 = new File(\"%s\")\n", + table0.getAbsolutePath())); + if (table1 != null) sb.append(String.format("command.table1 = new File(\"%s\")\n", + table1.getAbsolutePath())); + + if (removeSpatialCalibration != null) { + sb.append("command.removeSpatialCalibration = ").append(removeSpatialCalibration).append("\n"); + } + + sb.append("\ncommand.run()\n"); + + return sb.toString(); + } } diff --git a/src/main/java/org/embl/mobie/command/view/ViewerTransformLoggerCommand.java b/src/main/java/org/embl/mobie/command/view/ViewerTransformLoggerCommand.java index edbb74e4e..ba8cb0463 100644 --- a/src/main/java/org/embl/mobie/command/view/ViewerTransformLoggerCommand.java +++ b/src/main/java/org/embl/mobie/command/view/ViewerTransformLoggerCommand.java @@ -39,7 +39,6 @@ import org.embl.mobie.lib.transform.viewer.NormalVectorViewerTransform; import org.embl.mobie.lib.transform.NormalizedAffineViewerTransform; import org.embl.mobie.lib.transform.viewer.PositionViewerTransform; -import org.embl.mobie.lib.transform.TransformHelper; import net.imglib2.realtransform.AffineTransform3D; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; @@ -53,30 +52,29 @@ public class ViewerTransformLoggerCommand implements BdvPlaygroundActionCommand public static final String NAME = "Log Current Location"; @Parameter - BdvHandle bdv; + BdvHandle bdvHandle; @Override public void run() { new Thread( () -> { - final int timepoint = bdv.getViewerPanel().state().getCurrentTimepoint(); - final double[] position = BdvPlaygroundHelper.getWindowCentreInCalibratedUnits( bdv ); + final int timepoint = bdvHandle.getViewerPanel().state().getCurrentTimepoint(); + final double[] position = BdvPlaygroundHelper.getWindowCentreInCalibratedUnits( bdvHandle ); // position final PositionViewerTransform positionViewerTransform = new PositionViewerTransform( position, timepoint ); // affine final AffineTransform3D affineTransform3D = new AffineTransform3D(); - bdv.getViewerPanel().state().getViewerTransform( affineTransform3D ); + bdvHandle.getViewerPanel().state().getViewerTransform( affineTransform3D ); final AffineViewerTransform affineViewerTransform = new AffineViewerTransform( affineTransform3D.getRowPackedCopy(), timepoint ); // normalized affine - final AffineTransform3D normalisedViewerTransform = TransformHelper.createNormalisedViewerTransform( bdv.getViewerPanel() ); - final NormalizedAffineViewerTransform normalizedAffineViewerTransform = new NormalizedAffineViewerTransform( normalisedViewerTransform.getRowPackedCopy(), timepoint ); + final NormalizedAffineViewerTransform normalizedAffineViewerTransform = new NormalizedAffineViewerTransform( bdvHandle ); // normal vector - double[] currentNormalVector = BdvUtils.getCurrentViewNormalVector( bdv ); + double[] currentNormalVector = BdvUtils.getCurrentViewNormalVector( bdvHandle ); final NormalVectorViewerTransform normalVectorViewerTransform = new NormalVectorViewerTransform( currentNormalVector, timepoint ); // print diff --git a/src/main/java/org/embl/mobie/lib/SpimDataAdder.java b/src/main/java/org/embl/mobie/lib/SpimDataAdder.java index 474a60e3b..73edc340c 100644 --- a/src/main/java/org/embl/mobie/lib/SpimDataAdder.java +++ b/src/main/java/org/embl/mobie/lib/SpimDataAdder.java @@ -143,7 +143,7 @@ private void addImageView( AbstractSpimData< ? > spimData, int imageIndex, Strin } final ImageDisplay< ? > imageDisplay = new ImageDisplay<>( imageName, Arrays.asList( imageName ), color, contrastLimits ); - final View view = new View( imageName, "images", Arrays.asList( imageDisplay ), null, false ); + final View view = new View( imageName, "images", Arrays.asList( imageDisplay ), null, null, false, null ); dataset.views().put( view.getName(), view ); } @@ -155,7 +155,7 @@ private void addSegmentationView( AbstractSpimData< ? > spimData, int setupId, S final double pixelWidth = viewSetup.getVoxelSize().dimension( 0 ); display.setResolution3dView( new Double[]{ pixelWidth, pixelWidth, pixelWidth } ); - final View view = new View( name, "segmentations", Arrays.asList( display ), null, false ); + final View view = new View( name, "segmentations", Arrays.asList( display ), null, null, false, null ); dataset.views().put( view.getName(), view ); } diff --git a/src/main/java/org/embl/mobie/lib/create/DatasetJsonCreator.java b/src/main/java/org/embl/mobie/lib/create/DatasetSerializer.java similarity index 92% rename from src/main/java/org/embl/mobie/lib/create/DatasetJsonCreator.java rename to src/main/java/org/embl/mobie/lib/create/DatasetSerializer.java index bf311d019..38b4f04ac 100644 --- a/src/main/java/org/embl/mobie/lib/create/DatasetJsonCreator.java +++ b/src/main/java/org/embl/mobie/lib/create/DatasetSerializer.java @@ -57,7 +57,8 @@ /** * Class to create and modify the metadata stored in dataset Json files */ -public class DatasetJsonCreator { +public class DatasetSerializer +{ ProjectCreator projectCreator; @@ -66,7 +67,7 @@ public class DatasetJsonCreator { * stored in dataset Json files * @param projectCreator projectCreator */ - public DatasetJsonCreator( ProjectCreator projectCreator ) { + public DatasetSerializer( ProjectCreator projectCreator ) { this.projectCreator = projectCreator; } @@ -94,9 +95,15 @@ public void addDataset( String datasetName, boolean is2D ) { * images from the viewer? * @param sourceTransform affine transform of image view */ - public void addImage(String imageName, String datasetName, + public void addImage(String imageName, + String datasetName, String uiSelectionGroup, - ImageDataFormat imageDataFormat, double[] contrastLimits, String colour, boolean exclusive, AffineTransform3D sourceTransform ) { + ImageDataFormat imageDataFormat, + double[] contrastLimits, + String colour, + boolean exclusive, + AffineTransform3D sourceTransform ) + { Dataset dataset = fetchDataset( datasetName ); addNewImageSource( dataset, imageName, imageDataFormat ); @@ -146,7 +153,8 @@ public void addSegmentation(String imageName, String datasetName, String uiSelec * @param datasetName dataset name * @param is2D whether dataset only contains 2D images */ - public void makeDataset2D( String datasetName, boolean is2D ) { + public void makeDataset2D( String datasetName, boolean is2D ) + { Dataset dataset = projectCreator.getDataset( datasetName ); dataset.is2D( is2D ); writeDatasetJson( datasetName, dataset ); @@ -158,7 +166,8 @@ private Dataset fetchDataset( String datasetName ) return dataset; } - private void addNewImageSource( Dataset dataset, String imageName, ImageDataFormat imageDataFormat ) { + private void addNewImageSource( Dataset dataset, String imageName, ImageDataFormat imageDataFormat ) + { Map< ImageDataFormat, StorageLocation > imageDataLocations; ImageDataSource imageSource = new ImageDataSource(); imageDataLocations = makeImageDataLocations( imageDataFormat, imageName ); @@ -166,7 +175,8 @@ private void addNewImageSource( Dataset dataset, String imageName, ImageDataForm dataset.sources().put( imageName, imageSource ); } - private void addNewSegmentationSource( Dataset dataset, String imageName, ImageDataFormat imageDataFormat ) { + private void addNewSegmentationSource( Dataset dataset, String imageName, ImageDataFormat imageDataFormat ) + { Map< ImageDataFormat, StorageLocation > imageDataLocations; SegmentationDataSource annotatedLabelMaskSource = new SegmentationDataSource(); @@ -182,7 +192,8 @@ private void addNewSegmentationSource( Dataset dataset, String imageName, ImageD } private Map< ImageDataFormat, StorageLocation > makeImageDataLocations( ImageDataFormat imageDataFormat, - String imageName ) { + String imageName ) + { Map< ImageDataFormat, StorageLocation > imageDataLocations = new HashMap<>(); StorageLocation imageStorageLocation = new StorageLocation(); if ( imageDataFormat == ImageDataFormat.OmeZarr ) { @@ -199,29 +210,34 @@ private Map< ImageDataFormat, StorageLocation > makeImageDataLocations( ImageDat private void addNewImageView( Dataset dataset, String imageName, String uiSelectionGroup, double[] contrastLimits, String colour, boolean exclusive, - AffineTransform3D sourceTransform ) { + AffineTransform3D sourceTransform ) + { View view = createImageView( imageName, uiSelectionGroup, exclusive, contrastLimits, colour, sourceTransform ); dataset.views().put( imageName, view ); } - private void addNewSegmentationView( Dataset dataset, String imageName, String uiSelectionGroup, boolean exclusive, AffineTransform3D sourceTransform ) { + private void addNewSegmentationView( Dataset dataset, String imageName, String uiSelectionGroup, boolean exclusive, AffineTransform3D sourceTransform ) + { View view = createSegmentationView( imageName, uiSelectionGroup, exclusive, sourceTransform ); dataset.views().put( imageName, view ); } private void addNewDefaultImageView( Dataset dataset, String imageName, double[] contrastLimits, String colour, - AffineTransform3D sourceTransform ) { + AffineTransform3D sourceTransform ) + { View view = createImageView( imageName, "bookmark", true, contrastLimits, colour, sourceTransform ); dataset.views().put( View.DEFAULT, view ); } - private void addNewDefaultSegmentationView( Dataset dataset, String imageName, AffineTransform3D sourceTransform ) { + private void addNewDefaultSegmentationView( Dataset dataset, String imageName, AffineTransform3D sourceTransform ) + { View view = createSegmentationView( imageName, "bookmark", true, sourceTransform ); dataset.views().put( View.DEFAULT, view ); } - private List< Transformation > createSourceTransformerList( AffineTransform3D sourceTransform, List sources ) { + private List< Transformation > createSourceTransformerList( AffineTransform3D sourceTransform, List sources ) + { List< Transformation > imageTransformationList = new ArrayList<>(); Transformation imageTransformation = new AffineTransformation( "affine", sourceTransform.getRowPackedCopy(), sources ); imageTransformationList.add( imageTransformation ); @@ -239,16 +255,17 @@ private View createImageView( String imageName, String uiSelectionGroup, boolean View view; if ( sourceTransform.isIdentity() ) { - view = new View( imageName, uiSelectionGroup, displays, null, null, isExclusive); + view = new View( imageName, uiSelectionGroup, displays, null, null, isExclusive, null ); } else { List< Transformation > imageTransformationList = createSourceTransformerList( sourceTransform, sources ); - view = new View( imageName, uiSelectionGroup, displays, imageTransformationList, null, isExclusive ); + view = new View( imageName, uiSelectionGroup, displays, imageTransformationList, null, isExclusive, null ); } return view; } - private View createSegmentationView( String imageName, String uiSelectionGroup, boolean isExclusive, AffineTransform3D sourceTransform ) { + private View createSegmentationView( String imageName, String uiSelectionGroup, boolean isExclusive, AffineTransform3D sourceTransform ) + { ArrayList< Display< ? > > displays = new ArrayList<>(); ArrayList sources = new ArrayList<>(); sources.add( imageName ); @@ -257,10 +274,10 @@ private View createSegmentationView( String imageName, String uiSelectionGroup, displays.add( segmentationDisplay ); if ( sourceTransform.isIdentity() ) { - return new View( imageName, uiSelectionGroup, displays, null, null, isExclusive ); + return new View( imageName, uiSelectionGroup, displays, null, null, isExclusive, null ); } else { List< Transformation > imageTransformationList = createSourceTransformerList( sourceTransform, sources ); - return new View( imageName, uiSelectionGroup, displays, imageTransformationList, null, isExclusive ); + return new View( imageName, uiSelectionGroup, displays, imageTransformationList, null, isExclusive, null ); } } @@ -269,7 +286,8 @@ private View createSegmentationView( String imageName, String uiSelectionGroup, * @param datasetName dataset name * @param dataset dataset */ - public void writeDatasetJson ( String datasetName, Dataset dataset ) { + public void writeDatasetJson ( String datasetName, Dataset dataset ) + { try { String datasetJsonPath = IOHelper.combinePath( projectCreator.getProjectLocation().getAbsolutePath(), datasetName, "dataset.json" ); diff --git a/src/main/java/org/embl/mobie/lib/create/ImagesCreator.java b/src/main/java/org/embl/mobie/lib/create/ImagesCreator.java index ad26a5bad..af4338a33 100644 --- a/src/main/java/org/embl/mobie/lib/create/ImagesCreator.java +++ b/src/main/java/org/embl/mobie/lib/create/ImagesCreator.java @@ -34,7 +34,6 @@ import de.embl.cba.tables.Tables; import ij.IJ; import ij.ImagePlus; -import ij.gui.Overlay; import ij.process.LUT; import mpicbg.spim.data.SpimData; import mpicbg.spim.data.SpimDataException; @@ -645,15 +644,15 @@ private SpimData tryOpenSpimData( ImageDataFormat imageDataFormat, String filePa } private void updateTableAndJsonsForNewImage ( String imageName, String datasetName, String uiSelectionGroup, ImageDataFormat imageDataFormat, double[] contrastLimits, String colour, boolean exclusive, AffineTransform3D sourceTransform ) { - DatasetJsonCreator datasetJsonCreator = projectCreator.getDatasetJsonCreator(); - datasetJsonCreator.addImage( imageName, datasetName, uiSelectionGroup, + DatasetSerializer datasetSerializer = projectCreator.getDatasetJsonCreator(); + datasetSerializer.addImage( imageName, datasetName, uiSelectionGroup, imageDataFormat, contrastLimits, colour, exclusive, sourceTransform ); } private void updateTableAndJsonsForNewSegmentation( String imageName, String datasetName, String uiSelectionGroup, ImageDataFormat imageDataFormat, boolean exclusive, AffineTransform3D sourceTransform ) { addDefaultTableForImage( imageName, datasetName, imageDataFormat ); - DatasetJsonCreator datasetJsonCreator = projectCreator.getDatasetJsonCreator(); - datasetJsonCreator.addSegmentation( imageName, datasetName, uiSelectionGroup, imageDataFormat, exclusive, sourceTransform ); + DatasetSerializer datasetSerializer = projectCreator.getDatasetJsonCreator(); + datasetSerializer.addSegmentation( imageName, datasetName, uiSelectionGroup, imageDataFormat, exclusive, sourceTransform ); } private void copyImage ( ImageDataFormat imageFormat, SpimData spimData, diff --git a/src/main/java/org/embl/mobie/lib/create/ProjectCreator.java b/src/main/java/org/embl/mobie/lib/create/ProjectCreator.java index aac673569..305c432a8 100644 --- a/src/main/java/org/embl/mobie/lib/create/ProjectCreator.java +++ b/src/main/java/org/embl/mobie/lib/create/ProjectCreator.java @@ -68,7 +68,7 @@ public class ProjectCreator { private final DatasetsCreator datasetsCreator; private final ImagesCreator imagesCreator; - private final DatasetJsonCreator datasetJsonCreator; + private final DatasetSerializer datasetSerializer; private final ProjectJsonCreator projectJsonCreator; private final RemoteMetadataCreator remoteMetadataCreator; @@ -103,7 +103,7 @@ public ProjectCreator( File projectLocation ) throws IOException { } this.datasetsCreator = new DatasetsCreator( this ); - this.datasetJsonCreator = new DatasetJsonCreator( this ); + this.datasetSerializer = new DatasetSerializer( this ); this.projectJsonCreator = new ProjectJsonCreator( this ); this.imagesCreator = new ImagesCreator( this ); this.remoteMetadataCreator = new RemoteMetadataCreator( this ); @@ -216,8 +216,8 @@ public ImagesCreator getImagesCreator() { return imagesCreator; } - public DatasetJsonCreator getDatasetJsonCreator() { - return datasetJsonCreator; + public DatasetSerializer getDatasetJsonCreator() { + return datasetSerializer; } public ProjectJsonCreator getProjectJsonCreator() { return projectJsonCreator; } diff --git a/src/main/java/org/embl/mobie/lib/files/FileSourcesDataSetter.java b/src/main/java/org/embl/mobie/lib/files/FileSourcesDataSetter.java index 37f662dd1..78558898e 100644 --- a/src/main/java/org/embl/mobie/lib/files/FileSourcesDataSetter.java +++ b/src/main/java/org/embl/mobie/lib/files/FileSourcesDataSetter.java @@ -243,7 +243,7 @@ else if ( sources.getGridType().equals( GridType.Transformed ) ) // // FIXME: Maybe the viewerTransform could be something else? final ImageZoomViewerTransform viewerTransform = new ImageZoomViewerTransform( transformations.get( 0 ).getSources().get( 0 ), 0 ); - final View gridView = new View( "all images", "data", displays, transformations, viewerTransform, false ); + final View gridView = new View( "all images", "data", displays, transformations, viewerTransform, false, null ); //gridView.overlayNames( true ); // FIXME: Timepoint bug! dataset.views().put( gridView.getName(), gridView ); } diff --git a/src/main/java/org/embl/mobie/lib/hcs/HCSDataAdder.java b/src/main/java/org/embl/mobie/lib/hcs/HCSDataAdder.java index 185252f7c..b86fabac6 100644 --- a/src/main/java/org/embl/mobie/lib/hcs/HCSDataAdder.java +++ b/src/main/java/org/embl/mobie/lib/hcs/HCSDataAdder.java @@ -200,7 +200,7 @@ public void addData( Dataset dataset ) final ArrayList< String > wells = new ArrayList<>( wellDisplay.sources.keySet() ); Collections.sort( wells ); final ImageZoomViewerTransform viewerTransform = new ImageZoomViewerTransform( wells.get( 0 ), 0 ); - final View view = new View( plate.getName(), "plate", displays, imageTransforms, viewerTransform, true ); + final View view = new View( plate.getName(), "plate", displays, imageTransforms, viewerTransform, true, null ); dataset.views().put( view.getName(), view ); } diff --git a/src/main/java/org/embl/mobie/lib/serialize/View.java b/src/main/java/org/embl/mobie/lib/serialize/View.java index 26423d892..227c65abd 100644 --- a/src/main/java/org/embl/mobie/lib/serialize/View.java +++ b/src/main/java/org/embl/mobie/lib/serialize/View.java @@ -57,23 +57,30 @@ public class View private boolean isExclusive = false; - // Runtime + private String description = null; // TODO: make part of the spec + + // Runtime + // private transient String name; private transient boolean overlayNames = false; // TODO add to JSON spec? - - public View( String name, String uiSelectionGroup, List< Display< ? > > sourceDisplays, List< Transformation > sourceTransforms, boolean isExclusive ) { - this( name, uiSelectionGroup, sourceDisplays, sourceTransforms, null, isExclusive); - } - public View( String name, String uiSelectionGroup, List< Display< ? > > sourceDisplays, List< Transformation > sourceTransforms, @Nullable ViewerTransform viewerTransform, boolean isExclusive ) { + public View( String name, + String uiSelectionGroup, + List< Display< ? > > sourceDisplays, + List< Transformation > sourceTransforms, + @Nullable ViewerTransform viewerTransform, + boolean isExclusive, + @Nullable String description + ) { this.name = name; this.uiSelectionGroup = uiSelectionGroup; this.sourceDisplays = sourceDisplays; this.sourceTransforms = sourceTransforms; this.viewerTransform = viewerTransform; this.isExclusive = isExclusive; + this.description = description; } // map of data source name to the @@ -176,4 +183,9 @@ public void overlayNames( boolean overlayNames ) { this.overlayNames = overlayNames; } + + public String getDescription() + { + return description; + } } diff --git a/src/main/java/org/embl/mobie/lib/transform/NormalizedAffineViewerTransform.java b/src/main/java/org/embl/mobie/lib/transform/NormalizedAffineViewerTransform.java index 29d6841bd..083fb26bf 100644 --- a/src/main/java/org/embl/mobie/lib/transform/NormalizedAffineViewerTransform.java +++ b/src/main/java/org/embl/mobie/lib/transform/NormalizedAffineViewerTransform.java @@ -28,6 +28,8 @@ */ package org.embl.mobie.lib.transform; +import bdv.util.BdvHandle; +import net.imglib2.realtransform.AffineTransform3D; import org.embl.mobie.lib.transform.viewer.ViewerTransform; public class NormalizedAffineViewerTransform implements ViewerTransform @@ -36,6 +38,12 @@ public class NormalizedAffineViewerTransform implements ViewerTransform private double[] normalizedAffine; private Integer timepoint; + public NormalizedAffineViewerTransform( BdvHandle bdvHandle ) + { + normalizedAffine = TransformHelper.createNormalisedViewerTransform( bdvHandle.getViewerPanel() ).getRowPackedCopy(); + timepoint = bdvHandle.getViewerPanel().state().getCurrentTimepoint(); + } + public NormalizedAffineViewerTransform( double[] parameters, int timepoint ) { this.normalizedAffine = parameters; diff --git a/src/main/java/org/embl/mobie/lib/view/ViewManager.java b/src/main/java/org/embl/mobie/lib/view/ViewManager.java index 4185191ba..324ffd432 100644 --- a/src/main/java/org/embl/mobie/lib/view/ViewManager.java +++ b/src/main/java/org/embl/mobie/lib/view/ViewManager.java @@ -156,7 +156,7 @@ private void addImageTransforms( List< Transformation > imageTransforms, List< ? } } - public View createCurrentView( String uiSelectionGroup, boolean isExclusive, boolean includeViewerTransform ) + public View createViewFromCurrentState( String uiSelectionGroup, boolean isExclusive, boolean includeViewerTransform, String description ) { // Create serialisable copies of the current displays List< Display< ? > > displays = new ArrayList<>(); @@ -178,17 +178,9 @@ else if ( display instanceof SpotDisplay ) addImageTransforms( transformations, display.sourceAndConverters() ); } - if ( includeViewerTransform ) - { - final BdvHandle bdvHandle = sliceViewer.getBdvHandle(); - AffineTransform3D normalisedViewTransform = TransformHelper.createNormalisedViewerTransform( bdvHandle.getViewerPanel() ); - final NormalizedAffineViewerTransform transform = new NormalizedAffineViewerTransform( normalisedViewTransform.getRowPackedCopy(), bdvHandle.getViewerPanel().state().getCurrentTimepoint() ); - return new View( "", uiSelectionGroup, displays, transformations, transform, isExclusive); - } - else - { - return new View( "", uiSelectionGroup, displays, transformations, isExclusive ); - } + NormalizedAffineViewerTransform normalizedAffineViewerTransform = includeViewerTransform ? + new NormalizedAffineViewerTransform( sliceViewer.getBdvHandle() ) : null; + return new View( "", uiSelectionGroup, displays, transformations, normalizedAffineViewerTransform, isExclusive, description ); } public synchronized void show( String viewName ) @@ -202,6 +194,8 @@ public synchronized void show( View view ) { final long startTime = System.currentTimeMillis(); IJ.log( "Opening view: " + view.getName() ); + if ( view.getDescription() != null ) + IJ.log( "Description: " + view.getDescription() ); if ( view.isExclusive() ) { diff --git a/src/main/java/org/embl/mobie/lib/view/save/ViewSaver.java b/src/main/java/org/embl/mobie/lib/view/save/ViewSaver.java index 0bbb6c0cf..cd43b81e0 100644 --- a/src/main/java/org/embl/mobie/lib/view/save/ViewSaver.java +++ b/src/main/java/org/embl/mobie/lib/view/save/ViewSaver.java @@ -107,6 +107,7 @@ public void viewSettingsDialog( SaveMethod saveMethod, MoBIEHelper.FileLocation if ( saveMethod == SaveMethod.saveAsNewView ) { gd.addStringField("View name:", "", 25 ); + gd.addStringField( "View description:", "", 50 ); } String[] currentUiSelectionGroups = moBIE.getUserInterface().getUISelectionGroupNames(); @@ -130,8 +131,10 @@ public void viewSettingsDialog( SaveMethod saveMethod, MoBIEHelper.FileLocation if (!gd.wasCanceled()) { String viewName = null; + String viewDescription = ""; if( saveMethod == SaveMethod.saveAsNewView ) { viewName = UserInterfaceHelper.tidyString( gd.getNextString() ); + viewDescription = gd.getNextString(); if ( viewName == null ) { return; } @@ -155,7 +158,7 @@ public void viewSettingsDialog( SaveMethod saveMethod, MoBIEHelper.FileLocation uiSelectionGroup = ProjectCreatorHelper.makeNewUiSelectionGroup(currentUiSelectionGroups); } - View currentView = moBIE.getViewManager().createCurrentView(uiSelectionGroup, exclusive, includeViewerTransform); + View currentView = moBIE.getViewManager().createViewFromCurrentState(uiSelectionGroup, exclusive, includeViewerTransform, viewDescription); if ( uiSelectionGroup != null && currentView != null ) { if ( fileLocation == MoBIEHelper.FileLocation.Project && saveMethod == SaveMethod.saveAsNewView ) { diff --git a/src/test/java/org/embl/mobie/lib/serialize/DatasetJsonParserTest.java b/src/test/java/org/embl/mobie/lib/serialize/DatasetJsonParserTest.java index 676c23d73..377e2dd59 100644 --- a/src/test/java/org/embl/mobie/lib/serialize/DatasetJsonParserTest.java +++ b/src/test/java/org/embl/mobie/lib/serialize/DatasetJsonParserTest.java @@ -87,7 +87,7 @@ public void savePlatyView() throws IOException moBIE.getViewManager().show( views.get("Suppl. Fig. 2A: Neuron" ) ); // grab the current view and save it - View view = moBIE.getViewManager().createCurrentView( uiSelectionGroup, isExclusive, true ); + View view = moBIE.getViewManager().createViewFromCurrentState( uiSelectionGroup, isExclusive, true, "" ); dataset.views().put( viewName, view ); String datasetJSONPath = new File( tempDir, datasetJsonName ).getAbsolutePath(); From 4ceb54650d28cf9adc39d46dd586f78d2a2c13c8 Mon Sep 17 00:00:00 2001 From: Christian Tischer Date: Fri, 5 Jan 2024 19:52:50 +0100 Subject: [PATCH 12/12] Add ROIs to ScreenShotMaker --- .../context/AutomaticRegistrationCommand.java | 31 ++++--- .../context/ScreenShotMakerCommand.java | 10 +-- .../context/SetTransformationCommand.java | 5 +- .../context/ShowRasterImagesCommand.java | 2 +- .../context/SourceInfoLoggerCommand.java | 84 ------------------ .../command/context/SourcesInfoCommand.java | 8 +- .../embl/mobie/lib/bdv/ScreenShotMaker.java | 88 +++++++++++-------- .../embl/mobie/lib/bdv/view/SliceViewer.java | 3 +- .../embl/mobie/lib/image/SpimDataImage.java | 10 +-- .../em_xray_alignment/OpenEMXRAY.java | 2 +- 10 files changed, 88 insertions(+), 155 deletions(-) delete mode 100644 src/main/java/org/embl/mobie/command/context/SourceInfoLoggerCommand.java diff --git a/src/main/java/org/embl/mobie/command/context/AutomaticRegistrationCommand.java b/src/main/java/org/embl/mobie/command/context/AutomaticRegistrationCommand.java index 4c4484f57..2cdc86194 100644 --- a/src/main/java/org/embl/mobie/command/context/AutomaticRegistrationCommand.java +++ b/src/main/java/org/embl/mobie/command/context/AutomaticRegistrationCommand.java @@ -35,7 +35,11 @@ import ij.IJ; import ij.ImagePlus; import ij.ImageStack; +import ij.gui.Roi; +import ij.plugin.filter.ThresholdToSelection; +import ij.process.ByteProcessor; import ij.process.ImageConverter; +import ij.process.ImageProcessor; import net.imglib2.realtransform.AffineTransform3D; import org.embl.mobie.command.CommandConstants; import org.embl.mobie.lib.MoBIEHelper; @@ -51,6 +55,7 @@ import sc.fiji.bdvpg.bdv.BdvHandleHelper; import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; +import java.awt.*; import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutionException; @@ -75,9 +80,6 @@ public class AutomaticRegistrationCommand extends DynamicCommand implements BdvP @Parameter(label="Registration Voxel Size", persist = false, min = "0.0", style="format:#.00000") public Double voxelSize = 1D; - @Parameter(label="Out of Bounds Value") - public String outOfBoundsValue = "Do not use"; - @Parameter ( label = "Transformation", choices = { TRANSLATION, RIGID, SIMILARITY, AFFINE } ) private String transformationType = TRANSLATION; @@ -153,15 +155,7 @@ private void compute() // create two 2D ImagePlus that are to be aligned // ScreenShotMaker screenShotMaker = new ScreenShotMaker( bdvHandle, sacA.getSpimSource().getVoxelDimensions().unit() ); - try - { - // useful for images with bright background - // to avoid an edge with the black background of BDV - screenShotMaker.setOutOfBoundsValue( Float.parseFloat( this.outOfBoundsValue ) ); - } catch ( Exception e ) - { - // don't set an out-of-bounds value. - } + screenShotMaker.run( Arrays.asList( sacA, sacB ), voxelSize ); CompositeImage compositeImage = screenShotMaker.getCompositeImagePlus(); AffineTransform3D canvasToGlobalTransform = screenShotMaker.getCanvasToGlobalTransform(); @@ -170,16 +164,21 @@ private void compute() ImagePlus impA = new ImagePlus( imageA + " (fixed)", stack.getProcessor( 1 ) ); ImagePlus impB = new ImagePlus( imageB + " (moving)", stack.getProcessor( 2 ) ); - // set the display ranges and burn them in by converting to unit8 + // set the display ranges and burn them in by converting to uint8 // this is important for the intensity based registration methods compositeImage.setPosition( 1 ); impA.getProcessor().setMinAndMax( compositeImage.getDisplayRangeMin(), compositeImage.getDisplayRangeMax() ); + new ImageConverter( impA ).convertToGray8(); + compositeImage.setPosition( 2 ); impB.getProcessor().setMinAndMax( compositeImage.getDisplayRangeMin(), compositeImage.getDisplayRangeMax() ); - new ImageConverter( impA ).convertToGray8(); new ImageConverter( impB ).convertToGray8(); - double min = impA.getProcessor().getMax(); - double max = impA.getProcessor().getMax(); + + // set the rois within which the images contain valid pixel values + // those are used by some registration methods, e.g. turboReg + Roi[] rois = screenShotMaker.getMasks(); + impA.setRoi( rois[ 0 ] ); + impB.setRoi( rois[ 1 ] ); // compute the transformation that aligns the two images in 2D // 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 0ce0c18d5..e1a8c2876 100644 --- a/src/main/java/org/embl/mobie/command/context/ScreenShotMakerCommand.java +++ b/src/main/java/org/embl/mobie/command/context/ScreenShotMakerCommand.java @@ -30,6 +30,8 @@ import bdv.util.BdvHandle; import ij.IJ; +import ij.gui.Roi; +import ij.plugin.frame.RoiManager; import org.embl.mobie.MoBIE; import org.embl.mobie.command.CommandConstants; import org.embl.mobie.lib.bdv.ScreenShotMaker; @@ -63,13 +65,13 @@ public class ScreenShotMakerCommand extends DynamicCommand implements BdvPlaygro @Override public void run() { + if ( MoBIE.getInstance().getSettings().values.isOpenedFromCLI() ) + MoBIE.imageJ.ui().showUI(); + ScreenShotMaker screenShotMaker = new ScreenShotMaker( bdvHandle, pixelUnit ); screenShotMaker.run( targetSamplingInXY ); screenShotMaker.getRGBImagePlus().show(); screenShotMaker.getCompositeImagePlus().show(); - - if ( MoBIE.getInstance().getSettings().values.isOpenedFromCLI() ) - MoBIE.imageJ.ui().showUI(); } @Override @@ -99,7 +101,5 @@ private void showNumPixels() { final long[] sizeInPixels = ScreenShotMaker.getCaptureImageSizeInPixels( bdvHandle, targetSamplingInXY ); IJ.log( CAPTURE_SIZE_PIXELS + Arrays.toString( sizeInPixels ) ); -// final MutableModuleItem< String > message = getInfo().getMutableInput("message", String.class); -// message.setValue( this, CAPTURE_SIZE_PIXELS + sizeInPixels[ 0 ] + ", " + sizeInPixels[ 1 ] ); } } diff --git a/src/main/java/org/embl/mobie/command/context/SetTransformationCommand.java b/src/main/java/org/embl/mobie/command/context/SetTransformationCommand.java index 8052d9915..96c7f1d97 100644 --- a/src/main/java/org/embl/mobie/command/context/SetTransformationCommand.java +++ b/src/main/java/org/embl/mobie/command/context/SetTransformationCommand.java @@ -157,8 +157,9 @@ else if ( mode.equals( AdditionMode.Concatenate ) ) } } - public static double[] parseStringToDoubleArray(String arrayStr) { - arrayStr = arrayStr.replaceAll("[^0-9.,]", ""); + public static double[] parseStringToDoubleArray(String arrayStr) + { + arrayStr = arrayStr.replaceAll("\\[|\\]", ""); String[] items = arrayStr.split(",\\s*"); double[] doubles = Arrays.stream(items).mapToDouble(Double::parseDouble).toArray(); return doubles; diff --git a/src/main/java/org/embl/mobie/command/context/ShowRasterImagesCommand.java b/src/main/java/org/embl/mobie/command/context/ShowRasterImagesCommand.java index ca9990498..fd032e1aa 100644 --- a/src/main/java/org/embl/mobie/command/context/ShowRasterImagesCommand.java +++ b/src/main/java/org/embl/mobie/command/context/ShowRasterImagesCommand.java @@ -61,7 +61,7 @@ import java.util.Optional; import java.util.Set; -@Plugin(type = BdvPlaygroundActionCommand.class, menuPath = CommandConstants.CONTEXT_MENU_ITEMS_ROOT + "Show " + ShowRasterImagesCommand.RAW + " Image(s)" ) +@Plugin(type = BdvPlaygroundActionCommand.class, menuPath = CommandConstants.CONTEXT_MENU_ITEMS_ROOT + "Show " + ShowRasterImagesCommand.RAW + " Images" ) public class ShowRasterImagesCommand< T extends NumericType< T > > implements BdvPlaygroundActionCommand { static { net.imagej.patcher.LegacyInjector.preinit(); } diff --git a/src/main/java/org/embl/mobie/command/context/SourceInfoLoggerCommand.java b/src/main/java/org/embl/mobie/command/context/SourceInfoLoggerCommand.java deleted file mode 100644 index d29f2248b..000000000 --- a/src/main/java/org/embl/mobie/command/context/SourceInfoLoggerCommand.java +++ /dev/null @@ -1,84 +0,0 @@ -/*- - * #%L - * Fiji viewer for MoBIE projects - * %% - * Copyright (C) 2018 - 2023 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 org.embl.mobie.command.context; - -import bdv.util.BdvHandle; -import bdv.viewer.Source; -import bdv.viewer.SourceAndConverter; -import ij.IJ; -import mpicbg.spim.data.sequence.VoxelDimensions; -import net.imglib2.realtransform.AffineTransform3D; -import org.embl.mobie.command.CommandConstants; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; - -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -@Plugin( type = BdvPlaygroundActionCommand.class, name = SourceInfoLoggerCommand.NAME, menuPath = CommandConstants.CONTEXT_MENU_ITEMS_ROOT + SourceInfoLoggerCommand.NAME ) -public class SourceInfoLoggerCommand implements BdvPlaygroundActionCommand -{ - static { net.imagej.patcher.LegacyInjector.preinit(); } - - public static final String NAME = "Log Source(s) Info"; - - @Parameter - BdvHandle bdvHandle; - - @Override - public void run() - { - new Thread( () -> { - final int t = bdvHandle.getViewerPanel().state().getCurrentTimepoint(); - - final Set< SourceAndConverter< ? > > sourceAndConverters = bdvHandle.getViewerPanel().state().getVisibleAndPresentSources(); - - final List< ? extends Source< ? > > sources = sourceAndConverters.stream().map( sac -> sac.getSpimSource() ).sorted( Comparator.comparing( s -> s.getName() ) ).collect( Collectors.toList() ); - - for ( Source< ? > source : sources ) - { - IJ.log( "\n## " + source.getName() ); - final AffineTransform3D affineTransform3D = new AffineTransform3D(); - source.getSourceTransform( t, 0, affineTransform3D ); - final VoxelDimensions voxelDimensions = source.getVoxelDimensions(); - if ( voxelDimensions.numDimensions() > 0 ) - IJ.log( voxelDimensions.toString() ); - IJ.log( "Resolution levels: " + source.getNumMipmapLevels() ); - IJ.log( "Data type: " + source.getType().getClass() ); - IJ.log( "Shape: " + Arrays.toString( source.getSource( 0,0 ).dimensionsAsLongArray() ) ); - IJ.log( "Transform from array to global space: " + affineTransform3D ); - - } - } ).start(); - } -} diff --git a/src/main/java/org/embl/mobie/command/context/SourcesInfoCommand.java b/src/main/java/org/embl/mobie/command/context/SourcesInfoCommand.java index c7088196b..0e6945e35 100644 --- a/src/main/java/org/embl/mobie/command/context/SourcesInfoCommand.java +++ b/src/main/java/org/embl/mobie/command/context/SourcesInfoCommand.java @@ -55,8 +55,6 @@ public class SourcesInfoCommand implements BdvPlaygroundActionCommand { static { net.imagej.patcher.LegacyInjector.preinit(); } - public static final String CAPTURE_SIZE_PIXELS = "Capture size [pixels]: "; - @Parameter public BdvHandle bdvHandle; @@ -68,6 +66,8 @@ public void run() { Source< ? > source = sac.getSpimSource(); IJ.log( "# " + source.getName() ); + IJ.log( "Data type: " + source.getType().getClass() ); + IJ.log( "Shape: " + Arrays.toString( source.getSource( 0,0 ).dimensionsAsLongArray() ) ); IJ.log( "Number of resolution levels: " + source.getNumMipmapLevels() ); IJ.log( "Voxel size: " + Arrays.toString( source.getVoxelDimensions().dimensionsAsDoubleArray() ) ); @@ -75,9 +75,9 @@ public void run() { AffineTransform3D transform3D = new AffineTransform3D(); ( ( TransformedSource ) source ).getFixedTransform( transform3D ); - IJ.log( "Additional transform: " + transform3D ); + IJ.log( "Additional transform:\n" + Arrays.toString( transform3D.getRowPackedCopy() ) ); source.getSourceTransform( 0, 0, transform3D ); - IJ.log( "Full transform: " + transform3D ); + IJ.log( "Full transform:\n" + Arrays.toString( transform3D.getRowPackedCopy() ) ); } }); } 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 f643f4a10..1e560eaf3 100644 --- a/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java +++ b/src/main/java/org/embl/mobie/lib/bdv/ScreenShotMaker.java @@ -36,11 +36,15 @@ import ij.CompositeImage; import ij.IJ; import ij.ImagePlus; +import ij.gui.Overlay; +import ij.gui.Roi; import ij.plugin.Duplicator; +import ij.plugin.filter.ThresholdToSelection; import ij.process.LUT; import net.imglib2.*; import net.imglib2.Cursor; import net.imglib2.type.Type; +import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.real.FloatType; import net.imglib2.util.Intervals; import org.embl.mobie.lib.MoBIEHelper; @@ -83,8 +87,6 @@ public class ScreenShotMaker private long[] screenshotDimensions = new long[2]; private AffineTransform3D canvasToGlobalTransform; - private Float outOfBoundsValue = null; - public ScreenShotMaker( BdvHandle bdvHandle, String voxelUnit ) { this.bdvHandle = bdvHandle; this.voxelUnit = voxelUnit; @@ -100,9 +102,9 @@ public CompositeImage getCompositeImagePlus() return compositeImagePlus; } - public void setOutOfBoundsValue( Float outOfBoundsValue ) + public Roi[] getMasks() { - this.outOfBoundsValue = outOfBoundsValue; + return compositeImagePlus.getOverlay().toArray(); } public void run( Double targetSamplingInXY ) @@ -135,7 +137,8 @@ public void run( List< SourceAndConverter< ? > > sacs, double targetVoxelSpacing IJ.log( "Fetching data from " + sacs.size() + " images..." ); final ArrayList< RandomAccessibleInterval< FloatType > > floatCaptures = new ArrayList<>(); - final ArrayList< RandomAccessibleInterval< ARGBType > > argbSources = new ArrayList<>(); + final ArrayList< RandomAccessibleInterval< BitType > > maskCaptures = new ArrayList<>(); + final ArrayList< RandomAccessibleInterval< ARGBType > > argbCaptures = new ArrayList<>(); final ArrayList< ARGBType > colors = new ArrayList<>(); final ArrayList< double[] > displayRanges = new ArrayList<>(); @@ -154,8 +157,10 @@ public void run( List< SourceAndConverter< ? > > sacs, double targetVoxelSpacing final long currentTimeMillis = System.currentTimeMillis(); for ( SourceAndConverter< ? > sac : sacs ) { - final RandomAccessibleInterval< FloatType > rawCapture + final RandomAccessibleInterval< FloatType > floatCapture = ArrayImgs.floats( screenshotDimensions[ 0 ], screenshotDimensions[ 1 ] ); + final RandomAccessibleInterval< BitType > maskCapture + = ArrayImgs.bits( screenshotDimensions[ 0 ], screenshotDimensions[ 1 ] ); final RandomAccessibleInterval< ARGBType > argbCapture = ArrayImgs.argbs( screenshotDimensions[ 0 ], screenshotDimensions[ 1 ] ); @@ -181,17 +186,19 @@ public void run( List< SourceAndConverter< ? > > sacs, double targetVoxelSpacing ( ThreadHelper.ioExecutorService.submit( () -> { - RealRandomAccess< ? extends Type< ? > > access = getRealRandomAccess( ( Source< Type< ? > > ) source, currentTimepoint, level, interpolate ); + RealRandomAccess< ? extends Type< ? > > sourceAccess = getRealRandomAccess( ( Source< Type< ? > > ) source, currentTimepoint, level, interpolate ); RandomAccessibleInterval< ? > sourceInterval = source.getSource( currentTimepoint, level ); // to collect raw data - final IntervalView< FloatType > floatCrop = Views.interval( rawCapture, interval ); - final Cursor< FloatType > floatCaptureCursor = Views.iterable( floatCrop ).localizingCursor(); - final RandomAccess< FloatType > floatCaptureAccess = floatCrop.randomAccess(); + final IntervalView< FloatType > floatCrop = Views.interval( floatCapture, interval ); + final Cursor< FloatType > floatCursor = Views.iterable( floatCrop ).localizingCursor(); + final RandomAccess< FloatType > floatAccess = floatCrop.randomAccess(); + + // to collect masks + final RandomAccess< BitType > maskAccess = Views.interval( maskCapture, interval ).randomAccess(); // to collect colored data - final IntervalView< ARGBType > argbCrop = Views.interval( argbCapture, interval ); - final RandomAccess< ARGBType > argbCaptureAccess = argbCrop.randomAccess(); + final RandomAccess< ARGBType > argbAccess = Views.interval( argbCapture, interval ).randomAccess(); final double[] canvasPosition = new double[ 3 ]; final double[] sourceRealPosition = new double[ 3 ]; @@ -199,28 +206,30 @@ public void run( List< SourceAndConverter< ? > > sacs, double targetVoxelSpacing final ARGBType argbType = new ARGBType(); // iterate through the target image in pixel units - while ( floatCaptureCursor.hasNext() ) + while ( floatCursor.hasNext() ) { - floatCaptureCursor.fwd(); - floatCaptureCursor.localize( canvasPosition ); - floatCaptureAccess.setPosition( floatCaptureCursor ); - argbCaptureAccess.setPosition( floatCaptureCursor ); - + // set the positions + floatCursor.fwd(); + floatCursor.localize( canvasPosition ); + floatAccess.setPosition( floatCursor ); + maskAccess.setPosition( floatCursor ); + argbAccess.setPosition( floatCursor ); targetCanvasToSourceTransform.apply( canvasPosition, sourceRealPosition ); - access.setPosition( sourceRealPosition ); + sourceAccess.setPosition( sourceRealPosition ); - boolean contains = Intervals.contains( sourceInterval, new RealPoint( sourceRealPosition ) ); - if ( ! contains && outOfBoundsValue != null ) + // set the pixel values + if ( Intervals.contains( sourceInterval, new RealPoint( sourceRealPosition ) ) ) { - floatCaptureAccess.get().setReal( outOfBoundsValue ); - // TODO how to set the ARGB value? + maskAccess.get().set( true ); + setFloatPixelValue( sourceAccess, floatAccess ); + setArgbPixelValue( converter, sourceAccess, argbAccess, argbType ); } else { - setFloatPixelValue( access, floatCaptureAccess ); - setArgbPixelValue( converter, access, argbCaptureAccess, argbType ); + maskAccess.get().set( false ); } + // log progress pixelCount.incrementAndGet(); final double currentFractionDone = 1.0 * pixelCount.get() / numPixels; if ( currentFractionDone >= fractionDone.get() ) @@ -243,8 +252,9 @@ public void run( List< SourceAndConverter< ? > > sacs, double targetVoxelSpacing ThreadHelper.waitUntilFinished( futures ); - floatCaptures.add( rawCapture ); - argbSources.add( argbCapture ); + floatCaptures.add( floatCapture ); + maskCaptures.add( maskCapture ); + argbCaptures.add( argbCapture ); displayRanges.add( BdvHandleHelper.getDisplayRange( SourceAndConverterServices.getSourceAndConverterService().getConverterSetup( sac ) ) ); } @@ -255,8 +265,8 @@ public void run( List< SourceAndConverter< ? > > sacs, double targetVoxelSpacing if ( ! floatCaptures.isEmpty() ) { - rgbImagePlus = createRGBImagePlus( voxelUnit, argbSources, voxelSpacing, sacs ); - compositeImagePlus = createCompositeImagePlus( voxelSpacing, voxelUnit, floatCaptures, colors, displayRanges ); + rgbImagePlus = createRGBImagePlus( voxelUnit, argbCaptures, voxelSpacing, sacs ); + compositeImagePlus = createCompositeImagePlus( voxelSpacing, voxelUnit, floatCaptures, maskCaptures, displayRanges ); } } @@ -419,20 +429,19 @@ private ImagePlus asImagePlus( RandomAccessibleInterval< ARGBType > argbCapture, public static CompositeImage createCompositeImagePlus( double[] voxelSpacing, String voxelUnit, - ArrayList< RandomAccessibleInterval< FloatType > > rais, - ArrayList< ARGBType > colors, + ArrayList< RandomAccessibleInterval< FloatType > > floatCaptures, + ArrayList< RandomAccessibleInterval< BitType > > maskCaptures, ArrayList< double[] > displayRanges ) { - final RandomAccessibleInterval< FloatType > stack = Views.stack( rais ); - - final ImagePlus imp = ImageJFunctions.wrap( stack, "Multi-Channel" ); + final ImagePlus imp = ImageJFunctions.wrap( Views.stack( floatCaptures ), "Floats" ); + final ImagePlus mask = ImageJFunctions.wrap( Views.stack( maskCaptures ), "Masks" ); // duplicate: otherwise it is virtual and cannot be modified final ImagePlus dup = new Duplicator().run( imp ); IJ.run( dup, "Properties...", - "channels="+rais.size() + "channels="+floatCaptures.size() +" slices=1 frames=1 unit=" + voxelUnit +" pixel_width=" + voxelSpacing[ 0 ] +" pixel_height=" + voxelSpacing[ 1 ] @@ -440,6 +449,7 @@ public static CompositeImage createCompositeImagePlus( final CompositeImage compositeImage = new CompositeImage( dup ); + Overlay rois = new Overlay(); for ( int channel = 1; channel <= compositeImage.getNChannels(); ++channel ) { final LUT lut = compositeImage.createLutFromColor( Color.WHITE ); @@ -447,8 +457,16 @@ public static CompositeImage createCompositeImagePlus( compositeImage.setChannelLut( lut ); final double[] range = displayRanges.get( channel - 1 ); compositeImage.setDisplayRange( range[ 0 ], range[ 1 ] ); + mask.setPosition( channel ); + mask.getProcessor().setThreshold( 1.0, 255 ); + Roi roi = new ThresholdToSelection().convert( mask.getProcessor() ); + roi.setPosition( channel, 1, 1 ); + mask.getProcessor().setRoi( roi ); + rois.add( roi ); } + compositeImage.setOverlay( rois ); + compositeImage.setHideOverlay( true ); compositeImage.setTitle( "Multi-Channel" ); return compositeImage; } diff --git a/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java b/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java index 55c450e7d..7d2022216 100644 --- a/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java +++ b/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java @@ -148,10 +148,9 @@ private void installContextMenuAndKeyboardShortCuts( ) final ArrayList< String > actions = new ArrayList< String >(); actions.add( SourceAndConverterService.getCommandName( SourcesInfoCommand.class ) ); - actions.add( SourceAndConverterService.getCommandName( ScreenShotMakerCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( ShowRasterImagesCommand.class ) ); + actions.add( SourceAndConverterService.getCommandName( ScreenShotMakerCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( ViewerTransformLoggerCommand.class ) ); - actions.add( SourceAndConverterService.getCommandName( SourceInfoLoggerCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( BigWarpRegistrationCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( AutomaticRegistrationCommand.class ) ); actions.add( SourceAndConverterService.getCommandName( ManualRegistrationCommand.class ) ); diff --git a/src/main/java/org/embl/mobie/lib/image/SpimDataImage.java b/src/main/java/org/embl/mobie/lib/image/SpimDataImage.java index 27297f4ff..99c51cc3c 100644 --- a/src/main/java/org/embl/mobie/lib/image/SpimDataImage.java +++ b/src/main/java/org/embl/mobie/lib/image/SpimDataImage.java @@ -57,7 +57,7 @@ public class SpimDataImage< T extends NumericType< T > & RealType< T > > impleme private Boolean removeSpatialCalibration = false; @Nullable private RealMaskRealInterval mask; - private TransformedSource transformedSource; + private TransformedSource< T > transformedSource; private AffineTransform3D currentTransform = new AffineTransform3D(); public SpimDataImage( AbstractSpimData< ? > spimData, Integer setupId, String name, Boolean removeSpatialCalibration ) @@ -151,12 +151,12 @@ public void setMask( RealMaskRealInterval mask ) private void open() { - final AbstractSpimData spimData = openSpimData(); + final AbstractSpimData< ? > spimData = openSpimData(); createSourcePair( spimData, setupId, name ); } - private void createSourcePair( AbstractSpimData spimData, int setupId, String name ) + private void createSourcePair( AbstractSpimData< ? > spimData, int setupId, String name ) { final SpimSource< T > source = new SpimSource<>( spimData, setupId, name ); final VolatileSpimSource< ? extends Volatile< T > > vSource = new VolatileSpimSource<>( spimData, setupId, name ); @@ -169,10 +169,10 @@ private void createSourcePair( AbstractSpimData spimData, int setupId, String na SourceHelper.setVoxelDimensionsToPixels( vSource ); } - transformedSource = new TransformedSource( source ); + transformedSource = new TransformedSource<>( source ); transformedSource.setFixedTransform( currentTransform ); - sourcePair = new DefaultSourcePair( transformedSource, new TransformedSource( vSource, transformedSource ) ); + sourcePair = new DefaultSourcePair<>( transformedSource, new TransformedSource<>( vSource, transformedSource ) ); } private AbstractSpimData openSpimData( ) diff --git a/src/test/java/projects/em_xray_alignment/OpenEMXRAY.java b/src/test/java/projects/em_xray_alignment/OpenEMXRAY.java index 8b8a9ae01..e423ae853 100644 --- a/src/test/java/projects/em_xray_alignment/OpenEMXRAY.java +++ b/src/test/java/projects/em_xray_alignment/OpenEMXRAY.java @@ -14,7 +14,7 @@ public static void main( String[] args ) throws IOException // OpenerLogging.setLogging( true ); final ImageJ imageJ = new ImageJ(); imageJ.ui().showUI(); - MoBIESettings settings = new MoBIESettings().view( "em-sift-affine--xray-u8-manual-euler" ); + MoBIESettings settings = new MoBIESettings().view( "em-xray-manual" ); new MoBIE("/Volumes/cba/exchange/em-xray-alignment/mobie", settings ); } }