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 imagingbook.common.mser.components;
010
011import ij.process.ByteProcessor;
012import imagingbook.common.mser.MserData;
013import imagingbook.common.mser.components.PixelMap.Pixel;
014
015import java.io.ByteArrayOutputStream;
016import java.io.PrintStream;
017import java.util.Collection;
018import java.util.HashSet;
019import java.util.Iterator;
020import java.util.LinkedList;
021import java.util.Set;
022
023/**
024 * This class represents a tree of extremal image components.
025 * 
026 * @param <T> the data type of components (e.g., {@link MserData})
027 * @author WB
028 * @version 2022/11/19
029 */
030public abstract class ComponentTree<T> implements Iterable<Component<T>> {
031        
032        /**
033         * Enumeration specifying the method (algorithm) for building the component tree.
034         */
035        public enum Method {
036                /** Specifies the classic (global immersion) algorithm, see {@link ComponentTreeGlobalImmersion}. */
037                GlobalImmersion,
038                /** Specifies the linear-time algorithm, see {@link ComponentTreeLinearTime}. */
039                LinearTime;
040        }
041        
042        // --------------------------------------------------------------
043        
044        /** Constructor (non-public). */
045        ComponentTree() {}
046
047        /**
048         * Creates a new component tree for the specified image using the default method ({@link Method#LinearTime}).
049         *
050         * @param <T> the generic node type
051         * @param ip the input image
052         * @return the component tree for the specified image
053         */
054        public static <T> ComponentTree<T> from(ByteProcessor ip) {
055                return from(ip, Method.LinearTime);
056        }
057
058        /**
059         * Creates a new component tree for the specified image.
060         *
061         * @param <T> the data type of components (e.g., {@link MserData})
062         * @param ip the input (gray-level) image
063         * @param method the method for building the component tree
064         * @return the component tree
065         */
066        public static <T> ComponentTree<T> from(ByteProcessor ip, Method method) {
067                return from(new PixelMap(ip), method);
068        }
069
070        /**
071         * Creates a new component tree for the specified {@link PixelMap}.
072         *
073         * @param <T> the data type of components (e.g., {@link MserData})
074         * @param pm a {@link PixelMap} instance
075         * @param method the method for building the component tree
076         * @return the component tree
077         */
078        public static <T> ComponentTree<T> from(PixelMap pm, Method method) {
079                switch (method) {
080                case LinearTime:  return new ComponentTreeLinearTime<>(pm);
081                case GlobalImmersion: return new ComponentTreeGlobalImmersion<>(pm);
082                }
083                return null;
084        }
085        
086        // --------------------------------------------------------------
087
088        /**
089         * Returns the root component of this component tree. To be implemented by concrete subclasses.
090         *
091         * @return the root component
092         */
093        public abstract Component<T> getRoot();
094
095        /**
096         * Returns an unordered collection of all tree components. To be implemented by concrete subclasses.
097         *
098         * @return all tree components
099         */
100        public abstract Collection<Component<T>> getComponents();
101        
102        @Override
103        public Iterator<Component<T>> iterator() {
104                return getComponents().iterator();
105        }
106
107        /**
108         * Finds and returns a collection of all leaf components of this tree. A leaf is a component which has no children.
109         *
110         * @return all leaf components
111         */
112        public Collection<Component<T>> getLeaves() {
113                Collection<Component<T>> leaves = new LinkedList<>();
114                for (Component<T> c : getComponents()) {
115                        if (c.getChildren().isEmpty()) {
116                                leaves.add(c);
117                        }
118                }       
119                return leaves;
120        }
121        
122        // -------------------------------------------------------------------------------
123        
124        /**
125         * Performs integrity checks on this component tree.
126         * @return true iff all checks are passed
127         */
128        public boolean validate() {
129                if (!checkNodes()) {
130                        System.out.println("checkNodes failed");
131                        return false;
132                }
133                if (!checkParents()) {
134                        System.out.println("checkParents failed");
135                        return false;
136                }
137                if (!checkChildren()) {
138                        System.out.println("checkChildren failed");
139                        return false;
140                }
141                if (!checkRoot()) {
142                        System.out.println("checkRoot failed");
143                        return false;
144                }
145                if (!checkLevels()) {
146                        System.out.println("checkLevels failed");
147                        return false;
148                }
149                if (!checkSize()) {
150                        System.out.println("checkSize failed");
151                        return false;
152                }
153                return true;
154        }
155
156        /**
157         * Checks if all tree nodes reachable through root are contained in components list and the tree has no duplicate
158         * nodes (and no cycles).
159         *
160         * @return true iff OK
161         */
162        private boolean checkNodes() {
163                Set<Component<T>> componentSet = new HashSet<>(); // empty hash set
164                int duplicateID = traverse(getRoot(), componentSet);
165                if (duplicateID >= 0) {         // traverse() registers all visited components
166                        System.out.println("*** component tree has duplicate node: " + duplicateID);
167                        return false;
168                }
169                if (getComponents().size() != componentSet.size()) {
170                        System.out.println("*** some nodes in components list can not be reached!");
171                        return false;
172                }
173                return true;
174        }
175        
176        private int traverse(Component<T> c, Set<Component<T>> cS) {
177                if (cS.add(c)) {
178                        for (Component<T> cc : c.getChildren()) {
179                                int duplicateId = traverse(cc, cS);
180                                if (duplicateId >= 0) { // duplicate node
181                                        return duplicateId;
182                                };
183                        }
184                        return -1;      // everything OK, no duplicate node
185                }
186                else {  // c was already in set
187                        System.out.println("*** duplicate tree node: " + c.ID);
188                        return c.ID;
189                }
190        }
191
192        /**
193         * Checks if all parent relations are consistent.
194         * @return true iff OK
195         */
196        private boolean checkParents() {
197                Collection<Component<T>> comps = getComponents();
198                HashSet<Component<T>> compsH = new HashSet<>(comps); // to improve lookup speed
199                
200                for (Component<?> c : comps) {
201                        Component<?> p = c.getParent();
202                        if (p == null) {
203                                continue;
204                        }
205                        
206                        // check if parent is listed in 'components' (use HashSet for speed)
207                        if (!compsH.contains(p)) {
208                                System.out.println("*** component not contained in components list: " + c.ID);
209                                return false;
210                        }
211                                                
212                        // check if parent is not c itself
213                        if (p == c) {
214                                System.out.println("*** self-referring parent link in component " + c.ID);
215                                return false;
216                        }                               
217                }
218                return true;
219        }
220        
221        /**
222         * Checks if all child relations are consistent.
223         * 
224         * @return true iff OK
225         */
226        private boolean checkChildren() {
227                for (Component<?> c : getComponents()) {
228                        // check if children is a list (not null)
229                        if (c.getChildren() == null) {
230                                System.out.println("*** children is null (not an empty list) in component " + c.ID);
231                                return false;
232                        }
233                        
234                        // check if all children of c have c as parent
235                        for (Component<?> child : c.getChildren()) {
236                                // check if c is parent of its child:
237                                if (child.getParent() != c) {
238                                        System.out.println("*** incorrect parent link in component " + child.ID);
239                                        return false;
240                                }
241                        }       
242                }
243                return true;
244        }
245        
246        /**
247         * Checks if the tree has a unique root.
248         * 
249         * @return true iff OK
250         */
251        private boolean checkRoot() {
252                // check if a single root exists and is properly marked (with null parent)
253                int rootcnt = 0;
254                Component<T> root = getRoot();
255                for (Component<?> c : getComponents()) {
256                        if (c.getParent() == null) {
257                                rootcnt++;
258                                if (c != root) {
259                                        System.out.println("*** found non-root node without parent: " + c.ID);
260                                        return false;
261                                }
262                        }
263                }
264                if (rootcnt > 1) {
265                        System.out.println("*** found multiple roots: " + rootcnt);
266                        return false;
267                }
268                return true;
269        }
270        
271        /**
272         * Checks if all local pixels of have the same value as {@link Component#getLevel()}.
273         * 
274         * @return true iff OK
275         */
276        private boolean checkLevels() {
277                for (Component<?> c : getComponents()) {
278                        int lc = c.getLevel();
279                        
280                        // check if parent's level is greater than this component's level:
281                        Component<?> p = c.getParent();
282                        if (p != null) {
283                                int lp = p.getLevel();
284                                if (lc >= lp) { // must be lc < lp
285                                        throw new RuntimeException("checkLevels: value lc >= lp for component " + c.ID);
286                                }
287                        }
288                        
289                        // check if all local points have the same level as this component's level:
290                        for (Pixel x : c.getLocalPixels()) {
291                                if (x.val != lc) {
292                                        throw new RuntimeException("checkLevels: local pixel with wrong value in component " + c.ID);
293                                        // return false;
294                                }
295                        }
296                }
297                return true;
298        }
299
300        /**
301         * Checks if the size of each component is the same as the number of local pixels plus the combined size of all
302         * children.
303         *
304         * @return true iff OK
305         */
306        private boolean checkSize() {
307                for (Component<?> c : getComponents()) {
308                        int csize = c.getLocalPixels().size();
309                        for (Component<?> cc : c.getChildren()) {
310                                csize = csize + cc.getSize();
311                        }
312                        if (csize != c.getSize()) {
313                                System.out.println("*** wrong size in component " + c.ID);
314                                return false;
315                        }
316                }
317                return true;
318        }
319        
320        // ---------------------------------------------------------------
321        
322        @Override
323        public String toString() { 
324                ByteArrayOutputStream os = new ByteArrayOutputStream();
325                PrintStream ps = new PrintStream(os);
326                this.printToStream(ps);
327                return os.toString();
328        }
329        
330        private void printToStream(PrintStream strm) {
331                strm.print(this.getClass().getSimpleName() + ": ");
332                strm.format("   number of components: %d\n", getComponents().size());
333                for (Component<?> r : getComponents()) {
334                        r.printToStream(strm);
335                        strm.println();
336                }
337                strm.println("root = " + getRoot());
338        }
339        
340        // ---------------------------------------------------------------------
341        
342        private static int INDENT = 4;
343        
344        // print a forest with multiple roots
345        @SuppressWarnings("unused")
346        private static String toStringRecursive(ComponentTree<?> rt) { 
347                ByteArrayOutputStream os = new ByteArrayOutputStream();
348                PrintStream ps = new PrintStream(os);
349                
350                for (Component<?> c : rt) {
351                        if (c.getParent() == null || c.getParent() == c) {      // start at any root
352                                printToStreamRecursive(c, ps, 0);
353                        }       
354                }       
355                return os.toString();
356        }
357        
358        // print tree with a single root
359        @SuppressWarnings("unused")
360        private static String toStringRecursive(Component<?> rt) { 
361                ByteArrayOutputStream os = new ByteArrayOutputStream();
362                PrintStream ps = new PrintStream(os);
363                printToStreamRecursive(rt, ps, 0);      
364                return os.toString();
365        }
366        
367        
368        private static void printToStreamRecursive(Component<?> c, PrintStream ps, int indent) {
369                for (int i=0; i < indent; i++) {
370                        ps.append('|');
371                        for (int j = 0; j < INDENT; j++) {
372                                ps.append(' ');
373                        }
374                }
375                c.printToStream(ps);
376                ps.println();
377                for (Component<?> child : c.getChildren()) {
378                        printToStreamRecursive(child, ps, indent + 1);
379                }
380        }
381
382}