//----------------------------------------------------------------------------//
// //
// T e x t S c a n n 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.text;
import omr.constant.ConstantSet;
import omr.glyph.Glyphs;
import omr.glyph.facets.Glyph;
import omr.grid.LineInfo;
import omr.grid.StaffInfo;
import omr.lag.Section;
import omr.math.GeoPath;
import omr.math.ReversePathIterator;
import omr.score.entity.Page;
import omr.sheet.Scale;
import omr.sheet.SystemInfo;
import omr.util.HorizontalSide;
import omr.util.LiveParam;
import omr.util.Navigable;
import omr.util.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Class {@code TextScanner} retrieves the text lines by using OCR on
* the whole system area, ignoring internal staves areas.
*
* @author Hervé Bitteur
*/
public class TextScanner
{
//~ Static fields/initializers ---------------------------------------------
/** Specific application parameters */
private static final Constants constants = new Constants();
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(TextScanner.class);
//~ Instance fields --------------------------------------------------------
//
/** The dedicated system. */
@Navigable(false)
private final SystemInfo system;
/** TextBuilder companion. */
private final TextBuilder textBuilder;
/** Scale-dependent parameters. */
private final Parameters params;
/** Glyphs involved. */
private Collection<Glyph> allGlyphs;
/** Sections involved. */
private Collection<Section> allSections = new ArrayList<>();
//~ Constructors -----------------------------------------------------------
//
//-------------//
// TextScanner //
//-------------//
/**
* Creates a new TextScanner object.
*
* @param system the dedicated system
*/
public TextScanner (SystemInfo system)
{
this.system = system;
textBuilder = system.getTextBuilder();
params = new Parameters(system.getSheet().getScale());
}
//~ Methods ----------------------------------------------------------------
//
//------------//
// scanSystem //
//------------//
/**
* Look for text items by launching the OCR on system area.
*/
public void scanSystem ()
{
final Page page = system.getSheet().getPage();
final LiveParam<String> textParam = page.getTextParam();
final String language = textParam.getTarget();
logger.debug("scanSystem lan:{} on {}", language, system.idString());
textParam.setActual(language);
// Retrieve glyphs
allGlyphs = retrieveRegionGlyphs();
// Generate an image with these glyphs
final Rectangle bounds = system.getBounds();
final BufferedImage image = new BufferedImage(
bounds.width,
bounds.height,
BufferedImage.TYPE_BYTE_GRAY);
for (Glyph glyph : allGlyphs) {
allSections.addAll(glyph.getMembers());
}
for (Section section : allSections) {
section.fillImage(image, bounds);
}
// Perform OCR on image
final List<TextLine> lines = TextBuilder.getOcr().recognize(
image,
bounds.getLocation(),
language,
OCR.LayoutMode.MULTI_BLOCK,
system,
"s" + system.getId());
// Process results
if (lines != null) {
List<TextLine> newLines = textBuilder.recomposeLines(lines);
textBuilder.mapGlyphs(newLines,
allSections,
language);
} else {
logger.info("{} No line", system.idString());
}
}
//----------------------//
// retrieveRegionGlyphs //
//----------------------//
/**
* Among the system glyphs, retrieve the ones that should be
* considered for potential text items.
*
* @return the collection of glyph candidates
*/
private Collection<Glyph> retrieveRegionGlyphs ()
{
/** Map staff -> contour. */
final Map<StaffInfo, GeoPath> pathMap = new HashMap<>();
// Define system region with staves removed
for (StaffInfo staff : system.getStaves()) {
pathMap.put(staff, getStaffContour(staff));
}
// Safer
system.removeInactiveGlyphs();
// Discard glyphs that intersect a stave core area
return Glyphs.lookupGlyphs(system.getGlyphs(),
new Predicate<Glyph>()
{
@Override
public boolean check (Glyph glyph)
{
// Reject manual non-text glyphs
if (glyph.isManualShape() && !glyph.isText()) {
return false;
}
// Keep known text
if (glyph.isText()) {
return true;
}
// Check position wrt closest staff
StaffInfo staff = system.getStaffAt(glyph.getAreaCenter());
GeoPath contour = pathMap.get(staff);
if (contour != null) {
if (contour.intersects(glyph.getBounds())) {
return false;
}
// Also, to cope with edition of system boundaries,
// reject glyphs that belong to a structure intersecting
// a staff region (this former structure appeared as one
// glyph before being segmented along stems)
for (HorizontalSide side : HorizontalSide.values()) {
Glyph stem = glyph.getStem(side);
if (stem != null && contour.intersects(stem.getBounds())) {
return false;
}
}
}
// Discard too large glyphs
Rectangle bounds = glyph.getBounds();
if (bounds.width > params.maxGlyphWidth) {
return false;
}
if (bounds.height > params.maxGlyphHeight) {
return false;
}
// All tests are OK
return true;
}
});
}
//----------------//
// getSampledLine //
//----------------//
/**
* Define an approximating path for a provided line, enlarged in
* abscissa and shifted in ordinate
*
* @param line the line to approximate
* @param yShift the shift in abscissa
* @return the path to use
*/
private GeoPath getSampledLine (LineInfo line,
double yShift)
{
double left = line.getEndPoint(HorizontalSide.LEFT).getX() - params.marginLeft;
double right = line.getEndPoint(HorizontalSide.RIGHT).getX() + params.marginRight;
double width = right - left;
final int sampleCount = (int) Math.rint(width / params.samplingDx);
final double dx = width / sampleCount;
GeoPath path = new GeoPath();
for (int i = 0; i <= sampleCount; i++) {
int x = (int) Math.rint(left + i * dx);
double y = line.yAt(x) + yShift;
if (i == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
return path;
}
//-----------------//
// getStaffContour //
//-----------------//
/**
* Define an approximated contour for a staff, slightly enlarged
* in abscissa and ordinate.
*
* @param staff the staff to approximate
* @return an augmented contour for the staff
*/
private GeoPath getStaffContour (StaffInfo staff)
{
GeoPath topLimit = getSampledLine(staff.getFirstLine(),
-params.marginAbove);
GeoPath botLimit = getSampledLine(staff.getLastLine(),
params.marginBelow);
GeoPath contour = new GeoPath();
contour.append(topLimit, false);
contour.append(ReversePathIterator.getReversePathIterator(botLimit),
true);
contour.closePath();
// For visual check
///staff.addAttachment("core", contour);
///logger.info("Staff#{} contour:{}", staff.getId(), contour.toString());
return contour;
}
//~ Inner Classes ----------------------------------------------------------
//-----------//
// Constants //
//-----------//
private static final class Constants
extends ConstantSet
{
//~ Instance fields ----------------------------------------------------
Scale.Fraction samplingDx = new Scale.Fraction(
4d,
"Abscissa sampling for staff contour");
Scale.Fraction staffMarginAbove = new Scale.Fraction(
0.25,
"Minimum distance above staff");
Scale.Fraction staffMarginBelow = new Scale.Fraction(
0.25,
"Minimum distance below staff");
Scale.Fraction staffMarginLeft = new Scale.Fraction(
0.25,
"Minimum distance left of staff");
Scale.Fraction staffMarginRight = new Scale.Fraction(
0.25,
"Minimum distance right of staff");
Scale.Fraction maxGlyphWidth = new Scale.Fraction(
7,
"Maximum glyph width");
Scale.Fraction maxGlyphHeight = new Scale.Fraction(
5,
"Maximum glyph height");
}
//------------//
// Parameters //
//------------//
private class Parameters
{
final int marginAbove;
final int marginBelow;
final int marginLeft;
final int marginRight;
final int maxGlyphWidth;
final int maxGlyphHeight;
final double samplingDx;
public Parameters (Scale scale)
{
marginAbove = scale.toPixels(constants.staffMarginAbove);
marginBelow = scale.toPixels(constants.staffMarginBelow);
marginLeft = scale.toPixels(constants.staffMarginLeft);
marginRight = scale.toPixels(constants.staffMarginRight);
maxGlyphWidth = scale.toPixels(constants.maxGlyphWidth);
maxGlyphHeight = scale.toPixels(constants.maxGlyphHeight);
samplingDx = scale.toPixelsDouble(constants.samplingDx);
}
}
}