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.