// -------------//
  // computeLine //
  // -------------//
  public void computeLine() {
    line = new BasicLine();

    for (GlyphSection section : glyph.getMembers()) {
      StickSection ss = (StickSection) section;
      line.includeLine(ss.getLine());
    }

    if (logger.isFineEnabled()) {
      logger.fine(
          line
              + " pointNb="
              + line.getNumberOfPoints()
              + " meanDistance="
              + (float) line.getMeanDistance());
    }
  }
  // ---------------//
  // isExtensionOf //
  // ---------------//
  public boolean isExtensionOf(
      Stick other, int maxDeltaCoord, int maxDeltaPos, double maxDeltaSlope) {
    // Check that a pair of start/stop is compatible
    if ((Math.abs(other.getStart() - getStop()) <= maxDeltaCoord)
        || (Math.abs(other.getStop() - getStart()) <= maxDeltaCoord)) {
      // Check that a pair of positions is compatible
      if ((Math.abs(other.getLine().yAt(other.getStart()) - getLine().yAt(other.getStop()))
              <= maxDeltaPos)
          || (Math.abs(other.getLine().yAt(other.getStop()) - getLine().yAt(other.getStart()))
              <= maxDeltaPos)) {
        // Check that slopes are compatible (a useless test ?)
        if (Math.abs(other.getLine().getSlope() - getLine().getSlope()) <= maxDeltaSlope) {
          return true;
        } else if (logger.isFineEnabled()) {
          logger.fine("isExtensionOf:  Incompatible slopes");
        }
      } else if (logger.isFineEnabled()) {
        logger.fine("isExtensionOf:  Incompatible positions");
      }
    } else if (logger.isFineEnabled()) {
      logger.fine("isExtensionOf:  Incompatible coordinates");
    }

    return false;
  }
  // ------------------//
  // getAlienPixelsIn //
  // ------------------//
  public int getAlienPixelsIn(Rectangle area) {
    int count = 0;
    final int posMin = area.y;
    final int posMax = (area.y + area.height) - 1;
    final List<GlyphSection> neighbors = glyph.getLag().getSectionsIn(area);

    for (GlyphSection section : neighbors) {
      // Keep only non-patch sections that are not part of the stick
      if (!section.isPatch() && (section.getGlyph() != glyph)) {
        int pos = section.getFirstPos() - 1; // Ordinate for horizontal,
        // Abscissa for vertical

        for (Run run : section.getRuns()) {
          pos++;

          if (pos > posMax) {
            break;
          }

          if (pos < posMin) {
            continue;
          }

          int coordMin = Math.max(area.x, run.getStart());
          int coordMax = Math.min((area.x + area.width) - 1, run.getStop());

          if (coordMax >= coordMin) {
            count += (coordMax - coordMin + 1);
          }
        }
      }
    }

    if (logger.isFineEnabled()) {
      logger.fine("Stick" + glyph.getId() + " " + area + " getAlienPixelsIn=" + count);
    }

    return count;
  }
/**
 * Class {@code BasicAlignment} implements a basic handling of Alignment facet
 *
 * @author Hervé Bitteur
 */
class BasicAlignment extends BasicFacet implements GlyphAlignment {
  // ~ Static fields/initializers ---------------------------------------------

  /** Usual logger utility */
  private static final Logger logger = Logger.getLogger(BasicAlignment.class);

  // ~ Instance fields --------------------------------------------------------

  /** Best line equation */
  private Line line;

  // ~ Constructors -----------------------------------------------------------

  /**
   * Create a new BasicAlignment object
   *
   * @param glyph our glyph
   */
  public BasicAlignment(Glyph glyph) {
    super(glyph);
  }

  // ~ Methods ----------------------------------------------------------------

