Compare commits

...

42 Commits

Author SHA1 Message Date
Damien Goutte-Gattat ce283aba22 [maven-release-plugin] prepare for next development iteration 2 months ago
Damien Goutte-Gattat 6c35f22a01 [maven-release-plugin] prepare release incenp-imagej-plugins-0.9.6 2 months ago
Damien Goutte-Gattat b35fd4d14b Match header names in a case-insensitive manner by default. 2 months ago
Damien Goutte-Gattat 90f35b8bac Allow several names when looking for a specific cell. 2 months ago
Damien Goutte-Gattat 8a7339a2e8 [maven-release-plugin] prepare for next development iteration 2 months ago
Damien Goutte-Gattat b4a710ebbc [maven-release-plugin] prepare release incenp-imagej-plugins-0.9.5 2 months ago
Damien Goutte-Gattat 48fe51eb13 Update documentation. 2 months ago
Damien Goutte-Gattat f8c4267e18 Add the SciJava repository. 2 months ago
Damien Goutte-Gattat 1792a18875 Always call readCSV() if needed. 2 months ago
Damien Goutte-Gattat 7527b2e42f Bump dependencies. 2 months ago
Damien Goutte-Gattat ca7fa551bc Update documentation. 9 months ago
Damien Goutte-Gattat da77c7abc1 [maven-release-plugin] prepare for next development iteration 9 months ago
Damien Goutte-Gattat 3408d2b40a [maven-release-plugin] prepare release incenp-imagej-plugins-0.9.4 9 months ago
Damien Goutte-Gattat a6231d61e4 Prepare release to Maven Central. 9 months ago
Damien Goutte-Gattat c4ef652228 Add some more Maven plugins. 9 months ago
Damien Goutte-Gattat d1373bdfc0 Rename to incenp-imagej-plugins. 9 months ago
Damien Goutte-Gattat 3b2365967b [maven-release-plugin] prepare for next development iteration 10 months ago
Damien Goutte-Gattat e910fc440f [maven-release-plugin] prepare release incenp-plugins-0.9.3 10 months ago
Damien Goutte-Gattat 54f8063b3d Update documentation and copyright notices. 10 months ago
Damien Goutte-Gattat 454c1ff3f2 Fix the augmentHyperstack method. 10 months ago
Damien Goutte-Gattat aa1e4d776d Allow multiple mask applications. 10 months ago
Damien Goutte-Gattat 65a277a6ff Add the augmentHyperstack method. 10 months ago
Damien Goutte-Gattat 85fb7e7d65 Do not update display when creating an augmented hyperstack. 10 months ago
Damien Goutte-Gattat ada2ffacd4 Fix the extractVolumes helper method. 10 months ago
Damien Goutte-Gattat 3c71d8179e Bump ImageJ dependency. 11 months ago
Damien Goutte-Gattat cf9861b4e3 Accept alternative spelling for the Renyi_Entropy algorithm. 1 year ago
Damien Goutte-Gattat bf655ea73d [maven-release-plugin] prepare for next development iteration 1 year ago
Damien Goutte-Gattat 0b48f79d0e [maven-release-plugin] prepare release incenp-plugins-0.9.2 1 year ago
Damien Goutte-Gattat 727adc335a Update NEWS file. 1 year ago
Damien Goutte-Gattat e5490b6263 Fix typo in library documentation. 1 year ago
Damien Goutte-Gattat 92b240f10f Add the createAugmentedHyperstack helper method. 1 year ago
Damien Goutte-Gattat ba40f5f040 Do not set the "Stitch Tiles" option. 2 years ago
Damien Goutte-Gattat 9778b3db74 Use explicit importer options when processing batch file. 2 years ago
Damien Goutte-Gattat 810d58f029 [maven-release-plugin] prepare for next development iteration 2 years ago
Damien Goutte-Gattat 6f630e502e [maven-release-plugin] prepare release incenp-plugins-0.9.1 2 years ago
Damien Goutte-Gattat 6cff47c4f2 Prepare next release. 2 years ago
Damien Goutte-Gattat f84ca5c2e3 Document the helper library. 2 years ago
Damien Goutte-Gattat 007ea2d464 Add convenient constructors to the BatchReader class. 2 years ago
Damien Goutte-Gattat f034a3d519 BatchReader: Ignore empty lines. 2 years ago
Damien Goutte-Gattat f723ef8ba5 UnifiedMerge: Set source processor to grayscale if needed. 2 years ago
Damien Goutte-Gattat baba6b86d9 Update documentation. 2 years ago
Damien Goutte-Gattat 08fa631899 [maven-release-plugin] prepare for next development iteration 2 years ago
  1. 36
      NEWS
  2. 31
      README.md
  3. 92
      pom.xml
  4. 106
      src/main/java/org/incenp/imagej/BatchReader.java
  5. 2
      src/main/java/org/incenp/imagej/BinaryOperator.java
  6. 32
      src/main/java/org/incenp/imagej/ChannelMasker.java
  7. 66
      src/main/java/org/incenp/imagej/Helper.java
  8. 2
      src/main/java/org/incenp/imagej/Masking.java
  9. 2
      src/main/java/org/incenp/imagej/NucleiSegmenter.java
  10. 4
      src/main/java/org/incenp/imagej/ThresholdingMethod.java
  11. 2
      src/main/java/org/incenp/imagej/plugins/About.java
  12. 2
      src/main/java/org/incenp/imagej/plugins/ExtendedPanel.java
  13. 2
      src/main/java/org/incenp/imagej/plugins/FrameIntervalInfo.java
  14. 6
      src/main/java/org/incenp/imagej/plugins/Info.java
  15. 2
      src/main/java/org/incenp/imagej/plugins/Launcher.java
  16. 2
      src/main/java/org/incenp/imagej/plugins/MergeDialog.java
  17. 2
      src/main/java/org/incenp/imagej/plugins/PanelDialog.java
  18. 7
      src/main/java/org/incenp/imagej/plugins/UnifiedMerge.java
  19. 20
      src/site/apt/index.apt
  20. 310
      src/site/apt/library.apt
  21. 5
      src/site/site.xml

36
NEWS

@ -1,3 +1,39 @@
Changes in incenp-imagej-plugins-0.9.5
--------------------------------------
* Remove the need to explicitly call BatchReader.readCSV().
* Explicitly refer to the SciJava Maven repository in POM.
* Require more recent ImageJ (needed for MacOS development).
Changes in incenp-imagej-plugins-0.9.4
--------------------------------------
* Rename the artifact to incenp-imagej-plugins.
* Release to the Maven Central repository.
Changes in incenp-plugins-0.9.3
-------------------------------
* Allow application of multiple masks in a single operation.
* Accept alternative spelling for the Renyi_Entropy algorithm.
* Small bugfixes in methods from the Helper class.
Changes in incenp-plugins-0.9.2
-------------------------------
* Use explicit importer options when processing batch file.
Changes in incenp-plugins-0.9.1
-------------------------------
* UnifiedMerge: set source image to grayscale if needed.
* BatchReader: Ignore empty lines.
Changes in incenp-plugins-0.9.0
-------------------------------

31
README.md

@ -1,7 +1,7 @@
Incenp-Plugins - Incenp.org ImageJ Plugins
==========================================
Incenp.org ImageJ Plugins
=========================
Incenp-Plugins is a set of plugins for the [ImageJ](https://imagej.net)
Incenp-ImageJ-Plugins is a set of plugins for the [ImageJ](https://imagej.net)
image analysis software.
Available Plugins
@ -18,24 +18,39 @@ This plugin extracts an user-specified selection of frames from a
hyperstack and generates a new image containing the selected frames
disposed in a array.
Helper Library
--------------
Bundled with the plugins is a small library of helper classes for use in
other plugins or scripts. Notable classes include `BatchReader`, to
facilitate batch processing of images listed in a CSV input file, and
`ChannelMasker`, to facilitate the creation and manipulation of
binary masks. Refer to the package´s
[Javadoc](https://incenp.org/dvlpt/imagej-plugins/apidocs/index.html)
for more details.
Installation
------------
Build the plugins with [Maven](https://maven.apache.org/) by running
Build the plugins with [Maven](https://maven.apache.org/) by running,
from the source directory
```
$ mvn package
```
in the source directory. Then copy the generated JAR file (found in the
`target/` folder) to the `plugins` folder of your ImageJ installation.
Then copy the generated JAR file (found in the `target/` folder) to the
`plugins` folder of your ImageJ installation.
(Pre-compiled JAR files are also available for each release on the
[releases page](https://git.incenp.org/damien/imagej-plugins/releases)
for the project.)
Once installed (and after restarting ImageJ if it was already running),
the plugins will be available in the `Plugins > Incenp.org` menu.
Copying
-------
Incenp-Plugins is distributed under the terms of the GNU General Public
License, version 3 or higher. The full license is included in the
Incenp-ImageJ-Plugins is distributed under the terms of the GNU General
Public License, version 3 or higher. The full license is included in the
[LICENSE file](LICENSE) of the source distribution.
Homepage and repository

92
pom.xml

@ -1,14 +1,9 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.incenp</groupId>
<artifactId>incenp-base</artifactId>
<version>1.1.0</version>
</parent>
<groupId>org.incenp.imagej</groupId>
<artifactId>incenp-plugins</artifactId>
<version>0.9.0</version>
<groupId>org.incenp</groupId>
<artifactId>incenp-imagej-plugins</artifactId>
<version>0.9.7-SNAPSHOT</version>
<name>Incenp.org ImageJ Plugins</name>
<description>Some plugins for ImageJ</description>
<url>https://incenp.org/dvlpt/imagej-plugins/</url>
@ -34,34 +29,48 @@
<connection>scm:git:https://git.incenp.org/damien/imagej-plugins.git</connection>
<developerConnection>scm:git:ssh://git@git.incenp.org/damien/imagej-plugins.git</developerConnection>
<url>https://git.incenp.org/damien/imagej-plugins</url>
<tag>incenp-plugins-0.9.0</tag>
<tag>HEAD</tag>
</scm>
<issueManagement>
<system>Gitea</system>
<url>https://git.incenp.org/damien/imagej-plugins/issues</url>
</issueManagement>
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>scijava.public</id>
<url>https://maven.scijava.org/content/groups/public</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>net.imagej</groupId>
<artifactId>imagej</artifactId>
<version>2.0.0-rc-71</version>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>net.imagej</groupId>
<artifactId>imagej-legacy</artifactId>
<version>0.35.0</version>
<version>0.37.4</version>
</dependency>
<dependency>
<!-- For ImageJ2 plugin API -->
<groupId>org.scijava</groupId>
<artifactId>scijava-common</artifactId>
<version>2.77.0</version>
<version>2.85.0</version>
</dependency>
<dependency>
<!-- For util.opencsv -->
@ -88,7 +97,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.2</version>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
@ -114,12 +123,63 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.8.2</version>
<version>3.9.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
<version>3.1.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.8</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
</build>
@ -129,7 +189,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.1.1</version>
<version>3.2.0</version>
</plugin>
</plugins>
</reporting>

106
src/main/java/org/incenp/imagej/BatchReader.java

@ -1,5 +1,5 @@
/*
* Incenp.org Plugins
* Incenp.org ImageJ Plugins
* Copyright © 2019, 2020 Damien Goutte-Gattat
*
* This program is free software; you can redistribute it and/or modify
@ -28,6 +28,7 @@ import ij.ImagePlus;
import ij.measure.ResultsTable;
import loci.formats.FormatException;
import loci.plugins.BF;
import loci.plugins.in.ImporterOptions;
import util.opencsv.CSVReader;
/**
@ -63,6 +64,7 @@ public class BatchReader {
private String filename;
private String[] headers;
private boolean withHeaders;
private boolean caseSensitiveHeaders;
private ArrayList<String[]> rows;
private ImagePlus[] currentImages;
private Hashtable<String, Integer> headersIndex;
@ -77,6 +79,7 @@ public class BatchReader {
public BatchReader(String filename, boolean noHeaders) {
this.filename = filename;
this.withHeaders = !noHeaders;
caseSensitiveHeaders = false;
headers = null;
headersIndex = null;
cursor = subcursor = -1;
@ -85,7 +88,7 @@ public class BatchReader {
}
/**
* Creates a new reader
* Creates a new reader.
*
* @param filename pathname to the file to read
*/
@ -93,6 +96,39 @@ public class BatchReader {
this(filename, false);
}
/**
* Creates a new reader.
*
* @param file the file object to read from
*/
public BatchReader(File file) {
this(file.getAbsolutePath(), false);
}
/**
* Creates a new reader.
*
* @param file the file object to read from
* @param noHeaders if true, the first non-comment line will not be treated as a
* header line
*/
public BatchReader(File file, boolean noHeaders) {
this(file.getAbsolutePath(), noHeaders);
}
/**
* Sets the case-sensitivity option. If set to true, the
* {@link #getCell(String)} method and its overloaded variants will match header
* names in a case-sensitive way. The default is false.
*
* @param caseSensitive true to match header names case-sensitively.
*/
public void setCaseSensitive(boolean caseSensitive) {
caseSensitiveHeaders = caseSensitive;
if ( headersIndex != null )
headersIndex = null;
}
/**
* Moves to the next available image. Once this method has been called and if it
* returned true, the image can be obtained by calling the {@link #getImage()}
@ -120,7 +156,10 @@ public class BatchReader {
return false;
try {
currentImages = BF.openImagePlus(rows.get(cursor)[0]);
ImporterOptions options = new ImporterOptions();
options.setId(rows.get(cursor)[0]);
options.setOpenAllSeries(true);
currentImages = BF.openImagePlus(options);
subcursor = 0;
} catch ( FormatException | IOException e ) {
return false;
@ -190,29 +229,62 @@ public class BatchReader {
}
/**
* Gets the contents of a specific cell within the current row.
* Gets the contents of a specific cell within the current row. The cell is
* searched for using several equally accepted names.
*
* @param name the column name, as indicated in the header row
* @param names an array of accepted column names
* @param fallback the default value to return if the cell is not found
* @return the contents of the specified cell
*/
public String getCell(String name) {
public String getCell(String[] names, String fallback) {
if ( headersIndex == null ) {
if ( (headersIndex = getHeadersIndex()) == null )
return null;
}
Integer indexObject = headersIndex.get(name);
if ( indexObject == null )
return "";
Integer index = null;
for ( int i = 0; i < names.length && index == null; i++ ) {
if ( !caseSensitiveHeaders )
index = headersIndex.get(names[i].toLowerCase());
else
index = headersIndex.get(names[i]);
}
if ( index == null )
return fallback;
return getCell(index.intValue());
}
/**
* Gets the contents of a specific cell within the current row. The cell is
* searched for using several equally accepted names.
*
* @param names an array of accepted column names
* @return the contents of the specified cell
*/
public String getCell(String[] names) {
return getCell(names, "");
}
return getCell(indexObject.intValue());
/**
* Gets the contents of a specific cell within the current row.
*
* @param name the column name, as indicated in the header row
* @return the contents of the specified cell
*/
public String getCell(String name) {
return getCell(new String[] { name }, "");
}
private Hashtable<String, Integer> getHeadersIndex() {
if ( headersIndex == null && headers != null ) {
headersIndex = new Hashtable<String, Integer>();
for ( int i = 0; i < headers.length; i++ ) {
headersIndex.put(headers[i], i);
if ( !caseSensitiveHeaders )
headersIndex.put(headers[i].toLowerCase(), i);
else
headersIndex.put(headers[i], i);
}
}
@ -257,7 +329,11 @@ public class BatchReader {
*
* @return the CSV file headers
*/
public String[] getHeaders() {
public String[] getHeaders() throws IOException {
if ( rows == null ) {
this.readCSV();
}
if ( headers == null ) {
headers = new String[1];
headers[0] = "Image";
@ -302,9 +378,7 @@ public class BatchReader {
/**
* Reads the entire CSV file. This method is automatically called by the
* {@link #next()} method if needed. However it is necessary to explicitly call
* it if the CSV headers need to be accessed (through the {@link #getHeaders()}
* method) prior to any call to next().
* {@link #next()} or {@link #getHeaders()} methods if needed.
*
* @throws IOException if any I/O error occurs while attempting to read the file
*/
@ -315,7 +389,7 @@ public class BatchReader {
CSVReader reader = new CSVReader(new FileReader(filename));
String[] line;
while ( (line = reader.readNext()) != null ) {
if ( line[0].charAt(0) == '#' )
if ( line[0].length() == 0 || line[0].charAt(0) == '#' )
continue;
if ( withHeaders && rows.size() == 0 && headers == null ) {

2
src/main/java/org/incenp/imagej/BinaryOperator.java

@ -1,5 +1,5 @@
/*
* Incenp.org Plugins
* Incenp.org ImageJ Plugins
* Copyright © 2020 Damien Goutte-Gattat
*
* This program is free software; you can redistribute it and/or modify

32
src/main/java/org/incenp/imagej/ChannelMasker.java

@ -1,6 +1,6 @@
/*
* Incenp.org Plugins
* Copyright © 2019, 2020 Damien Goutte-Gattat
* Incenp.org ImageJ Plugins
* Copyright © 2019, 2020, 2021 Damien Goutte-Gattat
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -164,10 +164,14 @@ public class ChannelMasker {
break;
case APPLY:
BinaryOperator operator = operators[op.argument[op.argument.length - 1]];
src = image.getProcessor().duplicate();
image.setC(getChannel(op.argument[0], order));
ip = Masking.applyMask(src, image.getProcessor(), operators[op.argument[1]],
maskingOptions | Masking.NO_DUPLICATE);
for ( int m = 0; m < op.argument.length - 1; m++ ) {
ip = src;
image.setC(getChannel(op.argument[m], order));
Masking.applyMask(ip, image.getProcessor(), operator,
maskingOptions | Masking.NO_DUPLICATE);
}
break;
}
@ -236,9 +240,10 @@ public class ChannelMasker {
* threshold to use (when using the FIXED method) or the radius to consider when
* using one of the local thresholding methods (defaulting to 15 if no value is
* specified).
* <li>"X:APPLY(Y[,OP])": Apply the binary mask found in channel number Y to
* channel number X, using the binary operator OP which can be one of AND, NAND,
* OR, NOR, XOR, or XNOR. If no operator is specified, the default is AND.
* <li>"X:APPLY(Y[,Z...],OP)": Apply the binary mask found in channel number Y,
* Z, etc. to channel number X, using the binary operator OP which can be one of
* AND, NAND, OR, NOR, XOR, or XNOR. If only one channel is specified, the
* operator may be omitted, in which case it defaults to AND.
* <li>"X:INVERT()": Invert channel number X of the source image.
* </ul>
* <p>
@ -457,15 +462,18 @@ public class ChannelMasker {
throw new IllegalArgumentException("Invalid APPLY command: Parameter(s) expected");
}
operation.argument = new int[2];
operation.argument = new int[Math.max(2, args.length)];
operation.argument[0] = args[0].codePointAt(0);
if ( args.length >= 2 ) {
BinaryOperator operator = BinaryOperator.fromString(args[1]);
for ( int i = 1; i < args.length - 1; i++ ) {
operation.argument[i] = args[i].codePointAt(0);
}
BinaryOperator operator = BinaryOperator.fromString(args[args.length - 1]);
if ( operator == null ) {
throw new IllegalArgumentException(
String.format("Invalid APPLY command: Unknown operator %s", args[1]));
String.format("Invalid APPLY command: Unknown operator %s", args[args.length - 1]));
}
operation.argument[1] = operator.ordinal();
operation.argument[args.length - 1] = operator.ordinal();
} else {
operation.argument[1] = BinaryOperator.AND.ordinal();
}

66
src/main/java/org/incenp/imagej/Helper.java

@ -1,6 +1,6 @@
/*
* Incenp.org Plugins
* Copyright © 2019, 2020 Damien Goutte-Gattat
* Incenp.org ImageJ Plugins
* Copyright © 2019, 2020, 2021 Damien Goutte-Gattat
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,6 +20,7 @@ package org.incenp.imagej;
import java.awt.Window;
import ij.IJ;
import ij.ImagePlus;
import ij.WindowManager;
import ij.measure.Calibration;
@ -105,13 +106,13 @@ public class Helper {
double[] volumes = new double[channels.length];
int nslices = image.getNSlices();
for ( int ch : channels ) {
for ( int i = 0; i < channels.length; i++ ) {
double volume = 0;
for ( int i = 0; i < nslices; i++ ) {
image.setPosition(ch, i + 1, 1);
for ( int j = 0; j < nslices; j++ ) {
image.setPosition(channels[i], j + 1, 1);
volume = volume + (countWhitePixels(image.getProcessor()) * voxel);
}
volumes[ch - 1] = volume;
volumes[i] = volume;
}
return volumes;
@ -175,4 +176,57 @@ public class Helper {
return rt;
}
/**
* Creates a copy of a given hyperstack with added (empty) channel(s).
*
* @param image The source hyperstack to copy.
* @param channels The number of channels to add.
* @param destroy If true, the source hyperstack is closed once the copy is
* over.
* @return A copy of the original hyperstack, with the added channel(s).
*/
public static ImagePlus createAugmentedHyperstack(ImagePlus image, int channels, boolean destroy) {
int nchannels = image.getNChannels();
int nslices = image.getNSlices();
int nframes = image.getNFrames();
ImagePlus newimg = IJ.createHyperStack(image.getTitle(), image.getWidth(), image.getHeight(),
nchannels + channels, nslices, nframes, image.getBitDepth());
for ( int i = 0; i < nchannels; i++ ) {
for ( int j = 0; j < nslices; j++ ) {
for ( int k = 0; k < nframes; k++ ) {
image.setPositionWithoutUpdate(i + 1, j + 1, k + 1);
newimg.setPositionWithoutUpdate(i + 1, j + 1, k + 1);
newimg.setProcessor(image.getProcessor().duplicate());
}
}
}
if ( destroy ) {
image.close();
}
return newimg;
}
/**
* Augments a hyperstack with empty channel(s).
*
* @param image the hyperstack to modify
* @param channels the number of empty channels to add
*/
public static void augmentHyperstack(ImagePlus image, int channels) {
int mode = image.getDisplayMode();
if ( mode != IJ.GRAYSCALE )
image.setDisplayMode(IJ.GRAYSCALE);
ImagePlus tmp = createAugmentedHyperstack(image, channels, false);
image.setImage(tmp);
tmp.close();
if ( mode != 0 && mode != IJ.GRAYSCALE ) {
image.setDisplayMode(mode);
}
}
}

2
src/main/java/org/incenp/imagej/Masking.java

@ -1,5 +1,5 @@
/*
* Incenp.org Plugins
* Incenp.org ImageJ Plugins
* Copyright © 2020 Damien Goutte-Gattat
*
* This program is free software; you can redistribute it and/or modify

2
src/main/java/org/incenp/imagej/NucleiSegmenter.java

@ -1,5 +1,5 @@
/*
* Incenp.org Plugins
* Incenp.org ImageJ Plugins
* Copyright © 2019 Damien Goutte-Gattat
*
* This program is free software; you can redistribute it and/or modify

4
src/main/java/org/incenp/imagej/ThresholdingMethod.java

@ -1,5 +1,5 @@
/*
* Incenp.org Plugins
* Incenp.org ImageJ Plugins
* Copyright © 2020 Damien Goutte-Gattat
*
* This program is free software; you can redistribute it and/or modify
@ -279,6 +279,8 @@ public enum ThresholdingMethod {
return MIN_ERROR;
else if ( s.equalsIgnoreCase("renyi") )
return RENYI_ENTROPY;
else if ( s.equalsIgnoreCase("renyientropy") )
return RENYI_ENTROPY;
else if ( s.equalsIgnoreCase("meanlocal") || s.equalsIgnoreCase("localmean") )
return MEAN_LOCAL;
else if ( s.equalsIgnoreCase("otsulocal") || s.equalsIgnoreCase("localotsu") )

2
src/main/java/org/incenp/imagej/plugins/About.java

@ -1,5 +1,5 @@
/*
* Incenp.org Plugins
* Incenp.org ImageJ Plugins
* Copyright © 2017, 2018, 2019 Damien Goutte-Gattat
*
* This program is free software; you can redistribute it and/or modify

2
src/main/java/org/incenp/imagej/plugins/ExtendedPanel.java

@ -1,5 +1,5 @@
/*
* Incenp.org Plugins
* Incenp.org ImageJ Plugins
* Copyright © 2017, 2019 Damien Goutte-Gattat
*
* This program is free software; you can redistribute it and/or modify

2
src/main/java/org/incenp/imagej/plugins/FrameIntervalInfo.java

@ -1,6 +1,6 @@
/*
* Incenp.org Plugins
* Copyright © 2017 Damien Goutte-Gattat
* Copyright © 2017 ImageJ Damien Goutte-Gattat
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

6
src/main/java/org/incenp/imagej/plugins/Info.java

@ -1,5 +1,5 @@
/*
* Incenp.org Plugins
* Incenp.org ImageJ Plugins
* Copyright © 2017, 2018, 2019 Damien Goutte-Gattat
*
* This program is free software; you can redistribute it and/or modify
@ -25,8 +25,8 @@ public class Info {
}
public static String getPackageInfo() {
return "Incenp.org Plugins " + Info.class.getPackage().getImplementationVersion()
+ "\nCopyright © 2017–2020 Damien Goutte-Gattat\n \n"
return "Incenp.org ImageJ Plugins " + Info.class.getPackage().getImplementationVersion()
+ "\nCopyright © 2017–2021 Damien Goutte-Gattat\n \n"
+ "These plugins are released under the GNU General Public License.";
}
}

2
src/main/java/org/incenp/imagej/plugins/Launcher.java

@ -1,5 +1,5 @@
/*
* Incenp.org Plugins
* Incenp.org ImageJ Plugins
* Copyright © 2019 Damien Goutte-Gattat
*
* This program is free software; you can redistribute it and/or modify

2
src/main/java/org/incenp/imagej/plugins/MergeDialog.java

@ -1,5 +1,5 @@
/*
* Incenp.org Plugins
* Incenp.org ImageJ Plugins
* Copyright © 2017, 2019 Damien Goutte-Gattat
*
* This program is free software; you can redistribute it and/or modify

2
src/main/java/org/incenp/imagej/plugins/PanelDialog.java

@ -1,5 +1,5 @@
/*
* Incenp.org Plugins
* Incenp.org ImageJ Plugins
* Copyright © 2017 Damien Goutte-Gattat
*
* This program is free software; you can redistribute it and/or modify

7
src/main/java/org/incenp/imagej/plugins/UnifiedMerge.java

@ -1,5 +1,5 @@
/*
* Incenp.org Plugins
* Incenp.org ImageJ Plugins
* Copyright © 2017, 2018, 2019 Damien Goutte-Gattat
*
* This program is free software; you can redistribute it and/or modify
@ -82,6 +82,11 @@ public class UnifiedMerge implements Command {
image.setPosition(k + 1, j + 1, i + 1);
ImageProcessor ip = image.getProcessor();
/* Make sure the source processor is grayscale. */
if ( !ip.isGrayscale() ) {
ip.setColorModel(ip.getDefaultColorModel());
}
if ( ci.ShowIndividualChannel ) {
int x = ci.getXOffset();
int y = ci.getYOffset();

20
src/site/apt/index.apt

@ -38,8 +38,14 @@ Java library
classes that may be used in other ImageJ plugins or scripts. Those
classes are under the <<<org.incenp.imagej>>> namespace.
Refer to {{{./apidocs/index.html}API documentation}} of the library to
know what classes are available and what you can do with them.
Refer to the {{{./apidocs/index.html}API documentation}} of the
library to know what classes are available and what you can do with
them.
Notable classes include the <<<BatchReader>>> class, to facilitate
batch processing of images listed in a CSV input file, and the
<<<ChannelMasker>>> class, to facilitate the creation and manipulation
of binary masks.
Setup
@ -51,9 +57,9 @@ Setup
of the project.
Simply download the latest JAR file on that page (named
<incenp-plugins-X.X.Y.jar>, where <X.Y.X> is the latest version) and
save it anywhere under the <plugins> directory of your ImageJ
installation.
<incenp-imagej-plugins-X.X.Y.jar>, where <X.Y.X> is the latest
version) and save it anywhere under the <plugins> directory of your
ImageJ installation.
The plugins will be available upon the next restart of ImageJ under
the <Plugins\>Incenp.org> menu. You may also check the plugins were
@ -80,6 +86,6 @@ $ git clone https://git.incenp.org/damien/imagej-plugins.git
$ mvn package
+------------
This will generate a JAR archive named <incenp-plugins-X.Y.Z.jar> in
the <target> directory. Install that file in your ImageJ’s <plugins>
This will generate a JAR archive named <incenp-imagej-plugins-X.Y.Z.jar>
in the <target> directory. Install that file in your ImageJ’s <plugins>
directory as described in the previous section.

310
src/site/apt/library.apt

@ -0,0 +1,310 @@
The Helper library
The library bundled with the plugins contains a few helper classes
under the <<<org.incenp.imagej>>> namespace. Those classes may be used
in other plugins or scripts.
Refer to the {{{./apidocs/index.html}API documentation}} for the
complete list of available classes. This page describes the classes
expected to be the most useful.
* The BatchReader class
This class is intended to facilitate the batch processing of images
listed in a CSV input file.
The input file is expected to contain image filenames in the first
column; if those filenames are not absolute, they will be considered
relative to the directory containing the CSV file itself.
Once a BatchReader instance has been created, use the <<<next()>>>
method to iterate through the images listed in the file, and the
<<<getImage()>>> to access the current image, as in the following
example:
+-------------------------------------------------
BatchReader reader = new BatchReader("input.csv");
while ( reader.next() ) {
ImagePlus image = reader.getImage();
/* Process the image as required. */
image.close();
}
+-------------------------------------------------
The <<<getImage()>>> method will always return a single image, even if
the underlying file contains several images: in that case, calling the
<<<next()>>> method will move the reader to the next image within that
file instead of moving to the next row in the CSV input file, until
all the images have been read.
(This is probably the main interest of that class: discharging the
client code from having to handle a mix of files containing only one
image and files containing several images.)
If the CSV file contains more than one column, the contents of the
columns for any given row can be accessed using either the
<<<getRow()>>> method, which will return an array of strings (the
first string being the title of the current image), or the
<<<getCell(i)>>> method, which returns a string with the contents of
the <i>th column (0-based). This is useful if your image processing
requires any kind of supplementary information not found in the image
file themselves.
The first line of the CSV file is expected to be a header line, unless
the <<<BatchReader>>> constructor is called with boolean second
argument set to <true>. If a header line is present, then you can also
access the contents of an arbitrary cell within the current row with
the <<<getCell(s)>>> method, with <s> being a string matching the
header of the column you want.
As a convenience, the <<<fillResultsTable(rt)>>> method will add to
the specified <<<ResultsTable>>> a new line containing the title of
the current image and the contents of any additional columns in the
CSV file for the current row.
Here´s a full Jython example:
+------------------------------------------------------
#@ File (label='Choose a CSV file', style='file') input
from ij.mesure import ResultsTable
from org.incenp.imagej import BatchReader
reader = BatchReader(input)
results = ResultsTable()
while reader.next():
# Get the current image
image = reader.getImage()
# Pre-fill the results table with the image title
# and whatever data are contained in the CSV file
reader.fillResultsTable(rt)
# Do some meaningfull analysis of the image, using
# the contents of the "Extra1" column in the CSV file
value = foo_analysis(image, reader.getCell("Extra1"))
# Add the value to the current row in the table
results.addValue("Foo", value)
results.show("Foo Results")
image.close()
+------------------------------------------------------
* The ChannelMasker class
The <<<ChannelMasker>>> class is intended to facilitate creating and
applying binary masks to image.
A <<<ChannelMasker>>> object is creating by calling the
<<<createMasker>>> static method with a string describing the
operations to peform on the image the masker will be applied to. The
string is a comma-separated list of operations, each operation being
of the form <<<X:OPERATION(ARGUMENTS)>>> where <X> is the 1-based
index of the source channel of the image, or a one-letter code serving
as an indirect reference to the channel (more on that below).
If the image the mask is applied to contains several t-frames and/or
z-slices, the operation will be applied to all the frames and/or all
the slices. That is, the resulting hyperstack will always have the
same dimensions as the original image.
** Available operations
*** The MASK operation
The <MASK> operation creates a binary mask from the source channel by
applying a threshold to it; all pixels above the threshold in the
source channel will be white in the generated mask, and all pixels
below the threshold will be black.
The <MASK> operation expects at least one argument which is either the
value of the threshold to apply directly, or a string corresponding to
a value in the <<<org.incenp.imagej.ThresholdingMethod>>> enumeration
representing an automatic thresholding algorithm (method names are
matched in a case-insensitive way). If the thresholding algorithm
choosen is a <local> thresholding algorithm, then an optional second
argument may be specified, indicating the radius the thresholding
algorithm will consider; otherwise the default radius is 15.
Here are some examples of masking operations:
[1:MASK(127)] Creates a mask from the first channel by applying a
user-defined threshold of 127.
[1:MASK(fixed, 127)] This is the same as the previous example, with
the <FIXED> thresholding method explicitly specified.
[2:MASK(Huang)] Creates a mask from the second channel by applying
the <HUANG> automatic thresholding algorithm.
[2:MASK(Otsu_Local)] Creates a mask from the second channel by
applying the local variant of the Otsu thresholding algorithm, with
the default radius of 15 pixels.
[2:MASK(otsu_local, 20)] Same as above, but with a radius of 20
pixels.
*** The APPLY operation
The <APPLY> operation applies one or several binary masks found in
channels of an image to another channel of the same image.
The <APPLY> operation expects at least one argument which is the
1-based index of the channel containing a mask to apply. Other
channels may be specified after the first, the masks they contain will
be successively applied. The last argument indicates which logical
operation to perform when applying the mask(s); it should be one of
<<<AND>>>, <<<NAND>>>, <<<OR>>>, <<<NOR>>>, <<<XOR>>>, or <<<XNOR>>>.
That argument may be omitted if there is only one mask to apply, in
which case it will default to <<<AND>>>.
Here are some examples:
[1:APPLY(2)] Applies to the first channel of the image the binary
mask found in the second channel, with the <AND> logical operator.
[1:APPLY(3, or)] Applies to the first channel the binary mask found
in the third channel, with the <OR> logical operator.
[1:APPLY(2,3, and)] Applies to the first channel the binary masks
found in the second and third channels, with the <AND> logical
operator.
*** The COPY and INVERT operations
The <COPY> operation, as it name suggests, simply copies verbatim the
source channel to the destination image. It does not need any
argument.
The <INVERT> operation copies the source channel to the destination,
but inverts it first. It also does not take any argument.
** Specifying a channel order
When writing a ChannelMasker command, it is possible to refer to a
channel by a one-letter code rather than by the channel index
directly. A <channel order specification> is a string providing the
necessary information to match the one-letter codes with the actual
indexes in the image the ChannelMasker is applied to.
For example, let´s consider the following ChannelMasker command:
+-------------------------------------
1:MASK(Huang),2:MASK(Moments),3:COPY()
+-------------------------------------
This command will always apply the <Huang> algorithm to the first
channel, apply the <Moments> algorithm to the second channel, and copy
the third channel verbatim, to any images the masker is used on.
The command may however be re-written like this:
+-------------------------------------
A:MASK(Huang),B:MASK(Moments),C:COPY()
+-------------------------------------
With a channel order specification of <<<ABC>>>, that command will be
equivalent to the one above. But if the channel order is set to, say,
<<<BAC>>>, then the <Huang> algorithm will be applied to the second
channel and the <Moments> algorithm will be applied to the first
channel (the copy operation will still involve the third channel).
In short, the use of <channel order specification> allows to write a
ChannelMasker command without knowing, at the time the ChannelMasker
object is created, the precise order of the channels. That order can
be provided later, through the channel order specification string, at
the time the ChannelMasker object is actually used on a image.
** Chaining masker objects
The <<<chain()>>> method allows to <chain> ChannelMasker objects. When
two or more ChannelMasker objects are chained, applying the first
masker of the chain to an image will automatically apply the next
masker of the chain to the output of the previous masker.
The method returns the object it is called on, allowing to create a
chain as follows (assuming <<<CM1>>>, <<<CM2>>>, and <<<CM3>>> are all
previously created ChannelMasker objects):
+-------------------------
CM1.chain(CM2.chain(CM3));
+-------------------------
Applying the <<<CM1>>> masker to an image will return the output of
<<<CM3>>> applied to the output of <<<CM2>>> applied to the output of
<<<CM1>>>.
** A complete example
This is a complete Jython example, based on a real use-case, of using
both the BatchReader class and the ChannelMasker class. This code will
batch process images that are expected to contain three channels: one
channel contains the signal of interest (<S>), the two other contain
the signal from fluorophores used to mark regions in the observed
field (<D> and <M>). The aim of this code is to quantify the signal
from the <S> channel in the regions marked by the fluorophores from
the <D> and <M> channels.
+----------------------------------------------------------------------
#@ File (label='Choose a CSV file', style='file') input
from ij.measure import ResultsTable
from ij.process import ImageProcessor, ImageStatistics
from org.incenp.imagej.ChannelMasker import createMasker
from org.incenp.imagej import BatchReader
# Create the masker chain. There are 2 steps:
# 1. Threshold the 'D' channel with the "Minimum" algorithm,
# and the 'M' channel with the "Huang" algorithm;
# carry over the 'S' channel.
# 2. Apply both masks created in the previous steps to the 'S' channel;
# note that we no longer need to use letter codes here, since after
# the first masker is applied the order of the channels is always
# known.
masker = createMasker('D:MASK(Minimum),M:MASK(Huang),S:COPY()').chain(
createMasker('3:APPLY(1),3:APPLY(2)'))
batch = BatchReader(input)
results = ResultsTable()
while batch.next():
image = batch.getImage()
# Get the channel order from the column "Channel order"
# in the input CSV file
order = batch.getCell("Channel order")
# Perform the masking operations
masked = masker.apply(image, image.getTitle(), order)
# Quantify the signal on the masked image
for i,label in enumerate(["Control", "Marked"]):
# Select the channel
masked.setC(i + 1)
# Exclude black pixels (resulting from applying the masks)
masked.getProcessor().setThreshold(1, 255, ImageProcessor.NO_LUT_UPDATE)
# Extract mean intensity and area from the tresholded region
stats = ImageStatistics.getStatistics(masked.getProcessor(),
ImageStatistics.AREA | ImageStatistics.MEAN | ImageStatistics.LIMIT,
masked.getCalibration())
# Add the results to the table
results.incrementCounter()
results.addValue("Image", image.getTitle())
results.addValue("Region", label)
results.addValue("Area", stats.area)
results.addValue("Mean", stats.mean)
image.close()
+-----------------------------------------------------------------------

5
src/site/site.xml

@ -4,13 +4,14 @@
xsi:schemaLocation="http://maven.apache.org/DECORATION 1.8.0
http://maven.apache.org/xsd/decoration-1.8.0.xsd">
<body>
<menu name="Incenp Plugins">
<menu name="Incenp ImageJ Plugins">
<item name="About" href="index.html" />
<item name="Unified Merge Plugin" href="unified-merge.html" />
<item name="Extended Panel Plugin" href="extended-panel.html" />
<item name="Helper Library" href="library.html" />
<item name="API Documentation" href="apidocs/index.html" />
</menu>
<menu ref="reports" />
<footer><![CDATA[Copyright © 2020 Damien Goutte-Gattat]]></footer>
<footer><![CDATA[Copyright © 2021 Damien Goutte-Gattat]]></footer>
</body>
</project>
Loading…
Cancel
Save