// Java packages
import java.awt.*;
import java.awt.event.*;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.Reader;
import java.nio.*;
import java.util.*;

import javax.swing.*;

import com.bitplane.imaris.imaris.*;
import com.bitplane.imarisserver.imarisserver.*;
import com.jniwrapper.*;
import com.jniwrapper.win32.automation.types.*;
import com.jniwrapper.win32.com.types.*;
import com.jniwrapper.win32.ole.OleFunctions;

import ij.*;
import ij.gui.*;
import ij.io.*;
import ij.plugin.PlugIn;
import ij.plugin.frame.*;
import ij.process.*;


public class bpImaris_Adapter implements PlugIn {
	public GUI mTheGUI = null;
	public Adapter mAdapter = null;

	private int mImarisID = -1;
	private boolean mIsImageJStartedFromImaris = false;

	static private int mTheAdapter = 0;
  

	void showAbout() {
		IJ.showMessage(
									"This plugin permits communication between ImageJ\n" + 
									"and Bitplane Imaris(r). The communication protocol\n" +
									"COM is used through a third party library called\n" + 
									"JNIWrapper(r). This has to be purchased at\n" + 
									"www.jniwrapper.com in order to further develop the plugin.");
	}

	public void run(String arg) {
    if (arg.equals("about")) {
			showAbout(); return;
		}
		
		if (!arg.equals("")) {
			mImarisID = Integer.parseInt(arg.substring(arg.indexOf("ImarisID=")+9,arg.length()));
			if (mImarisID != -1) {
				mIsImageJStartedFromImaris = true;
			}                                   
		}


		// If the plugin was started from a macro, let's find the arguments if they exists
		String vMacroOptions = Macro.getOptions();                                        
		if (vMacroOptions != null) {
			if (vMacroOptions.indexOf("ImarisID=") != -1) {
				//IJ.showMessage(vMacroOptions);
				// The plugin is "called" by Imaris, so let's get the ID
				mImarisID = Integer.parseInt(vMacroOptions.substring(vMacroOptions.indexOf("ImarisID=")+9,vMacroOptions.indexOf(" ")));
				if (mImarisID != 0) {
					mIsImageJStartedFromImaris = true;
				}                            
			}
			
		}

		if (mTheAdapter == 0) {
			// We want only one Application
			mTheAdapter++;
			mTheGUI = new GUI();
			mTheGUI.show();
		}
		else {
			return;
		} 
	}

  
  /**
   *
   * @author  Michael
   */
  public class GUI extends javax.swing.JFrame {
	private javax.swing.JButton jButtonExit;
	private javax.swing.JButton jButtonExport;
	private javax.swing.JButton jButtonImport;
	private javax.swing.JButton jButtonLoadFile;
	private javax.swing.JButton jButtonShowInfo;
	private javax.swing.JButton jButtonStartMacroLoop;
	private javax.swing.JButton jButtonStartStop;
	private javax.swing.JCheckBox jCheckBoxMacroAllChannel;
	private javax.swing.JCheckBox jCheckBoxMacroNewChannel;
	private javax.swing.JCheckBox jCheckBoxNewDataSet;
	private javax.swing.JLabel jLabelChannel;
	private javax.swing.JLabel jLabelMacroChannel;
	private javax.swing.JLabel jLabelTime;
	private javax.swing.JPanel jPanelActionInner;
	private javax.swing.JPanel jPanelActionOuter;
	private javax.swing.JPanel jPanelChannel;
	private javax.swing.JPanel jPanelImpExp;
	private javax.swing.JPanel jPanelImpExpButtonsInner;
	private javax.swing.JPanel jPanelImpExpButtonsOuter;
	private javax.swing.JPanel jPanelImpExpMacro;
	private javax.swing.JPanel jPanelImpExpOptionsInner;
	private javax.swing.JPanel jPanelImpExpOptionsOuter;
	private javax.swing.JPanel jPanelInput;
	private javax.swing.JPanel jPanelMacro;
	private javax.swing.JPanel jPanelMacroButtonsInner;
	private javax.swing.JPanel jPanelMacroButtonsOuter;
	private javax.swing.JPanel jPanelMacroChannel;
	private javax.swing.JPanel jPanelMacroOptionsInner;
	private javax.swing.JPanel jPanelMacroOptionsOuter;
	private javax.swing.JPanel jPanelMain;
	private javax.swing.JPanel jPanelOutput;
	private javax.swing.JPanel jPanelTime;
	private javax.swing.JScrollPane jScrollPaneOutput;
	private javax.swing.JTextArea jTextAreaOutput;
	private javax.swing.JTextField jTextFieldChannel;
	private javax.swing.JTextField jTextFieldMacroChannel;
	private javax.swing.JTextField jTextFieldTime;
  
	  /** Creates new form TestGUI */
	  public GUI() {

		initComponents();

		// initialize the Adapter
		mAdapter = new Adapter();
	  }
  
	  
	  private void initComponents() {//GEN-BEGIN:initComponents
		jPanelMain = new javax.swing.JPanel();
		jPanelInput = new javax.swing.JPanel();
		jPanelImpExpMacro = new javax.swing.JPanel();
		jPanelImpExp = new javax.swing.JPanel();
		jPanelImpExpOptionsOuter = new javax.swing.JPanel();
		jPanelImpExpOptionsInner = new javax.swing.JPanel();
		jPanelTime = new javax.swing.JPanel();
		jLabelTime = new javax.swing.JLabel();
		jTextFieldTime = new javax.swing.JTextField();
		jCheckBoxNewDataSet = new javax.swing.JCheckBox();
		jPanelChannel = new javax.swing.JPanel();
		jLabelChannel = new javax.swing.JLabel();
		jTextFieldChannel = new javax.swing.JTextField();
		jPanelImpExpButtonsOuter = new javax.swing.JPanel();
		jPanelImpExpButtonsInner = new javax.swing.JPanel();
		jButtonImport = new javax.swing.JButton();
		jButtonExport = new javax.swing.JButton();
		jPanelMacro = new javax.swing.JPanel();
		jPanelMacroOptionsOuter = new javax.swing.JPanel();
		jPanelMacroOptionsInner = new javax.swing.JPanel();
		jCheckBoxMacroAllChannel = new javax.swing.JCheckBox();
		jPanelMacroChannel = new javax.swing.JPanel();
		jLabelMacroChannel = new javax.swing.JLabel();
		jTextFieldMacroChannel = new javax.swing.JTextField();
		jCheckBoxMacroNewChannel = new javax.swing.JCheckBox();
		jPanelMacroButtonsOuter = new javax.swing.JPanel();
		jPanelMacroButtonsInner = new javax.swing.JPanel();
		jButtonStartMacroLoop = new javax.swing.JButton();
		jPanelActionOuter = new javax.swing.JPanel();
		jPanelActionInner = new javax.swing.JPanel();
		jButtonStartStop = new javax.swing.JButton();
		jButtonShowInfo = new javax.swing.JButton();
		jButtonLoadFile = new javax.swing.JButton();
		jButtonExit = new javax.swing.JButton();
		jPanelOutput = new javax.swing.JPanel();
		jScrollPaneOutput = new javax.swing.JScrollPane();
		jTextAreaOutput = new javax.swing.JTextArea();

		setTitle("bpImaris Adapter");
		addWindowListener(new java.awt.event.WindowAdapter() {
			/**
			 * Invoked when a window has been opened.
			 */
			public void windowOpened(WindowEvent e) {
			  // perhaps a running Imaris asked for the connexion...
			  // wait until the plugin window is there and try to connect.
			  try {
				Thread.sleep(100);
			  } catch (Exception exc) {
			  }
			  if (mIsImageJStartedFromImaris) {
				mAdapter.ConnectToImaris();
			  }
			  UpdateUserInterface();
			}
			/**
			 * Invoked when a window is in the process of being closed.
			 * The close operation can be overridden at this point.
			 */
			public void windowClosing(WindowEvent e) {
			  // Ask the user if he wants to save something before closing...
			  ExitPlugin();
			}
		  });
		  
		jPanelMain.setLayout(new java.awt.BorderLayout());

		jPanelMain.setFont(new java.awt.Font("Arial", 0, 10));
		jPanelInput.setLayout(new java.awt.BorderLayout());

		jPanelInput.setBorder(new javax.swing.border.EmptyBorder(new java.awt.Insets(3, 3, 3, 3)));
		jPanelImpExpMacro.setLayout(new java.awt.BorderLayout());

		jPanelImpExp.setLayout(new java.awt.BorderLayout());

		jPanelImpExp.setBorder(new javax.swing.border.TitledBorder("Import / Export"));
		jPanelImpExpOptionsOuter.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT));

		jPanelImpExpOptionsInner.setLayout(new java.awt.GridLayout(0, 2, 5, 5));

		jPanelTime.setLayout(new java.awt.BorderLayout());

		jPanelTime.setToolTipText("Select time index for importing and exporting");
		jLabelTime.setText("Time: ");
		jPanelTime.add(jLabelTime, java.awt.BorderLayout.WEST);

		jTextFieldTime.setColumns(3);
		jTextFieldTime.setHorizontalAlignment(javax.swing.JTextField.RIGHT);		
		jTextFieldTime.setText("1");
		jPanelTime.add(jTextFieldTime, java.awt.BorderLayout.EAST);

		jPanelImpExpOptionsInner.add(jPanelTime);

		jCheckBoxNewDataSet.setText("New DataSet");
		jCheckBoxNewDataSet.setToolTipText("Create a new DataSet in Imaris when exporting");
		jCheckBoxNewDataSet.addActionListener(new java.awt.event.ActionListener() {
			/**
			 * Invoked when an action occurs.
			 */
			public void actionPerformed(ActionEvent e) {
			  UpdateUserInterface();
			}
		  });
		jPanelImpExpOptionsInner.add(jCheckBoxNewDataSet);

		jPanelChannel.setLayout(new java.awt.BorderLayout());

		jPanelChannel.setToolTipText("Select channel for importing and exporting.");
		jLabelChannel.setText("Channel: ");
		jPanelChannel.add(jLabelChannel, java.awt.BorderLayout.WEST);

		jTextFieldChannel.setColumns(3);
		jTextFieldChannel.setHorizontalAlignment(javax.swing.JTextField.RIGHT);		
		jTextFieldChannel.setText("1");
		jPanelChannel.add(jTextFieldChannel, java.awt.BorderLayout.EAST);

		jPanelImpExpOptionsInner.add(jPanelChannel);

		jPanelImpExpOptionsOuter.add(jPanelImpExpOptionsInner);

		jPanelImpExp.add(jPanelImpExpOptionsOuter, java.awt.BorderLayout.NORTH);

		jPanelImpExpButtonsOuter.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT));

		jPanelImpExpButtonsInner.setLayout(new java.awt.GridLayout(1, 0, 5, 5));

		jButtonImport.setText("Import Data");
		jButtonImport.setToolTipText("Import a DataSet from Imaris");
		jButtonImport.addActionListener(new java.awt.event.ActionListener() {
			public void actionPerformed(java.awt.event.ActionEvent evt) {
				jButtonImportActionPerformed(evt);
			}
		});

		jPanelImpExpButtonsInner.add(jButtonImport);

		jButtonExport.setText("Export Data");
		jButtonExport.setToolTipText("Export the current DataSet to Imaris");
		jButtonExport.addActionListener(new java.awt.event.ActionListener() {
			public void actionPerformed(java.awt.event.ActionEvent evt) {
				jButtonExportActionPerformed(evt);
			}
		});

		jPanelImpExpButtonsInner.add(jButtonExport);

		jPanelImpExpButtonsOuter.add(jPanelImpExpButtonsInner);

		jPanelImpExp.add(jPanelImpExpButtonsOuter, java.awt.BorderLayout.SOUTH);

		jPanelImpExpMacro.add(jPanelImpExp, java.awt.BorderLayout.NORTH);

		jPanelMacro.setLayout(new java.awt.BorderLayout());

		jPanelMacro.setBorder(new javax.swing.border.TitledBorder("Macro Loop"));
		jPanelMacroOptionsOuter.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT));

		jPanelMacroOptionsInner.setLayout(new java.awt.GridLayout(0, 2, 5, 5));

		jCheckBoxMacroAllChannel.setText("All Channels");
		jCheckBoxMacroAllChannel.setToolTipText("Macro loop effects all channels");
		jCheckBoxMacroAllChannel.setSelected(true);
		jCheckBoxMacroAllChannel.addActionListener(new java.awt.event.ActionListener() {
			/**
			 * Invoked when an action occurs.
			 */
			public void actionPerformed(ActionEvent e) {
			  UpdateUserInterface();
			}
		  });
		jPanelMacroOptionsInner.add(jCheckBoxMacroAllChannel);

		jPanelMacroChannel.setLayout(new java.awt.BorderLayout());

		jPanelMacroChannel.setToolTipText("Select channel for the macro loop");
		jLabelMacroChannel.setText("Channel: ");
		jPanelMacroChannel.add(jLabelMacroChannel, java.awt.BorderLayout.WEST);

		jTextFieldMacroChannel.setColumns(3);
		jTextFieldMacroChannel.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
		jTextFieldMacroChannel.setText("1");
		jPanelMacroChannel.add(jTextFieldMacroChannel, java.awt.BorderLayout.EAST);

		jPanelMacroOptionsInner.add(jPanelMacroChannel);

		jCheckBoxMacroNewChannel.setText("New Channels");
		jCheckBoxMacroNewChannel.setToolTipText("Macro loop creates new channels");
		jCheckBoxMacroNewChannel.setSelected(true);
		jCheckBoxMacroNewChannel.addActionListener(new java.awt.event.ActionListener() {
			/**
			 * Invoked when an action occurs.
			 */
			public void actionPerformed(ActionEvent e) {
			  UpdateUserInterface();
			}
		  });
		jPanelMacroOptionsInner.add(jCheckBoxMacroNewChannel);

		jPanelMacroOptionsOuter.add(jPanelMacroOptionsInner);

		jPanelMacro.add(jPanelMacroOptionsOuter, java.awt.BorderLayout.NORTH);

		jPanelMacroButtonsOuter.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT));

		jPanelMacroButtonsInner.setLayout(new java.awt.GridLayout(1, 0, 5, 5));

		jButtonStartMacroLoop.setText("Start Macro Loop");
		jButtonStartMacroLoop.setToolTipText("Select and Start Macro Loop");
		jButtonStartMacroLoop.addActionListener(new java.awt.event.ActionListener() {
			public void actionPerformed(java.awt.event.ActionEvent evt) {
				jButtonStartMacroLoopActionPerformed(evt);
			}
		});

		jPanelMacroButtonsInner.add(jButtonStartMacroLoop);

		jPanelMacroButtonsOuter.add(jPanelMacroButtonsInner);

		jPanelMacro.add(jPanelMacroButtonsOuter, java.awt.BorderLayout.SOUTH);

		jPanelImpExpMacro.add(jPanelMacro, java.awt.BorderLayout.SOUTH);

		jPanelInput.add(jPanelImpExpMacro, java.awt.BorderLayout.CENTER);

		jPanelActionOuter.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT));

		jPanelActionOuter.setBorder(new javax.swing.border.TitledBorder("Actions"));
		jPanelActionInner.setLayout(new java.awt.GridLayout(2, 0, 5, 5));

		jButtonStartStop.setText("Start/Stop Imaris");
		jButtonStartStop.setToolTipText("Start/Stop Imaris");
		jButtonStartStop.addActionListener(new java.awt.event.ActionListener() {
			public void actionPerformed(java.awt.event.ActionEvent evt) {
				jButtonStartStopActionPerformed(evt);
			}
		});

		jPanelActionInner.add(jButtonStartStop);

		jButtonShowInfo.setText("Show Info");
		jButtonShowInfo.setToolTipText("Show Information about the loaded Imaris file");
		jButtonShowInfo.addActionListener(new java.awt.event.ActionListener() {
			public void actionPerformed(java.awt.event.ActionEvent evt) {
				jButtonShowInfoActionPerformed(evt);
			}
		});

		jPanelActionInner.add(jButtonShowInfo);

		jButtonLoadFile.setText("Load a file in Imaris");
		jButtonLoadFile.setToolTipText("Load a file in Imaris");
		jButtonLoadFile.addActionListener(new java.awt.event.ActionListener() {
			public void actionPerformed(java.awt.event.ActionEvent evt) {
				jButtonLoadFileActionPerformed(evt);
			}
		});

		jPanelActionInner.add(jButtonLoadFile);

		jButtonExit.setText("Exit");
		jButtonExit.setToolTipText("Exit the plugin and stops Imaris");
		jButtonExit.addActionListener(new java.awt.event.ActionListener() {
			public void actionPerformed(java.awt.event.ActionEvent evt) {
				jButtonExitActionPerformed(evt);
			}
		});

		jPanelActionInner.add(jButtonExit);

		jPanelActionOuter.add(jPanelActionInner);

		jPanelInput.add(jPanelActionOuter, java.awt.BorderLayout.NORTH);

		jPanelMain.add(jPanelInput, java.awt.BorderLayout.NORTH);

		jPanelOutput.setLayout(new java.awt.BorderLayout());

		jPanelOutput.setBorder(new javax.swing.border.TitledBorder("Output"));
		jTextAreaOutput.setEditable(false);
		jTextAreaOutput.setBackground(Color.gray);
		jTextAreaOutput.setText("");
		jTextAreaOutput.setRows(5);
		jScrollPaneOutput.setViewportView(jTextAreaOutput);

		jPanelOutput.add(jScrollPaneOutput, java.awt.BorderLayout.CENTER);

		jPanelMain.add(jPanelOutput, java.awt.BorderLayout.CENTER);

		getContentPane().add(jPanelMain, java.awt.BorderLayout.CENTER);

        
		pack();
	  }//GEN-END:initComponents
  
	  private void jButtonStartMacroLoopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonStartMacroLoopActionPerformed
		  // TODO add your handling code here:
		  ApplyMacroToWholeDataSet();
	  }//GEN-LAST:event_jButtonStartMacroLoopActionPerformed
  
	  private void jButtonExportActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonExportActionPerformed
		  // TODO add your handling code here:
		  SendImarisDataSet();
	  }//GEN-LAST:event_jButtonExportActionPerformed
  
	  private void jButtonImportActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonImportActionPerformed
		  // TODO add your handling code here:
		  GetImarisDataSet();
	  }//GEN-LAST:event_jButtonImportActionPerformed
  
	  private void jButtonLoadFileActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonLoadFileActionPerformed
		  // TODO add your handling code here:
		  LoadFileInImaris();
	  }//GEN-LAST:event_jButtonLoadFileActionPerformed
  
	  private void jButtonExitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonExitActionPerformed
		  // TODO add your handling code here:
		  ExitPlugin();
	  }//GEN-LAST:event_jButtonExitActionPerformed
  
	  private void jButtonShowInfoActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonShowInfoActionPerformed
		  // TODO add your handling code here:
		  ShowInfo();
	  }//GEN-LAST:event_jButtonShowInfoActionPerformed
  
	  private void jButtonStartStopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonStartStopActionPerformed
		  // TODO add your handling code here:
		  StartStopImaris();
	  }//GEN-LAST:event_jButtonStartStopActionPerformed
  
	  /**
  		* Records the commands in the macro recorder if it is open.
	    * (Not in use in the current version)
	    * 
		* @param aKey a string representing the command
		* @param aValue
		*/
	  public void SetMacroCommand(String aKey, String aValue) {
		  if (Recorder.record) {
			  Recorder.setCommand("bpImaris Adapter");
			  if (aValue != "") {
				  Recorder.recordOption(aKey,aValue);
			  }
			  else {
				  Recorder.recordOption(aKey);
			  }                             
			  Recorder.saveCommand();
		  }
	  }



	  /**
	   * Updates the GUI
	   */
	  public void UpdateUserInterface() {
		  if (jCheckBoxMacroAllChannel.isSelected()) {
			  jLabelMacroChannel.setEnabled(false);
			  jTextFieldMacroChannel.setEnabled(false);      
		  } 
		  else {
			  jLabelMacroChannel.setEnabled(true);
			  jTextFieldMacroChannel.setEnabled(true);
		  }

		  if (mIsImageJStartedFromImaris) {
			  jButtonStartStop.setEnabled(false);
		  }
		  else {
			  jButtonStartStop.setEnabled(true);
		  }
		  // ask for redraw
		  this.invalidate();
	  }

	  /**
	   * Start or stop Imaris
	   */
	  public void StartStopImaris() {
		if (mAdapter.IsImarisConnected()) {
		  // Imaris is already started, so try to stop it
		  if (mAdapter.QuitImaris()) {
			//SetMacroCommand("Stop","");
			jTextAreaOutput.append("Imaris successfully quitted.\n");
		  }
		  else {
			jTextAreaOutput.append("Error during start from Imaris.\n");
		  }
		}
		else {
		  if (mAdapter.StartImaris()) {
			//SetMacroCommand("Start","");
			jTextAreaOutput.append("Imaris successfully started.\n");
		  } 
		  else {
			jTextAreaOutput.append("Error during start from Imaris.\n");
		  }
		}  
	  }

	  /**
	   * Exits the plugin
	   */
	  public void ExitPlugin() {
		if (mAdapter.IsImarisConnected()) {
		  if (!mIsImageJStartedFromImaris) {
			// ask the user if he is really sure, cause this will close Imaris as well...
			int i = JOptionPane.showConfirmDialog(this,"This will also close Imaris, are you sure ?","Exit",JOptionPane.YES_NO_OPTION,JOptionPane.WARNING_MESSAGE);
			if (i == JOptionPane.YES_OPTION) {
			  mAdapter.QuitImaris();
			  this.dispose();
			}
			else if (i == JOptionPane.NO_OPTION) {
			  return;
			}
		  }
		  else {
			// Imaris was already started before the plugin, so do not kill it!
			mAdapter.DisconnectFromImaris();
			this.dispose();
		  }
		} 
		else {
		  // There is no Imaris, so just close the plugin...
		  this.dispose();
		}
		if (mTheAdapter != 0) {
		  mTheAdapter = 0;
		  //SetMacroCommand("exit","");
		}
	  }

	  /**
	   * Imports Imaris DataSet into ImageJ
	   */
	  public void GetImarisDataSet() {
		if (mAdapter.IsImarisConnected()) {
		  int vSelectedChannel = 1;
		  int vSelectedTimepoint = 1;
		  // Get the selected channel and timepoint from GUI.
		  try {
			vSelectedChannel = Integer.parseInt(jTextFieldChannel.getText());
			vSelectedTimepoint = Integer.parseInt(jTextFieldTime.getText());
		  } catch (Exception e) {
			// either channel or timepoint was not an Integer!
			IJ.showMessage("Please insert an existing channel\n or/and timepoint index\n");
			return;
		  }
		  // Call the import method
		  if (mAdapter.ImportShowSelectedImarisDataSet(vSelectedChannel,vSelectedTimepoint)) {
			//SetMacroCommand("Import",Integer.toString(vSelectedChannel) + "," + Integer.toString(vSelectedTimepoint));
			jTextAreaOutput.append("Stack imported.\n");
		  } else {
			jTextAreaOutput.append("Error during Import.\n");
		  }
		}
	  }

	  /**
	   * Exports ImageJ DataSet into Imaris
	   */
	  public void SendImarisDataSet() {
		if (mAdapter.IsImarisConnected()) {
		  int vSelectedChannel = 1;
		  int vSelectedTimepoint = 1;
		  // Get the selected channel and timepoint and whether it is necessary to create a new DataSet into Imaris...
		  try {
			vSelectedChannel = Integer.parseInt(jTextFieldChannel.getText());
			vSelectedTimepoint = Integer.parseInt(jTextFieldTime.getText());
		  } catch (Exception e) {
			// either channel or timepoint was not an Integer!
			IJ.showMessage("Please insert a channel\n or/and timepoint index\n");
			return;
		  }
		  boolean vCreateNewImage = jCheckBoxNewDataSet.isSelected();
		  // Call the export method
		  if (mAdapter.ExportDataSetToImaris(vSelectedChannel,vSelectedTimepoint,vCreateNewImage)) {
			//SetMacroCommand("export", Integer.toString(vSelectedChannel) + "," + Integer.toString(vSelectedTimepoint) + " " + String.valueOf(vCreateNewImage));
			jTextAreaOutput.append("Stack exported.\n");
		  } else {
			jTextAreaOutput.append("Error during Export.\n");
		  }
		}
	  }

	  /**
	   * Loads a file into Imaris
	   */
	  public void LoadFileInImaris() {
		if (mAdapter.IsImarisConnected()) {
		  // ask for a file
		  OpenDialog vDialog = new OpenDialog("Select a file to load into Imaris","");
		  String vFileName = vDialog.getFileName();
		  String vDirectory = vDialog.getDirectory();
  
		  if (vFileName.equals("")) {
			// there is no file...
			return;
		  }
		  // Call the load file method
		  if (mAdapter.LoadFileInImaris(vDirectory + vFileName)) {
			//SetMacroCommand("Load",vDirectory + vFileName);
			jTextAreaOutput.append("File loaded into Imaris.\n");
		  } else {
			jTextAreaOutput.append("Error during Loading.\n");
		  }
		}
	  }

	  /**
	   * Displays information on the current Imaris DataSet
	   */
	  public void ShowInfo() {
		if (mAdapter.IsImarisConnected()) {
		  // Call the showInfo method
		  Vector vInfos = mAdapter.ShowInfo();
		  jTextAreaOutput.append("Imaris DataSet Information\n\n");
		  Iterator vPos = vInfos.iterator();
		  // Display the informations
		  while (vPos.hasNext()) {
			jTextAreaOutput.append(vPos.next() + "\n");
		  }
		  //SetMacroCommand("ShowInfo","");
		}
	  }

	  /**
	   * Apply a selected macro to all channels and timepoints of Imaris DataSet.
	   * Or to a selected channel.
	   */
	  public void ApplyMacroToWholeDataSet() {
		if (mAdapter.IsImarisConnected()) {
		  // Ask for a macro file
		  OpenDialog vDialog = new OpenDialog("Select the ImageJ Macro to be used","");
		  String vFileName = vDialog.getFileName();
		  String vDirectory = vDialog.getDirectory();
  
		  if (vFileName.equals("")) {
			// no macro file selected
			return;
		  }
		  if (!vFileName.endsWith(".txt")) {
			// a typical ImageJ macro file ends with .txt!
			IJ.showMessage("ImageJ macros are .txt files.");
			return;
		  }
  
		  // Get the information from the check-boxes, should the method create new channels or
		  // overwrite the existing ones? Should the method use one channel or all?
		  boolean vCreateNewChannels = jCheckBoxMacroNewChannel.isSelected();
		  boolean vApplyOnAllChannels = jCheckBoxMacroAllChannel.isSelected();
		  int vSelectedChannel = 0;
		  if (!vApplyOnAllChannels) {
			// The macro is for one channel only...
			try {
			  vSelectedChannel = Integer.parseInt(jTextFieldMacroChannel.getText());
			} catch (Exception e) {
			  // This was probably not an integer!
			  IJ.showMessage("Please insert a correct channel number.\n");
			  return;
			}
		  }

		  if (mAdapter.ApplyMacroToWholeDataSet(vDirectory+vFileName,vCreateNewChannels,vSelectedChannel)) {
			//SetMacroCommand("macroloop",vDirectory+vFileName + " " + String.valueOf(vCreateNewChannels) + " " + Integer.toString(vSelectedChannel));
			jTextAreaOutput.append("Macro succesfully applied to Imaris DataSet.\n");
		  } 
		  else {
			jTextAreaOutput.append("Error occured during Macro application.\n");
		  }
		}
	  }
	  }




	/***************************************************************************************************************
		/**
		 * THE Adapter
		 */
	class Adapter {
		// the Imaris Application
		private IApplication mImaris = null;
		// the ImarisServer Application
		//private IServer mImarisServer = null;

		/**
		 * Constructor
		 */
		public Adapter() {
		}


		/**
		 * Initialize COMmunication.
		 * 
		 * Necessary for any use of COM through JNIWrapper
		 */
		private void init() {
			IJ.showStatus("Starting COM connection...");
			OleFunctions.oleInitialize();
		}

		/**
		 * End COMmunication
		 * 
		 * Necessary to really stop Imaris from running...
		 */
		private void shutdown() {
			IJ.showStatus("Closing COM connection...");
			System.gc();
			OleFunctions.oleUninitialize();
		}

		

		/**
		 * Tell whether Imaris is launched or not
		 * 
		 * @return true if Imaris is launched
		 */
		public boolean IsImarisConnected() {
			return(mImaris!=null);
		}

		/**
		 * Connect the plugin to the running ImageJ.
		 * A valid ImarisID is needed in order to do that. It is provided by Imaris itself.
		 * 
		 * @return true if success
		 */
		public boolean ConnectToImaris() {
			if (mImaris == null && mIsImageJStartedFromImaris == true) {
			  init();
				// ImageJ was started from Imaris, so find Imaris server to get a connexion
				IJ.showStatus("Starting Imaris Server...");
				IServer mImarisServer = null;
				mImarisServer = Server.create(ClsCtx.LOCAL_SERVER);

				mImarisServer.setAutoDelete(false);
				// Get Imaris master with its Imaris ID.
				if (mImarisServer.getObject(new Int32(mImarisID)).isNull()) {
					mImarisServer.release();
					// No Imaris there...
					IJ.showMessage("Error","Could not connect to Imaris.");
					shutdown();
					mImarisServer = null;
	
					// Let the user use the plugin from ImageJ
					mIsImageJStartedFromImaris = false;
					return false;
				}
				// This system is not yet implemented in Imaris...
                mImaris = Application.queryInterface(mImarisServer.getObject(new Int32(mImarisID)));
				//mImaris = Application.queryInterface(mImarisServer.getObject(new Int32(mImarisID)));
				IJ.showStatus("Connected to Imaris");
                mImaris.setAutoDelete(false);
				// We do not need the server anymore
				mImarisServer.release();
				mImarisServer = null;
				return true;
			}
			return false;
		}

		/**
		 * Starts Imaris
		 * 
		 * @return true if it succeeded.
		 */
		public boolean StartImaris() {
			try {
				if (mImaris == null) {
					// The adapter has no handle on Imaris, initialize COM first...
					init();

					// create a new Imaris Application with CLSCTX parameter
					// CLSCTX_LOCAL_SERVER 
					// The EXE code that creates and manages objects of this class 
					// runs on same machine but is loaded in a separate process space. 
					IJ.showStatus("Starting Imaris...");
					mImaris = Application.create(ClsCtx.LOCAL_SERVER);
					mImaris.setAutoDelete(false);

                    if (!mImaris.getMVisible().getBooleanValue()) {
					  mImaris.setMVisible(new VariantBool(true));
					}

					return true;
				} 			
			} 
			catch (Exception e) {
				e.printStackTrace();
				mImaris = null;
				shutdown();
				return false;
			}
			// The compiler is happy so...
			return false;
		}


		public boolean DisconnectFromImaris() {
			if (mImaris != null && mIsImageJStartedFromImaris == true) {
				IJ.showStatus("Disconnecting from Imaris...");
				mImaris.release();
				mImaris = null;
				shutdown();
				return true;
			}
			return false;
		}

		/**
		 * Quits Imaris
		 * 
		 */
		public boolean QuitImaris() {
			if (mImaris != null) {
				// Quit Imaris and end the COM...
				try {
					IJ.showStatus("Closing Imaris...");
					mImaris.quit();
					mImaris.release();
					mImaris = null;
					shutdown();
          return true;
				} 
				catch (Exception e) {
					e.printStackTrace();
					shutdown();
					mImaris = null;
					return false;
				} 
			}
			return false;
		}

		/**
		 * Loads a file into Imaris through the COM interface
		 * 
		 * @param aFullName is the full path to the file
		 * 
		 * @return true if success
		 */
		public boolean LoadFileInImaris(String aFullName) {
			if (mImaris != null) {
				try {
					IJ.showStatus("Loading " + aFullName + " into Imaris...");
					mImaris.fileOpen(new BStr(aFullName),new BStr(""));
				} catch (Exception e) {
					e.printStackTrace();
					return false;
				}
        return true;
			} else {
				return false;
			}
		}

		/**
		 * retrieves infos about the current DataSet loaded into Imaris
		 * 
		 * @return Vector containing strings about X Size, Y size...
		 */
		public Vector ShowInfo() {
			// Create the vector
			Vector vInformation = new Vector();
			if (mImaris != null) {
				try {
					IJ.showStatus("Getting Imaris dataset infos...");
					// get Imaris DataSet
					IDataSet vImarisDataSet = mImaris.getMDataSet();

					// Get number of channels of the dataset
					UInt32 vSizeC = vImarisDataSet.getMSizeC();
					String[] vChannelName = new String[(int)vSizeC.getValue()];
					// Get the names
					for (int vChannel = 0; vChannel < vSizeC.getValue(); vChannel++) {
						vChannelName[vChannel] = (vImarisDataSet.getChannelName(new UInt32(vChannel))).getValue();
					}

					// Get number of TimePoints
					UInt32 vSizeT = vImarisDataSet.getMSizeT();

					// Get number of Slices
					UInt32 vSizeZ = vImarisDataSet.getMSizeZ();

					// Get number of Pixels along Y
					UInt32 vSizeY = vImarisDataSet.getMSizeY();

					// Get number of Pixels along X
					UInt32 vSizeX = vImarisDataSet.getMSizeX();

					// Get image type
					tType vImageType = vImarisDataSet.getMType();
					String vImageTypeDesc = new String();

					if (vImageType.getValue() == tType.eTypeUInt8) {
						vImageTypeDesc = "8-bit";
					} else if (vImageType.getValue() == tType.eTypeUInt16) {
						vImageTypeDesc = "16-bit";
					} else if (vImageType.getValue() == tType.eTypeFloat) {
						vImageTypeDesc = "32-bit / float";
					} else {
						vImageTypeDesc = "unknown";
					}

					// add the informations in the vector
					vInformation.add("Size X (in pixels): " + Long.toString(vSizeX.getValue()));
					vInformation.add("Size Y (in pixels): " + Long.toString(vSizeY.getValue()));
					vInformation.add("Number of Slices: " + Long.toString(vSizeZ.getValue()));
					vInformation.add("Number of Channels: " + Long.toString(vSizeC.getValue()));
					for (int vChannel=0;vChannel<vSizeC.getValue();vChannel++) {
						vInformation.add("  " + vChannelName[vChannel]);
					}
					vInformation.add("Number of Timepoints: " + Long.toString(vSizeT.getValue()));
					vInformation.add("Image Type: " + vImageTypeDesc);
					vImarisDataSet = null;
          return vInformation;
				} catch (Exception e) {
					e.printStackTrace();

					vInformation.clear();
					vInformation.add("No Information");
					return vInformation;
				}
			} else {
				vInformation.add("No Imaris connexion");
				return vInformation;
			}
		}

		/**
		 * Apply a selected macro to Imaris DataSet
		 * 
		 * @param aMacroDirectory The folder where the macro is lying
		 * 				aMacroFileName The filename of the macro
		 * 				aCreateNewChannels true if new channels should be created out of each existing ones
		 * 				aSelectedChannels if 0 the macro will apply to all channels and timepoints
		 * 
		 * @return true if success
		 */
		public boolean ApplyMacroToWholeDataSet(String aMacroFileName, boolean aCreateNewChannels, int aSelectedChannel) {
			if (mImaris != null) {
				int vNumberOfChannels = 0;
				int vNumberOfTimepoints = 0;
				try {
					// Get Imaris DataSet infos
					IDataSet vImarisDataSet = mImaris.getMDataSet();
					vImarisDataSet.setAutoDelete(false);
					vNumberOfChannels = (int)(vImarisDataSet.getMSizeC()).getValue();
					vNumberOfTimepoints = (int)(vImarisDataSet.getMSizeT()).getValue();
					vImarisDataSet.release();
					vImarisDataSet = null;
				} catch (com.jniwrapper.win32.com.ComException e) {
					IJ.showMessage("ApplyMacro retieve infos:: COM Exception");
					return false;
				}



				if (aSelectedChannel!=0) {
					// Apply macro on one channel for all timepoints!
					ImagePlus[] vImportedImages = ImportSelectedChannelImarisDataSet(aSelectedChannel);
					if (vImportedImages == null) {
						// An error occured while importing the DataSet...
						return false;
					}
					IJ.showProgress(0.0);
					for (int vTimepoint = 1; vTimepoint <= vNumberOfTimepoints; vTimepoint++) {
						ImagePlus vInputImage = vImportedImages[vTimepoint-1];
						if (vInputImage == null) {
							return false;
						}

						// show the image, as the macro will probably use the latest active image...
						vInputImage.show();
						// be sure it is unlocked
						vInputImage.unlock();
						// run the macro (it is run in another thread...)
						IJ.runMacroFile(aMacroFileName,"");

						// trick: as the macro is run in another thread, we do not know when the macro is finished
						//				Here we wait 100milliseconds to let the macro start, and we assume that the macro
						//				uses the current image and locks it.
						//				Then as long as the image is locked, the plugim waits.
						//				When the macro unlocks the image, the plugin goes on...
						IJ.wait(100);
						while (!vInputImage.lockSilently()) {
							IJ.wait(500);
						}
						vInputImage.unlock();

						int vOutputChannel = aSelectedChannel;
						if (aCreateNewChannels) {
							// A new channel should be created!
							vOutputChannel = vNumberOfChannels + 1;
						}

						// Export the image
						if (!ExportDataSetToImaris(vOutputChannel,vTimepoint,false)) {
							return false;
						}
						//Get the latest image and close it (we do not want 1million ImageJ images!!)
						ImagePlus vCurrentImage = IJ.getImage();
						if (!vInputImage.equals(vCurrentImage)) {
							// Another image was created out of the macro. So there is two images to close.
							vInputImage.flush();
							vInputImage.changes = false;
							ImageWindow vInputWindow = vInputImage.getWindow();
							vInputWindow.close();
						}
						vCurrentImage.flush();
						vCurrentImage.changes = false;
						ImageWindow vCurrentWindow = vCurrentImage.getWindow();
						vCurrentWindow.close();
						System.gc();
						IJ.showProgress(vTimepoint, vNumberOfTimepoints);
					}
          return true;
				} 
				else {
					// Apply macro on all channels and all timepoints!
					int vOutputChannelsIndent = 0;
					if (aCreateNewChannels) {
						// New channels should be created with the macro output.
						vOutputChannelsIndent = vNumberOfChannels;
					}


					ImagePlus[] vImages = ImportAllImarisDataSet();
					if (vImages == null) {
						// An error occured while importing the DataSet...
						return false;
					}
					for (int vChannel = 1; vChannel<=vNumberOfChannels;vChannel++) {
						for (int vTimepoint = 1; vTimepoint<=vNumberOfTimepoints;vTimepoint++) {
							ImagePlus vInputImage = vImages[(vTimepoint-1)+(vChannel-1)*vNumberOfTimepoints];
							if (vInputImage == null) {
								return false;
							}
							// show the image, as the macro will probably use the latest active image...
							vInputImage.show();
							// be sure it is unlocked
							vInputImage.unlock();
							// run the macro (it is run in another thread...)
							IJ.runMacroFile(aMacroFileName,"");

							// trick: as the macro is run in another thread, we do not know when the macro is finished
							//				Here we wait 100milliseconds to let the macro start, and we assume that the macro
							//				uses the current image and locks it.
							//				Then as long as the image is locked, the plugim waits.
							//				When the macro unlocks the image, the plugin goes on...
							IJ.wait(100);
							while (!vInputImage.lockSilently()) {
								IJ.wait(500);
							}
							vInputImage.unlock();

							// Export the image
							if (!ExportDataSetToImaris(vChannel+vOutputChannelsIndent,vTimepoint,false)) {
								return false;
							}

							//Get the latest image and close it (we do not want 1million ImageJ images!!)
							ImagePlus vCurrentImage = IJ.getImage();
							if (!vInputImage.equals(vCurrentImage)) {
								// Another image was created out of the macro. So there is two images to close.
								vInputImage.flush();
								vInputImage.changes = false;
								ImageWindow vInputWindow = vInputImage.getWindow();
								vInputWindow.close();
							}
							vCurrentImage.flush();
							vCurrentImage.changes = false;
							ImageWindow vCurrentWindow = vCurrentImage.getWindow();
							vCurrentWindow.close();
							System.gc();
						}
					}
          return true;
				}
			}
			return false;
		}

		/**
		 * Import and show the selected Imaris DataSet into ImageJ
		 * 
		 * @param aSelectedChannel int containing the desired channel to import from
		 * @param aSelectedTimepoint int containing the desired timepoint to import from
		 * 
		 * @return true if success
		 */
		public boolean ImportShowSelectedImarisDataSet(int aSelectedChannel, int aSelectedTimepoint) {
			if (mImaris != null) {
				// get the selected image
				ImagePlus vImage = ImportSelectedImarisDataSet(aSelectedChannel,aSelectedTimepoint);
				if (vImage != null) {
					// there is an image, so show it!
					vImage.show();
          return true;
				}
			}
			return false;
		}

		/**
		 * Import all timepoints from the selected channel of Imaris DataSet and stores them in an array of Images
		 * [Image t1, Image t2, Image t3, Image t4, ...]
		 * 
		 * @return array of ImagePlus null if unsuccessful
		 */
		private ImagePlus[] ImportSelectedChannelImarisDataSet(int aSelectedChannel) {
			if (mImaris!=null) {
				// Get the total number of timepoints
				int vNumberOfTimepoints = 0;
				IDataSet vImarisDataSet = null;
				try {
					vImarisDataSet = mImaris.getMDataSet();
					vNumberOfTimepoints = (int)vImarisDataSet.getMSizeT().getValue();
					vImarisDataSet = null;
				} catch (com.jniwrapper.win32.com.ComException e) {
					IJ.showMessage(e.getMessage());
					vImarisDataSet = null;
					return null;
				}


				// Get all the images from the selected channel
				ImagePlus[] vImages = new ImagePlus[vNumberOfTimepoints];
				for (int vTimepoint=1;vTimepoint<=vNumberOfTimepoints;vTimepoint++) {
					vImages[(vTimepoint-1)] = ImportSelectedImarisDataSet(aSelectedChannel,vTimepoint);
					if (vImages[(vTimepoint-1)] == null) {
						return null;
					}
				}
				return vImages;
			}
			return null;
		}

		/**
		 * Import all timepoints and channel of Imaris DataSet and stores them in an array of Images
		 * [Image Ch1 t1, Image Ch1 t2, ..., Image Ch1 tn, Image Ch2 t1, ...]
		 * 
		 * @return array of ImagePlus null if unsuccessful
		 */
		private ImagePlus[] ImportAllImarisDataSet() {
			if (mImaris!=null) {
				// Get the total number of channels and timepoints
				int vNumberOfChannels = 0;
				int vNumberOfTimepoints = 0;
				try {
					IDataSet vImarisDataSet = mImaris.getMDataSet();
					vImarisDataSet.setAutoDelete(false);
					vNumberOfChannels = (int)vImarisDataSet.getMSizeC().getValue();
					vNumberOfTimepoints = (int)vImarisDataSet.getMSizeT().getValue();
					vImarisDataSet.release();
					vImarisDataSet = null;
				} catch (com.jniwrapper.win32.com.ComException e) {
					IJ.showMessage("ImportAllImarisDataSet:: Exception");
					IJ.showMessage(e.getMessage());
				}

				// Get all the images
				ImagePlus[] vImages = new ImagePlus[vNumberOfChannels*vNumberOfTimepoints];
				for (int vChannel=1;vChannel<=vNumberOfChannels;vChannel++) {
					for (int vTimepoint=1;vTimepoint<=vNumberOfTimepoints;vTimepoint++) {
						vImages[(vTimepoint-1)+(vChannel-1)*vNumberOfTimepoints] = ImportSelectedImarisDataSet(vChannel,vTimepoint);
						if (vImages[(vTimepoint-1)+(vChannel-1)*vNumberOfTimepoints] == null) {
							return null;
						}
					}
				}
				return vImages;
			}
			return null;
		}
		/**
		 * Import Imaris DataSet into ImageJ
		 * 
		 * @param aSelectedChannel an int containing the desired channel to import from
		 * @param aSelectedTimepoint an int containing the desired timepoint to import from
		 * 
		 * @return ImagePlus null if the method failed
		 */
		private ImagePlus ImportSelectedImarisDataSet(int aSelectedChannel, int aSelectedTimepoint) {
			if (mImaris != null) {
				ImagePlus vCurrentImage = null;
				IDataSet vImarisDataSet = null;
				try {
					int vSelectedChannel = aSelectedChannel;
					int vSelectedTimepoint = aSelectedTimepoint;


					// Get Imaris DataSet interface
					vImarisDataSet = mImaris.getMDataSet();
					vImarisDataSet.setAutoDelete(false);
					// Get number of channels of the dataset
					UInt32 vSizeC = vImarisDataSet.getMSizeC();
					// Get number of TimePoints
					UInt32 vSizeT = vImarisDataSet.getMSizeT();
					// Get number of Slices
					UInt32 vSizeZ = vImarisDataSet.getMSizeZ();
					// Get number of Pixels along Y
					UInt32 vSizeY = vImarisDataSet.getMSizeY();
					// Get number of Pixels along X
					UInt32 vSizeX = vImarisDataSet.getMSizeX();
					// Get the type of the voxel intensities (uint8, uint16 or float, ie. byte, short or float)
					tType vImageType = vImarisDataSet.getMType();

					// If the asked channel or timepoint does not exist, return!
					if (vSelectedChannel > vSizeC.getValue() || vSelectedChannel <= 0 || 
							vSelectedTimepoint > vSizeT.getValue() || vSelectedTimepoint <= 0) {
						IJ.showMessage("Selected channel does not exist!");
						vImarisDataSet.release();
						vImarisDataSet = null;
						return vCurrentImage;
					}

					// Get channel name
					String vSelectedChannelName = (vImarisDataSet.getChannelName(new UInt32(vSelectedChannel-1))).getValue();


					// New ImageJ image
					if (vImageType.getValue() == tType.eTypeUInt8) {
						// Create an ImageJ image of type byte (8-bit) with the correct size
						vCurrentImage = ij.gui.NewImage.createByteImage(vSelectedChannelName,(int)vSizeX.getValue(),(int)vSizeY.getValue(),(int)vSizeZ.getValue(),ij.gui.NewImage.FILL_BLACK);
					} else if (vImageType.getValue() == tType.eTypeUInt16) {
						vCurrentImage = ij.gui.NewImage.createShortImage(vSelectedChannelName,(int)vSizeX.getValue(),(int)vSizeY.getValue(),(int)vSizeZ.getValue(),ij.gui.NewImage.FILL_BLACK);
					} else if (vImageType.getValue() == tType.eTypeFloat) {
						vCurrentImage = ij.gui.NewImage.createFloatImage(vSelectedChannelName,(int)vSizeX.getValue(),(int)vSizeY.getValue(),(int)vSizeZ.getValue(),ij.gui.NewImage.FILL_BLACK);
					} else {
						IJ.showMessage("Imaris DataSet type is unknown!");
						vImarisDataSet.release();
						vImarisDataSet = null;
						return vCurrentImage;
					}

					vCurrentImage.lockSilently();
					// get the stack in order to access each slice individually
					ImageStack vStack = vCurrentImage.getStack();

					IJ.showProgress(0.0);

					// Loop over each slice
					for (int z = 0; z < vSizeZ.getValue(); z++) {
						IJ.showProgress(z,(int)vSizeZ.getValue()-1);
						// Get the data (Variant)
						Variant vData = vImarisDataSet.getDataSlice(new UInt32(z),new UInt32(vSelectedChannel-1),new UInt32(vSelectedTimepoint-1));
						// Test if the Data in the Variant is a SafeArray of type uint8
						VarType vVARTYPE = vData.getVt();
						if (vVARTYPE.getValue() == VarType.VT_ARRAY + VarType.VT_UI1) {
							// get the safearray...
							SafeArray vSafeArray = vData.getSafeArray();
							// get the information in the safearray 1 byte = 1 pixel
							byte[] vPixels = vSafeArray.getBytes();
							// flip the order
							vStack.setPixels(vPixels,z+1);
							ImageProcessor vStackProcessor = vStack.getProcessor(z+1);
							vStackProcessor.flipVertical();


						} else if (vVARTYPE.getValue() == VarType.VT_ARRAY + VarType.VT_UI2) {
							SafeArray vSafeArray = vData.getSafeArray();
							//Get byte array
							byte[] vPixels = vSafeArray.getBytes();

							// Convert to short (2 bytes = 1 short)
							short[] v16BitPixels = new short[(int)vSizeX.getValue()*(int)vSizeY.getValue()];
							for (int i=0;i<v16BitPixels.length;i++) {
							  // Java reads Big Endian style...
							  v16BitPixels[i] = (short)(((vPixels[2*i+1] & 0xff) << 8) | (vPixels[2*i] & 0xff));
							}
							// Set the pixels
							vStack.setPixels(v16BitPixels,z+1);
							// Flip image upside down
							ImageProcessor vStackProcessor = vStack.getProcessor(z+1);
							vStackProcessor.flipVertical();
							vStackProcessor.resetMinAndMax();
							vStack.update(vStackProcessor);
						} else if (vVARTYPE.getValue() == VarType.VT_ARRAY + VarType.VT_R4) {
							SafeArray vSafeArray = vData.getSafeArray();
							//Get byte array
							byte[] vPixels = vSafeArray.getBytes();
							// Convert to float (4 bytes = 1 float)
							float[] v32BitPixels = new float[(int)vSizeX.getValue()*(int)vSizeY.getValue()];
							for (int i=0;i<v32BitPixels.length;i++) {
							  v32BitPixels[i] = Float.intBitsToFloat((int)(((vPixels[4*i+3] & 0xff) << 24) | ((vPixels[4*i+2] & 0xff) << 16) | ((vPixels[4*i+1] & 0xff) << 8) | (vPixels[4*i] & 0xff)));
							}
							// Set the pixels
							vStack.setPixels(v32BitPixels,z+1);
							// Flip image upside down
							ImageProcessor vStackProcessor = vStack.getProcessor(z+1);
							vStackProcessor.flipVertical();

							vStackProcessor.resetMinAndMax();
							vStack.update(vStackProcessor);
						}
					}
					// Set the new stack of images
					vCurrentImage.setStack(null,vStack);
					vCurrentImage.unlock();
				} catch (com.jniwrapper.win32.com.ComException e) {
					e.printStackTrace();
					IJ.showMessage("COM exception");
					if (vCurrentImage != null) {
						vCurrentImage.changes = false;
						ImageWindow vImageWindow = vCurrentImage.getWindow();
						vImageWindow.close();
						vCurrentImage.unlock();
						vCurrentImage = null;
					}
					vImarisDataSet.release();
					vImarisDataSet = null;
					return vCurrentImage;
				} catch (Exception e) {
					e.printStackTrace();
					IJ.showMessage("Exception occured while transferring image!");
					if (vCurrentImage != null) {
						vCurrentImage.changes = false;
						ImageWindow vImageWindow = vCurrentImage.getWindow();
						vImageWindow.close();
						vCurrentImage.unlock();
						vCurrentImage = null;
					}
					vImarisDataSet.release();
					vImarisDataSet = null;
					return vCurrentImage;
				}
				vImarisDataSet.release();
				vImarisDataSet = null;
				return vCurrentImage;
			}
			return null;
		}


		/**
		 * Export the active ImageJ DataSet to Imaris
		 * 
		 * @param aSelectedChannel an int containing the desired channel to export to
		 * @param aSelectedTimepoint an int containing the desired timepoint to export to
		 * @param aCreateNewImage true if a new DataSet should be created into Imaris
		 * 
		 * @return true if succeeded
		 */
		public boolean ExportDataSetToImaris(int aSelectedChannel, int aSelectedTimepoint, boolean aCreateNewImage) {
			// Test if Imaris is launched
			if (mImaris != null) {

				int vSelectedChannel = aSelectedChannel;
				int vSelectedTimepoint = aSelectedTimepoint;
				// Do we need to create a new DataSet in Imaris?
				boolean vCreateNewImage = aCreateNewImage;

				// Get ImageJ image infos...
				// Get the active ImageJ image...
				ImagePlus vImageToExport = IJ.getImage();
				vImageToExport.lock();
				if (vImageToExport == null) {
					IJ.showMessage("Please select an ImageJ image before exporting.");
					return false;
				}
				int vPixelType = vImageToExport.getBitDepth();
				ImageStack vStack = vImageToExport.getStack();
				int vJSizeX = vStack.getWidth();
				int vJSizeY = vStack.getHeight();
				int vJSizeZ = vStack.getSize();
				int vJSizeC = 1;
				int vJSizeT = 1;


				// ensure that we have a compatible DataSet ready to receive a new stack...
				IDataSet vImarisDataSet = null;
				try {
					vImarisDataSet = mImaris.getMDataSet();
					vImarisDataSet.setAutoDelete(false);

					if (vImarisDataSet == null || vCreateNewImage == true) {
						// A new Imaris DataSet should be created
						if (vPixelType == 8) {
							// 8-bit
							vImarisDataSet.create(new tType(tType.eTypeUInt8),new UInt32(vJSizeX),new UInt32(vJSizeY),new UInt32(vJSizeZ),new UInt32(vJSizeC),new UInt32(vJSizeT));
						} else if (vPixelType == 16) {
							// 16-bit
							vImarisDataSet.create(new tType(tType.eTypeUInt16),new UInt32(vJSizeX),new UInt32(vJSizeY),new UInt32(vJSizeZ),new UInt32(vJSizeC),new UInt32(vJSizeT));
						} else if (vPixelType == 32) {
							// float
							vImarisDataSet.create(new tType(tType.eTypeFloat),new UInt32(vJSizeX),new UInt32(vJSizeY),new UInt32(vJSizeZ),new UInt32(vJSizeC),new UInt32(vJSizeT));
						} else if (vPixelType == 24) {
							//vJSizeC = 3;
							//vImarisDataSet.create(imaris.tType.eTypeUInt8,vJSizeX,vJSizeY,vJSizeZ,vJSizeC,vJSizeT);
							IJ.showMessage("Error","Please use an image of type 8, 16 or float.");
							vImageToExport.unlock();
							vImarisDataSet.release();
							vImarisDataSet = null;
							return false;
						} else {
							IJ.showMessage("Error","Unknown image type");
							vImageToExport.unlock();
							vImarisDataSet.release();
							vImarisDataSet = null;
							return false;
						}
						// Set the right export information... we don't care about what was in the dialog here
						// since we have a new DataSet in Imaris.
						vSelectedChannel = 1;
						vSelectedTimepoint = 1;
					} else {
						// the user wants to add or replace a stack...

						// get Imaris DataSet infos...
						UInt32 vISizeX = vImarisDataSet.getMSizeX();
						UInt32 vISizeY = vImarisDataSet.getMSizeY();
						UInt32 vISizeZ = vImarisDataSet.getMSizeZ();
						UInt32 vISizeC = vImarisDataSet.getMSizeC();
						UInt32 vISizeT = vImarisDataSet.getMSizeT();
						tType vIType = vImarisDataSet.getMType();

						// check if the ImageJ image type is the same as Imaris DataSet
						if (vPixelType == 8 && vIType.getValue() != tType.eTypeUInt8 ||
								vPixelType == 16 && vIType.getValue() != tType.eTypeUInt16 ||
								vPixelType == 32 && vIType.getValue() != tType.eTypeFloat) {
							IJ.showMessage("Error","The type of the ImageJ DataSet is not the same as Imaris DataSet!");
							vImageToExport.unlock();
							vImarisDataSet.release();
							vImarisDataSet = null;
							return false;
						} else if (vPixelType == 24) {
							IJ.showMessage("Error", "ImageJ RGB type is not yet usable. Please convert to 8,16 or 32 bit image.");
							vImageToExport.unlock();
							vImarisDataSet.release();
							vImarisDataSet = null;
							return false;
						}


						// check if the ImageJ DataSet is of same size X,Y,Z as Imaris DataSet
						if ((int)vISizeX.getValue() != vJSizeX || (int)vISizeY.getValue() != vJSizeY || (int)vISizeZ.getValue() != vJSizeZ) {
							// Check whether ImageJ DataSet is bigger in X as Imaris'...
							if ((int)vISizeX.getValue() < vJSizeX) {
								vImarisDataSet.resize(new Int32(0),new UInt32(vJSizeX),new Int32(0),vISizeY,new Int32(0),vISizeZ,new Int32(0),vISizeC,new Int32(0),vISizeT);
								vISizeX.setValue(vJSizeX);
							}
							// Check if it's Imaris' X size which is bigger...
							else if ((int)vISizeX.getValue() > vJSizeX) {
								ij.plugin.CanvasResizer vCanvasResizer = new ij.plugin.CanvasResizer();
								vStack = vCanvasResizer.expandStack(vStack,(int)vISizeX.getValue(),vJSizeY,0,0);
								vJSizeX = (int)vISizeX.getValue();
							}

							// Check whether ImageJ DataSet is bigger in Y as Imaris'...
							if ((int)vISizeY.getValue() < vJSizeY) {
								vImarisDataSet.resize(new Int32(0),vISizeX,new Int32(0),new UInt32(vJSizeY),new Int32(0),vISizeZ,new Int32(0),vISizeC,new Int32(0),vISizeT);
								vISizeY.setValue(vJSizeY);
							}
							// Check if it's Imaris' Y size which is bigger...
							else if ((int)vISizeY.getValue() > vJSizeY) {
								ij.plugin.CanvasResizer vCanvasResizer = new ij.plugin.CanvasResizer();
								vStack = vCanvasResizer.expandStack(vStack,vJSizeX,(int)vISizeY.getValue(),0,0);
								vJSizeY = (int)vISizeY.getValue();
							}

							// Check whether ImageJ DataSet is bigger in Z as Imaris'...
							if ((int)vISizeZ.getValue() < vJSizeZ) {
								vImarisDataSet.resize(new Int32(0),vISizeX,new Int32(0),vISizeY,new Int32(0),new UInt32(vJSizeZ),new Int32(0),vISizeC,new Int32(0),vISizeT);
								vISizeZ.setValue(vJSizeZ);
							}
							// Check if it's Imaris' Z size which is bigger...
							else if ((int)vISizeZ.getValue() > vJSizeZ) {
								ImageProcessor vImageProcessor;
								if (vPixelType == 8) {
									vImageProcessor = new ByteProcessor(vJSizeX,vJSizeY);
								} else if (vPixelType == 16) {
									vImageProcessor = new ShortProcessor(vJSizeX, vJSizeY);
								} else if (vPixelType == 32) {
									vImageProcessor = new FloatProcessor(vJSizeX, vJSizeY);
								} else {
									vImageToExport.unlock();
									vImarisDataSet.release();
									vImarisDataSet = null;
									return false;
								}
								vImageProcessor.setColor(new Color(0.0f,0.0f,0.0f));
								for (int vIndex=0;vIndex< ((int)vISizeZ.getValue()-vJSizeZ);vIndex++) {
									vStack.addSlice("",vImageProcessor);
								}                                     
								vJSizeZ = (int)vISizeZ.getValue();
							}
						}

						// test if the desired export channel is bigger than the current Imaris DataSet
						// thus needing a resizing.
						if (vSelectedChannel > (int)vISizeC.getValue()) {
							vISizeC = new UInt32(vISizeC.getValue() + 1);
							vSelectedChannel = (int)vISizeC.getValue();
							vImarisDataSet.resize(new Int32(0),vISizeX,new Int32(0),vISizeY,new Int32(0),vISizeZ,new Int32(0),new UInt32(vSelectedChannel),new Int32(0),vISizeT);
						}
						// do the same for export timepoint
						if (vSelectedTimepoint > (int)vISizeT.getValue()) {
							vISizeT = new UInt32(vISizeT.getValue()+1);
							vSelectedTimepoint = (int)vISizeT.getValue();
							vImarisDataSet.resize(new Int32(0),vISizeX,new Int32(0),vISizeY,new Int32(0),vISizeZ,new Int32(0),vISizeC,new Int32(0),new UInt32(vSelectedTimepoint));
						}
					}
					vImarisDataSet.release();
					vImarisDataSet = null;
				} catch (Exception e) {
					e.printStackTrace();
					IJ.showMessage("Error","Unknown Exception occured");
					vImageToExport.unlock();
					vImarisDataSet.release();
					vImarisDataSet = null;
					return false;
				}

				// Send the data to Imaris...
				try {
					// get DataSet
					vImarisDataSet = mImaris.getMDataSet();
					vImarisDataSet.setAutoDelete(false);
					// Get the pixels from each slice in order to send them to Imaris
					for (int z=0; z<vJSizeZ;z++) {
						// we need to flip the image before sending it to Imaris
						ImageProcessor vImageProcessor = vStack.getProcessor(z+1);
						vImageProcessor.flipVertical();
						// vPixelType is 8 for 8bits, 16 for 16 bits, 32 for 32 bits GRAY images, or 24 for 32bits RGB images
						if (vPixelType == 8) {
							byte[] vPixels = (byte[])vStack.getPixels(z+1);
							// create 2D safearray
							SafeArrayBound[] vSafeArrayBound = {new SafeArrayBound(vJSizeX,0), new SafeArrayBound(vJSizeY,0)};

							SafeArray vSafeArray = new SafeArray(vSafeArrayBound,vPixels);

							Variant vData = new Variant(vSafeArray);
							vImarisDataSet.setDataSlice(vData,new UInt32(z),new UInt32(vSelectedChannel-1),new UInt32(vSelectedTimepoint-1));
						} 
						else if (vPixelType == 16) {
							short[] vPixels = (short[])vStack.getPixels(z+1);
							// create 2D safearray
							SafeArrayBound[] vSafeArrayBound = {new SafeArrayBound(vJSizeX,0), new SafeArrayBound(vJSizeY,0)};
							// Convert to byte array in the right order...
							byte[] vConvertedPixels = new byte[2*vPixels.length];
							for (int i=0;i<vPixels.length;i++) {
							  vConvertedPixels[2*i] = (byte)(0xff & (vPixels[i]));
							  vConvertedPixels[2*i + 1] = (byte)(0xff &(vPixels[i] >> 8));
							}
							SafeArray vSafeArray = new SafeArray(vSafeArrayBound,vConvertedPixels,UInt16.class);
							Variant vData = new Variant(vSafeArray);
							vImarisDataSet.setDataSlice(vData,new UInt32(z),new UInt32(vSelectedChannel-1),new UInt32(vSelectedTimepoint-1));
						} 
						else if (vPixelType == 32) {
							float[] vPixels = (float[])vStack.getPixels(z+1);
							// create 2D safearray
							SafeArrayBound[] vSafeArrayBound = {new SafeArrayBound(vJSizeX,0), new SafeArrayBound(vJSizeY,0)};

							// Convert to byte array in the right order...
							byte[] vConvertedPixels = new byte[4*vPixels.length];
							for (int i=0;i<vPixels.length;i++) {
							  vConvertedPixels[4*i] = (byte)(0xff & (Float.floatToIntBits(vPixels[i])));
							  vConvertedPixels[4*i + 1] = (byte)(0xff & (Float.floatToIntBits(vPixels[i]) >> 8));
							  vConvertedPixels[4*i + 2] = (byte)(0xff & (Float.floatToIntBits(vPixels[i]) >> 16));
							  vConvertedPixels[4*i + 3] = (byte)(0xff & (Float.floatToIntBits(vPixels[i]) >> 24));
							}
							SafeArray vSafeArray = new SafeArray(vSafeArrayBound,vConvertedPixels,SingleFloat.class);
							Variant vData = new Variant(vSafeArray);
							vImarisDataSet.setDataSlice(vData,new UInt32(z),new UInt32(vSelectedChannel-1),new UInt32(vSelectedTimepoint-1));
						} 
						else if (vPixelType == 24) {
							IJ.showMessage("Error","RGB images cannot be exported now, please use 8,16 or 32-bit images");
							vImageToExport.unlock();
							vImarisDataSet.release();
							vImarisDataSet = null;
							return false;
						} 
						else {
							IJ.showMessage("Error","Unknown image type");
							vImageToExport.unlock();
							vImarisDataSet.release();
							vImarisDataSet = null;
							return false;
						}
						// re-flip the image in ImageJ...
						vImageProcessor.flipVertical();

						IJ.showProgress(z,vStack.getSize()-1);
					}

				} catch (Exception e) {
					e.printStackTrace();
					IJ.showMessage("Error","Unknown Exception occured while transfer");
					vImageToExport.unlock();
					vImarisDataSet.release();
					vImarisDataSet = null;
					return false;
				}
				vImageToExport.unlock();
				vImarisDataSet.release();
				vImarisDataSet = null;
        return true;
			}
			return false;
		}
	}

}





