docs: public void process( T gray ) {
  		configureContourDetector(gray);
  
  		contourHelper.reshape(gray.width,gray.height);
  
  		inputToBinary.process(gray,contourHelper.withoutPadding());
  		squareDetector.process(gray,contourHelper.padded());
  		squareDetector.refineAll();
  		// These are in undistorted pixels
  		squareDetector.getPolygons(candidates,candidatesInfo);
  
  		found.reset();
  
  		if( verbose ) System.out.println("---------- Got Polygons! "+candidates.size());
  
  		for (int i = 0; i < candidates.size(); i++) {
  			// compute the homography from the input image to an undistorted square image
  			// If lens distortion has been specified this polygon will be in undistorted pixels
  			Polygon2D_F64 p = candidates.get(i);
  //			System.out.println(i+"  processing...  "+p.areaSimple()+" at "+p.get(0));
  
  			// sanity check before processing
  			if( !checkSideSize(p) ) {
  				if( verbose ) System.out.println("  rejected side aspect ratio or size");
  				continue;
  			}
  
  			// REMOVE EVENTUALLY  This is a hack around how interpolation is performed
  			// Using a surface integral instead would remove the need for this.  Basically by having it start
  			// interpolating from the lower extent it samples inside the image more
  			// A good unit test to see if this hack is no longer needed is to rotate the order of the polygon and
  			// see if it returns the same undistorted image each time
  			double best=Double.MAX_VALUE;
  			for (int j = 0; j < 4; j++) {
  				double found = p.get(0).normSq();
  				if( found < best ) {
  					best = found;
  					interpolationHack.set(p);
  				}
  				UtilPolygons2D_F64.shiftDown(p);
  			}
  
  			p.set(interpolationHack);
  
  			// remember, visual clockwise isn't the same as math clockwise, hence
  			// counter clockwise visual to the clockwise quad
  			pairsRemovePerspective.get(0).set(0, 0, p.get(0).x, p.get(0).y);
  			pairsRemovePerspective.get(1).set( square.width ,      0        , p.get(1).x , p.get(1).y );
  			pairsRemovePerspective.get(2).set( square.width , square.height , p.get(2).x , p.get(2).y );
  			pairsRemovePerspective.get(3).set( 0            , square.height , p.get(3).x , p.get(3).y );
  
  			if( !computeHomography.process(pairsRemovePerspective,H) ) {
  				if( verbose ) System.out.println("  rejected initial homography");
  				continue;
  			}
  
  			// refine homography estimate
  			if( !refineHomography.fitModel(pairsRemovePerspective,H,H_refined) ) {
  				if( verbose ) System.out.println("  rejected refine homography");
  				continue;
  			}
  
  			// pass the found homography onto the image transform
  			ConvertDMatrixStruct.convert(H_refined,H_fixed);
  			ConvertFloatType.convert(H_fixed, transformHomography.getModel());
  
  			// TODO Improve how perspective is removed
  			// The current method introduces artifacts.  If the "square" is larger
  			// than the detected region and bilinear interpolation is used then pixels outside will// influence the
  			// value of pixels inside and shift things over.  this is all bad
  
  			// remove the perspective distortion and process it
  			removePerspective.apply(gray, square);
  
  			DetectPolygonFromContour.Info info = candidatesInfo.get(i);
  
  			// see if the black border is actually black
  			if( minimumBorderBlackFraction > 0 ) {
  				double pixelThreshold = (info.edgeInside + info.edgeOutside) / 2;
  				double foundFraction = computeFractionBoundary((float) pixelThreshold);
  				if( foundFraction < minimumBorderBlackFraction ) {
  					if( verbose ) System.out.println("  rejected black border fraction "+foundFraction);
  					continue;
  				}
  			}
  			if( processSquare(square,result,info.edgeInside,info.edgeOutside)) {
  				prepareForOutput(p,result);
  
  				if( verbose ) System.out.println("  accepted!");
  			} else {
  				if( verbose ) System.out.println("  rejected process square");
  			}
  		}
  	}