Skip to content

Commit afbd8a3

Browse files
committed
add Binary morphological filters plugins (2D and 3D)
1 parent e8abcdc commit afbd8a3

3 files changed

Lines changed: 608 additions & 0 deletions

File tree

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
/*-
2+
* #%L
3+
* Mathematical morphology library and plugins for ImageJ/Fiji.
4+
* %%
5+
* Copyright (C) 2014 - 2017 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 ij.IJ;
25+
import ij.ImagePlus;
26+
import ij.ImageStack;
27+
import ij.WindowManager;
28+
import ij.gui.GenericDialog;
29+
import ij.plugin.PlugIn;
30+
import inra.ijpb.algo.DefaultAlgoListener;
31+
import inra.ijpb.morphology.BinaryMorphology;
32+
import inra.ijpb.morphology.binary.DistanceMapBinaryClosing3D;
33+
import inra.ijpb.morphology.binary.DistanceMapBinaryDilation3D;
34+
import inra.ijpb.morphology.binary.DistanceMapBinaryErosion3D;
35+
import inra.ijpb.morphology.binary.DistanceMapBinaryOpening3D;
36+
import inra.ijpb.util.IJUtils;
37+
38+
/**
39+
* Morphological filtering for 3D binary images, using ball-shaped structuring
40+
* element with user-specified radius.
41+
*
42+
* Morphological operations are computed using thresholding distance maps.
43+
*
44+
* @see net.ijt.BinaryMorphology
45+
*
46+
* @author David Legland
47+
*
48+
*/
49+
50+
public class BinaryMorphologicalFilter3DPlugin implements PlugIn
51+
{
52+
enum Operation
53+
{
54+
EROSION("Erosion"),
55+
DILATION("Dilation"),
56+
OPENING("Opening"),
57+
CLOSING("Closing");
58+
59+
String label;
60+
61+
Operation(String label)
62+
{
63+
this.label = label;
64+
}
65+
66+
public ImageStack process(ImageStack image, double radius)
67+
{
68+
switch(this)
69+
{
70+
case EROSION:
71+
{
72+
DistanceMapBinaryErosion3D algo = new DistanceMapBinaryErosion3D(radius);
73+
DefaultAlgoListener.monitor(algo);
74+
return algo.processBinary(image);
75+
}
76+
case DILATION:
77+
{
78+
DistanceMapBinaryDilation3D algo = new DistanceMapBinaryDilation3D(radius);
79+
DefaultAlgoListener.monitor(algo);
80+
return algo.processBinary(image);
81+
}
82+
case OPENING:
83+
{
84+
DistanceMapBinaryOpening3D algo = new DistanceMapBinaryOpening3D(radius);
85+
DefaultAlgoListener.monitor(algo);
86+
return algo.processBinary(image);
87+
}
88+
case CLOSING:
89+
{
90+
DistanceMapBinaryClosing3D algo = new DistanceMapBinaryClosing3D(radius);
91+
DefaultAlgoListener.monitor(algo);
92+
return algo.processBinary(image);
93+
}
94+
default:
95+
{
96+
throw new RuntimeException("Can not manange this Operation: " + label);
97+
}
98+
}
99+
}
100+
101+
public String toString()
102+
{
103+
return this.label;
104+
}
105+
106+
public static String[] getAllLabels()
107+
{
108+
int n = Operation.values().length;
109+
String[] result = new String[n];
110+
111+
int i = 0;
112+
for (Operation op : Operation.values())
113+
result[i++] = op.label;
114+
115+
return result;
116+
}
117+
118+
/**
119+
* Determines the operation type from its label.
120+
*
121+
* @param opLabel
122+
* the label of the operation
123+
* @return the parsed Operation
124+
* @throws IllegalArgumentException
125+
* if label is not recognized.
126+
*/
127+
public static Operation fromLabel(String opLabel)
128+
{
129+
if (opLabel != null)
130+
opLabel = opLabel.toLowerCase();
131+
for (Operation op : Operation.values())
132+
{
133+
String cmp = op.label.toLowerCase();
134+
if (cmp.equals(opLabel))
135+
return op;
136+
}
137+
throw new IllegalArgumentException("Unable to parse Operation with label: " + opLabel);
138+
}
139+
}
140+
141+
142+
/* (non-Javadoc)
143+
* @see ij.plugin.PlugIn#run(java.lang.String)
144+
*/
145+
@Override
146+
public void run(String arg)
147+
{
148+
if ( IJ.getVersion().compareTo("1.48a") < 0 )
149+
{
150+
IJ.error( "Morphological Filter 3D", "ERROR: detected ImageJ version " + IJ.getVersion()
151+
+ ".\nThis plugin requires version 1.48a or superior, please update ImageJ!" );
152+
return;
153+
}
154+
155+
ImagePlus imagePlus = WindowManager.getCurrentImage();
156+
if (imagePlus == null)
157+
{
158+
IJ.error("No image", "Need at least one image to work");
159+
return;
160+
}
161+
162+
// create the dialog
163+
GenericDialog gd = new GenericDialog("Morphological Filters (3D)");
164+
165+
gd.addChoice("Operation", Operation.getAllLabels(), Operation.DILATION.toString());
166+
gd.addNumericField("Radius (in voxels)", 2, 1);
167+
gd.addCheckbox("Show Element", false);
168+
169+
// Could also add an option for the type of operation
170+
gd.showDialog();
171+
172+
if (gd.wasCanceled())
173+
return;
174+
175+
long t0 = System.currentTimeMillis();
176+
177+
// extract chosen parameters
178+
Operation op = Operation.fromLabel(gd.getNextChoice());
179+
double radius = gd.getNextNumber();
180+
boolean showStrel = gd.getNextBoolean();
181+
182+
// Eventually display the structuring element used for processing
183+
if (showStrel)
184+
{
185+
showStrelImage(radius);
186+
}
187+
188+
// Execute core of the plugin
189+
ImagePlus resPlus = process(imagePlus, op, radius);
190+
191+
if (resPlus == null)
192+
return;
193+
194+
// Display the result image
195+
resPlus.show();
196+
resPlus.setSlice(imagePlus.getCurrentSlice());
197+
198+
// Display elapsed time
199+
long t1 = System.currentTimeMillis();
200+
IJUtils.showElapsedTime(op.toString(), t1 - t0, imagePlus);
201+
}
202+
203+
204+
/**
205+
* Displays the current structuring element in a new ImagePlus.
206+
* @param strel the 3D structuring element to display
207+
*/
208+
private void showStrelImage(double radius)
209+
{
210+
// Size of the strel image (little bit larger than strel)
211+
int intRadius = (int) Math.ceil(radius);
212+
int sizeX = 2 * intRadius + 20;
213+
int sizeY = 2 * intRadius + 20;
214+
int sizeZ = 2 * intRadius + 20;
215+
216+
// Creates strel image by dilating a point
217+
ImageStack stack = ImageStack.create(sizeX, sizeY, sizeZ, 8);
218+
stack.setVoxel(sizeX / 2, sizeY / 2, sizeZ / 2, 255);
219+
stack = BinaryMorphology.dilationBall(stack, radius);
220+
221+
// Display strel image
222+
ImagePlus strelImage = new ImagePlus("Structuring Element", stack);
223+
strelImage.setSlice(((sizeZ - 1) / 2) + 1);
224+
strelImage.show();
225+
}
226+
227+
public ImagePlus process(ImagePlus image, Operation op, double radius)
228+
{
229+
// Check validity of parameters
230+
if (image == null)
231+
return null;
232+
233+
// extract the input stack
234+
ImageStack inputStack = image.getStack();
235+
236+
// apply morphological operation
237+
ImageStack resultStack = op.process(inputStack, radius);
238+
239+
// create the new image plus from the processor
240+
String newName = image.getShortTitle() + "-" + op.toString();
241+
ImagePlus resultPlus = new ImagePlus(newName, resultStack);
242+
resultPlus.copyScale(image);
243+
244+
// return the created array
245+
return resultPlus;
246+
}
247+
}

0 commit comments

Comments
 (0)