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
011import ij.IJ;
012import ij.ImagePlus;
013import ij.gui.GenericDialog;
014import ij.plugin.filter.PlugInFilter;
015import ij.process.ByteProcessor;
016import ij.process.ImageProcessor;
017import imagingbook.common.color.sets.BasicAwtColor;
018import imagingbook.common.geometry.basic.NeighborhoodType2D;
019import imagingbook.common.ij.DialogUtils;
020import imagingbook.common.ij.IjUtils;
021import imagingbook.common.ij.overlay.ColoredStroke;
022import imagingbook.common.ij.overlay.ShapeOverlayAdapter;
023import imagingbook.common.regions.BinaryRegion;
024import imagingbook.common.regions.Contour;
025import imagingbook.common.regions.RegionContourSegmentation;
026import imagingbook.core.plugin.IjPluginName;
027import imagingbook.core.jdoc.JavaDocHelp;
028import imagingbook.sampleimages.GeneralSampleImage;
029
030import java.util.List;
031
032import static imagingbook.common.ij.IjUtils.noCurrentImage;
033
034/**
035 * <p>
036 * This ImageJ plugin demonstrates the use of the class
037 * {@link RegionContourSegmentation} to perform both region labeling and contour
038 * tracing simultaneously. See Sec. 8.2.2 of [1] for additional details.
039 * Requires a binary image. Zero-value pixels are considered background, all
040 * other pixels are foreground. Display lookup tables (LUTs) are not considered.
041 * The resulting contours are displayed as a non-destructive vector overlay on
042 * the original image. Outer contours of single-pixel regions are marked by an
043 * "X". If no image is currently open, the plugin optionally loads a suitable
044 * sample image.
045 * </p>
046 * <p>
047 * This plugin also demonstrates the use of the {@link ShapeOverlayAdapter}
048 * (provided by the imagingbook library) which handles 0.5 pixel offsets for
049 * vector graphics transparently.
050 * </p>
051 * <p>
052 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing &ndash; An Algorithmic
053 * Introduction</em>, 3rd ed, Springer (2022).
054 * </p>
055 * 
056 * @author WB
057 * @version 2020/12/20
058 * @version 2022/09/27 revised overlay generation
059 * 
060 * @see RegionContourSegmentation
061 */
062@IjPluginName("Region Contours Demo")
063public class Region_Contours_Demo implements PlugInFilter, JavaDocHelp {
064        
065        /** Neighborhood type used for region segmentation (4- or 8-neighborhood). */
066        public static NeighborhoodType2D Neighborhood = NeighborhoodType2D.N8;
067        
068        /** Stroke width used for drawing contours. */
069        public static double ContourStrokeWidth = 0.25;
070        /** Color used for drawing outer contours. */
071        public static BasicAwtColor OuterContourColor = BasicAwtColor.Red;
072        /** Color used for drawing inner contours. */
073        public static BasicAwtColor InnerContourColor = BasicAwtColor.Green;
074        
075        /** Set true to list detected regions to the text console. */
076        public static boolean ListRegions = false;
077        
078        private ImagePlus im = null;
079        
080        /**
081         * Constructor, asks to open a predefined sample image if no other image is currently open.
082         */
083        public Region_Contours_Demo() {
084                if (noCurrentImage()) {
085                        DialogUtils.askForSampleImage(GeneralSampleImage.ToolsSmall);
086                }
087        }
088        
089        @Override
090        public int setup(String arg, ImagePlus im) {
091                this.im = im;
092                return DOES_8G; 
093        }
094        
095        @Override
096        public void run(ImageProcessor ip) {
097                if (!IjUtils.isBinary(ip)) {
098                                IJ.showMessage("Plugin requires a binary image!");
099                                return;
100                }
101                
102                if (!runDialog())
103                return;
104                
105                // Make sure we have a proper byte image:
106                ByteProcessor bp = ip.convertToByteProcessor();
107                
108                // Create the region segmenter / contour tracer:
109                RegionContourSegmentation seg = new RegionContourSegmentation(bp, Neighborhood);
110                
111                // Get a list of detected regions (sorted by size):
112                List<BinaryRegion> regions = seg.getRegions(true);
113                if (regions.isEmpty()) {
114                        IJ.showMessage("No regions detected!");
115                        return;
116                }
117
118                // Draw outer and inner contours for each detected region:
119                ShapeOverlayAdapter ola = new ShapeOverlayAdapter();
120                ColoredStroke outerStroke = new ColoredStroke(ContourStrokeWidth, OuterContourColor.getColor());
121                ColoredStroke innerStroke = new ColoredStroke(ContourStrokeWidth, InnerContourColor.getColor());
122                
123                for (BinaryRegion r : seg.getRegions()) {
124                        Contour oc = r.getOuterContour();
125                        ola.addShape(oc.getPolygonPath(), outerStroke);
126                        for (Contour ic : r.getInnerContours()) {
127                                ola.addShape(ic.getPolygonPath(), innerStroke);
128                        }
129                }
130                
131                im.setOverlay(ola.getOverlay());
132                
133                // Optionally list regions to console:
134                if (ListRegions) {
135                        IJ.log("\nDetected regions: " + regions.size());
136                        for (BinaryRegion R : regions) {
137                                IJ.log(R.toString());
138                        }
139                }
140        }
141        
142        // --------------------------------------------------------------------------
143        
144        private boolean runDialog() {
145                GenericDialog gd = new GenericDialog(Region_Contours_Demo.class.getSimpleName());
146                gd.addHelp(getJavaDocUrl());
147                gd.addEnumChoice("Neighborhood type", Neighborhood);
148                gd.addEnumChoice("Outer contour color", OuterContourColor);
149                gd.addEnumChoice("Inner contour color", InnerContourColor);
150                gd.addNumericField("Stroke width", ContourStrokeWidth, 1);
151
152                gd.addCheckbox("List regions", ListRegions);
153                
154                gd.showDialog();
155                if (gd.wasCanceled()) {
156                        return false;
157                }
158                
159                Neighborhood = gd.getNextEnumChoice(NeighborhoodType2D.class);
160                OuterContourColor = gd.getNextEnumChoice(BasicAwtColor.class);
161                InnerContourColor = gd.getNextEnumChoice(BasicAwtColor.class);
162                ContourStrokeWidth = gd.getNextNumber();
163                ListRegions  = gd.getNextBoolean();
164                return true;
165        }
166}