//----------------------------------------------------------------------------//
// //
// C l e f P a t t e r n //
// //
//----------------------------------------------------------------------------//
// <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.pattern;
import omr.constant.ConstantSet;
import omr.glyph.Evaluation;
import omr.glyph.GlyphNetwork;
import omr.glyph.Glyphs;
import omr.glyph.Grades;
import omr.glyph.Nest;
import omr.glyph.Shape;
import omr.glyph.ShapeSet;
import omr.glyph.facets.Glyph;
import omr.grid.StaffInfo;
import omr.sheet.Scale;
import omr.sheet.SystemInfo;
import omr.util.HorizontalSide;
import omr.util.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Rectangle;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Class {@code ClefPattern} verifies all the initial clefs of a
* system, using an intersection inner rectangle and a containing
* outer rectangle to retrieve the clef glyphs and only those ones.
*
* @author Hervé Bitteur
*/
public class ClefPattern
extends GlyphPattern
{
//~ Static fields/initializers ---------------------------------------------
/** Specific application parameters */
private static final Constants constants = new Constants();
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(ClefPattern.class);
/** Specific predicate to filter clef shapes */
private static final Predicate<Shape> clefShapePredicate = new Predicate<Shape>()
{
@Override
public boolean check (Shape shape)
{
return ShapeSet.Clefs.contains(shape);
}
};
/** Specific predicate to filter clef glyphs */
private static final Predicate<Glyph> clefGlyphPredicate = new Predicate<Glyph>()
{
@Override
public boolean check (Glyph glyph)
{
return glyph.isClef();
}
};
//~ Instance fields --------------------------------------------------------
/** Glyphs nest */
private final Nest nest;
// Scale-dependent parameters
private final int clefWidth;
private final int xOffset;
private final int yOffset;
private final int xMargin;
private final int yMargin;
//~ Constructors -----------------------------------------------------------
/**
* Creates a new ClefPattern object.
*
* @param system the containing system
*/
public ClefPattern (SystemInfo system)
{
super("Clef", system);
nest = system.getSheet().getNest();
clefWidth = scale.toPixels(constants.clefWidth);
xOffset = scale.toPixels(constants.xOffset);
yOffset = scale.toPixels(constants.yOffset);
xMargin = scale.toPixels(constants.xMargin);
yMargin = scale.toPixels(constants.yMargin);
}
//~ Methods ----------------------------------------------------------------
//------------//
// runPattern //
//------------//
/**
* Check that each staff begins with a clef.
*
* @return the number of clefs rebuilt
*/
@Override
public int runPattern ()
{
int successNb = 0;
int staffId = 0;
for (StaffInfo staff : system.getStaves()) {
staffId++;
// Define the inner box to intersect clef glyph(s)
int left = (int) Math.rint(
staff.getAbscissa(HorizontalSide.LEFT));
Rectangle inner = new Rectangle(
left + (2 * xOffset) + (clefWidth / 2),
staff.getFirstLine().yAt(left) + (staff.getHeight() / 2),
0,
0);
inner.grow(
(clefWidth / 2) - xOffset,
(staff.getHeight() / 2) - yOffset);
// Remember the box, for visual debug
staff.addAttachment(" ci", inner);
// We must find a clef out of these glyphs
Collection<Glyph> glyphs = system.lookupIntersectedGlyphs(inner);
logger.debug("{}{}", staffId, Glyphs.toString(" int", glyphs));
// We assume than there can't be any alien among them, so we should
// rebuild the larger glyph which the alien had wrongly segmented
Set<Glyph> impacted = new HashSet<>();
for (Glyph glyph : glyphs) {
if (glyph.getShape() == Shape.STEM) {
logger.debug("Clef: Removed stem#{}", glyph.getId());
impacted.addAll(glyph.getConnectedNeighbors());
impacted.add(glyph);
}
}
if (!impacted.isEmpty()) {
// Rebuild the larger glyph
Glyph larger = system.buildCompound(impacted);
if (larger != null) {
logger.debug("Rebuilt stem-segmented {}", larger.idString());
}
// Recompute the set of intersected glyphs
glyphs = system.lookupIntersectedGlyphs(inner);
}
if (checkClef(glyphs, staff)) {
successNb++;
}
}
return successNb;
}
//-----------//
// checkClef //
//-----------//
/**
* Try to recognize a clef in the compound of the provided glyphs.
*
* @param glyphs the parts of a clef candidate
* @param staff the containing staff
* @return true if successful
*/
private boolean checkClef (Collection<Glyph> glyphs,
StaffInfo staff)
{
if (glyphs.isEmpty()) {
return false;
}
// Check if we already have a clef among the intersected glyphs
Set<Glyph> clefs = Glyphs.lookupGlyphs(glyphs, clefGlyphPredicate);
Glyph orgClef = null;
if (!clefs.isEmpty()) {
if (Glyphs.containsManual(clefs)) {
return false; // Respect user decision
} else {
// Remember grade of the best existing clef
for (Glyph glyph : clefs) {
if ((orgClef == null)
|| (glyph.getGrade() > orgClef.getGrade())) {
orgClef = glyph;
}
}
}
}
// Remove potential aliens
Glyphs.purgeManuals(glyphs);
Glyph compound = system.buildTransientCompound(glyphs);
// Check if a clef appears in the top evaluations
Evaluation vote = GlyphNetwork.getInstance().vote(
compound,
system,
Grades.clefMinGrade,
clefShapePredicate);
if ((vote != null)
&& ((orgClef == null) || (vote.grade > orgClef.getGrade()))) {
// We now have a clef!
// Look around for an even better result...
logger.debug("{} built from {}",
vote.shape, Glyphs.toString(glyphs));
// Look for larger stuff
Rectangle outer = compound.getBounds();
outer.grow(xMargin, yMargin);
// Remember the box, for visual debug
staff.addAttachment("co", outer);
List<Glyph> outerGlyphs = system.lookupIntersectedGlyphs(outer);
outerGlyphs.removeAll(glyphs);
Collections.sort(outerGlyphs, Glyph.byReverseWeight);
final double minWeight = constants.minWeight.getValue();
for (Glyph g : outerGlyphs) {
// Consider only glyphs with a minimum weight
if (g.getNormalizedWeight() < minWeight) {
break;
}
logger.debug("Considering {}", g);
Glyph newCompound = system.buildTransientCompound(
Arrays.asList(compound, g));
final Evaluation newVote = GlyphNetwork.getInstance().vote(
newCompound,
system,
Grades.clefMinGrade,
clefShapePredicate);
if ((newVote != null) && (newVote.grade > vote.grade)) {
logger.debug("{} better built with {}", vote, g.idString());
compound = newCompound;
vote = newVote;
}
}
// Register the last definition of the clef
compound = system.addGlyph(compound);
compound.setShape(vote.shape, Evaluation.ALGORITHM);
logger.debug("{} rebuilt as {}", vote.shape, compound.idString());
return true;
} else {
return false;
}
}
//~ Inner Classes ----------------------------------------------------------
//-----------//
// Constants //
//-----------//
private static final class Constants
extends ConstantSet
{
//~ Instance fields ----------------------------------------------------
Scale.Fraction clefWidth = new Scale.Fraction(
3d,
"Width of a clef");
Scale.Fraction xOffset = new Scale.Fraction(
0.2d,
"Clef horizontal offset since left bar");
Scale.Fraction yOffset = new Scale.Fraction(
0d,
"Clef vertical offset since staff line");
Scale.Fraction xMargin = new Scale.Fraction(
0d,
"Clef horizontal outer margin");
Scale.Fraction yMargin = new Scale.Fraction(
0.5d,
"Clef vertical outer margin");
Scale.AreaFraction minWeight = new Scale.AreaFraction(
0.1,
"Minimum normalized weight to be added to a clef");
}
}