16bit LUT for label masks

imagej
Tags: #<Tag:0x00007fb88209b7e8>

#1

@tinevez, @imagejan, @iarganda

Is there a way to have a 16bit LUT?

The application is to visualise a 16bit label mask image such that, e.g., object 561 and object 562 have different colours.

In principle, the “glasbey inverted” LUT is very good for label masks but, since it is 8bit based, it cannot show different colours for objects with similar labels in the 16bit range.

I think it is important to find a solution here, because label masks are very common and also have the advantage of being maybe the only “ROI” model that all software packages seem to agree on.

Just for illustration purposes I paste an image below showing a glasbey inverted label mask on top of grayscale raw data. I think this is very useful (if it worked for 16bit).

image


#2

Good day Christian,

no 16bit LUT with ImageJ mainly for the “8bit colour image”-reason.

I’m sure one may come up with a solution for the purpose you have in mind. Because colouring ROIs is a different affair, a custom solution appears possible. What kind of ImageJ image type would you consider for such purpose?

Regards

Herbie


#3

Hello @Herbie,
It is a 16bit grayscale('label mask") image, such as used by MorphoLibJ and 3DImageSuite for storing their segmentation results.


#4

So if this exist already (I neither use MorphoLibJ nor 3DImageSuite),
where is the problem?

Go ahead

Herbie


#5

The label mask images exist, but I don’t know how to implement a 16bit LUT (this does not exits as far as I know).


#6

Misunderstanding (I neither use MorphoLibJ nor 3DImageSuite):

Start coding!

Herbie


#7

Christian,

I’m still not quite sure what you like to obtain.

Here is a 16bit image with an overlay-fill in blue that has a transparency of 64:
Cell_16bit

Of course the PNG-image above is shown as 8bit RGB.

Here is the 16bit image with overlay zipped:
Cell_16bit.zip (50.3 KB)

Would this be a way to go?

Regards

Herbie


#8

@Herbie
Christian wants to display a label image (an image where each the pixel inside each object as a single value unique for that object) with more that 256 labels, using a different color for each.

We cannot do that with 8-bit LUT (max, 256 different colors) and are looking for a way to beat this limitation with an existing 16-bit LUT implementation.

A solution would be to generate a RGB image from the label image, but then the RGB image is not a label anymore (scalar image).


#9

I still don’t understand the problem because overlays can have much more than 255 colours: Every colour channel has a range of 255 and then there is the alpha-channel. Perhaps overlays are not the way to go, although I don’t understand why.

Regards

Herbie


#10

We do that in MorphoLibJ with the command “Plugins > MorphoLibJ > Label Images > Labels to RGB”.


#11

Still puzzeled: Isn’t an overlay a label?

Greetings

Herbie


#12

It seems that the visualization problem is that the objects are usually labeled in order of their position in the image. So if an 8-bit LUT is applied to images with >256 objects, nearby objects will have the same color, which is confusing.

If that is the problem, how important is it for the objects to be labeled in order of position? If it’s not important, could you work around it by randomizing the labels? Then it would (I think) be unlikely to have 2 regions of the same color next to one another.


#13

I thought of this as well! However, randomizing the numbers will make them inconsistent with measurements reported by morpholibj and 3dimagesuite (in terms of the object ids that are in the results tables).


#14

I was curious (and procrastinating)… but couldn’t solve it.

Here’s a Groovy script that investigates:

import ij.ImagePlus
import ij.process.ShortProcessor

import javax.imageio.ImageIO
import java.awt.image.BufferedImage
import java.awt.image.IndexColorModel

// Create a 16-bit image
int width = 256
int height = 256
def ip = new ShortProcessor(width, height)
for (int i = 0; i < width*height; i++)
    ip.setf(i, i)

// Create a random 16-bit LUT
int n = Math.pow(2, 16)-1 as int
def r = new byte[n]
def g = new byte[n]
def b = new byte[n]
def rand = new Random()
rand.nextBytes(r)
rand.nextBytes(g)
rand.nextBytes(b)
def model = new IndexColorModel(16, n, r, g, b)

// Set the color model (ok until we try to display it...)
ip.setColorModel(model)

// Create a 16-bit BufferedImage
def raster = model.createCompatibleWritableRaster(width, height)
def buffer = raster.getDataBuffer()
System.arraycopy(ip.getPixels(), 0, buffer.getData(), 0, buffer.getData().length);
def img = new BufferedImage(model, raster, false, null)

// Create an RGB version by drawing it
def img2 = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
def g2d = img2.createGraphics()
g2d.drawImage(img, 0, 0, null)
g2d.dispose()

// Optionally write the 16-bit image directly (change the path!)
// ImageIO.write(img, "PNG", new File("testing_lut16.png"))
// Optionally write the 'painted' RGB 16-bit version of the 16-bit image (change the path!)
// ImageIO.write(img2, "PNG", new File("testing_lut16_RGB.png"))

// Show the RGB version in ImageJ
def imp16RGB = new ImagePlus("Original 16-bit", img2)
imp16RGB.show()

// Try to show the image in ImageJ (doesn't work!)
// Fails with java.lang.IllegalArgumentException: Raster ByteInterleavedRaster: width = 256 height = 256 #numDataElements 1 dataOff[0] = 0 is incompatible with ColorModel IndexColorModel: #pixelBits = 16 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@384e4aaa transparency = 1 transIndex   = -1 has alpha = false isAlphaPre = false
def imp16 = new ImagePlus("Original 16-bit", ip)
imp16.show()

Basically, you can create a 16-bit IndexColorModel. And you can pass it to a ShortProcessor, but ImageJ will internally use an 8-bit representation for display and there it goes wrong.

If the BufferedImage was displayed by drawing it to a canvas, that it should work - as shown by creating the RGB version in the above code. But the fact that an 8-bit image is being generated instead using the same color model appears to thwart this.

I’d be very interested in any solution I’m missing. Otherwise I’d tend towards either going for RGB (and preferably storing a reverse LUT to be able to regain the original value from the packed RGB representation), or else trying to just shuffle the labels.


#15

Although that does hint at something you can do, which may or may not help…

import ij.ImagePlus
import ij.gui.ImageRoi
import ij.gui.Overlay
import ij.process.ShortProcessor

import java.awt.image.BufferedImage
import java.awt.image.IndexColorModel

// Create a 16-bit image
int width = 256
int height = 256
def ip = new ShortProcessor(width, height)
for (int i = 0; i < width*height; i++)
    ip.setf(i, i)

// Create a random 16-bit LUT
int n = Math.pow(2, 16)-1 as int
def r = new byte[n]
def g = new byte[n]
def b = new byte[n]
def rand = new Random()
rand.nextBytes(r)
rand.nextBytes(g)
rand.nextBytes(b)
def model = new IndexColorModel(16, n, r, g, b)

// Create a 16-bit BufferedImage
def raster = model.createCompatibleWritableRaster(width, height)
def buffer = raster.getDataBuffer()
System.arraycopy(ip.getPixels(), 0, buffer.getData(), 0, buffer.getData().length);
def img = new BufferedImage(model, raster, false, null)
println(img)
println(raster)

// Try to show the image in ImageJ with a translucent overlay
def imp16 = new ImagePlus("Original 16-bit", ip)
def roi = new ImageRoi(0, 0, img)
def overlay = new Overlay(roi)
roi.setOpacity(0.5)
imp16.setOverlay(overlay)
imp16.show()

This creates an ImageRoi with the same 16-bit IndexColorModel, and then displays it (with opacity = 0.5) on top of the image. This does appear to work, and means that your 16-bit data should be accessible unchanged.


#16

@Herbie

Below you find an label mask image from the segmentation.
Could you show me how you would covert the label mask image to an overlay?
Is the trick maybe the code that @petebankhead just posted few seconds ago?

label-maks-ij-forum.zip (113.5 KB)


#17

Christian,

I’ll have a look at your data and yes, Pete’s suggestion is based on a overlays.

Overlays can have the full RGB 24bit color scope plus alpha.

Here is a macro I’ve posted in Nov. 2017 that allows one to set the fill color for existing overlays:

run( "To ROI Manager" );
idx = roiManager( "count" );
rois = Array.getSequence( idx );
for ( i=0; i < idx; i++ ) { rois[i] = d2s( 1+rois[i], 0 ); }
Dialog.create("Set Overlay Fill");
Dialog.addChoice( "Index", rois );
Dialog.addSlider( "Opacity", 0, 255, 128 );
Dialog.addSlider( "Red", 0, 255, 0 );
Dialog.addSlider( "Green", 0, 255, 0 );
Dialog.addSlider( "Blue", 0, 255, 0 );
Dialog.show();
idx = parseInt( Dialog.getChoice() ) - 1;
c = padStr( toHex( Dialog.getNumber() ) );
c = c + padStr( toHex( Dialog.getNumber() ) );
c = c + padStr( toHex( Dialog.getNumber() ) );
c = c + padStr( toHex( Dialog.getNumber() ) );
roiManager( "select", idx );
roiManager( "Set Fill Color", c );
run( "From ROI Manager" );
run( "Overlay Options...", "stroke=none width=0 fill=none" ); // removes lable
close( "ROI Manager" );
exit();
function padStr( hex ) {
	if ( lengthOf( hex ) < 2 ) { return "0" + hex; } else { return hex; }
}

So if you have selections, e.g. in the ROI Manager, then make overlays of them. After that you can set the desired colors with this macro. Of course this is just to show how it looks like …

Regards

Herbie


#18

Christian,

could you please explain what is what in the provided stack and what you would like to see in the end.
(Slices 1 and 5…9 are empty.)

And please respect the fact that I have no experience with “MorphoLibJ” and “3DImageSuite”. So please explain without referring to their function.

Regards

Herbie


#19

For what it’s worth, this would be the approach ‘my’ way - ensuring that 0 is treated as transparent and stack slices are set appropriately:

import ij.IJ
import ij.gui.ImageRoi
import ij.gui.Overlay
import ij.process.ShortProcessor

import java.awt.image.BufferedImage
import java.awt.image.IndexColorModel

// Create a random 16-bit LUT
int n = Math.pow(2, 16)-1 as int
def r = new byte[n]
def g = new byte[n]
def b = new byte[n]
def a = new byte[n]
def rand = new Random(100L)
rand.nextBytes(r)
rand.nextBytes(g)
rand.nextBytes(b)
Arrays.fill(a, (byte)255)
a[0] = 0
def model = new IndexColorModel(16, n, r, g, b, a)

// Create 16-bit ImageRois, and add to an overlay
def imp = IJ.getImage()
int width = imp.getWidth()
int height = imp.getHeight()
def overlay = new Overlay()
for (int s = 1; s <= imp.getStack().getSize(); s++) {
    def pixels = imp.getStack().getPixels(s) as short[]
    def raster = model.createCompatibleWritableRaster(width, height)
    def buffer = raster.getDataBuffer()
    System.arraycopy(pixels, 0, buffer.getData(), 0, buffer.getData().length);
    def img = new BufferedImage(model, raster, false, null)
    println(img)
    println(raster)

    // Try to show the image in ImageJ with a translucent overlay
    def roi = new ImageRoi(0, 0, img)
    roi.setOpacity(0.75)
    roi.setPosition(s)
    overlay.add(roi)
}
imp.setOverlay(overlay)

Of course, it obeys the hide/show overlay commands as it should.


#20
import ij.IJ
import ij.ImagePlus
import ij.gui.ImageRoi
import ij.gui.Overlay
import ij.process.ShortProcessor

import java.awt.image.BufferedImage
import java.awt.image.IndexColorModel

impRaw = IJ.openImage("/Users/tischer/Documents/data-ij-forum.zip");
impLabel = IJ.openImage("/Users/tischer/Documents/label-maks-ij-forum.zip");

// Create a random 16-bit LUT
int n = Math.pow(2, 16)-1 as int
def r = new byte[n]
def g = new byte[n]
def b = new byte[n]
def a = new byte[n]
def rand = new Random(100L)
rand.nextBytes(r)
rand.nextBytes(g)
rand.nextBytes(b)
Arrays.fill(a, (byte)255)
a[0] = 0
def model = new IndexColorModel(16, n, r, g, b, a)

// Create a colored overlay
int width = impLabel.getWidth()
int height = impLabel.getHeight()
def overlay = new Overlay()
for (int s = 1; s <= impLabel.getStack().getSize(); s++) {
    def pixels = impLabel.getStack().getPixels(s) as short[]
    def raster = model.createCompatibleWritableRaster(width, height)
    def buffer = raster.getDataBuffer()
    System.arraycopy(pixels, 0, buffer.getData(), 0, buffer.getData().length);
    def img = new BufferedImage(model, raster, false, null)
    def roi = new ImageRoi(0, 0, img)
    roi.setOpacity(0.75)
    roi.setPosition(s)
    overlay.add(roi)
}

impRaw.setOverlay( overlay )
impRaw.show()

@petebankhead, thanks! Your approach works! I modified it to work with two input images: raw and label.

image

One drawback of this method is however that upon hovering with the mouse one does not see the object label ID anymore, which in practice is an issue, because it hinders one from comparing the segmented objects with measurement values as stored in a table.