Example of sulcal graph in slam

# Authors:
# Lucile Hashimoto lucile-hashimoto
# Guillaume Auzias <guillaume.auzias@univ-amu.fr>

# License: MIT
# sphinx_gallery_thumbnail_number = 2

importation of slam modules

import numpy as np
import networkx as nx
import slam.io as sio
import slam.sulcal_graph as ssg
import slam.watershed as swat
import slam.remeshing as srem

loading an examplar mesh and corresponding texture

path_to_mesh = "../examples/data/example_mesh.gii"
path_to_texture = "../examples/data/example_texture.gii"
path_to_mask = None
mesh = sio.load_mesh(path_to_mesh)
side = "left"
texture = sio.load_texture(path_to_texture)
dpf = np.array(texture.darray[0])

# define the exclusion mask (cingular pole)
if path_to_mask is not None:
    mask = sio.load_texture(path_to_mask).darray[0]
else:
    mask = None

thresh_dist_perc=0
thresh_ridge=0
thresh_area_perc=0

extract the sulcal graph from a mesh

        Computing the curvature

Calculating vertex normals .... Please wait
Finished calculating vertex normals
Calculating curvature tensors ... Please wait
Finished Calculating curvature tensors
Calculating Principal Components ... Please wait
Finished Calculating principal components

        Computing the DPF*

  Computing Laplacian
    Computing mesh weights of type conformal
    -edge length threshold needed for  0  values =  0.0  %
    -number of Nan in weights:  0  =  0.0  %
    -number of Negative values in weights:  936  =  6.706792777300086  %
    -nb Nan in Laplacian :  0
    -nb Inf in Laplacian :  0

        Computing Voronoi's vertex

    -percent polygon with obtuse angle  29.4067067927773
Computing watershed by flooding...
Thresholds provided:
-Distance between 2 pits: 0 % <=> 0.0 mm
-Ridge height: 0
-Basin area: 0 % <=> 0.0 mm2

add an attribute to nodes

g = ssg.add_node_attribute_from_texture(g, dpf, attribute_name='pit_depth')
print("Node attributes:\n", g.nodes[0].keys())
print("First node:\n", g.nodes[0])
Node attributes:
 dict_keys(['pit_index', 'basin_vertices', 'pit_depth', 'basin_area', 'basin_label'])
First node:
 {'pit_index': 530, 'basin_vertices': [530, 535, 2172, 529, 524, 2131, 2173, 531, 947, 964, 941, 911, 2199, 2174, 559, 912, 2132, 2187, 2198, 910, 909, 2158, 546, 2091, 561, 513, 2074, 2200, 871, 948, 537, 927, 860, 2211, 2133, 2188, 894, 861, 895, 862, 2216, 863, 875, 846, 966, 2204, 2212, 2104, 496, 873, 514, 913, 2038, 560, 2218, 847, 2134, 819, 876, 965, 2135, 2159, 2213, 516, 967, 949, 462, 874, 503, 548, 794, 2161, 2138, 2215, 2136, 2205, 463, 497, 968, 806, 782, 929, 884, 2163, 2219, 356, 2217, 831, 486, 464, 2220, 2227, 450, 155, 553, 2214, 2230, 848, 440, 952, 795, 456, 2177, 2190, 878, 741, 2232, 2231, 781, 1925, 774, 547, 896, 2242, 747, 2191, 808, 2221, 914, 942, 566, 885, 2250, 536, 2264, 2235, 769, 797, 850, 915, 2251, 552, 578, 515, 2265, 743, 879, 851, 2233, 886, 2266, 621, 770, 596, 2234, 587, 880, 488, 605, 562, 615, 2222, 579, 809, 852, 629, 853, 604, 2244, 640, 489, 748, 2252, 2236, 641, 458, 2223, 2243, 639, 608, 2267, 2268, 427, 2280, 403, 411, 393, 382, 370, 1529], 'pit_depth': np.float32(-0.050967123), 'basin_area': np.float64(445.9305499974789), 'basin_label': 0}

add an attribute to edges

