All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.mianalysis.mia.module.images.transform.ConcatenateStacks Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 1.6.12
Show newest version
package io.github.mianalysis.mia.module.images.transform;

import java.util.ArrayList;
import java.util.LinkedHashMap;

import org.scijava.Priority;
import org.scijava.plugin.Plugin;

import com.drew.lang.annotations.NotNull;

import ij.ImagePlus;
import ij.plugin.HyperStackConverter;
import ij.process.LUT;
import io.github.mianalysis.mia.MIA;
import io.github.mianalysis.mia.module.Categories;
import io.github.mianalysis.mia.module.Category;
import io.github.mianalysis.mia.module.Module;
import io.github.mianalysis.mia.module.Modules;
import io.github.mianalysis.mia.module.images.configure.SetLookupTable;
import io.github.mianalysis.mia.object.Workspace;
import io.github.mianalysis.mia.object.image.Image;
import io.github.mianalysis.mia.object.image.ImageFactory;
import io.github.mianalysis.mia.object.image.ImgPlusTools;
import io.github.mianalysis.mia.object.parameters.BooleanP;
import io.github.mianalysis.mia.object.parameters.ChoiceP;
import io.github.mianalysis.mia.object.parameters.InputImageP;
import io.github.mianalysis.mia.object.parameters.OutputImageP;
import io.github.mianalysis.mia.object.parameters.ParameterGroup;
import io.github.mianalysis.mia.object.parameters.Parameters;
import io.github.mianalysis.mia.object.parameters.SeparatorP;
import io.github.mianalysis.mia.object.parameters.abstrakt.Parameter;
import io.github.mianalysis.mia.object.refs.collections.ImageMeasurementRefs;
import io.github.mianalysis.mia.object.refs.collections.MetadataRefs;
import io.github.mianalysis.mia.object.refs.collections.ObjMeasurementRefs;
import io.github.mianalysis.mia.object.refs.collections.ObjMetadataRefs;
import io.github.mianalysis.mia.object.refs.collections.ParentChildRefs;
import io.github.mianalysis.mia.object.refs.collections.PartnerRefs;
import io.github.mianalysis.mia.object.system.Status;
import net.imagej.ImgPlus;
import net.imagej.axis.Axes;
import net.imagej.axis.AxisType;
import net.imagej.axis.DefaultLinearAxis;
import net.imglib2.Cursor;
import net.imglib2.RandomAccess;
import net.imglib2.cache.img.DiskCachedCellImg;
import net.imglib2.cache.img.DiskCachedCellImgFactory;
import net.imglib2.img.display.imagej.ImageJFunctions;
import net.imglib2.type.NativeType;
import net.imglib2.type.numeric.RealType;
import net.imglib2.view.Views;


