Skip to content

Commit 42bb13a

Browse files
authored
Merge pull request #316 from bear-rsg/connected-components-suggestions
Connected components suggestions
2 parents 7065592 + f072c71 commit 42bb13a

File tree

1 file changed

+30
-23
lines changed

1 file changed

+30
-23
lines changed

episodes/08-connected-components.md

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -316,24 +316,22 @@ labeled_image, count = connected_components(filename="data/shapes-01.jpg", sigma
316316

317317
fig, ax = plt.subplots()
318318
plt.imshow(labeled_image)
319-
plt.axis("off");
319+
plt.axis("off")
320320
```
321321

322322
:::::::::::::::: spoiler
323323

324-
## Color mappings
324+
## Do you see an empty image?
325325

326-
Here you might get a warning
326+
If you are using an old version of Matplotlib you might get a warning
327327
`UserWarning: Low image data range; displaying image with stretched contrast.`
328-
or just see an all black image
329-
(Note: this behavior might change in future versions or
330-
not occur with a different image viewer).
328+
or just see a visually empty image.
331329

332330
What went wrong?
333-
When you hover over the black image,
331+
When you hover over the image,
334332
the pixel values are shown as numbers in the lower corner of the viewer.
335333
You can see that some pixels have values different from `0`,
336-
so they are not actually pure black.
334+
so they are not actually all the same value.
337335
Let's find out more by examining `labeled_image`.
338336
Properties that might be interesting in this context are `dtype`,
339337
the minimum and maximum value.
@@ -345,29 +343,36 @@ print("min:", np.min(labeled_image))
345343
print("max:", np.max(labeled_image))
346344
```
347345

348-
Examining the output can give us a clue why the image appears black.
346+
Examining the output can give us a clue why the image appears empty.
349347

350348
```output
351349
dtype: int32
352350
min: 0
353351
max: 11
354352
```
355353

356-
The `dtype` of `labeled_image` is `int64`.
357-
This means that values in this image range from `-2 ** 63` to `2 ** 63 - 1`.
354+
The `dtype` of `labeled_image` is `int32`.
355+
This means that values in this image range from `-2 ** 31` to `2 ** 31 - 1`.
358356
Those are really big numbers.
359357
From this available space we only use the range from `0` to `11`.
360358
When showing this image in the viewer,
361-
it squeezes the complete range into 256 gray values.
362-
Therefore, the range of our numbers does not produce any visible change.
359+
it may squeeze the complete range into 256 gray values.
360+
Therefore, the range of our numbers does not produce any visible variation. One way to rectify this
361+
is to explicitly specify the data range we want the colormap to cover:
363362

364-
Fortunately, the scikit-image library has tools to cope with this situation.
363+
```python
364+
fig, ax = plt.subplots()
365+
plt.imshow(labeled_image, vmin=np.min(labeled_image), vmax=np.max(labeled_image))
366+
```
367+
368+
Note this is the default behaviour for newer versions of `matplotlib.pyplot.imshow`.
369+
Alternatively we could convert the image to RGB and then display it.
365370

366371

367372
:::::::::::::::::::::::::
368373

369374
We can use the function `ski.color.label2rgb()`
370-
to convert the colours in the image
375+
to convert the 32-bit grayscale labeled image to standard RGB colour
371376
(recall that we already used the `ski.color.rgb2gray()` function
372377
to convert to grayscale).
373378
With `ski.color.label2rgb()`,
@@ -380,7 +385,7 @@ colored_label_image = ski.color.label2rgb(labeled_image, bg_label=0)
380385

381386
fig, ax = plt.subplots()
382387
plt.imshow(colored_label_image)
383-
plt.axis("off");
388+
plt.axis("off")
384389
```
385390

386391
![](fig/shapes-01-labeled.png){alt='Labeled objects'}
@@ -402,7 +407,7 @@ How does changing the `sigma` and `threshold` values influence the result?
402407
## Solution
403408

404409
As you might have guessed, the return value `count` already
405-
contains the number of found images. So it can simply be printed
410+
contains the number of found objects in the image. So it can simply be printed
406411
with
407412

408413
```python
@@ -685,7 +690,7 @@ set the entries that belong to small objects to `0`.
685690
object_areas = np.array([objf["area"] for objf in object_features])
686691
object_labels = np.array([objf["label"] for objf in object_features])
687692
small_objects = object_labels[object_areas < min_area]
688-
labeled_image[np.isin(labeled_image,small_objects)] = 0
693+
labeled_image[np.isin(labeled_image, small_objects)] = 0
689694
```
690695

691696
An even more elegant way to remove small objects from the image is
@@ -698,7 +703,7 @@ i.e, their pixel values are set to `False`.
698703
We can then apply `ski.measure.label` to the masked image:
699704

700705
```python
701-
object_mask = ski.morphology.remove_small_objects(binary_mask,min_area)
706+
object_mask = ski.morphology.remove_small_objects(binary_mask, min_size=min_area)
702707
labeled_image, n = ski.measure.label(object_mask,
703708
connectivity=connectivity, return_num=True)
704709
```
@@ -712,7 +717,7 @@ def enhanced_connected_components(filename, sigma=1.0, t=0.5, connectivity=2, mi
712717
gray_image = ski.color.rgb2gray(image)
713718
blurred_image = ski.filters.gaussian(gray_image, sigma=sigma)
714719
binary_mask = blurred_image < t
715-
object_mask = ski.morphology.remove_small_objects(binary_mask,min_area)
720+
object_mask = ski.morphology.remove_small_objects(binary_mask, min_size=min_area)
716721
labeled_image, count = ski.measure.label(object_mask,
717722
connectivity=connectivity, return_num=True)
718723
return labeled_image, count
@@ -728,7 +733,7 @@ colored_label_image = ski.color.label2rgb(labeled_image, bg_label=0)
728733

729734
fig, ax = plt.subplots()
730735
plt.imshow(colored_label_image)
731-
plt.axis("off");
736+
plt.axis("off")
732737

733738
print("Found", count, "objects in the image.")
734739
```
@@ -772,14 +777,16 @@ the area by indexing the `object_areas` with the label values in `labeled_image`
772777

773778
```python
774779
object_areas = np.array([objf["area"] for objf in ski.measure.regionprops(labeled_image)])
775-
object_areas = np.insert(0,1,object_areas)
780+
# prepend zero to object_areas array for background pixels
781+
object_areas = np.insert(0, obj=1, values=object_areas)
782+
# create image where the pixels in each object are equal to that object's area
776783
colored_area_image = object_areas[labeled_image]
777784

778785
fig, ax = plt.subplots()
779786
im = plt.imshow(colored_area_image)
780787
cbar = fig.colorbar(im, ax=ax, shrink=0.85)
781788
cbar.ax.set_title("Area")
782-
plt.axis("off");
789+
plt.axis("off")
783790
```
784791

785792
![](fig/shapes-01-objects-coloured-by-area.png){alt='Objects colored by area'}

0 commit comments

Comments
 (0)