g = ssg.add_edge_attribute_from_texture(g, dpf, attribute_name='ridge_depth_bis')
print("Edge attributes:\n", g.edges[list(g.edges)[0]].keys())
print("First edge:\n", g.edges[list(g.edges)[0]])
Edge attributes:
 dict_keys(['weight', 'ridge_vertices', 'ridge_length', 'ridge_index', 'ridge_depth', 'ridge_height_min', 'ridge_height_max', 'ridge_depth_bis'])
First edge:
 {'weight': 1, 'ridge_vertices': array([ 444,  449,  480, 1997, 2045]), 'ridge_length': 5, 'ridge_index': np.int64(2045), 'ridge_depth': np.float64(-0.00562644488046583), 'ridge_height_min': np.float64(0.002235688506772268), 'ridge_height_max': np.float64(0.0074187486063752375), 'ridge_depth_bis': -0.01064499281346798}

#### compute Euclidian distance between pits and to the ridge

print("compute Euclidian dist")
g = ssg.add_edge_attribute_euclidian_distances(g,mesh)

##### compute geodesic distance between pits and to the ridge
print("compute geodesic dist")
import time
t1=time.time()
g = ssg.add_edge_attribute_geodesic_distances(g,mesh)
t2=time.time()
print(t2-t1)


edges_euclidian_dist = (np.array(list(nx.get_edge_attributes(
            g, "euclidian_distance_btw_pits").values())))
edges_geo_dist = (np.array(list(nx.get_edge_attributes(
            g, "geodesic_distance_btw_pits").values())))

import plotly.graph_objects as go
from plotly.subplots import make_subplots
fig_comp1 = go.Figure(data=[go.Scatter(
    x=edges_geo_dist,
    y=edges_euclidian_dist,
    mode='markers',
    marker_color='red',

)])
fig_comp1.show()
compute Euclidian dist
compute geodesic dist
0.06752467155456543

add mean value to nodes attributes

g = ssg.add_mean_value_to_nodes(g, dpf, attribute_name='basin_mean_depth')

get textures from graph

atex_labels, atex_pits, atex_ridges, atex_boundaries = ssg.get_textures_from_graph(g, mesh)
A more detailed computation of the sulcal graph

with explicit call to the watershed

mean_curvature, dpf, voronoi = swat.compute_mesh_features(mesh)
        Computing the curvature

Calculating vertex normals .... Please wait
Finished calculating vertex normals
Calculating curvature tensors ... Please wait
Finished Calculating curvature tensors
Calculating Principal Components ... Please wait
Finished Calculating principal components

        Computing the DPF*

  Computing Laplacian
    Computing mesh weights of type conformal
    -edge length threshold needed for  0  values =  0.0  %
    -number of Nan in weights:  0  =  0.0  %
    -number of Negative values in weights:  936  =  6.706792777300086  %
    -nb Nan in Laplacian :  0
    -nb Inf in Laplacian :  0

        Computing Voronoi's vertex

    -percent polygon with obtuse angle  29.4067067927773

extract sulcal pits and associated basins

basins, ridges, adjacency = swat.watershed(
    mesh, voronoi, dpf, thresh_dist_perc, thresh_ridge, thresh_area_perc, mask)

# generate the sulcal graph
g = ssg.get_sulcal_graph(adjacency, basins, ridges)
Computing watershed by flooding...
Thresholds provided:
-Distance between 2 pits: 0 % <=> 0.0 mm
-Ridge height: 0
-Basin area: 0 % <=> 0.0 mm2

generate the textures from graph

atex_labels_graph, atex_pits_graph, atex_ridges_graph, atex_boundaries = (ssg.get_textures_from_graph
                                    (g, mesh))
# compare the textures extracted from the watershed with the ones extarcted from the graph
# they should be identical
print("vertex-to-vertex difference between the texture extracted from the "
      "watershed and the one extracted from the graph, should be strictly qual to 0")
print(np.max(atex_ridges-atex_ridges_graph))
vertex-to-vertex difference between the texture extracted from the watershed and the one extracted from the graph, should be strictly qual to 0
0.0

add as a new node attribute the 3D coordinates of the vertex corresponding to the pit in the mesh used to compute the watershed

g = ssg.add_coords_attribute(g, mesh,
                                    attribute_vert_index='pit_index',
                                    new_attribute_key='3dcoords')
