io.github.mianalysis.mia.thirdparty.Stack_Focuser_ Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mia-modules Show documentation
Show all versions of mia-modules Show documentation
ModularImageAnalysis (MIA) is an ImageJ plugin which provides a modular framework for assembling image and object analysis workflows. Detected objects can be transformed, filtered, measured and related. Analysis workflows are batch-enabled by default, allowing easy processing of high-content datasets.
package io.github.mianalysis.mia.thirdparty;
import com.drew.lang.annotations.Nullable;import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.WindowManager;
import ij.gui.GenericDialog;
import ij.plugin.filter.PlugInFilter;
import ij.plugin.filter.RankFilters;
import ij.process.*;
import java.awt.*;
/*
* A modified copy of Stack_Focuser_.java plugin, created by Mikahil Umorin.
* Downloaded from https://imagej.nih.gov/ij/plugins/download/Stack_Focuser_.java on 06-June-2018.
*
* @author Mikhail Umorin
*
* 09.10.02 (i.e. October 9th, 2002) modified 10.07.03 -- added generation of
* height map; 4.08.03 -- changed interface for k_size and yes/no height map;
* 7.08.03 -- Plugin can be initialized with arg string to setup method and then
* runs non-interactively; sometime in 2004 -- added color support;
*
* Inspired by the mentioning of the capability of "flattening" a set of images
* of different focal planes in ImagePro 4.0. The author, however came up with
* the algorithm idea (and implementation) totally on his own without any
* reference to any similar algorithms, printed, digital, or otherwise. The
* author is open to any suggestions regarding algorithm(s), implementation,
* and/or features of the program. The program may be distributed and modified
* freely under GPL with the reference of the original source. No implicit or
* explicit warranty or suitabiluty for a particular purpose is given. See license.txt
* for detailed conditions on use, modification, and distribution of the source and binary code
* of the plugin.
*
* Contains a very simple algorythm of patching a *focused * image from a stack
* of images corresponding to different focal planes. It is very important that
* images in the stack are of the same brightness; otherwise pasting together
* pieces of different images will create artificial edges.
*
* The principle: 1) find edges for each image in a stack by running a Sobel
* filter (after noise-reducing 3x3 median filter); 2) create a "map" of how far
* the influence of edges extends; i.e. how far from a focused edge we still
* think that the image is focused by taking the maximum value in the
* neighborhood of the specified size; 3) for every pixel (x, y) based on the
* choice of the maximum "edge" value among different slices in the "map" stack
* copy the pixel value from the appropriate original image to the new image.
*
* Program works on 8-bit and 16-bit grayscale stacks, and accepts rectangular
* ROIs; if no ROI is given, plugin works on the whole image; For RGB stack the
* plugin decomposes the stack into three 8-bit R, G, and B component stacks and
* applies the above described algorithm to each one. The resulting *pasted" RGB
* image is created from the 8-bit pasted images from each component stack. The
* height map is an average of individual height maps for RGB case.
*
* If the option "R, G, and B come from same objects/structures" is set, the plugin
* determines focussed areas for green colour component only and pastes R and B components
* from the corresponding green-focussed areas.
*
* plugin converts stacks to 32-bit float to preserve precision before any
* manipulation is performed. The size in pixels (odd integer > 1) of the
* Maximum square filter is requested; trial and error would be the fastest way
* to optimize the result. The final image is written to "Focused"+ window, but not saved on the disk (the user can do that him/her
* self). Optionally, the plugin generates a "height map", i.e. an image of the
* heights of focused parts of the image. The home-grown maximum filter
* maxFilter has a square kernel and is MUCH faster then available in Process ->
* Filters -> Maximum menu. The sacrifice in quality is believed negligible for
* this kind of application even though the squareness makes it anisotropic (?)
*
* For a short but good reference on image analysis see
* http://www.cee.hw.ac.uk/hipr/html/hipr_top.html
*/
public class Stack_Focuser_ implements PlugInFilter
{
/*
* ImageStacl object of the original image
*/
private ImageStack i_stack;
public static final int BYTE=0;
public static final int SHORT=1;
public static final int FLOAT=2;
public static final int RGB=3;
/*
* Focusing kernel size, implies square kernel
*/
protected int k_size;
/*
* Index of the image type, {@link #BYTE}, {@link #SHORT}, {@link #FLOAT}, {@link #RGB}
*/
protected int type;
/*
* Width of the original image
*/
protected int o_width;
/*
* Height of the original image
*/
protected int o_height;
/*
* Num of slices in the original stack
*/
protected int n_slices;
/*
* Total number of pixels in the original image, = {@link #o_height} x {@link #o_width}
*/
protected int o_dim;
/*
* Rectangle object for image's ROI where to perform focusing. If ROI is not set,
* focusing is performed on the whole image.
*/
private Rectangle r;
/*
* Width of ROI
*/
protected int n_width;
/*
* Height of ROI
*/
protected int n_height;
/*
* Total number of pixels in ROI, = {@link #n_height } x {@link #n_width}
*/
protected int n_dim;
/*
* Filename of the original image
*/
private String o_title;
private boolean create_map = false;
private boolean onefocus = false;
private boolean smooth = false;
private GenericDialog input_dialog;
private boolean interact = true;
private ImageProcessor focused_ip = null, height_ip = null, existing_map = null;
private ImageStack focused_stack, height_stack;
private static final int redMask = 0xff0000, greenMask = 0x00ff00, blueMask = 0x0000ff;
private static final int redShift = 16, greenShift = 8, blueShift = 0;
public int setup(String arg, ImagePlus imp) {
k_size = 11;
if (arg.equalsIgnoreCase("about")) {
showAbout();
return DONE;
}
// check if the arg string has parameters to set
if (arg.indexOf("ksize=") >= 0) {
interact = false;
int pos = arg.indexOf("ksize=") + 6;
if (pos != arg.length()) {
if (arg.charAt(pos) != ' ') {
String kss;
int posn = arg.indexOf(' ', pos + 1);
if (posn > 0) {
kss = arg.substring(pos, posn);
} else {
kss = arg.substring(pos);
}
k_size = Integer.parseInt(kss);
}
}
}
if (arg.indexOf("hmap=") >= 0) {
interact = false;
int pos = arg.indexOf("hmap=") + 5;
if (pos != arg.length()) {
if (arg.charAt(pos) != ' ') {
String hms;
int posn = arg.indexOf(' ', pos + 1);
if (posn > 0) {
hms = arg.substring(pos, posn);
} else {
hms = arg.substring(pos);
}
create_map = hms.equalsIgnoreCase("true");
}
}
}
if (arg.indexOf("rgbone=") >= 0) {
interact = false;
int pos = arg.indexOf("rgbone=") + 7;
if (pos != arg.length()) {
if (arg.charAt(pos) != ' ') {
String hms;
int posn = arg.indexOf(' ', pos + 1);
if (posn > 0) {
hms = arg.substring(pos, posn);
} else {
hms = arg.substring(pos);
}
onefocus = hms.equalsIgnoreCase("true");
}
}
}
if (arg.indexOf("smooth=") >= 0) {
interact = false;
int pos = arg.indexOf("smooth=") + 7;
if (pos != arg.length()) {
if (arg.charAt(pos) != ' ') {
String hms;
int posn = arg.indexOf(' ', pos + 1);
if (posn > 0) {
hms = arg.substring(pos, posn);
} else {
hms = arg.substring(pos);
}
smooth = hms.equalsIgnoreCase("true");
}
}
}
//
if (imp == null) {
IJ.noImage();
return DONE;
}
//
ImageProcessor ip_p = imp.getProcessor();
o_title = imp.getTitle();
int dot_i = o_title.indexOf(".");
if (dot_i > 0)
o_title = o_title.substring(0, dot_i);
// determine the type of the image; getType() does not work for stacks
if (ip_p instanceof ByteProcessor)
type = BYTE;
else if (ip_p instanceof ShortProcessor)
type = SHORT;
else if (ip_p instanceof FloatProcessor)
type = FLOAT;
else
type = RGB;
i_stack = imp.getStack();
o_width = imp.getWidth();
o_height = imp.getHeight();
o_dim = o_width * o_height;
n_slices = imp.getStackSize();
// obtain ROI and if ROI not set set ROI to the whole image
r = i_stack.getRoi();
if ((r == null) || (r.width < 2) || (r.height < 2)) {
r = new Rectangle(0, 0, o_width, o_height);
i_stack.setRoi(r);
}
n_width = r.width;
n_height = r.height;
n_dim = n_width * n_height;
return DOES_8G + DOES_16 + DOES_RGB + STACK_REQUIRED + NO_CHANGES
+ NO_UNDO;
}
public void run(ImageProcessor ip)
{
// read options
// TODO allow for different x and y kern_size later
if (interact) {
input_dialog = new GenericDialog("Options");
input_dialog.addNumericField("Enter the n (>2) for n x n kernel:", k_size, 0);
input_dialog.addCheckbox("Generate height map", create_map);
input_dialog.addCheckbox("R, G, and B come from same objects/structures", onefocus);
input_dialog.addCheckbox("Smooth height map",smooth);
input_dialog.showDialog();
if (input_dialog.wasCanceled()) return;
k_size = (int)input_dialog.getNextNumber();
create_map = input_dialog.getNextBoolean();
onefocus = input_dialog.getNextBoolean();
smooth = input_dialog.getNextBoolean();
if ( input_dialog.invalidNumber() || k_size<3 ) {
IJ.error("Invalid number or " +k_size+" is incorrect! ");
return;
}
}
switch(type)
{
case BYTE: focused_ip = focusGreyStack(i_stack, BYTE); break;
case SHORT: focused_ip = focusGreyStack(i_stack, SHORT); break;
case FLOAT: focused_ip = focusGreyStack(i_stack, FLOAT); break;
case RGB:
if (onefocus) {
focusRGBStackOne(i_stack);
} else {
focusRGBStack(i_stack);
}
break;
default: break;
}
// construct the title of the new window
ImagePlus dispFocusedIpl = null;
ImagePlus dispHeightIpl = null;
String n_title = "Focused_"+o_title;
dispFocusedIpl = new ImagePlus(n_title, focused_ip);
dispFocusedIpl.setPosition(1,1,1);
dispFocusedIpl.updateChannelAndDraw();
dispFocusedIpl.show();
if (create_map) {
String nm_title = "HeightMap_" + o_title;
dispHeightIpl = new ImagePlus(nm_title, height_ip);
dispHeightIpl.setPosition(1,1,1);
dispHeightIpl.updateChannelAndDraw();
dispHeightIpl.show();
}
}
/*
* Focuses an RGB stack. All colors are focused independently.
*/
public void focusRGBStack (ImageStack rgb_stack) {
// IJ.showStatus("Processing RGB stack");
focused_stack = new ImageStack(n_width, n_height);
height_stack = new ImageStack(n_width, n_height);
// split RGB stack into R, G, and B components
// and then generateModuleList FocusGreyStack() on each independently
// Red
ImageStack colored_stack = extractColor(rgb_stack, redMask, redShift);
// IJ.showStatus("Extracted red color");
focused_ip = focusGreyStack(colored_stack, BYTE);
// IJ.showStatus("Focused red color stack");
focused_stack.addSlice("Red", focused_ip);
height_stack.addSlice("Red", height_ip);
colored_stack = null;
// Green
colored_stack = extractColor(rgb_stack, greenMask, greenShift);
// IJ.showStatus("Extracted green color");
focused_ip = focusGreyStack(colored_stack, BYTE);
// IJ.showStatus("Focused green color stack");
focused_stack.addSlice("Green", focused_ip);
height_stack.addSlice("Green", height_ip);
colored_stack = null;
// Blue
colored_stack = extractColor(rgb_stack, blueMask, blueShift);
// IJ.showStatus("Extracted blue color");
focused_ip = focusGreyStack(colored_stack, BYTE);
// IJ.showStatus("Focused blue color stack");
focused_stack.addSlice("Blue", focused_ip);
height_stack.addSlice("Blue", height_ip);
colored_stack = null;
//
ImagePlus fs_image = new ImagePlus("Focused stack", focused_stack);
fs_image.show();
fs_image.updateAndDraw();
// ImageWindow win = fs_image.getWindow();
IJ.run("RGB Color");
focused_ip = new ColorProcessor(n_width, n_height);
focused_ip.copyBits(WindowManager.getCurrentWindow().getImagePlus().getProcessor(),
0, 0, Blitter.COPY);
IJ.run("Close");
// ImagePlus hs_image = new ImagePlus("Focus height stack", height_stack);
// hs_image.show();
// hs_image.updateAndDraw();
long[] sum = new long[n_dim];
// int hs_size = height_stack.getSize();
for (int i=1; i<=height_stack.getSize(); i++)
{
byte[] pixels = (byte[]) height_stack.getPixels(i);
// addRef the value of each pixel an the corresponding position of the sum array
for (int j=0; jmax_e)
{
max_e = curr_pixels[i];
max_slice = z;
}
}
height_ip.putPixel(x, y, max_slice);
}
}
return height_ip;
}
/*
* Apply median filter to the provided height map image
*/
private void smoothHeightMap(ImageProcessor height_ip, int r) {
new RankFilters().rank(height_ip,r,RankFilters.MEDIAN);
}
/*
* By comparing max values for the same point in different slices we decide which
* original slice to use to paste into the new image at that location.
*/
private ImageProcessor pasteGreyImage(ImageStack g_stack, ImageStack m_stack, int stackType) {
ImageProcessor f_ip = null;
byte[] orig_pixels8 = null;
short[] orig_pixels16 = null;
float[] orig_pixels32 = null;
byte[] dest_pixels8 = null;
short[] dest_pixels16 = null;
float[] dest_pixels32 = null;
// If an existing map hasn't been provided, generateModuleList the following
if (existing_map == null) {
height_ip = createHeightMap(m_stack);
// If enabled, applying the median filter to smooth the height profile
if (smooth) smoothHeightMap(height_ip,k_size);
} else {
height_ip = existing_map;
}
switch (stackType)
{
case BYTE:
dest_pixels8 = new byte[n_dim];
break;
case SHORT:
dest_pixels16 = new short[n_dim];
break;
case FLOAT:
dest_pixels32 = new float[n_dim];
break;
default:
break;
}
int offset, i;
int copy_i, copy_x, copy_y;
for (int y=0; y1; odd and even do not matter, i.e. size=2 is same as size=3
*/
private float findMaxInNeigh(ImageProcessor ip_, int center_x, int center_y, int size_x, int size_y)
{
float maxVal = 0.0f;
int width_ = ip_.getWidth();
int height_ = ip_.getHeight();
float[] pixels_ = (float[]) ip_.getPixels();
int half_x= size_x / 2;
int half_y= size_y / 2;
int start_x = center_x-half_x;
int start_y = center_y-half_y;
if (start_x<0) {start_x = 0;}
if (start_y<0) {start_y = 0;}
int end_x = center_x+half_x;
int end_y = center_y+half_y;
if (end_x>width_) {end_x = width_;}
if (end_y>height_) {end_y = height_;}
int offset_, i_;
for (int y=start_y; ymaxVal) {maxVal = pixels_[i_];}
}
}
return maxVal;
}
private ImageStack extractColor(ImageStack rgbStack, int mask, int shift)
{
ImageProcessor sliceProcessor;
ImageStack g_stack = new ImageStack(n_width, n_height);
int offset, pos;
int w = rgbStack.getWidth();
int h = rgbStack.getHeight();
// gretStack = new ImageStack(w, h);
// match input stack and the new one slice by slice
for (int i=1; i<=rgbStack.getSize(); i++)
{
sliceProcessor = rgbStack.getProcessor(i);
int[] colorPixels = (int[]) sliceProcessor.getPixels();
byte[] greyPixels = new byte[w*h];
for (int y=0; y>shift);
}
}
g_stack.addSlice("", greyPixels);
}
return g_stack;
}
public void showAbout()
{
IJ.showMessage("About Stack Focuser...",
"Patches a *focused* image\n"+
" from a stack of images \n"+
"corresponding to different focal planes\n"+
"\n Mikhail Umorin ");
}
// NEW METHODS
public ImageProcessor getHeightImage() {
return height_ip;
}
public void setExistingHeightMap(@Nullable ImageProcessor existing_map) {
this.existing_map = existing_map;
}
}