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. */
5 import ij
.gui
.GenericDialog
;
6 import ij
.plugin
.PlugIn
;
7 import ij
.VirtualStack
;
9 import ij
.io
.DirectoryChooser
;
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
;
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
{
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);
56 if (gd
.wasCanceled()) {
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 );
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.");
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.");
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());
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();
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
;
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
]);
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
);
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
);
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());
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
);
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
++) {
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 ...
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
);
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);
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
) {
270 ControlWindow
.setGUIEnabled(false);