print("First node:\n", g.nodes[0])
First node:
 {'pit_index': 530, 'basin_vertices': [530, 535, 2172, 529, 524, 2131, 2173, 531, 947, 964, 941, 911, 2199, 2174, 559, 912, 2132, 2187, 2198, 910, 909, 2158, 546, 2091, 561, 513, 2074, 2200, 871, 948, 537, 927, 860, 2211, 2133, 2188, 894, 861, 895, 862, 2216, 863, 875, 846, 966, 2204, 2212, 2104, 496, 873, 514, 913, 2038, 560, 2218, 847, 2134, 819, 876, 965, 2135, 2159, 2213, 516, 967, 949, 462, 874, 503, 548, 794, 2161, 2138, 2215, 2136, 2205, 463, 497, 968, 806, 782, 929, 884, 2163, 2219, 356, 2217, 831, 486, 464, 2220, 2227, 450, 155, 553, 2214, 2230, 848, 440, 952, 795, 456, 2177, 2190, 878, 741, 2232, 2231, 781, 1925, 774, 547, 896, 2242, 747, 2191, 808, 2221, 914, 942, 566, 885, 2250, 536, 2264, 2235, 769, 797, 850, 915, 2251, 552, 578, 515, 2265, 743, 879, 851, 2233, 886, 2266, 621, 770, 596, 2234, 587, 880, 488, 605, 562, 615, 2222, 579, 809, 852, 629, 853, 604, 2244, 640, 489, 748, 2252, 2236, 641, 458, 2223, 2243, 639, 608, 2267, 2268, 427, 2280, 403, 411, 393, 382, 370, 1529], 'pit_depth': np.float64(-0.013045193486841068), 'basin_area': np.float64(445.9305499974789), 'basin_label': 0, '3dcoords': array([ 6.71122503, -2.44120479,  0.5532046 ])}

add as a new node attribute the 3D coordinates of the vertex corresponding to the pit in the spherical mesh obtained from the original mesh, so that ‘pit_index’ also gives the corresponding vertex in that mesh

source_spherical_mesh_file = "../examples/data/example_mesh_spherical.gii"
source_spherical_mesh = sio.load_mesh(source_spherical_mesh_file)

g = ssg.add_coords_attribute(g, source_spherical_mesh,
                                    attribute_vert_index='pit_index',
                                    new_attribute_key='sphere_3dcoords')
print("First node:\n", g.nodes[0])
First node:
 {'pit_index': 530, 'basin_vertices': [530, 535, 2172, 529, 524, 2131, 2173, 531, 947, 964, 941, 911, 2199, 2174, 559, 912, 2132, 2187, 2198, 910, 909, 2158, 546, 2091, 561, 513, 2074, 2200, 871, 948, 537, 927, 860, 2211, 2133, 2188, 894, 861, 895, 862, 2216, 863, 875, 846, 966, 2204, 2212, 2104, 496, 873, 514, 913, 2038, 560, 2218, 847, 2134, 819, 876, 965, 2135, 2159, 2213, 516, 967, 949, 462, 874, 503, 548, 794, 2161, 2138, 2215, 2136, 2205, 463, 497, 968, 806, 782, 929, 884, 2163, 2219, 356, 2217, 831, 486, 464, 2220, 2227, 450, 155, 553, 2214, 2230, 848, 440, 952, 795, 456, 2177, 2190, 878, 741, 2232, 2231, 781, 1925, 774, 547, 896, 2242, 747, 2191, 808, 2221, 914, 942, 566, 885, 2250, 536, 2264, 2235, 769, 797, 850, 915, 2251, 552, 578, 515, 2265, 743, 879, 851, 2233, 886, 2266, 621, 770, 596, 2234, 587, 880, 488, 605, 562, 615, 2222, 579, 809, 852, 629, 853, 604, 2244, 640, 489, 748, 2252, 2236, 641, 458, 2223, 2243, 639, 608, 2267, 2268, 427, 2280, 403, 411, 393, 382, 370, 1529], 'pit_depth': np.float64(-0.013045193486841068), 'basin_area': np.float64(445.9305499974789), 'basin_label': 0, '3dcoords': array([ 6.71122503, -2.44120479,  0.5532046 ]), 'sphere_3dcoords': array([ 0.46400985,  0.88582945, -0.00102251])}

Load another mesh and corresponding spherical version to be used as a target onto which the graph will be projected

