//----------------------------------------------------------------------------//
// //
// V a l i d a 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.glyph.Evaluation;
import omr.glyph.ShapeEvaluator;
import omr.glyph.GlyphRepository;
import omr.glyph.Grades;
import omr.glyph.facets.Glyph;
import omr.glyph.ui.SampleVerifier;
import omr.ui.field.LDoubleField;
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.List;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JProgressBar;
/**
* Class {@code ValidationPanel} handles the validation of an evaluator
* against the selected population of glyphs (either the whole base or
* the core base).
* It is a dedicated companion of class {@link GlyphTrainer}.
*
* @author Hervé Bitteur
*/
class ValidationPanel
implements Observer
{
//~ Static fields/initializers ---------------------------------------------
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(
ValidationPanel.class);
//~ Instance fields --------------------------------------------------------
/** Swing component */
private final Panel component;
/** Dedicated executor for validation */
private ExecutorService executor = Executors.newSingleThreadExecutor();
/** The evaluator to validate */
private final ShapeEvaluator evaluator;
/** User progress bar to visualize the validation process */
private JProgressBar progressBar = new JProgressBar();
/** Repository of known glyphs */
private final GlyphRepository repository = GlyphRepository.getInstance();
/** User interface that handles glyphs selection */
private final SelectionPanel selectionPanel;
/** User interface that handles evaluator training */
private final TrainingPanel trainingPanel;
/** User action to validate the evaluator against whole or core base */
private ValidateAction validateAction = new ValidateAction();
/** Display percentage of glyphs correctly recognized */
private LDoubleField pcValue = new LDoubleField(
false,
"% OK",
"Percentage of recognized glyphs",
" %5.2f%%");
/** Display number of glyphs correctly recognized */
private LIntegerField positiveValue = new LIntegerField(
false,
"Glyphs OK",
"Number of glyphs correctly recognized");
/** Display number of glyphs mistaken with some other shape */
private LIntegerField falsePositiveValue = new LIntegerField(
false,
"False Pos.",
"Number of glyphs incorrectly recognized");
/** Collection of glyph names leading to false positives */
private List<String> falsePositives = new ArrayList<>();
/** User action to investigate on false positives */
private FalsePositiveAction falsePositiveAction = new FalsePositiveAction();
/** Display number of glyphs not recognized */
private LIntegerField negativeValue = new LIntegerField(
false,
"Negative",
"Number of glyphs not recognized");
/** Collection of glyph names not recognized (negatives) */
private List<String> negatives = new ArrayList<>();
/** User action to investigate on negatives */
private NegativeAction negativeAction = new NegativeAction();
//~ Constructors -----------------------------------------------------------
/**
* Creates a new ValidationPanel object.
*
* @param task the current training activity
* @param standardWidth standard width for fields & buttons
* @param evaluator the evaluator to validate
* @param selectionPanel user panel for glyph selection
* @param trainingPanel user panel for evaluator training
*/
public ValidationPanel (GlyphTrainer.Task task,
String standardWidth,
ShapeEvaluator evaluator,
SelectionPanel selectionPanel,
TrainingPanel trainingPanel)
{
this.evaluator = evaluator;
this.selectionPanel = selectionPanel;
this.trainingPanel = trainingPanel;
task.addObserver(this);
component = new Panel();
component.setNoInsets();
defineLayout(standardWidth);
}
//~ Methods ----------------------------------------------------------------
//--------------//
// getComponent //
//--------------//
/**
* Give access to the encapsulated swing component.
*
* @return the user panel
*/
public JComponent getComponent ()
{
return component;
}
//--------//
// update //
//--------//
/**
* A degenerated version, just to disable by default the
* verification actions whenever a new task activity is notified.
* These actions are then re-enabled only at the end of the validation run.
*
* @param obs not used
* @param unused not used
*/
@Override
public void update (Observable obs,
Object unused)
{
negativeAction.setEnabled(!negatives.isEmpty());
falsePositiveAction.setEnabled(!falsePositives.isEmpty());
}
//--------------//
// defineLayout //
//--------------//
private void defineLayout (String standardWidth)
{
/** Common JGoogies constraints for this class and its subclass if any */
CellConstraints cst = new CellConstraints();
/** Common JGoogies builder for this class and its subclass if any */
FormLayout layout = Panel.makeFormLayout(
3,
4,
"",
standardWidth,
standardWidth);
PanelBuilder builder = new PanelBuilder(layout, component);
// Validation title & progress bar
int r = 1;
builder.addSeparator("Validation", cst.xyw(1, r, 7));
builder.add(progressBar, cst.xyw(9, r, 7));
r += 2; // ----------------------------
builder.add(positiveValue.getLabel(), cst.xy(5, r));
builder.add(positiveValue.getField(), cst.xy(7, r));
builder.add(negativeValue.getLabel(), cst.xy(9, r));
builder.add(negativeValue.getField(), cst.xy(11, r));
builder.add(falsePositiveValue.getLabel(), cst.xy(13, r));
builder.add(falsePositiveValue.getField(), cst.xy(15, r));
r += 2; // ----------------------------
JButton validateButton = new JButton(validateAction);
validateButton.setToolTipText(
"Validate the evaluator on current base of glyphs");
JButton negativeButton = new JButton(negativeAction);
negativeButton.setToolTipText(
"Display the impacted glyphs for verification");
JButton falsePositiveButton = new JButton(falsePositiveAction);
falsePositiveButton.setToolTipText(
"Display the impacted glyphs for verification");
builder.add(validateButton, cst.xy(3, r));
builder.add(pcValue.getLabel(), cst.xy(5, r));
builder.add(pcValue.getField(), cst.xy(7, r));
builder.add(negativeButton, cst.xy(11, r));
builder.add(falsePositiveButton, cst.xy(15, r));
}
//---------------//
// runValidation //
//---------------//
private void runValidation ()
{
logger.info("Validating {} evaluator on {} base ...",
evaluator.getName(),
trainingPanel.useWhole() ? "whole" : "core");
// Empty the display
positiveValue.setText("");
pcValue.setText("");
negativeValue.setText("");
falsePositiveValue.setText("");
negativeAction.setEnabled(false);
falsePositiveAction.setEnabled(false);
negatives.clear();
falsePositives.clear();
int positives = 0;
Collection<String> gNames = selectionPanel.getBase(
trainingPanel.useWhole());
progressBar.setValue(0);
progressBar.setMaximum(gNames.size());
int index = 0;
for (String gName : gNames) {
index++;
Glyph glyph = repository.getGlyph(gName, selectionPanel);
if (glyph != null) {
Evaluation vote = evaluator.rawVote(
glyph,
Grades.validationMinGrade,
null);
if (vote == null) {
negatives.add(gName);
System.out.printf("%-35s not recognized%n", gName);
} else if (vote.shape.getPhysicalShape() == glyph.getShape()
.getPhysicalShape()) {
positives++;
} else {
falsePositives.add(gName);
System.out.printf(
"%-35s mistaken for %s%n",
gName,
vote.shape.getPhysicalShape());
}
}
// Update progress bar
progressBar.setValue(index);
}
int total = gNames.size();
double pc = ((double) positives * 100) / (double) total;
String pcStr = String.format(" %5.2f%%", pc);
logger.info("{}Evaluator. Ratio={} : {}/{}",
evaluator.getName(), pcStr, positives, total);
positiveValue.setValue(positives);
pcValue.setValue(pc);
negativeValue.setValue(negatives.size());
falsePositiveValue.setValue(falsePositives.size());
}
//~ Inner Classes ----------------------------------------------------------
//---------------------//
// FalsePositiveAction //
//---------------------//
private class FalsePositiveAction
extends AbstractAction
{
//~ Constructors -------------------------------------------------------
public FalsePositiveAction ()
{
super("Verify");
}
//~ Methods ------------------------------------------------------------
@Override
public void actionPerformed (ActionEvent e)
{
SampleVerifier.getInstance()
.verify(falsePositives);
SampleVerifier.getInstance()
.setVisible(true);
}
}
//----------------//
// NegativeAction //
//----------------//
private class NegativeAction
extends AbstractAction
{
//~ Constructors -------------------------------------------------------
public NegativeAction ()
{
super("Verify");
}
//~ Methods ------------------------------------------------------------
@Override
public void actionPerformed (ActionEvent e)
{
SampleVerifier.getInstance()
.verify(negatives);
SampleVerifier.getInstance()
.setVisible(true);
}
}
//----------------//
// ValidateAction //
//----------------//
private class ValidateAction
extends AbstractAction
{
//~ Constructors -------------------------------------------------------
public ValidateAction ()
{
super("Validate");
}
//~ Methods ------------------------------------------------------------
@Override
public void actionPerformed (ActionEvent e)
{
executor.execute(
new Runnable()
{
@Override
public void run ()
{
setEnabled(false);
runValidation();
negativeAction.setEnabled(negatives.size() > 0);
falsePositiveAction.setEnabled(
!falsePositives.isEmpty());
setEnabled(true);
}
});
}
}
}