omr.sheet.SkewBuilder.java Source code

Java tutorial

Introduction

Here is the source code for omr.sheet.SkewBuilder.java

Source

//----------------------------------------------------------------------------//
//                                                                            //
//                           S k e w B u i l d e r                            //
//                                                                            //
//----------------------------------------------------------------------------//
// <editor-fold defaultstate="collapsed" desc="hdr">                          //
//  Copyright (C) Herve Bitteur 2000-2010. 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.sheet;

import omr.Main;

import omr.constant.Constant;
import omr.constant.ConstantSet;

import omr.glyph.GlyphLag;
import omr.glyph.GlyphSection;
import omr.glyph.facets.BasicStick;
import omr.glyph.facets.Glyph;
import omr.glyph.facets.Stick;

import omr.lag.HorizontalOrientation;
import omr.lag.JunctionRatioPolicy;
import omr.lag.SectionsBuilder;
import omr.lag.ui.LagView;
import omr.lag.ui.RunBoard;
import omr.lag.ui.ScrollLagView;
import omr.lag.ui.SectionBoard;
import omr.lag.ui.SectionView;

import omr.log.Logger;

import omr.sheet.picture.ImageFormatException;
import omr.sheet.picture.Picture;
import omr.sheet.ui.PixelBoard;

import omr.step.Step;
import omr.step.StepException;

import omr.stick.StickSection;

import omr.ui.BoardsPane;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartFrame;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.RefineryUtilities;

import java.awt.Color;
import java.awt.Dimension;
import java.util.*;

import javax.swing.WindowConstants;

/**
 * Class <code>SkewBuilder</code> computes the skew angle of a given sheet
 * picture.
 *
 * @see Skew
 *
 * @author Herv Bitteur
 */
public class SkewBuilder {
    //~ Static fields/initializers ---------------------------------------------

    /** Specific application parameters */
    private static final Constants constants = new Constants();

    /** Usual logger utility */
    private static final Logger logger = Logger.getLogger(SkewBuilder.class);

    //~ Instance fields --------------------------------------------------------

    /** (Skewed) lag of horizontal significant runs */
    private GlyphLag sLag;

    /** Sticks */
    private List<Stick> sticks = new ArrayList<Stick>();

    /** Sheet for which computation is to be done */
    private Sheet sheet;

    /** The target of this computation */
    private double angle;

    /** Skew slope as computed */
    private double slope;

    /** Length Threshold, for slope computation */
    private int lengthThreshold;

    /** Maximum accetable section thickness */
    private int maxThickness;

    /** Minimum acceptable section length */
    private int minSectionLength;

    //~ Constructors -----------------------------------------------------------

    //-------------//
    // SkewBuilder //
    //-------------//
    /**
     * This method is used to retrieve the skew angle in the hard way, by
     * processing the picture pixels.
     *
     * @param sheet the sheet to process
     */
    public SkewBuilder(Sheet sheet) {
        this.sheet = sheet;
    }

    //~ Methods ----------------------------------------------------------------

    //-----------//
    // buildInfo //
    //-----------//
    /**
     * Compute the skew of the sheet picture
     *
     * @return the skew info
     * @exception StepException to stop processing if needed
     */
    public Skew buildInfo() throws StepException {
        // Needed out for previous steps
        Picture picture = sheet.getPicture();
        Scale scale = sheet.getScale();

        // Parameters
        minSectionLength = scale.toPixels(constants.minSectionLength);

        // Retrieve the horizontal lag of runs
        sLag = new GlyphLag("sLag", StickSection.class, new HorizontalOrientation());

        SectionsBuilder<GlyphLag, GlyphSection> lagBuilder;
        lagBuilder = new SectionsBuilder<GlyphLag, GlyphSection>(sLag,
                new JunctionRatioPolicy(constants.maxHeightRatio.getValue())); // maxHeightRatio
        lagBuilder.createSections(picture, scale.toPixels(constants.minRunLength)); // minRunLength

        // Detect long sticks
        maxThickness = scale.mainFore();
        detectSticks();

        if (logger.isFineEnabled()) {
            logger.fine("angle=" + angle);
        }

        // Produce histogram of slopes
        if (constants.plotting.getValue()) {
            writePlot();
        }

        logger.info("Skew angle is " + (float) angle + " radians");

        // Display the resulting lag
        if (constants.displayFrame.getValue() && (Main.getGui() != null)) {
            displayFrame();
        }

        // De-skew the picture if necessary
        if (Math.abs(angle) > constants.maxSkewAngle.getValue()) {
            try {
                picture.rotate(-angle);
                sheet.getBench().recordImageDimension(picture.getWidth(), picture.getHeight());
            } catch (ImageFormatException ex) {
                throw new StepException(ex);
            }
        } else {
            logger.fine("No image rotation needed.");
        }

        // Report the computed info
        return new Skew(angle);
    }

