Example 5: Stickleback morphometrics - body and armor-plate shape

Variation in continuous phenotypic traits like shape or area of certain structures are difficult to quantify with landmarks, because they are too complex or have no underlying assumption of homology. In this example, the number and area of armor plating was measured as a continuous trait in a two-step process: first, a mask was set around the posterior region that contains the plates, second, the red channel (highest signal-to-noise-ratio) of the image was thresholded.

In this example we use the watershed algorithm, which helps to separate detected objects into “peaks of wanted information” and “valleys of unwanted information”. The principle is explained here: https://docs.opencv.org/master/d3/db4/tutorial_py_watershed.html

Before

Input - Stained threespine stickleback. The size of plates at a given plate index varies within at between ecotypes (e.g. lake and stream morphs)

After

Results - After applying adaptive thresholding and watershed algorithms, the plates separate. Where this is not the case they can be separated manually.

High throughput workflow 1 (Thresholding based)

First we need to create a project, as described in Tutorial 3 and Tutorial 4.

Note: If you have already created a project you can skip the following steps and load the project with pp.project.load("path/to/project").

[ ]:
import phenopype as pp

## relative from phenopype-master/tutorials
project_root = r"../_temp/output/ex5_project"
image_dir = "images"
reference_image = "images/stickleback_side.jpg"

Create project

[ ]:
ex5_1 = pp.project(root_dir=project_root)

This will create a folder structure and allows for easy creation of the pype-configuration files needed for high throughput. First we add the image files in the directory, but only “stickle1”, “stickle2”, and “stickle3”.

[ ]:
ex5_1.add_files(image_dir=image_dir, include="stickle", exclude=["side","top"])

Now we add the appropriate configuration file. As for the other examples I have created a preset (“ex5”) with appropriate settings, which is passed to the pype using config_preset="ex5".

[ ]:
ex5_1.add_config(name = "v1", config_preset="ex5")
print(pp.presets.ex5)

Now we add reference image to create a scale-template so we can adjust our landmark coordinate space. This is important if for example the distance between the camera and your sample changes.

Adding a scale

[ ]:
ex5_1.add_scale(reference_image = reference_image, overwrite=True)

Afterwards, we save the project (to the root folder).

[ ]:
pp.project.save(ex5_1)

Load project

[ ]:
ex5_1 = pp.project.load(project_root)

The procedure

The basic points of the procedure are:

  1. Draw a mask around the area of interest (i.e. the plates)

  2. Let the algorithm find the scale

  3. Enter the ID from the reference card into the mask

Create masks

The rest is automatic: a watershed algorithm helps to separate the plates. Sensitivity of the algorithm can be mostly controlled with the distance_cutoff argument, but also playing around with the thresholding arguments (blocksize and constant) can help to improve results.

Sometimes the overall results are goog except for one or two cases of plates that touch each other. In this case, the draw function can be used to separate those plates without changing the whole procedure.

Now we can run the pype routine with a simple loop on ex5.dirpaths, which is a list of all project directories that contain the copied raw images and the config files we generated before. Interrupt the loop with Esc. To resume to the point where you left, add the skip argument so directory with processed files are not run again.

[ ]:
for dirpath in ex5_1.dirpaths:
    out = pp.pype(dirpath, name="v1", skip=True)
[ ]:
## inspect results
## DataFrame
out.container.df_contours.drop(columns=["order", "idx_child", "idx_parent", "coords"])
[ ]:
## image
pp.show_image(out.container.canvas)

High throughput workflow 2 (Manual approach)

Sometimes thresholding algorithms don’t separate plates that are too close to each other or overap. For this case, the create_mask function introduced above can be used to separate out specific plates.

Find contours manually

Manual polygon drawing using the mask tool. Although this is more manual work, it may sometimes be the only way to separate overlapping or connected body-structures.

