logo Netzschleuder network catalog, repository, and centrifuge

History of fly_manc

The changes to the source code of this entry in the repository, newest first, each with the differences it made to the entry's files. They are also listed on GitLab.

[entries] Add the date when each entry was added, and remove icon_hash by Tiago de Paula Peixoto beff7c9
The dates are those of the commits that first added the entries' files
(2020-08-11 for the entries of the initial commit); renamed entries keep
the date of their first name.

Changed: none
diff --git a/entries/fly_manc/__init__.py b/entries/fly_manc/__init__.py
index 3fd1055..9f7f158 100644
--- a/entries/fly_manc/__init__.py
+++ b/entries/fly_manc/__init__.py
@@ -78,7 +78,7 @@ url = ['https://www.janelia.org/manc-connectome',
        'https://console.cloud.google.com/storage/browser/manc-seg-v1p2']
 citation = [('S. Takemura et al., "A connectome of the male Drosophila ventral nerve cord." eLife 13, RP97769 (2024)', 'https://doi.org/10.7554/eLife.97769'),
             ('E. C. Marin et al., "Systematic annotation of a complete adult male Drosophila nerve cord connectome reveals principles of functional organisation." eLife 13, RP97766 (2024)', 'https://doi.org/10.7554/eLife.97766')]
-icon_hash = None
+date_added = '2026-10-27'
 upstream_license = ("CC BY 4.0", "https://creativecommons.org/licenses/by/4.0/")
 upstream_prefix = 'https://storage.googleapis.com/flyem-manc-exports/v1.0'
 _adj = "manc-traced-adjacencies-v1.0"
[entries] Prefix the properties in the descriptions with (Node), (Edge) or (Graph) by Tiago de Paula Peixoto bc3dbbd
The lists of properties at the end of the descriptions marked edge
properties with "Edge" but left node properties unmarked, or grouped
them in "Nodes: `a`, ...; `b`, ..." items, so the kind of a property
was often unclear. Every item now starts with (Node), (Edge) or (Graph),
followed by the property names, a colon and the explanation, one item
per property or group of properties; qualifications (kinds of nodes,
networks) follow the names in parentheses. Properties that were only
named got a short explanation, and a few items that named the wrong
kind of property were corrected.
diff --git a/entries/fly_manc/__init__.py b/entries/fly_manc/__init__.py
index 69d030a..3fd1055 100644
--- a/entries/fly_manc/__init__.py
+++ b/entries/fly_manc/__init__.py
@@ -44,35 +44,35 @@ Their nodes are the 27,046 segments that have annotations in the neuroglancer se
 
 The networks of v1.0 have the following properties:
 
