Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c21037e
Beginnings of a new ImageJ macro runner
petebankhead Oct 15, 2024
e00fd4e
More steps towards a new macro runner
petebankhead Oct 15, 2024
812c46d
An almost-function macro runner
petebankhead Oct 16, 2024
18222a5
Externalize new macro runner strings
petebankhead Oct 16, 2024
8d5c947
Arrange macro options to 2 columns
petebankhead Oct 16, 2024
f56a092
Macro runner becomes closer to functional
petebankhead Oct 16, 2024
c9b1cdc
Add script support to macro runner
petebankhead Oct 16, 2024
8cf633c
Optionally log macro to the workflow
petebankhead Oct 16, 2024
813b99b
Make macro runner properties persistent
petebankhead Oct 16, 2024
8040a4d
Open macro runner with drag/drop ijm
petebankhead Oct 16, 2024
dc446b4
Add macro runner menubar
petebankhead Oct 16, 2024
6adafbe
Update NewImageJMacroRunner.java
petebankhead Oct 17, 2024
8ac6d62
Improve named params for workflow script
petebankhead Oct 17, 2024
561188f
Merge remote-tracking branch 'upstream/main' into ij-macros
petebankhead Oct 17, 2024
c71a006
Reduce GeometryTools complaints
petebankhead Oct 17, 2024
015de60
Test ImageJ macros, specify num threads
petebankhead Oct 17, 2024
c32942b
Rename ImageJ macro to script runner
petebankhead Oct 18, 2024
40e238b
Save & close menu items for ImageJ script runner
petebankhead Oct 18, 2024
6b62950
ImageJ script runner drag/drop
petebankhead Oct 18, 2024
38512b2
Initial support for example macros/scripts
petebankhead Oct 18, 2024
18cf24f
Set image properties for ImageJ script runner
petebankhead Oct 18, 2024
f061d7d
Introduce IJProcessing
petebankhead Oct 19, 2024
b506816
Adding more ImageJ convenience methods
petebankhead Oct 19, 2024
2be3a1e
More ImageJ filters and docs
petebankhead Oct 19, 2024
2fa3762
Remove problematic accelerators
petebankhead Oct 19, 2024
b0056e9
Use option to close open images
petebankhead Oct 19, 2024
c72e6e7
Support specifying channels for IJ script runner
petebankhead Oct 19, 2024
dda3926
Improve ImageJ script runner ROI masking
petebankhead Oct 19, 2024
546e5a3
Update ij-script-runner.fxml
petebankhead Oct 19, 2024
5507d15
Restrict ImageJ scripts to specific object types
petebankhead Oct 19, 2024
ae5bc08
Introduce syntax highlighting for ImageJ scripts
petebankhead Oct 20, 2024
5e5b131
Improve ImageJ macro syntax highlighting
petebankhead Oct 20, 2024
fee9cac
Improve ImageJ macro autocomplete
petebankhead Oct 20, 2024
9cd66c6
Basic syntax support for macro language
petebankhead Oct 20, 2024
352e5ab
Simplify use of ScriptLanguage
petebankhead Oct 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
package qupath.imagej.processing;

import ij.plugin.filter.RankFilters;
import ij.process.ByteProcessor;
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;

import java.util.function.Consumer;