  // ------------------//
  // getAlienPixelsIn //
  // ------------------//
  public int getAlienPixelsIn(Rectangle area) {
    int count = 0;
    final int posMin = area.y;
    final int posMax = (area.y + area.height) - 1;
    final List<GlyphSection> neighbors = glyph.getLag().getSectionsIn(area);

    for (GlyphSection section : neighbors) {
      // Keep only non-patch sections that are not part of the stick
      if (!section.isPatch() && (section.getGlyph() != glyph)) {
        int pos = section.getFirstPos() - 1; // Ordinate for horizontal,
        // Abscissa for vertical

        for (Run run : section.getRuns()) {
          pos++;

          if (pos > posMax) {
            break;
          }

          if (pos < posMin) {
            continue;
          }

          int coordMin = Math.max(area.x, run.getStart());
          int coordMax = Math.min((area.x + area.width) - 1, run.getStop());

          if (coordMax >= coordMin) {
            count += (coordMax - coordMin + 1);
          }
        }
      }
    }

    if (logger.isFineEnabled()) {
      logger.fine("Stick" + glyph.getId() + " " + area + " getAlienPixelsIn=" + count);
    }

    return count;
  }

  // ------------------//
  // getAliensAtStart //
  // ------------------//
  public int getAliensAtStart(int dCoord, int dPos) {
    return getAlienPixelsIn(new Rectangle(getStart(), getStartingPos() - dPos, dCoord, 2 * dPos));
  }

  // -----------------------//
  // getAliensAtStartFirst //
  // -----------------------//
  public int getAliensAtStartFirst(int dCoord, int dPos) {
    return getAlienPixelsIn(new Rectangle(getStart(), getStartingPos() - dPos, dCoord, dPos));
  }

  // ----------------------//
  // getAliensAtStartLast //
  // ----------------------//
  public int getAliensAtStartLast(int dCoord, int dPos) {
    return getAlienPixelsIn(new Rectangle(getStart(), getStartingPos(), dCoord, dPos));
  }

  // -----------------//
  // getAliensAtStop //
  // -----------------//
  public int getAliensAtStop(int dCoord, int dPos) {
    return getAlienPixelsIn(
        new Rectangle(getStop() - dCoord, getStoppingPos() - dPos, dCoord, 2 * dPos));
  }

  // ----------------------//
  // getAliensAtStopFirst //
  // ----------------------//
  public int getAliensAtStopFirst(int dCoord, int dPos) {
    return getAlienPixelsIn(
        new Rectangle(getStop() - dCoord, getStoppingPos() - dPos, dCoord, dPos));
  }

  // ---------------------//
  // getAliensAtStopLast //
  // ---------------------//
  public int getAliensAtStopLast(int dCoord, int dPos) {
    return getAlienPixelsIn(new Rectangle(getStop() - dCoord, getStoppingPos(), dCoord, dPos));
  }

  // -----------//
  // getAspect //
  // -----------//
  public double getAspect() {
    return (double) getLength() / (double) getThickness();
  }

  // ---------------//
  // isExtensionOf //
  // ---------------//
  public boolean isExtensionOf(
      Stick other, int maxDeltaCoord, int maxDeltaPos, double maxDeltaSlope) {
    // Check that a pair of start/stop is compatible
    if ((Math.abs(other.getStart() - getStop()) <= maxDeltaCoord)
        || (Math.abs(other.getStop() - getStart()) <= maxDeltaCoord)) {
      // Check that a pair of positions is compatible
      if ((Math.abs(other.getLine().yAt(other.getStart()) - getLine().yAt(other.getStop()))
              <= maxDeltaPos)
          || (Math.abs(other.getLine().yAt(other.getStop()) - getLine().yAt(other.getStart()))
              <= maxDeltaPos)) {
        // Check that slopes are compatible (a useless test ?)
        if (Math.abs(other.getLine().getSlope() - getLine().getSlope()) <= maxDeltaSlope) {
          return true;
        } else if (logger.isFineEnabled()) {
          logger.fine("isExtensionOf:  Incompatible slopes");
        }
      } else if (logger.isFineEnabled()) {
        logger.fine("isExtensionOf:  Incompatible positions");
      }
    } else if (logger.isFineEnabled()) {
      logger.fine("isExtensionOf:  Incompatible coordinates");
    }

    return false;
  }

