//----------------------------------------------------------------------------//
// //
// B a r s R e t r i e 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.Main;
import omr.constant.Constant;
import omr.constant.ConstantSet;
import omr.glyph.Glyphs;
import omr.glyph.facets.Glyph;
import omr.glyph.ui.NestView;
import omr.lag.BasicLag;
import omr.lag.JunctionRatioPolicy;
import omr.lag.Lag;
import omr.lag.Section;
import omr.lag.SectionsBuilder;
import omr.math.Barycenter;
import omr.math.BasicLine;
import omr.math.Line;
import omr.math.LineUtil;
import static omr.run.Orientation.*;
import omr.run.RunsTable;
import omr.sheet.BarsChecker;
import omr.sheet.Scale;
import omr.sheet.Sheet;
import omr.sheet.Skew;
import omr.sheet.SystemInfo;
import omr.step.StepException;
import omr.ui.Colors;
import omr.ui.util.UIUtil;
import omr.util.HorizontalSide;
import static omr.util.HorizontalSide.*;
import omr.util.VipUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
/**
* Class {@code BarsRetriever} focuses on the retrieval of vertical
* barlines.
* Barlines are used to determine the side limits of staves and, most
* importantly, the gathering of staves into systems.
* The other barlines are used to determine parts and measures.
*
* @author Hervé Bitteur
*/
public class BarsRetriever
implements NestView.ItemRenderer
{
//~ Static fields/initializers ---------------------------------------------
/** Specific application parameters. */
private static final Constants constants = new Constants();
/** Usual logger utility. */
private static final Logger logger = LoggerFactory.getLogger(BarsRetriever.class);
//~ Instance fields --------------------------------------------------------
//
/** Related sheet. */
private final Sheet sheet;
/** Scale-dependent constants for vertical stuff. */
private final Parameters params;
/** Lag of vertical runs. */
private Lag vLag;
/** Related staff manager. */
private final StaffManager staffManager;
/** Long vertical filaments found, non sorted. */
private final List<Glyph> filaments = new ArrayList<>();
/** Intersections between staves and bar sticks. */
private final Map<StaffInfo, IntersectionSequence> crossings = new TreeMap<>(StaffInfo.byId);
/**
* System tops.
* For each staff, gives the staff that starts the containing system
*/
private Integer[] systemTops;
/**
* Part tops.
* For each staff, gives the staff that starts the containing part
*/
private Integer[] partTops;
//~ Constructors -----------------------------------------------------------
//
//---------------//
// BarsRetriever //
//---------------//
/**
* Retrieve the frames of all staff lines.
*
* @param sheet the sheet to process
*/
public BarsRetriever (Sheet sheet)
{
this.sheet = sheet;
// Scale-dependent parameters
params = new Parameters(sheet.getScale());
// Companions
staffManager = sheet.getStaffManager();
}
//~ Methods ----------------------------------------------------------------
//
//----------//
// buildLag //
//----------//
/**
* Build the underlying lag, out of the provided runs table.
* This method must be called before building info.
*
* @param vertTable the provided table of vertical runs
*/
public void buildLag (RunsTable vertTable)
{
vLag = new BasicLag("vLag", VERTICAL);
SectionsBuilder sectionsBuilder = new SectionsBuilder(
vLag,
new JunctionRatioPolicy(params.maxLengthRatio));
sectionsBuilder.createSections(vertTable);
sheet.setVerticalLag(vLag);
// Debug sections VIPs
for (int id : params.vipSections) {
Section sect = vLag.getVertexById(id);
if (sect != null) {
sect.setVip();
}
}
}
//-------------//
// renderItems //
//-------------//
/**
* Render the filaments and their ending tangents if so desired.
*
* @param g graphics context
*/
@Override
public void renderItems (Graphics2D g)
{
if (!constants.showVerticalLines.isSet()) {
return;
}
final Stroke oldStroke = UIUtil.setAbsoluteStroke(g, 1f);
final Color oldColor = g.getColor();
g.setColor(Colors.ENTITY_MINOR);
// Draw filaments
for (Glyph filament : filaments) {
filament.renderLine(g);
}
// Draw tangent at each ending point (using max coord gap)?
if (constants.showTangents.isSet()) {
g.setColor(Colors.TANGENT);
double dy = sheet.getScale()
.toPixels(constants.maxCoordGap);
for (Glyph glyph : filaments) {
Point2D p = glyph.getStartPoint(VERTICAL);
double derivative = (glyph instanceof Filament)
? ((Filament) glyph).slopeAt(p.getY(),
VERTICAL)
: glyph.getInvertedSlope();
g.draw(new Line2D.Double(p.getX(), p.getY(),
p.getX() - (derivative * dy),
p.getY() - dy));
p = glyph.getStopPoint(VERTICAL);
derivative = (glyph instanceof Filament)
? ((Filament) glyph).slopeAt(p.getY(), VERTICAL)
: glyph.getInvertedSlope();
g.draw(new Line2D.Double(p.getX(), p.getY(),
p.getX() + (derivative * dy),
p.getY() + dy));
}
}
g.setStroke(oldStroke);
g.setColor(oldColor);
}
//---------------------//
// retrieveMeasureBars //
//---------------------//
/**
* Use long vertical sections to retrieve the barlines that define
* measures.
* We first filter the bar candidates in a less rough manner now that we
* have precise values for staff lines coordinates.
* Then, we use consistency checks within the same system, where measure
* barlines should be aligned vertically.
*
* <pre>
* retrieveMeasureBars()
* + recheckBarCandidates() // Strict barSticks checking
*
* + (per system)
* + checkBarsAlignment(system) // Check bars consistency w/i system
*
* + retrievePartTops() // Retrieve parts top staves
*
* + (per system)
* + refineBarsEndings(system) // Refine endings for each bar line
* </pre>
*/
public void retrieveMeasureBars ()
{
// Strict barSticks checking
recheckBarCandidates();
// Check bars consistency within the same system
for (SystemInfo system : sheet.getSystems()) {
checkBarsAlignment(system);
}
// We already have systemTop for each staff
// let's try to define partTop for each staff
partTops = retrievePartTops();
logger.info("{}Parts top staff ids: {}",
sheet.getLogPrefix(), Arrays.toString(partTops));
// Refine ending points for each bar line
for (SystemInfo system : sheet.getSystems()) {
refineBarsEndings(system);
}
}
//--------------------//
// retrieveSystemBars //
//--------------------//
/**
* Use the long vertical sections to retrieve the barlines that
* define the limits of systems and staves.
*
* <pre>
* retrieveSystemBars()
* + retrieveMajorBars()
* + buildVerticalFilaments() // Build vertical filaments
* + retrieveBarCandidates() // Retrieve initial barline candidates
* + populateCrossings() // Assign bar candidates to intersected staves
* + retrieveStaffSideBars() // Retrieve left staff bars and right staff bars
*
* + buildSystems()
* + retrieveSystemTops() // Retrieve top staves (they start systems)
* + createSystems() // Create system frames using top staves
* + mergeSystems() // Merge of systems as much as possible
*
* + adjustSides() // Adjust sides for systems, staves & lines
* + (per system)
* + (per side)
* + adjustSystemLimit(system, side) // Adjust system limit
* + adjustStaffLines(system) // Adjust staff lines to system limits
* </pre>
*
* @throws StepException raised if processing must stop
*/
public void retrieveSystemBars (Collection<Glyph> oldGlyphs,
Collection<Glyph> newGlyphs)
throws StepException
{
try {
// Retrieve major barSticks (for system & staves limits)
retrieveMajorBars(oldGlyphs, newGlyphs);
} catch (Exception ex) {
logger.warn(sheet.getLogPrefix()
+ "BarsRetriever cannot retrieveBars", ex);
}
try {
// Detect systems of staves aggregated via barlines
buildSystems();
} catch (Exception ex) {
logger.warn(sheet.getLogPrefix()
+ "BarsRetriever cannot retrieveSystems", ex);
}
// Adjust precise horizontal sides for systems, staves & lines
adjustSides();
}
//------------------//
// adjustStaffLines //
//------------------//
/**
* Staff by staff, align the lines endings with the system limits,
* and check the intermediate line points.
* Package access meant for LinesRetriever companion.
*
* @param system the system to process
*/
void adjustStaffLines (SystemInfo system)
{
for (StaffInfo staff : system.getStaves()) {
logger.debug("{}", staff);
// Adjust left and right endings of each line in the staff
for (LineInfo l : staff.getLines()) {
FilamentLine line = (FilamentLine) l;
line.setEndingPoints(getLineEnding(system, staff, line, LEFT),
getLineEnding(system, staff, line, RIGHT));
}
// Insert line intermediate points, if so needed
List<LineFilament> fils = new ArrayList<>();
for (LineInfo l : staff.getLines()) {
FilamentLine line = (FilamentLine) l;
fils.add(line.fil);
}
for (LineInfo l : staff.getLines()) {
FilamentLine line = (FilamentLine) l;
line.fil.fillHoles(fils);
}
}
}
//------------------//
// adjustSystemBars //
//------------------//
/**
* Adjust start and stop points of system side barSticks.
*/
void adjustSystemBars ()
{
for (SystemInfo system : sheet.getSystems()) {
try {
for (HorizontalSide side : HorizontalSide.values()) {
Object limit = system.getLimit(side);
if (limit != null) {
// Determine first point of barline
StaffInfo firstStaff = system.getFirstStaff();
Point2D pStart = firstStaff.getFirstLine().
getEndPoint(side);
// Determine last point of barline
StaffInfo lastStaff = system.getLastStaff();
Point2D pStop = lastStaff.getLastLine().
getEndPoint(side);
// [Dirty programming, sorry]
if (limit instanceof Glyph) {
((Glyph) limit).setEndingPoints(pStart, pStop);
}
}
}
} catch (Exception ex) {
logger.warn("BarsRetriever can't adjust side bars of "
+ system.idString(), ex);
}
}
}
//----------------------//
// getSideBarlineGlyphs //
//----------------------//
/**
* Report the set of all sticks that are actually part of the staff
* side barlines (left or right side).
*
* @return the collection of used barline sticks
*/
Set<Glyph> getSideBarlineGlyphs ()
{
Set<Glyph> sticks = new HashSet<>();
for (StaffInfo staff : staffManager.getStaves()) {
for (HorizontalSide side : HorizontalSide.values()) {
BarInfo bar = staff.getBar(side);
if (bar != null) {
sticks.addAll(bar.getSticksAncestors());
}
}
}
return sticks;
}
//--------//
// isLong //
//--------//
boolean isLong (Glyph stick)
{
return (stick != null)
&& (stick.getLength(VERTICAL) >= params.minLongLength);
}
//--------//
// isLong //
//--------//
boolean isLong (BarInfo bar)
{
if (bar != null) {
for (Glyph stick : bar.getSticksAncestors()) {
if (isLong(stick)) {
return true;
}
}
}
return false;
}
//-------------//
// adjustSides //
//-------------//
/**
* Adjust precise sides for systems, staves & lines.
*/
private void adjustSides ()
{
for (SystemInfo system : sheet.getSystems()) {
try {
for (HorizontalSide side : HorizontalSide.values()) {
// Determine the side limit of the system
adjustSystemLimit(system, side);
}
if (logger.isDebugEnabled()) {
logger.debug("System#{} left:{} right:{}", system.getId(),
system.getLimit(LEFT).getClass()
.getSimpleName(),
system.getLimit(RIGHT).getClass()
.getSimpleName());
}
// Use system limits to adjust staff lines
adjustStaffLines(system);
} catch (Exception ex) {
logger.warn("BarsRetriever cannot adjust system#"
+ system.getId(), ex);
}
}
}
//-------------------//
// adjustSystemLimit //
//-------------------//
/**
* Adjust the limit on the desired side of the provided system and
* store the chosen limit (whatever it is) in the system itself.
*
* @param system the system to process
* @param side the desired side
*
* @see SystemInfo#getLimit(HorizontalSide)
*/
private void adjustSystemLimit (SystemInfo system,
HorizontalSide side)
{
Glyph drivingStick = null;
// Do we have a bar embracing the whole system?
BarInfo bar = retrieveSystemBar(system, side);
if (bar != null) {
// We use the heaviest stick among the system-embracing bar sticks
SortedSet<Glyph> allSticks = new TreeSet<>(
Glyph.byReverseWeight);
for (Glyph stick : bar.getSticksAncestors()) {
Point2D start = stick.getStartPoint(VERTICAL);
StaffInfo topStaff = staffManager.getStaffAt(start);
Point2D stop = stick.getStopPoint(VERTICAL);
StaffInfo botStaff = staffManager.getStaffAt(stop);
if ((topStaff == system.getFirstStaff())
&& (botStaff == system.getLastStaff())) {
allSticks.add(stick);
}
}
if (!allSticks.isEmpty()) {
drivingStick = allSticks.first();
logger.debug("System#{} {} drivingStick: {}",
system.getId(), side, drivingStick);
// Polish long driving stick, if needed
if (isLong(drivingStick) && drivingStick instanceof Filament) {
((Filament) drivingStick).polishCurvature();
}
// Remember approximate limit abscissa for each staff
for (StaffInfo staff : system.getStaves()) {
Point2D inter = staff.intersection(drivingStick);
staff.setAbscissa(side, inter.getX());
}
system.setLimit(side, drivingStick);
}
}
if (drivingStick == null) {
// We fall back using some centroid & slope
// TODO: This algorithm could be refined
Barycenter bary = new Barycenter();
for (StaffInfo staff : system.getStaves()) {
double x = extendStaffAbscissa(staff, side);
double y = staff.getMidOrdinate(side);
bary.include(x, y);
}
logger.debug("System#{} {} barycenter: {}",
system.getId(), side, bary);
double slope = sheet.getSkew().getSlope();
BasicLine line = new BasicLine();
line.includePoint(bary.getX(), bary.getY());
line.includePoint(bary.getX() - (100 * slope), bary.getY() + 100);
system.setLimit(side, line);
}
}
//--------------//
// buildSystems //
//--------------//
/**
* Detect systems of staves aggregated via connecting barlines.
*/
private void buildSystems ()
{
do {
// Retrieve the staves that start systems
if (systemTops == null) {
systemTops = retrieveSystemTops();
}
logger.info("{}Systems top staff ids: {}", sheet.getLogPrefix(),
Arrays.toString(systemTops));
// Create system frames using staves tops
sheet.setSystems(createSystems(systemTops));
// Merge of systems as much as possible
if (mergeSystems()) {
logger.info("Systems modified, rebuilding...");
} else {
break;
}
} while (true);
}
//------------------------//
// buildVerticalFilaments //
//------------------------//
/**
* With vertical lag sections, build vertical filaments.
*
* @throws Exception
*/
private void buildVerticalFilaments ()
throws Exception
{
// Filaments factory
FilamentsFactory factory = new FilamentsFactory(sheet.getScale(),
sheet.getNest(),
VERTICAL,
Filament.class);
// Factory parameters adjustment
factory.setMaxSectionThickness(constants.maxSectionThickness);
factory.setMaxFilamentThickness(constants.maxFilamentThickness);
factory.setMaxCoordGap(constants.maxCoordGap);
factory.setMaxPosGap(constants.maxPosGap);
factory.setMaxSpace(constants.maxSpace);
factory.setMaxOverlapDeltaPos(constants.maxOverlapDeltaPos);
// Retrieve filaments out of vertical sections
filaments.addAll(factory.retrieveFilaments(vLag.getVertices(), true));
}
//-------------------//
// canConnectSystems //
//-------------------//
/**
* Try to merge the two provided systems into a single one.
*
* @param prevSystem the system above
* @param nextSystem the system below
*
* @return true if left barSticks have been merged
*/
private boolean canConnectSystems (SystemInfo prevSystem,
SystemInfo nextSystem)
{
List<SystemInfo> systems = sheet.getSystems();
Skew skew = sheet.getSkew();
if (logger.isDebugEnabled()) {
logger.info("Checking S#{}({}) - S#{}({})", prevSystem.getId(),
prevSystem.getStaves().size(), nextSystem.getId(),
nextSystem.getStaves().size());
}
StaffInfo prevStaff = prevSystem.getLastStaff();
Point2D prevStaffPt = skew.deskewed(prevStaff.getLastLine()
.getEndPoint(LEFT));
double prevY = prevStaffPt.getY();
BarInfo prevBar = prevStaff.getBar(LEFT); // Perhaps null
StaffInfo nextStaff = nextSystem.getFirstStaff();
Point2D nextStaffPt = skew.deskewed(nextStaff.getFirstLine()
.getEndPoint(LEFT));
double nextY = nextStaffPt.getY();
BarInfo nextBar = nextStaff.getBar(LEFT); // Perhaps null
// Check vertical connections between barSticks
if ((prevBar != null) && (nextBar != null)) {
// case: Bar - Bar
for (Glyph prevStick : prevBar.getSticksAncestors()) {
Point2D prevPoint = skew.deskewed(prevStick.getStopPoint(
VERTICAL));
for (Glyph nextStick : nextBar.getSticksAncestors()) {
Point2D nextPoint = skew.deskewed(nextStick.getStartPoint(
VERTICAL));
// Check dx
double dx = Math.abs(nextPoint.getX() - prevPoint.getX());
// Check dy
double dy = Math.abs(Math.min(nextY, nextPoint.getY())
- Math.max(prevY, prevPoint.getY()));
logger.debug("F{}-F{} dx:{} vs {}, dy:{} vs {}",
prevStick.getId(), nextStick.getId(),
(float) dx, params.maxBarPosGap, (float) dy,
params.maxBarCoordGap);
if ((dx <= params.maxBarPosGap)
&& (dy <= params.maxBarCoordGap)) {
logger.info("Merging systems S#{}({}) - S#{}({})",
prevSystem.getId(),
prevSystem.getStaves().size(),
nextSystem.getId(),
nextSystem.getStaves().size());
prevStick.stealSections(nextStick);
return tryRangeConnection(
systems.subList(systems.indexOf(prevSystem),
1 + systems.indexOf(nextSystem)));
}
}
}
} else if (prevBar != null) {
// case: Bar - noBar
Point2D prevPoint = null;
for (Glyph prevStick : prevBar.getSticksAncestors()) {
Point2D point = skew.deskewed(prevStick.getStopPoint(VERTICAL));
if ((prevPoint == null) || (prevPoint.getY() < point.getY())) {
prevPoint = point;
}
}
if ((prevPoint.getY() - prevY) > params.minBarChunkHeight) {
double dy = nextY - prevPoint.getY();
if (dy <= params.maxBarCoordGap) {
return tryRangeConnection(systems.subList(systems.indexOf(
prevSystem),
1
+ systems.indexOf(nextSystem)));
}
}
} else if (nextBar != null) {
// case: NoBar - Bar
Point2D nextPoint = null;
for (Glyph nextStick : nextBar.getSticksAncestors()) {
Point2D point = skew.deskewed(nextStick.getStartPoint(VERTICAL));
if ((nextPoint == null) || (nextPoint.getY() > point.getY())) {
nextPoint = point;
}
}
if ((nextY - nextPoint.getY()) > params.minBarChunkHeight) {
double dy = nextPoint.getY() - prevY;
if (dy <= params.maxBarCoordGap) {
return tryRangeConnection(systems.subList(systems.indexOf(
prevSystem),
1
+ systems.indexOf(nextSystem)));
}
}
}
return false;
}
//--------------------//
// checkBarsAlignment //
//--------------------//
/**
* Check that, within the same system, barSticks are vertically
* aligned across all staves.
* We first build BarAlignment instances to record the bar locations and
* finally check these alignments for correctness.
* Resulting alignments are stored in the SystemInfo instance.
*
* @param system the system to process
*/
private void checkBarsAlignment (SystemInfo system)
{
List<StaffInfo> staves = system.getStaves();
int staffCount = staves.size();
// Bar alignments for the system
List<BarAlignment> alignments = null;
for (int iStaff = 0; iStaff < staves.size(); iStaff++) {
StaffInfo staff = staves.get(iStaff);
IntersectionSequence staffCrossings = crossings.get(staff);
// logger.info("System#" + system.getId() +" Staff#" + staff.getId());
// for (StickIntersection inter : staffBars) {
// logger.info(inter.toString());
// }
//
if (alignments == null) {
// Initialize the alignments
alignments = new ArrayList<>();
system.setBarAlignments(alignments);
for (StickIntersection crossing : staffCrossings) {
BarAlignment align = new BarAlignment(sheet, staffCount);
align.addInter(iStaff, crossing);
alignments.add(align);
}
} else {
// Do we have a bar around each abscissa?
for (StickIntersection loc : staffCrossings) {
// Find closest alignment
Double[] dists = new Double[alignments.size()];
Integer bestIdx = null;
for (int ia = 0; ia < alignments.size(); ia++) {
BarAlignment align = alignments.get(ia);
Double dist = align.distance(iStaff, loc);
if (dist != null) {
dists[ia] = dist;
dist = Math.abs(dist);
if (bestIdx != null) {
if (Math.abs(dists[bestIdx]) > dist) {
dists[bestIdx] = dist;
bestIdx = ia;
}
} else {
bestIdx = ia;
}
}
}
if ((bestIdx != null)
&& (Math.abs(dists[bestIdx])
<= params.maxAlignmentDistance)) {
alignments.get(bestIdx)
.addInter(iStaff, loc);
} else {
// Insert a new alignment at proper index
BarAlignment align = new BarAlignment(sheet,
staffCount);
align.addInter(iStaff, loc);
if (bestIdx == null) {
alignments.add(align);
} else {
if (dists[bestIdx] < 0) {
alignments.add(bestIdx, align);
} else {
alignments.add(bestIdx + 1, align);
}
}
}
}
}
}
// Check the bar alignments
for (Iterator<BarAlignment> it = alignments.iterator(); it.hasNext();) {
BarAlignment align = it.next();
// Don't call manual alignments into question
if (align.isManual()) {
continue;
}
// If alignment is almost empty, remove it
// otherwise, try to fill the holes
int filled = align.getFilledCount();
// double ratio = (double) filled / staffCount;
//// if (ratio < constants.minAlignmentRatio.getValue()) {
// // We remove this alignment and deassign its sticks
// logger.debug("{}Removing {}", sheet.getLogPrefix(), align);
// it.remove();
//
// for (StickIntersection inter : align.getIntersections()) {
// if (inter != null) {
// inter.getStickAncestor().setShape(null);
// }
// }
// } else if (filled != staffCount) {
// // TODO: Should implement driven recognition here...
// logger.info("{}Should fill {}", sheet.getLogPrefix(), align);
// }
// Strict: we require all staves to have a barline in this alignment
if (filled < staffCount) {
// We remove this alignment and deassign its sticks
logger.debug("{}Removing {}", sheet.getLogPrefix(), align);
it.remove();
for (StickIntersection inter : align.getIntersections()) {
if (inter != null) {
inter.getStickAncestor()
.setShape(null);
}
}
}
}
}
//---------------//
// createSystems //
//---------------//
/**
* Build the frame of each system.
*
* @param tops the starting staff id for each system
* @return the sequence of system physical frames
*/
private List<SystemInfo> createSystems (Integer[] tops)
{
List<SystemInfo> newSystems = new ArrayList<>();
Integer staffTop = null;
int systemId = 0;
SystemInfo system = null;
for (int i = 0; i < staffManager.getStaffCount(); i++) {
StaffInfo staff = staffManager.getStaff(i);
// System break?
if ((staffTop == null) || (staffTop < tops[i])) {
// Start of a new system
staffTop = tops[i];
system = new SystemInfo(++systemId, sheet,
staffManager.getRange(staff, staff));
newSystems.add(system);
} else {
// Continuing current system
system.setStaves(staffManager.getRange(system.getFirstStaff(),
staff));
}
}
return newSystems;
}
//---------------------//
// extendStaffAbscissa //
//---------------------//
/**
* Determine a not-too-bad abscissa for staff end, extending the
* abscissa beyond the bar limit if staff lines so require.
*
* @param staff the staff to process
* @param side the desired side
* @return the staff abscissa
*/
private double extendStaffAbscissa (StaffInfo staff,
HorizontalSide side)
{
// Check that staff bar, if any, is not passed by lines
BarInfo bar = staff.getBar(side);
if (bar != null) {
Glyph drivingStick = null;
double linesX = staff.getLinesEnd(side);
// Pick up the heaviest bar stick
SortedSet<Glyph> allSticks = new TreeSet<>(
Glyph.byReverseWeight);
// Use long sticks first
for (Glyph stick : bar.getSticksAncestors()) {
if (isLong(stick)) {
allSticks.add(stick);
}
}
// use local sticks if needed
if (allSticks.isEmpty()) {
allSticks.addAll(bar.getSticksAncestors());
}
if (!allSticks.isEmpty()) {
drivingStick = allSticks.first();
// Extend approximate limit abscissa?
Point2D inter = staff.intersection(drivingStick);
double barX = inter.getX();
final int dir = (side == LEFT) ? 1 : (-1);
if ((dir * (barX - linesX)) > params.maxLineExtension) {
staff.setBar(side, null);
staff.setAbscissa(side, linesX);
logger.debug("{} extended {}", side, staff);
}
}
}
return staff.getAbscissa(side);
}
//---------------//
// getLineEnding //
//---------------//
/**
* Report the precise point where a given line should end.
*
* @param system the system to process
* @param staff containing staff
* @param line the line at hand
* @param side the desired ending
* @return the computed ending point
*
* @throws RuntimeException DOCUMENT ME!
*/
private Point2D getLineEnding (SystemInfo system,
StaffInfo staff,
LineInfo line,
HorizontalSide side)
{
double slope = staff.getEndingSlope(side);
Object limit = system.getLimit(side);
Point2D linePt = line.getEndPoint(side);
double staffX = staff.getAbscissa(side);
double y = linePt.getY() - ((linePt.getX() - staffX) * slope);
double x;
// Dirty programming, sorry
if (limit instanceof Glyph) {
x = ((Glyph) limit).getPositionAt(y, VERTICAL);
} else if (limit instanceof Line) {
x = ((Line) limit).xAtY(y);
} else {
throw new RuntimeException("Illegal system limit: " + limit);
}
return new Point2D.Double(x, y);
}
//--------------//
// mergeSystems //
//--------------//
private boolean mergeSystems ()
{
List<SystemInfo> systems = sheet.getSystems();
boolean modified = false;
// Check connection of left barSticks across systems
for (int i = 1; i < systems.size(); i++) {
if (canConnectSystems(systems.get(i - 1), systems.get(i))) {
modified = true;
}
}
// More touchy decisions here
// if (!modified) {
// if (constants.smartStavesConnections.getValue()) {
// return trySmartConnections();
// }
// } else {
systemTops = null; // To force recomputation
// }
return modified;
}
//-------------------//
// populateCrossings //
//-------------------//
/**
* Retrieve the sticks that intersect each staff, the results being
* kept as sequences of staff intersections in crossings structure.
*/
private void populateCrossings ()
{
// Global reset
crossings.clear();
for (Glyph stick : filaments) {
// Skip merged sticks
if (stick.getPartOf() != null) {
continue;
}
Point2D start = stick.getStartPoint(VERTICAL);
StaffInfo topStaff = staffManager.getStaffAt(start);
int top = topStaff.getId();
Point2D stop = stick.getStopPoint(VERTICAL);
StaffInfo botStaff = staffManager.getStaffAt(stop);
int bot = botStaff.getId();
if (logger.isDebugEnabled() || stick.isVip()) {
logger.info("Bar#{} top:{} bot:{}", stick.getId(), top, bot);
}
for (int id = top; id <= bot; id++) {
StaffInfo staff = staffManager.getStaff(id - 1);
IntersectionSequence staffCrossings = crossings.get(staff);
if (staffCrossings == null) {
staffCrossings = new IntersectionSequence(
StickIntersection.byAbscissa);
crossings.put(staff, staffCrossings);
}
staffCrossings.add(new StickIntersection(
staff.intersection(stick), stick));
}
}
}
//---------------------//
// preciseIntersection //
//---------------------//
/**
* Compute the precise point where the vertical bar stick intersects
* the horizontal (staff) line.
*
* @param stick the vertical (bar) stick
* @param line the horizontal (staff) line
* @return the precise intersection point
*/
private Point2D preciseIntersection (Glyph stick,
LineInfo line)
{
Point2D startPoint = stick.getStartPoint(VERTICAL);
Point2D stopPoint = stick.getStopPoint(VERTICAL);
// First, get a rough intersection
Point2D pt = LineUtil.intersection(line.getEndPoint(LEFT),
line.getEndPoint(RIGHT),
startPoint, stopPoint);
// Second, get a precise ordinate
double y = line.yAt(pt.getX());
// Third, get a precise abscissa
double x;
if (y < startPoint.getY()) { // Above stick
double invSlope = stick.getInvertedSlope();
x = startPoint.getX() + ((y - startPoint.getY()) * invSlope);
} else if (y > stopPoint.getY()) { // Below stick
double invSlope = stick.getInvertedSlope();
x = stopPoint.getX() + ((y - stopPoint.getY()) * invSlope);
} else { // Within stick height
x = stick.getLine().xAtY(y);
}
return new Point2D.Double(x, y);
}
//----------------------//
// recheckBarCandidates //
//----------------------//
/**
* Have a closer look at so-called barSticks, now that the grid of
* staves has been fully defined, to get rid of spurious barline
* sticks.
*/
private void recheckBarCandidates ()
{
// Do not check barSticks already involved in side barlines
Set<Glyph> sideBars = getSideBarlineGlyphs();
filaments.removeAll(sideBars);
// Check the others, now using more strict checks
sheet.setBarsChecker(new BarsChecker(sheet, false));
sheet.getBarsChecker().checkCandidates(filaments);
// Purge barSticks collection
for (Iterator<Glyph> it = filaments.iterator(); it.hasNext();) {
Glyph stick = it.next();
if (!stick.isBar()) {
it.remove();
}
}
filaments.addAll(sideBars);
// Purge crossings accordingly
for (IntersectionSequence seq : crossings.values()) {
for (Iterator<StickIntersection> it = seq.iterator();
it.hasNext();) {
StickIntersection crossing = it.next();
if (!crossing.getStickAncestor().isBar()) {
///logger.info("Purging " + stickPos);
it.remove();
}
}
}
}
//-------------------//
// refineBarsEndings //
//-------------------//
/**
* If we have reliable bar lines, refine their ending points to the
* staff lines.
*
* @param system the system to process
*/
private void refineBarsEndings (SystemInfo system)
{
if (system.getBarAlignments() == null) {
return;
}
List<StaffInfo> staves = system.getStaves();
for (BarAlignment alignment : system.getBarAlignments()) {
int iStaff = -1;
for (StickIntersection crossing : alignment.getIntersections()) {
iStaff++;
if (crossing != null) {
StaffInfo staff = staves.get(iStaff);
Glyph stick = crossing.getStickAncestor();
// Left bar items have already been adjusted
BarInfo leftBar = staff.getBar(LEFT);
if ((leftBar != null)
&& leftBar.getSticksAncestors().contains(stick)) {
continue;
}
// Perform adjustment only when on bottom staff
Point2D stop = stick.getStopPoint(VERTICAL);
StaffInfo botStaff = staffManager.getStaffAt(stop);
if (botStaff == staff) {
Point2D start = stick.getStartPoint(VERTICAL);
StaffInfo topStaff = staffManager.getStaffAt(start);
stick.setEndingPoints(
preciseIntersection(stick,
topStaff.getFirstLine()),
preciseIntersection(stick,
botStaff.getLastLine()));
}
}
}
}
}
//-----------------------//
// retrieveBarCandidates //
//-----------------------//
/**
* Retrieve initial barline candidates.
*
* @throws Exception
*/
private void retrieveBarCandidates ()
throws Exception
{
BarsChecker barsChecker = new BarsChecker(sheet, true); // Rough
barsChecker.checkCandidates(filaments);
// Consider only sticks with a barline shape
for (Iterator<Glyph> it = filaments.iterator(); it.hasNext();) {
Glyph glyph = it.next();
if (glyph.getPartOf() != null || !glyph.isBar()) {
it.remove();
}
}
}
//-------------------//
// retrieveMajorBars //
//-------------------//
private void retrieveMajorBars (Collection<Glyph> oldGlyphs,
Collection<Glyph> newGlyphs)
throws Exception
{
// Build vertical filaments
if (oldGlyphs.isEmpty() && newGlyphs.isEmpty()) {
// Build filaments from scratch
buildVerticalFilaments();
} else {
// Apply modifications to filaments
// Removal
filaments.removeAll(oldGlyphs);
// Addition
filaments.addAll(newGlyphs);
// Purge non-active glyphs
for (Iterator<Glyph> it = filaments.iterator(); it.hasNext();) {
Glyph glyph = it.next();
if (!glyph.isActive()) {
logger.debug("Purging non-active {}", glyph);
it.remove();
}
}
}
// Retrieve rough barline candidates
retrieveBarCandidates();
// Assign bar candidates to intersected staves
populateCrossings();
// Retrieve left staff bars (they define systems) and right staff bars
retrieveStaffSideBars();
}
//------------------//
// retrievePartTops //
//------------------//
/**
* Retrieve, for each staff, the staff that begins its containing
* part.
*
* @return the (id of) part-starting staff for each staff
*/
private Integer[] retrievePartTops ()
{
staffManager.setPartTops(partTops =
new Integer[staffManager.getStaffCount()]);
for (StaffInfo staff : staffManager.getStaves()) {
///logger.info("Staff#" + staff.getId());
int bot = staff.getId();
BarInfo leftBar = staff.getBar(LEFT);
BarInfo rightBar = staff.getBar(RIGHT);
double staffLeft = staff.getAbscissa(LEFT);
IntersectionSequence staffCrossings = crossings.get(staff);
for (StickIntersection stickCrossing : staffCrossings) {
Glyph stick = stickCrossing.getStickAncestor();
if (!stick.isBar()) {
continue;
}
// Do not use left bar items (they define systems, not parts)
if ((leftBar != null)
&& leftBar.getSticksAncestors().contains(stick)) {
///logger.info("Skipping left side stick#" + stick.getId());
continue;
}
if (stickCrossing.x < staffLeft) {
// logger.info(
// "Too left " + stickPos.x + " vs " + staffLeft +
// " stick#" + stick.getId());
continue;
}
// Use right bar, if any, even if not anchored ...
// Or use a plain bar stick provided it is anchored on both sides
if ((rightBar != null && rightBar.getSticksAncestors().contains(
stick)) || (stick.getResult() == BarsChecker.BAR_PART_DEFINING)) {
Point2D start = stick.getStartPoint(VERTICAL);
StaffInfo topStaff = staffManager.getStaffAt(start);
int top = topStaff.getId();
for (int id = top; id <= bot; id++) {
if ((partTops[id - 1] == null) || (top < partTops[id - 1])) {
partTops[id - 1] = top;
}
}
}
}
}
return partTops;
}
//------------------//
// retrieveStaffBar //
//------------------//
/**
* Retrieve the (perhaps multi-bar) complete side bar, if any, of a
* given staff and record it within the staff.
*
* @param staff the given staff
* @param side proper horizontal side
*/
private void retrieveStaffBar (StaffInfo staff,
HorizontalSide side)
{
final IntersectionSequence staffCrossings = crossings.get(staff);
final int dir = (side == LEFT) ? 1 : (-1);
final double staffX = staff.getAbscissa(side);
final double xBreak = staffX + (dir * params.maxDistanceFromStaffSide);
final IntersectionSequence seq = new IntersectionSequence(
StickIntersection.byAbscissa);
BarInfo bar = null;
staff.setBar(side, null); // Reset
// Give first importance to long (inter-staff) sticks
for (boolean takeAllSticks : new boolean[]{false, true}) {
// Browse bar sticks from outside to inside of staff
for (StickIntersection crossing
: (dir > 0) ? staffCrossings : staffCrossings.descendingSet()) {
double x = crossing.x;
if ((dir * (xBreak - x)) < 0) {
break; // Speed up
}
if (takeAllSticks || isLong(crossing.getStickAncestor())) {
if (seq.isEmpty()) {
seq.add(crossing);
} else {
// Perhaps a pack of bars, check total width
double width = Math.max(Math.abs(x - seq.first().x),
Math.abs(x - seq.last().x));
if (((side == LEFT)
&& (width <= params.maxLeftBarPackWidth))
|| ((side == RIGHT)
&& (width <= params.maxRightBarPackWidth))) {
seq.add(crossing);
}
}
}
}
}
if (!seq.isEmpty()) {
seq.reduce(params.maxPosGap); // Merge sticks vertically
double barX = seq.last().x;
if ((dir * (barX - staffX)) <= params.maxDistanceFromStaffSide) {
bar = new BarInfo(seq.getSticks());
staff.setBar(side, bar);
staff.setAbscissa(side, barX);
} else {
if (logger.isDebugEnabled()) {
logger.info("Staff#{} {} discarded stick#{}",
staff.getId(), side,
seq.last().getStickAncestor().getId());
}
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Staff#{} no {} bar {}", staff.getId(), side,
Glyphs.toString(StickIntersection.sticksOf(
staffCrossings)));
}
}
logger.debug("Staff#{} {} bar: {}", staff.getId(), side, bar);
}
//-----------------------//
// retrieveStaffSideBars //
//-----------------------//
/**
* Retrieve staffs left bar (which define systems) and right bar.
*/
private void retrieveStaffSideBars ()
{
for (HorizontalSide side : HorizontalSide.values()) {
for (StaffInfo staff : staffManager.getStaves()) {
retrieveStaffBar(staff, side);
}
}
}
//-------------------//
// retrieveSystemBar //
//-------------------//
/**
* Merge bar sticks across staves of the same system, as long as
* they can be connected even indirectly via other sticks.
*
* @param system the system to process
* @param side the side to process
*
* @return the bar info for the system side, or null if no consistency could
* be ensured
*/
private BarInfo retrieveSystemBar (SystemInfo system,
HorizontalSide side)
{
// Horizontal sequence of bar sticks applied to the whole system side
Glyph[] seq = null;
for (StaffInfo staff : system.getStaves()) {
BarInfo bar = staff.getBar(side);
if (bar == null) {
return null; // We cannot ensure consistency
}
List<Glyph> barSticks = bar.getSticksAncestors();
Integer delta = null; // Delta on stick indices between 2 staves
int ib = 0; // Index on sticks
if (seq == null) {
seq = barSticks.toArray(new Glyph[barSticks.size()]);
} else {
// Loop on bar sticks
BarLoop:
for (ib = 0; ib < barSticks.size(); ib++) {
Glyph barFil = barSticks.get(ib);
// Check with sequence
for (int is = 0; is < seq.length; is++) {
if (seq[is].getAncestor() == barFil) {
// We have a pivot stick!
delta = is - ib;
break BarLoop;
}
}
}
if (delta != null) {
// Update sequence accordingly
int isMin = Math.min(0, delta);
int isBrk = Math.max(seq.length, barSticks.size() + delta);
if ((isMin < 0) || (isBrk > seq.length)) {
// Allocate new sequence (shifted and/or enlarged)
Glyph[] newSeq = new Glyph[isBrk - isMin];
System.arraycopy(seq, 0, newSeq, -isMin, seq.length);
seq = newSeq;
}
for (ib = 0; ib < barSticks.size(); ib++) {
Glyph barStick = barSticks.get(ib);
int is = (ib + delta) - isMin;
Glyph seqStick = seq[is];
if (seqStick != null) {
seqStick = seqStick.getAncestor();
if (seqStick != barStick) {
logger.debug("Including F{} to F{}",
barStick.getId(), seqStick.getId());
seqStick.stealSections(barStick);
}
} else {
seq[is] = barStick;
}
}
} else {
return null;
}
}
}
BarInfo systemBar = new BarInfo(seq);
system.setBar(side, systemBar);
return systemBar;
}
//--------------------//
// retrieveSystemTops //
//--------------------//
/**
* Retrieve for each staff the staff that begins its containing
* system.
*
* @return the (id of) system-starting staff for each staff
*/
private Integer[] retrieveSystemTops ()
{
staffManager.setSystemTops(systemTops =
new Integer[staffManager.getStaffCount()]);
for (StaffInfo staff : staffManager.getStaves()) {
int bot = staff.getId();
BarInfo bar = staff.getBar(LEFT);
if (bar != null) {
// We have a starting bar line
for (Glyph stick : bar.getSticksAncestors()) {
Point2D start = stick.getStartPoint(VERTICAL);
StaffInfo topStaff = staffManager.getStaffAt(start);
int top = topStaff.getId();
for (int id = top; id <= bot; id++) {
if ((systemTops[id - 1] == null)
|| (top < systemTops[id - 1])) {
systemTops[id - 1] = top;
}
}
}
} else {
// We have no starting bar line, so staff = part = system
systemTops[bot - 1] = bot;
}
}
return systemTops;
}
//--------------------//
// tryRangeConnection //
//--------------------//
/**
* Try to connect all systems in the provided range.
*
* @param range sublist of systems
*
* @return true if OK
*/
private boolean tryRangeConnection (List<SystemInfo> range)
{
final SystemInfo firstSystem = range.get(0);
final StaffInfo firstStaff = firstSystem.getFirstStaff();
final int topId = firstStaff.getId();
int idx = staffManager.getStaves()
.indexOf(firstStaff);
for (SystemInfo system : range) {
for (StaffInfo staff : system.getStaves()) {
systemTops[idx++] = topId;
}
}
logger.info("Staves connection from {} to {}", topId,
range.get(range.size() - 1).getLastStaff().getId());
return true;
}
//~ Inner Classes ----------------------------------------------------------
// //---------------------//
// // trySmartConnections //
// //---------------------//
// /**
// * Method to try system connections not based on local bar connections
// * @return true if connection decided
// */
// private boolean trySmartConnections ()
// {
// // Valuable hints:
// // - Significant differences in systems lengths
// // - System w/o left bar, while others have some
// // - Bar that significantly departs from a staff
//
// // Systems lengths
// List<Integer> lengths = new ArrayList<Integer>(systems.size());
//
// // Extrema
// int smallestLength = Integer.MAX_VALUE;
// int largestLength = Integer.MIN_VALUE;
//
// for (SystemInfo system : systems) {
// int length = system.getStaves()
// .size();
// lengths.add(length);
// smallestLength = Math.min(smallestLength, length);
// largestLength = Math.max(largestLength, length);
// }
//
// // If all systems are equal in length, there is nothing to try
// if (smallestLength == largestLength) {
// return false;
// }
//
// if ((2 * largestLength) == staffManager.getStaffCount()) {
// // Check that we can add up to largest size
// if (lengths.get(0) == largestLength) {
// return tryRangeConnection(systems.subList(1, lengths.size()));
// } else if (lengths.get(lengths.size() - 1) == largestLength) {
// return tryRangeConnection(
// systems.subList(0, lengths.size() - 1));
// }
// } else if ((3 * largestLength) == staffManager.getStaffCount()) {
// // Check that we can add up to largest size
// // TBD
// }
//
// return false;
// }
//-----------//
// Constants //
//-----------//
/**
* TODO: This collection of parameters is way too long! It should be
* carefully reduced!!!
*/
private static final class Constants
extends ConstantSet
{
//~ Instance fields ----------------------------------------------------
Constant.Ratio maxLengthRatio = new Constant.Ratio(
1.4,
"Maximum ratio in length for a run to be combined with an existing section");
// Constants specified WRT mean interline
// --------------------------------------
Scale.Fraction maxSectionThickness = new Scale.Fraction(
0.8,
"Maximum horizontal section thickness WRT interline");
Scale.Fraction maxFilamentThickness = new Scale.Fraction(
0.8,
"Maximum horizontal filament thickness WRT interline");
Scale.Fraction maxOverlapDeltaPos = new Scale.Fraction(
0.4,
"Maximum delta position between two overlapping filaments");
Scale.Fraction maxCoordGap = new Scale.Fraction(
0.5,
"Maximum delta coordinate for a gap between filaments");
Scale.Fraction maxPosGap = new Scale.Fraction(
0.2,
"Maximum delta abscissa for a gap between filaments");
Scale.Fraction maxSpace = new Scale.Fraction(
0.1,
"Maximum space between overlapping bar filaments");
Scale.Fraction maxBarCoordGap = new Scale.Fraction(
2,
"Maximum delta coordinate for a vertical gap between bars");
Scale.Fraction maxBarPosGap = new Scale.Fraction(
0.3,
"Maximum delta position for a vertical gap between bars");
Scale.Fraction minRunLength = new Scale.Fraction(
1.5,
"Minimum length for a vertical run to be considered");
Scale.Fraction minLongLength = new Scale.Fraction(
8,
"Minimum length for a long vertical bar");
Scale.Fraction maxDistanceFromStaffSide = new Scale.Fraction(
2,
"Max abscissa delta when looking for left or right side bars");
Scale.Fraction maxLeftBarPackWidth = new Scale.Fraction(
1.5,
"Max width of a pack of vertical barlines");
Scale.Fraction maxRightBarPackWidth = new Scale.Fraction(
1,
"Max width of a pack of vertical barlines");
Scale.Fraction maxSideDx = new Scale.Fraction(
.5,
"Max difference on theoretical bar abscissa");
Scale.Fraction maxLineExtension = new Scale.Fraction(
.5,
"Max extension of line beyond staff bar");
Scale.Fraction minBarChunkHeight = new Scale.Fraction(
1,
"Min height of a bar chunk past system boundaries");
Scale.Fraction maxAlignmentDistance = new Scale.Fraction(
0.5,
"Max horizontal shift between aligned bars");
Constant.Ratio minAlignmentRatio = new Constant.Ratio(
0.5,
"Minimum percentage of mapped staves in a bar alignment ");
// Constants for display
//
Constant.Boolean showVerticalLines = new Constant.Boolean(
false,
"Should we display the vertical lines?");
Constant.Boolean showTangents = new Constant.Boolean(
false,
"Should we show filament ending tangents?");
Constant.Double splineThickness = new Constant.Double(
"thickness", 0.5,
"Stroke thickness to draw filaments curves");
Constant.Boolean smartStavesConnections = new Constant.Boolean(
true,
"(beta) Should we try smart staves connections into systems?");
// Constants for debugging
//
Constant.String verticalVipSections = new Constant.String(
"",
"(Debug) Comma-separated list of VIP sections");
}
//------------//
// Parameters //
//------------//
/**
* Class {@code Parameters} gathers all constants related to vertical
* frames.
*/
private static class Parameters
{
//~ Static fields/initializers -----------------------------------------
/** Usual logger utility. */
private static final Logger logger = LoggerFactory.getLogger(Parameters.class);
//~ Instance fields ----------------------------------------------------
/** Maximum delta abscissa for a gap between filaments. */
final int maxPosGap;
/** Minimum run length for vertical lag. */
final int minRunLength;
/** Used for section junction policy. */
final double maxLengthRatio;
/** Minimum for long vertical stick bars. */
final int minLongLength;
/** Maximum distance between a bar and the staff side. */
final int maxDistanceFromStaffSide;
/** Maximum width for a pack of bars on left side. */
final int maxLeftBarPackWidth;
/** Maximum width for a pack of bars on right side. */
final int maxRightBarPackWidth;
/** Max difference on theoretical bar abscissa. */
final int maxSideDx;
/** Max extension of line beyond staff bar. */
final int maxLineExtension;
/** Min height to detect a bar going past a staff. */
final int minBarChunkHeight;
/** Maximum abscissa shift for bar alignments. */
final double maxAlignmentDistance;
/** Maximum delta position for a vertical gap between bars. */
final int maxBarPosGap;
/** Maximum delta coordinate for a vertical gap between bars. */
final int maxBarCoordGap;
// Debug
final List<Integer> vipSections;
//~ Constructors -------------------------------------------------------
/**
* Creates a new Parameters object.
*
* @param scale the scaling factor
*/
public Parameters (Scale scale)
{
maxPosGap = scale.toPixels(constants.maxPosGap);
minRunLength = scale.toPixels(constants.minRunLength);
maxLengthRatio = constants.maxLengthRatio.getValue();
minLongLength = scale.toPixels(constants.minLongLength);
maxDistanceFromStaffSide = scale.toPixels(
constants.maxDistanceFromStaffSide);
maxLeftBarPackWidth = scale.toPixels(constants.maxLeftBarPackWidth);
maxRightBarPackWidth = scale.
toPixels(constants.maxRightBarPackWidth);
maxSideDx = scale.toPixels(constants.maxSideDx);
maxLineExtension = scale.toPixels(constants.maxLineExtension);
minBarChunkHeight = scale.toPixels(constants.minBarChunkHeight);
maxAlignmentDistance = scale.
toPixels(constants.maxAlignmentDistance);
maxBarPosGap = scale.toPixels(constants.maxBarPosGap);
maxBarCoordGap = scale.toPixels(constants.maxBarCoordGap);
// VIPs
vipSections = VipUtil.decodeIds(
constants.verticalVipSections.getValue());
if (logger.isDebugEnabled()) {
Main.dumping.dump(this);
}
if (!vipSections.isEmpty()) {
logger.info("Vertical VIP sections: {}", vipSections);
}
}
}
}