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.regions; 010 011import imagingbook.common.geometry.basic.Pnt2d; 012import imagingbook.common.geometry.basic.Pnt2d.PntInt; 013 014import java.awt.Rectangle; 015import java.util.Collections; 016import java.util.Iterator; 017import java.util.LinkedList; 018import java.util.List; 019import java.util.NoSuchElementException; 020 021/** 022 * Defines a binary region that is backed by the label array of a region segmentation. A 023 * {@link SegmentationBackedRegion} instance does not have its own list or array of contained pixel coordinates but 024 * refers to the label array of the associated {@link BinaryRegionSegmentation} instance. 025 * 026 * @author WB 027 * @version 2020/12/21 028 */ 029public class SegmentationBackedRegion extends BinaryRegion { 030 031 private final int label; // the label of this region 032 private final BinaryRegionSegmentation segmentation; // the segmentation backing this region 033 034 private int size = 0; 035 private int left = Integer.MAX_VALUE; 036 private int right = -1; 037 private int top = Integer.MAX_VALUE; 038 private int bottom = -1; 039 040 private Contour outerContour = null; 041 private List<Contour> innerContours = null; 042 043 // summation variables used for various statistics 044 private long x1Sum = 0; 045 private long y1Sum = 0; 046 private long x2Sum = 0; 047 private long y2Sum = 0; 048 private long xySum = 0; 049 050 // ------- constructor -------------------------- 051 052 /** 053 * Constructor. 054 * @param label the label (number) assigned to this region 055 * @param seg the backing region segmentation 056 */ 057 SegmentationBackedRegion(int label, BinaryRegionSegmentation seg) { 058 this.label = label; 059 this.segmentation = seg; 060 } 061 062 // ------- public methods -------------------------- 063 064 /** 065 * Returns the label (number) of this region. 066 * @return the region label 067 */ 068 public int getLabel() { 069 return this.label; 070 } 071 072 @Override 073 public int getSize() { 074 return this.size; 075 } 076 077 @Override 078 public long getX1Sum() { 079 return x1Sum; 080 } 081 082 @Override 083 public long getY1Sum() { 084 return y1Sum; 085 } 086 087 @Override 088 public long getX2Sum() { 089 return x2Sum; 090 } 091 092 @Override 093 public long getY2Sum() { 094 return y2Sum; 095 } 096 097 @Override 098 public long getXYSum() { 099 return xySum; 100 } 101 102 @Override 103 public Rectangle getBoundingBox() { 104 if (right < 0) 105 return null; 106 else 107 return new Rectangle(left, top, right-left + 1, bottom - top + 1); 108 } 109 110 @Override 111 public Iterator<Pnt2d> iterator() { 112 return new RegionPixelIterator(); 113 } 114 115 /** 116 * Adds a single pixel to this region and updates summation and boundary variables used to calculate various region 117 * statistics. 118 * 119 * @param u x-position 120 * @param v y-position 121 */ 122 void addPixel(int u, int v) { 123 size = size + 1; 124 x1Sum = x1Sum + u; 125 y1Sum = y1Sum + v; 126 x2Sum = x2Sum + u * u; 127 y2Sum = y2Sum + v * v; 128 xySum = xySum + u * v; 129 if (u < left) left = u; 130 if (v < top) top = v; 131 if (u > right) right = u; 132 if (v > bottom) bottom = v; 133 } 134 135 /** 136 * Updates the region's statistics. Does nothing but may be overridden by inheriting classes. 137 */ 138 void update() { 139 } 140 141 @Override 142 public Contour getOuterContour() { // TODO: invoke some contour tracer if contour not available? 143 return outerContour; 144 } 145 146 @Override 147 void setOuterContour(Contour.Outer contr) { 148 outerContour = contr; 149 } 150 151 @Override 152 public List<Contour> getInnerContours() { 153 return (innerContours != null) ? innerContours : Collections.emptyList(); 154// return innerContours; 155 } 156 157 void addInnerContour(Contour.Inner contr) { 158 if (innerContours == null) { 159 innerContours = new LinkedList<>(); 160 } 161 innerContours.add(contr); 162 } 163 164 /** 165 * Checks if the given pixel position is contained in this {@link SegmentationBackedRegion} instance. 166 * 167 * @param u x-coordinate 168 * @param v y-coordinate 169 * @return true if (u,v) is contained in this region 170 */ 171 @Override 172 public boolean contains(int u, int v) { 173 return segmentation.getLabel(u, v) == this.label; 174 } 175 176 // -------------------------------------------------------------------------------- 177 178 /** 179 * Instances of this class are returned by {@link SegmentationBackedRegion#iterator()}, which implements 180 * {@link Iterable} for instances of class {@link Pnt2d}. 181 */ 182 private class RegionPixelIterator implements Iterator<Pnt2d> { 183 private final int label; // the corresponding region's label 184 private final int uMin, uMax, vMin, vMax; // coordinates of region's bounding box 185 private int uCur, vCur; // current pixel position 186 private PntInt pNext; // coordinates of the next region pixel 187 private boolean first; // control flag 188 189 RegionPixelIterator() { 190 label = SegmentationBackedRegion.this.getLabel(); 191 Rectangle bb = SegmentationBackedRegion.this.getBoundingBox(); 192 first = true; 193 uMin = bb.x; 194 uMax = bb.x + bb.width; 195 vMin = bb.y; 196 vMax = bb.y + bb.height; 197 uCur = uMin; 198 vCur = vMin; 199 pNext = null; 200 } 201 202 /** 203 * Search from position (uCur, vCur) for the next valid region pixel. Return the next position as a Point or 204 * null if no such point can be found. Don't assume that (uCur, vCur) is a valid region pixel! 205 * 206 * @return the next point 207 */ 208 private Pnt2d.PntInt findNext() { 209 // start search for next region pixel at (u,v): 210 int u = (first) ? uCur : uCur + 1; 211 int v = vCur; 212 first = false; 213 while (v <= vMax) { 214 while (u <= uMax) { 215 if (segmentation.getLabel(u, v) == label) { // next pixel found (uses surrounding labeling) 216 uCur = u; 217 vCur = v; 218 return PntInt.from(uCur, vCur); 219 } 220 u++; 221 } 222 v++; 223 u = uMin; 224 } 225 uCur = uMax + 1; // just to make sure we'll never enter the loop again 226 vCur = vMax + 1; 227 return null; // no next pixel found 228 } 229 230 @Override 231 public boolean hasNext() { 232 if (pNext != null) { // next element has been queried before but not consumed 233 return true; 234 } 235 else { 236 pNext = findNext(); // keep next pixel coordinates in pNext 237 return (pNext != null); 238 } 239 } 240 241 // Returns: the next element in the iteration 242 // Throws: NoSuchElementException - if the iteration has no more elements. 243 @Override 244 public PntInt next() { 245 if (pNext != null || hasNext()) { 246 PntInt pn = pNext; 247 pNext = null; // "consume" pNext 248 return pn; 249 } 250 else { 251 throw new NoSuchElementException(); 252 } 253 } 254 255 @Override 256 public void remove() { 257 throw new UnsupportedOperationException(); 258 } 259 260 } 261 262}