/**
* Helper class for filtering ImageJ images.
* <p>
* Many of these methods call built-in ImageJ filters, but adding them as static methods
* in a single class here may make them easier to find and use... and there are some extras
* that aren't part of ImageJ.
* <p>
* <b>Important notes:</b>
* <ul>
* <li>In general, the input image is unchanged and a new output image is created.</li>
* <li>These methods do not pay attention to any Roi that has been set on the image!</li>
* </ul>
* This lack of Roi-attention may result in images being cropped when duplicates are created internally.
* <i>It is therefore strongly recommended to reset the Roi for any images provided as input.</i>
*
* @since v0.6.0
*/
public class IJFilters {

/**
* Apply a mean (average) filter.
* @param ip the input image
* @param radius the filter radius
* @return the filtered image
*/
public static ImageProcessor mean(ImageProcessor ip, double radius) {
return rankFilter(ip, radius, RankFilters.MEAN);
}

/**
* Apply a median filter.
* @param ip the input image
* @param radius the filter radius
* @return the filtered image
*/
public static ImageProcessor median(ImageProcessor ip, double radius) {
return rankFilter(ip, radius, RankFilters.MEDIAN);
}

/**
* Apply a maximum filter.
* @param ip the input image
* @param radius the filter radius
* @return the filtered image
*/
public static ImageProcessor maximum(ImageProcessor ip, double radius) {
return rankFilter(ip, radius, RankFilters.MAX);
}

/**
* Apply a minimum filter.
* @param ip the input image
* @param radius the filter radius
* @return the filtered image
*/
public static ImageProcessor minimum(ImageProcessor ip, double radius) {
return rankFilter(ip, radius, RankFilters.MIN);
}

/**
* Apply a dilation; this is equivalent to applying a maximum filter.
* @param ip the input image
* @param radius the filter radius
* @return the filtered image
*/
public static ImageProcessor dilate(ImageProcessor ip, double radius) {
return maximum(ip, radius);
}

/**
* Apply an erosion; this is equivalent to applying a minimum filter.
* @param ip the input image
* @param radius the filter radius
* @return the filtered image
*/
public static ImageProcessor erode(ImageProcessor ip, double radius) {
return minimum(ip, radius);
}

/**
* Apply a morphological opening; this is equivalent to applying a minimum followed by a
* maximum filter.
* @param ip the input image
* @param radius the filter radius
* @return the filtered image
*/
public static ImageProcessor open(ImageProcessor ip, double radius) {
var ip2 = ip.duplicate();
rankFilterInPlace(ip2, radius, RankFilters.MIN);
rankFilterInPlace(ip2, radius, RankFilters.MAX);
return ip2;
}

/**
* Apply a morphological closing; this is equivalent to applying a maximum followed by a
* minimum filter.
* @param ip the input image
* @param radius the filter radius
* @return the filtered image
*/
public static ImageProcessor close(ImageProcessor ip, double radius) {
var ip2 = ip.duplicate();
rankFilterInPlace(ip2, radius, RankFilters.MAX);
rankFilterInPlace(ip2, radius, RankFilters.MIN);
return ip2;
}

/**
* Apply a black tophat filter; this is equivalent to subtracting an 'opened' image from
* the original.
* @param ip the input image
* @param radius the filter radius
* @return the filtered image
* @see #open(ImageProcessor, double)
*/
public static ImageProcessor blackTopHat(ImageProcessor ip, double radius) {
return IJProcessing.subtract(
ip, open(ip, radius)
);
}

/**
* Apply a white tophat filter; this is equivalent to subtracting the original image from
* the a 'closed' image.
* @param ip the input image
* @param radius the filter radius
* @return the filtered image
* @see #close(ImageProcessor, double)
*/
public static ImageProcessor whiteTopHat(ImageProcessor ip, double radius) {
return IJProcessing.subtract(
close(ip, radius), ip
);
}

/**
* Apply an opening by (morphological) reconstruction.
* @param ip the input image
* @param radius the radius of the initial opening filter
* @return the filtered image
*/
public static ImageProcessor openingByReconstruction(ImageProcessor ip, double radius) {
return MorphologicalReconstruction.openingByReconstruction(ip, radius);
}

/**
* Apply a closing by (morphological) reconstruction.
* @param ip the input image
* @param radius the radius of the initial closing filter
* @return the filtered image
*/
public static ImageProcessor closingByReconstruction(ImageProcessor ip, double radius) {
return MorphologicalReconstruction.closingByReconstruction(ip, radius);
}

/**
* Find regional maxima in an image.
* <p>
* <b>Note:</b> Use with caution! This method is experimental and may change.
*
* @param ip the input image
* @return a binary image with 255 at the location of regional maxima and 0 elsewhere
*/
public static ByteProcessor regionalMaxima(ImageProcessor ip) {
var ipMask = ip.convertToFloatProcessor();
var ipMarker = ipMask.duplicate();
ipMask.add(1.0);
MorphologicalReconstruction.morphologicalReconstruction(ipMarker, ipMask);
return SimpleThresholding.greaterThan(ipMask, ipMarker);
}

/**
* Find regional minima in an image.
* <p>
* <b>Note:</b> Use with caution! This method is experimental and may change.
*
* @param ip the input image
* @return a binary image with 255 at the location of regional minima and 0 elsewhere
*/
public static ByteProcessor regionalMinima(ImageProcessor ip) {
var ip2 = ip.duplicate();
ip2.invert();
return regionalMaxima(ip2);
}

/**
* Suppress small local maxima in an image using a H-maxima transform.
* <p>
* <b>Note:</b> Use with caution! This method is experimental and may change.
* @param ip the input image
* @param h the height of maxima to suppress
* @return the input with maxima suppressed
*/
public static FloatProcessor hMaxima(ImageProcessor ip, double h) {
var fpMarker = ip.convertToFloatProcessor();
var fpMask = fpMarker.duplicate();
fpMarker.subtract(h);
MorphologicalReconstruction.morphologicalReconstruction(fpMarker, fpMask);
return fpMarker;
}

/**
* Suppress small local minima in an image using a H-minima transform.
* <p>
* <b>Note:</b> Use with caution! This method is experimental and may change.
* @param ip the input image
* @param h the height of minima to suppress
* @return the input with minima suppressed
*/
public static FloatProcessor hMinima(ImageProcessor ip, double h) {
var fpMarker = ip.convertToFloatProcessor();
fpMarker.multiply(-1.0);
var fpMask = fpMarker.duplicate();
fpMarker.subtract(h);
MorphologicalReconstruction.morphologicalReconstruction(fpMarker, fpMask);
fpMarker.multiply(-1.0);
return fpMarker;
}

/**
* Find regional maxima in an image above a defined height.
* <p>
* <b>Note:</b> Use with caution! This method is experimental and may change.
* @param ip the input image
* @param h the height of the maxima
* @return a binary image with 255 at the location of regional maxima and 0 elsewhere
*/
public static ByteProcessor extendedMaxima(ImageProcessor ip, double h) {
var hmax = hMaxima(ip, h);
return regionalMaxima(hmax);
}

/**
* Find regional minima in an image above a defined height.
* <p>
* <b>Note:</b> Use with caution! This method is experimental and may change.
* @param ip the input image
* @param h the height of the minima
* @return a binary image with 255 at the location of regional minima and 0 elsewhere
*/
public static ByteProcessor extendedMinima(ImageProcessor ip, double h) {
var ipDuplicate = ip.duplicate();
ipDuplicate.invert();
return extendedMaxima(ipDuplicate, h);
}

/**
* Apply a Gaussian filter to an input image.
* @param ip the input image
* @param sigma the sigma value of the Gaussian filter
* @return the filtered image
*/
public static ImageProcessor gaussian(ImageProcessor ip, double sigma) {
return applyToDuplicateInPlace(ip, ip2 -> ip2.blurGaussian(sigma));
}

/**
* Apply a Difference of Gaussians filter to an input image.
* @param ip the input image
* @param sigma1 the sigma value of the first Gaussian filter
* @param sigma2 the sigma value of the second Gaussian filter (to be subtracted)
* @return the filtered image
*/
public static FloatProcessor differenceOfGaussians(ImageProcessor ip, double sigma1, double sigma2) {
var fp1 = ip.convertToFloatProcessor();
var fp2 = fp1.duplicate();
fp1.blurGaussian(sigma1);
fp2.blurGaussian(sigma2);
return IJProcessing.subtract(fp1, fp2);
}

private static ImageProcessor rankFilter(ImageProcessor ip, double radius, int type) {
return applyToDuplicateInPlace(ip, ip2 -> rankFilterInPlace(ip2, radius, type));
}

private static ImageProcessor applyToDuplicateInPlace(ImageProcessor ip, Consumer<ImageProcessor> consumer) {
var ipDuplicate = ip.duplicate();
consumer.accept(ipDuplicate);
return ipDuplicate;
}

private static void rankFilterInPlace(ImageProcessor ip, double radius, int type) {
new RankFilters().rank(ip, radius, type);
}


}
Loading