-- `body_id`: the neuron's identifier in the data set (neuPrint body ID).
-- `type`, `systematic_type`: the cell type of the neuron (e.g. `DNp01`), and its systematic name (e.g. `DNlt002`); empty if not assigned.
-- `instance`: the neuron's instance name, typically the cell type followed by the side or segment (e.g. `IN13B025_T2_L`).
-- `class`, `subclass`: coarser classifications of the neuron (e.g. `descending neuron`, `intrinsic neuron`); empty if not assigned.
-- `status_label`: the proofreading status of the neuron (e.g. `Roughly traced`).
-- `soma_side`, `root_side`: the side of the neuron's soma, or for neurons without soma in the volume, of the place where they enter it (`LHS`, `RHS`, `Midline`, ...); empty if not assigned.
-- `soma_neuromere`: the neuromere (segment) of the neuron's soma (e.g. `T1`, `A2`); empty if not assigned.
-- `hemilineage`: the developmental hemilineage of the neuron; empty if not assigned.
-- `birthtime`: the developmental birth time of the neuron (`primary`, `early secondary`, `secondary`); empty if not assigned.
-- `entry_nerve`, `exit_nerve`: the nerve through which the neuron enters or exits the nerve cord; empty or `None` if none.
-- `origin`, `target`: the region where the neuron's inputs come from and where its outputs go (e.g. `brain`, `LegNpT1_R`); empty if not assigned.
-- `long_tract`: the longitudinal tract of the neuron; empty if not assigned.
-- `serial_motif`: the type of the neuron's serial (segmentally repeated) homology; empty if not assigned.
-- `modality`, `receptor_type`: the sensory modality and receptor type of sensory neurons; empty if not assigned.
-- `transmission`: annotations of electrical or neurosecretory transmission; empty if none.
-- `group`, `serial`: the identifiers of the neuron's group of left-right homologs and of its set of serial homologs (-1 if none).
-- `predicted_nt`, `predicted_nt_prob`: the neurotransmitter predicted from the neuron's synapses, and its probability (empty and NaN if not available).
-- `pre`, `post`: the numbers of presynaptic sites and of postsynaptic densities of the neuron.
-- `weight` (edges): the number of synapses from $i$ to $j$ (in the given ROI, in `traced_roi`).
-- `roi` (edges, `traced_roi` only): the primary ROI where the synapses are located.
+- (Node) `body_id`: the neuron's identifier in the data set (neuPrint body ID).
+- (Node) `type`, `systematic_type`: the cell type of the neuron (e.g. `DNp01`), and its systematic name (e.g. `DNlt002`); empty if not assigned.
+- (Node) `instance`: the neuron's instance name, typically the cell type followed by the side or segment (e.g. `IN13B025_T2_L`).
+- (Node) `class`, `subclass`: coarser classifications of the neuron (e.g. `descending neuron`, `intrinsic neuron`); empty if not assigned.
+- (Node) `status_label`: the proofreading status of the neuron (e.g. `Roughly traced`).
+- (Node) `soma_side`, `root_side`: the side of the neuron's soma, or for neurons without soma in the volume, of the place where they enter it (`LHS`, `RHS`, `Midline`, ...); empty if not assigned.
+- (Node) `soma_neuromere`: the neuromere (segment) of the neuron's soma (e.g. `T1`, `A2`); empty if not assigned.
+- (Node) `hemilineage`: the developmental hemilineage of the neuron; empty if not assigned.
+- (Node) `birthtime`: the developmental birth time of the neuron (`primary`, `early secondary`, `secondary`); empty if not assigned.
+- (Node) `entry_nerve`, `exit_nerve`: the nerve through which the neuron enters or exits the nerve cord; empty or `None` if none.
+- (Node) `origin`, `target`: the region where the neuron's inputs come from and where its outputs go (e.g. `brain`, `LegNpT1_R`); empty if not assigned.
+- (Node) `long_tract`: the longitudinal tract of the neuron; empty if not assigned.
+- (Node) `serial_motif`: the type of the neuron's serial (segmentally repeated) homology; empty if not assigned.
+- (Node) `modality`, `receptor_type`: the sensory modality and receptor type of sensory neurons; empty if not assigned.
+- (Node) `transmission`: annotations of electrical or neurosecretory transmission; empty if none.
+- (Node) `group`, `serial`: the identifiers of the neuron's group of left-right homologs and of its set of serial homologs (-1 if none).
+- (Node) `predicted_nt`, `predicted_nt_prob`: the neurotransmitter predicted from the neuron's synapses, and its probability (empty and NaN if not available).
+- (Node) `pre`, `post`: the numbers of presynaptic sites and of postsynaptic densities of the neuron.
+- (Edge) `weight` (edges): the number of synapses from $i$ to $j$ (in the given ROI, in `traced_roi`).
+- (Edge) `roi` (edges, `traced_roi` only): the primary ROI where the synapses are located.
 
 The networks of v1.2 have the following properties:
 
-- `body_id`: the neuron's identifier (segment ID of v1.2).
-- `type`: the cell type of the neuron; empty if not assigned.
-- `class`, `subclass`, `hemilineage`, `birthtime`, `soma_side`, `soma_neuromere`, `entry_nerve`, `exit_nerve`, `origin`, `target`, `long_tract`, `serial_motif`, `modality`: as above, from the tags of the segment properties; empty if not assigned.
-- `pre`, `post`: the numbers of presynaptic sites and of postsynaptic densities of the neuron, as given by the segment properties (all those of the synapse table, including the ones with any partner, without the threshold of `v1.2`).
-- `weight` (edges): the number of synaptic connections from $i$ to $j$ (of the given confidence).
-- `mean_conf_pre`, `mean_conf_post` (edges): the mean detection confidence of their presynaptic T-bars and of their postsynaptic densities."""
+- (Node) `body_id`: the neuron's identifier (segment ID of v1.2).
+- (Node) `type`: the cell type of the neuron; empty if not assigned.
+- (Node) `class`, `subclass`, `hemilineage`, `birthtime`, `soma_side`, `soma_neuromere`, `entry_nerve`, `exit_nerve`, `origin`, `target`, `long_tract`, `serial_motif`, `modality`: as above, from the tags of the segment properties; empty if not assigned.
+- (Node) `pre`, `post`: the numbers of presynaptic sites and of postsynaptic densities of the neuron, as given by the segment properties (all those of the synapse table, including the ones with any partner, without the threshold of `v1.2`).
+- (Edge) `weight` (edges): the number of synaptic connections from $i$ to $j$ (of the given confidence).
+- (Edge) `mean_conf_pre`, `mean_conf_post` (edges): the mean detection confidence of their presynaptic T-bars and of their postsynaptic densities."""
 tags = ['Biological', 'Connectome', 'Weighted', 'Multigraph', 'Metadata']
 url = ['https://www.janelia.org/manc-connectome',
        'https://console.cloud.google.com/storage/browser/manc-seg-v1p2']
