Reading czi file in python

Hello Everyone,
I’ve a few doubts on how to process czi file in python.

import czifile
from skimage import io

img = czifile.imread('file.czi')
print(img.shape)

The output is

(1, 1, 3, 1, 48, 1024, 1024, 1)

I couldn’t completely understand what each value in the output refers to

Does 3 denote the number of channels?
Does 48 denote the number of slices in the z-stack for each channel?

I would like to save the z-stack corresponding to channel 1 in tiff format. Could someone offer advice on how this can be done?

Hey!

You may want to try using aicsimageio for image reading. It utilizes and exposes the metadata for a lot of cases like this.

from aicsimageio import AICSImage

# Get an AICSImage object
img = AICSImage("file.czi")
img.data  # returns 6D STCZYX numpy array
img.dims  # returns string "STCZYX"
img.shape  # returns tuple of dimension sizes in STCZYX order

Or in your case if you want the truly raw data:

from aicsimageio.readers import CziReader

# Get reader
reader = CziReader("file.czi")
reader.data  # returns all image data from the file
reader.dims  # reads the dimensions found in metadata
reader.shape  # returns the tuple of dimension sizes in same order as dims

Plus it has some neat chunked reading options if you can’t load the whole file into memory at once with .dask_data, get_image_data, or get_image_dask_data.

Bias: I am the maintainer of the aicsimageio.

1 Like

Hi @JacksonMaxfield

Thanks a lot. aicsimageio is cool!

I could do

img = AICSImage('file.czi')
data_czyx = img.get_image_data("ZYX", C=1, S=0, T=0)  # returns 3D ZYX numpy array

to get the image data of the first channel.

May I know how to how to save this as a tiff file and how to use napari to view the first channel?

Simplest method would be something like this:

from aicsimageio import AICSImage
from aicsimageio.writers import OmeTiffWriter

img = AICSImage("file.czi")
first_channel_data = img.get_image_data("ZYX", C=1, S=0, T=0)

with OmeTiffWriter("file.ome.tiff") as writer:
    writer.save(
        first_channel_data,
        pixels_physical_size=img.get_physical_pixel_size(),
        dimension_order="ZYX",
    )

See docs for more info.

Regardless of if you save it out to a tiff or not, I would recommend installing napari-aicsimageio (pip install napari-aicsimageio).

Then you can just load napari in the terminal and do “Open File” one either the CZI or TIFF like normal. Depending on how large the file is it may be good to also de-select the aicsimageio_delayed plugin and just leave the aicsimageio plugin turned on. It will result in a much snappier experience in the napari viewer.

From it’s name: aicsimageio_delayed delays loading the image data into the napari viewer until explicitly necessary (good for files that don’t fit into machine memory) while, aicsimageio pulls all the image data in on start.

2 Likes

Thanks a lot. I installed napari and napari-aicsimageio

I tried

with napari.gui_qt():
    viewer = napari.Viewer()  # how do I use napari-aicsimageio instead?
    layer = viewer.add_image(img.data)

Now I see all 3 channels, I would like to know if there is a way to specify which channel to
visualize? Or how to display the channel name while visualizing?

I could get the channel names like this: print(img.get_channel_names())

For instance, now I see


I can navigate to different channels but not find the names.

Even if I try specifying a name,
viewer.add_image(img.data, name='Ch1-T2')

I find all channels.

While trying to do this, I get


UserWarning: Data has dimension B with depth 1, assuming B=0 is the desired value, if not the case specify B=x where x is an integer, list, tuple, range, or slice.
  f"Data has dimension {dim} with depth {data.shape[dim_index]}, "

Is it safe to ignore this warning?

Once you have the plugin installed, you should instead do:

viewer = napari.Viewer()
viewer.open('path/to/file.czi')  # or (<- uses default plugin order)
# viewer.open('path/to/file.czi', plugin='aicsimageio')  # or
# viewer.open('path/to/file.czi', plugin='aicsimageio_delayed')
1 Like

TIL about the plugin kwarg. Thanks!

