"open later" Display functionality is now thread-coherent.
[trakem2.git] / Register_Virtual_Stack_MT.java
blobf9451bc90547ce0ec3537b50887f6a1f6278e817
1 /** Albert Cardona 2008. This work released under the terms of the General Public License in its latest edition. */
2 /** Greg Jefferis enhanced progress report and interaction with the user. */
4 import ij.IJ;
5 import ij.gui.GenericDialog;
6 import ij.plugin.PlugIn;
7 import ij.VirtualStack;
8 import ij.ImagePlus;
9 import ij.io.DirectoryChooser;
10 import ij.io.Opener;
11 import ij.io.FileSaver;
13 import ini.trakem2.ControlWindow;
14 import ini.trakem2.Project;
15 import ini.trakem2.display.Layer;
16 import ini.trakem2.display.Patch;
17 import ini.trakem2.imaging.StitchingTEM;
18 import ini.trakem2.imaging.Registration;
19 import ini.trakem2.persistence.Loader;
20 import ini.trakem2.utils.IJError;
21 import ini.trakem2.utils.Utils;
22 import mpi.fruitfly.general.MultiThreading;
24 import java.util.concurrent.atomic.AtomicInteger;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.awt.geom.AffineTransform;
28 import java.awt.image.BufferedImage;
29 import java.awt.image.IndexColorModel;
30 import java.awt.Graphics2D;
31 import java.awt.Color;
32 import java.awt.Rectangle;
33 import java.io.File;
34 import java.io.FilenameFilter;
36 import java.util.Arrays;
38 /** Requires: a directory with images, all of the same dimensions
39 * Performs: registration of one image to the next, by phase- and cross-correlation or by SIFT
40 * Outputs: the list of new images, one for slice, into a target directory as .tif files.
42 public class Register_Virtual_Stack_MT implements PlugIn {
44 // Registration types
45 static public final int PHASE_CORRELATION = 0;
46 static public final int SIFT = 1;
48 public void run(String arg) {
50 GenericDialog gd = new GenericDialog("Options");
51 String[] types = {"translation only (phase-correlation)",
52 "translation and rotation (SIFT)"};
53 gd.addChoice("Registration: ", types, types[0]);
54 gd.addSlider("Scaling % (for performance): ", 1, 100, 25);
55 gd.showDialog();
56 if (gd.wasCanceled()) {
57 return;
59 final int registration_type = gd.getNextChoiceIndex();
60 // parameter to scale down images (1.0 means no scaling)
61 final float scale = (float)gd.getNextNumber() / 100.0f;
63 final Registration.SIFTParameters sp = (1 == registration_type ? new Registration.SIFTParameters() : null );
64 if (null != sp) {
65 sp.scale = scale;
66 sp.setup();
69 DirectoryChooser dc = new DirectoryChooser("Source images");
70 String source_dir = dc.getDirectory();
71 if (null == source_dir) return;
72 source_dir = source_dir.replace('\\', '/');
73 if (!source_dir.endsWith("/")) source_dir += "/";
75 dc = new DirectoryChooser("Target folder");
76 String target_dir = dc.getDirectory();
77 if (null == target_dir) return;
78 target_dir = target_dir.replace('\\', '/');
79 if (!target_dir.endsWith("/")) target_dir += "/";
81 if (source_dir.equals(target_dir)) {
82 IJ.showMessage("Source and target directories MUST be different\n or images would get overwritten.");
83 return;
85 exec(source_dir, target_dir, registration_type, sp, scale, StitchingTEM.DEFAULT_MIN_R);
88 /** @param source_dir Directory to read all images from, where each image is a slice in a sequence. Their names must be bit-sortable, i.e. if numbered, they must be padded with zeros.
89 * @param target_fir Directory to store registered slices into.
90 * @param registration_type Either PHASE_CORRELATION or SIFT (0 or 1)
91 * @param sp The ini.trakem2.imaging.Registration.SIFTParameters class instance containing all SIFT parameters. Can be null only if not using SIFT as @param registration_type.
92 * @param scale The scale at which phase-correlation should be executed.
93 * @param min_R The minimal acceptable cross-correlation score, from 0 to 1, to evaluate the goodness of a phase-correlation.
95 static public void exec(final String source_dir, final String target_dir, final int registration_type, final Registration.SIFTParameters sp, final float scale, final float min_R) {
96 if (SIFT == registration_type && null == sp) {
97 System.out.println("Can't use a null sp.");
98 return;
100 // get file listing
101 final String exts = ".tif.jpg.png.gif.tiff.jpeg.bmp.pgm";
102 final String[] names = new File(source_dir).list(new FilenameFilter() {
103 public boolean accept(File dir, String name) {
104 int idot = name.lastIndexOf('.');
105 if (-1 == idot) return false;
106 return exts.contains(name.substring(idot).toLowerCase());
109 Arrays.sort(names);
111 try {
112 // disable TrakEM2 windows
113 ControlWindow.setGUIEnabled(false);
114 // create temporary host project
115 final Project project = Project.newFSProject("blank", null, source_dir);
116 final Loader loader = project.getLoader();
117 // disable mipmaps
118 loader.setMipMapsRegeneration(false);
119 // create a layer to work on
120 // no need //Layer layer = project.getRootLayerSet().getLayer(0, 1, true);
122 if (null != sp) sp.project = project;
124 // Open first image
125 ImagePlus first = new Opener().openImage(source_dir + names[0]);
126 final int width = first.getWidth();
127 final int height = first.getHeight();
128 final int type = first.getType();
129 final double min = first.getProcessor().getMin();
130 final double max = first.getProcessor().getMax();
131 final boolean color = (type == ImagePlus.COLOR_RGB);
132 first = null; // don't interfere with memory management
134 // create all patches. Images are NOT loaded
135 IJ.showStatus("Loading Image Patches ...");
136 final ArrayList<Patch> pa = new ArrayList<Patch>();
138 for (int i=0; i<names.length; i++) {
139 if (!new File(source_dir + names[i]).exists()) {
140 System.out.println("Ignoring image " + names[i]);
141 continue;
143 Patch patch = new Patch(project, loader.getNextId(), names[i], width, height, type, false, min, max, new AffineTransform());
144 loader.addedPatchFrom(source_dir + names[i], patch);
145 pa.add(patch);
148 // find out the affine transform of each slice to the previous
149 final AffineTransform[] affine = new AffineTransform[pa.size()];
150 affine[0] = new AffineTransform(); // first slice doesn't move
152 final Thread[] threads = MultiThreading.newThreads();
153 final AtomicInteger ai = new AtomicInteger(1); // start at second slice
154 final AtomicInteger finished = new AtomicInteger(0);
156 for (int ithread = 0; ithread < threads.length; ++ithread) {
157 threads[ithread] = new Thread() { public void run() { setPriority(Thread.NORM_PRIORITY);
159 for (int i = ai.getAndIncrement(); i < affine.length; i = ai.getAndIncrement()) {
160 IJ.showStatus("Computing transform for slice (" + i + "/" + affine.length + ")");
161 if (IJ.debugMode) IJ.log("Computing transform for slice (" + i + "/" + affine.length + ")");
162 IJ.showProgress(finished.get(), affine.length);
164 Patch prev = pa.get(i-1);
165 Patch next = pa.get(i);
166 // will load images on its own (only once for each, guaranteed)
167 loader.releaseToFit(width * height * (ImagePlus.GRAY8 == type ? 1 : 5) * threads.length * 6);
168 if ( 0 == registration_type ) {
169 double[] c = StitchingTEM.correlate(prev, next, 1.0f, scale, StitchingTEM.TOP_BOTTOM, 0, 0, min_R);
170 affine[i] = new AffineTransform();
171 affine[i].translate(c[0], c[1]);
172 } else if ( 1 == registration_type ) {
173 Object[] result = Registration.registerWithSIFTLandmarks(prev, next, sp, null, false, true);
174 affine[i] = (null == result ? new AffineTransform() : (AffineTransform)result[2]);
176 IJ.showProgress(finished.incrementAndGet(), affine.length);
181 // wait until all threads finished
182 MultiThreading.startAndJoin(threads);
184 IJ.showProgress(0);
186 // determine maximum canvas, make affines global by concatenation
187 final Rectangle box = new Rectangle(0, 0, width, height);
188 final Rectangle one = new Rectangle(0, 0, width, height);
190 for (int i=1; i<affine.length; i++) {
191 affine[i].concatenate(affine[i-1]);
192 box.add(affine[i].createTransformedShape(one).getBounds());
193 // reset
194 one.setRect(0, 0, width, height);
196 box.width = box.width - box.x;
197 box.height = box.height - box.y;
198 final AffineTransform trans = new AffineTransform();
199 trans.translate(-box.x, -box.y);
200 box.x = box.y = 0;
202 // fix stupid java defaults for indexed images, which are not purely grayscale
203 final byte[] r = new byte[256];
204 final byte[] g = new byte[256];
205 final byte[] b = new byte[256];
206 for (int i=0; i<256; i++) {
207 r[i]=(byte)i;
208 g[i]=(byte)i;
209 b[i]=(byte)i;
211 final IndexColorModel icm = new IndexColorModel(8, 256, r, g, b);
213 // output images as 8-bit or RGB
214 final Thread[] threads2 = MultiThreading.newThreads();
215 final AtomicInteger ai2 = new AtomicInteger(0);
217 final String[] tiffnames = new String[affine.length];
219 for (int ithread = 0; ithread < threads2.length; ++ithread) {
220 threads2[ithread] = new Thread() { public void run() { setPriority(Thread.NORM_PRIORITY);
222 for (int i = ai2.getAndIncrement(); i < affine.length; i = ai2.getAndIncrement()) {
223 affine[i].concatenate(trans);
224 // ensure enough free memory
225 loader.releaseToFit(width * height * (ImagePlus.GRAY8 == type ? 1 : 5) * threads2.length * 6); // 3: 1 processor + 1 image + 1 for safety , times 2 ...
226 BufferedImage bi;
227 Graphics2D g;
228 if (color) {
229 bi = new BufferedImage(box.width, box.height, BufferedImage.TYPE_INT_ARGB);
230 g = bi.createGraphics();
231 g.setColor(Color.black);
232 g.fillRect(0, 0, box.width, box.height);
233 } else {
234 bi = new BufferedImage(box.width, box.height, BufferedImage.TYPE_BYTE_INDEXED, icm);
235 g = bi.createGraphics();
237 Patch patch = pa.get(i);
238 g.drawImage(loader.fetchImage(patch, 1.0), affine[i], null);
239 ImagePlus imp = new ImagePlus(patch.getTitle(), bi);
240 // Trim off file extension
241 String slice_name = patch.getTitle();
242 int idot = slice_name.lastIndexOf('.');
243 if (idot > 0) slice_name = slice_name.substring(0, idot);
244 tiffnames[i] = slice_name + ".tif";
245 new FileSaver(imp).saveAsTiff(target_dir + tiffnames[i]);
247 if (0 == i % threads.length) {
248 IJ.showStatus("Saving slice ("+i+"/"+affine.length+")");
249 IJ.showProgress(i, affine.length);
254 // wait until all threads finished
255 MultiThreading.startAndJoin(threads2);
257 IJ.showStatus("done.");
258 IJ.showProgress(1.0);
260 project.destroy();
262 // open virtual stack
263 VirtualStack vs = new VirtualStack(box.width, box.height, icm, target_dir);
264 for (int i=0; i<tiffnames.length; i++) vs.addSlice(tiffnames[i]);
265 new ImagePlus("Registered", vs).show();
267 } catch (Exception e) {
268 IJError.print(e);
269 } finally {
270 ControlWindow.setGUIEnabled(false);
271 IJ.showProgress(0);