// ~ Methods ------------------------------------------------------------ // Retrieve the distance with proper staff border @Override protected double getValue(GlyphContext context) { Glyph stick = context.stick; Point2D stop = stick.getStopPoint(VERTICAL); // Which staff area contains the bottom of the stick? StaffInfo staff = staffManager.getStaffAt(stop); // How far are we from the stop of the staff? double staffBottom = staff.getLastLine().yAt(stop.getX()); double dy = sheet.getScale().pixelsToFrac(Math.abs(staffBottom - stop.getY())); // Change limits according to rough & partDefining if (rough && context.isPartDefining) { setLowHigh(constants.maxStaffShiftDyLowRough, constants.maxStaffShiftDyHighRough); } else { setLowHigh(constants.maxStaffShiftDyLow, constants.maxStaffShiftDyHigh); } // Side-effect if (dy <= getLow()) { context.botStaff = context.bottomArea; } return dy; }
// ~ Methods ------------------------------------------------------------ @Override public void onEvent(UserEvent event) { try { // Ignore RELEASING if (event.movement == MouseMovement.RELEASING) { return; } if (event instanceof GlyphEvent) { BarsChecker.GlyphContext context = null; GlyphEvent glyphEvent = (GlyphEvent) event; Glyph glyph = glyphEvent.getData(); if (glyph != null) { // Make sure this is a rather vertical stick if (Math.abs(glyph.getInvertedSlope()) <= constants.maxCoTangentForCheck.getValue()) { // Apply a fresh suite context = new BarsChecker.GlyphContext(glyph); applySuite(new BarCheckSuite(), context); return; } } tellObject(null); } } catch (Exception ex) { logger.warn(getClass().getName() + " onEvent error", ex); } }
public boolean check(Glyph glyph) { Shape shape = glyph.getShape(); boolean res = glyph.isWellKnown() && ShapeRange.StemSymbols.contains(shape) && (shape != Shape.BEAM_HOOK); return res; }
// ~ Methods ------------------------------------------------------------ // Retrieve the difference between stick slope and global slope @Override protected double getValue(GlyphContext context) { Glyph stick = context.stick; Point2D start = stick.getStartPoint(VERTICAL); Point2D stop = stick.getStopPoint(VERTICAL); // Beware of sign of stickSlope (it is opposite of globalSlope) double stickSlope = -(stop.getX() - start.getX()) / (stop.getY() - start.getY()); return Math.abs(stickSlope - sheet.getSkew().getSlope()); }
/** * Check if the stick/bar is a thick one * * @param stick the bar stick to check * @return true if thick */ private boolean isThickBar(Glyph stick) { // Max width of a thin bar line, otherwise this must be a thick bar final int maxThinWidth = scale.toPixels(constants.maxThinWidth); // Average width of the stick final int meanWidth = (int) Math.rint((double) stick.getWeight() / (double) stick.getLength(Orientation.VERTICAL)); return meanWidth > maxThinWidth; }
// ~ Methods ------------------------------------------------------------ @Override protected Rectangle getBox(Glyph stick) { Point2D bottom = stick.getStopPoint(VERTICAL); Rectangle box = new Rectangle( (int) Math.rint(bottom.getX()), (int) Math.rint(bottom.getY() - (1.5 * nHeight)), nWidth, 2 * nHeight); stick.addAttachment("br", box); return box; }
// ~ Methods ------------------------------------------------------------ // Retrieve the length data @Override protected double getValue(GlyphContext context) { Glyph stick = context.stick; int height = Integer.MAX_VALUE; // Check wrt every staff in the stick getRange for (int i = context.topArea; i <= context.bottomArea; i++) { StaffInfo area = staffManager.getStaff(i); height = Math.min(height, area.getHeight()); } return sheet.getScale().pixelsToFrac(height - stick.getLength(Orientation.VERTICAL)); }
// ~ Methods ------------------------------------------------------------ @Override protected Rectangle getBox(Glyph stick) { Point2D top = stick.getStartPoint(VERTICAL); Rectangle box = new Rectangle( (int) Math.rint(top.getX()), (int) Math.rint(top.getY() - (nHeight / 2)), nWidth, 2 * nHeight); stick.addAttachment("tr", box); return box; }
// ~ Methods ------------------------------------------------------------ // Retrieve the distance with proper staff border @Override protected double getValue(GlyphContext context) { Glyph stick = context.stick; Point2D stop = stick.getStopPoint(VERTICAL); // Which staff area contains the bottom of the stick? StaffInfo staff = staffManager.getStaffAt(stop); // How far are we from the stop of the staff? double staffBottom = staff.getLastLine().yAt(stop.getX()); double dy = sheet.getScale().pixelsToFrac(Math.abs(staffBottom - stop.getY())); return dy; }
// -------------------// // initializeContext // // -------------------// private void initializeContext(GlyphContext context) { Glyph stick = context.stick; StaffInfo startStaff = staffManager.getStaffAt(stick.getStartPoint(VERTICAL)); StaffInfo stopStaff = staffManager.getStaffAt(stick.getStopPoint(VERTICAL)); // Remember top & bottom areas context.topArea = staffManager.getIndexOf(startStaff); context.bottomArea = staffManager.getIndexOf(stopStaff); // Check whether this stick embraces more than one staff context.isPartDefining = context.topArea != context.bottomArea; // Remember if this is a thick stick context.isThick = isThickBar(stick); }
// ------------------// // getAlienPixelsIn // // ------------------// private int getAlienPixelsIn(Glyph glyph, Rectangle absRoi) { Predicate<Section> predicate = new Predicate<Section>() { @Override public boolean check(Section section) { return (section.getGlyph() == null) || (section.getGlyph().getShape() != Shape.STAFF_LINE); } }; int total = 0; total += glyph.getAlienPixelsFrom(sheet.getVerticalLag(), absRoi, predicate); total += glyph.getAlienPixelsFrom(sheet.getHorizontalLag(), absRoi, predicate); return total; }
// --------------// // 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; }
/** * 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)); }
// ~ Methods ------------------------------------------------------------ // Retrieve the stick abscissa @Override protected double getValue(GlyphContext context) { Glyph stick = context.stick; double dist = Double.MAX_VALUE; // Check wrt every staff in the stick range for (int i = context.topArea; i <= context.bottomArea; i++) { StaffInfo staff = staffManager.getStaff(i); Point2D top = staff.getFirstLine().getEndPoint(LEFT); Point2D bot = staff.getLastLine().getEndPoint(LEFT); double y = (top.getY() + bot.getY()) / 2; double x = stick.getPositionAt(y, Orientation.VERTICAL); double dx = x - staff.getAbscissa(LEFT); dist = Math.min(dist, dx); } return sheet.getScale().pixelsToFrac(dist); }
@Override protected double getValue(GlyphContext context) { Glyph stick = context.stick; Rectangle box = getBox(stick); int aliens = getAlienPixelsIn(stick, box); int area = box.width * box.height; // Normalize the ratio with stick length double ratio = (1000 * aliens) / ((double) area * stick.getLength(Orientation.VERTICAL)); logger.debug( "{} {} aliens:{} area:{} ratio:{}", stick.idString(), getName(), aliens, area, (float) ratio); return ratio; }
/** * 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; }
@Override public boolean isCandidateSuitable(Glyph glyph) { return !glyph.isManualShape() || ShapeSet.BassClefs.contains(glyph.getShape()); }
// ~ Methods ---------------------------------------------------------------- // ------------// // runPattern // // ------------// @Override public int runPattern() { int successNb = 0; // Constants for clef verification final double maxBassDotPitchDy = constants.maxBassDotPitchDy.getValue(); final double maxBassDotDx = scale.toPixels(constants.maxBassDotDx); // Specific adapter definition for bass clefs CompoundAdapter bassAdapter = new BassAdapter(system, Grades.clefMinGrade); for (Glyph top : system.getGlyphs()) { // Look for top dot if ((top.getShape() != Shape.DOT_set) || (Math.abs(top.getPitchPosition() - -3) > maxBassDotPitchDy)) { continue; } int topX = top.getCentroid().x; StaffInfo topStaff = system.getStaffAt(top.getCentroid()); // Look for bottom dot right underneath, and in the same staff for (Glyph bot : system.getGlyphs()) { if ((bot.getShape() != Shape.DOT_set) || (Math.abs(bot.getPitchPosition() - -1) > maxBassDotPitchDy)) { continue; } if (Math.abs(bot.getCentroid().x - topX) > maxBassDotDx) { continue; } if (system.getStaffAt(bot.getCentroid()) != topStaff) { continue; } // Here we have a couple logger.debug("Got bass dots #{} & #{}", top.getId(), bot.getId()); Glyph compound = system.buildCompound(top, true, system.getGlyphs(), bassAdapter); if (compound != null) { successNb++; } } } return successNb; }
/** * 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; } }
@Override public boolean check(Glyph glyph) { return glyph.isClef(); }
/** * From the list of vertical sticks, this method uses several tests to provide the initial * collection of good barlines candidates. * * @param sticks the collection of candidate sticks */ public void checkCandidates(Collection<? extends Glyph> sticks) { // // Sort candidates according to their abscissa // List<Glyph> sortedSticks = new ArrayList<Glyph>(sticks); // Collections.sort(sortedSticks, Glyph.midPosComparator); double minResult = constants.minCheckResult.getValue(); // Check each candidate stick in turn for (Glyph stick : sticks) { // Allocate the candidate context, and pass the whole check suite GlyphContext context = new GlyphContext(stick); double res = suite.pass(context); if (logger.isDebugEnabled() || stick.isVip()) { logger.info( "suite => {}{} for {}", (float) res, (stick.getResult() != null) ? (" " + stick.getResult()) : "", stick); } if ((stick.isBar() && stick.isManualShape()) || res >= minResult) { // OK, we flag this candidate with proper barline shape contexts.put(stick, context); if ((!stick.isBar() || !stick.isManualShape())) { stick.setShape(isThickBar(stick) ? Shape.THICK_BARLINE : Shape.THIN_BARLINE); } // Additional processing for Bars that define a system or a part // (they start AND end with precise staves horizontal limits) if ((context.topStaff != -1) && (context.botStaff != -1)) { // Here, we have both part & system defining bars // System bars occur first // (since glyphs are sorted by increasing abscissa) stick.setResult(BAR_PART_DEFINING); logger.debug( "Part-defining Barline from staff {} to staff {} {}", context.topStaff, context.botStaff, stick); } else { if (logger.isDebugEnabled()) { logger.debug( "Non-Part-defining Bar line {}{}", (context.topStaff != -1) ? (" topIdx=" + context.topStaff) : "", (context.botStaff != -1) ? (" botIdx=" + context.botStaff) : ""); } stick.setResult(BAR_NOT_PART_DEFINING); } } else { if (stick.isBar()) { if (logger.isDebugEnabled() || stick.isVip()) { logger.info("Purged {} {}", stick.idString(), stick.getShape()); } stick.setShape(null); } } } }
@Override public void setVip() { stick.setVip(); }
@Override public void setResult(Result result) { stick.setResult(result); }
// ~ Methods ------------------------------------------------------------ @Override public boolean isVip() { return stick.isVip(); }
/** * 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; }
/** * Try to infer the role of this textual item. For the time being, this is a simple algorithm * based on sentence location within the page, augmented by valid chord name, etc. * * @param line the sentence * @param systemInfo the containing system * @return the role information inferred for the provided sentence glyph */ public static TextRoleInfo guessRole(TextLine line, SystemInfo systemInfo) { if (line == null) { return null; } if (line.isVip()) { logger.info("TextRoleInfo. guessRole for {}", line.getValue()); } int chordCount = 0; for (TextWord word : line.getWords()) { // At least one word/glyph with a role manually assigned Glyph glyph = word.getGlyph(); if (glyph != null) { if (glyph.getManualRole() != null) { return glyph.getManualRole(); } } // Word that could be a chord symbol? if (word.guessChordInfo() != null) { chordCount++; } } // Is line made entirely of potential chord symbols? boolean isAllChord = chordCount == line.getWords().size(); Rectangle box = line.getBounds(); if (box == null) { return null; } // Is line mainly in italic? boolean isMainlyItalic = systemInfo.getTextBuilder().isMainlyItalic(line); Sheet sheet = systemInfo.getSheet(); ScoreSystem system = systemInfo.getScoreSystem(); Scale scale = system.getScale(); Point left = new Point(box.x, box.y + (box.height / 2)); Point right = new Point(box.x + box.width, box.y + (box.height / 2)); // First system in page? boolean firstSystem = system.getId() == 1; // Last system in page? boolean lastSystem = sheet.getSystems().size() == system.getId(); // Vertical position wrt (system) staves StaffPosition systemPosition = system.getStaffPosition(left); // Vertical position wrt (part) staves SystemPart part = system.getPartAbove(left); StaffPosition partPosition = part.getStaffPosition(left); // Vertical distance from staff? Staff staff = system.getStaffAt(left); int staffDy = Math.abs(staff.getTopLeft().y - box.y); boolean closeToStaff = staffDy <= scale.toPixels(constants.maxStaffDy); // Begins on left side of the part? boolean leftOfStaves = system.isLeftOfStaves(left); // At the center of page width? int maxCenterDx = scale.toPixels(constants.maxCenterDx); int pageCenter = sheet.getWidth() / 2; boolean pageCentered = Math.abs((box.x + (box.width / 2)) - pageCenter) <= maxCenterDx; // Right aligned with staves? int maxRightDx = scale.toPixels(constants.maxRightDx); boolean rightAligned = Math.abs(right.x - system.getTopLeft().x - system.getDimension().width) <= maxRightDx; // Short Sentence? int maxShortLength = scale.toPixels(constants.maxShortLength); boolean shortSentence = box.width <= maxShortLength; // Tiny Sentence? int maxTinyLength = scale.toPixels(constants.maxTinyLength); boolean tinySentence = box.width <= maxTinyLength; // High text? int minTitleHeight = scale.toPixels(constants.minTitleHeight); boolean highText = box.height >= minTitleHeight; logger.debug( "{} firstSystem={} lastSystem={} systemPosition={}" + " partPosition={} closeToStaff={} leftOfStaves={}" + " pageCentered={} rightAligned={} shortSentence={}" + " highText={10}", box, firstSystem, lastSystem, systemPosition, partPosition, closeToStaff, leftOfStaves, pageCentered, rightAligned, shortSentence, highText); // Decisions ... switch (systemPosition) { case ABOVE_STAVES: // Title, Number, Creator, Direction, Chord if (tinySentence) { if (isAllChord) { return new TextRoleInfo(TextRole.Chord); } else { return new TextRoleInfo(TextRole.UnknownRole); } } if (firstSystem) { if (leftOfStaves) { return new TextRoleInfo(TextRole.Creator, Text.CreatorText.CreatorType.lyricist); } else if (rightAligned) { return new TextRoleInfo(TextRole.Creator, Text.CreatorText.CreatorType.composer); } else if (closeToStaff) { if (isAllChord) { return new TextRoleInfo(TextRole.Chord); } else { return new TextRoleInfo(TextRole.Direction); } } else if (pageCentered) { // Title, Number if (highText) { return new TextRoleInfo(TextRole.Title); } else { return new TextRoleInfo(TextRole.Number); } } } else { if (isAllChord) { return new TextRoleInfo(TextRole.Chord); } else { return new TextRoleInfo(TextRole.Direction); } } break; case WITHIN_STAVES: // Name, Lyrics, Direction if (leftOfStaves) { return new TextRoleInfo(TextRole.Name); } else if ((partPosition == StaffPosition.BELOW_STAVES) && !isMainlyItalic) { return new TextRoleInfo(TextRole.Lyrics); } else { return new TextRoleInfo(TextRole.Direction); } case BELOW_STAVES: // Copyright, Lyrics for single-staff part if (tinySentence) { return new TextRoleInfo(TextRole.UnknownRole); } if (pageCentered && shortSentence && lastSystem) { return new TextRoleInfo(TextRole.Rights); } if (part.getStaves().size() == 1) { if ((partPosition == StaffPosition.BELOW_STAVES) && !isMainlyItalic) { return new TextRoleInfo(TextRole.Lyrics); } } } // Default return new TextRoleInfo(TextRole.UnknownRole); }
@Override public String toString() { return "stick#" + stick.getId(); }