Exercise: image handling#

Duration ~30 min  ·  Session Day 1, Python notebooks

The tasks build on one another, so run the cells in order. Use the hints when you need them.

Data: data/bbbc020/ field 1h_1: a different field from the walkthrough, so your numbers will not match the ones you just saw.

import numpy as np
import tifffile
import matplotlib.pyplot as plt
import os; os.environ["DISPLAY"] = ":1"

from course import DATA, show

Task 1: load an image and describe it#

Load data/bbbc020/images/1h_1_nuclei.tif and print its shape, dtype and intensity range.

Hint 1: how do I load it?

The walkthrough used tifffile.imread(...), and DATA already points at the data/ folder, so you can build the path with DATA / "bbbc020" / "images" / ....

Hint 2, which attributes?

.shape, .dtype, and the .min() / .max() methods.

# --- your turn ---
nuclei = ...

print("shape:", ...)   # TODO: the array's shape
print("dtype:", ...)   # TODO: its data type
print("range:", ..., "to", ...)   # TODO: darkest and brightest pixel

Task 2: crop and display#

Display the image, then display a 150×150 crop taken from its centre.

Hint 1, where is the centre?

Half the number of rows and half the number of columns: nuclei.shape[0] // 2 and nuclei.shape[1] // 2.

Hint 2: how do I take 400 pixels around it?

75 either side: image[row - 75:row + 75, col - 75:col + 75].

# --- your turn ---
show(nuclei, title="full image")
plt.show()

row, col = ...   # TODO: centre row and column
crop = ...       # TODO: 150x150 around that point

show(crop, title=f"centre crop {crop.shape}")
plt.show()

Task 3: Visualize the image in Napari#

Make sure the nuclei are shown with a blue colormap.

# --- your turn ---

import napari
...

Your answer: a threshold around … (edit this cell)

Task 4: threshold and count#

Threshold the image with Otsu’s method, label the connected objects, and print how many you found.

from skimage.filters import threshold_otsu
from skimage.measure import label

# --- your turn ---
t = ...        # TODO: Otsu threshold for this image
mask = ...     # TODO: boolean mask of pixels above it
labels = ...   # TODO: label the connected objects

print("threshold:", ...)
print("objects  :", ...)

Task 5: check it against the truth#

We have a ground truth label image. Load data/bbbc020/gt/1h_1_nuclei_labels.tif and compare your object count with the number of annotated nuclei.

Then answer below: are they the same? If not, look at your labeled image and say what you think went wrong.

Hint

The ground truth is a label image, same as yours, so .max() gives the number of annotated nuclei.

# --- your turn ---
truth = ...   # TODO: load the ground-truth label image

print("you found :", labels.max())
print("annotated :", truth.max())

Your answer: … (edit this cell)

Task 6: build a composite#

Load the matching 1h_1_cells.tif and build an RGB composite with the nuclei in blue and the cell channel in green.

Hint 1: what shape does an RGB image have?

(rows, columns, 3). Start from np.zeros((*nuclei.shape, 3), dtype=np.uint8).

Hint 2, which index is which colour?

[..., 0] is red, [..., 1] green, [..., 2] blue.

# --- your turn ---
cells = ...       # TODO: load the cell channel

composite = ...   # TODO: an all-zero RGB array the same height and width
composite[..., 2] = ...   # TODO: nuclei into blue
composite[..., 1] = ...   # TODO: cells into green

plt.figure(figsize=(7, 5))
plt.imshow(composite)
plt.axis("off")
plt.show()

Task 7: Visualize the channels in Napari#

...

Bonus exercise#

Create a small Napari widget that lets you adjust the threshold and see the labeled objects update in real time.

As an example, the following code adds a function widget to the Napari viewer that takes two images and adds them together. You can adapt this to your thresholding and labeling function.

from napari.types import ImageData

def add_layers(img1: ImageData, img2: ImageData) -> ImageData:
    return img1 + img2

viewer = Viewer()
viewer.window.add_function_widget(add_layers)

Hint: You can return a layer of type LabelsData from your function, and Napari will automatically display it as a label layer.