Java tutorial
package com.jug; import java.awt.FileDialog; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Image; 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.FileNotFoundException; 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.ImageIcon; 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 org.apache.commons.cli.BasicParser; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.SystemUtils; import com.apple.eawt.Application; import com.jug.gui.MoMAGui; import com.jug.gui.MoMAModel; import com.jug.gui.progress.DialogProgress; import com.jug.loops.Loops; import com.jug.ops.cursor.FindLocalMaxima; import com.jug.ops.numerictype.SumOfRai; import com.jug.segmentation.GrowthLineSegmentationMagic; import com.jug.segmentation.SilentWekaSegmenter; import com.jug.util.DataMover; import com.jug.util.FloatTypeImgLoader; import com.jug.util.OSValidator; import com.jug.util.converter.RealFloatProbMapToSegmentation; /** * Main class for the MotherMachine project. */ import gurobi.GRBEnv; import gurobi.GRBException; import ij.ImageJ; import ij.Prefs; import net.imglib2.Cursor; import net.imglib2.Point; import net.imglib2.RandomAccessibleInterval; 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.array.ArrayImgFactory; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.type.numeric.ARGBType; import net.imglib2.type.numeric.integer.ShortType; import net.imglib2.type.numeric.real.FloatType; import net.imglib2.view.IntervalView; import net.imglib2.view.Views; /** * @author jug */ public class MoMA { /** * Identifier of current version */ public static final String VERSION_STRING = "MoMA_0.9.6"; // ------------------------------------------------------------------------------------- // statics // ------------------------------------------------------------------------------------- public static MoMA instance; public static boolean HEADLESS = false; /** * Parameter: sigma for gaussian blurring in x-direction of the raw image * data. Used while searching the growth line centers. */ public static float SIGMA_GL_DETECTION_X = 20f; public static float SIGMA_GL_DETECTION_Y = 0f; /** * Parameter: sigma for gaussian blurring in x-direction of the raw image * data. Used while searching the gaps between bacteria. */ private static float SIGMA_PRE_SEGMENTATION_X = 0f; private static float SIGMA_PRE_SEGMENTATION_Y = 0f; /** * Parameter: how many pixels wide is the image containing the selected * GrowthLine? */ public static int GL_WIDTH_IN_PIXELS = 20; public static int GL_FLUORESCENCE_COLLECTION_WIDTH_IN_PIXELS = 100; public static int GL_PIXEL_PADDING_IN_VIEWS = 15; public static int MOTHER_CELL_BOTTOM_TRICK_MAX_PIXELS = 10; /** * 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 = 20; /** * Prior knowledge: hard offset in detected well center lines - will be cut * of from top. */ public static int GL_OFFSET_TOP = 35; /** * Prior knowledge: hard offset in detected well center lines - will be cut * of from bottom. If set to -1, an automatic bottom offset detection will * be launched when data is read from disk. */ public static int GL_OFFSET_BOTTOM = -1; public static boolean GL_OFFSET_BOTTOM_AUTODETECT = true; /** * 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 = 18; /** * Prior knowledge: minimal contrast of a gap (also used for MSERs) */ public static float MIN_GAP_CONTRAST = 0.02f; // This is set to a very low // value that will basically // not filter anything... /** * When using the learned classification boosted paramaxflow segmentation, * how much of the midline data obtained by the 'simple' linescan + * component tree segmentation should mix in? Rational: if the * classification is flat, the original (simple) mehod might still offer * some modulation! */ public static float SEGMENTATION_MIX_CT_INTO_PMFRF = 0.25f; /** * String pointing at the weka-segmenter model file that should be used for * classification during segmentation. */ public static String SEGMENTATION_CLASSIFIER_MODEL_FILE = "CellGapClassifier.model"; /** * String pointing at the weka-segmenter model file that should be used for * classification during cell-stats export for cell-size estimation. */ public static String CELLSIZE_CLASSIFIER_MODEL_FILE = "CellSizeClassifier.model"; /** * Global switch that turns the use of the weka classifier for paramaxflow * on or off. * Default: ON (true) */ public static boolean USE_CLASSIFIER_FOR_PMF = true; /** * One of the test for paper: * What happens if exit constraints are NOT part of the model? */ public static final boolean DISABLE_EXIT_CONSTRAINTS = false; public static final int MAX_CELL_DROP = 50; // - - - - - - - - - - - - - - // Info about loaded data // - - - - - - - - - - - - - - private static int minTime = -1; private static int maxTime = -1; private static int initOptRange = -1; private static int minChannelIdx = 1; private static int numChannels = 1; // - - - - - - - - - - - - - - // 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!). */ public 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 = 620; /** * 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 = 740; /** * 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"); /** * The path to save ground truth and time statistics to (yes, we write * papers!). */ public static String STATS_OUTPUT_PATH = DEFAULT_PATH; /** * The maximum time in seconds GUROBI is allowed to search for a good * tracking solution. (After that period of time GUROBI will stop and best * solution found so far will be used.) */ public static double GUROBI_TIME_LIMIT = 15.0; public static double GUROBI_MAX_OPTIMALITY_GAP = 0.99; /** * Control if ImageJ and loaded data will be shown... */ private static boolean showIJ = false; private static MoMAGui gui; /** * A properties file that will be used to 'overwrite' default properties in * mm.properties. * This file can be set using the CLI. */ private static File fileUserProps; /** * Stores a string used to decorate filenames e.g. before export. */ private static String defaultFilenameDecoration; // ==================================================================================================================== /** * PROJECT MAIN * * @param args */ public static void main(final String[] args) { if (showIJ) new ImageJ(); // // ===== set look and feel ======================================================================== // try { // // Set cross-platform Java L&F (also called "Metal") // UIManager.setLookAndFeel( // UIManager.getCrossPlatformLookAndFeelClassName() ); // } catch ( final UnsupportedLookAndFeelException e ) { // // handle exception // } catch ( final ClassNotFoundException e ) { // // handle exception // } catch ( final InstantiationException e ) { // // handle exception // } catch ( final IllegalAccessException e ) { // // handle exception // } // ===== command line parsing ====================================================================== // create Options object & the parser final Options options = new Options(); final CommandLineParser parser = new BasicParser(); // defining command line options final Option help = new Option("help", "print this message"); final Option headless = new Option("h", "headless", false, "start without user interface (note: input-folder must be given!)"); headless.setRequired(false); final Option timeFirst = new Option("tmin", "min_time", true, "first time-point to be processed"); timeFirst.setRequired(false); final Option timeLast = new Option("tmax", "max_time", true, "last time-point to be processed"); timeLast.setRequired(false); final Option optRange = new Option("orange", "opt_range", true, "initial optimization range"); optRange.setRequired(false); final Option numChannelsOption = new Option("c", "channels", true, "number of channels to be loaded and analyzed."); numChannelsOption.setRequired(true); final Option minChannelIdxOption = new Option("cmin", "min_channel", true, "the smallest channel index (usually 0 or 1, default is 1)."); minChannelIdxOption.setRequired(false); final Option infolder = new Option("i", "infolder", true, "folder to read data from"); infolder.setRequired(false); final Option outfolder = new Option("o", "outfolder", true, "folder to write preprocessed data to (equals infolder if not given)"); outfolder.setRequired(false); final Option userProps = new Option("p", "props", true, "properties file to be loaded (mm.properties)"); userProps.setRequired(false); options.addOption(help); options.addOption(headless); options.addOption(numChannelsOption); options.addOption(minChannelIdxOption); options.addOption(timeFirst); options.addOption(timeLast); options.addOption(optRange); options.addOption(infolder); options.addOption(outfolder); options.addOption(userProps); // get the commands parsed CommandLine cmd = null; try { cmd = parser.parse(options, args); } catch (final ParseException e1) { final HelpFormatter formatter = new HelpFormatter(); formatter.printHelp( "... [-p props-file] -i in-folder [-o out-folder] -c <num-channels> [-cmin start-channel-ids] [-tmin idx] [-tmax idx] [-orange num-frames] [-headless]", "", options, "Error: " + e1.getMessage()); System.exit(0); } if (cmd.hasOption("help")) { final HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("... -i <in-folder> -o [out-folder] [-headless]", options); System.exit(0); } if (cmd.hasOption("h")) { System.out.println(">>> Starting MM in headless mode."); HEADLESS = true; if (!cmd.hasOption("i")) { final HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("Headless-mode requires option '-i <in-folder>'...", options); System.exit(0); } } File inputFolder = null; if (cmd.hasOption("i")) { inputFolder = new File(cmd.getOptionValue("i")); if (!inputFolder.isDirectory()) { System.out.println("Error: Input folder is not a directory!"); System.exit(2); } if (!inputFolder.canRead()) { System.out.println("Error: Input folder cannot be read!"); System.exit(2); } } File outputFolder = null; if (!cmd.hasOption("o")) { if (inputFolder == null) { System.out.println( "Error: Output folder would be set to a 'null' input folder! Please check your command line arguments..."); System.exit(3); } outputFolder = inputFolder; STATS_OUTPUT_PATH = outputFolder.getAbsolutePath(); } else { outputFolder = new File(cmd.getOptionValue("o")); if (!outputFolder.isDirectory()) { System.out.println("Error: Output folder is not a directory!"); System.exit(3); } if (!inputFolder.canWrite()) { System.out.println("Error: Output folder cannot be written to!"); System.exit(3); } STATS_OUTPUT_PATH = outputFolder.getAbsolutePath(); } fileUserProps = null; if (cmd.hasOption("p")) { fileUserProps = new File(cmd.getOptionValue("p")); } if (cmd.hasOption("cmin")) { minChannelIdx = Integer.parseInt(cmd.getOptionValue("cmin")); } if (cmd.hasOption("c")) { numChannels = Integer.parseInt(cmd.getOptionValue("c")); } if (cmd.hasOption("tmin")) { minTime = Integer.parseInt(cmd.getOptionValue("tmin")); } if (cmd.hasOption("tmax")) { maxTime = Integer.parseInt(cmd.getOptionValue("tmax")); } if (cmd.hasOption("orange")) { initOptRange = Integer.parseInt(cmd.getOptionValue("orange")); } // ******** CHECK GUROBI ********* CHECK GUROBI ********* CHECK GUROBI ********* final String jlp = System.getProperty("java.library.path"); // System.out.println( jlp ); try { new GRBEnv("MoMA_gurobi.log"); } catch (final GRBException e) { final String msgs = "Initial Gurobi test threw exception... check your Gruobi setup!\n\nJava library path: " + jlp; if (HEADLESS) { System.out.println(msgs); } else { JOptionPane.showMessageDialog(MoMA.guiFrame, msgs, "Gurobi Error?", JOptionPane.ERROR_MESSAGE); } e.printStackTrace(); System.exit(98); } catch (final UnsatisfiedLinkError ulr) { final String msgs = "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() + "\nJava library path: " + jlp; if (HEADLESS) { System.out.println(msgs); } else { JOptionPane.showMessageDialog(MoMA.guiFrame, msgs, "Gurobi Error?", JOptionPane.ERROR_MESSAGE); ulr.printStackTrace(); } System.out.println("\n>>>>> Java library path: " + jlp + "\n"); System.exit(99); } // ******* END CHECK GUROBI **** END CHECK GUROBI **** END CHECK GUROBI ******** final MoMA main = new MoMA(); if (!HEADLESS) { guiFrame = new JFrame(); main.initMainWindow(guiFrame); } System.out.println("VERSION: " + VERSION_STRING); 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_WIDTH_IN_PIXELS = Integer .parseInt(props.getProperty("GL_WIDTH_IN_PIXELS", Integer.toString(GL_WIDTH_IN_PIXELS))); MOTHER_CELL_BOTTOM_TRICK_MAX_PIXELS = Integer.parseInt(props.getProperty( "MOTHER_CELL_BOTTOM_TRICK_MAX_PIXELS", Integer.toString(MOTHER_CELL_BOTTOM_TRICK_MAX_PIXELS))); GL_FLUORESCENCE_COLLECTION_WIDTH_IN_PIXELS = Integer .parseInt(props.getProperty("GL_FLUORESCENCE_COLLECTION_WIDTH_IN_PIXELS", Integer.toString(GL_FLUORESCENCE_COLLECTION_WIDTH_IN_PIXELS))); GL_OFFSET_BOTTOM = Integer .parseInt(props.getProperty("GL_OFFSET_BOTTOM", Integer.toString(GL_OFFSET_BOTTOM))); if (GL_OFFSET_BOTTOM == -1) { GL_OFFSET_BOTTOM_AUTODETECT = true; } else { GL_OFFSET_BOTTOM_AUTODETECT = false; } 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 = Float .parseFloat(props.getProperty("MIN_GAP_CONTRAST", Float.toString(MIN_GAP_CONTRAST))); SIGMA_PRE_SEGMENTATION_X = Float.parseFloat( props.getProperty("SIGMA_PRE_SEGMENTATION_X", Float.toString(SIGMA_PRE_SEGMENTATION_X))); SIGMA_PRE_SEGMENTATION_Y = Float.parseFloat( props.getProperty("SIGMA_PRE_SEGMENTATION_Y", Float.toString(SIGMA_PRE_SEGMENTATION_Y))); SIGMA_GL_DETECTION_X = Float .parseFloat(props.getProperty("SIGMA_GL_DETECTION_X", Float.toString(SIGMA_GL_DETECTION_X))); SIGMA_GL_DETECTION_Y = Float .parseFloat(props.getProperty("SIGMA_GL_DETECTION_Y", Float.toString(SIGMA_GL_DETECTION_Y))); SEGMENTATION_MIX_CT_INTO_PMFRF = Float.parseFloat(props.getProperty("SEGMENTATION_MIX_CT_INTO_PMFRF", Float.toString(SEGMENTATION_MIX_CT_INTO_PMFRF))); SEGMENTATION_CLASSIFIER_MODEL_FILE = props.getProperty("SEGMENTATION_CLASSIFIER_MODEL_FILE", SEGMENTATION_CLASSIFIER_MODEL_FILE); CELLSIZE_CLASSIFIER_MODEL_FILE = props.getProperty("CELLSIZE_CLASSIFIER_MODEL_FILE", CELLSIZE_CLASSIFIER_MODEL_FILE); DEFAULT_PATH = props.getProperty("DEFAULT_PATH", DEFAULT_PATH); GUROBI_TIME_LIMIT = Double .parseDouble(props.getProperty("GUROBI_TIME_LIMIT", Double.toString(GUROBI_TIME_LIMIT))); GUROBI_MAX_OPTIMALITY_GAP = Double.parseDouble( props.getProperty("GUROBI_MAX_OPTIMALITY_GAP", Double.toString(GUROBI_MAX_OPTIMALITY_GAP))); 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))); if (!HEADLESS) { // 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++) { 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")); if (inputFolder == null || inputFolder.equals("")) { inputFolder = main.showStartupDialog(guiFrame, path); } System.out.println("Default filename decoration = " + inputFolder.getName()); defaultFilenameDecoration = inputFolder.getName(); path = inputFolder.getAbsolutePath(); props.setProperty("import_path", path); GrowthLineSegmentationMagic.setClassifier(SEGMENTATION_CLASSIFIER_MODEL_FILE, ""); if (!HEADLESS) { // Setting up console window... main.initConsoleWindow(); main.showConsoleWindow(true); } // ------------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------------ final MoMAModel mmm = new MoMAModel(main); instance = main; try { main.processDataFromFolder(path, minTime, maxTime, minChannelIdx, numChannels); } catch (final Exception e) { e.printStackTrace(); System.exit(11); } // ------------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------------ // show loaded and annotated data if (showIJ) { new ImageJ(); ImageJFunctions.show(main.imgRaw, "Rotated & cropped raw data"); // ImageJFunctions.show( main.imgTemp, "Temporary" ); // ImageJFunctions.show( main.imgAnnotated, "Annotated ARGB data" ); // main.getCellSegmentedChannelImgs() // ImageJFunctions.show( main.imgClassified, "Classification" ); // ImageJFunctions.show( main.getCellSegmentedChannelImgs(), "Segmentation" ); } gui = new MoMAGui(mmm); if (!HEADLESS) { System.out.print("Build GUI..."); main.showConsoleWindow(false); // final JFrameSnapper snapper = new JFrameSnapper(); // snapper.addFrame( main.frameConsoleWindow ); // snapper.addFrame( guiFrame ); gui.setVisible(true); 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!"); } else { // final String name = inputFolder.getName(); gui.exportHtmlOverview(); gui.exportDataFiles(); instance.saveParams(); System.exit(0); } } // ------------------------------------------------------------------------------------- // fields // ------------------------------------------------------------------------------------- /** * The singleton instance of ImageJ. */ public ImageJ ij; private List<Img<FloatType>> rawChannelImgs; private Img<FloatType> imgRaw; private Img<FloatType> imgTemp; private Img<ARGBType> imgAnnotated; private Img<FloatType> imgClassified; private Img<ShortType> imgSegmented; /** * 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; /** * Frame hosting the console output. */ private JFrame frameConsoleWindow; /** * TextArea hosting the console output within the JFrame frameConsoleWindow. */ private JTextArea consoleWindowTextArea; /** * String denoting the name of the loaded dataset (e.g. used in GUI) */ private String datasetName; // ------------------------------------------------------------------------------------- // setters and getters // ------------------------------------------------------------------------------------- /** * @return the imgRaw */ public Img<FloatType> getImgRaw() { return imgRaw; } /** * @return the rawChannelImgs */ public List<Img<FloatType>> getRawChannelImgs() { return rawChannelImgs; } /** * @param imgRaw * the imgRaw to set */ public void setImgRaw(final Img<FloatType> imgRaw) { this.imgRaw = imgRaw; } /** * @return the imgTemp */ public Img<FloatType> getImgTemp() { return imgTemp; } /** * @param imgTemp * the imgTemp to set */ public void setImgTemp(final Img<FloatType> 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 imgSegmented */ public RandomAccessibleInterval<FloatType> getCellClassificationImgs() { if (this.imgSegmented == null) { getCellSegmentedChannelImgs(); } return this.imgClassified; } /** * @return imgSegmented */ public RandomAccessibleInterval<ShortType> getCellSegmentedChannelImgs() { if (this.imgSegmented == null) { final DialogProgress dialogProgress = new DialogProgress(MoMA.getGui(), "Estimating cell-area using RF classifier...", MoMA.getGui().model.getCurrentGL().size()); if (!HEADLESS) { dialogProgress.setVisible(true); } final SilentWekaSegmenter<FloatType> oldClassifier = GrowthLineSegmentationMagic.getClassifier(); GrowthLineSegmentationMagic.setClassifier(MoMA.CELLSIZE_CLASSIFIER_MODEL_FILE, ""); imgClassified = new ArrayImgFactory<FloatType>().create(imgTemp, new FloatType()); imgSegmented = new ArrayImgFactory<ShortType>().create(imgTemp, new ShortType()); final RealFloatProbMapToSegmentation<FloatType> converter = new RealFloatProbMapToSegmentation<FloatType>( 0.5f); final int numProcessors = Prefs.getThreads(); final int numThreads = Math.min((int) getImgTemp().dimension(2), numProcessors); final Thread[] threads = new Thread[numThreads]; class ImageProcessingThread extends Thread { final int numThread; final int numThreads; public ImageProcessingThread(final int numThread, final int numThreads) { this.numThread = numThread; this.numThreads = numThreads; } @Override public void run() { RandomAccessibleInterval<FloatType> classified; for (int frameIdx = numThread; frameIdx < getImgTemp().dimension(2); frameIdx += numThreads) { // final IntervalView< FloatType > channel0Frame = Views.hyperSlice( getImgTemp(), 2, frameIdx ); // normalized and modified final IntervalView<FloatType> channel0Frame = Views.hyperSlice(getImgRaw(), 2, frameIdx); // RAWest data at hand ;) classified = Views .hyperSlice(GrowthLineSegmentationMagic.returnClassification(channel0Frame), 2, 0); final RandomAccessibleInterval<FloatType> newClassificationSlize = Views .hyperSlice(imgClassified, 2, frameIdx); final RandomAccessibleInterval<ShortType> newSegmentationSlize = Views .hyperSlice(imgSegmented, 2, frameIdx); DataMover.copy(classified, Views.iterable(newClassificationSlize)); DataMover.copy(classified, Views.iterable(newSegmentationSlize), converter); if (!HEADLESS) { dialogProgress.hasProgressed(); } } } } // start threads for (int i = 0; i < numThreads; i++) { threads[i] = new ImageProcessingThread(i, numThreads); threads[i].start(); } // wait for all threads to terminate for (final Thread thread : threads) { try { thread.join(); } catch (final InterruptedException e) { } } // clean up GrowthLineSegmentationMagic.setClassifier(oldClassifier); if (!HEADLESS) { dialogProgress.setVisible(false); dialogProgress.dispose(); } } return imgSegmented; } /** * @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(String.format("%s Console Window", this.VERSION_STRING)); // frameConsoleWindow.setResizable( false ); consoleWindowTextArea = new JTextArea(); consoleWindowTextArea.setLineWrap(true); consoleWindowTextArea.setWrapStyleWord(true); 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.setHorizontalScrollBarPolicy( ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ); 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(final boolean show) { frameConsoleWindow.setVisible(show); } /** * @return */ public boolean isConsoleVisible() { return this.frameConsoleWindow.isVisible(); } /** * 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) { setDatasetName(datasetName); guiFrame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(final WindowEvent we) { saveParams(); System.exit(0); } }); if (!HEADLESS) { Image image = null; try { image = new ImageIcon(MoMA.class.getClassLoader().getResource("IconMpiCbg128.png")).getImage(); } catch (final Exception e) { try { image = new ImageIcon(MoMA.class.getClassLoader().getResource("resources/IconMpiCbg128.png")) .getImage(); } catch (final Exception e2) { System.out.println(">>> Error: app icon not found..."); } } if (image != null) { if (OSValidator.isMac()) { System.out.println("On a Mac! --> trying to set icons..."); Application.getApplication().setDockIconImage(image); } else { System.out.println("Not a Mac! --> trying to set icons..."); guiFrame.setIconImage(image); } } } } /** * * @param guiFrame * parent frame * @param datapath * path to be suggested to open * @return */ private File showStartupDialog(final JFrame guiFrame, final String datapath) { File file = null; final String parentFolder = datapath.substring(0, datapath.lastIndexOf(File.separatorChar)); // DATA TO BE LOADED --- DATA TO BE LOADED --- DATA TO BE LOADED --- DATA TO BE LOADED int decision = 0; if (datapath.equals(System.getProperty("user.home"))) { decision = JOptionPane.NO_OPTION; } else { final String message = "Should the MotherMachine be opened with the data found in:\n" + datapath + "\n\nIn case you want to choose a folder please select 'No'..."; final String title = "MotherMachine Data Folder Selection"; decision = JOptionPane.showConfirmDialog(guiFrame, message, title, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); } if (decision == JOptionPane.YES_OPTION) { file = new File(datapath); } else { file = showFolderChooser(guiFrame, parentFolder); } // CLASSIFIER TO BE LOADED --- CLASSIFIER TO BE LOADED --- CLASSIFIER TO BE LOADED // final String message = "Should this classifier be used:\n" + SEGMENTATION_CLASSIFIER_MODEL_FILE + "\n\nIn case you want to choose a different one, please select 'No'..."; // final String title = "MotherMachine Classifier Selection"; // decision = JOptionPane.showConfirmDialog( guiFrame, message, title, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE ); // if ( decision == JOptionPane.YES_OPTION ) { // GrowthLineSegmentationMagic.setClassifier( SEGMENTATION_CLASSIFIER_MODEL_FILE, "" ); // } else { // final FileDialog fd = new FileDialog( guiFrame, "Select classifier model file...", FileDialog.LOAD ); // fd.setDirectory( SEGMENTATION_CLASSIFIER_MODEL_FILE ); // fd.setFilenameFilter( new FilenameFilter() { // // @Override // public boolean accept( final File dir, final String name ) { // final String lowercaseName = name.toLowerCase(); // if ( lowercaseName.endsWith( ".model" ) ) { // return true; // } else { // return false; // } // } // } ); // fd.setVisible( true ); // final String filename = fd.getDirectory() + "/" + fd.getFile(); // if ( filename != null ) { // SEGMENTATION_CLASSIFIER_MODEL_FILE = filename; // } // } return file; } /** * 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) { File selectedFile = null; if (SystemUtils.IS_OS_MAC) { // --- ON MAC SYSTEMS --- ON MAC SYSTEMS --- ON MAC SYSTEMS --- ON MAC SYSTEMS --- ON MAC SYSTEMS --- System.setProperty("apple.awt.fileDialogForDirectories", "true"); final FileDialog fd = new FileDialog(guiFrame, "Select folder containing image sequence...", FileDialog.LOAD); fd.setDirectory(path); // fd.setLocation(50,50); fd.setVisible(true); selectedFile = new File(fd.getDirectory() + "/" + fd.getFile()); if (fd.getFile() == null) { System.exit(0); return null; } System.setProperty("apple.awt.fileDialogForDirectories", "false"); } else { // --- NOT ON A MAC --- NOT ON A MAC --- NOT ON A MAC --- NOT ON A MAC --- NOT ON A MAC --- NOT ON A MAC --- 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) { selectedFile = chooser.getSelectedFile(); } else { System.exit(0); return null; } } return selectedFile; } /** * 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 defaultProps = new Properties(); // First try loading from the current directory try { final File f = new File("mm.properties"); System.out.println("Loading default properties from: " + f.getAbsolutePath()); is = new FileInputStream(f); } catch (final Exception e) { System.out.println("Could not load props... try from classpath next..."); is = null; } try { if (is == null) { // Try loading from classpath System.out.println("Loading default properties from: " + getClass().getResource("mm.properties")); is = getClass().getResourceAsStream("mm.properties"); } // Try loading properties from the file (if found) defaultProps.load(is); System.out.println(" >> default properties loaded!"); } catch (final Exception e) { System.out.println( "No default properties file 'mm.properties' found in current path or classpath... I will create one at termination time!"); } // ADD USER PROPS IF GIVEN VIA CLI final Properties props = new Properties(defaultProps); if (fileUserProps != null) { System.out.println("Loading user properties from: " + fileUserProps.getAbsolutePath()); try { is = new FileInputStream(fileUserProps); props.load(is); System.out.println(" >> user properties loaded!"); } catch (final FileNotFoundException e) { System.out.println("ERROR: Could not find user props!"); } catch (final IOException e) { System.out.println("ERROR: Could not read user props!"); } } 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_WIDTH_IN_PIXELS", Integer.toString(GL_WIDTH_IN_PIXELS)); props.setProperty("MOTHER_CELL_BOTTOM_TRICK_MAX_PIXELS", Integer.toString(MOTHER_CELL_BOTTOM_TRICK_MAX_PIXELS)); props.setProperty("GL_FLUORESCENCE_COLLECTION_WIDTH_IN_PIXELS", Integer.toString(GL_FLUORESCENCE_COLLECTION_WIDTH_IN_PIXELS)); int offset = GL_OFFSET_BOTTOM; if (GL_OFFSET_BOTTOM_AUTODETECT) { offset = -1; } props.setProperty("GL_OFFSET_BOTTOM", Integer.toString(offset)); 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("SEGMENTATION_MIX_CT_INTO_PMFRF", Double.toString(SEGMENTATION_MIX_CT_INTO_PMFRF)); props.setProperty("SEGMENTATION_CLASSIFIER_MODEL_FILE", SEGMENTATION_CLASSIFIER_MODEL_FILE); props.setProperty("CELLSIZE_CLASSIFIER_MODEL_FILE", CELLSIZE_CLASSIFIER_MODEL_FILE); props.setProperty("DEFAULT_PATH", DEFAULT_PATH); props.setProperty("GUROBI_TIME_LIMIT", Double.toString(GUROBI_TIME_LIMIT)); props.setProperty("GUROBI_MAX_OPTIMALITY_GAP", Double.toString(GUROBI_MAX_OPTIMALITY_GAP)); if (!MoMA.HEADLESS) { GUI_POS_X = guiFrame.getX(); GUI_POS_Y = guiFrame.getY(); 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. * * Note: multi-channel assumption is that filename encodes channel by * containing a substring of format "_c%02d". * * @param path * the folder to be processed. * @param minTime * @param maxTime * @param minChannelIdx * @param numChannels * @throws Exception */ private void processDataFromFolder(final String path, final int minTime, final int maxTime, final int minChannelIdx, final int numChannels) throws Exception { if (numChannels == 0) { throw new Exception("At least one color channel must be loaded!"); } // extract dataset name and set in GUI final File folder = new File(path); setDatasetName(String.format("%s >> %s", folder.getParentFile().getName(), folder.getName())); // load channels separately into Img objects rawChannelImgs = new ArrayList<Img<FloatType>>(); for (int cIdx = minChannelIdx; cIdx < minChannelIdx + numChannels; cIdx++) { // load tiffs from folder final String filter = String.format("_c%04d", cIdx); System.out.println(String.format("Loading tiff sequence for channel, identified by '%s', from '%s'...", filter, path)); try { if (cIdx == minChannelIdx) { rawChannelImgs.add(FloatTypeImgLoader.loadMMPathAsStack(path, minTime, maxTime, true, filter)); } else { rawChannelImgs.add(FloatTypeImgLoader.loadMMPathAsStack(path, minTime, maxTime, false, filter)); } } catch (final Exception e) { e.printStackTrace(); System.exit(10); } System.out.println("Done loading tiffs!"); } imgRaw = rawChannelImgs.get(0); // 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!"); restartFromGLSegmentation(); if (HEADLESS) { System.out.println("Generating Integer Linear Program(s)..."); generateILPs(); System.out.println(" done!"); System.out.println("Running Integer Linear Program(s)..."); runILPs(); System.out.println(" done!"); } } /** * 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<FloatType> img) { imgAnnotated = DataMover.createEmptyArrayImgLike(img, new ARGBType()); } private void normalizePerFrame(final Img<FloatType> img, final int topOffset, final int bottomOffset) { for (int f = 0; f < img.dimension(2); f++) { final IntervalView<FloatType> slice = Views.hyperSlice(img, 2, f); final IntervalView<FloatType> roi = Views.interval(slice, new long[] { img.min(0), img.min(1) + topOffset }, new long[] { img.max(0), img.max(1) - bottomOffset }); Normalize.normalize(Views.iterable(roi), new FloatType(0.0f), new FloatType(1.0f)); } } /** * Simple but effective method to subtract uneven illumination from the * growth-line data. * * @param img * DoubleType image stack. */ private void subtractBackgroundInTemp() { 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 int glfY1 = glf.getFirstPoint().getIntPosition(1) - 1; if (glfY1 < 0) glfY1 = 0; final int glfY2 = glf.getLastPoint().getIntPosition(1); final IntervalView<FloatType> frame = Views.hyperSlice(imgTemp, 2, f); float rowAvgs[] = new float[glfY2 - glfY1 + 1]; int colCount = 0; // Look to the left if you are not the first GLF if (glfX > MoMA.BGREM_TEMPLATE_XMAX) { final IntervalView<FloatType> leftBackgroundWindow = Views.interval(frame, new long[] { glfX - MoMA.BGREM_TEMPLATE_XMAX, glfY1 }, new long[] { glfX - MoMA.BGREM_TEMPLATE_XMIN, glfY2 }); rowAvgs = addRowSumsFromInterval(leftBackgroundWindow, rowAvgs); colCount += (MoMA.BGREM_TEMPLATE_XMAX - MoMA.BGREM_TEMPLATE_XMIN); } // Look to the right if you are not the last GLF if (glfX < imgTemp.dimension(0) - MoMA.BGREM_TEMPLATE_XMAX) { final IntervalView<FloatType> rightBackgroundWindow = Views.interval(frame, new long[] { glfX + MoMA.BGREM_TEMPLATE_XMIN, glfY1 }, new long[] { glfX + MoMA.BGREM_TEMPLATE_XMAX, glfY2 }); rowAvgs = addRowSumsFromInterval(rightBackgroundWindow, rowAvgs); colCount += (MoMA.BGREM_TEMPLATE_XMAX - MoMA.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 - MoMA.BGREM_X_OFFSET); final long x2 = Math.min(frame.dimension(0) - 1, glfX + MoMA.BGREM_X_OFFSET); final IntervalView<FloatType> 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 FloatType(0f), new FloatType(1f)); } } } /** * Adds all intensity values of row i in view to rowSums[i]. * * @param view * @param rowSums */ private float[] addRowSumsFromInterval(final IntervalView<FloatType> view, final float[] rowSums) { for (int i = (int) view.min(1); i <= view.max(1); i++) { final IntervalView<FloatType> row = Views.hyperSlice(view, 1, i); final Cursor<FloatType> cursor = Views.iterable(row).cursor(); while (cursor.hasNext()) { rowSums[i - (int) view.min(1)] += 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<FloatType> view, final float[] values) { for (int i = (int) view.min(1); i <= view.max(1); i++) { final Cursor<FloatType> cursor = Views.iterable(Views.hyperSlice(view, 1, i)).cursor(); while (cursor.hasNext()) { cursor.next().set(new FloatType(Math.max(0, cursor.get().get() - values[i - (int) view.min(1)]))); } } } /** * 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.extendZero(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<FloatType> ivFrame = Views.hyperSlice(imgTemp, 2, frameIdx); // Find maxima per image row (per frame) frameWellCenters = new Loops<FloatType, List<Point>>().forEachHyperslice(ivFrame, 1, new FindLocalMaxima<FloatType>()); // 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 >= imgTemp.dimension(1) - GL_OFFSET_BOTTOM) { frameWellCenters.get(y).clear(); } else { if (maxWellCenters < frameWellCenters.get(y).size()) { maxWellCenters = frameWellCenters.get(y).size(); maxWellCentersIdx = y; } } } if (maxWellCenters > 1) { final String msg = "ERROR: Two maxima in a single pixel row found while looking for GL centerline at frame " + frameIdx + ".\nPlease check input images or adjust (increase?) SIGMA_GL_DETECTION_X in properties."; System.out.println(msg); if (!HEADLESS) { JOptionPane.showMessageDialog(getGui(), msg, "Error while looking for GL centerline...", JOptionPane.ERROR_MESSAGE); } } // add filtered 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) { continue; } // 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) { continue; } // 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)); } } // sort points for (final GrowthLineFrame glf : glFrames) { glf.sortPoints(); } // 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 generateAllSimpleSegmentationHypotheses() { // ------ 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.generateSimpleSegmentationHypotheses(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(null); } } /** * 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++; } } /** * @return the guiFrame */ public static JFrame getGuiFrame() { return guiFrame; } /** * @return the MotherMachineGui instance. */ public static MoMAGui getGui() { return gui; } /** * @return the defaultFilenameDecoration */ public static String getDefaultFilenameDecoration() { return defaultFilenameDecoration; } /** * @param defaultFilenameDecoration * the defaultFilenameDecoration to set */ public static void setDefaultFilenameDecoration(final String defaultFilenameDecoration) { MoMA.defaultFilenameDecoration = defaultFilenameDecoration; } /** * @return the first time-point loaded */ public static int getMinTime() { return minTime; } /** * @return the last loaded time-point */ public static int getMaxTime() { return maxTime; } /** * @return the initial optimization range, -1 if it is infinity. */ public static int getInitialOptRange() { return initOptRange; } /** * @return the first channel index of the loaded data */ public static int getMinChannelIdx() { return minChannelIdx; } /** * @return the number of channels loaded */ public static int getNumChannels() { return numChannels; } /** * Allows one to restart by GL segmentation. This is e.g. needed after top * or bottom offsets are altered, which invalidates all analysis run so far. */ public void restartFromGLSegmentation() { boolean hideConsoleLater = false; if (!HEADLESS && !isConsoleVisible()) { showConsoleWindow(true); hideConsoleLater = true; } if (GL_OFFSET_BOTTOM_AUTODETECT) { System.out.print("Automatic estimation of GL_OFFSET_BOTTOM..."); resetImgTempToRaw(); autodetectBottomOffset(); System.out.println(" done!"); } else { if (GL_OFFSET_BOTTOM == -1) { System.err.println( "GL_OFFSET_BOTTOM and GL_OFFSET_BOTTOM_AUTODETECT are inconsistent. This is a programmatic problem, please contact the MotherMachine developers!"); } } System.out.print("Searching for GrowthLines..."); resetImgTempToRaw(); findGrowthLines(); // annotateDetectedWellCenters(); System.out.println(" done!"); // subtracting BG in RAW image... System.out.print("Subtracting background..."); // ...and make temp image be the same resetImgTempToRaw(); subtractBackgroundInTemp(); System.out.println(" done!"); System.out.print("Normalize loaded images..."); normalizePerFrame(imgTemp, MoMA.GL_OFFSET_TOP, MoMA.GL_OFFSET_BOTTOM); System.out.println(" done!"); System.out.print("Generating Segmentation Hypotheses..."); generateAllSimpleSegmentationHypotheses(); System.out.println(" done!"); if (!HEADLESS && hideConsoleLater) { showConsoleWindow(false); } } /** * Autodetects bottom offset. * This is implemented by averaging all pixel-rows over all time-points and * finding the first entry from below that is above the midpoint between * average intensity of the lowest 3 rows and the lower half of the GL. */ private void autodetectBottomOffset() { final int HEURISTIC_CONSTANT = 2; // that many pixels I move down after finding the onset point // Project all images (relevant parts only) + sum rows to single values final long[] mins = new long[getImgTemp().numDimensions()]; final long[] maxs = new long[getImgTemp().numDimensions()]; getImgTemp().min(mins); getImgTemp().max(maxs); final long xDimLen = getImgTemp().dimension(0); // Note: the '/3' below was not there. Basel noted some rare problems by averaging the entire lateral offset range when there was // some brighter background close below the GL (at the imgage border). In their case that came from the cropped numbers below the GL. mins[0] = xDimLen / 2 - GL_OFFSET_LATERAL / 3; // we use the fact that we know that the GL is in the center of the image given to us maxs[0] = xDimLen / 2 + GL_OFFSET_LATERAL / 3; // we use the fact that we know that the GL is in the center of the image given to us final RandomAccessibleInterval<FloatType> centralArea = Views.interval(getImgTemp(), mins, maxs); final List<FloatType> rowTimeAverages = new Loops<FloatType, FloatType>().forEachHyperslice(centralArea, 1, new SumOfRai<FloatType>()); // compute average of lower half averages float lowerHalfAvg = 0; for (int i = rowTimeAverages.size() / 2; i < rowTimeAverages.size(); i++) { lowerHalfAvg += rowTimeAverages.get(i).get(); } lowerHalfAvg /= rowTimeAverages.size() / 2; // compute average of lowest 3 (or such) rows float lowestRowsAvg = 0; final int numRows = 3; for (int i = rowTimeAverages.size() - numRows; i < rowTimeAverages.size(); i++) { lowestRowsAvg += rowTimeAverages.get(i).get(); } lowestRowsAvg /= numRows; // Locate bottom onset int bottom_offset = 25; for (int i = rowTimeAverages.size() - 1; i >= 0; i--) { if (rowTimeAverages.get(i).get() > (lowerHalfAvg + lowestRowsAvg) / 2) { bottom_offset = rowTimeAverages.size() - i + 1 - HEURISTIC_CONSTANT; break; } } System.out.println("\n >> Detected GL_OFFSET_BOTTOM is: " + bottom_offset); GL_OFFSET_BOTTOM = bottom_offset; } /** * @return the datasetName */ public String getDatasetName() { return datasetName; } /** * @param datasetName the datasetName to set */ public void setDatasetName(final String datasetName) { this.datasetName = datasetName; if (this.getGuiFrame() != null) { this.getGuiFrame().setTitle(String.format("%s -- %s", this.VERSION_STRING, this.datasetName)); } } }