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 imagingbook.common.mser.components.PixelMap.Pixel;
012import imagingbook.common.util.bits.BitMap;
013
014import java.util.ArrayList;
015import java.util.Collection;
016import java.util.Deque;
017import java.util.LinkedList;
018import java.util.List;
019import java.util.PriorityQueue;
020
021/**
022 * <p>
023 * This class implements the "linear-time" ("local flooding") component tree algorithm described in [1]. See Section
024 * 26.2.3 of [2] for a detailed description (Algs. 26.3 - 26.4).
025 * </p>
026 * <p>
027 * [1] D. Nister and H. Stewenius, "Linear Time Maximally Stable Extremal Regions", Computer Vision - ECCV 2008. pp.
028 * 183-196, Springer Berlin/Heidelberg (2008).
029 * <br>
030 * [2] W. Burger, M.J. Burge, <em>Digital Image Processing &ndash; An Algorithmic Introduction</em>, 3rd ed, Springer
031 * (2022).
032 * </p>
033 *
034 * @param <T> the type of properties to be attached to components
035 * @author WB
036 * @version 2022/11/19
037 */
038public class ComponentTreeLinearTime<T> extends ComponentTree<T> {
039        
040        private final List<Component<T>> components;
041        private final Component<T> root;
042
043        /**
044         * Constructor, creates a new component tree from a {@link PixelMap} representing a gray-level image.
045         *
046         * @param P a {@link PixelMap}
047         */
048        public ComponentTreeLinearTime(PixelMap P) {
049                components = new ArrayList<Component<T>>();
050                root = buildTree(P);
051        }
052        
053        // -----------------------------------------------------------------------------------
054        
055        @Override
056        public Component<T> getRoot() {
057//              return components.get(components.size() - 1);   // root is the last component
058                return root;
059        }
060
061        @Override
062        public Collection<Component<T>> getComponents() {
063                return components;
064        }
065        
066        // -----------------------------------------------------------------------------------
067
068        /**
069         * Creates the component set from a {@link PixelMap}. Modifies {@link #components}. The last item in
070         * {@link #components} is the root component.
071         *
072         * @param P the input image
073         */
074        private Component<T> buildTree(PixelMap P) {            
075                final BitMap V = new BitMap(P.width, P.height);         // "visited", all set to false by default
076                final PriorityQueue<Pixel> B = new PriorityQueue<>();   // heap of boundary points that is sorted by 'val'
077                final Deque<Component<T>> C = new LinkedList<>();               // stack of region components
078                
079                Pixel p = P.getPixel(0, 0);                             // start point (seed), could be any pixel
080                V.set(p); //p.setVisited();                                     // p is the current point, mark as accessible
081                C.push(makeComponent(p.val));                           // push an empty component onto stack
082                
083                while (p != null) {                                                     // repeat until all pixels are done
084                        Pixel n = p.getNextNeighbor();                  // get the next neighbor of p (pixels keep track of visited neighbors)
085                        while (n != null) {
086                                if (!V.get(n)) {                                        // (!n.isVisited())
087                                        V.set(n);                                               //n.setVisited();
088                                        if (n.val >= p.val) {           
089                                                B.add(n);                                       // add neighbor to the boundary heap
090                                        }
091                                        else {
092                                                B.add(p);                                                       // move current pixel back to boundary heap 
093                                                C.push(makeComponent(n.val));   // create an empty component for the neighbor pixel
094                                                p = n;                                                          // make neighbor the current pixel
095                                        }
096                                }
097                                n = p.getNextNeighbor();
098                        }
099                        
100                        C.peek().addPoint(p);                                   // add the current pixel to the top-of-stack component
101                        
102                        Pixel q = B.poll();                                             // remove the first point from the heap of boundary pixels
103                        if (q != null && q.val > p.val) {               // new pixel value (q) is greater than current value (p)
104                                processStack(q.val, C);                         // see below
105                        }
106                        p = q;                                                                  // update the current pixel
107                }
108                
109                if (C.size() != 1) {    // just to make sure!
110                        throw new RuntimeException("component stack size must be 1 but is " + C.size());
111                }
112                Component<T> r = C.peek();                                      // the root component is still on the stack
113                processStack(Integer.MAX_VALUE, C);             // process the root component
114                
115                return r;
116        }
117        
118
119        
120        // -------------------------------------------------------------------
121
122        /**
123         * This method is called whenever the current pixel value is raised. Processes the top items on the component stack.
124         * Modifies {@link #components}.
125         *
126         * @param v new "target" level
127         * @param C component stack
128         */
129        private void processStack(int v, Deque<Component<T>> C) {
130                while (!C.isEmpty() && v > C.peek().getLevel()) {               
131                        Component<T> c1 = C.poll();     // retrieve the top-of-stack component
132                        components.add(c1);                             // "emit" component c1 (note: c1.parent == null)
133
134                        if (v < Integer.MAX_VALUE) {
135                                Component<T> c2 = (C.isEmpty() || v < C.peek().getLevel()) ?
136                                                makeComponent(v) :              // c2 = artificial component with level v
137                                                C.pop();                                // c2 = next component on the stack 
138                                
139                                // merge c1 into c2 (c2 becomes parent of c1)
140                                c2.addChild(c1);
141                                c2.addToSize(c1.getSize());
142                                c1.setParent(c2);
143                                C.push(c2);     // insert c2 into the stack C (again)
144                        }
145                }
146        }
147
148        private int nextComponentIndex = 0;
149        
150        private Component<T> makeComponent(int level) {
151                return new Component<>(level, nextComponentIndex++);
152        }
153
154}