/**
* Combine two or more image stacks into a single stack.  This module allows images to be combined along any of the axes X,Y,C,Z or T.

Note: Image stack dimensions and bit-depths must be compatible. */ @Plugin(type = Module.class, priority = Priority.LOW, visible = true) public class ConcatenateStacks & NativeType> extends Module { /** * */ public static final String INPUT_SEPARATOR = "Image input"; /** * Add another image for concatenation. */ public static final String ADD_INPUT_IMAGE = "Add image"; public static final String INPUT_IMAGE = "Input image"; /** * If enabled, the moduule can ignore any images specified for inclusion that aren't present in the workspace. This is useful if an image's existence is dependent on optional modules. */ public static final String ALLOW_MISSING_IMAGES = "Allow missing images"; /** * */ public static final String OUTPUT_SEPARATOR = "Image output"; /** * The resultant image of concatenation to be added to the workspace. */ public static final String OUTPUT_IMAGE = "Output image"; /** * Axis along which to concatenate input images. */ public static final String AXIS_MODE = "Axis mode"; public ConcatenateStacks(Modules modules) { super("Concatenate stacks", modules); } public interface AxisModes { String X = "X"; String Y = "Y"; String Z = "Z"; String CHANNEL = "Channel"; String TIME = "Time"; String[] ALL = new String[] { X, Y, Z, CHANNEL, TIME }; } static & NativeType> ArrayList getAvailableImages(Workspace workspace, LinkedHashMap collections) { ArrayList available = new ArrayList<>(); for (Parameters collection : collections.values()) { Image image = workspace.getImage(collection.getValue(INPUT_IMAGE, workspace)); if (image != null) available.add(image); } return available; } static & NativeType> long getCombinedAxisLength(ImgPlus img1, ImgPlus img2, AxisType axis) { long lengthIn1 = getAxisLength(img1, axis); long lengthIn2 = getAxisLength(img2, axis); return lengthIn1 + lengthIn2; } static & NativeType> boolean checkAxisEquality(ImgPlus img1, ImgPlus img2, AxisType axis) { long lengthIn1 = getAxisLength(img1, axis); long lengthIn2 = getAxisLength(img2, axis); return lengthIn1 == lengthIn2; } static & NativeType> long getAxisLength(ImgPlus img, AxisType axis) { int idxIn = img.dimensionIndex(axis); return idxIn == -1 ? 1 : img.dimension(idxIn); } static & NativeType> void copyPixels(ImgPlus sourceImg, ImgPlus targetImg, long[] offset, long[] dims) { int xIdxIn1 = sourceImg.dimensionIndex(Axes.X); int yIdxIn1 = sourceImg.dimensionIndex(Axes.Y); int cIdxIn1 = sourceImg.dimensionIndex(Axes.CHANNEL); int zIdxIn1 = sourceImg.dimensionIndex(Axes.Z); int tIdxIn1 = sourceImg.dimensionIndex(Axes.TIME); // Adding the first image to the output Cursor cursor1 = sourceImg.localizingCursor(); RandomAccess randomAccess1 = Views.offsetInterval(targetImg, offset, dims).randomAccess(); while (cursor1.hasNext()) { cursor1.fwd(); // Getting position long[] posIn = new long[sourceImg.numDimensions()]; cursor1.localize(posIn); // Assigning position long[] location = new long[5]; if (xIdxIn1 == -1) location[0] = 0; else location[0] = posIn[xIdxIn1]; if (yIdxIn1 == -1) location[1] = 0; else location[1] = posIn[yIdxIn1]; if (cIdxIn1 == -1) location[2] = 0; else location[2] = posIn[cIdxIn1]; if (zIdxIn1 == -1) location[3] = 0; else location[3] = posIn[zIdxIn1]; if (tIdxIn1 == -1) location[4] = 0; else location[4] = posIn[tIdxIn1]; randomAccess1.setPositionAndGet(location).set(cursor1.get()); } } public static & NativeType> ImgPlus concatenateImages(ImgPlus imgPlus, ImgPlus imgPlus2, String axis) { long[] dimsOutCombined = new long[5]; long[] offsetOut1 = new long[5]; long[] offsetOut2 = new long[5]; long[] dimsOut1 = ImgPlusTools.getDimensionsXYCZT(imgPlus); long[] dimsOut2 = ImgPlusTools.getDimensionsXYCZT(imgPlus2); // Checking bit depths if (imgPlus.firstElement().getBitsPerPixel() != imgPlus2.firstElement().getBitsPerPixel()) { MIA.log.writeWarning("Concatenate stacks: Image bit depths not the same"); return null; } if (axis.equals(AxisModes.X)) { dimsOutCombined[0] = getCombinedAxisLength(imgPlus, imgPlus2, Axes.X); offsetOut2[0] = getAxisLength(imgPlus, Axes.X); } else { if (!checkAxisEquality(imgPlus, imgPlus2, Axes.X)) { MIA.log.writeWarning("Concatenate stacks: Axes not equal along X axis"); return null; } dimsOutCombined[0] = getAxisLength(imgPlus, Axes.X); } if (axis.equals(AxisModes.Y)) { dimsOutCombined[1] = getCombinedAxisLength(imgPlus, imgPlus2, Axes.Y); offsetOut2[1] = getAxisLength(imgPlus, Axes.Y); } else { if (!checkAxisEquality(imgPlus, imgPlus2, Axes.Y)) { MIA.log.writeWarning("Concatenate stacks: Axes not equal along Y axis"); return null; } dimsOutCombined[1] = getAxisLength(imgPlus, Axes.Y); } if (axis.equals(AxisModes.CHANNEL)) { dimsOutCombined[2] = getCombinedAxisLength(imgPlus, imgPlus2, Axes.CHANNEL); offsetOut2[2] = getAxisLength(imgPlus, Axes.CHANNEL); } else { if (!checkAxisEquality(imgPlus, imgPlus2, Axes.CHANNEL)) { MIA.log.writeWarning("Concatenate stacks: Axes not equal along channel axis"); return null; } dimsOutCombined[2] = getAxisLength(imgPlus, Axes.CHANNEL); } if (axis.equals(AxisModes.Z)) { dimsOutCombined[3] = getCombinedAxisLength(imgPlus, imgPlus2, Axes.Z); offsetOut2[3] = getAxisLength(imgPlus, Axes.Z); } else { if (!checkAxisEquality(imgPlus, imgPlus2, Axes.Z)) { MIA.log.writeWarning("Concatenate stacks: Axes not equal along Z axis"); return null; } dimsOutCombined[3] = getAxisLength(imgPlus, Axes.Z); } if (axis.equals(AxisModes.TIME)) { dimsOutCombined[4] = getCombinedAxisLength(imgPlus, imgPlus2, Axes.TIME); offsetOut2[4] = getAxisLength(imgPlus, Axes.TIME); } else { if (!checkAxisEquality(imgPlus, imgPlus2, Axes.TIME)) { MIA.log.writeWarning("Concatenate stacks: Axes not equal along time axis"); return null; } dimsOutCombined[4] = getAxisLength(imgPlus, Axes.TIME); } // Creating the new Img DiskCachedCellImgFactory factory = new DiskCachedCellImgFactory<>((T) imgPlus.firstElement()); DiskCachedCellImg dcImage = factory.create(dimsOutCombined); ImgPlus imgOut = new ImgPlus(dcImage); imgOut.setAxis(new DefaultLinearAxis(Axes.X, 1), 0); imgOut.setAxis(new DefaultLinearAxis(Axes.Y, 1), 1); imgOut.setAxis(new DefaultLinearAxis(Axes.CHANNEL, 1), 2); imgOut.setAxis(new DefaultLinearAxis(Axes.Z, 1), 3); imgOut.setAxis(new DefaultLinearAxis(Axes.TIME, 1), 4); copyPixels(imgPlus, imgOut, offsetOut1, dimsOut1); copyPixels(imgPlus2, imgOut, offsetOut2, dimsOut2); dcImage.shutdown(); return imgOut; } public static & NativeType> Image concatenateImages(ArrayList inputImages, String axis, String outputImageName) { // Processing first two images ImgPlus im1 = inputImages.get(0).getImgPlus(); ImgPlus im2 = inputImages.get(1).getImgPlus(); ImgPlus imgOut = concatenateImages(im1, im2, axis); // Appending any additional images for (int i = 2; i < inputImages.size(); i++) imgOut = concatenateImages(imgOut, inputImages.get(i).getImgPlus(), axis); // If concatenation failed (for example, if the dimensions were inconsistent) it // returns null if (imgOut == null) return null; // For some reason the ImagePlus produced by ImageJFunctions.wrap() behaves // strangely, but this can be remedied // by duplicating it ImagePlus outputImagePlus = ImageJFunctions.wrap(imgOut, outputImageName).duplicate(); outputImagePlus.setCalibration(inputImages.get(0).getImagePlus().getCalibration()); ImgPlusTools.applyDimensions(imgOut, outputImagePlus); return ImageFactory.createImage(outputImageName, outputImagePlus); } static LUT[] getLUTs(Image[] images) { int count = 0; for (int i = 0; i < images.length; i++) { count = count + images[i].getImagePlus().getNChannels(); } LUT[] luts = new LUT[count]; count = 0; for (int i = 0; i < images.length; i++) { ImagePlus currIpl = images[i].getImagePlus(); for (int c = 0; c < currIpl.getNChannels(); c++) { currIpl.setPosition(c + 1, 1, 1); luts[count++] = currIpl.getProcessor().getLut(); } } return luts; } public static & NativeType> void convertToColour(Image image, ArrayList inputImages) { ImagePlus ipl = image.getImagePlus(); int nChannels = ipl.getNChannels(); int nSlices = ipl.getNSlices(); int nFrames = ipl.getNFrames(); if (nChannels > 1) ipl = HyperStackConverter.toHyperStack(ipl, nChannels, nSlices, nFrames, "xyczt", "color"); image.setImagePlus(ipl); // Set LUTs int count = 1; for (int i = 0; i < inputImages.size(); i++) { ImagePlus currIpl = inputImages.get(i).getImagePlus(); for (int c = 0; c < currIpl.getNChannels(); c++) { currIpl.setPosition(c + 1, 1, 1); LUT lut = currIpl.getProcessor().getLut(); SetLookupTable.setLUT(image, lut, SetLookupTable.ChannelModes.SPECIFIC_CHANNELS, count++); } } } @Override public Category getCategory() { return Categories.IMAGES_TRANSFORM; } @Override public String getVersionNumber() { return "1.0.0"; } @Override public String getDescription() { return "Combine two or more image stacks into a single stack. This module allows images to be combined along any of the axes X,Y,C,Z or T.
" + "
Note: Image stack dimensions and bit-depths must be compatible."; } @Override protected Status process(Workspace workspace) { // Getting parameters boolean allowMissingImages = parameters.getValue(ALLOW_MISSING_IMAGES, workspace); String outputImageName = parameters.getValue(OUTPUT_IMAGE, workspace); String axisMode = parameters.getValue(AXIS_MODE, workspace); // Creating a collection of images LinkedHashMap collections = parameters.getValue(ADD_INPUT_IMAGE, workspace); ArrayList inputImages = getAvailableImages(workspace, collections); if (!allowMissingImages && collections.size() != inputImages.size()) { MIA.log.writeError("Input images missing."); return Status.FAIL; } // If only one image was specified, simply create a duplicate of the input, // otherwise do concatenation. Image outputImage; if (inputImages.size() == 1) { outputImage = ImageFactory.createImage(outputImageName, inputImages.get(0).getImagePlus()); } else { outputImage = concatenateImages(inputImages, axisMode, outputImageName); } if (outputImage == null) return Status.FAIL; if (axisMode.equals(AxisModes.CHANNEL)) convertToColour(outputImage, inputImages); if (showOutput) outputImage.show(); workspace.addImage(outputImage); return Status.PASS; } @Override protected void initialiseParameters() { parameters.add(new SeparatorP(INPUT_SEPARATOR, this)); Parameters collection = new Parameters(); collection.add(new CustomInputImageP(INPUT_IMAGE, this, "", "Image for concatenation.")); parameters .add(new ParameterGroup(ADD_INPUT_IMAGE, this, collection, 2, "Add another image for concatenation.")); parameters.add(new BooleanP(ALLOW_MISSING_IMAGES, this, false, "If enabled, the moduule can ignore any images specified for inclusion that aren't present in the workspace. This is useful if an image's existence is dependent on optional modules.")); parameters.add(new SeparatorP(OUTPUT_SEPARATOR, this)); parameters.add(new OutputImageP(OUTPUT_IMAGE, this, "", "The resultant image of concatenation to be added to the workspace.")); parameters.add(new ChoiceP(AXIS_MODE, this, AxisModes.X, AxisModes.ALL, "Axis along which to concatenate input images.")); } @Override public Parameters updateAndGetParameters() { Workspace workspace = null; boolean allowMissingImages = parameters.getValue(ALLOW_MISSING_IMAGES, workspace); LinkedHashMap collections = parameters.getValue(ADD_INPUT_IMAGE, workspace); for (Parameters collection : collections.values()) { CustomInputImageP parameter = collection.getParameter(INPUT_IMAGE); parameter.setAllowMissingImages(allowMissingImages); } return parameters; } @Override public ImageMeasurementRefs updateAndGetImageMeasurementRefs() { return null; } @Override public ObjMeasurementRefs updateAndGetObjectMeasurementRefs() { return null; } @Override public ObjMetadataRefs updateAndGetObjectMetadataRefs() { return null; } @Override public MetadataRefs updateAndGetMetadataReferences() { return null; } @Override public ParentChildRefs updateAndGetParentChildRefs() { return null; } @Override public PartnerRefs updateAndGetPartnerRefs() { return null; } @Override public boolean verify() { return true; } // Creating a custom class for this module, which always returns true. This way // channels can go missing and this will still work. class CustomInputImageP extends InputImageP { private boolean allowMissingImages = false; private CustomInputImageP(String name, Module module) { super(name, module); } public CustomInputImageP(String name, Module module, @NotNull String imageName) { super(name, module, imageName); } public CustomInputImageP(String name, Module module, @NotNull String imageName, String description) { super(name, module, imageName, description); } @Override public boolean verify() { if (allowMissingImages) return true; else return super.verify(); } @Override public boolean isValid() { if (allowMissingImages) return true; else return super.isValid(); } @Override public T duplicate(Module newModule) { CustomInputImageP newParameter = new CustomInputImageP(name, module, getImageName(), getDescription()); newParameter.setNickname(getNickname()); newParameter.setVisible(isVisible()); newParameter.setExported(isExported()); newParameter.setAllowMissingImages(allowMissingImages); return (T) newParameter; } public boolean isAllowMissingImages() { return allowMissingImages; } public void setAllowMissingImages(boolean allowMissingImages) { this.allowMissingImages = allowMissingImages; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy