<jupyter_start><jupyter_text>Working with 2D labelsThis notebook enables the user to reproduce the panels from figure 2 of ["Griottes: a generalist tool for network generation from segmented tissue images"](https://www.biorxiv.org/content/10.1101/2022.01.14.476345v1). The first part of the document shows how to generate a graph object from a segmented image using the contact-based network construction method. The second part of the notebook contains the code corresponding to the more advanced analysis shown in the paper to demonstrate the power of network-based analysis methods. The data are available on the github release and will be directly downloaded to the local repository. I. Building graph 1. Load image and mask of nucleiFirst download the image from the latest release of `Griottes` from the GitHub release and load the image to the notebook.<jupyter_code>import tifffile
import pandas
from griottes import get_cell_properties, generate_contact_graph, generate_delaunay_graph, generate_geometric_graph, plot_2D

import urllib3
import shutil
import os

import matplotlib.pyplot as plt
import seaborn as sns
url = 'https://github.com/BaroudLab/Griottes/releases/download/v1.0-alpha/zebrafish_brain_cell_labels.tiff'
filename = 'zebrafish_brain_cell_labels.tiff'

if not os.path.exists(filename):
    c = urllib3.PoolManager()

    with c.request('GET',url, preload_content=False) as resp, open(filename, 'wb') as out_file:
        shutil.copyfileobj(resp, out_file)

    resp.release_conn()
else:
    print('dataset already exists')
    
sub_image = tifffile.imread(filename)<jupyter_output>dataset already exists<jupyter_text>2. Generating a contact graphFrom the masks of the complete cells we can build the contact graph of the network, connecting together cells sharing a common border.<jupyter_code>G_contact = generate_contact_graph(sub_image[4],
                                   analyze_fluo_channels=False)<jupyter_output>The input image has 2 dimensions, it will be analyzed as a labeled image.<jupyter_text>3. Plotting a representation of the graphFrom the graph `G` it is possible to plot a visual representation of the network. To facilitate this, `Griottes` contains several specific plotting functions adapted for the network representation of tisssues.<jupyter_code>plot_2D(G_contact,
                figsize = (5,5),
                alpha_line = 1,
                scatterpoint_size = 10,
                legend = True,
                edge_color = 'k',
                line_factor = 2)<jupyter_output><empty_output><jupyter_text>Adding the `background_image` variable allows to plot the network on to the tissue image of your choice. Notice that we change the edge color to white at the same time:<jupyter_code>plot_2D(G_contact,
        background_image = sub_image[1],
        figsize = (5,5),
        alpha_line = 1,
        scatterpoint_size = 10,
        legend = True,
        edge_color = 'w',
        line_factor = 2)<jupyter_output><empty_output><jupyter_text>The `generate_contact_graph` method can measure the strength of the bond between two cells as a function of the shared boundary size. This can be shown by changing the parameter `include_weights = True`, revealing the width of each link as a function of the contact size between two cells:<jupyter_code>plot_2D(G_contact,
        background_image = sub_image[1],
        include_weights = True,
        figsize = (5,5),
        alpha_line = 1,
        scatterpoint_size = 10,
        legend = True,
        edge_color = 'w',
        line_factor = 0.15)<jupyter_output><empty_output><jupyter_text>II. Estimating the properties of the tissue graphsWe have shown how we can generate contact-based graphs from segmented images containing the whole cell. We can also build geometric graphs and Delaunay graphs from these images as shown in the cells immediately below. The purpose of the second half of the notebook is to compare the statistics of these graphs and illustrate applications of the network construction method.  1. Geometric graphsWe can build geometric graphs of the segmented image using the `generate_geometric_graph` method. The maximum cell-cell distance authorized in the graph is set by the `radius` parameter. For practical purposes it will be set to `radius = 100` in the ulterior examples<jupyter_code>G_geometric = generate_geometric_graph(sub_image[4],
                                   analyze_fluo_channels=False, 
                                   distance = 60)

plot_2D(G_geometric,
        background_image = sub_image[1],
        figsize = (5,5),
        alpha_line = 1,
        scatterpoint_size = 10,
        legend = True,
        edge_color = 'w',
        line_factor = 2)<jupyter_output>The input image has 2 dimensions, it will be analyzed as a labeled image.<jupyter_text>2. Delaunay graphsWe can build Delaunay graphs of the segmented image using the `generate_delaunay_graph` method. Contrary to a "pure" Delaunay graph, the method has an option to remove links that are beyond a given radius, thereby removing unrealistic connections in the tissue. The maximum cell-cell distance authorized in the graph is here also set by the `radius` parameter.<jupyter_code>G_delaunay = generate_delaunay_graph(sub_image[4],
                                   analyze_fluo_channels=False, 
                                   distance = 70)

plot_2D(G_delaunay,
        background_image = sub_image[1],
        figsize = (5,5),
        alpha_line = 1,
        scatterpoint_size = 10,
        legend = True,
        edge_color = 'w',
        line_factor = 2)<jupyter_output>The input image has 2 dimensions, it will be analyzed as a labeled image.<jupyter_text>3. Comparing graph construction propertiesThe different graph construction methods can be compared by looking at the mean number of links as a function of the graph construction method for example.<jupyter_code>url = 'https://github.com/BaroudLab/Griottes/releases/download/v1.0-alpha/zebrafish_brain_cell_labels_whole_cut.tiff'
filename = 'zebrafish_brain_cell_labels_whole_cut.tiff'

if not os.path.exists(filename):
    c = urllib3.PoolManager()

    with c.request('GET',url, preload_content=False) as resp, open(filename, 'wb') as out_file:
        shutil.copyfileobj(resp, out_file)

    resp.release_conn()
else:
    print('dataset already exists')
    
image = tifffile.imread(filename)
filename = 'zebrafish_brain_cell_labels_whole_cut.tiff'
image = tifffile.imread(filename)

descriptors = ['label', 'x', 'y']

G_contact = generate_contact_graph(image[4],
                                   descriptors = descriptors,
                                   analyze_fluo_channels=False)

G_geometric = generate_geometric_graph(image[4],
                                   descriptors = descriptors,
                                   analyze_fluo_channels=False, 
                                   distance = 70)

G_delaunay = generate_delaunay_graph(image[4],
                                   descriptors = descriptors,
                                   analyze_fluo_channels=False, 
                                   distance = 70)<jupyter_output>The input image has 2 dimensions, it will be analyzed as a labeled image.
The input image has 2 dimensions, it will be analyzed as a labeled image.
The input image has 2 dimensions, it will be analyzed as a labeled image.<jupyter_text>For instance, we can compare the different neighborhoods generated for each cell as a function of the network construction method. Cell by cell we compare the degrees, using the contact-based properties as a ground truth:<jupyter_code>degree_array = pandas.DataFrame()
j = 0

for node in G_contact.nodes():
    
    label = G_contact.nodes[node]['label']
    
    if label > 0:
    
        node_in_geometric_graph = [i for i in G_geometric.nodes() if G_geometric.nodes[i]['label'] == label][0]
        node_in_delaunay_graph = [i for i in G_delaunay.nodes() if G_delaunay.nodes[i]['label'] == label][0]

        degree_array.loc[j, 'node'] = node
        degree_array.loc[j, 'label'] = label
        degree_array.loc[j, 'degree_contact'] = G_contact.degree[node]
        degree_array.loc[j, 'degree_geometric'] = G_geometric.degree[node_in_geometric_graph]
        degree_array.loc[j, 'degree_voronoi'] = G_delaunay.degree[node_in_delaunay_graph]

        j += 1
fig, ax = plt.subplots(1,3,figsize = (11,3), sharex = True, sharey = True)

ax[0].tick_params(axis='both', which='major', labelsize=14)
ax[0].tick_params(axis='both', which='minor', labelsize=14)
ax[1].tick_params(axis='both', which='minor', labelsize=14)
ax[1].tick_params(axis='both', which='major', labelsize=14)
ax[2].tick_params(axis='both', which='minor', labelsize=14)
ax[2].tick_params(axis='both', which='major', labelsize=14)

ax[0] = sns.histplot(data = degree_array,
             x = 'degree_contact',
             binwidth = 1,
             ax = ax[0], kde=False, stat = 'probability')

ax[1] = sns.histplot(data = degree_array,
             x = 'degree_voronoi',
             binwidth = 1,
             ax = ax[1], kde=False, stat = 'probability')

ax[2] = sns.histplot(data = degree_array,
             x = 'degree_geometric',
             binwidth = 1,
             ax = ax[2], kde=False, stat = 'probability')

ax[0].set_ylabel('Probability', fontsize = 15)

ax[0].set_xlabel('Degree', fontsize = 15)
ax[1].set_xlabel('Degree', fontsize = 15)
ax[2].set_xlabel('Degree', fontsize = 15)

ax[0].set_title('Contact', fontsize = 15)
ax[1].set_title('Delaunay', fontsize = 15)
ax[2].set_title('Geometric', fontsize = 15)

plt.tight_layout()<jupyter_output><empty_output><jupyter_text>The importance of cell-cell contacts ("weights") are contained within the contact-based graph generated by the `generate_contact_graph` method. We can access all the individual weights using `NetworkX` and plot the distribution of cell-cell contacts in the tissue:<jupyter_code>weight_frame = pandas.DataFrame()
i = 0

for u,v,a in G_contact.edges(data=True):
    
    weight_frame.loc[i, 'weight'] = a['weight']
    i += 1
    
fig, ax = plt.subplots(1,1,figsize = (5,3), sharex = True, sharey = True)
ax.tick_params(axis='both', which='major', labelsize=14)
ax.tick_params(axis='both', which='minor', labelsize=14)

ax = sns.histplot(data = weight_frame,
             x = 'weight',
             binwidth = 5,
             ax = ax, kde=False, stat = 'probability')

ax.set_ylabel('Probability', fontsize = 15)
ax.set_xlabel('Link weight (A.U.)', fontsize = 15)

plt.tight_layout()<jupyter_output><empty_output><jupyter_text>4. Populating nodes with different cell typesImages can contain relevant fluorescence data. This information can be used to populate the network with specific node attributes. Here I show one way how this can be done.<jupyter_code>filename = 'zebrafish_brain_cell_labels_whole_cut.tiff'
sub_image = tifffile.imread(filename)

single_cell_properties = get_cell_properties(
    sub_image,
    mask_channel = 4,
    analyze_fluo_channels = True,
    fluo_channel_analysis_method = 'basic',
    min_area = 50,
    ndim = 2
    )
fig, ax = plt.subplots(1,1,figsize = (5,3), sharex = True, sharey = True)
ax.tick_params(axis='both', which='major', labelsize=14)
ax.tick_params(axis='both', which='minor', labelsize=14)

ax = sns.histplot(data = single_cell_properties,
             x = 'mean_intensity_3',
             binwidth = 500,
             ax = ax, kde=False, stat = 'probability')

plt.axvline(x = 6500, ls = '--', c = 'r', lw = 3)

ax.set_ylabel('Probability', fontsize = 15)
ax.set_xlabel('Cell intensity (A.U.)', fontsize = 15)

plt.tight_layout()<jupyter_output><empty_output><jupyter_text>The different cells have different fluorescent intensities. From the measured data we can classify the cells into different cell types and populate the cell types into the graph:<jupyter_code>filename = 'zebrafish_brain_cell_labels.tiff'
sub_image = tifffile.imread(filename)

single_cell_properties = get_cell_properties(
    sub_image,
    mask_channel = 4,
    analyze_fluo_channels = True,
    fluo_channel_analysis_method = 'basic',
    min_area = 50,
    ndim = 2
    )
single_cell_properties['type'] = (single_cell_properties.mean_intensity_3 > 6500).astype(int)
single_cell_properties['cell_properties'] = 'PCNA-'
single_cell_properties.loc[single_cell_properties['type'] > 0, 'cell_properties'] = 'PCNA+'

single_cell_properties['color'] = 'tab:blue'
single_cell_properties.loc[single_cell_properties['type'] > 0, 'color'] = 'tab:red'
single_cell_properties['legend'] = single_cell_properties['cell_properties']

descriptors = ['label', 'color', 'legend', 'x', 'y', 'cell_properties']
G_delaunay = generate_delaunay_graph(single_cell_properties,
                                   descriptors = descriptors,
                                   image_is_2D = True,
                                   distance = 100)<jupyter_output><empty_output><jupyter_text>`plot_2D` recognizes specific attributes in the graph that affect the plot: - `color`: interpreted as a cmap variable, it determines the node color - `legend`: corresponding legend for each node These variables are included in the graph and so plotting the graph gives:<jupyter_code>plot_2D(G_delaunay,
        background_image = sub_image[3],
        figsize = (5,5),
        alpha_line = 1,
        scatterpoint_size = 10,
        legend = True,
        edge_color = 'w',
        line_factor = 2)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09213165911286128, 'fraction_numerical': 0.016643138420387844, 'mean_word_length': 3.5735643900781517, 'pattern_counts': {'":': 0, '<': 48, '<?xml version=': 0, '>': 52, 'https://': 3, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 1, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '13', 'dir': 'jupyter-structured-clean-dedup', 'id': '315279', 'license': 'MIT', 'n_tokens_mistral': 4247, 'n_tokens_neox': 3993, 'n_words': 1193, 'path': 'docs/source/Figure_2.ipynb', 'repo_name': 'BaroudLab/Griottes', 'size': '13459'}