Just as before we need to create a phenpype project, add the image files, and the appropriate preset (for this part is "ex5_2"). Again we also set the reference image for scale detection - see above or Tutorial 4, or Example 1, Example 2.

[1]:
import phenopype as pp

## relative from phenopype-master/tutorials
project_root = r"../_temp/output/ex5_project2"
image_dir = "images"
reference_image = "images/stickleback_side.jpg"
[2]:
ex5_2 = pp.project(root_dir=project_root)
ex5_2.add_files(image_dir=image_dir, include="stickle", exclude=["side","top"])
ex5_2.add_config(name = "v1", config_preset="ex5_2")
ex5_2.add_scale(reference_image = reference_image, overwrite=True)
pp.project.save(ex5_2)
--------------------------------------------
Phenopype will create a new project at
E:\git_repos\phenopype\_temp\output\ex5_project2

Proceed? (y/n)
y
Warning - project root_dir already exists - overwrite? (y/n)y

"E:\git_repos\phenopype\_temp\output\ex5_project2" created (overwritten)

project attributes written to E:\git_repos\phenopype\_temp\output\ex5_project2\attributes.yaml
--------------------------------------------
--------------------------------------------
phenopype will search for files at

E:\git_repos\phenopype\tutorials\images

using the following settings:

filetypes: ['jpg', 'JPG', 'jpeg', 'JPEG', 'tif', 'png'], include: stickle, exclude: ['side', 'top'], raw_mode: copy, search_mode: dir, unique_mode: path

Found image stickle1.JPG - phenopype-project folder 0__stickle1 created
dirpath defaulted to file directory - E:\git_repos\phenopype\tutorials\images
Directory to save files set at - E:\git_repos\phenopype\tutorials\images
no meta-data found
Found image stickle2.JPG - phenopype-project folder 0__stickle2 created
dirpath defaulted to file directory - E:\git_repos\phenopype\tutorials\images
Directory to save files set at - E:\git_repos\phenopype\tutorials\images
no meta-data found
Found image stickle3.JPG - phenopype-project folder 0__stickle3 created
dirpath defaulted to file directory - E:\git_repos\phenopype\tutorials\images
Directory to save files set at - E:\git_repos\phenopype\tutorials\images
no meta-data found

Found 3 files
--------------------------------------------
pype config generated from ex5_2.
pype_v1.yaml created for 0__stickle1
pype_v1.yaml created for 0__stickle2
pype_v1.yaml created for 0__stickle3
- scale template saved under E:\git_repos\phenopype\_temp\output\ex5_project2\scale_template.jpg.
- measure pixel-to-mm-ratio
Scale set
- add column length
Template selected
added scale information to 0__stickle1
added scale information to 0__stickle2
added scale information to 0__stickle3
Project data saved under E:\git_repos\phenopype\_temp\output\ex5_project2\project.data.

Use this to load a previously saved projec:

[2]:
ex5_2 = pp.project.load(project_root)
--------------------------------------------
Project loaded from
E:\git_repos\phenopype\_temp\output\ex5_project2
--------------------------------------------
[3]:
for dirpath in ex5_2.dirpaths:
    out = pp.pype(dirpath, name="v1", skip=False)
E:\git_repos\phenopype\_temp\output\ex5_project2\data\0__stickle1\pype_config_v1.yaml


------------+++ new pype iteration 2020:07:03 10:04:53 +++--------------


AUTOLOAD
- columns ID from attributes.yaml
- template scale information loaded from attributes.yaml
- current scale information loaded from attributes.yaml
- template loaded from root directory
- masks_v1.csv
PREPROCESSING
create_mask
- mask with label mask1 already created (overwrite=False)
find_scale
- scale already detected (overwrite=False)
enter_data
- column ID already created (overwrite=False)
SEGMENTATION
blur
threshold
- include mask "mask1" pixels
- include mask "mask1" pixels
- include mask "mask1" pixels
- include mask "mask1" pixels
- include mask "mask1" pixels
- include mask "mask1" pixels
- exclude mask "scale" pixels
find_contours
VISUALIZATION
select_canvas
- red channel
draw_contours
draw_masks
 - show mask: mask1.
 - show mask: mask1.
 - show mask: mask1.
 - show mask: mask1.
 - show mask: mask1.
 - show mask: mask1.
 - show mask: scale.
