//----------------------------------------------------------------------------// // // // L i n e 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.run.Orientation; import static omr.run.Orientation.*; import omr.run.Run; import omr.run.RunsTable; import omr.run.RunsTableFactory; import omr.sheet.Scale; import omr.sheet.Sheet; import omr.sheet.Skew; import omr.sheet.SystemInfo; import omr.ui.Colors; import omr.ui.util.UIUtil; import static omr.util.HorizontalSide.*; import omr.util.Predicate; import omr.util.StopWatch; import omr.util.VipUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.Stroke; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Class {@code LinesRetriever} retrieves the staff lines of a sheet. * * @author Hervé Bitteur */ public class LinesRetriever 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(LinesRetriever.class); //~ Instance fields -------------------------------------------------------- // /** related sheet */ private final Sheet sheet; /** Related scale */ private final Scale scale; /** Scale-dependent constants for horizontal stuff */ private final Parameters params; /** Lag of horizontal runs */ private Lag hLag; /** Filaments factory */ private FilamentsFactory factory; /** Long horizontal filaments found, non sorted */ private final List<LineFilament> filaments = new ArrayList<>(); /** Second collection of filaments */ private List<LineFilament> secondFilaments; /** Discarded filaments */ private List<LineFilament> discardedFilaments; /** Global slope of the sheet */ private double globalSlope; /** Companion in charge of clusters of main interline */ private ClustersRetriever clustersRetriever; /** Companion in charge of clusters of second interline, if any */ private ClustersRetriever secondClustersRetriever; /** Companion in charge of bar lines */ private final BarsRetriever barsRetriever; /** Too-short horizontal runs */ private RunsTable shortHoriTable; /** For runs display, if any */ private final RunsViewer runsViewer; //~ Constructors ----------------------------------------------------------- // //----------------// // LinesRetriever // //----------------// /** * Retrieve the frames of all staff lines. * * @param sheet the sheet to process * @param barsRetriever the companion in charge of bars */ public LinesRetriever (Sheet sheet, BarsRetriever barsRetriever) { this.sheet = sheet; this.barsRetriever = barsRetriever; runsViewer = (Main.getGui() != null) ? new RunsViewer(sheet, this, barsRetriever) : null; scale = sheet.getScale(); params = new Parameters(scale); } //~ Methods ---------------------------------------------------------------- //----------// // buildLag // //----------// /** * Build the underlying lag, out of the provided runs table. * * @param wholeVertTable the provided table of all (vertical) runs * @param showRuns (debug) true to create intermediate views on runs * @return the vertical runs too long to be part of any staff line */ public RunsTable buildLag (RunsTable wholeVertTable, boolean showRuns, RunsTable wholeHorzTable) { hLag = new BasicLag("hLag", Orientation.HORIZONTAL); // Create filament factory try { factory = new FilamentsFactory( scale, sheet.getNest(), Orientation.HORIZONTAL, LineFilament.class); } catch (Exception ex) { logger.warn("Cannot create lines filament factory", ex); } // start JLP RunsTable longHorizTable = new RunsTable( "longHorizontal", HORIZONTAL, new Dimension(sheet.getWidth(), sheet.getHeight())); // Remove runs whose height is larger than line thickness RunsTable shortHorizTable = wholeHorzTable.copy("shortHorizontal") .purge(new Predicate<Run>() { @Override public final boolean check (Run run) { //return run.getLength() > params.maxVerticalRunLength; //TODO: create parameter for integer value below return run.getLength() > 500; } }, longHorizTable); if (showRuns) { runsViewer.display(longHorizTable); System.out.println(longHorizTable.getRunDispersion()); //wholeHorzTable.printRunLengthStats(200); // 200 @400dpi = 1/2 " wholeHorzTable.printRunStats(200); logger.info(longHorizTable.getRunDispersion()); runsViewer.display(shortHorizTable); } // create a vertical table derived from the long horizontal // to "include" with the short vertical table // below. RunsTable fromLongHorizontal = new RunsTableFactory( VERTICAL, longHorizTable.getBuffer(),0 ).createTable("transitionTable"); // end JLP // To record the purged vertical runs RunsTable longVertTable = new RunsTable( "long-vert", VERTICAL, new Dimension(sheet.getWidth(), sheet.getHeight())); // Remove runs whose height is larger than line thickness RunsTable shortVertTable = wholeVertTable.copy("short-vert").purge( new Predicate<Run>() { @Override public final boolean check (Run run) { return run.getLength() > params.maxVerticalRunLength; } }, longVertTable); if (showRuns) { runsViewer.display(longVertTable); runsViewer.display(shortVertTable); } // Build table of long horizontal runs RunsTable wholeHoriTable = new RunsTableFactory( HORIZONTAL, shortVertTable.getBuffer(), 0).createTable("whole-hori"); // To record the purged horizontal runs shortHoriTable = new RunsTable( "short-hori", HORIZONTAL, new Dimension(sheet.getWidth(), sheet.getHeight())); RunsTable longHoriTable = wholeHoriTable.copy("long-hori").purge( new Predicate<Run>() { @Override public final boolean check (Run run) { return run.getLength() < params.minRunLength; } }, shortHoriTable); // jlp start // try combining both tables so long horizontals fill in what was removed // by long verticals longHoriTable.include(longHorizTable); // jlp end if (showRuns) { runsViewer.display(shortHoriTable); runsViewer.display(longHoriTable); } // Build the horizontal hLag with the long horizontal runs // (short horizontal runs will be added later) SectionsBuilder sectionsBuilder = new SectionsBuilder( hLag, new JunctionRatioPolicy(params.maxLengthRatio)); sectionsBuilder.createSections(longHoriTable); sheet.setHorizontalLag(hLag); setVipSections(); return longVertTable; } //---------------// // completeLines // //---------------// /** * Complete the retrieved staff lines whenever possible with * filaments and short sections left over. * * <p><b>Synopsis:</b> * <pre> * + includeDiscardedFilaments * + canIncludeFilament(fil1, fil2) * + createShortSections() * + includeSections() * + canIncludeSection(fil, sct) * </pre> */ public void completeLines () { StopWatch watch = new StopWatch("completeLines"); try { // Browse discarded filaments for possible inclusion watch.start("include discarded filaments"); includeDiscardedFilaments(); // Build sections out of shortHoriTable (too short horizontal runs) watch.start("create shortSections"); List<Section> shortSections = createShortSections(); // Dispatch sections into thick & thin ones watch.start( "dispatching " + shortSections.size() + " thick / thin"); List<Section> thickSections = new ArrayList<>(); List<Section> thinSections = new ArrayList<>(); for (Section section : shortSections) { if (section.getWeight() > params.maxThinStickerWeight) { thickSections.add(section); } else { thinSections.add(section); } } // First, consider thick sections and update geometry watch.start("include " + thickSections.size() + " thick stickers"); includeSections(thickSections, true); // Second, consider thin sections w/o updating the geometry watch.start("include " + thinSections.size() + " thin stickers"); includeSections(thinSections, false); // Update system coordinates for (SystemInfo system : sheet.getSystems()) { system.updateCoordinates(); } } finally { if (constants.printWatch.getValue()) { watch.print(); } } } //-------------// // renderItems // //-------------// /** * Render the filaments, their ending tangents, their combs * * @param g graphics context */ @Override public void renderItems (Graphics2D g) { final Stroke oldStroke = UIUtil.setAbsoluteStroke(g, 1f); final Color oldColor = g.getColor(); g.setColor(Colors.ENTITY_MINOR); // Combs stuff? if (constants.showCombs.isSet()) { if (clustersRetriever != null) { clustersRetriever.renderItems(g); } if (secondClustersRetriever != null) { secondClustersRetriever.renderItems(g); } } // Filament lines? if (constants.showHorizontalLines.isSet()) { List<LineFilament> allFils = new ArrayList<>(filaments); if (secondFilaments != null) { allFils.addAll(secondFilaments); } for (Filament filament : allFils) { filament.renderLine(g); } // Draw tangent at each ending point? if (constants.showTangents.isSet()) { g.setColor(Colors.TANGENT); double dx = sheet.getScale().toPixels(constants.tangentLg); for (Filament filament : allFils) { Point2D p = filament.getStartPoint(HORIZONTAL); double der = filament.slopeAt(p.getX(), HORIZONTAL); g.draw( new Line2D.Double( p.getX(), p.getY(), p.getX() - dx, p.getY() - (der * dx))); p = filament.getStopPoint(HORIZONTAL); der = filament.slopeAt(p.getX(), HORIZONTAL); g.draw( new Line2D.Double( p.getX(), p.getY(), p.getX() + dx, p.getY() + (der * dx))); } } } g.setStroke(oldStroke); g.setColor(oldColor); } //---------------// // retrieveLines // //---------------// /** * Organize the long and thin horizontal sections into filaments * (glyphs) that will be good candidates for staff lines. * <ol> * <li>First, retrieve long horizontal sections and merge them into * filaments.</li> * <li>Second, detect series of filaments regularly spaced and aggregate * them into clusters of lines (as staff candidates). </li> * </ol> * * <p><b>Synopsis:</b> * <pre> * + filamentFactory.retrieveFilaments() * + retrieveGlobalSlope() * + clustersRetriever.buildInfo() * + secondClustersRetriever.buildInfo() * + buildStaves() * </pre> */ public void retrieveLines () { StopWatch watch = new StopWatch("retrieveLines"); try { // Retrieve filaments out of merged long sections watch.start("retrieveFilaments"); for (Glyph fil : factory.retrieveFilaments( hLag.getSections(), true)) { filaments.add((LineFilament) fil); } // Compute global slope out of longest filaments watch.start("retrieveGlobalSlope"); globalSlope = retrieveGlobalSlope(); sheet.setSkew(new Skew(globalSlope, sheet)); logger.info("{}Global slope: {}", sheet.getLogPrefix(), (float) globalSlope); // Retrieve regular patterns of filaments and pack them into clusters clustersRetriever = new ClustersRetriever( sheet, filaments, scale.getInterline(), Colors.COMB); watch.start("clustersRetriever"); discardedFilaments = clustersRetriever.buildInfo(); // Check for a second interline Integer secondInterline = scale.getSecondInterline(); if (secondInterline != null && !discardedFilaments.isEmpty()) { secondFilaments = discardedFilaments; Collections.sort(secondFilaments, Glyph.byId); logger.info("{}Searching clusters with secondInterline: {}", sheet.getLogPrefix(), secondInterline); secondClustersRetriever = new ClustersRetriever( sheet, secondFilaments, secondInterline, Colors.COMB_MINOR); watch.start("secondClustersRetriever"); discardedFilaments = secondClustersRetriever.buildInfo(); } logger.debug("Discarded filaments: {}", Glyphs.toString( discardedFilaments)); // Convert clusters into staves watch.start("BuildStaves"); buildStaves(); } finally { if (constants.printWatch.getValue()) { watch.print(); } } } //-------------// // buildStaves // //-------------// /** * Register line clusters as staves */ private void buildStaves () { // Accumulate all clusters, and sort them by ordinate List<LineCluster> allClusters = new ArrayList<>(); allClusters.addAll(clustersRetriever.getClusters()); if (secondClustersRetriever != null) { allClusters.addAll(secondClustersRetriever.getClusters()); } Collections.sort(allClusters, clustersRetriever.ordinateComparator); // Populate the staff manager StaffManager staffManager = sheet.getStaffManager(); int staffId = 0; staffManager.reset(); for (LineCluster cluster : allClusters) { logger.debug(cluster.toString()); List<LineInfo> lines = new ArrayList<LineInfo>(cluster.getLines()); double left = Integer.MAX_VALUE; double right = Integer.MIN_VALUE; for (LineInfo line : lines) { left = Math.min(left, line.getEndPoint(LEFT).getX()); right = Math.max(right, line.getEndPoint(RIGHT).getX()); } StaffInfo staff = new StaffInfo( ++staffId, left, right, new Scale(cluster.getInterline(), scale.getMainFore()), lines); staffManager.addStaff(staff); } staffManager.computeStaffLimits(); // Polish staff lines for (StaffInfo staff : staffManager.getStaves()) { staff.getArea(); for (LineInfo l : staff.getLines()) { FilamentLine line = (FilamentLine) l; line.fil.polishCurvature(); } } } //------------// // canInclude // //------------// /** * Check whether the staff line filament could include the provided * entity (section or filament) * * @param filament the staff line filament * @param idStr (debug) entity id * @param isVip true if entity is vip * @param box the entity contour box * @param center the entity center * @param candidate the section or glyph candidate * @return true if OK, false otherwise */ private boolean canInclude (LineFilament filament, boolean isVip, String idStr, Rectangle box, Point center, Object candidate) { // For VIP debugging String vips = null; if (isVip) { vips = idStr + ": "; // BP here! } // Check entity thickness int height = box.height; if (height > params.maxStickerThickness) { if (logger.isDebugEnabled() || isVip) { logger.info("{}SSS height:{} vs {}", vips, height, params.maxStickerThickness); } return false; } // Check entity center gap with theoretical line double yFil = filament.getPositionAt(center.x, HORIZONTAL); double dy = Math.abs(yFil - center.y); double gap = dy - (scale.getMainFore() / 2.0); if (gap > params.maxStickerGap) { if (logger.isDebugEnabled() || isVip) { logger.info("{}GGG gap:{} vs {}", vips, (float) gap, (float) params.maxStickerGap); } return false; } // Check max extension from theoretical line double extension = Math.max( Math.abs(yFil - box.y), Math.abs((box.y + height) - yFil)); if (extension > params.maxStickerExtension) { if (logger.isDebugEnabled() || isVip) { logger.info("{}XXX ext:{} vs {}", vips, (float) extension, params.maxStickerExtension); } return false; } // Check resulting thickness double thickness = 0; if (candidate instanceof Section) { thickness = Glyphs.getThicknessAt( center.x, HORIZONTAL, (Section) candidate, filament); } else if (candidate instanceof Glyph) { thickness = Glyphs.getThicknessAt( center.x, HORIZONTAL, (Glyph) candidate, filament); } if (thickness > params.maxStickerThickness) { if (logger.isDebugEnabled() || isVip) { logger.info("{}RRR thickness:{} vs {}", vips, (float) thickness, params.maxStickerExtension); } return false; } if (logger.isDebugEnabled() || isVip) { logger.info("{}---", vips); } return true; } //--------------------// // canIncludeFilament // //--------------------// /** * Check whether the staff line filament could include the candidate * filament * * @param filament the staff line filament * @param fil the candidate filament * @return true if OK */ private boolean canIncludeFilament (LineFilament filament, Filament fil) { return canInclude( filament, fil.isVip(), "Fil#" + fil.getId(), fil.getBounds(), fil.getCentroid(), fil); } //-------------------// // canIncludeSection // //-------------------// /** * Check whether the staff line filament could include the candidate * section * * @param filament the staff line filament * @param section the candidate sticker * @return true if OK, false otherwise */ private boolean canIncludeSection (LineFilament filament, Section section) { return canInclude( filament, section.isVip(), "Sct#" + section.getId(), section.getBounds(), section.getCentroid(), section); } //---------------------// // createShortSections // //---------------------// /** * Build horizontal sections out of shortHoriTable runs * * @return the list of created sections */ private List<Section> createShortSections () { // Note the current section id sheet.setLongSectionMaxId(hLag.getLastVertexId()); // Augment the horizontal hLag with the short sections SectionsBuilder sectionsBuilder = new SectionsBuilder( hLag, new JunctionRatioPolicy(params.maxLengthRatioShort)); List<Section> shortSections = sectionsBuilder.createSections( shortHoriTable); setVipSections(); return shortSections; } //---------------------------// // includeDiscardedFilaments // //---------------------------// /** * Last attempt to include discarded filaments to retrieved staff lines */ private void includeDiscardedFilaments () { // Sort these discarded filaments by top ordinate Collections.sort(discardedFilaments, Filament.topComparator); int iMin = 0; int iMax = discardedFilaments.size() - 1; for (SystemInfo system : sheet.getSystems()) { for (StaffInfo staff : system.getStaves()) { for (LineInfo l : staff.getLines()) { FilamentLine line = (FilamentLine) l; LineFilament filament = line.fil; Rectangle lineBox = filament.getBounds(); lineBox.grow(0, scale.getMainFore()); double minX = filament.getStartPoint(HORIZONTAL).getX(); double maxX = filament.getStopPoint(HORIZONTAL).getX(); int minY = lineBox.y; int maxY = lineBox.y + lineBox.height; for (int i = iMin; i <= iMax; i++) { Filament fil = discardedFilaments.get(i); if (fil.getPartOf() != null) { continue; } int firstPos = fil.getBounds().y; if (firstPos < minY) { iMin = i; continue; } if (firstPos > maxY) { break; } Point center = fil.getCentroid(); if ((center.x >= minX) && (center.x <= maxX)) { if (canIncludeFilament(filament, fil)) { filament.stealSections(fil); } } } } } barsRetriever.adjustStaffLines(system); } } //-----------------// // includeSections // //-----------------// /** * Include "sticker" sections into their related lines, when * applicable * * @param sections List of sections that are stickers candidates * @param update should we update the line geometry with stickers (this * should be limited to large sections). */ private void includeSections (List<Section> sections, boolean update) { // Sections are sorted according to their top run (Y first and X second) int iMin = 0; int iMax = sections.size() - 1; // Inclusion on the fly would imply recomputation of filament at each // section inclusion. So we need to retrieve all "stickers" for a given // staff line, and perform a global inclusion at the end only. for (SystemInfo system : sheet.getSystems()) { for (StaffInfo staff : system.getStaves()) { for (LineInfo l : staff.getLines()) { FilamentLine line = (FilamentLine) l; LineFilament fil = line.fil; Rectangle lineBox = fil.getBounds(); lineBox.grow(0, scale.getMainFore()); double minX = fil.getStartPoint(HORIZONTAL).getX(); double maxX = fil.getStopPoint(HORIZONTAL).getX(); int minY = lineBox.y; int maxY = lineBox.y + lineBox.height; List<Section> stickers = new ArrayList<>(); for (int i = iMin; i <= iMax; i++) { Section section = sections.get(i); if (section.isGlyphMember()) { continue; } int firstPos = section.getFirstPos(); if (firstPos < minY) { iMin = i; continue; } if (firstPos > maxY) { break; } Point center = section.getCentroid(); if ((center.x >= minX) && (center.x <= maxX)) { if (canIncludeSection(fil, section)) { stickers.add(section); } } } // Actually include the retrieved stickers? for (Section section : stickers) { if (update) { fil.addSection(section); } else { section.setGlyph(fil); } } } } barsRetriever.adjustStaffLines(system); } } //---------------------// // retrieveGlobalSlope // //---------------------// private double retrieveGlobalSlope () { // Use the top longest filaments to determine slope final double ratio = params.topRatioForSlope; final int topCount = Math.max( 1, (int) Math.rint(filaments.size() * ratio)); double slopes = 0; Collections.sort( filaments, Glyphs.getReverseLengthComparator(HORIZONTAL)); for (int i = 0; i < topCount; i++) { Filament fil = filaments.get(i); Point2D start = fil.getStartPoint(HORIZONTAL); Point2D stop = fil.getStopPoint(HORIZONTAL); slopes += ((stop.getY() - start.getY()) / (stop.getX() - start.getX())); } return slopes / topCount; } //----------------// // setVipSections // //----------------// private void setVipSections () { // Debug sections VIPs for (int id : params.vipSections) { Section sect = hLag.getVertexById(id); if (sect != null) { sect.setVip(); logger.info("Horizontal vip section: {}", sect); } } } //~ Inner Classes ---------------------------------------------------------- //-----------// // Constants // //-----------// private static final class Constants extends ConstantSet { //~ Instance fields ---------------------------------------------------- final Constant.Ratio topRatioForSlope = new Constant.Ratio( 0.1, "Percentage of top filaments used to retrieve global slope"); // Constants for building horizontal sections // ------------------------------------------ final Constant.Ratio maxLengthRatio = new Constant.Ratio( 1.5, "Maximum ratio in length for a run to be combined with an existing section"); final Constant.Ratio maxLengthRatioShort = new Constant.Ratio( 3.0, "Maximum ratio in length for a short run to be combined with an existing section"); // Constants specified WRT *maximum* line thickness (scale.getmaxFore()) // ---------------------------------------------- // Should be 1.0, unless ledgers are thicker than staff lines final Constant.Ratio ledgerThickness = new Constant.Ratio( 1.2, // 2.0, "Ratio of ledger thickness vs staff line MAXIMUM thickness"); final Constant.Ratio stickerThickness = new Constant.Ratio( 1.0, //1.2, "Ratio of sticker thickness vs staff line MAXIMUM thickness"); // Constants specified WRT mean line thickness // ------------------------------------------- // final Scale.LineFraction maxStickerGap = new Scale.LineFraction( 0.5, "Maximum vertical gap between sticker and closest line side"); final Scale.LineFraction maxStickerExtension = new Scale.LineFraction( 1.2, "Maximum vertical sticker extension from line"); final Scale.AreaFraction maxThinStickerWeight = new Scale.AreaFraction( 0.06, "Maximum weight for a thin sticker (w/o impact on line geometry)"); // Constants specified WRT mean interline // -------------------------------------- final Scale.Fraction minRunLength = new Scale.Fraction( 1.0, "Minimum length for a horizontal run to be considered"); // Constants for display // --------------------- final Constant.Boolean showHorizontalLines = new Constant.Boolean( true, "Should we display the horizontal lines?"); final Scale.Fraction tangentLg = new Scale.Fraction( 1, "Typical length to display tangents at ending points"); final Constant.Boolean printWatch = new Constant.Boolean( false, "Should we print out the stop watch?"); final Constant.Boolean showTangents = new Constant.Boolean( false, "Should we show filament ending tangents?"); // final Constant.Boolean showCombs = new Constant.Boolean( false, "Should we show staff lines combs?"); // Constants for debugging // ----------------------- final Constant.String horizontalVipSections = new Constant.String( "", "(Debug) Comma-separated list of VIP sections"); } //------------// // Parameters // //------------// /** * Class {@code Parameters} gathers all pre-scaled constants * related to horizontal frames. */ private static class Parameters { //~ Instance fields ---------------------------------------------------- /** Maximum vertical run length (to exclude too long vertical runs) */ final int maxVerticalRunLength; /** Minimum run length for horizontal lag */ final int minRunLength; /** Used for section junction policy */ final double maxLengthRatio; /** Used for section junction policy for short sections */ final double maxLengthRatioShort; /** Percentage of top filaments used to retrieve global slope */ final double topRatioForSlope; /** Maximum sticker thickness */ final int maxStickerThickness; /** Maximum sticker extension */ final int maxStickerExtension; /** Maximum vertical gap between a sticker and the closest line side */ final double maxStickerGap; /** Maximum weight for a thin sticker */ final int maxThinStickerWeight; // Debug final List<Integer> vipSections; //~ Constructors ------------------------------------------------------- /** * Creates a new Parameters object. * * @param scale the scaling factor */ public Parameters (Scale scale) { // Special parameters maxVerticalRunLength = (int) Math.rint( scale.getMaxFore() * constants.ledgerThickness.getValue()); maxStickerThickness = (int) Math.rint( scale.getMaxFore() * constants.stickerThickness.getValue()); // Others minRunLength = scale.toPixels(constants.minRunLength); maxLengthRatio = constants.maxLengthRatio.getValue(); maxLengthRatioShort = constants.maxLengthRatioShort.getValue(); topRatioForSlope = constants.topRatioForSlope.getValue(); maxStickerGap = scale.toPixelsDouble(constants.maxStickerGap); maxThinStickerWeight = scale.toPixels( constants.maxThinStickerWeight); maxStickerExtension = (int) Math.ceil( scale.toPixelsDouble(constants.maxStickerExtension)); // VIPs vipSections = VipUtil.decodeIds( constants.horizontalVipSections.getValue()); if (logger.isDebugEnabled()) { Main.dumping.dump(this); } if (!vipSections.isEmpty()) { logger.info("Horizontal VIP sections: {}", vipSections); } } } }