  // -------------//
  // getFirstPos //
  // -------------//
  public int getFirstPos() {
    return glyph.getBounds().y;
  }

  // ---------------//
  // getFirstStuck //
  // ---------------//
  public int getFirstStuck() {
    int stuck = 0;

    for (GlyphSection section : glyph.getMembers()) {
      Run sectionRun = section.getFirstRun();

      for (GlyphSection sct : section.getSources()) {
        if (!sct.isGlyphMember() || (sct.getGlyph() != glyph)) {
          stuck += sectionRun.getCommonLength(sct.getLastRun());
        }
      }
    }

    return stuck;
  }

  // ------------//
  // getLastPos //
  // ------------//
  public int getLastPos() {
    return (getFirstPos() + getThickness()) - 1;
  }

  // --------------//
  // getLastStuck //
  // --------------//
  public int getLastStuck() {
    int stuck = 0;

    for (GlyphSection section : glyph.getMembers()) {
      Run sectionRun = section.getLastRun();

      for (GlyphSection sct : section.getTargets()) {
        if (!sct.isGlyphMember() || (sct.getGlyph() != glyph)) {
          stuck += sectionRun.getCommonLength(sct.getFirstRun());
        }
      }
    }

    return stuck;
  }

  // -----------//
  // getLength //
  // -----------//
  public int getLength() {
    return glyph.getBounds().width;
  }

  // ---------//
  // getLine //
  // ---------//
  public Line getLine() {
    if (line == null) {
      computeLine();
    }

    return line;
  }

  // -----------//
  // getMidPos //
  // -----------//
  public int getMidPos() {
    if (getLine().isVertical()) {
      // Fall back value
      return (int) Math.rint((getFirstPos() + getLastPos()) / 2.0);
    } else {
      return (int) Math.rint(getLine().yAt((getStart() + getStop()) / 2.0));
    }
  }

  // ----------//
  // getStart //
  // ----------//
  public int getStart() {
    return glyph.getBounds().x;
  }

  // ---------------//
  // getStartPoint //
  // ---------------//
  public PixelPoint getStartPoint() {
    Point start = glyph.getLag().switchRef(new Point(getStart(), line.yAt(getStart())), null);

    return new PixelPoint(start.x, start.y);
  }

  // ----------------//
  // getStartingPos //
  // ----------------//
  public int getStartingPos() {
    if ((getThickness() >= 2) && !getLine().isVertical()) {
      return getLine().yAt(getStart());
    } else {
      return getFirstPos() + (getThickness() / 2);
    }
  }

  // ---------//
  // getStop //
  // ---------//
  public int getStop() {
    return (getStart() + getLength()) - 1;
  }

  // --------------//
  // getStopPoint //
  // --------------//
  public PixelPoint getStopPoint() {
    Point stop = glyph.getLag().switchRef(new Point(getStop(), line.yAt(getStop())), null);

    return new PixelPoint(stop.x, stop.y);
  }

  // ----------------//
  // getStoppingPos //
  // ----------------//
  public int getStoppingPos() {
    if ((getThickness() >= 2) && !getLine().isVertical()) {
      return getLine().yAt(getStop());
    } else {
      return getFirstPos() + (getThickness() / 2);
    }
  }

  // --------------//
  // getThickness //
  // --------------//
  public int getThickness() {
    return glyph.getBounds().height;
  }

  // -------------//
  // computeLine //
  // -------------//
  public void computeLine() {
    line = new BasicLine();

    for (GlyphSection section : glyph.getMembers()) {
      StickSection ss = (StickSection) section;
      line.includeLine(ss.getLine());
    }

    if (logger.isFineEnabled()) {
      logger.fine(
          line
              + " pointNb="
              + line.getNumberOfPoints()
              + " meanDistance="
              + (float) line.getMeanDistance());
    }
  }

