Skip to content

Commit 0a75171

Browse files
committed
Splitting Napari exercise into separate console/tool menu exercises. Adding confocal-series_zstack.tif
1 parent d32a01a commit 0a75171

2 files changed

Lines changed: 128 additions & 58 deletions

File tree

data/confocal-series_zstack.tif

7.64 MB
Binary file not shown.

episodes/05-napari.md

Lines changed: 128 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: 'Introduction to Napari'
3-
teaching: 10
4-
exercises: 2
3+
teaching: 60
4+
exercises: 4
55
---
66

77
:::::::::::::::::::::::::::::::::::::: questions
@@ -15,17 +15,17 @@ exercises: 2
1515

1616
## Starting Napari
1717

18-
With Napari installed, run:
18+
With Napari installed and its Conda environment active, run:
1919

2020
napari
2121

2222
This will start up Napari in a new window. This may take about a minute, especially
23-
if being done for the first time.
23+
the first time.
2424

2525

2626
## Looking around
2727

28-
In the 'File' menu, go to 'Open Sample -> 'napari builtins -> Cells (3D+2Ch). Looking
28+
In the 'File' menu, go to 'Open Sample -> napari builtins -> Cells (3D+2Ch)'. Looking
2929
at the sidebar, Napari has opened two **layers** - 'membrane' and 'nuclei'. At the top
3030
of the sidebar there are options for displaying layers in different colours and opacity,
3131
and the eye symbol on the layer can be checked or unchecked to show/hide it:
@@ -40,7 +40,7 @@ In the case of z-stack or time series images such as this one, there is also a s
4040
at the bottom for scrolling through the layers.
4141

4242
::::::::::::::::::::::::::::::::::::: challenge
43-
## Exercise 15: Opening an image
43+
## Exercise 14: Napari tools
4444

4545
With 'Cells (3D + 2Ch)' open, go to 'Tools -> Filtering / Noise Removal' and
4646
look at the options available. What are these options? What happens if you
@@ -60,14 +60,13 @@ for Gaussian filters:
6060

6161
![](fig/5_3_gaussian_filter.jpg){alt='Gaussian filter'}
6262

63-
6463
:::::::::::::::::::::::::::::::::
6564
:::::::::::::::::::::::::::::::::::::::::::::::
6665

6766

6867
## Loading real-world images
6968

70-
The example images work nicely with Napari, but sometimes it may be necessary to
69+
The example images work nicely with Napari, but sometimes it may be more difficult to
7170
coax Napari into loading an image correctly. In 'File', select 'Open File(s)...'
7271
and load up FluorescentCells_3channel.tif. Doing this, we find the results aren't
7372
quite what we expect - the three channels have been loaded as if they're a z-stack.
@@ -87,107 +86,178 @@ at the image's shape:
8786

8887
```python
8988
from skimage.io import imread
90-
img = imread('path/to/FluorescentCells_3channel.tif')
91-
img.shape
92-
(512, 512, 3)
89+
iage = imread('path/to/FluorescentCells_3channel.tif')
90+
image.shape
91+
# returns: (512, 512, 3)
9392
```
9493

9594
From this, we can see that the colour channels are the third axis. Using this,
9695
We can now load each channel as a new layer:
9796

9897
```python
99-
viewer.add_image(img[:, :, 0], name='FluorescentCells_ch0', colormap='cyan')
100-
viewer.add_image(img[:, :, 1], name='FluorescentCells_ch1', colormap='yellow', blending='additive')
101-
viewer.add_image(img[:, :, 2], name='FluorescentCells_ch2', colormap='magenta', blending='additive')
98+
viewer.add_image(image[:, :, 0], name='FluorescentCells_ch0', colormap='red')
99+
viewer.add_image(image[:, :, 1], name='FluorescentCells_ch1', colormap='green', blending='additive')
100+
viewer.add_image(image[:, :, 2], name='FluorescentCells_ch2', colormap='blue', blending='additive')
102101
```
103102

104103
The `blending='additive'` option prevents layers on top from obscuring layers below it
105-
so that we can view multiple layers at once. We also need to colour the layers so we
104+
so that we can view multiple layers at once. We also use `colormap` to colour the layers so we
106105
can distinguish them from each other.
107106

108107
`viewer.add_image` is also able to load all these layers in one if we tell it which axis
109108
represents the colour channels:
110109

111110
```python
112-
viewer.add_image(img, name='FluorescentCells', channel_axis=2)
111+
viewer.add_image(image, name='FluorescentCells', channel_axis=2)
113112
```
114113

