BinarySegmentation_.java
package com.github.celldynamics.quimp.plugin.binaryseg;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.FileNotFoundException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.celldynamics.quimp.BOAState;
import com.github.celldynamics.quimp.BOA_;
import com.github.celldynamics.quimp.CellStatsEval;
import com.github.celldynamics.quimp.Constrictor;
import com.github.celldynamics.quimp.Nest;
import com.github.celldynamics.quimp.QuimP;
import com.github.celldynamics.quimp.QuimpException;
import com.github.celldynamics.quimp.QuimpException.MessageSinkTypes;
import com.github.celldynamics.quimp.Serializer;
import com.github.celldynamics.quimp.SnakeHandler;
import com.github.celldynamics.quimp.ViewUpdater;
import com.github.celldynamics.quimp.filesystem.DataContainer;
import com.github.celldynamics.quimp.filesystem.FileExtensions;
import com.github.celldynamics.quimp.filesystem.StatsCollection;
import com.github.celldynamics.quimp.geom.SegmentedShapeRoi;
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.IQuimpPluginAttachImagePlus;
import com.github.celldynamics.quimp.plugin.IQuimpPluginAttachNest;
import com.github.celldynamics.quimp.plugin.IQuimpPluginExchangeData;
import com.github.celldynamics.quimp.plugin.IQuimpPluginSynchro;
import com.github.celldynamics.quimp.plugin.ParamList;
import com.github.celldynamics.quimp.plugin.QuimpPluginException;
import com.github.celldynamics.quimp.utils.QuimpToolsCollection;
import ij.IJ;
import ij.ImagePlus;
import ij.WindowManager;
import ij.io.FileInfo;
import ij.io.OpenDialog;
import ij.io.SaveDialog;
import ij.process.ImageProcessor;
/**
* Binary segmentation plugin called from Fiji.
*
* <p>Use {@link BinarySegmentation} for API calls or {@link BinarySegmentation_#run(String)}
*
* <p>This is front-end of {@link BinarySegmentation} used as stand alone plugin and BOA component.
*
* @author p.baniukiewicz
* @see BinarySegmentationView
* @See {@link BinarySegmentationOptions}
*
*/
public class BinarySegmentation_ extends AbstractPluginTemplate implements IQuimpPluginSynchro,
IQuimpPluginAttachNest, IQuimpPluginExchangeData, IQuimpPluginAttachImagePlus {
/**
* The Constant LOGGER.
*/
static final Logger LOGGER = LoggerFactory.getLogger(BinarySegmentation_.class.getName());
private static String thisPluginName = "Generate Qconf";
private Nest nest = null; // reference to Nest object, can be null
private ViewUpdater vu = null; // BOA context for updating it
private BinarySegmentationView bsp = new BinarySegmentationView();
private boolean wasNest = false; // true if attached nest from outside, used to save file or not
private ImagePlus ip = null;
/**
* Default constructor.
*/
public BinarySegmentation_() {
super(new BinarySegmentationOptions(), thisPluginName);
BinarySegmentationOptions opts = (BinarySegmentationOptions) options;
bsp.addApplyListener(new ActionListener() {
/**
* Apply button.
*
* <p>Run plugin in GUI mode, handle exceptions.
*
* @param e event
*/
@Override
public void actionPerformed(ActionEvent e) {
BinarySegmentationOptions opts = (BinarySegmentationOptions) options;
// update config for export, always handle current one
opts.options = bsp.getValues();
try {
runPlugin(); // run after apply
publishMacroString(thisPluginName);
} catch (QuimpException qe) {
qe.setMessageSinkType(errorSink);
qe.handleException(IJ.getInstance(), BinarySegmentation_.class.getSimpleName());
} catch (Exception ee) { // catch all exceptions here
logger.debug(ee.getMessage(), ee);
logger.error("Problem with running plugin: " + ee.getMessage());
}
}
});
bsp.addLoadMaskListener(new ActionListener() {
/**
* Load mask button.
*
* <p>Grab path to image but technically does not load it yet.
*
* @param e event
*/
@Override
public void actionPerformed(ActionEvent e) {
opts.paramFile = ""; // clear path to ask again on new file
OpenDialog od = new OpenDialog("Load mask file", "");
if (od.getPath() != null) { // not cancelled
opts.maskFileName = od.getPath(); // not part of UI, store separately
}
}
});
bsp.addSelectImageListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
opts.paramFile = ""; // clear path to ask again on new file
}
});
opts.options = bsp.getValues(); // store initial values
}
/**
* Constructor for API calls.
*
* <p>Call {@link #runPlugin()} afterwards. Note that {@link AbstractOptionsParser#apiCall} is
* set to true and sink to Console.
*
* @param options configuration options
*/
public BinarySegmentation_(AbstractPluginOptions options) {
super(options, thisPluginName);
apiCall = true;
errorSink = MessageSinkTypes.CONSOLE;
}
/**
* Main plugin logic.
*
* @throws QuimpPluginException on any error, handled by {@link #run(String)}
*/
@Override
protected void runPlugin() throws QuimpPluginException {
bsp.setValues(((BinarySegmentationOptions) options).options); // update GUI
DataContainer dt = null;
BinarySegmentationOptions opts = (BinarySegmentationOptions) options;
LOGGER.debug(opts.toString());
// try to open images selected mask override loaded one (if both specified)
String selectedImage = opts.options.get(BinarySegmentationView.SELECT_MASK);
String selectedOriginalImage = opts.options.get(BinarySegmentationView.SELECT_ORIGINAL_IMAGE);
ImagePlus maskFile = null;
ImagePlus orgFile = null;
Path orgFilePath = null; // depending on source will be read from fileinfo or provided path
if (selectedImage != null && !selectedImage.equals(BOA_.NONE)) {
maskFile = WindowManager.getImage(selectedImage);
} else { // try file if exists
if (opts.maskFileName != null && !opts.maskFileName.isEmpty()) {
maskFile = IJ.openImage(opts.maskFileName);
}
}
if (selectedOriginalImage != null && !selectedOriginalImage.equals(BOA_.NONE)) {
orgFile = WindowManager.getImage(selectedOriginalImage);
if (orgFile != null) { // if window failed try as path
FileInfo orgfileinfo = orgFile.getFileInfo();
orgFilePath = Paths.get(orgfileinfo.directory, orgFile.getTitle());
} else {
orgFile = IJ.openImage(selectedOriginalImage);
orgFilePath = Paths.get(selectedOriginalImage);
}
} else {
orgFilePath = Paths.get(""); // just to show something in exception
}
if (maskFile == null) {
throw new QuimpPluginException("Mask can not be loaded or found.");
}
// here we have maskFile filled
FileInfo maskfileinfo = maskFile.getFileInfo();
// reconstruct BOA structures if called without it
if (wasNest == false) {
nest = new Nest(); // run as plugin outside BOA
// initialise static fields in BOAState, required for nest.addHandlers(ret)
// use mask file but replace to initialise sizes, etc but replace names to org
BOA_.qState = new BOAState(maskFile);
dt = new DataContainer();
dt.BOAState = BOA_.qState;
dt.BOAState.nest = nest;
dt.BOAState.binarySegmentationPlugin = this;
dt.Stats = new StatsCollection();
// try if original image was provided and recalculate Stats, required for BOAfree mode
if (orgFile == null) {
throw new QuimpPluginException(
"Original image " + orgFilePath.toString() + " can not be opened");
}
dt.BOAState.boap.setOrgFile(orgFilePath.toFile());
dt.BOAState.boap.setOutputFileCore(opts.paramFile);
}
LOGGER.debug("Segmentation: " + (maskFile != null ? maskFile.toString() : "null") + "params: "
+ opts.toString());
BinarySegmentation obj = new BinarySegmentation(maskFile); // create segmentation object
obj.trackObjects(); // run tracking
ArrayList<ArrayList<SegmentedShapeRoi>> ret = obj.getChains(); // get results
// set interpolation params for every tracker. They are used when converting from
// SegmentedShapeRoi to points in SnakeHandler
boolean smoothing = opts.options.getBooleanValue(BinarySegmentationView.SMOOTHING2);
int step = opts.options.getIntValue(BinarySegmentationView.STEP2);
LOGGER.debug("step: " + step + " smooth: " + smoothing);
for (ArrayList<SegmentedShapeRoi> asS : ret) {
for (SegmentedShapeRoi ss : asS) {
ss.setInterpolationParameters(step, smoothing);
}
}
if (opts.options.getBooleanValue(BinarySegmentationView.CLEAR_NEST)) {
nest.cleanNest(); // remove old stuff
}
nest.addHandlers(ret); // convert from array of SegmentedShapeRoi to SnakeHandlers
// if run as plugin, original image is not available, use mask instead
if (ip == null) {
ip = maskFile;
}
if (opts.options.getBooleanValue(BinarySegmentationView.RESTORE_SNAKE)) {
Constrictor constrictor = new Constrictor();
for (SnakeHandler sh : nest.getHandlers()) {
for (int f = sh.getStartFrame(); f <= sh.getEndFrame(); f++) {
sh.getBackupSnake(f).calcCentroid(); // actually this is calculated in Snake constr.
sh.getBackupSnake(f).setPositions(); // actually this is calculated in Snake constr.
sh.getBackupSnake(f).updateNormals(true); // calculated in Snake constr. but for other
sh.getBackupSnake(f).getBounds(); // actually this is calculated in Snake constr.
sh.getStoredSnake(f).calcCentroid();
sh.getStoredSnake(f).setPositions();
sh.getStoredSnake(f).updateNormals(true);
sh.getStoredSnake(f).getBounds();
constrictor.constrict(sh.getStoredSnake(f), ip.getStack().getProcessor(f));
constrictor.constrict(sh.getBackupSnake(f), ip.getStack().getProcessor(f));
}
sh.getLiveSnake().calcCentroid();
sh.getLiveSnake().setPositions();
sh.getLiveSnake().updateNormals(true);
sh.getLiveSnake().getBounds();
constrictor.constrict(sh.getLiveSnake(), ip.getStack().getProcessor(sh.getStartFrame()));
}
}
if (vu != null) {
vu.updateView(); // update view if we can
}
if (wasNest == false && vu == null) { // will not execute if run from BOA
// save file, assume if ViewUpdater is not attached we are in standalone mode
Serializer<DataContainer> n = new Serializer<>(dt, QuimP.TOOL_VERSION);
n.setPretty();
List<CellStatsEval> retstat = nest.analyse(orgFile, true);
dt.Stats.copyFromCellStat(retstat);
if (opts.paramFile == null || opts.paramFile.isEmpty()) { // ask for path
String folder = maskfileinfo.directory;
if (folder == null || folder.isEmpty()) {
folder = IJ.getDirectory("current");
}
String name = QuimpToolsCollection.removeExtension(maskFile.getTitle());
SaveDialog sd = new SaveDialog("Save QCONF", folder, name, FileExtensions.newConfigFileExt);
String selPath = sd.getDirectory();
String selName = sd.getFileName();
if (selPath != null && selName != null && !selPath.isEmpty() && !selName.isEmpty()) {
opts.paramFile = Paths.get(selPath, selName).toString();
dt.BOAState.boap.setOutputFileCore(opts.paramFile); // update in BOA
}
}
// save
try {
n.save(opts.paramFile);
} catch (FileNotFoundException e) {
throw new QuimpPluginException(e);
}
}
}
/**
* Return true if window is visible.
*
* @return true if window is visible
*/
public boolean isWindowVisible() {
return bsp.isWindowVisible();
}
/*
* (non-Javadoc)
*
* @see
* com.github.celldynamics.quimp.plugin.IQuimpPluginAttachNest#attachNest(com.github.celldynamics.
* quimp.Nest)
*/
@Override
public void attachNest(Nest data) {
this.nest = data;
wasNest = true;
}
/*
* (non-Javadoc)
*
* @see
* com.github.celldynamics.quimp.plugin.IQuimpPluginSynchro#attachContext(com.github.celldynamics.
* quimp.ViewUpdater)
*/
@Override
public void attachContext(ViewUpdater b) {
vu = b;
}
/*
* (non-Javadoc)
*
* @see com.github.celldynamics.quimp.plugin.PluginTemplate#showUi(boolean)
*/
@Override
public void showUi(boolean val) {
bsp.setValues(((BinarySegmentationOptions) options).options); // update GUI
bsp.showWindow(val);
}
/*
* (non-Javadoc)
*
* @see com.github.celldynamics.quimp.plugin.IQuimpPluginExchangeData#getPluginConfig()
*/
@Override
public ParamList getPluginConfig() {
BinarySegmentationOptions opts = (BinarySegmentationOptions) options;
ParamList tmp = bsp.getValues();
if (opts.maskFileName != null) {
tmp.put(BinarySegmentationView.LOADED_FILE, opts.maskFileName);
}
return tmp;
}
/*
* (non-Javadoc)
*
* @see IQuimpCorePlugin#setPluginConfig(com.github.celldynamics.quimp.plugin.ParamList)
*/
@Override
public void setPluginConfig(ParamList par) throws QuimpPluginException {
bsp.setValues(par); // will not update values kept outside UI in ParamList
}
/*
* (non-Javadoc)
*
* @see com.github.celldynamics.quimp.plugin.IQuimpPluginAttachImage#attachImage(ij.process.
* ImageProcessor)
*/
@Override
public void attachImage(ImageProcessor img) {
ip = new ImagePlus("", img);
}
/*
* (non-Javadoc)
*
* @see
* com.github.celldynamics.quimp.plugin.IQuimpPluginAttachImagePlus#attachImagePlus(ij.ImagePlus)
*/
@Override
public void attachImagePlus(ImagePlus img) {
ip = img;
}
/*
* (non-Javadoc)
*
* @see com.github.celldynamics.quimp.plugin.IQuimpPlugin#about()
*/
@Override
public String about() {
return "Binary segmentation.\n" + "Author: Piotr Baniukiewicz\n"
+ "mail: p.baniukiewicz@warwick.ac.uk";
}
}