Synthetic Validation#

Import Statement :#

[1]:
%matplotlib inline

import matplotlib.pyplot as plt
import cv2
import collections
import numpy
import math
import pandas
import scipy.integrate

import openalea.phenomenal.data as phm_data
import openalea.phenomenal.object as phm_obj
import openalea.phenomenal.multi_view_reconstruction as phm_mvr
import openalea.phenomenal.mesh as phm_mesh
import openalea.phenomenal.display as phm_display
import openalea.phenomenal.display.notebook as phm_display_notebook
import openalea.phenomenal.segmentation as phm_seg
from openalea.phenotyping_data.fetch import fetch_all_data

1. Import synthetic data#

1.1 Select plant_number#

[2]:
calibration_dir = fetch_all_data('plant_1/calibration').parent
data_dir = fetch_all_data('synthetic_plant_1')
Downloading file 'plant_1/calibration/calibration_camera_side.json' from 'https://raw.githubusercontent.com/openalea/phenotyping_data/main/data/plant_1/calibration/calibration_camera_side.json' to '/home/docs/.cache/phenotyping_data'.
Downloading file 'plant_1/calibration/calibration_camera_top.json' from 'https://raw.githubusercontent.com/openalea/phenotyping_data/main/data/plant_1/calibration/calibration_camera_top.json' to '/home/docs/.cache/phenotyping_data'.
Downloading file 'synthetic_plant_1/synthetic_plant.json' from 'https://raw.githubusercontent.com/openalea/phenotyping_data/main/data/synthetic_plant_1/synthetic_plant.json' to '/home/docs/.cache/phenotyping_data'.
Downloading file 'synthetic_plant_1/synthetic_plant.ply' from 'https://raw.githubusercontent.com/openalea/phenotyping_data/main/data/synthetic_plant_1/synthetic_plant.ply' to '/home/docs/.cache/phenotyping_data'.
[3]:
vertices, faces, meta_data = phm_data.synthetic_plant(data_dir)

1.2 Viewing#

[4]:
phm_display_notebook.show_synthetic_plant(vertices, faces, meta_data=None)

2. Phenomenal Measurements#

2.1 Projection & binairies images#

[5]:
image_shape = (2448, 2048)
angles = range(0, 360, 30)
calibrations = phm_data.calibrations(calibration_dir)

selected = [("side", angle, (2448, 2048)) for angle in range(0, 360, 30)]
selected.append(("top", 0, (2048, 2448)))


bin_images = collections.defaultdict(dict)
for id_camera, angle, image_shape in selected:
    projection = calibrations[id_camera].get_projection(angle)
    image = phm_mesh.project_mesh_on_image(vertices, faces, image_shape, projection)
    bin_images[id_camera][angle] = image
[6]:
# Display it
phm_display.show_images(
    list(bin_images["side"].values()) + list(bin_images["top"].values())
)
../_images/examples_Synthetic_Validation_9_0.png

2.2 Multi-view reconstruction#

2.2.1 Associate images and projection function#

[7]:
def routine_select_ref_angle(bin_side_images):
    max_len = 0
    max_angle = None

    for angle in bin_side_images:
        x_pos, y_pos, x_len, y_len = cv2.boundingRect(
            cv2.findNonZero(bin_side_images[angle])
        )

        if x_len > max_len:
            max_len = x_len
            max_angle = angle

    return max_angle
[8]:
refs_angle_list = [routine_select_ref_angle(bin_images["side"])]

image_views = list()
for id_camera in bin_images:
    for angle in bin_images[id_camera]:
        projection = calibrations[id_camera].get_projection(angle)

        image_ref = None
        if id_camera == "side" and angle in refs_angle_list:
            image_ref = bin_images[id_camera][angle]

        inclusive = False
        if id_camera == "top":
            inclusive = True

        image_views.append(
            phm_obj.ImageView(
                bin_images[id_camera][angle],
                projection,
                inclusive=inclusive,
                image_ref=image_ref,
            )
        )

2.2.2 Do the mul-view reconstruction#

[9]:
error_tolerance = 1
voxels_size = 4

voxel_grid = phm_mvr.reconstruction_3d(
    image_views, voxels_size=voxels_size, error_tolerance=error_tolerance
)
[10]:
# Display it
phm_display_notebook.show_voxel_grid(voxel_grid, size=1)

2.3 Skeletonization#

[11]:
graph = phm_seg.graph_from_voxel_grid(voxel_grid, connect_all_point=True)
voxel_skeleton = phm_seg.skeletonize(voxel_grid, graph)

# Select images
image_projection = list()
for angle in bin_images["side"]:
    projection = calibrations["side"].get_projection(angle)
    image_projection.append((bin_images["side"][angle], projection))

voxel_skeleton_denoised = phm_seg.segment_reduction(
    voxel_skeleton, image_projection, required_visible=4, nb_min_pixel=100
)
[12]:
# Display it
phm_display_notebook.show_skeleton(voxel_skeleton_denoised, with_voxel=True, size=1.2)

2.4 Cereals Segmentation#

[13]:
vms = phm_seg.maize_segmentation(voxel_skeleton_denoised, graph)
vmsi = phm_seg.maize_analysis(vms)
[14]:
# Phenomenal measurements of each organs
pm_rows = [vo.info for vo in vmsi.voxel_organs]
[15]:
# Display it
phm_display_notebook.show_segmentation(vmsi, size=1.2)

3. Synthetic measurement#

3.1 Diplay leaf meta-data#

[16]:
phm_display_notebook.show_synthetic_plant(vertices, faces, meta_data=meta_data)

