Compare commits

...

121 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
Damien Goutte-Gattat 77d1d81443 [maven-release-plugin] prepare release incenp-plugins-0.9.0 2 years ago
Damien Goutte-Gattat 21b6485cf3 Update NEWS file. 2 years ago
Damien Goutte-Gattat 076f1021a6 Avoid needless duplication of ImageProcessor. 2 years ago
Damien Goutte-Gattat c876b59c75 Fix bogus reference in a Javadoc comment. 2 years ago
Damien Goutte-Gattat 6b8abccbd6 Remove deprecated methods. 2 years ago
Damien Goutte-Gattat 597c8cefa2 Bump version to 0.9.0-SNAPSHOT. 2 years ago
Damien Goutte-Gattat 2f44ac0983 Count white pixels when extracting volumes. 2 years ago
Damien Goutte-Gattat 2f15101b2e Rework the ChannelMasker class. 2 years ago
Damien Goutte-Gattat 7e7a83c61b Add convenience methods to parse enum values. 2 years ago
Damien Goutte-Gattat 671f840383 Add a new unified interface to create masks. 2 years ago
Damien Goutte-Gattat 877afcb7ff Make local thresholding available to ChannelMasker. 2 years ago
Damien Goutte-Gattat 6dbed10b4e Add a helper method interfacing the Auto Local Threshold plugin. 2 years ago
Damien Goutte-Gattat f2171e88ec Do not invert image after nuclei segmentation. 2 years ago
Damien Goutte-Gattat 827e1a7ed2 Let ImageJ count black pixels. 2 years ago
Damien Goutte-Gattat 3a5e7af317 Support chaining ChannelMasker objects. 2 years ago
Damien Goutte-Gattat e7a6d206cd [maven-release-plugin] prepare for next development iteration 2 years ago
Damien Goutte-Gattat aeb781d0e5 [maven-release-plugin] prepare release incenp-plugins-0.8.4 2 years ago
Damien Goutte-Gattat 7dc83415e8 Bump version number. 2 years ago
Damien Goutte-Gattat 1d6d127f91 [maven-release-plugin] prepare release incenp-plugins-0.8.3 2 years ago
Damien Goutte-Gattat 62c8252c98 Fix some Javadoc issues. 2 years ago
Damien Goutte-Gattat a6f68a8dcb [maven-release-plugin] rollback the release of incenp-plugins-0.8.3 2 years ago
Damien Goutte-Gattat 456e698aee [maven-release-plugin] prepare for next development iteration 2 years ago
Damien Goutte-Gattat 8f31c13049 [maven-release-plugin] prepare release incenp-plugins-0.8.3 2 years ago
Damien Goutte-Gattat fbf0136e42 Update NEWS file. 2 years ago
Damien Goutte-Gattat e6a1049b61 Update ChannelMasker documentation. 2 years ago
Damien Goutte-Gattat ce36579fe3 Delay translation of channel codes. 2 years ago
Damien Goutte-Gattat 9071b78fc5 Add the getResultsTable helper method. 2 years ago
Damien Goutte-Gattat ebe1bd0818 Add the MASKTHR operation to the ChannelMasker. 2 years ago
Damien Goutte-Gattat 27eaf468d4 Allow to invert the meaning of masks. 2 years ago
Damien Goutte-Gattat fbd703281a Make applying a binary mask slightly more efficient. 2 years ago
Damien Goutte-Gattat 528da5a2bd [maven-release-plugin] prepare for next development iteration 2 years ago
Damien Goutte-Gattat 0ab52b310b [maven-release-plugin] prepare release incenp-plugins-0.8.2 2 years ago
Damien Goutte-Gattat 98cf94c838 Pre-release updates. 2 years ago
Damien Goutte-Gattat 0e29402370 Allow to get row cells based on column name. 2 years ago
Damien Goutte-Gattat 9dfad496b5 Streamline access to batch fields. 2 years ago
Damien Goutte-Gattat 915d3fa7ba [maven-release-plugin] prepare for next development iteration 2 years ago
Damien Goutte-Gattat 4a747ac0fa [maven-release-plugin] prepare release incenp-plugins-0.8.1 2 years ago
Damien Goutte-Gattat 2e397f2eec Update web site. 2 years ago
Damien Goutte-Gattat 880c809edb Update home page location. 2 years ago
Damien Goutte-Gattat a8b2676625 Use the Maven Site plugin for the documentation. 2 years ago
Damien Goutte-Gattat 63ad7e762e Ignore the javadoc directory. 2 years ago
Damien Goutte-Gattat 85f19ee782 Add the fillResultsTable method. 2 years ago
Damien Goutte-Gattat 4d18cefc61 New syntax for combining operations. 2 years ago
Damien Goutte-Gattat 8b88bb923b Add NOT, NAND, NOR, and XNOR binary operators. 2 years ago
Damien Goutte-Gattat e23ec90074 Add the AND, OR, and XOR ChannelMasker operations. 2 years ago
Damien Goutte-Gattat 3dff3677bd Merge branch 'develop'. 2 years ago
Damien Goutte-Gattat 135ad77420 Check the image file exists when reading the CSV file. 2 years ago
Damien Goutte-Gattat d5eab4d582 Put the channel settings in a scrollable pane. 2 years ago
Damien Goutte-Gattat 0376d25a09 Merge branch 'develop' 2 years ago
Damien Goutte-Gattat 2fbbd1b3a6 [maven-release-plugin] prepare for next development iteration 2 years ago
Damien Goutte-Gattat d644a40f00 [maven-release-plugin] prepare release incenp-plugins-0.8.0 2 years ago
Damien Goutte-Gattat 18122782ab Merge the libaux helper library. 2 years ago
Damien Goutte-Gattat 99fdb3aec9 Bump version to 0.8.0. 2 years ago
Damien Goutte-Gattat 4da6213ca3 Convert the plugins to the ImageJ2 plugins API. 2 years ago
Damien Goutte-Gattat 7168e83753 Enable use of the Maven release plugin. 2 years ago
Damien Goutte-Gattat 16ba9be727 Move plugins to a dedicated package. 2 years ago
Damien Goutte-Gattat 266718ef9b Update dependency versions. 2 years ago
Damien Goutte-Gattat 4e2c8bb649 Add parent POM. 2 years ago
Damien Goutte-Gattat fdabb5fb60 Bump version number. 3 years ago
Damien Goutte-Gattat ca84175b61 Merge branch 'develop' for 0.7.6 release. 3 years ago
Damien Goutte-Gattat 4e68ba20e0 Update README and add NEWS file. 3 years ago
Damien Goutte-Gattat 0c0a087540 Update copyright notices. 3 years ago
Damien Goutte-Gattat b9ce0d2ee0 Move the manual to a doc subdirectory. 3 years ago
Damien Goutte-Gattat 20fe0c82de Add draft manual. 3 years ago
Damien Goutte-Gattat 2a8d1a7c01 Fix position of labels for individual channels. 3 years ago
Damien Goutte-Gattat 410f45e83f Avoid unneeded merge label. 3 years ago
Damien Goutte-Gattat eb6c55a26d Use label size value read from the preferences. 3 years ago
Damien Goutte-Gattat 0d81d1a222 Bump version number. 3 years ago
Damien Goutte-Gattat 3a96c92988 Fix position of labels. 3 years ago
Damien Goutte-Gattat aecc7a253a Draw labels over a black background. 3 years ago
Damien Goutte-Gattat 9dd88856ca Merge branch 'develop' 3 years ago
Damien Goutte-Gattat 8a69a106e9 Fix spurious tab. 3 years ago
Damien Goutte-Gattat ce92e24f5c Explicity specify source encoding. 3 years ago
Damien Goutte-Gattat 6c359638a6 Remove useless Override annotation. 3 years ago
Damien Goutte-Gattat 6b566340ae Fix out-of-bounds exception when calling the Panel plugin. 3 years ago
Damien Goutte-Gattat 0e3ee1f0eb Apply formatting style. 3 years ago
Damien Goutte-Gattat f6136f6a68 Explicitly set target Java version. 3 years ago
Damien Goutte-Gattat 3b36a05e88 Add the "copy overlay only once" option. 3 years ago
Damien Goutte-Gattat 90d958debb Bump version to 0.7.5-SNAPSHOT. 3 years ago
  1. 118
      NEWS
  2. 62
      README.md
  3. 212
      pom.xml
  4. 412
      src/main/java/org/incenp/imagej/BatchReader.java
  5. 41
      src/main/java/org/incenp/imagej/BinaryOperator.java
  6. 493
      src/main/java/org/incenp/imagej/ChannelMasker.java
  7. 232
      src/main/java/org/incenp/imagej/Helper.java
  8. 223
      src/main/java/org/incenp/imagej/Masking.java
  9. 480
      src/main/java/org/incenp/imagej/MergeDialog.java
  10. 141
      src/main/java/org/incenp/imagej/NucleiSegmenter.java
  11. 291
      src/main/java/org/incenp/imagej/ThresholdingMethod.java
  12. 37
      src/main/java/org/incenp/imagej/plugins/About.java
  13. 72
      src/main/java/org/incenp/imagej/plugins/ExtendedPanel.java
  14. 22
      src/main/java/org/incenp/imagej/plugins/FrameIntervalInfo.java
  15. 13
      src/main/java/org/incenp/imagej/plugins/Info.java
  16. 36
      src/main/java/org/incenp/imagej/plugins/Launcher.java
  17. 374
      src/main/java/org/incenp/imagej/plugins/MergeDialog.java
  18. 107
      src/main/java/org/incenp/imagej/plugins/PanelDialog.java
  19. 241
      src/main/java/org/incenp/imagej/plugins/UnifiedMerge.java
  20. 3
      src/main/resources/plugins.config
  21. 34
      src/site/apt/extended-panel.apt
  22. 91
      src/site/apt/index.apt
  23. 310
      src/site/apt/library.apt
  24. 126
      src/site/apt/unified-merge.apt
  25. BIN
      src/site/resources/images/merge-output-all-panels.png
  26. BIN
      src/site/resources/images/merge-output-single-merge.png
  27. BIN
      src/site/resources/images/merge-output-vertical.png
  28. BIN
      src/site/resources/images/merge-settings-1.png
  29. BIN
      src/site/resources/images/panel-output-1.png
  30. BIN
      src/site/resources/images/panel-settings-1.png
  31. 17
      src/site/site.xml

