package joshua.ui.tree_visualizer; import javax.swing.*; import javax.swing.event.*; import java.io.InputStream; import java.io.FileInputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Scanner; import java.util.LinkedList; import java.util.zip.GZIPInputStream; import java.awt.*; import java.awt.event.*; import edu.uci.ics.jung.algorithms.layout.StaticLayout; /** * A stand-alone application for viewing derivation trees. Given files holding the source-side * sentences and n-best output from the Joshua decoder, the user can browse through visualizations * showing derivation trees for each sentence. * * @author jonny * */ public class DerivationBrowser { /** * A frame that displays the list of source-side sentences and the list of target-side * sentences. */ private static JFrame chooserFrame; /** * A list that will display all the sentences in the current source-side file. */ private static JList sourceList; /** * A list that will display all the candidate translations for the currently-selected * source sentence. */ private static JList targetList; /** * A file containing source-side sentences. */ private static File sourceFile; /** * A file containing candidate translations for all source sentences, annotated with * source-side alignments. */ private static File targetFile; /** * Each member of this array is a linked list holding the candidate translations for one * particular sentence. */ private static LinkedList<String> [] nBestLists; /** * A JFileChooser to allow the user to choose the source or target file. */ private static JFileChooser fileChooser; /** * The active frame is the frame that displays a derivation tree for the currently selected * candidate translation. */ private static ActiveFrame activeFrame; public static final int DEFAULT_WIDTH = 640; public static final int DEFAULT_HEIGHT = 480; /** * The main method. * * @param argv command-line parameters. Can specify the source or target file. */ public static void main(String [] argv) { initializeJComponents(); if (argv.length > 0) { sourceFile = new File(argv[0]); populateSourceList(); } if (argv.length > 1) { targetFile = new File(argv[1]); setNBestLists(); populateTargetList(); } activeFrame.drawGraph(); chooserFrame.setVisible(true); return; } /** * Initializes the various components in the chooser frame. The two JLists are laid out, * and a menu bar is attached. */ private static void initializeJComponents() { // JFrame init chooserFrame = new JFrame("Joshua Derivation Tree Browser"); chooserFrame.setSize(640, 480); chooserFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); chooserFrame.setJMenuBar(createJMenuBar()); chooserFrame.setLayout(new GridLayout(2,1)); sourceList = new JList(new DefaultListModel()); sourceList.setFixedCellWidth(200); sourceList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); targetList = new JList(new DefaultListModel()); targetList.setFixedCellWidth(200); targetList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); chooserFrame.getContentPane().add(new JScrollPane(sourceList)); chooserFrame.getContentPane().add(new JScrollPane(targetList)); SentenceListListener sll = new SentenceListListener(sourceList, targetList); sourceList.getSelectionModel().addListSelectionListener(sll); targetList.getSelectionModel().addListSelectionListener(sll); // fileChooser fileChooser = new JFileChooser(); activeFrame = new ActiveFrame(); return; } /** * Creates the menu bar for the chooser frame. We put this in a seperate method so that * it can be easily found and changed if need be. * * @return the menu bar for the chooser frame */ private static JMenuBar createJMenuBar() { JMenuBar mb = new JMenuBar(); JMenu openMenu = new JMenu("Control"); JMenuItem creat = new JMenuItem("New tree viewer window"); JMenuItem src = new JMenuItem("Open source file ..."); JMenuItem tgt = new JMenuItem("Open n-best derivations file ..."); JMenuItem quit = new JMenuItem("Quit"); FileChoiceListener fcl = new FileChoiceListener(src, tgt); src.addActionListener(fcl); tgt.addActionListener(fcl); creat.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { activeFrame.disableNavigationButtons(); activeFrame = new ActiveFrame(); return; } }); quit.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); } }); openMenu.add(creat); openMenu.add(src); openMenu.add(tgt); openMenu.add(quit); mb.add(openMenu); return mb; } /** * Displays the source-side sentences in their list. This method is called whenever the source- * side file is changed. */ private static void populateSourceList() { if (sourceFile == null) return; try { DefaultListModel model = (DefaultListModel) sourceList.getModel(); InputStream inp; if (sourceFile.getName().endsWith("gz")) inp = new GZIPInputStream(new FileInputStream(sourceFile)); else inp = new FileInputStream(sourceFile); Scanner scanner = new Scanner(inp, "UTF-8"); model.removeAllElements(); while (scanner.hasNextLine()) model.addElement(scanner.nextLine()); } catch (FileNotFoundException e) { } catch (IOException e) { } } /** * Displays the candidate translations for the current source sentence in their list. This method * is called whenever the source list's selected value is changed. */ private static void populateTargetList() { DefaultListModel model = (DefaultListModel) targetList.getModel(); model.removeAllElements(); if (sourceList.getSelectedValue() == null) { return; } for (String s : nBestLists[sourceList.getSelectedIndex()]) { model.addElement(new Derivation(s)); } return; } /** * Reads in the currently set target file and organizes it into seperate n-best lists. Each * n-best list corresponds to a different source sentence. */ private static void setNBestLists() { if (targetFile == null) { System.err.println("setNBestLists: target file null"); return; } nBestLists = new LinkedList[sourceList.getModel().getSize()]; for (int i = 0; i < nBestLists.length; i++) nBestLists[i] = new LinkedList<String>(); try { InputStream inp; if (targetFile.getName().endsWith("gz")) inp = new GZIPInputStream(new FileInputStream(targetFile)); else inp = new FileInputStream(targetFile); Scanner scanner = new Scanner(inp, "UTF-8"); int src; while (scanner.hasNextLine()) { String line = scanner.nextLine(); String [] tokens = line.split(DerivationTree.DELIMITER); try { src = Integer.parseInt(tokens[0].trim()); // System.err.println("Derivation for source sentence " + src); nBestLists[src].add(line); } catch (NumberFormatException e) { System.err.println("Caught NumberFormatException in setNBestLists"); // fall through } } } catch (FileNotFoundException e) { } catch (IOException e) { } return; } /** * ActiveFrame is a Frame that's specially designed to display a derivation tree. An important * property of an ActiveFrame is that it can be "fixed", meaning that its displayed derivation * tree can no longer be changed. * * The DerivationBrowser will have at most one un-fixed frame at any one time. Other, fixed * frames can be maintained so that derivation trees can be compared. * * @author Jonathan Weese * */ public static class ActiveFrame extends JFrame { /** * The serial version UID. I don't know what it's for, but eclipse is convinced it's * necessary. */ private static final long serialVersionUID = 6482495170692955314L; /** * A button that should be pressed to view the next candidate translation in the list. */ JButton nextDerivation; /** * A button that should be pressed to view the previous candidate translation in the list. */ JButton previousDerivation; /** * A button to move to the next source-side sentence in the file. */ JButton nextSource; /** * A button to move to the previous source-side sentence in the file. */ JButton previousSource; /** * A panel that holds the buttons, as well as labels to show which derivation is currently * being displayed. */ private JPanel controlPanel; /** * A panel used to display the derivation tree itself. */ private JPanel viewPanel; /** * This label displays the text of the source sentence. */ private JLabel source; /** * This label displays the text of the candidate translation. */ private JLabel derivation; /** * This component displays the derivation tree's JUNG graph. */ private DerivationViewer dv; /** * The default constructor. */ public ActiveFrame() { super("Joshua Derivation Tree"); setLayout(new BorderLayout()); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); controlPanel = new JPanel(new BorderLayout()); source = new JLabel("source: none"); derivation = new JLabel("derivation: none"); initializeButtons(); layoutControl(); viewPanel = new JPanel(new BorderLayout()); dv = null; getContentPane().add(viewPanel, BorderLayout.CENTER); getContentPane().add(controlPanel, BorderLayout.SOUTH); drawGraph(); setVisible(true); } /** * Lays out the control buttons of this frame. */ private void layoutControl() { JPanel ctlLeft = new JPanel(new GridLayout(2, 1)); JPanel ctlCenter = new JPanel(new GridLayout(2, 1)); JPanel ctlRight = new JPanel(new GridLayout(2, 1)); controlPanel.add(ctlLeft, BorderLayout.WEST); controlPanel.add(ctlCenter, BorderLayout.CENTER); controlPanel.add(ctlRight, BorderLayout.EAST); ctlLeft.add(previousDerivation); ctlLeft.add(previousSource); ctlCenter.add(derivation); ctlCenter.add(source); ctlRight.add(nextDerivation); ctlRight.add(nextSource); return; } /** * Initializes the control buttons of this frame. */ private void initializeButtons() { nextDerivation = new JButton(">"); previousDerivation = new JButton("<"); nextSource = new JButton(">"); previousSource = new JButton("<"); nextDerivation.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int x = targetList.getSelectedIndex(); targetList.setSelectedIndex(x + 1); return; } }); previousDerivation.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { int x = targetList.getSelectedIndex(); targetList.setSelectedIndex(x - 1); return; } }); nextSource.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int x = sourceList.getSelectedIndex(); sourceList.setSelectedIndex(x + 1); return; } }); previousSource.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int x = sourceList.getSelectedIndex(); sourceList.setSelectedIndex(x - 1); return; } }); return; } /** * Displays the derivation tree for the current candidate translation. The current candidate * translation is whichever translation is currently highlighted in the Derivation Browser's * chooser frame. */ public void drawGraph() { viewPanel.removeAll(); String src = (String) sourceList.getSelectedValue(); Derivation tgtDer = (Derivation) targetList.getSelectedValue(); if ((src == null) || (tgtDer == null)) { return; } source.setText(src); derivation.setText(tgtDer.toString()); String tgt = tgtDer.complete(); DerivationTree tree = new DerivationTree(tgt.split(DerivationTree.DELIMITER)[1], src); if (dv == null) { dv = new DerivationViewer(tree, viewPanel.getSize(), Color.red, DerivationViewer.AnchorType.ANCHOR_LEFTMOST_LEAF); } else { dv.setGraphLayout(new StaticLayout<Node,DerivationTreeEdge>(tree, new DerivationTreeTransformer(tree, dv.getSize(), true))); tree.addCorrespondences(); } viewPanel.add(dv, BorderLayout.CENTER); dv.revalidate(); repaint(); getContentPane().repaint(); return; } /** * Makes this frame unmodifiable, so that the tree it displays cannot be changed. In fact, * all that happens is the title is update and the navigation buttons are disabled. This * method is intended to prevent the user from modifying the frame, not to prevent other code * from modifying it. */ public void disableNavigationButtons() { setTitle(getTitle() + " (fixed)"); nextDerivation.setEnabled(false); previousDerivation.setEnabled(false); nextSource.setEnabled(false); previousSource.setEnabled(false); return; } } /** * An action listener that updates the source and target sentence lists when a new file is chosen * in the main frame of the application. * * @author Jonathan Weese * */ public static class FileChoiceListener implements ActionListener { /** * The button in the main frame's "Control" menu that's labelled "Open source file". */ private JMenuItem source; /** * The default constructor. * * @param s the button in the main frame's "Control" menu for opening a source file. * @param t the button in the main frame's "Control" menu for opening a target file. */ public FileChoiceListener(JMenuItem s, JMenuItem t) { super(); source = s; } /** * */ public void actionPerformed(ActionEvent e) { int ret = fileChooser.showOpenDialog(chooserFrame); if (ret == JFileChooser.APPROVE_OPTION) { File chosen = fileChooser.getSelectedFile(); JMenuItem origin = (JMenuItem) e.getSource(); if (origin.equals(source)) { sourceFile = chosen; populateSourceList(); } else { targetFile = chosen; setNBestLists(); populateTargetList(); } } return; } } public static class SentenceListListener implements ListSelectionListener { private JList source; public SentenceListListener(JList s, JList t) { source = s; } public void valueChanged(ListSelectionEvent e) { if (e.getSource().equals(source.getSelectionModel())) { populateTargetList(); targetList.setSelectedIndex(0); } int src = sourceList.getSelectedIndex(); int tgt = targetList.getSelectedIndex(); if (src < 1) activeFrame.previousSource.setEnabled(false); else activeFrame.previousSource.setEnabled(true); if (src == sourceList.getModel().getSize() - 1) activeFrame.nextSource.setEnabled(false); else activeFrame.nextSource.setEnabled(true); if (tgt < 1) activeFrame.previousDerivation.setEnabled(false); else activeFrame.previousDerivation.setEnabled(true); if (tgt == targetList.getModel().getSize() - 1) activeFrame.nextDerivation.setEnabled(false); else activeFrame.nextDerivation.setEnabled(true); activeFrame.drawGraph(); return; } } /** * This class represents a candidate translation. We want a distinct class for this because we * are keeping the candidate translations in a JList, and the JList by default displays items * as the results of their toString() method. Hence we want a toString method that will extract * the terminal symbols from the annotated derivation. * * @author Jonathan Weese * */ public static class Derivation { /** * The raw output from the joshua decoder. */ private String complete; /** * The terminal symbols of the derivation; a "human-readable" translation. */ private String terminals; /** * This constructor takes the raw annotated output from the joshua decoder. * * @param c a candidate translation from the joshua decoder. */ public Derivation(String c) { complete = c; terminals = extractTerminals(c); } /** * Gets the raw derivation from the joshua decoder. * * @return the annotated derivation-tree representation of this candidate translation. */ public String complete() { return complete; } public String toString() { return terminals; } /** * Extracts only the terminal symbols of a derivation-tree representation, in order. * * @param s a derivation-tree representation of a candidate translation * @return the terminal symbols of the representation. */ private static String extractTerminals(String s) { String tree = s.split(DerivationTree.DELIMITER)[1]; String [] tokens = tree.replaceAll("\\)", "\n)").split("\\s+"); String result = ""; for (String t : tokens) { if (t.startsWith("(") || t.equals(")")) continue; result += " " + t; } return result; } } }