001/******************************************************************************* 002 * This software is provided as a supplement to the authors' textbooks on digital 003 * image processing published by Springer-Verlag in various languages and editions. 004 * Permission to use and distribute this software is granted under the BSD 2-Clause 005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause). 006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved. 007 * Visit https://imagingbook.com for additional details. 008 ******************************************************************************/ 009package Ch08_Binary_Regions; 010 011 012import ij.IJ; 013import ij.ImagePlus; 014import ij.gui.GenericDialog; 015import ij.plugin.filter.PlugInFilter; 016import ij.process.ByteProcessor; 017import ij.process.ColorProcessor; 018import ij.process.ImageProcessor; 019import ij.process.ShortProcessor; 020import imagingbook.common.color.iterate.RandomHueGenerator; 021import imagingbook.common.geometry.basic.NeighborhoodType2D; 022import imagingbook.common.ij.DialogUtils; 023import imagingbook.common.regions.BinaryRegion; 024import imagingbook.common.regions.BinaryRegionSegmentation; 025import imagingbook.common.regions.BreadthFirstSegmentation; 026import imagingbook.common.regions.DepthFirstSegmentation; 027import imagingbook.common.regions.RecursiveSegmentation; 028import imagingbook.common.regions.RegionContourSegmentation; 029import imagingbook.common.regions.SequentialSegmentation; 030import imagingbook.core.plugin.IjPluginName; 031import imagingbook.core.jdoc.JavaDocHelp; 032import imagingbook.sampleimages.GeneralSampleImage; 033 034import java.util.List; 035 036import static imagingbook.common.ij.IjUtils.noCurrentImage; 037 038/** 039 * <p> 040 * This ImageJ plugin demonstrates the use of various region labeling techniques 041 * provided by the imagingbook "regions" package: 042 * </p> 043 * <ul> 044 * <li>{@link BreadthFirstSegmentation},</li> 045 * <li>{@link DepthFirstSegmentation},</li> 046 * <li>{@link RecursiveSegmentation},</li> 047 * <li>{@link RegionContourSegmentation},</li> 048 * <li>{@link SequentialSegmentation}.</li> 049 * </ul> 050 * <p> 051 * See Sec. 8.1 of [1] for additional details. One of four labeling types can be 052 * selected (see the {@link #run(ImageProcessor)} method). All methods should 053 * produce the same results (except {@link RegionContourSegmentation}, which may 054 * run out of memory easily). Requires a binary image. Zero-value pixels are 055 * considered background, all other pixels are foreground. Display lookup tables 056 * (LUTs) are not considered. The plugin creates a new image with connected 057 * components either randomly-colored or region labels shown as gray values. The 058 * original image is not modified. 059 * If no image is currently open, the plugin optionally loads a suitable 060 * sample image. 061 * </p> 062 * <p> 063 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing – An Algorithmic 064 * Introduction</em>, 3rd ed, Springer (2022). 065 * </p> 066 * 067 * @author WB 068 * @version 2020/12/20 069 * @version 2022/09/28 revised 070 */ 071@IjPluginName("Region Segmentation Demo") 072public class Region_Segmentation_Demo implements PlugInFilter, JavaDocHelp { 073 074 /** Enum type for various region labeling methods. */ 075 public enum SegmentationMethod { 076 Recursive, 077 DepthFirst, 078 BreadthFirst, 079 Sequential, 080 RegionAndContours, 081 } 082 083 /** The region labeling method to be used. */ 084 public static SegmentationMethod Method = SegmentationMethod.BreadthFirst; 085 /** Neighborhood type used for region segmentation (4- or 8-neighborhood). */ 086 public static NeighborhoodType2D Neighborhood = NeighborhoodType2D.N8; 087 088 /** Set true to randomly color segmented regions. */ 089 public static boolean ColorComponents = true; 090 /** Set true to to list segmented regions to the console. */ 091 public static boolean ListRegions = false; 092 093 public static int RandomSeed = 0; 094 095 private ImagePlus im; 096 097 /** 098 * Constructor, asks to open a predefined sample image if no other image 099 * is currently open. 100 */ 101 public Region_Segmentation_Demo() { 102 if (noCurrentImage()) { 103 DialogUtils.askForSampleImage(GeneralSampleImage.ToolsSmall); 104 } 105 } 106 107 @Override 108 public int setup(String arg, ImagePlus im) { 109 this.im = im; 110 return DOES_8G + NO_CHANGES; 111 } 112 113 @Override 114 public void run(ImageProcessor ip) { 115 116 if (!runDialog()) 117 return; 118 119 if (Method == SegmentationMethod.Recursive && 120 !IJ.showMessageWithCancel("Recursive labeling", 121 "This may run out of stack memory!\n" + "Continue?")) { 122 return; 123 } 124 125 // Copy the original to a new byte image: 126 ByteProcessor bp = ip.convertToByteProcessor(false); 127 128 BinaryRegionSegmentation segmentation = null; 129 switch(Method) { 130 case BreadthFirst : 131 segmentation = new BreadthFirstSegmentation(bp, Neighborhood); break; 132 case DepthFirst : 133 segmentation = new DepthFirstSegmentation(bp, Neighborhood); break; 134 case Sequential : 135 segmentation = new SequentialSegmentation(bp, Neighborhood); break; 136 case RegionAndContours : 137 segmentation = new RegionContourSegmentation(bp, Neighborhood); break; 138 case Recursive : 139 segmentation = new RecursiveSegmentation(bp, Neighborhood); break; 140 } 141 142 if (!segmentation.isSegmented()) { 143 IJ.showMessage("Something went wrong, segmentation failed!"); 144 return; 145 } 146 147 // Retrieve a list of detected regions (sorted by size): 148 List<BinaryRegion> regions = segmentation.getRegions(true); 149 150 if (regions.isEmpty()) { 151 IJ.showMessage("No regions detected!"); 152 return; 153 } 154 155 // Show the resulting labeling as a color or gray image: 156 String title = im.getShortTitle() + "-" + Method.name(); 157 ImageProcessor labelIp = ColorComponents ? //Display.makeLabelImage(segmenter, ColorRegions); 158 makeLabelImageColor(segmentation) : makeLabelImageGray(segmentation); 159 (new ImagePlus(title, labelIp)).show(); 160 161 if (ListRegions) { 162 IJ.log("Regions sorted by size: " + regions.size()); 163 for (BinaryRegion r: regions) { 164 IJ.log(r.toString()); 165 } 166 } 167 168 } 169 170 // --------------------------------------------------------------------- 171 172 /** 173 * Returns a color image of the specified segmentation with randomly 174 * colored components. 175 * 176 * @param segmenter a binary region segmentation 177 * @return a {@link ColorProcessor} with randomly colored regions 178 */ 179 private ColorProcessor makeLabelImageColor(BinaryRegionSegmentation segmenter) { 180 int minLabel = segmenter.getMinLabel(); 181 int maxLabel = segmenter.getMaxLabel(); 182 183 // set up a table of random colors, one for each label: 184 RandomHueGenerator rcg = new RandomHueGenerator(RandomSeed); 185 int[] labelColor = new int[maxLabel + 1]; 186 for (int i = minLabel; i <= maxLabel; i++) { 187 labelColor[i] = rcg.next().getRGB(); 188 } 189 190 final int width = segmenter.getWidth(); 191 final int height = segmenter.getHeight(); 192 193 ColorProcessor cp = new ColorProcessor(width, height); 194 for (int v = 0; v < height; v++) { 195 for (int u = 0; u < width; u++) { 196 int lb = segmenter.getLabel(u, v); 197 if (lb >= 0 && lb < labelColor.length) { 198 cp.set(u, v, labelColor[lb]); 199 } 200 } 201 } 202 return cp; 203 } 204 205 /** 206 * Returns a 16-bit unsigned gray image of the specified segmentation with component 207 * pixels set to the associated segmentation labels. 208 * 209 * @param segmenter a binary region segmentation 210 * @return a 16-bit {@link ShortProcessor} with the original region labels 211 */ 212 private ShortProcessor makeLabelImageGray(BinaryRegionSegmentation segmenter) { 213 int width = segmenter.getWidth(); 214 int height = segmenter.getHeight(); 215 ShortProcessor sp = new ShortProcessor(width, height); 216 for (int v = 0; v < height; v++) { 217 for (int u = 0; u < width; u++) { 218 int lb = segmenter.getLabel(u, v); 219 sp.set(u, v, (lb >= 0) ? lb : 0); 220 } 221 } 222 sp.resetMinAndMax(); 223 return sp; 224 } 225 226 // --------------------------------------------------------------------- 227 228 private boolean runDialog() { 229 GenericDialog gd = new GenericDialog(Region_Segmentation_Demo.class.getSimpleName()); 230 gd.addHelp(getJavaDocUrl()); 231 gd.addEnumChoice("Segmentation method", Method); 232 gd.addEnumChoice("Neighborhood type", Neighborhood); 233 gd.addCheckbox("Color components", ColorComponents); 234 gd.addCheckbox("List regions", ListRegions); 235 gd.addNumericField("Random seed (0 = none)", RandomSeed, 0); 236 237 gd.showDialog(); 238 if (gd.wasCanceled()) { 239 return false; 240 } 241 242 Method = gd.getNextEnumChoice(SegmentationMethod.class); 243 Neighborhood = gd.getNextEnumChoice(NeighborhoodType2D.class); 244 ColorComponents = gd.getNextBoolean(); 245 ListRegions = gd.getNextBoolean(); 246 RandomSeed = (int) gd.getNextNumber(); 247 return true; 248 } 249 250 251} 252 253 254