target_mesh_file = "../examples/data/example_mesh_2.gii"
target_mesh = sio.load_mesh(target_mesh_file)
target_spherical_mesh_file = "../examples/data/example_mesh_2_spherical.gii"
target_spherical_mesh = sio.load_mesh(target_spherical_mesh_file)

Project the depth texture using resampling.texture_spherical_interpolation_nearest_neighbor for the visu

interpolated_dpf = srem.texture_spherical_interpolation_nearest_neighbor(
    source_spherical_mesh, target_spherical_mesh, dpf)

Compute the ‘interpolated_pits_index’ corresponding to the index of the nearest neighbor of each pit in the target spherical mesh

g = ssg.vertex_index_interpolation(g, target_spherical_mesh,
                                              graph_spherical_coords_attribute='sphere_3dcoords',
                                              interpolated_attribute='interpolated_pits_index')
print("Get the number of nodes in the graph:\n", g.number_of_nodes())
print("Get the number of edges in the graph:\n", g.number_of_edges())
print("Get the number of edges incident to the node 1:\n", g.degree[1])
print("Show the first node:\n", g.nodes[0])
print("Print the attributes of the first node:\n", g.nodes[0].keys())
print("Show the first edge:\n", g.edges[0,4])
print("Print the attributes of the first edge:\n", g.edges[0,4].keys())
Get the number of nodes in the graph:
 27
Get the number of edges in the graph:
 75
Get the number of edges incident to the node 1:
 5
Show the first node:
 {'pit_index': 530, 'basin_vertices': [530, 535, 2172, 529, 524, 2131, 2173, 531, 947, 964, 941, 911, 2199, 2174, 559, 912, 2132, 2187, 2198, 910, 909, 2158, 546, 2091, 561, 513, 2074, 2200, 871, 948, 537, 927, 860, 2211, 2133, 2188, 894, 861, 895, 862, 2216, 863, 875, 846, 966, 2204, 2212, 2104, 496, 873, 514, 913, 2038, 560, 2218, 847, 2134, 819, 876, 965, 2135, 2159, 2213, 516, 967, 949, 462, 874, 503, 548, 794, 2161, 2138, 2215, 2136, 2205, 463, 497, 968, 806, 782, 929, 884, 2163, 2219, 356, 2217, 831, 486, 464, 2220, 2227, 450, 155, 553, 2214, 2230, 848, 440, 952, 795, 456, 2177, 2190, 878, 741, 2232, 2231, 781, 1925, 774, 547, 896, 2242, 747, 2191, 808, 2221, 914, 942, 566, 885, 2250, 536, 2264, 2235, 769, 797, 850, 915, 2251, 552, 578, 515, 2265, 743, 879, 851, 2233, 886, 2266, 621, 770, 596, 2234, 587, 880, 488, 605, 562, 615, 2222, 579, 809, 852, 629, 853, 604, 2244, 640, 489, 748, 2252, 2236, 641, 458, 2223, 2243, 639, 608, 2267, 2268, 427, 2280, 403, 411, 393, 382, 370, 1529], 'pit_depth': np.float64(-0.013045193486841068), 'basin_area': np.float64(445.9305499974789), 'basin_label': 0, '3dcoords': array([ 6.71122503, -2.44120479,  0.5532046 ]), 'sphere_3dcoords': array([ 0.46400985,  0.88582945, -0.00102251]), 'interpolated_pits_index': 492}
Print the attributes of the first node:
 dict_keys(['pit_index', 'basin_vertices', 'pit_depth', 'basin_area', 'basin_label', '3dcoords', 'sphere_3dcoords', 'interpolated_pits_index'])
Show the first edge:
 {'weight': 1, 'ridge_vertices': array([ 444,  449,  480, 1997, 2045]), 'ridge_length': 5, 'ridge_index': np.int64(2045), 'ridge_depth': np.float64(-0.00562644488046583), 'ridge_height_min': np.float64(0.002235688506772268), 'ridge_height_max': np.float64(0.0074187486063752375)}
Print the attributes of the first edge:
 dict_keys(['weight', 'ridge_vertices', 'ridge_length', 'ridge_index', 'ridge_depth', 'ridge_height_min', 'ridge_height_max'])

Here is the way to get the list of ‘interpolated_pits_index’ for all nodes

