@Override
  public void process(T image) {
    // detect features and setup describe and orientation
    detector.detect(image);

    describe.setImage(image);
    if (orientation != null) orientation.setImage(image);

    // go through each set of features
    for (int i = 0; i < info.length; i++) {
      FoundPointSO points = detector.getFeatureSet(i);
      SetInfo<TD> setInfo = info[i];
      setInfo.reset();

      // describe each detected feature
      for (int j = 0; j < points.getNumberOfFeatures(); j++) {
        Point2D_F64 p = points.getLocation(j);
        double radius = points.getRadius(j);
        double ori = points.getOrientation(j);

        if (orientation != null) {
          orientation.setObjectRadius(radius);
          ori = orientation.compute(p.x, p.y);
        }

        TD d = setInfo.descriptors.grow();

        if (describe.process(p.x, p.y, ori, radius, d)) {
          setInfo.location.grow().set(p);
        } else {
          setInfo.descriptors.removeTail();
        }
      }
    }
  }
  private void extractImageFeatures(
      MultiSpectral<T> color, T gray, FastQueue<TupleDesc> descs, List<Point2D_F64> locs) {
    detector.detect(gray);
    if (describe.getImageType().getFamily() == ImageType.Family.SINGLE_BAND)
      describe.setImage(gray);
    else describe.setImage(color);
    orientation.setImage(gray);

    if (detector.hasScale()) {
      for (int i = 0; i < detector.getNumberOfFeatures(); i++) {
        double yaw = 0;

        Point2D_F64 pt = detector.getLocation(i);
        double scale = detector.getScale(i);
        if (describe.requiresOrientation()) {
          orientation.setScale(scale);
          yaw = orientation.compute(pt.x, pt.y);
        }

        TupleDesc d = descs.grow();
        if (describe.process(pt.x, pt.y, yaw, scale, d)) {
          locs.add(pt.copy());
        } else {
          descs.removeTail();
        }
      }
    } else {
      orientation.setScale(1);
      for (int i = 0; i < detector.getNumberOfFeatures(); i++) {
        double yaw = 0;

        Point2D_F64 pt = detector.getLocation(i);
        if (describe.requiresOrientation()) {
          yaw = orientation.compute(pt.x, pt.y);
        }

        TupleDesc d = descs.grow();
        if (describe.process(pt.x, pt.y, yaw, 1, d)) {
          locs.add(pt.copy());
        } else {
          descs.removeTail();
        }
      }
    }
  }
 @Override
 public Class<TD> getDescriptionType() {
   return describe.getDescriptionType();
 }
 @Override
 public TD createDescription() {
   return describe.createDescription();
 }
 private AssociateDescription createMatcher() {
   ScoreAssociation scorer = FactoryAssociation.defaultScore(describe.getDescriptionType());
   return FactoryAssociation.greedy(scorer, Double.MAX_VALUE, associateBackwards);
 }