EXPORT
save_contours
- contours saved under E:\git_repos\phenopype\_temp\output\ex5_project2\data\0__stickle1\contours_v1.csv (overwritten).
AUTOSAVE
save_canvas
- canvas saved under E:\git_repos\phenopype\_temp\output\ex5_project2\data\0__stickle1\canvas_v1.jpg (overwritten).
save_data_entry
- column ID not saved (overwrite=False)
save_masks
- masks not saved - file already exists (overwrite=False).
save_scale
- save scale to attributes (overwriting)


------------+++ new pype iteration 2020:07:03 10:05:09 +++--------------


Nothing loaded.
PREPROCESSING
create_mask
- mask with label mask1 already created (overwrite=False)
find_scale
- scale already detected (overwrite=False)
enter_data
- column ID already created (overwrite=False)
SEGMENTATION
blur
threshold
- include mask "mask1" pixels
- include mask "mask1" pixels
- include mask "mask1" pixels
- include mask "mask1" pixels
- include mask "mask1" pixels
- include mask "mask1" pixels
- exclude mask "scale" pixels
find_contours
VISUALIZATION
select_canvas
- red channel
draw_contours
EXPORT
save_contours
- contours saved under E:\git_repos\phenopype\_temp\output\ex5_project2\data\0__stickle1\contours_v1.csv (overwritten).
AUTOSAVE
save_canvas
- canvas saved under E:\git_repos\phenopype\_temp\output\ex5_project2\data\0__stickle1\canvas_v1.jpg (overwritten).
save_data_entry
- column ID not saved (overwrite=False)
save_masks
- masks not saved - file already exists (overwrite=False).
save_scale
- save scale to attributes (overwriting)


------------+++ new pype iteration 2020:07:03 10:05:19 +++--------------


Nothing loaded.
PREPROCESSING
create_mask
- mask with label mask1 already created (overwrite=False)
find_scale
- scale already detected (overwrite=False)
enter_data
- column ID already created (overwrite=False)
SEGMENTATION
blur
threshold
- include mask "mask1" pixels
- include mask "mask1" pixels
- include mask "mask1" pixels
- include mask "mask1" pixels
- include mask "mask1" pixels
- include mask "mask1" pixels
- exclude mask "scale" pixels
find_contours
VISUALIZATION
select_canvas
- red channel
draw_contours
draw_masks
 - show mask: mask1.
 - show mask: mask1.
 - show mask: mask1.
 - show mask: mask1.
 - show mask: mask1.
 - show mask: mask1.
 - show mask: scale.
EXPORT
save_contours
- contours saved under E:\git_repos\phenopype\_temp\output\ex5_project2\data\0__stickle1\contours_v1.csv (overwritten).
AUTOSAVE
save_canvas
- canvas saved under E:\git_repos\phenopype\_temp\output\ex5_project2\data\0__stickle1\canvas_v1.jpg (overwritten).
save_data_entry
- column ID not saved (overwrite=False)
save_masks
- masks not saved - file already exists (overwrite=False).
save_scale
- save scale to attributes (overwriting)
An exception has occurred, use %tb to see the full traceback.

SystemExit:

TERMINATE (by user)

WARNING: To exit: use 'exit', 'quit', or Ctrl-D.

The data containing the size of each contour are now in “contours_v1.csv”, just as above. Using the “current_px_mm_ratio” colum you can calculate size and area in mm, with the first (x-coordinate) value in the “center” colum you can order the plates from left to right.

[ ]:

[ ]: