docs: public int addPattern(GrayU8 inputBinary, double lengthSide) {
  		if( inputBinary == null ) {
  			throw new IllegalArgumentException("Input image is null.");
  		} else if( lengthSide <= 0 ) {
  			throw new IllegalArgumentException("Parameter lengthSide must be more than zero");
  		} else if(ImageStatistics.max(inputBinary) > 1 )
  			throw new IllegalArgumentException("A binary image is composed on 0 and 1 pixels.  This isn't binary!");
  
  		// see if it needs to be resized
  		if ( inputBinary.width != squareLength || inputBinary.height != squareLength ) {
  			// need to create a new image and rescale it to better handle the resizing
  			GrayF32 inputGray = new GrayF32(inputBinary.width,inputBinary.height);
  			ConvertImage.convert(inputBinary,inputGray);
  			PixelMath.multiply(inputGray,255,inputGray);
  
  			GrayF32 scaled = new GrayF32(squareLength,squareLength);
  
  			// See if it can use the better algorithm for scaling down the image
  			if( inputBinary.width > squareLength && inputBinary.height > squareLength ) {
  				AverageDownSampleOps.down(inputGray,scaled);
  			} else {
  				new FDistort(inputGray,scaled).scaleExt().apply();
  			}
  			GThresholdImageOps.threshold(scaled,binary,255/2.0,false);
  		} else {
  			binary.setTo(inputBinary);
  		}
  
  		// describe it in 4 different orientations
  		FiducialDef def = new FiducialDef();
  		def.lengthSide = lengthSide;
  
  		// CCW rotation so that the index refers to how many CW rotation it takes to put it into the nominal pose
  		binaryToDef(binary, def.desc[0]);
  		ImageMiscOps.rotateCCW(binary);
  		binaryToDef(binary, def.desc[1]);
  		ImageMiscOps.rotateCCW(binary);
  		binaryToDef(binary, def.desc[2]);
  		ImageMiscOps.rotateCCW(binary);
  		binaryToDef(binary, def.desc[3]);
  
  		int index = targets.size();
  		targets.add( def );
  		return index;
  	}