fixed improper generic parameter use in Tree.duplicateAs > Map, necessary
[trakem2.git] / TrakEM2_ / src / main / java / ini / trakem2 / imaging / Blending.java
blobe0c158555dab5def55ae5109896758e142d824d0
1 /**
3 */
4 package ini.trakem2.imaging;
6 import ij.process.ByteProcessor;
7 import ini.trakem2.display.Display;
8 import ini.trakem2.display.Displayable;
9 import ini.trakem2.display.Layer;
10 import ini.trakem2.display.Patch;
11 import ini.trakem2.utils.Bureaucrat;
12 import ini.trakem2.utils.Filter;
13 import ini.trakem2.utils.IJError;
14 import ini.trakem2.utils.Utils;
15 import ini.trakem2.utils.Worker;
17 import java.awt.geom.AffineTransform;
18 import java.awt.geom.NoninvertibleTransformException;
19 import java.awt.geom.Point2D;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28 import java.util.concurrent.ExecutorService;
29 import java.util.concurrent.Executors;
30 import java.util.concurrent.Future;
32 import mpicbg.models.NoninvertibleModelException;
33 import mpicbg.trakem2.transform.TransformMesh;
35 /** Utility functions for blending images together, to remove contrast seams.
36 * Inspired and guided by Stephan Preibisch's blending functions in his Stitching plugins. */
37 public final class Blending {
39 static public final Bureaucrat blend(final List<Layer> layers, final boolean respect_current_mask, final Filter<Patch> filter) {
40 return Bureaucrat.createAndStart(
41 new Worker.Task("Blending layer-wise") {
42 @Override
43 public void exec() {
44 blendLayerWise(layers, respect_current_mask, filter);
46 }, layers.get(0).getProject());
49 static public final void blendLayerWise(final List<Layer> layers, final boolean respect_current_mask, final Filter<Patch> filter) {
50 for (final Layer layer : layers) {
51 final List<Patch> patches = layer.getAll(Patch.class);
52 final Set<Patch> s = new HashSet<Patch>();
53 if (null == filter) {
54 s.addAll(patches);
55 } else {
56 for (final Iterator<Patch> it = patches.iterator(); it.hasNext(); ) {
57 final Patch p = it.next();
58 if (filter.accept(p)) s.add(p);
61 blendPatches(s, respect_current_mask);
65 /** For each file, find the weight for the alpha mask according to
66 * wether the pixel overlaps with other images (weighted alpha
67 * dependent on the distante to the image border and of that on
68 * the other images) or not (full alpha).
69 * An image that doesn't overlap at all gets no alpha set at all.
71 static public final Bureaucrat blend(final Set<Patch> patches, final boolean respect_current_mask) {
72 if (null == patches || patches.size() < 2) return null;
74 return Bureaucrat.createAndStart(
75 new Worker.Task("Blending images") {
76 @Override
77 public void exec() {
78 blendPatches(patches, respect_current_mask);
80 }, patches.iterator().next().getProject());
83 static public final void blendPatches(final Set<Patch> patches, final boolean respect_current_mask) {
84 ExecutorService exe = null;
85 try {
86 if (null == patches || patches.size() < 2) return;
88 final Layer layer = patches.iterator().next().getLayer();
90 for (final Patch p : patches) {
91 if (null != p.getCoordinateTransform()) {
92 Utils.log("CANNOT blend: at least one image has a coordinate transform.\nBlending of coordinate-transformed images will be enabled in the near future.");
93 return;
95 if (p.getLayer() != layer) {
96 Utils.log("CANNOT blend: all images must belong to the same layer!\n Otherwise the overlap cannot be computed.");
97 return;
101 final HashMap<Patch,TransformMesh> meshes = new HashMap<Patch,TransformMesh>();
102 for (final Patch p : patches) {
103 meshes.put(p, null == p.getCoordinateTransform() ? null
104 : new TransformMesh(p.getCoordinateTransform(), p.getMeshResolution(), p.getOWidth(), p.getOHeight()));
107 exe = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
108 final List<Future<?>> futures = Collections.synchronizedList(new ArrayList<Future<?>>());
109 final List<Future<?>> futures2 = Collections.synchronizedList(new ArrayList<Future<?>>());
112 // Cache the indices that determine overlap order within the layer
113 final HashMap<Patch,Integer> indices = new HashMap<Patch,Integer>();
114 int i = 0;
115 for (final Displayable d : layer.getDisplayables()) {
116 if (d.getClass() == Patch.class && patches.contains((Patch)d)) {
117 indices.put((Patch)d, i);
119 i += 1;
122 for (final Patch p : patches) {
123 if (Thread.currentThread().isInterrupted()) break;
124 futures.add(exe.submit(new Runnable() { @Override
125 public void run() {
126 final int pLayerIndex = indices.get(p);
127 final Set<Patch> overlapping = new HashSet<Patch>();
128 for (final Patch op : patches) {
129 if (indices.get(op) < pLayerIndex) overlapping.add(op);
131 if (setBlendingMask(p, overlapping, meshes, respect_current_mask)) {
132 futures2.add(p.updateMipMaps());
134 }}, null));
137 // join all:
138 Utils.waitIfAlive(futures, false);
139 Utils.waitIfAlive(futures2, false);
141 } catch (final Exception e) {
142 IJError.print(e);
143 } finally {
144 if (null != exe) exe.shutdown();
145 Display.repaint();
149 /** Returns true if a new mask has been set to Patch p. */
150 static private boolean setBlendingMask(final Patch p, Set<Patch> overlapping, final Map<Patch,TransformMesh> meshes, final boolean respect_current_mask) {
152 Utils.log2("Blending " + p);
154 if (overlapping.contains(p)) {
155 overlapping = new HashSet<Patch>(overlapping);
156 overlapping.remove(p);
159 final AffineTransform at = p.getAffineTransform();
160 final TransformMesh mesh = meshes.get(p);
162 ByteProcessor mask = null;
163 if (respect_current_mask) {
164 mask = p.getAlphaMask();
166 if (null == mask) {
167 mask = new ByteProcessor(p.getOWidth(), p.getOHeight());
168 mask.setValue(255);
169 mask.fill();
172 final byte[] pix = (byte[]) mask.getPixels();
174 final Point2D.Double po = new Point2D.Double();
175 final double[] fo = new double[2];
177 final int p_o_width = p.getOWidth();
178 final int p_o_height = p.getOHeight();
180 int next = 0;
181 final double[] weights = new double[overlapping.size() + 1]; // the self as well
182 int masked = 0;
184 for (int y=0; y<p_o_height; y++) {
186 if (Thread.currentThread().isInterrupted()) return false;
188 for (int x=0; x<p_o_width; x++) {
190 // transform x,y to world coords
191 if (null != mesh) {
192 fo[0] = x;
193 fo[1] = y;
194 mesh.applyInPlace(fo);
195 po.x = fo[0];
196 po.y = fo[1];
197 } else {
198 po.x = x;
199 po.y = y;
202 at.transform(po, po);
204 fo[0] = po.x;
205 fo[1] = po.y;
207 // debug:
208 if (0 == x && 0 == y) {
209 Utils.log2("point 0,0 goes to " + fo[0] + ", " + fo[1]);
212 // check if it intersects any Patch
213 next = 0;
214 for (final Patch other : overlapping) {
215 final double weight = intersects(fo, other, meshes.get(other));
216 if (weight > 0) weights[next++] = weight;
219 final int i = y * p_o_width + x;
221 if (respect_current_mask) {
222 // Don't compute if no overlap or if current mask value is zero
223 if (next > 0 && pix[i] != 0) {
224 weights[next++] = computeWeight(x, y, p_o_width, p_o_height); // the weight of Patch p, added last
225 double sum = 0;
226 for (int f=0; f<next; f++) sum += weights[f];
227 pix[i] = (byte)((int)(255 * (weights[next-1] / sum) * ((pix[i]&0xff) / 255.0f) ));
228 masked++;
230 // else leave current value untouched
231 } else if (next > 0) {
232 // Overwritting current mask
233 weights[next++] = computeWeight(x, y, p_o_width, p_o_height); // the weight of Patch p, added last
234 double sum = 0;
235 for (int f=0; f<next; f++) sum += weights[f];
236 pix[i] = (byte)((int)(255 * (weights[next-1] / sum)));
237 masked++;
242 Utils.log2("Masked = " + masked + " for " + p);
244 if (masked > 0) {
245 p.setAlphaMask(mask);
247 //new ij.ImagePlus("mask for " + p.getId(), mask).show();
249 return true;
253 Utils.log("Nothing to blend in image " + p);
255 return false;
258 static private final double computeWeight(final double x, final double y, final int width, final int height) {
259 //return Math.min(Math.min(x, width - x),
260 // Math.min(y, height - y));
261 // Normalized, as suggested by Stephan Preibisch:
262 return (Math.min(x, width - x) / (width/2)) * (Math.min(y, height - y) / (height/2));
265 /** Returns true if fo[0,1] x,y world coords intersect the affine and potentially coordinate transformed pixels of the other Patch. */
266 static private double intersects(final double[] fo, final Patch other, final TransformMesh mesh) {
267 // First inverse affine transform
268 final AffineTransform at = other.getAffineTransform();
269 final Point2D.Double po = new Point2D.Double(fo[0], fo[1]);
270 final int o_width = other.getOWidth();
271 final int o_height = other.getOHeight();
272 try {
273 at.inverseTransform(po, po);
274 } catch (final NoninvertibleTransformException nite) {
275 return -1;
277 if (null == mesh) {
278 if (po.x >= 0 && po.x < o_width
279 && po.y >= 0 && po.y < o_height) {
280 return computeWeight(po.x, po.y, o_width, o_height);
281 } else {
282 return -1;
285 // Then inverse the coordinate transform
286 try {
287 fo[0] = po.x;
288 fo[1] = po.y;
289 mesh.applyInverseInPlace(fo);
290 return computeWeight(fo[0], fo[1], o_width, o_height);
291 } catch (final NoninvertibleModelException nime) {
292 // outside boundaries
293 return -1;