docs: private void findCandidateShapes() {
  
  		// find blobs where all 4 edges are lines
  		List<ContourPacked> blobs = contourFinder.getContours();
  		for (int i = 0; i < blobs.size(); i++) {
  			ContourPacked c = blobs.get(i);
  
  			contourTmp.reset();
  			contourFinder.loadContour(c.externalIndex,contourTmp);
  			if( contourTmp.size() >= minimumContour) {
  				float edgeInside=-1,edgeOutside=-1;
  
  //				System.out.println("----- candidate "+contourTmp.size()+"  "+contourTmp.get(0));
  
  				// ignore shapes which touch the image border
  				boolean touchesBorder = touchesBorder(contourTmp.toList());
  				if( !canTouchBorder && touchesBorder ) {
  					if( verbose ) System.out.println("rejected polygon, touched border");
  					continue;
  				}
  
  				if( helper != null )
  					if( !helper.filterContour(contourTmp.toList(),touchesBorder,true) )
  						continue;
  
  				// filter out contours which are noise
  				if( contourEdgeIntensity != null ) {
  					contourEdgeIntensity.process(contourTmp.toList(),true);
  					edgeInside = contourEdgeIntensity.getInsideAverage();
  					edgeOutside = contourEdgeIntensity.getOutsideAverage();
  
  					// take the ABS because CCW/CW isn't known yet
  					if( Math.abs(edgeOutside-edgeInside) < contourEdgeThreshold ) {
  						if( verbose ) System.out.println("rejected polygon. contour edge intensity");
  						continue;
  					}
  				}
  
  				// remove lens distortion
  				List<Point2D_I32> undistorted;
  				if( distToUndist != null ) {
  					undistorted = this.undistorted.toList();
  					removeDistortionFromContour(contourTmp.toList(),this.undistorted);
  					if( helper != null )
  						if( !helper.filterContour(this.undistorted.toList(),touchesBorder,false) )
  							continue;
  				} else {
  					undistorted = contourTmp.toList();
  				}
  
  				if( helper != null ) {
  					helper.configureBeforePolyline(contourToPolyline,touchesBorder);
  				}
  
  				// Find the initial approximate fit of a polygon to the contour
  				if( !contourToPolyline.process(undistorted,splits) ) {
  					if( verbose ) System.out.println("rejected polygon initial fit failed. contour size = "+contourTmp.size());
  					continue;
  				}
  
  				// determine the polygon's orientation
  				polygonPixel.clear();
  				for (int j = 0; j < splits.size; j++) {
  					polygonPixel.add(undistorted.get(splits.get(j)));
  				}
  
  				boolean isCCW = UtilPolygons2D_I32.isCCW(polygonPixel);
  
  				// Now that the orientation is known it can check to see if it's actually trying to fit to a
  				// white blob instead of a black blob
  				if( contourEdgeIntensity != null ) {
  					// before it assumed it was CCW
  					if( !isCCW ) {
  						float tmp = edgeInside;
  						edgeInside = edgeOutside;
  						edgeOutside = tmp;
  					}
  
  					if( edgeInside > edgeOutside ) {
  						if( verbose ) System.out.println("White blob. Rejected");
  						continue;
  					}
  				}
  
  				// see if it should be flipped so that the polygon has the correct orientation
  				if( outputClockwise == isCCW ) {
  					flip(splits.data,splits.size);
  				}
  
  				// convert the format of the initial crude polygon
  				polygonWork.vertexes.resize(splits.size());
  				polygonDistorted.vertexes.resize(splits.size());
  				for (int j = 0; j < splits.size(); j++) {
  					Point2D_I32 p = undistorted.get( splits.get(j) );
  					Point2D_I32 q = contourTmp.get( splits.get(j));
  					polygonWork.get(j).set(p.x,p.y);
  					polygonDistorted.get(j).set(q.x,q.y);
  				}
  
  				if( touchesBorder ) {
  					determineCornersOnBorder(polygonDistorted, borderCorners);
  				} else {
  					borderCorners.resize(0);
  				}
  
  				if( helper != null ) {
  					if( !helper.filterPixelPolygon(polygonWork,polygonDistorted,borderCorners,touchesBorder) ) {
  						if( verbose ) System.out.println("rejected by helper.filterPixelPolygon()");
  						continue;
  					}
  				}
  
  				// make sure it's big enough
  				double area = Area2D_F64.polygonSimple(polygonWork);
  
  				if( area < minimumArea ) {
  					if( verbose ) System.out.println("Rejected area");
  					continue;
  				}
  
  				// Get the storage for a new polygon. This is recycled and has already been cleaned up
  				Info info = foundInfo.grow();
  
  				if( distToUndist != null ) {
  					// changed the save points in the packed contour list with undistorted coordinates
  					contourFinder.writeContour(c.externalIndex,undistorted);
  				}
  
  				// save results
  				info.splits.setTo(splits);
  				info.contourTouchesBorder = touchesBorder;
  				info.external = true;
  				info.edgeInside = edgeInside;
  				info.edgeOutside = edgeOutside;
  				info.contour = c;
  				info.polygon.set(polygonWork);
  				info.polygonDistorted.set(polygonDistorted);
  				info.borderCorners.setTo(borderCorners);
  			}
  		}
  	}