package gdsc.utils; import java.awt.Rectangle; import java.util.ArrayList; import gdsc.UsageTracker; import gdsc.core.ij.AlignImagesFFT; import gdsc.core.ij.AlignImagesFFT.SubPixelMethod; import gdsc.core.ij.AlignImagesFFT.WindowMethod; import ij.IJ; import ij.ImagePlus; import ij.WindowManager; import ij.gui.GenericDialog; import ij.plugin.PlugIn; import ij.process.ImageProcessor; /** * Aligns an image stack to a reference image using XY translation to maximise the correlation. Takes in: * <ul> * <li>The reference image * <li>The image/stack to align. * <li>Optional Max/Min values for the X and Y translation * <li>Window function to reduce edge artifacts in frequency space * </ul> * <p> * The alignment is calculated using the maximum correlation between the images. The correlation is computed using the * frequency domain (note that conjugate multiplication in the frequency domain is equivalent to correlation in the * space domain). * <p> * Output new stack with the best alignment with optional sub-pixel accuracy. * <p> * By default restricts translation so that at least half of the smaller image width/height is within the larger image * (half-max translation). This can be altered by providing a translation bounds. Note that when using normalised * correlation all scores are set to zero outside the half-max translation due to potential floating-point summation * error during normalisation. */ // This code is based on the Fast Normalised Cross-Correlation algorithm by J.P.Lewis // http://scribblethink.org/Work/nvisionInterface/nip.pdf public class Align_Images_FFT implements PlugIn { private static final String TITLE = "Align Images FFT"; public static final String[] windowFunctions; private static int myWindowFunction = 3; private static boolean restrictTranslation = false; private static int myMinXShift = -20, myMaxXShift = 20; private static int myMinYShift = -20, myMaxYShift = 20; public static final String[] subPixelMethods; private static int subPixelMethod = 2; public static final String[] methods = ImageProcessor.getInterpolationMethods(); private static int interpolationMethod = ImageProcessor.NONE; private static final String NONE = "[Reference stack]"; private static String reference = ""; private static String target = ""; private static boolean normalised = true; private static boolean showCorrelationImage = false; private static boolean showNormalisedImage = false; private static boolean clipOutput = false; static { WindowMethod[] m = AlignImagesFFT.WindowMethod.values(); windowFunctions = new String[m.length]; for (int i = 0; i < m.length; i++) windowFunctions[i] = m[i].toString(); SubPixelMethod[] m2 = AlignImagesFFT.SubPixelMethod.values(); subPixelMethods = new String[m2.length]; for (int i = 0; i < m2.length; i++) subPixelMethods[i] = m2[i].toString(); } /** Ask for parameters and then execute. */ public void run(String arg) { UsageTracker.recordPlugin(this.getClass(), arg); String[] imageList = getImagesList(); if (imageList.length < 1) { IJ.showMessage("Error", "Only 8-bit and 16-bit images are supported"); return; } if (!showDialog(imageList)) return; ImagePlus refImp = WindowManager.getImage(reference); ImagePlus targetImp = WindowManager.getImage(target); Rectangle bounds = null; if (restrictTranslation) { bounds = createBounds(myMinXShift, myMaxXShift, myMinYShift, myMaxYShift); } AlignImagesFFT align = new AlignImagesFFT(); ImagePlus alignedImp = align.align(refImp, targetImp, WindowMethod.values()[myWindowFunction], bounds, SubPixelMethod.values()[subPixelMethod], interpolationMethod, normalised, showCorrelationImage, showNormalisedImage, clipOutput); if (alignedImp != null) alignedImp.show(); } private String[] getImagesList() { // Find the currently open images ArrayList<String> newImageList = new ArrayList<String>(); for (int id : gdsc.core.ij.Utils.getIDList()) { ImagePlus imp = WindowManager.getImage(id); // Image must be 8-bit/16-bit if (imp != null && (imp.getType() == ImagePlus.GRAY8 || imp.getType() == ImagePlus.GRAY16 || imp.getType() == ImagePlus.GRAY32)) { // Check it is not one the result images String imageTitle = imp.getTitle(); newImageList.add(imageTitle); } } return newImageList.toArray(new String[0]); } private boolean showDialog(String[] imageList) { GenericDialog gd = new GenericDialog(TITLE); if (!contains(imageList, reference)) reference = imageList[0]; // Add option to have no target String[] targetList = new String[imageList.length + 1]; targetList[0] = NONE; for (int i = 0; i < imageList.length; i++) { targetList[i + 1] = imageList[i]; } if (!contains(targetList, target)) target = targetList[0]; gd.addMessage( "Align target image stack to a reference using\ncorrelation in the frequency domain. Edge artifacts\ncan be reduced using a window function or by\nrestricting the translation."); gd.addChoice("Reference_image", imageList, reference); gd.addChoice("Target_image", targetList, target); gd.addChoice("Window_function", windowFunctions, windowFunctions[myWindowFunction]); gd.addCheckbox("Restrict_translation", restrictTranslation); gd.addNumericField("Min_X_translation", myMinXShift, 0); gd.addNumericField("Max_X_translation", myMaxXShift, 0); gd.addNumericField("Min_Y_translation", myMinYShift, 0); gd.addNumericField("Max_Y_translation", myMaxYShift, 0); gd.addChoice("Sub-pixel_method", subPixelMethods, subPixelMethods[subPixelMethod]); gd.addChoice("Interpolation", methods, methods[interpolationMethod]); gd.addCheckbox("Normalised", normalised); gd.addCheckbox("Show_correlation_image", showCorrelationImage); gd.addCheckbox("Show_normalised_image", showNormalisedImage); gd.addCheckbox("Clip_output", clipOutput); gd.addHelp(gdsc.help.URL.UTILITY); gd.showDialog(); if (gd.wasCanceled()) return false; reference = gd.getNextChoice(); target = gd.getNextChoice(); myWindowFunction = gd.getNextChoiceIndex(); restrictTranslation = gd.getNextBoolean(); myMinXShift = (int) gd.getNextNumber(); myMaxXShift = (int) gd.getNextNumber(); myMinYShift = (int) gd.getNextNumber(); myMaxYShift = (int) gd.getNextNumber(); subPixelMethod = gd.getNextChoiceIndex(); interpolationMethod = gd.getNextChoiceIndex(); normalised = gd.getNextBoolean(); showCorrelationImage = gd.getNextBoolean(); showNormalisedImage = gd.getNextBoolean(); clipOutput = gd.getNextBoolean(); return true; } private boolean contains(String[] imageList, String title) { for (String t : imageList) if (t.equals(title)) return true; return false; } public static Rectangle createBounds(int minXShift, int maxXShift, int minYShift, int maxYShift) { int w = maxXShift - minXShift; int h = maxYShift - minYShift; Rectangle bounds = new Rectangle(minXShift, minYShift, w, h); return bounds; } }