//----------------------------------------------------------------------------//
// //
// H o r i z o n t a l s B u i l d e 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.sheet;
import omr.check.Check;
import omr.check.CheckBoard;
import omr.check.CheckSuite;
import omr.check.Checkable;
import omr.check.FailureResult;
import omr.check.Result;
import omr.check.SuccessResult;
import omr.constant.Constant;
import omr.constant.ConstantSet;
import omr.glyph.Shape;
import omr.glyph.facets.Glyph;
import omr.glyph.ui.GlyphsController;
import omr.grid.Filament;
import omr.grid.FilamentsFactory;
import omr.grid.LineInfo;
import omr.grid.StaffInfo;
import omr.lag.Section;
import static omr.run.Orientation.*;
import omr.selection.GlyphEvent;
import omr.selection.MouseMovement;
import omr.selection.UserEvent;
import omr.step.Step;
import omr.step.StepException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Class {@code HorizontalsBuilder} is in charge of retrieving
* horizontal dashes (ledgers, legato signs and endings) in a system.
*
* <p>Nota: Endings and legato signs are currently disabled.
*
* <p>TODO: This class is a monster, without any compelling reason!
* Serious simplification is needed.
*
* @author Hervé Bitteur
*/
public class HorizontalsBuilder
{
//~ Static fields/initializers ---------------------------------------------
/** Specific application parameters */
private static final Constants constants = new Constants();
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(
HorizontalsBuilder.class);
/** Events this entity is interested in */
private static final Class<?>[] eventClasses = new Class<?>[]{
GlyphEvent.class};
/** Success codes */
private static final SuccessResult LEDGER = new SuccessResult("Ledger");
private static final SuccessResult ENDING = new SuccessResult("Ending");
/** Failure codes */
private static final FailureResult TOO_SHORT = new FailureResult(
"Hori-TooShort");
private static final FailureResult TOO_LONG = new FailureResult(
"Hori-TooLong");
private static final FailureResult TOO_THIN = new FailureResult(
"Hori-TooThin");
private static final FailureResult TOO_THICK = new FailureResult(
"Hori-TooThick");
private static final FailureResult TOO_HOLLOW = new FailureResult(
"Hori-TooHollow");
private static final FailureResult IN_STAFF = new FailureResult(
"Hori-InStaff");
private static final FailureResult TOO_FAR = new FailureResult(
"Hori-TooFar");
private static final FailureResult TOO_ADJA = new FailureResult(
"Hori-TooHighAdjacency");
private static final FailureResult BI_CHUNK = new FailureResult(
"Hori-BiChunk");
private static final FailureResult TOO_SLOPED = new FailureResult(
"Hori-TooSloped");
//~ Instance fields --------------------------------------------------------
/** Related sheet */
private final Sheet sheet;
/** Dedicated system */
private final SystemInfo system;
/** Global sheet scale */
private final Scale scale;
/** Check suite for common tests */
private CheckSuite<Glyph> commonSuite;
/** Check suite for Additional tests for endings */
private CheckSuite<Glyph> endingSuite;
/** Check suite for Additional tests for ledgers */
private CheckSuite<Glyph> ledgerSuite;
/** Total check suite for ending */
private ArrayList<CheckSuite<Glyph>> endingList;
/** Total check suite for ledger */
private ArrayList<CheckSuite<Glyph>> ledgerList;
/** The current collection of ledger candidates */
private final List<GlyphContext> ledgerCandidates = new ArrayList<>();
/** Tenuto signs found */
private final List<Glyph> tenutos;
/** Endings found */
private final List<Glyph> endings;
/** Glyphs controller, if any */
private GlyphsController controller;
/** Minimum length for a full ledger */
private final int minFullLedgerLength;
//~ Constructors -----------------------------------------------------------
//--------------------//
// HorizontalsBuilder //
//--------------------//
/**
* @param system the related system to process
*/
public HorizontalsBuilder (SystemInfo system)
{
this.system = system;
sheet = system.getSheet();
scale = system.getSheet().getScale();
tenutos = system.getTenutos();
endings = system.getEndings();
minFullLedgerLength = scale.toPixels(constants.minFullLedgerLength);
}
//~ Methods ----------------------------------------------------------------
//---------------//
// addCheckBoard //
//---------------//
public void addCheckBoard ()
{
sheet.getAssembly().addBoard(Step.DATA_TAB, new LedgerCheckBoard());
}
// //--------------//
// // assignGlyphs //
// //--------------//
// /**
// * Assign a collection of glyphs to a Ledger or Ending shape
// * @param glyphs the collection of glyphs to be assigned
// * @param shape the shape to be assigned
// * @param compound flag to build one compound, rather than assign each
// * individual glyph
// * @param grade the grade we have wrt the assigned shape
// */
// @Override
// public void assignGlyphs (Collection<Glyph> glyphs,
// Shape shape,
// boolean compound,
// double grade)
// {
// super.assignGlyphs(glyphs, shape, compound, grade);
//
// lagView.colorizeAllSections();
// }
//-----------//
// buildInfo //
//-----------//
/**
* Run the Horizontals step, searching all horizontal sticks for
* typical things like ledgers, endings and legato signs.
*
* @throws StepException raised if process gets stopped
*/
public void buildInfo ()
throws Exception
{
try {
// Filter which sections to provide to factory
List<Section> sections = getCandidateSections(
system.getHorizontalSections());
/// NO: sections.addAll(system.getVerticalSections());
// Retrieve candidate glyphs out of candidate sections
List<Glyph> sticks = getCandidateGlyphs(sections);
// Apply basic checks for ledgers candidates, tenutos, endings
checkHorizontals(sticks);
// Filter ledgers more accurately
filterLedgers();
} catch (Throwable ex) {
logger.warn("Error retrieving horizontals", ex);
} finally {
// User feedback
feedback();
}
}
//---------------//
// getController //
//---------------//
/**
* @return the controller
*/
public GlyphsController getController ()
{
return controller;
}
//--------------//
// isFullLedger //
//--------------//
public boolean isFullLedger (Glyph glyph)
{
return glyph.getLength(HORIZONTAL) >= minFullLedgerLength;
}
//---------------//
// filterLedgers //
//---------------//
/**
* Use smart tests on ledger candidates.
* Starting from each staff, check one interline higher (and lower) for
* candidates, etc.
*/
private void filterLedgers ()
{
for (StaffInfo staff : system.getStaves()) {
logger.debug("Staff#{}", staff.getId());
// Above staff
for (int i = -1;; i--) {
int count = lookupLine(i, staff.getFirstLine());
if (count == 0) {
break;
}
}
// Below staff
for (int i = 1;; i++) {
int count = lookupLine(i, staff.getLastLine());
if (count == 0) {
break;
}
}
}
}
//----------------------//
// getCandidateSections //
//----------------------//
private List<Section> getCandidateSections (Collection<Section> allSections)
{
List<Section> keptSections = new ArrayList<>();
for (Section section : allSections) {
if ((section.getGlyph() == null)
|| !section.getGlyph().isWellKnown()) {
keptSections.add(section);
}
}
return keptSections;
}
//---------------//
// staffDistance //
//---------------//
private double staffDistance (Glyph stick)
{
// Compute the (algebraic) distance from the stick to the nearest
// staff. Distance is negative if the stick is within the staff,
// positive outside.
final Point2D mid = new Point2D.Double(
(stick.getStartPoint(HORIZONTAL).getX() + stick.getStopPoint(
HORIZONTAL).getX()) / 2,
(stick.getStartPoint(HORIZONTAL).getY() + stick.getStopPoint(
HORIZONTAL).getY()) / 2);
final StaffInfo staff = system.getStaffAt(mid);
final double top = staff.getFirstLine().yAt(mid.getX());
final double bottom = staff.getLastLine().yAt(mid.getX());
final double dist = Math.max(top - mid.getY(), mid.getY() - bottom);
return system.getSheet().getScale().pixelsToFrac(dist);
}
//------------------//
// checkHorizontals //
//------------------//
private void checkHorizontals (List<Glyph> sticks)
{
ledgerCandidates.clear();
// Define the suites of Checks
double minResult = constants.minCheckResult.getValue();
// Create suites and collections
createSuites();
for (Glyph stick : sticks) {
// Allocate the candidate context, and pass the whole check suite
GlyphContext context = new GlyphContext(stick);
// Run the Ledger Checks
if (CheckSuite.passCollection(stick, ledgerList) >= minResult) {
// stick.setResult(LEDGER);
// stick.setShape(Shape.LEDGER);
// ledgers.add(stick);
ledgerCandidates.add(context);
// if (logger.isDebugEnabled()) {
// logger.debug("Ledger candidate " + stick);
// }
//TODO Ending are disabled for the time being
// } else {
// // Then, if failed, the Ending Checks
// if (CheckSuite.passCollection(stick, endingList) >= minResult) {
// stick.setResult(ENDING);
// stick.setShape(Shape.ENDING_HORIZONTAL);
// endings.add(stick);
//
// if (logger.isDebugEnabled()) {
// logger.info("Ending " + stick);
// }
// }
}
}
}
// //----------------//
// // deassignGlyphs //
// //----------------//
// /**
// * De-Assign a collection of glyphs.
// *
// * @param glyphs the collection of glyphs to be de-assigned
// */
// @Override
// public void deassignGlyphs (Collection<Glyph> glyphs)
// {
// // super.deassignGlyphs(glyphs);
// //
// // lagView.colorizeAllSections();
// }
// //-------------//
// // assignGlyph //
// //-------------//
// @Override
// protected Glyph assignGlyph (Glyph glyph,
// Shape shape,
// double grade)
// {
// // if (shape != null) {
// // // Assign
// // if (logger.isDebugEnabled()) {
// // logger.debug(
// // "Assign horizontal glyph#" + glyph.getId() + " to " +
// // shape);
// // }
// //
// // Dash dash = null;
// //
// // switch (shape) {
// // case LEDGER :
// // dash = new Ledger((Glyph) glyph);
// // info.getLedgers()
// // .add((Ledger) dash);
// //
// // break;
// //
// // case ENDING_HORIZONTAL :
// // dash = new Ending((Glyph) glyph);
// // info.getEndings()
// // .add((Ending) dash);
// //
// // break;
// //
// // default :
// // assert false;
// // }
// //
// // cleanup(Collections.singletonList(dash));
// // allDashes.add(dash);
// // dashSections.addAll(glyph.getMembers());
// // } else {
// // // Deassign
// // if (logger.isDebugEnabled()) {
// // logger.debug("Deassign horizontal glyph#" + glyph.getId());
// // }
// //
// // Dash dash = null;
// //
// // switch (glyph.getShape()) {
// // case LEDGER :
// // dash = info.getLedgerOf(glyph);
// // info.getLedgers()
// // .remove((Ledger) dash);
// //
// // break;
// //
// // case ENDING_HORIZONTAL :
// // dash = info.getEndingOf(glyph);
// // info.getEndings()
// // .remove((Ending) dash);
// //
// // break;
// //
// // default :
// // assert false;
// // }
// //
// // // Remove the patches and restore the glyph
// // lineCleaner.restoreStick((Glyph) glyph, dash.getPatches());
// // allDashes.remove(dash);
// // dashSections.removeAll(glyph.getMembers());
// // }
// //
// // return super.assignGlyph(glyph, shape, grade);
// return null;
// }
// //---------//
// // cleanup //
// //---------//
// private void cleanup (List<?extends Dash> dashes)
// {
// for (Dash dash : dashes) {
// dash.setPatches(lineCleaner.cleanupStick(dash.getStick()));
// }
// }
//--------------//
// createSuites //
//--------------//
private void createSuites ()
{
// Common horizontal suite
commonSuite = new CheckSuite<>(
"Common",
constants.minCheckResult.getValue());
commonSuite.add(1, new MinThicknessCheck()); // Minimum thickness
commonSuite.add(1, new MaxThicknessCheck());
commonSuite.add(1, new MinDistCheck()); // Not within staves
commonSuite.add(1, new MaxDistCheck()); // Not too far from staves
commonSuite.add(1, new FirstAdjacencyCheck());
commonSuite.add(1, new LastAdjacencyCheck());
// ledgerSuite
ledgerSuite = new CheckSuite<>(
"Ledger",
constants.minCheckResult.getValue());
ledgerSuite.add(
1,
new MinLengthCheck(
constants.minLedgerLengthLow,
constants.minLedgerLengthHigh)); // Minimum length
ledgerSuite.add(1, new MaxLengthCheck()); // Maximum length
ledgerSuite.add(1, new MinDensityCheck());
///ledgerSuite.add(1, new ChunkCheck()); // At least one edge WITHOUT a chunk
// Ledger collection
ledgerList = new ArrayList<>();
ledgerList.add(commonSuite);
ledgerList.add(ledgerSuite);
// endingSuite
endingSuite = new CheckSuite<>(
"Ending",
constants.minCheckResult.getValue());
endingSuite.add(
1,
new MinLengthCheck(
constants.minEndingLengthLow,
constants.minEndingLengthHigh)); // Minimum length
endingSuite.add(1, new SlopeCheck());
// Ending collection
endingList = new ArrayList<>();
endingList.add(commonSuite);
endingList.add(endingSuite);
if (logger.isDebugEnabled()) {
commonSuite.dump();
ledgerSuite.dump();
endingSuite.dump();
}
}
//----------//
// feedback //
//----------//
private void feedback ()
{
int nl = 0;
for (Glyph glyph : system.getGlyphs()) {
if (glyph.getShape() == Shape.LEDGER) {
nl++;
}
}
int nt = tenutos.size();
int ne = endings.size();
// A bit tedious
StringBuilder sb = new StringBuilder();
sb.append("S#").append(system.getId());
sb.append(" ");
if (nl > 0) {
sb.append(nl).append(" ledger").append((nl > 1) ? "s" : "");
} else if (logger.isDebugEnabled()) {
sb.append("No ledger");
}
if (nt > 0) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(nt).append(" tenuto").append((nt > 1) ? "s" : "");
} else if (logger.isDebugEnabled()) {
sb.append("No tenuto");
}
if (ne > 0) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(ne).append(" ending").append((ne > 1) ? "s" : "");
} else if (logger.isDebugEnabled()) {
sb.append("No ending");
}
logger.debug("{}{}", sheet.getLogPrefix(), sb.toString());
}
// //------------------//
// // getAliensAtStart //
// //------------------//
// /**
// * Count alien pixels in the following rectangle...
// * <pre>
// * +-------+
// * | |
// * +=======+==================================+
// * | |
// * +-------+
// * </pre>
// *
// * @param glyph the glyph at stake
// * @param halfHeight half rectangle size along stick thickness
// * @param width rectangle size along stick length
// * @return the number of alien pixels found
// */
// private int getAliensAtStart (Glyph glyph,
// int halfHeight,
// int width)
// {
// Point2D start = glyph.getStartPoint();
// Rectangle roi = new Rectangle(
// (int) Math.rint(start.getX()),
// (int) Math.rint(start.getY() - halfHeight),
// width,
// 2 * halfHeight);
// glyph.addAttachment("ll", roi);
//
// int count = 0;
// count += glyph.getAlienPixelsFrom(sheet.getHorizontalLag(), roi, null);
// count += glyph.getAlienPixelsFrom(sheet.getVerticalLag(), roi, null);
//
// return count;
// }
//
// //-----------------//
// // getAliensAtStop //
// //-----------------//
// /**
// * Count alien pixels in the following rectangle...
// * <pre>
// * +-------+
// * | |
// * +==================================+=======+
// * | |
// * +-------+
// * </pre>
// *
// * @param glyph the glyph at stake
// * @param halfHeight half rectangle size along stick thickness
// * @param width rectangle size along stick length
// * @return the number of alien pixels found
// */
// private int getAliensAtStop (Glyph glyph,
// int halfHeight,
// int width)
// {
// Point2D stop = glyph.getStopPoint();
// Rectangle roi = new Rectangle(
// (int) Math.rint(stop.getX() - width),
// (int) Math.rint(stop.getY() - halfHeight),
// width,
// 2 * halfHeight);
// glyph.addAttachment("lr", roi);
// int count = 0;
// count += glyph.getAlienPixelsFrom(sheet.getHorizontalLag(), roi, null);
// count += glyph.getAlienPixelsFrom(sheet.getVerticalLag(), roi, null);
//
// return count;
// }
//--------------------//
// getCandidateGlyphs //
//--------------------//
private List<Glyph> getCandidateGlyphs (List<Section> sections)
throws Exception
{
// Use filament factory
FilamentsFactory factory = new FilamentsFactory(
scale,
sheet.getNest(),
HORIZONTAL,
Filament.class);
// Adjust factory parameters
factory.setMaxCoordGap(constants.maxCoordGap);
factory.setMaxExpansionSpace(constants.maxExpansionSpace);
factory.setMaxFilamentThickness(constants.maxFilamentThickness);
factory.setMaxGapSlope(constants.maxGapSlope.getValue());
factory.setMaxInvolvingLength(constants.maxInvolvingLength);
factory.setMaxOverlapDeltaPos(constants.maxOverlapDeltaPos);
factory.setMaxPosGap(constants.maxPosGap);
factory.setMaxPosGapForSlope(constants.maxPosGapForSlope);
factory.setMaxSectionThickness(constants.maxSectionThickness);
factory.setMaxSpace(constants.maxSpace);
factory.setMinCoreSectionLength(constants.minCoreSectionLength);
factory.setMinSectionAspect(constants.minSectionAspect.getValue());
///factory.dump();
// Reset the "fat" attribute of the sections, since this depends on
// max thickness parameters
for (Section section : sections) {
section.resetFat();
section.setGlyph(null);
}
return factory.retrieveFilaments(sections, true);
}
//------------//
// lookupLine //
//------------//
/**
* Look up for ledgers on a specific line above or below the
* provided staff line.
*
* @param index index of line, above if positive, below if negative
* @param staffLine the staff line used as reference
* @return the number of ledgers found on this "virtual line"
*/
private int lookupLine (int index,
LineInfo staffLine)
{
logger.debug("Checking line {}", index);
int ledgerMarginX = scale.toPixels(constants.ledgerMarginX);
int found = 0; // Number of ledgers found on this line
// Define bounds for the virtual line, properly shifted and enlarged
Rectangle box = staffLine.getBounds();
box.y += (index * scale.getInterline());
box.grow(0, scale.toPixels(constants.ledgerMarginY));
// Filter enclosed candidates
for (GlyphContext context : ledgerCandidates) {
// Rough containment
if (!box.contains(context.mid)) {
continue;
}
// Check precise distance
double dist = Math.abs(index - context.dist);
logger.debug("{} {}", dist, context);
if (dist > constants.ledgerMarginY.getValue()) {
if (context.stick.isVip()) {
logger.info("Ledger candidate {} dy:{} vs {}",
context, dist, constants.ledgerMarginY.getValue());
}
continue;
}
// Check for presence of ledger on previous line
int prevIndex = (index > 0) ? (index - 1) : (index + 1);
if (prevIndex != 0) {
Set<Glyph> prevLedgers = context.staff.getLedgers(prevIndex);
// Check abscissa compatibility
if (prevLedgers == null) {
if (context.stick.isVip()) {
logger.info("Ledger candidate {} orphan", context);
}
continue;
}
boolean foundPrevious = false;
for (Glyph ledger : prevLedgers) {
Point mid = ledger.getAreaCenter();
double dx = Math.abs(mid.x - context.mid.getX());
if (dx <= ledgerMarginX) {
foundPrevious = true;
break;
}
}
if (!foundPrevious) {
if (context.stick.isVip()) {
logger.info("Ledger candidate {} local orphan", context);
}
continue;
}
// } else if (context.stick.getLength(HORIZONTAL) < minFullLedgerLength) {
// // If ledger is short, check for presence of stem
// // This test discards ledgers of whole notes!
// Rectangle stickBox = context.stick.getBounds();
// stickBox.grow(maxStemDx, maxStemDy);
//
// List<Glyph> others = system.lookupIntersectedGlyphs(stickBox);
// boolean foundStem = false;
//
// for (Glyph glyph : others) {
// if (glyph.isStem()) {
// foundStem = true;
//
// break;
// }
// }
//
// if (!foundStem) {
// continue;
// }
}
// OK!
Glyph glyph = system.addGlyph(context.stick);
// Ledger ledger = new Ledger(glyph, context.staff, index);
// glyph.setTranslation(ledger);
glyph.setShape(Shape.LEDGER);
context.staff.addLedger(glyph, index);
found++;
if (context.stick.isVip()) {
logger.info("Ledger at {}", context);
} else {
logger.debug("Ledger at {}", context);
}
}
return found;
}
//~ Inner Classes ----------------------------------------------------------
// //------------//
// // ChunkCheck //
// //------------//
// /**
// * Class {@code ChunkCheck} checks for absence of a chunk either at
// * start or stop
// */
// private class ChunkCheck
// extends Check<Glyph>
// {
// //~ Instance fields ----------------------------------------------------
//
// // Half width for chunk window at top and bottom
// private final int nWidth;
//
// // Half height for chunk window at top and bottom
// private final int nHeight;
//
// // Total area for chunk window
// private final double area;
//
// //~ Constructors -------------------------------------------------------
//
// protected ChunkCheck ()
// {
// super(
// "Chunk",
// "Check no chunk is stuck on either side of the stick",
// constants.chunkRatioLow,
// constants.chunkRatioHigh,
// false,
// BI_CHUNK);
//
// // Adjust chunk window according to system scale
// nWidth = scale.toPixels(constants.chunkWidth);
// nHeight = scale.toPixels(constants.chunkHeight);
// area = 2 * nWidth * nHeight;
//
// if (logger.isDebugEnabled()) {
// logger.debug(
// "MaxPixLow=" + getLow() + ", MaxPixHigh=" + getHigh());
// }
// }
//
// //~ Methods ------------------------------------------------------------
//
// protected double getValue (Glyph stick)
// {
// // Retrieve the smallest stick chunk either at top or bottom
// double res = Math.min(
// getAliensAtStart(stick, nHeight, nWidth),
// getAliensAtStop(stick, nHeight, nWidth));
// res /= area;
//
// if (logger.isDebugEnabled()) {
// logger.info("MinAliensRatio= " + res + " for " + stick);
// }
//
// return res;
// }
// }
//-----------//
// Constants //
//-----------//
private static final class Constants
extends ConstantSet
{
//~ Instance fields ----------------------------------------------------
// For factory
Constant.Double maxGapSlope = new Constant.Double(
"tangent",
0.5,
"Maximum absolute slope for a gap");
Constant.Ratio minSectionAspect = new Constant.Ratio(
1.0,
"Minimum section aspect (length / thickness)");
Constant.Ratio maxConsistentRatio = new Constant.Ratio(
1.7,
"Maximum thickness ratio for consistent merge");
// Constants specified WRT mean line thickness
// -------------------------------------------
Scale.LineFraction maxSectionThickness = new Scale.LineFraction(
1.85,
"Maximum horizontal section thickness WRT mean line height");
Scale.LineFraction maxFilamentThickness = new Scale.LineFraction(
1.75,
"Maximum filament thickness WRT mean line height");
Scale.LineFraction maxOverlapDeltaPos = new Scale.LineFraction(
1.0,
"Maximum delta position between two overlapping filaments");
// Constants specified WRT mean interline
// --------------------------------------
Scale.Fraction minCoreSectionLength = new Scale.Fraction(
0.25,
"Minimum length for a section to be considered as core");
Scale.Fraction maxCoordGap = new Scale.Fraction(
0,
"Maximum delta coordinate for a gap between filaments");
Scale.Fraction maxPosGap = new Scale.Fraction(
0.2,
"Maximum delta position for a gap between filaments");
Scale.Fraction maxSpace = new Scale.Fraction(
0.0,
"Maximum space between overlapping filaments");
Scale.Fraction maxExpansionSpace = new Scale.Fraction(
0.02,
"Maximum space when expanding filaments");
Scale.Fraction maxPosGapForSlope = new Scale.Fraction(
0.1,
"Maximum delta Y to check slope for a gap between filaments");
Scale.Fraction maxInvolvingLength = new Scale.Fraction(
2,
"Maximum filament length to apply thickness test");
///
Scale.Fraction ledgerMarginY = new Scale.Fraction(
0.4,
"Margin on ledger ordinate");
Scale.Fraction ledgerMarginX = new Scale.Fraction(
2.0,
"Margin on ledger abscissa across lines");
Scale.Fraction maxStemDx = new Scale.Fraction(
1.5,
"Maximum horizontal distance between ledger and stem");
Scale.Fraction maxStemDy = new Scale.Fraction(
0.5,
"Maximum vertical distance between ledger and stem");
Scale.Fraction minFullLedgerLength = new Scale.Fraction(
1.5,
"Minimum length for a full ledger with no stem");
///
Scale.Fraction minCoreLength = new Scale.Fraction(
0.2,
"Minimum length for ledger core");
// Scale.Fraction chunkHeight = new Scale.Fraction(
// 0.33,
// "Height of half area to look for chunks");
// Constant.Ratio chunkRatioHigh = new Constant.Ratio(
// 0.05,
// "HighMaximum ratio of alien pixels to detect chunks");
// Constant.Ratio chunkRatioLow = new Constant.Ratio(
// 0.02,
// "LowMaximum ratio of alien pixels to detect chunks");
// Scale.Fraction chunkWidth = new Scale.Fraction(
// 0.33,
// "Width of half area to look for chunks");
Scale.Fraction extensionMinPointNb = new Scale.Fraction(
0.2,
"Minimum number of points to compute extension of crossing objects");
Constant.Ratio maxAdjacencyHigh = new Constant.Ratio(
0.3,
"High Maximum adjacency ratio for an ending");
Constant.Ratio maxAdjacencyLow = new Constant.Ratio(
0.2,
"Low Maximum adjacency ratio for an ending");
Scale.Fraction maxLengthHigh = new Scale.Fraction(
3.1,
"High Maximum length for a horizontal");
Scale.Fraction maxLengthLow = new Scale.Fraction(
2.2,
"Low Maximum length for a horizontal");
Scale.Fraction maxStaffDistanceHigh = new Scale.Fraction(
6,
"High Maximum staff distance for a horizontal");
Scale.Fraction maxStaffDistanceLow = new Scale.Fraction(
4,
"Low Maximum staff distance for a horizontal");
Scale.LineFraction maxThicknessHigh = new Scale.LineFraction(
1.2,
"High Maximum thickness of an interesting stick");
Scale.LineFraction maxThicknessLow = new Scale.LineFraction(
1.0,
"Low Maximum thickness of an interesting stick");
Check.Grade minCheckResult = new Check.Grade(
0.50,
"Minimum result for suite of check");
Constant.Ratio minDensityHigh = new Constant.Ratio(
0.7,
"High Minimum density for a horizontal");
Constant.Ratio minDensityLow = new Constant.Ratio(
0.6,
"Low Minimum density for a horizontal");
Scale.Fraction minEndingLengthHigh = new Scale.Fraction(
15,
"High Minimum length for an ending");
Scale.Fraction minEndingLengthLow = new Scale.Fraction(
10,
"Low Minimum length for an ending");
Scale.Fraction minLedgerLengthHigh = new Scale.Fraction(
0.3,
"High Minimum length for a ledger");
Scale.Fraction minLedgerLengthLow = new Scale.Fraction(
0.2,
"Low Minimum length for a ledger");
Scale.Fraction minStaffDistanceHigh = new Scale.Fraction(
0.8,
"High Minimum staff distance for a horizontal");
Scale.Fraction minStaffDistanceLow = new Scale.Fraction(
0.6,
"Low Minimum staff distance for a horizontal");
Scale.Fraction minThicknessHigh = new Scale.Fraction(
0.23,
"High Minimum thickness of an interesting stick");
Scale.Fraction minThicknessLow = new Scale.Fraction(
0.06,
"Low Minimum thickness of an interesting stick");
Constant.Double maxSlopeLow = new Constant.Double(
"slope",
0.01,
"Low Maximum slope for ending");
Constant.Double maxSlopeHigh = new Constant.Double(
"slope",
0.02,
"High Maximum slope for ending");
//
Constant.Double maxSlopeForCheck = new Constant.Double(
"slope",
1.5,
"Maximum slope for checking a ledger candidate");
}
//---------------------//
// FirstAdjacencyCheck //
//---------------------//
private static class FirstAdjacencyCheck
extends Check<Glyph>
{
//~ Constructors -------------------------------------------------------
protected FirstAdjacencyCheck ()
{
super(
"TopAdj",
"Check that stick is open on top side",
constants.maxAdjacencyLow,
constants.maxAdjacencyHigh,
false,
TOO_ADJA);
}
//~ Methods ------------------------------------------------------------
// Retrieve the adjacency value
@Override
protected double getValue (Glyph stick)
{
int length = stick.getLength(HORIZONTAL);
return (double) stick.getFirstStuck() / (double) length;
}
}
//--------------------//
// LastAdjacencyCheck //
//--------------------//
private static class LastAdjacencyCheck
extends Check<Glyph>
{
//~ Constructors -------------------------------------------------------
protected LastAdjacencyCheck ()
{
super(
"BottomAdj",
"Check that stick is open on bottom side",
constants.maxAdjacencyLow,
constants.maxAdjacencyHigh,
false,
TOO_ADJA);
}
//~ Methods ------------------------------------------------------------
// Retrieve the adjacency value
@Override
protected double getValue (Glyph stick)
{
int length = stick.getLength(HORIZONTAL);
return (double) stick.getLastStuck() / (double) length;
}
}
//-----------------//
// MinDensityCheck //
//-----------------//
private static class MinDensityCheck
extends Check<Glyph>
{
//~ Constructors -------------------------------------------------------
protected MinDensityCheck ()
{
super(
"MinDensity",
"Check that stick fills its bounding rectangle",
constants.minDensityLow,
constants.minDensityHigh,
true,
TOO_HOLLOW);
}
//~ Methods ------------------------------------------------------------
// Retrieve the density
@Override
protected double getValue (Glyph stick)
{
Rectangle rect = stick.getBounds();
double area = rect.width * rect.height;
return (double) stick.getWeight() / area;
}
}
//--------------//
// GlyphContext //
//--------------//
private class GlyphContext
implements Checkable
{
//~ Instance fields ----------------------------------------------------
/** The stick being checked */
final Glyph stick;
/** Stick middle point */
final Point2D mid;
/** Nearest staff */
final StaffInfo staff;
/** Absolute normalized distance from staff */
final double absDist;
/** Algebraic normalized distance from closest staff line */
final double dist;
//~ Constructors -------------------------------------------------------
public GlyphContext (Glyph stick)
{
this.stick = stick;
mid = new Point2D.Double(
(stick.getStartPoint(HORIZONTAL).getX() + stick.
getStopPoint(
HORIZONTAL).getX()) / 2,
(stick.getStartPoint(HORIZONTAL).getY() + stick.
getStopPoint(
HORIZONTAL).getY()) / 2);
staff = system.getStaffAt(mid);
double toTop = scale.pixelsToFrac(
staff.getFirstLine().yAt(mid.getX()) - mid.getY());
double fromBottom = scale.pixelsToFrac(
mid.getY() - staff.getLastLine().yAt(mid.getX()));
if (toTop > fromBottom) {
absDist = toTop;
dist = -toTop;
} else {
absDist = fromBottom;
dist = fromBottom;
}
}
//~ Methods ------------------------------------------------------------
@Override
public boolean isVip ()
{
return stick.isVip();
}
@Override
public void setResult (Result result)
{
stick.setResult(result);
}
@Override
public void setVip ()
{
stick.setVip();
}
@Override
public String toString ()
{
return "stick#" + stick.getId();
}
}
//------------------//
// LedgerCheckBoard //
//------------------//
/**
* A specific board dedicated to physical checks of ledger sticks
*/
private class LedgerCheckBoard
extends CheckBoard<Glyph>
{
//~ Constructors -------------------------------------------------------
public LedgerCheckBoard ()
{
super(
"LedgerCheck",
ledgerSuite,
sheet.getNest().getGlyphService(),
eventClasses);
}
//~ Methods ------------------------------------------------------------
@Override
public void onEvent (UserEvent event)
{
try {
// Ignore RELEASING
if (event.movement == MouseMovement.RELEASING) {
return;
}
if (event instanceof GlyphEvent) {
GlyphEvent glyphEvent = (GlyphEvent) event;
Glyph glyph = glyphEvent.getData();
if (glyph != null) {
// Make sure this is a rather horizontal stick
if (Math.abs(glyph.getSlope()) <= constants.maxSlopeForCheck.
getValue()) {
// Get a fresh suite
//setSuite(system.createLedgerCheckSuite(true));
tellObject(glyph);
return;
}
}
tellObject(null);
}
} catch (Exception ex) {
logger.warn(getClass().getName() + " onEvent error", ex);
}
}
}
//--------------//
// MaxDistCheck //
//--------------//
private class MaxDistCheck
extends Check<Glyph>
{
//~ Constructors -------------------------------------------------------
protected MaxDistCheck ()
{
super(
"MaxDist",
"Check that stick is not too far from staff",
constants.maxStaffDistanceLow,
constants.maxStaffDistanceHigh,
false,
TOO_FAR);
}
//~ Methods ------------------------------------------------------------
// Retrieve the position with respect to the various staves of the
// system being checked.
@Override
protected double getValue (Glyph stick)
{
return staffDistance(stick);
}
}
//----------------//
// MaxLengthCheck //
//----------------//
private class MaxLengthCheck
extends Check<Glyph>
{
//~ Constructors -------------------------------------------------------
protected MaxLengthCheck ()
{
super(
"MaxLength",
"Check that stick is not too long",
constants.maxLengthLow,
constants.maxLengthHigh,
false,
TOO_LONG);
}
//~ Methods ------------------------------------------------------------
// Retrieve the length data
@Override
protected double getValue (Glyph stick)
{
return sheet.getScale().pixelsToFrac(stick.getLength(HORIZONTAL));
}
}
//-------------------//
// MaxThicknessCheck //
//-------------------//
private class MaxThicknessCheck
extends Check<Glyph>
{
//~ Constructors -------------------------------------------------------
protected MaxThicknessCheck ()
{
super(
"MaxThickness",
"Check that stick is not too thick",
constants.maxThicknessLow,
constants.maxThicknessHigh,
false,
TOO_THICK);
}
//~ Methods ------------------------------------------------------------
// Retrieve the thickness data
@Override
protected double getValue (Glyph stick)
{
return sheet.getScale().pixelsToFrac(stick.getThickness(HORIZONTAL));
}
}
//--------------//
// MinDistCheck //
//--------------//
private class MinDistCheck
extends Check<Glyph>
{
//~ Constructors -------------------------------------------------------
protected MinDistCheck ()
{
super(
"MinDist",
"Check that stick is not within staff height",
constants.minStaffDistanceLow,
constants.minStaffDistanceHigh,
true,
IN_STAFF);
}
//~ Methods ------------------------------------------------------------
// Retrieve the position with respect to the various staves of the
// system being checked.
@Override
protected double getValue (Glyph stick)
{
return staffDistance(stick);
}
}
//----------------//
// MinLengthCheck //
//----------------//
private class MinLengthCheck
extends Check<Glyph>
{
//~ Constructors -------------------------------------------------------
protected MinLengthCheck (Constant.Double low,
Constant.Double high)
{
super(
"MinLength",
"Check that stick is long enough",
low,
high,
true,
TOO_SHORT);
}
//~ Methods ------------------------------------------------------------
// Retrieve the length data
@Override
protected double getValue (Glyph stick)
{
return sheet.getScale().pixelsToFrac(stick.getLength(HORIZONTAL));
}
}
//-------------------//
// MinThicknessCheck //
//-------------------//
private class MinThicknessCheck
extends Check<Glyph>
{
//~ Constructors -------------------------------------------------------
protected MinThicknessCheck ()
{
super(
"MinThickness",
"Check that stick is thick enough",
constants.minThicknessLow,
constants.minThicknessHigh,
true,
TOO_THIN);
}
//~ Methods ------------------------------------------------------------
// Retrieve the thickness data
@Override
protected double getValue (Glyph stick)
{
return sheet.getScale().pixelsToFrac(stick.getThickness(HORIZONTAL));
}
}
//------------//
// SlopeCheck //
//------------//
private class SlopeCheck
extends Check<Glyph>
{
//~ Constructors -------------------------------------------------------
protected SlopeCheck ()
{
super(
"Slope",
"Check that stick slope is close to global page slope",
constants.maxSlopeLow,
constants.maxSlopeHigh,
false,
TOO_SLOPED);
}
//~ Methods ------------------------------------------------------------
// Retrieve the absolute slope
@Override
protected double getValue (Glyph stick)
{
return Math.abs(stick.getSlope() - sheet.getSkew().getSlope());
}
}
}