[entries] New entry: fly_manc by Tiago de Paula Peixoto db7fafd
diff --git a/entries/fly_manc/__init__.py b/entries/fly_manc/__init__.py
new file mode 100644
index 0000000..69d030a
--- /dev/null
+++ b/entries/fly_manc/__init__.py
@@ -0,0 +1,236 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2020-2026 Tiago de Paula Peixoto <tiago@skewed.de>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License along
+# with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from .. import *
+import numpy as np
+import pandas as pd
+import json
+import pyarrow
+import pyarrow.ipc
+import pyarrow.compute as pc
+
+title = "Male fly adult nerve cord (2024)"
+description = r"""The connectome of the ventral nerve cord (VNC, the fly's analog of the spinal cord) of an adult male fruit fly *Drosophila melanogaster*, the "Male Adult Nerve Cord" (MANC) data set, reconstructed by the FlyEM project at the Janelia Research Campus and collaborators from focused ion beam scanning electron microscopy (FIB-SEM) images, with automated segmentation and synapse detection followed by extensive manual proofreading, and annotated systematically by the Cambridge Drosophila Connectomics Group. Nodes are neurons, and a directed edge $(i, j)$ indicates chemical synapses from neuron $i$ (presynaptic) to neuron $j$ (postsynaptic). This is release v1.0 (June 2023) of the data set, as exported by the authors from neuPrint: the 23,188 neurons with status "Traced" (the proofread neurons) and the connections between them.
+
+There are three networks of v1.0:
+
+- `traced_min5`: only the pairs of neurons with at least five synapses are considered connected, since weak connections are unreliable, and for consistency with the `flywire`, `fly_banc` and `fly_male_cns` entries. These are 1,360,021 of the 5,243,574 connected pairs, carrying 24.0 million of their 30.7 million synapses.
+- `traced_all`: all connections, without threshold on the number of synapses.
+- `traced_roi`: all connections, split according to the primary region of interest (ROI, i.e. neuropil or nerve) where the synapses reside, so that the same pair of neurons may be connected by several parallel edges, one per ROI; synapses outside the primary ROIs have the ROI `NotPrimary`.
+
+All networks of v1.0 have the same nodes, so that neurons without connections (with at least five synapses, in `traced_min5`) are isolated nodes. Self-loops are synapses of a neuron onto itself (autapses).
+
+The entry also contains two networks of the later release v1.2 of the data set, which is not available as a connection table, but as a table of the individual synaptic connections ("synapse partners": 87 million pairs of a presynaptic T-bar and a postsynaptic density) with the confidence of the automated detection of each side (a value between 0 and 1). They are aggregated here into one weighted edge per pair of neurons:
+
+- `v1.2_minconf0`: all the synaptic connections of the table. Despite its name ("minconf-0.0"), the table only contains presynaptic T-bars with a confidence of at least 0.75 and postsynaptic densities with a confidence of at least 0.2; this corresponds to the "high recall" weights (`weightHR`) of neuPrint.
+- `v1.2`: only the synaptic connections whose postsynaptic density has a confidence of at least 0.4, the threshold that neuPrint uses for its default connection weights (`postHighAccuracyThreshold` in the metadata of the MANC database). The connection tables of v1.0 above have these default weights: for instance, the 136 synaptic connections of the table from neuron 10000 to neuron 14882 are 122 with this threshold, the weight of the pair in v1.0. (neuPrint also has "high precision" weights, `weightHP`, with confidences of at least 0.7 for both sides, which are not included.)
+
+Their nodes are the 27,046 segments that have annotations in the neuroglancer segment properties of v1.2 published with the table (neurons, including sensory and motor neurons, and a few hundred glia; v1.2 has no export of the neuPrint neuron table with statuses as v1.0 has), and their edges are the synapses between them; the synaptic connections with the other segments (51 million of the 87 million) are left out. The v1.2 annotations are less detailed than those of v1.0, and are encoded as tags in neuroglancer (with underscores instead of spaces, e.g. `descending_neuron`). The later annotation releases v1.2.1 and v1.2.3 (in neuPrint) are not included.
+
+The networks of v1.0 have the following properties:
+
+- `body_id`: the neuron's identifier in the data set (neuPrint body ID).
+- `type`, `systematic_type`: the cell type of the neuron (e.g. `DNp01`), and its systematic name (e.g. `DNlt002`); empty if not assigned.
+- `instance`: the neuron's instance name, typically the cell type followed by the side or segment (e.g. `IN13B025_T2_L`).
+- `class`, `subclass`: coarser classifications of the neuron (e.g. `descending neuron`, `intrinsic neuron`); empty if not assigned.
+- `status_label`: the proofreading status of the neuron (e.g. `Roughly traced`).
+- `soma_side`, `root_side`: the side of the neuron's soma, or for neurons without soma in the volume, of the place where they enter it (`LHS`, `RHS`, `Midline`, ...); empty if not assigned.
+- `soma_neuromere`: the neuromere (segment) of the neuron's soma (e.g. `T1`, `A2`); empty if not assigned.
+- `hemilineage`: the developmental hemilineage of the neuron; empty if not assigned.
+- `birthtime`: the developmental birth time of the neuron (`primary`, `early secondary`, `secondary`); empty if not assigned.
+- `entry_nerve`, `exit_nerve`: the nerve through which the neuron enters or exits the nerve cord; empty or `None` if none.
+- `origin`, `target`: the region where the neuron's inputs come from and where its outputs go (e.g. `brain`, `LegNpT1_R`); empty if not assigned.
+- `long_tract`: the longitudinal tract of the neuron; empty if not assigned.
+- `serial_motif`: the type of the neuron's serial (segmentally repeated) homology; empty if not assigned.
+- `modality`, `receptor_type`: the sensory modality and receptor type of sensory neurons; empty if not assigned.
+- `transmission`: annotations of electrical or neurosecretory transmission; empty if none.
+- `group`, `serial`: the identifiers of the neuron's group of left-right homologs and of its set of serial homologs (-1 if none).
+- `predicted_nt`, `predicted_nt_prob`: the neurotransmitter predicted from the neuron's synapses, and its probability (empty and NaN if not available).
+- `pre`, `post`: the numbers of presynaptic sites and of postsynaptic densities of the neuron.
+- `weight` (edges): the number of synapses from $i$ to $j$ (in the given ROI, in `traced_roi`).
+- `roi` (edges, `traced_roi` only): the primary ROI where the synapses are located.
+
+The networks of v1.2 have the following properties:
+
+- `body_id`: the neuron's identifier (segment ID of v1.2).
+- `type`: the cell type of the neuron; empty if not assigned.
+- `class`, `subclass`, `hemilineage`, `birthtime`, `soma_side`, `soma_neuromere`, `entry_nerve`, `exit_nerve`, `origin`, `target`, `long_tract`, `serial_motif`, `modality`: as above, from the tags of the segment properties; empty if not assigned.
+- `pre`, `post`: the numbers of presynaptic sites and of postsynaptic densities of the neuron, as given by the segment properties (all those of the synapse table, including the ones with any partner, without the threshold of `v1.2`).
+- `weight` (edges): the number of synaptic connections from $i$ to $j$ (of the given confidence).
+- `mean_conf_pre`, `mean_conf_post` (edges): the mean detection confidence of their presynaptic T-bars and of their postsynaptic densities."""
+tags = ['Biological', 'Connectome', 'Weighted', 'Multigraph', 'Metadata']
+url = ['https://www.janelia.org/manc-connectome',
+       'https://console.cloud.google.com/storage/browser/manc-seg-v1p2']
+citation = [('S. Takemura et al., "A connectome of the male Drosophila ventral nerve cord." eLife 13, RP97769 (2024)', 'https://doi.org/10.7554/eLife.97769'),
+            ('E. C. Marin et al., "Systematic annotation of a complete adult male Drosophila nerve cord connectome reveals principles of functional organisation." eLife 13, RP97766 (2024)', 'https://doi.org/10.7554/eLife.97766')]
+icon_hash = None
+upstream_license = ("CC BY 4.0", "https://creativecommons.org/licenses/by/4.0/")
+upstream_prefix = 'https://storage.googleapis.com/flyem-manc-exports/v1.0'
+_adj = "manc-traced-adjacencies-v1.0"
+_props = "manc-v1.0-neuron-properties.feather"
+files = [((f"{_adj}/traced-neurons.csv", f"{_adj}/traced-connections.csv", _props),
+          "traced_min5", None),
+         ((f"{_adj}/traced-neurons.csv", f"{_adj}/traced-connections.csv", _props),
+          "traced_all", None),
+         ((f"{_adj}/traced-neurons.csv", f"{_adj}/traced-connections-per-roi.csv", _props),
+          "traced_roi", None)]
+
+# minimum number of synapses between two neurons for them to be connected
+min_synapses = {"traced_min5": 5, "traced_all": 0, "traced_roi": 0}
+
+# Release v1.2: the table of synaptic connections (with their detection
+# confidences, without threshold) and the neuroglancer segment properties
+# of the segmentation (annotations of the neurons)
+v12_prefix = 'https://storage.googleapis.com/manc-seg-v1p2'
+_v12_syn = "manc-v1.2-synapse-partners-minconf-0.0.feather"
+_v12_props = "manc-seg-v1.2/segment_properties/info"
+v12_files = [((_v12_props, _v12_syn), "v1.2_minconf0", None),
+             ((_v12_props, _v12_syn), "v1.2", None)]
+v10_files = files
+files = v10_files + v12_files
+
+# minimum presynaptic and postsynaptic confidence (the table has none below
+# 0.75 and 0.2); 0.4 is neuPrint's threshold of the default weights
+v12_minconf = {"v1.2_minconf0": (0, 0), "v1.2": (0, 0.4)}
+
+def fetch_upstream(force=False):
+    name = __name__.split(".")[-1]
+    fetch_upstream_files(name, upstream_prefix, v10_files, force)
+    fetch_upstream_files(name, v12_prefix, v12_files, force)
+
+# the prefixes of the tags of the v1.2 segment properties, as properties
+v12_tags = ["class", "subclass", "hemilineage", "birthtime", "soma_side",
+            "soma_neuromere", "entry_nerve", "exit_nerve", "origin", "target",
+            "long_tract", "serial_motif", "modality"]
+
+def parse_v12(name, alt):
+    """A network of release v1.2, from the synapse table, aggregated per
+    pair of neurons."""
+    with open_upstream_file(name, _v12_props, "rb") as f:
+        info = json.load(f)["inline"]
+    ids = np.array(info["ids"], dtype="uint64")
+    assert len(np.unique(ids)) == len(ids)
+    g = Graph()
+    g.add_vertex(len(ids))
+    g.vp.body_id = g.new_vp("int64_t", vals=ids.astype("int64"))
+    props = {x["id"]: x for x in info["properties"]}
+    assert set(props) == {"type", "PreSyn", "PostSyn", "tags"}, set(props)
+    g.vp.type = g.new_vp("string", vals=props["type"]["values"])
+    tags = props["tags"]["tags"]
+    vals = {k: [""] * len(ids) for k in v12_tags}
+    for i, ts in enumerate(props["tags"]["values"]):
+        for t in ts:
+            k, _, v = tags[t].partition(":")
+            if k in vals:
+                assert vals[k][i] == "", (ids[i], k)
+                vals[k][i] = v
+            else:
+                assert k in ("_has_group", "_has_type"), tags[t]
+    for k in v12_tags:
+        g.vp[k] = g.new_vp("string", vals=vals[k])
+    g.vp.pre = g.new_vp("int", vals=props["PreSyn"]["values"])
+    g.vp.post = g.new_vp("int", vals=props["PostSyn"]["values"])
+
+    # The synapse table (87 million rows) is read in batches, keeping only
+    # the synapses between the nodes with the given confidences; each
+    # synapse is given by the index of its pair of nodes
+    tpre, tpost = v12_minconf[alt]
+    vids = pyarrow.array(ids)
+    N = len(ids)
+    keys, cpre, cpost = [], [], []
+    with open_upstream_file(name, _v12_syn, "rb", seekable=True) as f:
+        reader = pyarrow.ipc.open_file(f)
+        for i in range(reader.num_record_batches):
+            b = reader.get_batch(i).select(["body_pre", "body_post",
+                                            "conf_pre", "conf_post"])
+            si = pc.index_in(b["body_pre"], value_set=vids)
+            ti = pc.index_in(b["body_post"], value_set=vids)
+            mask = pc.and_(pc.and_(pc.is_valid(si), pc.is_valid(ti)),
+                           pc.and_(pc.greater_equal(b["conf_pre"], tpre),
+                                   pc.greater_equal(b["conf_post"], tpost)))
+            si = si.filter(mask).to_numpy().astype("int64")
+            ti = ti.filter(mask).to_numpy().astype("int64")
+            keys.append(si * N + ti)
+            cpre.append(b["conf_pre"].filter(mask).to_numpy())
+            cpost.append(b["conf_post"].filter(mask).to_numpy())
+    keys = np.concatenate(keys)
+    idx = np.argsort(keys, kind="stable")
+    keys = keys[idx]
+    cpre = np.concatenate(cpre)[idx].astype("float64")
+    cpost = np.concatenate(cpost)[idx].astype("float64")
+    del idx
+    ukeys, start, count = np.unique(keys, return_index=True, return_counts=True)
+    del keys
+    g.add_edge_list(np.column_stack((ukeys // N, ukeys % N)))
+    g.ep.weight = g.new_ep("int", vals=count)
+    g.ep.mean_conf_pre = g.new_ep("double", vals=np.add.reduceat(cpre, start) / count)
+    g.ep.mean_conf_post = g.new_ep("double", vals=np.add.reduceat(cpost, start) / count)
+    return g
+
+vprops = ["type", ("systematicType", "systematic_type"), "instance", "class",
+          "subclass", ("statusLabel", "status_label"), ("somaSide", "soma_side"),
+          ("rootSide", "root_side"), ("somaNeuromere", "soma_neuromere"),
+          "hemilineage", "birthtime", ("entryNerve", "entry_nerve"),
+          ("exitNerve", "exit_nerve"), "origin", "target",
+          ("longTract", "long_tract"), ("serialMotif", "serial_motif"),
+          "modality", ("receptorType", "receptor_type"), "transmission",
+          ("predictedNt", "predicted_nt")]
+
+@cache_network()
+@coerce_props()
+@annotate()
+def parse(alts=None):
+    name = __name__.split(".")[-1]
+    for fnames, alt, fmt in files:
+        if alts is not None and alt not in alts:
+            continue
+        if alt.startswith("v1.2"):
+            yield alt, parse_v12(name, alt)
+            continue
+        fneurons, fconn, fprops = fnames
+        with open_upstream_file(name, fneurons, "rb") as f:
+            neurons = pd.read_csv(f)
+        with open_upstream_file(name, fprops, "rb", seekable=True) as f:
+            props = pd.read_feather(f)
+        props = props.set_index("bodyId").reindex(neurons["bodyId"].to_numpy())
+
+        g = Graph()
+        g.add_vertex(len(neurons))
+        g.vp.body_id = g.new_vp("int64_t", vals=neurons["bodyId"].to_numpy())
+        for p in vprops:
+            col, pname = p if isinstance(p, tuple) else (p, p)
+            vals = props[col].fillna("").astype(str)
+            g.vp[pname] = g.new_vp("string", vals=vals.tolist())
+        for c in ["group", "serial"]:
+            g.vp[c] = g.new_vp("int64_t", vals=props[c].fillna(-1).astype("int64").to_numpy())
+        g.vp.predicted_nt_prob = \
+            g.new_vp("double", vals=props["predictedNtProb"].astype(float).to_numpy())
+        for c in ["pre", "post"]:
+            g.vp[c] = g.new_vp("int", vals=props[c].fillna(0).astype(int).to_numpy())
+
+        with open_upstream_file(name, fconn, "rb") as f:
+            w = pd.read_csv(f)
+        w = w[w["weight"] >= min_synapses[alt]]
+        idx = pd.Series(np.arange(len(neurons)), index=neurons["bodyId"].to_numpy())
+        s = idx.loc[w["bodyId_pre"].to_numpy()].to_numpy()
+        t = idx.loc[w["bodyId_post"].to_numpy()].to_numpy()
+        g.add_edge_list(np.column_stack((s, t)))
+        g.ep.weight = g.new_ep("int", vals=w["weight"].to_numpy())
+        if "roi" in w.columns:
+            g.ep.roi = g.new_ep("string", vals=w["roi"].astype(str).tolist())
+        yield alt, g