4 package ini
.trakem2
.imaging
;
6 import ini
.trakem2
.display
.Patch
;
7 import ini
.trakem2
.display
.Display
;
8 import ini
.trakem2
.utils
.Utils
;
9 import ini
.trakem2
.utils
.Bureaucrat
;
10 import ini
.trakem2
.utils
.Worker
;
11 import ini
.trakem2
.utils
.IJError
;
12 import mpicbg
.trakem2
.transform
.CoordinateTransform
;
13 import mpicbg
.trakem2
.transform
.TransformMesh
;
14 import mpicbg
.models
.NoninvertibleModelException
;
16 import java
.awt
.geom
.NoninvertibleTransformException
;
17 import java
.awt
.geom
.AffineTransform
;
18 import java
.awt
.geom
.Point2D
;
21 import java
.util
.HashMap
;
22 import java
.util
.HashSet
;
23 import java
.util
.ArrayList
;
25 import ij
.process
.ByteProcessor
;
27 import java
.util
.concurrent
.FutureTask
;
28 import java
.util
.concurrent
.Executors
;
29 import java
.util
.concurrent
.ExecutorService
;
30 import java
.util
.concurrent
.Callable
;
32 /** Utility functions for blending images together, to remove contrast seams.
33 * Inspired and guided by Stephan Preibisch's blending functions in his Stitching plugins. */
34 public final class Blending
{
36 /** For each file, find the weight for the alpha mask according to
37 * wether the pixel overlaps with other images (weighted alpha
38 * dependent on the distante to the image border and of that on
39 * the other images) or not (full alpha).
40 * An image that doesn't overlap at all gets no alpha set at all.
42 static public final Bureaucrat
blend(final Set
<Patch
> patches
, final boolean respect_current_mask
) {
43 if (null == patches
|| patches
.size() < 2) return null;
45 return Bureaucrat
.createAndStart(new Worker("Blending images") {
50 for (final Patch p
: patches
) {
51 if (null != p
.getCoordinateTransform()) {
52 Utils
.log("CANNOT blend: at least one image has a coordinate transform.\nBlending of coordinate-transformed images will be enabled in the near future.");
57 final HashMap
<Patch
,TransformMesh
> meshes
= new HashMap
<Patch
,TransformMesh
>();
58 for (final Patch p
: patches
) {
59 meshes
.put(p
, null == p
.getCoordinateTransform() ?
null
60 : new TransformMesh(p
.getCoordinateTransform(), 32, p
.getOWidth(), p
.getOHeight()));
63 ExecutorService exe
= Executors
.newFixedThreadPool(Runtime
.getRuntime().availableProcessors());
64 final ArrayList
<FutureTask
> futures
= new ArrayList
<FutureTask
>();
66 for (final Patch p
: patches
) {
67 if (Thread
.currentThread().isInterrupted()) break;
68 FutureTask future
= new FutureTask(new Runnable() { public void run() {
69 final int pLayerIndex
= p
.getLayer().indexOf( p
);
70 final Set
< Patch
> overlapping
= new HashSet
< Patch
>();
71 for ( Patch op
: patches
)
72 if ( p
.getLayer().indexOf( op
) < pLayerIndex
)
73 overlapping
.add( op
);
74 if (setBlendingMask(p
, overlapping
, meshes
, respect_current_mask
)) {
83 for (final FutureTask future
: futures
) {
84 if (Thread
.currentThread().isInterrupted()) break;
87 } catch (InterruptedException ie
) {} // thrown when canceled
92 } catch (Exception e
) {
99 }, patches
.iterator().next().getProject());
102 /** Returns true if a new mask has been set to Patch p. */
103 static private boolean setBlendingMask(final Patch p
, Set
<Patch
> overlapping
, final Map
<Patch
,TransformMesh
> meshes
, final boolean respect_current_mask
) {
105 Utils
.log2("Blending " + p
);
107 if (overlapping
.contains(p
)) {
108 overlapping
= new HashSet
<Patch
>(overlapping
);
109 overlapping
.remove(p
);
112 final AffineTransform at
= p
.getAffineTransform();
113 final TransformMesh mesh
= meshes
.get(p
);
115 ByteProcessor mask
= null;
116 if (respect_current_mask
) {
117 mask
= p
.getProject().getLoader().fetchImageMask(p
);
120 mask
= new ByteProcessor(p
.getOWidth(), p
.getOHeight());
125 final byte[] pix
= (byte[]) mask
.getPixels();
127 final Point2D
.Double po
= new Point2D
.Double();
128 final float[] fo
= new float[2];
130 final int p_o_width
= p
.getOWidth();
131 final int p_o_height
= p
.getOHeight();
134 final float[] weights
= new float[overlapping
.size() + 1]; // the self as well
137 for (int y
=0; y
<p_o_height
; y
++) {
139 if (Thread
.currentThread().isInterrupted()) return false;
141 for (int x
=0; x
<p_o_width
; x
++) {
143 // transform x,y to world coords
147 mesh
.applyInPlace(fo
);
155 at
.transform(po
, po
);
157 fo
[0] = (float) po
.x
;
158 fo
[1] = (float) po
.y
;
161 if (0 == x
&& 0 == y
) {
162 Utils
.log2("point 0,0 goes to " + fo
[0] + ", " + fo
[1]);
165 // check if it intersects any Patch
167 for (final Patch other
: overlapping
) {
168 float weight
= intersects(fo
, other
, meshes
.get(other
));
169 if (weight
> 0) weights
[next
++] = weight
;
172 final int i
= y
* p_o_height
+ x
;
174 if (respect_current_mask
) {
175 // Don't compute if no overlap or if current mask value is zero
176 if (next
> 0 && pix
[i
] != 0) {
177 weights
[next
++] = computeWeight(x
, y
, p_o_width
, p_o_height
); // the weight of Patch p, added last
179 for (int f
=0; f
<next
; f
++) sum
+= weights
[f
];
180 pix
[i
] = (byte)((int)(255 * (weights
[next
-1] / sum
) * ((pix
[i
]&0xff) / 255.0f
) ));
183 // else leave current value untouched
184 } else if (next
> 0) {
185 // Overwritting current mask
186 weights
[next
++] = computeWeight(x
, y
, p_o_width
, p_o_height
); // the weight of Patch p, added last
188 for (int f
=0; f
<next
; f
++) sum
+= weights
[f
];
189 pix
[i
] = (byte)((int)(255 * (weights
[next
-1] / sum
)));
195 Utils
.log2("Masked = " + masked
+ " for " + p
);
198 p
.setAlphaMask(mask
);
200 //new ij.ImagePlus("mask for " + p.getId(), mask).show();
208 static private final float computeWeight(final float x
, final float y
, final int width
, final int height
) {
209 //return Math.min(Math.min(x, width - x),
210 // Math.min(y, height - y));
211 // Normalized, as suggested by Stephan Preibisch:
212 return (Math
.min(x
, width
- x
) / (width
/2)) * (Math
.min(y
, height
- y
) / (height
/2));
215 /** Returns true if fo[0,1] x,y world coords intersect the affine and potentially coordinate transformed pixels of the other Patch. */
216 static private float intersects(final float[] fo
, final Patch other
, final TransformMesh mesh
) {
217 // First inverse affine transform
218 final AffineTransform at
= other
.getAffineTransform();
219 final Point2D
.Double po
= new Point2D
.Double(fo
[0], fo
[1]);
220 final int o_width
= other
.getOWidth();
221 final int o_height
= other
.getOHeight();
223 at
.inverseTransform(po
, po
);
224 } catch (NoninvertibleTransformException nite
) {
228 if (po
.x
>= 0 && po
.x
< o_width
229 && po
.y
>= 0 && po
.y
< o_height
) {
230 return computeWeight((float)po
.x
, (float)po
.y
, o_width
, o_height
);
235 // Then inverse the coordinate transform
237 fo
[0] = (float) po
.x
;
238 fo
[1] = (float) po
.y
;
239 mesh
.applyInverseInPlace(fo
);
240 return computeWeight(fo
[0], fo
[1], o_width
, o_height
);
241 } catch (NoninvertibleModelException nime
) {
242 // outside boundaries