Colors wrong when saving ND2 to png
Hi,
I am using ND2reader to take each color channel (3 in my case) combine and export to a tif. This is an example of what it should look like:

but instead I get this image which seems like the colors are completely off.

doing an image analysis and comparison using ImageMagick it seems like my script is just not saving it as 16 bit or combining as 16 bit at all. Any thoughts?
Image from script -> ImageFromND2_hour0.frame0.png PNG 512x512 512x512+0+0 8-bit sRGB 400303B 0.000u 0:00.002
Image From NisElements -> 31.5.21 alk ascending concentration_croppedt01xy01.tif TIFF 512x512 512x512+0+0 16-bit sRGB 1.51925MiB 0.031u 0:00.035
My script:
import time
start_time = time.time()
from nd2reader import ND2Reader, legacy
import cv2
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
# Print iterations progress
filelocation = 'Nd2 Conversion\\31.5.21 ALK ascending concentration_Cropped.nd2'
from nd2reader import ND2Reader
with ND2Reader(filelocation) as images:
l = len(images) * images[0].metadata['fields_of_view'][-1]
addition = 100 / l
np.set_printoptions(precision=3)
numberoffiles = 0
for x in range(len(images)):
for fov in images[x].metadata['fields_of_view']:
c0 = images.parser.get_image_by_attributes(frame_number=x,field_of_view=fov,channel=0,z_level=0,height=512,width=512)
c1 = images.parser.get_image_by_attributes(frame_number=x,field_of_view=fov,channel=1,z_level=0,height=512,width=512)
c2 = images.parser.get_image_by_attributes(frame_number=x,field_of_view=fov,channel=2,z_level=0,height=512,width=512)
no_img = 3
imagefinal = c0/no_img + c1/no_img + c2/no_img
plt.imsave("Nd2 Conversion\\ALK Cropped Python\\ImageFromND2_hour" + str(x) + ".frame" + str(fov) + ".png", imagefinal)
numberoffiles += 1
print("Complete!")
print("--- %s seconds ---" % (time.time() - start_time))
Hi @LordKnish, I believe the color issue is due to the default value that matplotlib assigns to the cmap option of imsave (your plt.imsave part. The default values is set to use the viridis colormap, while you want an RGB image (see here).
I see different options here:
- Change the colormap value to one that suits you best.
- Instead of summing the channels, concatenate them to obtain an MxNx3 image and pass it as RGB.
imsaveshould then print it assigning the Red, Green, and Blue colors to the three channels. Just bare in mind that the order here is crucial.
Hope this helps! Best, G
I tried experimenting with colormaps both through matplotlib and also through cv2 and still had issues with colors not showing up. Also when concatenating the Images together I get an error when trying to save since each array is uin16. When I have tried converting to Uint8 I lose data and get lines in the image.
Also some more info on the channels themselves. C0 is a gray / white layer that is just the device we are conducting analysis on C1 is the blue layer (Dapi) C2 is the red layer (Cy3)
each are two channels and I;16.
Let me know what you think Thanks, B
@ggirelli This is the results of the colormaps tests I tried. These are the most similar results of colormaps with their respective names. The issue is I am still missing that layer of blue and red from the Cy3 and Dapi layers.

I see. I have to admit that I have limited experience with cv2 and generally use skimage and tifffileinstead.
Regarding the issue with bit-depth, I would recommend checking out the tifffile package, as I have never had issue in importing/exporting images with it.
Regarding the colormap issue, I have never assigned colors to channels in Python. To the best of my knowledge (quite possibly outdated or simply wrong), the TIFF format has tags for the colorspace and colormap.
I found a post by the curator of the tifffile package with details on how to handle extra ImageJ metadata to specify channel colors, maybe that can help.
@ggirelli I am going to try out Tifffile and see how it works with saving a concatenated image.
As for the TIFF formats tags. below is a full analysis of the tiff original image during ImageMagick. Let me know what you think.
Filename: 31.5.21 alk ascending concentration_croppedt01xy01.tif
Format: TIFF (Tagged Image File Format)
Mime type: image/tiff
Class: DirectClass
Geometry: 512x512+0+0
Units: PixelsPerInch
Colorspace: sRGB
Type: TrueColor
Endianness: LSB
Depth: 16-bit
Channel depth:
Red: 16-bit
Green: 16-bit
Blue: 16-bit
Channel statistics:
Pixels: 262144
Red:
min: 0 (0)
max: 65280 (0.996109)
mean: 29203.9 (0.445623)
median: 5120 (0.0781262)
standard deviation: 11398.8 (0.173935)
kurtosis: 0.200502
skewness: 0.0848955
entropy: 0.93399
Green:
min: 0 (0)
max: 65280 (0.996109)
mean: 18319.7 (0.27954)
median: 512 (0.00781262)
standard deviation: 9184.9 (0.140153)
kurtosis: 3.60796
skewness: 1.29658
entropy: 0.843653
Blue:
min: 512 (0.00781262)
max: 65280 (0.996109)
mean: 41153.6 (0.627964)
median: 15104 (0.230472)
standard deviation: 10664.5 (0.162729)
kurtosis: 0.205034
skewness: -0.545971
entropy: 0.924799
Image statistics:
Overall:
min: 0 (0)
max: 65280 (0.996109)
mean: 29559.1 (0.451042)
median: 6912 (0.10547)
standard deviation: 10416.1 (0.158939)
kurtosis: -0.783263
skewness: 0.191493
entropy: 0.900814
Rendering intent: Perceptual
Gamma: 0.454545
Chromaticity:
red primary: (0.64,0.33)
green primary: (0.3,0.6)
blue primary: (0.15,0.06)
white point: (0.3127,0.329)
Matte color: grey74
Background color: white
Border color: srgb(223,223,223)
Transparent color: none
Interlace: None
Intensity: Undefined
Compose: Over
Page geometry: 512x512+0+0
Dispose: Undefined
Iterations: 0
Compression: None
Orientation: TopLeft
Properties:
date:create: 2021-06-23T13:33:59+00:00
date:modify: 2021-06-22T11:13:00+00:00
signature: da8934404d6a972eef62315c5a993a2a9d7300419660cec52bd535270fcc81cd
tiff:alpha: unspecified
tiff:endian: lsb
tiff:photometric: RGB
tiff:rows-per-strip: 2
Artifacts:
verbose: true
Tainted: False
Filesize: 1.51925MiB
Number pixels: 262144
Pixels per second: 5.11624MP
User time: 0.016u
Elapsed time: 0:01.051
Version: ImageMagick 7.1.0-1 Q16 x64 2021-06-21 https://imagemagick.org
identify: Unknown field with tag 65325 (0xff2d) encountered. `TIFFReadDirectory' @ warning/tiff.c/TIFFWarnings/960.
identify: Unknown field with tag 65326 (0xff2e) encountered. `TIFFReadDirectory' @ warning/tiff.c/TIFFWarnings/960.
identify: Unknown field with tag 65327 (0xff2f) encountered. `TIFFReadDirectory' @ warning/tiff.c/TIFFWarnings/960.
identify: Unknown field with tag 65329 (0xff31) encountered. `TIFFReadDirectory' @ warning/tiff.c/TIFFWarnings/960.
identify: Unknown field with tag 65330 (0xff32) encountered. `TIFFReadDirectory' @ warning/tiff.c/TIFFWarnings/960.
identify: Unknown field with tag 65331 (0xff33) encountered. `TIFFReadDirectory' @ warning/tiff.c/TIFFWarnings/960.
identify: Unknown field with tag 65332 (0xff34) encountered. `TIFFReadDirectory' @ warning/tiff.c/TIFFWarnings/960.
identify: Unknown field with tag 65333 (0xff35) encountered. `TIFFReadDirectory' @ warning/tiff.c/TIFFWarnings/960.
going to investigate that post from tifffile curator but not sure how well it will work since I want to use the images for analysis from an AI not with ImageJ
If the analysis is done via deep learning or similar, I would not expect the color label to matter, only the intensity of the different channels (if the ML approach you use can handle them separately). If that is the case, even if you see the wrong colors or use different color maps, would that affect your results? 🤔
I would also recommend opening an issue for help in the opencv or tifffile repositories, as they might know more details on this :smile:
If the analysis is done via deep learning or similar, I would not expect the color label to matter, only the intensity of the different channels (if the ML approach you use can handle them separately). If that is the case, even if you see the wrong colors or use different color maps, would that affect your results? 🤔
The main thing is that our AI works a bit differently but I can't really go into detail on that. Either way we need to convert the ND2 file to either tiff or png and have the output be the same as NisElements output. First of all I think that combining the channels is the first thing that needs to change. You said concatenating the channels together so I will try that out with Tifffile but just keep in mind that each channel are two channel PIMs frames so I wonder if they need some sort of filter on them? Thoughts?
Update @ggirelli I tried out Tifffile and it outputs a black image and posts the following error to the console TiffWriter: data are stored as RGB with contiguous samples. Specify the 'photometric' parameter to silence this warningomplete [ImageFromND2_x0.fov4.png]
I can open an issue there if you are unsure
code:
import time
start_time = time.time()
from nd2reader import ND2Reader, legacy
import cv2
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
import tifffile
# Print iterations progress
def printProgressBar (iteration, total, prefix = '', suffix = '', decimals = 1, length = 100, fill = '█', printEnd = "\r"):
"""
Call in a loop to create terminal progress bar
@params:
iteration - Required : current iteration (Int)
total - Required : total iterations (Int)
prefix - Optional : prefix string (Str)
suffix - Optional : suffix string (Str)
decimals - Optional : positive number of decimals in percent complete (Int)
length - Optional : character length of bar (Int)
fill - Optional : bar fill character (Str)
printEnd - Optional : end character (e.g. "\r", "\r\n") (Str)
"""
percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
filledLength = int(length * iteration // total)
bar = fill * filledLength + '-' * (length - filledLength)
print(f'\r{prefix} |{bar}| {percent}% {suffix}', end = printEnd)
# Print New Line on Complete
if iteration == total:
print()
def clearConsole():
command = 'clear'
if os.name in ('nt', 'dos'): # If Machine is running on Windows, use cls
command = 'cls'
os.system(command)
filelocation = 'Nd2 Conversion\\31.5.21 ALK ascending concentration_Cropped.nd2'
from nd2reader import ND2Reader
with ND2Reader(filelocation) as images:
l = len(images) * images[0].metadata['fields_of_view'][-1]
addition = 100 / l
np.set_printoptions(precision=3)
numberoffiles = 0
for x in range(len(images)):
for fov in images[x].metadata['fields_of_view']:
printProgressBar(numberoffiles + addition, l, prefix = 'Progress:', suffix = 'Complete [ImageFromND2_x' + str(x) + ".fov" + str(fov) + ".png]" , length = 100)
c0 = images.parser.get_image_by_attributes(frame_number=x,field_of_view=fov,channel=0,z_level=0,height=512,width=512)
c1 = images.parser.get_image_by_attributes(frame_number=x,field_of_view=fov,channel=1,z_level=0,height=512,width=512)
c2 = images.parser.get_image_by_attributes(frame_number=x,field_of_view=fov,channel=2,z_level=0,height=512,width=512)
no_img = 3
#imagefinal = c0/no_img + c1/no_img + c2/no_img
imagefinal = np.stack((c2,c0,c1),axis=2)
tifffile.imsave("Nd2 Conversion\\ALK Cropped Python\\ImageFromND2_hour" + str(x) + ".frame" + str(fov) + str(x) + ".tif", imagefinal)
numberoffiles += 1
input()
clearConsole()
clearConsole()
print("Complete!")
print("--- %s seconds ---" % (time.time() - start_time))