//----------------------------------------------------------------------------//
// //
// C o m p o u n d B u i l d e r //
// //
//----------------------------------------------------------------------------//
// <editor-fold defaultstate="collapsed" desc="hdr"> //
// Copyright © Hervé Bitteur and others 2000-2013. 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;
import omr.glyph.facets.Glyph;
import omr.sheet.SystemInfo;
import omr.util.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Class {@code CompoundBuilder} defines a generic way to smartly
* build glyph compounds, and provides derived variants.
*
* @author Hervé Bitteur
*/
public class CompoundBuilder
{
//~ Static fields/initializers ---------------------------------------------
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(
CompoundBuilder.class);
//~ Instance fields --------------------------------------------------------
/** Dedicated system */
protected final SystemInfo system;
//~ Constructors -----------------------------------------------------------
//-----------------//
// CompoundBuilder //
//-----------------//
/**
* Creates a new CompoundBuilder object.
*
* @param system the containing system
*/
public CompoundBuilder (SystemInfo system)
{
this.system = system;
}
//~ Methods ----------------------------------------------------------------
//---------------//
// buildCompound //
//---------------//
/**
* Try to build a compound, starting from given seed and looking
* into the collection of suitable glyphs.
*
* <p>If successful, this method assigns the proper shape to the compound,
* and inserts it in the system environment.
*
* @param seed the initial glyph around which the compound is built
* @param includeSeed true if seed must be included in compound
* @param suitables collection of potential glyphs
* @param adapter the specific behavior of the compound tests
* @return the compound built if successful, null otherwise
*/
public Glyph buildCompound (Glyph seed,
boolean includeSeed,
Collection<Glyph> suitables,
CompoundAdapter adapter)
{
// Set seed (and reference box)
adapter.setSeed(seed);
// Retrieve good neighbors among the suitable glyphs
Set<Glyph> neighbors = new HashSet<>();
// Include the seed in the compound glyphs?
int minCount = 1;
if (includeSeed) {
neighbors.add(seed);
minCount++;
}
for (Glyph g : suitables) {
if (includeSeed || (g != seed)) {
if (adapter.isCandidateSuitable(g)
&& adapter.isCandidateClose(g)) {
neighbors.add(g);
}
}
}
if (neighbors.size() >= minCount) {
if (logger.isDebugEnabled()) {
logger.debug("neighbors={} seed={}",
Glyphs.toString(neighbors), seed);
}
Glyph compound = system.buildTransientCompound(neighbors);
if (adapter.isCompoundValid(compound)) {
// Assign and insert into system & nest environments
compound = system.addGlyph(compound);
compound.setEvaluation(adapter.getChosenEvaluation());
logger.debug("Compound #{} built as {}",
compound.getId(), compound.getShape());
return compound;
}
}
return null;
}
//---------------//
// buildCompound //
//---------------//
/**
* A basic building, which simply takes all the provided glyphs
* and build a persistent compound out of them.
*
* @param parts the glyphs to merge
* @return the compound built
*/
public Glyph buildCompound (Collection<Glyph> parts)
{
if (parts.isEmpty()) {
return null;
}
List<Glyph> list = new ArrayList<>(parts);
return buildCompound(
list.get(0),
true,
list.subList(1, list.size()),
new NoAdapter(system));
}
//~ Inner Interfaces -------------------------------------------------------
//-----------------//
// CompoundAdapter //
//-----------------//
/**
* Interface {@code CompoundAdapter} provides the needed features
* for building compounds out of glyphs.
*/
public static interface CompoundAdapter
{
//~ Methods ------------------------------------------------------------
/**
* Report the evaluation chosen for the compound.
*
* @return the evaluation (shape + grade) chosen
*/
Evaluation getChosenEvaluation ();
/**
* Predicate to check whether a given candidate glyph is close
* enough to the reference box.
*
* @param glyph the glyph to check for proximity
* @return true if glyph is close enough
*/
boolean isCandidateClose (Glyph glyph);
/**
* Predicate for a glyph to be a potential part of the building.
* (the location criteria is handled by {@link #isCandidateClose}).
*
* @param glyph the glyph to check
* @return true if the glyph is suitable for inclusion
*/
boolean isCandidateSuitable (Glyph glyph);
/**
* Predicate to check the validity of the newly built compound.
* If valid, the chosenEvaluation is assigned accordingly.
*
* @param compound the resulting compound glyph to check
* @return true if the compound is found OK. The compound shape is not
* assigned by this method, but can be later retrieved through
* getChosenEvaluation() method.
*/
boolean isCompoundValid (Glyph compound);
/**
* Define the seed glyph around which the compound will be built.
*
* @param seed the seed glyph
* @return the computed reference box
*/
Rectangle setSeed (Glyph seed);
/**
* Should we filter the provided candidates?. (by calling
* {@link #isCandidateSuitable} and {@link #isCandidateClose}).
*
* @return true to apply filter
*/
boolean shouldFilterCandidates ();
}
//~ Inner Classes ----------------------------------------------------------
//-----------------//
// AbstractAdapter //
//-----------------//
/**
* Basic abstract class to implement the {@link CompoundAdapter}
* interface.
*/
public abstract static class AbstractAdapter
implements CompoundAdapter
{
//~ Instance fields ----------------------------------------------------
/** Dedicated system */
protected final SystemInfo system;
/** Maximum grade for a compound */
protected final double minGrade;
/** Originating seed */
protected Glyph seed;
/** Search box */
protected Rectangle box;
/** The result of compound evaluation */
protected Evaluation chosenEvaluation;
//~ Constructors -------------------------------------------------------
/**
* Construct an AbstractAdapter.
*
* @param system the containing system
* @param minGrade maximum acceptable grade for the compound shape
*/
public AbstractAdapter (SystemInfo system,
double minGrade)
{
this.system = system;
this.minGrade = minGrade;
}
//~ Methods ------------------------------------------------------------
@Override
public Evaluation getChosenEvaluation ()
{
// By default, use shape and grade from evaluator
return chosenEvaluation;
}
@Override
public boolean isCandidateClose (Glyph glyph)
{
// By default, use box intersection
return box.intersects(glyph.getBounds());
}
@Override
public Rectangle setSeed (Glyph seed)
{
this.seed = seed;
box = computeReferenceBox();
return box;
}
@Override
public boolean shouldFilterCandidates ()
{
// By default, filter candidates
return true;
}
/**
* Compute the reference box.
* This method is called when seed has just been set.
*/
protected abstract Rectangle computeReferenceBox ();
}
//-----------//
// NoAdapter //
//-----------//
/**
* A passthrough fake adapter.
*/
public static class NoAdapter
extends AbstractAdapter
{
//~ Constructors -------------------------------------------------------
public NoAdapter (SystemInfo system)
{
super(system, 0);
}
//~ Methods ------------------------------------------------------------
@Override
public Evaluation getChosenEvaluation ()
{
return new Evaluation(null, Evaluation.ALGORITHM);
}
@Override
public boolean isCandidateClose (Glyph glyph)
{
return true;
}
@Override
public boolean isCandidateSuitable (Glyph glyph)
{
return true;
}
@Override
public boolean isCompoundValid (Glyph compound)
{
return true;
}
@Override
public boolean shouldFilterCandidates ()
{
return false;
}
@Override
protected Rectangle computeReferenceBox ()
{
return null;
}
}
//---------------//
// TopRawAdapter //
//---------------//
/**
* This compound adapter tries to find some specific shapes among
* the top raw evaluations found for the compound.
*/
public abstract static class TopRawAdapter
extends AbstractAdapter
{
//~ Instance fields ----------------------------------------------------
/** Collection of desired shapes for a valid compound */
protected final EnumSet<Shape> desiredShapes;
/** Specific predicate for desired shapes */
protected final Predicate<Shape> predicate = new Predicate<Shape>()
{
@Override
public boolean check (Shape shape)
{
return desiredShapes.contains(shape);
}
};
//~ Constructors -------------------------------------------------------
/**
* Create a TopRawAdapter instance.
*
* @param system the containing system
* @param minGrade maximum acceptable grade on compound shape
* @param desiredShapes the valid shapes for the compound
*/
public TopRawAdapter (SystemInfo system,
double minGrade,
EnumSet<Shape> desiredShapes)
{
super(system, minGrade);
this.desiredShapes = desiredShapes;
}
//~ Methods ------------------------------------------------------------
@Override
public boolean isCompoundValid (Glyph compound)
{
// Check if a desired shape appears in the top raw evaluations
final Evaluation vote = GlyphNetwork.getInstance().rawVote(
compound,
minGrade,
predicate);
if (vote != null) {
chosenEvaluation = vote;
return true;
} else {
return false;
}
}
}
//-----------------//
// TopShapeAdapter //
//-----------------//
/**
* This compound adapter tries to find some specific shapes among
* the top evaluations found for the compound.
*/
public abstract static class TopShapeAdapter
extends TopRawAdapter
{
//~ Constructors -------------------------------------------------------
/**
* Create a TopShapeAdapter instance.
*
* @param system the containing system
* @param minGrade maximum acceptable grade on compound shape
* @param desiredShapes the valid shapes for the compound
*/
public TopShapeAdapter (SystemInfo system,
double minGrade,
EnumSet<Shape> desiredShapes)
{
super(system, minGrade, desiredShapes);
}
//~ Methods ------------------------------------------------------------
@Override
public boolean isCompoundValid (Glyph compound)
{
// Check if a desired shape appears in the top evaluations
final Evaluation vote = GlyphNetwork.getInstance().vote(
compound,
system,
minGrade,
predicate);
if (vote != null) {
chosenEvaluation = vote;
return true;
} else {
return false;
}
}
}
}