content: /*
   * Copyright (C) 2010-2017 - Andreas Maier 
   * CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
   */
  package edu.stanford.rsl.apps.gui;
  
  import ij.ImagePlus;
  
  import java.awt.Color;
  import java.awt.GridBagConstraints;
  import java.awt.GridBagLayout;
  import java.awt.Insets;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.awt.event.KeyEvent;
  import java.awt.event.WindowListener;
  import java.io.File;
  import java.io.IOException;
  
  import javax.sound.sampled.AudioInputStream;
  import javax.sound.sampled.AudioSystem;
  import javax.sound.sampled.Clip;
  import javax.sound.sampled.DataLine;
  import javax.swing.AbstractAction;
  import javax.swing.JButton;
  import javax.swing.BorderFactory;
  import javax.swing.JFrame;
  import javax.swing.JLabel;
  import javax.swing.JMenu;
  import javax.swing.JMenuBar;
  import javax.swing.JMenuItem;
  import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  import javax.swing.JTextField;
  import javax.swing.SwingUtilities;
  import javax.swing.text.DefaultEditorKit;
  
  import edu.stanford.rsl.conrad.data.numeric.Grid3D;
  import edu.stanford.rsl.conrad.filtering.ImageFilteringTool;
  import edu.stanford.rsl.conrad.io.FileProjectionSource;
  import edu.stanford.rsl.conrad.io.ImagePlusProjectionDataSource;
  import edu.stanford.rsl.conrad.pipeline.BufferedProjectionSink;
  import edu.stanford.rsl.conrad.pipeline.ParallelImageFilterPipeliner;
  import edu.stanford.rsl.conrad.pipeline.ProjectionSource;
  import edu.stanford.rsl.conrad.reconstruction.ReconstructionFilter;
  import edu.stanford.rsl.conrad.utils.CONRAD;
  import edu.stanford.rsl.conrad.utils.Configuration;
  import edu.stanford.rsl.conrad.utils.DicomConfigurationUpdater;
  import edu.stanford.rsl.conrad.utils.DoubleArrayUtil;
  import edu.stanford.rsl.conrad.utils.FileUtil;
  import edu.stanford.rsl.conrad.utils.GUIUtil;
  import edu.stanford.rsl.conrad.utils.ImageUtil;
  import edu.stanford.rsl.conrad.utils.RegKeys;
  
  
  /**
   * This code was edited or generated using CloudGarden's Jigloo
   * SWT/Swing GUI Builder, which is free for non-commercial
   * use. If Jigloo is being used commercially (ie, by a corporation,
   * company or business for any purpose whatever) then you
   * should purchase a license for each developer using Jigloo.
   * Please visit www.cloudgarden.com for details.
   * Use of Jigloo implies acceptance of these licensing terms.
   * A COMMERCIAL LICENSE HAS NOT BEEN PURCHASED FOR
   * THIS MACHINE, SO JIGLOO OR THIS CODE CANNOT BE USED
   * LEGALLY FOR ANY CORPORATE OR COMMERCIAL PURPOSE.
   */
  public class ReconstructionPipelineFrame extends JFrame implements ActionListener, UpdateableGUI, Runnable {
  
  	/**
  	 * 
  	 */
  	private static final long serialVersionUID = -2788658929679662808L;
  	private JLabel jLabel1;
  	private JTextField jProjectionsTextField;
  	private JPanel jPanel1;
  	private JButton jLoadDataButton;
  	private JButton jEditConfigurationButton;
  	private JButton jEditPipelineButton;
  	private JButton jChooseBackprojector;
  	private JLabel jPipelineLabel;
  	private JButton jReconstructButton;
  	private JButton jProjectionDataChooseButton;
  	private JLabel jProjectionsLabel;
  	private GUICompatibleObjectVisualizationPanel sinkPanel;
  	private BufferedProjectionSink sink;
  	private boolean debug = false;
  	private ConfigurePipelineFrame pipelineFrame = null;
  	private ConfigurationFrame configFrame = null;
  	private ParallelImageFilterPipeliner filteringPipeline;
  	private Clip soundClip = null;
  
  
  	public ReconstructionPipelineFrame() {
  		sink = Configuration.getGlobalConfiguration().getSink();
  
  		
  		SwingUtilities.invokeLater(this);
  
  		// Load the sound file and setup playback
  		if (Configuration.getGlobalConfiguration().getRegistry().get(RegKeys.SOUND_FILE) != null) {
  			String name = Configuration.getGlobalConfiguration().getRegistry().get(RegKeys.SOUND_FILE);
  			if (name.isEmpty())
  				return;
  
  			AudioInputStream as = null;
  			try {
  				File in = new File(name);
  				as = AudioSystem.getAudioInputStream(in);
  			} catch (Exception ex) {
  				System.out.println("Failed to load sound file " + name);
  				return;
  			}
  
  			DataLine.Info info = new DataLine.Info(Clip.class, as.getFormat());
  			if (!AudioSystem.isLineSupported(info)) {
  				System.out.println("Audio output is not supported for this format");
  				return;
  			}
  
  			try {
  				soundClip = (Clip)AudioSystem.getLine(info);
  				soundClip.open(as);
  			} catch (Exception ex) {
  				soundClip = null;
  				System.out.println("Failed to setup sound playback (sound card probably busy)");
  				return;
  			}
  		}
  	}
  
  	public void updateGUI(){
  		sinkPanel.updateGUI();
  		repaint();
  	}
  
  	/**
  	 * Create an Edit menu to support cut/copy/paste.
  	 */
  	@SuppressWarnings("serial")
  	public JMenuBar createMenuBar () {
  		JMenuItem menuItem = null;
  		JMenuBar menuBar = new JMenuBar();
  
  		JMenu mainMenu = new JMenu("Configuration");
  		mainMenu.setMnemonic(KeyEvent.VK_C);
  
  
  		menuItem = new JMenuItem(new AbstractAction(){
  			public void actionPerformed(ActionEvent e) {
  				try {
  					String filename = FileUtil.myFileChoose(".xml", false);
  					Configuration.setGlobalConfiguration(Configuration.loadConfiguration(filename));
  					if (configFrame != null) configFrame.exit();
  					if (pipelineFrame != null) pipelineFrame.exit();
  				} catch (Exception e1) {
  					System.out.println(e1.getLocalizedMessage());
  				}
  			}
  		});
  		menuItem.setText("Load");
  		menuItem.setMnemonic(KeyEvent.VK_L);
  		mainMenu.add(menuItem);
  
  		menuItem = new JMenuItem(new AbstractAction(){
  			public void actionPerformed(ActionEvent e) {
  				try {
  					String filename = FileUtil.myFileChoose(".xml", true);
  					if (!filename.endsWith(".xml")) filename += ".xml";
  					Configuration.saveConfiguration(Configuration.getGlobalConfiguration(), filename);
  					Configuration.loadConfiguration(filename);
  				} catch (Exception e1) {
  					System.out.println(e1.getLocalizedMessage());
  				}
  			}
  		});
  		menuItem.setText("Save");
  		menuItem.setMnemonic(KeyEvent.VK_S);
  		mainMenu.add(menuItem);
  
  		menuBar.add(mainMenu);
  
  		mainMenu = new JMenu("Edit");
  		mainMenu.setMnemonic(KeyEvent.VK_E);
  
  		menuItem = new JMenuItem(new DefaultEditorKit.CutAction());
  		menuItem.setText("Cut");
  		menuItem.setMnemonic(KeyEvent.VK_T);
  		mainMenu.add(menuItem);
  
  		menuItem = new JMenuItem(new DefaultEditorKit.CopyAction());
  		menuItem.setText("Copy");
  		menuItem.setMnemonic(KeyEvent.VK_C);
  		mainMenu.add(menuItem);
  
  		menuItem = new JMenuItem(new DefaultEditorKit.PasteAction());
  		menuItem.setText("Paste");
  		menuItem.setMnemonic(KeyEvent.VK_P);
  		mainMenu.add(menuItem);
  
  		menuBar.add(mainMenu);
  
  
  		return menuBar;
  	}
  
  
  	private void initGUI() {
  		Configuration config = Configuration.getGlobalConfiguration();
  		try {
  			{
  				{
  					// Set Look & Feel
  					try {
  						javax.swing.UIManager
  						.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
  					} catch (Exception e) {
  						//e.printStackTrace();
  					}
  				}
  				getContentPane().setBackground(Color.WHITE);
  				this.setSize(640, 480);
  				this.setTitle("Reconstruction Pipeline " + CONRAD.VersionString);
  				this.setJMenuBar(createMenuBar());
  				GridBagLayout thisLayout = new GridBagLayout();
  				thisLayout.rowWeights = new double[] {0.1, 0.1, 0.1, 0.1, 0.1 };
  				thisLayout.rowHeights = new int[] {50, 50, 50, 50, 7 };
  				thisLayout.columnWeights = new double[] { 0.1, 0.1, 0.1, 0.1,
  						0.1 };
  				thisLayout.columnWidths = new int[] { 7, 7, 223, 7, 7 };
  				getContentPane().setLayout(thisLayout);
  				{
  					jLabel1 = new JLabel();
  					getContentPane().add(jLabel1, new GridBagConstraints(1, 0, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
  					jLabel1
  					.setText(CONRAD.CONRADDefinition);
  				}
  				jPanel1 = new JPanel();
  				jPanel1.setBorder(BorderFactory.createTitledBorder("Input"));
  				jPanel1.setBackground(Color.WHITE);
  				GridBagLayout jPanel1Layout = new GridBagLayout();
  				getContentPane().add(
  						jPanel1,
  						new GridBagConstraints(1, 1, 3, 1, 0.0, 0.0,
  								GridBagConstraints.CENTER,
  								GridBagConstraints.BOTH, new Insets(0, 0,
  										0, 0), 0, 0));
  				jPanel1Layout.rowWeights = new double[] { 0.1, 0.1 };
  				jPanel1Layout.rowHeights = new int[] { 40, 20 };
  				jPanel1Layout.columnWeights = new double[] {0.1, 0.1, 0.1};
  				jPanel1Layout.columnWidths = new int[] {80, 80, 80};
  				jPanel1.setLayout(jPanel1Layout);
  
  				{
  					jProjectionsTextField = new JTextField();
  					jPanel1.add(
  							jProjectionsTextField,
  							new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0,
  									GridBagConstraints.NORTH,
  									GridBagConstraints.HORIZONTAL, new Insets(
  											0, 0, 0, 0), 0, 0));
  					jProjectionsTextField.setText(config.getRecentFileOne());
  					jProjectionsTextField.addActionListener(this);
  					GUIUtil.enableDragAndDrop(jProjectionsTextField);
  					jProjectionsLabel = new JLabel();
  					jPanel1.add(
  							jProjectionsLabel,
  							new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
  									GridBagConstraints.NORTH,
  									GridBagConstraints.NONE, new Insets(0, 0,
  											0, 0), 0, 0));
  					jProjectionsLabel.setText("Projection Data:");
  					jProjectionDataChooseButton = new JButton();
  					jPanel1.add(jProjectionDataChooseButton, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.NORTH, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
  					jProjectionDataChooseButton.setText("Choose");
  					jProjectionDataChooseButton.addActionListener(this);
  					jLoadDataButton = new JButton();
  					jPanel1.add(jLoadDataButton, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.NORTH, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
  					jLoadDataButton.setText("Use Current ImagePlus");
  					jLoadDataButton.addActionListener(this);
  				}
  				jPanel1 = new JPanel();
  				jPanel1.setBorder(BorderFactory.createTitledBorder("Processing"));
  				jPanel1.setBackground(Color.WHITE);
  				jPanel1Layout = new GridBagLayout();
  				getContentPane().add(
  						jPanel1,
  						new GridBagConstraints(1, 2, 3, 1, 0.0, 0.0,
  								GridBagConstraints.CENTER,
  								GridBagConstraints.BOTH, new Insets(0, 0,
  										0, 0), 0, 0));
  				jPanel1Layout.rowWeights = new double[] { 0.1, 0.1, 0.1 };
  				jPanel1Layout.rowHeights = new int[] { 50, 0, 0 };
  				jPanel1Layout.columnWeights = new double[] {0.1,  0.1};
  				jPanel1Layout.columnWidths = new int[] {80, 80};
  				jPanel1.setLayout(jPanel1Layout);
  				{
  					{
  						jEditPipelineButton = new JButton();
  						jPanel1.add(jEditPipelineButton, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
  						jEditPipelineButton.setText("Edit Pipeline");
  						jEditPipelineButton.addActionListener(this);
  					}
  					{
  						jEditConfigurationButton = new JButton();
  						jPanel1.add(jEditConfigurationButton, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
  						jEditConfigurationButton.setText("Edit Configuration");
  						jEditConfigurationButton.addActionListener(this);
  					}
  
  				}
  				jPanel1 = new JPanel();
  				jPanel1.setBorder(BorderFactory.createTitledBorder("Output"));
  				jPanel1.setBackground(Color.WHITE);
  				jPanel1Layout = new GridBagLayout();
  				getContentPane().add(
  						jPanel1,
  						new GridBagConstraints(1, 3, 3, 1, 0.0, 0.0,
  								GridBagConstraints.CENTER,
  								GridBagConstraints.BOTH, new Insets(0, 0,
  										0, 0), 0, 0));
  				jPanel1Layout.rowWeights = new double[] { 0.1, 0.1, 0.1 };
  				jPanel1Layout.rowHeights = new int[] { 30, 80, 0 };
  				jPanel1Layout.columnWeights = new double[] {0.1, 0.1, 0.1};
  				jPanel1Layout.columnWidths = new int[] {80, 80, 80};
  				jPanel1.setLayout(jPanel1Layout);
  				{
  					sinkPanel = new GUICompatibleObjectVisualizationPanel(sink);
  					jPanel1.add(sinkPanel, new GridBagConstraints(0, 1, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
  					sinkPanel.setParentFrame(this);
  
  					jChooseBackprojector = new JButton();
  					jPanel1.add(jChooseBackprojector, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
  					jChooseBackprojector.setText("Choose Output");
  					jChooseBackprojector.addActionListener(this);
  				}
  			}
  			jPipelineLabel = new JLabel();
  			getContentPane().add(jPipelineLabel, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(30, 0, 0, 0), 0, 0));
  			jPipelineLabel.setText("Reconstruction Pipeline");
  			{
  				jReconstructButton = new JButton();
  				getContentPane().add(jReconstructButton, new GridBagConstraints(1, 4, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
  				jReconstructButton.setText("Reconstruct");
  				jReconstructButton.addActionListener(this);
  			}
  
  
  
  		} catch (Exception e) {
  			e.printStackTrace();
  		}
  	}
  
  
  	ImagePlus volumePreview = null;
  	ProjectionSource pSource = null;
  
  	public void actionPerformed(ActionEvent e) {
  		Object source = e.getSource();
  		if (source != null){
  			//jChooseBackprojector
  			if (source.equals(this.jProjectionDataChooseButton)){
  				try {
  					String file = FileUtil.myFileChoose("", false);
  					this.jProjectionsTextField.setText(file);
  					if (Configuration.getGlobalConfiguration().getImportFromDicomAutomatically()){
  						DicomConfigurationUpdater update = new DicomConfigurationUpdater();
  						Configuration config = Configuration.getGlobalConfiguration();
  						update.setConfig(config);
  						update.setFilename(this.jProjectionsTextField.getText());
  						update.readConfiguration();
  						config.setGeometry(Configuration.loadGeometrySource(config));
  						Configuration.setGlobalConfiguration(config);
  					}
  				} catch (Exception e1) {
  					// TODO Auto-generated catch block
  					//e1.printStackTrace();
  				}
  			}
  			if (source.equals(this.jEditPipelineButton)){
  				if (pipelineFrame == null){
  					pipelineFrame = new ConfigurePipelineFrame();
  					pipelineFrame.setParentFrame(this);
  					pipelineFrame.setVisible(true);
  					pipelineFrame.setLocation(CONRAD.getWindowTopCorner());
  				} else {
  					if (pipelineFrame.isExited()){
  						pipelineFrame = new ConfigurePipelineFrame();
  						pipelineFrame.setParentFrame(this);
  						pipelineFrame.setVisible(true);		
  					} else {
  						if (! pipelineFrame.isVisible()) {
  							pipelineFrame.setVisible(true);
  						}
  					}
  				}
  			}
  			if (source.equals(this.jEditConfigurationButton)){
  				if (configFrame == null){
  					configFrame = new ConfigurationFrame();
  
  					configFrame.setParentFrame(this);
  					configFrame.setVisible(true);
  					configFrame.setLocation(CONRAD.getWindowTopCorner());
  				} else {
  					if (configFrame.isExited()){
  						configFrame = new ConfigurationFrame();
  						configFrame.setParentFrame(this);
  						configFrame.setVisible(true);		
  					} else {
  						if (! configFrame.isVisible()) {
  							configFrame.setVisible(true);
  						}
  					}
  				}
  			}
  			if (source.equals(this.jLoadDataButton)){
  				pSource = new ImagePlusProjectionDataSource();
  				((ImagePlusProjectionDataSource)pSource).initStream(null);
  			}
  			if (source.equals(this.jChooseBackprojector)){
  				Configuration config = Configuration.getGlobalConfiguration(); 
  				BufferedProjectionSink [] sinks = BufferedProjectionSink.getProjectionDataSinks();
  				BufferedProjectionSink selected = (BufferedProjectionSink) JOptionPane.showInputDialog(this, "Please select the reconstruction algorithm: ", "Select Reconstruction Algorithm", JOptionPane.INFORMATION_MESSAGE, null, sinks, sink);
  				if (selected != null) {
  					sink = selected;
  					sink.setConfiguration(config);
  					sinkPanel.setVisualizedObject(sink);
  					config.setSink(sink);
  				}
  				updateGUI();
  			}
  			if (source.equals(this.jReconstructButton)){
  				if (pSource == null){
  					try {
  						pSource = FileProjectionSource.openProjectionStream(jProjectionsTextField.getText());
  					} catch (IOException e1) {
  						if (debug) e1.printStackTrace();
  						JOptionPane.showMessageDialog(this, "Could not open projection source.");
  					}
  				}
  				if (pSource != null){
  					if (sink.isConfigured()) {
  						ImageFilteringTool [] filters = Configuration.getGlobalConfiguration().getFilterPipeline();
  						boolean isConfigured = true;
  						boolean recoTest = false;
  
  						for (int i = 0; i < filters.length; i++){
  							if (!filters[i].isConfigured()) isConfigured = false;
  							if(filters[i] instanceof ReconstructionFilter) recoTest = true;
  						}
  						final boolean isReconstruction = recoTest;
  						if (isConfigured) {
  							volumePreview = ImageUtil.wrapGrid3D(sink.getProjectionVolume(), "Preview");
  							if (volumePreview != null) volumePreview.show();
  							filteringPipeline = new ParallelImageFilterPipeliner(pSource, filters, sink);
  							Thread thread = new Thread(new Runnable(){
  								public void run(){
  									long time = System.currentTimeMillis();
  									try {
  										filteringPipeline.project();
  									} catch (Exception e) {
  										e.printStackTrace();
  									}
  									Grid3D result = sink.getResult();
  
  									time = System.currentTimeMillis() - time;
  									if (volumePreview == null) {
  										volumePreview = ImageUtil.wrapGrid3D(result, "Result of " + pSource);
  
  
  										ImageUtil.applyConradImageCalibration(volumePreview, isReconstruction);
  
  
  										if (volumePreview != null){
  											volumePreview.show();
  										}
  									}
  									if (volumePreview != null){
  										File filename = new File(jProjectionsTextField.getText());
  										volumePreview.setTitle(filename.getName());
  										if (pSource instanceof ImagePlusProjectionDataSource){
  											ImagePlusProjectionDataSource s = (ImagePlusProjectionDataSource) pSource;
  											volumePreview.setTitle("Reconstruction of " + s.getTitle());
  										}
  									}
  									filteringPipeline = null;
  									pSource = null;
  									CONRAD.log("Reconstruction time (sec):" + time);
  									try {
  										sink = sink.getClass().newInstance();
  										sinkPanel.setVisualizedObject(sink);
  										updateGUI();
  									} catch (InstantiationException e) {
  										// TODO Auto-generated catch block
  										e.printStackTrace();
  									} catch (IllegalAccessException e) {
  										// TODO Auto-generated catch block
  										e.printStackTrace();
  									}
  									DoubleArrayUtil.visualizeBufferedArrays("Current Weighting");
  
  									// Play the sound if so desired
  									if (soundClip != null) {
  										soundClip.setFramePosition(0);
  										soundClip.start();
  									}
  								}
  							});
  							thread.start();
  
  						} else {
  							JOptionPane.showMessageDialog(this, "Not all filters of the reconstruction pipeline are configured. Please correct this.");
  						}
  
  
  					} else {
  						JOptionPane.showMessageDialog(this, "Reconstruction Algorithm is not configured.");
  					}
  				}
  			}
  			updateGUI();
  		}
  	}
  
  	/**
  	 * Method to start Conrad Reconstruction Pipeline Frame. This method allows to register your own window listener for call backs. If "null" is used the default window listener will be used that will terminate the VM if the pipeline frame or ImageJ are closed.
  	 * @param listen the window listener
  	 * @return
  	 */
  	public static WindowListener startConrad(WindowListener listen) {
  		WindowListener global = listen;
  		if (listen==null) global = CONRAD.setup();
  		else CONRAD.setup(listen);
  		ReconstructionPipelineFrame oscar = new ReconstructionPipelineFrame();
  		oscar.setVisible(true);
  		oscar.setLocation(CONRAD.getWindowTopCorner());
  		oscar.addWindowListener(global);
  		return global;
  	}
  
  	/**
  	 * Start Conrad with default options.
  	 * @param args
  	 */
  	public static void main(String [] args){
  		startConrad(null);
  	}
  
  	@Override
  	public void run() {
  		initGUI();
  		updateGUI();
  	}
  
  }
  