com.jug.MotherMachine.java Source code

Java tutorial

Introduction

Here is the source code for com.jug.MotherMachine.java

Source

package com.jug;

/**
 * Main class for the MotherMachine project.
 */

import ij.ImageJ;

import java.awt.DisplayMode;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.swing.BorderFactory;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;

import net.imglib2.Cursor;
import net.imglib2.Point;
import net.imglib2.RandomAccessible;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.RealRandomAccessible;
import net.imglib2.algorithm.gauss3.Gauss3;
import net.imglib2.algorithm.stats.Normalize;
import net.imglib2.exception.IncompatibleTypeException;
import net.imglib2.img.Img;
import net.imglib2.img.display.imagej.ImageJFunctions;
import net.imglib2.interpolation.randomaccess.NLinearInterpolatorFactory;
import net.imglib2.realtransform.AffineTransform2D;
import net.imglib2.realtransform.RealViews;
import net.imglib2.type.numeric.ARGBType;
import net.imglib2.type.numeric.real.DoubleType;
import net.imglib2.view.IntervalView;
import net.imglib2.view.Views;

import org.apache.commons.math3.stat.regression.SimpleRegression;

import com.jug.gui.JFrameSnapper;
import com.jug.gui.MotherMachineGui;
import com.jug.gui.MotherMachineModel;
import com.jug.loops.Loops;
import com.jug.lp.GrowthLineTrackingILP;
import com.jug.ops.cursor.FindLocalMaxima;
import com.jug.ops.cursor.FindLocationAboveThreshold;
import com.jug.ops.numerictype.VarOfRai;
import com.jug.util.DataMover;
import com.jug.util.DoubleTypeImgLoader;

/**
 * @author jug
 */
public class MotherMachine {

    // -------------------------------------------------------------------------------------
    // statics
    // -------------------------------------------------------------------------------------
    /**
     * Parameter: sigma for gaussian blurring in x-direction of the raw image
     * data. Used while searching the growth line centers.
     */
    public static double SIGMA_GL_DETECTION_X = 15.0;
    public static double SIGMA_GL_DETECTION_Y = 3.0;
    /**
     * Parameter: sigma for gaussian blurring in x-direction of the raw image
     * data. Used while searching the gaps between bacteria.
     */
    private static double SIGMA_PRE_SEGMENTATION_X = 0.0; // 3.5;
    private static double SIGMA_PRE_SEGMENTATION_Y = 0.0; // 0.5;
    /**
     * Parameter: later border in pixels - well centers detected too close to
     * the left and right image border will be neglected. Reason: detection not
     * reliable if well is truncated.
     */
    public static int GL_OFFSET_LATERAL = 5;
    /**
     * Prior knowledge: hard offset in detected well center lines - will be cut
     * of from top.
     */
    public static int GL_OFFSET_TOP = 40;
    /**
     * Prior knowledge: hard offset in detected well center lines - will be cut
     * of from bottom.
     */
    public static int GL_OFFSET_BOTTOM = 10;
    /**
     * Maximum offset in x direction (with respect to growth line center) to
     * take the background intensities from that will be subtracted from the
     * growth line.
     */
    private static int BGREM_TEMPLATE_XMAX = 35;
    /**
     * Minimum offset in x direction (with respect to growth line center) to
     * take the background intensities from that will be subtracted from the
     * growth line.
     */
    private static int BGREM_TEMPLATE_XMIN = 20;
    /**
     * Offsets in +- x direction (with respect to growth line center) where the
     * measured background values will be subtracted from.
     */
    private static int BGREM_X_OFFSET = 35;
    /**
     * Prior knowledge: minimal length of detected cells
     */
    public static int MIN_CELL_LENGTH = 25;
    /**
     * Prior knowledge: minimal contrast of an gap (also used for MSERs)
     */
    public static double MIN_GAP_CONTRAST = 0.02; // This is set to a very low value that will basically not filter anything...

    // - - - - - - - - - - - - - -
    // GUI-WINDOW RELATED STATICS
    // - - - - - - - - - - - - - -
    /**
     * The <code>JFrame</code> containing the main GUI.
     */
    private static JFrame guiFrame;
    /**
     * Properties to configure app (loaded and saved to properties file!).
     */
    private static Properties props;
    /**
     * Default x-position of the main GUI-window.
     * This value will be used if the values in the properties file are not
     * fitting on any of the currently attached screens.
     */
    private static int DEFAULT_GUI_POS_X = 100;
    /**
     * X-position of the main GUI-window. This value will be loaded from and
     * stored in the properties file!
     */
    private static int GUI_POS_X;
    /**
     * Default y-position of the main GUI-window.
     * This value will be used if the values in the properties file are not
     * fitting on any of the currently attached screens.
     */
    private static int DEFAULT_GUI_POS_Y = 100;
    /**
     * Y-position of the main GUI-window. This value will be loaded from and
     * stored in the properties file!
     */
    private static int GUI_POS_Y;
    /**
     * Width (in pixels) of the main GUI-window. This value will be loaded from
     * and stored in the properties file!
     */
    private static int GUI_WIDTH = 800;
    /**
     * Width (in pixels) of the main GUI-window. This value will be loaded from
     * and stored in the properties file!
     */
    private static int GUI_HEIGHT = 630;
    /**
     * Width (in pixels) of the console window. This value will be loaded from
     * and stored in the properties file!
     */
    private static int GUI_CONSOLE_WIDTH = 600;
    /**
     * The path to usually open JFileChoosers at (except for initial load
     * dialog).
     */
    public static String DEFAULT_PATH = System.getProperty("user.home");

    // ====================================================================================================================

    /**
     * PROJECT MAIN
     * ============
     *
     * @param args
     *            muh!
     */
    public static void main(final String[] args) {
        try {

            final MotherMachine main = new MotherMachine();
            guiFrame = new JFrame("Interactive MotherMachine");
            main.initMainWindow(guiFrame);

            props = main.loadParams();
            BGREM_TEMPLATE_XMIN = Integer
                    .parseInt(props.getProperty("BGREM_TEMPLATE_XMIN", Integer.toString(BGREM_TEMPLATE_XMIN)));
            BGREM_TEMPLATE_XMAX = Integer
                    .parseInt(props.getProperty("BGREM_TEMPLATE_XMAX", Integer.toString(BGREM_TEMPLATE_XMAX)));
            BGREM_X_OFFSET = Integer
                    .parseInt(props.getProperty("BGREM_X_OFFSET", Integer.toString(BGREM_X_OFFSET)));
            GL_OFFSET_BOTTOM = Integer
                    .parseInt(props.getProperty("GL_OFFSET_BOTTOM", Integer.toString(GL_OFFSET_BOTTOM)));
            GL_OFFSET_TOP = Integer.parseInt(props.getProperty("GL_OFFSET_TOP", Integer.toString(GL_OFFSET_TOP)));
            GL_OFFSET_LATERAL = Integer
                    .parseInt(props.getProperty("GL_OFFSET_LATERAL", Integer.toString(GL_OFFSET_LATERAL)));
            MIN_CELL_LENGTH = Integer
                    .parseInt(props.getProperty("MIN_CELL_LENGTH", Integer.toString(MIN_CELL_LENGTH)));
            MIN_GAP_CONTRAST = Double
                    .parseDouble(props.getProperty("MIN_GAP_CONTRAST", Double.toString(MIN_GAP_CONTRAST)));
            SIGMA_PRE_SEGMENTATION_X = Double.parseDouble(
                    props.getProperty("SIGMA_PRE_SEGMENTATION_X", Double.toString(SIGMA_PRE_SEGMENTATION_X)));
            SIGMA_PRE_SEGMENTATION_Y = Double.parseDouble(
                    props.getProperty("SIGMA_PRE_SEGMENTATION_Y", Double.toString(SIGMA_PRE_SEGMENTATION_Y)));
            SIGMA_GL_DETECTION_X = Double
                    .parseDouble(props.getProperty("SIGMA_GL_DETECTION_X", Double.toString(SIGMA_GL_DETECTION_X)));
            SIGMA_GL_DETECTION_Y = Double
                    .parseDouble(props.getProperty("SIGMA_GL_DETECTION_Y", Double.toString(SIGMA_GL_DETECTION_Y)));
            DEFAULT_PATH = props.getProperty("DEFAULT_PATH", DEFAULT_PATH);

            GUI_POS_X = Integer.parseInt(props.getProperty("GUI_POS_X", Integer.toString(DEFAULT_GUI_POS_X)));
            GUI_POS_Y = Integer.parseInt(props.getProperty("GUI_POS_Y", Integer.toString(DEFAULT_GUI_POS_X)));
            GUI_WIDTH = Integer.parseInt(props.getProperty("GUI_WIDTH", Integer.toString(GUI_WIDTH)));
            GUI_HEIGHT = Integer.parseInt(props.getProperty("GUI_HEIGHT", Integer.toString(GUI_HEIGHT)));
            GUI_CONSOLE_WIDTH = Integer
                    .parseInt(props.getProperty("GUI_CONSOLE_WIDTH", Integer.toString(GUI_CONSOLE_WIDTH)));
            // Iterate over all currently attached monitors and check if sceen position is actually possible,
            // otherwise fall back to the DEFAULT values and ignore the ones coming from the properties-file.
            boolean pos_ok = false;
            final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
            final GraphicsDevice[] gs = ge.getScreenDevices();
            for (int i = 0; i < gs.length; i++) {
                final DisplayMode dm = gs[i].getDisplayMode();
                if (gs[i].getDefaultConfiguration().getBounds()
                        .contains(new java.awt.Point(GUI_POS_X, GUI_POS_Y))) {
                    pos_ok = true;
                }
            }
            // None of the screens contained the top-left window coordinates --> fall back onto default values...
            if (!pos_ok) {
                GUI_POS_X = DEFAULT_GUI_POS_X;
                GUI_POS_Y = DEFAULT_GUI_POS_Y;
            }

            String path = props.getProperty("import_path", System.getProperty("user.home"));
            final File fPath = main.showStartupDialog(guiFrame, path);
            path = fPath.getAbsolutePath();
            props.setProperty("import_path", fPath.getAbsolutePath());

            // Setting up console window and window snapper...
            main.initConsoleWindow();
            main.showConsoleWindow();
            final JFrameSnapper snapper = new JFrameSnapper();
            snapper.addFrame(main.frameConsoleWindow);
            snapper.addFrame(guiFrame);

            // ---------------------------------------------------
            main.processDataFromFolder(path);
            // ---------------------------------------------------

            System.out.print("Build and show GUI...");
            // show loaded and annotated data
            ImageJFunctions.show(main.imgRaw, "Rotated & cropped raw data");
            ImageJFunctions.show(main.imgTemp, "Temporary");
            ImageJFunctions.show(main.imgAnnotated, "Annotated ARGB data");

            final MotherMachineGui gui = new MotherMachineGui(new MotherMachineModel(main));
            gui.setVisible(true);

            main.ij = new ImageJ();
            guiFrame.add(gui);
            guiFrame.setSize(GUI_WIDTH, GUI_HEIGHT);
            guiFrame.setLocation(GUI_POS_X, GUI_POS_Y);
            guiFrame.setVisible(true);

            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    snapper.snapFrames(main.frameConsoleWindow, guiFrame, JFrameSnapper.EAST);
                }
            });

            System.out.println(" done!");
        } catch (final UnsatisfiedLinkError ulr) {
            JOptionPane.showMessageDialog(MotherMachine.guiFrame,
                    "Could initialize Gurobi.\n"
                            + "You might not have installed Gurobi properly or you miss a valid license.\n"
                            + "Please visit 'www.gurobi.com' for further information.\n\n" + ulr.getMessage(),
                    "Gurobi Error?", JOptionPane.ERROR_MESSAGE);
        }
    }

    // -------------------------------------------------------------------------------------
    // fields
    // -------------------------------------------------------------------------------------

    /**
     * The singleton instance of ImageJ.
     */
    public ImageJ ij;

    private double dCorrectedSlope;

    private Img<DoubleType> imgRaw;
    private Img<DoubleType> imgTemp;
    private Img<ARGBType> imgAnnotated;

    /**
     * Contains all detected growth line center points.
     * The structure goes in line with image data:
     * Outermost list: one element per frame (image in stack).
     * 2nd list: one element per detected growth-line.
     * 3rd list: one element (Point) per location downwards along the growth
     * line.
     */
    private List<List<List<Point>>> glCenterPoints;

    /**
     * Contains all GrowthLines found in the given data.
     */
    private List<GrowthLine> growthLines;

    /**
     * All ILP-related structures are within mmILP.
     */
    private GrowthLineTrackingILP mmILP;

    /**
     * Frame hosting the console output.
     */
    private JFrame frameConsoleWindow;
    /**
     * TextArea hosting the console output within the JFrame frameConsoleWindow.
     */
    private JTextArea consoleWindowTextArea;

    // -------------------------------------------------------------------------------------
    // setters and getters
    // -------------------------------------------------------------------------------------
    /**
     * @return the imgRaw
     */
    public Img<DoubleType> getImgRaw() {
        return imgRaw;
    }

    /**
     * @param imgRaw
     *            the imgRaw to set
     */
    public void setImgRaw(final Img<DoubleType> imgRaw) {
        this.imgRaw = imgRaw;
    }

    /**
     * @return the imgTemp
     */
    public Img<DoubleType> getImgTemp() {
        return imgTemp;
    }

    /**
     * @param imgTemp
     *            the imgTemp to set
     */
    public void setImgTemp(final Img<DoubleType> imgTemp) {
        this.imgTemp = imgTemp;
    }

    /**
     * @return the imgRendered
     */
    public Img<ARGBType> getImgAnnotated() {
        return imgAnnotated;
    }

    /**
     * @param imgRendered
     *            the imgRendered to set
     */
    public void setImgRendered(final Img<ARGBType> imgRendered) {
        this.imgAnnotated = imgRendered;
    }

    /**
     * @return the growthLines
     */
    public List<GrowthLine> getGrowthLines() {
        return growthLines;
    }

    /**
     * @param growthLines
     *            the growthLines to set
     */
    public void setGrowthLines(final List<GrowthLine> growthLines) {
        this.growthLines = growthLines;
    }
    // -------------------------------------------------------------------------------------
    // methods
    // -------------------------------------------------------------------------------------

    /**
     * Created and shows the console window and redirects System.out and
     * System.err to it.
     */
    private void initConsoleWindow() {
        frameConsoleWindow = new JFrame("MotherMachine Console Window");
        //      frameConsoleWindow.setResizable( false );
        consoleWindowTextArea = new JTextArea();

        final int centerX = (int) Toolkit.getDefaultToolkit().getScreenSize().getWidth() / 2;
        final int centerY = (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight() / 2;
        frameConsoleWindow.setBounds(centerX - GUI_CONSOLE_WIDTH / 2, centerY - GUI_HEIGHT / 2, GUI_CONSOLE_WIDTH,
                GUI_HEIGHT);
        final JScrollPane scrollPane = new JScrollPane(consoleWindowTextArea);
        scrollPane.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
        frameConsoleWindow.getContentPane().add(scrollPane);

        final OutputStream out = new OutputStream() {

            private final PrintStream original = new PrintStream(System.out);

            @Override
            public void write(final int b) throws IOException {
                updateConsoleTextArea(String.valueOf((char) b));
                original.print(String.valueOf((char) b));
            }

            @Override
            public void write(final byte[] b, final int off, final int len) throws IOException {
                updateConsoleTextArea(new String(b, off, len));
                original.print(new String(b, off, len));
            }

            @Override
            public void write(final byte[] b) throws IOException {
                write(b, 0, b.length);
            }
        };

        final OutputStream err = new OutputStream() {

            private final PrintStream original = new PrintStream(System.out);

            @Override
            public void write(final int b) throws IOException {
                updateConsoleTextArea(String.valueOf((char) b));
                original.print(String.valueOf((char) b));
            }

            @Override
            public void write(final byte[] b, final int off, final int len) throws IOException {
                updateConsoleTextArea(new String(b, off, len));
                original.print(new String(b, off, len));
            }

            @Override
            public void write(final byte[] b) throws IOException {
                write(b, 0, b.length);
            }
        };

        System.setOut(new PrintStream(out, true));
        System.setErr(new PrintStream(err, true));
    }

    private void updateConsoleTextArea(final String text) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                consoleWindowTextArea.append(text);
            }
        });
    }

    /**
     * Shows the ConsoleWindow
     */
    public void showConsoleWindow() {
        frameConsoleWindow.setVisible(true);
    }

    /**
     * Hide the ConsoleWindow
     */
    public void hideConsoleWindow() {
        frameConsoleWindow.setVisible(false);
    }

    /**
     * Initializes the MotherMachine main app. This method contains platform
     * specific code like setting icons, etc.
     *
     * @param guiFrame
     *            the JFrame containing the MotherMachine.
     */
    private void initMainWindow(final JFrame guiFrame) {
        guiFrame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(final WindowEvent we) {
                saveParams();
                System.exit(0);
            }
        });
        //      final java.net.URL url = MotherMachine.class.getResource( "gui/media/IconMotherMachine128.png" );
        //      final Toolkit kit = Toolkit.getDefaultToolkit();
        //      final Image img = kit.createImage( url );
        //      if ( !OSValidator.isMac() ) {
        //         guiFrame.setIconImage( img );
        //      }
        //      if ( OSValidator.isMac() ) {
        //         Application.getApplication().setDockIconImage( img );
        //      }
    }

    /**
     *
     * @param guiFrame
     *            parent frame
     * @param path
     *            path to be suggested to open
     * @return
     */
    private File showStartupDialog(final JFrame guiFrame, final String path) {

        final String parentFolder = path.substring(0, path.lastIndexOf(File.separatorChar));

        int decision = 0;
        if (path.equals(System.getProperty("user.home"))) {
            decision = JOptionPane.NO_OPTION;
        } else {
            final String message = "Should the MotherMachine be opened with the data found in:\n" + path
                    + "\n\nIn case you want to choose a folder please select 'No'...";
            final String title = "MotherMachine Start Dialog";
            decision = JOptionPane.showConfirmDialog(guiFrame, message, title, JOptionPane.YES_NO_OPTION,
                    JOptionPane.QUESTION_MESSAGE);
        }
        if (decision == JOptionPane.YES_OPTION) {
            return new File(path);
        } else {
            return showFolderChooser(guiFrame, parentFolder);
        }
    }

    /**
     * Shows a JFileChooser set up to accept the selection of folders.
     * If 'cancel' is pressed this method terminates the MotherMachine app.
     *
     * @param guiFrame
     *            parent frame
     * @param path
     *            path to the folder to open initially
     * @return an instance of {@link File} pointing at the selected folder.
     */
    private File showFolderChooser(final JFrame guiFrame, final String path) {
        final JFileChooser chooser = new JFileChooser();
        chooser.setCurrentDirectory(new java.io.File(path));
        chooser.setDialogTitle("Select folder containing image sequence...");
        chooser.setFileFilter(new FileFilter() {

            @Override
            public final boolean accept(final File file) {
                return file.isDirectory();
            }

            @Override
            public String getDescription() {
                return "We only take directories";
            }
        });
        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        chooser.setAcceptAllFileFilterUsed(false);

        if (chooser.showOpenDialog(guiFrame) == JFileChooser.APPROVE_OPTION) {
            return chooser.getSelectedFile();
        } else {
            System.exit(0);
            return null;
        }
    }

    /**
     * Loads the file 'mm.properties' and returns an instance of
     * {@link Properties} containing the key-value pairs found in that file.
     *
     * @return instance of {@link Properties} containing the key-value pairs
     *         found in that file.
     */
    @SuppressWarnings("resource")
    private Properties loadParams() {
        InputStream is = null;
        final Properties props = new Properties();

        // First try loading from the current directory
        try {
            final File f = new File("mm.properties");
            is = new FileInputStream(f);
        } catch (final Exception e) {
            is = null;
        }

        try {
            if (is == null) {
                // Try loading from classpath
                is = getClass().getResourceAsStream("mm.properties");
            }

            // Try loading properties from the file (if found)
            props.load(is);
        } catch (final Exception e) {
            System.out.println(
                    "No properties file 'mm.properties' found in current path or classpath... I will create one!");
        }

        return props;
    }

    /**
     * Saves a file 'mm.properties' in the current folder.
     * This file contains all MotherMachine specific properties as key-value
     * pairs.
     *
     * @param props
     *            an instance of {@link Properties} containing all key-value
     *            pairs used by the MotherMachine.
     */
    public void saveParams() {
        try {
            final File f = new File("mm.properties");
            final OutputStream out = new FileOutputStream(f);

            props.setProperty("BGREM_TEMPLATE_XMIN", Integer.toString(BGREM_TEMPLATE_XMIN));
            props.setProperty("BGREM_TEMPLATE_XMAX", Integer.toString(BGREM_TEMPLATE_XMAX));
            props.setProperty("BGREM_X_OFFSET", Integer.toString(BGREM_X_OFFSET));
            props.setProperty("GL_OFFSET_BOTTOM", Integer.toString(GL_OFFSET_BOTTOM));
            props.setProperty("GL_OFFSET_TOP", Integer.toString(GL_OFFSET_TOP));
            props.setProperty("GL_OFFSET_LATERAL", Integer.toString(GL_OFFSET_LATERAL));
            props.setProperty("MIN_CELL_LENGTH", Integer.toString(MIN_CELL_LENGTH));
            props.setProperty("MIN_GAP_CONTRAST", Double.toString(MIN_GAP_CONTRAST));
            props.setProperty("SIGMA_PRE_SEGMENTATION_X", Double.toString(SIGMA_PRE_SEGMENTATION_X));
            props.setProperty("SIGMA_PRE_SEGMENTATION_Y", Double.toString(SIGMA_PRE_SEGMENTATION_Y));
            props.setProperty("SIGMA_GL_DETECTION_X", Double.toString(SIGMA_GL_DETECTION_X));
            props.setProperty("SIGMA_GL_DETECTION_Y", Double.toString(SIGMA_GL_DETECTION_Y));
            props.setProperty("DEFAULT_PATH", DEFAULT_PATH);

            final java.awt.Point loc = guiFrame.getLocation();
            GUI_POS_X = loc.x;
            GUI_POS_Y = loc.y;
            GUI_WIDTH = guiFrame.getWidth();
            GUI_HEIGHT = guiFrame.getHeight();

            props.setProperty("GUI_POS_X", Integer.toString(GUI_POS_X));
            props.setProperty("GUI_POS_Y", Integer.toString(GUI_POS_Y));
            props.setProperty("GUI_WIDTH", Integer.toString(GUI_WIDTH));
            props.setProperty("GUI_HEIGHT", Integer.toString(GUI_HEIGHT));
            props.setProperty("GUI_CONSOLE_WIDTH", Integer.toString(GUI_CONSOLE_WIDTH));

            props.store(out, "MotherMachine properties");
        } catch (final Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Opens all tiffs in the given folder, straightens and crops images,
     * extracts growth lines, subtracts background, builds segmentation
     * hypothesis and a Markov random field for tracking. Finally it even solves
     * this model using Gurobi and reads out the MAP.
     *
     * @param path
     *            the folder to be processed.
     */
    private void processDataFromFolder(final String path) {
        // load tiffs from folder
        System.out.print("Loading tiff sequence...");
        loadTiffSequence(path);
        System.out.println(" done!");

        // straighten loaded images
        System.out.print("Staighten loaded images...");
        straightenRawImg();
        System.out.println(" done!");

        // cropping loaded images
        System.out.print("Cropping to ROI...");
        cropRawImgToROI();
        System.out.println(" done!");

        // setup ARGB image (that will eventually contain annotations)
        System.out.print("Spawning off annotation image (ARGB)...");
        resetImgAnnotatedLike(getImgRaw());
        try {
            DataMover.convertAndCopy(getImgRaw(), getImgAnnotated());
        } catch (final Exception e) {
            // conversion might not be supported
            e.printStackTrace();
        }
        System.out.println(" done!");

        System.out.print("Searching for GrowthLines...");
        resetImgTempToRaw();
        findGrowthLines();
        annotateDetectedWellCenters();
        System.out.println(" done!");

        // subtracting BG in RAW image...
        System.out.print("Subtracting background...");
        subtractBackgroundInRaw();
        // ...and make temp image be the same
        resetImgTempToRaw();
        System.out.println(" done!");

        System.out.print("Generating Segmentation Hypotheses...");
        generateSegmentationHypotheses();
        System.out.println(" done!");

        //      System.out.println( "Generating Integer Linear Programs..." );
        //      generateILPs();
        //      System.out.println( " done!" );
        //
        //      System.out.println( "Running Integer Linear Programs..." );
        //      runILPs();
        //      System.out.println( " done!" );
    }

    /**
     * Loads all files contained in the given folder that end on '.tif' into an
     * image stack and assigns it to imgRaw.
     *
     * @param folder
     *            string containing a sequence of '.tif' files.
     */
    public void loadTiffSequence(final String folder) {
        try {
            imgRaw = DoubleTypeImgLoader.loadStackOfTiffsFromFolder(folder);
        } catch (final Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Resets imgTemp to contain the raw data from imgRaw.
     */
    public void resetImgTempToRaw() {
        setImgTemp(imgRaw.copy());
    }

    /**
     * Resets imgTemp to contain the raw data from imgRaw.
     */
    public void resetImgAnnotatedLike(final Img<DoubleType> img) {
        imgAnnotated = DataMover.createEmptyArrayImgLike(img, new ARGBType());
    }

    /**
     * Rotates the whole stack (each Z-slize) in order to vertically align the
     * wells seen in the micrographs come from the MotherMachine. Note: This is
     * NOT done in-place! The returned <code>Img</code> in newly created!
     *
     * @param img
     *            - the 3d <code>Img</code> to be straightened.
     * @return the straightened <code>Img</code>. (Might be larger to avoid
     *         loosing data!)
     */
    private void straightenRawImg() {
        assert (imgRaw.numDimensions() == 3);

        // new raw image
        Img<DoubleType> rawNew;

        // find out how slanted the given stack is...
        final List<Cursor<DoubleType>> points = new Loops<DoubleType, Cursor<DoubleType>>().forEachHyperslice(
                Views.hyperSlice(imgRaw, 2, 0), 0,
                new FindLocationAboveThreshold<DoubleType>(new DoubleType(0.33)));

        final SimpleRegression regression = new SimpleRegression();
        final long[] pos = new long[2];
        int i = 0;
        final double[] plotData = new double[points.size()];
        for (final Cursor<DoubleType> c : points) {
            c.localize(pos);
            regression.addData(i, -pos[0]);
            plotData[i] = -pos[0];
            // System.out.println("Regression.addData ( " + i + ", " + (-pos[0])
            // + " )");
            i++;
        }
        // Plot2d.simpleLinePlot("Global positioning regression data",
        // plotData);

        this.dCorrectedSlope = regression.getSlope();
        final double radSlant = Math.atan(regression.getSlope());
        // System.out.println("slope = " + regression.getSlope());
        // System.out.println("intercept = " + regression.getIntercept());
        final double[] dCenter2d = new double[] { imgRaw.dimension(0) * 0.5,
                -regression.getIntercept() + points.size() * regression.getSlope() };

        // ...and inversely rotate the whole stack in XY
        final AffineTransform2D affine = new AffineTransform2D();
        affine.translate(-dCenter2d[0], -dCenter2d[1]);
        affine.rotate(radSlant);
        affine.translate(dCenter2d[0], dCenter2d[1]);

        long minX = 0, maxX = imgRaw.dimension(0);
        long minY = 0;
        final long maxY = imgRaw.dimension(1);
        final double[][] corners = { new double[] { minX, minY }, new double[] { maxX, minY },
                new double[] { minX, maxY }, new double[] { maxX, maxY } };
        final double[] tmp = new double[2];
        for (final double[] corner : corners) {
            affine.apply(corner, tmp);
            minX = Math.min(minX, (long) tmp[0]);
            maxX = Math.max(maxX, (long) tmp[0]);
            minY = Math.min(minY, (long) tmp[1]);
            // maxY = Math.max(maxY, (long)tmp[1]); // if this line is active
            // also the bottom would be extenden while rotating (currently not
            // wanted!)
        }

        rawNew = imgRaw.factory().create(new long[] { maxX - minX, maxY - minY, imgRaw.dimension(2) },
                imgRaw.firstElement());

        for (i = 0; i < imgRaw.dimension(2); i++) {
            final RandomAccessibleInterval<DoubleType> viewZSlize = Views.hyperSlice(imgRaw, 2, i);
            final RandomAccessible<DoubleType> raInfZSlize = Views.extendValue(viewZSlize, new DoubleType(0.0));
            final RealRandomAccessible<DoubleType> rraInterpolatedZSlize = Views.interpolate(raInfZSlize,
                    new NLinearInterpolatorFactory<DoubleType>());
            final RandomAccessible<DoubleType> raRotatedZSlize = RealViews.affine(rraInterpolatedZSlize, affine);

            final RandomAccessibleInterval<DoubleType> raiRotatedAndTruncatedZSlize = Views
                    .zeroMin(Views.interval(raRotatedZSlize, new long[] { minX, minY }, new long[] { maxX, maxY }));

            DataMover.copy(raiRotatedAndTruncatedZSlize, Views.iterable(Views.hyperSlice(rawNew, 2, i)));
        }

        // set new, straightened image to be the new imgRaw
        imgRaw = rawNew;
    }

    /**
     * Finds the region of interest in the given 3d image stack and crops it.
     * Note: each cropped z-slize will be renormalized to [0,1]. Precondition:
     * given <code>Img</code> should be rotated such that the seen wells are
     * axis parallel.
     *
     * @param img
     *            - the streightened 3d <code>Img</code> that should be cropped
     *            down to contain only the ROI.
     */
    private void cropRawImgToROI() {
        assert (imgRaw.numDimensions() == 3);

        // return image
        Img<DoubleType> rawNew = null;

        // crop positions to be evaluated
        long top = 0, bottom = imgRaw.dimension(1);
        long left, right;

        // check for possible crop in first and last image
        final long[] lZPositions = new long[] { 0, imgRaw.dimension(2) - 1 };
        for (final long lZPos : lZPositions) {
            // find out how slanted the given stack is...
            final List<DoubleType> points = new Loops<DoubleType, DoubleType>()
                    .forEachHyperslice(Views.hyperSlice(imgRaw, 2, lZPos), 1, new VarOfRai<DoubleType>());

            final double[] y = new double[points.size()];
            int i = 0;
            for (final DoubleType dtPoint : points) {
                y[i] = dtPoint.get();
                i++;
            }

            // Plot2d.simpleLinePlot("Variance in image rows", y, "Variance");
            final double threshold = 0.005;

            // looking for longest interval above threshold
            int curMaxLen = 0;
            int curLen = 0;
            long topCandidate = 0;
            boolean below = true;
            for (int j = 0; j < y.length; j++) {
                if (below && y[j] > threshold) {
                    topCandidate = j;
                    below = false;
                    curLen = 1;
                }
                if (!below && y[j] > threshold) {
                    curLen++;
                }
                if (!below && (y[j] <= threshold || j == y.length - 1)) {
                    below = true;
                    if (curLen > curMaxLen) {
                        curMaxLen = curLen;
                        top = topCandidate;
                        bottom = j;
                    }
                    curLen = 0;
                }
            }
            // System.out.println(">> Top/bottom: " + top + " / " + bottom);
        }
        left = Math.round(Math.floor(0 - this.dCorrectedSlope * bottom));
        right = Math.round(Math.ceil(imgRaw.dimension(0) + this.dCorrectedSlope * (imgRaw.dimension(1) - top)));

        // create image that can host cropped data
        rawNew = imgRaw.factory().create(new long[] { right - left, bottom - top, imgRaw.dimension(2) },
                imgRaw.firstElement());

        // and copy it there
        for (int i = 0; i < imgRaw.dimension(2); i++) {
            final RandomAccessibleInterval<DoubleType> viewZSlize = Views.hyperSlice(imgRaw, 2, i);
            final RandomAccessibleInterval<DoubleType> viewCroppedZSlize = Views
                    .zeroMin(Views.interval(viewZSlize, new long[] { left, top }, new long[] { bottom, right }));

            DataMover.copy(viewCroppedZSlize, Views.iterable(Views.hyperSlice(rawNew, 2, i)));
            // Normalize.normalize(Views.iterable( Views.hyperSlice(ret, 2, i)
            // ), new DoubleType(0.0), new DoubleType(1.0));
        }

        // set new, straightened image to be the new imgRaw
        imgRaw = rawNew;
    }

    /**
     * Simple but effective method to subtract uneven illumination from the
     * growth-line data.
     *
     * @param img
     *            DoubleType image stack.
     */
    private void subtractBackgroundInRaw() {

        for (int i = 0; i < getGrowthLines().size(); i++) {
            for (int f = 0; f < getGrowthLines().get(i).size(); f++) {
                final GrowthLineFrame glf = getGrowthLines().get(i).get(f);

                final int glfX = glf.getAvgXpos();
                if (glfX == -1)
                    continue; // do not do anything with empty GLFs

                final int glfY1 = 0; // gl.getFirstPoint().getIntPosition(1);
                final int glfY2 = (int) imgRaw.dimension(1) - 1; // gl.getLastPoint().getIntPosition(1);

                final IntervalView<DoubleType> frame = Views.hyperSlice(imgRaw, 2, f);

                double rowAvgs[] = new double[glfY2 - glfY1 + 1];
                int colCount = 0;
                // Look to the left if you are not the first GLF
                if (glfX > MotherMachine.BGREM_TEMPLATE_XMAX) {
                    final IntervalView<DoubleType> leftBackgroundWindow = Views.interval(frame,
                            new long[] { glfX - MotherMachine.BGREM_TEMPLATE_XMAX, glfY1 },
                            new long[] { glfX - MotherMachine.BGREM_TEMPLATE_XMIN, glfY2 });
                    rowAvgs = addRowSumsFromInterval(leftBackgroundWindow, rowAvgs);
                    colCount += (MotherMachine.BGREM_TEMPLATE_XMAX - MotherMachine.BGREM_TEMPLATE_XMIN);
                }
                // Look to the right if you are not the last GLF
                if (glfX < imgRaw.dimension(0) - MotherMachine.BGREM_TEMPLATE_XMAX) {
                    final IntervalView<DoubleType> rightBackgroundWindow = Views.interval(frame,
                            new long[] { glfX + MotherMachine.BGREM_TEMPLATE_XMIN, glfY1 },
                            new long[] { glfX + MotherMachine.BGREM_TEMPLATE_XMAX, glfY2 });
                    rowAvgs = addRowSumsFromInterval(rightBackgroundWindow, rowAvgs);
                    colCount += (MotherMachine.BGREM_TEMPLATE_XMAX - MotherMachine.BGREM_TEMPLATE_XMIN);
                }
                // compute averages
                for (int j = 0; j < rowAvgs.length; j++) {
                    rowAvgs[j] /= colCount;
                }

                // Subtract averages you've seen to your left and/or to your
                // right
                final long x1 = Math.max(0, glfX - MotherMachine.BGREM_X_OFFSET);
                final long x2 = Math.min(frame.dimension(0) - 1, glfX + MotherMachine.BGREM_X_OFFSET);
                final IntervalView<DoubleType> growthLineArea = Views.interval(frame, new long[] { x1, glfY1 },
                        new long[] { x2, glfY2 });
                removeValuesFromRows(growthLineArea, rowAvgs);
                // Normalize the zone we removed the background from...
                Normalize.normalize(Views.iterable(growthLineArea), new DoubleType(0.0), new DoubleType(1.0));
            }
        }
    }

    /**
     * Adds all intensity values of row i in view to rowSums[i].
     *
     * @param view
     * @param rowSums
     */
    private double[] addRowSumsFromInterval(final IntervalView<DoubleType> view, final double[] rowSums) {
        for (int i = 0; i < view.dimension(1); i++) {
            final IntervalView<DoubleType> row = Views.hyperSlice(view, 1, i);
            final Cursor<DoubleType> cursor = Views.iterable(row).cursor();
            while (cursor.hasNext()) {
                rowSums[i] += cursor.next().get();
            }
        }
        return rowSums;
    }

    /**
     * Removes the value values[i] from all columns in row i of the given view.
     *
     * @param view
     * @param values
     */
    private void removeValuesFromRows(final IntervalView<DoubleType> view, final double[] values) {
        for (int i = 0; i < view.dimension(1); i++) {
            final Cursor<DoubleType> cursor = Views.iterable(Views.hyperSlice(view, 1, i)).cursor();
            while (cursor.hasNext()) {
                cursor.next().set(new DoubleType(Math.max(0, cursor.get().get() - values[i])));
            }
        }
    }

    /**
     * Estimates the centers of the growth lines given in 'imgTemp'.
     * The found center lines are computed by a linear regression of growth line
     * center estimates.
     * Those estimates are obtained by convolving the image with a Gaussian
     * (parameterized by SIGMA_GL_DETECTION_*) and looking for local maxima in
     * that image.
     *
     * This function operates on 'imgTemp' and sets 'glCenterPoints' as
     * well as 'growthLines'.
     */
    private void findGrowthLines() {

        this.setGrowthLines(new ArrayList<GrowthLine>());
        this.glCenterPoints = new ArrayList<List<List<Point>>>();

        List<List<Point>> frameWellCenters;

        // ------ GAUSS -----------------------------

        final int n = imgTemp.numDimensions();
        final double[] sigmas = new double[n];
        sigmas[0] = SIGMA_GL_DETECTION_X;
        sigmas[1] = SIGMA_GL_DETECTION_Y;
        try {
            Gauss3.gauss(sigmas, Views.extendMirrorDouble(imgTemp), imgTemp);
        } catch (final IncompatibleTypeException e) {
            e.printStackTrace();
        }

        // ------ FIND AND FILTER MAXIMA -------------

        final List<List<GrowthLineFrame>> collectionOfFrames = new ArrayList<List<GrowthLineFrame>>();

        for (long frameIdx = 0; frameIdx < imgTemp.dimension(2); frameIdx++) {
            final IntervalView<DoubleType> ivFrame = Views.hyperSlice(imgTemp, 2, frameIdx);

            // Find maxima per image row (per frame)
            frameWellCenters = new Loops<DoubleType, List<Point>>().forEachHyperslice(ivFrame, 1,
                    new FindLocalMaxima<DoubleType>());

            // Delete detected points that are too lateral
            for (int y = 0; y < frameWellCenters.size(); y++) {
                final List<Point> lstPoints = frameWellCenters.get(y);
                for (int x = lstPoints.size() - 1; x >= 0; x--) {
                    if (lstPoints.get(x).getIntPosition(0) < GL_OFFSET_LATERAL
                            || lstPoints.get(x).getIntPosition(0) > imgTemp.dimension(0) - GL_OFFSET_LATERAL) {
                        lstPoints.remove(x);
                    }
                }
                frameWellCenters.set(y, lstPoints);
            }

            // Delete detected points that are too high or too low
            // (and use this sweep to compute 'maxWellCenterIdx' and 'maxWellCenters')
            int maxWellCenters = 0;
            int maxWellCentersIdx = 0;
            for (int y = 0; y < frameWellCenters.size(); y++) {
                if (y < GL_OFFSET_TOP || y > frameWellCenters.size() - 1 - GL_OFFSET_BOTTOM) {
                    frameWellCenters.get(y).clear();
                } else {
                    if (maxWellCenters < frameWellCenters.get(y).size()) {
                        maxWellCenters = frameWellCenters.get(y).size();
                        maxWellCentersIdx = y;
                    }
                }
            }

            // add all the remaining points to 'glCenterPoints'
            this.glCenterPoints.add(frameWellCenters);

            // ------ DISTRIBUTE POINTS TO CORRESPONDING GROWTH LINES -------

            final List<GrowthLineFrame> glFrames = new ArrayList<GrowthLineFrame>();

            final Point pOrig = new Point(3);
            pOrig.setPosition(frameIdx, 2); // location in original Img (will
            // be recovered step by step)

            // start at the row containing the maximum number of well centers
            // (see above for the code that found maxWellCenter*)
            pOrig.setPosition(maxWellCentersIdx, 1);
            for (int x = 0; x < maxWellCenters; x++) {
                glFrames.add(new GrowthLineFrame()); // add one GLF for each found column
                final Point p = frameWellCenters.get(maxWellCentersIdx).get(x);
                pOrig.setPosition(p.getLongPosition(0), 0);
                glFrames.get(x).addPoint(new Point(pOrig));
            }
            // now go backwards from 'maxWellCenterIdx' and find the right assignment in case
            // a different number of wells was found (going forwards comes below!)
            for (int y = maxWellCentersIdx - 1; y >= 0; y--) {
                pOrig.setPosition(y, 1); // location in orig. Img (2nd of 3 steps)

                final List<Point> maximaPerImgRow = frameWellCenters.get(y);
                if (maximaPerImgRow.size() == 0) {
                    break;
                }
                // find best matching well for first point
                final int posX = frameWellCenters.get(y).get(0).getIntPosition(0);
                int mindist = (int) imgTemp.dimension(0);
                int offset = 0;
                for (int x = 0; x < maxWellCenters; x++) {
                    final int wellPosX = glFrames.get(x).getFirstPoint().getIntPosition(0);
                    if (mindist > Math.abs(wellPosX - posX)) {
                        mindist = Math.abs(wellPosX - posX);
                        offset = x;
                    }
                }
                // move points into detected wells
                for (int x = offset; x < maximaPerImgRow.size(); x++) {
                    final Point p = maximaPerImgRow.get(x);
                    pOrig.setPosition(p.getLongPosition(0), 0);
                    glFrames.get(x).addPoint(new Point(pOrig));
                }
            }
            // now go forward from 'maxWellCenterIdx' and find the right assignment in case
            // a different number of wells was found
            for (int y = maxWellCentersIdx + 1; y < frameWellCenters.size(); y++) {
                pOrig.setPosition(y, 1); // location in original Img (2nd of 3
                // steps)

                final List<Point> maximaPerImgRow = frameWellCenters.get(y);
                if (maximaPerImgRow.size() == 0) {
                    break;
                }
                // find best matching well for first point
                final int posX = frameWellCenters.get(y).get(0).getIntPosition(0);
                int mindist = (int) imgTemp.dimension(0);
                int offset = 0;
                for (int x = 0; x < maxWellCenters; x++) {
                    final int wellPosX = glFrames.get(x).getLastPoint().getIntPosition(0);
                    if (mindist > Math.abs(wellPosX - posX)) {
                        mindist = Math.abs(wellPosX - posX);
                        offset = x;
                    }
                }
                // move points into GLFs
                for (int x = offset; x < maximaPerImgRow.size(); x++) {
                    final Point p = maximaPerImgRow.get(x);
                    pOrig.setPosition(p.getLongPosition(0), 0);
                    glFrames.get(x).addPoint(new Point(pOrig));
                }
            }
            // add this list of GrowhtLIneFrames to the collection
            collectionOfFrames.add(glFrames);
        }

        // ------ SORT GrowthLineFrames FROM collectionOfFrames INTO this.growthLines -------------
        int maxGLsPerFrame = 0;
        int maxGLsPerFrameIdx = 0;
        for (int i = 0; i < collectionOfFrames.size(); i++) {
            if (maxGLsPerFrame < collectionOfFrames.get(i).size()) {
                maxGLsPerFrame = collectionOfFrames.get(i).size();
                maxGLsPerFrameIdx = i;
            }
        }
        // copy the max-GLs frame into this.growthLines
        this.setGrowthLines(new ArrayList<GrowthLine>(maxGLsPerFrame));
        for (int i = 0; i < maxGLsPerFrame; i++) {
            getGrowthLines().add(new GrowthLine());
            getGrowthLines().get(i).add(collectionOfFrames.get(maxGLsPerFrameIdx).get(i));
        }
        // go backwards from there and prepand into GL
        for (int j = maxGLsPerFrameIdx - 1; j >= 0; j--) {
            final int deltaL = maxGLsPerFrame - collectionOfFrames.get(j).size();
            int offset = 0; // here we would like to have the shift to consider when copying GLFrames into GLs
            double minDist = Double.MAX_VALUE;
            for (int i = 0; i <= deltaL; i++) {
                double dist = collectionOfFrames.get(maxGLsPerFrameIdx).get(i).getAvgXpos();
                dist -= collectionOfFrames.get(j).get(0).getAvgXpos();
                if (dist < minDist) {
                    minDist = dist;
                    offset = i;
                }
            }
            for (int i = 0; i < collectionOfFrames.get(j).size(); i++) {
                getGrowthLines().get(offset + i).prepand(collectionOfFrames.get(j).get(i));
            }
        }
        // go forwards and append into GL
        for (int j = maxGLsPerFrameIdx + 1; j < collectionOfFrames.size(); j++) {
            final int deltaL = maxGLsPerFrame - collectionOfFrames.get(j).size();
            int offset = 0; // here we would like to have the shift to consider when copying GLFrames into GLs
            double minDist = Double.MAX_VALUE;
            for (int i = 0; i <= deltaL; i++) {
                double dist = collectionOfFrames.get(maxGLsPerFrameIdx).get(i).getAvgXpos();
                dist -= collectionOfFrames.get(j).get(0).getAvgXpos();
                if (dist < minDist) {
                    minDist = dist;
                    offset = i;
                }
            }
            for (int i = 0; i < collectionOfFrames.get(j).size(); i++) {
                getGrowthLines().get(offset + i).add(collectionOfFrames.get(j).get(i));
            }
        }

    }

    /**
     * Draws the detected well centers, <code>detectedWellCenters</code>, into
     * the annotation layer, <code>imgAnnotated</code>.
     */
    private void annotateDetectedWellCenters() {
        for (final GrowthLine gl : this.getGrowthLines()) {
            for (final GrowthLineFrame glf : gl.getFrames()) {
                glf.drawCenterLine(imgAnnotated);
            }
        }
    }

    /**
     * Iterates over all found GrowthLines and evokes
     * GrowthLine.findGapHypotheses(Img).
     * Note that this function always uses the image data in 'imgTemp'.
     */
    public void generateSegmentationHypotheses() {

        // ------ GAUSS -----------------------------

        if (SIGMA_PRE_SEGMENTATION_X + SIGMA_PRE_SEGMENTATION_Y > 0.000001) {
            System.out.print(" ...Note: smoothing performed before building GapHypotheses... ");
            final int n = imgTemp.numDimensions();
            final double[] sigmas = new double[n];
            sigmas[0] = SIGMA_PRE_SEGMENTATION_X;
            sigmas[1] = SIGMA_PRE_SEGMENTATION_Y;
            try {
                Gauss3.gauss(sigmas, Views.extendMirrorDouble(imgTemp), imgTemp);
            } catch (final IncompatibleTypeException e) {
                e.printStackTrace();
            }
        }

        // ------ DETECTION --------------------------

        System.out.println("");
        int i = 0;
        for (final GrowthLine gl : getGrowthLines()) {
            i++;
            System.out.print("   Working on GL#" + i + " of " + getGrowthLines().size() + "... ");
            for (final GrowthLineFrame glf : gl.getFrames()) {
                System.out.print(".");
                glf.generateSegmentationHypotheses(imgTemp);
            }
            System.out.println(" ...done!");
        }
    }

    /**
     * Creates and triggers filling of mmILP, containing all
     * optimization-related structures used to compute the optimal tracking.
     */
    private void generateILPs() {
        //      for ( final GrowthLine gl : getGrowthLines() ) {
        //         gl.generateILP();
        //      }
        //      getGrowthLines().get( 0 ).generateILP();
    }

    /**
     * Runs all the generated ILPs.
     */
    private void runILPs() {
        //      int i = 0;
        //      for ( final GrowthLine gl : getGrowthLines() ) {
        //         System.out.println( " > > > > > Starting LP for GL# " + i + " < < < < < " );
        //         gl.runILP();
        //         i++;
        //      }
        //      getGrowthLines().get( 0 ).runILP();
    }

}