118
NEWS

@ -0,0 +1,118 @@
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
-------------------------------
* Support chaining ChannelMasker objects.
* Breaking: Do not invert image after nuclei segmentation.
* Breaking: New interface for masking operations.
* Support local thresholding algorithms.
* Breaking: Use white pixels when extracting volumes.
Changes in incenp-plugins-0.8.4
-------------------------------
* Speed up some masking operations.
* Allow to invert the meaning of black/white pixels in binary masks.
* Add the MASKTHR operation to the ChannelMasker.
* A single ChannelMasker can now be applied to images with different
channel orders.
Changes in incenp-plugins-0.8.2
-------------------------------
* BatchReader: New interface to access row contents.
Changes in incenp-plugins-0.8.1
-------------------------------
* Unified_Merge: Use a scrollable panel for channel settings.
* BatchReader: Throw IOException if an image file does not exist.
* Channelmasker: New COMBINE and INVERT operations.
* New documentation powered by the Maven Site plugin.
Changes in incenp-plugins-0.8.0
-------------------------------
* Convert the plugins to the ImageJ2 plugins API.
* Merge the former incenp-ij-libaux class library.
Changes in incenp-plugins 0.7.6
-------------------------------
* Draw labels over a black background.
* Use label size value as read from the preferences.
* Fix position of labels for individual channels.
* Add draft manual.
Changes in incenp-plugins 0.7.5
-------------------------------
* Add the "Copy overlay only once" option.
* Fix out-of-bounds exception in the Extended_Panel plugin.
* Fix some compile-time issues.
Changes in incenp-plugins 0.7.4
-------------------------------
* Fix incorrect placement of labels.
Changes in incenp-plugins 0.7.3
-------------------------------
* Allow Extended_Panel to work with 1-frame images.
Changes in incenp-plugins 0.7.2
-------------------------------
* Add the "Offset first frame" option.
* Add the "Use 00:00 format" option.
Changes in incenp-plugins 0.7.1
-------------------------------
* Save more settings in the preferences.