    //--------------//
    // displayChart //
    //--------------//
    /**
     * Build and display the slope histogram of lengthy horizontal sticks
     */
    public void displayChart() {
        writePlot();
    }

    //--------------//
    // isMajorChunk //
    //--------------//
    private boolean isMajorChunk(StickSection section) {
        // Check section length
        // Check /quadratic mean/ section thickness
        int length = section.getLength();
        boolean result = (length >= minSectionLength) && ((section.getWeight() / length) <= maxThickness);

        return result;
    }

    //---------------//
    // areCompatible //
    //---------------//
    private boolean areCompatible(StickSection left, StickSection right) {
        // Make sure that two sections can be combined into a stick.
        // Tests are based on the angle between them,
        boolean result = Math.abs(left.getLine().getSlope() - right.getLine().getSlope()) <= constants.maxDeltaSlope
                .getValue();

        return result;
    }

    //---------------//
    // detectSticks //
    //---------------//
    private void detectSticks() {
        Stick stick;

        // Try to aggregate sections into sticks.  Visit all sections of the lag
        for (GlyphSection s : sLag.getVertices()) {
            StickSection section = (StickSection) s;

            if (logger.isFineEnabled()) {
                logger.fine(section.toString());
            }

            // We consider only significant chunks
            if (isMajorChunk(section)) {
                // Is this chunk already part of a stick ?
                if (section.getGlyph() != null) {
                    // If so, reuse the stick
                    stick = (Stick) section.getGlyph();
                } else {
                    // Otherwise, start a brand new stick
                    stick = new BasicStick(sheet.getInterline());

                    // Include this section in the stick list
                    stick.addSection(section, Glyph.Linking.LINK_BACK);

                    // Register the stick in containing lag
                    // Store this new stick into the stick table
                    sticks.add((Stick) sLag.addGlyph(stick));
                }

                // Now, from this stick section, Look at following connected
                // chunks
                for (GlyphSection gs : section.getTargets()) {
                    StickSection lnkSection = (StickSection) gs;

                    if (isMajorChunk(lnkSection) && areCompatible(section, lnkSection)) {
                        // If this section is already part of (another) stick,
                        // then merge this other stick with ours
                        if (lnkSection.getGlyph() != null) {
                            // Add the content of the other stick
                            if (lnkSection.getGlyph() != stick) {
                                stick.addGlyphSections(lnkSection.getGlyph(), Glyph.Linking.LINK_BACK);
                                sticks.remove(lnkSection.getGlyph());
                            }
                        } else {
                            // Let's add this section to the stick
                            stick.addSection(lnkSection, Glyph.Linking.LINK_BACK);
                        }
                    }
                }

                if (logger.isFineEnabled()) {
                    stick.dump();
                }
            }
        }

        // Now process these sticks
        if (!sticks.isEmpty()) {
            // Sort the sticks on their length, longest first (so the swap)
            Collections.sort(sticks, Stick.reverseLengthComparator);

            // Length of longest stick
            Iterator<Stick> it = sticks.iterator();
            Stick longest = it.next();
            lengthThreshold = (int) ((double) longest.getLength() * constants.sizeRatio.getValue());

            double slopeSum = longest.getLine().getSlope() * longest.getLength();
            double slopeNb = longest.getLength();

            // Compute on sticks w/ significant length
            while (it.hasNext()) {
                stick = it.next();

                if (stick.getLength() >= lengthThreshold) {
                    slopeSum += (stick.getLine().getSlope() * stick.getLength());
                    slopeNb += stick.getLength();

                    //stick.dump (false);
                } else {
                    break; // Remaining ones are shorter!
                }
            }

            slope = slopeSum / slopeNb;
            angle = Math.atan(slope);
        }
    }

    //--------------//
    // displayFrame //
    //--------------//
    private void displayFrame() {
        // Create a view
        MyView view = new MyView();

        // Create a hosting frame for the view
        final String unit = sheet.getRadix() + ":SkewBuilder";
        ScrollLagView slv = new ScrollLagView(view);
        BoardsPane boards = new BoardsPane(sheet, view, new PixelBoard(unit, sheet), new RunBoard(unit, sLag),
                new SectionBoard(unit, sLag.getLastVertexId(), sLag));

        sheet.getAssembly().addViewTab(Step.SKEW, slv, boards);
    }

