Skip to content

Commit

Permalink
Improve ScreenshotMaker
Browse files Browse the repository at this point in the history
  • Loading branch information
tischi committed Dec 27, 2023
1 parent 849fecb commit 4267cfa
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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
{
Expand Down Expand Up @@ -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;

Expand All @@ -131,7 +127,7 @@ public SIFTPointsExtractor( BdvHandle bdvHandle )
decimalFormat.setMinimumFractionDigits( 3 );
}

public void run()
public boolean run()
{
// cleanup
fs1.clear();
Expand All @@ -144,47 +140,39 @@ 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 );
gd.addNumericField( "minimal_number_of_inliers :", p.minNumInliers, 0 );

gd.showDialog();

if (gd.wasCanceled()) return;
if (gd.wasCanceled()) return false;

String fixedImageName = gd.getNextChoice();
String movingImageName = gd.getNextChoice();
Expand All @@ -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();

Expand All @@ -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 );
Expand All @@ -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 );
Expand All @@ -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." );
Expand All @@ -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(
Expand All @@ -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 )
{
Expand All @@ -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." );
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() );
}
}
}
Loading

0 comments on commit 4267cfa

Please sign in to comment.