interp_pits_inds = np.array(list(nx.get_node_attributes(g, 'interpolated_pits_index').values()))

add as a new node attribute the 3D coordinates of the vertex corresponding to # the inperpolated pit in the target spherical mesh

g = ssg.add_coords_attribute(g, target_spherical_mesh,
                                    attribute_vert_index='interpolated_pits_index',
                                    new_attribute_key='target_sphere_3dcoords')
print("Show the first node to see the impact of the previous command:\n", g.nodes[0])
Show the first node to see the impact of the previous command:
 {'pit_index': 530, 'basin_vertices': [530, 535, 2172, 529, 524, 2131, 2173, 531, 947, 964, 941, 911, 2199, 2174, 559, 912, 2132, 2187, 2198, 910, 909, 2158, 546, 2091, 561, 513, 2074, 2200, 871, 948, 537, 927, 860, 2211, 2133, 2188, 894, 861, 895, 862, 2216, 863, 875, 846, 966, 2204, 2212, 2104, 496, 873, 514, 913, 2038, 560, 2218, 847, 2134, 819, 876, 965, 2135, 2159, 2213, 516, 967, 949, 462, 874, 503, 548, 794, 2161, 2138, 2215, 2136, 2205, 463, 497, 968, 806, 782, 929, 884, 2163, 2219, 356, 2217, 831, 486, 464, 2220, 2227, 450, 155, 553, 2214, 2230, 848, 440, 952, 795, 456, 2177, 2190, 878, 741, 2232, 2231, 781, 1925, 774, 547, 896, 2242, 747, 2191, 808, 2221, 914, 942, 566, 885, 2250, 536, 2264, 2235, 769, 797, 850, 915, 2251, 552, 578, 515, 2265, 743, 879, 851, 2233, 886, 2266, 621, 770, 596, 2234, 587, 880, 488, 605, 562, 615, 2222, 579, 809, 852, 629, 853, 604, 2244, 640, 489, 748, 2252, 2236, 641, 458, 2223, 2243, 639, 608, 2267, 2268, 427, 2280, 403, 411, 393, 382, 370, 1529], 'pit_depth': np.float64(-0.013045193486841068), 'basin_area': np.float64(445.9305499974789), 'basin_label': 0, '3dcoords': array([ 6.71122503, -2.44120479,  0.5532046 ]), 'sphere_3dcoords': array([ 0.46400985,  0.88582945, -0.00102251]), 'interpolated_pits_index': 492, 'target_sphere_3dcoords': array([0.45437539, 0.89028907, 0.03046896])}

add as a new node attribute the 3D coordinates of the vertex corresponding to # the inperpolated pit in the target mesh

g = ssg.add_coords_attribute(g, target_mesh,
                                    attribute_vert_index='interpolated_pits_index',
                                    new_attribute_key='target_mesh_3dcoords')
print("Show the first node to see the impact of the previous command:\n", g.nodes[0])
Show the first node to see the impact of the previous command:
 {'pit_index': 530, 'basin_vertices': [530, 535, 2172, 529, 524, 2131, 2173, 531, 947, 964, 941, 911, 2199, 2174, 559, 912, 2132, 2187, 2198, 910, 909, 2158, 546, 2091, 561, 513, 2074, 2200, 871, 948, 537, 927, 860, 2211, 2133, 2188, 894, 861, 895, 862, 2216, 863, 875, 846, 966, 2204, 2212, 2104, 496, 873, 514, 913, 2038, 560, 2218, 847, 2134, 819, 876, 965, 2135, 2159, 2213, 516, 967, 949, 462, 874, 503, 548, 794, 2161, 2138, 2215, 2136, 2205, 463, 497, 968, 806, 782, 929, 884, 2163, 2219, 356, 2217, 831, 486, 464, 2220, 2227, 450, 155, 553, 2214, 2230, 848, 440, 952, 795, 456, 2177, 2190, 878, 741, 2232, 2231, 781, 1925, 774, 547, 896, 2242, 747, 2191, 808, 2221, 914, 942, 566, 885, 2250, 536, 2264, 2235, 769, 797, 850, 915, 2251, 552, 578, 515, 2265, 743, 879, 851, 2233, 886, 2266, 621, 770, 596, 2234, 587, 880, 488, 605, 562, 615, 2222, 579, 809, 852, 629, 853, 604, 2244, 640, 489, 748, 2252, 2236, 641, 458, 2223, 2243, 639, 608, 2267, 2268, 427, 2280, 403, 411, 393, 382, 370, 1529], 'pit_depth': np.float64(-0.013045193486841068), 'basin_area': np.float64(445.9305499974789), 'basin_label': 0, '3dcoords': array([ 6.71122503, -2.44120479,  0.5532046 ]), 'sphere_3dcoords': array([ 0.46400985,  0.88582945, -0.00102251]), 'interpolated_pits_index': 492, 'target_sphere_3dcoords': array([0.45437539, 0.89028907, 0.03046896]), 'target_mesh_3dcoords': array([69.98622894, 68.32143402, 50.87675095])}

