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 Ch26_MSER; 010 011import ij.IJ; 012import ij.ImagePlus; 013import ij.ImageStack; 014import ij.gui.GenericDialog; 015import ij.plugin.filter.PlugInFilter; 016import ij.process.ByteProcessor; 017import ij.process.ColorProcessor; 018import ij.process.ImageProcessor; 019import imagingbook.common.color.sets.CssColor; 020import imagingbook.common.ij.DialogUtils; 021import imagingbook.common.mser.MserData; 022import imagingbook.common.mser.components.Component; 023import imagingbook.common.mser.components.ComponentTree; 024import imagingbook.common.mser.components.ComponentTree.Method; 025import imagingbook.common.mser.components.PixelMap.Pixel; 026import imagingbook.core.jdoc.JavaDocHelp; 027import imagingbook.core.resource.ImageResource; 028import imagingbook.sampleimages.GeneralSampleImage; 029 030import java.awt.Color; 031 032import static imagingbook.common.ij.IjUtils.noCurrentImage; 033 034/** 035 * <p> 036 * This ImageJ plugin creates the component tree of the given image and reconstructs the associated threshold stack by 037 * coloring the individual components. The component tree is the basis of the MSER feature detection algorithm. The user 038 * may choose from two different component tree algorithms: 039 * </p> 040 * <ol> 041 * <li> "global immersion" (quasi-linear time) or </li> 042 * <li>"local flooding" (linear time).</li> 043 * </ol> 044 * <p> 045 * See Sec. 26.2 of [1] for details. If no image is currently open, the user is asked to load a predefined sample image. 046 * Note: This implementation is quite inefficient since the image pixels contained in each tree component must be 047 * collected recursively at each threshold level for visualization. Use on small images only! 048 * </p> 049 * <p> 050 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing – An Algorithmic Introduction</em>, 051 * 3rd ed, Springer (2022). 052 * </p> 053 * 054 * @author WB 055 * @version 2023/01/04 056 */ 057public class Show_Component_Tree_Stack implements PlugInFilter, JavaDocHelp { 058 059 private static ImageResource SampleImage = GeneralSampleImage.DotBlotSmall; 060 private static ComponentTree.Method method = Method.LinearTime; 061 private static Color[] COLORS = CssColor.SelectColors; 062 063 /** 064 * Constructor, asks to open a predefined sample image if no other image is currently open. 065 */ 066 public Show_Component_Tree_Stack() { 067 if (noCurrentImage()) { 068 DialogUtils.askForSampleImage(SampleImage); 069 } 070 } 071 072 @Override 073 public int setup(String arg0, ImagePlus im) { 074 return DOES_8G + NO_CHANGES; 075 } 076 077 @Override 078 public void run(ImageProcessor ip) { 079 if (!runDialog()) { 080 return; 081 } 082 ComponentTree<MserData> compTree = ComponentTree.from((ByteProcessor) ip, method); 083 ImageStack compStack = makeComponentStack(compTree, ip.getWidth(), ip.getHeight()); 084 ImagePlus stackIm = new ImagePlus("Component Tree Stack - " + method.name(), compStack); 085 stackIm.show(); 086 } 087 088 // ------------------------------------------------------------------- 089 090 private ImageStack makeComponentStack(ComponentTree<?> rt, int width, int height) { 091 ImageStack stack = new ImageStack(width, height); 092 for (int level = 0; level < 256; level++) { 093 IJ.showProgress(level, 256); 094 ColorProcessor cp = new ColorProcessor(width, height); 095 fillComponentImage(rt, cp, level); 096 stack.addSlice("Level " + level, cp); 097 } 098 IJ.showProgress(256, 256); 099 return stack; 100 } 101 102 /** 103 * Creates a thresholded image with positions set if pixelvalue <= level. 104 */ 105 private void fillComponentImage(ComponentTree<?> rt, ColorProcessor cp, int level) { 106 cp.setColor(Color.white); 107 cp.fill(); 108 for (Component<?> c : rt.getComponents()) { 109 if (c.getLevel() <= level) { 110 int col = COLORS[c.ID % COLORS.length].getRGB(); 111 cp.setColor(col); 112 for (Pixel p : c.getAllPixels()) { 113 cp.set(p.x, p.y, col); 114 } 115 } 116 } 117 } 118 119 // ------------------------------------------------------------------- 120 121 private boolean runDialog() { 122 GenericDialog gd = new GenericDialog("Settings"); 123 gd.addHelp(getJavaDocUrl()); 124 gd.addEnumChoice("Component tree method", method); 125 126 gd.showDialog(); 127 if (gd.wasCanceled()) 128 return false; 129 130 method = gd.getNextEnumChoice(Method.class); 131 return true; 132 } 133 134 135}