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;
012
013import java.io.ByteArrayOutputStream;
014import java.io.PrintStream;
015import java.util.ArrayList;
016import java.util.Arrays;
017import java.util.Collection;
018import java.util.Collections;
019import java.util.Comparator;
020import java.util.HashSet;
021import java.util.LinkedList;
022import java.util.List;
023import java.util.Set;
024
025/**
026 * This class represents a connected component (i.e., a binary image region). Instances of this class form the nodes of
027 * a {@link ComponentTree}.
028 *
029 * @param <T> the type of properties that can be attached to instances of this class
030 * @author WB
031 * @version 2022/11/19
032 */
033public class Component<T> {
034        
035        /** The ID number of this component (only used for debugging). */
036        public final int ID;
037        
038        // ------------------------------------------------------------
039        
040        private final int level;                                // intensity level associated with this region
041        private final List<Pixel> points;               // local points in this component (does not include points in child components) 
042        private int size;                                               // the total size of this component (number of points, includes children) 
043        
044        private Component<T> parent;                    // reference to the parent component (null if this is the root)
045        private Component<T> shortcut;                  // reference to a component in the same tree but closer to the root
046        private final Set<Component<T>> children; // the components at the next lower level
047        private int height;                                             // the height of the sub-tree rooted at this component
048        
049        private T properties = null;                    // additional component properties to be attached
050
051        // ------------------------------------------------------------
052        
053        /**
054         * Constructor.
055         * @param level the maximum pixel value in this component
056         * @param id a unique component id (assigned by the factory)
057         */
058        protected Component(int level, int id) {
059                this.ID = id;
060                this.level = level;
061                this.points = new LinkedList<>();
062                this.parent = null;
063                this.shortcut = null;
064                this.children = new HashSet<>();
065                this.size = 0;
066                this.height = 0;
067        }
068        
069        // --------------------------------------------------------
070
071        /**
072         * Returns the parent component of this component ({@code null} if this node is the tree's root).
073         *
074         * @return the parent component
075         */
076        public Component<T> getParent() {
077                return parent;
078        }
079
080        void setParent(Component<T> parent) {
081                this.parent = parent;
082                this.shortcut = parent;
083        }
084        
085        void addToSize(int sizeInc) {
086                size = size + sizeInc;
087        }
088
089        /**
090         * Returns the size (number of pixels) of this component.
091         * 
092         * @return the size of this component
093         */
094        public int getSize() {
095                return size;
096        }
097        
098        void addChild(Component<T> c) {
099                children.add(c);
100        }
101        
102        /**
103         * Returns the list of this component's child components.
104         * 
105         * @return a (possibly empty) list of components, never {@code null}
106         */
107        public Collection<Component<T>> getChildren() {
108                if (children == null) {
109                        return Collections.emptyList();
110                }
111                else {
112                        return children;
113                }
114        }
115
116        /**
117         * Returns the level (max. gray value) of this component.
118         * 
119         * @return the component's level
120         */
121        public int getLevel() {
122                return this.level;
123        }
124        
125        void addPoint(Pixel p) {
126                points.add(p);
127                size = size + 1;
128        }
129        
130        /**
131         * Returns the height of the sub-tree which this component is the root of.
132         * 
133         * @return the height of the sub-tree rooted by this component
134         */
135        public int getHeight() {
136                return this.height;
137        }
138        
139        void setHeight(int newHeight) {
140                height = newHeight;
141        }
142
143        /**
144         * Returns a list of "local" pixels that directly belong to this component All these pixels have the same 'level' as
145         * the component itself.
146         *
147         * @return the component's local pixels
148         */
149        public Collection<Pixel> getLocalPixels() {
150                return this.points;
151        }
152
153        /**
154         * Returns a collection of all pixels of this component, including the component's local pixels and the pixels of
155         * all child components. Not needed in actual code, used only for debugging.
156         *
157         * @return all pixels contained in this component
158         */
159        public Collection<Pixel> getAllPixels() {
160                Collection<Pixel> compPoints = this.getChildPixels();
161                compPoints.addAll(this.points);
162                return compPoints;
163        }
164        
165//      public Collection<Pixel> getAllPixels() {
166//              Collection<Pixel> allPoints = new ArrayList<>(this.size);
167//              allPoints.addAll(this.points);
168//              allPoints.addAll(this.getChildPixels());
169//              return allPoints;
170//      }
171
172        /**
173         * Returns a collection of all pixels contained in the children of this component.
174         *
175         * @return all pixels from child components
176         */
177        public Collection<Pixel> getChildPixels() {
178                Collection<Pixel> childPoints = new ArrayList<>(this.size);
179                for (Component<T> child : this.children) {
180                        childPoints.addAll(child.getAllPixels());
181                }
182                return childPoints;
183        }
184        
185        /**
186         * Sets the properties of this component.
187         * 
188         * @param properties a property object (of type T)
189         */
190        public void setProperties(T properties) {
191                this.properties = properties;
192        }
193        
194        /**
195         * Returns the properties attached to this component.
196         * @return the properties attached to this component
197         */
198        public T getProperties() {
199                return properties;
200        }
201        // ------------------------------------------------------------------
202
203        /**
204         * Recursively locates the root of the tree that contains this component, returning the first ancestor node that has
205         * no parent, which my be this node itself. The {@link #shortcut} field is used to quickly move up to nodes closer
206         * to the root. The {@link #shortcut} field is updated "on the way back", i.e., by the unwinding recursion.
207         *
208         * @return the root of the sub-tree containing this component
209         */
210        Component<T> findRoot() {
211                if (this.isRoot()) {
212                        return this;
213                }
214                else {
215                        shortcut = shortcut.findRoot(); // unwinding recursion updates shortcut 
216                        return shortcut;
217                }
218        }
219
220        /**
221         * Returns {@code true} if this component is the root of the associated {@link ComponentTree}.
222         *
223         * @return {@code true} if this component is the root
224         */
225        public boolean isRoot() {       // TODO: change/clean/check!
226                return parent == null; // || this.parent == this;
227        }
228        
229        /**
230         * Returns {@code true} if this component represents an extremal region.
231         * 
232         * @return {@code true} if the component is extremal
233         */
234        public boolean isExtremal() {
235                return isRoot() || parent.level > this.level;
236        }
237        
238        // ----------------------------------------------------------------------
239        
240//      @Override
241//      public int compareTo(Component<?> other) {
242//              return Integer.compare(this.level, other.level);
243//      }       
244        
245        // -------------------------------------------------------------------------
246        
247        @SuppressWarnings("unused")
248        private String listChildIds() {
249                int[] ids = new int[this.children.size()];
250                int i = 0;
251                for (Component<T> child : children) {
252                        ids[i] = child.ID;
253                        i++;
254                }
255                return Arrays.toString(ids);
256        }
257
258
259        /**
260         * Sorts a list of Components by (decreasing) component size, i.e., the largest component (with the most pixels)
261         * becomes the first.
262         *
263         * @param components a list of {@link Component} instances
264         */
265        public static void sortBySize(List<? extends Component<?>> components) {
266                Comparator<Component<?>> cmp = new Comparator<Component<?>>() {
267                        @Override
268                        public int compare(Component<?> mser1, Component<?> mser2) {
269                                return Integer.compare(mser2.getSize(), mser1.getSize());
270                        }
271                };
272                Collections.sort(components, cmp);
273        }
274
275        /**
276         * Sorts a list of Components by (increasing) component level, i.e., the component with the lowest level becomes the
277         * first.
278         *
279         * @param components a list of {@link Component} instances
280         */
281        public static void sortByLevel(List<? extends Component<?>> components) {
282                Comparator<Component<?>> cmp = new Comparator<Component<?>>() {
283                        @Override
284                        public int compare(Component<?> mser1, Component<?> mser2) {
285                                return Integer.compare(mser1.level, mser2.level);
286                        }
287                };
288                Collections.sort(components, cmp);
289        }
290        
291
292        // ---------------------------
293        
294        void printToStream(PrintStream strm) {
295                strm.format("Component %d(%d): size=%d locPts=%d chldPts=%d allPts=%d parent=%s",
296                                this.ID, this.level, 
297                                this.size,
298                                this.points.size(),
299                                this.getChildPixels().size(),
300                                this.getAllPixels().size(),
301                                (this.parent == null) ? "x" : (this.parent.ID + "(" + this.parent.level + ")")
302                                );
303        }
304        
305        @Override
306        public String toString() {
307                ByteArrayOutputStream os = new ByteArrayOutputStream();
308                PrintStream ps = new PrintStream(os);
309                this.printToStream(ps);
310                return os.toString();
311        }
312        
313}