Source code for openalea.phenomenal.segmentation.skeleton_phenomenal

# -*- python -*-
#
#       Copyright INRIA - CIRAD - INRA
#
#       Distributed under the Cecill-C License.
#       See accompanying file LICENSE.txt or copy at
#           http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html
#
# ==============================================================================
from __future__ import division, print_function, absolute_import

import numpy

try:
    import nx_cugraph as networkx
except ImportError:
    import networkx

import openalea.phenomenal.segmentation._c_skeleton as c_skeleton

from ..multi_view_reconstruction import project_voxel_centers_on_image
from .plane_interception import (
    intercept_points_along_path_with_planes,
    intercept_points_along_polyline_with_ball,
)
from ..object import VoxelSkeleton, VoxelGrid, VoxelSegment


# ==============================================================================


[docs] def segment_reduction( voxel_skeleton, image_projection, required_visible=4, nb_min_pixel=100 ): """ Reduce the number of segments in a VoxelSkeleton object, according to their projection results. Each segment is kept if their projection on the images are not cover by the projection of the other segments in number required_visible. Segments are not cover if their remaining projected pixel are superior to nb_min_pixel. Parameters ---------- voxel_skeleton : VoxelSkeleton image_projection : list of tuple (image, projection) required_visible : int, optional Number of required not_covered segment to kept it. nb_min_pixel : int, optional Number of remaining pixel required to consider segment not covered Returns ------- """ # ========================================================================== # Order orderer_voxel_segments = sorted( voxel_skeleton.segments, key=lambda vs: len(vs.polyline) ) # ========================================================================== # import time # start = time.time() # ========================================================================== # tips = dict() len_segments = len(orderer_voxel_segments) len_images = len(image_projection) list_array = [None] * len_segments * len_images for i, vs in enumerate(orderer_voxel_segments): for j, (image, projection) in enumerate(image_projection): vp = numpy.array(list(vs.voxels_position)) list_array[i * len_images + j] = project_voxel_centers_on_image( vp, voxel_skeleton.voxels_size, image.shape, projection, dtype=numpy.int32, value=1, ) # vp = numpy.array([vs.polyline[-1]]) # tips[(i, j)] = openalea.phenomenal.multi_view_reconstruction. \ # project_voxel_centers_on_image( # vp, # voxel_skeleton.voxels_size, # iv.image.shape, # iv.projection) # print("time processing , project_voxel_centers_on_image : {}".format( # time.time() - start)) # ========================================================================== # start = time.time() list_negative_image = [] for j, (image, projection) in enumerate(image_projection): negative_image = image.copy().astype(numpy.int32) negative_image[negative_image > 0] = 2 negative_image[negative_image == 0] = 1 negative_image[negative_image == 2] = 0 list_negative_image.append(negative_image) list_array.append(negative_image) # print("time processing , negative_image : {}".format(time.time() - start)) # ========================================================================== # start = time.time() is_removed = numpy.zeros(len_segments, dtype=numpy.uint8) c_skeleton.skeletonize( list_array, is_removed, len_segments, len_images, nb_min_pixel, required_visible ) segments = [ orderer_voxel_segments[i] for i in range(len_segments) if is_removed[i] == 0 ] # print("time processing , C code covered : {}".format(time.time() - start)) return VoxelSkeleton(segments, voxel_skeleton.voxels_size)
# ========================================================================== # is_removed = numpy.zeros(len_segments, dtype=numpy.uint8) # # for i in range(len_segments): # weight = 0 # for j in range(len(image_projection)): # im = list_array[i * len_images + j] - list_negative_image[j] # for k in range(len_segments): # if k != i and not is_removed[k]: # im -= list_array[k * len_images + j] # # if numpy.count_nonzero(im > 0) >= nb_min_pixel: # weight += 1 # # if weight >= required_visible: # break # # if weight < required_visible: # is_removed[i] = True # # print("time processing , covered : {}".format(time.time() - start)) # segments = [orderer_voxel_segments[i] for i in range(len_segments) if # not is_removed[i]] # return VoxelSkeleton(segments, voxel_skeleton.voxels_size) # ============================================================================== def _get_longest_shortest_path_in_nodes(nodes, paths): leaf_skeleton_path = None longest_length = 0 for node in nodes: p = paths[node] if len(p) > longest_length: longest_length = len(p) leaf_skeleton_path = p return leaf_skeleton_path def _segment_path( voxels, array_voxels, skeleton_path, graph, voxels_size=4, mode="plane", plane_width=4, ball_radius=10, ): # ========================================================================== # Get the longest shorted path of voxels polyline = _get_longest_shortest_path_in_nodes(voxels, skeleton_path) # ========================================================================== if polyline: if mode == "ball": intercept_points = intercept_points_along_polyline_with_ball( array_voxels, graph, polyline, ball_radius=ball_radius ) else: intercept_points, _ = intercept_points_along_path_with_planes( array_voxels, polyline, distance_from_plane=plane_width, points_graph=graph, voxels_size=voxels_size, ) voxels_position = set().union(*intercept_points) segment = VoxelSegment(polyline, voxels_position, intercept_points) remain = set(voxels).difference(segment.voxels_position) return segment, remain return None, None
[docs] def find_base_stem_position(voxels_position, voxels_size, neighbor_size=45): """Function to find the base stem position of the plant from the voxels center positions list. Voxels position are converted in 3d image to find around the x and y-axis the points with the minimum value in a range of neighbor_side. Parameters ---------- voxels_position : ndarray 3d voxels center positions [(x, y, z), ...] voxels_size : int Voxel size diameter neighbor_size : int, optional Radius size in mm to search the base stem position Returns ------- base_stem_position : 3-tuple """ image_3d = VoxelGrid(voxels_position, voxels_size).to_image_3d() x = int(round(0 - image_3d.world_coordinate[0] / image_3d.voxels_size)) y = int(round(0 - image_3d.world_coordinate[1] / image_3d.voxels_size)) k = neighbor_size / voxels_size x_len, y_len, _ = image_3d.shape roi = image_3d[ int(max(x - k, 0)) : int(min(x + k, x_len)), int(max(y - k, 0)) : int(min(y + k, y_len)), :, ] xx, yy, zz = numpy.where(roi == 1) min_z_value = numpy.min(zz) index_min_z_value = numpy.where(zz == min_z_value) mean_float_point = numpy.array( [ numpy.mean(xx[index_min_z_value]), numpy.mean(yy[index_min_z_value]), numpy.mean(zz[index_min_z_value]), ] ) mean_point = None min_dist = float("inf") for xxx, yyy, zzz in zip(xx, yy, zz): pt = numpy.array([xxx, yyy, zzz]) dist = numpy.linalg.norm(mean_float_point - pt) if dist < min_dist: min_dist = dist mean_point = pt stem_base_position = ( int(max(x - k, 0)) + mean_point[0], int(max(y - k, 0)) + mean_point[1], mean_point[2], ) base_stem_position = ( numpy.array(stem_base_position) * voxels_size + image_3d.world_coordinate ) return base_stem_position
[docs] def compute_all_shorted_path(graph, voxels_size, neighbor_size=45, src_node=None): """Compute all the shorted path from the base position of the graph position. Parameters ---------- graph : networkx.Graph Graph of 3d voxel center point position voxels_size : int Voxels diameter size Returns ------- all_shorted_path_to_stem_base : dict List of all the shorted path of the graph from the base """ # ========================================================================== # Get the high points in the matrix and the supposed base plant points if src_node is None: x_stem, y_stem, z_stem = find_base_stem_position( graph.nodes(), voxels_size, neighbor_size=neighbor_size ) src_node = (x_stem, y_stem, z_stem) # ========================================================================== # Compute the shorted path all_shorted_path_to_stem_base = networkx.single_source_dijkstra_path( graph, src_node, weight="weight" ) return all_shorted_path_to_stem_base
[docs] def skeletonize( voxel_grid, graph, subgraph=None, voxels_position_remain=None, mode="plane", plane_width=None, ball_radius=None, neighbor_size=45, src_node=None ): """Compute phenomenal skeletonization on the voxel_grid based on the graph. Parameters ---------- voxel_grid : VoxelGrid graph : networkx.Graph subgraph: networkx.graph, optional If not None, perform the computation of the shorted paths on the subgraph and remove voxels mode : str, optional Mode for intercept point along the paths. Two mode available, "ball" or "plane". By default, "plane" mode. plane_width : int, optional Size in mm of the width of the plane. By default, or if None is equal to the voxel_size of the voxel_grid ball_radius : int, optional Size in mm of the radius of the ball. By default, or if None is equal to the voxel_size * 4 of the voxel_grid Returns ------- voxel_skeleton : VoxelSkeleton """ if plane_width is None: plane_width = voxel_grid.voxels_size * 2 if ball_radius is None: ball_radius = voxel_grid.voxels_size * 4 if subgraph is None: subgraph = graph voxels_size = voxel_grid.voxels_size all_shorted_path_to_stem_base = compute_all_shorted_path( subgraph, voxels_size, neighbor_size=neighbor_size, src_node=src_node ) # ========================================================================== if voxels_position_remain is None: voxels_position_remain = subgraph.nodes() np_arr_all_graph_voxels_plant = numpy.array(graph.nodes()) # ========================================================================== segments = [] while len(voxels_position_remain) != 0: (voxel_segment, voxels_position_remain) = _segment_path( voxels_position_remain, np_arr_all_graph_voxels_plant, all_shorted_path_to_stem_base, graph, voxels_size=voxels_size, mode=mode, plane_width=plane_width, ball_radius=ball_radius, ) segments.append(voxel_segment) return VoxelSkeleton(segments, voxels_size)