package edu.cmu.minorthird.text.gui; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.swing.AbstractAction; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.filechooser.FileFilter; import edu.cmu.minorthird.text.MutableTextLabels; import edu.cmu.minorthird.text.Span; import edu.cmu.minorthird.text.TextBase; import edu.cmu.minorthird.text.TextLabels; import edu.cmu.minorthird.text.learn.ClassifierAnnotator; import edu.cmu.minorthird.text.learn.OnlineTextClassifierLearner; import edu.cmu.minorthird.util.IOUtil; import edu.cmu.minorthird.util.gui.SmartVanillaViewer; import edu.cmu.minorthird.util.gui.ViewerFrame; /** * Interactivly edit the documents for an OnlineLearning Experiment * * @author Cameron Williams */ public class OnlineClassifierDocumentEditor extends ViewerTracker{ static final long serialVersionUID=20080314L; public static final String LABEL_DOCUMENT="-choose label-"; // internal state private String importType,exportType; private JLabel ioTypeLabel; // private int editSpanCursor=-1; // indicates nothing selected private boolean readOnly=false; // private SpanFeatureExtractor fe=null; private OnlineTextClassifierLearner textLearner=null; // private OnlineBinaryClassifierLearner learner=null; private String[] spanTypes=null; // private String learnerName=""; public OnlineClassifierDocumentEditor ocdEditor; private TextBaseViewer tbViewer=null; public List<EditedSpan> editedSpans=null; public ClassifierAnnotator ann=null; // buttons JComboBox labelBox=new JComboBox(); JButton addButton=new JButton(new AddSelection("Add Doc(s)")); JButton classifierButton=new JButton(new GetClassifier("Show Classifier")); JButton saveAnnButton=new JButton(new SaveAnnotator("Save TextLearner")); JButton resetButton=new JButton(new Reset("Reset")); JButton completeButton=new JButton(new CompleteTraining("Complete Training")); JButton thisUpButton=new JButton(new MoveOnlineDocumentCursor("Up",-1)); JButton thisDownButton=new JButton(new MoveOnlineDocumentCursor("Down",+1)); private List<JButton> buttonsThatChangeStuff=new ArrayList<JButton>(); /** * @param viewLabels * a superset of editLabels which may include some additional * read-only information * @param editLabels * the labels being modified * @param documentList * the document Span being edited is associated with the selected * entry of the documentList. * @param spanPainter * used to repaint documentList elements * @param statusMsg * a JLabel used for status messages. */ public OnlineClassifierDocumentEditor(OnlineTextClassifierLearner learner, TextLabels viewLabels,TextBaseViewer tbViewer, MutableTextLabels editLabels,JList documentList,SpanPainter spanPainter, StatusMessage statusMsg){ super(viewLabels,editLabels,documentList,spanPainter,statusMsg); this.textLearner=learner; ann=textLearner.getAnnotator(); this.editLabels=editLabels; TextBase tb=editLabels.getTextBase(); this.spanTypes=learner.getTypes(); // Initialize editedSpans to include any data that is already labeled editedSpans=new ArrayList<EditedSpan>(); int index=0; for(Iterator<Span> j=tb.documentSpanIterator();j.hasNext();){ Span s=j.next(); for(int x=0;x<spanTypes.length;x++){ if(editLabels.hasType(s,spanTypes[x])) editedSpans.add(new EditedSpan(s,spanTypes[x],index)); } index++; } this.tbViewer=tbViewer; init(); ocdEditor=this; } private void init(){ this.importType=this.exportType=null; this.ioTypeLabel=new JLabel("Types: [None/None]"); initLayout(); for(int i=0;i<spanTypes.length;i++){ labelBox.addItem(spanTypes[i]); } labelBox.addActionListener(new LabelDocument("Label Document")); loadSpan(nullSpan()); } private void initLayout(){ // // layout stuff // setLayout(new GridBagLayout()); GridBagConstraints gbc; int col=0; gbc=new GridBagConstraints(); // ------------- up button -----------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; add(thisUpButton,gbc); // ------------- down button ------------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; add(thisDownButton,gbc); // ------------ label box -------------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; labelBox.addItem(LABEL_DOCUMENT); add(labelBox,gbc); // ------------- add document button -----------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; add(addButton,gbc); // ------------- get classifier button -----------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; add(classifierButton,gbc); // ------------- save annotator button -----------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; add(saveAnnButton,gbc); // ------------- reset button -----------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; add(resetButton,gbc); // ------------- complete training button -----------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; add(completeButton,gbc); // ----------- save button -------------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; add(saveButton,gbc); buttonsThatChangeStuff.add(saveButton); saveButton.setEnabled(saveAsFile!=null); // ------------- editorHolder ---------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.BOTH; gbc.weightx=1.0; gbc.weighty=1.0; gbc.gridx=1; gbc.gridy=1; gbc.gridwidth=col; add(editorHolder,gbc); } /** * Set mode to read-only or not. In read-only mode, the document viewed has * the same highlighting as in the documentList. In write mode, the "truth" * spans are shown, and the "guess" spans are imported. */ public void setReadOnly(boolean readOnly){ for(Iterator<JButton> i=buttonsThatChangeStuff.iterator();i.hasNext();){ JButton button=i.next(); button.setEnabled(readOnly?false:true); } this.readOnly=readOnly; } /** Declare which types are being edited. */ public void setTypesBeingEdited(String inType,String outType){ this.importType=inType; this.exportType=outType; ioTypeLabel.setText("Edit: "+importType+"/"+exportType); } @Override protected void loadSpanHook(){ if(readOnly&&!DUMMY_ID.equals(documentSpan.getDocumentId())){ importDocumentListMarkup(documentSpan.getDocumentId()); } // Keymap keymap=JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP); } /** Toggles readOnly status */ // private class ReadOnlyButton extends AbstractAction{ // static final long serialVersionUID=20080314L; // public ReadOnlyButton(String msg){ // super(msg); // } // // public void actionPerformed(ActionEvent event){ // setReadOnly(!readOnly); // if(documentSpan!=null) // loadSpan(documentSpan); // } // } /** Add span associated with selected text. */ private class LabelDocument extends AbstractAction{ static final long serialVersionUID=200803014L; public LabelDocument(String msg){ super(msg); } @Override public void actionPerformed(ActionEvent event){ JComboBox cb=(JComboBox)event.getSource(); String type=(String)cb.getSelectedItem(); int docIndex=documentList.getMinSelectionIndex(); if(type!="-choose label-"){ if(editDocument(documentSpan,type,docIndex)) statusMsg.display("Document labeled: "+type); else statusMsg .display("Document label cannot be changed - document already added"); }else statusMsg.display("Document has NOT been labeled"); } } /** Add span associated with selected text. */ private class AddSelection extends AbstractAction{ static final long serialVersionUID=200803014L; public AddSelection(String msg){ super(msg); } @Override public void actionPerformed(ActionEvent event){ AddDocuments(); tbViewer.highlightAction.paintDocument(null); statusMsg.display("Documents added to learner"); } } /** Return the current classifier */ private class GetClassifier extends AbstractAction{ static final long serialVersionUID=200803014L; public GetClassifier(String msg){ super(msg); } @Override public void actionPerformed(ActionEvent event){ new ViewerFrame("Classifier",new SmartVanillaViewer(textLearner .getClassifier())); statusMsg.display("Getting the Classifier"); } } public void saveAnn(File file,String format) throws IOException{ // if (!format.equals(FORMAT_NAME)) throw new // IllegalArgumentException("illegal format "+format); try{ try{ // Annotator ann = (Annotator)textLearner; IOUtil.saveSerialized((Serializable)textLearner,file); }catch(IOException e){ throw new IllegalArgumentException("can't save to "+file+": "+e); } }catch(Exception e){ System.out.println("Error Opening Excel File"); e.printStackTrace(); } } /** Return the current classifier */ private class SaveAnnotator extends AbstractAction{ static final long serialVersionUID=200803014L; public SaveAnnotator(String msg){ super(msg); } @Override public void actionPerformed(ActionEvent event){ JFileChooser chooser=new JFileChooser(); int returnVal=chooser.showSaveDialog(ocdEditor); if(returnVal==JFileChooser.APPROVE_OPTION){ try{ FileFilter filter=chooser.getFileFilter(); // String fmt=filter.getDescription(); String ext=".ann"; File file0=chooser.getSelectedFile(); File file= (file0.getName().endsWith(ext))?file0:new File(file0 .getParentFile(),file0.getName()+ext); ocdEditor.saveAnn(file,filter.getDescription()); }catch(Exception ex){ statusMsg.display("Error Saving Annotator"); } } // statusMsg.display("Saving the Annotator"); } } /** Forget about all previous examples */ private class Reset extends AbstractAction{ static final long serialVersionUID=200803014L; public Reset(String msg){ super(msg); } @Override public void actionPerformed(ActionEvent event){ textLearner.reset(); statusMsg.display("Learner Reset - previous examples forgotten"); } } /** Complete Training - Announce that no more examples will be coming */ private class CompleteTraining extends AbstractAction{ static final long serialVersionUID=200803014L; public CompleteTraining(String msg){ super(msg); } @Override public void actionPerformed(ActionEvent event){ textLearner.completeTraining(); statusMsg.display("Training Completed - no more examples will be added"); } } protected void documentMessage(int nextCursor){ if(nextCursor>-1){ String label=checkLabel(nextCursor); if(checkIfAdded(nextCursor)){ statusMsg .display("Cannot change label: Document has already been added to Classifier as: "+ label); labelBox.setSelectedItem(label); }else if(label!=null){ statusMsg.display("Document is currently labeled: "+label); labelBox.setSelectedItem(label); }else{ statusMsg .display("In MOVEDOCCURSOI: This document has NOT been labeled"); labelBox.setSelectedItem(LABEL_DOCUMENT); } } } @Override protected void loadSpan(Span span){ super.loadSpan(span); documentMessage(documentList.getMinSelectionIndex()); } /** Move through list of spans */ protected class MoveOnlineDocumentCursor extends AbstractAction{ static final long serialVersionUID=200803014L; private int delta; public MoveOnlineDocumentCursor(String msg,int delta){ super(msg); this.delta=delta; } @Override public void actionPerformed(ActionEvent event){ int nextCursor; synchronized(documentList){ int currentCursor=documentList.getSelectedIndex(); // if nothing's selected, pretend it was the first thing if(currentCursor<0) currentCursor=0; nextCursor=currentCursor+delta; if(nextCursor<documentList.getModel().getSize()&&nextCursor>=0){ documentList.setSelectedIndex(nextCursor); } } documentMessage(nextCursor); } } // private class SpanPropertyViewer extends ComponentViewer{ // static final long serialVersionUID=200803014L; // public JComponent componentFor(Object o){ // final Span span=(Span)o; // final JTabbedPane pane=new JTabbedPane(); // final JTextField propField=new JTextField(10); // final JTextField valField=new JTextField(10); // final JTable table=makePropertyTable(span); // final JScrollPane tableScroller=new JScrollPane(table); // final JButton addButton= // new JButton(new AbstractAction("Insert Property"){ // static final long serialVersionUID=200803014L; // public void actionPerformed(ActionEvent event){ // editLabels.setProperty(span,propField.getText(),valField // .getText()); // tableScroller.getViewport().setView(makePropertyTable(span)); // tableScroller.revalidate(); // pane.revalidate(); // } // }); // GridBagConstraints gbc=fillerGBC(); // // gbc.fill = GridBagConstraints.HORIZONTAL; // gbc.gridwidth=3; // final JPanel subpanel=new JPanel(); // subpanel.setLayout(new GridBagLayout()); // subpanel.add(tableScroller,gbc); // subpanel.add(addButton,myGBC(0)); // subpanel.add(propField,myGBC(1)); // subpanel.add(valField,myGBC(2)); // pane.add("Properties",subpanel); // pane.add("Span",new SmartVanillaViewer(span)); // return pane; // } // // private GridBagConstraints myGBC(int col){ // GridBagConstraints gbc=fillerGBC(); // gbc.fill=GridBagConstraints.HORIZONTAL; // gbc.gridx=col; // gbc.gridy=1; // return gbc; // } // // private JTable makePropertyTable(final Span span){ // Object[] spanProps=editLabels.getSpanProperties().toArray(); // Object[][] table=new Object[spanProps.length][2]; // for(int i=0;i<spanProps.length;i++){ // table[i][0]=spanProps[i]; // table[i][1]=editLabels.getProperty(span,(String)spanProps[i]); // } // String[] colNames=new String[]{"Property","Property's Value"}; // return new JTable(table,colNames); // } // } /** * Check if the documentSpan with docID has already been added to the * classifier */ public boolean checkIfAdded(int docID){ for(int i=0;i<editedSpans.size();i++){ EditedSpan eSpan=editedSpans.get(i); if(eSpan.id==docID&&eSpan.added){ return true; } } return false; } /** Returns the label of the document if it has been labeled */ public String checkLabel(int docID){ for(int i=0;i<editedSpans.size();i++){ EditedSpan eSpan=editedSpans.get(i); if(eSpan.id==docID){ return eSpan.label; } } return null; } /** * Labels a document - unless the document has already been added to the * classifier */ public boolean editDocument(Span s,String label,int docID){ // Checks if Span has already been edited // If Span is alread added to the classifier, return false // Otherwise change label for(int i=0;i<editedSpans.size();i++){ EditedSpan eSpan=editedSpans.get(i); if(eSpan.id==docID){ if(eSpan.added) return false; else{ eSpan.label=label; return true; } } } // If Span is not added, create new Edited span, return true EditedSpan eSpan=new EditedSpan(s,label,docID); editedSpans.add(eSpan); return true; } /** * Adds all the documents that have been edited but not already added to the * classifier */ public void AddDocuments(){ // Add all edited spans to editLabels for(int i=0;i<editedSpans.size();i++){ EditedSpan eSpan=(editedSpans.get(i)); if(!eSpan.added&&eSpan.s!=null){ editLabels.addToType(eSpan.s,eSpan.label); textLearner.addDocument(eSpan.label,eSpan.s.asString()); } } // Mark all edited Spans as added for(int j=0;j<editedSpans.size();j++){ EditedSpan eSpan=(editedSpans.get(j)); eSpan.add(); } ann=textLearner.getAnnotator(); viewLabels=textLearner.annotatedCopy(editLabels); tbViewer.updateTextLabels(viewLabels); } // private Span getEditSpan(int k){ // /* // * for (int i=0; i<editedSpans.length; i++) { Span s = editedSpans[i]; if // * (k-- == 0) return s; } // */ // throw new IllegalStateException("bad editedSpan index "+k); // } /** * Stores a documentSpan, its label, its index, and whether or not it has been * added to the Classifier */ public class EditedSpan{ public Span s; public String label; public int id; public boolean added=false; public EditedSpan(Span s,String label,int id){ this.s=s; this.label=label; this.id=id; } public void add(){ this.added=true; } } }