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")