Skip to content

Commit

Permalink
Staff lines are now less impacted by isolated chunks
Browse files Browse the repository at this point in the history
  • Loading branch information
hbitteur committed Jan 8, 2025
1 parent 325c0fb commit be8bbad
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<Point2D> points;

/** Curved line across all defining points. */
Expand Down Expand Up @@ -88,37 +88,49 @@ public CurvedFilament (int interline,
//-------------//
/**
* Compute cached data: curve, startPoint, stopPoint, slope.
* <p>
* Curve goes from startPoint to stopPoint through intermediate points rather regularly spaced.
* <p>
* 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<Point2D> newPoints = new ArrayList<>(segCount + 1);
final int segCount = (int) Math.rint(length / segmentLength);
final double segLength = length / segCount;
final List<Point2D> 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()));
}

Expand All @@ -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()));
}

Expand Down
17 changes: 17 additions & 0 deletions app/src/main/java/org/audiveris/omr/glyph/dynamic/Filament.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 ------------------------------------------------------------------------------

//-----------//
Expand All @@ -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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>StaffFilament</code> object.
* <p>
* NOTA: this constructor is needed for {@link FilamentFactory} which calls this kind of
* constructor via a newInstance() method.
*
* @param interline scaling data
*/
Expand Down

0 comments on commit be8bbad

Please sign in to comment.