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
;
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") {
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
>();
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") {
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;
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.");
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.");
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
>();
115 for (final Displayable d
: layer
.getDisplayables()) {
116 if (d
.getClass() == Patch
.class && patches
.contains((Patch
)d
)) {
117 indices
.put((Patch
)d
, i
);
122 for (final Patch p
: patches
) {
123 if (Thread
.currentThread().isInterrupted()) break;
124 futures
.add(exe
.submit(new Runnable() { @Override
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());
138 Utils
.waitIfAlive(futures
, false);
139 Utils
.waitIfAlive(futures2
, false);
141 } catch (final Exception e
) {
144 if (null != exe
) exe
.shutdown();
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();
167 mask
= new ByteProcessor(p
.getOWidth(), p
.getOHeight());
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();
181 final double[] weights
= new double[overlapping
.size() + 1]; // the self as well
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
194 mesh
.applyInPlace(fo
);
202 at
.transform(po
, po
);
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
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
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
) ));
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
235 for (int f
=0; f
<next
; f
++) sum
+= weights
[f
];
236 pix
[i
] = (byte)((int)(255 * (weights
[next
-1] / sum
)));
242 Utils
.log2("Masked = " + masked
+ " for " + p
);
245 p
.setAlphaMask(mask
);
247 //new ij.ImagePlus("mask for " + p.getId(), mask).show();
253 Utils
.log("Nothing to blend in image " + p
);
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();
273 at
.inverseTransform(po
, po
);
274 } catch (final NoninvertibleTransformException nite
) {
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
);
285 // Then inverse the coordinate transform
289 mesh
.applyInverseInPlace(fo
);
290 return computeWeight(fo
[0], fo
[1], o_width
, o_height
);
291 } catch (final NoninvertibleModelException nime
) {
292 // outside boundaries