62
README.md

@ -1,22 +1,60 @@
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.
The Unified_Merge Plugin
------------------------
This plugin allows to create a panel showing the individual channels of
an image (in grayscale) alongside a colored merge of the same image.
Available Plugins
-----------------
The Extended_Panel Plugin
-------------------------
### The Unified_Merge Plugin
This plugin allows to quickly create a composite image from a
hyperstack. The resulting image may contain a panel for each individual
channels of the source hyperstack or a single panel showing a colorized
merge of all channels, or both.
### The Extended_Panel Plugin
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,
from the source directory
```
$ mvn package
```
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
[LICENSE file](LICENSE) of the source distribution.
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
-----------------------
The project is located at <https://incenp.org/dvlpt/imagej-plugins/>.
The source code is available in a Git repository at
<https://git.incenp.org/damien/imagej-plugins>.

212
pom.xml

@ -1,33 +1,197 @@
<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>
<groupId>org.incenp.imagej</groupId>
<artifactId>incenp-plugins</artifactId>
<version>0.7.4</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>
<licenses>
<license>
<name>GPL-3.0-or-later</name>
<url>https://www.gnu.org/licenses/gpl-3.0-standalone.html</url>
<distribution>manual</distribution>
<comments>GNU General Public License v3.0 or later</comments>
</license>
</licenses>
<developers>
<developer>
<id>damien</id>
<name>Damien Goutte-Gattat</name>
<email>dgouttegattat@incenp.org</email>
</developer>
</developers>
<scm>
<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>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>ij</artifactId>
<version>1.51h</version>
</dependency>
<dependency>
<groupId>net.imagej</groupId>
<artifactId>imagej</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>net.imagej</groupId>
<artifactId>imagej-legacy</artifactId>
<version>0.37.4</version>
</dependency>
<dependency>
<!-- For ImageJ2 plugin API -->
<groupId>org.scijava</groupId>
<artifactId>scijava-common</artifactId>
<version>2.85.0</version>
</dependency>
<dependency>
<!-- For util.opencsv -->
<groupId>sc.fiji</groupId>
<artifactId>VIB-lib</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<!-- For loci.plugins.BF -->
<groupId>ome</groupId>
<artifactId>bio-formats_plugins</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<!-- For fiji.threshold.Auto_Local_Threshold -->
<groupId>sc.fiji</groupId>
<artifactId>Auto_Local_Threshold</artifactId>
<version>1.10.1</version>
</dependency>
</dependencies>
<build>
<finalName>incenp_plugins-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.5.3</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.9.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<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>
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
</plugin>
</plugins>
</reporting>
</project>

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

