-
-
Notifications
You must be signed in to change notification settings - Fork 126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Connected components suggestions #316
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -316,24 +316,22 @@ labeled_image, count = connected_components(filename="data/shapes-01.jpg", sigma | |||||||
|
||||||||
fig, ax = plt.subplots() | ||||||||
plt.imshow(labeled_image) | ||||||||
plt.axis("off"); | ||||||||
plt.axis("off") | ||||||||
``` | ||||||||
|
||||||||
:::::::::::::::: spoiler | ||||||||
|
||||||||
## Color mappings | ||||||||
## Do you see an empty image? | ||||||||
|
||||||||
Here you might get a warning | ||||||||
If you are using an old version of Matplotlib you might get a warning | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
`UserWarning: Low image data range; displaying image with stretched contrast.` | ||||||||
or just see an all black image | ||||||||
(Note: this behavior might change in future versions or | ||||||||
not occur with a different image viewer). | ||||||||
or just see a visually empty image. | ||||||||
|
||||||||
What went wrong? | ||||||||
When you hover over the black image, | ||||||||
When you hover over the image, | ||||||||
the pixel values are shown as numbers in the lower corner of the viewer. | ||||||||
You can see that some pixels have values different from `0`, | ||||||||
so they are not actually pure black. | ||||||||
so they are not actually all the same value. | ||||||||
Let's find out more by examining `labeled_image`. | ||||||||
Properties that might be interesting in this context are `dtype`, | ||||||||
the minimum and maximum value. | ||||||||
|
@@ -345,29 +343,36 @@ print("min:", np.min(labeled_image)) | |||||||
print("max:", np.max(labeled_image)) | ||||||||
``` | ||||||||
|
||||||||
Examining the output can give us a clue why the image appears black. | ||||||||
Examining the output can give us a clue why the image appears empty. | ||||||||
|
||||||||
```output | ||||||||
dtype: int32 | ||||||||
min: 0 | ||||||||
max: 11 | ||||||||
``` | ||||||||
|
||||||||
The `dtype` of `labeled_image` is `int64`. | ||||||||
This means that values in this image range from `-2 ** 63` to `2 ** 63 - 1`. | ||||||||
The `dtype` of `labeled_image` is `int32`. | ||||||||
This means that values in this image range from `-2 ** 31` to `2 ** 31 - 1`. | ||||||||
Those are really big numbers. | ||||||||
From this available space we only use the range from `0` to `11`. | ||||||||
When showing this image in the viewer, | ||||||||
it squeezes the complete range into 256 gray values. | ||||||||
Therefore, the range of our numbers does not produce any visible change. | ||||||||
it may squeeze the complete range into 256 gray values. | ||||||||
Therefore, the range of our numbers does not produce any visible variation. One way to rectify this | ||||||||
is to explicitly specify the data range we want the colormap to cover: | ||||||||
|
||||||||
Fortunately, the scikit-image library has tools to cope with this situation. | ||||||||
```python | ||||||||
fig, ax = plt.subplots() | ||||||||
plt.imshow(labeled_image, vmin=np.min(labeled_image), vmax=np.max(labeled_image)) | ||||||||
Comment on lines
+364
to
+365
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, ok, thanks for explaining! So, I would suggest you keep this line but replace the next one with: ax.imshow(labeled_image, vmin=np.min(labeled_image), vmax=np.max(labeled_image)) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree this is better but its not consistent with how its done in the rest of the course so perhaps this should be a different PR where all similar examples are changed at once. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree; I believe that we should use this "object-oriented (OO) style" throughout the lesson, since we create figure and axis objects now. At the moment, we are mixing up the OO-style with the pyplot-style [1]. For instance, 328d544 adds figure and axis objects, but we still have all the pyplot-style legacy. I would change, e.g., fig, ax = plt.subplots()
plt.plot(bin_edges[0:-1], histogram)
plt.title("Grayscale Histogram")
plt.xlabel("grayscale value")
plt.ylabel("pixels")
plt.xlim(0, 1.0) into fig, ax = plt.subplots()
ax.plot(bin_edges[0:-1], histogram)
ax.set_title("Grayscale Histogram")
ax.set_xlabel("grayscale value")
ax.set_ylabel("pixels")
ax.set_xlim(0, 1.0); /cc @datacarpentry/image-processing-curriculum-maintainers There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch, @mkcor. Please could you open a new issue where we can track this? |
||||||||
``` | ||||||||
|
||||||||
Note this is the default behaviour for newer versions of `matplotlib.pyplot.imshow`. | ||||||||
Alternatively we could convert the image to RGB and then display it. | ||||||||
|
||||||||
|
||||||||
::::::::::::::::::::::::: | ||||||||
|
||||||||
We can use the function `ski.color.label2rgb()` | ||||||||
to convert the colours in the image | ||||||||
to convert the 32-bit grayscale labeled image to standard RGB colour | ||||||||
(recall that we already used the `ski.color.rgb2gray()` function | ||||||||
to convert to grayscale). | ||||||||
With `ski.color.label2rgb()`, | ||||||||
|
@@ -380,7 +385,7 @@ colored_label_image = ski.color.label2rgb(labeled_image, bg_label=0) | |||||||
|
||||||||
fig, ax = plt.subplots() | ||||||||
plt.imshow(colored_label_image) | ||||||||
plt.axis("off"); | ||||||||
plt.axis("off") | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment as above. |
||||||||
``` | ||||||||
|
||||||||
{alt='Labeled objects'} | ||||||||
|
@@ -402,7 +407,7 @@ How does changing the `sigma` and `threshold` values influence the result? | |||||||
## Solution | ||||||||
|
||||||||
As you might have guessed, the return value `count` already | ||||||||
contains the number of found images. So it can simply be printed | ||||||||
contains the number of found objects in the image. So it can simply be printed | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
with | ||||||||
|
||||||||
```python | ||||||||
|
@@ -685,7 +690,7 @@ set the entries that belong to small objects to `0`. | |||||||
object_areas = np.array([objf["area"] for objf in object_features]) | ||||||||
object_labels = np.array([objf["label"] for objf in object_features]) | ||||||||
small_objects = object_labels[object_areas < min_area] | ||||||||
labeled_image[np.isin(labeled_image,small_objects)] = 0 | ||||||||
labeled_image[np.isin(labeled_image, small_objects)] = 0 | ||||||||
``` | ||||||||
|
||||||||
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`. | |||||||
We can then apply `ski.measure.label` to the masked image: | ||||||||
|
||||||||
```python | ||||||||
object_mask = ski.morphology.remove_small_objects(binary_mask,min_area) | ||||||||
object_mask = ski.morphology.remove_small_objects(binary_mask, min_size=min_area) | ||||||||
labeled_image, n = ski.measure.label(object_mask, | ||||||||
connectivity=connectivity, return_num=True) | ||||||||
``` | ||||||||
|
@@ -712,7 +717,7 @@ def enhanced_connected_components(filename, sigma=1.0, t=0.5, connectivity=2, mi | |||||||
gray_image = ski.color.rgb2gray(image) | ||||||||
blurred_image = ski.filters.gaussian(gray_image, sigma=sigma) | ||||||||
binary_mask = blurred_image < t | ||||||||
object_mask = ski.morphology.remove_small_objects(binary_mask,min_area) | ||||||||
object_mask = ski.morphology.remove_small_objects(binary_mask, min_size=min_area) | ||||||||
labeled_image, count = ski.measure.label(object_mask, | ||||||||
connectivity=connectivity, return_num=True) | ||||||||
return labeled_image, count | ||||||||
|
@@ -728,7 +733,7 @@ colored_label_image = ski.color.label2rgb(labeled_image, bg_label=0) | |||||||
|
||||||||
fig, ax = plt.subplots() | ||||||||
plt.imshow(colored_label_image) | ||||||||
plt.axis("off"); | ||||||||
plt.axis("off") | ||||||||
|
||||||||
print("Found", count, "objects in the image.") | ||||||||
``` | ||||||||
|
@@ -772,14 +777,16 @@ the area by indexing the `object_areas` with the label values in `labeled_image` | |||||||
|
||||||||
```python | ||||||||
object_areas = np.array([objf["area"] for objf in ski.measure.regionprops(labeled_image)]) | ||||||||
object_areas = np.insert(0,1,object_areas) | ||||||||
# prepend zero to object_areas array for background pixels | ||||||||
object_areas = np.insert(0, obj=1, values=object_areas) | ||||||||
# create image where the pixels in each object are equal to that object's area | ||||||||
colored_area_image = object_areas[labeled_image] | ||||||||
|
||||||||
fig, ax = plt.subplots() | ||||||||
im = plt.imshow(colored_area_image) | ||||||||
cbar = fig.colorbar(im, ax=ax, shrink=0.85) | ||||||||
cbar.ax.set_title("Area") | ||||||||
plt.axis("off"); | ||||||||
plt.axis("off") | ||||||||
``` | ||||||||
|
||||||||
{alt='Objects colored by area'} | ||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would keep the semicolon, so the output is not cluttered with text and memory addresses... But maybe the semicolon should be explained to learners? cf. mkcor/python-prog#8 (comment) 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, sorry, I probably shouldn't have removed this! I don't use Jupyter much apart from for teaching so from my perspective I thought it might be confusing for some participants and I didn't mind seeing the limits of the axis printed. Happy to put back in with a brief explanation :)