3.2 Extract measurements from meta-data#

[17]:
def remove_duplicate(poly, width):
    index, d = list(), dict()
    for i, item in enumerate(map(tuple, list(poly))):
        if item not in d:
            index.append(i)
            d[item] = True
    return poly[index], width[index]
[18]:
sm_rows = list()
x, y, z, r = zip(*meta_data["stem"])
polyline = numpy.array(list(zip(x, y, z))) * 10 - numpy.array([0, 0, 750])
r = numpy.array(r) * 10
polyline, r = remove_duplicate(polyline, r)

row = dict()
row["sm_label"] = "stem"
row["sm_max_width"] = max(r)
row["sm_average_width"] = numpy.mean(r)

row["sm_surface"] = scipy.integrate.simpson(
    r, x=phm_seg.compute_curvilinear_abscissa(polyline, 1)
)
row["sm_length"] = phm_seg.compute_length_organ(polyline)
angle, stem_vector_mean = phm_seg.compute_azimuth_angle(polyline)
sm_rows.append(row)

ranks = meta_data["leaf_order"]
polylines = {
    n: map(numpy.array, list(zip(*meta_data["leaf_polylines"][i])))
    for i, n in enumerate(ranks)
}

for leaf_order in polylines:
    x, y, z, r = polylines[leaf_order]
    polyline = numpy.array(list(zip(x, y, z))) * 10 - numpy.array([0, 0, 750])
    r = numpy.array(r) * 10
    polyline, r = remove_duplicate(polyline, r)

    row = dict()
    row["sm_leaf_number"] = leaf_order
    row["sm_label"] = "leaf"
    row["sm_position_tip"] = tuple(polyline[-1])
    row["sm_position_base"] = tuple(polyline[0])
    row["sm_max_width"] = max(r)
    row["sm_average_width"] = numpy.mean(r)

    row["sm_surface"] = scipy.integrate.simpson(
        r, x=phm_seg.compute_curvilinear_abscissa(polyline, 1)
    )

    row["sm_length"] = phm_seg.compute_length_organ(polyline)
    angle, vector_mean = phm_seg.compute_azimuth_angle(polyline)
    row["sm_azimuth_angle"] = angle
    insertion_angle, vector = phm_seg.compute_insertion_angle(
        polyline, stem_vector_mean
    )
    row["sm_insertion_angle_vector"] = vector
    row["sm_insertion_angle"] = insertion_angle
    row["sm_inclination_angle"] = phm_seg.compute_inclination_angle(polyline)
    row["sm_full_length"] = row["sm_length"] + polyline[0][2] + 750

    sm_rows.append(row)

4. Measures registration#

Merge synthetic and phenomenal row measure together

[19]:
def registration_leafs(pm_rows, sm_rows):
    def cannot_registred(pm_row, sm_row):
        for label in ["stem", "plant", "unknown"]:
            if pm_row["pm_label"] == label or sm_row["sm_label"] == label:
                return True
        if math.isnan(pm_row["pm_length"]):
            return True
        return False

    def same_label(pm_row, sm_row):
        for label in ["stem", "plant"]:
            if pm_row["pm_label"] == label and sm_row["sm_label"] == label:
                return True
        return False

    rows_distance = []
    registered_row = []
    for pm_row in pm_rows:
        for sm_row in sm_rows:
            if same_label(pm_row, sm_row):
                registered_row.append((pm_row, sm_row, 0))
                continue
            if cannot_registred(pm_row, sm_row):
                continue

            pos1 = numpy.array(pm_row["pm_position_tip"])
            pos2 = numpy.array(sm_row["sm_position_tip"])
            distance = numpy.linalg.norm(pos2 - pos1)

            rows_distance.append((pm_row, sm_row, distance))

    while rows_distance:
        (pm_row, sm_row, d) = min(rows_distance, key=lambda t: t[2])
        registered_row.append((pm_row, sm_row, d))
        rows_distance = [
            v for v in rows_distance if pm_row not in v and sm_row not in v
        ]
    return registered_row
[20]:
registred_rows = list()
for pm_row, sm_row, d in registration_leafs(pm_rows, sm_rows):
    sm_row.update(pm_row)
    sm_row["distance_registration"] = d
    registred_rows.append(sm_row)

4.2 Display result#

[21]:
df = pandas.DataFrame(registred_rows)
# df = df[df["distance_registration"] < 100]
# df_filtred_label = df[df["pm_label"] == "mature_leaf"]

# print df["sm_inclination_angle"], df["pm_inclination_angle"],

plt.plot(df["sm_full_length"], df["pm_full_length"], "k+")
plt.plot(df["sm_surface"] / 100.0, df["pm_surface"] / 100.0, "c+")
plt.plot(df["sm_inclination_angle"] * 100.0, df["pm_inclination_angle"] * 100.0, "co")
for label, color in [("mature_leaf", "b+"), ("growing_leaf", "r+"), ("stem", "g+")]:
    df_filtred_label = df[df["pm_label"] == label]
    plt.plot(df_filtred_label["sm_length"], df_filtred_label["pm_length"], color)

plt.plot(df_filtred_label["sm_length"], df_filtred_label["pm_length"], color)
plt.xlabel("Phenomenal measurements")
plt.ylabel("Synthetic measurements")

m = int(
    max(
        [
            df[k].max()
            for k in ["pm_length", "sm_length", "pm_full_length", "sm_full_length"]
        ]
    )
)
plt.plot(range(m), range(m), "k-")
[21]:
[<matplotlib.lines.Line2D at 0x721de1655e50>]
../_images/examples_Synthetic_Validation_32_1.png