RandomWalkSegmentationPlugin_.java
package com.github.celldynamics.quimp.plugin.randomwalk;
import java.awt.Color;
import java.awt.FileDialog;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.net.URI;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.swing.JOptionPane;
import javax.swing.SwingWorker;
import org.scijava.vecmath.Point2d;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.celldynamics.quimp.BoaException;
import com.github.celldynamics.quimp.PropertyReader;
import com.github.celldynamics.quimp.QuimpException.MessageSinkTypes;
import com.github.celldynamics.quimp.geom.SegmentedShapeRoi;
import com.github.celldynamics.quimp.geom.filters.HatSnakeFilter;
import com.github.celldynamics.quimp.plugin.AbstractOptionsParser;
import com.github.celldynamics.quimp.plugin.AbstractPluginOptions;
import com.github.celldynamics.quimp.plugin.AbstractPluginTemplate;
import com.github.celldynamics.quimp.plugin.QuimpPluginException;
import com.github.celldynamics.quimp.plugin.binaryseg.BinarySegmentation;
import com.github.celldynamics.quimp.plugin.generatemask.GenerateMask_;
import com.github.celldynamics.quimp.plugin.randomwalk.RandomWalkModel.SeedSource;
import com.github.celldynamics.quimp.plugin.randomwalk.RandomWalkSegmentation.SeedTypes;
import com.github.celldynamics.quimp.plugin.utils.QuimpDataConverter;
import com.github.celldynamics.quimp.utils.QuimpToolsCollection;
import ch.qos.logback.core.status.OnConsoleStatusListener;
import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.WindowManager;
import ij.gui.Roi;
import ij.io.OpenDialog;
import ij.plugin.ContrastEnhancer;
import ij.plugin.Converter;
import ij.plugin.tool.BrushTool;
import ij.process.AutoThresholder;
import ij.process.ByteProcessor;
import ij.process.ImageProcessor;
import ij.process.StackStatistics;
/*
* !>
*
* @startuml doc-files/RandomWalkSegmentationPlugin_3_UML.png
* actor "IJ plugin runner"
* actor User
* "IJ plugin runner" -> RandomWalkSegmentationPlugin_ : <<create>>
* activate RandomWalkSegmentationPlugin_
* RandomWalkSegmentationPlugin_ -> RandomWalkSegmentationPlugin_ : <<model>>
* RandomWalkSegmentationPlugin_ -> RandomWalkSegmentationPlugin_ : <<view>>
* RandomWalkSegmentationPlugin_ -> RandomWalkSegmentationPlugin_ : writeUI()
* "IJ plugin runner" -> RandomWalkSegmentationPlugin_ : run()
* RandomWalkSegmentationPlugin_ -> RandomWalkSegmentationPlugin_ :showUI(true)
* ...
* User -> Dialog : click Apply
* Dialog -> Dialog : readUI()
* Dialog -> RWWorker : <<create>>
* activate RWWorker
* RWWorker -> Dialog : enableUI(false)
* RWWorker -> Dialog : rename button
* RWWorker -> RandomWalkSegmentationPlugin_ : runPlugin()
* RandomWalkSegmentationPlugin_ --> IJ : update progress
* RandomWalkSegmentationPlugin_ --> RWWorker : done
* RWWorker -> Dialog : enableUI(true)
* RWWorker -> Dialog : rename button
* RWWorker --> Dialog : <<end>>
* deactivate RWWorker
* @enduml
* !<
*/
/**
* Run RandomWalkSegmentation in IJ environment.
*
* <p>Implements common PlugIn interface as both images are provided after run. The seed can be one
* image - in this case seed propagation is used to generate seed for subsequent frames, or it can
* be stack of the same size as image. In latter case every slice from seed is used for seeding
* related slice from image.
*
* <p>Principles of working:<br>
* <img src="doc-files/RandomWalkSegmentationPlugin_3_UML.png"/><br>
*
* @author p.baniukiewicz
*
*/
public class RandomWalkSegmentationPlugin_ extends AbstractPluginTemplate {
/** The this plugin name. */
private static String thisPluginName = "RandomWalk";
/**
* The Constant LOGGER.
*/
static final Logger LOGGER =
LoggerFactory.getLogger(RandomWalkSegmentationPlugin_.class.getName());
/** The view. */
RandomWalkView view;
/** The seed picker wnd. */
private SeedPicker seedPickerWnd = null;
/** The br. */
private BrushTool br = new BrushTool();
/** The last tool. */
private String lastTool; // tool selected in IJ
/** The is canceled. */
private boolean isCanceled; // true if user click Cancel, false if clicked Apply
/** The is run. */
private boolean isRun; // true if segmentation is running
/** The one slice. */
private boolean oneSlice = false; // true if only current slice is segmented
/** The start slice. */
private int startSlice = 1; // number of slice to segment If oneSlice == false, segment all from 1
/**
* Result of {@link #runPlugin()}.
*/
private ImagePlus segmented = null; // result of segmentation
/**
* Default constructor.
*/
public RandomWalkSegmentationPlugin_() {
super(new RandomWalkModel(), thisPluginName);
if (IJ.getInstance() != null) {
lastTool = IJ.getToolName(); // remember selected tool
}
isCanceled = false;
isRun = false;
view = new RandomWalkView();
seedPickerWnd = new SeedPicker(false);
writeUI();
view.addWindowController(new ActivateWindowController());
view.addImageController(new ImageController());
view.addSeedController(new SeedController());
view.addRunController(new RunBtnController());
view.addCancelController(new CancelBtnController());
view.addBgController(new BgController());
view.addFgController(new FgController());
view.addCloneController(new CloneController());
view.addLoadQconfController(new LoadQconfController());
view.addQconfShowSeedImageController(new QconfShowSeedImageController());
view.addRunActiveController(new RunActiveBtnController());
view.addHelpController(new HelpBtnController());
view.addSeedRoiController(new SeedRoiController());
seedPickerWnd.addFinishController(new FinishControllerSeedPicker());
}
/**
* Constructor that allows to provide own configuration parameters.
*
* @param paramString parameter string.
* @throws QuimpPluginException on error
*/
public RandomWalkSegmentationPlugin_(String paramString) throws QuimpPluginException {
super(paramString, new RandomWalkModel(), thisPluginName);
view = new RandomWalkView();
writeUI(); // fill UI controls with default options
}
/**
* This constructor allows to provide user configuration.
*
* <p>Call {@link #runPlugin()} afterwards. Note that {@link AbstractOptionsParser#apiCall} is set
* to true and sink to Console.
*
* @param options configuration object.
*/
public RandomWalkSegmentationPlugin_(AbstractPluginOptions options) {
super(options, thisPluginName);
apiCall = true;
errorSink = MessageSinkTypes.CONSOLE;
view = new RandomWalkView();
writeUI(); // fill UI controls with default options
}
/**
* Updates view from model.
*/
public void writeUI() {
RandomWalkModel model = (RandomWalkModel) options;
if (model.getOriginalImage() != null) {
view.setCbOrginalImage(new String[] { model.getOriginalImage().getTitle() }, "");
}
view.setSeedSource(model.getSeedSources(), model.getSelectedSeedSource().name());
if (model.getSeedImage() != null) {
view.setCbRgbSeedImage(new String[] { model.getSeedImage().getTitle() }, "");
view.setCbCreatedSeedImage(new String[] { model.getSeedImage().getTitle() }, "");
view.setCbMaskSeedImage(new String[] { model.getSeedImage().getTitle() }, "");
}
view.setSrAlpha(model.algOptions.alpha);
view.setSrBeta(model.algOptions.beta);
view.setSrGamma0(model.algOptions.gamma[0]);
view.setSrGamma1(model.algOptions.gamma[1]);
view.setSrIter(model.algOptions.iter);
view.setSrRelerr(model.algOptions.relim[0]);
view.setShrinkMethod(model.getShrinkMethods(), model.getselectedShrinkMethod().name());
view.setSrShrinkPower(model.shrinkPower);
view.setSrExpandPower(model.expandPower);
view.setSrScaleSigma(model.scaleSigma);
view.setSrScaleMagn(model.scaleMagn);
view.setSrScaleCurvDistDist(model.scaleCurvDistDist);
view.setSrScaleEqNormalsDist(model.scaleEqNormalsDist);
view.setFilteringMethod(model.getFilteringMethods(), model.getSelectedFilteringMethod().name());
view.setChLocalMean(model.algOptions.useLocalMean);
view.setSrLocalMeanWindow(model.algOptions.localMeanMaskSize);
view.setChTrueBackground(model.estimateBackground);
view.setChInterFrameFilter(model.interFrameFilter);
view.setChHatFilter(model.hatFilter);
view.setSrAlev(model.alev);
view.setSrNum(model.num);
view.setSrWindow(model.window);
view.setFilteringPostMethod(model.getFilteringMethods(),
model.getSelectedFilteringPostMethod().name());
view.setChMaskCut(model.algOptions.maskLimit);
view.setChShowSeed(model.showPreview);
view.setChShowPreview(model.showPreview);
view.setChShowProbMaps(model.showProbMaps);
}
/**
* Updates model from view.
*
* @return updated model. It is reference of model stored in this class.
*/
public RandomWalkModel readUI() {
RandomWalkModel model = (RandomWalkModel) options;
model.setOriginalImage(WindowManager.getImage(view.getCbOrginalImage()));
model.setSelectedSeedSource(view.getSeedSource());
switch (model.getSelectedSeedSource()) {
case RGBImage:
model.setSeedImage(WindowManager.getImage(view.getCbRgbSeedImage()));
break;
case CreatedImage:
model.setSeedImage(WindowManager.getImage(view.getCbCreatedSeedImage()));
break;
case MaskImage:
model.setSeedImage(WindowManager.getImage(view.getCbMaskSeedImage()));
break;
case QconfFile:
break; // no control to display or read from for this case
case Rois:
break;
default:
throw new IllegalArgumentException("Unknown seed source");
}
model.algOptions.alpha = view.getSrAlpha();
model.algOptions.beta = view.getSrBeta();
model.algOptions.gamma[0] = view.getSrGamma0();
model.algOptions.gamma[1] = view.getSrGamma1();
model.algOptions.iter = view.getSrIter();
model.algOptions.relim[0] = view.getSrRelerr();
model.algOptions.relim[1] = model.algOptions.relim[0] * 10;
model.setselectedShrinkMethod(view.getShrinkMethod());
model.shrinkPower = view.getSrShrinkPower();
model.expandPower = view.getSrExpandPower();
model.scaleSigma = view.getSrScaleSigma();
model.scaleMagn = view.getSrScaleMagn();
model.scaleCurvDistDist = view.getSrScaleCurvDistDist();
model.scaleEqNormalsDist = view.getSrScaleEqNormalsDist();
model.setSelectedFilteringMethod(view.getFilteringMethod());
model.algOptions.useLocalMean = view.getChLocalMean();
model.algOptions.localMeanMaskSize = view.getSrLocalMeanWindow();
model.estimateBackground = view.getChTrueBackground();
model.hatFilter = view.getChHatFilter();
model.alev = view.getSrAlev();
model.num = view.getSrNum();
model.window = view.getSrWindow();
model.setSelectedFilteringPostMethod(view.getFilteringPostMethod());
model.algOptions.maskLimit = view.getChMaskCut();
model.showSeeds = view.getChShowSeed();
model.showPreview = view.getChShowPreview();
model.showProbMaps = view.getChShowProbMaps();
return model;
}
/**
* Build main dialog.
* <br>
* <img src="doc-files/RandomWalkSegmentationPlugin_1_UML.png"/><br>
* State diagram <br>
* <img src="doc-files/RandomWalkSegmentationPlugin_2_UML.png"/><br>
*
* @param val the val
*/
public void showUi(boolean val) {
view.show();
}
/**
* Action {@link OnConsoleStatusListener} {@link SeedPicker}.
*
* <p>Fill
*
* @author p.baniukiewicz
*
*/
class FinishControllerSeedPicker implements ActionListener {
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent e) {
if (seedPickerWnd == null) {
return;
}
RandomWalkModel model = (RandomWalkModel) options;
model.getOriginalImage().deleteRoi(); // just in case if ROI tool left something
List<Seeds> rois = seedPickerWnd.seedsRoi;
if (rois != null) {
String fgsize;
String bgsize;
if (rois.get(0).get(SeedTypes.FOREGROUNDS) == null) {
fgsize = "<no FG>";
} else {
fgsize = "" + rois.get(0).get(SeedTypes.FOREGROUNDS).size();
}
if (rois.get(0).get(SeedTypes.BACKGROUND) == null) {
bgsize = "<no BG>";
} else {
bgsize = "" + rois.get(0).get(SeedTypes.BACKGROUND).size();
}
view.setLroiSeedsInfo("Objects: " + fgsize + " FG and " + bgsize + " BG");
}
}
}
/**
* Updates list of images in selector on activation or deactivation of window.
*
* @author p.baniukiewicz
*
*/
class ActivateWindowController implements WindowFocusListener {
/*
* (non-Javadoc)
*
* @see java.awt.event.WindowFocusListener#windowGainedFocus(java.awt.event.WindowEvent)
*/
@Override
public void windowGainedFocus(WindowEvent e) {
action();
}
/*
* (non-Javadoc)
*
* @see java.awt.event.WindowFocusListener#windowLostFocus(java.awt.event.WindowEvent)
*/
@Override
public void windowLostFocus(WindowEvent e) {
action();
}
/**
* Action.
*/
private void action() {
if (isRun == true) {
return;
}
RandomWalkModel model = (RandomWalkModel) options;
// list of open windows
String[] images = WindowManager.getImageTitles();
String selection = "";
// try to find that stored in model in list of opened windows and select it
if (model.getOriginalImage() != null) {
selection = model.getOriginalImage().getTitle();
}
// select that found (if any, first position otherwise)
view.setCbOrginalImage(images, selection);
selection = "";
// the same with seeds
if (model.getSeedImage() != null) {
selection = model.getSeedImage().getTitle();
}
// all seeds selectors share the same image in model
view.setCbCreatedSeedImage(images, selection);
view.setCbRgbSeedImage(images, selection);
view.setCbMaskSeedImage(images, selection);
}
}
/**
* Detect change on image JComboBox, this change can be due to user action adding new images to
* list by {@link ActivateWindowController}.
*
* <p>Stores selected image in model.
*
* @author p.baniukiewicz
*
*/
class ImageController implements ActionListener {
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent e) {
RandomWalkModel model = (RandomWalkModel) options;
model.setOriginalImage(WindowManager.getImage(view.getCbOrginalImage()));
}
}
/**
* Detect change on seeds selector, these change can be due to user action adding new images
* to list by {@link ActivateWindowController}.
*
* <p>Stores selected image in model.
*
* @author p.baniukiewicz
*
*/
class SeedController implements ActionListener {
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent e) {
RandomWalkModel model = (RandomWalkModel) options;
SeedSource src = SeedSource.valueOf(model.getSeedSources()[view.getSeedSource()]);
switch (src) {
case RGBImage:
model.setSeedImage(WindowManager.getImage(view.getCbRgbSeedImage()));
break;
case MaskImage:
model.setSeedImage(WindowManager.getImage(view.getCbMaskSeedImage()));
break;
case CreatedImage:
model.setSeedImage(WindowManager.getImage(view.getCbCreatedSeedImage()));
break;
case QconfFile:
break;
case Rois:
break;
default:
throw new IllegalArgumentException("Unknown seed source");
}
}
}
/**
* Handle Run button.
*
* <p>Copy view status to model and start processing.
*
* @author p.baniukiewicz
*
*/
class RunBtnController implements ActionListener {
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent e) {
RandomWalkModel model = (RandomWalkModel) options;
startSlice = 1;// segment from first
oneSlice = false;
readUI();
RWWorker rww = new RWWorker();
rww.execute();
LOGGER.trace("model: " + model.toString());
}
}
/**
* Handle Help button.
*
* <p>Open browser.
*
* @author p.baniukiewicz
*
*/
class HelpBtnController implements ActionListener {
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent e) {
String url = new PropertyReader().readProperty("quimpconfig.properties", "manualURL");
try {
java.awt.Desktop.getDesktop().browse(new URI(url));
} catch (Exception e1) {
LOGGER.error("Could not open help: " + e1.getMessage(), e1);
}
}
}
/**
* Handle Run button from active.
*
* <p>Copy view status to model and start processing.
*
* @author p.baniukiewicz
*
*/
class RunActiveBtnController implements ActionListener {
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent e) {
RandomWalkModel model = (RandomWalkModel) options;
ImagePlus orgImg = model.getOriginalImage();
if (orgImg == null) {
return;
}
readUI();
startSlice = orgImg.getCurrentSlice();
oneSlice = true;
RWWorker rww = new RWWorker();
rww.execute();
LOGGER.trace("model: " + model.toString());
}
}
/**
* Handle Cancel button.
*
* @author p.baniukiewicz
*
*/
public class CancelBtnController implements ActionListener {
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent arg0) {
isCanceled = true;
if (isRun == false) {
view.getWnd().dispose();
}
}
}
/**
* Handle Clone button.
*
* @author p.baniukiewicz
*
*/
public class CloneController implements ActionListener {
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent arg0) {
ImagePlus tmpImage = WindowManager.getImage(view.getCbOrginalImage());
if (tmpImage == null) {
return;
}
ImagePlus duplicatedImage;
Object[] options = { "Whole stack", "Current slice", "Cancel" };
int ret = JOptionPane.showOptionDialog(view.getWnd(),
QuimpToolsCollection
.stringWrap("Do you want to duplicte the whole stack or only current slice?"),
"Duplicate stack", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE,
null, options, null);
switch (ret) {
case 0: // stack
duplicatedImage = tmpImage.duplicate();
break;
case 1: // slice
duplicatedImage = new ImagePlus("",
tmpImage.getStack().getProcessor(tmpImage.getCurrentSlice()).duplicate());
break;
case 2: // cancel
default: // closed window
return;
}
duplicatedImage.show();
new Converter().run("RGB Color");
duplicatedImage.setTitle("SEED_" + tmpImage.getTitle());
view.setCbCreatedSeedImage(WindowManager.getImageTitles(), duplicatedImage.getTitle());
}
}
/**
* Handle seed ROI button. Open {@link SeedPicker}.
*
* @author p.baniukiewicz
*
*/
public class SeedRoiController implements ActionListener {
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent e) {
ImagePlus tmpImage = WindowManager.getCurrentImage();
if (tmpImage == null) {
return;
}
if (seedPickerWnd != null) {
seedPickerWnd.image = tmpImage;
seedPickerWnd.reset();
seedPickerWnd.setVisible(true);
}
}
}
/**
* Clone whole image or selected slice. Helper
*
* @param tmpImage image to clone
* @return cloned image
*/
private ImagePlus cloneImageAndAsk(ImagePlus tmpImage) {
Object[] options = { "Whole stack", "Current slice", "Cancel" };
int ret = JOptionPane.showOptionDialog(view.getWnd(),
QuimpToolsCollection
.stringWrap("Do you want to duplicte the whole stack or only current slice?"),
"Duplicate stack", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null,
options, null);
ImagePlus duplicatedImage;
switch (ret) {
case 0: // stack
duplicatedImage = tmpImage.duplicate();
break;
case 1: // slice
duplicatedImage = new ImagePlus("",
tmpImage.getStack().getProcessor(tmpImage.getCurrentSlice()).duplicate());
break;
case 2: // cancel
default: // closed window
return null;
}
return duplicatedImage;
}
/**
* Handle FG button.
*
* @author p.baniukiewicz
*
*/
public class FgController implements ActionListener {
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent arg0) {
if (view.getBnFore().isSelected()) {
IJ.setForegroundColor(Color.RED.getRed(), Color.RED.getGreen(), Color.RED.getBlue());
BrushTool.setBrushWidth(10); // set brush width
br.run(""); // run macro
} else {
IJ.setTool(lastTool); // if unselected just switch off BrushTool selecting other tool
}
}
}
/**
* Handle BG button.
*
* @author p.baniukiewicz
*
*/
public class BgController implements ActionListener {
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent arg0) {
if (view.getBnBack().isSelected()) {
IJ.setForegroundColor(Color.GREEN.getRed(), Color.GREEN.getGreen(), Color.GREEN.getBlue());
BrushTool.setBrushWidth(10); // set brush width
br.run(""); // run macro
} else {
IJ.setTool(lastTool); // if unselected just switch off BrushTool selecting other tool
}
}
}
/**
* Handle Load qconf button.
*
* @author p.baniukiewicz
*
*/
public class LoadQconfController implements ActionListener {
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent arg0) {
FileDialog od = new FileDialog(IJ.getInstance(), "Open Qconf file");
od.setFile("*.QCONF");
od.setDirectory(OpenDialog.getLastDirectory());
od.setMultipleMode(false);
od.setMode(FileDialog.LOAD);
od.setVisible(true);
if (od.getFile() == null) {
IJ.log("Cancelled - exiting...");
return;
}
String directory = od.getDirectory();
String filename = od.getFile();
RandomWalkModel model = (RandomWalkModel) options;
model.setQconfFile(Paths.get(directory, filename).toString());
view.setLqconfFile(filename);
}
}
/**
* Handle show loaded qconf file mask button.
*
* @author p.baniukiewicz
*
*/
public class QconfShowSeedImageController implements ActionListener {
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent arg0) {
RandomWalkModel model = (RandomWalkModel) options;
if (model.getQconfFile() == null || model.getQconfFile().isEmpty()
|| Paths.get(model.getQconfFile()).getFileName() == null) {
return;
}
try {
ImagePlus seedImage;
// temporary load, it is repeated in runPlugin
seedImage =
new GenerateMask_("opts={paramFile:(" + model.getQconfFile() + "),binary:false}")
.getRes();
seedImage.setTitle(
"Mask-" + Paths.get(model.getQconfFile()).getFileName().toString() + ".tif");
new ContrastEnhancer().stretchHistogram(seedImage, 0.35);
seedImage.show();
} catch (QuimpPluginException e) {
LOGGER.debug("Can not load QCONF"); // not important, error handled in runPlugin
}
}
}
/*
* (non-Javadoc)
*
* @see com.github.celldynamics.quimp.plugin.AbstractPluginBase#run(java.lang.String)
*/
@Override
public void run(String arg) {
super.run(arg);
}
/**
* Run segmentation.
*
* <p>Set {@link AbstractOptionsParser#apiCall} (this.apiCall) to true and
* {@link RandomWalkModel#showPreview} to false to block visual output.
*
* @see RandomWalkModel
* @see RandomWalkSegmentationPlugin_#RandomWalkSegmentationPlugin_(String)
* @see #getResult()
*/
@Override
public void runPlugin() {
RandomWalkModel model = (RandomWalkModel) options;
ImagePlus prev = null; // preview window, null if not opened
// local mean should not be applied for first slice if seeds are rgb - remember status here
// to temporarily disable it and enable before processing second slice
boolean useSeedStack; // true if seed has the same size as image, slices are seeds
isCanceled = false; // erase any previous state
Color foreColor; // color of seed image foreground pixels
Color backColor; // color of seed image background pixels
boolean localMeanUserStatus = model.algOptions.useLocalMean;
ImageStack ret; // all images treated as stacks
ImageStack is = null;
Seeds seeds;
PropagateSeeds propagateSeeds;
isRun = true; // segmentation started
// if preview selected - prepare image
if (model.showPreview) {
prev = new ImagePlus();
}
AutoThresholder.Method thresholdBackground = null;
if (model.estimateBackground == true) {
thresholdBackground = AutoThresholder.Method.Otsu;
}
try {
ImagePlus image = model.getOriginalImage();
ImagePlus seedImage;
// find if we have stack seeds or not
if (model.getSelectedSeedSource() == SeedSource.Rois) {
seedImage = seedPickerWnd.image; // stored image in seedpicker (at SeedRoiController)
} else {
seedImage = model.getSeedImage(); // stored in model (rgb, mask)
}
if (image == null || seedImage == null) {
throw new QuimpPluginException("Input image or seed image can not be opened.");
}
is = image.getStack(); // get current stack (size 1 for one image)
if (seedImage.getStackSize() == 1) {
useSeedStack = false; // use propagateSeed for generating next frame seed from prev
} else if (seedImage.getStackSize() == image.getStackSize()) {
useSeedStack = true; // use slices as seeds
} else {
throw new RandomWalkException("Seed stack and image stack must have the same z dimension");
}
// create seeding object with or without storing the history of configured type
propagateSeeds = PropagateSeeds.getPropagator(model.selectedShrinkMethod, model.showSeeds,
thresholdBackground);
if (propagateSeeds instanceof PropagateSeeds.Contour) {
((PropagateSeeds.Contour) propagateSeeds).scaleMagn = model.scaleMagn;
((PropagateSeeds.Contour) propagateSeeds).scaleSigma = model.scaleSigma;
((PropagateSeeds.Contour) propagateSeeds).averageNormalsDist = model.scaleEqNormalsDist;
((PropagateSeeds.Contour) propagateSeeds).averageCurvDist = model.scaleCurvDistDist;
((PropagateSeeds.Contour) propagateSeeds).useFiltering = model.interFrameFilter;
}
ret = new ImageStack(image.getWidth(), image.getHeight()); // output stack
// create segmentation engine
RandomWalkSegmentation obj =
new RandomWalkSegmentation(is.getProcessor(startSlice), model.algOptions);
// decode provided seeds depending on selected option
switch (model.getSelectedSeedSource()) {
case RGBImage: // use seeds as they are
case CreatedImage:
foreColor = Color.RED;
backColor = Color.GREEN;
if (seedImage != null && seedImage.equals(image)) {
throw new RandomWalkException("Seed image and segmented image are the same.");
}
if (seedImage.getNSlices() >= startSlice) {
seeds = SeedProcessor.decodeSeedsfromRgb(seedImage.getStack().getProcessor(startSlice),
Arrays.asList(foreColor), backColor);
} else {
seeds = SeedProcessor.decodeSeedsfromRgb(seedImage.getStack().getProcessor(1),
Arrays.asList(foreColor), backColor);
}
if (model.algOptions.useLocalMean) {
LOGGER.warn("LocalMean is not used for first frame when seed is RGB image");
}
model.algOptions.useLocalMean = false; // do not use LM on first frame (reenable it later)
break;
case Rois:
if (seedPickerWnd.seedsRoi.isEmpty()) {
throw new RandomWalkException("No ROIs processed, did you forget to click Finish?");
}
seeds = seedPickerWnd.seedsRoi.get(0); // TODO startSlice - 1 if it will support frames
if (model.algOptions.useLocalMean) {
LOGGER.warn("LocalMean is not used for first frame when seeds are ROIs.");
}
model.algOptions.useLocalMean = false; // do not use LM on first frame (reenable it later)
break;
case QconfFile:
seedImage =
new GenerateMask_("opts={paramFile:(" + model.getQconfFile() + "),binary:false}")
.getRes(); // it throws in case
// and continue to the next case
case MaskImage:
if (seedImage != null && seedImage.equals(image)) {
throw new RandomWalkException("Seed image and segmented image are the same.");
}
double max = new StackStatistics(seedImage).max;
if (max > 255) {
LOGGER.warn("There are more than 255 objects in loaded QCONF file. Only first"
+ " 255 will be segmented");
}
// get seeds split to FG and BG
// this is mask (bigger) so produce seeds, overwrite seeds
// do no scale here as seedImage is 16bit and it would remove some colors. Assume clipping
seeds = propagateSeeds.propagateSeed(
seedImage.getStack().getProcessor(startSlice).duplicate().convertToByte(false),
is.getProcessor(startSlice), model.shrinkPower, model.expandPower);
// mask to local mean
seeds.put(SeedTypes.ROUGHMASK,
seedImage.getStack().getProcessor(startSlice).duplicate().convertToByte(false));
seeds.get(SeedTypes.ROUGHMASK, 1).threshold(0); // to have BW map in case
break;
default:
throw new IllegalArgumentException("Unsupported seed source");
}
// segment first slice (or image if it is not stack)
ImageProcessor retIp = obj.run(seeds);
if (retIp == null) { // segmentation failed, return empty image
LOGGER.error("Segmentation failed - no Foreground maps provided"); // not very important
retIp = new ByteProcessor(image.getWidth(), image.getHeight());
}
model.algOptions.useLocalMean = localMeanUserStatus; // restore status after 1st frame
if (model.hatFilter) {
retIp = applyHatSnakeFilter(retIp, is.getProcessor(startSlice));
}
ret.addSlice(retIp.convertToByte(true)); // store output in new stack
if (model.showPreview) { // display first slice
prev.setProcessor(retIp);
prev.setTitle("Previev - frame: " + 1);
prev.show();
prev.updateAndDraw();
}
// iterate over all slices after first (may not run for one image and for current image seg)
for (int s = 2; s <= is.getSize() && isCanceled == false && oneSlice == false; s++) {
LOGGER.info("----- Slice " + s + " -----");
Seeds nextseed = new Seeds(); // just to remove null warning
obj = new RandomWalkSegmentation(is.getProcessor(s), model.algOptions);
// get seeds from previous result
if (useSeedStack) { // true - use slices
switch (model.getSelectedSeedSource()) {
case RGBImage:
case Rois:
// TODO add support for multislice seeds
throw new RandomWalkException(
"This combination is not supported - for ROI seeds should be selected in "
+ "single image, " + "not a stack");
case QconfFile:
case MaskImage:
// do no scale here as seedImage is 16bit and it would remove some colors. Assume
// clipping
nextseed = propagateSeeds.propagateSeed(
seedImage.getStack().getProcessor(s).duplicate().convertToByte(false),
is.getProcessor(s), model.shrinkPower, model.expandPower);
nextseed.put(SeedTypes.ROUGHMASK,
seedImage.getStack().getProcessor(s).duplicate().convertToByte(false));
nextseed.get(SeedTypes.ROUGHMASK, 1).threshold(0); // to have BW map in case
break;
default:
}
} else { // false - use previous frame
// modify masks and convert to lists
// retIp can be grayscale but it does not matter, return from propagateSeed is BW, each
// object separated
nextseed = propagateSeeds.propagateSeed(retIp, is.getProcessor(s), model.shrinkPower,
model.expandPower);
nextseed.put(SeedTypes.ROUGHMASK, retIp.duplicate());
nextseed.get(SeedTypes.ROUGHMASK, 1).threshold(0); // to have BW map in case
}
// segmentation and results stored for next seeding
retIp = obj.run(nextseed);
if (retIp == null) { // segmentation failed, return empty image
LOGGER.error("Segmentation failed - no Foreground maps provided"); // not very important
retIp = new ByteProcessor(image.getWidth(), image.getHeight());
}
if (model.hatFilter) {
retIp = applyHatSnakeFilter(retIp, is.getProcessor(s));
}
ret.addSlice(retIp); // add next slice
if (model.showPreview) { // show preview remaining slices
prev.setProcessor(retIp);
prev.setTitle("Previev - frame: " + s);
prev.setActivated();
prev.updateAndDraw();
}
IJ.showProgress(s - 1, is.getSize());
}
// convert to ImagePlus and show
segmented = new ImagePlus("Segmented_" + image.getTitle(), ret);
if (!apiCall) {
segmented.show();
segmented.updateAndDraw();
}
// show maps (last used - not stack!)
if (model.showProbMaps == true) {
ProbabilityMaps pm = obj.getProbabilityMaps();
if (pm != null) { // if seg run
ImageStack pmstackfg = pm.convertToImageStack(SeedTypes.FOREGROUNDS);
if (pmstackfg == null) { // if not successful seg
logger.warn("showProbMaps is selected but segmentation returned empty FG maps.");
} else { // if successful seg
ImageStack pmstackbg = pm.convertToImageStack(SeedTypes.BACKGROUND);
// add bg if exists
if (pmstackbg != null) {
for (int s = 1; s <= pmstackbg.size(); s++) {
pmstackfg.addSlice(pmstackbg.getProcessor(s));
}
}
new ImagePlus("Last Probability maps", pmstackfg).show();
} // Successful segm
}
}
// show seeds if selected and not stack seeds (throw must be last
if (model.showSeeds) {
if (useSeedStack == true) {
switch (model.getSelectedSeedSource()) {
case QconfFile:
case MaskImage:
if (oneSlice) { // have stack but want only one slice
propagateSeeds.getCompositeSeed(image.duplicate(), startSlice).show();
} else { // have stack and segmented stack
propagateSeeds.getCompositeSeed(image.duplicate(), 0).show();
}
break;
default:
LOGGER.warn("Effective seeds are not displayed if"
+ " initial seeds are provided as stack");
}
} else {
propagateSeeds.getCompositeSeed(image.duplicate(), 0).show();
}
}
} catch (QuimpPluginException rwe) {
// if (!(rwe instanceof RandomWalkException)) { // RandomWalkException has set proper sink
rwe.setMessageSinkType(errorSink);
// }
rwe.handleException(view.getWnd(), "Segmentation problem.");
} catch (Exception e) {
LOGGER.debug(e.getMessage(), e);
IJ.error("Random Walk Segmentation error",
e.getClass().getSimpleName() + ": " + e.getMessage());
} finally {
isRun = false; // segmentation stopped
IJ.showProgress(2, 1); // erase progress bar
if (prev != null) {
prev.close();
}
model.algOptions.useLocalMean = localMeanUserStatus; // restore status
}
}
/**
* Retrieve result of segmentation.
*
* <p>Valid after {@link #runPlugin()}.
*
* @return result of segmentation
*/
public ImagePlus getResult() {
return segmented;
}
/**
* Helper method, applies HSF.
*
* @param retIp image to filter (mask)
* @param orIp original image
* @return Filtered processor
* @throws QuimpPluginException on problem with HatSnakeFilter
*/
private ImageProcessor applyHatSnakeFilter(ImageProcessor retIp, ImageProcessor orIp)
throws QuimpPluginException {
RandomWalkModel model = (RandomWalkModel) options;
BinarySegmentation obj = new BinarySegmentation(new ImagePlus("", retIp));
obj.trackObjects(); // run tracking
ArrayList<ArrayList<SegmentedShapeRoi>> ret = obj.getChains();
for (ArrayList<SegmentedShapeRoi> asS : ret) {
for (SegmentedShapeRoi ss : asS) {
ss.setInterpolationParameters(1, false);
}
}
SegmentedShapeRoi ssR = ret.get(0).get(0); // FIXME possible trap for multi object images
HatSnakeFilter hsf = new HatSnakeFilter(model.window, model.num, model.alev);
hsf.setMode(HatSnakeFilter.CAVITIES);
// dont use interpolation - provide list of points as they are on image
List<Point2d> retf = hsf.runPlugin(ssR.getOutlineasRawPoints(), orIp);
Roi ssRF;
try {
ssRF = new QuimpDataConverter(retf).getSnake(0).asFloatRoi();
} catch (BoaException e) { // lesss than 3 points
e.setMessageSinkType(MessageSinkTypes.IJERROR);
e.handleException(null, "HatSnake Filter failed");
return retIp;
}
ImageProcessor retIptmp = new ByteProcessor(orIp.getWidth(), orIp.getHeight());
retIptmp.setColor(Color.WHITE);
retIptmp.fill(ssRF);
return retIptmp;
}
/**
* About string.
*
* @return About string
*/
@Override
public String about() {
return "Random Walk plugin.\n" + "Author: Piotr Baniukiewicz\n"
+ "mail: p.baniukiewicz@warwick.ac.uk\n"
+ "This plugin does support macro parameters\n";
}
/**
* Swing worker class.
*
* <p>Run segmentation and take care about renaming/blocking UI elements.
*
* @author p.baniukiewicz
*
*/
class RWWorker extends SwingWorker<Object, Object> {
/*
* (non-Javadoc)
*
* @see javax.swing.SwingWorker#doInBackground()
*/
@Override
protected Object doInBackground() throws Exception {
view.setCancelLabel("STOP"); // use cancel to stopping
view.enableUI(false);
runPlugin(); // will update IJ progress bar
view.enableUI(true);
view.setCancelLabel("Cancel");
return true;
}
/*
* (non-Javadoc)
*
* @see javax.swing.SwingWorker#done()
*/
@Override
protected void done() {
try {
get();
} catch (ExecutionException e) {
LOGGER.error(e.getMessage());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
view.enableUI(true);
view.setCancelLabel("Cancel");
}
}
}
}