115114
::::::::::::::::::::::::::::::::::::: challenge
116-
## Exercise 16: Real world images
115+
## Exercise 15: Real world images
117116

118-
- Load the image 'confocal-series_zstack.tif', look at its shape and identify which
119-
axis looks like the channels.
120-
- Use `viewer.add_image` with the `channel_axis` argument to load the image.
121-
How has the fourth axis been represented?
117+
- Go to 'File' -> 'Open File(s)...' and load the image 'confocal-series_zstack.tif'. How
118+
does it look? Can you view each channel simultaneously?
119+
- Now try loading the same image in the console as above, looking at the image's shape and
120+
passing the channel axis to `viewer_add_image`.
121+
axis looks like the channels. How has the fourth axis been represented?
122122

123-
:::::::::::::::::::::::: solution
124-
From loading the image in the Python console:
123+
:::::::::::::::::::::::: solution
124+
Loading the image from the menu, we can see some results but it's all in one layer and the
125+
channel axis is represented as a slider, meaning we can't view the two at the same time.
126+
127+
As an alternative, we can load the image in the Python console:
125128

126129
```python
127-
img = skimage.io.imread('path/to/confocal-series_zstack.tif')
128-
img.shape
129-
(25, 2, 400, 400)
130+
image = skimage.io.imread('path/to/confocal-series_zstack.tif')
131+
image.shape
132+
# returns: (25, 2, 400, 400)
130133
```
131134

132-
The second number, i.e. `img.shape[1]` looks like the channel axis. We can then
135+
The second number, i.e. `image.shape[1]` looks like the channel axis. We can then
133136
provide this when loading the image:
134137

135138
```python
136-
viewer.add_image(img, name='FluorescentCells', channel_axis=1)
139+
viewer.add_image(image, name='FluorescentCells', channel_axis=1)
137140
```
138141

139142
Napari will load the image as two coloured layers, with the Z axis represented via the slider
140143
at the bottom.
141144
:::::::::::::::::::::::::::::::::
142145
:::::::::::::::::::::::::::::::::::::::::::::::
143146

144-
## Wrapup challenge: image segmentation in Napari
145-
146-
These two exercises will put together everything that has been covered in previous
147-
chapters, and apply the data processing performed in Python/Jupyter to
148-
Napari. Finally, we will see how the processing that we have built up in Napari
149-
can be exported back to Python code that we can then run on many images as a script
150-
to help automate our image processing.
151-
152147
::::::::::::::::::::::::::::::::::::: challenge
153148

154-
## Exercise 17: Image segmentation in Napari
149+
## Exercise 16: Image segmentation in the Napari console
150+
151+
This exercise will put together everything that has been covered in previous
152+
chapters, and apply the same steps in the Napari console.
155153

156-
Look at the 'nuclei' layer of 'Cells (3D + 2Ch)'. Explore
157-
the options available in 'Tools' and try segmenting and
158-
labelling the nuclei. A few things to think of:
154+
Look at the 'nuclei' layer of 'Cells (3D + 2Ch)'. In the Python console, try doing the
155+
same smoothing, binarisation, segmentation and labelling that you performed throughout
156+
exercises 6-12, except displaying each step as a new layer in the Napari viewer.
159157

160-
- Although we're working in a different environment from
161-
Jupyter, the stages of processing will remain the same
162-
- Remember that Napari works with layers, so the results
163-
of each tool used will depend on which layer it's used on
164-
- Napari names some functions differently. When it's referring
165-
to thresholding, for example, it uses the term '**binarising**'
166-
- Napari has some tools that combine esveral steps of processing
167-
into one. Try using the tools to perform each individual
168-
step
158+
Remember that instead of `matplotlib.pyplot.imshow()`, you'll need to use `viewer.add_image()`
169159