@ -0,0 +1,412 @@
/*
* Incenp.org ImageJ Plugins
* Copyright © 2019, 2020 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the Gnu General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.incenp.imagej;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import ij.ImagePlus;
import ij.measure.ResultsTable;
import loci.formats.FormatException;
import loci.plugins.BF;
import loci.plugins.in.ImporterOptions;
import util.opencsv.CSVReader;
/**
* A class to open images from a CSV file containing filenames and optional
* associated data.
* <p>
* A BatchReader object reads a CSV file whose first column is expected to
* contain pathnames pointing to image files. If the pathnames are not absolute,
* they are supposed to be relative to the directory containing the CSV file
* itself.
* <p>
* Typical usage is as follows:
*
* <pre>
* BatchReader reader = new BatchReader("filename.csv");
* while ( reader.next() ) {
* ImagePlus image = reader.getImage();
* String[] fields = reader.getRow();
*
* // Work with the image and the associated fields...
*
* image.close()
* }
* </pre>
* <p>
* Blank lines and lines starting with a '#' are ignored. The first line of the
* CSV file is treated as a header line.
*/
public class BatchReader {
private int cursor;
private int subcursor;
private String filename;
private String[] headers;
private boolean withHeaders;
private boolean caseSensitiveHeaders;
private ArrayList<String[]> rows;
private ImagePlus[] currentImages;
private Hashtable<String, Integer> headersIndex;
/**
* Creates a new reader.
*
* @param filename pathname to the file to read
* @param noHeaders if true, the first non-comment line will not be treated as a
* header line
*/
public BatchReader(String filename, boolean noHeaders) {
this.filename = filename;
this.withHeaders = !noHeaders;
caseSensitiveHeaders = false;
headers = null;
headersIndex = null;
cursor = subcursor = -1;
rows = null;
currentImages = null;
}
/**
* Creates a new reader.
*
* @param filename pathname to the file to read
*/
public BatchReader(String filename) {
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()}
* method.
*
* @return true if there is another image available, false otherwise
*/
public boolean next() {
if ( rows == null ) {
try {
readCSV();
} catch ( IOException e ) {
return false;
}
}
if ( currentImages != null ) {
if ( ++subcursor < currentImages.length )
return true;
else
currentImages = null;
}
if ( ++cursor >= rows.size() )
return false;
try {
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;
}
return true;
}
/**
* Indicates if theres an image ready to be processed. This method is mostly
* intended for private use within the BatchReader class, but is made public
* anyway just in case.
*
* @return true if an image is available, false otherwise
*/
public boolean hasImage() {
return currentImages != null && subcursor < currentImages.length;
}
/**
* Gets the current image.
*
* @return the current image, if one is available; otherwise null
*/
public ImagePlus getImage() {
if ( !hasImage() )
return null;
return currentImages[subcursor];
}
/**
* Gets a copy of the current row.
*
* @return an array containing the fields of the current row.
*/
public String[] getRow() {
if ( !hasImage() )
return null;
String[] row = rows.get(cursor);
String[] copy = new String[row.length];
System.arraycopy(row, 1, copy, 1, row.length - 1);
copy[0] = getImage().getTitle();
return copy;
}
/**
* Gets the contents of a specific cell within the current row.
*
* @param index a 0-based column index
* @return the contents of the specified cell
*/
public String getCell(int index) {
if ( !hasImage() )
return null;
if ( index <= 0 )
return currentImages[subcursor].getTitle();
else {
String[] row = rows.get(cursor);
if ( index >= row.length )
index = row.length - 1;
return row[index];
}
}
/**
* 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
* @param fallback the default value to return if the cell is not found
* @return the contents of the specified cell
*/
public String getCell(String[] names, String fallback) {
if ( headersIndex == null ) {
if ( (headersIndex = getHeadersIndex()) == null )
return null;
}
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, "");
}
/**
* 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++ ) {
if ( !caseSensitiveHeaders )
headersIndex.put(headers[i].toLowerCase(), i);
else
headersIndex.put(headers[i], i);
}
}
return headersIndex;
}
/**
* Gets the current row as a single string, with values separated by the
* specified character.
*
* @param separator the column separator character
* @return the current row as a string
*/
public String getCSVRow(char separator) {
if ( currentImages == null )
return null;
StringBuilder sb = new StringBuilder();
sb.append(currentImages[subcursor].getTitle());
String[] row = rows.get(cursor);
for ( int i = 1; i < row.length; i++ ) {
sb.append(separator);
sb.append(row[i]);
}
return sb.toString();
}
/**
* Gets the current row as a single string containing comma-separated values.
*
* @return the current row as string
*/
public String getCSVRow() {
return getCSVRow(',');
}
/**
* Gets the headers as found in the CSV file. If the constructor was called with
* the withHeaders parameter set to false, then this method will only return a
* 1-sized array containing the single header image,
*
* @return the CSV file headers
*/
public String[] getHeaders() throws IOException {
if ( rows == null ) {
this.readCSV();
}
if ( headers == null ) {
headers = new String[1];
headers[0] = "Image";
}
return headers;
}
/**
* Update a ResultsTable with the contents of the current row.
*
* @param rt the ResultsTable object to update.
*/
public void fillResultsTable(ResultsTable rt) {
if ( !hasImage() )
return;
rt.incrementCounter();
rt.addValue("Image", getImage().getTitle());
if ( headers != null ) {
for ( int i = 1; i < headers.length; i++ ) {
rt.addValue(headers[i], getCell(i));
}
}
}
/**
* Closes any opened images. Calling this method is only necessary if the client
* code wishes to stop batch processing before the next() method returns false.
* <p>
* Note that this only closes images that have not been reached yet by any call
* to next(). Closing previous images is the responsibility of the caller.
*/
public void close() {
if ( currentImages != null ) {
while ( subcursor < currentImages.length ) {
currentImages[subcursor++].close();
}
}
}
/**
* Reads the entire CSV file. This method is automatically called by the
* {@link #next()} or {@link #getHeaders()} methods if needed.
*
* @throws IOException if any I/O error occurs while attempting to read the file
*/
public void readCSV() throws IOException {
rows = new ArrayList<String[]>();
String basename = new File(filename).getParentFile().getAbsolutePath();
CSVReader reader = new CSVReader(new FileReader(filename));
String[] line;
while ( (line = reader.readNext()) != null ) {
if ( line[0].length() == 0 || line[0].charAt(0) == '#' )
continue;
if ( withHeaders && rows.size() == 0 && headers == null ) {
headers = line;
continue;
}
File imageFile = new File(line[0]);
if ( !imageFile.isAbsolute() )
imageFile = new File(basename, line[0]);
if ( !imageFile.exists() )
throw new IOException(String.format("%s does not exist", line[0]));
line[0] = imageFile.getAbsolutePath();
rows.add(line);
}
reader.close();
}
}

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

