Skip to content

Who talks to LC16?

The picture Codex shows under Network Graphs: one node per cell type, LC16 in the middle, and an edge \(A \to B\) when type \(A\) has synapses onto type \(B\), with its width by synapse count. Here the edges are the strong connections of lc_output_strength, for the right-hemisphere LC16 cells: each edge counts the synapses that LC16 exchanges with the strong partners of one type. One color leaves LC16 and another enters it.

census = load_census()
sides = load_visual_types().select("root_id", "side")
proofread_ids = load_proofread_ids()
cells = cells_of_types(census, sides, [CENTER_TYPE])
_scored = cells[CENTER_TYPE]
strong_out = strong_set(
    labeled_partners(
        with_pre_type(load_connections_of(_scored), cells),
        cells,
        census,
        proofread_ids,
    )
)
strong_in = strong_set(
    labeled_partners(
        with_pre_type(
            reversed_connections(load_connections_of(_scored, end="post")),
            cells,
        ),
        cells,
        census,
        proofread_ids,
    )
)
out_mass = type_mass(strong_out)
in_mass = type_mass(strong_in)

Keep the heaviest partners

The partner types are ranked by the synapses that LC16 exchanges with them in both directions, and the top TOP_PARTNERS are kept. Partner-to-partner edges are not drawn. build_graph returns a directed graph with the synapses as the weight of each edge.

def build_graph(
    out_mass: pl.DataFrame,
    in_mass: pl.DataFrame,
    center_type: str,
    *,
    top_partners: int = TOP_PARTNERS,
) -> nx.DiGraph:
    """A directed graph of center_type and its top_partners heaviest
    partner types, with an edge for each direction that has synapses.

    out_mass and in_mass are DataFrame[type, partner_type, synapses], as
    type_mass returns them for the outputs and the inputs of the center
    type.
    """
    both = pl.concat([out_mass, in_mass]).filter(pl.col("type") == center_type)
    keep = set(
        both.group_by("partner_type")
        .agg(pl.col("synapses").sum())
        .sort("synapses", descending=True)
        .head(top_partners)["partner_type"]
    )
    graph = nx.DiGraph()
    graph.add_node(center_type)
    for row in out_mass.filter(pl.col("type") == center_type).iter_rows(
        named=True
    ):
        if row["partner_type"] in keep:
            graph.add_edge(
                center_type, row["partner_type"], weight=row["synapses"]
            )
    for row in in_mass.filter(pl.col("type") == center_type).iter_rows(
        named=True
    ):
        if row["partner_type"] in keep:
            graph.add_edge(
                row["partner_type"], center_type, weight=row["synapses"]
            )
    return graph
graph = build_graph(out_mass, in_mass, CENTER_TYPE)
mo.md(
    f"{graph.number_of_nodes()} types: "
    f"{graph.in_degree(CENTER_TYPE)} input edges, "
    f"{graph.out_degree(CENTER_TYPE)} output edges."
)

25 types: 13 input edges, 14 output edges.

paint draws the graph as an interactive network: the center type in its color, the partner types in gray and larger the more edges they have, and the edges in the color of their direction.

def paint(graph: nx.DiGraph, center_type: str) -> Network:
    """A pyvis Network of the graph: the center type in TYPE_COLORS,
    each partner sized by its degree, and edges colored by whether they
    enter or leave the center type."""
    net = Network(
        height="640px",
        width="100%",
        directed=True,
        cdn_resources="in_line",
    )
    net.barnes_hut(gravity=-12000, spring_length=140, central_gravity=0.2)
    for node in graph.nodes:
        if node == center_type:
            net.add_node(
                node, label=node, color=TYPE_COLORS[center_type], size=28
            )
        else:
            net.add_node(
                node,
                label=node,
                color=CONTEXT_COLOR,
                # Larger for a partner with more edges, up to a cap, so
                # the hubs stand out without hiding the rest.
                size=12 + min(graph.degree(node), 20),
            )
    for src, dst, data in graph.edges(data=True):
        color = (
            DIRECTION_COLORS["out"]
            if src == center_type
            else DIRECTION_COLORS["in"]
        )
        net.add_edge(
            src,
            dst,
            value=data["weight"],
            title=f"{data['weight']} synapses",
            color=color,
            arrows="to",
        )
    return net
_net = paint(graph, CENTER_TYPE)
_net.write_html(
    str(figure_path("lc16_network.html")),
    open_browser=False,
    notebook=False,
)
mo.iframe(_net.generate_html(), height="680px")