170160
:::::::::::::::::::::::: solution
161+
```python
162+
import skimage
163+
164+
image = skimage.io.cells3d()
165+
image.shape
166+
# returns: (60, 2, 256, 256)
167+
168+
frame = image[30, 1, :, :]
169+
smoothed_image = skimage.filters.gaussian(frame, sigma=1)
170+
viewer.add_image(smoothed_image, name='smoothed')
171171

172+
threshold = skimage.filters.threshold_otsu(smoothed_image)
173+
binary_image = smoothed_image > threshold
174+
viewer.add_image(binary_image, name='Otsu threshold')
175+
176+
import scipy.ndimage
177+
fill_holes = scipy.ndimage.binary_fill_holes(binary_image)
178+
viewer.add_image(fill_holes, name='fill_holes')
179+
180+
distance_transform = scipy.ndimage.distance_transform_edt(fill_holes)
181+
viewer.add_image(distance_transform, name='distance_transform')
182+
183+
coords = skimage.feature.peak_local_max(skimage.filters.gaussian(distance_transform, sigma=4), labels=fill_holes)
184+
185+
import numpy
186+
mask = numpy.zeros(distance_transform.shape, dtype=bool)
187+
mask[tuple(coords.T)] = True
188+
markers = skimage.morphology.label(mask)
189+
viewer.add_image(markers, name='peaks')
190+
191+
watershed_transform = skimage.segmentation.watershed(-distance_transform, markers)
192+
viewer.add_image(watershed_transform, name='watershed')
193+
194+
masked_watershed = watershed_transform * fill_holes
195+
viewer.add_image(masked_watershed, name='masked_watershed')
196+
```
172197
:::::::::::::::::::::::::::::::::
198+
::::::::::::::::::::::::::::::::::::::::::::::::
199+
200+
## Image segmentation with Napari tools
173201

174-
## Exercise 18: Exporting code
202+
Aside from running analyses in the Python console, Napari has a variety of processing steps that can
203+
be browsed under 'Tools'.
175204

176-
Open the Napari Assistant and find the 'Export Python code'
205+
For example, we can go to 'Open Sample' -> 'napari builtins' and -> 'Binary Blobs'. Browse the tools available and run:
206+
207+
- Otsu threshold
208+
- Split touching objects (nsbatwm). Try experimenting with different sigma values until you get some good cell separations.
209+
- Connected component labelling (scikit-image, nsbatwm)
210+
211+
Note: The Otsu threshold shouldn't be required since the test image is already a binary, but for some reason the 'Split
212+
touching objects' Napari tool isn't able to recognise the original as a binary image.
213+
214+
::::::::::::::::::::::::::::::::::::: challenge
215+
216+
## Exercise 17: Exporting code
217+
218+
This exercise will see how the processing that we have built up with Napari tools
219+
can be exported back to Python code that we can then check into version control
220+
and integrate into a workflow to help automate the processing of many images.
221+
222+
Under 'Plugins', open the Napari Assistant and find the 'Export Python code'
177223
tool. Use this to generate a Python script that will perform
178-
the processing that you performed in the previous exercise. Is
179-
the script usable immediately without modification?
224+
the processing that you just performed above on the Binary Blobs
225+
image. Does the script look usable immediately without modification?
180226

181227
:::::::::::::::::::::::: solution
182228
There are several options to export in different ways, including to
183229
a Jupyter notebook. The simplest way is probably to copy to clipboard -
184-
this can be pasted into a text editor or IDE.
230+
this can be pasted into a text editor or IDE:
231+
232+
```python
233+
from skimage.io import imread
234+
import napari_segment_blobs_and_things_with_membranes as nsbatwm # version 0.3.6
235+
import napari
236+
if 'viewer' not in globals():
237+
viewer = napari.Viewer()
238+
239+
image0_bb = viewer.layers['binary_blobs'].data
240+
241+
# threshold otsu
242+
243+
image1_to = nsbatwm.threshold_otsu(image0_bb)
244+
viewer.add_labels(image1_to, name='Result of threshold_otsu')
245+
246+
# split touching objects
247+
248+
image2_sto = nsbatwm.split_touching_objects(image1_to, 5.6)
249+
viewer.add_labels(image2_sto, name='Result of split_touching_objects')
250+
251+
# connected component labeling
252+
253+
image3_ccl = nsbatwm.connected_component_labeling(image2_sto, False)
254+
viewer.add_labels(image3_ccl, name='Result of connected_component_labeling')
255+
```
185256

186-
Looking at it, though, we can see that it's not immediately usable.
257+
Looking at it, we can see that it's not immediately usable but it's pretty close.
187258
Exported scripts do not make any reference to what image file was loaded,
188-
and there is no code at the end for writing the resuults to a file.
189-
Therefore, the script will require some tweaking to parameterise it
190-
and get it to produce tangible output.
259+
and there is no code at the end for writing the resuults to a file, so this is what
260+
you would need to add to get it to produce tangible output.
191261

192262
:::::::::::::::::::::::::::::::::
193263

0 commit comments

Comments
 (0)