From 0bb035c282d2db59eb2a552ce361454126263b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Bitteur?= Date: Sun, 21 Jan 2024 20:00:32 +0100 Subject: [PATCH] InterController: manually removed inters now reveal and merge their underlying glyphs --- .../org/audiveris/omr/glyph/GlyphIndex.java | 11 +- src/main/org/audiveris/omr/glyph/Glyphs.java | 3 +- .../org/audiveris/omr/image/ImageUtil.java | 26 ++ .../org/audiveris/omr/image/Template.java | 19 +- .../org/audiveris/omr/sheet/PageCleaner.java | 149 +++++--- src/main/org/audiveris/omr/sheet/Picture.java | 3 +- src/main/org/audiveris/omr/sheet/Sheet.java | 4 +- .../org/audiveris/omr/sheet/SystemInfo.java | 6 +- .../omr/sheet/beam/BeamsBuilder.java | 13 +- .../audiveris/omr/sheet/beam/BeamsStep.java | 7 +- .../omr/sheet/beam/BlackHeadSizer.java | 1 - .../omr/sheet/beam/SpotsBuilder.java | 2 +- .../audiveris/omr/sheet/key/KeyExtractor.java | 1 - .../omr/sheet/rhythm/MeasureRhythm.java | 9 +- .../audiveris/omr/sheet/stem/HeadLinker.java | 49 +-- .../omr/sheet/symbol/SymbolsBuilder.java | 2 +- .../omr/sheet/symbol/SymbolsFilter.java | 89 +++-- .../omr/sheet/time/HeaderTimeBuilder.java | 19 +- .../audiveris/omr/sheet/ui/SheetPainter.java | 12 +- .../sig/inter/AbstractHorizontalInter.java | 18 +- .../omr/sig/inter/AbstractInter.java | 19 +- .../omr/sig/inter/AbstractInterVisitor.java | 2 +- .../org/audiveris/omr/sig/inter/Inters.java | 30 +- .../omr/sig/inter/MultipleRestInter.java | 17 - .../omr/sig/inter/SmallChordInter.java | 2 +- .../omr/sig/inter/VerticalSerifInter.java | 19 + .../omr/sig/ui/GlyphsAdditionTask.java | 91 +++++ .../omr/sig/ui/GlyphsRemovalTask.java | 81 +++++ .../audiveris/omr/sig/ui/InterController.java | 337 +++++++++++++++++- .../audiveris/omr/sig/ui/RemovalScenario.java | 42 +-- .../org/audiveris/omr/sig/ui/UITaskList.java | 12 +- .../org/audiveris/omr/step/ReductionStep.java | 34 ++ .../org/audiveris/omr/text/TextBuilder.java | 3 +- .../omr/ui/selection/SelectionHint.java | 22 +- .../audiveris/omr/ui/symbol/MusicFont.java | 17 +- src/main/org/audiveris/omr/util/ByteUtil.java | 4 +- src/main/org/audiveris/omr/util/Entities.java | 21 +- 37 files changed, 858 insertions(+), 338 deletions(-) create mode 100644 src/main/org/audiveris/omr/sig/ui/GlyphsAdditionTask.java create mode 100644 src/main/org/audiveris/omr/sig/ui/GlyphsRemovalTask.java diff --git a/src/main/org/audiveris/omr/glyph/GlyphIndex.java b/src/main/org/audiveris/omr/glyph/GlyphIndex.java index eb45844f5..5cdfff648 100644 --- a/src/main/org/audiveris/omr/glyph/GlyphIndex.java +++ b/src/main/org/audiveris/omr/glyph/GlyphIndex.java @@ -76,10 +76,10 @@ public class GlyphIndex // Persistent data //---------------- - /* + /** * See {@link #getEntities()} and {@link #setEntities(java.util.ArrayList)} methods * which are called by private methods - * Sheet#getGlyphIndexContent() and Sheet#setGlyphIndexContent() + * {@link Sheet#getGlyphIndexContent()} and {@link Sheet#setGlyphIndexContent()} * triggered by JAXB (un)marshalling. */ @@ -399,9 +399,9 @@ public int register (Glyph glyph) */ public synchronized Glyph registerOriginal (Glyph glyph) { - WeakGlyph weak = new WeakGlyph(glyph); - WeakGlyph orgWeak = originals.putIfAbsent(weak, weak); - Glyph orgGlyph = (orgWeak != null) ? orgWeak.get() : null; + final WeakGlyph weak = new WeakGlyph(glyph); + final WeakGlyph orgWeak = originals.putIfAbsent(weak, weak); + final Glyph orgGlyph = (orgWeak != null) ? orgWeak.get() : null; if (orgGlyph == null) { privateRegister(glyph); @@ -409,6 +409,7 @@ public synchronized Glyph registerOriginal (Glyph glyph) return glyph; } else { logger.debug("Reuse original {}", orgGlyph); + weakIndex.insert(orgWeak); // Safer if original has been removed from index return orgGlyph; } diff --git a/src/main/org/audiveris/omr/glyph/Glyphs.java b/src/main/org/audiveris/omr/glyph/Glyphs.java index 5f2bac5a3..00842c4b0 100644 --- a/src/main/org/audiveris/omr/glyph/Glyphs.java +++ b/src/main/org/audiveris/omr/glyph/Glyphs.java @@ -21,6 +21,7 @@ // package org.audiveris.omr.glyph; +import static org.audiveris.omr.image.PixelSource.BACKGROUND; import org.audiveris.omr.util.Entities; import org.audiveris.omr.util.Table; @@ -491,7 +492,7 @@ public static boolean intersect (Glyph one, // More precise test Table.UnsignedByte table = new Table.UnsignedByte(clip.width, clip.height); - table.fill(255); // All white + table.fill(BACKGROUND); // All white one.fillTable(table, clip.getLocation(), fat); return two.intersects(table, clip.getLocation()); diff --git a/src/main/org/audiveris/omr/image/ImageUtil.java b/src/main/org/audiveris/omr/image/ImageUtil.java index 3fa5aa475..779f6b8b6 100644 --- a/src/main/org/audiveris/omr/image/ImageUtil.java +++ b/src/main/org/audiveris/omr/image/ImageUtil.java @@ -26,6 +26,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.Raster; @@ -280,6 +282,30 @@ public static void saveOnDisk (BufferedImage image, saveOnDisk(image, "", name); } + //------------// + // saveOnDisk // + //------------// + /** + * Convenient method to save a BufferedImage to disk (in application temp area) + * + * @param s applied scaling + * @param image the image to save + * @param name file name, without extension + */ + public static void saveOnDisk (int s, + BufferedImage image, + String name) + { + final BufferedImage scaledImg = new BufferedImage( + s * image.getWidth(), + s * image.getHeight(), + image.getType()); + final AffineTransform at = AffineTransform.getScaleInstance(s, s); + AffineTransformOp scaleOp = new AffineTransformOp(at, null); + scaleOp.filter(image, scaledImg); + saveOnDisk(scaledImg, "", name); + } + //------------// // saveOnDisk // //------------// diff --git a/src/main/org/audiveris/omr/image/Template.java b/src/main/org/audiveris/omr/image/Template.java index 65d5a6fd3..e455d13a0 100644 --- a/src/main/org/audiveris/omr/image/Template.java +++ b/src/main/org/audiveris/omr/image/Template.java @@ -397,9 +397,9 @@ public List getForegroundPixels (Rectangle tplBox, * Collect the image foreground pixels located under the template foreground areas, * with some additional margin. * - * @param tplBox absolute positioning of template box in global image - * @param image global image to be read - * @param dilated true for applying dilation before processing + * @param tplBox positioning of template box in provided image + * @param image the provided image to be read + * @param dilated true for applying dilation on foreground areas * @return the collection of foreground pixels, relative to template box. */ public List getForegroundPixels (Rectangle tplBox, @@ -643,6 +643,19 @@ private Point upperLeft (int x, //~ Static Methods ----------------------------------------------------------------------------- + //----------// + // dilation // + //----------// + /** + * Report the erasing dilation value applied on templates. + * + * @return the dilation fraction + */ + public static Scale.Fraction dilation () + { + return constants.dilation; + } + //----------// // impactOf // //----------// diff --git a/src/main/org/audiveris/omr/sheet/PageCleaner.java b/src/main/org/audiveris/omr/sheet/PageCleaner.java index a063f7f1f..f65361251 100644 --- a/src/main/org/audiveris/omr/sheet/PageCleaner.java +++ b/src/main/org/audiveris/omr/sheet/PageCleaner.java @@ -27,7 +27,10 @@ import org.audiveris.omr.sheet.Scale.InterlineScale; import org.audiveris.omr.sig.inter.AbstractBeamInter; import org.audiveris.omr.sig.inter.AbstractChordInter; +import org.audiveris.omr.sig.inter.AbstractFlagInter; import org.audiveris.omr.sig.inter.AbstractInterVisitor; +import org.audiveris.omr.sig.inter.AbstractNumberInter; +import org.audiveris.omr.sig.inter.ArpeggiatoInter; import org.audiveris.omr.sig.inter.BarConnectorInter; import org.audiveris.omr.sig.inter.BarlineInter; import org.audiveris.omr.sig.inter.BraceInter; @@ -40,18 +43,23 @@ import org.audiveris.omr.sig.inter.KeyAlterInter; import org.audiveris.omr.sig.inter.KeyInter; import org.audiveris.omr.sig.inter.LedgerInter; +import org.audiveris.omr.sig.inter.MultipleRestInter; +import org.audiveris.omr.sig.inter.OctaveShiftInter; import org.audiveris.omr.sig.inter.SentenceInter; import org.audiveris.omr.sig.inter.SlurInter; import org.audiveris.omr.sig.inter.StaffBarlineInter; import org.audiveris.omr.sig.inter.StemInter; +import org.audiveris.omr.sig.inter.TimeCustomInter; import org.audiveris.omr.sig.inter.TimePairInter; import org.audiveris.omr.sig.inter.TimeWholeInter; +import org.audiveris.omr.sig.inter.VerticalSerifInter; import org.audiveris.omr.sig.inter.WedgeInter; import org.audiveris.omr.sig.inter.WordInter; import org.audiveris.omr.ui.symbol.Alignment; import org.audiveris.omr.ui.symbol.FontSymbol; import org.audiveris.omr.ui.symbol.MusicFamily; import org.audiveris.omr.ui.symbol.MusicFont; +import org.audiveris.omr.ui.symbol.OmrFont; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -114,16 +122,16 @@ public abstract class PageCleaner /** Preferred font family for sheet at hand. */ private final MusicFamily family; - /** Slightly dilated font point size. */ - private final int dilatedSize; + /** Slightly enlarged font point size. */ + private final int enlargedSize; - /** Slightly dilated font point size for heads. */ - private final int dilatedHeadSize; + /** Slightly enlarged font point size for heads. */ + private final int enlargedHeadSize; /** Font point size on small staves, if any. */ private final int smallPointSize; - /** Font point size for heads on small staves, if any. */ + /** Font point size for small heads, if any. */ private final int smallHeadSize; //~ Constructors ------------------------------------------------------------------------------- @@ -148,17 +156,15 @@ public PageCleaner (ByteProcessor buffer, // NOTA: To clean items from buffer, we use slightly dilated music fonts final int interline = scale.getInterline(); - dilatedSize = dilated(MusicFont.getPointSize(interline)); - dilatedHeadSize = dilated(MusicFont.getHeadPointSize(scale, interline)); + enlargedSize = enlarged(MusicFont.getPointSize(interline)); + enlargedHeadSize = enlarged(MusicFont.getHeadPointSize(scale, interline)); - // TODO: Should we use a similar dilation for small items? + // Item size for small staves final Integer smallInterline = scale.getSmallInterline(); - if (smallInterline != null) { - smallPointSize = MusicFont.getPointSize(smallInterline); - smallHeadSize = MusicFont.getHeadPointSize(scale, smallInterline); - } else { - smallPointSize = smallHeadSize = 0; // To keep compiler happy - } + smallPointSize = (smallInterline != null) ? MusicFont.getPointSize(smallInterline) : 0; + + // Size for small heads (regardless of staff size) + smallHeadSize = MusicFont.getHeadPointSize(scale, interline * OmrFont.RATIO_SMALL); // Thickness of margins applied (around areas & lines) final float marginThickness = (float) scale.toPixelsDouble(constants.lineMargin); @@ -191,18 +197,18 @@ protected boolean canHide (Inter inter) return inter.isFrozen() || inter.isContextuallyGood(); } - //---------// - // dilated // - //---------// + //----------// + // enlarged // + //----------// /** - * Report a slightly dilated value of provided point size + * Report a slightly enlarged value of provided point size * * @param pointSize the provided point size * @return slightly increased point size value */ - private int dilated (int pointSize) + private int enlarged (int pointSize) { - return (int) Math.rint(pointSize * constants.dilationRatio.getValue()); + return (int) Math.rint(pointSize * constants.enlargementRatio.getValue()); } //-------------------// @@ -224,11 +230,11 @@ protected void eraseStavesHeader (SystemInfo system, continue; } - int dy = InterlineScale.toPixels(staff.getSpecificInterline(), yMargin); - int left = staff.getHeaderStart(); - int right = staff.getHeaderStop(); - int top = staff.getFirstLine().yAt(right) - dy; - int bot = staff.getLastLine().yAt(right) + dy; + final int dy = InterlineScale.toPixels(staff.getSpecificInterline(), yMargin); + final int left = staff.getHeaderStart(); + final int right = staff.getHeaderStop(); + final int top = staff.getFirstLine().yAt(right) - dy; + final int bot = staff.getLastLine().yAt(right) + dy; g.fillRect(left, top, right - left + 1, bot - top + 1); } } @@ -275,9 +281,7 @@ protected void eraseSystemHeader (SystemInfo system, */ public void eraseTablatures (Scale.Fraction yMargin) { - for (SystemInfo system : sheet.getSystems()) { - eraseTablatures(system, yMargin); - } + sheet.getSystems().forEach(system -> eraseTablatures(system, yMargin)); } //-----------------// @@ -294,8 +298,8 @@ protected void eraseTablatures (SystemInfo system, { for (Staff staff : system.getStaves()) { if (staff.isTablature()) { - int dy = InterlineScale.toPixels(staff.getSpecificInterline(), yMargin); - Area core = StaffManager.getCoreArea(staff, 0, dy); + final int dy = InterlineScale.toPixels(staff.getSpecificInterline(), yMargin); + final Area core = StaffManager.getCoreArea(staff, 0, dy); g.fill(core); } } @@ -347,9 +351,25 @@ public void visit (AbstractBeamInter inter) @Override public void visit (AbstractChordInter inter) { - for (Inter member : inter.getNotes()) { - member.accept(this); - } + inter.getNotes().forEach(note -> note.accept(this)); + } + + @Override + public void visit (AbstractFlagInter inter) + { + processGlyph(inter.getGlyph()); + } + + @Override + public void visit (AbstractNumberInter inter) + { + processGlyph(inter.getGlyph()); + } + + @Override + public void visit (ArpeggiatoInter inter) + { + processGlyph(inter.getGlyph()); } @Override @@ -364,6 +384,8 @@ public void visit (BarlineInter inter) processArea(inter.getArea()); } + // No BeamGroup + @Override public void visit (BraceInter inter) { @@ -406,8 +428,8 @@ public void visit (EndingInter ending) @Override public void visit (HeadInter head) { - - final int size = head.getStaff().isSmall() ? smallHeadSize : dilatedHeadSize; + // We now check small size for head directly (regardless of the containing staff size) + final int size = head.getShape().isSmallHead() ? smallHeadSize : enlargedHeadSize; final FontSymbol fs = head.getShape().getFontSymbolBySize(family, size); if (fs.symbol == null) { @@ -421,7 +443,7 @@ public void visit (HeadInter head) } /** - * Default strategy for a basic inter is to paint in white the related symbol. + * Default strategy for a basic inter is to paint the related symbol. * * @param inter the basic inter to erase */ @@ -433,7 +455,7 @@ public void visit (Inter inter) } final boolean isSmall = (inter.getStaff() != null) && inter.getStaff().isSmall(); - final int size = isSmall ? smallPointSize : dilatedSize; + final int size = isSmall ? smallPointSize : enlargedSize; final FontSymbol fs = inter.getShape().getFontSymbolBySize(family, size); if (fs.symbol == null) { @@ -458,10 +480,7 @@ public void visit (KeyAlterInter alter) @Override public void visit (KeyInter key) { - for (Inter member : key.getMembers()) { - KeyAlterInter alter = (KeyAlterInter) member; - processGlyph(alter.getGlyph()); - } + key.getMembers().forEach(member -> processGlyph(((KeyAlterInter) member).getGlyph())); } @Override @@ -471,13 +490,27 @@ public void visit (LedgerInter inter) } @Override - public void visit (SentenceInter inter) + public void visit (MultipleRestInter inter) { - for (Inter member : inter.getMembers()) { - processGlyph(member.getGlyph()); + if (inter.getGlyph() != null) { + processGlyph(inter.getGlyph()); + } else { + processArea(inter.getArea()); } } + @Override + public void visit (OctaveShiftInter inter) + { + processGlyph(inter.getGlyph()); + } + + @Override + public void visit (SentenceInter inter) + { + inter.getMembers().forEach(member -> processGlyph(member.getGlyph())); + } + @Override public void visit (SlurInter inter) { @@ -487,12 +520,10 @@ public void visit (SlurInter inter) @Override public void visit (StaffBarlineInter inter) { - List members = inter.getMembers(); + final List members = inter.getMembers(); if (!members.isEmpty()) { - for (Inter member : members) { - member.accept(this); - } + members.forEach(member -> member.accept(this)); } else if (inter.getShape() != null) { visit((Inter) inter); } @@ -508,12 +539,16 @@ public void visit (StemInter inter) } } + @Override + public void visit (TimeCustomInter inter) + { + processGlyph(inter.getGlyph()); + } + @Override public void visit (TimePairInter pair) { - for (Inter member : pair.getMembers()) { - processGlyph(member.getGlyph()); - } + pair.getMembers().forEach(member -> processGlyph(member.getGlyph())); } @Override @@ -522,6 +557,12 @@ public void visit (TimeWholeInter inter) processGlyph(inter.getGlyph()); } + @Override + public void visit (VerticalSerifInter inter) + { + processGlyph(inter.getGlyph()); + } + @Override public void visit (WedgeInter wedge) { @@ -545,14 +586,10 @@ private static class Constants extends ConstantSet { - private final Constant.Ratio dilationRatio = new Constant.Ratio( + private final Constant.Ratio enlargementRatio = new Constant.Ratio( 1.1, "Size augmentation to use with eraser music font"); - private final Scale.Fraction symbolSize = new Scale.Fraction( - 1.1, - "Symbols size to use with eraser music font"); - private final Scale.Fraction lineMargin = new Scale.Fraction( 0.1, "Thickness of white lines drawn on items borders"); diff --git a/src/main/org/audiveris/omr/sheet/Picture.java b/src/main/org/audiveris/omr/sheet/Picture.java index 1d1dcbc5d..0c549c7b4 100644 --- a/src/main/org/audiveris/omr/sheet/Picture.java +++ b/src/main/org/audiveris/omr/sheet/Picture.java @@ -32,6 +32,7 @@ import org.audiveris.omr.image.MedianGrayFilter; import org.audiveris.omr.image.PixelFilter; import org.audiveris.omr.image.PixelSource; +import static org.audiveris.omr.image.PixelSource.BACKGROUND; import static org.audiveris.omr.run.Orientation.VERTICAL; import org.audiveris.omr.run.RunTable; import org.audiveris.omr.run.RunTableFactory; @@ -471,7 +472,7 @@ public void dumpRectangle (SourceKey key, for (int x = xMin; x <= xMax; x++) { int pix = source.get(x, y); - if (pix == 255) { // White background + if (pix == BACKGROUND) { // White background sb.append(" ."); } else { sb.append(String.format("%4d", pix)); diff --git a/src/main/org/audiveris/omr/sheet/Sheet.java b/src/main/org/audiveris/omr/sheet/Sheet.java index 57b4dc5e1..68acd9968 100644 --- a/src/main/org/audiveris/omr/sheet/Sheet.java +++ b/src/main/org/audiveris/omr/sheet/Sheet.java @@ -250,6 +250,8 @@ public class Sheet /** Delta measurements. */ private SheetDiff sheetDelta; + //~ Constructors ------------------------------------------------------------------------------- + /** * No-arg constructor needed for JAXB. */ @@ -294,8 +296,6 @@ public Sheet (SheetStub stub, } } - //~ Constructors ------------------------------------------------------------------------------- - /** * Creates a new Sheet object with a binary table. * diff --git a/src/main/org/audiveris/omr/sheet/SystemInfo.java b/src/main/org/audiveris/omr/sheet/SystemInfo.java index 728d4105c..d29c557d3 100644 --- a/src/main/org/audiveris/omr/sheet/SystemInfo.java +++ b/src/main/org/audiveris/omr/sheet/SystemInfo.java @@ -1623,7 +1623,7 @@ public void numberParts () // registerGlyph // //---------------// /** - * Make glyph original, registered and included in freeGlyphs. + * Make glyph original and registered. * * @param glyph the glyph to register * @param group group to assign, or null @@ -1636,7 +1636,9 @@ public Glyph registerGlyph (Glyph glyph, glyph = glyphIndex.registerOriginal(glyph); glyph.addGroup(group); - addFreeGlyph(glyph); + + /// TODO: This seems no longer mandatory + /// addFreeGlyph(glyph); return glyph; } diff --git a/src/main/org/audiveris/omr/sheet/beam/BeamsBuilder.java b/src/main/org/audiveris/omr/sheet/beam/BeamsBuilder.java index f25b4701f..2de9d2cc4 100644 --- a/src/main/org/audiveris/omr/sheet/beam/BeamsBuilder.java +++ b/src/main/org/audiveris/omr/sheet/beam/BeamsBuilder.java @@ -1193,8 +1193,7 @@ private List getCueAggregates () // We look for collections of good cue black heads + stem, close enough // to be able to be connected by a cue beam. - List smallBlacks = sig.inters( (Inter inter) -> - { + List smallBlacks = sig.inters( (Inter inter) -> { if (inter.isRemoved() || (inter.getShape() != Shape.NOTEHEAD_BLACK_SMALL)) { return false; } @@ -1322,8 +1321,7 @@ private AbstractBeamInter getSideBeam (AbstractBeamInter beam, Collections.sort( others, (Inter o1, - Inter o2) -> - { + Inter o2) -> { AbstractBeamInter b1 = (AbstractBeamInter) o1; AbstractBeamInter b2 = (AbstractBeamInter) o2; @@ -1511,9 +1509,6 @@ private void registerBeam (AbstractBeamInter beam) Glyph glyph = retrieveGlyph(beam); beam.setGlyph(glyph); - - // Make this glyph survive the beam removal if any - system.addFreeGlyph(glyph); } //---------------// @@ -2055,13 +2050,13 @@ private void process (List spots) // Determine stem direction in the aggregate globalDir = getDirection(); if (globalDir == 0) { - logger.info("Mixed or unknown direction in cue area {}", this); + logger.info("Mixed or unknown direction in cue area {} {}", id, bounds); return; } // Retrieve candidate glyphs from spots - List glyphs = getCueGlyphs(); + final List glyphs = getCueGlyphs(); // Retrieve beams from candidate glyphs final List beams = new ArrayList<>(); diff --git a/src/main/org/audiveris/omr/sheet/beam/BeamsStep.java b/src/main/org/audiveris/omr/sheet/beam/BeamsStep.java index ae4fe2011..d4d6e7586 100644 --- a/src/main/org/audiveris/omr/sheet/beam/BeamsStep.java +++ b/src/main/org/audiveris/omr/sheet/beam/BeamsStep.java @@ -80,9 +80,7 @@ protected void doEpilog (Sheet sheet, Context context) throws StepException { - for (SystemInfo system : sheet.getSystems()) { - system.removeGroupedGlyphs(GlyphGroup.BEAM_SPOT); - } + sheet.getSystems().forEach(system -> system.removeGroupedGlyphs(GlyphGroup.BEAM_SPOT)); } //----------// @@ -99,7 +97,7 @@ protected void doEpilog (Sheet sheet, @Override protected Context doProlog (Sheet sheet) { - Lag spotLag = new BasicLag(Lags.SPOT_LAG, SpotsBuilder.SPOT_ORIENTATION); + final Lag spotLag = new BasicLag(Lags.SPOT_LAG, SpotsBuilder.SPOT_ORIENTATION); // Retrieve significant spots for the whole sheet new SpotsBuilder(sheet).buildSheetSpots(spotLag); @@ -131,7 +129,6 @@ public void doSystem (SystemInfo system, */ protected static class Context { - /** Lag of spot sections. */ public final Lag spotLag; diff --git a/src/main/org/audiveris/omr/sheet/beam/BlackHeadSizer.java b/src/main/org/audiveris/omr/sheet/beam/BlackHeadSizer.java index d8ade2f40..98abe5547 100644 --- a/src/main/org/audiveris/omr/sheet/beam/BlackHeadSizer.java +++ b/src/main/org/audiveris/omr/sheet/beam/BlackHeadSizer.java @@ -151,7 +151,6 @@ private Glyph closeBlackHead (MorphoProcessor mp, // Check glyph is within system abscissa boundaries if ((center.x >= system.getLeft()) && (center.x <= system.getRight())) { glyph = sheet.getGlyphIndex().registerOriginal(glyph); - system.addFreeGlyph(glyph); } } diff --git a/src/main/org/audiveris/omr/sheet/beam/SpotsBuilder.java b/src/main/org/audiveris/omr/sheet/beam/SpotsBuilder.java index 5c2a08ed4..2feecd624 100644 --- a/src/main/org/audiveris/omr/sheet/beam/SpotsBuilder.java +++ b/src/main/org/audiveris/omr/sheet/beam/SpotsBuilder.java @@ -303,7 +303,7 @@ private void dispatchSheetSpots (List spots) if ((center.x >= system.getLeft()) && (center.x <= system.getRight())) { glyph = glyphIndex.registerOriginal(glyph); glyph.addGroup(GlyphGroup.BEAM_SPOT); - system.addFreeGlyph(glyph); + system.addFreeGlyph(glyph); // Needed to survive until end of BEAMS step created = true; } } diff --git a/src/main/org/audiveris/omr/sheet/key/KeyExtractor.java b/src/main/org/audiveris/omr/sheet/key/KeyExtractor.java index 89f05b7fd..1bf599c78 100644 --- a/src/main/org/audiveris/omr/sheet/key/KeyExtractor.java +++ b/src/main/org/audiveris/omr/sheet/key/KeyExtractor.java @@ -613,7 +613,6 @@ protected void evaluateSliceGlyph (KeySlice slice, if (glyph.getId() == 0) { glyph = sheet.getGlyphIndex().registerOriginal(glyph); - system.addFreeGlyph(glyph); } if (glyph.isVip()) { diff --git a/src/main/org/audiveris/omr/sheet/rhythm/MeasureRhythm.java b/src/main/org/audiveris/omr/sheet/rhythm/MeasureRhythm.java index dbf639d60..0b29bce80 100644 --- a/src/main/org/audiveris/omr/sheet/rhythm/MeasureRhythm.java +++ b/src/main/org/audiveris/omr/sheet/rhythm/MeasureRhythm.java @@ -564,9 +564,9 @@ private void processStartingChords () for (AbstractChordInter ch : rookies) { // Voice measure.addVoice( - measureRestsChords.contains(ch) ? Voice.createMeasureRestVoice( - (RestChordInter) ch, - measure) : new Voice(ch, measure)); + measureRestsChords.contains(ch) // + ? Voice.createMeasureRestVoice((RestChordInter) ch, measure) + : new Voice(ch, measure)); // Time ch.setAndPushTime(Rational.ZERO); @@ -1070,8 +1070,7 @@ private List getOrderedSiblings (final AbstractChordInter ro Collections.sort( siblings, (AbstractChordInter c1, - AbstractChordInter c2) -> - { + AbstractChordInter c2) -> { // In fact any SlotsRetriever (narrow or wide) could fit! Rel r1 = narrowSlotsRetriever.getRel(rookie, c1); Rel r2 = narrowSlotsRetriever.getRel(rookie, c2); diff --git a/src/main/org/audiveris/omr/sheet/stem/HeadLinker.java b/src/main/org/audiveris/omr/sheet/stem/HeadLinker.java index 2ee66b747..42c12bf33 100644 --- a/src/main/org/audiveris/omr/sheet/stem/HeadLinker.java +++ b/src/main/org/audiveris/omr/sheet/stem/HeadLinker.java @@ -478,56 +478,9 @@ public static List lookupBeamGroups (List beams, return new ArrayList<>(groups); } - // - // //------------------// - // // saveHeadVicinity // - // //------------------// - // /** - // * An attempt to check usable chunks in head vicinity. - // *

- // * We erase head glyph before looking for suitable vertical sections as stem chunks. - // */ - // private void saveHeadVicinity () - // { - // final Sheet sheet = head.getSig().getSystem().getSheet(); - // final ByteProcessor noStaff = sheet.getPicture().getSource(NO_STAFF); - // final Rectangle bounds = head.getBounds(); - // final Rectangle box = new Rectangle(bounds); - // - // final int interline = sheet.getInterline(); - // box.grow(interline, 2 * interline); - // final Point offset = box.getLocation(); - // - // noStaff.setRoi(box); - // final ByteProcessor bp = (ByteProcessor) noStaff.crop(); - // noStaff.resetRoi(); - // - // // Erase head glyph pixels - // final Glyph headGlyph = head.getGlyph(); - // for (int y = 0; y < box.height; y++) { - // for (int x = 0; x < box.width; x++) { - // final Point p = new Point(offset.x + x, offset.y + y); - // if (headGlyph.contains(p)) { - // bp.putPixel(x, y, 255); - //// bp.putPixel(x - 1, y, 255); - //// bp.putPixel(x + 1, y, 255); - // } - // } - // } - // - // final int zoom = 20; - // final BufferedImage img = new BufferedImage(zoom * box.width, - // zoom * box.height, - // BufferedImage.TYPE_INT_RGB); - // final Graphics2D g = img.createGraphics(); - // final AffineTransform at = AffineTransform.getScaleInstance(zoom, zoom); - // g.drawImage(bp.getBufferedImage(), at, null); - // - // ImageUtil.saveOnDisk(img, sheet.getStub().getId(), "head#" + head.getId()); - // } - // //~ Inner Classes ------------------------------------------------------------------------------ + //---------// // SLinker // //---------// diff --git a/src/main/org/audiveris/omr/sheet/symbol/SymbolsBuilder.java b/src/main/org/audiveris/omr/sheet/symbol/SymbolsBuilder.java index e9af783f4..7acdeee89 100644 --- a/src/main/org/audiveris/omr/sheet/symbol/SymbolsBuilder.java +++ b/src/main/org/audiveris/omr/sheet/symbol/SymbolsBuilder.java @@ -404,7 +404,7 @@ private static class Constants "Maximum number of evaluations kept for a symbol"); private final Scale.Fraction maxGap = new Scale.Fraction( - 0.6, // Was 0.5, but 0.55 is minimum needed for measure repeat sign + 0.7, "Maximum distance between two compound parts"); private final Scale.AreaFraction minWeight = new Scale.AreaFraction( diff --git a/src/main/org/audiveris/omr/sheet/symbol/SymbolsFilter.java b/src/main/org/audiveris/omr/sheet/symbol/SymbolsFilter.java index 0865a8a74..8b4b6ad77 100644 --- a/src/main/org/audiveris/omr/sheet/symbol/SymbolsFilter.java +++ b/src/main/org/audiveris/omr/sheet/symbol/SymbolsFilter.java @@ -27,8 +27,10 @@ import org.audiveris.omr.glyph.Glyph; import org.audiveris.omr.glyph.GlyphFactory; import org.audiveris.omr.glyph.GlyphGroup; +import static org.audiveris.omr.glyph.GlyphGroup.SYMBOL; import org.audiveris.omr.glyph.GlyphIndex; import org.audiveris.omr.image.ImageUtil; +import static org.audiveris.omr.image.PixelSource.FOREGROUND; import org.audiveris.omr.image.Template; import org.audiveris.omr.run.Orientation; import org.audiveris.omr.run.RunTable; @@ -123,11 +125,11 @@ public SymbolsFilter (Sheet sheet) private void buildSymbolsGlyphs (ByteProcessor buffer) { // Runs - RunTableFactory runFactory = new RunTableFactory(SYMBOL_ORIENTATION); - RunTable runTable = runFactory.createTable(buffer); + final RunTableFactory runFactory = new RunTableFactory(SYMBOL_ORIENTATION); + final RunTable runTable = runFactory.createTable(buffer); // Glyphs - List glyphs = GlyphFactory.buildGlyphs(runTable, new Point(0, 0), GlyphGroup.SYMBOL); + final List glyphs = GlyphFactory.buildGlyphs(runTable, new Point(0, 0), SYMBOL); logger.debug("Symbol glyphs: {}", glyphs.size()); // Dispatch each glyph to its relevant system(s) @@ -149,15 +151,12 @@ private void dispatchPageSymbols (List glyphs) final SystemManager systemManager = sheet.getSystemManager(); for (Glyph glyph : glyphs) { - glyph = glyphIndex.registerOriginal(glyph); - glyph.addGroup(GlyphGroup.SYMBOL); + final Glyph registeredGlyph = glyphIndex.registerOriginal(glyph); + registeredGlyph.addGroup(GlyphGroup.SYMBOL); - Point center = glyph.getCentroid(); + final Point center = registeredGlyph.getCentroid(); systemManager.getSystemsOf(center, relevants); - - for (SystemInfo system : relevants) { - system.addFreeGlyph(glyph); - } + relevants.forEach(system -> system.addFreeGlyph(registeredGlyph)); } } @@ -180,13 +179,13 @@ public void process (Map> optionalsMap) { logger.debug("SymbolsFilter running..."); - ByteProcessor rawBuf = sheet.getPicture().getSource(Picture.SourceKey.NO_STAFF); - BufferedImage img = rawBuf.getBufferedImage(); - ByteProcessor buffer = new ByteProcessor(img); + final ByteProcessor rawBuf = sheet.getPicture().getSource(Picture.SourceKey.NO_STAFF); + final BufferedImage img = rawBuf.getBufferedImage(); + final ByteProcessor buffer = new ByteProcessor(img); // Prepare the ground for symbols retrieval, noting optional (weak) glyphs per system - Graphics2D g = img.createGraphics(); - SymbolsCleaner eraser = new SymbolsCleaner(buffer, g, sheet); + final Graphics2D g = img.createGraphics(); + final SymbolsCleaner eraser = new SymbolsCleaner(buffer, g, sheet); eraser.eraseInters(optionalsMap); buffer.threshold(127); @@ -231,6 +230,14 @@ private static class Constants "letter count", 3, "Maximum number of chars for a word to be checked as a symbol"); + + private final Constant.Ratio minHeadContextualGrade = new Constant.Ratio( + 0.6, + "Minimum contextual grade to hide a head"); + + private final Constant.Ratio minStemContextualGrade = new Constant.Ratio( + 0.7, + "Minimum contextual grade to hide a stem"); } //----------------// @@ -244,7 +251,7 @@ private static class Constants * Doing so, the {@link SymbolsBuilder} will be able to try all combinations with, as well as * without, these optional weak glyphs. * - * Nota for text items: A one-char word within a one-word sentence is very suspicious and might + * NOTA for text items: A one-char word within a one-word sentence is very suspicious and might * well be a symbol. Hence, we consider it as a "weak" inter whatever its assigned grade. *