Get and visualize the distribution of some attirbutes on nodes and edges

nodes_pit_depth = (np.array(list(nx.get_node_attributes(
            g, "pit_depth").values())))
nodes_basin_area = (np.array(list(nx.get_node_attributes(
            g, "basin_area").values())))
nodes_basin_label = (np.array(list(nx.get_node_attributes(
            g, "basin_label").values())))

edges_ridge_depth = (np.array(list(nx.get_edge_attributes(
            g, "ridge_depth").values())))
edges_ridge_depth_min = (np.array(list(nx.get_edge_attributes(
            g, "ridge_depth_diff_min").values())))
edges_ridge_depth_max = (np.array(list(nx.get_edge_attributes(
            g, "ridge_depth_diff_max").values())))

import plotly.graph_objects as go
from plotly.subplots import make_subplots

fig = make_subplots(rows=2, cols=4)
## nodes_pit_depth
fig_nodes_pit_depth = go.Figure(data=[go.Histogram(
    x=nodes_pit_depth,
    nbinsx=10,  # Number of bins
    histnorm='probability',
    marker_color='blue',  # Bar color
    opacity=0.4  # Bar opacity
)])
# Update layout
fig_nodes_pit_depth.update_layout(
    title='nodes_pit_depth',
    xaxis_title='dpf*',
    yaxis_title='Count'
)
fig.add_trace(fig_nodes_pit_depth.data[0], row=1, col=1)
## pits depth
fig_nodes_basin_area = go.Figure(data=[go.Histogram(
    x=nodes_basin_area,
    nbinsx=10,  # Number of bins
    histnorm='probability',
    marker_color='green',  # Bar color
    opacity=0.4  # Bar opacity
)])
# Update layout
fig_nodes_basin_area.update_layout(
    title='nodes_basin_area',
    xaxis_title='mm2',
    yaxis_title='Count'
)
fig.add_trace(fig_nodes_basin_area.data[0], row=1, col=2)
## pits depth VS label
fig_nodes_pit_depth_vs_label = go.Figure(data=[go.Scatter(
    x=nodes_basin_label,
    y=nodes_pit_depth,
    mode='markers',
    marker_color='red',

)])
# Update layout
fig_nodes_pit_depth_vs_label.update_layout(
    title='pit_depth VS bassin_label',
    xaxis_title='label',
    yaxis_title='pit_depth'
)
fig.add_trace(fig_nodes_pit_depth_vs_label.data[0], row=1, col=3)
## basin_area VS label
fig_nodes_basin_area_vs_label = go.Figure(data=[go.Scatter(
    x=nodes_basin_label,
    y=nodes_basin_area,
    mode='markers',
    marker_color='black',
)])
# Update layout
fig_nodes_basin_area_vs_label.update_layout(
    title='basin_area VS bassin_label',
    xaxis_title='label',
    yaxis_title='basin_area'
)
fig.add_trace(fig_nodes_basin_area_vs_label.data[0], row=1, col=4)


