From 5ca648f79f9a197ccd9113c15ca641669190d432 Mon Sep 17 00:00:00 2001 From: Nicolas Chiaruttini Date: Mon, 10 Jun 2024 22:47:20 +0200 Subject: [PATCH 01/14] Adds command for 2D affine SIFT registration of sources --- pom.xml | 9 +- ...tract2DRegistrationInRectangleCommand.java | 27 ++ ...astix2DRegistrationInRectangleCommand.java | 22 +- .../register/Sift2DAffineRegisterCommand.java | 53 +++ .../register/SIFTRegister.java | 378 ++++++++++++++++++ .../DemoRegistrationElastixAffine.java} | 4 +- .../DemoRegistrationElastixSpline.java} | 4 +- ...egistrationMultiChannelElastixAffine.java} | 4 +- ...egistrationMultiChannelElastixSpline.java} | 4 +- .../register/DemoRegistrationSIFTAffine.java | 229 +++++++++++ 10 files changed, 708 insertions(+), 26 deletions(-) create mode 100644 src/main/java/ch/epfl/biop/scijava/command/source/register/Abstract2DRegistrationInRectangleCommand.java create mode 100644 src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java create mode 100644 src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java rename src/test/java/{DemoRegistrationAffine.java => register/DemoRegistrationElastixAffine.java} (99%) rename src/test/java/{DemoRegistrationSpline.java => register/DemoRegistrationElastixSpline.java} (99%) rename src/test/java/{DemoRegistrationMultiChannelAffine.java => register/DemoRegistrationMultiChannelElastixAffine.java} (97%) rename src/test/java/{DemoRegistrationMultiChannelSpline.java => register/DemoRegistrationMultiChannelElastixSpline.java} (97%) create mode 100644 src/test/java/register/DemoRegistrationSIFTAffine.java diff --git a/pom.xml b/pom.xml index 7dff757e..2a060a54 100644 --- a/pom.xml +++ b/pom.xml @@ -102,7 +102,7 @@ 0.10.2 0.4.2 0.1.7 - 0.7.1 + 0.7.2 0.9.7 @@ -294,6 +294,13 @@ TrackMate + + + mpicbg + mpicbg_ + + + diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/Abstract2DRegistrationInRectangleCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/Abstract2DRegistrationInRectangleCommand.java new file mode 100644 index 00000000..477cd72b --- /dev/null +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/Abstract2DRegistrationInRectangleCommand.java @@ -0,0 +1,27 @@ +package ch.epfl.biop.scijava.command.source.register; + +import net.imglib2.realtransform.AffineTransform3D; +import org.scijava.ItemIO; +import org.scijava.plugin.Parameter; + +abstract class Abstract2DRegistrationInRectangleCommand extends SelectSourcesForRegistrationCommand { + + @Parameter(label = "ROI for registration (position x)", style = "format:0.#####E0") + double px; + + @Parameter(label = "ROI for registration (position y)", style = "format:0.#####E0") + double py; + + @Parameter(label = "ROI for registration (position z)", style = "format:0.#####E0") + double pz; + + @Parameter(label = "ROI for registration (size x)", style = "format:0.#####E0") + double sx; + + @Parameter(label = "ROI for registration (size y)", style = "format:0.#####E0") + double sy; + + @Parameter(type = ItemIO.OUTPUT) + AffineTransform3D at3D; + +} diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/AbstractElastix2DRegistrationInRectangleCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/AbstractElastix2DRegistrationInRectangleCommand.java index 1dc451ec..ab4f5472 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/source/register/AbstractElastix2DRegistrationInRectangleCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/AbstractElastix2DRegistrationInRectangleCommand.java @@ -1,25 +1,8 @@ package ch.epfl.biop.scijava.command.source.register; -import net.imglib2.realtransform.AffineTransform3D; -import org.scijava.ItemIO; import org.scijava.plugin.Parameter; -abstract class AbstractElastix2DRegistrationInRectangleCommand extends SelectSourcesForRegistrationCommand { - - @Parameter(label = "ROI for registration (position x)", style = "format:0.#####E0") - double px; - - @Parameter(label = "ROI for registration (position y)", style = "format:0.#####E0") - double py; - - @Parameter(label = "ROI for registration (position z)", style = "format:0.#####E0") - double pz; - - @Parameter(label = "ROI for registration (size x)", style = "format:0.#####E0") - double sx; - - @Parameter(label = "ROI for registration (size y)", style = "format:0.#####E0") - double sy; +abstract class AbstractElastix2DRegistrationInRectangleCommand extends Abstract2DRegistrationInRectangleCommand { @Parameter(label = "Inspect registration result in ImageJ 1 windows (do not work with RGB images)") boolean showImagePlusRegistrationResult = false; @@ -33,9 +16,6 @@ abstract class AbstractElastix2DRegistrationInRectangleCommand extends SelectSou @Parameter(label = "Starts by aligning gravity centers") boolean automaticTransformInitialization = false; - @Parameter(type = ItemIO.OUTPUT) - AffineTransform3D at3D; - @Parameter boolean verbose = false; diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java new file mode 100644 index 00000000..51077116 --- /dev/null +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java @@ -0,0 +1,53 @@ +package ch.epfl.biop.scijava.command.source.register; + +import ch.epfl.biop.sourceandconverter.register.SIFTRegister; +import mpicbg.imagefeatures.FloatArray2DSIFT; +import org.scijava.plugin.Plugin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; +import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; + +/** + * This command automatically computes the number of scales needed for registration + * It resamples the original sources in order to allow for a registration based on a specific + * region and not on the whole image. This also allows to register landmark regions + * on big images. See {@link RegisterWholeSlideScans2DCommand} + */ + +@Plugin(type = BdvPlaygroundActionCommand.class, + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Sources with SIFT (Affine, 2D)", + description = "Performs an affine registration in 2D between 2 sources. Low level command which\n"+ + "requires many parameters. For more user friendly command, use wizards instead.\n"+ + "Outputs the transform to apply to the moving source." ) +public class Sift2DAffineRegisterCommand extends Abstract2DRegistrationInRectangleCommand implements BdvPlaygroundActionCommand { + + private static Logger logger = LoggerFactory.getLogger(Sift2DAffineRegisterCommand.class); + + @Override + public void run() { + + SIFTRegister reg = new SIFTRegister( + sacs_fixed,levelFixedSource,tpFixed, + sacs_moving,levelMovingSource,tpMoving, + pxSizeInCurrentUnit, + px,py,pz,sx,sy, + new FloatArray2DSIFT.Param(), + 0.92f, + 25.0f, + 0.05f, + 7 + ); + + reg.setInterpolate(interpolate); + + boolean success = reg.run(); + + if (success) { + at3D = reg.getAffineTransform(); + } else { + logger.error("Error during registration"); + } + } + +} diff --git a/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java b/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java new file mode 100644 index 00000000..7da36d9f --- /dev/null +++ b/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java @@ -0,0 +1,378 @@ +package ch.epfl.biop.sourceandconverter.register; + +import bdv.viewer.Source; +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.sourceandconverter.exporter.CZTRange; +import ch.epfl.biop.sourceandconverter.exporter.ImagePlusGetter; +import ij.IJ; +import ij.ImagePlus; +import mpicbg.ij.FeatureTransform; +import mpicbg.ij.SIFT; +import mpicbg.imagefeatures.Feature; +import mpicbg.imagefeatures.FloatArray2DSIFT; +import mpicbg.models.AffineModel2D; +import mpicbg.models.NotEnoughDataPointsException; +import mpicbg.models.Point; +import mpicbg.models.PointMatch; +import net.imglib2.FinalRealInterval; +import net.imglib2.RealPoint; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.NumericType; +import sc.fiji.bdvpg.sourceandconverter.importer.EmptySourceAndConverterCreator; +import sc.fiji.bdvpg.sourceandconverter.transform.SourceAffineTransformer; +import sc.fiji.bdvpg.sourceandconverter.transform.SourceResampler; + +import java.awt.geom.AffineTransform; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class SIFTRegister & NumericType, + MT extends NativeType & NumericType> { + + SourceAndConverter[] sacs_fixed; + SourceAndConverter[] sacs_moving; + int levelMipmapFixed, levelMipmapMoving; + int tpMoving,tpFixed; + + AffineTransform3D affineTransformOut; + + double px,py,pz,sx,sy; + + double pxSizeInCurrentUnit; + + boolean interpolate = false; + + String errorMessage = ""; + + final FloatArray2DSIFT.Param paramSift; + final float rod; + final double maxEpsilon; + final double minInlierRatio; + final int minNumInliers; + + public SIFTRegister(SourceAndConverter[] sacs_fixed, + int levelMipmapFixed, + int tpFixed, + SourceAndConverter[] sacs_moving, + int levelMipmapMoving, + int tpMoving, + double pxSizeInCurrentUnit, + double px, + double py, + double pz, + double sx, + double sy, + FloatArray2DSIFT.Param paramSift, + final float rod, // rod – Ratio of distances (closest/next closest match) + final double maxEpsilon, + final double minInlierRatio, + final int minNumInliers + ) { + this.sacs_fixed = sacs_fixed; + this.sacs_moving = sacs_moving; + this.pxSizeInCurrentUnit = pxSizeInCurrentUnit; + this.px = px; + this.py = py; + this.pz = pz; + this.sx = sx; + this.sy = sy; + this.levelMipmapFixed = levelMipmapFixed; + this.levelMipmapMoving = levelMipmapMoving; + this.tpFixed = tpFixed; + this.tpMoving = tpMoving; + this.paramSift = paramSift; + this.rod = rod; + this.maxEpsilon = maxEpsilon; + this.minNumInliers = minNumInliers; + this.minInlierRatio = minInlierRatio; + } + + public void setInterpolate(boolean interpolate) { + this.interpolate = interpolate; + } + + public boolean run() { + + // Check mipmap level + levelMipmapFixed = Math.min(levelMipmapFixed, sacs_fixed[0].getSpimSource().getNumMipmapLevels()-1); + levelMipmapMoving = Math.min(levelMipmapMoving, sacs_moving[0].getSpimSource().getNumMipmapLevels()-1); + + ImagePlus croppedMoving = getCroppedImage("Moving", sacs_moving, tpMoving, levelMipmapMoving); + ImagePlus croppedFixed = getCroppedImage("Fixed", sacs_fixed, tpFixed, levelMipmapFixed); + + Source sMoving = sacs_moving[0].getSpimSource(); + Source sFixed = sacs_fixed[0].getSpimSource(); + + AffineTransform3D at3D = new AffineTransform3D(); + at3D.identity(); + at3D.translate(-px,-py,-pz); + + AffineTransform3D atMoving = new AffineTransform3D(); + sMoving.getSourceTransform(tpMoving,levelMipmapMoving,atMoving); + + AffineTransform3D atFixed = new AffineTransform3D(); + sFixed.getSourceTransform(tpMoving,levelMipmapFixed,atFixed); + + at3D.identity(); + at3D.translate(-px,-py,-pz); + + //----------------- SIFT + + // FloatArray2DSIFT.Param paramSift = new FloatArray2DSIFT.Param(); + final FloatArray2DSIFT sift = new FloatArray2DSIFT( paramSift ); + final SIFT ijSIFT = new SIFT( sift ); + + final List fs1 = new ArrayList<>(); + final List fs2 = new ArrayList<>(); + ijSIFT.extractFeatures( croppedFixed.getProcessor(), fs1 ); + IJ.log( fs1.size() + " features extracted for fixed image" ); + ijSIFT.extractFeatures( croppedMoving.getProcessor(), fs2 ); + IJ.log( fs2.size() + " features extracted for moving image" ); + IJ.log( "Identifying correspondence candidates using brute force ..." ); + final List candidates = new ArrayList<>(); + FeatureTransform.matchFeatures( fs1, fs2, candidates, rod ); + + IJ.log( candidates.size() + " potentially corresponding features identified." ); + IJ.log( "Filtering correspondence candidates by geometric consensus ..." ); + ArrayList inliers = new ArrayList<>(); + + AffineModel2D model = new AffineModel2D(); + + try + { + model.filterRansac( + candidates, + inliers, + 1000, + maxEpsilon, + minInlierRatio, + minNumInliers ); + } + catch ( final NotEnoughDataPointsException e ) + { + IJ.log( "No correspondences found." ); + return false; + } + + PointMatch.apply( inliers, model ); + + IJ.log( inliers.size() + " corresponding features with an average displacement of " + PointMatch.meanDistance( inliers ) + "px identified." ); + IJ.log( "Estimated transformation model: " + model ); + + final ArrayList< Point > points1 = new ArrayList< Point >(); + final ArrayList< Point > points2 = new ArrayList(); + + PointMatch.sourcePoints( inliers, points1 ); + PointMatch.targetPoints( inliers, points2 ); + + //----------------- END OF SIFT + + AffineTransform3D affine3D = convertToAffineTransform3D(model.createAffine()); + + AffineTransform3D mPatchPixToRegPatchPix = new AffineTransform3D(); + mPatchPixToRegPatchPix.set(affine3D); + + AffineTransform3D nonRegisteredPatchTransformPixToGlobal = new AffineTransform3D(); + nonRegisteredPatchTransformPixToGlobal.identity(); + nonRegisteredPatchTransformPixToGlobal.scale(pxSizeInCurrentUnit); + double cx = px; + double cy = py; + double cz = pz; + + nonRegisteredPatchTransformPixToGlobal.translate(cx,cy,cz); + + AffineTransform3D nonRegPatchGlobalToPix = nonRegisteredPatchTransformPixToGlobal.inverse(); + + RealPoint nonRegUNorm = getMatrixAxis(nonRegPatchGlobalToPix,0); + RealPoint nonRegVNorm = getMatrixAxis(nonRegPatchGlobalToPix,1); + RealPoint nonRegWNorm = getMatrixAxis(nonRegPatchGlobalToPix,2); + + double u0PatchCoord = prodScal(getMatrixAxis(atMoving,0), nonRegUNorm ); + double v0PatchCoord = prodScal(getMatrixAxis(atMoving,0), nonRegVNorm ); + double w0PatchCoord = prodScal(getMatrixAxis(atMoving,0), nonRegWNorm ); + + double u1PatchCoord = prodScal(getMatrixAxis(atMoving,1), nonRegUNorm ); + double v1PatchCoord = prodScal(getMatrixAxis(atMoving,1), nonRegVNorm ); + double w1PatchCoord = prodScal(getMatrixAxis(atMoving,1), nonRegWNorm ); + + double u2PatchCoord = prodScal(getMatrixAxis(atMoving,2), nonRegUNorm ); + double v2PatchCoord = prodScal(getMatrixAxis(atMoving,2), nonRegVNorm ); + double w2PatchCoord = prodScal(getMatrixAxis(atMoving,2), nonRegWNorm ); + + // New origin + RealPoint newOrigin = new RealPoint(3); + newOrigin.setPosition(atMoving.get(0,3),0); + newOrigin.setPosition(atMoving.get(1,3),1); + newOrigin.setPosition(atMoving.get(2,3),2); + nonRegPatchGlobalToPix.apply(newOrigin,newOrigin); + + double u3PatchCoord = newOrigin.getDoublePosition(0); + double v3PatchCoord = newOrigin.getDoublePosition(1); + double w3PatchCoord = newOrigin.getDoublePosition(2); + + // New Location : + RealPoint p0 = new RealPoint(u0PatchCoord, v0PatchCoord, w0PatchCoord); + RealPoint p1 = new RealPoint(u1PatchCoord, v1PatchCoord, w1PatchCoord); + RealPoint p2 = new RealPoint(u2PatchCoord, v2PatchCoord, w2PatchCoord); + RealPoint p3 = new RealPoint(u3PatchCoord, v3PatchCoord, w3PatchCoord); + + // mCopy.set(nonRegisteredPatchTransformPixToGLobal); + // Computes new location in real coordinates + // Removes translation for this computation + AffineTransform3D mPatchPixToGlobal = new AffineTransform3D(); + mPatchPixToGlobal.set(nonRegisteredPatchTransformPixToGlobal); + mPatchPixToGlobal = nonRegisteredPatchTransformPixToGlobal.concatenate(mPatchPixToRegPatchPix); + + double shiftX = mPatchPixToGlobal.get(0,3); + double shiftY = mPatchPixToGlobal.get(1,3); + double shiftZ = mPatchPixToGlobal.get(2,3); + + mPatchPixToGlobal.set(0,0,3); + mPatchPixToGlobal.set(0,1,3); + mPatchPixToGlobal.set(0,2,3); + + mPatchPixToGlobal.apply(p0,p0); + mPatchPixToGlobal.apply(p1,p1); + mPatchPixToGlobal.apply(p2,p2); + + mPatchPixToGlobal.set(shiftX,0,3); + mPatchPixToGlobal.set(shiftY,1,3); + mPatchPixToGlobal.set(shiftZ,2,3); + mPatchPixToGlobal.apply(p3,p3); + + double[] newMatrix = new double[12]; + newMatrix[0] = p0.getDoublePosition(0); + newMatrix[4] = p0.getDoublePosition(1); + newMatrix[8] = p0.getDoublePosition(2); + + newMatrix[1] = p1.getDoublePosition(0); + newMatrix[5] = p1.getDoublePosition(1); + newMatrix[9] = p1.getDoublePosition(2); + + newMatrix[2] = p2.getDoublePosition(0); + newMatrix[6] = p2.getDoublePosition(1); + newMatrix[10] = p2.getDoublePosition(2); + + newMatrix[3] = p3.getDoublePosition(0); + newMatrix[7] = p3.getDoublePosition(1); + newMatrix[11] = p3.getDoublePosition(2); + + affineTransformOut = new AffineTransform3D(); + + affineTransformOut.set(newMatrix); + + affineTransformOut = atMoving.concatenate(affineTransformOut.inverse()); + + return true; + } + + private & NumericType> ImagePlus getCroppedImage(String name, SourceAndConverter[] sacs, int tp, int level) { + + // Fetch cropped images from source -> resample sources + FinalRealInterval window = new FinalRealInterval(new double[]{px,py,pz}, new double[]{px+sx, py+sy, pz+pxSizeInCurrentUnit}); + + SourceAndConverter model = new EmptySourceAndConverterCreator("model",window,pxSizeInCurrentUnit,pxSizeInCurrentUnit,pxSizeInCurrentUnit).get(); + + SourceResampler resampler = new SourceResampler<>(null, + model,model.getSpimSource().getName(), false, false, interpolate, level + ); + + List> resampled = + Arrays.stream(sacs) + .map(resampler) + .collect(Collectors.toList()); + + List channels = new ArrayList<>(sacs.length); + for (int i = 0; i< sacs.length;i++) { + channels.add(i); + } + List slices = new ArrayList<>(); + slices.add(0); + List timepoints = new ArrayList<>(); + timepoints.add(tp); + + CZTRange range = new CZTRange(channels, slices, timepoints); + + return ImagePlusGetter.getImagePlus(name,resampled,0,range,false,false,false,null); + } + + public static double prodScal(RealPoint pt1, RealPoint pt2) { + return pt1.getDoublePosition(0)*pt2.getDoublePosition(0)+ + pt1.getDoublePosition(1)*pt2.getDoublePosition(1)+ + pt1.getDoublePosition(2)*pt2.getDoublePosition(2); + } + + public RealPoint getMatrixAxis(AffineTransform3D at3D, int axis) { + RealPoint pt = new RealPoint(3); + double[] m = at3D.getRowPackedCopy(); + pt.setPosition(m[0+axis],0); + pt.setPosition(m[4+axis],1); + pt.setPosition(m[8+axis],2); + return pt; + } + + public SourceAndConverter[] getRegisteredSacs() { + SourceAndConverter[] out = new SourceAndConverter[sacs_moving.length]; + SourceAffineTransformer sat = new SourceAffineTransformer(null, affineTransformOut); + for (int iCh=0;iCh< sacs_moving.length;iCh++) { + out[iCh] = sat.apply(sacs_moving[iCh]); + } + return out; + } + + public AffineTransform3D getAffineTransform() { + return affineTransformOut; + } + + public String getErrorMessage() { + return errorMessage; + } + + private static AffineTransform3D convertToAffineTransform3D(AffineTransform at) { + // Create a new AffineTransform3D object + AffineTransform3D at3d = new AffineTransform3D(); + + // Get the matrix elements from the 2D AffineTransform + double[] matrix2D = new double[6]; + at.getMatrix(matrix2D); + + // Map the 2D matrix to a 3D matrix + double[] matrix3D = new double[12]; + + /* + this.a.m00 = values[0]; + this.a.m01 = values[1]; + this.a.m02 = values[2]; + this.a.m03 = values[3]; + this.a.m10 = values[4]; + this.a.m11 = values[5]; + this.a.m12 = values[6]; + this.a.m13 = values[7]; + this.a.m20 = values[8]; + this.a.m21 = values[9]; + this.a.m22 = values[10]; + this.a.m23 = values[11]; + */ + // Initialize to identity matrix + matrix3D[0] = matrix2D[0]; // m00 + matrix3D[1] = matrix2D[2]; // m01 + matrix3D[2] = 0; // m02 + matrix3D[3] = matrix2D[4]; // m03 - Translation X + matrix3D[4] = matrix2D[1]; // m10 + matrix3D[5] = matrix2D[3]; // m11 + matrix3D[6] = 0; // m12 + matrix3D[7] = matrix2D[5]; // m13 - Translation Y + matrix3D[8] = 0; // m20 + matrix3D[9] = 0; // m21 + matrix3D[10] = 1; // m22 + matrix3D[11] = 0; // m23 - Translation Z + + // Set the 3D matrix to the AffineTransform3D object + at3d.set(matrix3D); + + return at3d; + } +} diff --git a/src/test/java/DemoRegistrationAffine.java b/src/test/java/register/DemoRegistrationElastixAffine.java similarity index 99% rename from src/test/java/DemoRegistrationAffine.java rename to src/test/java/register/DemoRegistrationElastixAffine.java index 37bb4ccc..77c9833c 100644 --- a/src/test/java/DemoRegistrationAffine.java +++ b/src/test/java/register/DemoRegistrationElastixAffine.java @@ -1,3 +1,5 @@ +package register; + import bdv.util.BdvFunctions; import bdv.util.BdvHandle; import bdv.util.BdvOptions; @@ -32,7 +34,7 @@ import java.util.concurrent.Future; -public class DemoRegistrationAffine { +public class DemoRegistrationElastixAffine { static { LegacyInjector.preinit(); diff --git a/src/test/java/DemoRegistrationSpline.java b/src/test/java/register/DemoRegistrationElastixSpline.java similarity index 99% rename from src/test/java/DemoRegistrationSpline.java rename to src/test/java/register/DemoRegistrationElastixSpline.java index 09e91326..9c608bc8 100644 --- a/src/test/java/DemoRegistrationSpline.java +++ b/src/test/java/register/DemoRegistrationElastixSpline.java @@ -1,3 +1,5 @@ +package register; + import bdv.util.BdvFunctions; import bdv.util.BdvHandle; import bdv.util.BdvOptions; @@ -29,7 +31,7 @@ import java.util.Collection; import java.util.concurrent.Future; -public class DemoRegistrationSpline { +public class DemoRegistrationElastixSpline { static { LegacyInjector.preinit(); diff --git a/src/test/java/DemoRegistrationMultiChannelAffine.java b/src/test/java/register/DemoRegistrationMultiChannelElastixAffine.java similarity index 97% rename from src/test/java/DemoRegistrationMultiChannelAffine.java rename to src/test/java/register/DemoRegistrationMultiChannelElastixAffine.java index 17c7d304..15988d1e 100644 --- a/src/test/java/DemoRegistrationMultiChannelAffine.java +++ b/src/test/java/register/DemoRegistrationMultiChannelElastixAffine.java @@ -1,3 +1,5 @@ +package register; + import bdv.util.BdvHandle; import bdv.viewer.SourceAndConverter; import ch.epfl.biop.bdv.img.OpenersToSpimData; @@ -12,7 +14,7 @@ import java.util.List; -public class DemoRegistrationMultiChannelAffine { +public class DemoRegistrationMultiChannelElastixAffine { static SourceAndConverter fixedSource; diff --git a/src/test/java/DemoRegistrationMultiChannelSpline.java b/src/test/java/register/DemoRegistrationMultiChannelElastixSpline.java similarity index 97% rename from src/test/java/DemoRegistrationMultiChannelSpline.java rename to src/test/java/register/DemoRegistrationMultiChannelElastixSpline.java index 22d35f39..1f968fcd 100644 --- a/src/test/java/DemoRegistrationMultiChannelSpline.java +++ b/src/test/java/register/DemoRegistrationMultiChannelElastixSpline.java @@ -1,3 +1,5 @@ +package register; + import bdv.util.BdvHandle; import bdv.viewer.SourceAndConverter; import ch.epfl.biop.bdv.img.OpenersToSpimData; @@ -15,7 +17,7 @@ import java.util.List; -public class DemoRegistrationMultiChannelSpline { +public class DemoRegistrationMultiChannelElastixSpline { static SourceAndConverter fixedSource; diff --git a/src/test/java/register/DemoRegistrationSIFTAffine.java b/src/test/java/register/DemoRegistrationSIFTAffine.java new file mode 100644 index 00000000..00248e52 --- /dev/null +++ b/src/test/java/register/DemoRegistrationSIFTAffine.java @@ -0,0 +1,229 @@ +package register; + +import bdv.util.BdvFunctions; +import bdv.util.BdvHandle; +import bdv.util.BdvOptions; +import bdv.util.BdvStackSource; +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.bdv.img.imageplus.ImagePlusToSpimData; +import ch.epfl.biop.bdv.select.SelectedSourcesListener; +import ch.epfl.biop.bdv.select.SourceSelectorBehaviour; +import ch.epfl.biop.bdv.select.ToggleListener; +import ch.epfl.biop.scijava.command.source.register.Sift2DAffineRegisterCommand; +import ij.IJ; +import ij.ImagePlus; +import mpicbg.spim.data.SpimData; +import mpicbg.spim.data.XmlIoSpimData; +import mpicbg.spim.data.generic.AbstractSpimData; +import net.imagej.ImageJ; +import net.imagej.patcher.LegacyInjector; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.type.numeric.ARGBType; +import org.scijava.command.CommandModule; +import org.scijava.command.CommandService; +import org.scijava.ui.behaviour.ClickBehaviour; +import org.scijava.ui.behaviour.io.InputTriggerConfig; +import org.scijava.ui.behaviour.util.Behaviours; +import sc.fiji.bdvpg.sourceandconverter.SourceAndConverterAndTimeRange; +import sc.fiji.bdvpg.sourceandconverter.transform.SourceTransformHelper; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Future; + + +public class DemoRegistrationSIFTAffine { + + static { + LegacyInjector.preinit(); + } + + static SourceAndConverter fixedSource; + + static SourceAndConverter movingSource; + + static final ImageJ ij = new ImageJ(); + + static public void main(String... args) throws Exception { + + ij.ui().showUI(); + + // Creates a demo bdv frame with demo images + BdvHandle bdvh = initAndShowSources(); + + // Setup a source selection mode with a trigger input key that toggles it on and off + SourceSelectorBehaviour ssb = new SourceSelectorBehaviour(bdvh, "E"); + + // Adds a listener which displays the events - either GUI or programmatically triggered + ssb.addSelectedSourcesListener(new SelectedSourcesListener() { + @Override + public void selectedSourcesUpdated(Collection> selectedSources, String triggerMode) { + if (selectedSources.size()==1) { + bdvh.getViewerPanel().showMessage("Fixed Source Set "); + fixedSource = selectedSources.stream().findAny().get(); + movingSource = null; + } + } + + @Override + public void lastSelectionEvent(Collection> lastSelectedSources, String mode, String triggerMode) { + bdvh.getViewerPanel().showMessage(mode + " " + lastSelectedSources.size()); + if ((lastSelectedSources.size()==1)&&(fixedSource!=null)) { + bdvh.getViewerPanel().showMessage("Moving Source Set "); + movingSource = lastSelectedSources.stream().findAny().get(); + if (movingSource==fixedSource) { + movingSource = null; + } + } else { + bdvh.getViewerPanel().showMessage("Fixed and Moving Source Reset"); + fixedSource = null; + movingSource = null; + } + } + }); + + // Example of simple behaviours that can be added on top of the source selector + // Here it adds an editor behaviour which only action is to remove the selected sources from the window + // When the delete key is pressed + addEditorBehaviours(bdvh, ssb); + + // Programmatic API Demo : triggers a list of actions separated in time + // programmaticAPIDemo(bdvh, ssb); + ssb.enable(); + } + + static BdvHandle initAndShowSources() throws Exception { + // load and convert the famous blobs image + ImagePlus imp = IJ.openImage("src/test/resources/blobs.tif"); + RandomAccessibleInterval blob = ImageJFunctions.wrapReal(imp); + + // load 3d mri image spimdataset + SpimData sd = new XmlIoSpimData().load("src/test/resources/mri-stack.xml"); + + // Display mri image + BdvStackSource bss = BdvFunctions.show(sd).get(0); + bss.setDisplayRange(0,255); + + // Gets reference of BigDataViewer + BdvHandle bdvh = bss.getBdvHandle(); + bss.removeFromBdv(); + // Defines location of blobs image + AffineTransform3D m = new AffineTransform3D(); + m.rotate(2,Math.PI/20); + m.translate(0, -40,0); + + // Display first blobs image + bss = BdvFunctions.show(blob, "Blobs 1", BdvOptions.options().sourceTransform(m).addTo(bdvh)); + bss.setColor(new ARGBType(ARGBType.rgba(255,0,0,0))); + + // Defines location of blobs image + m.identity(); + m.rotate(2,Math.PI/25); + m.translate(0,-60,0); + + // Display second blobs image + bss = BdvFunctions.show(blob, "Blobs 2", BdvOptions.options().sourceTransform(m).addTo(bdvh)); + bss.setColor(new ARGBType(ARGBType.rgba(0,255,255,0))); + /* + // Defines location of blobs image + m.identity(); + m.rotate(2,Math.PI/6); + m.rotate(0,Math.PI/720); + m.translate(312,256,0); + + // Display third blobs image + BdvFunctions.show(blob, "Blobs Rot Z Y ", BdvOptions.options().sourceTransform(m).addTo(bdvh)); + */ + // Sets BigDataViewer view + m.identity(); + m.scale(1); + m.translate(150,0,0); + + bdvh.getViewerPanel().state().setViewerTransform(m); + bdvh.getViewerPanel().requestRepaint(); + + + ImagePlus impRGB = IJ.openImage("src/test/resources/blobsrgb.tif"); + AbstractSpimData sdblob = ImagePlusToSpimData.getSpimData(impRGB); + AffineTransform3D at3D = new AffineTransform3D(); + at3D.rotate(2,15); + List> bssL = BdvFunctions.show(sdblob, BdvOptions.options().addTo(bdvh)); + //SourceAndConverter rgbSac = bssL.get(0).getSources().get(0); + //bssL.remove(0); + + + + + + return bdvh; + } + + static void addEditorBehaviours(BdvHandle bdvh, SourceSelectorBehaviour ssb) { + Behaviours editor = new Behaviours(new InputTriggerConfig()); + + ClickBehaviour delete = (x, y) -> bdvh.getViewerPanel().state().removeSources(ssb.getSelectedSources()); + + ClickBehaviour registerLocal = (x,y) -> { + if ((movingSource==null)||(fixedSource==null)) { + bdvh.getViewerPanel().showMessage("Please define a fixed and a moving source"); + } else { + // Go for the registration - on a selected rectangle + Future task = ij.context() + .getService(CommandService.class) + .run(Sift2DAffineRegisterCommand.class, true, + "sacs_fixed", new SourceAndConverter[]{fixedSource}, + "tpFixed", 0, + "levelFixedSource", 0, + "sacs_moving", new SourceAndConverter[]{movingSource}, + "tpMoving", 0, + "levelMovingSource", 0, + "pxSizeInCurrentUnit", 1, + "interpolate", false, + "px",-50, + "py",-10, + "pz",0, + "sx",250, + "sy",250 + ); + + Thread t = new Thread(() -> { + try { + AffineTransform3D at3d = (AffineTransform3D) task.get().getOutput("at3D"); + SourceTransformHelper.mutate(at3d, new SourceAndConverterAndTimeRange(movingSource,0)); + bdvh.getViewerPanel().requestRepaint(); + } catch (Exception e) { + e.printStackTrace(); + } + }); + t.start(); + } + }; + + + editor.behaviour(delete, "remove-sources-from-bdv", new String[]{"DELETE"}); + editor.behaviour(registerLocal, "register-sources-local", new String[]{"R"}); + + // One way to chain the behaviour : install and uninstall on source selector toggling: + // The delete key will act only when the source selection mode is on + ssb.addToggleListener(new ToggleListener() { + @Override + public void isEnabled() { + bdvh.getViewerPanel().showMessage("Selection Mode Enable"); + //bdvh.getViewerPanel().showMessage(ssb.getSelectedSources().size()+" sources selected"); + // Enable the editor behaviours when the selector is enabled + editor.install(bdvh.getTriggerbindings(), "sources-editor"); + } + + @Override + public void isDisabled() { + bdvh.getViewerPanel().showMessage("Selection Mode Disable"); + // Disable the editor behaviours the selector is disabled + bdvh.getTriggerbindings().removeInputTriggerMap("sources-editor"); + bdvh.getTriggerbindings().removeBehaviourMap("sources-editor"); + } + }); + } + +} From 831221100c2f9fb90cc174769c18eb0390402367 Mon Sep 17 00:00:00 2001 From: Nicolas Chiaruttini Date: Mon, 10 Jun 2024 22:53:51 +0200 Subject: [PATCH 02/14] Fixes a few warnings --- .../command/source/register/Sift2DAffineRegisterCommand.java | 2 +- .../epfl/biop/sourceandconverter/register/SIFTRegister.java | 4 ++-- .../register/DemoRegistrationMultiChannelElastixSpline.java | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java index 51077116..8da7e7e7 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java @@ -46,7 +46,7 @@ public void run() { if (success) { at3D = reg.getAffineTransform(); } else { - logger.error("Error during registration"); + logger.error("Error during registration: "+reg.getErrorMessage()); } } diff --git a/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java b/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java index 7da36d9f..14df11c6 100644 --- a/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java +++ b/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java @@ -162,8 +162,8 @@ public boolean run() { IJ.log( inliers.size() + " corresponding features with an average displacement of " + PointMatch.meanDistance( inliers ) + "px identified." ); IJ.log( "Estimated transformation model: " + model ); - final ArrayList< Point > points1 = new ArrayList< Point >(); - final ArrayList< Point > points2 = new ArrayList(); + final ArrayList< Point > points1 = new ArrayList<>(); + final ArrayList< Point > points2 = new ArrayList<>(); PointMatch.sourcePoints( inliers, points1 ); PointMatch.targetPoints( inliers, points2 ); diff --git a/src/test/java/register/DemoRegistrationMultiChannelElastixSpline.java b/src/test/java/register/DemoRegistrationMultiChannelElastixSpline.java index 1f968fcd..fd74ab0a 100644 --- a/src/test/java/register/DemoRegistrationMultiChannelElastixSpline.java +++ b/src/test/java/register/DemoRegistrationMultiChannelElastixSpline.java @@ -26,8 +26,6 @@ public class DemoRegistrationMultiChannelElastixSpline { static final ImageJ ij = new ImageJ(); static public void main(String... args) throws Exception { - ConvertibleRois cr; - DefaultTransformixTask dtt; ij.ui().showUI(); OpenerSettings atlasSettings = OpenerSettings.BioFormats() From fb6deee0dc76d3734256f2b1628b98586859e818 Mon Sep 17 00:00:00 2001 From: Nicolas Chiaruttini Date: Thu, 13 Jun 2024 16:27:51 +0200 Subject: [PATCH 03/14] Collects registration module from ImageToAtlasRegister --- .../epfl/biop/registration/Registration.java | 171 +++++++++ .../plugin/ExternalRegistrationPlugin.java | 29 ++ .../plugin/IRegistrationPlugin.java | 9 + .../plugin/RegistrationPluginHelper.java | 43 +++ .../plugin/RegistrationTypeProperties.java | 23 ++ .../plugin/SimpleRegistrationPlugin.java | 32 ++ .../plugin/SimpleRegistrationWrapper.java | 337 ++++++++++++++++++ .../SourceAndConverterRegistration.java | 95 +++++ .../affine/AffineRegistration.java | 62 ++++ ...ansformSourceAndConverterRegistration.java | 64 ++++ ...eTransformedSourceWrapperRegistration.java | 70 ++++ .../affine/CenterZeroRegistration.java | 54 +++ .../affine/Elastix2DAffineRegistration.java | 145 ++++++++ .../bigwarp/SacBigWarp2DRegistration.java | 123 +++++++ .../mirror/MirrorXRegistration.java | 47 +++ .../mirror/MirrorXTransform.java | 64 ++++ .../mirror/MirrorXTransformAdapter.java | 44 +++ .../spline/Elastix2DSplineRegistration.java | 317 ++++++++++++++++ ...ansformSourceAndConverterRegistration.java | 79 ++++ 19 files changed, 1808 insertions(+) create mode 100644 src/main/java/ch/epfl/biop/registration/Registration.java create mode 100644 src/main/java/ch/epfl/biop/registration/plugin/ExternalRegistrationPlugin.java create mode 100644 src/main/java/ch/epfl/biop/registration/plugin/IRegistrationPlugin.java create mode 100644 src/main/java/ch/epfl/biop/registration/plugin/RegistrationPluginHelper.java create mode 100644 src/main/java/ch/epfl/biop/registration/plugin/RegistrationTypeProperties.java create mode 100644 src/main/java/ch/epfl/biop/registration/plugin/SimpleRegistrationPlugin.java create mode 100644 src/main/java/ch/epfl/biop/registration/plugin/SimpleRegistrationWrapper.java create mode 100644 src/main/java/ch/epfl/biop/registration/sourceandconverter/SourceAndConverterRegistration.java create mode 100644 src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/AffineRegistration.java create mode 100644 src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/AffineTransformSourceAndConverterRegistration.java create mode 100644 src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/AffineTransformedSourceWrapperRegistration.java create mode 100644 src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/CenterZeroRegistration.java create mode 100644 src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/Elastix2DAffineRegistration.java create mode 100644 src/main/java/ch/epfl/biop/registration/sourceandconverter/bigwarp/SacBigWarp2DRegistration.java create mode 100644 src/main/java/ch/epfl/biop/registration/sourceandconverter/mirror/MirrorXRegistration.java create mode 100644 src/main/java/ch/epfl/biop/registration/sourceandconverter/mirror/MirrorXTransform.java create mode 100644 src/main/java/ch/epfl/biop/registration/sourceandconverter/mirror/MirrorXTransformAdapter.java create mode 100644 src/main/java/ch/epfl/biop/registration/sourceandconverter/spline/Elastix2DSplineRegistration.java create mode 100644 src/main/java/ch/epfl/biop/registration/sourceandconverter/spline/RealTransformSourceAndConverterRegistration.java diff --git a/src/main/java/ch/epfl/biop/registration/Registration.java b/src/main/java/ch/epfl/biop/registration/Registration.java new file mode 100644 index 00000000..adfad215 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/Registration.java @@ -0,0 +1,171 @@ +package ch.epfl.biop.registration; +import ch.epfl.biop.java.utilities.roi.types.RealPointList; +import net.imglib2.realtransform.RealTransform; +import org.scijava.Context; + +import java.util.Map; + +/** + * Most upstream + * @param Type of the image to register ( ImagePlus / SourceAndConverter, what else ? ) + */ + +public interface Registration { + + /** + * Is called just after the Registration object creation to pass + * the current scijava context + * is adopted by all registrations + * @param context + */ + void setScijavaContext(Context context); + + /** + * Is called before registration to pass any extra registration parameter + * argument. Passed as a dictionary of String to preserve serialization + * capability. + * @param parameters dictionary of parameters + */ + void setRegistrationParameters(Map parameters); + + /** + * For serialization and reproducibility + * @return parameters that were used for the registration + */ + Map getRegistrationParameters(); + + /** + * Sets the fixed image + * @param fimg fixed image + */ + void setFixedImage(T fimg); + + /** + * Set the moving image + * @param mimg moving image + */ + void setMovingImage(T mimg); + + /** + * Optional - sets the fixed image mask + * @param fimg_mask fixed image + */ + void setFixedMask(T fimg_mask); + + /** + * Optional - set the moving image mask + * @param mimg_mask moving image + */ + void setMovingMask(T mimg_mask); + + /** + * Sets the state of the registration to not done + * Called when the registration needs to be rerun for real + * instead of just restored + * + */ + void resetRegistration(); + + /** + * Sets the timepoint of the source that should be used for the + * registration. 0 in most cases + * @param timePoint + */ + void setTimePoint(int timePoint); + + /** + * Blocking function which performs the registration + * @return true if the registration was run succesfully + */ + boolean register(); + + /** + * Can be called after register() return false in order to get + * a more meaningful explanation + * @return an error message for a failed registration + */ + default String getExceptionMessage() { + return "Unspecified error"; + } + + /** + * Blocking function which is called when the user wants + * to manually edit the result of the registration + * @return true is the transform is been edited successfully. If not, + * the previous state of the registration is restored thanks to the serialization + * of the transform. An edition cannot occur if the transform has not been set + * before (either via the run method, or via setting the transform via the + * {@link Registration#setTransform(String)} method + */ + boolean edit(); + + /** + * Flag functions which serves to know whether the result of the + * registration is available. Should not be blocking. + * + * @return true if the transform is available + */ + boolean isRegistrationDone(); + + /** + * Function which takes an input moving image, transform it + * according to the result of the registration (transformation), + * and returns it + * @param img should not be modified + * @return the transformed image + */ + T getTransformedImageMovingToFixed(final T img); + + /** + * Reverse transforms a list of points. This function takes a list + * of points given in the fixed coordinates system, inverse transform + * their coordinates according to the result of the registration. + * The points can be mutated + * @param pts points to transform from fixed to moving system coordinates + * @return the transformed points + */ + RealPointList getTransformedPtsFixedToMoving(RealPointList pts); + + /** + * Function called when a registration is cancelled while being processed + */ + void abort(); + + /** + * This function is used to: + * - save the registration into a json file + * - store transiently the state of a registration to cancel the edition of a transform, if needed + * @return a serialized representation of the transform as a String + */ + String getTransform(); + + /** + * Function used to bypass the real registration process (run) + * in order to set directly the result of the registration. + * This is used when: + * - loading a registration from a state file + * - restore if needed a previous transformed state after an edition + * a serialized representation of the transform is sent, leading + * to an immediate registration being done + * isDone should return true after this function is being called + * @param serialized_transform + */ + void setTransform(String serialized_transform); + + /** + * If the transform can be returned as a serializable RealTransform object, + * this can be used to serialize the successive registrations as a + * {@link net.imglib2.realtransform.RealTransformSequence} object, or even, + * if all transformations are invertible, as a {@link net.imglib2.realtransform.InvertibleRealTransformSequence} object + * @return + */ + RealTransform getTransformAsRealTransform(); + + /** + * Used for serialisation + * @return + */ + default String getRegistrationTypeName() { + return this.getClass().getSimpleName(); + } +} diff --git a/src/main/java/ch/epfl/biop/registration/plugin/ExternalRegistrationPlugin.java b/src/main/java/ch/epfl/biop/registration/plugin/ExternalRegistrationPlugin.java new file mode 100644 index 00000000..66adeaf5 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/plugin/ExternalRegistrationPlugin.java @@ -0,0 +1,29 @@ +package ch.epfl.biop.registration.plugin; +import org.scijava.command.Command; + +/** + * An registration plugin interface without any annotation - useful + * mostly useful in order to add a plugin from PyImageJ + */ +@SuppressWarnings("SameReturnValue") +public interface ExternalRegistrationPlugin extends IRegistrationPlugin { + + /** + * Does the registration required an user input ? + * @return true if some user action is required + */ + boolean isManual(); + + /** + * Can the registration be edited after it has run ? + * Considered a manual task by default + * @return true if the registration can be edited a posteriori + */ + boolean isEditable(); + + /** + * @return the command class the user has to call in order to start a registration + */ + Class[] userInterface(); + +} diff --git a/src/main/java/ch/epfl/biop/registration/plugin/IRegistrationPlugin.java b/src/main/java/ch/epfl/biop/registration/plugin/IRegistrationPlugin.java new file mode 100644 index 00000000..8d9115d0 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/plugin/IRegistrationPlugin.java @@ -0,0 +1,9 @@ +package ch.epfl.biop.registration.plugin; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.Registration; +import org.scijava.plugin.SciJavaPlugin; + +public interface IRegistrationPlugin extends SciJavaPlugin, Registration[]> { + +} diff --git a/src/main/java/ch/epfl/biop/registration/plugin/RegistrationPluginHelper.java b/src/main/java/ch/epfl/biop/registration/plugin/RegistrationPluginHelper.java new file mode 100644 index 00000000..7a05a6e7 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/plugin/RegistrationPluginHelper.java @@ -0,0 +1,43 @@ +package ch.epfl.biop.registration.plugin; + +import ch.epfl.biop.registration.Registration; + +// Facilitates accessing annotation values +public class RegistrationPluginHelper { + + /** + * Does the registration required an user input ? + * @return true if user input is required + */ + public static boolean isManual(Registration reg) { + if (reg.getClass().isAnnotationPresent(RegistrationTypeProperties.class)) { + final RegistrationTypeProperties annotation = reg.getClass() + .getAnnotation(RegistrationTypeProperties.class); + return annotation.isManual(); + } else { + if (reg instanceof ExternalRegistrationPlugin) { + return ((ExternalRegistrationPlugin) reg).isManual(); + } else { + return false; // Default value if no annotation is present + } + } + } + + /** + * Can the registration be edited after it has run ? + * Considered a manual task by default + */ + public static boolean isEditable(Registration reg) { + if (reg.getClass().isAnnotationPresent(RegistrationTypeProperties.class)) { + final RegistrationTypeProperties annotation = reg.getClass() + .getAnnotation(RegistrationTypeProperties.class); + return annotation.isEditable(); + } else { + if (reg instanceof ExternalRegistrationPlugin) { + return ((ExternalRegistrationPlugin) reg).isEditable(); + } else { + return false; // Default value if no annotation is present + } + } + } +} diff --git a/src/main/java/ch/epfl/biop/registration/plugin/RegistrationTypeProperties.java b/src/main/java/ch/epfl/biop/registration/plugin/RegistrationTypeProperties.java new file mode 100644 index 00000000..c6d85578 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/plugin/RegistrationTypeProperties.java @@ -0,0 +1,23 @@ +package ch.epfl.biop.registration.plugin; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface RegistrationTypeProperties { + + /** + * Does the registration required an user input ? + * @return true if the registration requires user input + */ + boolean isManual(); + + /** + * Can the registration be edited after it has run ? + * Considered a manual task by default + * @return true if the registration can be edited a posteriori + */ + boolean isEditable(); + +} + diff --git a/src/main/java/ch/epfl/biop/registration/plugin/SimpleRegistrationPlugin.java b/src/main/java/ch/epfl/biop/registration/plugin/SimpleRegistrationPlugin.java new file mode 100644 index 00000000..666b76b0 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/plugin/SimpleRegistrationPlugin.java @@ -0,0 +1,32 @@ +package ch.epfl.biop.registration.plugin; + +import ij.ImagePlus; +import net.imglib2.realtransform.InvertibleRealTransform; + +import java.util.Map; + +@SuppressWarnings("SameReturnValue") +public interface SimpleRegistrationPlugin { + + /** + * @return Sampling required for the registration, in micrometer + */ + double getVoxelSizeInMicron(); + + /** + * Is called before registration to pass any extra registration parameter + * argument. Passed as a dictionary of String to preserve serialization + * capability. + * @param parameters dictionary of parameters + */ + void setRegistrationParameters(Map parameters); + + /** + * + * @param fixed image + * @param moving image + * @return the transform, result of the registration, in + * going from fixed to moving coordinates, in pixels + */ + InvertibleRealTransform register(ImagePlus fixed, ImagePlus moving, ImagePlus fixedMask, ImagePlus movingMask); +} diff --git a/src/main/java/ch/epfl/biop/registration/plugin/SimpleRegistrationWrapper.java b/src/main/java/ch/epfl/biop/registration/plugin/SimpleRegistrationWrapper.java new file mode 100644 index 00000000..f73c9e64 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/plugin/SimpleRegistrationWrapper.java @@ -0,0 +1,337 @@ +package ch.epfl.biop.registration.plugin; + +import bdv.util.BoundedRealTransform; +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.bdv.img.imageplus.ImagePlusHelper; +import ch.epfl.biop.java.utilities.roi.types.RealPointList; +import ij.ImagePlus; +import net.imglib2.RealPoint; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.realtransform.InvertibleRealTransform; +import net.imglib2.realtransform.InvertibleRealTransformSequence; +import net.imglib2.realtransform.RealTransform; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.NumericType; +import org.scijava.Context; +import org.scijava.command.Command; +import sc.fiji.bdvpg.sourceandconverter.SourceAndConverterHelper; +import sc.fiji.bdvpg.sourceandconverter.importer.EmptySourceAndConverterCreator; +import sc.fiji.bdvpg.sourceandconverter.transform.SourceRealTransformer; +import sc.fiji.bdvpg.sourceandconverter.transform.SourceResampler; +import sc.fiji.persist.ScijavaGsonHelper; + +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.Arrays; +import java.util.stream.Collectors; + +public class SimpleRegistrationWrapper implements ExternalRegistrationPlugin { + + public String getRegistrationTypeName() { + return registrationTypeName; + } + + public SimpleRegistrationWrapper(String registrationTypeName, final SimpleRegistrationPlugin simpleRegistration) { + this.registration = simpleRegistration; + this.registrationTypeName = registrationTypeName; + } + + final String registrationTypeName; + final SimpleRegistrationPlugin registration; + + Context context; + + Map parameters; + + boolean isDone = false; + + protected RealTransform rt; + + @Override + public boolean isManual() { + return false; + } + + @Override + public boolean isEditable() { + return false; + } + + @Override + public Class[] userInterface() { + return new Class[0]; + } + + @Override + public void setScijavaContext(Context context) { + this.context = context; + } + + @Override + public void setRegistrationParameters(Map parameters) { + this.parameters = parameters; + } + + @Override + public Map getRegistrationParameters() { + return parameters; + } + + ImagePlus fixedImage, movingImage, fixedMask, movingMask; + + @Override + public void setFixedImage(SourceAndConverter[] fimg) { + fixedImage = export("Fixed-", Arrays.asList(fimg), + (Double.parseDouble(parameters.get("px"))), + (Double.parseDouble(parameters.get("py"))), + (Double.parseDouble(parameters.get("sx"))), + (Double.parseDouble(parameters.get("sy"))), + registration.getVoxelSizeInMicron()/1000.0, + 0, + false + ); + } + + @Override + public void setMovingImage(SourceAndConverter[] mimg) { + movingImage = export("Moving-", Arrays.asList(mimg), + (Double.parseDouble(parameters.get("px"))), + (Double.parseDouble(parameters.get("py"))), + (Double.parseDouble(parameters.get("sx"))), + (Double.parseDouble(parameters.get("sy"))), + registration.getVoxelSizeInMicron()/1000.0, + 0, + false + ); + } + + @Override + public void setFixedMask(SourceAndConverter[] fimg_mask) { + fixedMask = export("FixedMask-", Arrays.asList(fimg_mask), + (Double.parseDouble(parameters.get("px"))), + (Double.parseDouble(parameters.get("py"))), + (Double.parseDouble(parameters.get("sx"))), + (Double.parseDouble(parameters.get("sy"))), + registration.getVoxelSizeInMicron()/1000.0, + 0, + false + ); + } + + @Override + public void setMovingMask(SourceAndConverter[] mimg_mask) { + movingMask = export("MovingMask-", Arrays.asList(mimg_mask), + (Double.parseDouble(parameters.get("px"))), + (Double.parseDouble(parameters.get("py"))), + (Double.parseDouble(parameters.get("sx"))), + (Double.parseDouble(parameters.get("sy"))), + registration.getVoxelSizeInMicron()/1000.0, + 0, + false + ); + } + + @Override + public void resetRegistration() { + isDone = false; + } + + @Override + public void setTimePoint(int timePoint) { + // ignored: it's 0 + } + + @Override + public boolean register() { + InvertibleRealTransform inner_rt = registration.register(fixedImage, movingImage, fixedMask, movingMask); + + // This transform is for pixel to pixel coordinates -> we need to convert it to physical space. + // Thus adding an AffineTransform to a sequence + + InvertibleRealTransformSequence irts = new InvertibleRealTransformSequence(); + AffineTransform3D m = new AffineTransform3D(); + + double px = (Double.parseDouble(parameters.get("px"))); + double py = (Double.parseDouble(parameters.get("py"))); + double voxSize = registration.getVoxelSizeInMicron(); + + // We work in millimeters + m.scale(voxSize/1000.0); + m.translate(px, py, 0); + + // The umbrella trick + irts.add(m.inverse()); + irts.add(inner_rt); + irts.add(m); + + rt = irts; + + isDone = true; + return true; // no error handling + } + + @Override + public boolean edit() { + // TODO : find a way to edit an affine transform -> that shouldn't be so complicated + throw new UnsupportedOperationException(); + } + + @Override + public boolean isRegistrationDone() { + return isDone; + } + + @Override + public SourceAndConverter[] getTransformedImageMovingToFixed(SourceAndConverter[] img) { + SourceAndConverter[] out = new SourceAndConverter[img.length]; + SourceRealTransformer srt = new SourceRealTransformer(rt); + for (int idx = 0;idx cvtList = new ArrayList<>(); + + for (RealPoint p : pts.ptList) { + RealPoint pt3d = new RealPoint(3); + pt3d.setPosition(new double[]{p.getDoublePosition(0), p.getDoublePosition(1),0}); + innerRT.apply(pt3d, pt3d); + RealPoint cpt = new RealPoint(pt3d.getDoublePosition(0), pt3d.getDoublePosition(1)); + cvtList.add(cpt); + } + + return new RealPointList(cvtList); + } + + @Override + public void abort() { + + } + + public RealTransform getRealTransform() { + return rt; + } + + public void setRealTransform(RealTransform transform) { + this.rt = transform.copy(); + } + + @Override + final public String getTransform() { + //logger.debug("Serializing transform of class "+rt.getClass().getSimpleName()); + String transform = ScijavaGsonHelper.getGson(context).toJson(rt, RealTransform.class); + //logger.debug("Serialization result = "+transform); + return transform; + } + + @Override + final public void setTransform(String serialized_transform) { + setRealTransform(ScijavaGsonHelper.getGson(context).fromJson(serialized_transform, RealTransform.class)); + isDone = true; + } + + public RealTransform getTransformAsRealTransform() { + return rt.copy(); + } + + + public static & NativeType> ImagePlus export(String name, List> sourceList, + double px, double py, double sx, double sy, + double pixelSizeMillimeter, int timepoint, + boolean interpolate) { + + AffineTransform3D at3D = new AffineTransform3D(); + + SourceAndConverter model = createModelSource(px, py, sx, sy, pixelSizeMillimeter, at3D); + + List> resampledSourceList = sourceList + .stream() + .map(sac -> (SourceAndConverter) (new SourceResampler(sac,model,sac.getSpimSource().getName()+"_ResampledLike_"+model.getSpimSource().getName(), true, false, interpolate,0).get())) + .collect(Collectors.toList()); + + if ((sourceList.size()>1)) { + + Map, Integer> mapMipmap = new HashMap<>(); + sourceList.forEach(src -> { + int mipmapLevel = SourceAndConverterHelper.bestLevel(src, timepoint, pixelSizeMillimeter); + //logger.debug("Mipmap level chosen for source ["+src.getSpimSource().getName()+"] : "+mipmapLevel); + mapMipmap.put(resampledSourceList.get(sourceList.indexOf(src)), mipmapLevel); + }); + + ImagePlus resultImage = ImagePlusHelper.wrap( + resampledSourceList, + mapMipmap, + timepoint, + 1, + 1); + + resultImage.setTitle(name+"-["+px+":"+(px+sx)+" | "+py+":"+(py+sy)+"]"); + ImagePlusHelper.storeExtendedCalibrationToImagePlus(resultImage, at3D.inverse(), "mm", timepoint); + + return resultImage; + + } else { + SourceAndConverter source = resampledSourceList.get(0); + int mipmapLevel = SourceAndConverterHelper.bestLevel(sourceList.get(0), timepoint, pixelSizeMillimeter); + ImagePlus singleChannel = ImagePlusHelper.wrap( + source, + mipmapLevel, + timepoint, + 1, + 1); + singleChannel.setTitle(source.getSpimSource().getName()); + ImagePlusHelper.storeExtendedCalibrationToImagePlus(singleChannel, at3D.inverse(), "mm", timepoint); + + return singleChannel; + } + + } + + + private static SourceAndConverter createModelSource( + double px, double py, + double sx, double sy, + double pixel_size_mm, + AffineTransform3D transform) { + // Origin is in fact the point 0,0,0 of the image + // Get current big dataviewer transformation : source transform and viewer transform + AffineTransform3D at3D = new AffineTransform3D(); // Empty Transform + // viewer transform + // Center on the display center of the viewer ... + at3D.translate(-px, -py, 0); + + at3D.scale(1/pixel_size_mm); + + // Getting an image independent of the view scaling unit (not sure) + long nPx = (long)(sx / pixel_size_mm); + long nPy = (long)(sy / pixel_size_mm); + long nPz = 1; + + // At least a pixel in all directions + if (nPz == 0) nPz = 1; + if (nPx == 0) nPx = 1; + if (nPy == 0) nPy = 1; + + transform.set(at3D); + + return new EmptySourceAndConverterCreator("model", at3D.inverse(), nPx, nPy, nPz).get(); + } + + @Override + public String toString() { + return registrationTypeName; + } + +} diff --git a/src/main/java/ch/epfl/biop/registration/sourceandconverter/SourceAndConverterRegistration.java b/src/main/java/ch/epfl/biop/registration/sourceandconverter/SourceAndConverterRegistration.java new file mode 100644 index 00000000..7837fbf8 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/sourceandconverter/SourceAndConverterRegistration.java @@ -0,0 +1,95 @@ +package ch.epfl.biop.registration.sourceandconverter; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.plugin.IRegistrationPlugin; +import org.scijava.Context; +import org.scijava.plugin.Parameter; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +abstract public class SourceAndConverterRegistration implements IRegistrationPlugin { + + protected SourceAndConverter[] fimg; + + protected SourceAndConverter[] mimg; + + protected SourceAndConverter[] fimg_mask; + + protected SourceAndConverter[] mimg_mask; + + protected int timePoint = 0; + + @Parameter + protected Context context; + + @Override + public void setFixedImage(SourceAndConverter[] fimg) { + this.fimg = fimg; + } + + @Override + public void setMovingImage(SourceAndConverter[] mimg) { + this.mimg = mimg; + } + + @Override + public void setFixedMask(SourceAndConverter[] fimg) { + this.fimg_mask = fimg; + } + + @Override + public void setMovingMask(SourceAndConverter[] mimg) { + this.mimg_mask = mimg; + } + + @Override + public void setTimePoint(int timePoint) { + this.timePoint = timePoint; + } + + /** + * Is called just after the Registration object creation to pass + * the current scijava context + * is adopted by all registrations + * @param context + */ + public void setScijavaContext(Context context) { + this.context = context; + } + + protected boolean isDone = false; + + @Override + public boolean isRegistrationDone() { + return isDone; + } + + @Override + public void resetRegistration() { + isDone = false; + } + + protected Map parameters = new HashMap<>(); + + @Override + public Map getRegistrationParameters() { + if (parameters!=null) { + return parameters; + } else { + return new HashMap<>(); + } + } + + @Override + public void setRegistrationParameters(Map parameters) { + this.parameters = parameters; + } + + protected static void addToFlatParameters(List flatParameters, Object... args) { + flatParameters.addAll(Arrays.asList(args)); + } + +} diff --git a/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/AffineRegistration.java b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/AffineRegistration.java new file mode 100644 index 00000000..f7cae925 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/AffineRegistration.java @@ -0,0 +1,62 @@ +package ch.epfl.biop.registration.sourceandconverter.affine; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.plugin.IRegistrationPlugin; +import ch.epfl.biop.registration.plugin.RegistrationTypeProperties; +import com.google.gson.Gson; +import net.imglib2.realtransform.AffineTransform3D; +import org.scijava.plugin.Plugin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * To make an affine transform programmatically conveniently + * + */ +@Plugin(type = IRegistrationPlugin.class) +@RegistrationTypeProperties( + isManual = false, + isEditable = false) + +public class AffineRegistration extends AffineTransformSourceAndConverterRegistration{ + + protected static Logger logger = LoggerFactory.getLogger(AffineRegistration.class); + + @Override + public void setFixedImage(SourceAndConverter[] fimg) { + super.setFixedImage(fimg); + } + + @Override + public void setMovingImage(SourceAndConverter[] mimg) { + super.setMovingImage(mimg); + } + + public static String affineTransform3DToString(AffineTransform3D transform) { + return new Gson().toJson(transform.getRowPackedCopy()); + } + + public static AffineTransform3D stringToAffineTransform3D(String string) { + AffineTransform3D transform3D = new AffineTransform3D(); + double[] matrix = new Gson().fromJson(string, double[].class); + transform3D.set(matrix); + return transform3D; + } + + @Override + public boolean register() { + at3d = stringToAffineTransform3D(parameters.get("transform")); + isDone = true; + return true; + } + + @Override + public void abort() { + + } + + public String toString() { + return "Affine"; + } + +} diff --git a/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/AffineTransformSourceAndConverterRegistration.java b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/AffineTransformSourceAndConverterRegistration.java new file mode 100644 index 00000000..baf57aa6 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/AffineTransformSourceAndConverterRegistration.java @@ -0,0 +1,64 @@ +package ch.epfl.biop.registration.sourceandconverter.affine; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.java.utilities.roi.types.RealPointList; +import ch.epfl.biop.registration.sourceandconverter.SourceAndConverterRegistration; +import net.imglib2.RealPoint; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.realtransform.RealTransform; +import sc.fiji.bdvpg.sourceandconverter.SourceAndConverterAndTimeRange; +import sc.fiji.bdvpg.sourceandconverter.transform.SourceTransformHelper; +import sc.fiji.persist.ScijavaGsonHelper; + +import java.util.ArrayList; + +abstract public class AffineTransformSourceAndConverterRegistration extends SourceAndConverterRegistration { + + protected AffineTransform3D at3d = new AffineTransform3D(); + + @SuppressWarnings("CanBeFinal") + public int timePoint = 0; + + @Override + public SourceAndConverter[] getTransformedImageMovingToFixed(SourceAndConverter[] img) { + SourceAndConverter[] out = new SourceAndConverter[img.length]; + for (int idx = 0;idx(img[idx],timePoint)); + } + return out; + } + + @Override + public RealPointList getTransformedPtsFixedToMoving(RealPointList pts) { + ArrayList cvtList = new ArrayList<>(); + for (RealPoint p : pts.ptList) { + RealPoint pt3d = new RealPoint(3); + pt3d.setPosition(new double[]{p.getDoublePosition(0), p.getDoublePosition(1),0}); + at3d.inverse().apply(pt3d, pt3d); + RealPoint cpt = new RealPoint(pt3d.getDoublePosition(0), pt3d.getDoublePosition(1)); + cvtList.add(cpt); + } + return new RealPointList(cvtList); + } + + @Override + public String getTransform() { + return ScijavaGsonHelper.getGson(context).toJson(at3d, AffineTransform3D.class); + } + + @Override + public void setTransform(String serialized_transform) { + at3d = ScijavaGsonHelper.getGson(context).fromJson(serialized_transform, AffineTransform3D.class); + isDone = true; + } + + @Override + public boolean edit() { + // TODO : find a way to edit an affine transform -> that shouldn't be so complicated + throw new UnsupportedOperationException(); + } + + public RealTransform getTransformAsRealTransform() { + return at3d.inverse().copy(); + } +} diff --git a/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/AffineTransformedSourceWrapperRegistration.java b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/AffineTransformedSourceWrapperRegistration.java new file mode 100644 index 00000000..b723fbaf --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/AffineTransformedSourceWrapperRegistration.java @@ -0,0 +1,70 @@ +package ch.epfl.biop.registration.sourceandconverter.affine; + +import bdv.viewer.SourceAndConverter; +import net.imglib2.realtransform.AffineTransform3D; +import sc.fiji.bdvpg.sourceandconverter.SourceAndConverterAndTimeRange; +import sc.fiji.bdvpg.sourceandconverter.transform.SourceTransformHelper; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Fake registration which simply wraps a transform which is used to modify either: + * - the place of the image in the user GUI, or to + * TODO : write a bit more clearly what this does + */ +public class AffineTransformedSourceWrapperRegistration extends AffineTransformSourceAndConverterRegistration { + + Map, SourceAndConverter> alreadyTransformedSources = new ConcurrentHashMap<>(); + + @Override + public boolean register() { + isDone = true; + return true; + } + + /** + * These function are kept in order to avoid serializing many times + * unnecessary affinetransform + * @param at3d_in affine transform + */ + public synchronized void setAffineTransform(AffineTransform3D at3d_in) { + this.at3d = at3d_in; + alreadyTransformedSources.keySet().forEach(sac -> SourceTransformHelper.set(at3d_in, new SourceAndConverterAndTimeRange<>(alreadyTransformedSources.get(sac), timePoint))); + } + + public AffineTransform3D getAffineTransform() { + return at3d.copy(); + } + + /** + * Overriding to actually mutate SourceAndConverter, + * it's the only registration which does that, because + * it's actually not really a registration + * @param img image + * @return mutates the transform + */ + @Override + public SourceAndConverter[] getTransformedImageMovingToFixed(SourceAndConverter[] img) { + + SourceAndConverter[] out = new SourceAndConverter[img.length]; + + for (int idx = 0;idx(out[idx], timePoint)); + } else { + out[idx] = SourceTransformHelper.createNewTransformedSourceAndConverter(at3d, new SourceAndConverterAndTimeRange<>(img[idx], timePoint)); + alreadyTransformedSources.put(img[idx], out[idx]); + } + } + + return out; + } + + @Override + public void abort() { + + } + +} diff --git a/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/CenterZeroRegistration.java b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/CenterZeroRegistration.java new file mode 100644 index 00000000..70eb70dc --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/CenterZeroRegistration.java @@ -0,0 +1,54 @@ +package ch.epfl.biop.registration.sourceandconverter.affine; + +import bdv.viewer.SourceAndConverter; +import net.imglib2.RealPoint; +import net.imglib2.realtransform.AffineTransform3D; +import sc.fiji.bdvpg.sourceandconverter.SourceAndConverterAndTimeRange; +import sc.fiji.bdvpg.sourceandconverter.SourceAndConverterHelper; +import sc.fiji.bdvpg.sourceandconverter.transform.SourceTransformHelper; + +/** + * Fake registration : simply centers the image in order to put its center + * at the origin of the global coordinate system. + */ + +public class CenterZeroRegistration extends AffineTransformSourceAndConverterRegistration { + + @Override + public boolean register() { + + SourceAndConverter sac = mimg[0]; + + RealPoint center = SourceAndConverterHelper.getSourceAndConverterCenterPoint(sac,0); + + at3d = new AffineTransform3D(); + + at3d.translate( + -center.getDoublePosition(0), + -center.getDoublePosition(1), + -center.getDoublePosition(2)); + + isDone = true; + + return true; + } + + /** + * @param img image + * @return transformed images (new source created) + */ + @Override + public SourceAndConverter[] getTransformedImageMovingToFixed(SourceAndConverter[] img) { + SourceAndConverter[] out = new SourceAndConverter[img.length]; + for (int idx = 0;idx(img[idx], timePoint)); + } + return out; + } + + @Override + public void abort() { + // Cannot be aborted - is very fast anyway + } + +} diff --git a/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/Elastix2DAffineRegistration.java b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/Elastix2DAffineRegistration.java new file mode 100644 index 00000000..25b48036 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/Elastix2DAffineRegistration.java @@ -0,0 +1,145 @@ +package ch.epfl.biop.registration.sourceandconverter.affine; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.plugin.IRegistrationPlugin; +import ch.epfl.biop.registration.plugin.RegistrationTypeProperties; +import ch.epfl.biop.scijava.command.source.register.Elastix2DAffineRegisterCommand; +import net.imglib2.realtransform.AffineTransform3D; +import org.scijava.command.Command; +import org.scijava.command.CommandModule; +import org.scijava.command.CommandService; +import org.scijava.plugin.Plugin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sc.fiji.bdvpg.sourceandconverter.SourceAndConverterHelper; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Future; + +/** + * Uses Elastix backend to perform an affine transform registration + * + */ +@Plugin(type = IRegistrationPlugin.class) +@RegistrationTypeProperties( + isManual = false, + isEditable = false) + +public class Elastix2DAffineRegistration extends AffineTransformSourceAndConverterRegistration{ + + + protected static final Logger logger = LoggerFactory.getLogger(Elastix2DAffineRegistration.class); + + @Override + public void setFixedImage(SourceAndConverter[] fimg) { + if (fimg.length==0) { + logger.error("Error, no fixed image set in class "+this.getClass().getSimpleName()); + } + super.setFixedImage(fimg); + } + + @Override + public void setMovingImage(SourceAndConverter[] mimg) { + if (mimg.length==0) { + logger.error("Error, no fixed image set in class "+this.getClass().getSimpleName()); + } + super.setMovingImage(mimg); + } + + Future task; + + @Override + public boolean register() { + try { + Class registrationCommandClass; + // Is it supposed to be done on a server ? + registrationCommandClass = Elastix2DAffineRegisterCommand.class; + + // Registration success flag + boolean success = true; + + // Transforms map into flat String : key1, value1, key2, value2, etc. + // Necessary for CommandService + List flatParameters = new ArrayList<>(parameters.size()*2+4); + + double voxSizeInMm = Double.parseDouble(parameters.get("pxSizeInCurrentUnit")); + + parameters.keySet().forEach(k -> { + flatParameters.add(k); + flatParameters.add(parameters.get(k)); + }); + + addToFlatParameters(flatParameters, + // Fixed image + "sacs_fixed", fimg, + // Moving image + "sacs_moving", mimg, + // No interpolation in resampling + "interpolate", false, + // Start registration with a 4x4 pixel image + "minPixSize",4, + // 100 iteration steps + "maxIterationNumberPerScale",100, + // Do not write anything + "verbose", false, + // Centers centers of mass of both images before starting registration + "automaticTransformInitialization", true, + // Atlas image : a single timepoint + "tpFixed", 0, + // Level 2 for the atlas + "levelFixedSource", SourceAndConverterHelper.bestLevel(fimg[0], timePoint, voxSizeInMm), + // Timepoint moving source (normally 0) + "tpMoving", timePoint, + // Tries to be clever for the moving source sampling + "levelMovingSource", SourceAndConverterHelper.bestLevel(mimg[0], timePoint, voxSizeInMm) + ); + + task = context + .getService(CommandService.class) + .run(registrationCommandClass, false, + flatParameters.toArray(new Object[0]) + ); + + CommandModule module = task.get(); + + if (module.getOutputs().containsKey("success")) { + success = (boolean) module.getOutput("success"); + } + if (success) { + at3d = (AffineTransform3D) module.getOutput("at3D"); + } else { + if (module.getOutputs().containsKey("error")) { + errorMessage = (String) module.getOutput("error"); + } + } + + isDone = true; + return success; + } catch (Exception e) { + errorMessage = e.getMessage(); + e.printStackTrace(); + return false; + } + } + + String errorMessage = ""; + + @Override + public String getExceptionMessage() { + return errorMessage; + } + + @Override + public void abort() { + if (task!=null) { + logger.info(this.getClass().getSimpleName()+": Attempt to interrupt registration..."); + task.cancel(true); + } + } + + public String toString() { + return "Elastix 2D Affine"; + } + +} diff --git a/src/main/java/ch/epfl/biop/registration/sourceandconverter/bigwarp/SacBigWarp2DRegistration.java b/src/main/java/ch/epfl/biop/registration/sourceandconverter/bigwarp/SacBigWarp2DRegistration.java new file mode 100644 index 00000000..e8597ee5 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/sourceandconverter/bigwarp/SacBigWarp2DRegistration.java @@ -0,0 +1,123 @@ +package ch.epfl.biop.registration.sourceandconverter.bigwarp; + +import bdv.gui.TransformTypeSelectDialog; +import bdv.tools.brightness.ConverterSetup; +import bdv.util.BdvHandle; +import bdv.viewer.DisplayMode; +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.plugin.IRegistrationPlugin; +import ch.epfl.biop.registration.plugin.RegistrationTypeProperties; +import ch.epfl.biop.registration.sourceandconverter.spline.RealTransformSourceAndConverterRegistration; +import ij.gui.WaitForUserDialog; +import org.scijava.plugin.Plugin; +import sc.fiji.bdvpg.bdv.BdvHandleHelper; +import sc.fiji.bdvpg.services.SourceAndConverterServices; +import sc.fiji.bdvpg.sourceandconverter.register.BigWarpLauncher; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static bdv.util.RealTransformHelper.BigWarpFileFromRealTransform; + +@Plugin(type = IRegistrationPlugin.class) +@RegistrationTypeProperties( + isManual = true, + isEditable = true +) +public class SacBigWarp2DRegistration extends RealTransformSourceAndConverterRegistration { + + BigWarpLauncher bwl; + + Runnable waitForUser = () -> { + WaitForUserDialog dialog = new WaitForUserDialog("Big Warp registration","Please perform carefully your registration then press ok. Do not forget to press 't' when 4 landmarks are placed."); + dialog.show(); + }; + + public void setWaitForUserMethod(Runnable r) { + waitForUser = r; + } + + @Override + public boolean register() { + { + + List> movingSacs = Arrays.stream(mimg).collect(Collectors.toList()); + + List> fixedSacs = Arrays.stream(fimg).collect(Collectors.toList()); + + List converterSetups = Arrays.stream(mimg).map(src -> SourceAndConverterServices.getSourceAndConverterService().getConverterSetup(src)).collect(Collectors.toList()); + + converterSetups.addAll(Arrays.stream(fimg).map(src -> SourceAndConverterServices.getSourceAndConverterService().getConverterSetup(src)).collect(Collectors.toList())); + + // Launch BigWarp + bwl = new BigWarpLauncher(movingSacs, fixedSacs, "Big Warp", converterSetups); + bwl.set2d(); + bwl.run(); + + // Output bdvh handles -> will be put in the object service + BdvHandle bdvhQ = bwl.getBdvHandleQ(); + BdvHandle bdvhP = bwl.getBdvHandleP(); + + bdvhQ.getViewerPanel().state().setDisplayMode(DisplayMode.FUSED); + bdvhP.getViewerPanel().state().setDisplayMode(DisplayMode.FUSED); + + bdvhP.getViewerPanel().state().setViewerTransform(BdvHandleHelper.getViewerTransformWithNewCenter(bdvhP, new double[]{0,0,0})); + bdvhQ.getViewerPanel().state().setViewerTransform(BdvHandleHelper.getViewerTransformWithNewCenter(bdvhQ, new double[]{0,0,0})); + + SourceAndConverterServices.getBdvDisplayService().pairClosing(bdvhQ,bdvhP); + + bdvhP.getViewerPanel().requestRepaint(); + bdvhQ.getViewerPanel().requestRepaint(); + + bwl.getBigWarp().getLandmarkFrame().repaint(); + + // Restores landmarks if some were already defined + if (rt!=null) { + String bigWarpFile = BigWarpFileFromRealTransform(rt); + if (bigWarpFile!=null) { // If the transform is not a spline, no landmarks are saved, the user has to redo his job + bwl.getBigWarp().loadLandmarks(bigWarpFile); + bwl.getBigWarp().setInLandmarkMode(true); + bwl.getBigWarp().setIsMovingDisplayTransformed(true); + } + } + + waitForUser.run(); + + switch (bwl.getBigWarp().getBwTransform().getTransformType()) { + case (TransformTypeSelectDialog.TPS) : + // Thin plate spline transform + rt = bwl.getBigWarp().getBwTransform().getTransformation(); + break; + default: + // Any other transform, currently Affine 3D + rt = bwl.getBigWarp().getBwTransform().affine3d(); + } + + bwl.getBigWarp().closeAll(); + + isDone = true; + + return true; + + } + } + + @Override + public boolean edit() { + // Just launching again BigWarp + this.register(); + return true; + } + + @Override + public void abort() { + + } + + + public String toString() { + return "Big Warp"; + } + +} diff --git a/src/main/java/ch/epfl/biop/registration/sourceandconverter/mirror/MirrorXRegistration.java b/src/main/java/ch/epfl/biop/registration/sourceandconverter/mirror/MirrorXRegistration.java new file mode 100644 index 00000000..ffd1058e --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/sourceandconverter/mirror/MirrorXRegistration.java @@ -0,0 +1,47 @@ +package ch.epfl.biop.registration.sourceandconverter.mirror; + +import ch.epfl.biop.registration.plugin.IRegistrationPlugin; +import ch.epfl.biop.registration.plugin.RegistrationTypeProperties; +import ch.epfl.biop.registration.sourceandconverter.spline.RealTransformSourceAndConverterRegistration; +import org.scijava.plugin.Plugin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Plugin(type = IRegistrationPlugin.class) +@RegistrationTypeProperties( + isManual = false, + isEditable = true +) + +public class MirrorXRegistration extends RealTransformSourceAndConverterRegistration { + + protected static Logger logger = LoggerFactory.getLogger(MirrorXRegistration.class); + + @Override + public boolean register() { + String mirrorSide = parameters.get("mirror_side"); + if (mirrorSide.equals("Left")) { + rt = new MirrorXTransform(-1); + } else { + rt = new MirrorXTransform(1); + } + isDone = true; + return true; + } + + @Override + public boolean edit() { + return false; + } + + @Override + public void abort() { + + } + + final public static String MIRROR_X_REGISTRATION_NAME = "Mirror X"; + public String toString() { + return MIRROR_X_REGISTRATION_NAME; + } + +} \ No newline at end of file diff --git a/src/main/java/ch/epfl/biop/registration/sourceandconverter/mirror/MirrorXTransform.java b/src/main/java/ch/epfl/biop/registration/sourceandconverter/mirror/MirrorXTransform.java new file mode 100644 index 00000000..6de805ee --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/sourceandconverter/mirror/MirrorXTransform.java @@ -0,0 +1,64 @@ +package ch.epfl.biop.registration.sourceandconverter.mirror; + +import net.imglib2.RealLocalizable; +import net.imglib2.RealPositionable; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.realtransform.InvertibleRealTransform; + +public class MirrorXTransform implements InvertibleRealTransform { + + final double xFactor; + + public MirrorXTransform(double xFactor) { + this.xFactor = xFactor; + } + + @Override + public int numSourceDimensions() { + return 3; + } + + @Override + public int numTargetDimensions() { + return 3; + } + + @Override + public void apply(double[] source, double[] target) { + target[0] = source[0]*xFactor>0 ? source[0]:-source[0]; + target[1] = source[1]; + target[2] = source[2]; + } + + @Override + public void apply(RealLocalizable realLocalizable, RealPositionable realPositionable) { + double xPos = realLocalizable.getDoublePosition(0); + realPositionable.setPosition(xPos*xFactor>0 ? xPos:-xPos,0); + realPositionable.setPosition(realLocalizable.getDoublePosition(1),1); + realPositionable.setPosition(realLocalizable.getDoublePosition(2),2); + } + + @Override + public void applyInverse(double[] source, double[] target) { + target[0] = source[0]; + target[1] = source[1]; + target[2] = source[2]; + } + + @Override + public void applyInverse(RealPositionable realPositionable, RealLocalizable realLocalizable) { + realPositionable.setPosition(realLocalizable.getDoublePosition(0),0); + realPositionable.setPosition(realLocalizable.getDoublePosition(1),1); + realPositionable.setPosition(realLocalizable.getDoublePosition(2),2); + } + + @Override + public InvertibleRealTransform inverse() { + return new AffineTransform3D(); + } + + @Override + public InvertibleRealTransform copy() { + return new MirrorXTransform(xFactor); + } +} diff --git a/src/main/java/ch/epfl/biop/registration/sourceandconverter/mirror/MirrorXTransformAdapter.java b/src/main/java/ch/epfl/biop/registration/sourceandconverter/mirror/MirrorXTransformAdapter.java new file mode 100644 index 00000000..2eb20869 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/sourceandconverter/mirror/MirrorXTransformAdapter.java @@ -0,0 +1,44 @@ +package ch.epfl.biop.registration.sourceandconverter.mirror; + + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import net.imglib2.realtransform.RealTransform; +import org.scijava.plugin.Plugin; +import sc.fiji.persist.IClassRuntimeAdapter; + +import java.lang.reflect.Type; +@Plugin(type = IClassRuntimeAdapter.class) +public class MirrorXTransformAdapter implements IClassRuntimeAdapter { + @Override + public Class getBaseClass() { + return RealTransform.class; + } + + @Override + public Class getRunTimeClass() { + return MirrorXTransform.class; + } + + @Override + public boolean useCustomAdapter() { + return true; + } + + @Override + public MirrorXTransform deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + JsonObject obj = jsonElement.getAsJsonObject(); + double xFactor = obj.getAsJsonPrimitive("xFactor").getAsDouble(); + return new MirrorXTransform(xFactor); + } + + @Override + public JsonElement serialize(MirrorXTransform transform, Type type, JsonSerializationContext jsonSerializationContext) { + JsonObject obj = new JsonObject(); + obj.addProperty("xFactor", transform.xFactor); + return obj; + } +} diff --git a/src/main/java/ch/epfl/biop/registration/sourceandconverter/spline/Elastix2DSplineRegistration.java b/src/main/java/ch/epfl/biop/registration/sourceandconverter/spline/Elastix2DSplineRegistration.java new file mode 100644 index 00000000..2a85b92e --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/sourceandconverter/spline/Elastix2DSplineRegistration.java @@ -0,0 +1,317 @@ +package ch.epfl.biop.registration.sourceandconverter.spline; + +import bdv.tools.brightness.ConverterSetup; +import bdv.util.BdvHandle; +import bdv.viewer.DisplayMode; +import bdv.viewer.Interpolation; +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.plugin.IRegistrationPlugin; +import ch.epfl.biop.registration.plugin.RegistrationTypeProperties; +import ch.epfl.biop.scijava.command.source.register.Elastix2DSplineRegisterCommand; +import ij.gui.WaitForUserDialog; +import jitk.spline.ThinPlateR2LogRSplineKernelTransform; +import net.imglib2.RealPoint; +import net.imglib2.RealRandomAccessible; +import net.imglib2.realtransform.*; +import net.imglib2.realtransform.inverse.WrappedIterativeInvertibleRealTransform; +import net.imglib2.type.numeric.IntegerType; +import org.scijava.command.Command; +import org.scijava.command.CommandModule; +import org.scijava.command.CommandService; +import org.scijava.plugin.Plugin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sc.fiji.bdvpg.bdv.BdvHandleHelper; +import sc.fiji.bdvpg.services.SourceAndConverterServices; +import sc.fiji.bdvpg.sourceandconverter.SourceAndConverterHelper; +import sc.fiji.bdvpg.sourceandconverter.register.BigWarpLauncher; + +import java.util.*; +import java.util.concurrent.Future; +import java.util.stream.Collectors; + +import static bdv.util.RealTransformHelper.BigWarpFileFromRealTransform; + +@Plugin(type = IRegistrationPlugin.class) +@RegistrationTypeProperties( + isManual = false, + isEditable = true +) + +public class Elastix2DSplineRegistration extends RealTransformSourceAndConverterRegistration { + + protected static final Logger logger = LoggerFactory.getLogger(Elastix2DSplineRegistration.class); + + Future task; + + @Override + public void setFixedImage(SourceAndConverter[] fimg) { + if (fimg.length==0) { + logger.error("Error, no fixed image set in class "+this.getClass().getSimpleName()); + } + super.setFixedImage(fimg); + } + + @Override + public void setMovingImage(SourceAndConverter[] mimg) { + if (mimg.length==0) { + logger.error("Error, no fixed image set in class "+this.getClass().getSimpleName()); + } + super.setMovingImage(mimg); + } + + @Override + public boolean register() { + try { + boolean success = true; + Class registrationCommandClass; + registrationCommandClass = Elastix2DSplineRegisterCommand.class; + + // Transforms map into flat String : key1, value1, key2, value2, etc. + // Necessary for CommandService + List flatParameters = new ArrayList<>(parameters.size()*2+4); + + double voxSizeInMm = Double.parseDouble(parameters.get("pxSizeInCurrentUnit")); + + parameters.keySet().forEach(k -> { + flatParameters.add(k); + flatParameters.add(parameters.get(k)); + }); + + addToFlatParameters(flatParameters, + // Fixed image + "sacs_fixed",fimg, + // Moving image + "sacs_moving", mimg, + // Interpolation in resampling + "interpolate", true, + // Start registration with a 32x32 pixel image + "minPixSize", 32, + // 100 iteration steps + "maxIterationNumberPerScale",100, + // Do not write anything + "verbose", false, + // Do not center centers of mass of both images before starting registration + "automaticTransformInitialization", false, + // Atlas image : a single timepoint + "tpFixed", 0, + // Level 2 for the atlas + "levelFixedSource", SourceAndConverterHelper.bestLevel(fimg[0], timePoint, voxSizeInMm), + // Timepoint moving source (normally 0) + "tpMoving", timePoint, + // Tries to be clever for the moving source sampling + "levelMovingSource", SourceAndConverterHelper.bestLevel(mimg[0], timePoint, voxSizeInMm) + ); + + task = context + .getService(CommandService.class) + .run(registrationCommandClass, false, + flatParameters.toArray(new Object[0])); + + CommandModule module = task.get(); + + if (module.getOutputs().containsKey("success")) { + success = (boolean) module.getOutput("success"); + } + + if (success) { + rt = (RealTransform) module.getOutput("rt"); + rt = pruneLandMarksOutsideAtlas(rt); + } else { + if (module.getOutputs().containsKey("error")) { + errorMessage = (String) module.getOutput("error"); + } + } + + + isDone = true; + return success; + } catch (Exception e) { + errorMessage = e.getMessage(); + e.printStackTrace(); + return false; + } + } + + String errorMessage = ""; + + @Override + public String getExceptionMessage() { + return errorMessage; + } + + /** + * This function removes the landmarks located outside the atlas, + * meaning where the atlas 3d image value is zero + * 4 landmarks are kept + * @param rt_in the realtransfrom spline to prune + * @return same transform with landmarks pruned + */ + private RealTransform pruneLandMarksOutsideAtlas(RealTransform rt_in) { + + RealTransform input = rt_in; + boolean wrapped2d3d = false; + boolean wrappedInvertible = false; + if (rt_in instanceof Wrapped2DTransformAs3D) { + rt_in = ((Wrapped2DTransformAs3D)rt_in).transform; + wrapped2d3d = true; + } + + if (rt_in instanceof WrappedIterativeInvertibleRealTransform) { + rt_in = ((WrappedIterativeInvertibleRealTransform)rt_in).getTransform(); + wrappedInvertible = true; + } + + if (!(rt_in instanceof ThinplateSplineTransform)) { + System.err.println("Cannot edit the transform : it's not of class thinplatesplinetransform"); + return input; + } else { + if (fimg_mask !=null) { + ThinplateSplineTransform tst = (ThinplateSplineTransform) rt_in; + ThinPlateR2LogRSplineKernelTransform kernel = ThinPlateSplineTransformAdapter.getKernel(tst); + double[][] srcPts = ThinPlateSplineTransformAdapter.getSrcPts(kernel); + double[][] tgtPts = ThinPlateSplineTransformAdapter.getTgtPts(kernel); + int nbLandmarks = kernel.getNumLandmarks(); + int nbDimensions = kernel.getNumDims(); + + List ptsSource = new ArrayList<>(); + List ptsTarget = new ArrayList<>(); + + for (int i = 0; i < nbLandmarks; ++i) { + RealPoint ptSource = new RealPoint(3); + RealPoint ptTarget = new RealPoint(3); + int d; + for (d = 0; d < nbDimensions; ++d) { + ptTarget.setPosition(tgtPts[d][i], d); + } + ptTarget.setPosition(0,2); // 0 position in z + + for (d = 0; d < nbDimensions; ++d) { + ptSource.setPosition(srcPts[d][i], d); + } + ptSource.setPosition(0,2); // 0 position in z + + ptsSource.add(ptSource); + ptsTarget.add(ptTarget); + } + + RealRandomAccessible rawMask = fimg_mask[0].getSpimSource().getInterpolatedSource(timePoint,0, Interpolation.NEARESTNEIGHBOR); + + RealRandomAccessible mask = (RealRandomAccessible) rawMask; + + AffineTransform3D at3D = new AffineTransform3D(); + fimg_mask[0].getSpimSource().getSourceTransform(timePoint,0,at3D); + + List landMarksToKeep = new ArrayList<>(); + for (int i = 0; i < nbLandmarks; ++i) { + at3D.inverse().apply(ptsTarget.get(i), ptsTarget.get(i)); + at3D.inverse().apply(ptsSource.get(i), ptsSource.get(i)); + + if ((mask.getAt(ptsSource.get(i)).getInteger() == 0) && (mask.getAt(ptsTarget.get(i)).getInteger() == 0)) { + + } else { + landMarksToKeep.add(i); + } + } + + if (landMarksToKeep.size()<4) { + // Too many landmarks removed + System.err.println("Too few landmarks after pruning - skip pruning"); + return input; + } + + // Ok, now let's reconstruct the transform + + double[][] srcPtsKept = new double[nbDimensions][landMarksToKeep.size()]; + double[][] tgtPtsKept = new double[nbDimensions][landMarksToKeep.size()]; + + for (int i = 0;i(pruned); + } + + if (wrapped2d3d) { + pruned = new Wrapped2DTransformAs3D((InvertibleRealTransform) pruned); + } + + return pruned; + + } else return input; + } + } + + Runnable waitForUser = () -> { + WaitForUserDialog dialog = new WaitForUserDialog("Choose slice","Please perform carefully your registration then press ok."); + dialog.show(); + }; + + @Override + public boolean edit() { + + List> movingSacs = Arrays.stream(mimg).collect(Collectors.toList()); + + List> fixedSacs = Arrays.stream(fimg).collect(Collectors.toList()); + + List converterSetups = Arrays.stream(mimg).map(src -> SourceAndConverterServices.getSourceAndConverterService().getConverterSetup(src)).collect(Collectors.toList()); + + converterSetups.addAll(Arrays.stream(fimg).map(src -> SourceAndConverterServices.getSourceAndConverterService().getConverterSetup(src)).collect(Collectors.toList())); + + // Launch BigWarp + BigWarpLauncher bwl = new BigWarpLauncher(movingSacs, fixedSacs, "Big Warp", converterSetups); + bwl.set2d(); + bwl.run(); + + // Output bdvh handles -> will be put in the object service + BdvHandle bdvhQ = bwl.getBdvHandleQ(); + BdvHandle bdvhP = bwl.getBdvHandleP(); + + bdvhP.getViewerPanel().state().setViewerTransform(BdvHandleHelper.getViewerTransformWithNewCenter(bdvhP, new double[]{0,0,0})); + bdvhQ.getViewerPanel().state().setViewerTransform(BdvHandleHelper.getViewerTransformWithNewCenter(bdvhQ, new double[]{0,0,0})); + + bdvhQ.getViewerPanel().state().setDisplayMode(DisplayMode.FUSED); + bdvhP.getViewerPanel().state().setDisplayMode(DisplayMode.FUSED); + + SourceAndConverterServices.getBdvDisplayService().pairClosing(bdvhQ,bdvhP); + + bdvhP.getViewerPanel().requestRepaint(); + bdvhQ.getViewerPanel().requestRepaint(); + + bwl.getBigWarp().getLandmarkFrame().repaint(); + + if (rt!=null) { + bwl.getBigWarp().loadLandmarks(BigWarpFileFromRealTransform(rt)); + bwl.getBigWarp().setIsMovingDisplayTransformed(true); + } + + waitForUser.run(); + + rt = bwl.getBigWarp().getBwTransform().getTransformation(); + + bwl.getBigWarp().closeAll(); + + isDone = true; + + return true; + + } + + @Override + public void abort() { + if (task!=null) { + task.cancel(true); + } + } + + public String toString() { + return "Elastix 2D Spline"; + } + +} diff --git a/src/main/java/ch/epfl/biop/registration/sourceandconverter/spline/RealTransformSourceAndConverterRegistration.java b/src/main/java/ch/epfl/biop/registration/sourceandconverter/spline/RealTransformSourceAndConverterRegistration.java new file mode 100644 index 00000000..8c7e0f18 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/sourceandconverter/spline/RealTransformSourceAndConverterRegistration.java @@ -0,0 +1,79 @@ +package ch.epfl.biop.registration.sourceandconverter.spline; + +import bdv.util.BoundedRealTransform; +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.java.utilities.roi.types.RealPointList; +import ch.epfl.biop.registration.sourceandconverter.SourceAndConverterRegistration; +import net.imglib2.RealPoint; +import net.imglib2.realtransform.RealTransform; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sc.fiji.bdvpg.sourceandconverter.transform.SourceRealTransformer; +import sc.fiji.persist.ScijavaGsonHelper; + +import java.util.ArrayList; + +abstract public class RealTransformSourceAndConverterRegistration extends SourceAndConverterRegistration { + + final static Logger logger = LoggerFactory.getLogger(RealTransformSourceAndConverterRegistration.class); + + protected RealTransform rt; + + @Override + public SourceAndConverter[] getTransformedImageMovingToFixed(SourceAndConverter[] img) { + SourceAndConverter[] out = new SourceAndConverter[img.length]; + SourceRealTransformer srt = new SourceRealTransformer(rt); + for (int idx = 0;idx cvtList = new ArrayList<>(); + for (RealPoint p : pts.ptList) { + RealPoint pt3d = new RealPoint(3); + pt3d.setPosition(new double[]{p.getDoublePosition(0), p.getDoublePosition(1),0}); + innerRT.apply(pt3d, pt3d); + RealPoint cpt = new RealPoint(pt3d.getDoublePosition(0), pt3d.getDoublePosition(1)); + cvtList.add(cpt); + } + + return new RealPointList(cvtList); + } + + public RealTransform getRealTransform() { + return rt; + } + + public void setRealTransform(RealTransform transform) { + this.rt = transform.copy(); + } + + @Override + final public String getTransform() { + //logger.debug("Serializing transform of class "+rt.getClass().getSimpleName()); + String transform = ScijavaGsonHelper.getGson(context).toJson(rt, RealTransform.class); + //logger.debug("Serialization result = "+transform); + return transform; + } + + @Override + final public void setTransform(String serialized_transform) { + setRealTransform(ScijavaGsonHelper.getGson(context).fromJson(serialized_transform, RealTransform.class)); + isDone = true; + } + + public RealTransform getTransformAsRealTransform() { + return rt.copy(); + } + +} From a6968b234667df95994c076efe21649bf235eb1f Mon Sep 17 00:00:00 2001 From: Nicolas Chiaruttini Date: Fri, 14 Jun 2024 11:29:04 +0200 Subject: [PATCH 04/14] Adds a way to invert images (when registering black bg over white bg) --- .../source/register/Sift2DAffineRegisterCommand.java | 11 +++++++++-- .../sourceandconverter/register/SIFTRegister.java | 7 +++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java index 8da7e7e7..b3fb1f2a 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java @@ -2,6 +2,7 @@ import ch.epfl.biop.sourceandconverter.register.SIFTRegister; import mpicbg.imagefeatures.FloatArray2DSIFT; +import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,12 +25,18 @@ public class Sift2DAffineRegisterCommand extends Abstract2DRegistrationInRectang private static Logger logger = LoggerFactory.getLogger(Sift2DAffineRegisterCommand.class); + @Parameter + boolean invert_moving; + + @Parameter + boolean invert_fixed; + @Override public void run() { SIFTRegister reg = new SIFTRegister( - sacs_fixed,levelFixedSource,tpFixed, - sacs_moving,levelMovingSource,tpMoving, + sacs_fixed,levelFixedSource,tpFixed,invert_fixed, + sacs_moving,levelMovingSource,tpMoving,invert_moving, pxSizeInCurrentUnit, px,py,pz,sx,sy, new FloatArray2DSIFT.Param(), diff --git a/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java b/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java index 14df11c6..df937ccc 100644 --- a/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java +++ b/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java @@ -35,6 +35,7 @@ public class SIFTRegister & NumericType, SourceAndConverter[] sacs_fixed; SourceAndConverter[] sacs_moving; int levelMipmapFixed, levelMipmapMoving; + final boolean invertFixed, invertMoving; int tpMoving,tpFixed; AffineTransform3D affineTransformOut; @@ -56,9 +57,11 @@ public class SIFTRegister & NumericType, public SIFTRegister(SourceAndConverter[] sacs_fixed, int levelMipmapFixed, int tpFixed, + boolean invertFixed, SourceAndConverter[] sacs_moving, int levelMipmapMoving, int tpMoving, + boolean invertMoving, double pxSizeInCurrentUnit, double px, double py, @@ -88,6 +91,8 @@ public SIFTRegister(SourceAndConverter[] sacs_fixed, this.maxEpsilon = maxEpsilon; this.minNumInliers = minNumInliers; this.minInlierRatio = minInlierRatio; + this.invertFixed = invertFixed; + this.invertMoving = invertMoving; } public void setInterpolate(boolean interpolate) { @@ -127,6 +132,8 @@ public boolean run() { final List fs1 = new ArrayList<>(); final List fs2 = new ArrayList<>(); + if (invertFixed) croppedFixed.getProcessor().invert(); + if (invertMoving) croppedMoving.getProcessor().invert(); ijSIFT.extractFeatures( croppedFixed.getProcessor(), fs1 ); IJ.log( fs1.size() + " features extracted for fixed image" ); ijSIFT.extractFeatures( croppedMoving.getProcessor(), fs2 ); From f3a5daf96a4142cabb5e55371bcc48489215c548 Mon Sep 17 00:00:00 2001 From: Nicolas Chiaruttini Date: Fri, 14 Jun 2024 16:06:30 +0200 Subject: [PATCH 05/14] Hides previous existing commands for registrations. Adds commands for new registrations interface. WIP --- .../epfl/biop/registration/Registration.java | 6 + .../AbstractSourcesRegistrationCommand.java | 159 ++++++++++++++++++ .../Elastix2DAffineRegistrationCommand.java | 43 +++++ .../Elastix2DSplineRegistrationCommand.java | 47 ++++++ .../SacBigWarp2DRegistrationCommand.java | 34 ++++ .../command/Sift2DRegistrationCommand.java | 45 +++++ .../affine/Sift2DAffineRegistration.java | 147 ++++++++++++++++ .../Elastix2DAffineRegisterCommand.java | 4 +- .../Elastix2DSparsePointsRegisterCommand.java | 4 +- .../Elastix2DSplineRegisterCommand.java | 3 +- .../register/MultiscaleRegisterCommand.java | 2 +- .../RegisterWholeSlideScans2DCommand.java | 4 +- .../SelectSourcesForRegistrationCommand.java | 2 +- .../register/Sift2DAffineRegisterCommand.java | 4 +- .../SourcesAffineTransformCommand.java | 3 +- .../register/SourcesRealTransformCommand.java | 3 +- .../transform/EditSourcesWarpingCommand.java | 4 +- 17 files changed, 499 insertions(+), 15 deletions(-) create mode 100644 src/main/java/ch/epfl/biop/registration/scijava/command/AbstractSourcesRegistrationCommand.java create mode 100644 src/main/java/ch/epfl/biop/registration/scijava/command/Elastix2DAffineRegistrationCommand.java create mode 100644 src/main/java/ch/epfl/biop/registration/scijava/command/Elastix2DSplineRegistrationCommand.java create mode 100644 src/main/java/ch/epfl/biop/registration/scijava/command/SacBigWarp2DRegistrationCommand.java create mode 100644 src/main/java/ch/epfl/biop/registration/scijava/command/Sift2DRegistrationCommand.java create mode 100644 src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/Sift2DAffineRegistration.java diff --git a/src/main/java/ch/epfl/biop/registration/Registration.java b/src/main/java/ch/epfl/biop/registration/Registration.java index adfad215..a8d5552f 100644 --- a/src/main/java/ch/epfl/biop/registration/Registration.java +++ b/src/main/java/ch/epfl/biop/registration/Registration.java @@ -12,6 +12,12 @@ public interface Registration { + String ROI_PX = "px"; + String ROI_PY = "py"; + String ROI_SX = "sx"; + String ROI_SY = "sy"; + String RESAMPLING_PX_SIZE = "pxSizeInCurrentUnit"; + /** * Is called just after the Registration object creation to pass * the current scijava context diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractSourcesRegistrationCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractSourcesRegistrationCommand.java new file mode 100644 index 00000000..6d135c50 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractSourcesRegistrationCommand.java @@ -0,0 +1,159 @@ +package ch.epfl.biop.registration.scijava.command; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.Registration; +import ch.epfl.biop.sourceandconverter.processor.SourcesChannelsSelect; +import org.scijava.Context; +import org.scijava.ItemIO; +import org.scijava.command.Command; +import org.scijava.convert.ConvertService; +import org.scijava.plugin.Parameter; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +abstract public class AbstractSourcesRegistrationCommand implements Command { + + @Parameter + Context ctx; + + @Parameter(label = "Fixed source for registration", description = "fixed sources", style = "sorted") + SourceAndConverter[] fixed_sources; + + @Parameter(label = "Moving source for registration", description = "moving sources", style = "sorted") + SourceAndConverter[] moving_sources; + + @Parameter(label = "Fixed image channels used for registration (comma separated)") + String channels_fixed_csv; + + @Parameter(label = "Moving image channels used for registration (comma separated)") + String channels_moving_csv; + + @Parameter(label = "ROI for registration (position x)", style = "format:0.#####E0") + double px; + + @Parameter(label = "ROI for registration (position y)", style = "format:0.#####E0") + double py; + + @Parameter(label = "ROI for registration (size x)", style = "format:0.#####E0") + double sx; + + @Parameter(label = "ROI for registration (size y)", style = "format:0.#####E0") + double sy; + + @Parameter(type = ItemIO.OUTPUT) + boolean success; + + @Parameter(type = ItemIO.OUTPUT) + SourceAndConverter[] registered_sources; + + @Parameter(type = ItemIO.OUTPUT) + Registration[]> registration; + + @Override + public void run() { + + List moving_channels; + List fixed_channels; + + try { + moving_channels = Arrays.stream(channels_moving_csv.split(",")) + .map(Integer::parseInt) + .collect(Collectors.toList()); + fixed_channels = Arrays.stream(channels_fixed_csv.split(",")) + .map(Integer::parseInt) + .collect(Collectors.toList()); + } catch (NumberFormatException e) { + System.err.println("Number parsing exception "+e.getMessage()); + return; + } + + if (moving_channels.isEmpty()) { + System.err.println("Error, you did not specify any channel within the moving image."); + return; + } + + if (fixed_channels.isEmpty()) { + System.err.println("Error, you did not specify any channel within the fixed image."); + return; + } + + int maxIndexMoving = Collections.max(moving_channels); + int minIndexMoving = Collections.min(moving_channels); + + int maxIndexFixed = Collections.max(fixed_channels); + int minIndexFixed = Collections.min(fixed_channels); + + if ((minIndexMoving<0)||(minIndexFixed<0)) { + System.err.println("All channels indices should be positive"); + return; + } + + if (!(maxIndexFixed parameters = new HashMap<>(); + + parameters.put(Registration.ROI_PX, px); + parameters.put(Registration.ROI_PY, py); + parameters.put(Registration.ROI_SX, sx); + parameters.put(Registration.ROI_SY, sy); + + addRegistrationSpecificParameters(parameters); + + registration.setRegistrationParameters(convertToString(ctx, parameters)); + + boolean ok = validate(); + + if (!ok) { + System.err.println("Validation failed."); + return; + } + + + + success = registration.register(); // Do it! + + if (success) { + registered_sources = registration.getTransformedImageMovingToFixed(moving_sources); + } else { + System.err.println("Registration unsuccessful: "+registration.getExceptionMessage()); + } + + } + + protected abstract void addRegistrationSpecificParameters(Map parameters); + + abstract Registration[]> getRegistration(); + + protected abstract boolean validate(); + + public static Map convertToString(Context ctx, Map params) { + Map convertedParams = new HashMap<>(); + + ConvertService cs = ctx.getService(ConvertService.class); + + params.keySet().forEach(k -> convertedParams.put(k, cs.convert(params.get(k), String.class))); + + return convertedParams; + } +} diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/Elastix2DAffineRegistrationCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/Elastix2DAffineRegistrationCommand.java new file mode 100644 index 00000000..96b43ba9 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/Elastix2DAffineRegistrationCommand.java @@ -0,0 +1,43 @@ +package ch.epfl.biop.registration.scijava.command; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.Registration; +import ch.epfl.biop.registration.sourceandconverter.affine.Elastix2DAffineRegistration; +import org.scijava.command.Command; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; +import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; + +import java.util.Map; + +@Plugin(type = BdvPlaygroundActionCommand.class, + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Sources with Elastix - 2D - Affine", + description = "Performs a manual registration with BigWarp between two sources." ) + +public class Elastix2DAffineRegistrationCommand extends AbstractSourcesRegistrationCommand implements Command { + + @Parameter(label = "Registration re-sampling (micrometers)") + double pixel_size_micrometer = 20; + + @Parameter(label = "Show registration results as ImagePlus") + boolean show_imageplus_registration_result; + + @Override + protected void addRegistrationSpecificParameters(Map parameters) { + parameters.put(Registration.RESAMPLING_PX_SIZE, pixel_size_micrometer/1000.0); + parameters.put("background_offset_value_moving", 0); + parameters.put("background_offset_value_fixed", 0); + parameters.put("showImagePlusRegistrationResult", show_imageplus_registration_result); + } + + @Override + Registration[]> getRegistration() { + return new Elastix2DAffineRegistration(); + } + + @Override + protected boolean validate() { + return true; + } +} diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/Elastix2DSplineRegistrationCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/Elastix2DSplineRegistrationCommand.java new file mode 100644 index 00000000..a82254e2 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/Elastix2DSplineRegistrationCommand.java @@ -0,0 +1,47 @@ +package ch.epfl.biop.registration.scijava.command; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.Registration; +import ch.epfl.biop.registration.sourceandconverter.spline.Elastix2DSplineRegistration; +import org.scijava.command.Command; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; +import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; + +import java.util.Map; + +@Plugin(type = BdvPlaygroundActionCommand.class, + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Sources with Elastix - 2D - Spline", + description = "Performs a manual registration with BigWarp between two sources." ) + +public class Elastix2DSplineRegistrationCommand extends AbstractSourcesRegistrationCommand implements Command { + + @Parameter(label = "Number of control points along X, minimum 2.") + int nb_control_points_x = 10; + + @Parameter(label = "Registration re-sampling (micrometers)") + double pixel_size_micrometer = 20; + + @Parameter(label = "Show registration results as ImagePlus") + boolean show_imageplus_registration_result; + + @Override + protected void addRegistrationSpecificParameters(Map parameters) { + parameters.put(Registration.RESAMPLING_PX_SIZE, pixel_size_micrometer/1000.0); + parameters.put("background_offset_value_moving", 0); + parameters.put("background_offset_value_fixed", 0); + parameters.put("showImagePlusRegistrationResult", show_imageplus_registration_result); + parameters.put("nbControlPointsX", nb_control_points_x); + } + + @Override + Registration[]> getRegistration() { + return new Elastix2DSplineRegistration(); + } + + @Override + protected boolean validate() { + return true; + } +} diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/SacBigWarp2DRegistrationCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/SacBigWarp2DRegistrationCommand.java new file mode 100644 index 00000000..54d388b2 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/SacBigWarp2DRegistrationCommand.java @@ -0,0 +1,34 @@ +package ch.epfl.biop.registration.scijava.command; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.Registration; +import ch.epfl.biop.registration.sourceandconverter.bigwarp.SacBigWarp2DRegistration; +import org.scijava.command.Command; +import org.scijava.plugin.Plugin; +import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; +import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; + +import java.util.Map; + +@Plugin(type = BdvPlaygroundActionCommand.class, + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Sources with BigWarp - 2D - Spline", + description = "Performs a manual registration with BigWarp between two sources." ) + +public class SacBigWarp2DRegistrationCommand extends AbstractSourcesRegistrationCommand implements Command { + + + @Override + protected void addRegistrationSpecificParameters(Map parameters) { + // Nothing required + } + + @Override + Registration[]> getRegistration() { + return new SacBigWarp2DRegistration(); + } + + @Override + protected boolean validate() { + return true; + } +} diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/Sift2DRegistrationCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/Sift2DRegistrationCommand.java new file mode 100644 index 00000000..f64c58c8 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/Sift2DRegistrationCommand.java @@ -0,0 +1,45 @@ +package ch.epfl.biop.registration.scijava.command; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.Registration; +import ch.epfl.biop.registration.sourceandconverter.affine.Sift2DAffineRegistration; +import org.scijava.command.Command; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; +import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; + +import java.util.Map; + +@Plugin(type = BdvPlaygroundActionCommand.class, + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Sources with Sift - 2D - Affine", + description = "Performs a manual registration with BigWarp between two sources." ) + +public class Sift2DRegistrationCommand extends AbstractSourcesRegistrationCommand implements Command { + + @Parameter(label = "Registration re-sampling (micrometers)") + double pixel_size_micrometer = 20; + + @Parameter(label = "Invert moving image") + boolean invert_moving; + + @Parameter(label = "Invert fixed image") + boolean invert_fixed; + + @Override + protected void addRegistrationSpecificParameters(Map parameters) { + parameters.put(Registration.RESAMPLING_PX_SIZE, pixel_size_micrometer/1000.0); + parameters.put("invert_moving", invert_moving); + parameters.put("invert_fixed", invert_fixed); + } + + @Override + Registration[]> getRegistration() { + return new Sift2DAffineRegistration(); + } + + @Override + protected boolean validate() { + return true; + } +} diff --git a/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/Sift2DAffineRegistration.java b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/Sift2DAffineRegistration.java new file mode 100644 index 00000000..722ffe45 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/Sift2DAffineRegistration.java @@ -0,0 +1,147 @@ +package ch.epfl.biop.registration.sourceandconverter.affine; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.plugin.IRegistrationPlugin; +import ch.epfl.biop.registration.plugin.RegistrationTypeProperties; +import ch.epfl.biop.scijava.command.source.register.Sift2DAffineRegisterCommand; +import net.imglib2.realtransform.AffineTransform3D; +import org.scijava.command.Command; +import org.scijava.command.CommandModule; +import org.scijava.command.CommandService; +import org.scijava.plugin.Plugin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sc.fiji.bdvpg.sourceandconverter.SourceAndConverterHelper; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Future; + +/** + * Uses Elastix backend to perform an affine transform registration + * + */ +@Plugin(type = IRegistrationPlugin.class) +@RegistrationTypeProperties( + isManual = false, + isEditable = false) + +public class Sift2DAffineRegistration extends AffineTransformSourceAndConverterRegistration{ + + + protected static final Logger logger = LoggerFactory.getLogger(Sift2DAffineRegistration.class); + + @Override + public void setFixedImage(SourceAndConverter[] fimg) { + if (fimg.length==0) { + logger.error("Error, no fixed image set in class "+this.getClass().getSimpleName()); + } + if (fimg.length>1) { + logger.error("Error, sift registration only works with a single channel "+this.getClass().getSimpleName()); + } + super.setFixedImage(fimg); + } + + @Override + public void setMovingImage(SourceAndConverter[] mimg) { + if (mimg.length==0) { + logger.error("Error, no fixed image set in class "+this.getClass().getSimpleName()); + } + if (mimg.length>1) { + logger.error("Error, sift registration only works with a single channel "+this.getClass().getSimpleName()); + } + super.setMovingImage(mimg); + } + + Future task; + + @Override + public boolean register() { + try { + Class registrationCommandClass; + // Is it supposed to be done on a server ? + registrationCommandClass = Sift2DAffineRegisterCommand.class; + + // Registration success flag + boolean success = true; + + // Transforms map into flat String : key1, value1, key2, value2, etc. + // Necessary for CommandService + List flatParameters = new ArrayList<>(parameters.size()*2+4); + + double voxSizeInMm = Double.parseDouble(parameters.get("pxSizeInCurrentUnit")); + boolean invert_moving = Boolean.parseBoolean(parameters.get("invert_moving")); + boolean invert_fixed = Boolean.parseBoolean(parameters.get("invert_fixed")); + + parameters.keySet().forEach(k -> { + flatParameters.add(k); + flatParameters.add(parameters.get(k)); + }); + + addToFlatParameters(flatParameters, + // Fixed image + "sacs_fixed", fimg, + // Moving image + "sacs_moving", mimg, + // No interpolation in resampling + "interpolate", false, + // Atlas image : a single timepoint + "tpFixed", 0, + // Level 2 for the atlas + "levelFixedSource", SourceAndConverterHelper.bestLevel(fimg[0], timePoint, voxSizeInMm), + // Timepoint moving source (normally 0) + "tpMoving", timePoint, + // Tries to be clever for the moving source sampling + "levelMovingSource", SourceAndConverterHelper.bestLevel(mimg[0], timePoint, voxSizeInMm), + "invert_moving", invert_moving, + "invert_fixed", invert_fixed + ); + + task = context + .getService(CommandService.class) + .run(registrationCommandClass, false, + flatParameters.toArray(new Object[0]) + ); + + CommandModule module = task.get(); + + if (module.getOutputs().containsKey("success")) { + success = (boolean) module.getOutput("success"); + } + if (success) { + at3d = (AffineTransform3D) module.getOutput("at3D"); + } else { + if (module.getOutputs().containsKey("error")) { + errorMessage = (String) module.getOutput("error"); + } + } + + isDone = true; + return success; + } catch (Exception e) { + errorMessage = e.getMessage(); + e.printStackTrace(); + return false; + } + } + + String errorMessage = ""; + + @Override + public String getExceptionMessage() { + return errorMessage; + } + + @Override + public void abort() { + if (task!=null) { + logger.info(this.getClass().getSimpleName()+": Attempt to interrupt registration..."); + task.cancel(true); + } + } + + public String toString() { + return "Elastix 2D Affine"; + } + +} diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DAffineRegisterCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DAffineRegisterCommand.java index 4475f649..9f4900c5 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DAffineRegisterCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DAffineRegisterCommand.java @@ -19,11 +19,11 @@ * on big images. See {@link RegisterWholeSlideScans2DCommand} */ -@Plugin(type = BdvPlaygroundActionCommand.class, +@Plugin(type = BdvPlaygroundActionCommand.class/*, menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Sources with Elastix (Affine, 2D)", description = "Performs an affine registration in 2D between 2 sources. Low level command which\n"+ "requires many parameters. For more user friendly command, use wizards instead.\n"+ - "Outputs the transform to apply to the moving source." ) + "Outputs the transform to apply to the moving source." */) public class Elastix2DAffineRegisterCommand extends AbstractElastix2DRegistrationInRectangleCommand implements BdvPlaygroundActionCommand { private static Logger logger = LoggerFactory.getLogger(Elastix2DAffineRegisterCommand.class); diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DSparsePointsRegisterCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DSparsePointsRegisterCommand.java index 1cf8ffea..13f72164 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DSparsePointsRegisterCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DSparsePointsRegisterCommand.java @@ -25,13 +25,13 @@ import java.util.function.Consumer; import java.util.stream.Stream; -@Plugin(type = BdvPlaygroundActionCommand.class, +@Plugin(type = BdvPlaygroundActionCommand.class/*, menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>AutoWarp Sources with Elastix and BigWarp (2D)", description = "Register two 2D sources by automatically registering small fields of view \n" + "surrounding the specified points of interests using elastix. The returned result \n" + "is a thin plate spline transform object that can be applied to other sources then \n" + - "edited in BigWarp.") + "edited in BigWarp."*/) public class Elastix2DSparsePointsRegisterCommand extends SelectSourcesForRegistrationCommand implements BdvPlaygroundActionCommand { diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DSplineRegisterCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DSplineRegisterCommand.java index eefe806a..8eea5fed 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DSplineRegisterCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DSplineRegisterCommand.java @@ -9,7 +9,8 @@ import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; -@Plugin(type = BdvPlaygroundActionCommand.class, menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Sources with Elastix (Spline, 2D)") +@Plugin(type = BdvPlaygroundActionCommand.class/*, + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Sources with Elastix (Spline, 2D)"*/) public class Elastix2DSplineRegisterCommand extends AbstractElastix2DRegistrationInRectangleCommand implements BdvPlaygroundActionCommand { @Parameter(label = "Number of control points along the X axis") diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/MultiscaleRegisterCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/MultiscaleRegisterCommand.java index 9eb45f14..85c4bdf0 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/source/register/MultiscaleRegisterCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/MultiscaleRegisterCommand.java @@ -46,7 +46,7 @@ // TODO test edge cases @Plugin(type = BdvPlaygroundActionCommand.class, - menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Multiscale Registration (2D)", + //menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Multiscale Registration (2D)", headless = true, // User interface not required initializer = "updateInfo" ) diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/RegisterWholeSlideScans2DCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/RegisterWholeSlideScans2DCommand.java index c2b19eb3..26dd85cb 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/source/register/RegisterWholeSlideScans2DCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/RegisterWholeSlideScans2DCommand.java @@ -25,8 +25,8 @@ import java.util.ArrayList; import java.util.concurrent.ExecutionException; -@Plugin(type = BdvPlaygroundActionCommand.class, - menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Align Slides (2D)") +@Plugin(type = BdvPlaygroundActionCommand.class/*, + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Align Slides (2D)"*/) public class RegisterWholeSlideScans2DCommand implements BdvPlaygroundActionCommand { private static Logger logger = LoggerFactory.getLogger(RegisterWholeSlideScans2DCommand.class); diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/SelectSourcesForRegistrationCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/SelectSourcesForRegistrationCommand.java index 85cfce2e..ce1830a5 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/source/register/SelectSourcesForRegistrationCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/SelectSourcesForRegistrationCommand.java @@ -7,7 +7,7 @@ abstract public class SelectSourcesForRegistrationCommand implements Command { @Parameter(label = "Fixed source for registration", description = "fixed source") - SourceAndConverter[] sacs_fixed; + SourceAndConverter[] sacs_fixed; @Parameter(label = "Timepoint of the fixed source") int tpFixed; diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java index b3fb1f2a..e6d859c5 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java @@ -16,11 +16,11 @@ * on big images. See {@link RegisterWholeSlideScans2DCommand} */ -@Plugin(type = BdvPlaygroundActionCommand.class, +@Plugin(type = BdvPlaygroundActionCommand.class/*, menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Sources with SIFT (Affine, 2D)", description = "Performs an affine registration in 2D between 2 sources. Low level command which\n"+ "requires many parameters. For more user friendly command, use wizards instead.\n"+ - "Outputs the transform to apply to the moving source." ) + "Outputs the transform to apply to the moving source." */) public class Sift2DAffineRegisterCommand extends Abstract2DRegistrationInRectangleCommand implements BdvPlaygroundActionCommand { private static Logger logger = LoggerFactory.getLogger(Sift2DAffineRegisterCommand.class); diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/SourcesAffineTransformCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/SourcesAffineTransformCommand.java index 210299e7..e5c27664 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/source/register/SourcesAffineTransformCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/SourcesAffineTransformCommand.java @@ -12,7 +12,8 @@ import java.util.Arrays; -@Plugin(type = BdvPlaygroundActionCommand.class, menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Transform>Affine Transform Sources") +@Plugin(type = BdvPlaygroundActionCommand.class/*, + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Transform>Affine Transform Sources"*/) public class SourcesAffineTransformCommand implements BdvPlaygroundActionCommand { @Parameter SourceAndConverter[] sources_in; diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/SourcesRealTransformCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/SourcesRealTransformCommand.java index 31266bf8..2f3e07df 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/source/register/SourcesRealTransformCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/SourcesRealTransformCommand.java @@ -12,7 +12,8 @@ import java.util.Arrays; import java.util.stream.Collectors; -@Plugin(type = BdvPlaygroundActionCommand.class, menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Transform>Real Transform Sources") +@Plugin(type = BdvPlaygroundActionCommand.class/*, + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Transform>Real Transform Sources"*/) public class SourcesRealTransformCommand implements BdvPlaygroundActionCommand { @Parameter diff --git a/src/main/java/ch/epfl/biop/scijava/command/transform/EditSourcesWarpingCommand.java b/src/main/java/ch/epfl/biop/scijava/command/transform/EditSourcesWarpingCommand.java index f531059b..4060c03e 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/transform/EditSourcesWarpingCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/transform/EditSourcesWarpingCommand.java @@ -25,8 +25,8 @@ import static bdv.util.RealTransformHelper.BigWarpFileFromRealTransform; -@Plugin(type = BdvPlaygroundActionCommand.class, - menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Edit Sources Warping") +@Plugin(type = BdvPlaygroundActionCommand.class/*, + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Edit Sources Warping"*/) public class EditSourcesWarpingCommand implements BdvPlaygroundActionCommand { From ba5469bfd1f8d51f48d1ff350c9e4c677cdfc196 Mon Sep 17 00:00:00 2001 From: Nicolas Chiaruttini Date: Fri, 14 Jun 2024 23:41:29 +0200 Subject: [PATCH 06/14] Adds working commands for new registration interface. No QuPath export yet --- .../biop/registration/RegistrationPair.java | 133 +++++++++++++++ .../AbstractPairRegistration2DCommand.java | 154 +++++++++++++++++ .../AbstractSourcesRegistrationCommand.java | 159 ------------------ ...irRegistrationBigWarp2DSplineCommand.java} | 4 +- .../PairRegistrationCreateCommand.java | 38 +++++ .../PairRegistrationDeleteCommand.java | 28 +++ ...gistrationEditLastRegistrationCommand.java | 22 +++ ...irRegistrationElastix2DAffineCommand.java} | 4 +- ...irRegistrationElastix2DSplineCommand.java} | 4 +- ...strationRemoveLastRegistrationCommand.java | 25 +++ ... PairRegistrationSift2DAffineCommand.java} | 4 +- .../widget/RegistrationPairWidget.java | 37 ++++ .../widget/SwingRegistrationPairWidget.java | 99 +++++++++++ .../spline/Elastix2DSplineRegistration.java | 34 ++-- .../Elastix2DAffineRegisterCommand.java | 7 +- .../Elastix2DSplineRegisterCommand.java | 5 +- .../register/Sift2DAffineRegisterCommand.java | 6 +- .../transform/RemoveZOffsetCommand.java | 2 +- .../register/SIFTRegister.java | 8 + .../register/DemoRegistrationWorkflow.java | 18 ++ 20 files changed, 604 insertions(+), 187 deletions(-) create mode 100644 src/main/java/ch/epfl/biop/registration/RegistrationPair.java create mode 100644 src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java delete mode 100644 src/main/java/ch/epfl/biop/registration/scijava/command/AbstractSourcesRegistrationCommand.java rename src/main/java/ch/epfl/biop/registration/scijava/command/{SacBigWarp2DRegistrationCommand.java => PairRegistrationBigWarp2DSplineCommand.java} (86%) create mode 100644 src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCreateCommand.java create mode 100644 src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationDeleteCommand.java create mode 100644 src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationEditLastRegistrationCommand.java rename src/main/java/ch/epfl/biop/registration/scijava/command/{Elastix2DAffineRegistrationCommand.java => PairRegistrationElastix2DAffineCommand.java} (90%) rename src/main/java/ch/epfl/biop/registration/scijava/command/{Elastix2DSplineRegistrationCommand.java => PairRegistrationElastix2DSplineCommand.java} (91%) create mode 100644 src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationRemoveLastRegistrationCommand.java rename src/main/java/ch/epfl/biop/registration/scijava/command/{Sift2DRegistrationCommand.java => PairRegistrationSift2DAffineCommand.java} (90%) create mode 100644 src/main/java/ch/epfl/biop/registration/scijava/widget/RegistrationPairWidget.java create mode 100644 src/main/java/ch/epfl/biop/registration/scijava/widget/SwingRegistrationPairWidget.java create mode 100644 src/test/java/register/DemoRegistrationWorkflow.java diff --git a/src/main/java/ch/epfl/biop/registration/RegistrationPair.java b/src/main/java/ch/epfl/biop/registration/RegistrationPair.java new file mode 100644 index 00000000..3c4aaa60 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/RegistrationPair.java @@ -0,0 +1,133 @@ +package ch.epfl.biop.registration; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.plugin.RegistrationPluginHelper; +import ch.epfl.biop.sourceandconverter.processor.SourcesAffineTransformer; +import net.imglib2.realtransform.AffineTransform3D; +import org.scijava.Named; +import sc.fiji.bdvpg.services.SourceAndConverterServices; + +import java.util.ArrayList; +import java.util.List; + +public class RegistrationPair implements Named { + + final SourceAndConverter[] movingSourcesOrigin; + final SourceAndConverter[] fixedSources; + + final int timepointMoving; + final int timepointFixed; + final String name; + + SourceAndConverter[] movingSourcesRegistered; + final List registrationAndSources = new ArrayList<>(); + + public RegistrationPair(SourceAndConverter[] fixedSources, + int timepointFixed, + SourceAndConverter[] movingSources, + int timepointMoving, + String name, boolean removezOffset + ) { + if (removezOffset) { + this.fixedSources = new SourcesAffineTransformer(findZ0Transform(fixedSources[0], timepointFixed)).apply(fixedSources); + this.movingSourcesOrigin = new SourcesAffineTransformer(findZ0Transform(movingSources[0], timepointMoving)).apply(movingSources); + } else { + this.fixedSources = fixedSources; + this.movingSourcesOrigin = movingSources; + } + + this.movingSourcesRegistered = movingSourcesOrigin; + + this.timepointFixed = timepointFixed; + this.timepointMoving = timepointMoving; + this.name = name; + } + + public SourceAndConverter[] getFixedSources() { + return fixedSources; + } + + public SourceAndConverter[] getMovingSourcesOrigin() { + return movingSourcesOrigin; + } + + public synchronized SourceAndConverter[] getMovingSourcesRegistered() { + return movingSourcesRegistered; + } + + public synchronized void appendRegistration(Registration[]> reg) { + RegistrationAndSources ras = new RegistrationAndSources(reg, reg.getTransformedImageMovingToFixed(getMovingSourcesRegistered())); + movingSourcesRegistered = ras.sacs; + SourceAndConverterServices.getSourceAndConverterService().register(ras.sacs[0]); + registrationAndSources.add(ras); + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + throw new UnsupportedOperationException("You can't rename a registration pair object"); + } + + public synchronized void removeLastRegistration() { + if (registrationAndSources.size() == 0) return; + if (registrationAndSources.size()==1) { + registrationAndSources.remove(0); + this.movingSourcesRegistered = movingSourcesOrigin; + } else { + RegistrationAndSources ras = registrationAndSources.get(registrationAndSources.size()-2); + registrationAndSources.remove(registrationAndSources.size()-1); + this.movingSourcesRegistered = ras.sacs; + } + } + + public synchronized void editLastRegistration() { + if (registrationAndSources.size() == 0) { + System.err.println("There is no registration to edit"); + return; + } + + Registration lastReg = registrationAndSources.get(registrationAndSources.size()-1).reg; + if (!RegistrationPluginHelper.isEditable(lastReg)) { + System.err.println("The last registration is not editable"); + return; + } + + removeLastRegistration(); + + lastReg.edit(); + + appendRegistration(lastReg); + } + + private static class RegistrationAndSources { + + final Registration[]> reg; + final SourceAndConverter[] sacs; + + public RegistrationAndSources(Registration[]> reg, SourceAndConverter[] sacs) { + this.reg = reg; + this.sacs = sacs; + } + } + + @Override + public String toString() { + return name+" [#f="+fixedSources.length+" #m="+movingSourcesOrigin.length+" #regs="+registrationAndSources.size()+"]"; + } + + private static AffineTransform3D findZ0Transform(SourceAndConverter source, int timePoint) { + long sz = source.getSpimSource().getSource(timePoint, 0).dimension(2); + AffineTransform3D at3D = new AffineTransform3D(); + source.getSpimSource().getSourceTransform(timePoint, 0, at3D); + AffineTransform3D at3DCenter = new AffineTransform3D(); + at3DCenter.concatenate(at3D.inverse()); + at3DCenter.translate(0, 0, -sz/2.0+0.5); // Humpf + at3D.set(0,2,3); + at3DCenter.preConcatenate(at3D); + return at3DCenter; + } +} diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java new file mode 100644 index 00000000..2688a609 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java @@ -0,0 +1,154 @@ +package ch.epfl.biop.registration.scijava.command; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.Registration; +import ch.epfl.biop.registration.RegistrationPair; +import ch.epfl.biop.sourceandconverter.processor.SourcesChannelsSelect; +import org.scijava.Context; +import org.scijava.ItemIO; +import org.scijava.command.Command; +import org.scijava.convert.ConvertService; +import org.scijava.plugin.Parameter; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +abstract public class AbstractPairRegistration2DCommand implements Command { + + @Parameter + Context ctx; + + @Parameter + RegistrationPair registration_pair; + + @Parameter(label = "Fixed image channels used for registration (comma separated)") + String channels_fixed_csv; + + @Parameter(label = "Moving image channels used for registration (comma separated)") + String channels_moving_csv; + + @Parameter(label = "ROI for registration (position x)", style = "format:0.#####E0") + double px; + + @Parameter(label = "ROI for registration (position y)", style = "format:0.#####E0") + double py; + + @Parameter(label = "ROI for registration (size x)", style = "format:0.#####E0") + double sx; + + @Parameter(label = "ROI for registration (size y)", style = "format:0.#####E0") + double sy; + + @Parameter(type = ItemIO.OUTPUT) + boolean success; + + @Override + public void run() { + synchronized (registration_pair) { + SourceAndConverter[] moving_sources = registration_pair.getMovingSourcesRegistered(); + SourceAndConverter[] fixed_sources = registration_pair.getFixedSources(); + + List moving_channels; + List fixed_channels; + + try { + moving_channels = Arrays.stream(channels_moving_csv.split(",")) + .map(Integer::parseInt) + .collect(Collectors.toList()); + fixed_channels = Arrays.stream(channels_fixed_csv.split(",")) + .map(Integer::parseInt) + .collect(Collectors.toList()); + } catch (NumberFormatException e) { + System.err.println("Number parsing exception " + e.getMessage()); + return; + } + + if (moving_channels.isEmpty()) { + System.err.println("Error, you did not specify any channel within the moving image."); + return; + } + + if (fixed_channels.isEmpty()) { + System.err.println("Error, you did not specify any channel within the fixed image."); + return; + } + + int maxIndexMoving = Collections.max(moving_channels); + int minIndexMoving = Collections.min(moving_channels); + + int maxIndexFixed = Collections.max(fixed_channels); + int minIndexFixed = Collections.min(fixed_channels); + + if ((minIndexMoving < 0) || (minIndexFixed < 0)) { + System.err.println("All channels indices should be positive"); + return; + } + + if (!(maxIndexFixed < fixed_sources.length)) { + System.err.println("The max index within the fixed sources (" + maxIndexFixed + ") is above its maximum (" + (fixed_sources.length - 1) + ")"); + return; + } + + if (!(maxIndexMoving < moving_sources.length)) { + System.err.println("The max index within the moving sources (" + maxIndexFixed + ") is above its maximum (" + (moving_sources.length - 1) + ")"); + return; + } + + Registration[]> registration = getRegistration(); + registration.setScijavaContext(ctx); + + registration.setTimePoint(0); + + registration.setMovingImage(new SourcesChannelsSelect(moving_channels).apply(moving_sources)); + registration.setFixedImage(new SourcesChannelsSelect(fixed_channels).apply(fixed_sources)); + + Map parameters = new HashMap<>(); + + parameters.put(Registration.ROI_PX, px); + parameters.put(Registration.ROI_PY, py); + parameters.put(Registration.ROI_SX, sx); + parameters.put(Registration.ROI_SY, sy); + + addRegistrationSpecificParameters(parameters); + + registration.setRegistrationParameters(convertToString(ctx, parameters)); + + boolean ok = validate(); + + if (!ok) { + System.err.println("Validation failed."); + return; + } + + success = registration.register(); // Do it! + + if (success) { + //registered_sources = registration.getTransformedImageMovingToFixed(moving_sources); + registration_pair.appendRegistration(registration); + } else { + System.err.println("Registration unsuccessful: " + registration.getExceptionMessage()); + } + } + + } + + protected abstract void addRegistrationSpecificParameters(Map parameters); + + abstract Registration[]> getRegistration(); + + protected abstract boolean validate(); + + public static Map convertToString(Context ctx, Map params) { + Map convertedParams = new HashMap<>(); + + ConvertService cs = ctx.getService(ConvertService.class); + + params.keySet().forEach(k -> convertedParams.put(k, cs.convert(params.get(k), String.class))); + + return convertedParams; + } +} diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractSourcesRegistrationCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractSourcesRegistrationCommand.java deleted file mode 100644 index 6d135c50..00000000 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractSourcesRegistrationCommand.java +++ /dev/null @@ -1,159 +0,0 @@ -package ch.epfl.biop.registration.scijava.command; - -import bdv.viewer.SourceAndConverter; -import ch.epfl.biop.registration.Registration; -import ch.epfl.biop.sourceandconverter.processor.SourcesChannelsSelect; -import org.scijava.Context; -import org.scijava.ItemIO; -import org.scijava.command.Command; -import org.scijava.convert.ConvertService; -import org.scijava.plugin.Parameter; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -abstract public class AbstractSourcesRegistrationCommand implements Command { - - @Parameter - Context ctx; - - @Parameter(label = "Fixed source for registration", description = "fixed sources", style = "sorted") - SourceAndConverter[] fixed_sources; - - @Parameter(label = "Moving source for registration", description = "moving sources", style = "sorted") - SourceAndConverter[] moving_sources; - - @Parameter(label = "Fixed image channels used for registration (comma separated)") - String channels_fixed_csv; - - @Parameter(label = "Moving image channels used for registration (comma separated)") - String channels_moving_csv; - - @Parameter(label = "ROI for registration (position x)", style = "format:0.#####E0") - double px; - - @Parameter(label = "ROI for registration (position y)", style = "format:0.#####E0") - double py; - - @Parameter(label = "ROI for registration (size x)", style = "format:0.#####E0") - double sx; - - @Parameter(label = "ROI for registration (size y)", style = "format:0.#####E0") - double sy; - - @Parameter(type = ItemIO.OUTPUT) - boolean success; - - @Parameter(type = ItemIO.OUTPUT) - SourceAndConverter[] registered_sources; - - @Parameter(type = ItemIO.OUTPUT) - Registration[]> registration; - - @Override - public void run() { - - List moving_channels; - List fixed_channels; - - try { - moving_channels = Arrays.stream(channels_moving_csv.split(",")) - .map(Integer::parseInt) - .collect(Collectors.toList()); - fixed_channels = Arrays.stream(channels_fixed_csv.split(",")) - .map(Integer::parseInt) - .collect(Collectors.toList()); - } catch (NumberFormatException e) { - System.err.println("Number parsing exception "+e.getMessage()); - return; - } - - if (moving_channels.isEmpty()) { - System.err.println("Error, you did not specify any channel within the moving image."); - return; - } - - if (fixed_channels.isEmpty()) { - System.err.println("Error, you did not specify any channel within the fixed image."); - return; - } - - int maxIndexMoving = Collections.max(moving_channels); - int minIndexMoving = Collections.min(moving_channels); - - int maxIndexFixed = Collections.max(fixed_channels); - int minIndexFixed = Collections.min(fixed_channels); - - if ((minIndexMoving<0)||(minIndexFixed<0)) { - System.err.println("All channels indices should be positive"); - return; - } - - if (!(maxIndexFixed parameters = new HashMap<>(); - - parameters.put(Registration.ROI_PX, px); - parameters.put(Registration.ROI_PY, py); - parameters.put(Registration.ROI_SX, sx); - parameters.put(Registration.ROI_SY, sy); - - addRegistrationSpecificParameters(parameters); - - registration.setRegistrationParameters(convertToString(ctx, parameters)); - - boolean ok = validate(); - - if (!ok) { - System.err.println("Validation failed."); - return; - } - - - - success = registration.register(); // Do it! - - if (success) { - registered_sources = registration.getTransformedImageMovingToFixed(moving_sources); - } else { - System.err.println("Registration unsuccessful: "+registration.getExceptionMessage()); - } - - } - - protected abstract void addRegistrationSpecificParameters(Map parameters); - - abstract Registration[]> getRegistration(); - - protected abstract boolean validate(); - - public static Map convertToString(Context ctx, Map params) { - Map convertedParams = new HashMap<>(); - - ConvertService cs = ctx.getService(ConvertService.class); - - params.keySet().forEach(k -> convertedParams.put(k, cs.convert(params.get(k), String.class))); - - return convertedParams; - } -} diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/SacBigWarp2DRegistrationCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationBigWarp2DSplineCommand.java similarity index 86% rename from src/main/java/ch/epfl/biop/registration/scijava/command/SacBigWarp2DRegistrationCommand.java rename to src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationBigWarp2DSplineCommand.java index 54d388b2..7ad2ef27 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/SacBigWarp2DRegistrationCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationBigWarp2DSplineCommand.java @@ -11,10 +11,10 @@ import java.util.Map; @Plugin(type = BdvPlaygroundActionCommand.class, - menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Sources with BigWarp - 2D - Spline", + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair with BigWarp - 2D - Spline", description = "Performs a manual registration with BigWarp between two sources." ) -public class SacBigWarp2DRegistrationCommand extends AbstractSourcesRegistrationCommand implements Command { +public class PairRegistrationBigWarp2DSplineCommand extends AbstractPairRegistration2DCommand implements Command { @Override diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCreateCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCreateCommand.java new file mode 100644 index 00000000..0e3c000d --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCreateCommand.java @@ -0,0 +1,38 @@ +package ch.epfl.biop.registration.scijava.command; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.RegistrationPair; +import org.scijava.ItemIO; +import org.scijava.command.Command; +import org.scijava.object.ObjectService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; +import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; + +@Plugin(type = BdvPlaygroundActionCommand.class, + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Create registration pair", + description = "Defines a registration pair" ) +public class PairRegistrationCreateCommand implements Command { + + @Parameter(label = "Fixed source for registration", description = "fixed sources", style = "sorted") + SourceAndConverter[] fixed_sources; + + @Parameter(label = "Moving source for registration", description = "moving sources", style = "sorted") + SourceAndConverter[] moving_sources; + + @Parameter + String registration_name; + + @Parameter(type = ItemIO.OUTPUT) + RegistrationPair registration_pair; + + @Parameter + ObjectService objectService; + + @Override + public void run() { + registration_pair = new RegistrationPair(fixed_sources,0,moving_sources,0, registration_name, true); + objectService.addObject(registration_pair); + } +} diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationDeleteCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationDeleteCommand.java new file mode 100644 index 00000000..d5b0cb7d --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationDeleteCommand.java @@ -0,0 +1,28 @@ +package ch.epfl.biop.registration.scijava.command; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.RegistrationPair; +import org.scijava.ItemIO; +import org.scijava.command.Command; +import org.scijava.object.ObjectService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; +import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; + +@Plugin(type = BdvPlaygroundActionCommand.class, + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Delete registration pair", + description = "Delete a registration pair" ) +public class PairRegistrationDeleteCommand implements Command { + + @Parameter + RegistrationPair registration_pair; + + @Parameter + ObjectService objectService; + + @Override + public void run() { + objectService.removeObject(registration_pair); + } +} diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationEditLastRegistrationCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationEditLastRegistrationCommand.java new file mode 100644 index 00000000..00983f9e --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationEditLastRegistrationCommand.java @@ -0,0 +1,22 @@ +package ch.epfl.biop.registration.scijava.command; + +import ch.epfl.biop.registration.RegistrationPair; +import org.scijava.command.Command; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; +import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; + +@Plugin(type = BdvPlaygroundActionCommand.class, + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Registration pair - edit last registration", + description = "Edit the last registration of a registration pair" ) +public class PairRegistrationEditLastRegistrationCommand implements Command { + + @Parameter + RegistrationPair registration_pair; + + @Override + public void run() { + registration_pair.editLastRegistration(); + } +} diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/Elastix2DAffineRegistrationCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DAffineCommand.java similarity index 90% rename from src/main/java/ch/epfl/biop/registration/scijava/command/Elastix2DAffineRegistrationCommand.java rename to src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DAffineCommand.java index 96b43ba9..90b9e98a 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/Elastix2DAffineRegistrationCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DAffineCommand.java @@ -12,10 +12,10 @@ import java.util.Map; @Plugin(type = BdvPlaygroundActionCommand.class, - menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Sources with Elastix - 2D - Affine", + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair with Elastix - 2D - Affine", description = "Performs a manual registration with BigWarp between two sources." ) -public class Elastix2DAffineRegistrationCommand extends AbstractSourcesRegistrationCommand implements Command { +public class PairRegistrationElastix2DAffineCommand extends AbstractPairRegistration2DCommand implements Command { @Parameter(label = "Registration re-sampling (micrometers)") double pixel_size_micrometer = 20; diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/Elastix2DSplineRegistrationCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DSplineCommand.java similarity index 91% rename from src/main/java/ch/epfl/biop/registration/scijava/command/Elastix2DSplineRegistrationCommand.java rename to src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DSplineCommand.java index a82254e2..ae1fb603 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/Elastix2DSplineRegistrationCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DSplineCommand.java @@ -12,10 +12,10 @@ import java.util.Map; @Plugin(type = BdvPlaygroundActionCommand.class, - menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Sources with Elastix - 2D - Spline", + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair with Elastix - 2D - Spline", description = "Performs a manual registration with BigWarp between two sources." ) -public class Elastix2DSplineRegistrationCommand extends AbstractSourcesRegistrationCommand implements Command { +public class PairRegistrationElastix2DSplineCommand extends AbstractPairRegistration2DCommand implements Command { @Parameter(label = "Number of control points along X, minimum 2.") int nb_control_points_x = 10; diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationRemoveLastRegistrationCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationRemoveLastRegistrationCommand.java new file mode 100644 index 00000000..41d44a00 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationRemoveLastRegistrationCommand.java @@ -0,0 +1,25 @@ +package ch.epfl.biop.registration.scijava.command; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.RegistrationPair; +import org.scijava.ItemIO; +import org.scijava.command.Command; +import org.scijava.object.ObjectService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; +import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; + +@Plugin(type = BdvPlaygroundActionCommand.class, + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Registration pair - remove last registration", + description = "Defines a registration pair" ) +public class PairRegistrationRemoveLastRegistrationCommand implements Command { + + @Parameter + RegistrationPair registration_pair; + + @Override + public void run() { + registration_pair.removeLastRegistration(); + } +} diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/Sift2DRegistrationCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationSift2DAffineCommand.java similarity index 90% rename from src/main/java/ch/epfl/biop/registration/scijava/command/Sift2DRegistrationCommand.java rename to src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationSift2DAffineCommand.java index f64c58c8..c02fe569 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/Sift2DRegistrationCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationSift2DAffineCommand.java @@ -12,10 +12,10 @@ import java.util.Map; @Plugin(type = BdvPlaygroundActionCommand.class, - menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Sources with Sift - 2D - Affine", + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair with Sift - 2D - Affine", description = "Performs a manual registration with BigWarp between two sources." ) -public class Sift2DRegistrationCommand extends AbstractSourcesRegistrationCommand implements Command { +public class PairRegistrationSift2DAffineCommand extends AbstractPairRegistration2DCommand implements Command { @Parameter(label = "Registration re-sampling (micrometers)") double pixel_size_micrometer = 20; diff --git a/src/main/java/ch/epfl/biop/registration/scijava/widget/RegistrationPairWidget.java b/src/main/java/ch/epfl/biop/registration/scijava/widget/RegistrationPairWidget.java new file mode 100644 index 00000000..30305294 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/scijava/widget/RegistrationPairWidget.java @@ -0,0 +1,37 @@ +/*- + * #%L + * BigDataViewer-Playground + * %% + * Copyright (C) 2019 - 2024 Nicolas Chiaruttini, EPFL - Robert Haase, MPI CBG - Christian Tischer, EMBL + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package ch.epfl.biop.registration.scijava.widget; + +import ch.epfl.biop.registration.RegistrationPair; +import org.scijava.widget.InputWidget; + +public interface RegistrationPairWidget extends + InputWidget +{} diff --git a/src/main/java/ch/epfl/biop/registration/scijava/widget/SwingRegistrationPairWidget.java b/src/main/java/ch/epfl/biop/registration/scijava/widget/SwingRegistrationPairWidget.java new file mode 100644 index 00000000..2f196168 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/scijava/widget/SwingRegistrationPairWidget.java @@ -0,0 +1,99 @@ +/*- + * #%L + * BigDataViewer-Playground + * %% + * Copyright (C) 2019 - 2024 Nicolas Chiaruttini, EPFL - Robert Haase, MPI CBG - Christian Tischer, EMBL + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package ch.epfl.biop.registration.scijava.widget; + +import ch.epfl.biop.registration.RegistrationPair; +import org.scijava.Priority; +import org.scijava.object.ObjectService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.ui.swing.widget.SwingInputWidget; +import org.scijava.widget.InputWidget; +import org.scijava.widget.WidgetModel; + +import javax.swing.*; +import java.util.List; + +/** + * + * @author Nicolas Chiaruttini + */ + +@SuppressWarnings("unused") +@Plugin(type = InputWidget.class, priority = Priority.EXTREMELY_HIGH) +public class SwingRegistrationPairWidget extends + SwingInputWidget implements + RegistrationPairWidget +{ + + @Override + protected void doRefresh() {} + + @Override + public boolean supports(final WidgetModel model) { + return super.supports(model) && model.isType(RegistrationPair.class); + } + + @Override + public RegistrationPair getValue() { + return jList.getSelectedValue(); + } + + JList jList; + + @Parameter + ObjectService objectService; + + List allRegistrations; + + @Override + public void set(final WidgetModel model) { + super.set(model); + allRegistrations = objectService.getObjects(RegistrationPair.class); + + // Convert the ArrayList to an array + RegistrationPair[] itemArray = new RegistrationPair[allRegistrations.size()]; + allRegistrations.toArray(itemArray); + + jList = new JList<>(itemArray); + + // Set the selection mode to single selection + jList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + JScrollPane scrollPane = new JScrollPane(jList); + + getComponent().add(scrollPane); + refreshWidget(); + model.setValue(null); + jList.addListSelectionListener((e) -> model.setValue(getValue())); + + } + +} diff --git a/src/main/java/ch/epfl/biop/registration/sourceandconverter/spline/Elastix2DSplineRegistration.java b/src/main/java/ch/epfl/biop/registration/sourceandconverter/spline/Elastix2DSplineRegistration.java index 2a85b92e..bb87c903 100644 --- a/src/main/java/ch/epfl/biop/registration/sourceandconverter/spline/Elastix2DSplineRegistration.java +++ b/src/main/java/ch/epfl/biop/registration/sourceandconverter/spline/Elastix2DSplineRegistration.java @@ -107,25 +107,29 @@ public boolean register() { .getService(CommandService.class) .run(registrationCommandClass, false, flatParameters.toArray(new Object[0])); + try { + CommandModule module = task.get(); - CommandModule module = task.get(); - - if (module.getOutputs().containsKey("success")) { - success = (boolean) module.getOutput("success"); - } - - if (success) { - rt = (RealTransform) module.getOutput("rt"); - rt = pruneLandMarksOutsideAtlas(rt); - } else { - if (module.getOutputs().containsKey("error")) { - errorMessage = (String) module.getOutput("error"); + if (module.getOutputs().containsKey("success")) { + success = (boolean) module.getOutput("success"); } - } + if (success) { + rt = (RealTransform) module.getOutput("rt"); + rt = pruneLandMarksOutsideAtlas(rt); + } else { + if (module.getOutputs().containsKey("error")) { + errorMessage = (String) module.getOutput("error"); + } + } - isDone = true; - return success; + isDone = true; + return success; + } catch (Exception e) { + isDone = true; + errorMessage = e.getMessage(); + return success; + } } catch (Exception e) { errorMessage = e.getMessage(); e.printStackTrace(); diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DAffineRegisterCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DAffineRegisterCommand.java index 9f4900c5..ffe67c29 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DAffineRegisterCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DAffineRegisterCommand.java @@ -5,11 +5,11 @@ import ch.epfl.biop.wrappers.elastix.RegisterHelper; import ch.epfl.biop.wrappers.elastix.RegistrationParameters; import org.scijava.Context; +import org.scijava.ItemIO; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; /** @@ -31,6 +31,9 @@ public class Elastix2DAffineRegisterCommand extends AbstractElastix2DRegistratio @Parameter Context ctx; + @Parameter(type = ItemIO.OUTPUT) + boolean success; + @Override public void run() { ElastixHelper.checkOrSetLocal(ctx); @@ -69,7 +72,7 @@ public void run() { showImagePlusRegistrationResult); reg.setInterpolate(interpolate); - boolean success = reg.run(); + success = reg.run(); if (success) { at3D = reg.getAffineTransform(); diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DSplineRegisterCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DSplineRegisterCommand.java index 8eea5fed..b920241a 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DSplineRegisterCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/Elastix2DSplineRegisterCommand.java @@ -25,6 +25,9 @@ public class Elastix2DSplineRegisterCommand extends AbstractElastix2DRegistratio @Parameter Context ctx; + @Parameter(type = ItemIO.OUTPUT) + boolean success; + @Override public void run() { ElastixHelper.checkOrSetLocal(ctx); @@ -41,7 +44,7 @@ public void run() { reg.setInterpolate(interpolate); - reg.run(); + success = reg.run(); //registeredSource = reg.getRegisteredSac(); rt = reg.getRealTransform(); diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java index e6d859c5..6440cd85 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java @@ -2,6 +2,7 @@ import ch.epfl.biop.sourceandconverter.register.SIFTRegister; import mpicbg.imagefeatures.FloatArray2DSIFT; +import org.scijava.ItemIO; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import org.slf4j.Logger; @@ -31,6 +32,9 @@ public class Sift2DAffineRegisterCommand extends Abstract2DRegistrationInRectang @Parameter boolean invert_fixed; + @Parameter(type = ItemIO.OUTPUT) + boolean success; + @Override public void run() { @@ -48,7 +52,7 @@ public void run() { reg.setInterpolate(interpolate); - boolean success = reg.run(); + success = reg.run(); if (success) { at3D = reg.getAffineTransform(); diff --git a/src/main/java/ch/epfl/biop/scijava/command/transform/RemoveZOffsetCommand.java b/src/main/java/ch/epfl/biop/scijava/command/transform/RemoveZOffsetCommand.java index 8a6432df..4620cded 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/transform/RemoveZOffsetCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/transform/RemoveZOffsetCommand.java @@ -46,7 +46,7 @@ public void run() { AffineTransform3D at3DCenter = new AffineTransform3D(); at3DCenter.concatenate(at3D.inverse()); - at3DCenter.translate(0, 0,-sz/2); + at3DCenter.translate(0, 0,-sz/2.0); //at3D.set(cx,0,3); //at3D.set(cy,1,3); at3D.set(0,2,3); diff --git a/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java b/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java index df937ccc..8be06f85 100644 --- a/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java +++ b/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java @@ -108,6 +108,9 @@ public boolean run() { ImagePlus croppedMoving = getCroppedImage("Moving", sacs_moving, tpMoving, levelMipmapMoving); ImagePlus croppedFixed = getCroppedImage("Fixed", sacs_fixed, tpFixed, levelMipmapFixed); + croppedFixed.show(); + croppedMoving.show(); + Source sMoving = sacs_moving[0].getSpimSource(); Source sFixed = sacs_fixed[0].getSpimSource(); @@ -166,6 +169,11 @@ public boolean run() { PointMatch.apply( inliers, model ); + if (inliers.size() Date: Sun, 16 Jun 2024 10:38:19 +0200 Subject: [PATCH 07/14] Improves structure of abstract registration command: split between those who require a roi and those who don't. Also adds a way to specify the roi which is macro recordability compatible. Adds a converter from String to RegistrationPair object. --- .../AbstractPairRegistration2DCommand.java | 23 +---- ...bstractPairRegistrationInROI2DCommand.java | 91 +++++++++++++++++++ ...airRegistrationBigWarp2DSplineCommand.java | 2 +- .../PairRegistrationCenterCommand.java | 37 ++++++++ ...airRegistrationElastix2DAffineCommand.java | 4 +- ...airRegistrationElastix2DSplineCommand.java | 4 +- .../PairRegistrationSift2DAffineCommand.java | 4 +- .../StringToRegistrationPairConverter.java | 37 ++++++++ .../register/DemoRegistrationWorkflow.java | 3 +- 9 files changed, 177 insertions(+), 28 deletions(-) create mode 100644 src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistrationInROI2DCommand.java create mode 100644 src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCenterCommand.java create mode 100644 src/main/java/ch/epfl/biop/registration/scijava/converter/StringToRegistrationPairConverter.java diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java index 2688a609..37dccf4c 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java @@ -31,23 +31,11 @@ abstract public class AbstractPairRegistration2DCommand implements Command { @Parameter(label = "Moving image channels used for registration (comma separated)") String channels_moving_csv; - @Parameter(label = "ROI for registration (position x)", style = "format:0.#####E0") - double px; - - @Parameter(label = "ROI for registration (position y)", style = "format:0.#####E0") - double py; - - @Parameter(label = "ROI for registration (size x)", style = "format:0.#####E0") - double sx; - - @Parameter(label = "ROI for registration (size y)", style = "format:0.#####E0") - double sy; - @Parameter(type = ItemIO.OUTPUT) boolean success; @Override - public void run() { + final public void run() { synchronized (registration_pair) { SourceAndConverter[] moving_sources = registration_pair.getMovingSourcesRegistered(); SourceAndConverter[] fixed_sources = registration_pair.getFixedSources(); @@ -108,12 +96,7 @@ public void run() { Map parameters = new HashMap<>(); - parameters.put(Registration.ROI_PX, px); - parameters.put(Registration.ROI_PY, py); - parameters.put(Registration.ROI_SX, sx); - parameters.put(Registration.ROI_SY, sy); - - addRegistrationSpecificParameters(parameters); + addRegistrationParameters(parameters); registration.setRegistrationParameters(convertToString(ctx, parameters)); @@ -136,7 +119,7 @@ public void run() { } - protected abstract void addRegistrationSpecificParameters(Map parameters); + protected abstract void addRegistrationParameters(Map parameters); abstract Registration[]> getRegistration(); diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistrationInROI2DCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistrationInROI2DCommand.java new file mode 100644 index 00000000..856bdb9c --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistrationInROI2DCommand.java @@ -0,0 +1,91 @@ +package ch.epfl.biop.registration.scijava.command; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.Registration; +import net.imglib2.FinalRealInterval; +import net.imglib2.Interval; +import net.imglib2.RealInterval; +import net.imglib2.RealPoint; +import net.imglib2.realtransform.AffineTransform3D; +import org.scijava.plugin.Parameter; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +abstract public class AbstractPairRegistrationInROI2DCommand extends AbstractPairRegistration2DCommand { + + @Parameter(label = "ROI for registration (choose custom in order to use the parameters below)", choices = {"intersection", "union", "custom"}) + String bounds = "intersection"; + + @Parameter(label = "ROI for registration (position x)", style = "format:0.#####E0", required = false) + double px; + + @Parameter(label = "ROI for registration (position y)", style = "format:0.#####E0", required = false) + double py; + + @Parameter(label = "ROI for registration (size x)", style = "format:0.#####E0", required = false) + double sx; + + @Parameter(label = "ROI for registration (size y)", style = "format:0.#####E0", required = false) + double sy; + + final protected void addRegistrationParameters(Map parameters) { + switch (bounds) { + case "intersection": setIntersectionAsROI(); break; + case "union": setUnionAsROI(); break; + case "custom": break; + default: System.err.println("Invalid bounds parameter, choose custom"); + } + + parameters.put(Registration.ROI_PX, px); + parameters.put(Registration.ROI_PY, py); + parameters.put(Registration.ROI_SX, sx); + parameters.put(Registration.ROI_SY, sy); + addRegistrationSpecificParametersExceptRoi(parameters); + } + + abstract protected void addRegistrationSpecificParametersExceptRoi(Map parameters); + + void setIntersectionAsROI() { + if (registration_pair == null) return; + RealInterval boundMoving = getBoundingBox(registration_pair.getMovingSourcesRegistered()); + RealInterval boundFixed = getBoundingBox(registration_pair.getFixedSources()); + px = Math.max(boundMoving.realMin(0), boundFixed.realMin(0)); + py = Math.max(boundMoving.realMin(1), boundFixed.realMin(1)); + + sx = Math.min(boundMoving.realMax(0), boundFixed.realMax(0))-px; + sy = Math.min(boundMoving.realMax(1), boundFixed.realMax(1))-py; + + } + + void setUnionAsROI() { + if (registration_pair == null) return; + RealInterval boundMoving = getBoundingBox(registration_pair.getMovingSourcesRegistered()); + RealInterval boundFixed = getBoundingBox(registration_pair.getFixedSources()); + px = Math.min(boundMoving.realMin(0), boundFixed.realMin(0)); + py = Math.min(boundMoving.realMin(1), boundFixed.realMin(1)); + + sx = Math.max(boundMoving.realMax(0), boundFixed.realMax(0))-px; + sy = Math.max(boundMoving.realMax(1), boundFixed.realMax(1))-py; + if ((sx<0)||(sy<0)) System.err.println("Warning, null intersection!"); + } + + static RealInterval getBoundingBox(SourceAndConverter[] sources) { + List intervalList = Arrays.stream(sources).map((sourceAndConverter) -> { + Interval interval = sourceAndConverter.getSpimSource().getSource(0, 0); + AffineTransform3D sourceTransform = new AffineTransform3D(); + sourceAndConverter.getSpimSource().getSourceTransform(0, 0, sourceTransform); + RealPoint corner0 = new RealPoint(new float[]{(float)interval.min(0), (float)interval.min(1), (float)interval.min(2)}); + RealPoint corner1 = new RealPoint(new float[]{(float)interval.max(0), (float)interval.max(1), (float)interval.max(2)}); + sourceTransform.apply(corner0, corner0); + sourceTransform.apply(corner1, corner1); + return new FinalRealInterval(new double[]{Math.min(corner0.getDoublePosition(0), corner1.getDoublePosition(0)), Math.min(corner0.getDoublePosition(1), corner1.getDoublePosition(1)), Math.min(corner0.getDoublePosition(2), corner1.getDoublePosition(2))}, new double[]{Math.max(corner0.getDoublePosition(0), corner1.getDoublePosition(0)), Math.max(corner0.getDoublePosition(1), corner1.getDoublePosition(1)), Math.max(corner0.getDoublePosition(2), corner1.getDoublePosition(2))}); + }).collect(Collectors.toList()); + RealInterval maxInterval = intervalList.stream().reduce((i1, i2) -> { + return new FinalRealInterval(new double[]{Math.min(i1.realMin(0), i2.realMin(0)), Math.min(i1.realMin(1), i2.realMin(1)), Math.min(i1.realMin(2), i2.realMin(2))}, new double[]{Math.max(i1.realMax(0), i2.realMax(0)), Math.max(i1.realMax(1), i2.realMax(1)), Math.max(i1.realMax(2), i2.realMax(2))}); + }).get(); + return maxInterval; + } +} diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationBigWarp2DSplineCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationBigWarp2DSplineCommand.java index 7ad2ef27..eb6b1919 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationBigWarp2DSplineCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationBigWarp2DSplineCommand.java @@ -18,7 +18,7 @@ public class PairRegistrationBigWarp2DSplineCommand extends AbstractPairRegistra @Override - protected void addRegistrationSpecificParameters(Map parameters) { + protected void addRegistrationParameters(Map parameters) { // Nothing required } diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCenterCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCenterCommand.java new file mode 100644 index 00000000..d9da5374 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCenterCommand.java @@ -0,0 +1,37 @@ +package ch.epfl.biop.registration.scijava.command; + +import bdv.viewer.SourceAndConverter; +import ch.epfl.biop.registration.Registration; +import ch.epfl.biop.registration.RegistrationPair; +import org.scijava.command.Command; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; +import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; + +import java.util.Map; + +@Plugin(type = BdvPlaygroundActionCommand.class, + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair - Center moving on fixed", + description = "Defines a registration pair" ) +public class PairRegistrationCenterCommand extends AbstractPairRegistration2DCommand implements Command { + + + @Parameter + RegistrationPair registration_pair; + + @Override + protected void addRegistrationParameters(Map parameters) { + + } + + @Override + Registration[]> getRegistration() { + return null; + } + + @Override + protected boolean validate() { + return true; + } +} diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DAffineCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DAffineCommand.java index 90b9e98a..cf448518 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DAffineCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DAffineCommand.java @@ -15,7 +15,7 @@ menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair with Elastix - 2D - Affine", description = "Performs a manual registration with BigWarp between two sources." ) -public class PairRegistrationElastix2DAffineCommand extends AbstractPairRegistration2DCommand implements Command { +public class PairRegistrationElastix2DAffineCommand extends AbstractPairRegistrationInROI2DCommand implements Command { @Parameter(label = "Registration re-sampling (micrometers)") double pixel_size_micrometer = 20; @@ -24,7 +24,7 @@ public class PairRegistrationElastix2DAffineCommand extends AbstractPairRegistra boolean show_imageplus_registration_result; @Override - protected void addRegistrationSpecificParameters(Map parameters) { + protected void addRegistrationSpecificParametersExceptRoi(Map parameters) { parameters.put(Registration.RESAMPLING_PX_SIZE, pixel_size_micrometer/1000.0); parameters.put("background_offset_value_moving", 0); parameters.put("background_offset_value_fixed", 0); diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DSplineCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DSplineCommand.java index ae1fb603..78472526 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DSplineCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DSplineCommand.java @@ -15,7 +15,7 @@ menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair with Elastix - 2D - Spline", description = "Performs a manual registration with BigWarp between two sources." ) -public class PairRegistrationElastix2DSplineCommand extends AbstractPairRegistration2DCommand implements Command { +public class PairRegistrationElastix2DSplineCommand extends AbstractPairRegistrationInROI2DCommand implements Command { @Parameter(label = "Number of control points along X, minimum 2.") int nb_control_points_x = 10; @@ -27,7 +27,7 @@ public class PairRegistrationElastix2DSplineCommand extends AbstractPairRegistra boolean show_imageplus_registration_result; @Override - protected void addRegistrationSpecificParameters(Map parameters) { + protected void addRegistrationSpecificParametersExceptRoi(Map parameters) { parameters.put(Registration.RESAMPLING_PX_SIZE, pixel_size_micrometer/1000.0); parameters.put("background_offset_value_moving", 0); parameters.put("background_offset_value_fixed", 0); diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationSift2DAffineCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationSift2DAffineCommand.java index c02fe569..6c97990f 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationSift2DAffineCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationSift2DAffineCommand.java @@ -15,7 +15,7 @@ menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair with Sift - 2D - Affine", description = "Performs a manual registration with BigWarp between two sources." ) -public class PairRegistrationSift2DAffineCommand extends AbstractPairRegistration2DCommand implements Command { +public class PairRegistrationSift2DAffineCommand extends AbstractPairRegistrationInROI2DCommand implements Command { @Parameter(label = "Registration re-sampling (micrometers)") double pixel_size_micrometer = 20; @@ -27,7 +27,7 @@ public class PairRegistrationSift2DAffineCommand extends AbstractPairRegistratio boolean invert_fixed; @Override - protected void addRegistrationSpecificParameters(Map parameters) { + protected void addRegistrationSpecificParametersExceptRoi(Map parameters) { parameters.put(Registration.RESAMPLING_PX_SIZE, pixel_size_micrometer/1000.0); parameters.put("invert_moving", invert_moving); parameters.put("invert_fixed", invert_fixed); diff --git a/src/main/java/ch/epfl/biop/registration/scijava/converter/StringToRegistrationPairConverter.java b/src/main/java/ch/epfl/biop/registration/scijava/converter/StringToRegistrationPairConverter.java new file mode 100644 index 00000000..fe77b842 --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/scijava/converter/StringToRegistrationPairConverter.java @@ -0,0 +1,37 @@ +package ch.epfl.biop.registration.scijava.converter; + +import ch.epfl.biop.registration.RegistrationPair; +import org.scijava.convert.AbstractConverter; +import org.scijava.object.ObjectService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; + +import java.util.Optional; + + +@SuppressWarnings("unused") +@Plugin(type = org.scijava.convert.Converter.class) +public class StringToRegistrationPairConverter extends + AbstractConverter +{ + + @Parameter + ObjectService os; + + @Override + public T convert(Object src, Class dest) { + Optional ans = os.getObjects(RegistrationPair.class).stream().filter( + rp -> (rp.getName().equals(src))).findFirst(); + return (T) ans.orElse(null); + } + + @Override + public Class getOutputType() { + return (Class) RegistrationPair.class; + } + + @Override + public Class getInputType() { + return (Class) String.class; + } +} diff --git a/src/test/java/register/DemoRegistrationWorkflow.java b/src/test/java/register/DemoRegistrationWorkflow.java index 2faad61e..6e0454c9 100644 --- a/src/test/java/register/DemoRegistrationWorkflow.java +++ b/src/test/java/register/DemoRegistrationWorkflow.java @@ -11,8 +11,9 @@ public class DemoRegistrationWorkflow { static public void main(String... args) throws Exception { ij.ui().showUI(); + /*Thread.sleep(5000); ij.command().run(CreateBdvDatasetQuPathCommand.class,true).get(); ij.command().run(PairRegistrationCreateCommand.class, true).get(); - ij.command().run(PairRegistrationSift2DAffineCommand.class, true).get(); + ij.command().run(PairRegistrationSift2DAffineCommand.class, true).get();*/ } } From 4b7f887f1569c3af71c5694d5598d5cb6eaab9af Mon Sep 17 00:00:00 2001 From: Nicolas Chiaruttini Date: Sun, 16 Jun 2024 15:06:34 +0200 Subject: [PATCH 08/14] Asks for roi only in registrations requiring rois. Also, asks for sources preprocessor all the time, for all registrations. --- .../biop/registration/RegistrationPair.java | 8 ++ .../AbstractPairRegistration2DCommand.java | 117 ++++++++---------- ...bstractPairRegistrationInROI2DCommand.java | 13 +- ...airRegistrationBigWarp2DSplineCommand.java | 13 ++ .../PairRegistrationCenterCommand.java | 34 +++-- ...airRegistrationElastix2DAffineCommand.java | 17 +++ ...airRegistrationElastix2DSplineCommand.java | 17 +++ .../PairRegistrationSift2DAffineCommand.java | 17 +++ 8 files changed, 155 insertions(+), 81 deletions(-) diff --git a/src/main/java/ch/epfl/biop/registration/RegistrationPair.java b/src/main/java/ch/epfl/biop/registration/RegistrationPair.java index 3c4aaa60..0c71a271 100644 --- a/src/main/java/ch/epfl/biop/registration/RegistrationPair.java +++ b/src/main/java/ch/epfl/biop/registration/RegistrationPair.java @@ -103,6 +103,14 @@ public synchronized void editLastRegistration() { appendRegistration(lastReg); } + public int getFixedTimepoint() { + return timepointFixed; + } + + public int getMovingTimepoint() { + return timepointMoving; + } + private static class RegistrationAndSources { final Registration[]> reg; diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java index 37dccf4c..73b2ee13 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java @@ -4,6 +4,7 @@ import ch.epfl.biop.registration.Registration; import ch.epfl.biop.registration.RegistrationPair; import ch.epfl.biop.sourceandconverter.processor.SourcesChannelsSelect; +import ch.epfl.biop.sourceandconverter.processor.SourcesProcessor; import org.scijava.Context; import org.scijava.ItemIO; import org.scijava.command.Command; @@ -25,104 +26,83 @@ abstract public class AbstractPairRegistration2DCommand implements Command { @Parameter RegistrationPair registration_pair; - @Parameter(label = "Fixed image channels used for registration (comma separated)") - String channels_fixed_csv; - - @Parameter(label = "Moving image channels used for registration (comma separated)") - String channels_moving_csv; - @Parameter(type = ItemIO.OUTPUT) boolean success; @Override final public void run() { synchronized (registration_pair) { - SourceAndConverter[] moving_sources = registration_pair.getMovingSourcesRegistered(); - SourceAndConverter[] fixed_sources = registration_pair.getFixedSources(); - - List moving_channels; - List fixed_channels; - try { - moving_channels = Arrays.stream(channels_moving_csv.split(",")) - .map(Integer::parseInt) - .collect(Collectors.toList()); - fixed_channels = Arrays.stream(channels_fixed_csv.split(",")) - .map(Integer::parseInt) - .collect(Collectors.toList()); - } catch (NumberFormatException e) { - System.err.println("Number parsing exception " + e.getMessage()); - return; - } + SourceAndConverter[] moving_sources = registration_pair.getMovingSourcesRegistered(); + SourceAndConverter[] fixed_sources = registration_pair.getFixedSources(); - if (moving_channels.isEmpty()) { - System.err.println("Error, you did not specify any channel within the moving image."); - return; - } + Registration[]> registration = getRegistration(); + registration.setScijavaContext(ctx); - if (fixed_channels.isEmpty()) { - System.err.println("Error, you did not specify any channel within the fixed image."); - return; - } + registration.setTimePoint(0); - int maxIndexMoving = Collections.max(moving_channels); - int minIndexMoving = Collections.min(moving_channels); + registration.setMovingImage(getSourcesProcessorMoving().apply(moving_sources)); + registration.setFixedImage(getSourcesProcessorFixed().apply(fixed_sources)); - int maxIndexFixed = Collections.max(fixed_channels); - int minIndexFixed = Collections.min(fixed_channels); + Map parameters = new HashMap<>(); - if ((minIndexMoving < 0) || (minIndexFixed < 0)) { - System.err.println("All channels indices should be positive"); - return; - } + addRegistrationParameters(parameters); - if (!(maxIndexFixed < fixed_sources.length)) { - System.err.println("The max index within the fixed sources (" + maxIndexFixed + ") is above its maximum (" + (fixed_sources.length - 1) + ")"); - return; - } - - if (!(maxIndexMoving < moving_sources.length)) { - System.err.println("The max index within the moving sources (" + maxIndexFixed + ") is above its maximum (" + (moving_sources.length - 1) + ")"); - return; - } + registration.setRegistrationParameters(convertToString(ctx, parameters)); - Registration[]> registration = getRegistration(); - registration.setScijavaContext(ctx); + boolean ok = validate(); - registration.setTimePoint(0); + if (!ok) { + System.err.println("Validation failed."); + return; + } - registration.setMovingImage(new SourcesChannelsSelect(moving_channels).apply(moving_sources)); - registration.setFixedImage(new SourcesChannelsSelect(fixed_channels).apply(fixed_sources)); + success = registration.register(); // Do it! - Map parameters = new HashMap<>(); - - addRegistrationParameters(parameters); + if (success) { + //registered_sources = registration.getTransformedImageMovingToFixed(moving_sources); + registration_pair.appendRegistration(registration); + } else { + System.err.println("Registration unsuccessful: " + registration.getExceptionMessage()); + } + } catch (Exception e) { + System.err.println("Error during registration: "+e.getMessage()); + e.printStackTrace(); + success = false; + } + } - registration.setRegistrationParameters(convertToString(ctx, parameters)); + } - boolean ok = validate(); + protected static SourcesChannelsSelect getChannelProcessorFromCsv(String channelsCsv, int nChannels) throws NumberFormatException, IndexOutOfBoundsException { + List channels = Arrays.stream(channelsCsv.split(",")) + .map(Integer::parseInt) + .collect(Collectors.toList()); - if (!ok) { - System.err.println("Validation failed."); - return; - } + int maxIndex = Collections.max(channels); + int minIndex = Collections.min(channels); - success = registration.register(); // Do it! + if (minIndex < 0) { + System.err.println("All channels indices should be positive"); + throw new IndexOutOfBoundsException(); + } - if (success) { - //registered_sources = registration.getTransformedImageMovingToFixed(moving_sources); - registration_pair.appendRegistration(registration); - } else { - System.err.println("Registration unsuccessful: " + registration.getExceptionMessage()); - } + if (!(maxIndex < nChannels)) { + System.err.println("The max index (" + maxIndex + ") is above its maximum (" + (nChannels) + ")"); + throw new IndexOutOfBoundsException(); } + return new SourcesChannelsSelect(channels); } protected abstract void addRegistrationParameters(Map parameters); abstract Registration[]> getRegistration(); + abstract protected SourcesProcessor getSourcesProcessorFixed(); + + abstract protected SourcesProcessor getSourcesProcessorMoving(); + protected abstract boolean validate(); public static Map convertToString(Context ctx, Map params) { @@ -134,4 +114,5 @@ public static Map convertToString(Context ctx, Map[] sources) { Interval interval = sourceAndConverter.getSpimSource().getSource(0, 0); AffineTransform3D sourceTransform = new AffineTransform3D(); sourceAndConverter.getSpimSource().getSourceTransform(0, 0, sourceTransform); - RealPoint corner0 = new RealPoint(new float[]{(float)interval.min(0), (float)interval.min(1), (float)interval.min(2)}); - RealPoint corner1 = new RealPoint(new float[]{(float)interval.max(0), (float)interval.max(1), (float)interval.max(2)}); + RealPoint corner0 = new RealPoint((float)interval.min(0), (float)interval.min(1), (float)interval.min(2)); + RealPoint corner1 = new RealPoint((float)interval.max(0), (float)interval.max(1), (float)interval.max(2)); sourceTransform.apply(corner0, corner0); sourceTransform.apply(corner1, corner1); return new FinalRealInterval(new double[]{Math.min(corner0.getDoublePosition(0), corner1.getDoublePosition(0)), Math.min(corner0.getDoublePosition(1), corner1.getDoublePosition(1)), Math.min(corner0.getDoublePosition(2), corner1.getDoublePosition(2))}, new double[]{Math.max(corner0.getDoublePosition(0), corner1.getDoublePosition(0)), Math.max(corner0.getDoublePosition(1), corner1.getDoublePosition(1)), Math.max(corner0.getDoublePosition(2), corner1.getDoublePosition(2))}); }).collect(Collectors.toList()); - RealInterval maxInterval = intervalList.stream().reduce((i1, i2) -> { - return new FinalRealInterval(new double[]{Math.min(i1.realMin(0), i2.realMin(0)), Math.min(i1.realMin(1), i2.realMin(1)), Math.min(i1.realMin(2), i2.realMin(2))}, new double[]{Math.max(i1.realMax(0), i2.realMax(0)), Math.max(i1.realMax(1), i2.realMax(1)), Math.max(i1.realMax(2), i2.realMax(2))}); - }).get(); + RealInterval maxInterval = intervalList.stream() + .reduce((i1, i2) -> + new FinalRealInterval( + new double[]{Math.min(i1.realMin(0), i2.realMin(0)), Math.min(i1.realMin(1), i2.realMin(1)), Math.min(i1.realMin(2), i2.realMin(2))}, + new double[]{Math.max(i1.realMax(0), i2.realMax(0)), Math.max(i1.realMax(1), i2.realMax(1)), Math.max(i1.realMax(2), i2.realMax(2))})) + .get(); return maxInterval; } } diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationBigWarp2DSplineCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationBigWarp2DSplineCommand.java index eb6b1919..65584355 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationBigWarp2DSplineCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationBigWarp2DSplineCommand.java @@ -3,6 +3,8 @@ import bdv.viewer.SourceAndConverter; import ch.epfl.biop.registration.Registration; import ch.epfl.biop.registration.sourceandconverter.bigwarp.SacBigWarp2DRegistration; +import ch.epfl.biop.sourceandconverter.processor.SourcesIdentity; +import ch.epfl.biop.sourceandconverter.processor.SourcesProcessor; import org.scijava.command.Command; import org.scijava.plugin.Plugin; import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; @@ -31,4 +33,15 @@ Registration[]> getRegistration() { protected boolean validate() { return true; } + + + @Override + protected SourcesProcessor getSourcesProcessorFixed() { + return new SourcesIdentity(); + } + + @Override + protected SourcesProcessor getSourcesProcessorMoving() { + return new SourcesIdentity(); + } } diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCenterCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCenterCommand.java index d9da5374..d4f8eee4 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCenterCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCenterCommand.java @@ -2,12 +2,16 @@ import bdv.viewer.SourceAndConverter; import ch.epfl.biop.registration.Registration; -import ch.epfl.biop.registration.RegistrationPair; +import ch.epfl.biop.registration.sourceandconverter.affine.AffineRegistration; +import ch.epfl.biop.sourceandconverter.processor.SourcesIdentity; +import ch.epfl.biop.sourceandconverter.processor.SourcesProcessor; +import net.imglib2.RealPoint; +import net.imglib2.realtransform.AffineTransform3D; import org.scijava.command.Command; -import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; +import sc.fiji.bdvpg.sourceandconverter.SourceAndConverterHelper; import java.util.Map; @@ -16,22 +20,36 @@ description = "Defines a registration pair" ) public class PairRegistrationCenterCommand extends AbstractPairRegistration2DCommand implements Command { - - @Parameter - RegistrationPair registration_pair; - @Override protected void addRegistrationParameters(Map parameters) { - + AffineTransform3D affineTransform3D = new AffineTransform3D(); + RealPoint centerFixed = SourceAndConverterHelper.getSourceAndConverterCenterPoint(registration_pair.getFixedSources()[0], registration_pair.getFixedTimepoint()); + RealPoint centerMoving = SourceAndConverterHelper.getSourceAndConverterCenterPoint(registration_pair.getMovingSourcesRegistered()[0], registration_pair.getMovingTimepoint()); + double dx = centerFixed.getDoublePosition(0)-centerMoving.getDoublePosition(0); + double dy = centerFixed.getDoublePosition(1)-centerMoving.getDoublePosition(1); + double dz = centerFixed.getDoublePosition(2)-centerMoving.getDoublePosition(2); + affineTransform3D.translate(dx,dy,dz); + parameters.put("transform", AffineRegistration.affineTransform3DToString(affineTransform3D)); } @Override Registration[]> getRegistration() { - return null; + return new AffineRegistration(); } @Override protected boolean validate() { return true; } + + + @Override + protected SourcesProcessor getSourcesProcessorFixed() { + return new SourcesIdentity(); + } + + @Override + protected SourcesProcessor getSourcesProcessorMoving() { + return new SourcesIdentity(); + } } diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DAffineCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DAffineCommand.java index cf448518..4c1fbde3 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DAffineCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DAffineCommand.java @@ -3,6 +3,7 @@ import bdv.viewer.SourceAndConverter; import ch.epfl.biop.registration.Registration; import ch.epfl.biop.registration.sourceandconverter.affine.Elastix2DAffineRegistration; +import ch.epfl.biop.sourceandconverter.processor.SourcesProcessor; import org.scijava.command.Command; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; @@ -17,6 +18,12 @@ public class PairRegistrationElastix2DAffineCommand extends AbstractPairRegistrationInROI2DCommand implements Command { + @Parameter(label = "Fixed image channels used for registration (comma separated)") + String channels_fixed_csv; + + @Parameter(label = "Moving image channels used for registration (comma separated)") + String channels_moving_csv; + @Parameter(label = "Registration re-sampling (micrometers)") double pixel_size_micrometer = 20; @@ -40,4 +47,14 @@ Registration[]> getRegistration() { protected boolean validate() { return true; } + + @Override + protected SourcesProcessor getSourcesProcessorFixed() { + return AbstractPairRegistration2DCommand.getChannelProcessorFromCsv(channels_fixed_csv, registration_pair.getFixedSources().length); + } + + @Override + protected SourcesProcessor getSourcesProcessorMoving() { + return AbstractPairRegistration2DCommand.getChannelProcessorFromCsv(channels_moving_csv, registration_pair.getMovingSourcesOrigin().length); + } } diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DSplineCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DSplineCommand.java index 78472526..8e548a2c 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DSplineCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DSplineCommand.java @@ -3,6 +3,7 @@ import bdv.viewer.SourceAndConverter; import ch.epfl.biop.registration.Registration; import ch.epfl.biop.registration.sourceandconverter.spline.Elastix2DSplineRegistration; +import ch.epfl.biop.sourceandconverter.processor.SourcesProcessor; import org.scijava.command.Command; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; @@ -20,6 +21,12 @@ public class PairRegistrationElastix2DSplineCommand extends AbstractPairRegistra @Parameter(label = "Number of control points along X, minimum 2.") int nb_control_points_x = 10; + @Parameter(label = "Fixed image channels used for registration (comma separated)") + String channels_fixed_csv; + + @Parameter(label = "Moving image channels used for registration (comma separated)") + String channels_moving_csv; + @Parameter(label = "Registration re-sampling (micrometers)") double pixel_size_micrometer = 20; @@ -44,4 +51,14 @@ Registration[]> getRegistration() { protected boolean validate() { return true; } + + @Override + protected SourcesProcessor getSourcesProcessorFixed() { + return AbstractPairRegistration2DCommand.getChannelProcessorFromCsv(channels_fixed_csv, registration_pair.getFixedSources().length); + } + + @Override + protected SourcesProcessor getSourcesProcessorMoving() { + return AbstractPairRegistration2DCommand.getChannelProcessorFromCsv(channels_moving_csv, registration_pair.getMovingSourcesOrigin().length); + } } diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationSift2DAffineCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationSift2DAffineCommand.java index 6c97990f..60306857 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationSift2DAffineCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationSift2DAffineCommand.java @@ -3,6 +3,7 @@ import bdv.viewer.SourceAndConverter; import ch.epfl.biop.registration.Registration; import ch.epfl.biop.registration.sourceandconverter.affine.Sift2DAffineRegistration; +import ch.epfl.biop.sourceandconverter.processor.SourcesProcessor; import org.scijava.command.Command; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; @@ -17,6 +18,12 @@ public class PairRegistrationSift2DAffineCommand extends AbstractPairRegistrationInROI2DCommand implements Command { + @Parameter(label = "Fixed image channels used for registration (comma separated)") + String channels_fixed_csv; + + @Parameter(label = "Moving image channels used for registration (comma separated)") + String channels_moving_csv; + @Parameter(label = "Registration re-sampling (micrometers)") double pixel_size_micrometer = 20; @@ -42,4 +49,14 @@ Registration[]> getRegistration() { protected boolean validate() { return true; } + + @Override + protected SourcesProcessor getSourcesProcessorFixed() { + return AbstractPairRegistration2DCommand.getChannelProcessorFromCsv(channels_fixed_csv, registration_pair.getFixedSources().length); + } + + @Override + protected SourcesProcessor getSourcesProcessorMoving() { + return AbstractPairRegistration2DCommand.getChannelProcessorFromCsv(channels_moving_csv, registration_pair.getMovingSourcesOrigin().length); + } } From 787d14fb78b105f404a6e46894ec0abf0ecf2a8d Mon Sep 17 00:00:00 2001 From: Nicolas Chiaruttini Date: Sun, 16 Jun 2024 17:02:10 +0200 Subject: [PATCH 09/14] Improves structure of code: - separate commands from registration sequence - store sources processor in RegistrationPair object - changes hardcoded String by final static public key String --- .../biop/registration/RegistrationPair.java | 91 ++++++++++++++----- .../AbstractPairRegistration2DCommand.java | 68 ++++++-------- ...bstractPairRegistrationInROI2DCommand.java | 2 +- ...airRegistrationBigWarp2DSplineCommand.java | 3 +- .../PairRegistrationCenterCommand.java | 7 +- .../PairRegistrationCreateCommand.java | 9 ++ .../PairRegistrationDeleteCommand.java | 2 - ...gistrationEditLastRegistrationCommand.java | 2 +- ...airRegistrationElastix2DAffineCommand.java | 2 +- ...airRegistrationElastix2DSplineCommand.java | 2 +- ...strationRemoveLastRegistrationCommand.java | 5 +- .../PairRegistrationSift2DAffineCommand.java | 6 +- .../affine/AffineRegistration.java | 4 +- .../affine/Sift2DAffineRegistration.java | 7 +- 14 files changed, 125 insertions(+), 85 deletions(-) diff --git a/src/main/java/ch/epfl/biop/registration/RegistrationPair.java b/src/main/java/ch/epfl/biop/registration/RegistrationPair.java index 0c71a271..4be96497 100644 --- a/src/main/java/ch/epfl/biop/registration/RegistrationPair.java +++ b/src/main/java/ch/epfl/biop/registration/RegistrationPair.java @@ -3,12 +3,14 @@ import bdv.viewer.SourceAndConverter; import ch.epfl.biop.registration.plugin.RegistrationPluginHelper; import ch.epfl.biop.sourceandconverter.processor.SourcesAffineTransformer; +import ch.epfl.biop.sourceandconverter.processor.SourcesProcessor; import net.imglib2.realtransform.AffineTransform3D; import org.scijava.Named; import sc.fiji.bdvpg.services.SourceAndConverterServices; import java.util.ArrayList; import java.util.List; +import java.util.Map; public class RegistrationPair implements Named { @@ -20,14 +22,16 @@ public class RegistrationPair implements Named { final String name; SourceAndConverter[] movingSourcesRegistered; - final List registrationAndSources = new ArrayList<>(); + final List registrationPairSteps = new ArrayList<>(); public RegistrationPair(SourceAndConverter[] fixedSources, int timepointFixed, SourceAndConverter[] movingSources, int timepointMoving, - String name, boolean removezOffset + String name, + boolean removezOffset ) { + if (removezOffset) { this.fixedSources = new SourcesAffineTransformer(findZ0Transform(fixedSources[0], timepointFixed)).apply(fixedSources); this.movingSourcesOrigin = new SourcesAffineTransformer(findZ0Transform(movingSources[0], timepointMoving)).apply(movingSources); @@ -55,11 +59,45 @@ public synchronized SourceAndConverter[] getMovingSourcesRegistered() { return movingSourcesRegistered; } - public synchronized void appendRegistration(Registration[]> reg) { - RegistrationAndSources ras = new RegistrationAndSources(reg, reg.getTransformedImageMovingToFixed(getMovingSourcesRegistered())); - movingSourcesRegistered = ras.sacs; - SourceAndConverterServices.getSourceAndConverterService().register(ras.sacs[0]); - registrationAndSources.add(ras); + String errorMessage = ""; + + public String getRegistrationErrorMessage() { + return errorMessage; + } + + public synchronized boolean executeRegistration(Registration[]> reg, + Map parameters, + SourcesProcessor fixedProcessorForRegistration, + SourcesProcessor movingProcessorForRegistration) { + reg.setRegistrationParameters(parameters); + reg.setMovingImage(movingProcessorForRegistration.apply(getMovingSourcesRegistered())); + reg.setFixedImage(fixedProcessorForRegistration.apply(getFixedSources())); + + boolean success = reg.register(); + + if (!success) { + errorMessage = reg.getExceptionMessage(); + return false; + } + + appendRegistration(reg, fixedProcessorForRegistration, movingProcessorForRegistration); + + return true; + } + + private void appendRegistration(Registration[]> reg, + SourcesProcessor fixedProcessorForRegistration, + SourcesProcessor movingProcessorForRegistration) { + + movingSourcesRegistered = reg.getTransformedImageMovingToFixed(getMovingSourcesRegistered()); + + RegistrationStep rp = new RegistrationStep( + reg, + getMovingSourcesRegistered(), + fixedProcessorForRegistration, movingProcessorForRegistration); + + SourceAndConverterServices.getSourceAndConverterService().register(rp.sacs[0]); + registrationPairSteps.add(rp); } @Override @@ -69,38 +107,36 @@ public String getName() { @Override public void setName(String name) { - throw new UnsupportedOperationException("You can't rename a registration pair object"); + throw new UnsupportedOperationException("You can't rename a registration pair sequence object"); } public synchronized void removeLastRegistration() { - if (registrationAndSources.size() == 0) return; - if (registrationAndSources.size()==1) { - registrationAndSources.remove(0); + if (registrationPairSteps.isEmpty()) return; + if (registrationPairSteps.size()==1) { + registrationPairSteps.remove(0); this.movingSourcesRegistered = movingSourcesOrigin; } else { - RegistrationAndSources ras = registrationAndSources.get(registrationAndSources.size()-2); - registrationAndSources.remove(registrationAndSources.size()-1); - this.movingSourcesRegistered = ras.sacs; + RegistrationStep rs = registrationPairSteps.get(registrationPairSteps.size()-2); + registrationPairSteps.remove(registrationPairSteps.size()-1); + this.movingSourcesRegistered = rs.sacs; } } public synchronized void editLastRegistration() { - if (registrationAndSources.size() == 0) { + if (registrationPairSteps.isEmpty()) { System.err.println("There is no registration to edit"); return; } - Registration lastReg = registrationAndSources.get(registrationAndSources.size()-1).reg; - if (!RegistrationPluginHelper.isEditable(lastReg)) { + RegistrationStep lastStep = registrationPairSteps.get(registrationPairSteps.size()-1); + if (!RegistrationPluginHelper.isEditable(lastStep.reg)) { System.err.println("The last registration is not editable"); return; } removeLastRegistration(); - - lastReg.edit(); - - appendRegistration(lastReg); + lastStep.reg.edit(); + appendRegistration(lastStep.reg, lastStep.fixedProcessor, lastStep.movingProcessor); } public int getFixedTimepoint() { @@ -111,20 +147,27 @@ public int getMovingTimepoint() { return timepointMoving; } - private static class RegistrationAndSources { + private static class RegistrationStep { final Registration[]> reg; final SourceAndConverter[] sacs; + final SourcesProcessor fixedProcessor; + final SourcesProcessor movingProcessor; - public RegistrationAndSources(Registration[]> reg, SourceAndConverter[] sacs) { + public RegistrationStep(Registration[]> reg, + SourceAndConverter[] sacs, + SourcesProcessor fixedProcessor, + SourcesProcessor movingProcessor) { this.reg = reg; this.sacs = sacs; + this.fixedProcessor = fixedProcessor; + this.movingProcessor = movingProcessor; } } @Override public String toString() { - return name+" [#f="+fixedSources.length+" #m="+movingSourcesOrigin.length+" #regs="+registrationAndSources.size()+"]"; + return name;//+" [#f="+fixedSources.length+" #m="+movingSourcesOrigin.length+" #regs="+registrationAndSources.size()+"]"; } private static AffineTransform3D findZ0Transform(SourceAndConverter source, int timePoint) { diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java index 73b2ee13..6a013244 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java @@ -27,29 +27,18 @@ abstract public class AbstractPairRegistration2DCommand implements Command { RegistrationPair registration_pair; @Parameter(type = ItemIO.OUTPUT) - boolean success; + boolean success = false; @Override final public void run() { synchronized (registration_pair) { try { - SourceAndConverter[] moving_sources = registration_pair.getMovingSourcesRegistered(); - SourceAndConverter[] fixed_sources = registration_pair.getFixedSources(); - + Map parameters = new HashMap<>(); Registration[]> registration = getRegistration(); registration.setScijavaContext(ctx); - registration.setTimePoint(0); - - registration.setMovingImage(getSourcesProcessorMoving().apply(moving_sources)); - registration.setFixedImage(getSourcesProcessorFixed().apply(fixed_sources)); - - Map parameters = new HashMap<>(); - addRegistrationParameters(parameters); - registration.setRegistrationParameters(convertToString(ctx, parameters)); - boolean ok = validate(); if (!ok) { @@ -57,13 +46,14 @@ final public void run() { return; } - success = registration.register(); // Do it! + success = registration_pair + .executeRegistration(registration, + convertToString(ctx, parameters), + getSourcesProcessorFixed(), + getSourcesProcessorMoving()); - if (success) { - //registered_sources = registration.getTransformedImageMovingToFixed(moving_sources); - registration_pair.appendRegistration(registration); - } else { - System.err.println("Registration unsuccessful: " + registration.getExceptionMessage()); + if (!success) { + System.err.println("Registration unsuccessful: " + registration_pair.getRegistrationErrorMessage()); } } catch (Exception e) { System.err.println("Error during registration: "+e.getMessage()); @@ -74,6 +64,26 @@ final public void run() { } + abstract protected void addRegistrationParameters(Map parameters); + + abstract Registration[]> getRegistration(); + + abstract protected SourcesProcessor getSourcesProcessorFixed(); + + abstract protected SourcesProcessor getSourcesProcessorMoving(); + + abstract protected boolean validate(); + + public static Map convertToString(Context ctx, Map params) { + Map convertedParams = new HashMap<>(); + + ConvertService cs = ctx.getService(ConvertService.class); + + params.keySet().forEach(k -> convertedParams.put(k, cs.convert(params.get(k), String.class))); + + return convertedParams; + } + protected static SourcesChannelsSelect getChannelProcessorFromCsv(String channelsCsv, int nChannels) throws NumberFormatException, IndexOutOfBoundsException { List channels = Arrays.stream(channelsCsv.split(",")) .map(Integer::parseInt) @@ -95,24 +105,4 @@ protected static SourcesChannelsSelect getChannelProcessorFromCsv(String channel return new SourcesChannelsSelect(channels); } - protected abstract void addRegistrationParameters(Map parameters); - - abstract Registration[]> getRegistration(); - - abstract protected SourcesProcessor getSourcesProcessorFixed(); - - abstract protected SourcesProcessor getSourcesProcessorMoving(); - - protected abstract boolean validate(); - - public static Map convertToString(Context ctx, Map params) { - Map convertedParams = new HashMap<>(); - - ConvertService cs = ctx.getService(ConvertService.class); - - params.keySet().forEach(k -> convertedParams.put(k, cs.convert(params.get(k), String.class))); - - return convertedParams; - } - } diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistrationInROI2DCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistrationInROI2DCommand.java index 356631c0..25754360 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistrationInROI2DCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistrationInROI2DCommand.java @@ -16,7 +16,7 @@ abstract public class AbstractPairRegistrationInROI2DCommand extends AbstractPairRegistration2DCommand { - @Parameter(label = "ROI for registration (choose custom in order to use the parameters below)", choices = {"intersection", "union", "custom"}) + @Parameter(label = "ROI for registration (select custom in order to use the parameters below)", choices = {"intersection", "union", "custom"}) String bounds = "intersection"; @Parameter(label = "ROI for registration (position x)", style = "format:0.#####E0", required = false) diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationBigWarp2DSplineCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationBigWarp2DSplineCommand.java index 65584355..0949e5f2 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationBigWarp2DSplineCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationBigWarp2DSplineCommand.java @@ -13,12 +13,11 @@ import java.util.Map; @Plugin(type = BdvPlaygroundActionCommand.class, - menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair with BigWarp - 2D - Spline", + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair 2D - BigWarp Spline", description = "Performs a manual registration with BigWarp between two sources." ) public class PairRegistrationBigWarp2DSplineCommand extends AbstractPairRegistration2DCommand implements Command { - @Override protected void addRegistrationParameters(Map parameters) { // Nothing required diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCenterCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCenterCommand.java index d4f8eee4..41bce568 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCenterCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCenterCommand.java @@ -16,8 +16,8 @@ import java.util.Map; @Plugin(type = BdvPlaygroundActionCommand.class, - menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair - Center moving on fixed", - description = "Defines a registration pair" ) + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair - Center moving sources on fixed sources", + description = "Registration that centers moving sources over fixed sources." ) public class PairRegistrationCenterCommand extends AbstractPairRegistration2DCommand implements Command { @Override @@ -29,7 +29,7 @@ protected void addRegistrationParameters(Map parameters) { double dy = centerFixed.getDoublePosition(1)-centerMoving.getDoublePosition(1); double dz = centerFixed.getDoublePosition(2)-centerMoving.getDoublePosition(2); affineTransform3D.translate(dx,dy,dz); - parameters.put("transform", AffineRegistration.affineTransform3DToString(affineTransform3D)); + parameters.put(AffineRegistration.TRANSFORM_KEY, AffineRegistration.affineTransform3DToString(affineTransform3D)); } @Override @@ -42,7 +42,6 @@ protected boolean validate() { return true; } - @Override protected SourcesProcessor getSourcesProcessorFixed() { return new SourcesIdentity(); diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCreateCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCreateCommand.java index 0e3c000d..fba9ae7c 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCreateCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCreateCommand.java @@ -10,6 +10,8 @@ import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; +import java.util.List; + @Plugin(type = BdvPlaygroundActionCommand.class, menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Create registration pair", description = "Defines a registration pair" ) @@ -32,6 +34,13 @@ public class PairRegistrationCreateCommand implements Command { @Override public void run() { + List allRegistrations = objectService.getObjects(RegistrationPair.class); + + if (allRegistrations.stream().anyMatch(rps -> rps.getName().equals(registration_name))) { + System.err.println("A registration sequence named "+registration_name+" already exists! Please name it differently or delete the existing one."); + return; + } + registration_pair = new RegistrationPair(fixed_sources,0,moving_sources,0, registration_name, true); objectService.addObject(registration_pair); } diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationDeleteCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationDeleteCommand.java index d5b0cb7d..7b5243a2 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationDeleteCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationDeleteCommand.java @@ -1,8 +1,6 @@ package ch.epfl.biop.registration.scijava.command; -import bdv.viewer.SourceAndConverter; import ch.epfl.biop.registration.RegistrationPair; -import org.scijava.ItemIO; import org.scijava.command.Command; import org.scijava.object.ObjectService; import org.scijava.plugin.Parameter; diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationEditLastRegistrationCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationEditLastRegistrationCommand.java index 00983f9e..bbb7888a 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationEditLastRegistrationCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationEditLastRegistrationCommand.java @@ -8,7 +8,7 @@ import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; @Plugin(type = BdvPlaygroundActionCommand.class, - menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Registration pair - edit last registration", + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair - Edit last registration", description = "Edit the last registration of a registration pair" ) public class PairRegistrationEditLastRegistrationCommand implements Command { diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DAffineCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DAffineCommand.java index 4c1fbde3..a157d1f9 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DAffineCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DAffineCommand.java @@ -13,7 +13,7 @@ import java.util.Map; @Plugin(type = BdvPlaygroundActionCommand.class, - menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair with Elastix - 2D - Affine", + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair 2D - Elastix Affine", description = "Performs a manual registration with BigWarp between two sources." ) public class PairRegistrationElastix2DAffineCommand extends AbstractPairRegistrationInROI2DCommand implements Command { diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DSplineCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DSplineCommand.java index 8e548a2c..95fa56c8 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DSplineCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationElastix2DSplineCommand.java @@ -13,7 +13,7 @@ import java.util.Map; @Plugin(type = BdvPlaygroundActionCommand.class, - menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair with Elastix - 2D - Spline", + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair 2D - Elastix Spline", description = "Performs a manual registration with BigWarp between two sources." ) public class PairRegistrationElastix2DSplineCommand extends AbstractPairRegistrationInROI2DCommand implements Command { diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationRemoveLastRegistrationCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationRemoveLastRegistrationCommand.java index 41d44a00..1b268d0f 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationRemoveLastRegistrationCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationRemoveLastRegistrationCommand.java @@ -1,17 +1,14 @@ package ch.epfl.biop.registration.scijava.command; -import bdv.viewer.SourceAndConverter; import ch.epfl.biop.registration.RegistrationPair; -import org.scijava.ItemIO; import org.scijava.command.Command; -import org.scijava.object.ObjectService; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; @Plugin(type = BdvPlaygroundActionCommand.class, - menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Registration pair - remove last registration", + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair - Remove last registration", description = "Defines a registration pair" ) public class PairRegistrationRemoveLastRegistrationCommand implements Command { diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationSift2DAffineCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationSift2DAffineCommand.java index 60306857..417be146 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationSift2DAffineCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationSift2DAffineCommand.java @@ -13,7 +13,7 @@ import java.util.Map; @Plugin(type = BdvPlaygroundActionCommand.class, - menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair with Sift - 2D - Affine", + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair 2D - Sift Affine", description = "Performs a manual registration with BigWarp between two sources." ) public class PairRegistrationSift2DAffineCommand extends AbstractPairRegistrationInROI2DCommand implements Command { @@ -36,8 +36,8 @@ public class PairRegistrationSift2DAffineCommand extends AbstractPairRegistratio @Override protected void addRegistrationSpecificParametersExceptRoi(Map parameters) { parameters.put(Registration.RESAMPLING_PX_SIZE, pixel_size_micrometer/1000.0); - parameters.put("invert_moving", invert_moving); - parameters.put("invert_fixed", invert_fixed); + parameters.put(Sift2DAffineRegistration.INVERT_MOVING_KEY, invert_moving); + parameters.put(Sift2DAffineRegistration.INVERT_FIXED_KEY, invert_fixed); } @Override diff --git a/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/AffineRegistration.java b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/AffineRegistration.java index f7cae925..8ce5cd00 100644 --- a/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/AffineRegistration.java +++ b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/AffineRegistration.java @@ -22,6 +22,8 @@ public class AffineRegistration extends AffineTransformSourceAndConverterRegistr protected static Logger logger = LoggerFactory.getLogger(AffineRegistration.class); + public final static String TRANSFORM_KEY = "transform"; + @Override public void setFixedImage(SourceAndConverter[] fimg) { super.setFixedImage(fimg); @@ -45,7 +47,7 @@ public static AffineTransform3D stringToAffineTransform3D(String string) { @Override public boolean register() { - at3d = stringToAffineTransform3D(parameters.get("transform")); + at3d = stringToAffineTransform3D(parameters.get(TRANSFORM_KEY)); isDone = true; return true; } diff --git a/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/Sift2DAffineRegistration.java b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/Sift2DAffineRegistration.java index 722ffe45..e9770c40 100644 --- a/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/Sift2DAffineRegistration.java +++ b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/Sift2DAffineRegistration.java @@ -31,6 +31,9 @@ public class Sift2DAffineRegistration extends AffineTransformSourceAndConverterR protected static final Logger logger = LoggerFactory.getLogger(Sift2DAffineRegistration.class); + public final static String INVERT_MOVING_KEY = "invert_moving"; + public final static String INVERT_FIXED_KEY = "invert_fixed"; + @Override public void setFixedImage(SourceAndConverter[] fimg) { if (fimg.length==0) { @@ -70,8 +73,8 @@ public boolean register() { List flatParameters = new ArrayList<>(parameters.size()*2+4); double voxSizeInMm = Double.parseDouble(parameters.get("pxSizeInCurrentUnit")); - boolean invert_moving = Boolean.parseBoolean(parameters.get("invert_moving")); - boolean invert_fixed = Boolean.parseBoolean(parameters.get("invert_fixed")); + boolean invert_moving = Boolean.parseBoolean(parameters.get(INVERT_MOVING_KEY)); + boolean invert_fixed = Boolean.parseBoolean(parameters.get(INVERT_FIXED_KEY)); parameters.keySet().forEach(k -> { flatParameters.add(k); From 3837c533ed95a15fe97997b2d40eef4b223ed926 Mon Sep 17 00:00:00 2001 From: Nicolas Chiaruttini Date: Sun, 16 Jun 2024 17:36:20 +0200 Subject: [PATCH 10/14] WIP - prepare for the registration export to the QuPath project --- .../biop/registration/RegistrationPair.java | 86 ++++++++++++++++++- .../AbstractPairRegistration2DCommand.java | 2 +- ...PairRegistrationExportToQuPathCommand.java | 25 ++++++ 3 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationExportToQuPathCommand.java diff --git a/src/main/java/ch/epfl/biop/registration/RegistrationPair.java b/src/main/java/ch/epfl/biop/registration/RegistrationPair.java index 4be96497..a392fa78 100644 --- a/src/main/java/ch/epfl/biop/registration/RegistrationPair.java +++ b/src/main/java/ch/epfl/biop/registration/RegistrationPair.java @@ -1,5 +1,6 @@ package ch.epfl.biop.registration; +import bdv.util.QuPathBdvHelper; import bdv.viewer.SourceAndConverter; import ch.epfl.biop.registration.plugin.RegistrationPluginHelper; import ch.epfl.biop.sourceandconverter.processor.SourcesAffineTransformer; @@ -8,6 +9,7 @@ import org.scijava.Named; import sc.fiji.bdvpg.services.SourceAndConverterServices; +import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -61,7 +63,7 @@ public synchronized SourceAndConverter[] getMovingSourcesRegistered() { String errorMessage = ""; - public String getRegistrationErrorMessage() { + public String getLastErrorMessage() { return errorMessage; } @@ -147,6 +149,88 @@ public int getMovingTimepoint() { return timepointMoving; } + public boolean checkQuPathCompatibility() { + // A few checks are necessary in order to know if this registration pair is compatible with an export to QuPath + + // Is the first fixed source belonging to a QuPath project -> Strict requirement + if (!QuPathBdvHelper.isSourceLinkedToQuPath(fixedSources[0])) { + errorMessage = "The first fixed source is not linked to a QuPath project"; + return false; + } + + // Is the first moving source belonging to a QuPath project -> Strict requirement + if (!QuPathBdvHelper.isSourceLinkedToQuPath(movingSourcesOrigin[0])) { + errorMessage = "The first moving source is not linked to a QuPath project"; + return false; + } + + // Do they belong to the same QuPath project ? -> Strict requirement + File quPathProject = QuPathBdvHelper.getProjectFile(fixedSources[0]); + + if (!quPathProject.equals(QuPathBdvHelper.getProjectFile(movingSourcesOrigin[0]))) { + errorMessage = "Moving and fixed sources do not belong to the same QuPath project."; + return false; + } + + // Do moving and fixed sources belong to different entries ? -> Strict requirement + int entryIdFixed = QuPathBdvHelper.getEntryId(fixedSources[0]); + int entryIdMoving = QuPathBdvHelper.getEntryId(movingSourcesOrigin[0]); + + if (entryIdFixed==entryIdMoving) { + errorMessage = "The first moving source and the first fixed source belong to the same QuPath entry"; + return false; + } + + // Are all moving sources belonging to a QuPath entry ? -> Warning + for (int i = 1; i Warning + for (int i = 1; i[]> reg; diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java index 6a013244..ced3ebe1 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/AbstractPairRegistration2DCommand.java @@ -53,7 +53,7 @@ final public void run() { getSourcesProcessorMoving()); if (!success) { - System.err.println("Registration unsuccessful: " + registration_pair.getRegistrationErrorMessage()); + System.err.println("Registration unsuccessful: " + registration_pair.getLastErrorMessage()); } } catch (Exception e) { System.err.println("Error during registration: "+e.getMessage()); diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationExportToQuPathCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationExportToQuPathCommand.java new file mode 100644 index 00000000..fd71a49d --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationExportToQuPathCommand.java @@ -0,0 +1,25 @@ +package ch.epfl.biop.registration.scijava.command; + +import ch.epfl.biop.registration.RegistrationPair; +import org.scijava.command.Command; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; +import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; + +@Plugin(type = BdvPlaygroundActionCommand.class, + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Register Pair - Export registration to QuPath project", + description = "If properly defined, exports the registration to the QuPath project" ) +public class PairRegistrationExportToQuPathCommand implements Command { + + @Parameter + RegistrationPair registration_pair; + + @Parameter + boolean allow_overwrite = true; + + @Override + public void run() { + registration_pair.exportToQuPath(allow_overwrite); + } +} From 3775dacb9384e02b7b660040f92f5f152e240ea1 Mon Sep 17 00:00:00 2001 From: Nicolas Chiaruttini Date: Sun, 16 Jun 2024 19:33:00 +0200 Subject: [PATCH 11/14] Export to QuPath is working --- .../biop/registration/RegistrationPair.java | 66 ++++++++++++++++++- ...PairRegistrationExportToQuPathCommand.java | 6 +- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/epfl/biop/registration/RegistrationPair.java b/src/main/java/ch/epfl/biop/registration/RegistrationPair.java index a392fa78..f7f57e7a 100644 --- a/src/main/java/ch/epfl/biop/registration/RegistrationPair.java +++ b/src/main/java/ch/epfl/biop/registration/RegistrationPair.java @@ -6,10 +6,18 @@ import ch.epfl.biop.sourceandconverter.processor.SourcesAffineTransformer; import ch.epfl.biop.sourceandconverter.processor.SourcesProcessor; import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.realtransform.InvertibleRealTransform; +import net.imglib2.realtransform.InvertibleRealTransformSequence; +import net.imglib2.realtransform.RealTransform; +import org.apache.commons.io.FileUtils; +import org.scijava.Context; import org.scijava.Named; import sc.fiji.bdvpg.services.SourceAndConverterServices; +import sc.fiji.persist.ScijavaGsonHelper; import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -224,11 +232,67 @@ public boolean checkQuPathCompatibility() { return true; } - public synchronized boolean exportToQuPath(boolean allowOverwrite) { + public synchronized boolean exportToQuPath(boolean allowOverwrite, Context scijavaCtx) { boolean result = checkQuPathCompatibility(); if (!result) return false; + + SourceAndConverter moving_source = movingSourcesOrigin[0]; + SourceAndConverter fixed_source = fixedSources[0]; // Is there already a registration ? Can I erase it ? // All right, now it is the + + // Because QuPath works in pixel coordinates and bdv playground in real space coordinates + // We need to account for this + + AffineTransform3D movingToPixel = new AffineTransform3D(); + + moving_source.getSpimSource().getSourceTransform(0,0,movingToPixel); + + AffineTransform3D fixedToPixel = new AffineTransform3D(); + + fixed_source.getSpimSource().getSourceTransform(0,0,fixedToPixel); + + InvertibleRealTransformSequence rt = new InvertibleRealTransformSequence(); + for (RegistrationStep rp: registrationPairSteps) { + RealTransform rt_temp = rp.reg.getTransformAsRealTransform(); + if (rt_temp instanceof InvertibleRealTransform) { + rt.add((InvertibleRealTransform) rt_temp); + } else { + errorMessage = "A transformation within the sequence is not invertible!"; + return false; + } + } + + InvertibleRealTransformSequence irts = new InvertibleRealTransformSequence(); + + irts.add(fixedToPixel); + irts.add(rt); + irts.add(movingToPixel.inverse()); + + String jsonMovingToFixed = ScijavaGsonHelper.getGson(scijavaCtx).toJson(irts, RealTransform.class); + + int moving_series_entry_id = QuPathBdvHelper.getEntryId(moving_source); + int fixed_series_entry_id = QuPathBdvHelper.getEntryId(fixed_source); + + String movingToFixedLandmarkName = "transform_"+moving_series_entry_id+"_"+fixed_series_entry_id+".json"; + + File moving_entry_folder = QuPathBdvHelper.getDataEntryFolder(movingSourcesOrigin[0]); + + File resultFile = new File(moving_entry_folder.getAbsolutePath(), movingToFixedLandmarkName); + if (resultFile.exists() && (allowOverwrite == false)) { + errorMessage = "The registration file already exists, overwrite not allowed."; + return false; + } + try { + FileUtils.writeStringToFile(resultFile, jsonMovingToFixed, Charset.defaultCharset()); + } catch (IOException e) { + errorMessage = e.getMessage(); + return false; + } + + System.out.println("Fixed: "+fixed_source.getSpimSource().getName()+" | Moving: "+moving_source.getSpimSource().getName()); + System.out.println("Transformation file successfully written to QuPath project: "+result); + return true; } private static class RegistrationStep { diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationExportToQuPathCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationExportToQuPathCommand.java index fd71a49d..6e8dd137 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationExportToQuPathCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationExportToQuPathCommand.java @@ -1,6 +1,7 @@ package ch.epfl.biop.registration.scijava.command; import ch.epfl.biop.registration.RegistrationPair; +import org.scijava.Context; import org.scijava.command.Command; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; @@ -15,11 +16,14 @@ public class PairRegistrationExportToQuPathCommand implements Command { @Parameter RegistrationPair registration_pair; + @Parameter + Context ctx; + @Parameter boolean allow_overwrite = true; @Override public void run() { - registration_pair.exportToQuPath(allow_overwrite); + registration_pair.exportToQuPath(allow_overwrite, ctx); } } From 249e20fbd6aaddc8b62779540aac06693b071592 Mon Sep 17 00:00:00 2001 From: Nicolas Chiaruttini Date: Mon, 17 Jun 2024 00:15:11 +0200 Subject: [PATCH 12/14] Fix QuPath export Adds working GUI Increases max size for SIFT --- .../biop/registration/RegistrationPair.java | 45 +++- .../PairRegistrationAddGUICommand.java | 224 ++++++++++++++++++ .../PairRegistrationCreateCommand.java | 2 + .../PairRegistrationDeleteCommand.java | 7 + .../register/Sift2DAffineRegisterCommand.java | 4 +- .../register/SIFTRegister.java | 3 - 6 files changed, 277 insertions(+), 8 deletions(-) create mode 100644 src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationAddGUICommand.java diff --git a/src/main/java/ch/epfl/biop/registration/RegistrationPair.java b/src/main/java/ch/epfl/biop/registration/RegistrationPair.java index f7f57e7a..0088c464 100644 --- a/src/main/java/ch/epfl/biop/registration/RegistrationPair.java +++ b/src/main/java/ch/epfl/biop/registration/RegistrationPair.java @@ -12,9 +12,9 @@ import org.apache.commons.io.FileUtils; import org.scijava.Context; import org.scijava.Named; -import sc.fiji.bdvpg.services.SourceAndConverterServices; import sc.fiji.persist.ScijavaGsonHelper; +import java.io.Closeable; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; @@ -22,7 +22,7 @@ import java.util.List; import java.util.Map; -public class RegistrationPair implements Named { +public class RegistrationPair implements Named, Closeable { final SourceAndConverter[] movingSourcesOrigin; final SourceAndConverter[] fixedSources; @@ -106,8 +106,8 @@ private void appendRegistration(Registration[]> reg, getMovingSourcesRegistered(), fixedProcessorForRegistration, movingProcessorForRegistration); - SourceAndConverterServices.getSourceAndConverterService().register(rp.sacs[0]); registrationPairSteps.add(rp); + listeners.forEach(listener -> listener.newEvent(RegistrationEvents.STEP_ADDED)); } @Override @@ -130,6 +130,7 @@ public synchronized void removeLastRegistration() { registrationPairSteps.remove(registrationPairSteps.size()-1); this.movingSourcesRegistered = rs.sacs; } + listeners.forEach(listener -> listener.newEvent(RegistrationEvents.STEP_REMOVED)); } public synchronized void editLastRegistration() { @@ -253,7 +254,8 @@ public synchronized boolean exportToQuPath(boolean allowOverwrite, Context scija fixed_source.getSpimSource().getSourceTransform(0,0,fixedToPixel); InvertibleRealTransformSequence rt = new InvertibleRealTransformSequence(); - for (RegistrationStep rp: registrationPairSteps) { + for (int iReg = 0; iReg listener.newEvent(RegistrationEvents.CLOSED)); + listeners.clear(); + } + + public synchronized List[]> getAllSourcesPerStep() { + List[]> sourcesPerStep = new ArrayList<>(); + for (RegistrationStep rs: registrationPairSteps) { + sourcesPerStep.add(rs.sacs); + } + return sourcesPerStep; + } + private static class RegistrationStep { final Registration[]> reg; @@ -329,4 +345,25 @@ private static AffineTransform3D findZ0Transform(SourceAndConverter source, i at3DCenter.preConcatenate(at3D); return at3DCenter; } + + public enum RegistrationEvents { + STEP_ADDED, + STEP_REMOVED, + CLOSED + } + + final List listeners = new ArrayList<>(); + + public void addListener(RegistrationPairListener listener) { + listeners.add(listener); + } + + public void removeListener(RegistrationPairListener listener) { + listeners.remove(listener); + } + + public interface RegistrationPairListener { + void newEvent(RegistrationEvents event); + } + } diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationAddGUICommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationAddGUICommand.java new file mode 100644 index 00000000..e35a6c7e --- /dev/null +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationAddGUICommand.java @@ -0,0 +1,224 @@ +package ch.epfl.biop.registration.scijava.command; + +import bdv.util.BdvFunctions; +import bdv.util.BdvHandle; +import bdv.util.BdvOptions; +import bdv.util.BdvStackSource; +import bdv.viewer.DisplayMode; +import bdv.viewer.SourceAndConverter; +import bdv.viewer.SourceGroup; +import ch.epfl.biop.registration.RegistrationPair; +import org.scijava.Context; +import org.scijava.command.Command; +import org.scijava.object.ObjectService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import sc.fiji.bdvpg.bdv.BdvHandleHelper; +import sc.fiji.bdvpg.bdv.navigate.ViewerTransformAdjuster; +import sc.fiji.bdvpg.scijava.BdvScijavaHelper; +import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; +import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +@Plugin(type = BdvPlaygroundActionCommand.class, + menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Registration pair - Add GUI", + description = "Delete a registration pair" ) +public class PairRegistrationAddGUICommand implements Command { + + @Parameter + RegistrationPair registration_pair; + + @Parameter + ObjectService objectService; + + @Parameter + Context ctx; + + RegistrationPair.RegistrationPairListener listener; + + @Override + public void run() { + + BdvStackSource bdvStack = BdvFunctions.show(registration_pair.getFixedSources()[0], 1, BdvOptions.options().is2D()); + + final BdvHandle bdvh = bdvStack.getBdvHandle(); + + synchronized (registration_pair) { + + + for (SourceAndConverter source : registration_pair.getFixedSources()) { + BdvFunctions.show(source, 1, BdvOptions.options().addTo(bdvh)); + } + for (SourceAndConverter source : registration_pair.getMovingSourcesOrigin()) { + BdvFunctions.show(source, 1, BdvOptions.options().addTo(bdvh)); + } + + SourceGroup fixedGroup = bdvh.getViewerPanel().state().getGroups().get(0); + bdvh.getViewerPanel().state().setGroupName(fixedGroup, "Fixed sources"); + bdvh.getViewerPanel().state().setGroupActive(fixedGroup,true); + bdvh.getViewerPanel().state() + .addSourcesToGroup(Arrays.asList(registration_pair.getFixedSources()), fixedGroup); + + SourceGroup movingOriginGroup = bdvh.getViewerPanel().state().getGroups().get(1); + + bdvh.getViewerPanel().state().setGroupActive(movingOriginGroup,false); + bdvh.getViewerPanel().state().setGroupName(movingOriginGroup, "Moving sources - origin"); + bdvh.getViewerPanel().state() + .addSourcesToGroup(Arrays.asList(registration_pair.getMovingSourcesOrigin()), movingOriginGroup); + + for (int g = 2; g[]> sourcesPerStep = registration_pair.getAllSourcesPerStep(); + for (int step = 0; step < sourcesPerStep.size(); step++) { + SourceGroup group = bdvh.getViewerPanel().state().getGroups().get(step+2); + bdvh.getViewerPanel().state().removeSources(bdvh.getViewerPanel().state().getSourcesInGroup(group)); + + bdvh.getViewerPanel().state().setGroupActive(group,false); + List> sources = Arrays.asList(sourcesPerStep.get(step)); + + for (SourceAndConverter source : sources) { + BdvFunctions.show(source, 1, BdvOptions.options().addTo(bdvh)); + } + bdvh.getViewerPanel().state() + .addSourcesToGroup(sources, group); + } + + for (SourceAndConverter source : registration_pair.getFixedSources()) { + BdvFunctions.show(source, 1, BdvOptions.options().addTo(bdvh)); + } + for (SourceAndConverter source : registration_pair.getMovingSourcesOrigin()) { + BdvFunctions.show(source, 1, BdvOptions.options().addTo(bdvh)); + } + + SourceGroup fixedGroup = bdvh.getViewerPanel().state().getGroups().get(0); + bdvh.getViewerPanel().state() + .addSourcesToGroup(Arrays.asList(registration_pair.getFixedSources()), fixedGroup); + + SourceGroup movingOriginGroup = bdvh.getViewerPanel().state().getGroups().get(1); + bdvh.getViewerPanel().state() + .addSourcesToGroup(Arrays.asList(registration_pair.getMovingSourcesOrigin()), movingOriginGroup); + + if (sourcesPerStep.isEmpty()) { + bdvh.getViewerPanel().state().setGroupActive(bdvh.getViewerPanel().state().getGroups().get(1), true); + } else { + bdvh.getViewerPanel().state().setGroupActive(bdvh.getViewerPanel().state().getGroups().get(1), false); + bdvh.getViewerPanel().state().setGroupActive( + bdvh.getViewerPanel().state().getGroups().get(sourcesPerStep.size()+1), + true); + } + } + + boolean closeAlreadyActivated = false; + + private void addConfirmationCloseHook(final BdvHandle bdvh) { + JFrame frame = BdvHandleHelper.getJFrame(bdvh); + WindowListener[] listeners = frame.getWindowListeners(); + + for (WindowListener listener:listeners) { + frame.removeWindowListener(listener); + } + + frame.addWindowListener(new WindowAdapter() { + + @Override + public void windowClosing(WindowEvent e) { + if (!closeAlreadyActivated) { + String message = "Are you sure you want to exit the registration GUI?"; + + int confirmed = JOptionPane.showConfirmDialog(frame, + message, "Close window ?", + JOptionPane.YES_NO_OPTION); + if (confirmed == JOptionPane.YES_OPTION) { + + registration_pair.removeListener(listener); + + closeAlreadyActivated = true; + + int clearRegistration = JOptionPane.showConfirmDialog(frame, + "Keep registration pair in memory?", "Keep registration in memory.", + JOptionPane.YES_NO_OPTION); + + if (clearRegistration == JOptionPane.NO_OPTION) { + try { + registration_pair.close(); + objectService.removeObject(registration_pair); + registration_pair = null; + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + for (WindowListener listener : listeners) { + listener.windowClosing(e); + } + PairRegistrationAddGUICommand.this.registration_pair = null; + + } else { + frame.setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE); + } + } + } + }); + } +} diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCreateCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCreateCommand.java index fba9ae7c..d889c3e5 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCreateCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationCreateCommand.java @@ -43,5 +43,7 @@ public void run() { registration_pair = new RegistrationPair(fixed_sources,0,moving_sources,0, registration_name, true); objectService.addObject(registration_pair); + } } + diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationDeleteCommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationDeleteCommand.java index 7b5243a2..4c7c9928 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationDeleteCommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationDeleteCommand.java @@ -8,6 +8,8 @@ import sc.fiji.bdvpg.scijava.ScijavaBdvDefaults; import sc.fiji.bdvpg.scijava.command.BdvPlaygroundActionCommand; +import java.io.IOException; + @Plugin(type = BdvPlaygroundActionCommand.class, menuPath = ScijavaBdvDefaults.RootMenu+"Sources>Register>Delete registration pair", description = "Delete a registration pair" ) @@ -22,5 +24,10 @@ public class PairRegistrationDeleteCommand implements Command { @Override public void run() { objectService.removeObject(registration_pair); + try { + registration_pair.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } } } diff --git a/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java b/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java index 6440cd85..d56a51f4 100644 --- a/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java +++ b/src/main/java/ch/epfl/biop/scijava/command/source/register/Sift2DAffineRegisterCommand.java @@ -37,13 +37,15 @@ public class Sift2DAffineRegisterCommand extends Abstract2DRegistrationInRectang @Override public void run() { + FloatArray2DSIFT.Param param = new FloatArray2DSIFT.Param(); + param.maxOctaveSize = 2048; SIFTRegister reg = new SIFTRegister( sacs_fixed,levelFixedSource,tpFixed,invert_fixed, sacs_moving,levelMovingSource,tpMoving,invert_moving, pxSizeInCurrentUnit, px,py,pz,sx,sy, - new FloatArray2DSIFT.Param(), + param, 0.92f, 25.0f, 0.05f, diff --git a/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java b/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java index 8be06f85..445646a7 100644 --- a/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java +++ b/src/main/java/ch/epfl/biop/sourceandconverter/register/SIFTRegister.java @@ -108,9 +108,6 @@ public boolean run() { ImagePlus croppedMoving = getCroppedImage("Moving", sacs_moving, tpMoving, levelMipmapMoving); ImagePlus croppedFixed = getCroppedImage("Fixed", sacs_fixed, tpFixed, levelMipmapFixed); - croppedFixed.show(); - croppedMoving.show(); - Source sMoving = sacs_moving[0].getSpimSource(); Source sFixed = sacs_fixed[0].getSpimSource(); From 27f8b90fa96841b8c6fb8809323fa6443b3729a5 Mon Sep 17 00:00:00 2001 From: Nicolas Chiaruttini Date: Mon, 17 Jun 2024 08:04:21 +0200 Subject: [PATCH 13/14] Improves name of registrations within GUI --- .../ch/epfl/biop/registration/RegistrationPair.java | 13 +++++++++++++ .../command/PairRegistrationAddGUICommand.java | 8 +++----- .../affine/Sift2DAffineRegistration.java | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/epfl/biop/registration/RegistrationPair.java b/src/main/java/ch/epfl/biop/registration/RegistrationPair.java index 0088c464..08364bf4 100644 --- a/src/main/java/ch/epfl/biop/registration/RegistrationPair.java +++ b/src/main/java/ch/epfl/biop/registration/RegistrationPair.java @@ -311,6 +311,19 @@ public synchronized List[]> getAllSourcesPerStep() { return sourcesPerStep; } + public String getRegistrationName(int step) { + if ((step>=0)&&(step[]> reg; diff --git a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationAddGUICommand.java b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationAddGUICommand.java index e35a6c7e..63268f0a 100644 --- a/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationAddGUICommand.java +++ b/src/main/java/ch/epfl/biop/registration/scijava/command/PairRegistrationAddGUICommand.java @@ -73,10 +73,6 @@ public void run() { bdvh.getViewerPanel().state() .addSourcesToGroup(Arrays.asList(registration_pair.getMovingSourcesOrigin()), movingOriginGroup); - for (int g = 2; g[]> sourcesPerStep = registration_pair.getAllSourcesPerStep(); for (int step = 0; step < sourcesPerStep.size(); step++) { SourceGroup group = bdvh.getViewerPanel().state().getGroups().get(step+2); diff --git a/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/Sift2DAffineRegistration.java b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/Sift2DAffineRegistration.java index e9770c40..c4dade9b 100644 --- a/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/Sift2DAffineRegistration.java +++ b/src/main/java/ch/epfl/biop/registration/sourceandconverter/affine/Sift2DAffineRegistration.java @@ -144,7 +144,7 @@ public void abort() { } public String toString() { - return "Elastix 2D Affine"; + return "Sift 2D Affine"; } } From 30a2f9717d33c4312165cfc9174afe978a0a97ca Mon Sep 17 00:00:00 2001 From: Nicolas Chiaruttini Date: Wed, 19 Jun 2024 16:33:51 +0200 Subject: [PATCH 14/14] Limit logging --- src/test/java/register/DemoRegistrationWorkflow.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/register/DemoRegistrationWorkflow.java b/src/test/java/register/DemoRegistrationWorkflow.java index 6e0454c9..dcad9986 100644 --- a/src/test/java/register/DemoRegistrationWorkflow.java +++ b/src/test/java/register/DemoRegistrationWorkflow.java @@ -3,6 +3,7 @@ import ch.epfl.biop.bdv.img.qupath.command.CreateBdvDatasetQuPathCommand; import ch.epfl.biop.registration.scijava.command.PairRegistrationCreateCommand; import ch.epfl.biop.registration.scijava.command.PairRegistrationSift2DAffineCommand; +import loci.common.DebugTools; import net.imagej.ImageJ; public class DemoRegistrationWorkflow { @@ -10,6 +11,7 @@ public class DemoRegistrationWorkflow { static final ImageJ ij = new ImageJ(); static public void main(String... args) throws Exception { + DebugTools.setRootLevel("INFO"); ij.ui().showUI(); /*Thread.sleep(5000); ij.command().run(CreateBdvDatasetQuPathCommand.class,true).get();