Prot_Analysis.java
package com.github.celldynamics.quimp.plugin.protanalysis;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.celldynamics.quimp.Outline;
import com.github.celldynamics.quimp.QParamsQconf;
import com.github.celldynamics.quimp.QuimpException;
import com.github.celldynamics.quimp.QuimpException.MessageSinkTypes;
import com.github.celldynamics.quimp.filesystem.FileExtensions;
import com.github.celldynamics.quimp.plugin.AbstractPluginQconf;
import com.github.celldynamics.quimp.plugin.QuimpPluginException;
import com.github.celldynamics.quimp.plugin.qanalysis.STmap;
import ij.ImagePlus;
// TODO Update UML below
/*
* !>
* @startuml doc-files/Prot_Analysis_1_UML.png
* salt
* {
* {^"Visual tracking"
* {+
* Text field with help
* ...
* // Select points with //
* // CTRL key//
* }
* **Selected:** 4
* {
* [to ROI] | [from ROI]
* }
* [Clear all points]
* { (X) Static | () Dynamic}
* [X] Show tracked point
* [X] Smooth tracks
* ^Outline color ^
* [ ] Open in new image
* [Track ]
* [Clear Overlay ]
* }
* {^"Maps"
* ^Select cell ^
* { [Mot ] | [Convex] | [Fluo] }
* }
* {^"Tables and plots"
* [X] Plot selected
* ^Select cell ^
* { (X) Ch1 | ( ) Ch2 | ( ) Ch3}
* ...
* {
* [X] X-Centr | [ ] Y-Centr
* [ ] Displ | [ ] Distance
* [ ] Direct | [ ] Speed
* [ ] Perim | [ ] Elong
* [ ] Circ | [ ] Area
* ==== | ===
* [ ] Total fl | [ ] Mean fl
* [ ] Cortex wd | [ ] Cyto ar
* [ ] Total ctf | [ ] Mean ctf
* [ ] Cortex ar | [ ] Total ctf
* [ ] Mean ctf |
* }
* ===
* [Generate ]
* }
* {^"Ploar plots"
* [Click point ]
* [Get from ROI ]
* Selected point: 127,45
* ^Select relative to^
* [Show plots ]
* }
* }
* @enduml
*
* @startuml doc-files/Prot_Analysis_2_UML.png
*
* usecase UC0 as "**Load QCONF**
* --
* Open QCONF file
* ..UC0.."
*
* usecase UC1 as "**Select points on contour**
* --
* Use can click and select multiple
* points in cell contour.
* ==
* This works within all frames
* ..UC1..
* "
*
* usecase UC2 as "**Transfer points to ROI**
* --
* Selected points can be
* copied to ROI manager
* ..UC2..
* "
*
* usecase UC3 as "**Transfer points from ROI**
* --
* Copy points from ROI
* manager and show them
* in contour.
* ==
* * Delete old points
* * Deal with different
* frames
* ..UC3..
* "
*
* usecase UC4 as "**Show selected points**
* --
* Show points for each frame
* as user slide slider
* ..UC4..
* "
*
* usecase UC5 as "**Clear points**
* --
* Remove all points
* ..UC5.."
*
* usecase UC6 as "**Track points**
* --
* Perform tracking for
* selected points
* ==
* Regards static or dynamic
* ..UC6..
* "
*
* usecase UC6b as "**Plot intensity**
* --
* Plot intensity change
* over tracking point
* ==
* This hsould be default
* for each tracking
* ..UC6b..
* "
*
* usecase UC6a as "**Save tracks**
* --
* Save tracks after
* tracking to csv
* file
* ==
* ..UC6a..
* "
*
* usecase UC7 as "**Display tracking**
* --
* Show results on screen
* ==
* * Depending on settings show
* in original window or separate
* * Show dynamic or static
* * Color outline
* * Smooth if option selected
* ..UC7..
* "
*
* usecase UC8 as "**Color outline**
* --
* Show outline in selected
* color
* ==
* Colorscale scaled to range
* ..UC8..
* "
*
* usecase UC9 as "**Clear overlay**
* --
* Clear tracking
* ==
* * Clear original window
* * Remove points
* ..UC9.."
*
* usecase UC10 as "**Plot maps**
* --
* Show selected maps
* ==
* Together with **UC10a**
* ROI allows to select
* maxim on the map and
* track them
* ..UC10.."
*
* usecase UC10a as "**Plot raw maps**
* --
* Show selected maps as unscaled
* ==
* Together with transferring
* ROI allows to select
* maxim on the map and
* track them
* ..UC10a.."
*
* usecase UC11 as "**Plot 2d**
* --
* Plot selected metrics as
* 2D plot in function of
* frames
* ==
* * Can open many plots at
* once
* * Should allow to select
* cell and channel
* ..UC11.."
*
* usecase UC12 as "**Copy to table**
* --
* Copy selected metrics to
* IJ table.
* ==
* * Should allow to select
* cell and channel
* ..UC12..
* "
*
* usecase UC13 as "**Polar plots**
* --
* Generate polar plots
* ==
* * Save or show (depending on IJ
* features in showing vector files)
* * Show in log if saved
* ..UC13..
* "
*
* usecase UC14 as "**Select origin point**
* --
* Allow to select origin
* point for polar plots
* ==
* * click on screen
* * Relative to screen
* * Relative to cell
* ..UC14..
* "
*
* usecase UC15 as "**Predefined trackings**
* --
* Allow to track points
* from predefined settings
* ==
* Like:
* * Max from motility map
* ..UC15..
* "
*
* usecase UC16 as "**Smooth tracks**
* --
* Apply smoothing to tracks
* ==
* * If option selected
* ..UC16..
* "
*
* note bottom of (UC12) : Decide how to deal\nwith many tables
*
* note right of (UC8)
* Decide if standalone
* Now dependend from UC7
* Tracking must be done first and
* tracking map shown but this can
* be a standalone option as well
* end note
*
* User -> (UC0)
* User -> (UC1)
* (UC1) ..> (UC4) : <<include>>
* User -> (UC12)
* User -> (UC5)
* User -> (UC3)
* (UC3) ..> (UC5) : <<extend>>
* User -> (UC2)
* User -> (UC6)
* (UC6) ..> (UC7) : <<include>>
* (UC6) ..> (UC6a) : <<include>>
* (UC6) ..> (UC6b) : <<include>>
* (UC6) ..> (UC15) : <<include>>
* (UC7) ..> (UC8) : <<include>>
* (UC7) ..> (UC16) : <<include>>
* User -> (UC9)
* (UC9) ..> (UC5) : <<extend>>
* User --> (UC10)
* (UC10) ..> (UC10a) : <<include>>
* User --> (UC11)
* User --> (UC13)
* (UC13) ..> (UC14) : <<include>>
* @enduml
* !<
*/
/**
* Main class for Protrusion Analysis module.
*
* <p>Contain business logic for protrusion analysis. The UI is built by
* {@link com.github.celldynamics.quimp.plugin.protanalysis.ProtAnalysisUi}. The communication
* between
* these modules is through
* {@link com.github.celldynamics.quimp.plugin.protanalysis.ProtAnalysisOptions}
*
* <br>
* <img src="doc-files/Prot_Analysis_1_UML.png"/><br>
*
* <br>
* <img src="doc-files/Prot_Analysis_2_UML.png"/><br>
*
* @author p.baniukiewicz
*/
public class Prot_Analysis extends AbstractPluginQconf {
static final Logger LOGGER = LoggerFactory.getLogger(Prot_Analysis.class.getName());
private static String thisPluginName = "Protrusion Analysis";
ImagePlus image = null;
// points selected by user for current frame, cleared on each slice shift. In image coordinates
PointHashSet selected = new PointHashSet();
// updated on each slice, outlines for current frame
ArrayList<Outline> outlines = new ArrayList<>();
/**
* Instance of module UI.
*
* <p>Initialised by this constructor.
*/
ProtAnalysisUi frameGui;
/**
* Current frame, 0-based.
*/
int currentFrame = 0;
/**
* Default constructor.
*
*/
public Prot_Analysis() {
// here we do not load file! so can not create ui
super(new ProtAnalysisOptions(), thisPluginName);
selected = new PointHashSet();
outlines = new ArrayList<>();
}
/**
* Constructor that allows to provide own configuration parameters.
*
* <p>Immediately executes all computations.
*
* @param paramString parameter string.
* @throws QuimpPluginException on error
*/
public Prot_Analysis(String paramString) throws QuimpPluginException {
// 1. Load File
// 2. Run runFormQconf (but we overwritten it here to be empty so nothing happens yet)
super(paramString, new ProtAnalysisOptions(), thisPluginName);
selected = new PointHashSet();
outlines = new ArrayList<>();
createUIInstance();
}
/**
* Create UI instance.
*
* <p>Require loaded file.
*/
private void createUIInstance() {
ImagePlus image = getImage(); // obain image from loaded file
LOGGER.trace("Attached image " + image.toString());
frameGui = new ProtAnalysisUi(this, image); // build UI (we need image)
}
/**
* get current sink.
*
* @return sink type
*/
MessageSinkTypes getSink() {
return errorSink;
}
/*
* (non-Javadoc)
*
* @see com.github.celldynamics.quimp.plugin.PluginTemplate#validate()
*/
@Override
protected void validate() throws QuimpException {
super.validate();
qconfLoader.getEcmm();
qconfLoader.getQ();
qconfLoader.getStats();
}
/**
* Write cell statistic and protrusion statistics to files.
*
* <p>Currently not used might be useful. Example of use:
*
* <pre>
* <code>
* // write stats, and add to table
* writeStats(h, mapCell, mf, trackCollection).cellStatistics.addCellToCellTable(rt);
* </code>
* </pre>
*
* @param h Cell number
* @param mapCell cell map
* @param mf maxima finder object
* @param trackCollection track collection object
* @return ProtStat instance of object that keeps cell statistics. Can be used to form e.g.
* table with results.
*
* @throws FileNotFoundException if stats can not be written
*/
private ProtStat writeStats(int h, STmap mapCell, MaximaFinder mf,
TrackCollection trackCollection) throws FileNotFoundException {
QParamsQconf qp = (QParamsQconf) qconfLoader.getQp();
// Maps are correlated in order with Outlines in DataContainer.
// write data
PrintWriter cellStatFile = new PrintWriter(
Paths.get(qp.getPath(), qp.getFileName() + "_" + h + FileExtensions.cellStatSuffix)
.toFile());
PrintWriter protStatFile = new PrintWriter(
Paths.get(qp.getPath(), qp.getFileName() + "_" + h + FileExtensions.protStatSuffix)
.toFile());
new ProtStat(mf, trackCollection, qp.getLoadedDataContainer().getStats().sHs.get(h), mapCell)
.writeProtrusion(protStatFile, h);
ProtStat cellStat = new ProtStat(mf, trackCollection,
qp.getLoadedDataContainer().getStats().sHs.get(h), mapCell);
cellStat.writeCell(cellStatFile, h);
protStatFile.close();
cellStatFile.close();
return cellStat;
}
/*
* (non-Javadoc)
*
* @see com.github.celldynamics.quimp.plugin.AbstractPluginBase#showUi(boolean)
*/
@Override
public void showUi(boolean val) throws Exception {
// we need to load file here because UI require image from QCONF
// execute this only if run with default constructor and empty run("") method (plugin from menu)
// if (options.paramFile == null || options.paramFile.isEmpty()) {
// loadFile(options.paramFile);
// if (qconfLoader != null && qconfLoader.getQp() != null) {
// options.paramFile = qconfLoader.getQp().getParamFile().getAbsolutePath();
// }
// createUIInstance();
// }
if (frameGui != null) {
frameGui.showUI(true);
// gui.setVisible(true);
} else {
LOGGER.error("You need image (and QCONF) to see UI");
}
}
/*
* (non-Javadoc)
*
* @see com.github.celldynamics.quimp.plugin.IQuimpPlugin#about()
*/
@Override
public String about() {
return "Protrusion Analysis Plugin.\n" + "Author: Piotr Baniukiewicz\n"
+ "mail: p.baniukiewicz@warwick.ac.uk";
}
/*
* (non-Javadoc)
*
* @see ij.plugin.PlugIn#run(java.lang.String)
*/
@Override
public void run(String arg) {
super.run(arg);
}
/*
* (non-Javadoc)
*
* @see com.github.celldynamics.quimp.plugin.AbstractOptionsParser#parseArgumentString(java.lang.
* String)
*/
@Override
protected boolean parseArgumentString(String arg) throws QuimpPluginException {
// override only to get always true at output, hack that will cause execute executer in run()
// method, and then LoadFile and runFromQconf. We need this because we do not support parameters
// and want to see UI every time but we need loaded file to build UI.
// With this approach we have the same path for IJ plugin run and IJ macro run (only file):
// run like IJ macro - scenario 1
// Prot_Analysis pa = new Prot_Analysis();
// pa.run("");
// IJ from script - scenario 2
// Prot_Analysis pa = new Prot_Analysis();
// pa.run("{paramFile:src/test/Resources-static/ProtAnalysisTest/fluoreszenz-test.QCONF}");
// Without this hack scenario 1 will try to open UI (showUI) without loaded file.
super.parseArgumentString(arg);
return true;
}
@Override
protected void runFromPaqp() throws QuimpException {
throw new QuimpException("This plugin does not support paQP files.");
}
/*
* (non-Javadoc)
*
* @see com.github.celldynamics.quimp.plugin.AbstractPluginQconf#runFromQconf()
*/
@Override
protected void runFromQconf() throws QuimpException {
// we do not support run from macro so just show UI here
createUIInstance();
try {
showUi(true);
} catch (Exception e) {
throw new QuimpException(e);
}
}
/**
* Get image associated with loaded QCONF.
*
* @return image or null if image could not be loaded
*/
ImagePlus getImage() {
if (image == null) {
if (getQconfLoader() != null) {
image = getQconfLoader().getImage();
} else {
throw new RuntimeException("Can not obtain image");
}
}
return image;
}
/**
* Get gui.
*
* @return Main window class.
*/
ProtAnalysisUi getGui() {
return frameGui;
}
/**
* Keep list of selected points.
*
* <p>Reason of this class is that {@link ProtAnalysisUi} and {@link CustomCanvas} operate on
* 2D
* images without knowledge about frame, which is needed. They also use java.awt.Point as main
* class. Therefore the point selected in the image by user in {@link CustomCanvas} contains only
* x,y and cell number (all stored in {@link PointCoords}). Frame number is appended with
* {@link PointHashSet#add(PointCoords)}, called in this context (frame is stored in
* Prot_Analysis.currentFrame)
*
* <p>Field {@link Prot_Analysis#currentFrame} is updated by {@link ProtAnalysisUi} whereas
* point operations happen in {@link CustomCanvas}. {@link Prot_Analysis} integrates all
* informations.
*
* @author p.baniukiewicz
*
*/
@SuppressWarnings("serial")
class PointHashSet extends HashSet<PointCoords> {
/**
* Add information about current frame to point.
*
* @param e 2D point from current frame. Field {@link PointCoords#frame} will be overwritten by
* current frame.
* @return true if point exists in set.
*/
@Override
public boolean add(PointCoords e) {
e.frame = currentFrame;
LOGGER.debug("Added point: " + e);
return super.add(e);
}
/**
* Add {@link PointCoords} with frame number.
*
* <p>In contrary to {@link #add(PointCoords)} this method does not override frame number in
* specified {@link PointCoords}.
*
* @param e 2D point from current frame.
* @return true if point exists in set.
*/
boolean addRaw(PointCoords e) {
LOGGER.debug("Added raw point: " + e);
return super.add(e);
}
/**
* Remove point from set.
*
* @param e point to remove
* @return true if exist
*/
public boolean remove(PointCoords e) {
e.frame = currentFrame;
return super.remove(e);
}
}
}