### edges
## edges_ridge_depth
fig_edges_ridge_depth = go.Figure(data=[go.Histogram(
    x=edges_ridge_depth,
    nbinsx=10,  # Number of bins
    histnorm='probability',
    marker_color='yellow',  # Bar color
    opacity=0.4  # Bar opacity
)])
# Update layout
fig_edges_ridge_depth.update_layout(
    title='edges_ridge_depth',
    xaxis_title='dpf*',
    yaxis_title='Count'
)
fig.add_trace(fig_edges_ridge_depth.data[0], row=2, col=1)
## edges_ridge_depth_min
fig_edges_ridge_depth_min = go.Figure(data=[go.Histogram(
    x=edges_ridge_depth_min,
    nbinsx=10,  # Number of bins
    histnorm='probability',
    marker_color='cyan',  # Bar color
    opacity=0.4  # Bar opacity
)])
# Update layout
fig_edges_ridge_depth_min.update_layout(
    title='edges_ridge_depth_min',
    xaxis_title='dpf*',
    yaxis_title='Count'
)
fig.add_trace(fig_edges_ridge_depth_min.data[0], row=2, col=2)
## edges_ridge_depth_max
fig_edges_ridge_depth_max = go.Figure(data=[go.Histogram(
    x=edges_ridge_depth_max,
    nbinsx=10,  # Number of bins
    histnorm='probability',
    marker_color='violet',  # Bar color
    opacity=0.4  # Bar opacity
)])
# Update layout
fig_edges_ridge_depth_max.update_layout(
    title='edges_ridge_depth_max',
    xaxis_title='dpf*',
    yaxis_title='Count'
)
fig.add_trace(fig_edges_ridge_depth_max.data[0], row=2, col=3)
fig.show()
fig


VISUALIZATION USING plotly

import slam.plot as splt

mesh_data = {
    "vertices": mesh.vertices,
    "faces": mesh.faces,
    "opacity": 0.5,
    "title": 'Source'
}
intensity_data = {
    "values": dpf,
    "mode": "vertex",
}
fig1 = splt.plot_mesh(
    mesh_data=mesh_data,
    intensity_data=intensity_data)
fig1 = splt.plot_graph(g, coords_attribute='3dcoords', fig=fig1,
        marker={"size": 6, "color": "white", "line":{"color":"black", "width":2}},
)
fig1.show()
fig1


mesh_data = {
    "vertices": source_spherical_mesh.vertices,
    "faces": source_spherical_mesh.faces,
    "title": 'Source spherical'
}
fig2 = splt.plot_mesh(
    mesh_data=mesh_data,
    intensity_data=intensity_data)
# add the pits to the plot
pits_coords = np.array(list(nx.get_node_attributes(g, 'sphere_3dcoords').values()))
trace_points = splt.plot_points(
    pits_coords,
    marker={"size": 6, "color": "white", "line":{"color":"black", "width":2}},
)
fig2.add_trace(trace_points)
fig2.show()
fig2


mesh_data = {
    "vertices": target_spherical_mesh.vertices,
    "faces": target_spherical_mesh.faces,
    "title": 'Target sphere'
}
intensity_data = {
    "values": interpolated_dpf,
    "mode": "vertex",
}
fig3 = splt.plot_mesh(
    mesh_data=mesh_data,
    intensity_data=intensity_data)
# add the pits to the plot
pits_init = splt.plot_points(
    pits_coords,
    marker={"size": 6, "color": "white", "line":{"color":"black", "width":2}},
)
fig3.add_trace(pits_init)
# add the interpolated pits to the plot
interp_pits_coords_sphere = np.array(list(nx.get_node_attributes(g, 'target_sphere_3dcoords').values()))
pits_interpolated = splt.plot_points(
    interp_pits_coords_sphere,
    marker={"size": 6, "color": "red", "line":{"color":"black", "width":2}},
)
fig3.add_trace(pits_interpolated)
fig3.show()
fig3


mesh_data = {
    "vertices": target_mesh.vertices,
    "faces": target_mesh.faces,
    "title": 'Target mesh'
}
intensity_data = {
    "values": interpolated_dpf,
    "mode": "vertex",
}
fig4 = splt.plot_mesh(
    mesh_data=mesh_data,
    intensity_data=intensity_data)
# add the interpolated pits to the plot
interp_pits_coords_mesh = np.array(list(nx.get_node_attributes(g, 'target_mesh_3dcoords').values()))
pits_interpolated = splt.plot_points(
    interp_pits_coords_mesh,
    marker={"size": 6, "color": "red", "line":{"color":"black", "width":2}}
)
fig4.add_trace(pits_interpolated)
fig4.show()
fig4


Total running time of the script: (0 minutes 23.016 seconds)

Gallery generated by Sphinx-Gallery