How to combine qptiff file with 5 channels

I have qptiffs with 5 channels, is there a automated way that bio-formats puts them together? When I open it with QuPath I see a merged image, but I want to do it in python, and I’m not sure what scaling I need.

I figure out the color profile of the channels, Blue, Yellow, Red, Green and last one I think grayscale overlay of all(?)
Channel Color=“65535” ID=“Channel:6:0” Name=“DAPI”
Channel Color="-65281" ID=“Channel:6:1” Name=“Opal 570”
Channel Color="-16776961" ID=“Channel:6:2” Name=“Opal 690”
Channel Color=“16711935” ID=“Channel:6:3” Name=“Opal 520”
Channel Color=“255” ID=“Channel:6:4” Name=“Sample AF”

So I combined the channels manually by adding yellow channel values with equal weight to red and green channel and finally clipping the values so they are not above 255 for 8bit channel depth. Carefully comparing my output with what I see in QuPath it seems this solution is working.

However, I would still like to know what is the right or automated way to do it?
@petebankhead @dgault

Hi @mbobu I don’t think I understand the question fully enough to answer.

Do you want to convert your 5-channel image into 8-bit RGB using the same display settings as in QuPath? The min/max values per channel in QuPath are somewhat arbitrary, but the lookup table colors for each channel are indeed read through Bio-Formats where available.

If you can share screenshots and code it may be clearer.

3 Likes

Thank you so much @petebankhead for quick response! Yes, that’s what I’m looking for!
My code snippet for doing that is this:

import javabridge
import bioformats
javabridge.start_vm(class_path = bioformats.JARS)
from PIL import Image
import numpy as np

slide=bioformats.ImageReader(qptiff_files[0])
img=slide.read(series=0, rescale=False, XYWH=(35000,65000,500,500))
img_int=img.astype('int')
stack_RGB = np.stack( (img_int[:,:,2]+img_int[:,:,1], img_int[:,:,3]+img_int[:,:,1], img_int[:,:,0]), axis=2 )
img_RGB=np.clip(stack_RGB,0,255).astype('uint8')
img_PIL=Image.fromarray(img_RGB)

The goal is that this last PIL image to be be identical to whatever 0-255 range that QuPath calculates.
As you can see I’m swapping the order of channels and adding values from another channel. However I would like a more generic way of doing that. For example assumption that the colors I have here are Blue, Yellow, Red, Green is purely based on what QuPath is showing me:
image
and I didn’t actually convert the channel colors to their RGB equivalent based on values given in the XML ( I am able to extract the XML metadata, which I included the relevant part of it in the previous reply)

Thanks @mbobu it sounds a bit similar to this parallel discussion: Display multi-channel images with scikit-image

3 Likes

Yes very similar!

Not sure if it’s the right question to ask, as I’m not really familiar with how QuPath processes and displays output, but is it possible that you direct me to the part of the QuPath codebase (in Java I assume) that reads the channel colors from the metadata/ lookuptable and combines them for display in the output?

Thanks again for bearing with me!

It’s really not the prettiest part of the codebase, and I’ve planned to revisit it for a long time… but it’s here.

From memory of what it does (i.e. always a chance I’ve forgotten), if I would try to replicate it in Python/Numpy I would

  • Start with 2D N x M arrays for each channel
  • Multiply each 2D channel by a 1 x 1 x 3 array representing an RGB version of the color for that channel
  • Add the N x M x 3 arrays together, and clip to the range 0-255

At some point, scaling will need to be applied according to the minimum and maximum display range values.

This could be calculated as np.percentile(channel, 0.1) and np.percentile(channel, 99.9), for example; QuPath has a preference that allows the user to specify the Auto Brightness/Contrast saturation %.

If you’re interested, the relevant parts of ImageJ that do something similar at here and especially

3 Likes