//----------------------------------------------------------------------------//
// //
// S p l i t P a t t e r n //
// //
//----------------------------------------------------------------------------//
// <editor-fold defaultstate="collapsed" desc="hdr"> //
// Copyright (C) Hervé Bitteur 2000-2010. All rights reserved. //
// This software is released under the GNU General Public License. //
// Goto http://kenai.com/projects/audiveris to report bugs or suggestions. //
//----------------------------------------------------------------------------//
// </editor-fold>
package omr.glyph.pattern;
import omr.constant.ConstantSet;
import omr.glyph.Evaluation;
import omr.glyph.GlyphNetwork;
import omr.glyph.GlyphSignature;
import omr.glyph.Grades;
import omr.glyph.Shape;
import omr.glyph.facets.BasicGlyph;
import omr.glyph.facets.Glyph;
import omr.glyph.facets.GlyphComposition.Linking;
import omr.lag.Section;
import omr.sheet.Scale;
import omr.sheet.SystemInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.Map.Entry;
/**
* Class {@code SplitPattern} tries to split large unknown glyphs into
* two valid chunks.
*
* @author Hervé Bitteur
*/
public class SplitPattern
extends GlyphPattern
{
//~ Static fields/initializers ---------------------------------------------
/** Specific application parameters */
private static final Constants constants = new Constants();
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(SplitPattern.class);
/** Set of shapes not accepted for glyph chunks. TODO: expand the set */
private static final EnumSet<Shape> invalidShapes = EnumSet.of(
Shape.CLUTTER,
Shape.NOISE);
//~ Instance fields --------------------------------------------------------
// Scale-dependent parameters
private final double minGlyphWeight;
private final double minChunkWeight;
//~ Constructors -----------------------------------------------------------
//--------------//
// SplitPattern //
//--------------//
/**
* Creates a new SplitPattern object.
*
* @param system the dedicated system
*/
public SplitPattern (SystemInfo system)
{
super("Split", system);
minGlyphWeight = scale.toPixels(constants.minGlyphWeight);
minChunkWeight = scale.toPixels(constants.minChunkWeight);
}
//~ Methods ----------------------------------------------------------------
//------------//
// runPattern //
//------------//
/**
* In the related system, look for heavy unknown glyphs which might
* be composed of several glyphs, each with a valid shape.
*
* @return the number of glyphs actually split
*/
@Override
public int runPattern ()
{
int nb = 0;
for (Glyph glyph : system.getGlyphs()) {
if (!glyph.isActive()
|| glyph.isKnown()
|| (glyph.getStemNumber() == 0)
|| (glyph.getWeight() < minGlyphWeight)) {
continue;
}
if (splitGlyph(glyph)) {
nb++;
}
}
return nb;
}
//--------//
// expand //
//--------//
/**
* Try to expand the provided glyph with the provided section.
*
* @param glyph to glyph to expand
* @param section the section to cehck for expandion pf the glyph
* @param master the master glyph which contains the section candidates
*/
private void expand (Glyph glyph,
Section section,
Glyph master)
{
// Check whether this section is suitable to expand the glyph
if (section.isProcessed() || (section.getGlyph() != master)) {
return;
}
section.setProcessed(true);
glyph.addSection(section, Glyph.Linking.NO_LINK_BACK);
// Check recursively all sections linked to this one...
// Incoming ones
for (Section source : section.getSources()) {
expand(glyph, source, master);
}
// Outgoing ones
for (Section target : section.getTargets()) {
expand(glyph, target, master);
}
// Sections from other orientation
for (Section other : section.getOppositeSections()) {
expand(glyph, other, master);
}
}
//------------//
// splitGlyph //
//------------//
/**
* Try to split the provided master glyph into two valid glyph
* chunks.
*
* @param master the master glyph to split
* @return true if successful
*/
private boolean splitGlyph (Glyph master)
{
if (master.isVip()) {
logger.info("Trying to split G#{}", master.getId());
}
List<Split> splits = new ArrayList<>();
// Retrieve all binary splits of this glyph
for (Section seed : master.getMembers()) {
for (Section s : master.getMembers()) {
s.setProcessed(false);
}
seed.setProcessed(true); // To not use this one
Split split = new Split(master, seed);
List<Section> others = new ArrayList<>();
others.addAll(seed.getSources());
others.addAll(seed.getTargets());
others.addAll(seed.getOppositeSections());
for (Section s : others) {
if ((s.getGlyph() == master) && !s.isProcessed()) {
Glyph g = new BasicGlyph(scale.getInterline());
expand(g, s, master);
split.sigs.put(g.getSignature(), g);
}
}
// Check if we have exactly two significant chunks
// (neglecting very small others)
int count = split.sigs.size();
for (GlyphSignature sig : split.sigs.keySet()) {
if (sig.getWeight() < minChunkWeight) {
count--;
}
}
if (count == 2) {
if (master.isVip()) {
logger.info("Split candidate: {}", split);
}
splits.add(split);
}
}
if (splits.isEmpty()) {
return false;
}
// Pickup the more weightwise balanced split
Collections.sort(splits);
Split bestSplit = splits.get(0);
bestSplit.register(system);
if (master.isVip() || logger.isDebugEnabled()) {
logger.info("Checking {}", bestSplit);
}
// Check whether each of the chunks can be assigned a valid shape
for (Glyph chunk : bestSplit.sigs.values()) {
if (chunk.getWeight() < minChunkWeight) {
continue;
}
system.computeGlyphFeatures(chunk);
Evaluation vote = GlyphNetwork.getInstance().vote(
chunk,
system,
Grades.partMinGrade);
if ((vote == null) || invalidShapes.contains(vote.shape)) {
if (master.isVip() || logger.isDebugEnabled()) {
logger.info("No valid shape for chunk {}", chunk);
}
} else {
if (master.isVip() || logger.isDebugEnabled()) {
logger.info("{} for chunk {}", vote, chunk);
}
chunk.setEvaluation(vote);
}
}
// Now actually perform the split!
if (master.isVip() || logger.isDebugEnabled()) {
logger.info("{}Performing {}", system.getLogPrefix(), bestSplit);
}
for (Glyph glyph : bestSplit.sigs.values()) {
system.addGlyph(glyph);
}
return true;
}
//~ Inner Classes ----------------------------------------------------------
//-----------//
// Constants //
//-----------//
private static final class Constants
extends ConstantSet
{
//~ Instance fields ----------------------------------------------------
Scale.AreaFraction minGlyphWeight = new Scale.AreaFraction(
1.0,
"Minimum normalized glyph weight to look for split");
//
Scale.AreaFraction minChunkWeight = new Scale.AreaFraction(
0.025,
"Minimum normalized weight of a chunk to be part of a split");
}
//-------//
// Split //
//-------//
/**
* Records information about a potential split of a master glyph.
*/
private static class Split
implements Comparable<Split>
{
//~ Instance fields ----------------------------------------------------
// Master glyph
private final Glyph master;
// The section used for the split
private final Section seed;
// The resulting glyph chunks, kept sorted by (increasing) weight
private final SortedMap<GlyphSignature, Glyph> sigs = new TreeMap<>();
//~ Constructors -------------------------------------------------------
/**
* Create a split information.
*
* @param master the master glyph to be split
* @param seed the section where split would be performed
*/
public Split (Glyph master,
Section seed)
{
this.master = master;
this.seed = seed;
}
//~ Methods ------------------------------------------------------------
@Override
public int compareTo (Split that)
{
// Bigger first!
return Integer.signum(
that.getLowerWeight() - this.getLowerWeight());
}
public void register (SystemInfo system)
{
// Include section seed into the second largest chunk
Entry<GlyphSignature, Glyph> lowEntry = getSecondLargest();
Glyph smallerGlyph = lowEntry.getValue();
sigs.remove(lowEntry.getKey());
smallerGlyph.addSection(seed, Linking.NO_LINK_BACK);
sigs.put(smallerGlyph.getSignature(), smallerGlyph);
// Register the chunks (copy needed to avoid concurrent modifs)
Set<Entry<GlyphSignature, Glyph>> entries = new HashSet<>(
sigs.entrySet());
for (Entry<GlyphSignature, Glyph> entry : entries) {
Glyph value = entry.getValue();
Glyph glyph = system.registerGlyph(value);
if (glyph != value) {
GlyphSignature key = entry.getKey();
sigs.remove(key);
sigs.put(key, glyph);
}
}
}
@Override
public String toString ()
{
StringBuilder sb = new StringBuilder("{SplitOf#");
sb.append(master.getId());
sb.append(" @S").append(seed.isVertical() ? "V" : "H").append(seed.
getId());
for (Entry<GlyphSignature, Glyph> entry : sigs.entrySet()) {
Glyph glyph = entry.getValue();
sb.append(" #").append(glyph.getId());
Evaluation eval = glyph.getEvaluation();
if (eval != null) {
sb.append(":").append(eval);
}
}
sb.append("}");
return sb.toString();
}
private int getLowerWeight ()
{
return getSecondLargest().getKey().getWeight();
}
private Entry<GlyphSignature, Glyph> getSecondLargest ()
{
// The map entries are sorted from small to large key
Entry<GlyphSignature, Glyph> prevEntry = null;
GlyphSignature lastKey = sigs.lastKey();
for (Entry<GlyphSignature, Glyph> entry : sigs.entrySet()) {
if (entry.getKey() == lastKey) {
return prevEntry;
} else {
prevEntry = entry;
}
}
return prevEntry;
}
}
}