Skip to content

Commit bcf99f6

Browse files
committed
add EuclideanDistanceMapPlugin (for both 2D and 3D images)
1 parent 819de93 commit bcf99f6

2 files changed

Lines changed: 260 additions & 0 deletions

File tree

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
/*-
2+
* #%L
3+
* Mathematical morphology library and plugins for ImageJ/Fiji.
4+
* %%
5+
* Copyright (C) 2014 - 2024 INRA.
6+
* %%
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Lesser General Public License as
9+
* published by the Free Software Foundation, either version 3 of the
10+
* License, or (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Lesser Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Lesser Public
18+
* License along with this program. If not, see
19+
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
20+
* #L%
21+
*/
22+
package inra.ijpb.plugins;
23+
24+
import java.awt.AWTEvent;
25+
26+
import ij.ImagePlus;
27+
import ij.ImageStack;
28+
import ij.gui.DialogListener;
29+
import ij.gui.GenericDialog;
30+
import ij.measure.Calibration;
31+
import ij.plugin.filter.ExtendedPlugInFilter;
32+
import ij.plugin.filter.PlugInFilterRunner;
33+
import ij.process.ImageProcessor;
34+
import inra.ijpb.algo.DefaultAlgoListener;
35+
import inra.ijpb.data.image.Images3D;
36+
import inra.ijpb.label.distmap.SaitoToriwakiDistanceTransform2DFloat;
37+
import inra.ijpb.label.distmap.SaitoToriwakiDistanceTransform3DFloat;
38+
import inra.ijpb.util.IJUtils;
39+
40+
/**
41+
* Compute distance map, with possibility to choose the chamfer mask, the result
42+
* type (integer or floating point), and to normalize result or not.
43+
*
44+
* Can process either binary images, or label images (that can be coded with 8,
45+
* 16 or 32 bits).
46+
*
47+
* @see inra.ijpb.label.distmap.DistanceTransform2D
48+
* @see inra.ijpb.binary.distmap.ChamferMask2D
49+
* @see inra.ijpb.label.distmap.ChamferDistanceTransform2DShort
50+
* @see inra.ijpb.label.distmap.ChamferDistanceTransform2DFloat
51+
*
52+
* @author dlegland
53+
*/
54+
public class EuclideanDistanceMapPlugin
55+
implements ExtendedPlugInFilter, DialogListener
56+
{
57+
/** Apparently, it's better to store flags in plugin */
58+
private int flags = DOES_8G | DOES_16 | DOES_32 | KEEP_PREVIEW | FINAL_PROCESSING;
59+
PlugInFilterRunner pfr;
60+
int nPasses;
61+
boolean previewing = false;
62+
63+
/** need to keep the instance of ImagePlus */
64+
private ImagePlus imagePlus;
65+
66+
/** keep the original image, to restore it after the preview */
67+
private ImageProcessor baseImage;
68+
69+
// private boolean floatProcessing = false;
70+
private boolean normalize = false;
71+
72+
/** Keep instance of result image */
73+
private ImageProcessor result;
74+
75+
/**
76+
* Called at the beginning of the process to know if the plugin can be run
77+
* with current image, and at the end to finalize.
78+
*/
79+
public int setup(String arg, ImagePlus imp)
80+
{
81+
// Special case of plugin called to finalize the process
82+
if (arg.equals("final"))
83+
{
84+
// replace the preview image by the original image
85+
imagePlus.setProcessor(baseImage);
86+
imagePlus.draw();
87+
88+
// Create a new ImagePlus with the filter result
89+
ImagePlus resPlus = process(imagePlus, normalize);
90+
resPlus.show();
91+
92+
if (imagePlus.getStackSize() > 1)
93+
{
94+
resPlus.setSlice(imagePlus.getCurrentSlice());
95+
}
96+
return DONE;
97+
}
98+
99+
return flags;
100+
}
101+
102+
public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr)
103+
{
104+
// Store user data
105+
this.imagePlus = imp;
106+
this.baseImage = imp.getProcessor().duplicate();
107+
this.pfr = pfr;
108+
109+
// Create a new generic dialog with appropriate options
110+
GenericDialog gd = new GenericDialog("Euclidean Distance Transform");
111+
gd.addCheckbox("Normalize", true);
112+
gd.addPreviewCheckbox(pfr);
113+
gd.addDialogListener(this);
114+
previewing = true;
115+
gd.addHelp("https://imagej.net/MorphoLibJ");
116+
gd.showDialog();
117+
previewing = false;
118+
119+
// test cancel
120+
if (gd.wasCanceled())
121+
return DONE;
122+
123+
// set up current parameters
124+
normalize = gd.getNextBoolean();
125+
126+
return flags;
127+
}
128+
129+
/**
130+
* Called when a dialog widget has been modified: recomputes option values
131+
* from dialog content.
132+
*/
133+
public boolean dialogItemChanged(GenericDialog gd, AWTEvent evt)
134+
{
135+
// set up current parameters
136+
normalize = gd.getNextBoolean();
137+
138+
return true;
139+
}
140+
141+
public void setNPasses(int nPasses)
142+
{
143+
this.nPasses = nPasses;
144+
}
145+
146+
/**
147+
* Uses the current settings of the algorithm to process the current (2D)
148+
* image.
149+
*
150+
* This method is used to compute the "preview" image.
151+
*/
152+
public void run(ImageProcessor image)
153+
{
154+
result = process(image, normalize);
155+
156+
if (previewing)
157+
{
158+
// Fill up the values of original image with values of the result
159+
double valMax = result.getMax();
160+
for (int i = 0; i < image.getPixelCount(); i++)
161+
{
162+
image.set(i, (int) (255 * result.getf(i) / valMax));
163+
}
164+
image.resetMinAndMax();
165+
if (image.isInvertedLut())
166+
image.invertLut();
167+
}
168+
}
169+
170+
/**
171+
* Computes the result of Euclidean distance map algorithm on the specified
172+
* image.
173+
*
174+
* @param imagePlus
175+
* the image to use as a binary mask
176+
* @param normalize
177+
* the option to compute Euclidean distance (true) or squared
178+
* Euclidean distance (false)
179+
* @return the Eucldiean distance map, in a 32-bits floating point 2D or 3D
180+
* image.
181+
*/
182+
private ImagePlus process(ImagePlus imagePlus, boolean normalize)
183+
{
184+
String newName = createResultImageName(imagePlus);
185+
186+
long t0 = System.currentTimeMillis();
187+
188+
ImagePlus resultPlus;
189+
if (imagePlus.getStackSize() == 1)
190+
{
191+
// compute array of pixel dimensions
192+
Calibration calib = imagePlus.getCalibration();
193+
double[] spacings = new double[] { calib.pixelWidth, calib.pixelHeight };
194+
195+
// Initialize calculator
196+
SaitoToriwakiDistanceTransform2DFloat algo = new SaitoToriwakiDistanceTransform2DFloat(normalize);
197+
DefaultAlgoListener.monitor(algo);
198+
199+
// Compute distance on specified images
200+
ImageProcessor res = algo.distanceMap(imagePlus.getProcessor(), spacings);
201+
202+
res.resetMinAndMax();
203+
if (res.isInvertedLut())
204+
res.invertLut();
205+
206+
// Create a new ImagePlus with the filter result
207+
resultPlus = new ImagePlus(newName, res);
208+
}
209+
else
210+
{
211+
// compute array of pixel dimensions
212+
Calibration calib = imagePlus.getCalibration();
213+
double[] spacings = new double[] { calib.pixelWidth, calib.pixelHeight, calib.pixelDepth };
214+
215+
// Initialize calculator
216+
SaitoToriwakiDistanceTransform3DFloat algo = new SaitoToriwakiDistanceTransform3DFloat(normalize);
217+
DefaultAlgoListener.monitor(algo);
218+
219+
// Compute distance on specified images
220+
ImageStack res = algo.distanceMap(imagePlus.getStack(), spacings);
221+
222+
// Create a new ImagePlus with the filter result
223+
resultPlus = new ImagePlus(newName, res);
224+
225+
// calibrate display range of distances
226+
double[] distExtent = Images3D.findMinAndMax(resultPlus);
227+
resultPlus.setDisplayRange(0, distExtent[1]);
228+
}
229+
230+
// Display elapsed time
231+
long t1 = System.currentTimeMillis();
232+
IJUtils.showElapsedTime("Euclidean distance map", t1 - t0, imagePlus);
233+
234+
resultPlus.copyScale(imagePlus);
235+
return resultPlus;
236+
}
237+
238+
private ImageProcessor process(ImageProcessor image, boolean normalize)
239+
{
240+
// Initialize calculator
241+
SaitoToriwakiDistanceTransform2DFloat algo = new SaitoToriwakiDistanceTransform2DFloat(normalize);
242+
243+
// compute array of pixel dimensions
244+
Calibration calib = imagePlus.getCalibration();
245+
double[] spacings = new double[] { calib.pixelWidth, calib.pixelHeight };
246+
247+
// add monitoring
248+
DefaultAlgoListener.monitor(algo);
249+
250+
// Compute distance on specified images
251+
return algo.distanceMap(image, spacings);
252+
}
253+
254+
255+
private static String createResultImageName(ImagePlus baseImage)
256+
{
257+
return baseImage.getShortTitle() + "-dist";
258+
}
259+
}

‎src/main/resources/plugins.config‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ Plugins>MorphoLibJ>Analyze, "Label Overlap Measures", inra.ijpb.plugins.LabelOve
5151
# List of entries for binary images sub-menu
5252
Plugins>MorphoLibJ>Binary Images, "Connected Components Labeling", inra.ijpb.plugins.LabelingPlugin
5353
Plugins>MorphoLibJ>Binary Images, "-"
54+
Plugins>MorphoLibJ>Binary Images, "Euclidean Distance Map", inra.ijpb.plugins.EuclideanDistanceMapPlugin
5455
Plugins>MorphoLibJ>Binary Images, "Chamfer Distance Map", inra.ijpb.plugins.ChamferDistanceMapPlugin
5556
Plugins>MorphoLibJ>Binary Images, "Chamfer Distance Map 3D", inra.ijpb.plugins.ChamferDistanceMap3DPlugin
5657
Plugins>MorphoLibJ>Binary Images, "Geodesic Distance Map", inra.ijpb.plugins.GeodesicDistanceMapPlugin

0 commit comments

Comments
 (0)