  // ------//
  // dump //
  // ------//
  /** Print out glyph internal data */
  @Override
  public void dump() {
    super.dump();
    System.out.println("   line=" + getLine());
  }

  // --------------//
  // overlapsWith //
  // --------------//
  public boolean overlapsWith(Stick other) {
    return Math.max(getStart(), other.getStart()) < Math.min(getStop(), other.getStop());
  }

  // ------------//
  // renderLine //
  // ------------//
  public void renderLine(Graphics g) {
    if (glyph.getContourBox().intersects(g.getClipBounds())) {
      getLine(); // To make sure the line has been computed

      Point start =
          glyph
              .getLag()
              .switchRef(
                  new Point(getStart(), (int) Math.rint(line.yAt((double) getStart()))), null);
      Point stop =
          glyph
              .getLag()
              .switchRef(
                  new Point(getStop() + 1, (int) Math.rint(line.yAt((double) getStop() + 1))),
                  null);
      g.drawLine(start.x, start.y, stop.x, stop.y);
    }
  }
}
/**
 * Class <code>Dynamics</code> represents a dynamics event
 *
 * @author Hervé Bitteur
 */
public class Dynamics extends MeasureElement implements Direction, Notation {
  // ~ Static fields/initializers ---------------------------------------------

  /** Usual logger utility */
  private static final Logger logger = Logger.getLogger(Dynamics.class);

  /** Specific application parameters */
  private static final Constants constants = new Constants();

  /** Map Shape -> Signature */
  private static final Map<Shape, String> sigs = new HashMap<Shape, String>();

  static {
    // Additional characters : m, r, s & z
    sigs.put(Shape.DYNAMICS_CHAR_M, "m");
    sigs.put(Shape.DYNAMICS_CHAR_R, "r");
    sigs.put(Shape.DYNAMICS_CHAR_S, "s");
    sigs.put(Shape.DYNAMICS_CHAR_Z, "z");
    //
    // True dynamics symbols
    sigs.put(Shape.DYNAMICS_F, "f");
    sigs.put(Shape.DYNAMICS_FF, "ff");
    sigs.put(Shape.DYNAMICS_FFF, "fff");
    sigs.put(Shape.DYNAMICS_FFFF, "ffff");
    sigs.put(Shape.DYNAMICS_FFFFF, "fffff");
    sigs.put(Shape.DYNAMICS_FFFFFF, "ffffff");
    sigs.put(Shape.DYNAMICS_FP, "fp");
    sigs.put(Shape.DYNAMICS_FZ, "fz");
    sigs.put(Shape.DYNAMICS_MF, "mf");
    sigs.put(Shape.DYNAMICS_MP, "mp");
    sigs.put(Shape.DYNAMICS_P, "p");
    sigs.put(Shape.DYNAMICS_PP, "pp");
    sigs.put(Shape.DYNAMICS_PPP, "ppp");
    sigs.put(Shape.DYNAMICS_PPPP, "pppp");
    sigs.put(Shape.DYNAMICS_PPPPP, "ppppp");
    sigs.put(Shape.DYNAMICS_PPPPPP, "pppppp");
    sigs.put(Shape.DYNAMICS_RF, "rf");
    sigs.put(Shape.DYNAMICS_RFZ, "rfz");
    sigs.put(Shape.DYNAMICS_SF, "sf");
    sigs.put(Shape.DYNAMICS_SFFZ, "sffz");
    sigs.put(Shape.DYNAMICS_SFP, "sfp");
    sigs.put(Shape.DYNAMICS_SFPP, "sfpp");
    sigs.put(Shape.DYNAMICS_SFZ, "sfz");
  }

  /* Map Signature -> Shape */
  private static final Map<String, Shape> shapes = new HashMap<String, Shape>();

  static {
    shapes.put("f", Shape.DYNAMICS_F);
    shapes.put("ff", Shape.DYNAMICS_FF);
    shapes.put("fff", Shape.DYNAMICS_FFF);
    shapes.put("ffff", Shape.DYNAMICS_FFFF);
    shapes.put("fffff", Shape.DYNAMICS_FFFFF);
    shapes.put("ffffff", Shape.DYNAMICS_FFFFFF);
    shapes.put("fp", Shape.DYNAMICS_FP);
    shapes.put("fz", Shape.DYNAMICS_FZ);
    shapes.put("mf", Shape.DYNAMICS_MF);
    shapes.put("mp", Shape.DYNAMICS_MP);
    shapes.put("p", Shape.DYNAMICS_P);
    shapes.put("pp", Shape.DYNAMICS_PP);
    shapes.put("ppp", Shape.DYNAMICS_PPP);
    shapes.put("pppp", Shape.DYNAMICS_PPPP);
    shapes.put("ppppp", Shape.DYNAMICS_PPPPP);
    shapes.put("pppppp", Shape.DYNAMICS_PPPPPP);
    shapes.put("rf", Shape.DYNAMICS_RF);
    shapes.put("rfz", Shape.DYNAMICS_RFZ);
    shapes.put("sf", Shape.DYNAMICS_SF);
    shapes.put("sffz", Shape.DYNAMICS_SFFZ);
    shapes.put("sfp", Shape.DYNAMICS_SFP);
    shapes.put("sfpp", Shape.DYNAMICS_SFPP);
    shapes.put("sfz", Shape.DYNAMICS_SFZ);
  }

  // ~ Constructors -----------------------------------------------------------

  // ----------//
  // Dynamics //
  // ----------//
  /**
   * Creates a new instance of Dynamics event
   *
   * @param measure measure that contains this mark
   * @param point location of mark
   * @param chord the chord related to the mark
   * @param glyph the underlying glyph
   */
  public Dynamics(Measure measure, SystemPoint point, Chord chord, Glyph glyph) {
    super(measure, true, point, chord, glyph);

    if (chord != null) {
      chord.addDirection(this); // //// TODO: Not always !!!!!!!!!!!!!!!!!!!
    }
  }

  // ~ Methods ----------------------------------------------------------------

  // --------//
  // accept //
  // --------//
  @Override
  public boolean accept(ScoreVisitor visitor) {
    return visitor.visit(this);
  }

  // ----------//
  // populate //
  // ----------//
  /**
   * Used by SystemTranslator to allocate the dynamics marks
   *
   * @param glyph underlying glyph
   * @param measure measure where the mark is located
   * @param point location for the mark
   */
  public static void populate(Glyph glyph, Measure measure, SystemPoint point) {
    // Can we gather with another dynamics letter? (e.g. m + p -> mp)
    for (TreeNode node : measure.getChildren()) {
      if (node instanceof Dynamics) {
        Dynamics d = (Dynamics) node;

        if (d.isCompatibleWith(point)) {
          d.addGlyph(glyph);
          glyph.setTranslation(d);

          return;
        }
      }
    }

    // Otherwise, create a brand new instance
    glyph.setTranslation(new Dynamics(measure, point, findChord(measure, point), glyph));
  }

  // ---------------//
  // computeCenter //
  // ---------------//
  @Override
  protected void computeCenter() {
    setCenter(computeGlyphsCenter(getGlyphs()));
  }

  // --------------//
  // computeShape //
  // --------------//
  @Override
  protected Shape computeShape() {
    StringBuilder sig = new StringBuilder();

    for (Glyph glyph : getGlyphs()) {
      sig.append(sigs.get(glyph.getShape()));
    }

    Shape shape = shapes.get(sig.toString());

    if (shape == null) {
      addError("Invalid dynamics signature:" + sig);
    }

    return shape;
  }

