Skip to content

Commit

Permalink
v0.2.0 release
Browse files Browse the repository at this point in the history
-implement function to process time frames
-fix issues on MacOS (see
63a5aff
)
-update and fix Readme
  • Loading branch information
sRassmann committed Aug 18, 2020
1 parent 63a5aff commit f32de35
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 75 deletions.
42 changes: 16 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,19 @@ An ImageJ Plugin implementing a modified [Canny 3d Edge Detection](https://en.wi
Developed and optimized for [CiliaQ](https://github.com/hansenjn/CiliaQ).

## Plugin Description

<<<<<<< HEAD

The plugin implements a [custom batch processing handler](https://github.com/sRassmann/imageJ-plugin-template) and performs the following steps:
1. Smoothes the image stack to suppress random noise via a 2D Gaussian blur filter (the z dimension is assumed to have a lower resolution and a blur effect due to confocal imaging)
=======
The plugin implements a [custom batch processing handler](https://github.com/sRassmann/imageJ-plugin-template) and performs the follwoing steps:
1. Smoothes the image stack to supress random noise via a 2D Gaussian blur filter (the z dimension is assumed to have a lower resolution and a blur efect due to confocal imaging)
>>>>>>> branch 'master' of https://github.com/sRassmann/canny3d-thresholder
2. Detect edges using a 3D Sobel kernel
3. Performing a 3D Hysterisis Threshold: All pixels above a defined high threshold are kept, pixel below the defined low threshold are neglected, pixels in between the low and high threshold are only kept if they are connected to pixels above the high threshold.
1. Smoothes the image stack to suppress random noise via a 2D Gaussian blur filter (the z dimension is assumed to have a lower resolution and a blur effect due to confocal imaging).
2. Detects edges using a 3D Sobel kernel.
3. Performs a 3D Hysterisis Threshold: All pixels above a defined high threshold are kept, pixel below the defined low threshold are neglected, pixels in between the low and high threshold are only kept if they are connected to pixels above the high threshold.
High and low thresholds can be defined using either custom values or can be calculated using ImageJ's thresholding methods based on the histogram of the whole stack.
4. Holes encapsulated in all dimensions are filled
<<<<<<< HEAD
4. Holes encapsulated in all dimensions are filled.
5. The original intensity values within the detected objects are kept, whereas the remaining voxels are set to 0. Thus, the output image is pseudo binarized and intensity values within the objects can be evaluated in downstream analysis (e.g. CiliaQ).
=======
5. The original intensity values within the detected objects are kept, wheares the remaining voxels are set to 0. Thus, the output image is pseudo binarized and intensity values within the objects can be evaluated in downstream analysis (e.g. CiliaQ).
>>>>>>> branch 'master' of https://github.com/sRassmann/canny3d-thresholder

The plugin can also save the remaining channels of the stack, thus, can act as an additional channel splitter.

## User Guide

<<<<<<< HEAD

### installation

1. This plugin requires packages included in the [Fiji release](https://imagej.net/Fiji/Downloads) of ImageJ. Core ImageJ might work if the dependencies are installed manually but is not recommended!
Expand All @@ -36,7 +27,7 @@ High and low thresholds can be defined using either custom values or can be calc


### input
As of v0.0.2 the plugin can handle multi-channel (up to 4 channels) TIFF stacks as well as common Bioformats (e.g. Nikon's .nd2 or Olympus' .oib). The tool extracts a defined channel and performs the thresholding and can also save the remaining channels. Thus, the tool can be used as a channel splitter.
The plugin can handle multi-channel (up to 4 channels) timelapse image stacks ("4D") provided either as TIFF stack or as a common Bioformat (e.g. Nikon's .nd2 or Olympus' .oib).

### processing
1. Open ImageJ and select *Plugin*>*SR*>*Canny 3D Thresholder*
Expand All @@ -51,8 +42,8 @@ As of v0.0.2 the plugin can handle multi-channel (up to 4 channels) TIFF stacks
3. If the manual file selection mode was choose a file selection dialog will apear:
* Click on *select files individually* to select single files from the file system.
* The option *select files by pattern* allows to load all files containing a specific character pattern within a defined root directory. Select a directory to start the search in the *Choose directory to start pattern matching* dialog and define the pattern which the name of each file should contain (e.g. '.nd2' for all microscopy files in the folder) - all files containing this character string at least on time will be added to the file list. Further, it is possible to neglect all files containing a certain pattern (e.g. a date) or which are located in directory containing a defined pattern in the name (e.g. directories marked with 'processed'). For more advanced selection (e.g. any date followed by an specific experiment ID) you can choose *Input as Regex* and specify patterns as regular expressions (regex). See [here](https://www.vogella.com/tutorials/JavaRegularExpressions/article.html) for an introduction.
* Within the Multi-File-Manager selection dialog individual files can be manually excluded by selecting the name of the file and pressing *remove selected file*.
=======
* Within the Multi-File-Manager selection dialog individual files can be manually excluded by selecting the name of the file and pressing *remove selected file*.
### Installation

1. This plugin requires packages included in the [Fiji release](https://imagej.net/Fiji/Downloads) of ImageJ. Core ImageJ might work if the dependencies are installed manually but is not recommended!
Expand All @@ -68,25 +59,24 @@ As of v0.0.2 the plugin can handle multi-channel (up to 4 channels) TIFF stacks
1. Open ImageJ and select *Plugin*>*SR*>*Canny 3D Thresholder*
2. In the user dialog the following options are available:
* File selection mode: Allows to use the active (=last used) image in ImageJ (*active image in FIJI*), all images open images (*all images open in FIJI*), or to load a previously defined set of files from .txt containing the full paths to the images in separate lines (*use list*). The pre-selected option (*manual file selection*) lets the user select files in a subsequent dialog (*Multi-File-Manager*).
* Select image input image format: Choose between single channel stack, multi channel stack, or a raw microscopy file. If multi channel stack or raw microscopy file is chosen another dialog the thresholding channel and the set of addtionally saved channels need to be defined in a subsequent dialog.
* Sigma for Gaussian blur: Defines the amount of blurr added to the image as the sigma ("radius") of the Gaussian distribution in pixels.
* Select image input image format: Choose between single channel stack, multi channel stack, or a raw microscopy file. If multi channel stack or raw microscopy file is chosen another dialog the thresholding channel and the set of additionaly saved channels need to be defined in a subsequent dialog.
* Sigma for Gaussian blur: Defines the amount of blur added to the image as the sigma ("radius") of the Gaussian distribution in pixels.
In the test data a sigma of 1 and 0.5 pixels yielded good results for images with 0.1 and 0.2 µm/pixel, respectively. This values might vary with the quality (less noise might allow for less blur and, thus a lower value for sigma).
* Alpha: The parameters defines the sensitivity of the edge detection.
* Method for low and high threshold: Select one of ImageJ's core thresholding algorithms to calculate the low and high threshold or choose *Custom Value* to define a fixed value.
* Method for low and high threshold: Select one of ImageJ's core thresholding algorithms to calculate the low and high threshold or choose *Custom Value* to define a fixed value.
* Output to new Folder: Allows to select a folder where all output data is saved. If this option is not chosen, each output file will be saved in the same directory as the corresponding raw input file.
3. If the manual file selection mode was choose a file selection dialog will apear:
* Click on *select files individually* to select single files from the file system.
* The option *select files by pattern* allows to load all files containing a specific character pattern within a defined root directory. Select a directory to start the search in the *Choose directory to start pattern matching* dialog and define the pattern which the name of each file should contain (e.g. '.nd2' for all microscopy files in the folder) - all files containing this character string at least on time will be added to the file list. Further, it is possible to neglect all files containing a certaing pattern (e.g. a date) or which are located in directory containg a defined pattern in the name (e.g. directories marked with 'processed'). For more advanced selection (e.g. any date followed by an specific experiment ID) you can choose *Input as Regex* and specify patterns as regular expressions (regex). See [here](https://www.vogella.com/tutorials/JavaRegularExpressions/article.html) for an introduction.
* Within the Multi-File-Manager selection dialog indivdual files can be manually excluded by selecting the name of the file and pressing *remove selected file*.
>>>>>>> branch 'master' of https://github.com/sRassmann/canny3d-thresholder
* Within the Multi-File-Manager selection dialog individual files can be manually excluded by selecting the name of the file and pressing *remove selected file*.
* Press '*start processing*' to confirm the set of selected files.
4. A progress bar will appear to inform over the progress in processing. Note: During processing some windows might pop up for some microseconds and the log dialog from ImageJ will appear. Unfortunatly, this happens within the used plugins, so for now there is no way to turn it off.

### output
If single channel images are processed the thresholded images are saved with suffix '*_canny3d.tif*' as a 16 bit grayscale image.

If multi channel images are processed the separated channels are stored with the suffix '*_C*<*channel index*>*.tif*' . The thresholded images are stored as '*_C*<*channel index*>*_canny3d.tif*'.
If multi channel images are processed the separated channels are stored with the suffix '*_C*<*channel index*>*.tif*'. The thresholded images are stored as '*_C*<*channel index*>*_canny3d.tif*'.

Jointly with the files a log file (suffix '*_C*<*channel index*>*_canny3d_log.txt*') is saved.
A log file (suffix '*_C*<*channel index*>*_canny3d_log.txt*') is saved jointly with the output files.


2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<version>17.0.0</version>
<relativePath />
</parent>
<version>0.1.0</version>
<version>0.2.0</version>

<url>https://github.com/sRassmann/canny3d-thresholder</url>
<inceptionYear>2020</inceptionYear>
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/canny3d_thresholder/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
public class Main implements PlugIn {

static String pluginName = "Canny 3D Thresholder";
static String pluginVersion = "0.1.0";
static String pluginVersion = "0.2.0";
ProgressDialog progressDialog;
boolean processingDone = false;
boolean continueProcessing = true;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/canny3d_thresholder/ProcessSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public static ProcessSettings initByGD(String pluginName, String pluginVersion)
ProcessSettings.pluginVersion = pluginVersion;

GenericDialog gd = new GenericDialog(pluginName + " - Image Processing Settings");
gd.addMessage(pluginName + " - Version " + pluginVersion + " (© 2020 Sebastian Rassmann)",
gd.addMessage(pluginName + " - Version " + pluginVersion + " (© 2020 SR)",
new Font("Sansserif", Font.BOLD, 14));
gd.addMessage("Insert Processing settings", new Font("Sansserif", Font.PLAIN, 14));

Expand Down
124 changes: 80 additions & 44 deletions src/main/java/canny3d_thresholder/Processing.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import ij.IJ;
import ij.ImagePlus;
import ij.plugin.Duplicator;
import ij.text.TextPanel;

public class Processing {
Expand All @@ -17,8 +18,8 @@ public class Processing {
* @param name name of the image - path/image should be exact path of the
* image to be processed
* @param outputDir Path to dir where the output should be saved
* @param pS ProcessSettings of the task
* @param pD Reference to ProgressDialog
* @param pS ProcessSettings of the task
* @param pD Reference to ProgressDialog
* @return
*/

Expand All @@ -33,18 +34,52 @@ static boolean doProcessing(String path, String name, String outputDir, ProcessS
imp.hide();
ImagePlus thrChannel = splitAndSaveChannels(imp, pS, name, outputDir); // does nothing if plain
// thresholding channel is input
double[][] thresholds = new double[thrChannel.getNFrames()][2];
for (int f = 1; f <= thrChannel.getNFrames(); f++) {
thresholds[f - 1] = processSlice(pD, f, imp.getNFrames(), thrChannel, pS);
}

pD.updateBarText("performing edge detection");
IJ.run(thrChannel, "Gaussian Blur...", "sigma=" + pS.gaussSigma + " stack");
IJ.run(thrChannel, "3D Edge and Symmetry Filter",
IJ.save(thrChannel,
outputDir + System.getProperty("file.separator") + ProcessSettings.removeFileSuffix(name)
+ ((pS.selectedInputFormat == ProcessSettings.INPUTFORMATS[0]) ? ""
: ("_" + ProcessSettings.CHANNELSUFFIX + (pS.thrChannel + 1)))
+ ("_canny3d.tif"));
thrChannel.changes = false;
thrChannel.close();
saveLogFile(path, name, outputDir, pS, thresholds); // TODO modify log file
return true;
}

/**
* performs the image processing logic (modified Canny object detection) on each
* frame, returns the obtained high and low threshold value
*
* @param pD
* @param currentFrame
* @param nFrames
* @param thrChannel
* @param pS
* @return
*/

private static double[] processSlice(ProgressDialog pD, int currentFrame, int nFrames, ImagePlus thrChannel,
ProcessSettings pS) {
pD.updateBarText("Frame " + currentFrame + "/" + nFrames + " - performing edge detection");
ImagePlus slice = new Duplicator().run(thrChannel, 1, 1, 1, thrChannel.getNSlices(), currentFrame,
currentFrame);
IJ.run(slice, "Gaussian Blur...", "sigma=" + pS.gaussSigma + " stack");
IJ.run(slice, "3D Edge and Symmetry Filter",
"alpha=" + pS.cannyAlpha + " radius=10 normalization=10 scaling=2 improved");
slice.changes = false;
slice.close();
ImagePlus edges = IJ.getImage();
thrChannel.changes = false;
thrChannel.hide();
edges.hide();

pD.updateBarText("thresholding image");
pD.updateBarText("Frame " + currentFrame + "/" + nFrames + " - thresholding image");

double[] thresholds = new double[2];
double lowThr, highThr;
if (pS.lowThrAlgorithm == "Custom") {
lowThr = pS.lowThr; // use custom value
Expand All @@ -58,42 +93,41 @@ static boolean doProcessing(String path, String name, String outputDir, ProcessS
IJ.setAutoThreshold(edges, pS.lowThrAlgorithm + " dark stack");
highThr = edges.getProcessor().getMinThreshold(); // calculate from stack
}
thresholds[0] = lowThr;
thresholds[1] = highThr;

IJ.run(edges, "3D Hysteresis Thresholding", "high=" + highThr + " low=" + lowThr);
ImagePlus bin = IJ.getImage();
ImagePlus mask = IJ.getImage();
edges.changes = false;
edges.close();
bin.hide();
mask.hide();

IJ.run(bin, "8-bit", "");
IJ.run(bin, "3D Fill Holes", "");
IJ.run(bin, "16-bit", "");
IJ.run(mask, "8-bit", "");
IJ.run(mask, "3D Fill Holes", "");

rewriteOriginalIntensities(bin, thrChannel);
thrChannel.close();
IJ.save(bin,
outputDir + System.getProperty("file.separator") + ProcessSettings.removeFileSuffix(name)
+ ((pS.selectedInputFormat == ProcessSettings.INPUTFORMATS[0]) ? ""
: ("_" + ProcessSettings.CHANNELSUFFIX + (pS.thrChannel + 1)))
+ ("_canny3d.tif"));
bin.changes = false;
bin.close();
saveLogFile(path, name, outputDir, pS, lowThr, highThr);
return true;
rewriteOriginalIntensities(mask, thrChannel, currentFrame);

mask.changes = false;
mask.close();
return thresholds;
}

private static void rewriteOriginalIntensities(ImagePlus bin, ImagePlus thrChannel) {
int t, z, x, y, stackindex;
for (t = 0; t < bin.getNFrames(); t++) {
for (z = 0; z < bin.getNSlices(); z++) {
stackindex = bin.getStackIndex(1, z + 1, t + 1) - 1; // handling IJs indexing
bin.setSliceWithoutUpdate(stackindex);
thrChannel.setSliceWithoutUpdate(stackindex);
for (y = 0; y < bin.getHeight(); y++) {
for (x = 0; x < bin.getWidth(); x++) {
if (bin.getProcessor().get(x, y) != 0) {
bin.getProcessor().set(x, y, thrChannel.getProcessor().get(x, y));
}
/**
* @param mask binarised image - intensities > 0 are considered positive
* @param thrChannel
* @param frameIndex index in IJ's indexing convention (1 based)
*/
private static void rewriteOriginalIntensities(ImagePlus mask, ImagePlus thrChannel, int frameIndex) {
int z, x, y, stackindexThr, stackindexMask;
for (z = 1; z <= mask.getNSlices(); z++) {
stackindexThr = thrChannel.getStackIndex(1, z, frameIndex) - 1; // handling IJs indexing
stackindexMask = mask.getStackIndex(1, z, 1) - 1;
thrChannel.setSliceWithoutUpdate(stackindexThr);
mask.setSliceWithoutUpdate(stackindexMask);
for (y = 0; y < mask.getHeight(); y++) {
for (x = 0; x < mask.getWidth(); x++) {
if (mask.getStack().getVoxel(x, y, stackindexMask) == 0) {
thrChannel.getStack().setVoxel(x, y, stackindexThr, 0);
}
}
}
Expand Down Expand Up @@ -132,23 +166,25 @@ private static ImagePlus splitAndSaveChannels(ImagePlus imp, ProcessSettings pS,
imp.close();
return thrChannel;
}


private static void saveLogFile(String path, String name, String outputDir, ProcessSettings pS, double lowThr, double highThr) {
private static void saveLogFile(String path, String name, String outputDir, ProcessSettings pS,
double[][] thresholds) {
TextPanel sb = new TextPanel();
sb.append("Log File for: " + path + name);
sb.append("\nprocessed on " + new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss").format(new Date()) + "\n");

sb.append(pS.toString());

sb.append("\nLow Threshold Value (calculated): " + lowThr);
sb.append("High Threshold Value (calculated): " + highThr);


sb.append("\nFrame # Low Threshold High Threshold");
for (int i = 0; i < thresholds.length; i -= -1) {
sb.append("\n" + (i+1) + " " + thresholds[i][0] + " " + thresholds[i][1]);
}

sb.saveAs(outputDir + System.getProperty("file.separator") + ProcessSettings.removeFileSuffix(name)
+ ((pS.selectedInputFormat == ProcessSettings.INPUTFORMATS[0]) ? ""
: ("_" + ProcessSettings.CHANNELSUFFIX + (pS.thrChannel + 1)))
+ ("_canny3d_log.txt"));
+ ((pS.selectedInputFormat == ProcessSettings.INPUTFORMATS[0]) ? ""
: ("_" + ProcessSettings.CHANNELSUFFIX + (pS.thrChannel + 1)))
+ ("_canny3d_log.txt"));

}

}
4 changes: 2 additions & 2 deletions src/main/resources/plugins.config
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
# Name: canny3d_thresholder

# Author: Sebastian Rassmann <[email protected]>
# Version: 0.1.0
# Date: 2020/04/29
# Version: 0.2.0
# Date: 2020/08/18
# Requires: ImageJ

Plugins>SR, "Canny 3D Thresholder", canny3d_thresholder.Main

0 comments on commit f32de35

Please sign in to comment.