Merging channels using ImageJ2

Hello everyone,

Is there already a plugin that uses the ImageJ2 structure to convert multichannel datasets into composite images ? I searched through the img2lib but I didn’t anything really clear.

Cheers,

Cyril

1 Like

Hey Cyril,

I would expect something like this to work:

ImgPlus<UnsignedByteType> dataset = ...;
Img<ARGBType> out = opService.create().img(dataset, new ARGBType());
CompositeXYProjector<UnsignedByteType> compositeXYProjector = new CompositeXYProjector<UnsignedByteType>(dataset, Views.iterable(out), converterlistrgba, 2);
uiService.show(out);

I can’t get it to work properly at the moment. But you can take a look at the Javadoc of CompositeXYProjector. Maybe someone else can provide more information (since I’d be interested in the solution as well).

Best,
Stefan

Hello @stelfrich,

Thank you for you input. It actually inspired me for writing a solution to this problem.
However, I went away from trying to transform the dataset into a RGBAType dataset.

Indeed, I opened JPEG and PNG images and looked how the dataset was encoded. It turns out they don’t use ARGBType datasets as I first though,t but simply multichannel Unsigned-8-Byte datasets. I used this base to create an algorithm that takes a multichannel image (regardless of how many channels it contains) and transform it into a 3-channels (RGB) image. The RGB value is obtained from the CompositeXYProjector you gave me.

This is how the algorithm works :

  • goes through everything pixel of every channel of the dataset
  • for each pixel, it retrieves the corresponding LUT and transform it into a ARGB value
  • adds the RGB values to the newly created dataset (which only contains 3 channels)

However, the algorithm doesn’t work properly. When extracting the RGB values from the RealLUTConverter, it seems that the RGB values are all the same (Red = Green = Blue). I don’t know exactly where it comes from but that’s where I’m stuck for now.

@Plugin(type = Command.class, menuPath = "Image > Stacks > Merge channels")
public class MergeChannels implements Command {

    @Parameter(type = ItemIO.INPUT)
    Dataset input;

    @Parameter(type = ItemIO.OUTPUT)
    Dataset output;

 
    @Parameter
    private DatasetService datasetService;


    @Parameter
    private UIService uiService;


    @Override
    public void run() {

        
        
        //Dataset dataset = imageDisplayService.getActiveDataset(input);

        output = convert2(input);
    }

    public <T extends RealType<T>> Dataset convert2(Dataset inputDataset) {
        ImgPlus<T> imgPlus = (ImgPlus<T>) inputDataset.getImgPlus();

        long[] dims = new long[imgPlus.numDimensions()];
        AxisType[] axes = new AxisType[imgPlus.numDimensions()];

        for (int i = 0; i != dims.length; i++) {
            dims[i] = imgPlus.dimension(i);
            axes[i] = imgPlus.axis(i).type();
        }

        ArrayList<Converter<T, ARGBType>> converters
                = new ArrayList<>();
        long channelNumber = imgPlus.max(imgPlus.dimensionIndex(Axes.CHANNEL)) + 1;
        int channelAxisIdex = imgPlus.dimensionIndex(Axes.CHANNEL);

        dims[channelAxisIdex] = 3;
        //DatasetView view = imageDisplayService.getActiveDatasetView(input);
        for (int i = 0; i != channelNumber; i++) {
            converters.add(new RealLUTConverter<>(inputDataset.getChannelMinimum(i), inputDataset.getChannelMaximum(i), imgPlus.getColorTable(i)));
        }

        output = datasetService.create(new UnsignedByteType(), dims, "", axes);

        //RandomAccess<T> inputRandomAccess = (RandomAccess<T>) inputDataset.randomAccess();
        Cursor<T> inputCursor = (Cursor<T>) imgPlus.cursor();
        RandomAccess<? extends RealType> outputCursor = output.randomAccess();
        long[] position = new long[imgPlus.numDimensions()];

        
        final ARGBType color = new ARGBType();
        
        T t;
        inputCursor.reset();
        while (inputCursor.hasNext()) {
            inputCursor.fwd();

           
            // copy the localization of the input cursor so we can localized the output
            inputCursor.localize(position);
          
            // i don't know if it's necessary
            //inputRandomAccess.setPosition(position);
            
            t = inputCursor.get();

            int channel = inputCursor.getIntPosition(channelAxisIdex);
            
            double d = t.getRealDouble();

            
          
            // get the color associated to the the pixel depending on which channel it comes from
            converters.get(channel).convert(t, color);

            // set the output cur
            position[channelAxisIdex] = 0;
            outputCursor.setPosition(position);

            // we separate the rgb from the argb integer
            int value = color.get();

            final int red = (value >> 16 ) & 0xff;
            final int green = ( value >> 8 ) & 0xff;
            final int blue = value & 0xff;

            // put it in a array
            int[] rgb = new int[]{red, green, blue};
            for (int c = 0; c != rgb.length; c++) {

                // now we go from 0 to 2 (rgb) of the output and additionate
                // the r, g and blue of the channel
                outputCursor.setPosition(c, channelAxisIdex);

                // calculate the new value of the pixel
                double p = outputCursor.get().getRealDouble() + rgb[c];

                // make sure it's not too much
                p = p > 255 ? 255 : p;

                // set the new output
                outputCursor.get().setReal(p);
            }
        }
        
        System.out.println("over");

        //output.setCompositeChannelCount(3);
        return output;
    }

}

Any idea ?

Cheers,

Cyril

What exactly are you trying to achieve, @cmongis?

The following code snippet generates an Img<ARGBType> which is not really useful in the ImageJ2 context since it is not supported on the UI level:

Img<UnsignedByteType> in = opService.convert().uint8(dataset);
out = opService.create().img(dataset, new ARGBType());
ArrayList<Converter<UnsignedByteType, ARGBType>> converterlistrgba = new ArrayList<>();
converterlistrgba.add(new ChannelARGBConverter(Channel.R));
converterlistrgba.add(new ChannelARGBConverter(Channel.G));
CompositeXYProjector<UnsignedByteType> compositeXYProjector = new CompositeXYProjector<>(in, Views.iterable(out), converterlistrgba, 2);
compositeXYProjector.map();

You could also work with Views.collapse() and operate on the CompositeType if you wanted.

1 Like

I don’t want to simply change the view but the dataset itself. I want to be able to take any dataset and transpose into a RGB dataset (3-channel Uint8) depending on which set of LUT the view is using.

==UPDATE==
The code actually work well and transform now any multichannel dataset into a RGB image. It was just that I was using the ColorTable from the dataset which is always grayscale instead of using the ColorTable of the DatasetView.

1 Like

I finally published the code for the plugin. You can find it at

The object can be used as a Command or as a simple java object (for use inside other plugin).

Cheers,

Cyril

3 Likes