  // ------------------//
  // isCompatibleWith //
  // ------------------//
  private boolean isCompatibleWith(SystemPoint point) {
    // Check x-proximity and y-alignment
    Scale scale = getSystem().getScale();
    int dx = scale.toUnits(constants.maxDx);
    int dy = scale.toUnits(constants.maxDy);

    // Horizontal distance
    int xDist =
        Math.min(Math.abs(getBox().x - point.x), Math.abs((getBox().x + getBox().width) - point.x));

    // Vertical distance
    int yDist = Math.abs(getReferencePoint().y - point.y);

    return (xDist <= dx) && (yDist <= dy);
  }

  // ~ Inner Classes ----------------------------------------------------------

  // -----------//
  // Constants //
  // -----------//
  private static final class Constants extends ConstantSet {
    // ~ Instance fields ----------------------------------------------------

    /** Maximum abscissa difference */
    Scale.Fraction maxDx = new Scale.Fraction(1.5, "Maximum abscissa difference");

    /** Maximum ordinate difference */
    Scale.Fraction maxDy = new Scale.Fraction(0.5, "Maximum ordinate difference");
  }
}
  /**
   * In a specified system, look for all stems that should not be kept, rebuild surrounding glyphs
   * and try to recognize them. If this action does not lead to some recognized symbol, then we
   * restore the stems.
   *
   * @return the number of symbols recognized
   */
  public int runStemPattern() {
    int nb = 0;

    // Collect all undue stems
    List<Glyph> SuspectedStems = new ArrayList<Glyph>();

    for (Glyph glyph : system.getGlyphs()) {
      if (glyph.isStem() && glyph.isActive()) {
        Set<Glyph> goods = new HashSet<Glyph>();
        Set<Glyph> bads = new HashSet<Glyph>();
        glyph.getSymbolsBefore(reliableStemSymbols, goods, bads);
        glyph.getSymbolsAfter(reliableStemSymbols, goods, bads);

        if (goods.isEmpty()) {
          if (logger.isFineEnabled()) {
            logger.finest("Suspected Stem " + glyph);
          }

          SuspectedStems.add(glyph);

          // Discard "bad" ones
          for (Glyph g : bads) {
            if (logger.isFineEnabled()) {
              logger.finest("Deassigning bad glyph " + g);
            }

            g.setShape((Shape) null);
          }
        }
      }
    }

    // Remove these stem glyphs since nearby stems are used for recognition
    for (Glyph glyph : SuspectedStems) {
      system.removeGlyph(glyph);
    }

    // Extract brand new glyphs (removeInactiveGlyphs + retrieveGlyphs)
    system.extractNewGlyphs();

    // Try to recognize each glyph in turn
    List<Glyph> symbols = new ArrayList<Glyph>();
    final GlyphEvaluator evaluator = GlyphNetwork.getInstance();
    final double maxDoubt = GlyphInspector.getPatternsMaxDoubt();

    for (Glyph glyph : system.getGlyphs()) {
      if (glyph.getShape() == null) {
        Evaluation vote = evaluator.vote(glyph, maxDoubt);

        if (vote != null) {
          glyph.setShape(vote.shape, vote.doubt);

          if (glyph.isWellKnown()) {
            if (logger.isFineEnabled()) {
              logger.finest("New symbol " + glyph);
            }

            symbols.add(glyph);
            nb++;
          }
        }
      }
    }

    // Keep stems that have not been replaced by symbols, definitively
    // remove the others
    for (Glyph stem : SuspectedStems) {
      // Check if one of its section is now part of a symbol
      boolean known = false;
      Glyph glyph = null;

      for (GlyphSection section : stem.getMembers()) {
        glyph = section.getGlyph();

        if ((glyph != null) && glyph.isWellKnown()) {
          known = true;

          break;
        }
      }

      if (!known) {
        // Remove the newly created glyph
        if (glyph != null) {
          system.removeGlyph(glyph);
        }

        // Restore the stem
        system.addGlyph(stem);
      }
    }

    return nb;
  }
/**
 * Class <code>StemInspector</code> is a GlyphInspector dedicated to the inspection of Stems at
 * System level
 *
 * @author Hervé Bitteur
 */
public class StemInspector {
  // ~ Static fields/initializers ---------------------------------------------

  /** Usual logger utility */
  private static final Logger logger = Logger.getLogger(StemInspector.class);

  /** Predicate to filter only reliable symbols attached to a stem */
  private static final Predicate<Glyph> reliableStemSymbols =
      new Predicate<Glyph>() {
        public boolean check(Glyph glyph) {
          Shape shape = glyph.getShape();

          boolean res =
              glyph.isWellKnown()
                  && ShapeRange.StemSymbols.contains(shape)
                  && (shape != Shape.BEAM_HOOK);

          return res;
        }
      };

  // ~ Instance fields --------------------------------------------------------

  /** Dedicated system */
  private final SystemInfo system;

  // ~ Constructors -----------------------------------------------------------

  /**
   * Creates a new StemInspector object.
   *
   * @param system the dedicated system
   */
  public StemInspector(SystemInfo system) {
    this.system = system;
  }

  // ~ Methods ----------------------------------------------------------------

  // ----------------//
  // runStemPattern //
  // ----------------//
  /**
   * In a specified system, look for all stems that should not be kept, rebuild surrounding glyphs
   * and try to recognize them. If this action does not lead to some recognized symbol, then we
   * restore the stems.
   *
   * @return the number of symbols recognized
   */
  public int runStemPattern() {
    int nb = 0;

    // Collect all undue stems
    List<Glyph> SuspectedStems = new ArrayList<Glyph>();

    for (Glyph glyph : system.getGlyphs()) {
      if (glyph.isStem() && glyph.isActive()) {
        Set<Glyph> goods = new HashSet<Glyph>();
        Set<Glyph> bads = new HashSet<Glyph>();
        glyph.getSymbolsBefore(reliableStemSymbols, goods, bads);
        glyph.getSymbolsAfter(reliableStemSymbols, goods, bads);

        if (goods.isEmpty()) {
          if (logger.isFineEnabled()) {
            logger.finest("Suspected Stem " + glyph);
          }

          SuspectedStems.add(glyph);

          // Discard "bad" ones
          for (Glyph g : bads) {
            if (logger.isFineEnabled()) {
              logger.finest("Deassigning bad glyph " + g);
            }

            g.setShape((Shape) null);
          }
        }
      }
    }

    // Remove these stem glyphs since nearby stems are used for recognition
    for (Glyph glyph : SuspectedStems) {
      system.removeGlyph(glyph);
    }

    // Extract brand new glyphs (removeInactiveGlyphs + retrieveGlyphs)
    system.extractNewGlyphs();

    // Try to recognize each glyph in turn
    List<Glyph> symbols = new ArrayList<Glyph>();
    final GlyphEvaluator evaluator = GlyphNetwork.getInstance();
    final double maxDoubt = GlyphInspector.getPatternsMaxDoubt();

    for (Glyph glyph : system.getGlyphs()) {
      if (glyph.getShape() == null) {
        Evaluation vote = evaluator.vote(glyph, maxDoubt);

        if (vote != null) {
          glyph.setShape(vote.shape, vote.doubt);

          if (glyph.isWellKnown()) {
            if (logger.isFineEnabled()) {
              logger.finest("New symbol " + glyph);
            }

            symbols.add(glyph);
            nb++;
          }
        }
      }
    }

    // Keep stems that have not been replaced by symbols, definitively
    // remove the others
    for (Glyph stem : SuspectedStems) {
      // Check if one of its section is now part of a symbol
      boolean known = false;
      Glyph glyph = null;

      for (GlyphSection section : stem.getMembers()) {
        glyph = section.getGlyph();

        if ((glyph != null) && glyph.isWellKnown()) {
          known = true;

          break;
        }
      }

      if (!known) {
        // Remove the newly created glyph
        if (glyph != null) {
          system.removeGlyph(glyph);
        }

        // Restore the stem
        system.addGlyph(stem);
      }
    }

    return nb;
  }
}