stainingestimation.StainingEstimation.java Source code

Java tutorial

Introduction

Here is the source code for stainingestimation.StainingEstimation.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package stainingestimation;

import TMARKERPluginInterface.PluginManager;
import com.boxysystems.jgoogleanalytics.FocusPoint;
import com.boxysystems.jgoogleanalytics.JGoogleAnalyticsTracker;
import ij.ImagePlus;
import ij.plugin.filter.GaussianBlur;
import ij.plugin.filter.MaximumFinder;
import ij.process.ImageConverter;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.table.DefaultTableModel;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.labels.XYToolTipGenerator;
import org.jfree.chart.plot.DatasetRenderingOrder;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import plugins.TMARKERPluginManager;
import tmarker.FileChooser;
import tmarker.TMAspot.TMALabel;
import tmarker.TMAspot.TMApoint;
import tmarker.TMAspot.TMAspot;
import tmarker.misc.Misc;
import tmarker.misc.saveImagesThread;
import tmarker.tmarker;

/**
 *
 * @author Peter J. Schueffler
 */
public class StainingEstimation extends javax.swing.JFrame implements TMARKERPluginInterface.Pluggable {

    // For Plugin handling
    PluginManager manager = null;
    private static final String PLUGINNAME = "Color Deconvolution";
    private static final String PLUGINVERSION = "1."
            + java.util.ResourceBundle.getBundle("stainingestimation/Bundle").getString("build");

    StainingEstimationThread set = null;

    StringToIntConverter stic = new StringToIntConverter();
    Image previewOriginal = null;
    Image previewOriginal_gray = null;
    List<ImagePlus> HE_preview = null;
    List<ImagePlus> HE_preview_gray = null;
    Color userColor1 = Color.WHITE;
    Color userColor2 = Color.WHITE;
    Color userColor3 = Color.WHITE;
    double imageResizeFactor = 1.0;
    String param_lastStain = "";
    boolean param_lastSubstractChannels = false;
    StainingEstimationParameterChangeThread sepct = null;
    TMAspot current_TMAspot = null;
    List<TMAspot> processedTMAspots = new ArrayList<>();

    /**
     * The original image of this TMAspot.
     */
    public final transient static int SHOW_ORIGINAL_IMAGE = 0;

    /**
     * The channel 1 image of this TAMspot after color deconvolution.
     */
    public final transient static int SHOW_CHANNEL1_IMAGE = 1;

    /**
     * The channel 2 image of this TAMspot after color deconvolution.
     */
    public final transient static int SHOW_CHANNEL2_IMAGE = 2;

    /**
     * The channel 3 image of this TAMspot after color deconvolution.
     */
    public final transient static int SHOW_CHANNEL3_IMAGE = 3;

    /**
     * Creates new form NewJFrame
     */
    public StainingEstimation() {
        initComponents();
        jComboBox1.setSelectedIndex(1);
        jXTable1.removeColumn(jXTable1.getColumn(8));
        jXTable1.removeColumn(jXTable1.getColumn(7));
        jXTable1.packTable(0);
        jScrollPane4.setPreferredSize(
                new Dimension(jXTable1.getPreferredSize().width, jScrollPane4.getPreferredSize().height));
        jScrollPane4.getHorizontalScrollBar().setUnitIncrement(20);
        jScrollPane4.getVerticalScrollBar().setUnitIncrement(20);
        jScrollPane1.getHorizontalScrollBar().setUnitIncrement(20);
        jScrollPane1.getVerticalScrollBar().setUnitIncrement(20);
        calculateFScore();
        toggleAdvancedView(); // pack();
        try {
            this.setIconImage(
                    ImageIO.read((getClass().getResource("/stainingestimation/ColorDeconvolution24.png"))));
        } catch (IOException ex) {
            Logger.getLogger(StainingEstimation.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;
        bindingGroup = new org.jdesktop.beansbinding.BindingGroup();

        buttonGroup1 = new javax.swing.ButtonGroup();
        jScrollPane1 = new javax.swing.JScrollPane();
        jPanel1 = new javax.swing.JPanel();
        jLabel3 = new javax.swing.JLabel();
        jSlider1 = new javax.swing.JSlider();
        jTextField3 = new javax.swing.JTextField();
        jSlider3 = new javax.swing.JSlider();
        jTextField4 = new javax.swing.JTextField();
        jSlider2 = new javax.swing.JSlider();
        jLabel2 = new javax.swing.JLabel();
        jLabel4 = new javax.swing.JLabel();
        jTextField5 = new javax.swing.JTextField();
        jLabel1 = new javax.swing.JLabel();
        jCheckBox4 = new javax.swing.JCheckBox();
        jLabel6 = new javax.swing.JLabel();
        jLabel7 = new javax.swing.JLabel();
        jLabel8 = new javax.swing.JLabel();
        jLabel9 = new javax.swing.JLabel();
        jLabel10 = new javax.swing.JLabel();
        jLabel11 = new javax.swing.JLabel();
        jLabel12 = new javax.swing.JLabel();
        jLabel13 = new javax.swing.JLabel();
        jXLabel1 = new org.jdesktop.swingx.JXLabel();
        jXLabel2 = new org.jdesktop.swingx.JXLabel();
        jPanel13 = new javax.swing.JPanel();
        jButton4 = new javax.swing.JButton();
        jButton6 = new javax.swing.JButton();
        jXLabel3 = new org.jdesktop.swingx.JXLabel();
        jXLabel4 = new org.jdesktop.swingx.JXLabel();
        jXLabel5 = new org.jdesktop.swingx.JXLabel();
        jXLabel6 = new org.jdesktop.swingx.JXLabel();
        jXLabel7 = new org.jdesktop.swingx.JXLabel();
        jXLabel8 = new org.jdesktop.swingx.JXLabel();
        jLabel14 = new javax.swing.JLabel();
        jCheckBox7 = new javax.swing.JCheckBox();
        jLabel15 = new javax.swing.JLabel();
        jTextField6 = new javax.swing.JTextField();
        jSlider5 = new javax.swing.JSlider();
        jLabel16 = new javax.swing.JLabel();
        jLabel18 = new javax.swing.JLabel();
        jLabel19 = new javax.swing.JLabel();
        jLabel20 = new javax.swing.JLabel();
        jXLabel9 = new org.jdesktop.swingx.JXLabel();
        jXLabel10 = new org.jdesktop.swingx.JXLabel();
        jXLabel11 = new org.jdesktop.swingx.JXLabel();
        jLabel26 = new javax.swing.JLabel();
        jPanel3 = new javax.swing.JPanel();
        jScrollPane4 = new javax.swing.JScrollPane();
        jXTable1 = new org.jdesktop.swingx.JXTable();
        jPanel6 = new javax.swing.JPanel();
        jLabel27 = new javax.swing.JLabel();
        jPanel7 = new javax.swing.JPanel();
        jButton12 = new javax.swing.JButton();
        jButton11 = new javax.swing.JButton();
        jComboBox1 = new javax.swing.JComboBox();
        jLabel5 = new javax.swing.JLabel();
        jRadioButton1 = new javax.swing.JRadioButton();
        jRadioButton2 = new javax.swing.JRadioButton();
        jRadioButton3 = new javax.swing.JRadioButton();
        jRadioButton4 = new javax.swing.JRadioButton();
        jLabel30 = new javax.swing.JLabel();
        jButton3 = new javax.swing.JButton();
        jLabel17 = new javax.swing.JLabel();
        jPanel14 = new javax.swing.JPanel();
        jCheckBox12 = new javax.swing.JCheckBox();
        jTextField14 = new javax.swing.JTextField();
        jLabel25 = new javax.swing.JLabel();
        jTextField15 = new javax.swing.JTextField();
        jCheckBox8 = new javax.swing.JCheckBox();
        jTextField2 = new javax.swing.JTextField();
        jLabel21 = new javax.swing.JLabel();
        jTextField7 = new javax.swing.JTextField();
        jCheckBox9 = new javax.swing.JCheckBox();
        jTextField8 = new javax.swing.JTextField();
        jLabel22 = new javax.swing.JLabel();
        jTextField9 = new javax.swing.JTextField();
        jCheckBox10 = new javax.swing.JCheckBox();
        jTextField10 = new javax.swing.JTextField();
        jLabel23 = new javax.swing.JLabel();
        jTextField11 = new javax.swing.JTextField();
        jCheckBox11 = new javax.swing.JCheckBox();
        jTextField12 = new javax.swing.JTextField();
        jLabel24 = new javax.swing.JLabel();
        jTextField13 = new javax.swing.JTextField();
        jButton5 = new javax.swing.JButton();
        jButton2 = new javax.swing.JButton();
        jLabel28 = new javax.swing.JLabel();
        jTextField18 = new javax.swing.JTextField();
        jLabel29 = new javax.swing.JLabel();
        jTextField19 = new javax.swing.JTextField();
        jLabel31 = new javax.swing.JLabel();
        jTextField21 = new javax.swing.JTextField();
        jLabel32 = new javax.swing.JLabel();
        jTextField22 = new javax.swing.JTextField();
        jLabel33 = new javax.swing.JLabel();
        jTextField23 = new javax.swing.JTextField();
        jToggleButton2 = new javax.swing.JToggleButton();
        jButton1 = new javax.swing.JButton();
        jButton7 = new javax.swing.JButton();
        jButton8 = new javax.swing.JButton();
        jButton9 = new javax.swing.JButton();
        jCheckBox1 = new javax.swing.JCheckBox();
        jLabel34 = new javax.swing.JLabel();

        setTitle(PLUGINNAME + " v1."
                + java.util.ResourceBundle.getBundle("stainingestimation/Bundle").getString("build")); // NOI18N

        jPanel1.setLayout(new java.awt.GridBagLayout());

        jLabel3.setText("t_dab = "); // NOI18N
        java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("stainingestimation/Bundle"); // NOI18N
        jLabel3.setToolTipText(bundle.getString("StainingEstimationDialog.jLabel3.toolTipText")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 6;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        jPanel1.add(jLabel3, gridBagConstraints);

        jSlider1.setMajorTickSpacing(5);
        jSlider1.setMaximum(50);
        jSlider1.setMinorTickSpacing(1);
        jSlider1.setToolTipText(bundle.getString("StainingEstimationDialog.jSlider1.toolTipText")); // NOI18N
        jSlider1.setPreferredSize(new java.awt.Dimension(105, 26));

        org.jdesktop.beansbinding.Binding binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(
                org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, jTextField3,
                org.jdesktop.beansbinding.ELProperty.create("${text}"), jSlider1,
                org.jdesktop.beansbinding.BeanProperty.create("value"), "jSlider1Binding");
        binding.setSourceNullValue(0);
        binding.setSourceUnreadableValue(0);
        binding.setConverter(stic);
        bindingGroup.addBinding(binding);

        jSlider1.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                jSlider1StateChanged(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 7;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel1.add(jSlider1, gridBagConstraints);

        jTextField3.setColumns(4);
        jTextField3.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField3.setText("8"); // NOI18N
        jTextField3.setToolTipText(bundle.getString("StainingEstimationDialog.jTextField3.toolTipText")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 6;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
        jPanel1.add(jTextField3, gridBagConstraints);

        jSlider3.setMajorTickSpacing(25);
        jSlider3.setMaximum(256);
        jSlider3.setMinorTickSpacing(5);
        jSlider3.setToolTipText(bundle.getString("StainingEstimationDialog.jSlider3.toolTipText")); // NOI18N
        jSlider3.setPreferredSize(new java.awt.Dimension(105, 26));

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(
                org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, jTextField5,
                org.jdesktop.beansbinding.ELProperty.create("${text}"), jSlider3,
                org.jdesktop.beansbinding.BeanProperty.create("value"), "jSlider3Binding");
        binding.setSourceNullValue(0);
        binding.setSourceUnreadableValue(0);
        binding.setConverter(stic);
        bindingGroup.addBinding(binding);

        jSlider3.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                jSlider3StateChanged(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 7;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.gridheight = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        jPanel1.add(jSlider3, gridBagConstraints);

        jTextField4.setColumns(4);
        jTextField4.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField4.setText("60"); // NOI18N
        jTextField4.setToolTipText(bundle.getString("StainingEstimationDialog.jTextField4.toolTipText")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 5;
        gridBagConstraints.gridy = 6;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        jPanel1.add(jTextField4, gridBagConstraints);

        jSlider2.setMajorTickSpacing(25);
        jSlider2.setMaximum(256);
        jSlider2.setMinorTickSpacing(5);
        jSlider2.setToolTipText(bundle.getString("StainingEstimationDialog.jSlider2.toolTipText")); // NOI18N
        jSlider2.setPreferredSize(new java.awt.Dimension(105, 26));

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(
                org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, jTextField4,
                org.jdesktop.beansbinding.ELProperty.create("${text}"), jSlider2,
                org.jdesktop.beansbinding.BeanProperty.create("value"), "jSlider2Binding");
        binding.setSourceNullValue(0);
        binding.setSourceUnreadableValue(0);
        binding.setConverter(stic);
        bindingGroup.addBinding(binding);

        jSlider2.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                jSlider2StateChanged(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 7;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        jPanel1.add(jSlider2, gridBagConstraints);

        jLabel2.setText("tolerance = "); // NOI18N
        jLabel2.setToolTipText(bundle.getString("StainingEstimationDialog.jLabel2.toolTipText")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 6;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel1.add(jLabel2, gridBagConstraints);

        jLabel4.setText("t_hema = "); // NOI18N
        jLabel4.setToolTipText(bundle.getString("StainingEstimationDialog.jLabel4.toolTipText")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 6;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        jPanel1.add(jLabel4, gridBagConstraints);

        jTextField5.setColumns(4);
        jTextField5.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField5.setText("60");
        jTextField5.setToolTipText(bundle.getString("StainingEstimationDialog.jTextField5.toolTipText")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 6;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        jPanel1.add(jTextField5, gridBagConstraints);

        jLabel1.setText("<html><b>1. Select the staining protocol:</b></html>"); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridwidth = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(10, 5, 5, 0);
        jPanel1.add(jLabel1, gridBagConstraints);

        jCheckBox4.setSelected(true);
        jCheckBox4.setText(bundle.getString("StainingEstimationDialog.jCheckBox4.text")); // NOI18N
        jCheckBox4.setToolTipText(bundle.getString("StainingEstimationDialog.jCheckBox4.toolTipText")); // NOI18N
        jCheckBox4.setActionCommand(bundle.getString("StainingEstimationDialog.jCheckBox4.actionCommand")); // NOI18N
        jCheckBox4.setVerticalTextPosition(javax.swing.SwingConstants.TOP);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 19;
        gridBagConstraints.gridwidth = 8;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel1.add(jCheckBox4, gridBagConstraints);

        jLabel6.setIcon(new javax.swing.ImageIcon(getClass().getResource("/stainingestimation/nopreview.png"))); // NOI18N
        jLabel6.setText(bundle.getString("StainingEstimationDialog.jLabel10.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.gridheight = 3;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel1.add(jLabel6, gridBagConstraints);

        jLabel7.setIcon(new javax.swing.ImageIcon(getClass().getResource("/stainingestimation/nopreview.png"))); // NOI18N
        jLabel7.setText(bundle.getString("StainingEstimationDialog.jLabel7.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.gridheight = 3;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 3, 0, 0);
        jPanel1.add(jLabel7, gridBagConstraints);

        jLabel8.setIcon(new javax.swing.ImageIcon(getClass().getResource("/stainingestimation/nopreview.png"))); // NOI18N
        jLabel8.setText(bundle.getString("StainingEstimationDialog.jLabel8.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.gridheight = 3;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 3, 0, 0);
        jPanel1.add(jLabel8, gridBagConstraints);

        jLabel9.setIcon(new javax.swing.ImageIcon(getClass().getResource("/stainingestimation/nopreview.png"))); // NOI18N
        jLabel9.setText(bundle.getString("StainingEstimationDialog.jLabel9.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 6;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.gridheight = 3;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 3, 0, 5);
        jPanel1.add(jLabel9, gridBagConstraints);

        jLabel10.setIcon(new javax.swing.ImageIcon(getClass().getResource("/stainingestimation/nopreview.png"))); // NOI18N
        jLabel10.setText(bundle.getString("StainingEstimationDialog.jLabel10.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 13;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel1.add(jLabel10, gridBagConstraints);

        jLabel11.setIcon(new javax.swing.ImageIcon(getClass().getResource("/stainingestimation/nopreview.png"))); // NOI18N
        jLabel11.setText(bundle.getString("StainingEstimationDialog.jLabel11.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 13;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 3, 0, 0);
        jPanel1.add(jLabel11, gridBagConstraints);

        jLabel12.setIcon(new javax.swing.ImageIcon(getClass().getResource("/stainingestimation/nopreview.png"))); // NOI18N
        jLabel12.setText(bundle.getString("StainingEstimationDialog.jLabel12.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 13;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 3, 0, 0);
        jPanel1.add(jLabel12, gridBagConstraints);

        jLabel13.setIcon(new javax.swing.ImageIcon(getClass().getResource("/stainingestimation/nopreview.png"))); // NOI18N
        jLabel13.setText(bundle.getString("StainingEstimationDialog.jLabel13.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 6;
        gridBagConstraints.gridy = 13;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 3, 0, 5);
        jPanel1.add(jLabel13, gridBagConstraints);

        jXLabel1.setText(bundle.getString("StainingEstimationDialog.jXLabel1.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel1.add(jXLabel1, gridBagConstraints);

        jXLabel2.setText(bundle.getString("StainingEstimationDialog.jXLabel2.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel1.add(jXLabel2, gridBagConstraints);

        jPanel13.setLayout(new java.awt.GridLayout(1, 0));

        jButton4.setText("Estimate"); // NOI18N
        jButton4.setToolTipText(bundle.getString("StainingEstimationDialog.jButton4.toolTipText")); // NOI18N
        jButton4.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton4ActionPerformed(evt);
            }
        });
        jPanel13.add(jButton4);

        jButton6.setText(bundle.getString("StainingEstimationDialog.jButton6.text")); // NOI18N
        jButton6.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton6ActionPerformed(evt);
            }
        });
        jPanel13.add(jButton6);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 20;
        gridBagConstraints.gridwidth = 8;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
        jPanel1.add(jPanel13, gridBagConstraints);

        jXLabel3.setText(bundle.getString("StainingEstimationDialog.jXLabel3.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel1.add(jXLabel3, gridBagConstraints);

        jXLabel4.setText(bundle.getString("StainingEstimationDialog.jXLabel4.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 6;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 5);
        jPanel1.add(jXLabel4, gridBagConstraints);

        jXLabel5.setText(bundle.getString("StainingEstimationDialog.jXLabel5.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 14;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel1.add(jXLabel5, gridBagConstraints);

        jXLabel6.setText(bundle.getString("StainingEstimationDialog.jXLabel6.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 6;
        gridBagConstraints.gridy = 14;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 5);
        jPanel1.add(jXLabel6, gridBagConstraints);

        jXLabel7.setText(bundle.getString("StainingEstimationDialog.jXLabel7.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 14;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel1.add(jXLabel7, gridBagConstraints);

        jXLabel8.setText(bundle.getString("StainingEstimationDialog.jXLabel8.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 14;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel1.add(jXLabel8, gridBagConstraints);

        jLabel14.setText(bundle.getString("StainingEstimationDialog.jLabel14.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 5;
        gridBagConstraints.gridwidth = 8;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(15, 5, 5, 5);
        jPanel1.add(jLabel14, gridBagConstraints);

        jCheckBox7.setSelected(true);
        jCheckBox7.setText(bundle.getString("StainingEstimationDialog.jCheckBox7.text")); // NOI18N
        jCheckBox7.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCheckBox7ActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 15;
        gridBagConstraints.gridwidth = 8;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LAST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 2, 0, 5);
        jPanel1.add(jCheckBox7, gridBagConstraints);

        jLabel15.setText(bundle.getString("StainingEstimationDialog.jLabel15.text")); // NOI18N
        jLabel15.setToolTipText(bundle.getString("StainingEstimationDialog.jLabel15.toolTipText")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 8;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel1.add(jLabel15, gridBagConstraints);

        jTextField6.setColumns(4);
        jTextField6.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField6.setText("1"); // NOI18N
        jTextField6.setToolTipText(bundle.getString("StainingEstimationDialog.jTextField6.toolTipText")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 8;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
        jPanel1.add(jTextField6, gridBagConstraints);

        jSlider5.setMajorTickSpacing(5);
        jSlider5.setMaximum(10);
        jSlider5.setMinorTickSpacing(1);
        jSlider5.setToolTipText(bundle.getString("StainingEstimationDialog.jSlider5.toolTipText")); // NOI18N
        jSlider5.setPreferredSize(new java.awt.Dimension(105, 26));

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(
                org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, jTextField6,
                org.jdesktop.beansbinding.ELProperty.create("${text}"), jSlider5,
                org.jdesktop.beansbinding.BeanProperty.create("value"), "jSlider5Binding");
        binding.setSourceNullValue(0);
        binding.setSourceUnreadableValue(0);
        binding.setConverter(stic);
        bindingGroup.addBinding(binding);

        jSlider5.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                jSlider5StateChanged(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 9;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 0);
        jPanel1.add(jSlider5, gridBagConstraints);

        jLabel16.setText(bundle.getString("StainingEstimationDialog.jLabel16.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 18;
        gridBagConstraints.gridwidth = 8;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(15, 5, 0, 5);
        jPanel1.add(jLabel16, gridBagConstraints);

        jLabel18.setText(bundle.getString("StainingEstimationDialog.jLabel18.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 11;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 3, 0, 0);
        jPanel1.add(jLabel18, gridBagConstraints);

        jLabel19.setText(bundle.getString("StainingEstimationDialog.jLabel19.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 11;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 3, 0, 0);
        jPanel1.add(jLabel19, gridBagConstraints);

        jLabel20.setText(bundle.getString("StainingEstimationDialog.jLabel20.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 6;
        gridBagConstraints.gridy = 11;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 3, 0, 5);
        jPanel1.add(jLabel20, gridBagConstraints);

        jXLabel9.setText(bundle.getString("StainingEstimationDialog.jXLabel9.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 6;
        gridBagConstraints.gridy = 12;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 5);
        jPanel1.add(jXLabel9, gridBagConstraints);

        jXLabel10.setText(bundle.getString("StainingEstimationDialog.jXLabel10.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 12;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel1.add(jXLabel10, gridBagConstraints);

        jXLabel11.setText(bundle.getString("StainingEstimationDialog.jXLabel11.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 12;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel1.add(jXLabel11, gridBagConstraints);

        jLabel26.setText(bundle.getString("StainingEstimationDialog.jLabel26.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 9;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridheight = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(10, 15, 0, 5);
        jPanel1.add(jLabel26, gridBagConstraints);

        jPanel3.setLayout(new java.awt.BorderLayout(0, 5));

        jXTable1.setModel(new javax.swing.table.DefaultTableModel(new Object[][] {

        }, new String[] { "ID", "Staining", "Radius", "Tolerance", "Blur", "t_hema", "t_dab", "TMBlur_hema",
                "TMBlur_dab", "Precision", "Recall", "F-Score" }) {
            Class[] types = new Class[] { java.lang.Integer.class, java.lang.String.class, java.lang.Integer.class,
                    java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class,
                    java.lang.Integer.class, java.lang.Integer.class, java.lang.Integer.class,
                    java.lang.Double.class, java.lang.Double.class, java.lang.Double.class };
            boolean[] canEdit = new boolean[] { false, false, false, false, false, false, false, false, false,
                    false, false, false };

            public Class getColumnClass(int columnIndex) {
                return types[columnIndex];
            }

            public boolean isCellEditable(int rowIndex, int columnIndex) {
                return canEdit[columnIndex];
            }
        });
        jXTable1.setPreferredScrollableViewportSize(new java.awt.Dimension(825, 100));
        jXTable1.setVisibleRowCount(12);
        jXTable1.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                jXTable1MouseClicked(evt);
            }
        });
        jScrollPane4.setViewportView(jXTable1);

        jPanel3.add(jScrollPane4, java.awt.BorderLayout.CENTER);

        jPanel6.setLayout(new java.awt.BorderLayout());

        jLabel27.setText(bundle.getString("StainingEstimationDialog.jLabel27.text")); // NOI18N
        jPanel6.add(jLabel27, java.awt.BorderLayout.LINE_START);

        jPanel7.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.RIGHT, 0, 0));

        jButton12.setText(bundle.getString("StainingEstimationDialog.jButton12.text")); // NOI18N
        jButton12.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton12ActionPerformed(evt);
            }
        });
        jPanel7.add(jButton12);

        jButton11.setText(bundle.getString("StainingEstimationDialog.jButton11.text")); // NOI18N
        jButton11.setEnabled(false);
        jButton11.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton11ActionPerformed(evt);
            }
        });
        jPanel7.add(jButton11);

        jPanel6.add(jPanel7, java.awt.BorderLayout.LINE_END);

        jPanel3.add(jPanel6, java.awt.BorderLayout.SOUTH);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 8;
        gridBagConstraints.gridy = 5;
        gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
        gridBagConstraints.gridheight = 19;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LAST_LINE_END;
        gridBagConstraints.insets = new java.awt.Insets(0, 10, 5, 5);
        jPanel1.add(jPanel3, gridBagConstraints);

        jComboBox1.setMaximumRowCount(17);
        jComboBox1.setModel(new javax.swing.DefaultComboBoxModel(
                new String[] { "H&E", "H&E 2", "H DAB", "Feulgen Light Green", "Giemsa", "FastRed FastBlue DAB",
                        "Methyl Green DAB", "H&E DAB", "H AEC", "Azan-Mallory", "Masson Trichrome",
                        "Alcian blue & H", "H PAS", "RGB", "CMY", "Black / White", "User values" }));
        jComboBox1.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                jComboBox1ItemStateChanged(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridwidth = 5;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(5, 0, 5, 0);
        jPanel1.add(jComboBox1, gridBagConstraints);

        jLabel5.setText(
                "<html>See: <cite>Ruifrok AC, Johnston DA.<br><b>Quantification of histochemical staining by color deconvolution.</b><br><i>Anal Quant Cytol Histol 23: 291-299, 2001.</i></cite></html>");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 23;
        gridBagConstraints.gridwidth = 6;
        gridBagConstraints.gridheight = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LAST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(5, 10, 5, 5);
        jPanel1.add(jLabel5, gridBagConstraints);

        buttonGroup1.add(jRadioButton1);
        jRadioButton1.setSelected(true);
        jRadioButton1.setText("Display Original Image");
        jRadioButton1.setToolTipText("Display Original TMAspot in TMARKER Main Window");
        jRadioButton1.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                jRadioButton1ItemStateChanged(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 16;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(5, 1, 0, 0);
        jPanel1.add(jRadioButton1, gridBagConstraints);

        buttonGroup1.add(jRadioButton2);
        jRadioButton2.setText("Display Channel 1");
        jRadioButton2.setToolTipText("Display Channel 1 in TMARKER Main Window");
        jRadioButton2.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                jRadioButton2ItemStateChanged(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 16;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(5, 0, 0, 0);
        jPanel1.add(jRadioButton2, gridBagConstraints);

        buttonGroup1.add(jRadioButton3);
        jRadioButton3.setText("Display Channel 2");
        jRadioButton3.setToolTipText("Display Channel 2 in TMARKER Main Window");
        jRadioButton3.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                jRadioButton3ItemStateChanged(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 16;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(5, 0, 0, 0);
        jPanel1.add(jRadioButton3, gridBagConstraints);

        buttonGroup1.add(jRadioButton4);
        jRadioButton4.setText("Display Channel 3");
        jRadioButton4.setToolTipText("Display Channel 3 in TMARKER Main Window");
        jRadioButton4.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                jRadioButton4ItemStateChanged(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 6;
        gridBagConstraints.gridy = 16;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(5, 0, 0, 5);
        jPanel1.add(jRadioButton4, gridBagConstraints);

        jLabel30.setText(" ");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 22;
        gridBagConstraints.gridwidth = 8;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 10, 5, 5);
        jPanel1.add(jLabel30, gridBagConstraints);

        jButton3.setText("Calculate F-Score"); // NOI18N
        jButton3.setToolTipText(bundle.getString("StainingEstimationDialog.jButton3.toolTipText")); // NOI18N
        jButton3.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton3ActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 8;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(40, 10, 0, 0);
        jPanel1.add(jButton3, gridBagConstraints);

        jLabel17.setText(bundle.getString("StainingEstimationDialog.jLabel17.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 8;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridheight = 4;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(10, 10, 0, 0);
        jPanel1.add(jLabel17, gridBagConstraints);

        jPanel14.setLayout(new java.awt.GridBagLayout());

        jCheckBox12.setText(bundle.getString("StainingEstimationDialog.jCheckBox12.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        jPanel14.add(jCheckBox12, gridBagConstraints);

        jTextField14.setColumns(2);
        jTextField14.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField14.setText(bundle.getString("StainingEstimationDialog.jTextField14.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        jPanel14.add(jTextField14, gridBagConstraints);

        jLabel25.setText(bundle.getString("StainingEstimationDialog.jLabel25.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        jPanel14.add(jLabel25, gridBagConstraints);

        jTextField15.setColumns(2);
        jTextField15.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField15.setText(bundle.getString("StainingEstimationDialog.jTextField15.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        jPanel14.add(jTextField15, gridBagConstraints);

        jCheckBox8.setText(bundle.getString("StainingEstimationDialog.jCheckBox8.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        jPanel14.add(jCheckBox8, gridBagConstraints);

        jTextField2.setColumns(2);
        jTextField2.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField2.setText(bundle.getString("StainingEstimationDialog.jTextField2.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        jPanel14.add(jTextField2, gridBagConstraints);

        jLabel21.setText(bundle.getString("StainingEstimationDialog.jLabel21.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        jPanel14.add(jLabel21, gridBagConstraints);

        jTextField7.setColumns(2);
        jTextField7.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField7.setText(bundle.getString("StainingEstimationDialog.jTextField7.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        jPanel14.add(jTextField7, gridBagConstraints);

        jCheckBox9.setText(bundle.getString("StainingEstimationDialog.jCheckBox9.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        jPanel14.add(jCheckBox9, gridBagConstraints);

        jTextField8.setColumns(2);
        jTextField8.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField8.setText(bundle.getString("StainingEstimationDialog.jTextField8.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        jPanel14.add(jTextField8, gridBagConstraints);

        jLabel22.setText(bundle.getString("StainingEstimationDialog.jLabel22.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        jPanel14.add(jLabel22, gridBagConstraints);

        jTextField9.setColumns(2);
        jTextField9.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField9.setText(bundle.getString("StainingEstimationDialog.jTextField9.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        jPanel14.add(jTextField9, gridBagConstraints);

        jCheckBox10.setText(bundle.getString("StainingEstimationDialog.jCheckBox10.text")); // NOI18N
        jCheckBox10.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCheckBox10ActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        jPanel14.add(jCheckBox10, gridBagConstraints);

        jTextField10.setColumns(3);
        jTextField10.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField10.setText(bundle.getString("StainingEstimationDialog.jTextField10.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        jPanel14.add(jTextField10, gridBagConstraints);

        jLabel23.setText(bundle.getString("StainingEstimationDialog.jLabel23.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        jPanel14.add(jLabel23, gridBagConstraints);

        jTextField11.setColumns(3);
        jTextField11.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField11.setText(bundle.getString("StainingEstimationDialog.jTextField11.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        jPanel14.add(jTextField11, gridBagConstraints);

        jCheckBox11.setText(bundle.getString("StainingEstimationDialog.jCheckBox11.text")); // NOI18N
        jCheckBox11.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCheckBox11ActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        jPanel14.add(jCheckBox11, gridBagConstraints);

        jTextField12.setColumns(3);
        jTextField12.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField12.setText(bundle.getString("StainingEstimationDialog.jTextField12.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        jPanel14.add(jTextField12, gridBagConstraints);

        jLabel24.setText(bundle.getString("StainingEstimationDialog.jLabel24.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        jPanel14.add(jLabel24, gridBagConstraints);

        jTextField13.setColumns(3);
        jTextField13.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField13.setText(bundle.getString("StainingEstimationDialog.jTextField13.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        jPanel14.add(jTextField13, gridBagConstraints);

        jButton5.setText(bundle.getString("StainingEstimationDialog.jButton5.text")); // NOI18N
        jButton5.setToolTipText(bundle.getString("StainingEstimationDialog.jButton5.toolTipText")); // NOI18N
        jButton5.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton5ActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 5;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 3, 0, 0);
        jPanel14.add(jButton5, gridBagConstraints);

        jButton2.setText(bundle.getString("StainingEstimationDialog.jButton2.text")); // NOI18N
        jButton2.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton2ActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 5;
        gridBagConstraints.gridwidth = 5;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_END;
        jPanel14.add(jButton2, gridBagConstraints);

        jLabel28.setText(bundle.getString("StainingEstimationDialog.jLabel28.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel14.add(jLabel28, gridBagConstraints);

        jTextField18.setColumns(2);
        jTextField18.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField18.setText(bundle.getString("StainingEstimationDialog.jTextField18.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 2, 0, 0);
        jPanel14.add(jTextField18, gridBagConstraints);

        jLabel29.setText(bundle.getString("StainingEstimationDialog.jLabel29.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 5;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel14.add(jLabel29, gridBagConstraints);

        jTextField19.setColumns(2);
        jTextField19.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField19.setText(bundle.getString("StainingEstimationDialog.jTextField19.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 6;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 2, 0, 0);
        jPanel14.add(jTextField19, gridBagConstraints);

        jLabel31.setText(bundle.getString("StainingEstimationDialog.jLabel31.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 5;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel14.add(jLabel31, gridBagConstraints);

        jTextField21.setColumns(2);
        jTextField21.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField21.setText(bundle.getString("StainingEstimationDialog.jTextField21.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 6;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 2, 0, 0);
        jPanel14.add(jTextField21, gridBagConstraints);

        jLabel32.setText(bundle.getString("StainingEstimationDialog.jLabel32.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 5;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel14.add(jLabel32, gridBagConstraints);

        jTextField22.setColumns(2);
        jTextField22.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField22.setText(bundle.getString("StainingEstimationDialog.jTextField22.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 6;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 2, 0, 0);
        jPanel14.add(jTextField22, gridBagConstraints);

        jLabel33.setText(bundle.getString("StainingEstimationDialog.jLabel33.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 5;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPanel14.add(jLabel33, gridBagConstraints);

        jTextField23.setColumns(2);
        jTextField23.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
        jTextField23.setText(bundle.getString("StainingEstimationDialog.jTextField23.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 6;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 2, 0, 0);
        jPanel14.add(jTextField23, gridBagConstraints);

        jToggleButton2.setText(bundle.getString("StainingEstimationDialog.jToggleButton2.text")); // NOI18N
        jToggleButton2.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 5, 0, 3));
        jToggleButton2.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jToggleButton2ActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 7;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.gridheight = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(6, 0, 6, 0);
        jPanel14.add(jToggleButton2, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 9;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.gridheight = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 15, 0, 5);
        jPanel1.add(jPanel14, gridBagConstraints);

        jButton1.setText("Advanced <<");
        jButton1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton1ActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 7;
        gridBagConstraints.gridy = 24;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LAST_LINE_END;
        gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5);
        jPanel1.add(jButton1, gridBagConstraints);

        jButton7.setText("Save Channel 1 ...");
        jButton7.setToolTipText("Save channel 1 of processed images");
        jButton7.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton7ActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 17;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 0);
        jPanel1.add(jButton7, gridBagConstraints);

        jButton8.setText("Save Channel 2 ...");
        jButton8.setToolTipText("Save channel 2 of processed images");
        jButton8.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton8ActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 17;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 0);
        jPanel1.add(jButton8, gridBagConstraints);

        jButton9.setText("Save Channel 3 ...");
        jButton9.setToolTipText("Save channel 2 of processed images");
        jButton9.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton9ActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 6;
        gridBagConstraints.gridy = 17;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 0);
        jPanel1.add(jButton9, gridBagConstraints);

        jCheckBox1.setText("Substract channel 2 from channel 1 for enhanced nucleus detection");
        jCheckBox1.setToolTipText("Select this if channel 1 and 2 show large overlap on deconvoluted images.");
        jCheckBox1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCheckBox1ActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 10;
        gridBagConstraints.gridwidth = 8;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
        jPanel1.add(jCheckBox1, gridBagConstraints);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 21;
        gridBagConstraints.gridwidth = 8;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(0, 10, 5, 5);
        jPanel1.add(jLabel34, gridBagConstraints);

        jScrollPane1.setViewportView(jPanel1);

        getContentPane().add(jScrollPane1, java.awt.BorderLayout.CENTER);

        bindingGroup.bind();

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void jSlider1StateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_jSlider1StateChanged
        if (previewOriginal != null) {
            calculatePreviewConvolution_gray(showNucleiPreview());
            displayPreviewImages_gray();
        }
    }//GEN-LAST:event_jSlider1StateChanged

    private void jSlider3StateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_jSlider3StateChanged
        if (previewOriginal != null) {
            calculatePreviewConvolution_gray(showNucleiPreview());
            displayPreviewImages_gray();
        }
    }//GEN-LAST:event_jSlider3StateChanged

    private void jSlider2StateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_jSlider2StateChanged
        if (previewOriginal != null) {
            calculatePreviewConvolution_gray(showNucleiPreview());
            displayPreviewImages_gray();
        }
    }//GEN-LAST:event_jSlider2StateChanged

    private void jButton3ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton3ActionPerformed
        calculateFScore();
    }//GEN-LAST:event_jButton3ActionPerformed

    private void jButton4ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton4ActionPerformed
        doStainingEstimation(true);
        JGoogleAnalyticsTracker tracker = new JGoogleAnalyticsTracker("TMARKER", "UA-61194283-1");
        FocusPoint focusPoint = new FocusPoint("StainingEstimationUsage");
        tracker.trackAsynchronously(focusPoint);
    }//GEN-LAST:event_jButton4ActionPerformed

    private void jButton6ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton6ActionPerformed
        interruptStainingEstimation();
        closeAndRelease();
    }//GEN-LAST:event_jButton6ActionPerformed

    private void jCheckBox7ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBox7ActionPerformed
        if (previewOriginal != null) {
            calculatePreviewConvolution_gray(showNucleiPreview());
            displayPreviewImages_gray();
        }
    }//GEN-LAST:event_jCheckBox7ActionPerformed

    private void jSlider5StateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_jSlider5StateChanged
        if (previewOriginal != null) {
            calculatePreviewConvolution_gray(showNucleiPreview());
            displayPreviewImages_gray();
        }
    }//GEN-LAST:event_jSlider5StateChanged

    private void jCheckBox10ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBox10ActionPerformed
        if (!jCheckBox10.isSelected()) {
            jToggleButton2.setSelected(false);
        }
    }//GEN-LAST:event_jCheckBox10ActionPerformed

    private void jCheckBox11ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBox11ActionPerformed
        if (!jCheckBox11.isSelected()) {
            jToggleButton2.setSelected(false);
        }
    }//GEN-LAST:event_jCheckBox11ActionPerformed

    private void jButton5ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton5ActionPerformed
        doParameterChangeExperiment();
    }//GEN-LAST:event_jButton5ActionPerformed

    private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed
        interruptStainingEstimationParameterChangeThread();
    }//GEN-LAST:event_jButton2ActionPerformed

    private void jToggleButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jToggleButton2ActionPerformed
        if (jToggleButton2.isSelected()) {
            jCheckBox10.setSelected(jToggleButton2.isSelected());
            jCheckBox11.setSelected(jToggleButton2.isSelected());

            jTextField12.setText(jTextField10.getText());
            jTextField13.setText(jTextField11.getText());
            jTextField23.setText(jTextField22.getText());
        }
    }//GEN-LAST:event_jToggleButton2ActionPerformed

    private void jXTable1MouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jXTable1MouseClicked
        if (evt.getClickCount() == 1) {
            int row = jXTable1.getSelectedRow();
            if (row >= 0) {
                row = jXTable1.convertRowIndexToModel(row);
                setParam_ColorChannel((String) jXTable1.getModel().getValueAt(row, 1));
                manager.setLabelRadius((int) jXTable1.getModel().getValueAt(row, 2));
                setParam_tolerance((int) jXTable1.getModel().getValueAt(row, 3));
                setParam_blur((int) jXTable1.getModel().getValueAt(row, 4));
                setParam_t_hema((int) jXTable1.getModel().getValueAt(row, 5));
                setParam_t_dab((int) jXTable1.getModel().getValueAt(row, 6));
                doStainingEstimation(false);
                calculateFScore();
            }
        }
    }//GEN-LAST:event_jXTable1MouseClicked

    private void jButton12ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton12ActionPerformed
        loadTable();
    }//GEN-LAST:event_jButton12ActionPerformed

    private void jButton11ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton11ActionPerformed
        saveTable();
    }//GEN-LAST:event_jButton11ActionPerformed

    private void jComboBox1ItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_jComboBox1ItemStateChanged
        if (previewOriginal != null && evt.getStateChange() == java.awt.event.ItemEvent.SELECTED) {
            if (((String) jComboBox1.getSelectedItem()).equalsIgnoreCase("User values")) {
                Colour_Deconvolution.getUserColors(this, manager.getVisibleTMAspot());
            }
            setParam_ColorChannel((String) jComboBox1.getSelectedItem());
            calculatePreviewConvolution(
                    new ImagePlus("", previewOriginal.getScaledInstance(-1, -1, Image.SCALE_DEFAULT)));
            displayPreviewImages();
        }
    }//GEN-LAST:event_jComboBox1ItemStateChanged

    private void jRadioButton1ItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_jRadioButton1ItemStateChanged
        if (jRadioButton1.isSelected() && current_TMAspot != null) {
            manager.updateTMAspot(current_TMAspot, true);
        }
    }//GEN-LAST:event_jRadioButton1ItemStateChanged

    private void jRadioButton2ItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_jRadioButton2ItemStateChanged
        if (jRadioButton2.isSelected() && current_TMAspot != null) {
            manager.updateTMAspot(current_TMAspot, true);
        }
    }//GEN-LAST:event_jRadioButton2ItemStateChanged

    private void jRadioButton3ItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_jRadioButton3ItemStateChanged
        if (jRadioButton3.isSelected() && current_TMAspot != null) {
            manager.updateTMAspot(current_TMAspot, true);
        }
    }//GEN-LAST:event_jRadioButton3ItemStateChanged

    private void jRadioButton4ItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_jRadioButton4ItemStateChanged
        if (jRadioButton4.isSelected() && current_TMAspot != null) {
            manager.updateTMAspot(current_TMAspot, true);
        }
    }//GEN-LAST:event_jRadioButton4ItemStateChanged

    private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed
        toggleAdvancedView();
    }//GEN-LAST:event_jButton1ActionPerformed

    private void jButton7ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton7ActionPerformed
        saveChannelImages(SHOW_CHANNEL1_IMAGE);
    }//GEN-LAST:event_jButton7ActionPerformed

    private void jButton8ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton8ActionPerformed
        saveChannelImages(SHOW_CHANNEL2_IMAGE);
    }//GEN-LAST:event_jButton8ActionPerformed

    private void jButton9ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton9ActionPerformed
        saveChannelImages(SHOW_CHANNEL3_IMAGE);
    }//GEN-LAST:event_jButton9ActionPerformed

    private void jCheckBox1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBox1ActionPerformed
        if (previewOriginal != null) {
            calculatePreviewConvolution(
                    new ImagePlus("", previewOriginal.getScaledInstance(-1, -1, Image.SCALE_DEFAULT)));
            displayPreviewImages();
        }
    }//GEN-LAST:event_jCheckBox1ActionPerformed

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(StainingEstimation.class.getName())
                    .log(java.util.logging.Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            java.util.logging.Logger.getLogger(StainingEstimation.class.getName())
                    .log(java.util.logging.Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            java.util.logging.Logger.getLogger(StainingEstimation.class.getName())
                    .log(java.util.logging.Level.SEVERE, null, ex);
        } catch (javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(StainingEstimation.class.getName())
                    .log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new StainingEstimation().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.ButtonGroup buttonGroup1;
    private javax.swing.JButton jButton1;
    private javax.swing.JButton jButton11;
    private javax.swing.JButton jButton12;
    private javax.swing.JButton jButton2;
    private javax.swing.JButton jButton3;
    private javax.swing.JButton jButton4;
    private javax.swing.JButton jButton5;
    private javax.swing.JButton jButton6;
    private javax.swing.JButton jButton7;
    private javax.swing.JButton jButton8;
    private javax.swing.JButton jButton9;
    private javax.swing.JCheckBox jCheckBox1;
    private javax.swing.JCheckBox jCheckBox10;
    private javax.swing.JCheckBox jCheckBox11;
    private javax.swing.JCheckBox jCheckBox12;
    private javax.swing.JCheckBox jCheckBox4;
    private javax.swing.JCheckBox jCheckBox7;
    private javax.swing.JCheckBox jCheckBox8;
    private javax.swing.JCheckBox jCheckBox9;
    private javax.swing.JComboBox jComboBox1;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel10;
    private javax.swing.JLabel jLabel11;
    private javax.swing.JLabel jLabel12;
    private javax.swing.JLabel jLabel13;
    private javax.swing.JLabel jLabel14;
    private javax.swing.JLabel jLabel15;
    private javax.swing.JLabel jLabel16;
    private javax.swing.JLabel jLabel17;
    private javax.swing.JLabel jLabel18;
    private javax.swing.JLabel jLabel19;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel20;
    private javax.swing.JLabel jLabel21;
    private javax.swing.JLabel jLabel22;
    private javax.swing.JLabel jLabel23;
    private javax.swing.JLabel jLabel24;
    private javax.swing.JLabel jLabel25;
    private javax.swing.JLabel jLabel26;
    private javax.swing.JLabel jLabel27;
    private javax.swing.JLabel jLabel28;
    private javax.swing.JLabel jLabel29;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JLabel jLabel30;
    private javax.swing.JLabel jLabel31;
    private javax.swing.JLabel jLabel32;
    private javax.swing.JLabel jLabel33;
    private javax.swing.JLabel jLabel34;
    private javax.swing.JLabel jLabel4;
    private javax.swing.JLabel jLabel5;
    private javax.swing.JLabel jLabel6;
    private javax.swing.JLabel jLabel7;
    private javax.swing.JLabel jLabel8;
    private javax.swing.JLabel jLabel9;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPanel jPanel13;
    private javax.swing.JPanel jPanel14;
    private javax.swing.JPanel jPanel3;
    private javax.swing.JPanel jPanel6;
    private javax.swing.JPanel jPanel7;
    private javax.swing.JRadioButton jRadioButton1;
    private javax.swing.JRadioButton jRadioButton2;
    private javax.swing.JRadioButton jRadioButton3;
    private javax.swing.JRadioButton jRadioButton4;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JScrollPane jScrollPane4;
    private javax.swing.JSlider jSlider1;
    private javax.swing.JSlider jSlider2;
    private javax.swing.JSlider jSlider3;
    private javax.swing.JSlider jSlider5;
    private javax.swing.JTextField jTextField10;
    private javax.swing.JTextField jTextField11;
    private javax.swing.JTextField jTextField12;
    private javax.swing.JTextField jTextField13;
    private javax.swing.JTextField jTextField14;
    private javax.swing.JTextField jTextField15;
    private javax.swing.JTextField jTextField18;
    private javax.swing.JTextField jTextField19;
    private javax.swing.JTextField jTextField2;
    private javax.swing.JTextField jTextField21;
    private javax.swing.JTextField jTextField22;
    private javax.swing.JTextField jTextField23;
    private javax.swing.JTextField jTextField3;
    private javax.swing.JTextField jTextField4;
    private javax.swing.JTextField jTextField5;
    private javax.swing.JTextField jTextField6;
    private javax.swing.JTextField jTextField7;
    private javax.swing.JTextField jTextField8;
    private javax.swing.JTextField jTextField9;
    private javax.swing.JToggleButton jToggleButton2;
    private org.jdesktop.swingx.JXLabel jXLabel1;
    private org.jdesktop.swingx.JXLabel jXLabel10;
    private org.jdesktop.swingx.JXLabel jXLabel11;
    private org.jdesktop.swingx.JXLabel jXLabel2;
    private org.jdesktop.swingx.JXLabel jXLabel3;
    private org.jdesktop.swingx.JXLabel jXLabel4;
    private org.jdesktop.swingx.JXLabel jXLabel5;
    private org.jdesktop.swingx.JXLabel jXLabel6;
    private org.jdesktop.swingx.JXLabel jXLabel7;
    private org.jdesktop.swingx.JXLabel jXLabel8;
    private org.jdesktop.swingx.JXLabel jXLabel9;
    private org.jdesktop.swingx.JXTable jXTable1;
    private org.jdesktop.beansbinding.BindingGroup bindingGroup;
    // End of variables declaration//GEN-END:variables

    @Override
    public String getAuthor() {
        return "Peter J. Schffler";
    }

    @Override
    public String getVersion() {
        return PLUGINVERSION;
    }

    @Override
    public boolean start() {
        return true;
    }

    @Override
    public boolean stop() {
        setVisible(false);
        return true;
    }

    @Override
    public void setPluginManager(PluginManager manager) {
        this.manager = manager;
    }

    public PluginManager getPluginManager() {
        return this.manager;
    }

    @Override
    public Icon getIcon() {
        return new ImageIcon(this.getIconImage());
    }

    @Override
    public String getPluginName() {
        return PLUGINNAME;
    }

    @Override
    public void actionPerformed(java.awt.event.ActionEvent evt) {
        this.setVisible(true);
        updateOptionsToTMAspot(manager.getVisibleTMAspot(), manager.getSelectedTMAspots());
    }

    @Override
    public void setParameterDefaults() {
        setParam_tolerance(8);
        setParam_t_dab(60);
        setParam_t_hema(50);
        setParam_ColorChannel("H DAB");
        setParam_UserColor1(Color.WHITE);
        setParam_UserColor2(Color.WHITE);
        setParam_UserColor3(Color.WHITE);
        setParam_hideLegend(true);
        setParam_respectAreas(true);
        setParam_useThresholdMap(false);
        setParam_blur(1);
        setParam_substractChannels(false);
    }

    @Override
    public void setParameters(Properties parameters) {
        String value;
        value = parameters.getProperty("tolerance");
        if (value != null) {
            setParam_tolerance(Integer.parseInt(value));
        }
        value = parameters.getProperty("t_dab");
        if (value != null) {
            setParam_t_dab(Integer.parseInt(value));
        }
        value = parameters.getProperty("t_hema");
        if (value != null) {
            setParam_t_hema(Integer.parseInt(value));
        }
        value = parameters.getProperty("colorChannel");
        if (value != null) {
            setParam_ColorChannel(value);
        }
        value = parameters.getProperty("userColor1");
        if (value != null) {
            setParam_UserColor1(new Color(Integer.parseInt(value)));
        }
        value = parameters.getProperty("userColor2");
        if (value != null) {
            setParam_UserColor2(new Color(Integer.parseInt(value)));
        }
        value = parameters.getProperty("userColor3");
        if (value != null) {
            setParam_UserColor3(new Color(Integer.parseInt(value)));
        }
        value = parameters.getProperty("hideLegend");
        if (value != null) {
            setParam_hideLegend(Boolean.parseBoolean(value));
        }
        value = parameters.getProperty("respectAreas");
        if (value != null) {
            setParam_respectAreas(Boolean.parseBoolean(value));
        }
        value = parameters.getProperty("useThresholdMap");
        if (value != null) {
            setParam_useThresholdMap(Boolean.parseBoolean(value));
        }
        value = parameters.getProperty("blur");
        if (value != null) {
            setParam_blur(Integer.parseInt(value));
        }
        value = parameters.getProperty("substractChannels");
        if (value != null) {
            setParam_substractChannels(Boolean.parseBoolean(value));
        }
    }

    @Override
    public Properties getParameters() {
        Properties parameters = new Properties();
        parameters.setProperty("tolerance", Integer.toString(getParam_tolerance()));
        parameters.setProperty("t_dab", Integer.toString(getParam_t_dab()));
        parameters.setProperty("t_hema", Integer.toString(getParam_t_hema()));
        parameters.setProperty("colorChannel", getParam_ColorChannel());
        parameters.setProperty("userColor1", Integer.toString(getParam_UserColor1().getRGB()));
        parameters.setProperty("userColor2", Integer.toString(getParam_UserColor2().getRGB()));
        parameters.setProperty("userColor3", Integer.toString(getParam_UserColor3().getRGB()));
        parameters.setProperty("hideLegend", Boolean.toString(getParam_hideLegend()));
        parameters.setProperty("respectAreas", Boolean.toString(getParam_respectAreas()));
        parameters.setProperty("useThresholdMap", Boolean.toString(getParam_useThresholdMap()));
        parameters.setProperty("blur", Integer.toString(getParam_blur()));
        parameters.setProperty("substractChannels", Boolean.toString(getParam_substractChannels()));
        return parameters;
    }

    @Override
    public String getHTMLReport(String HTMLFolderPath) {
        String output = "<html>";
        char linebreak = '\n';
        String HTMLFolderName = new File(HTMLFolderPath).getName() + File.separator;

        output += "<table><tr>" + linebreak + "  <td colspan=2><i><u>Color Deconvolution Parameters</u></i></td>"
                + linebreak + " </tr>" + linebreak

                + " <tr>" + linebreak + "  <td><b>Staining Protocol</b></td>" + linebreak + "  <td>"
                + getParam_ColorChannel() + "</td>" + linebreak + " </tr>" + linebreak

                + " <tr>" + linebreak + "  <td><b>Tolerance</b></td>" + linebreak + "  <td>" + getParam_tolerance()
                + "</td>" + linebreak + " </tr>" + linebreak

                + " <tr>" + linebreak + "  <td><b>Blurring</b></td>" + linebreak + "  <td>" + getParam_blur()
                + "</td>" + linebreak + " </tr>" + linebreak

                + " <tr>" + linebreak + "  <td><b>T_hema</b></td>" + linebreak + "  <td>" + getParam_t_hema()
                + "</td>" + linebreak + " </tr>" + linebreak

                + " <tr>" + linebreak + "  <td><b>T_dab</b></td>" + linebreak + "  <td>" + getParam_t_dab()
                + "</td>" + linebreak + " </tr>" + linebreak;

        output += "</table><br>" + linebreak;

        output += "</html>";
        return output;
    }

    @Override
    public void updateOptionsToTMAspot(TMAspot visible_TMAspot, List<TMAspot> selected_TMAspots) {
        if (isShowing()) {
            this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
            updateOptionsToTMAspot(visible_TMAspot);
            this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        }
    }

    @Override
    public void drawInformationPreNuclei(TMAspot ts, Graphics g, double z, int x_min, int y_min, int x_max,
            int y_max) {
    }

    @Override
    public void drawInformationPostNuclei(TMAspot ts, Graphics g, double z, int x_min, int y_min, int x_max,
            int y_max) {
    }

    @Override
    public BufferedImage showAlternativeImage(TMAspot ts) {
        if (jRadioButton2.isSelected()) {
            // show channel 1 image
            return getBufferedImage(ts, SHOW_CHANNEL1_IMAGE);
        } else if (jRadioButton3.isSelected()) {
            // show channel 2 image
            return getBufferedImage(ts, SHOW_CHANNEL2_IMAGE);
        } else if (jRadioButton4.isSelected()) {
            // show channel 3 image
            return getBufferedImage(ts, SHOW_CHANNEL3_IMAGE);
        }
        return null;
    }

    @Override
    public void TMAspotMouseClicked(TMAspot ts, TMApoint tp, MouseEvent evt) {

    }

    /**
     * Expands this dialog to the advanced view (Precision/Recall Plots).
     */
    void toggleAdvancedView() {
        boolean b = jButton1.getText().contains(">>");
        jLabel17.setVisible(b);
        jButton3.setVisible(b);
        jLabel26.setVisible(b);
        jPanel14.setVisible(b);
        jPanel3.setVisible(b);

        jButton1.setText(b ? "Advanced <<" : "Advanced >>");
        pack();
    }

    /**
     * Runs the parameter search throughout all parameters (as indicated by the user). For each parameter set, the color deconvolution is done and the precision/recall value
     * is evaulated. Is only meaningful, if manual gold-standard nuclei are present. Results are displayed as plot and table.
     */
    void doParameterChangeExperiment() {
        List<TMAspot> tss = manager.getSelectedTMAspots();

        int radius_start, radius_end, radius_d, tol_start, tol_end, tol_d, blur_start, blur_end, blur_d,
                t_hema_start, t_hema_end, t_hema_d, t_dab_start, t_dab_end, t_dab_d;
        radius_start = jCheckBox12.isSelected() ? Integer.parseInt(jTextField14.getText()) : getParam_tolerance();
        radius_end = jCheckBox12.isSelected() ? Integer.parseInt(jTextField15.getText()) : getParam_tolerance();
        radius_d = Integer.parseInt(jTextField18.getText());
        tol_start = jCheckBox8.isSelected() ? Integer.parseInt(jTextField2.getText()) : getParam_tolerance();
        tol_end = jCheckBox8.isSelected() ? Integer.parseInt(jTextField7.getText()) : getParam_tolerance();
        tol_d = Integer.parseInt(jTextField19.getText());
        blur_start = jCheckBox9.isSelected() ? Integer.parseInt(jTextField8.getText()) : getParam_blur();
        blur_end = jCheckBox9.isSelected() ? Integer.parseInt(jTextField9.getText()) : getParam_blur();
        blur_d = Integer.parseInt(jTextField21.getText());
        t_hema_start = jCheckBox10.isSelected() ? Integer.parseInt(jTextField10.getText()) : getParam_t_hema();
        t_hema_end = jCheckBox10.isSelected() ? Integer.parseInt(jTextField11.getText()) : getParam_t_hema();
        t_hema_d = Integer.parseInt(jTextField22.getText());
        t_dab_start = jCheckBox11.isSelected() ? Integer.parseInt(jTextField12.getText()) : getParam_t_dab();
        t_dab_end = jCheckBox11.isSelected() ? Integer.parseInt(jTextField13.getText()) : getParam_t_dab();
        t_dab_d = Integer.parseInt(jTextField23.getText());

        interruptStainingEstimationParameterChangeThread();
        sepct = new StainingEstimationParameterChangeThread((TMARKERPluginManager) manager, this, tss, radius_start,
                radius_end, radius_d, tol_start, tol_end, tol_d, blur_start, blur_end, blur_d, t_hema_start,
                t_hema_end, t_hema_d, t_dab_start, t_dab_end, t_dab_d, jToggleButton2.isSelected(),
                jCheckBox4.isSelected());
        sepct.start();
    }

    /**
     * Draws the given chartpanel on the right position of this dialog window. If the position is already occupied by an old chartpanel, the old chartpanel will be removed.
     * @param chartpanel The chartpanel to be drawn.
     */
    void setPrecisionRecallPlot(ChartPanel chartpanel) {
        if (((java.awt.BorderLayout) (jPanel3.getLayout()))
                .getLayoutComponent(java.awt.BorderLayout.NORTH) != null) {
            jPanel3.remove(((java.awt.BorderLayout) (jPanel3.getLayout()))
                    .getLayoutComponent(java.awt.BorderLayout.NORTH));
        }
        chartpanel.setPreferredSize(new Dimension(jXTable1.getPreferredSize().width, 300));
        jPanel3.add(chartpanel, java.awt.BorderLayout.NORTH);
        validate();
        pack();
    }

    /**
     * Stops the parameter search if there is any running.
     */
    public void interruptStainingEstimationParameterChangeThread() {
        if (sepct != null && sepct.isAlive()) {
            sepct.continu = false;
            sepct.interrupt();
        }
    }

    /**
     * Refreshes the parameters with the given TMAspot, e.g. redraw the thumbnail images.
     * @param ts The TMAspot which is considered (should be the currently visible TMAspot in the main window).
     */
    void updateOptionsToTMAspot(TMAspot ts) {
        if (ts != null && ts != current_TMAspot) {
            //jPanel13.removeAll();
            //((DefaultTableModel)jXTable1.getModel()).setRowCount(0);
            jXTable1.packTable(0);
            jScrollPane4.setPreferredSize(
                    new Dimension(jXTable1.getPreferredSize().width, jScrollPane4.getPreferredSize().height));
            if (ts.getParam_r() > 0) {
                jSlider1.setValue(ts.getParam_r());
            }
            if (ts.getParam_t_hema() > 0) {
                jSlider3.setValue(ts.getParam_t_hema());
            }
            if (ts.getParam_t_dab() > 0) {
                jSlider2.setValue(ts.getParam_t_dab());
            }
            double[] f = new double[] { 1.0 };
            previewOriginal = ts.getThumbnailImage(150, 150, f);
            imageResizeFactor = f[0];
            jXLabel1.setText("Original  ( " + Math.round(100.0 * imageResizeFactor) / 100.0 + " x )");
            ImagePlus previewOriginal_gray_tmp = new ImagePlus("",
                    previewOriginal.getScaledInstance(-1, -1, Image.SCALE_DEFAULT));
            new ImageConverter(previewOriginal_gray_tmp).convertToGray8();
            previewOriginal_gray = previewOriginal_gray_tmp.getImage();
            calculatePreviewConvolution(
                    new ImagePlus("", previewOriginal.getScaledInstance(-1, -1, Image.SCALE_DEFAULT)));
            displayPreviewImages();
            current_TMAspot = ts;
        }
    }

    /**
     * Calculates color deconvolution on the small preview image.
     * @param imp The images to be deconvolved.
     */
    void calculatePreviewConvolution(ImagePlus imp) {
        HE_preview = Colour_Deconvolution.get_deconvolution_images(imp, this, manager.getVisibleTMAspot(), true,
                getParam_ColorChannel(), getParam_substractChannels());
        calculatePreviewConvolution_gray(showNucleiPreview());
    }

    /**
     * Sets the color deconvolution preview images.
     * @param withNuclei If true, the (preview) nuclei are drawn on the image.
     */
    void calculatePreviewConvolution_gray(boolean withNuclei) {
        HE_preview_gray = new ArrayList<>();
        /*if (getParam_useThresholdMap()) {
        for (int i=0; i<HE.size(); i++) {
            ImagePlus impl = HE_preview.get(i);
            ImagePlus impl_gray = impl.duplicate();
            new ImageConverter(impl_gray).convertToGray8();
            ImagePlus tmap = channelImage2ThresholdMap(impl_gray, imageResizeFactor*getParam_TMblur());
            if (i==0) {
                jLabel18.setIcon(new ImageIcon(tmap.getImage()));
            }
            if (i==1) {
                jLabel19.setIcon(new ImageIcon(tmap.getImage()));
            }
            if (i==2) {
                jLabel20.setIcon(new ImageIcon(tmap.getImage()));
            }
            if (withNuclei) calculateNucleiPreview(impl_gray, imageResizeFactor*getParam_blur(), getParam_tolerance(), tmap);
                
            HE_preview_gray.add(impl_gray);
        }
                
        } else {*/
        for (int i = 0; i < HE_preview.size(); i++) {
            ImagePlus impl = HE_preview.get(i);
            ImagePlus impl_gray = impl.duplicate();
            new ImageConverter(impl_gray).convertToGray8();
            if (i == 0) {
                if (withNuclei)
                    calculateNucleiPreview(impl_gray, imageResizeFactor * getParam_blur(), getParam_tolerance(),
                            getParam_t_hema());
            }
            if (i == 1) {
                if (withNuclei)
                    calculateNucleiPreview(impl_gray, imageResizeFactor * getParam_blur(), getParam_tolerance(),
                            getParam_t_dab());
            }

            HE_preview_gray.add(impl_gray);
        }
        //}

        jLabel18.setVisible(getParam_useThresholdMap());
        jLabel19.setVisible(getParam_useThresholdMap());
        jLabel20.setVisible(getParam_useThresholdMap());
        jXLabel9.setVisible(getParam_useThresholdMap());
        jXLabel10.setVisible(getParam_useThresholdMap());
        jXLabel11.setVisible(getParam_useThresholdMap());

    }

    /**
     * Displays the top three preview images on the right positions of the screen.
     */
    void displayPreviewImages() {
        if (previewOriginal != null)
            jLabel6.setIcon(new ImageIcon(previewOriginal));
        if (HE_preview != null && HE_preview.size() > 0 && HE_preview.get(0) != null)
            jLabel7.setIcon(new ImageIcon(HE_preview.get(0).getImage()));
        if (HE_preview != null && HE_preview.size() > 1 && HE_preview.get(1) != null)
            jLabel8.setIcon(new ImageIcon(HE_preview.get(1).getImage()));
        if (HE_preview != null && HE_preview.size() > 2 && HE_preview.get(2) != null)
            jLabel9.setIcon(new ImageIcon(HE_preview.get(2).getImage()));

        displayPreviewImages_gray();
    }

    /**
     * Displays the bottom three preview images on the right positions of the screen.
     */
    void displayPreviewImages_gray() {
        if (previewOriginal_gray != null)
            jLabel10.setIcon(new ImageIcon(previewOriginal_gray));
        if (HE_preview_gray != null && HE_preview_gray.size() > 0 && HE_preview_gray.get(0) != null)
            jLabel11.setIcon(new ImageIcon(HE_preview_gray.get(0).getImage()));
        if (HE_preview_gray != null && HE_preview_gray.size() > 1 && HE_preview_gray.get(1) != null)
            jLabel12.setIcon(new ImageIcon(HE_preview_gray.get(1).getImage()));
        if (HE_preview_gray != null && HE_preview_gray.size() > 2 && HE_preview_gray.get(2) != null)
            jLabel13.setIcon(new ImageIcon(HE_preview_gray.get(2).getImage()));
    }

    /**
     * Performs nucleus finding on the given color chanel image imp (heatmap) with the given parameters. Nuclei are drawn on the image.
     * @param imp The gray scaled color channel image after color deconvolution. It is altered by the method.
     * @param blur The blur parameter before nucleus detection.
     * @param tolerance The tolerance parameter of the local maximum method.
     * @param t The minimum value of a local maximum.
     */
    void calculateNucleiPreview(ImagePlus imp, double blur, int tolerance, int t) {
        List<TMApoint> tps = StainingEstimation.find_nucleus_lm(null, imp.duplicate(), 0, 0, blur, tolerance, t,
                TMALabel.STAINING_0, true);
        new ImageConverter(imp).convertToRGB();
        Graphics g = imp.getImage().getGraphics();
        g.setColor(Color.RED);
        for (TMApoint tp : tps) {
            g.drawRect(tp.x - 1, tp.y - 1, 3, 3);
        }
    }

    /**
     * Performs nucleus finding on the given color chanel image imp (heatmap) with the given parameters. Nuclei are drawn on the image.
     * @param imp The gray scaled color channel image after color deconvolution. It is altered by the method.
     * @param blur The blur parameter before nucleus detection.
     * @param tolerance The tolerance parameter of the local maximum method.
     * @param tmap The grayscaled image (same size as imp). Each pixel give a minimum value of a local maximum at this point.
     */
    void calculateNucleiPreview(ImagePlus imp, double blur, int tolerance, ImagePlus tmap) {
        List<TMApoint> tps = StainingEstimation.find_nucleus_lm(null, imp.duplicate(), 0, 0, blur, tolerance, tmap,
                TMALabel.STAINING_0, true);
        new ImageConverter(imp).convertToRGB();
        Graphics g = imp.getImage().getGraphics();
        g.setColor(Color.RED);
        for (TMApoint tp : tps) {
            g.drawRect(tp.x - 1, tp.y - 1, 3, 3);
        }
    }

    /**
     * Sets the tolerance for the color deconvolution method.
     * @param tolerance The tolerance (0-255).
     */
    public void setParam_tolerance(int tolerance) {
        if (tolerance >= 0) {
            jSlider1.setValue(tolerance);
        }
    }

    /**
     * Returns the tolerance for the color deconvolution method.
     * @return The tolerance (0-255).
     */
    public int getParam_tolerance() {
        return jSlider1.getValue();
    }

    /**
     * Sets the fixed threshold for channel 2.
     * @param t The the fixed threshold (0-255).
     */
    public void setParam_t_dab(int t) {
        if (t >= 0) {
            jSlider2.setValue(t);
        }
    }

    /**
     * Returns the fixed threshold for channel 2.
     * @return The the fixed threshold (0-255).
     */
    public int getParam_t_dab() {
        return jSlider2.getValue();
    }

    /**
     * Sets the fixed threshold for channel 1.
     * @param t The the fixed threshold (0-255).
     */
    public void setParam_t_hema(int t) {
        if (t >= 0) {
            jSlider3.setValue(t);
        }
    }

    /**
     * Returns the fixed threshold for channel 1.
     * @return The the fixed threshold (0-255).
     */
    public int getParam_t_hema() {
        return jSlider3.getValue();
    }

    /**
     * Sets the blur radius.
     * @param blur The blur radius.
     */
    public void setParam_blur(int blur) {
        if (blur >= 0) {
            jSlider5.setValue(blur);
        }
    }

    /**
     * Returns the blur radius.
     * @return The blur radius.
     */
    public int getParam_blur() {
        return jSlider5.getValue();
    }

    /**
     * Sets the flag of substraction of channel 2 from channel 1.
     * @param b True, if channel 2 should be substracted from channel 1.
     */
    public void setParam_substractChannels(boolean b) {
        jCheckBox1.setSelected(b);
    }

    /**
     * Returns the flag of substraction of channel 2 from channel 1.
     * @return True, if channel 2 should be substracted from channel 1.
     */
    public boolean getParam_substractChannels() {
        return jCheckBox1.isSelected();
    }

    /**
     * Sets the selected staining protocol.
     * @param c The selected staining protocol (e.g. "H&E" or "H DAB").
     */
    public void setParam_ColorChannel(String c) {
        jComboBox1.setSelectedItem(c);
    }

    /**
     * Returns the selected staining protocol.
     * @return The selected staining protocol (e.g. "H&E" or "H DAB").
     */
    public String getParam_ColorChannel() {
        return (String) jComboBox1.getSelectedItem();
    }

    /**
     * Returns the selected staining protocol index.
     * @return The selected staining protocol index.
     */
    public int getParam_ColorChannelIndex() {
        return jComboBox1.getSelectedIndex();
    }

    /**
     * Sets the parameter whether or not the legend produced by the color deconvolution algorithm should be hidden.
     * @param b If false, the legend is shown.
     */
    public void setParam_hideLegend(boolean b) {
        //jCheckBox1.setSelected(b);
    }

    /**
     * Returns whether or not the legend produced by the color deconvolution algorithm should be hidden.
     * @return If false, the legend is shown.
     */
    public boolean getParam_hideLegend() {
        //return jCheckBox1.isSelected();
        return true;
    }

    /**
     * Sets whether or not a dynamic threshold map should be used for staining estimation instead of a fixed threshold.
     * @param b If true, a dynamic threshold map should be used for staining estimation instead of a fixed threshold.
     */
    public void setParam_useThresholdMap(boolean b) {
        //jCheckBox6.setSelected(b);
    }

    /**
     * Returns whether or not a dynamic threshold map should be used for staining estimation instead of a fixed threshold.
     * @return If true, a dynamic threshold map should be used for staining estimation instead of a fixed threshold.
     */
    public boolean getParam_useThresholdMap() {
        //return jCheckBox6.isSelected();
        return false;
    }

    /**
     * Returns whether or not including/excluding areas should be respected for staining estimation.
     * @return True, if including/excluding areas should be respected for staining estimation. Otherwise, the whole images will be used.
     */
    public boolean getParam_respectAreas() {
        return jCheckBox4.isSelected();
    }

    /**
     * Sets the parameter if including/excluding areas should be respected for staining estimation.
     * @param b True, if including/excluding areas should be respected for staining estimation. Otherwise, the whole images will be used.
     */
    public void setParam_respectAreas(boolean b) {
        jCheckBox4.setSelected(b);
    }

    /**
     * Defines what to do when the user clicks on close.
     * @return 0 for successful closing of this dialog, otherwise a number larger than 0.
     */
    private int closeAndRelease() {
        this.dispose();
        return 0;
    }

    /**
     * Returns the color deconvolution channel 1 color (if "custom channels" are chosen).
     * @return The color used as channel 1.
     */
    public Color getParam_UserColor1() {
        return userColor1;
    }

    /**
     * Sets the color deconvolution channel 1 color (if "custom channels" are chosen).
     * @param userC1 The color used as channel 1.
     */
    public void setParam_UserColor1(Color userC1) {
        this.userColor1 = userC1;
    }

    /**
     * Returns the color deconvolution channel 2 color (if "custom channels" are chosen).
     * @return The color used as channel 2.
     */
    public Color getParam_UserColor2() {
        return userColor2;
    }

    /**
     * Sets the color deconvolution channel 2 color (if "custom channels" are chosen).
     * @param userC2 The color used as channel 2.
     */
    public void setParam_UserColor2(Color userC2) {
        this.userColor2 = userC2;
    }

    /**
     * Returns the color deconvolution channel 3 color (if "custom channels" are chosen).
     * @return The color used as channel 3.
     */
    public Color getParam_UserColor3() {
        return userColor3;
    }

    /**
     * Sets the color deconvolution channel 3 color (if "custom channels" are chosen).
     * @param userC3 The color used as channel 3.
     */
    public void setParam_UserColor3(Color userC3) {
        this.userColor3 = userC3;
    }

    /**
     * Returns the parameter of last staining String (e.g. "H&E" or "H DAB").
     * @return The last staining protocol string used for color deconvolution.
     */
    public String getParam_lastStain() {
        return param_lastStain;
    }

    /**
     * Sets the parameter of last staining String (e.g. "H&E" or "H DAB").
     * @param lastStain The last staining protocol string used for color deconvolution.
     */
    public void setParam_lastStain(String lastStain) {
        this.param_lastStain = lastStain;
    }

    /**
     * Returns the flag parameter of substract channels in the last processing.
     * @return The last "substractChannels" parameter used for color deconvolution.
     */
    public boolean getParam_lastSubstractChannels() {
        return param_lastSubstractChannels;
    }

    /**
     * Sets the flag parameter of substract channels in the last processing.
     * @param b The last "substractChannels" parameter used for color deconvolution.
     */
    public void setParam_lastSubstractChannels(boolean b) {
        this.param_lastSubstractChannels = b;
    }

    /**
     * Returns whether or not the nuclei should be sketched when the sliders are moved.
     * @return True, if the nuclei should be sketched as preview (might cost runtime).
     */
    private boolean showNucleiPreview() {
        return jCheckBox7.isSelected();
    }

    /**
     * Calls the performStainingEstimation() function in the parent TMARKER program.
     * @param asParallelThread If true, the staining estimation is done on a second thread, and the program can be used in parallel. 
     */
    public void doStainingEstimation(boolean asParallelThread) {
        performStainingEstimation(null, manager.getLabelRadius(), getParam_blur(), jSlider1.getValue(), 10, 10,
                jSlider3.getValue(), jSlider2.getValue(), false, true, getParam_hideLegend(), false,
                getParam_ColorChannel(), getParam_substractChannels(), getParam_respectAreas(), asParallelThread);
    }

    /**
     * Detects and counts the cell nuclei on a given TMAspot with the color deconvolution method.
     * @param se The StainingEstimation instance, used for User chosen colors which are stored in the StainingEstimation instance.
     * @param ts The given TMAspot to be processed.
     * @param radius The radius of the cell nuclei (used by the color devconvolution method).
     * @param blur The blurring applied to the channels prior to local maxima finding.
     * @param tolerance The tolerance used for local maxima finding.
     * @param TMblur_hema The blurring for the dynamic threshold map for the channel 1 (if any).
     * @param TMblur_dab The blurring for the dynamic threshold map for the channel 2 (if any).
     * @param t_hema The intensity threshold for chanel 1 (used by the color devconvolution method).
     * @param t_dab The intensity threshold for chanel 2 (used by the color devconvolution method).
     * @param delete_cur_gs_spots If true, current gold standard nuclei are deleted before processing.
     * @param delete_cur_es_spots If true, current estimated nuclei are deleted before processing.
     * @param hide_legend Hide the color deconvolution legend (used by the color devconvolution method).
     * @param markCancerous If true, found nuclei are marked as TMAspot.LABEL_POS, otherwise as TMAspot.LABEL_UNK
     * @param asParallelThread If true, saving of the Channel Images is done in a parallel thread.
     * @param myStain The staining colors description (e.g. "H&E" or "H DAB") (used by the color devconvolution method).
     * @param respectAreas If true, including and excluding areas (ROI) on the images are respected and found nuclei are filtered accordingly.
     * @param substractChannels If true, channel 2 will be substracted from channel 1.
     */
    public static void doStainingEstimation(StainingEstimation se, TMAspot ts, int radius, double blur,
            int tolerance, int TMblur_hema, int TMblur_dab, int t_hema, int t_dab, boolean delete_cur_gs_spots,
            boolean delete_cur_es_spots, boolean hide_legend, boolean markCancerous, String myStain,
            boolean substractChannels, boolean respectAreas, boolean asParallelThread) {
        if (delete_cur_gs_spots) {
            ts.deleteAllPoints_GS();
        }
        if (delete_cur_es_spots) {
            ts.deleteAllPoints_ES();
        }
        ts.setParam_blur(blur);
        ts.setParam_tolerance(tolerance);
        ts.setParam_t_hema(t_hema);
        ts.setParam_t_dab(t_dab);
        List<TMApoint> tps = StainingEstimation.tma_stain(se, ts, radius, blur, tolerance, TMblur_hema, TMblur_dab,
                t_hema, t_dab, hide_legend, myStain, substractChannels, false, respectAreas, asParallelThread);
        if (tps != null) {
            if (markCancerous) {
                for (TMApoint tp : tps) {
                    tp.setLabel(TMALabel.LABEL_POS);
                }
            }
            ts.addPoints(tps);
            ts.dispStainingInfo();
            if (ts == ts.getCenter().getVisibleTMAspot()) {
                se.manager.updateTMAspot(ts, true);
            }
        }
    }

    /**
     * Performs the staining estimation of all given TMAspots.
     * @param tss The TMAspots to be processed.
     * @param radius The radius of the nuclei.
     * @param blur The blurring applied to the channels prior to local maxima finding.
     * @param tolerance The tolerance used for local maxima finding.
     * @param TMblur_hema The blurring for the dynamic threshold map for the channel 1 (if any).
     * @param TMblur_dab The blurring for the dynamic threshold map for the channel 2 (if any).
     * @param t_hema The fixed channel 1 threshold (between 0-255).
     * @param t_dab The fixed channel 2 threshold (between 0-255).
     * @param delete_cur_gs_spots If true, current gold standard nuclei will be deleted.
     * @param delete_cur_es_spots If true, current estimated nuclei will be deleted.
     * @param hide_legend If false, a legend of the color deconvolution algorithm will appear.
     * @param markCancerous If true, all found nuclei will be labeled as "cancerous" nuclei
     * @param myStain String of the staining protocol (e.g. "H&E" or "H DAB").
     * @param respectAreas If true, including and excluding areas (ROI) on the images are respected and found nuclei are filtered accordingly.
     * @param substractChannels If true, channel 2 will be substracted from channel 1.
     */
    public void performStainingEstimation(List<TMAspot> tss, int radius, double blur, int tolerance,
            int TMblur_hema, int TMblur_dab, int t_hema, int t_dab, boolean delete_cur_gs_spots,
            boolean delete_cur_es_spots, boolean hide_legend, boolean markCancerous, String myStain,
            boolean respectAreas, boolean substractChannels) {
        performStainingEstimation(tss, radius, blur, tolerance, TMblur_hema, TMblur_dab, t_hema, t_dab,
                delete_cur_gs_spots, delete_cur_es_spots, hide_legend, markCancerous, myStain, substractChannels,
                respectAreas, true);
    }

    /**
     * Performs the staining estimation of all given TMAspots.
     * @param tss The TMAspots to be processed.
     * @param radius The radius of the nuclei.
     * @param blur The blurring applied to the channels prior to local maxima finding.
     * @param tolerance The tolerance used for local maxima finding.
     * @param TMblur_hema The blurring for the dynamic threshold map for the channel 1 (if any).
     * @param TMblur_dab The blurring for the dynamic threshold map for the channel 2 (if any).
     * @param t_hema The fixed channel 1 threshold (between 0-255).
     * @param t_dab The fixed channel 2 threshold (between 0-255).
     * @param delete_cur_gs_spots If true, current gold standard nuclei will be deleted.
     * @param delete_cur_es_spots If true, current estimated nuclei will be deleted.
     * @param hide_legend If false, a legend of the color deconvolution algorithm will appear.
     * @param markCancerous If true, all found nuclei will be labeled as "cancerous" nuclei
     * @param myStain String of the staining protocol (e.g. "H&E" or "H DAB").
     * @param substractChannels If true, channel 2 will be substracted from channel 1.
     * @param respectAreas If true, including and excluding areas (ROI) on the images are respected and found nuclei are filtered accordingly.
     * @param asParallelThread If true, staining estimation is done in a separate thread. Otherwise, TMARKER is blocked as long as the TMAspots are processed.
     */
    public void performStainingEstimation(List<TMAspot> tss, int radius, double blur, int tolerance,
            int TMblur_hema, int TMblur_dab, int t_hema, int t_dab, boolean delete_cur_gs_spots,
            boolean delete_cur_es_spots, boolean hide_legend, boolean markCancerous, String myStain,
            boolean substractChannels, boolean respectAreas, boolean asParallelThread) {
        if (tss == null) {
            tss = manager.getSelectedTMAspots();
        }
        if (asParallelThread) {
            if (set != null) {
                if (set.isAlive())
                    set.interrupt();
            }
            set = new StainingEstimationThread((TMARKERPluginManager) manager, this, tss, radius, blur, tolerance,
                    TMblur_hema, TMblur_dab, t_hema, t_dab, delete_cur_gs_spots, delete_cur_es_spots, hide_legend,
                    markCancerous, myStain, substractChannels, respectAreas);
            set.start();
        } else {
            for (TMAspot ts : tss) {
                doStainingEstimation(this, ts, radius, blur, tolerance, TMblur_hema, TMblur_dab, t_hema, t_dab,
                        delete_cur_gs_spots, delete_cur_es_spots, hide_legend, markCancerous, myStain,
                        substractChannels, respectAreas, false);
            }
            String infoText = "<html><font size=\"3\" face=\"Arial\"><h2><strong>Congratulations! :-) <br><br>Automatic nucleus detection was successful!</strong></h2></strong><br>"
                    + "<u>You might want to review TMARKER's guesses by:</u>"
                    + "<ul type=\"circle\" style=\"margin:5; padding:10;\">"
                    + "<li>changing the nucleus radius to fit actual nuclei in the images,<br><br> </li>"
                    + "<li>changing the parameters in the <i>Color Deconvolution Options</i> dialog. </li>"
                    + "</ul>" + "<u>You may continue as follows:</u>" + "<ul style=\"margin:5; padding:10;\">"
                    + "<li><b>Inspect the channels</b> created by color deconvolution, as shown as <i>Channel 1</i> and <i>Channel 2</i> in the TMA View.<br><br>"
                    + "These channels show the bases for unstained and stained nuclei, respectively.<br><br></li>"

                    + "<li><b>Cluster the found nuclei</b> to get more more detailed staining information (1+/2+/3+).<br><br>"
                    + "TMARKER will guide you in <i>\"Tools -> Plugins -> Intensity Clustering\"</i> for nucleus clustering.<br><br></li>"

                    + "<li>You can always <b>save a XML file</b> containing the TMA spots and detected nuclei with <i>\"File -> Save As...\"</i> for future sessions.<br><br></li>"
                    + "<li>You can always <b>save a HTML report</b> with <i>\"File -> Save As...\"</i>.</li>"
                    + "</ul>"
                    + "<b><i>TIP:</i></b> As most analyses are performed only on selected images, you might first want to select all TMA spots in the TMA List on the left.<br><br>"
                    + "<b><i>TIP:</i></b> Hold the mouse over buttons and labels to show additional information.<br><br>"
                    + "</font></html>";
            manager.setInfoText(infoText);
            manager.setStatusMessageLabel("Performing Staining Estimation ... Done.");
            manager.setProgressbar(0);
        }
    }

    /**
     * Interrupts the staining estimation thread, if there is any.
     */
    public void interruptStainingEstimation() {
        if (set != null && set.isAlive()) {
            set.continu = false;
            set.interrupt();
            manager.setStatusMessageLabel("Staining Estimation Stopped.");
            manager.setProgressbar(0);
        }
    }

    /**
     * Writes an entry into the staining estimation parameter table.
     * @param fscores The parameters to be written.
     */
    void writePrecisionRecallTable(double[][][] fscores) {
        if (fscores != null) {
            DefaultTableModel tm = ((DefaultTableModel) jXTable1.getModel());
            tm.setRowCount(0);
            int i = 0;
            for (double[][] d1 : fscores) {
                for (double[] d2 : d1) {
                    tm.setRowCount(++i);
                    for (int j = 0; j < jXTable1.getModel().getColumnCount(); j++) {
                        if (((DefaultTableModel) jXTable1.getModel()).getColumnClass(j) == Double.class) {
                            tm.setValueAt(d2[j], i - 1, j);
                        } else if (((DefaultTableModel) jXTable1.getModel()).getColumnClass(j) == Integer.class) {
                            tm.setValueAt((int) d2[j], i - 1, j);
                        } else {
                            if (jXTable1.getModel().getColumnName(j).equalsIgnoreCase("staining")) {
                                tm.setValueAt(jComboBox1.getModel().getElementAt(Math.round((float) d2[j])), i - 1,
                                        j);
                            } else {
                                tm.setValueAt(Double.toString(d2[j]), i - 1, j);
                            }
                        }
                    }
                }
            }
            jXTable1.doLayout();
            jXTable1.packAll();
            jXTable1.packTable(0);
            jButton11.setEnabled(jXTable1.getRowCount() > 0);
        }
    }

    /**
     * Creates a Precision/Recall Plot in the staining estimation dialog. Gold-standard and estimated nuclei have to exist.
     */
    void calculateFScore() {
        // draw the points in the plot
        XYSeriesCollection dataset = new XYSeriesCollection();
        if (manager != null) {
            List<TMAspot> tss = manager.getSelectedTMAspots();
            double[] stats;
            for (TMAspot ts : tss) {
                stats = ts.calculateMatchStatistics();
                XYSeries series = new XYSeries(
                        "P_" + Misc.FilePathStringtoFilenameWOExtension(ts.getName()).replaceAll("spots_", "")
                                .replaceAll("_top_left", "").replaceAll("prostate_cancer_mib_validation_", ""));
                series.add(stats[2 + 7], stats[1 + 7]);
                dataset.addSeries(series);
            }
        }
        JFreeChart chart;
        // If there is already a chart, re-use it.
        if (((java.awt.BorderLayout) (jPanel3.getLayout()))
                .getLayoutComponent(java.awt.BorderLayout.NORTH) != null) {
            chart = ((ChartPanel) ((java.awt.BorderLayout) (jPanel3.getLayout()))
                    .getLayoutComponent(java.awt.BorderLayout.NORTH)).getChart();

            chart.getXYPlot().setDataset(1, dataset);
            chart.getXYPlot().setDatasetRenderingOrder(DatasetRenderingOrder.FORWARD);
            XYLineAndShapeRenderer las = new XYLineAndShapeRenderer(false, true);
            XYToolTipGenerator ttg = new StandardXYToolTipGenerator();
            las.setBaseToolTipGenerator(ttg);
            las.setUseFillPaint(true);
            las.setUseOutlinePaint(true);

            chart.getXYPlot().setRenderer(1, las);
            // Otherwise create a new chart.
        } else {
            chart = ChartFactory.createScatterPlot("Precision Recall Plot", "Recall", "Precision", dataset,
                    PlotOrientation.VERTICAL, true, false, false);
            chart.getXYPlot().getDomainAxis().setRange(0, 1);
            chart.getXYPlot().getRangeAxis().setRange(0, 1);
            chart.setBackgroundPaint(null);
            chart.getPlot().setBackgroundPaint(null);
            //chart.getPlot().setBackgroundPaint(Color.WHITE);
            ChartPanel chartPanel = new ChartPanel(chart);
            chartPanel.setPreferredSize(new Dimension(jXTable1.getPreferredSize().width, 300));
            jPanel3.add(chartPanel, java.awt.BorderLayout.NORTH);
            validate();
            pack();
        }
    }

    /**
     * Opens a dialog to let the user choose a file in which the table of the staining estimation parameters is saved.
     */
    private void saveTable() {
        if (previewOriginal != null && jXTable1.getRowCount() > 0) {
            String sep = manager.getColumnSeparator();
            String currentDir = manager.getCurrentDir();
            File file;
            List<String> exts = new ArrayList<>(1);
            exts.add("csv");
            List<String> descrs = new ArrayList<>(1);
            descrs.add("Semicolon separated file");
            String filename = null;
            if (manager.getVisibleTMAspot() != null) {
                filename = Misc.FilePathStringtoFilenameWOExtension(manager.getVisibleTMAspot().getName()) + ".csv";
            }
            file = FileChooser.chooseSavingFile(this, currentDir, filename, exts, descrs);

            if (file != null) {
                setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

                // Save csv
                try {
                    BufferedWriter bfw = new BufferedWriter(new FileWriter(file));
                    for (int i = 0; i < jXTable1.getModel().getColumnCount(); i++) {
                        bfw.write("\"" + jXTable1.getModel().getColumnName(i) + "\"");
                        if (i < jXTable1.getModel().getColumnCount() - 1)
                            bfw.write(sep);
                    }
                    for (int i = 0; i < jXTable1.getRowCount(); i++) {
                        bfw.newLine();
                        for (int j = 0; j < jXTable1.getModel().getColumnCount(); j++) {
                            Object o = jXTable1.getModel().getValueAt(i, j);
                            if (((DefaultTableModel) jXTable1.getModel()).getColumnClass(j) == Double.class) {
                                bfw.write((Double.toString((Double) jXTable1.getModel().getValueAt(i, j))));
                            } else if (((DefaultTableModel) jXTable1.getModel())
                                    .getColumnClass(j) == Integer.class) {
                                bfw.write((Integer.toString((Integer) jXTable1.getModel().getValueAt(i, j))));
                            } else if (((DefaultTableModel) jXTable1.getModel())
                                    .getColumnClass(j) == String.class) {
                                bfw.write("\"" + (String) (jXTable1.getModel().getValueAt(i, j)) + "\"");
                            } else {
                                bfw.write(jXTable1.getModel().getValueAt(i, j).toString());
                            }
                            if (j < jXTable1.getModel().getColumnCount() - 1)
                                bfw.write(sep);
                        }
                    }
                    bfw.close();
                } catch (Exception e) {
                    Logger.getLogger(StainingEstimation.class.getName()).log(Level.SEVERE, null, e);
                    JOptionPane.showMessageDialog(this,
                            "Could not save file " + file.getName() + "\n(Maybe it is in use?)",
                            "Error writing file", JOptionPane.ERROR_MESSAGE);
                }

                manager.setCurrentDir(file.getAbsolutePath());
                setCursor(Cursor.getDefaultCursor());
            }

        }
    }

    /**
     * Opens a dialog to let the user chose a table which is then loaded in the staining estimation parameters table view.
     */
    private void loadTable() {
        if (previewOriginal != null) {
            String currentDir = manager.getCurrentDir();
            File file = FileChooser.chooseCSVFile(this, currentDir);
            if (file != null) {
                try {
                    ((DefaultTableModel) jXTable1.getModel()).setRowCount(0);
                    BufferedReader bfr = new BufferedReader(new FileReader(file));
                    String line = bfr.readLine();
                    int i = 0;
                    while (bfr.ready()) {
                        line = bfr.readLine();
                        String[] cells = line.split(";");
                        ((DefaultTableModel) jXTable1.getModel()).setRowCount(++i);
                        for (int j = 0; j < jXTable1.getModel().getColumnCount(); j++) {
                            if (((DefaultTableModel) jXTable1.getModel()).getColumnClass(j) == Double.class) {
                                ((DefaultTableModel) jXTable1.getModel()).setValueAt(Double.parseDouble(cells[j]),
                                        i - 1, j);
                            } else if (((DefaultTableModel) jXTable1.getModel())
                                    .getColumnClass(j) == Integer.class) {
                                ((DefaultTableModel) jXTable1.getModel()).setValueAt(Integer.parseInt(cells[j]),
                                        i - 1, j);
                            } else if (((DefaultTableModel) jXTable1.getModel())
                                    .getColumnClass(j) == String.class) {
                                ((DefaultTableModel) jXTable1.getModel()).setValueAt(cells[j].replaceAll("\"", ""),
                                        i - 1, j);
                            } else {
                                ((DefaultTableModel) jXTable1.getModel()).setValueAt(cells[j], i - 1, j);
                            }
                        }
                    }
                    bfr.close();
                } catch (Exception e) {
                    Logger.getLogger(StainingEstimation.class.getName()).log(Level.SEVERE, null, e);
                }
            }
        } else {
            JOptionPane.showMessageDialog(this, "Please select first a TMA spot in TMARKER's main window.",
                    "Select Image", JOptionPane.WARNING_MESSAGE);
        }
    }

    /**
     * Performs the staining estimation of a given TMAspot. The image is patchized into small chunks (regular images are one patch, NDPIs might be cut into real patches. For NDPI, only the region of interests are processed).
     * Then the staining estimation is done in parallel for the patches, and the found nuclei are stored in a list of TMApoints. The size of the patches for NDPI is calculated by the number of available processors and max Memory.
     * @param se The StainingEstimation instance.
     * @param ts The TMAspot of the image.
     * @param radius The radius of the nuclei.
     * @param blur The blurring applied to the channels prior to local maxima finding.
     * @param tolerance The tolerance used for local maxima finding.
     * @param TMblur_hema The blurring for the dynamic threshold map for the channel 1 (if any).
     * @param TMblur_dab The blurring for the dynamic threshold map for the channel 2 (if any).
     * @param t_hema The fixed channel 1 threshold (between 0-255).
     * @param t_dab The fixed channel 2 threshold (between 0-255).
     * @param hide_legend If false, a legend of the color deconvolution algorithm will appear.
     * @param myStain String of the staining protocol (e.g. "H&E" or "H DAB").
     * @param substractChannels If true, channel 2 will be substracted from channel 1. 
     * @param asParallelThread If true, saving of the images will be done in a parallel thread. Saving the channel images takes long CPU time.
     * @param useThresholdMap If true, a dynamic threshold map is used for maximum acceptance instead of a fixed value.
     * @param respectAreas If true, including and excluding areas (ROI) on the images are respected and found nuclei are filtered accordingly.
     * @return A list of all found TMApoints on the given TMAspot. Points are already filtered by the including or excluding areas. Also, points to not overlap to each other (in the given radius).
     */
    public static List<TMApoint> tma_stain(StainingEstimation se, TMAspot ts, int radius, double blur,
            int tolerance, int TMblur_hema, int TMblur_dab, int t_hema, int t_dab, boolean hide_legend,
            String myStain, boolean substractChannels, boolean useThresholdMap, boolean respectAreas,
            boolean asParallelThread) {
        tmarker tm = ts.getCenter();

        // RANDOM STREAM RESETTEN
        tm.resetRandomSeed();

        List<TMApoint> brown_spots_total = new ArrayList<>();

        try {
            String text = tm.getStatusMessageLabel().getText();

            if (!asParallelThread) {
                tm.setStatusMessageLabel(ts.getName() + ": Performing Staining Estimation ...");
                tm.setProgressbar(15);
            }

            List<Point> offsets = new ArrayList();
            List<Point> sizes = new ArrayList();

            int maxbytes = (int) (0.1 * se.manager.getMaxMemory() / se.manager.getNumberProcessors());
            int size = (int) Math.floor(Math.sqrt(maxbytes / 4.0));
            int w = ts.getWidth();
            int h = ts.getHeight();

            // collect the ROIS (we do only need to compute everything here, which is faster for large NDPI images)
            List<Polygon> rois_incl = new ArrayList();
            if (respectAreas && ts.isNDPI()) {
                rois_incl.addAll(ts.getIncludingAreas());
            }
            if (rois_incl.isEmpty())
                rois_incl.add(new Polygon(new int[] { 0, 0, w, w }, new int[] { 0, h, h, 0 }, 4)); // for tif, jpeg and other normal images, compute the whole image (not ROIS), since we can store the deconvolved channels on harddisk, making post-processing faster.

            // union overlaping ROIS
            List<Rectangle> rects = new ArrayList();
            rects.add(rois_incl.get(0).getBounds());
            for (int z = 1; z < rois_incl.size(); z++) {
                for (int k = 0; k < rects.size(); k++) {
                    if (rects.get(k).intersects(rois_incl.get(z).getBounds())) {
                        rects.get(k).add(rois_incl.get(z).getBounds());
                    } else {
                        rects.add(rois_incl.get(z).getBounds());
                    }
                }
            }

            for (int z = 0; z < rects.size(); z++) {
                Rectangle rect = rects.get(z);
                int r_w = rect.width;
                int r_h = rect.height;

                // Patchize the large ROI
                for (int i = 0; i < 1.0 * r_w / size; i++) {
                    for (int j = 0; j < 1.0 * r_h / size; j++) {
                        int size_x = Math.min(size, r_w - i * size);
                        int size_y = Math.min(size, r_h - j * size);
                        sizes.add(new Point(size_x, size_y));
                        offsets.add(new Point(rect.x + i * size, rect.y + j * size));
                    }
                }
            }

            StainingEstimationCoreThread sect = new StainingEstimationCoreThread(
                    (TMARKERPluginManager) se.getPluginManager(), se, ts, radius, blur, tolerance, TMblur_hema,
                    TMblur_dab, t_hema, t_dab, hide_legend, myStain, substractChannels, useThresholdMap,
                    respectAreas, brown_spots_total, offsets, sizes, size);
            sect.start();
            sect.join();

        } catch (InterruptedException ex) {
            Logger.getLogger(StainingEstimation.class.getName()).log(Level.SEVERE, null, ex);
        }
        return brown_spots_total;
    }

    /**
     * Performs the staining estimation of a given TMAspot and a given sub-patch (workhorse function).
     * @param se The StainingEstimation instance.
     * @param ts The TMAspot of the image.
     * @param radius The radius of the nuclei.
     * @param blur The blurring applied to the channels prior to local maxima finding.
     * @param tolerance The tolerance used for local maxima finding.
     * @param TMblur_hema The blurring for the dynamic threshold map for the channel 1 (if any).
     * @param TMblur_dab The blurring for the dynamic threshold map for the channel 2 (if any).
     * @param t_hema The fixed channel 1 threshold (between 0-255).
     * @param t_dab The fixed channel 2 threshold (between 0-255).
     * @param hide_legend If false, a legend of the color deconvolution algorithm will appear.
     * @param asParallelThread If true, saving of the images will be done in a parallel thread. Saving the channel images takes long CPU time.
     * @param useThresholdMap If true, a dynamic threshold map is used for maximum acceptance instead of a fixed value.
     * @param respectAreas If true, including and excluding areas (ROI) on the images are respected and found nuclei are filtered accordingly.
     * @param myStain String of the staining protocol (e.g. "H&E" or "H DAB").
     * @param substractChannels If true, channel 2 will be substracted from channel 1. 
     * @param brown_spots_total The resulting list of found TMApoints. These are added to this list.
     * @param offset The offset of the processed sub-patch.
     * @param size The size of the processed sub-patch, expressed as a point.
     * @param maxsize The maximum size of the sub-patch (edge length, only needed for NDPI, might be equal to the maximum patch-edge-size).
     * 
     */
    static void tma_stainCore(StainingEstimation se, TMAspot ts, int radius, double blur, int tolerance,
            int TMblur_hema, int TMblur_dab, int t_hema, int t_dab, boolean hide_legend, String myStain,
            boolean substractChannels, boolean useThresholdMap, boolean respectAreas, boolean asParallelThread,
            List<TMApoint> brown_spots_total, Point offset, Point size, int maxsize) {
        tmarker tm = ts.getCenter();

        String text = tm.getStatusMessageLabel().getText();

        List<TMApoint> brown_spots = new ArrayList<>();

        BufferedImage img = ts.getSubimage(offset.x, offset.y, size.x, size.y, maxsize);

        int offset_x = offset.x;
        int offset_y = offset.y;

        // Retrieve the Channel images HE
        List<ImagePlus> HE;
        List<ImagePlus> HE_old = ts.isNDPI() ? null : getChannelImages(se, ts);
        List<ImagePlus> ThresholdMaps = null; // = ts.getThresholdMaps();
        if (!myStain.equalsIgnoreCase("User values") && myStain.equalsIgnoreCase(se.getParam_lastStain())
                && substractChannels == se.getParam_lastSubstractChannels() && HE_old != null
                && !HE_old.isEmpty()) { // if the staining did not change, use the old channel images
            HE = new ArrayList<>();
            HE.add(HE_old.get(0));
            HE.add(HE_old.get(1));
            HE.add(HE_old.get(2));
        } else { // otherwise create new channel images
            HE = deconvolveImage(img, tm, se, ts, hide_legend, myStain, substractChannels, asParallelThread);
        }

        // What to do if we use dynamic thresholds instead of fixed thresholds
        if (useThresholdMap) {
            // ThresholdMaps are created
            if (!asParallelThread) {
                tm.setStatusMessageLabel(ts.getName() + ": Creating dynamic threshold maps ...");
                tm.setProgressbar(67);
            }
            ThresholdMaps = new ArrayList<>(2);
            ThresholdMaps.add(channelImage2ThresholdMap(HE.get(0), TMblur_hema));
            ThresholdMaps.add(channelImage2ThresholdMap(HE.get(1), TMblur_dab));
            for (ImagePlus ip : ThresholdMaps) {
                //normalize_image_gray(ip);
            }
            if (tmarker.DEBUG > 0) {
                List<ImagePlus> TM_ = new ArrayList<>();
                for (ImagePlus ip : ThresholdMaps) {
                    TM_.add(ip.duplicate());
                }
                ts.saveThresholdMaps(TM_, asParallelThread);
            }
        }

        // Find the channel 1 nuclei
        List<TMApoint> blue_spots = new ArrayList<>();
        if (HE.size() > 0) {
            if (!asParallelThread) {
                tm.setStatusMessageLabel(
                        ts.getName() + ": Performing Staining Estimation: Local Maximum Channel 1 ...");
                tm.setProgressbar(70);
            }
            if (useThresholdMap) {
                blue_spots = find_nucleus_lm(ts, HE.get(0), offset_x, offset_y, blur, tolerance,
                        ThresholdMaps.get(0), TMALabel.STAINING_0, false);
            } else {
                blue_spots = find_nucleus_lm(ts, HE.get(0), offset_x, offset_y, blur, tolerance, t_hema,
                        TMALabel.STAINING_0, false);
            }
        }

        // Find the channel 2 nuclei
        if (HE.size() > 1) {
            if (!asParallelThread) {
                tm.setStatusMessageLabel(
                        ts.getName() + ": Performing Staining Estimation: Local Maximum Channel 2 ...");
                tm.setProgressbar(80);
            }
            if (useThresholdMap) {
                brown_spots = find_nucleus_lm(ts, HE.get(1), offset_x, offset_y, blur, tolerance,
                        ThresholdMaps.get(1), TMALabel.STAINING_3, false);
            } else {
                brown_spots = find_nucleus_lm(ts, HE.get(1), offset_x, offset_y, blur, tolerance, t_dab,
                        TMALabel.STAINING_3, false);
            }

        }

        //Filter nuclei according to Areas
        if (respectAreas) {
            if (!asParallelThread) {
                tm.setStatusMessageLabel(ts.getName()
                        + ": Performing Staining Estimation: Filter Nuclei in Regions of Interest ...");
                tm.setProgressbar(85);
            }
            TMAspot.filter_centroids_on_Areas(blue_spots, ts);
            TMAspot.filter_centroids_on_Areas(brown_spots, ts);
        }

        // Filter overlapping nuclei
        if (!asParallelThread) {
            tm.setStatusMessageLabel(
                    ts.getName() + ": Performing Staining Estimation: Filter Overlapping Nuclei ...");
            tm.setProgressbar(90);
        }
        TMAspot.filter_centroids_on_distance(brown_spots, brown_spots, radius);
        TMAspot.filter_centroids_on_distance(blue_spots, blue_spots, radius);
        TMAspot.filter_centroids_on_distance(blue_spots, brown_spots, radius);

        // Finish staining estimation
        if (!asParallelThread) {
            tm.setStatusMessageLabel(ts.getName() + ": Performing Staining Estimation: Done.");
            tm.setProgressbar(100);
        }
        brown_spots.addAll(blue_spots);
        for (ImagePlus HE1 : HE) {
            HE1.flush();
        }
        if (!asParallelThread) {
            tm.setStatusMessageLabel(text);
            tm.setProgressbar(0);
        }
        brown_spots_total.addAll(brown_spots);
    }

    /**
     * Returns the three channel images of a given RGB image.
     * @param img The image to be processed.
     * @param tm The main program, used for status message.
     * @param se The StainingEstimation instance.
     * @param ts The TMAspot which is to be processed.
     * @param hide_legend If false, a legend of the color deconvolution algorithm will appear.
     * @param myStain String of the staining protocol (e.g. "H&E" or "H DAB").
     * @param substractChannels If true, channel 2 will be substracted from channel 1.
     * @param asParallelThread If true, processing done in a parallel thread.
     * @return A list with the deconvolution images.
     * 
     */
    public static List<ImagePlus> deconvolveImage(Image img, tmarker tm, StainingEstimation se, TMAspot ts,
            boolean hide_legend, String myStain, boolean substractChannels, boolean asParallelThread) {

        ImagePlus imp = new ImagePlus("", img);
        List<ImagePlus> HE;

        if (!asParallelThread) {
            tm.setStatusMessageLabel(ts.getName() + ": Performing Staining Estimation: Colour Deconvolution ...");
            tm.setProgressbar(30);
        }
        if (se != null) {
            se.setParam_lastStain(myStain);
            se.setParam_lastSubstractChannels(substractChannels);
        }
        HE = Colour_Deconvolution.get_deconvolution_images(imp, se, ts, hide_legend, myStain, substractChannels);
        //while (HE.size()>2) {
        //    HE.remove(2);
        //}

        // Convert to gray and normalize
        for (int i = 0; i < HE.size(); i++) {
            // HE_preview.get(0) is hema channel (blue), HE_preview.get(1) is DAB channel (brown).
            if (!asParallelThread) {
                tm.setStatusMessageLabel(
                        ts.getName() + ": Performing Staining Estimation: Normalize Channel " + i + "...");
                tm.setProgressbar((int) (45 + i * (20.0 / HE.size())));
            }
            new ImageConverter(HE.get(i)).convertToGray8();
            //normalize_image_gray(HE_preview.get(i), (i==0?t_hema:t_dab)); // Normalization already done before color deconvolution
            //new FileSaver(HE_preview.get(i)).saveAsJpeg(ts.getTmpDir()+ tmarker.fs + "channel" + i + ".jpg");
            //new File(ts.getTmpDir()+ tmarker.fs + "channel" + i + ".jpg").deleteOnExit();
        }

        // Save in TMARKER
        if (!ts.isNDPI()) {
            if (!asParallelThread) {
                tm.setStatusMessageLabel(ts.getName() + ": Saving Channel Images ...");
                tm.setProgressbar(65);
            }
            List<ImagePlus> HE_ = new ArrayList<>();
            for (ImagePlus ip : HE) {
                HE_.add(ip.duplicate());
            }
            setChannelImages(se, ts, HE_, asParallelThread);
        }
        return HE;
    }

    /**
     * Returns the channel images of a TMAspot.
     * @param se The StainingEstimation instance which stores the information about channel images.
     * @param ts The TMAspot whose channel images are returned.
     * @return The channel images of this TMAspot. Empty if color deconvolution has not been performed yet.
     */
    public static List<ImagePlus> getChannelImages(StainingEstimation se, TMAspot ts) {
        List<ImagePlus> HEs = new ArrayList<>();
        if (se.processedTMAspots.contains(ts)) {
            for (int i = 0; i < 3; i++) {
                String ext = Misc.FilePathStringtoExtension(ts.getOriginalImagename()).toLowerCase();
                // read the images from hard disk. Otherwise they have to be stored in the TMARKER session which drastically increases RAM demand.
                HEs.add(new ImagePlus(
                        ts.getTmpDir() + File.separator + "channel" + Integer.toString(i + 1) + "." + ext));
            }
        }
        return HEs;
    }

    /**
     * Sets the channel images of a TMAspot (e.g. after color deconvolution).
     * Channel images are temporary saved on harddisk to save RAM.
     * @param se The StainingEstimation instance which stores the information about channel images.
     * @param ts The TMAspot to which the channel images belong.
     * @param HE The images to be saved.
     * @param asParallelThread If true, the saving happens in a parallel thread.
     */
    public static void setChannelImages(StainingEstimation se, TMAspot ts, List<ImagePlus> HE,
            boolean asParallelThread) {
        if (HE == null || HE.isEmpty()) {
            se.processedTMAspots.remove(ts);
        } else {
            if (!se.processedTMAspots.contains(ts)) {
                se.processedTMAspots.add(ts);
            }
            String ext = Misc.FilePathStringtoExtension(ts.getOriginalImagename()).toLowerCase();
            if (asParallelThread) {
                saveImagesThread sit = new saveImagesThread(HE, ext, ts.getTmpDir() + File.separator, null, "");
                sit.start();
            } else {
                for (int i = 0; i < HE.size(); i++) {
                    try {
                        File file = new File(
                                ts.getTmpDir() + File.separator + "channel" + Integer.toString(i + 1) + "." + ext);
                        file.deleteOnExit();
                        ImageIO.write(HE.get(i).getBufferedImage(), ext, file);
                    } catch (IOException ex) {
                        Logger.getLogger(StainingEstimation.class.getName()).log(Level.SEVERE, null, ex);
                    }

                }
            }
        }
    }

    /**
     * Returns the filename of a TMAspot image (can be the original image name, or an image name of the channel images).
     * @param ts The TMAspot whose image is returned.
     * @param which_image One of SHOW_ORIGINAL_IMAGE, SHOW_CHANNEL1_IMAGE, SHOW_CHANNEL2_IMAGE, SHOW_CHANNEL3_IMAGE or SHOW_HEATMAP_IMAGE.
     * @return The filename of this TMAspot.
     */
    public String getImagename(TMAspot ts, int which_image) {
        String name = null;
        String ext = Misc.FilePathStringtoExtension(ts.getOriginalImagename()).toLowerCase();
        switch (which_image) {
        case SHOW_ORIGINAL_IMAGE:
            ts.getImagename();
            break;
        case SHOW_CHANNEL1_IMAGE:
            name = ts.getTmpDir() + File.separator + "channel1." + ext;
            break;
        case SHOW_CHANNEL2_IMAGE:
            name = ts.getTmpDir() + File.separator + "channel2." + ext;
            break;
        case SHOW_CHANNEL3_IMAGE:
            name = ts.getTmpDir() + File.separator + "channel3." + ext;
            break;
        default:
            break;
        }
        return name;
    }

    /**
     * Returns the image of a TMAspot.
     * @param ts The TMAspot whose image should be returned.
     * @param whichImage One of SHOW_ORIGINAL_IMAGE, SHOW_CHANNEL1_IMAGE, SHOW_CHANNEL2_IMAGE, SHOW_CHANNEL3_IMAGE.
     * @return The image of the TMAspot.
     */
    public BufferedImage getBufferedImage(TMAspot ts, int whichImage) {
        BufferedImage bi = null;
        if (processedTMAspots.contains(ts)) {
            try {
                //bi = ImageIO.read(new File(getOriginalImagename(whichImage)));
                bi = Misc.loadImageFast(getImagename(ts, whichImage));
            } catch (Exception ex) {
                if (tmarker.DEBUG > 0) {
                    Logger.getLogger(StainingEstimation.class.getName()).log(Level.SEVERE, null, ex);
                }
                JOptionPane.showMessageDialog(this,
                        "An error occurred while opening " + getImagename(ts, whichImage) + "\n\n"
                                + "Maybe the thread that generates this image has not finished, yet\n"
                                + "(e.g. after color deconvolution).",
                        "Error opening image", JOptionPane.ERROR_MESSAGE);
            }
        }
        return bi;
    }

    /**
     * Normalizes a gray scaled image.
     * @param imp Image to be normalized
     */
    public static void normalize_image_gray(ImagePlus imp) {
        imp.getProcessor().setMinAndMax(imp.getStatistics().min, imp.getStatistics().max);
    }

    /**
     * Normalizes a RGB image.
     * @param imp Image to be normalized
     */
    public static void normalize_image_rgb(ImagePlus imp) {
        imp.getProcessor().setMinAndMax(imp.getStatistics().min, imp.getStatistics().max);
    }

    /**
     * Finds nuclei on a gray scaled heatmap image (channel image) with the local maximum finding method.
     * @param ts The TMAspot which is processed (found TMApoints are assigned to it).
     * @param imp The grayscaled heatmap image.
     * @param offset_x The offset of the image(patch) in horizontal direction.
     * @param offset_y The offset of the image(patch) in vertical direction.
     * @param blur A blur radius which is applied before local maxima finding. 0 for no blurring.
     * @param tolerance The tolerance (between 0 and 255) according to which to local maxima are accepted which lie on the same ridge.
     * @param threshold A threshold (0-255) below which all maxima are rejected.
     * @param staining A staining label which is stored in the found TMApoints. If the channel represents e.g. "no staining", this could be TMAspot.STAINING_0.
     * If the channel is the DAB channel, this could be TMAspot.STAINING_3.
     * @param forPreview Set this TRUE, if this function is used for the small preview images. Upscaling of coordinates is for whole slide images is then skipped.
     * @return A list with all found local maxima on this image.
     */
    public static List<TMApoint> find_nucleus_lm(TMAspot ts, ImagePlus imp, int offset_x, int offset_y, double blur,
            double tolerance, double threshold, byte staining, boolean forPreview) {
        List<TMApoint> tps = new ArrayList<>();

        //if (imp!=null && imp.getProcessor()!=null) {
        imp.getProcessor().invert();

        // For whole slide images: if the image at hand is downscaled, the loci have to be upscaled again.
        double factor_x = 1.0;
        double factor_y = 1.0;
        /*if (!forPreview && ts.isNDPI()) {
            if (imp.getWidth()<ts.getWidth()) {
                factor_x = ts.getWidth()/imp.getWidth();
            }
            if (imp.getHeight()<ts.getHeight()) {
                factor_y = ts.getHeight()/imp.getHeight();
            }
        }*/

        if (blur > 0) {
            GaussianBlur gb = new GaussianBlur();
            gb.blurGaussian(imp.getProcessor(), blur / factor_x, blur / factor_y, 0.2);
        }

        MaximumFinder mf = new MaximumFinder();
        Polygon pol = mf.getMaxima(imp.getProcessor(), tolerance, threshold, true);
        for (int i = 0; i < pol.npoints; i++) {
            tps.add(new TMApoint(ts, (int) (factor_x * (pol.xpoints[i] + offset_x)),
                    (int) (factor_y * (pol.ypoints[i] + offset_y)), TMALabel.LABEL_UNK, staining));
        }
        //}
        return tps;
    }

    /**
     * Finds nuclei on a gray scaled heatmap image (channel image) with the local maximum finding method and a dynamic threshold map
     * @param ts The TMAspot which is processed (found TMApoints are assigned to it).
     * @param imp The grayscaled heatmap image.
     * @param offset_x The offset of the image(patch) in horizontal direction.
     * @param offset_y The offset of the image(patch) in vertical direction.
     * @param blur A blur radius which is applied before local maxima finding. 0 for no blurring.
     * @param tolerance The tolerance (between 0 and 255) according to which to local maxima are accepted which lie on the same ridge.
     * @param threshold A threshold image (0-255). A local maximum on a point (x,y) must be larger than the threshold on the threshold map on the same position.
     * Otherwise it will be rejected.
     * @param staining A staining label which is stored in the found TMApoints. If the channel represents e.g. "no staining", this could be TMAspot.STAINING_0.
     * If the channel is the DAB channel, this could be TMAspot.STAINING_3.
     * @param forPreview Set this TRUE, if this function is used for the small preview images. Upscaling of coordinates is for whole slide images is then skipped.
     * @return A list with all found local maxima on this image.
     */
    public static List<TMApoint> find_nucleus_lm(TMAspot ts, ImagePlus imp, int offset_x, int offset_y, double blur,
            double tolerance, ImagePlus threshold, byte staining, boolean forPreview) {
        List<TMApoint> tps = new ArrayList<>();

        //if (imp!=null && imp.getProcessor()!=null) {
        imp.getProcessor().invert();

        // For whole slide images: if the image at hand is downscaled, the loci have to be upscaled again.
        double factor_x = 1.0;
        double factor_y = 1.0;
        /*if (!forPreview && ts.isNDPI()) {
            if (imp.getWidth()<ts.getWidth()) {
                factor_x = ts.getWidth()/imp.getWidth();
            }
            if (imp.getHeight()<ts.getHeight()) {
                factor_y = ts.getHeight()/imp.getHeight();
            }
        }*/

        if (blur > 0) {
            GaussianBlur gb = new GaussianBlur();
            gb.blurGaussian(imp.getProcessor(), blur / factor_x, blur / factor_y, 0.2);
        }
        MaximumFinder mf = new MaximumFinder();
        Polygon pol = mf.getMaxima(imp.getProcessor(), tolerance, threshold, true);
        for (int i = 0; i < pol.npoints; i++) {
            tps.add(new TMApoint(ts, (int) (factor_x * (pol.xpoints[i] + offset_x)),
                    (int) (factor_y * (pol.ypoints[i] + offset_y)), TMALabel.LABEL_UNK, staining));
        }

        //}
        return tps;
    }

    /**
     * Generates a threshold heatmap of a given grayscaled channel image. According to implementation
     * this might simply be a blurred version of the input image or a mosaic image.
     * @param imp The grayscaled input image which should be converted.
     * @param patchsize Determines the blurring radius or the mosaic size.
     * @return A dynamic threshold map for this input image.
     */
    public static ImagePlus channelImage2ThresholdMap(ImagePlus imp, double patchsize) {
        ImagePlus map = imp.duplicate();

        if (map.getBitDepth() != 8) {
            new ImageConverter(map).convertToGray8();
        }

        //map.getProcessor().invert();

        //* Method 1: Blur the image
        if (patchsize > 0) {
            GaussianBlur gb = new GaussianBlur();
            gb.blurGaussian(map.getProcessor(), 2 * patchsize, 2 * patchsize, 0.02);
        }
        /*/ Method 2: Mosaic the image
        Image I = map.getBufferedImage().getScaledInstance(map.getWidth()/patchsize, map.getHeight()/patchsize, Image.SCALE_DEFAULT);
        BufferedImage BI = new BufferedImage(I.getWidth(null), I.getHeight(null), BufferedImage.TYPE_BYTE_GRAY);
        BI.getGraphics().drawImage(I, 0, 0, null);
        I = BI.getScaledInstance(map.getWidth(), map.getHeight(), Image.SCALE_DEFAULT);
        BI = new BufferedImage(I.getWidth(null), I.getHeight(null), BufferedImage.TYPE_BYTE_GRAY);
        BI.getGraphics().drawImage(I, 0, 0, null);
        map.setImage(BI);
        //*/

        return map;
    }

    /**
     * Writes the progress numbers and estimated time according to total number 
     * of instances, already processed number of instances and process start time
     * to a JLabel. If total is 0, " " is written (making the progress information
     * invisible). If startTimeMillis > 0, the estimated time for the remaining
     * instances is added.
     * @param processed Processed number of instances.
     * @param total Total number of instances (if 0, " " will be written).
     * @param startTimeMillis The starting time of the process.
     */
    void setProgressNumber(int processed, int total, long startTimeMillis) {
        if (processed <= 0 || total <= 0) {
            jLabel30.setText(" ");
        } else {
            String text = "Processed  " + processed + "/" + total + "  images  (" + 100 * processed / total + " %)";
            if (startTimeMillis > 0) {
                long time = (total - processed) * (System.currentTimeMillis() - startTimeMillis) / processed;
                text += "    (est. " + String.format("%d min, %d sec", TimeUnit.MILLISECONDS.toMinutes(time),
                        TimeUnit.MILLISECONDS.toSeconds(time)
                                - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(time)))
                        + ")";
            }
            jLabel30.setText(text);
        }
    }

    /**
     * Writes the second progress numbers and estimated time according to total number 
     * of patches, already processed number of instances and process start time
     * to a JLabel. If total is 0, " " is written (making the progress information
     * invisible). If startTimeMillis > 0, the estimated time for the remaining
     * instances is added.
     * @param processed Processed number of instances.
     * @param total Total number of instances (if 0, " " will be written).
     * @param startTimeMillis The starting time of the process.
     */
    void setProgressNumber_2(int processed, int total, long startTimeMillis) {
        if (processed <= 0 || total <= 0) {
            jLabel34.setText(" ");
        } else {
            String text = "Processed  " + processed + "/" + total + "  patches  (" + 100 * processed / total
                    + " %)";
            if (startTimeMillis > 0) {
                long time = (total - processed) * (System.currentTimeMillis() - startTimeMillis) / processed;
                text += "    (est. " + String.format("%d min, %d sec", TimeUnit.MILLISECONDS.toMinutes(time),
                        TimeUnit.MILLISECONDS.toSeconds(time)
                                - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(time)))
                        + ")";
            }
            jLabel34.setText(text);
        }
    }

    /**
     * Saves the channel images of processed TMAspots. The user has to choose a
     * folder in which the images are stored. This does nothing if
     * color-deconvolution has not been performed, yet.
     *
     * @param whichImage The channel to be saves (SHOW_CHANNEL1_IMAGE,
     * SHOW_CHANNEL2_IMAGE or SHOW_CHANNEL3_IMAGE).
     */
    public void saveChannelImages(int whichImage) {
        if (!processedTMAspots.isEmpty()) {
            // Let the user chose a folder
            File file = FileChooser.chooseSavingFolder(this, manager.getCurrentDir());

            if (file != null) {
                manager.setCurrentDir(file.getPath());
                for (TMAspot ts : processedTMAspots) {
                    try {
                        Files.copy((new File(getImagename(ts, whichImage))).toPath(),
                                (new File(file.getPath() + File.separator + ts.getName() + "_"
                                        + Misc.FilePathStringtoFilename(getImagename(ts, whichImage)))).toPath(),
                                StandardCopyOption.REPLACE_EXISTING);
                    } catch (Exception ex) {
                        if (tmarker.DEBUG > 0) {
                            Logger.getLogger(StainingEstimation.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
            }
        }
    }

}