@ -0,0 +1,41 @@
/*
* Incenp.org ImageJ Plugins
* Copyright © 2020 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the Gnu General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.incenp.imagej;
/**
* Binary operations that can be used when combining two images.
*/
public enum BinaryOperator {
AND,
NAND,
OR,
NOR,
XOR,
XNOR;
public static BinaryOperator fromString(String s) {
for ( BinaryOperator operator : BinaryOperator.values() ) {
if ( operator.name().equalsIgnoreCase(s) ) {
return operator;
}
}
return null;
}
}

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

@ -0,0 +1,493 @@
/*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the Gnu General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.incenp.imagej;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ij.IJ;
import ij.ImagePlus;
import ij.process.ImageProcessor;
/**
* A helper class to create and apply binary masks. A ChannelMasker object is
* defined by a list of operations that describes how to generate a new image
* from a given source image. The generated image will contain as many channels
* as there are operations in the list.
* <p>
* Four different types of operations are supported:
* <ul>
* <li>copying a channel verbatim from the source image;
* <li>creating a binary mask from one the channel of the source image;
* <li>applying a binary mask to one of the channel of the source image; the
* binary mask itself is also taken from a channel of the source image;
* <li>inverting a binary mask.
* </ul>
*/
public class ChannelMasker {
private static final ThresholdingMethod[] methods = ThresholdingMethod.values();
private static final BinaryOperator[] operators = BinaryOperator.values();
private ChannelOperation[] operations;
private int maskingOptions;
private String channelOrder;
private ChannelMasker next;
/**
* Creates a new ChannelMasker object with the given list of operations. This
* constructor is public and may be called directly, but the
* {@link #createMasker(String)} static method is the preferred high-level
* interface to create a ChannelMasker.
*
* @param operations the list of operations to apply
*/
public ChannelMasker(ChannelOperation[] operations) {
this.operations = operations;
this.maskingOptions = Masking.CLOSE_OPEN_MASK;
this.channelOrder = null;
this.next = null;
}
/**
* Sets the default channel order specification. A channel order specification
* is a string of one-letter channel codes; those channel codes may then be used
* when calling the {@link #createMasker} method to refer to channels without
* knowing the position of the channels in the images the masker will be applied
* to.
* <p>
* For example, a masker object created with the "A:MASK(Huang),B:COPY()"
* command and applied to an image with channel order "ABC" will perform the
* MASK operation to the first channel and the COPY operation to the second
* channel; the same masker applied to an image with channel order "CADB" will
* perform the MASK operation on the second channel and the COPY operation to
* the fourth channel.
*
* @param order the default channel order to use when applying the masker
*/
public void setChannelOrder(String order) {
channelOrder = order;
}
/**
* Gets the default channel order specification.
*
* @return the default channel order used by this masker
*
* @see #setChannelOrder(String)
*/
public String getChannelOrder() {
return channelOrder;
}
/**
* Sets the masking options used by this object. See the constant fields in
* {@link Masking} for available options. By default ChannelMasker objects uses
* the CLOSE_OPEN_MASK option.
*
* @param options the new masking options
*/
public void setMaskingOptions(int options) {
maskingOptions = options;
}
/**
* Gets the masking options currently used by this object.
*
* @return the current masking options
*/
public int getMaskingOptions() {
return maskingOptions;
}
/**
* Apply this ChannelMasker object to an image. This method will create a new
* image by performing all the operations defined in the objects operations
* list.
*
* @param image the source image to apply the operations to
* @param name the name to give to the new image
* @param order the order of channels in the source image (may be null, in which
* case the default channel order will be used)
* @return the resulting image
*/
public ImagePlus apply(ImagePlus image, String name, String order) {
int nslices = image.getNSlices();
int nframes = image.getNFrames();
if ( order == null || order == "" ) {
order = channelOrder;
}
ImagePlus result = IJ.createHyperStack(name, image.getWidth(), image.getHeight(), operations.length, nslices,
nframes, 8);
result.setCalibration(image.getCalibration());
for ( int i = 0; i < nframes; i++ ) {
for ( int j = 0; j < nslices; j++ ) {
for ( int k = 0; k < operations.length; k++ ) {
ChannelOperation op = operations[k];
image.setPosition(getChannel(op.source, order), j + 1, i + 1);
ImageProcessor ip = null;
ImageProcessor src = null;
switch ( op.type ) {
case COPY:
ip = image.getProcessor().duplicate();
break;
case MASK:
ip = Masking.createMask(image.getProcessor(), methods[op.argument[0]], op.argument[1],
maskingOptions);
break;
case INVERT:
ip = image.getProcessor().duplicate();
ip.invert();
break;
case APPLY:
BinaryOperator operator = operators[op.argument[op.argument.length - 1]];
src = image.getProcessor().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;
}
result.setPosition(k + 1, j + 1, i + 1);
result.setProcessor(ip);
}
}
}
if ( next != null ) {
result = next.apply(result, name, null);
}
return result;
}
/**
* Apply this ChannelMasker object to an image. This method will create a new
* image by performing all the operations defined in the objects operations
* list. If the ChannelMasker uses channel codes instead of channel indexes,
* they will be translated using the default channel order specification.
*
* @param image the source image to apply the operations to
* @param name the name to give to the new image
* @return the resulting image
*/
public ImagePlus apply(ImagePlus image, String name) {
return apply(image, name, null);
}
/**
* Sets a ChannelMasker to apply after the current one. When the {@link #apply}
* method is called, the specified ChannelMasker will be applied to the
* resulting image, and the output of that application is what will be returned.
* <p>
* The method returns the object it is called on, allowing to create a chain as
* follows:
*
* <pre>
* CM1.chain(CM2.chain(CM3))
* </pre>
* <p>
* When <code>CM1.apply(...)</code> is called, it will return the output of CM3
* applied to the output of CM2 applied to the output of CM1 applied to the
* original image.
*
* @param next the ChannelMasker object to apply after this one
* @return the current ChannelMasker object
*/
public ChannelMasker chain(ChannelMasker next) {
this.next = next;
return this;
}
/**
* Creates a ChannelMasker from a text description of the operations. This is
* the preferred way to instanciate a ChannelMasker. The command parameter is a
* comma-separated list of operation descriptors, each descriptor being one of
* the following:
* <ul>
* <li>"X:COPY()": Copy verbatim channel number X of the source image.
* <li>"X:MASK(algo[,param])": Create a binary mask by applying the specified
* automatic thresholding algorithm to channel number X (see
* {@link ThresholdingMethod} for possible values of "algo"; values are matched
* in a case-insensitive way). The optional "param" value is either the actual
* 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[,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>
* For example, the command "2:MASK(MaxEntropy),1:COPY()" will create a
* ChannelMasker that, when applied to an image, will produce an output image
* with two channels: the first channel will be a binary mask created by
* thresholding the second channel of the source image with the Max Entropy
* algorithm, and the second channel will be a copy of the first channel of the
* source image.
* <p>
* The command "2:APPLY(1)" will create a ChannelMasker that, when applied to an
* image, will produce an output image containing a single channel, which will
* be result of applying the binary mask found in the first channel of the
* source image to the second channel of that same image, with the AND operator.
* <p>
* The order parameter, if non-null, is expected to be a list of letters.
* Channels in the command string may then be specified by a letter from that
* list instead of a numerical index. The rationale behind this parameter is to
* allow to use command strings that are independent of the orders of the
* channels in the source image.
* <p>
* For example, if the order parameter is "GRB", then the command
* "R:MASK(MaxEntropy),G:COPY()" will be equivalent to the first example above.
*
* @param command a text description of the operations to perform
* @param order the default channel order specification, as described in
* {@link #setChannelOrder(String)} (may be null)
* @param options flags for masking operation (see constant fields in
* {@link Masking} for available flags)
* @return a ChannelMasker object
*/
public static ChannelMasker createMasker(String command, String order, int options) {
ArrayList<ChannelOperation> operations = new ArrayList<ChannelOperation>();
ChannelOperation op;
Parser p = new Parser(command);
while ( (op = p.getNextOperation()) != null ) {
operations.add(op);
}
ChannelOperation[] opArray = new ChannelOperation[operations.size()];
operations.toArray(opArray);
ChannelMasker cm = new ChannelMasker(opArray);
cm.setChannelOrder(order);
cm.setMaskingOptions(options);
return cm;
}
/**
* Creates a ChannelMasker object from a text description of the operations.
* This method is similar to {@link #createMasker(String, String, int)} but uses
* the default option CLOSE_OPEN_MASK.
*
* @param command a text description of the operations to perform
* @param order the default channel order specification, as described in
* {@link #setChannelOrder(String)} (may be null)
* @return a ChannelMasker object
*/
public static ChannelMasker createMasker(String command, String order) {
return createMasker(command, order, Masking.CLOSE_OPEN_MASK);
}
/**
* Creates a ChannelMasker object from a text description of the operations.
* This method is similar to {@link #createMasker(String, String)} but does not
* allow to set a default channel order.
*
* @param command a text description of the operations to perform
* @return a ChannelMasker object
*/
public static ChannelMasker createMasker(String command) {
return createMasker(command, null, Masking.CLOSE_OPEN_MASK);
}
/**
* Apply a list of operations to an image. This is a convenience method to apply
* a list of operations (defined in a string as in the
* {@link #createMasker(String, String)} method) directly to an image without
* having to manipulate a ChannelMasker object.
*
* @param image the image to apply the operations to
* @param command the list of operations to apply
* @param name the name to give to the new image
* @param order the order of channels in the source image (may be null)
* @return the resulting image
*/
public static ImagePlus applyMasker(ImagePlus image, String command, String name, String order) {
ChannelMasker masker = createMasker(command);
return masker.apply(image, name, order);
}
/**
* Types of operations supported by the ChannelMasker object.
*/
enum OperationType {
COPY,
INVERT,
MASK,
APPLY;
static OperationType fromString(String s) {
for ( OperationType operation : OperationType.values() ) {
if ( operation.name().equalsIgnoreCase(s) ) {
return operation;
}
}
return null;
}
}
/**
* Represents a single operation.
*/
static class ChannelOperation {
int source;
OperationType type;
int[] argument;
}
/*
* Translates a channel-specifying character into a channel index, according to
* the specified order.
*/
private int getChannel(int source, String order) {
int ch;
if ( order != null && (ch = order.indexOf(source)) != -1 ) {
ch += 1;
} else {
ch = source - '0';
}
return ch;
}
/*
* Helper class to parse a ChannelMasker text description as expected by the
* ChannelMasker.createMasker methods.
*/
private static class Parser {
private static Pattern commandPattern = Pattern.compile("[ \n\t\r]*([0-9A-Z]):([A-Z]+)\\(([0-9A-Za-z_, ]*)\\)");
String command;
boolean done;
public Parser(String command) {
this.command = command;
this.done = false;
}
public ChannelOperation getNextOperation() {
if ( done ) {
return null;
}
Matcher m = commandPattern.matcher(command);
if ( !m.find() ) {
throw new IllegalArgumentException(String.format("Invalid command: %s", command));
}
ChannelOperation operation = new ChannelOperation();
operation.source = m.group(1).codePointAt(0);
if ( (operation.type = OperationType.fromString(m.group(2))) == null ) {
throw new IllegalArgumentException(String.format("Invalid command: Unknown operation %s", m.group(2)));
}
String[] args = m.group(3).trim().split(" *, *");
switch ( operation.type ) {
case COPY:
case INVERT:
operation.argument = null;
break;
case MASK:
if ( args.length == 0 ) {
throw new IllegalArgumentException("Invalid MASK command: Parameter(s) expected");