docs: private void trackFeatures(Image input, RectangleRotate_F64 region) {
  		pairs.reset();
  
  		currentImage.process(input);
  		for( int i = 0; i < currentImage.getNumLayers(); i++ ) {
  			Image layer = currentImage.getLayer(i);
  			gradient.process(layer,currentDerivX[i],currentDerivY[i]);
  		}
  
  		// convert to float to avoid excessive conversions from double to float
  		float cx = (float)region.cx;
  		float cy = (float)region.cy;
  
  		float height = (float)(region.height);
  		float width = (float)(region.width);
  
  		float c = (float)Math.cos(region.theta);
  		float s = (float)Math.sin(region.theta);
  
  		float p = 1.0f/(config.numberOfSamples-1);
  		for( int i = 0; i < config.numberOfSamples; i++ ) {
  			float y = (p*i-0.5f)*height;
  			for( int j = 0; j < config.numberOfSamples; j++ ) {
  				float x = (p*j - 0.5f)*width;
  				float xx = cx + x*c - y*s;
  				float yy = cy + x*s + y*c;
  
  				// track in the forward direction
  				track.x = xx;
  				track.y = yy;
  
  				klt.setImage(previousImage,previousDerivX,previousDerivY);
  				if( !klt.setDescription(track) ) {
  					continue;
  				}
  
  				klt.setImage(currentImage,currentDerivX,currentDerivY);
  				KltTrackFault fault = klt.track(track);
  				if( fault != KltTrackFault.SUCCESS ) {
  					continue;
  				}
  
  				float xc = track.x;
  				float yc = track.y;
  
  				// validate by tracking backwards
  				if( !klt.setDescription(track) ) {
  					continue;
  				}
  				klt.setImage(previousImage,previousDerivX,previousDerivY);
  				fault = klt.track(track);
  				if( fault != KltTrackFault.SUCCESS ) {
  					continue;
  				}
  
  				float error = UtilPoint2D_F32.distanceSq(track.x, track.y, xx, yy);
  
  				if( error > maximumErrorFB ) {
  					continue;
  				}
  
  				// create a list of the observations
  				AssociatedPair a = pairs.grow();
  
  				a.p1.x = xx;
  				a.p1.y = yy;
  				a.p2.x = xc;
  				a.p2.y = yc;
  			}
  		}
  	}