* TODO: The saving of weak inters is implemented only for glyph-based inters and for notes. @@ -254,7 +261,6 @@ private static class Constants private static class SymbolsCleaner extends PageCleaner { - /** * Current system list of weak glyphs. * Null value when processing strong inters, non-null value when processing weak ones. @@ -288,14 +294,14 @@ private static class SymbolsCleaner @Override protected boolean canHide (Inter inter) { - double ctxGrade = inter.getBestGrade(); + final double ctxGrade = inter.getBestGrade(); if (inter instanceof StemInter) { - return ctxGrade >= 0.7; // TODO a stem should be protected via its head chord? + return ctxGrade >= constants.minStemContextualGrade.getValue(); } if (inter instanceof HeadInter) { - return ctxGrade >= 0.6; // TODO + return ctxGrade >= constants.minHeadContextualGrade.getValue(); } return super.canHide(inter); @@ -344,7 +350,8 @@ public void eraseInters (Map> weaksMap) // Members are handled via their ensemble // Except for beams and words - if ((inter.getEnsemble() != null) && !(inter instanceof AbstractBeamInter) + if ((inter.getEnsemble() != null) // + && !(inter instanceof AbstractBeamInter) && !(inter instanceof WordInter)) { continue; } @@ -362,17 +369,12 @@ public void eraseInters (Map> weaksMap) } // Simply erase the strongs - for (Inter inter : strongs) { - inter.accept(this); - } + strongs.forEach(inter -> inter.accept(this)); // Save the weaks apart and erase them systemWeaks = new ArrayList<>(); weaksMap.put(system, systemWeaks); - - for (Inter inter : weaks) { - inter.accept(this); - } + weaks.forEach(inter -> inter.accept(this)); systemWeaks = null; } @@ -431,19 +433,17 @@ protected void processGlyph (Glyph glyph) private void savePixels (Rectangle box, List fores) { - ByteProcessor buf = new ByteProcessor(box.width, box.height); + final ByteProcessor buf = new ByteProcessor(box.width, box.height); ByteUtil.raz(buf); // buf.invert(); - for (Point p : fores) { - buf.set(p.x, p.y, 0); - } + fores.forEach(p -> buf.set(p.x, p.y, FOREGROUND)); // Runs - RunTableFactory factory = new RunTableFactory(SYMBOL_ORIENTATION); - RunTable runTable = factory.createTable(buf); + final RunTableFactory factory = new RunTableFactory(SYMBOL_ORIENTATION); + final RunTable runTable = factory.createTable(buf); // Glyphs - List glyphs = GlyphFactory.buildGlyphs( + final List glyphs = GlyphFactory.buildGlyphs( runTable, new Point(0, 0), GlyphGroup.SYMBOL); @@ -460,13 +460,11 @@ public void visit (HeadInter head) final Template tpl = head.getTemplate(); final Rectangle tplBox = tpl.getBounds(head.getBounds()); - // Use underlying glyph (enlarged only for strong inters) + // Use underlying glyph (dilated only for strong inters) final List fores = tpl.getForegroundPixels(tplBox, buffer, systemWeaks == null); // Erase foreground pixels - for (final Point p : fores) { - g.fillRect(tplBox.x + p.x, tplBox.y + p.y, 1, 1); - } + fores.forEach(p -> g.fillRect(tplBox.x + p.x, tplBox.y + p.y, 1, 1)); // Save foreground pixels for optional (weak) glyphs if (systemWeaks != null) { @@ -484,20 +482,15 @@ public void visit (HeadInter head) private class SymbolsView extends ImageView { - - // All optional glyphs. */ - private final Set optionals; + /** All optional glyphs. */ + private final Set optionals = new LinkedHashSet<>(); SymbolsView (BufferedImage image, Map> optionalMap) { super(image); - optionals = new LinkedHashSet<>(); - - for (List glyphs : optionalMap.values()) { - optionals.addAll(glyphs); - } + optionalMap.values().forEach(glyphs -> optionals.addAll(glyphs)); } @Override @@ -507,7 +500,7 @@ protected void renderItems (Graphics2D g) // Good inters are erased and not taken into account for symbols // Weak ones are temporarily erased and used as optional glyphs for symbols - Color oldColor = g.getColor(); + final Color oldColor = g.getColor(); g.setColor(Color.GREEN); for (Glyph glyph : optionals) { diff --git a/src/main/org/audiveris/omr/sheet/time/HeaderTimeBuilder.java b/src/main/org/audiveris/omr/sheet/time/HeaderTimeBuilder.java index 5d76c48b5..78ec27424 100644 --- a/src/main/org/audiveris/omr/sheet/time/HeaderTimeBuilder.java +++ b/src/main/org/audiveris/omr/sheet/time/HeaderTimeBuilder.java @@ -208,13 +208,13 @@ private void browseProjection () @Override public void cleanup () { - // for (TimeBuilder.TimeKind kind : TimeBuilder.TimeKind.values()) { - // TimeAdapter adapter = adapters.get(kind); - // - // if (adapter != null) { - // adapter.cleanup(); - // } - // } + for (TimeBuilder.TimeKind kind : TimeBuilder.TimeKind.values()) { + TimeAdapter adapter = adapters.get(kind); + + if (adapter != null) { + adapter.cleanup(); + } + } } //---------------// @@ -279,7 +279,6 @@ private List getParts (Rectangle rect) for (ListIterator li = parts.listIterator(); li.hasNext();) { final Glyph part = li.next(); Glyph glyph = glyphIndex.registerOriginal(part); - system.addFreeGlyph(glyph); li.set(glyph); } @@ -526,6 +525,8 @@ private void purgeParts (List parts, } } + //~ Inner Classes ------------------------------------------------------------------------------ + //-------------// // HalfAdapter // //-------------// @@ -592,8 +593,6 @@ public boolean isTooLight (int weight) } } - //~ Inner Classes ------------------------------------------------------------------------------ - //-------// // Space // //-------// diff --git a/src/main/org/audiveris/omr/sheet/ui/SheetPainter.java b/src/main/org/audiveris/omr/sheet/ui/SheetPainter.java index f97014d17..dcbb8f60e 100644 --- a/src/main/org/audiveris/omr/sheet/ui/SheetPainter.java +++ b/src/main/org/audiveris/omr/sheet/ui/SheetPainter.java @@ -1258,6 +1258,8 @@ public void visit (BarlineInter barline) g.fill(barline.getArea()); } + // No beam group + //-------// // visit // //-------// @@ -1635,6 +1637,8 @@ public void visit (TimeCustomInter inter) } } + // No time pair + //-------// // visit // //-------// @@ -1650,10 +1654,12 @@ public void visit (TimeWholeInter inter) @Override public void visit (VerticalSerifInter serif) { - // We don't display the vertical serifs + // We don't display the vertical serifs if the MultipleRestInter is present // because the MultipleRestInter already displays the left & right portions - // setColor(serif); - // g.fill(serif.getArea()); + if (serif.isAbnormal()) { + setColor(serif); + g.fill(serif.getArea()); + } } //-------// diff --git a/src/main/org/audiveris/omr/sig/inter/AbstractHorizontalInter.java b/src/main/org/audiveris/omr/sig/inter/AbstractHorizontalInter.java index bbda40b22..c15339e4a 100644 --- a/src/main/org/audiveris/omr/sig/inter/AbstractHorizontalInter.java +++ b/src/main/org/audiveris/omr/sig/inter/AbstractHorizontalInter.java @@ -33,6 +33,7 @@ import java.awt.Point; import java.awt.Rectangle; +import java.awt.geom.Area; import java.awt.geom.Line2D; import java.awt.geom.Point2D; @@ -184,11 +185,20 @@ public boolean contains (Point point) return true; } + return getArea().contains(point); + } + + //---------// + // getArea // + //---------// + @Override + public Area getArea () + { if (area == null) { computeArea(); } - return area.contains(point); + return area; } //-----------// @@ -225,11 +235,7 @@ public Rectangle getBounds () return new Rectangle(bounds = glyph.getBounds()); } - if (area == null) { - computeArea(); - } - - return new Rectangle(bounds = area.getBounds()); + return new Rectangle(bounds = getArea().getBounds()); } //-----------// diff --git a/src/main/org/audiveris/omr/sig/inter/AbstractInter.java b/src/main/org/audiveris/omr/sig/inter/AbstractInter.java index f95983351..792fb90bb 100644 --- a/src/main/org/audiveris/omr/sig/inter/AbstractInter.java +++ b/src/main/org/audiveris/omr/sig/inter/AbstractInter.java @@ -46,7 +46,6 @@ import org.audiveris.omr.sig.ui.InterEditor; import org.audiveris.omr.sig.ui.InterTracker; import org.audiveris.omr.sig.ui.UITask; -import org.audiveris.omr.step.OmrStep; import org.audiveris.omr.ui.Colors; import org.audiveris.omr.ui.symbol.FontSymbol; import org.audiveris.omr.ui.symbol.MusicFamily; @@ -1308,15 +1307,15 @@ public void remove (boolean extensive) sig.removeVertex(this); - // Make sure the underlying glyph remains accessible - if (glyph != null) { - final SystemInfo system = sig.getSystem(); - final OmrStep step = system.getSheet().getStub().getLatestStep(); - - if (step != null && step.compareTo(OmrStep.CURVES) >= 0) { - system.addFreeGlyph(glyph); - } - } + // // Make sure the underlying glyph remains accessible + // if (glyph != null) { + // final SystemInfo system = sig.getSystem(); + // final OmrStep step = system.getSheet().getStub().getLatestStep(); + // + // if (step != null && step.compareTo(OmrStep.CURVES) >= 0) { + // system.addFreeGlyph(glyph); + // } + // } } } diff --git a/src/main/org/audiveris/omr/sig/inter/AbstractInterVisitor.java b/src/main/org/audiveris/omr/sig/inter/AbstractInterVisitor.java index 35121a9cc..0e998fd9a 100644 --- a/src/main/org/audiveris/omr/sig/inter/AbstractInterVisitor.java +++ b/src/main/org/audiveris/omr/sig/inter/AbstractInterVisitor.java @@ -116,7 +116,7 @@ public void visit (EndingInter inter) @Override public void visit (GraceChordInter inter) { - visit((SmallChordInter) inter); + visit((AbstractChordInter) inter); } @Override diff --git a/src/main/org/audiveris/omr/sig/inter/Inters.java b/src/main/org/audiveris/omr/sig/inter/Inters.java index 1f590e6c4..66b84c811 100644 --- a/src/main/org/audiveris/omr/sig/inter/Inters.java +++ b/src/main/org/audiveris/omr/sig/inter/Inters.java @@ -52,8 +52,7 @@ public abstract class Inters * Comparator to put members first and ensembles last. */ public static final Comparator membersFirst = (o1, - o2) -> - { + o2) -> { if (o1 instanceof InterEnsemble interEnsemble1) { if (o2 instanceof InterEnsemble interEnsemble2) { if (interEnsemble1.getMembers().contains(o2)) { @@ -127,8 +126,7 @@ public abstract class Inters * For comparing interpretations by right abscissa. */ public static final Comparator byRightAbscissa = (i1, - i2) -> - { + i2) -> { Rectangle b1 = i1.getBounds(); Rectangle b2 = i2.getBounds(); @@ -141,8 +139,7 @@ public abstract class Inters * This comparator can thus be used for a TreeSet. */ public static final Comparator byFullAbscissa = (o1, - o2) -> - { + o2) -> { if (o1 == o2) { return 0; } @@ -174,8 +171,7 @@ public abstract class Inters * This comparator can thus be used for a TreeSet. */ public static final Comparator byFullCenterAbscissa = (o1, - o2) -> - { + o2) -> { if (o1 == o2) { return 0; } @@ -253,8 +249,7 @@ public abstract class Inters * For comparing inter instances by decreasing mean grade. */ public static final Comparator> byReverseMeanGrade = (c1, - c2) -> - { + c2) -> { return Double.compare(getMeanGrade(c2), getMeanGrade(c1)); }; @@ -262,8 +257,7 @@ public abstract class Inters * For comparing inter instances by decreasing mean contextual grade. */ public static final Comparator> byReverseMeanContextualGrade = (c1, - c2) -> - { + c2) -> { return Double.compare(getMeanBestGrade(c2), getMeanBestGrade(c1)); }; @@ -294,7 +288,7 @@ public static Double computeMeanContextualGrade (Collection col final SIGraph sig = inter.getSig(); if ((sig != null) && sig.containsVertex(inter)) { - sum += sig.computeContextualGrade(inter); + sum += inter.getBestGrade(); count++; } } @@ -590,23 +584,19 @@ public static List intersectedInters (List inters, found.add(inter); } else { switch (order) { - case BY_ABSCISSA -> - { + case BY_ABSCISSA -> { if (iBox.x > xMax) { return found; } } - case BY_ORDINATE -> - { + case BY_ORDINATE -> { if (iBox.y > yMax) { return found; } } - case NONE -> - { - } + case NONE -> {} } } } diff --git a/src/main/org/audiveris/omr/sig/inter/MultipleRestInter.java b/src/main/org/audiveris/omr/sig/inter/MultipleRestInter.java index 56c2206b8..0cfb30c3e 100644 --- a/src/main/org/audiveris/omr/sig/inter/MultipleRestInter.java +++ b/src/main/org/audiveris/omr/sig/inter/MultipleRestInter.java @@ -129,23 +129,6 @@ public void accept (InterVisitor visitor) @Override public boolean checkAbnormal () { - // boolean left = false; - // boolean right = false; - // - // for (Relation rel : sig.getRelations(this, MultipleRestSerifRelation.class)) { - // final MultipleRestSerifRelation msRel = (MultipleRestSerifRelation) rel; - // final HorizontalSide restSide = msRel.getRestSide(); - // - // if (restSide == HorizontalSide.LEFT) { - // left = true; - // } else if (restSide == HorizontalSide.RIGHT) { - // right = true; - // } - // } - // - // setAbnormal(!left || !right); - // - setAbnormal(!sig.hasRelation(this, MultipleRestCountRelation.class)); return isAbnormal(); diff --git a/src/main/org/audiveris/omr/sig/inter/SmallChordInter.java b/src/main/org/audiveris/omr/sig/inter/SmallChordInter.java index 750d61543..274f562b0 100644 --- a/src/main/org/audiveris/omr/sig/inter/SmallChordInter.java +++ b/src/main/org/audiveris/omr/sig/inter/SmallChordInter.java @@ -237,6 +237,6 @@ public Collection searchLinks (SystemInfo system) @Override public void setVoice (Voice voice) { - throw new IllegalStateException("Attempt to setVoice() on a SmallChordInter"); + throw new IllegalStateException("Attempt to setVoice() on " + this); } } diff --git a/src/main/org/audiveris/omr/sig/inter/VerticalSerifInter.java b/src/main/org/audiveris/omr/sig/inter/VerticalSerifInter.java index 85e857946..3495b0063 100644 --- a/src/main/org/audiveris/omr/sig/inter/VerticalSerifInter.java +++ b/src/main/org/audiveris/omr/sig/inter/VerticalSerifInter.java @@ -24,6 +24,7 @@ import org.audiveris.omr.glyph.Glyph; import org.audiveris.omr.glyph.Shape; import org.audiveris.omr.sig.GradeImpacts; +import org.audiveris.omr.sig.relation.MultipleRestSerifRelation; import java.awt.geom.Line2D; @@ -64,6 +65,7 @@ public VerticalSerifInter (Glyph glyph, Double width) { super(glyph, Shape.VERTICAL_SERIF, grade, median, width); + setAbnormal(true); } /** @@ -80,6 +82,7 @@ public VerticalSerifInter (Glyph glyph, Double width) { super(glyph, Shape.VERTICAL_SERIF, impacts, median, width); + setAbnormal(true); } //~ Methods ------------------------------------------------------------------------------------ @@ -92,4 +95,20 @@ public void accept (InterVisitor visitor) { visitor.visit(this); } + + //---------------// + // checkAbnormal // + //---------------// + /** + * Check if a multiple rest is connected. + * + * @return true if abnormal + */ + @Override + public boolean checkAbnormal () + { + setAbnormal(!sig.hasRelation(this, MultipleRestSerifRelation.class)); + + return isAbnormal(); + } } diff --git a/src/main/org/audiveris/omr/sig/ui/GlyphsAdditionTask.java b/src/main/org/audiveris/omr/sig/ui/GlyphsAdditionTask.java new file mode 100644 index 000000000..321b10375 --- /dev/null +++ b/src/main/org/audiveris/omr/sig/ui/GlyphsAdditionTask.java @@ -0,0 +1,91 @@ +//------------------------------------------------------------------------------------------------// +// // +// G l y p h s A d d i t i o n T a s k // +// // +//------------------------------------------------------------------------------------------------// +// +// +// Copyright © Audiveris 2023. 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 . +//------------------------------------------------------------------------------------------------// +// +package org.audiveris.omr.sig.ui; + +import org.audiveris.omr.glyph.Glyph; +import org.audiveris.omr.glyph.GlyphIndex; +import org.audiveris.omr.sheet.SystemInfo; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Class GlyphsAdditionTask adds a collection of glyphs. + * + * @author Hervé Bitteur + */ +public class GlyphsAdditionTask + extends UITask +{ + //~ Static fields/initializers ----------------------------------------------------------------- + + private static final Logger logger = LoggerFactory.getLogger(GlyphsAdditionTask.class); + + //~ Instance fields ---------------------------------------------------------------------------- + + /** The glyphs to add. */ + protected final Set glyphs = new LinkedHashSet<>(); + + //~ Constructors ------------------------------------------------------------------------------- + + /** + * Creates a new GlyphsAdditionTask object, with the glyphs to add. + * + * @param system the referent system + * @param glyphs the glyphs to add + */ + public GlyphsAdditionTask (SystemInfo system, + Collection glyphs) + { + super(system.getSig(), "addGlyphs"); + this.glyphs.addAll(glyphs); + } + + //~ Methods ------------------------------------------------------------------------------------ + + @Override + public void performDo () + { + final SystemInfo system = sig.getSystem(); + final GlyphIndex glyphIndex = sheet.getGlyphIndex(); + + glyphs.forEach(glyph -> { + if (glyph.getId() == 0) { + system.addFreeGlyph(glyphIndex.registerOriginal(glyph)); + } else { + glyphIndex.setEntities(Arrays.asList(glyph)); + } + }); + } + + @Override + public void performUndo () + { + final GlyphIndex glyphIndex = sheet.getGlyphIndex(); + glyphs.forEach(glyph -> glyphIndex.remove(glyph)); + } +} diff --git a/src/main/org/audiveris/omr/sig/ui/GlyphsRemovalTask.java b/src/main/org/audiveris/omr/sig/ui/GlyphsRemovalTask.java new file mode 100644 index 000000000..91cc865e7 --- /dev/null +++ b/src/main/org/audiveris/omr/sig/ui/GlyphsRemovalTask.java @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------------------------// +// // +// G l y p h s R e m o v a l T a s k // +// // +//------------------------------------------------------------------------------------------------// +// +// +// Copyright © Audiveris 2023. 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 . +//------------------------------------------------------------------------------------------------// +// +package org.audiveris.omr.sig.ui; + +import org.audiveris.omr.glyph.Glyph; +import org.audiveris.omr.glyph.GlyphIndex; +import org.audiveris.omr.sheet.SystemInfo; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Class GlyphsRemovalTask removes a collection of glyphs. + * + * @author Hervé Bitteur + */ +public class GlyphsRemovalTask + extends UITask +{ + //~ Static fields/initializers ----------------------------------------------------------------- + + private static final Logger logger = LoggerFactory.getLogger(GlyphsRemovalTask.class); + + //~ Instance fields ---------------------------------------------------------------------------- + + /** The glyphs to remove. */ + final Set glyphs = new LinkedHashSet<>(); + + //~ Constructors ------------------------------------------------------------------------------- + + /** + * Creates a new GlyphsRemovalTask object, with the glyphs to remove. + * + * @param system the referent system + * @param glyphs the glyphs to remove + */ + public GlyphsRemovalTask (SystemInfo system, + Collection glyphs) + { + super(system.getSig(), "delGlyphs"); + this.glyphs.addAll(glyphs); + } + + //~ Methods ------------------------------------------------------------------------------------ + + @Override + public void performDo () + { + final GlyphIndex glyphIndex = sheet.getGlyphIndex(); + glyphs.forEach(glyph -> glyphIndex.remove(glyph)); + } + + @Override + public void performUndo () + { + sheet.getGlyphIndex().setEntities(glyphs); + } +} diff --git a/src/main/org/audiveris/omr/sig/ui/InterController.java b/src/main/org/audiveris/omr/sig/ui/InterController.java index 65bcd08b3..c780bd341 100644 --- a/src/main/org/audiveris/omr/sig/ui/InterController.java +++ b/src/main/org/audiveris/omr/sig/ui/InterController.java @@ -26,15 +26,25 @@ import org.audiveris.omr.constant.ConstantSet; import org.audiveris.omr.glyph.Glyph; import org.audiveris.omr.glyph.GlyphFactory; +import org.audiveris.omr.glyph.Glyphs; import org.audiveris.omr.glyph.Shape; import org.audiveris.omr.glyph.ui.NestView; +import static org.audiveris.omr.image.PixelSource.BACKGROUND; +import static org.audiveris.omr.image.PixelSource.FOREGROUND; +import org.audiveris.omr.image.Template; import org.audiveris.omr.math.AreaUtil; import org.audiveris.omr.math.LineUtil; import org.audiveris.omr.math.PointUtil; +import static org.audiveris.omr.run.Orientation.VERTICAL; +import org.audiveris.omr.run.RunTable; +import org.audiveris.omr.run.RunTableFactory; import org.audiveris.omr.score.Page; import org.audiveris.omr.score.TimeRational; +import org.audiveris.omr.sheet.PageCleaner; +import org.audiveris.omr.sheet.Picture; import org.audiveris.omr.sheet.Sheet; import org.audiveris.omr.sheet.Staff; +import org.audiveris.omr.sheet.StaffLine; import org.audiveris.omr.sheet.SystemInfo; import org.audiveris.omr.sheet.rhythm.MeasureStack; import org.audiveris.omr.sheet.symbol.InterFactory; @@ -87,12 +97,18 @@ import org.audiveris.omr.text.TextLine; import org.audiveris.omr.text.TextRole; import org.audiveris.omr.text.TextWord; +import org.audiveris.omr.ui.selection.LocationEvent; +import static org.audiveris.omr.ui.selection.MouseMovement.PRESSING; import org.audiveris.omr.ui.selection.SelectionHint; +import static org.audiveris.omr.ui.selection.SelectionHint.LOCATION_INIT; +import org.audiveris.omr.ui.selection.SelectionService; import org.audiveris.omr.ui.util.UIThread; +import org.audiveris.omr.util.ByteUtil; import org.audiveris.omr.util.Entities; import org.audiveris.omr.util.HorizontalSide; import static org.audiveris.omr.util.HorizontalSide.LEFT; import static org.audiveris.omr.util.HorizontalSide.RIGHT; +import org.audiveris.omr.util.StopWatch; import org.audiveris.omr.util.VoidTask; import org.audiveris.omr.util.WrappedBoolean; import org.audiveris.omr.util.Wrapper; @@ -100,23 +116,32 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ij.process.Blitter; import ij.process.ByteProcessor; +import java.awt.Color; +import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.geom.Area; import java.awt.geom.Line2D; import java.awt.geom.Point2D; +import java.awt.image.BufferedImage; +import static java.awt.image.BufferedImage.TYPE_BYTE_GRAY; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; +import java.util.ListIterator; import java.util.Set; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; import javax.swing.AbstractAction; import javax.swing.InputMap; @@ -1363,6 +1388,185 @@ private List> partitionHeads (HeadChordInter chord) return lists; } + //---------------// + // rebuildGlyphs // + //---------------// + /** + * After inters removal, we'll have to rebuild the glyphs landscape around these inters + * as if the inters had not existed and broken up the regions of foreground pixels. + *

+ * When inters are removed, their underlying glyphs (together with the dilation ring applied for + * heads) should return to the glyph landscape from which the user could pick up separated + * glyph(s) for a manual assignment. + *

+ * Strategy: + *

    + *
  1. Work on the bounding box of the removed inters (box augmented with dilation value). + *
  2. Retrieve all the foreground pixels previously occupied by each removed inter and its + * dilation ring if any, but not by any (non-removed) neighbor. + *
  3. From this whole buffer, extract separated glyphs. + *
+ * And only once the REDUCTION step has been run: + *
    + *
  1. For each of the extracted glyphs, look for adjacent existing glyphs (free of inter). + *
  2. Merge each extracted glyph with the touching existing glyph(s). + *
+ *

+ * This method does not really remove or create glyphs, but it builds the corresponding + * removal and addition tasks in the UI task list. + * + * @param seq (input/output) the UI task list + */ + private void rebuildGlyphs (UITaskList seq) + { + // All inters to be removed (directly or indirectly) + final List intersToRemove = seq.getInters(); + + final StopWatch watch = new StopWatch("rebuildGlyphs"); + final int dilation = sheet.getScale().toPixels(Template.dilation()); + final Rectangle scene = Inters.getBounds(intersToRemove); + scene.grow(dilation, dilation); + final Point sceneLoc = scene.getLocation(); + + // Region of no-staff for scene + watch.start("Extracting scene noStaff"); + final ByteProcessor sheetBuf = sheet.getPicture().getSource(Picture.SourceKey.NO_STAFF); + final ByteProcessor noStaff = new ByteProcessor(scene.width, scene.height); + noStaff.copyBits(sheetBuf, -scene.x, -scene.y, Blitter.COPY); + + // Impacted neighbors in scene + watch.start("Retrieving neighbors"); + final List systems = sheet.getSystems().stream() // + .filter(s -> s.getBounds().intersects(scene)).collect(toList()); + final Set neighbors = systems.stream().flatMap( + system -> system.getSig().vertexSet().stream()).filter( + inter -> inter.getGlyph() != null // + && !inter.isRemoved() // + && !intersToRemove.contains(inter) // + && inter.getBounds().intersects(scene)).collect(toSet()); + + // Retrieving pixels from the inters to be removed (and from their dilation ring) + watch.start("Retrieving revealed pixels"); + final BufferedImage itemsImg = new BufferedImage(scene.width, scene.height, TYPE_BYTE_GRAY); + final ByteProcessor items = new ByteProcessor(itemsImg); + ByteUtil.fill(items, BACKGROUND); + final Graphics2D g = itemsImg.createGraphics(); + new PixelsRetriever(noStaff, items, sceneLoc, g, sheet, neighbors).processInters( + intersToRemove); + + // To avoid GC to discard our old/new glyphs, we simply pick up the first system involved + // to host them in its collection of free glyphs. + final SystemInfo system = intersToRemove.get(0).getSig().getSystem(); + + // Deleting glyphs from removed inters + watch.start("Retrieving old glyphs"); + final Set oldGlyphs = intersToRemove.stream().map(inter -> inter.getGlyph()) // + .filter(gl -> gl != null).collect(toSet()); + + // Picking up from items foreground only those pixels that are also foreground in noStaff + watch.start("Picking up pixels"); + final ByteProcessor finalBuf = new ByteProcessor(scene.width, scene.height); + ByteUtil.fill(finalBuf, BACKGROUND); + for (int idx = scene.height * scene.width - 1; idx >= 0; idx--) { + if ((items.get(idx) == FOREGROUND) && (noStaff.get(idx) == FOREGROUND)) { + finalBuf.set(idx, FOREGROUND); + } + } + + // Building new glyphs + watch.start("Building new glyphs"); + final RunTable runTable = new RunTableFactory(VERTICAL).createTable(finalBuf); + final List newGlyphs = GlyphFactory.buildGlyphs(runTable, scene.getLocation()); + if (newGlyphs.isEmpty()) { + logger.info("Inter deletion with no new glyphs!"); + } + + // Merging is really interesting only after the REDUCTION step + if (sheet.getStub().getLatestStep().compareTo(OmrStep.REDUCTION) >= 0) { + // Retrieving candidate scene glyphs (non-removed, non inter-assigned, non staff lines) + watch.start("Retrieving scene glyphs"); + final List sceneGlyphs = sheet.getGlyphIndex().getIntersectedEntities(scene); + sceneGlyphs.removeAll(oldGlyphs); + neighbors.forEach(inter -> sceneGlyphs.remove(inter.getGlyph())); + systems.forEach( + s -> s.getStaves().forEach( + staff -> staff.getLines().forEach( + l -> sceneGlyphs.remove(((StaffLine) l).getGlyph())))); + + // Merging the new glyphs with the adjacent scene glyphs + watch.start("Reducing glyphs"); + oldGlyphs.addAll(reduceGlyphs(sceneGlyphs, newGlyphs)); + } + + // Tasks added to UI task list + seq.add(new GlyphsRemovalTask(system, oldGlyphs)); + seq.add(new GlyphsAdditionTask(system, newGlyphs)); + + if (constants.printWatch.isSet()) { + watch.print(); + } + } + + //--------------// + // reduceGlyphs // + //--------------// + /** + * Merge the provided new glyphs with the candidate scene glyphs where they touch to + * reduce the number of glyph parts. + * + * @param sceneGlyphs the candidate scene glyphs + * @param newGlyphs the new glyphs + * @return the scene glyphs to be removed + */ + private Set reduceGlyphs (List sceneGlyphs, + List newGlyphs) + { + final Set parts = new LinkedHashSet<>(); + final Set toRemove = new LinkedHashSet<>(); + + // Merging new glyphs with scene glyphs? + for (ListIterator it = newGlyphs.listIterator(); it.hasNext();) { + final Glyph g1 = it.next(); + parts.clear(); + + for (Iterator it2 = sceneGlyphs.iterator(); it2.hasNext();) { + final Glyph g2 = it2.next(); + if (Glyphs.intersect(g1, g2, true)) { + parts.add(g2); + toRemove.add(g2); + it2.remove(); + } + } + + if (!parts.isEmpty()) { + parts.add(g1); + it.set(GlyphFactory.buildGlyph(parts)); + } + } + + // Merging the new glyphs themselves? + for (int i = 0; i < newGlyphs.size(); i++) { + final Glyph g1 = newGlyphs.get(i); + parts.clear(); + + for (int j = i + 1; j < newGlyphs.size(); j++) { + final Glyph g2 = newGlyphs.get(j); + + if (Glyphs.intersect(g1, g2, true)) { + parts.add(g2); + newGlyphs.remove(j--); + } + } + + if (!parts.isEmpty()) { + parts.add(g1); + newGlyphs.set(i, GlyphFactory.buildGlyph(parts)); + } + } + + return toRemove; + } + //------// // redo // //------// @@ -1535,7 +1739,14 @@ public void removeInters (final List inters, @Override protected void build () { + // The inters to be removed (directly or indirectly) new RemovalScenario().populate(inters, seq); + + if (!seq.isCancelled()) { + // Handle the glyphs to be removed with inters + // Handle the glyphs to be added/rebuilt once inters are removed + rebuildGlyphs(seq); + } } @Override @@ -1544,11 +1755,20 @@ protected void publish () sheet.getInterIndex().publish(null); // Make sure an on-going editing is not impacted - Inter edited = sheet.getSheetEditor().getEditedInter(); + final Inter edited = sheet.getSheetEditor().getEditedInter(); if (edited != null && inters.contains(edited)) { sheet.getSheetEditor().closeEditMode(); } + + // Re-publish a proper location event to update the dependent artifacts + final SelectionService locService = sheet.getLocationService(); + final LocationEvent event = (LocationEvent) locService.getLastEvent( + LocationEvent.class); + if (event != null) { + locService.publish( + new LocationEvent(this, LOCATION_INIT, PRESSING, event.getData())); + } } }.execute(); } @@ -1766,7 +1986,7 @@ protected void build () public void undo () { // If an object is being edited, "undo" simply cancels the ongoing editing - ObjectEditor objectEditor = sheet.getSheetEditor().getObjectEditor(); + final ObjectEditor objectEditor = sheet.getSheetEditor().getObjectEditor(); if (objectEditor != null) { objectEditor.undo(); @@ -1832,6 +2052,9 @@ protected void publish () private static class Constants extends ConstantSet { + private final Constant.Boolean printWatch = new Constant.Boolean( + false, + "Should we print out the stop watch?"); private final Constant.Boolean useStaffLink = new Constant.Boolean( true, @@ -1855,14 +2078,17 @@ private static class Constants private abstract class CtrlTask extends VoidTask { + /** Kind of operation to be performed (DO/UNDO/REDO). */ + protected final OpKind opKind; - protected final OpKind opKind; // Kind of operation to be performed (DO/UNDO/REDO) - - protected final String opName; // Descriptive name of user action + /** Descriptive name of user action. */ + protected final String opName; - protected UITaskList seq = new UITaskList(); // Atomic sequence of tasks + /** Atomic sequence of tasks. */ + protected UITaskList seq = new UITaskList(); - protected Wrapper toPublish = new Wrapper<>(null); // Inter to publish + /** Inter to publish. */ + protected Wrapper toPublish = new Wrapper<>(null); public CtrlTask (OpKind opKind, String opName, @@ -1987,6 +2213,103 @@ protected void succeeded (Void result) } } + //-----------------// + // PixelsRetriever // + //-----------------// + /** + * This is a specific PageCleaner, to gather the foreground pixels related to some + * inters (perhaps dilated) while preserving the pixels of the neighbors. + */ + private static class PixelsRetriever + extends PageCleaner + { + /** The source buffer. */ + protected final ByteProcessor source; + + /** Buffer offset with respect to sheet. */ + protected final Point bufferOffset; + + /** The neighbors to protect. */ + protected final Collection neighbors; + + /** + * Creates a retriever operating on provided inters to be removed, preserving neighbors. + * + * @param source (input) the buffer to read, perhaps smaller than the sheet + * @param target (output) the buffer to write, perhaps smaller than the sheet + * @param bufferOffset origin of buffer, relative to sheet origin + * @param g the graphics context on buffer. + * 'g' is to be used with absolute (sheet-based) coordinates + * @param sheet the containing sheet + * @param neighbors the relevant neighbors + */ + public PixelsRetriever (ByteProcessor source, + ByteProcessor target, + Point bufferOffset, + Graphics2D g, + Sheet sheet, + Collection neighbors) + { + super(target, g, sheet); + this.source = source; + this.bufferOffset = new Point(bufferOffset); + this.neighbors = neighbors; + + g.translate(-bufferOffset.x, -bufferOffset.y); + g.setColor(Color.BLACK); + } + + /** + * Process the provided (to be removed) inters while respecting the neighbors. + * + * @param intersToRemove the provided inters meant to be removed + */ + public void processInters (List intersToRemove) + { + intersToRemove.forEach(inter -> inter.accept(this)); + } + + /** + * Specific painting of any note head, which must preserve the neighbors. + * + * @param head the note head to paint (head and dilation ring) + */ + @Override + public void visit (HeadInter head) + { + final Template tpl = head.getTemplate(); + final Rectangle tplBox = tpl.getBounds(head.getBounds()); + tplBox.translate(-bufferOffset.x, -bufferOffset.y); + + // Use underlying glyph (dilated) + final List fores = tpl.getForegroundPixels(tplBox, source, true); + + // Paint foreground pixels + // (Only those which don't overlap neighbors) + ForesLoop: + for (final Point p : fores) { + // Graphics expect sheet absolute coordinates + final Point absPt = new Point( + bufferOffset.x + tplBox.x + p.x, + bufferOffset.y + tplBox.y + p.y); + + for (Inter neighbor : neighbors) { + if (neighbor.getGlyph().contains(absPt)) { + continue ForesLoop; + } + } + + g.fillRect(absPt.x, absPt.y, 1, 1); + } + } + + @Override + public void visit (StemInter inter) + { + processGlyph(inter.getGlyph()); + } + } + //--------------// // RemoveAction // //--------------// diff --git a/src/main/org/audiveris/omr/sig/ui/RemovalScenario.java b/src/main/org/audiveris/omr/sig/ui/RemovalScenario.java index f8f53ade6..37171a42b 100644 --- a/src/main/org/audiveris/omr/sig/ui/RemovalScenario.java +++ b/src/main/org/audiveris/omr/sig/ui/RemovalScenario.java @@ -41,7 +41,7 @@ import java.util.Set; /** - * Class RemovalScenario defines order of inter removals. + * Class RemovalScenario defines the order of inter removals. * * @author Hervé Bitteur */ @@ -54,13 +54,13 @@ public class RemovalScenario //~ Instance fields ---------------------------------------------------------------------------- /** Non-ensemble inters to be removed. */ - LinkedHashSet inters = new LinkedHashSet<>(); + final LinkedHashSet inters = new LinkedHashSet<>(); /** Ensemble inters to be removed. */ - LinkedHashSet ensembles = new LinkedHashSet<>(); + final LinkedHashSet ensembles = new LinkedHashSet<>(); /** Ensemble inters to be watched for potential removal. */ - LinkedHashSet watched = new LinkedHashSet<>(); + final LinkedHashSet watched = new LinkedHashSet<>(); //~ Constructors ------------------------------------------------------------------------------- @@ -78,9 +78,8 @@ public RemovalScenario () //---------// private void include (Inter inter) { - if (inter instanceof InterEnsemble) { + if (inter instanceof InterEnsemble ens) { // Include the ensemble and its members - final InterEnsemble ens = (InterEnsemble) inter; final List members = ens.getMembers(); if (members.isEmpty()) { @@ -91,6 +90,7 @@ private void include (Inter inter) } } else { inters.add(inter); + // Watch the containing ensemble (if not already to be removed) final SIGraph sig = inter.getSig(); @@ -128,18 +128,16 @@ public void populate (Collection inters, } // Removals: this inter plus related inters to remove as well - WrappedBoolean cancel = seq.isOptionSet(Option.VALIDATED) ? null + final WrappedBoolean cancel = seq.isOptionSet(Option.VALIDATED) ? null : new WrappedBoolean(false); - Set toRemove = inter.preRemove(cancel); + final Set toRemove = inter.preRemove(cancel); if ((cancel != null) && cancel.isSet()) { seq.setCancelled(true); return; } - for (Inter item : toRemove) { - include(item); - } + toRemove.forEach(item -> include(item)); } // Now set the removal tasks @@ -158,7 +156,7 @@ private void populateTaskList (UITaskList seq) { // Examine watched ensembles for (InterEnsemble ens : watched) { - List members = new ArrayList<>(ens.getMembers()); + final List members = new ArrayList<>(ens.getMembers()); members.removeAll(inters); if (members.isEmpty()) { @@ -167,18 +165,13 @@ private void populateTaskList (UITaskList seq) } // Ensembles to remove first - List sortedEnsembles = new ArrayList<>(ensembles); + final List sortedEnsembles = new ArrayList<>(ensembles); Collections.sort(sortedEnsembles, Inters.membersFirst); Collections.reverse(sortedEnsembles); - - for (InterEnsemble ens : sortedEnsembles) { - seq.add(new RemovalTask(ens)); - } + sortedEnsembles.forEach(ens -> seq.add(new RemovalTask(ens))); // Simple inters to remove second - for (Inter inter : inters) { - seq.add(new RemovalTask(inter)); - } + inters.forEach(inter -> seq.add(new RemovalTask(inter))); } //----------// @@ -187,10 +180,9 @@ private void populateTaskList (UITaskList seq) @Override public String toString () { - StringBuilder sb = new StringBuilder("RemovalScenario{"); - sb.append("ensembles:").append(ensembles); - sb.append(" inters:").append(inters); - sb.append("}"); - return sb.toString(); + return new StringBuilder("RemovalScenario{") // + .append("ensembles:").append(ensembles) // + .append(" inters:").append(inters) // + .append("}").toString(); } } diff --git a/src/main/org/audiveris/omr/sig/ui/UITaskList.java b/src/main/org/audiveris/omr/sig/ui/UITaskList.java index 2bcf0b240..e22762db3 100644 --- a/src/main/org/audiveris/omr/sig/ui/UITaskList.java +++ b/src/main/org/audiveris/omr/sig/ui/UITaskList.java @@ -116,11 +116,11 @@ public void addAll (List tasks) */ public List getInters (Class... classes) { - List found = new ArrayList<>(); + final List found = new ArrayList<>(); for (UITask task : list) { - if (task instanceof InterTask) { - final Inter inter = ((InterTask) task).getInter(); + if (task instanceof InterTask interTask) { + final Inter inter = interTask.getInter(); if (classes.length == 0) { found.add(inter); @@ -150,11 +150,11 @@ public List getInters (Class... classes) */ public List getRelations (Class... classes) { - List found = new ArrayList<>(); + final List found = new ArrayList<>(); for (UITask task : list) { - if (task instanceof RelationTask) { - final Relation relation = ((RelationTask) task).getRelation(); + if (task instanceof RelationTask relationTask) { + final Relation relation = relationTask.getRelation(); if (classes.length == 0) { found.add(relation); diff --git a/src/main/org/audiveris/omr/step/ReductionStep.java b/src/main/org/audiveris/omr/step/ReductionStep.java index 022f89e31..954cc54f4 100644 --- a/src/main/org/audiveris/omr/step/ReductionStep.java +++ b/src/main/org/audiveris/omr/step/ReductionStep.java @@ -23,8 +23,12 @@ import org.audiveris.omr.constant.Constant; import org.audiveris.omr.constant.ConstantSet; +import org.audiveris.omr.glyph.Glyph; +import org.audiveris.omr.glyph.GlyphIndex; import org.audiveris.omr.sheet.Sheet; +import org.audiveris.omr.sheet.StaffLine; import org.audiveris.omr.sheet.SystemInfo; +import org.audiveris.omr.sig.SIGraph; import org.audiveris.omr.sig.SigReducer; import org.audiveris.omr.sig.inter.Inter; import org.audiveris.omr.sig.inter.StemInter; @@ -34,7 +38,9 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; /** * Class ReductionStep implements REDUCTION step, which tries to reduce @@ -95,6 +101,34 @@ protected void doEpilog (Sheet sheet, medianValue, String.format("%.1f", medianFraction)); } + + // Clean up glyphs + final Set toKeep = new LinkedHashSet<>(); + sheet.getSystems().forEach(system -> { + // Staves lines glyphs + system.getStaves().forEach(staff -> staff.getLines().forEach(line -> { + final Glyph glyph = ((StaffLine) line).getGlyph(); + if (glyph != null) { + toKeep.add(glyph); + } + })); + + // SIG inters + final SIGraph sig = system.getSig(); + sig.vertexSet().forEach(inter -> { + final Glyph glyph = inter.getGlyph(); + if (glyph != null) { + toKeep.add(glyph); + } + }); + }); + + final GlyphIndex glyphIndex = sheet.getGlyphIndex(); + glyphIndex.getEntities().forEach(glyph -> { + if (!toKeep.contains(glyph)) { + glyphIndex.remove(glyph); + } + }); } //----------// diff --git a/src/main/org/audiveris/omr/text/TextBuilder.java b/src/main/org/audiveris/omr/text/TextBuilder.java index 3765b8866..f2f110e66 100644 --- a/src/main/org/audiveris/omr/text/TextBuilder.java +++ b/src/main/org/audiveris/omr/text/TextBuilder.java @@ -1184,8 +1184,7 @@ private List recomposeLines (Collection longLines) // retrieveSections // //------------------// /** - * Report the set of sections that relate to the provided collection of TextChar - * instances. + * Report the set of sections that relate to the provided collection of TextChar instances. * * @param chars the OCR char descriptors * @param allSections the candidate sections diff --git a/src/main/org/audiveris/omr/ui/selection/SelectionHint.java b/src/main/org/audiveris/omr/ui/selection/SelectionHint.java index e3839ce87..293d26cbe 100644 --- a/src/main/org/audiveris/omr/ui/selection/SelectionHint.java +++ b/src/main/org/audiveris/omr/ui/selection/SelectionHint.java @@ -78,13 +78,10 @@ public enum SelectionHint */ public boolean isContext () { - switch (this) { - case CONTEXT_INIT: - case CONTEXT_ADD: - return true; - } - - return false; + return switch (this) { + case CONTEXT_INIT, CONTEXT_ADD -> true; + default -> false; + }; } //------------// @@ -97,12 +94,9 @@ public boolean isContext () */ public boolean isLocation () { - switch (this) { - case LOCATION_INIT: - case LOCATION_ADD: - return true; - } - - return false; + return switch (this) { + case LOCATION_INIT, LOCATION_ADD -> true; + default -> false; + }; } } diff --git a/src/main/org/audiveris/omr/ui/symbol/MusicFont.java b/src/main/org/audiveris/omr/ui/symbol/MusicFont.java index 5880061f5..dbb529d6a 100644 --- a/src/main/org/audiveris/omr/ui/symbol/MusicFont.java +++ b/src/main/org/audiveris/omr/ui/symbol/MusicFont.java @@ -585,23 +585,14 @@ public static MusicFont getHeadFont (MusicFamily family, * @return proper point size for font */ public static int getHeadPointSize (Scale scale, - int staffInterline) + double staffInterline) { final Scale.MusicFontScale musicFontScale = scale.getMusicFontScale(); - final Integer smallInterline = scale.getSmallInterline(); - final boolean isSmallStaff = (smallInterline != null) && (smallInterline == staffInterline); if (musicFontScale != null) { - if (isSmallStaff) { - // Interpolate from large staff information - final double smallRatio = (double) staffInterline / scale.getInterline(); - - return (int) Math.rint(smallRatio * musicFontScale.getPointSize()); - } else { - // Precise large information - logger.debug("MusicFont. Using {}", musicFontScale); - return musicFontScale.getPointSize(); - } + // Interpolate from large staff information + final double ratio = (double) staffInterline / scale.getInterline(); + return (int) Math.rint(ratio * musicFontScale.getPointSize()); } else { // No precise information available, fall back using head ratio constant... logger.debug("MusicFont. Using head ratio constant"); diff --git a/src/main/org/audiveris/omr/util/ByteUtil.java b/src/main/org/audiveris/omr/util/ByteUtil.java index 2a6af7ebe..306381da4 100644 --- a/src/main/org/audiveris/omr/util/ByteUtil.java +++ b/src/main/org/audiveris/omr/util/ByteUtil.java @@ -21,6 +21,8 @@ // package org.audiveris.omr.util; +import static org.audiveris.omr.image.PixelSource.BACKGROUND; + import ij.process.ByteProcessor; import java.util.Arrays; @@ -60,6 +62,6 @@ public static void fill (ByteProcessor bp, */ public static void raz (ByteProcessor bp) { - fill(bp, 255); + fill(bp, BACKGROUND); } } diff --git a/src/main/org/audiveris/omr/util/Entities.java b/src/main/org/audiveris/omr/util/Entities.java index faebf440f..3cb8cc3d1 100644 --- a/src/main/org/audiveris/omr/util/Entities.java +++ b/src/main/org/audiveris/omr/util/Entities.java @@ -29,6 +29,7 @@ import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.stream.Collectors; /** * Class Entities provides utility methods for entities. @@ -47,8 +48,7 @@ public class Entities * For comparing entities by center abscissa. */ public static final Comparator byCenterAbscissa = (e1, - e2) -> - { + e2) -> { final Rectangle b1 = e1.getBounds(); final Rectangle b2 = e2.getBounds(); @@ -220,19 +220,14 @@ public static String ids (String label, Collection entities) { if (entities == null) { - return ""; - } - - StringBuilder sb = new StringBuilder(); - sb.append(label).append("["); - - for (Entity entity : entities) { - sb.append("#").append(entity.getFullId()); + return "null"; } - sb.append("]"); - - return sb.toString(); + return new StringBuilder() // + .append(label) // + .append("[") // + .append(entities.stream().map(e -> e.getFullId()).collect(Collectors.joining(", "))) + .append("]").toString(); } //---------------------//