From be8bbadd469f0d1e75ebc922deb111c4cefb67ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Bitteur?= Date: Wed, 8 Jan 2025 13:54:10 +0100 Subject: [PATCH] Staff lines are now less impacted by isolated chunks --- .../omr/glyph/dynamic/CurvedFilament.java | 53 ++++++++++++------- .../audiveris/omr/glyph/dynamic/Filament.java | 17 ++++++ .../omr/glyph/dynamic/FilamentFactory.java | 2 +- .../omr/glyph/dynamic/SectionCompound.java | 23 +++++++- .../omr/sheet/grid/BarsRetriever.java | 3 +- .../omr/sheet/grid/StaffFilament.java | 7 +-- 6 files changed, 77 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/org/audiveris/omr/glyph/dynamic/CurvedFilament.java b/app/src/main/java/org/audiveris/omr/glyph/dynamic/CurvedFilament.java index 964c0360d..12333007d 100644 --- a/app/src/main/java/org/audiveris/omr/glyph/dynamic/CurvedFilament.java +++ b/app/src/main/java/org/audiveris/omr/glyph/dynamic/CurvedFilament.java @@ -60,7 +60,7 @@ public class CurvedFilament /** Typical length between points. */ protected final int segmentLength; - /** Absolute defining points (including start & stop points). */ + /** Absolute defining points (including start and stop points). */ protected List points; /** Curved line across all defining points. */ @@ -88,37 +88,49 @@ public CurvedFilament (int interline, //-------------// /** * Compute cached data: curve, startPoint, stopPoint, slope. + *

* Curve goes from startPoint to stopPoint through intermediate points rather regularly spaced. + *

+ * At each intermediate location, a probe is used to compute the centroid of contained pixels, + * and then define the intermediate point. + * Since the line expected thickness varies with the filament at hand, we must be rather + * conservative regarding the minimum probe weight. + * We thus use a simple fraction of the probe width. */ @Override public void computeLine () { try { - /** Width of window to retrieve pixels */ - int probeWidth = InterlineScale.toPixels(interline, Filament.getProbeWidth()); + // Width and minWeight of window to retrieve pixels + final int probeWidth = InterlineScale.toPixels(interline, Filament.getProbeWidth()); + final int minWeight = Math.max( + 1, + InterlineScale.toPixels(interline, Filament.getProbeMinWeight())); // We need a rough orientation right now - Orientation orientation = getRoughOrientation(); - Point2D orientedStart = (startPoint == null) ? null : orientation.oriented(startPoint); - Point2D orientedStop = (stopPoint == null) ? null : orientation.oriented(stopPoint); - Rectangle oBounds = orientation.oriented(getBounds()); - double oStart = (orientedStart != null) ? orientedStart.getX() : oBounds.x; - double oStop = (orientedStop != null) ? orientedStop.getX() + final Orientation orientation = getRoughOrientation(); + final Point2D orientedStart = (startPoint == null) ? null + : orientation.oriented(startPoint); + final Point2D orientedStop = (stopPoint == null) ? null + : orientation.oriented(stopPoint); + final Rectangle oBounds = orientation.oriented(getBounds()); + final double oStart = (orientedStart != null) ? orientedStart.getX() : oBounds.x; + final double oStop = (orientedStop != null) ? orientedStop.getX() : (oBounds.x + (oBounds.width - 1)); - double length = oStop - oStart + 1; + final double length = oStop - oStart + 1; - Rectangle oProbe = new Rectangle(oBounds); + final Rectangle oProbe = new Rectangle(oBounds); oProbe.x = (int) Math.ceil(oStart); oProbe.width = probeWidth; // Determine the number of segments and their precise length - int segCount = (int) Math.rint(length / segmentLength); - double segLength = length / segCount; - List newPoints = new ArrayList<>(segCount + 1); + final int segCount = (int) Math.rint(length / segmentLength); + final double segLength = length / segCount; + final List newPoints = new ArrayList<>(segCount + 1); - // First point + // First point (minimum weight: 1) if (startPoint == null) { - Point2D p = orientation.oriented(getCentroid(orientation.absolute(oProbe))); + final Point2D p = orientation.oriented(getCentroid(orientation.absolute(oProbe))); startPoint = orientation.absolute(new Point2D.Double(oStart, p.getY())); } @@ -128,19 +140,20 @@ public void computeLine () for (int i = 1; i < segCount; i++) { oProbe.x = (int) Math.rint(oStart + (i * segLength)); - Point2D pt = getCentroid(orientation.absolute(oProbe)); + // Here we can impose a true minimum weight + final Point2D pt = getCentroid(orientation.absolute(oProbe), minWeight); - // If, unfortunately, we are in a filament hole, just skip it + // If, unfortunately, we are in a filament "hole", we can just skip it if (pt != null) { newPoints.add(pt); } } - // Last point + // Last point (minimum weight: 1) if (stopPoint == null) { oProbe.x = (int) Math.floor(oStop - oProbe.width + 1); - Point2D p = orientation.oriented(getCentroid(orientation.absolute(oProbe))); + final Point2D p = orientation.oriented(getCentroid(orientation.absolute(oProbe))); stopPoint = orientation.absolute(new Point2D.Double(oStop, p.getY())); } diff --git a/app/src/main/java/org/audiveris/omr/glyph/dynamic/Filament.java b/app/src/main/java/org/audiveris/omr/glyph/dynamic/Filament.java index e06857658..889ca6a32 100644 --- a/app/src/main/java/org/audiveris/omr/glyph/dynamic/Filament.java +++ b/app/src/main/java/org/audiveris/omr/glyph/dynamic/Filament.java @@ -275,6 +275,19 @@ public static Scale.Fraction getProbeWidth () return constants.probeWidth; } + //-------------------// + // getProbeMinWeight // + //-------------------// + /** + * Report the minimum weight in the window used to determine filament position + * + * @return the scale-independent probe minimum weight + */ + public static Scale.Fraction getProbeMinWeight () + { + return constants.probeMinWeight; + } + //~ Inner Classes ------------------------------------------------------------------------------ //-----------// @@ -286,5 +299,9 @@ private static class Constants private final Scale.Fraction probeWidth = new Scale.Fraction( 0.5, "Width of probing window to retrieve filament ordinate"); + + private final Scale.Fraction probeMinWeight = new Scale.Fraction( + 0.2, + "Minimum weight in probing window to validate filament ordinate"); } } diff --git a/app/src/main/java/org/audiveris/omr/glyph/dynamic/FilamentFactory.java b/app/src/main/java/org/audiveris/omr/glyph/dynamic/FilamentFactory.java index c8b84f0de..bb81bcdd4 100644 --- a/app/src/main/java/org/audiveris/omr/glyph/dynamic/FilamentFactory.java +++ b/app/src/main/java/org/audiveris/omr/glyph/dynamic/FilamentFactory.java @@ -1088,7 +1088,7 @@ private static class Constants "Minimum length for a section to be considered as core"); private final Scale.Fraction maxOverlapDeltaPos = new Scale.Fraction( - 0.4, // Was 0.5 which led to half notes bowing the underlying staff line + 0.25, // Was 0.5 which led to half notes bowing the underlying staff line "Maximum delta position between two overlapping filaments"); private final Scale.Fraction maxCoordGap = new Scale.Fraction( diff --git a/app/src/main/java/org/audiveris/omr/glyph/dynamic/SectionCompound.java b/app/src/main/java/org/audiveris/omr/glyph/dynamic/SectionCompound.java index 8d3bfe1fb..7d4b62d05 100644 --- a/app/src/main/java/org/audiveris/omr/glyph/dynamic/SectionCompound.java +++ b/app/src/main/java/org/audiveris/omr/glyph/dynamic/SectionCompound.java @@ -245,13 +245,32 @@ public Point getCentroid () */ public Point2D getCentroid (Rectangle roi) { - Barycenter barycenter = new Barycenter(); + return getCentroid(roi, 1); + } + + //-------------// + // getCentroid // + //-------------// + /** + * Report the validated glyph absolute centroid (mass center) of all pixels found in the + * provided absolute ROI if any. + * + * @param roi the region of interest, if null all symbol pixels are considered + * @param minWeight minimum weight in region of interest to validate the centroid + * @return the absolute mass center point + */ + public Point2D getCentroid (Rectangle roi, + int minWeight) + { + final Barycenter barycenter = new Barycenter(); for (Section section : getMembers()) { section.cumulate(barycenter, roi); } - if (barycenter.getWeight() != 0) { + final double w = barycenter.getWeight(); + + if (w >= minWeight) { return new Point2D.Double(barycenter.getX(), barycenter.getY()); } else { return null; diff --git a/app/src/main/java/org/audiveris/omr/sheet/grid/BarsRetriever.java b/app/src/main/java/org/audiveris/omr/sheet/grid/BarsRetriever.java index 6b61189d9..0e1f1adad 100644 --- a/app/src/main/java/org/audiveris/omr/sheet/grid/BarsRetriever.java +++ b/app/src/main/java/org/audiveris/omr/sheet/grid/BarsRetriever.java @@ -517,8 +517,7 @@ private Filament buildSerifFilament (Staff staff, Collections.sort( compounds, (SectionCompound g1, - SectionCompound g2) -> - { + SectionCompound g2) -> { double d1 = PointUtil.length(GeoUtil.vectorOf(g1.getCentroid(), vertex)); double d2 = PointUtil.length(GeoUtil.vectorOf(g2.getCentroid(), vertex)); diff --git a/app/src/main/java/org/audiveris/omr/sheet/grid/StaffFilament.java b/app/src/main/java/org/audiveris/omr/sheet/grid/StaffFilament.java index b474303e5..2a63ce0a3 100644 --- a/app/src/main/java/org/audiveris/omr/sheet/grid/StaffFilament.java +++ b/app/src/main/java/org/audiveris/omr/sheet/grid/StaffFilament.java @@ -76,9 +76,10 @@ public class StaffFilament //~ Constructors ------------------------------------------------------------------------------- /** - * Creates a new LineFilament object. - * Nota: this constructor is needed for FilamentFactory which calls this - * kind of constructor via a newInstance() method. + * Creates a new StaffFilament object. + *

+ * NOTA: this constructor is needed for {@link FilamentFactory} which calls this kind of + * constructor via a newInstance() method. * * @param interline scaling data */