Q1

In this question, you'll be introduced to the scikit-image package. Only a small portion of the package will be explored; you're encouraged to check it out if this interests you!

Part A

scikit-image is a pretty awesome all-purpose lightweight image analysis package for Python. In this part, you'll write a function to help prepare an image for analysis.

In the first step, you'll need to convert the image to a grayscale format that is amenable to use by the rest of the scikit-image API. Write a function which:

  • is named to_grayscale_uint (for "unsigned integer")
  • takes 1 argument: a NumPy array representing an image
  • returns 1 value: a NumPy array, containing the converted image

You can't use any built-in Python functions or imports beyond NumPy and skimage.color; you're welcome to use Matplotlib as well for debugging your code.

Some relevant hints:

  1. Look at the skimage.color subpackage for some useful conversion functions.
  2. By default, scikit-image conversion functions will return images with floating-point representations from 0-1. We don't want that; we want integer representations from 0-255. NumPy has a data type for that: np.uint8 (for 8-bit unsigned integer).
  3. To the point above, recall that the np.array function takes an option argument dtype to specify the type of the underlying array.

In [ ]:


In [ ]:
import numpy as np
import skimage.data
q1 = np.load("coffee_gray.npy")
np.testing.assert_allclose(to_grayscale_uint(skimage.data.coffee()), q1)

In [ ]:
q2 = np.load("chelsea_gray.npy")
np.testing.assert_allclose(to_grayscale_uint(skimage.data.chelsea()), q2)

Part B

Next, you'll write some code to start segmenting the 8-bit grayscale image. This will involve thresholding it, applying a possible smoothing filter, and connecting the objects that remain.

Write a function which:

  • is named segment
  • takes 2 arguments: a NumPy array of the 8-bit grayscale image (the lone required argument), and a default argument that sets the pixel threshold (default: None)
  • returns 1 value: the integer count of the number of objects found in the processed image

To do the object connecting, you'll need to look at the skimage.measure.label function.

You can assume that the image your function receives is effectively the output of your Part A function above (i.e. it's already in grayscale, and has pixel values that range from 0 to 255). Your job is to

  1. threshold it, by setting any pixel below the threshold to 0 (unless the optional threshold parameter is None)
  2. label the objects

The third item comes from the aforementioned skimage.measure.label function. That function can return the number of objects (which is what your function is supposed to return, too), but take careful notice of how to activate this ability, as it is turned OFF by default.

You can't use any built-in Python functions or other imports aside from skimage and numpy, though you may use matplotlib to debug.


In [ ]:


In [ ]:
import numpy as np

img1 = np.load("chelsea_gray.npy")
obj1 = 91585
assert obj1 == segment(img1)

In [ ]:
img1 = np.load("chelsea_gray.npy")
obj2 = 88223
t1 = 50
assert obj2 == segment(img1, t1)

In [ ]:
img2 = np.load("ihc_gray.npy")
obj3 = 217299
assert obj3 == segment(img2)

In [ ]:
img2 = np.load("ihc_gray.npy")
obj4 = 76677
t2 = 180
assert obj4 == segment(img2, t2)

In [ ]:
img3 = np.load("coffee_gray.npy")
obj5 = 150792
assert obj5 == segment(img3)

Part C

In this part, you'll use the function you wrote in Part B to compute some properties of the objects that are revealed using the skimage.measure.label output.

Your goal is to find the object with the largest area, and return a ratio of that object's other properties. Write a function which:

  • is named ratio_largest_area
  • takes 2 arguments: a NumPy array of the 8-bit grayscale image (the lone required argument), and a default argument that sets the pixel threshold (default: None)
  • returns 1 value: a floating-point number that is the ratio of the minor axis length to the major axis length

Yes, this takes the same arguments as the function you wrote in Part B. You'll need to perform the same operations, but instead of computing the number of objects, you'll need the other output from the skimage.measure.label function: the actual labeled array.

After thresholding the input image (or not) and determining the labeled array, you'll need to feed that array to the skimage.measure.regionprops function. This computes a variety of properties on each object that's found by the label function.

In this list of region properties, find the one with the largest area. Then, take that region's minor and major axis lengths, and create the ratio:

$$ \frac{minor}{major} $$

This should be somewhere between 0 and 1. Your function should return that ratio.

You can use loops (len and range, though you shouldn't really need them), NumPy, and scikit-image. Nothing else.


In [ ]:


In [ ]:
import numpy as np
img1 = np.load("ihc_gray.npy")
rat1 = 0.4251056202959775
np.testing.assert_allclose(ratio_largest_area(img1), rat1)

In [ ]:
img1 = np.load("ihc_gray.npy")
t1 = 240
rat2 = 0.38196601125010504
np.testing.assert_allclose(ratio_largest_area(img1, t1), rat2)

In [ ]:
img2 = np.load("chelsea_gray.npy")
t2 = 50
rat3 = 0.7494253874318644
np.testing.assert_allclose(ratio_largest_area(img2, t2), rat3)