//----------------------------------------------------------------------------//
// //
// L a g W e a v 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.grid;
import omr.glyph.GlyphsBuilder;
import omr.glyph.Shape;
import omr.glyph.facets.Glyph;
import omr.lag.Lag;
import omr.lag.Section;
import omr.lag.Sections;
import omr.run.Orientation;
import omr.run.Run;
import omr.sheet.Sheet;
import omr.util.Predicate;
import omr.util.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Point;
import java.awt.geom.PathIterator;
import static java.awt.geom.PathIterator.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
/**
* Class {@code LagWeaver} is just a prototype. TODO.
*
* @author Hervé Bitteur
*/
public class LagWeaver
{
//~ Static fields/initializers ---------------------------------------------
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(LagWeaver.class);
/** Table dx/dy -> Heading */
private static final Heading[][] headings = {
{null, Heading.NORTH, null},
{
Heading.WEST, null,
Heading.EAST
},
{null, Heading.SOUTH, null}
};
//~ Enumerations -----------------------------------------------------------
private static enum Heading
{
//~ Enumeration constant initializers ----------------------------------
NORTH,
EAST,
SOUTH,
WEST;
//~ Methods ------------------------------------------------------------
public boolean insideCornerTo (Heading next)
{
switch (this) {
case NORTH:
return next == WEST;
case EAST:
return next == NORTH;
case SOUTH:
return next == EAST;
case WEST:
return next == SOUTH;
}
return false; // Unreachable stmt
}
}
//~ Instance fields --------------------------------------------------------
/** Related sheet */
private final Sheet sheet;
/** Vertical lag */
private final Lag vLag;
/** Horizontal lag */
private final Lag hLag;
/**
* Actual points around current vLag section to check to hLag presence
* (relevant only during horiWithVert)
*/
private final List<Point> pointsAside = new ArrayList<>();
/** Points to check for source sections above in hLag */
private final List<Point> pointsAbove = new ArrayList<>();
/** Points to check for target sections below in hLag */
private final List<Point> pointsBelow = new ArrayList<>();
//~ Constructors -----------------------------------------------------------
//-----------//
// LagWeaver //
//-----------//
/**
* Creates a new LagWeaver object.
*
* @param sheet the related sheet, which holds the v & h lags
*/
public LagWeaver (Sheet sheet)
{
this.sheet = sheet;
vLag = sheet.getVerticalLag();
hLag = sheet.getHorizontalLag();
}
//~ Methods ----------------------------------------------------------------
//-----------//
// buildInfo //
//-----------//
public void buildInfo ()
{
StopWatch watch = new StopWatch("LagWeaver");
// Remove staff line stuff from hLag
watch.start("purge hLag");
List<Section> staffLinesSections = removeStaffLines(hLag);
logger.debug("{}StaffLine sections removed: {}",
sheet.getLogPrefix(), staffLinesSections.size());
watch.start("Hori <-> Hori");
horiWithHori();
watch.start("Hori <-> Vert");
horiWithVert();
watch.start("buildGlyphs");
buildGlyphs();
// The end
///watch.print();
}
//---------------//
// addPointAbove //
//---------------//
private void addPointAbove (int x,
int y)
{
logger.debug("addPointAbove {},{}", x, y);
pointsAbove.add(new Point(x, y));
}
//---------------//
// addPointAside //
//---------------//
private void addPointAside (int x,
int y)
{
//logger.debug("addPointAside " + x + "," + y);
pointsAside.add(new Point(x, y));
}
//---------------//
// addPointBelow //
//---------------//
private void addPointBelow (int x,
int y)
{
logger.debug("addPointBelow {},{}", x, y);
pointsBelow.add(new Point(x, y));
}
//-------------//
// buildGlyphs //
//-------------//
private void buildGlyphs ()
{
// Group (unknown) sections into glyphs
// Consider all unknown vertical & horizontal sections
List<Section> allSections = new ArrayList<>();
for (Section section : sheet.getVerticalLag().getSections()) {
if (!section.isKnown()) {
section.setProcessed(false);
allSections.add(section);
} else {
section.setProcessed(true);
}
}
for (Section section : sheet.getHorizontalLag().getSections()) {
if (!section.isKnown()) {
section.setProcessed(false);
allSections.add(section);
} else {
section.setProcessed(true);
}
}
GlyphsBuilder.retrieveGlyphs(
allSections,
sheet.getNest(),
sheet.getScale());
}
//------------------//
// checkPointsAbove //
//------------------//
private void checkPointsAbove (Section lSect)
{
boolean added = false;
for (Point pt : pointsAbove) {
Run run = hLag.getRunAt(pt.x, pt.y);
if (run != null) {
Section hSect = run.getSection();
if (hSect != null) {
hSect.addTarget(lSect);
added = true;
}
}
}
if (added && logger.isDebugEnabled()) {
logger.info("lSect#{} checks:{}{}",
lSect.getId(),
pointsAbove.size(),
Sections.toString(" sources", lSect.getSources()));
}
}
//------------------//
// checkPointsAside //
//------------------//
private void checkPointsAside (Section vSect)
{
boolean added = false;
for (Point pt : pointsAside) {
Run run = hLag.getRunAt(pt.x, pt.y);
if (run != null) {
Section hSect = run.getSection();
if (hSect != null) {
vSect.addOppositeSection(hSect);
hSect.addOppositeSection(vSect);
added = true;
}
}
}
if (added && logger.isDebugEnabled()) {
logger.info("vSect#{} checks:{}{}",
vSect.getId(),
pointsAside.size(),
Sections.toString(" hSects", vSect.getOppositeSections()));
}
}
//------------------//
// checkPointsBelow //
//------------------//
private void checkPointsBelow (Section lSect)
{
boolean added = false;
for (Point pt : pointsBelow) {
Run run = hLag.getRunAt(pt.x, pt.y);
if (run != null) {
Section hSect = run.getSection();
if (hSect != null) {
lSect.addTarget(hSect);
added = true;
}
}
}
if (added && logger.isDebugEnabled()) {
logger.info("lSect#{} checks:{}{}", lSect.getId(),
pointsBelow.size(),
Sections.toString(" targets", lSect.getTargets()));
}
}
//------------//
// getHeading //
//------------//
private Heading getHeading (Point prevPt,
Point pt)
{
int dx = Integer.signum(pt.x - prevPt.x);
int dy = Integer.signum(pt.y - prevPt.y);
return headings[1 + dy][1 + dx];
}
//--------------//
// horiWithHori //
//--------------//
/**
* Connect, when appropriate, the long horizontal sections (built from long
* runs) with short horizontal sections (built later from shorter runs).
* Without such connections, glyph building would suffer over-segmentation.
*
* <p>We take each long section in turn and check for connection, above and
* below, with short sections. If positive, we cross-connect them.
*/
private void horiWithHori ()
{
int maxLongId = sheet.getLongSectionMaxId();
// Process each long section in turn
for (Section lSect : hLag.getSections()) {
if (lSect.getId() > maxLongId) {
continue;
}
final int sectTop = lSect.getFirstPos();
final int sectLeft = lSect.getStartCoord();
final int sectBottom = lSect.getLastPos();
final double[] coords = new double[2];
final boolean[] occupied = new boolean[lSect.getLength(
Orientation.HORIZONTAL)];
Point prevPt = null;
Point pt;
Heading prevHeading = null;
Heading heading = null;
pointsAbove.clear();
pointsBelow.clear();
for (PathIterator it = lSect.getPathIterator(); !it.isDone();) {
int kind = it.currentSegment(coords);
pt = new Point((int) coords[0], (int) coords[1]);
if (kind == SEG_LINETO) {
heading = getHeading(prevPt, pt);
logger.debug("{} {} {}", prevPt, heading, pt);
switch (heading) {
case NORTH:
// No pixel on right
if (prevHeading == Heading.WEST) {
removePointAbove(prevPt.x, prevPt.y - 1);
}
break;
case WEST: {
int dir = -1;
// Check pixels on row above
Arrays.fill(occupied, false);
int y = pt.y - 1;
int xStart = prevPt.x - 1;
if (prevHeading == Heading.SOUTH) {
xStart += dir;
}
// Special case for first run, check adjacent section
if (pt.y == sectTop) {
for (Section adj : lSect.getSources()) {
Run run = adj.getLastRun();
int left = Math.max(run.getStart() - 1, pt.x);
int right = Math.min(run.getStop() + 1, xStart);
for (int x = left; x <= right; x++) {
occupied[x - sectLeft] = true;
}
}
}
int xBreak = pt.x - 1;
for (int x = xStart; x != xBreak; x += dir) {
if (!occupied[x - sectLeft]) {
addPointAbove(x, y);
}
}
break;
}
case SOUTH:
// No pixel on left
if (prevHeading == Heading.EAST) {
removePointBelow(prevPt.x - 1, prevPt.y);
}
break;
case EAST: {
int dir = +1;
// Check pixels on row below
Arrays.fill(occupied, false);
int y = pt.y;
int xStart = prevPt.x;
if (prevHeading == Heading.NORTH) {
xStart += dir;
}
int xBreak = pt.x;
// Special case for last run, check adjacent section
if ((pt.y - 1) == sectBottom) {
for (Section adj : lSect.getTargets()) {
Run run = adj.getFirstRun();
int left = Math.max(run.getStart() - 1, xStart);
int right = Math.min(
run.getStop() + 1,
xBreak - 1);
for (int x = left; x <= right; x++) {
occupied[x - sectLeft] = true;
}
}
}
for (int x = xStart; x != xBreak; x += dir) {
if (!occupied[x - sectLeft]) {
addPointBelow(x, y);
}
}
break;
}
}
}
prevHeading = heading;
prevPt = pt;
it.next();
}
checkPointsAbove(lSect);
checkPointsBelow(lSect);
}
}
//--------------//
// horiWithVert //
//--------------//
private void horiWithVert ()
{
// Process each vertical section in turn
for (Section vSect : vLag.getSections()) {
final int sectTop = vSect.getStartCoord();
final int sectLeft = vSect.getFirstPos();
final int sectRight = vSect.getLastPos();
final double[] coords = new double[2];
final boolean[] occupied = new boolean[vSect.getLength(
Orientation.VERTICAL)];
Point prevPt = null;
Point pt = null;
Heading prevHeading = null;
Heading heading = null;
pointsAside.clear();
for (PathIterator it = vSect.getPathIterator(); !it.isDone();) {
int kind = it.currentSegment(coords);
pt = new Point((int) coords[0], (int) coords[1]);
if (kind == SEG_LINETO) {
heading = getHeading(prevPt, pt);
//logger.info(prevPt + " " + heading + " " + pt);
switch (heading) {
case NORTH: {
int dir = -1;
// Check pixels on left column
Arrays.fill(occupied, false);
int x = pt.x - 1;
int yStart = prevPt.y - 1;
if (prevHeading == Heading.EAST) {
yStart += dir;
}
// Special case for section left run
if (pt.x == sectLeft) {
for (Section adj : vSect.getSources()) {
Run run = adj.getLastRun();
int top = Math.max(run.getStart() - 1, pt.y);
int bot = Math.min(run.getStop() + 1, yStart);
for (int y = top; y <= bot; y++) {
occupied[y - sectTop] = true;
}
}
}
int yBreak = pt.y - 1;
for (int y = yStart; y != yBreak; y += dir) {
if (!occupied[y - sectTop]) {
addPointAside(x, y);
}
}
}
break;
case WEST:
// No pixel above
if (prevHeading == Heading.NORTH) {
removePointAside(prevPt.x - 1, prevPt.y);
}
break;
case SOUTH: {
int dir = +1;
// Check pixels on right column
Arrays.fill(occupied, false);
int x = pt.x;
int yStart = prevPt.y;
if (prevHeading == Heading.WEST) {
yStart += dir;
}
int yBreak = pt.y;
// Special case for section right run
if ((pt.x - 1) == sectRight) {
for (Section adj : vSect.getTargets()) {
Run run = adj.getFirstRun();
int top = Math.max(run.getStart() - 1, yStart);
int bot = Math.min(
run.getStop() + 1,
yBreak - 1);
for (int y = top; y <= bot; y++) {
occupied[y - sectTop] = true;
}
}
}
for (int y = yStart; y != yBreak; y += dir) {
if (!occupied[y - sectTop]) {
addPointAside(x, y);
}
}
}
break;
case EAST:
// No pixel below
if (prevHeading == Heading.SOUTH) {
removePointAside(prevPt.x, prevPt.y - 1);
}
break;
}
}
prevHeading = heading;
prevPt = pt;
it.next();
}
checkPointsAside(vSect);
}
}
//-------------//
// removePoint //
//-------------//
private void removePoint (List<Point> points,
int x,
int y)
{
if (!points.isEmpty()) {
ListIterator<Point> iter = points.listIterator(points.size());
Point lastCorner = iter.previous();
if ((lastCorner.x == x) && (lastCorner.y == y)) {
iter.remove();
}
}
}
//------------------//
// removePointAbove //
//------------------//
private void removePointAbove (int x,
int y)
{
logger.debug("Removing corner above x:{} y:{}", x, y);
removePoint(pointsAbove, x, y);
}
//------------------//
// removePointAside //
//------------------//
private void removePointAside (int x,
int y)
{
removePoint(pointsAside, x, y);
}
//------------------//
// removePointBelow //
//------------------//
private void removePointBelow (int x,
int y)
{
logger.debug("Removing corner below x:{} y:{}", x, y);
removePoint(pointsBelow, x, y);
}
//------------------//
// removeStaffLines //
//------------------//
private List<Section> removeStaffLines (Lag hLag)
{
return hLag.purgeSections(
new Predicate<Section>()
{
@Override
public boolean check (Section section)
{
Glyph glyph = section.getGlyph();
if ((glyph != null)
&& (glyph.getShape() == Shape.STAFF_LINE)) {
/**
* Narrow horizontal section can be kept to avoid
* over-segmentation between vertical sections
*/
if ((section.getLength(Orientation.HORIZONTAL) == 1)
&& (section.getLength(Orientation.VERTICAL) > 1)) {
if (section.isVip() || logger.isDebugEnabled()) {
logger.info("Keeping staffline section {}",
section);
}
section.setGlyph(null);
return false;
} else {
return true;
}
} else {
return false;
}
}
});
}
}