|
| 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