    //-----------//
    // writePlot //
    //-----------//
    private void writePlot() {
        if (logger.isFineEnabled()) {
            logger.fine("Slope computation based on following sticks :");
        }

        final int RESOLUTION = 10000;

        // Range -0.4 .. +0.4 Radians (-24 .. +24 Degrees)
        final int MAX_INDEX = 400;
        double[] histo = new double[MAX_INDEX];

        for (int i = MAX_INDEX - 1; i >= 0; i--) {
            histo[i] = 0;
        }

        for (Stick stick : sticks) {
            if (stick.getLength() >= lengthThreshold) {
                if (logger.isFineEnabled()) {
                    stick.dump();
                }

                double slope = stick.getLine().getSlope();
                int length = stick.getLength();
                int index = (int) (slope * RESOLUTION) + (MAX_INDEX / 2);

                if ((index >= 0) && (index < MAX_INDEX)) {
                    histo[index] += stick.getLength();
                }
            } else {
                break;
            }
        }

        XYSeriesCollection dataset = new XYSeriesCollection();
        XYSeries slopeSeries = new XYSeries("Slope");

        for (int i = 0; i < MAX_INDEX; i++) {
            slopeSeries.add(i - (MAX_INDEX / 2), histo[i]);
        }

        dataset.addSeries(slopeSeries);

        // Chart
        JFreeChart chart = ChartFactory.createXYLineChart(sheet.getRadix() + " (Slope Histogram)", // Title
                "Slope [" + (float) (RESOLUTION * angle) + " Radians/" + RESOLUTION + "]", // X-Axis label
                "Counts", // Y-Axis label
                dataset, // Dataset
                PlotOrientation.VERTICAL, // orientation,
                true, // Show legend
                false, // Show tool tips
                false // urls
        );

        // Hosting frame
        ChartFrame frame = new ChartFrame(sheet.getRadix() + " - Slope", chart, true);
        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        RefineryUtilities.centerFrameOnScreen(frame);
        frame.setVisible(true);
    }

    //~ Inner Classes ----------------------------------------------------------

    //-----------//
    // Constants //
    //-----------//
    private static final class Constants extends ConstantSet {
        //~ Instance fields ----------------------------------------------------

        Constant.Boolean displayFrame = new Constant.Boolean(false, "Should we display a frame on Lags found ?");
        Constant.Angle maxDeltaSlope = new Constant.Angle(0.05,
                "Maximum difference in slope between two sections in the same stick");
        Constant.Ratio maxHeightRatio = new Constant.Ratio(2.5,
                "Maximum ratio in height for a run to be combined with an existing section");
        Constant.Angle maxSkewAngle = new Constant.Angle(0.001,
                "Maximum value for skew angle before a rotation is performed");
        Scale.Fraction minRunLength = new Scale.Fraction(1, "Minimum length for a run to be considered");
        Scale.Fraction minSectionLength = new Scale.Fraction(5,
                "Minimum length for a section to be considered in skew computation");
        Constant.Boolean plotting = new Constant.Boolean(false,
                "Should we produce a GnuPlot about computed skew data ?");
        Constant.Ratio sizeRatio = new Constant.Ratio(0.5,
                "Only sticks with length higher than this threshold are used for final computation");
    }

    //--------//
    // MyView //
    //--------//
    private class MyView extends LagView<GlyphLag, GlyphSection> {
        //~ Constructors -------------------------------------------------------

        public MyView() {
            super(sLag, null, null, sheet.getSelectionService());
            setName("SkewBuilder-View");
        }

        //~ Methods ------------------------------------------------------------

        //---------------------//
        // colorizeAllSections //
        //---------------------//
        @Override
        public void colorizeAllSections() {
            int viewIndex = lag.viewIndexOf(this);

            // Default colors for lag sections
            super.colorizeAllSections();

            // Colorize the sections of the sticks
            for (Stick stick : sticks) {
                // Determine suitable color
                Color color;

                if (stick.getLength() >= lengthThreshold) {
                    color = Color.red; // Sticks used for slope computation
                } else {
                    color = Color.pink; // Other sticks
                }

                for (GlyphSection section : stick.getMembers()) {
                    SectionView view = (SectionView) section.getView(viewIndex);
                    view.setColor(color);
                }
            }
        }
    }
}