//------------------------------------------------------------------------------------------------// // // // B a r F i l a m e n t F a c t o r y // // // //------------------------------------------------------------------------------------------------// // <editor-fold defaultstate="collapsed" desc="hdr"> // // Copyright © Audiveris 2018. All rights reserved. // // This program is free software: you can redistribute it and/or modify it under the terms of the // GNU Affero General Public License as published by the Free Software Foundation, either version // 3 of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License along with this // program. If not, see <http://www.gnu.org/licenses/>. //------------------------------------------------------------------------------------------------// // </editor-fold> package org.audiveris.omr.sheet.grid; import org.audiveris.omr.constant.Constant; import org.audiveris.omr.constant.ConstantSet; import org.audiveris.omr.glyph.dynamic.CurvedFilament; import org.audiveris.omr.glyph.dynamic.Filament; import org.audiveris.omr.glyph.dynamic.FilamentFactory; import org.audiveris.omr.lag.Section; import org.audiveris.omr.math.GeoUtil; import static org.audiveris.omr.run.Orientation.VERTICAL; import org.audiveris.omr.sheet.Scale; import org.audiveris.omr.util.Dumping; import org.audiveris.omr.util.StopWatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.Point; import java.awt.Rectangle; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; /** * Class {@code BarFilamentFactory} builds the underlying filament of a bar line * candidate. * <p> * As opposed to {@link FilamentFactory}, this class focused on the building of a single filament * defined by the bar line rectangular core area. * <p> * It allows to detect if a bar goes beyond staff height. * It also allows to evaluate filament straightness and thus discard peaks due to braces. * * @author Hervé Bitteur */ public class BarFilamentFactory { private static final Constants constants = new Constants(); private static final Logger logger = LoggerFactory.getLogger(BarFilamentFactory.class); /** Related scale. */ private final Scale scale; /** Scale-dependent constants. */ private final Parameters params; /** * Creates a new {@code BarFilamentFactory} object. * * @param scale the related scale */ public BarFilamentFactory (Scale scale) { this.scale = scale; params = new Parameters(scale); } //------------------// // buildBarFilament // //------------------// /** * Aggregate sections into one filament guided by the provided core rectangle. * * @param source the collection of candidate input sections * @param absCore the core absolute rectangle * @return the retrieved filament, or null */ public Filament buildBarFilament (Collection<Section> source, Rectangle absCore) { StopWatch watch = new StopWatch("buildBarFilament"); try { // Aggregate long sections that intersect line core onto skeleton line watch.start("populateCore"); Rectangle core = VERTICAL.oriented(absCore); Filament fil = populateCore(source, core); if (fil == null) { return null; } // Expand with sections left over, when they touch already included ones watch.start("expandFilament"); expandFilament(fil, core, source); return fil; } catch (Exception ex) { logger.warn("BarFilamentFactory cannot buildBarFilament", ex); return null; } finally { if (constants.printWatch.isSet()) { watch.print(); } } } //----------// // canMerge // //----------// private boolean canMerge (Filament fil, Rectangle core, Section section) { // A section must always touch one of fil current member sections if (!fil.touches(section)) { return false; } // If this does not increase thickness beyond core, it's OK Rectangle oSct = VERTICAL.oriented(section.getBounds()); return core.union(oSct).height <= core.height; } //----------------// // expandFilament // //----------------// private void expandFilament (Filament fil, Rectangle core, Collection<Section> source) { final List<Section> sections = new ArrayList<>(source); sections.removeAll(fil.getMembers()); boolean expanding; do { expanding = false; for (Iterator<Section> it = sections.iterator(); it.hasNext();) { Section section = it.next(); if (canMerge(fil, core, section)) { if (logger.isDebugEnabled() || fil.isVip() || section.isVip()) { logger.info("VIP merging {} w/ {}", fil, section); } fil.addSection(section); it.remove(); expanding = true; break; } } } while (expanding); } //--------------// // populateCore // //--------------// /** * Use the long source sections to stick to the provided skeleton and return * the resulting filament. * <p> * Strategy: We use only the long sections that intersect core length (height) and are close * enough to the core mid line. * * @param source the input sections * @param core the oriented core rectangle */ private Filament populateCore (Collection<Section> source, Rectangle core) { final Filament fil = new CurvedFilament(scale.getInterline(), params.segmentLength); for (Section section : source) { Rectangle sectRect = VERTICAL.oriented(section.getBounds()); // Section with significant length, intersecting core and centroid in core alignment? if (sectRect.width >= params.minCoreSectionLength) { if (sectRect.intersects(core)) { Point oCentroid = VERTICAL.oriented(section.getCentroid()); if (GeoUtil.yEmbraces(core, oCentroid.y)) { fil.addSection(section); } } } } if (!fil.getMembers().isEmpty()) { return fil; } else { return null; } } //-----------// // Constants // //-----------// private static class Constants extends ConstantSet { private final Constant.Boolean printWatch = new Constant.Boolean( false, "Should we print out the stop watch?"); private final Scale.Fraction minCoreSectionLength = new Scale.Fraction( 0.5, "Minimum length for a section to be considered as core"); private final Scale.Fraction segmentLength = new Scale.Fraction( 1, "Typical length between filament curve intermediate points"); } //------------// // Parameters // //------------// /** * Class {@code Parameters} gathers all scale-dependent parameters. */ private static class Parameters { public int minCoreSectionLength; public int segmentLength; Parameters (Scale scale) { minCoreSectionLength = scale.toPixels(constants.minCoreSectionLength); segmentLength = scale.toPixels(constants.segmentLength); if (logger.isDebugEnabled()) { new Dumping().dump(this); } } } }