1 /** Released under the GPL. Use at your own risk.
3 * @author Albert Cardona, Johannes Schindelin
5 package ini
.trakem2
.imaging
;
7 import java
.util
.concurrent
.Callable
;
8 import java
.util
.concurrent
.ExecutionException
;
9 import java
.util
.concurrent
.ExecutorService
;
10 import java
.util
.concurrent
.Executors
;
11 import java
.util
.concurrent
.Future
;
13 import mpicbg
.imglib
.algorithm
.OutputAlgorithm
;
14 import mpicbg
.imglib
.container
.array
.ArrayContainerFactory
;
15 import mpicbg
.imglib
.cursor
.Cursor
;
16 import mpicbg
.imglib
.cursor
.LocalizableByDimCursor
;
17 import mpicbg
.imglib
.image
.Image
;
18 import mpicbg
.imglib
.image
.ImageFactory
;
19 import mpicbg
.imglib
.type
.logic
.BitType
;
20 import mpicbg
.imglib
.type
.numeric
.integer
.IntType
;
23 /** Given two binary images of the same dimensions,
24 * generate an interpolated image that sits somewhere
25 * in between, as specified by the weight.
27 * For each binary image, the edges are found
28 * and then each pixel is assigned a distance to the nearest edge.
29 * Inside, distance values are positive; outside, negative.
30 * Then both processed images are compared, and wherever
31 * the weighted sum is larger than zero, the result image
32 * gets a pixel set to true (or white, meaning inside).
34 * A weight of zero means that the first image is not present at all
35 * in the interpolated image;
36 * a weight of one means that the first image is present exclusively.
38 * The code was originally created by Johannes Schindelin
39 * in the VIB's vib.BinaryInterpolator class, for ij.ImagePlus.
41 public class BinaryInterpolation2D
implements OutputAlgorithm
<BitType
>
44 final private Image
<BitType
> img1
, img2
;
46 private Image
<BitType
> interpolated
;
47 private String errorMessage
;
48 private IDT2D idt1
, idt2
;
50 public BinaryInterpolation2D(final Image
<BitType
> img1
, final Image
<BitType
> img2
, final float weight
) {
56 /** NOT thread safe, stateful. */
57 private final class IDT2D
{
58 final Image
<IntType
> result
;
60 final int[] position
= new int[2];
61 final LocalizableByDimCursor
<BitType
> csrc
;
62 final LocalizableByDimCursor
<IntType
> cout
;
64 IDT2D(final Image
<BitType
> img
) {
65 this.w
= img
.getDimension(0);
66 this.h
= img
.getDimension(1);
67 ImageFactory
<IntType
> f
= new ImageFactory
<IntType
>(new IntType(), new ArrayContainerFactory());
68 this.result
= f
.createImage(new int[]{w
, h
});
70 // Set all result pixels to infinity
71 final int infinity
= (w
+ h
) * 9;
72 for (final IntType v
: this.result
) {
76 // init result pixels with those of the image:
77 this.csrc
= img
.createLocalizableByDimCursor();
78 this.cout
= result
.createLocalizableByDimCursor();
81 for (int y
= 0; y
< h
; y
++) {
82 for (int x
= 0; x
< w
; x
++) {
83 if (isBoundary(x
, y
)) {
84 setOutValueAt(x
, y
, 0);
86 } else if (isJustOutside(x
, y
)) {
87 setOutValueAt(x
, y
, -1);
100 private final void setPosition(final LocalizableByDimCursor
<?
> c
, final int x
, final int y
) {
103 c
.setPosition(position
);
106 private final void setOutValueAt(final int x
, final int y
, final int value
) {
107 setPosition(cout
, x
, y
);
108 cout
.getType().set(value
);
111 private final int getSrcValueAt(final int x
, final int y
) {
112 setPosition(csrc
, x
, y
);
113 return csrc
.getType().get() ?
1 : 0;
116 private final int getOutValueAt(final int x
, final int y
) {
117 setPosition(cout
, x
, y
);
118 return cout
.getType().get();
121 // reads from result, writes to result
122 private final void idt(final int x
, final int y
, final int dx
, final int dy
) {
123 if (x
+ dx
< 0 || y
+ dy
< 0 ||
124 x
+ dx
>= w
|| y
+ dy
>= h
) {
127 int value
= getOutValueAt(x
+ dx
, y
+ dy
);
128 final int distance
= (dx
== 0 || dy
== 0 ?
3 : 4);
129 value
+= distance
* (value
< 0 ?
-1 : 1);
130 setPosition(cout
, x
, y
);
131 if (Math
.abs(cout
.getType().get()) > Math
.abs(value
)) {
132 cout
.getType().set(value
);
136 private final void propagate() {
137 for (int j
= 0; j
< h
; j
++)
138 for (int i
= 0; i
< w
; i
++) {
144 for (int j
= h
- 1; j
>= 0; j
--)
145 for (int i
= w
- 1; i
>= 0; i
--) {
151 for (int i
= w
- 1; i
>= 0; i
--)
152 for (int j
= h
- 1; j
>= 0; j
--) {
158 for (int i
= 0; i
< w
; i
++)
159 for (int j
= 0; j
< h
; j
++) {
166 private final boolean isBoundary(final int x
, final int y
) {
167 if (getSrcValueAt(x
, y
) == 0)
169 if (x
<= 0 || getSrcValueAt(x
- 1, y
) == 0)
171 if (x
>= w
- 1 || getSrcValueAt(x
+ 1, y
) == 0)
173 if (y
<= 0 || getSrcValueAt(x
, y
- 1) == 0)
175 if (y
>= h
- 1 || getSrcValueAt(x
, y
+ 1) == 0)
177 if (x
<= 0 || y
<= 0 || getSrcValueAt(x
- 1, y
- 1) == 0)
179 if (x
<= 0 || y
>= h
- 1 || getSrcValueAt(x
- 1, y
+ 1) == 0)
181 if (x
>= w
- 1 || y
<= 0 || getSrcValueAt(x
+ 1, y
- 1) == 0)
183 if (x
>= w
- 1 || y
>= h
- 1 || getSrcValueAt(x
+ 1, y
+ 1) == 0)
188 private final boolean isJustOutside(final int x
, final int y
) {
189 if (getSrcValueAt(x
, y
) != 0)
191 if (x
> 0 && getSrcValueAt(x
- 1, y
) != 0)
193 if (x
< w
- 1 && getSrcValueAt(x
+ 1, y
) != 0)
195 if (y
> 0 && getSrcValueAt(x
, y
- 1) != 0)
197 if (y
< h
- 1 && getSrcValueAt(x
, y
+ 1) != 0)
199 if (x
> 0 && y
> 0 && getSrcValueAt(x
- 1, y
- 1) != 0)
201 if (x
> 0 && y
< h
- 1 && getSrcValueAt(x
- 1, y
+ 1) != 0)
203 if (x
< w
- 1 && y
> 0 && getSrcValueAt(x
+ 1, y
- 1) != 0)
205 if (x
< w
- 1 && y
< h
- 1 && getSrcValueAt(x
+ 1, y
+ 1) != 0)
212 public Image
<BitType
> getResult() {
217 public boolean checkInput() {
218 if (img1
.getNumDimensions() < 2 || img2
.getNumDimensions() < 2) {
219 errorMessage
= "Need at least 2 dimensions";
222 if (img1
.getDimension(0) != img2
.getDimension(0) || img1
.getDimension(1) != img2
.getDimension(1)) {
223 errorMessage
= "Dimensions do not match";
226 if (weight
< 0 || weight
> 1) {
227 errorMessage
= "Weight must be between 0 and 1, both inclusive.";
234 public String
getErrorMessage() {
238 private final class NewITD2D
implements Callable
<IDT2D
> {
239 private final Image
<BitType
> img
;
240 NewITD2D(final Image
<BitType
> img
) {
244 public IDT2D
call() throws Exception
{
245 return new IDT2D(img
);
249 /** After changing the weight, it's totally valid to call process() again,
250 * and then getResult(). */
251 public void setWeight(final float weight
) throws IllegalArgumentException
{
252 if (weight
< 0 || weight
> 1) {
253 throw new IllegalArgumentException("Weight must be between 0 and 1, both inclusive.");
255 this.weight
= weight
;
259 public boolean process() {
260 this.interpolated
= process(this.weight
);
261 return null != this.interpolated
;
264 /** The first time, it will prepare the distance transform images, which are computed only once. */
265 public Image
<BitType
> process(final float weight
)
267 synchronized (this) {
268 if (null == idt1
|| null == idt2
) {
269 ExecutorService exec
= Executors
.newFixedThreadPool(Math
.min(2, Runtime
.getRuntime().availableProcessors()));
270 Future
<IDT2D
> fu1
= exec
.submit(new NewITD2D(img1
));
271 Future
<IDT2D
> fu2
= exec
.submit(new NewITD2D(img2
));
275 this.idt1
= fu1
.get();
276 this.idt2
= fu2
.get();
277 } catch (InterruptedException ie
) {
278 throw new RuntimeException(ie
);
279 } catch (ExecutionException e
) {
280 throw new RuntimeException(e
);
285 // Cannot just img1.createNewImage() because the container may not be able to receive data,
286 // such as the ShapeList container.
287 final ImageFactory
<BitType
> f
= new ImageFactory
<BitType
>(new BitType(), new ArrayContainerFactory());
288 final Image
<BitType
> interpolated
= f
.createImage(new int[]{img1
.getDimension(0), img1
.getDimension(1)});
290 if (img1
.getContainer().compareStorageContainerCompatibility(img2
.getContainer())) {
291 final Cursor
<IntType
> c1
= idt1
.result
.createCursor();
292 final Cursor
<IntType
> c2
= idt2
.result
.createCursor();
293 final Cursor
<BitType
> ci
= interpolated
.createCursor();
295 while (ci
.hasNext()) {
300 if ((c1
.getType().get() * weight
) + (c2
.getType().get() * (1 - weight
)) > 0) {
301 ci
.getType().set(true);
309 System
.out
.println("using option 2");
310 final LocalizableByDimCursor
<IntType
> c1
= idt1
.result
.createLocalizableByDimCursor();
311 final LocalizableByDimCursor
<IntType
> c2
= idt2
.result
.createLocalizableByDimCursor();
312 final LocalizableByDimCursor
<BitType
> ci
= interpolated
.createLocalizableByDimCursor();
314 while (ci
.hasNext()) {
319 if (0 <= c1
.getType().get() * weight
+ c2
.getType().get() * (1 - weight
)) {
320 ci
.getType().set(true);