Cropping Subregion from Image in Plugin

imagej
plugin
java
crop

#1

Okay… I gave up on a supposedly simple task as cropping a subregion of an image in order to write a very simple ImageJ plugin. I don’t want to be cocky with it, and I don’t want to imply that development is easy, but I really have difficulties to understand why such a basic data manipulation task requires me to understand concept of ImgLib2, RandomAccessibles, the Dataset type and other ImageFactories and of course maintaining overview about legacy versions. Don’t get me wrong I didn’t intend to offend anybody, and if I did anyway I apologize. I am just frustrated about how much time this can eat, which in my opinion should be an intuitive one-liner in every image processing library.

Alright here is my example which is basically just the example-imagej-command from github. Assume I have read in a RGBA *.png image and I want to get completely rid of the alpha channel, and want to split the remaining channels into three separate variables. And then crop different regions from these three distinct 2D datasets (specified by x and y coordinates).

public class CropImage<T extends RealType<T>> implements Command {

	private int channel = 2; // selected channel e.g. red=0, green=1, blue=2

	@Parameter
	private Dataset currentData;
	@Parameter
	private UIService uiService;
	@Parameter
	private OpService opService;

	@Override
	public void run() {
		Img<T> orig_img = (Img<T>) currentData.getImgPlus();
		// ... insufficient capabilities to crop orig_img
        // RandomAccessibleInterval<T> view;
        // view = Views.Interval( orig_img, ... , ... );
        // view = Views.offsetInterval( orig_img, ... , ... );
	}

	public static void main(final String... args) throws Exception {
		final ImageJ ij = new ImageJ();
		ij.ui().showUI();
		final File file = ij.ui().chooseFile(null, "open");

		if (file != null) {
			final Dataset dataset = ij.scifio().datasetIO().open(file.getPath());
			ij.ui().show(dataset);
			ij.command().run(CropImage.class, true);
		}
	}
}

Now from there, how do I get the above mentioned separation as an example? I would prefer to use the Views.interval as this was the most promising and obvious from the imglib2 examples page but it turned out, that this command first ignores any offset passed, i.e I always get the cut from the upper left corner (0, 0) and second doesn’t seem to respect the color channels passed.

I also tried to apply the static SplitChannels() method as I found it by search on the forum and seem not be able to import the correct library to do so. I would appreciate if someone helps out now. Thank you in advance.


#2

Good day,

I bear with you!

Did you try it the ImageJ-1 way?

Here is all you need to know to write such PlugIns
https://imagingbook.files.wordpress.com/2013/06/tutorial171.pdf
and I think it is fairly easy.

Otherwise, I recommend for such tasks to use the Image-macro language (much easier to use than scripting languages) which is really easy to learn and provides fast execution, except for pixelwise operations.

Regards

Herbie


#3

Hey Herbie,

thank you for your quick answer and especially the linked document with the tutorial which I saved immediately :smiley: and of course will take a look on. As you probably could see easily I am a newcomer to Java, so I actually only tried out the most recent tutorial as linked above.

Right after airing my grievances, I think I got it working as expected but can’t really tell what exactly happened meanwhile, which altered behaviour to my pleasure.

However, I just share my code now here, once anybody else maybe runs into a similar situation :sweat_smile:

import net.imagej.Dataset;
import net.imagej.ImageJ;

import net.imagej.ops.OpService;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.img.Img;
import net.imglib2.type.numeric.RealType;
import net.imglib2.view.Views;

import org.scijava.command.Command;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.Plugin;
import org.scijava.ui.UIService;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;

@Plugin(type = Command.class, menuPath = "Plugins>CropImage")
public class CropImage<T extends RealType<T>> implements Command {

	@Parameter
	private Dataset currentData;
	@Parameter
	private UIService uiService;
	@Parameter
	private OpService opService;

	public int binlog(int bits) {
		
		//determine next lower binary logarithm to passed int via bitshifts
		int log = 0;
		if ((bits & 0xffff0000) != 0) {
			bits >>>= 16;
			log = 16;
		}
		if (bits >= 256) {
			bits >>>= 8;
			log += 8;
		}
		if (bits >= 16) {
			bits >>>= 4;
			log += 4;
		}
		if (bits >= 4) {
			bits >>>= 2;
			log += 2;
		}
		return log + (bits >>> 1);
	}

	@Override
	public void run() {
		
		Img<T> orig_img = (Img<T>) currentData.getImgPlus();		
		int ndims = orig_img.numDimensions();	
		long[] dims = new long[ndims]; 
		orig_img.dimensions(dims);
		
		ArrayList<Integer> log2_dims = new ArrayList<>();
		
		for (long item : dims) {
			//binlog calculates next lower exponent to base 2 based upon passed integer
			log2_dims.add( (int)Math.pow(2, binlog((int)item)) ); 
		}
		
		//assuming first and second index of whatever image read is XY-Plane according to a greyscale 2D Image
		List<Integer> xy_plane = log2_dims.subList(0, 1);
		
		//get minimum dimension in order to crop the maximum quadratic piece of the image without truncation
		long ld_min = Collections.min(xy_plane);
		
		long x = (dims[0] - ld_min) / 2;
		long y = (dims[1] - ld_min) / 2;
		
		//crop image
		RandomAccessibleInterval<T> view;
		
		if (ndims>2) {
			long[] crop_strt = new long[] { x, y, 0 };
			long[] crop_length = new long[] { ld_min, ld_min, dims[ndims-1] };
			view = Views.offsetInterval( orig_img, crop_strt, crop_length );
		}
		else {
			long[] crop_strt = new long[] { x, y };
			long[] crop_length = new long[] { ld_min, ld_min };
			view = Views.offsetInterval(orig_img, crop_strt, crop_length );
		}
		
		uiService.show(view);
	}

	public static void main(final String... args) throws Exception {
		// create the ImageJ application context with all available services
		final ImageJ ij = new ImageJ();
		ij.ui().showUI();

		// ask the user for a file to open
		final File file = ij.ui().chooseFile(null, "open");

		if (file != null) {
			// load the dataset
			final Dataset dataset = ij.scifio().datasetIO().open(file.getPath());

			// show the image
			ij.ui().show(dataset);

			// invoke the plugin
			ij.command().run(CropImage.class, true);
		}
	}
}

#4

Glad that you figured it out, @KansasCityShuffle

ImgLib2 can be quite confusing, I agree :slight_smile:

You might want to have a look at the ImageJ Ops framework that aims at making stuff like this (a little) easier (although you still require some knowledge of the underlying ImgLib2 data structures, such as Img and RandomAccessibleInterval, I’m afraid…). The following Groovy script (runnable from the script editor) illustrates how to use the intervalView op to perform the crop operation on the first two axes of the input, independent on how many axes are present in total:

#@ Img input
#@ OpService ops
#@ Long xStart
#@ Long yStart
#@ Long xStop
#@ Long yStop
#@output result

min = new long[input.numDimensions()]
max = new long[input.numDimensions()]
input.min(min)
input.max(max)

min[0] = xStart
min[1] = yStart
max[0] = xStop
max[1] = yStop

result = ops.run("intervalView", input, min, max)