1 Like
UserWarning: Data has dimension B with depth 1, assuming B=0 is the desired value, if not the case specify B=x where x is an integer, list, tuple, range, or slice.
  f"Data has dimension {dim} with depth {data.shape[dim_index]}, "

This is totally fine to ignore. In aicsimageio==4.0.0 it will be moved to debug to avoid this confusion.

1 Like

Hi @jni
Thanks a lot.

Could you please let me know how to specify the channel name that has to be viewed while using

viewer = napari.Viewer() 
    layer = viewer.add_image(img.data)

I do basically like below, when looping over all channels of an image. Specifically for an CZI the trick is to know which one of the indicies actually belongs to the C-Dimension

...
viewer.add_image(channel,
                 name=chname,
                 scale=scalefactors,
                 contrast_limits=clim,
                 blending=blending,
                 gamma=gamma)

Regarding your initial question, this snippet might help:

import czifile as zis

# get CZI object and read array
czi = zis.CziFile(myczile)

# axes will give a string 
czi_dimstring = czi.axes

# shape will give you the dimensions of the array
czi_shapeinfo = czi.shape

"""
Information CZI Dimension Characters:
- '0': 'Sample',  # e.g. RGBA
- 'X': 'Width',
- 'Y': 'Height',
- 'C': 'Channel',
- 'Z': 'Slice',  # depth
- 'T': 'Time',
- 'R': 'Rotation',
- 'S': 'Scene',  # contiguous regions of interest in a mosaic image
- 'I': 'Illumination',  # direction
- 'B': 'Block',  # acquisition
- 'M': 'Mosaic',  # index of tile for compositing a scene
- 'H': 'Phase',  # e.g. Airy detector fibers
- 'V': 'View',  # e.g. for SPIM
"""
2 Likes

import czifile as cz # You will first have to install this library by typing “pip install czifile”
import xml.etree.ElementTree as ET
import tifffile as tf
import tkinter as tk
from tkinter import filedialog
from tkinter import simpledialog
import os

#Gets the path to the image.
root = tk.Tk()
root.withdraw()
filepath_sample = filedialog.askopenfilename(title = “Select the sample image”) # This line creates a dialog window that allows the user to select the image.

#This manipulates the path to the image.
string_sample = os.path.splitext(filepath_sample)[0]
filename_sample = os.path.basename(string_sample) # This gets the actual filename without the .czi extension.

#Gets the metadata.
with cz.CziFile(filepath_sample) as czi:
xml_metadata = czi.metadata()
#find timestamps (this is relevant only if you have a time series, but you don’t have one because the fourth axis in your .czi file is empty)
#for attachment in czi.attachments():
#if attachment.attachment_entry.name == ‘TimeStamps’:
#timestamps = attachment.data()
#break

#This gets the resolution of the image in microns.
root = ET.fromstring(xml_metadata)
for neighbor in root.iter(‘ScalingX’):
pixel_size_in_meters = neighbor.text
pixel_size_in_microns = float(pixel_size_in_meters)*1000000
resolution_x = 1/pixel_size_in_microns

#This gets the time between images in seconds. Not relevant in your case because you don’t have a time series, as mentioned above.
#time_between = int(round((timestamps[1]-timestamps[0])))

#This imports the image.
img_sample = cz.imread(filepath_sample)

#Gets rid of unnecessary info (in your case the third axis is the number of channels, the fifth is the Z plane (from a Z-stack), the sixth is the X pixels, the seventh is the Y pixels). All the rest is rubbish.
img_sample = img_sample[0,0,:,0,:,:,:,0] # : means import all values. 0 means discard.

#Saves the image as a tiff for ImageJ.
tf.imwrite(filename_sample+’_for_imageJ.tiff’, img_sample, imagej=True, resolution=(resolution_x, resolution_x), metadata={‘spacing’: 1.0, ‘unit’: ‘micron’})

#This website messes with the " and the ’ and with the indentations. You’ll have to correct that to use this script in Python.

1 Like