//----------------------------------------------------------------------------// // // // A b s t r a c t G l y p h E v a l u a t o r // // // //----------------------------------------------------------------------------// // <editor-fold defaultstate="collapsed" desc="hdr"> // // Copyright © Hervé Bitteur and others 2000-2013. All rights reserved. // // This software is released under the GNU General Public License. // // Goto http://kenai.com/projects/audiveris to report bugs or suggestions. // //----------------------------------------------------------------------------// // </editor-fold> package omr.glyph; import omr.WellKnowns; import omr.constant.ConstantSet; import omr.glyph.ShapeEvaluator.Condition; import static omr.glyph.ShapeEvaluator.Condition.*; import omr.glyph.facets.Glyph; import omr.sheet.Scale; import omr.sheet.SystemInfo; import omr.util.Predicate; import omr.util.UriUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import javax.swing.JOptionPane; import javax.xml.bind.JAXBException; /** * Class {@code AbstractEvaluationEngine} is an abstract implementation * for any evaluation engine. * * <p> <img src="doc-files/GlyphEvaluator.jpg" /> * * @author Hervé Bitteur */ public abstract class AbstractEvaluationEngine implements EvaluationEngine { //~ Static fields/initializers --------------------------------------------- /** Specific application parameters */ private static final Constants constants = new Constants(); /** Usual logger utility */ private static final Logger logger = LoggerFactory.getLogger( AbstractEvaluationEngine.class); /** Number of shapes to differentiate. */ protected static final int shapeCount = 1 + Shape.LAST_PHYSICAL_SHAPE.ordinal(); /** A special evaluation array, used to report NOISE. */ protected static final Evaluation[] noiseEvaluations = { new Evaluation(Shape.NOISE, Evaluation.ALGORITHM) }; //~ Instance fields -------------------------------------------------------- // /** The glyph checker for additional specific checks. */ protected ShapeChecker glyphChecker = ShapeChecker.getInstance(); //~ Methods ---------------------------------------------------------------- // //----------// // evaluate // //----------// @Override public Evaluation[] evaluate (Glyph glyph, SystemInfo system, int count, double minGrade, EnumSet<ShapeEvaluator.Condition> conditions, Predicate<Shape> predicate) { List<Evaluation> best = new ArrayList<>(); Evaluation[] evals = getRawEvaluations(glyph); EvalsLoop: for (Evaluation eval : evals) { // Bounding test? if ((best.size() >= count) || (eval.grade < minGrade)) { break; } // Predicate? if ((predicate != null) && !predicate.check(eval.shape)) { continue; } // Allowed? if (conditions.contains(Condition.ALLOWED) && glyph.isShapeForbidden(eval.shape)) { continue; } // Successful checks? if (conditions.contains(Condition.CHECKED)) { Evaluation oldEval = new Evaluation(eval.shape, eval.grade); double[] ins = ShapeDescription.features(glyph); // This may change the eval shape... glyphChecker.annotate(system, eval, glyph, ins); if (eval.failure != null) { continue; } // In case the specific checks have changed eval shape // we have to retest against the glyph blacklist if ((eval.shape != oldEval.shape) && conditions.contains(Condition.ALLOWED) && glyph.isShapeForbidden(eval.shape)) { continue; } } // Everything is OK, add the shape if not already in the list for (Evaluation e : best) { if (e.shape == eval.shape) { continue EvalsLoop; } } best.add(eval); } return best.toArray(new Evaluation[0]); } //-------------// // isBigEnough // //-------------// @Override public boolean isBigEnough (Glyph glyph) { return glyph.getNormalizedWeight() >= constants.minWeight.getValue(); } //---------// // marshal // //---------// /** * Store the engine in XML format, always as a user file. */ @Override public void marshal () { final File file = new File(WellKnowns.EVAL_FOLDER, getFileName()); OutputStream os = null; try { os = new FileOutputStream(file); marshal(os); logger.info("Engine marshalled to {}", file); } catch (FileNotFoundException ex) { logger.warn("Could not find file " + file, ex); } catch (IOException ex) { logger.warn("IO error on file " + file, ex); } catch (JAXBException ex) { logger.warn("Error marshalling engine to " + file, ex); } finally { if (os != null) { try { os.close(); } catch (Exception ignored) { } } } } //---------// // rawVote // //---------// @Override public Evaluation rawVote (Glyph glyph, double minGrade, Predicate<Shape> predicate) { Evaluation[] evals = evaluate(glyph, null, 1, minGrade, EnumSet.of(ALLOWED), predicate); if (evals.length > 0) { return evals[0]; } else { return null; } } //------// // stop // //------// /** * Stop the on-going training. * By default, this is a no-op */ @Override public void stop () { } //------// // Vote // //------// @Override public Evaluation vote (Glyph glyph, SystemInfo system, double minGrade, EnumSet<Condition> conditions, Predicate<Shape> predicate) { Evaluation[] evals = evaluate(glyph, system, 1, minGrade, conditions, predicate); if (evals.length > 0) { return evals[0]; } else { return null; } } //------// // vote // //------// @Override public Evaluation vote (Glyph glyph, SystemInfo system, double minGrade, Predicate<Shape> predicate) { Evaluation[] evals = evaluate(glyph, system, 1, minGrade, EnumSet.of(ALLOWED, CHECKED), predicate); if (evals.length > 0) { return evals[0]; } else { return null; } } //------// // vote // //------// @Override public Evaluation vote (Glyph glyph, SystemInfo system, double minGrade) { Evaluation[] evals = evaluate(glyph, system, 1, minGrade, EnumSet.of(ALLOWED, CHECKED), null); if (evals.length > 0) { return evals[0]; } else { return null; } } //-------------// // getFileName // //-------------// /** * Report the simple file name, including extension but excluding * parent, which contains the marshalled data of the evaluator. * * @return the file name */ protected abstract String getFileName (); //-------------------// // getRawEvaluations // //-------------------// /** * Run the evaluator with the specified glyph, and return a * sequence of interpretations (ordered from best to worst) with * no additional check. * * @param glyph the glyph to be examined * @return the ordered best evaluations */ protected abstract Evaluation[] getRawEvaluations (Glyph glyph); //---------// // marshal // //---------// protected abstract void marshal (OutputStream os) throws FileNotFoundException, IOException, JAXBException; //-----------// // unmarshal // //-----------// /** * The specific unmarshalling method which builds a suitable engine. * * @param is the input stream to read * @return the newly built evaluation engine * @throws JAXBException, IOException */ protected abstract Object unmarshal (InputStream is) throws JAXBException, IOException; //-----------// // unmarshal // //-----------// /** * Unmarshal the evaluation engine from the most suitable file. * If a user file does not exist or cannot be unmarshalled, the * system default file is used * * @return the unmarshalled engine, or null if everything failed */ protected Object unmarshal () { // First try user file, if any (in user EVAL folder) { File file = new File(WellKnowns.EVAL_FOLDER, getFileName()); if (file.exists()) { Object obj = unmarshal(file); if (obj == null) { logger.warn("Could not load {}", file); } else { if (!isCompatible(obj)) { final String msg = "Obsolete user data for " + getName() + " in " + file + ", trying default data"; logger.warn(msg); JOptionPane.showMessageDialog(null, msg); } else { // Tell the user we are not using the default logger.info("{} unmarshalled from {}", getName(), file); return obj; } } } } // Use default file (in program RES folder) //file = new File(WellKnowns.RES_URI, getFileName()); URI uri = UriUtil.toURI(WellKnowns.RES_URI, getFileName()); InputStream input; try { input = uri.toURL().openStream(); } catch (Exception ex) { logger.warn("Error in " + uri, ex); return null; } Object obj = unmarshal(input, getFileName()); if (obj == null) { logger.warn("Could not load {}", uri); } else { if (!isCompatible(obj)) { final String msg = "Obsolete default data for " + getName() + " in " + uri + ", please retrain from scratch"; logger.warn(msg); JOptionPane.showMessageDialog(null, msg); obj = null; } else { logger.debug("{} unmarshalled from {}", getName(), uri); } } return obj; } //--------------// // isCompatible // //--------------// /** * Make sure the provided engine object is compatible with the * current application. * * @param obj the engine object * @return true if engine is usable and found compatible */ protected abstract boolean isCompatible (Object obj); //-----------// // unmarshal // //-----------// /** * Unmarshal the evaluation engine using provided file. * * @return the unmarshalled engine, or null if failed */ private Object unmarshal (File file) { try { InputStream input = new FileInputStream(file); return unmarshal(input, getFileName()); } catch (FileNotFoundException ex) { logger.warn("File not found " + file, ex); return null; } catch (Exception ex) { logger.warn("Error unmarshalling from " + file, ex); return null; } } //-----------// // unmarshal // //-----------// private Object unmarshal (InputStream is, String name) { if (is == null) { logger.warn("No data stream for {} engine as {}", getName(), name); } else { try { Object engine = unmarshal(is); is.close(); return engine; } catch (FileNotFoundException ex) { logger.warn("Cannot find or read " + name, ex); } catch (IOException ex) { logger.warn("IO error on " + name, ex); } catch (JAXBException ex) { logger.warn("Error unmarshalling evaluator from " + name, ex); } } return null; } //-----------// // Constants // //-----------// private static final class Constants extends ConstantSet { Scale.AreaFraction minWeight = new Scale.AreaFraction(0.08, "Minimum normalized weight to be considered not a noise"); } }