//----------------------------------------------------------------------------// // // // S e l e c t i o n P a n e l // // // //----------------------------------------------------------------------------// // <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.glyph.ui.panel; import omr.constant.Constant; import omr.constant.ConstantSet; import omr.glyph.EvaluationEngine; import omr.glyph.GlyphRegression; import omr.glyph.GlyphRepository; import omr.glyph.Shape; import omr.glyph.facets.Glyph; import static omr.glyph.ui.panel.GlyphTrainer.Task.Activity.*; import omr.ui.field.LIntegerField; import omr.ui.util.Panel; import com.jgoodies.forms.builder.PanelBuilder; import com.jgoodies.forms.layout.CellConstraints; import com.jgoodies.forms.layout.FormLayout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Observable; import java.util.Observer; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JProgressBar; import javax.swing.KeyStroke; /** * Class {@code SelectionPanel} handles a user panel to select <B>names</B> * from glyph repository, either the whole population or a core set of * glyphs. * This class is a dedicated companion of {@link GlyphTrainer}. * * @author Hervé Bitteur */ class SelectionPanel implements GlyphRepository.Monitor, Observer { //~ Static fields/initializers --------------------------------------------- /** Specific application parameters */ private static final Constants constants = new Constants(); /** Usual logger utility */ private static final Logger logger = LoggerFactory.getLogger(SelectionPanel.class); //~ Instance fields -------------------------------------------------------- /** Reference of network panel companion (TBI) */ private TrainingPanel trainingPanel; /** Swing component */ private final Panel component; /** Current activity */ private final GlyphTrainer.Task task; /** Underlying repository of known glyphs */ private final GlyphRepository repository = GlyphRepository.getInstance(); /** For asynchronous execution of the glyph selection */ private ExecutorService executor = Executors.newSingleThreadExecutor(); /** Visual progression of the selection */ private JProgressBar progressBar = new JProgressBar(); /** To dump the current selection of glyphs used for training/validation */ private DumpAction dumpAction = new DumpAction(); /** To refresh the application wrt to the training material on disk */ private RefreshAction refreshAction = new RefreshAction(); /** To select a core out of whole base */ private SelectAction selectAction = new SelectAction(); /** Counter on loaded glyphs */ private int nbLoaded; /** Input/output on maximum number of glyphs with same shape */ private LIntegerField similar = new LIntegerField( "Max Similar", "Max number of similar shapes"); /** Displayed counter on existing glyph files */ private LIntegerField totalFiles = new LIntegerField( false, "Total", "Total number of glyph files"); /** Displayed counter on loaded glyphs */ private LIntegerField nbLoadedFiles = new LIntegerField( false, "Loaded", "Number of glyph files loaded so far"); /** Displayed counter on selected glyphs */ private LIntegerField nbSelectedFiles = new LIntegerField( false, "Selected", "Number of selected glyph files to load"); //~ Constructors ----------------------------------------------------------- //----------------// // SelectionPanel // //----------------// /** * Creates a new SelectionPanel object. * * @param task the common training task object * @param standardWidth standard width to be used for fields & buttons */ public SelectionPanel (GlyphTrainer.Task task, String standardWidth) { this.task = task; task.addObserver(this); component = new Panel(); component.setNoInsets(); component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) .put(KeyStroke.getKeyStroke("ENTER"), "readParams"); component.getActionMap() .put("readParams", new ParamAction()); displayParams(); defineLayout(standardWidth); } //~ Methods ---------------------------------------------------------------- //---------// // getBase // //---------// /** * Retrieve the selected collection of glyph names * * @param whole indicate whether the whole population is to be selected, or * just the core * @return the collection of selected glyphs names */ public List<String> getBase (boolean whole) { nbLoaded = 0; progressBar.setValue(nbLoaded); if (whole) { return repository.getWholeBase(this); } else { return repository.getCoreBase(this); } } //--------------// // getComponent // //--------------// /** * Give access to the encapsulated swinb component * * @return the user panel */ public JComponent getComponent () { return component; } //-------------// // loadedGlyph // //-------------// /** * Call-back when a glyph has just been loaded * * @param gName the normalized glyph name */ @Override public void loadedGlyph (String gName) { nbLoadedFiles.setValue(++nbLoaded); progressBar.setValue(nbLoaded); } //-------------------// // setSelectedGlyphs // //-------------------// /** * Notify the number of glyphs selected * * @param selected number of selected glyphs */ @Override public void setSelectedGlyphs (int selected) { nbSelectedFiles.setValue(selected); } //----------------// // setTotalGlyphs // //----------------// /** * Notify the total number of glyphs in the base * * @param total the total number of glyphs available */ @Override public void setTotalGlyphs (int total) { totalFiles.setValue(total); progressBar.setMaximum(total); } //--------// // update // //--------// /** * Method triggered whenever the activity changes * * @param obs the new current task activity * @param unused not used */ @Override public void update (Observable obs, Object unused) { switch (task.getActivity()) { case INACTIVE: selectAction.setEnabled(true); break; case SELECTING: case TRAINING: selectAction.setEnabled(false); break; } } //------------------// // setTrainingPanel // //------------------// void setTrainingPanel (TrainingPanel trainingPanel) { this.trainingPanel = trainingPanel; } //------------// // defineCore // //------------// private void defineCore () { // What for ? TODO inputParams(); // Train regression on them GlyphRegression regression = GlyphRegression.getInstance(); Collection<String> gNames = getBase(true); // use whole List<Glyph> glyphs = new ArrayList<>(); // Actually load each glyph description, if not yet done for (String gName : gNames) { Glyph glyph = repository.getGlyph(gName, this); if (glyph != null) { glyphs.add(glyph); } } // Quickly train the regression evaluator (on the whole base) regression.train(glyphs, null, EvaluationEngine.StartingMode.SCRATCH); // Measure all glyphs of each shape Map<Shape, List<NotedGlyph>> palmares = new HashMap<>(); for (String gName : gNames) { Glyph glyph = repository.getGlyph(gName, this); if (glyph != null) { try { Shape shape = glyph.getShape(); double grade = regression.measureDistance( glyph, shape); List<NotedGlyph> shapeNotes = palmares.get(shape); if (shapeNotes == null) { shapeNotes = new ArrayList<>(); palmares.put(shape, shapeNotes); } shapeNotes.add(new NotedGlyph(gName, glyph, grade)); } catch (Exception ex) { logger.warn("Cannot evaluate {}", glyph); } } } // Set of chosen shapes final Set<NotedGlyph> set = new HashSet<>(); final int maxSimilar = similar.getValue(); // Sort the palmares, shape by shape, by (decreasing) grade for (List<NotedGlyph> shapeNotes : palmares.values()) { Collections.sort(shapeNotes, NotedGlyph.reverseGradeComparator); // Take a sample equally distributed on instances of this shape final int size = shapeNotes.size(); final float delta = ((float) (size - 1)) / (maxSimilar - 1); for (int i = 0; i < maxSimilar; i++) { int idx = Math.min(size - 1, Math.round(i * delta)); NotedGlyph ng = shapeNotes.get(idx); if (ng.glyph.getShape() .isTrainable()) { set.add(ng); } } } // Build the core base List<String> base = new ArrayList<>(set.size()); for (NotedGlyph ng : set) { base.add(ng.gName); } repository.setCoreBase(base); setSelectedGlyphs(base.size()); } //--------------// // defineLayout // //--------------// private void defineLayout (String standardWidth) { FormLayout layout = Panel.makeFormLayout( 3, 4, "", standardWidth, standardWidth); PanelBuilder builder = new PanelBuilder(layout, component); CellConstraints cst = new CellConstraints(); builder.setDefaultDialogBorder(); int r = 1; // ---------------------------- builder.addSeparator("Selection", cst.xyw(1, r, 7)); builder.add(progressBar, cst.xyw(9, r, 7)); r += 2; // ---------------------------- builder.add(new JButton(dumpAction), cst.xy(3, r)); builder.add(new JButton(refreshAction), cst.xy(5, r)); builder.add(similar.getLabel(), cst.xy(9, r)); builder.add(similar.getField(), cst.xy(11, r)); builder.add(totalFiles.getLabel(), cst.xy(13, r)); builder.add(totalFiles.getField(), cst.xy(15, r)); r += 2; // ---------------------------- builder.add(new JButton(selectAction), cst.xy(3, r)); builder.add(nbSelectedFiles.getLabel(), cst.xy(9, r)); builder.add(nbSelectedFiles.getField(), cst.xy(11, r)); builder.add(nbLoadedFiles.getLabel(), cst.xy(13, r)); builder.add(nbLoadedFiles.getField(), cst.xy(15, r)); } //---------------// // displayParams // //---------------// private void displayParams () { similar.setValue(constants.maxSimilar.getValue()); } //-------------// // inputParams // //-------------// private void inputParams () { constants.maxSimilar.setValue(similar.getValue()); } //~ Inner Classes ---------------------------------------------------------- //-----------// // Constants // //-----------// private static final class Constants extends ConstantSet { //~ Instance fields ---------------------------------------------------- Constant.Integer maxSimilar = new Constant.Integer( "Glyphs", 10, "Absolute maximum number of instances for the same shape" + " used in training"); } //------------// // NotedGlyph // //------------// /** * Handle a glyph together with its name and grade */ private static class NotedGlyph { //~ Static fields/initializers ----------------------------------------- /** For comparing NotedGlyph instance in reverse grade order */ static final Comparator<NotedGlyph> reverseGradeComparator = new Comparator<NotedGlyph>() { @Override public int compare (NotedGlyph ng1, NotedGlyph ng2) { return Double.compare(ng2.grade, ng1.grade); } }; //~ Instance fields ---------------------------------------------------- final String gName; final Glyph glyph; final double grade; //~ Constructors ------------------------------------------------------- public NotedGlyph (String gName, Glyph glyph, double grade) { this.gName = gName; this.glyph = glyph; this.grade = grade; } //~ Methods ------------------------------------------------------------ @Override public String toString () { return "{NotedGlyph " + gName + " " + grade + "}"; } } //------------// // DumpAction // //------------// private class DumpAction extends AbstractAction { //~ Constructors ------------------------------------------------------- public DumpAction () { super("Dump"); putValue( Action.SHORT_DESCRIPTION, "Dump the current glyph selection"); } //~ Methods ------------------------------------------------------------ @Override public void actionPerformed (ActionEvent e) { List<String> gNames = getBase(trainingPanel.useWhole()); System.out.println( "Content of " + (trainingPanel.useWhole() ? "whole" : "core") + " population (" + gNames.size() + "):"); Collections.sort(gNames, GlyphRepository.shapeComparator); int glyphNb = 0; String prevName = null; for (String gName : gNames) { if (prevName != null) { if (!GlyphRepository.shapeNameOf(gName) .equals(prevName)) { System.out.println( String.format("%4d %s", glyphNb, prevName)); glyphNb = 1; } } glyphNb++; prevName = GlyphRepository.shapeNameOf(gName); } System.out.println(String.format("%4d %s", glyphNb, prevName)); } } //-------------// // ParamAction // //-------------// private class ParamAction extends AbstractAction { //~ Methods ------------------------------------------------------------ // Purpose is just to read and remember the data from the various // input fields. Triggered when user presses Enter in one of these // fields. @Override public void actionPerformed (ActionEvent e) { inputParams(); displayParams(); } } //---------------// // RefreshAction // //---------------// private class RefreshAction extends AbstractAction { //~ Constructors ------------------------------------------------------- public RefreshAction () { super("Disk Refresh"); putValue( Action.SHORT_DESCRIPTION, "Refresh trainer with disk information"); } //~ Methods ------------------------------------------------------------ @Override public void actionPerformed (ActionEvent e) { repository.refreshBases(); } } //--------------// // SelectAction // //--------------// private class SelectAction extends AbstractAction { //~ Constructors ------------------------------------------------------- public SelectAction () { super("Select Core"); putValue( Action.SHORT_DESCRIPTION, "Build core selection out of whole glyph base"); } //~ Methods ------------------------------------------------------------ @Override public void actionPerformed (ActionEvent e) { executor.execute( new Runnable() { @Override public void run () { task.setActivity(SELECTING); // Define Core from Whole defineCore(); repository.storeCoreBase(); task.setActivity(INACTIVE); } }); } } }