vignettes/search_banc_with_lm.Rmd
search_banc_with_lm.RmdTake a 3-D light-microscopy image of a candidate SREN (sex-peptide- receptor rectal enteric neuron) and use it as a query against the BANC female adult connectome to recover the matching EM neuron(s). SRENs are a pair of cells at the posterior tip of the abdominal ganglion that express the sex peptide receptor and send efferent processes to the rectum; they are required for the rod-shaped fecal pellets (“RODs”) that mated females produce in response to seminal-fluid sex peptide. See Chae et al., Citations below, for the upstream/downstream circuit context.
The pipeline starts from the raw .lsm (a 40× confocal
stack of the abdominal tip with the cord at an angle in the field of
view) and lands a clean BANC ranking:
inst/python/lsm_to_nrrd.py.inst/python/lsm_pca_rotate.py). The NC82 tissue mask’s
principal axes are aligned to template (X, Y, Z); the GFP CoG is
anchored to a known abdominal-ganglion reference point so the rotated
volume lands at the right tip in template coords. No Elastix optimiser
run — the partial FOV / weak metric overlap made the rigid+affine
optimiser unreliable, and PCA + an explicit anchor is more robust.inst/python/lsm_neuropil_mask.py). Over half of the raw
GFP voxels (52% in our female sample) sit outside the cord neuropil —
bleed-through into cuticle, rectal muscle, and background. Restricting
to in-neuropil GFP triples the cMIP pixel-match count and promotes the
right BANC matches.JRC2018VNCU_HR (the NeuronBridge VNC reference grid, 573 ×
1119 × 219 voxels @ 0.461 × 0.461 × 0.7 µm) via the Saalfeld lab H5
displacement field shipped by navis-flybrains.neuronbridger::nrrd_to_mip(target_space = "VNC").flow == "efferent" cells in v888, 782
rendered) using colormip_search().nat.nblast::nblast(..., normalised = TRUE) both directions
and reporting the fwd+rev mean. Points are bridged from
JRC2018VNCF µm into BANC µm via the full elastix JRC2018F→BANC
chain
(bancr::banc_to_JRC2018F(..., method = "elastix", inverse = TRUE))
— a stack of manual affine + elastix affine + coarse B-spline + fine
B-spline. An earlier revision of this vignette used the simpler
bancr::jrcvnc2018f_to_banc_tpsreg (a 5710-landmark TPS
approximation) but that mapping collapses bilateral signal onto one side
of the cord at the abdominal tip; the full elastix chain preserves
it.nat.ggplot panels in BANC native µm, plus a side-by-side
cMIP of the SREN query and the top BANC hit, plus a bilateral-pair
overlay for the current best hypothesis (EN00B016).Why use BANC for this? BANC is the only female adult Drosophila CNS currently reconstructed at synapse resolution (v888 here, brain + VNC). Identifying the EM counterpart of the SRENs gives us their downstream connectivity — the descending input from SAG (Sex Peptide Abdominal Ganglion) neurons, the ascending afferents that signal back to the brain, and the rectal target. Colour-MIP search + NBLAST against BANC is the route to recovering these cells from a single LM volume.

Neither the source .lsm (~480 MB, private) nor the BANC
efferent MIP library (~12 MB across 782 PNGs) ship with
neuronbridger. The vignette walks through the download
steps; the only artefacts shipped are the rendered figures under
inst/images/:
banc_colormip_sren_query.png — SREN query MIP in
JRC2018VNCU_HR after PCA pre-rotation + neuropil mask.banc_colormip_sren_top_hits.png — top-6 hits from
colormip_search() (BANC EM mesh in purple, SREN LM cloud
coloured by depth, faded by intensity). Each tile shows a top-down YX
view + an XZ side projection. Plotted in BANC native µm; SREN cloud
bridged via the full elastix JRC2018F→BANC chain.banc_colormip_sren_nblast_top.png — same layout, ranked
by NBLAST fwd+rev mean (normalised) against the full ~1,033-cell
efferent library.banc_colormip_sren_en00b016_pair.png — the two BANC
cells whose manc_cell_type = EN00B016 (candidate SREN
bilateral pair): 720575941680133053 (left, BANC
cell_type = EFFabg09, ACh) +
720575941659397456 (right, BANC
cell_type = EN00B016, octopamine).banc_colormip_sren_vs_top_hit.png — side-by-side cMIP
of the SREN query and the current top BANC cMIP hit (label written
dynamically from live SeaTable annotations, since these have shifted
over successive proofreading rounds).A self-contained reproducer is at
inst/scripts/banc_colormip_sren.R. The Python helpers it
shells out to live under inst/python/:
lsm_to_nrrd.py, lsm_build_nc82_mask.py,
lsm_pca_rotate.py, lsm_neuropil_mask.py.
# R packages
if (!require("remotes")) install.packages("remotes")
remotes::install_github("natverse/neuronbridger")
remotes::install_github("flyconnectome/bancr") # BANC meshes + meta
remotes::install_github("natverse/nat.ggplot") # 2-D neuron panels
# External tooling (one-off)
# * Java 17+ (Saalfeldlab RenderTransformed JAR runtime)
# * gsutil (Google Cloud SDK), authenticated to BANC's GCS bucket
# * Python deps for the LSM pipeline:
# conda activate r-reticulate
# pip install tifffile SimpleITK scipy scikit-image pillow matplotlib
#
# The Saalfeldlab JRC VNC H5 transforms (~110 MB), via the navis
# `flybrains` Python package:
#
# reticulate::py_run_string(
# "import flybrains; flybrains.download_jrc_vnc_transforms()")
#
# This writes JRCVNC2018U_JRCVNC2018F.h5 to ~/flybrain-data/.The raw LSM here is a partial FOV of the abdominal tip with the cord at an angle — the tip is roughly in the middle of the image and the rest of the VNC would be off to the top-right. A whole-VNC Elastix recipe doesn’t work (the optimiser keeps falling into local minima that put the cord on the wrong hemisphere), so we use a deterministic PCA pre-rotation instead.
WORK <- tempfile("banc_colormip_work_"); dir.create(WORK, recursive = TRUE)
LSM <- "/path/to/20250306_C24-53_EN1-5-1060_female_vnc_40x-2.lsm"
TPL <- "/path/to/JRC2018_VNC_FEMALE_461.nrrd"
py <- reticulate::conda_python("r-reticulate")
pydir <- system.file("python", package = "neuronbridger")
# (a) Split the LSM into per-channel NRRDs. For this acquisition
# ch0 = GFP, ch1 = unused red, ch2 = NC82.
system2(py, c(file.path(pydir, "lsm_to_nrrd.py"), LSM, WORK,
"--prefix", "sren_female",
"--gfp-channel", 0L, "--nc82-channel", 2L))
# (b) NC82 tissue mask (Otsu + close + largest CC + small dilation).
system2(py, c(file.path(pydir, "lsm_build_nc82_mask.py"),
file.path(WORK, "sren_female_nc82.nrrd"),
file.path(WORK, "sren_female_nc82_mask.nrrd")))
# (c) PCA pre-rotate onto JRC2018VNCF axes, anchor GFP CoG at
# (131, 508, 92) um -- this is the GFP CoG of the upstream KDRC
# registered TIF, taken as a known-good rough placement of the SREN
# arborisation in template coords.
#
# PC1 (longest mask axis) -> template Y (anterior-posterior)
# PC2 (medium) -> template X (lateral)
# PC3 (shortest) -> template Z (dorso-ventral)
# Sign of PC1 is set so the GFP CoG projects positively onto PC1
# (i.e. lands at high template Y, the posterior tip). det(R)=+1.
dir.create(file.path(WORK, "rot"), recursive = TRUE)
system2(py, c(file.path(pydir, "lsm_pca_rotate.py"),
file.path(WORK, "sren_female_nc82.nrrd"),
file.path(WORK, "sren_female_gfp.nrrd"),
file.path(WORK, "sren_female_nc82_mask.nrrd"),
TPL,
file.path(WORK, "tip_mask.nrrd"), # ignored, see --target-um
file.path(WORK, "rot"),
"--align-source", "gfp_cog",
"--target-um", "\"131,508,92\""))The script writes rot/nc82_rot.nrrd,
rot/gfp_rot.nrrd, and rot/nc82_mask_rot.nrrd,
all sharing the same physical origin and voxdims (0.4 µm XY / 0.7 µm Z)
with identity Direction. The R wrapper in
inst/scripts/banc_colormip_sren.R then resamples these onto
the JRC2018VNCF grid via SimpleITK (one-shot linear resample), so the
output reads cleanly as a template-space NRRD.
A simple Otsu of the resampled NC82 gives a neuropil mask; we zero any GFP voxel that falls outside it. About half of the raw GFP voxels in this acquisition sit outside the neuropil — fluorescence bleed-through into cuticle, rectal muscle, and background — so this step is a big lever:
system2(py, c(file.path(pydir, "lsm_neuropil_mask.py"),
"nc82_PCAonly_JRC2018VNCF.nrrd", # rotated + resampled NC82
"gfp_PCAonly_JRC2018VNCF.nrrd", # rotated + resampled GFP
"gfp_PCAonly_neuropilmasked_JRC2018VNCF.nrrd",
"--mask-out", "neuropil_mask_JRC2018VNCF.nrrd"))
#> NC82 Otsu threshold: 40.04
#> neuropil mask voxels: 5,462,433
#> GFP after neuropil mask: nonzero 36,798 (49.9% of pre-mask)The masked GFP NRRD on the JRC2018VNCF grid is what feeds into the rest of the pipeline.
ser_in_HR <- jrcvnc2018f_to_jrcvnc2018u_hr_h5(
input = "gfp_PCAonly_neuropilmasked_JRC2018VNCF.nrrd",
output = file.path(WORK, "sren_female_in_JRC2018VNCU_HR.nrrd"))
# ~70 s on Apple silicon with 8 threads. Output: 573 x 1119 x 219
# voxels at 0.461 / 0.461 / 0.7 um.The H5 (JRCVNC2018U_JRCVNC2018F.h5 from
flybrains) stores its displacement field on the JRCVNC2018U
grid, with dfield direction JRCVNC2018F →
JRCVNC2018U (the file follows the brain
<DEST>_<SRC>.h5 naming convention used by
nat.jrcbrains). For F → U image resampling we want OUTPUT
(U) → SOURCE (F) lookup — the inverse direction — so
jrcvnc2018f_to_jrcvnc2018u_hr_h5() passes -i
to RenderTransformed. Asking for the
JRC2018VNCU_HR output grid directly works because
JRC2018VNCU and JRC2018VNCU_HR share the same
physical coordinate system; the JAR interpolates the dfield onto the
requested grid in one shot.
query_png <- nrrd_to_mip(
input = ser_in_HR,
savefolder = WORK,
method = "direct",
target_space = "VNC",
threshold = 0.80,
denoise = "median3d",
format = "png",
save = TRUE,
overwrite = TRUE)threshold = 0.80 keeps the top 20 % of non-zero voxels
by intensity in the bridged volume; the neuropil mask already removed
most of the dim halo so this floor is just a small extra noise cut.

LIB_DIR <- file.path(WORK, "library", "mips")
dir.create(LIB_DIR, showWarnings = FALSE, recursive = TRUE)
# Shared root for every BANC GCS path used in this vignette.
GCS_ROOT <- "gs://lee-lab_brain-and-nerve-cord-fly-connectome"
# (a) BANC metadata — pull from the LIVE SeaTable `banc_meta` so we
# get the current `cell_type` + `manc_cell_type` bridge (both change
# with proofreading). The frozen `banc_888_meta.feather` on GCS is a
# snapshot; SeaTable is authoritative for annotation state and carries
# both the current `root_id` and the frozen `root_888` (which is what
# the cMIP filenames and precomputed layer object lists key on).
# Requires `BANCTABLE_TOKEN` in `.Renviron` — see `bancr::banctable_login`.
meta_live <- bancr::banctable_query(paste(
"SELECT root_id, root_888, cell_type, manc_cell_type, cell_class,",
"cell_sub_class, side, hemilineage, nerve, neuromere,",
"neurotransmitter_predicted, peripheral_target_type,",
"cell_function, flow",
"FROM banc_meta"))
meta <- as.data.frame(meta_live) |>
dplyr::filter(flow == "efferent") |>
dplyr::select(root_888, side, hemilineage, nerve, neuromere,
cell_class, cell_sub_class, cell_type, manc_cell_type,
neurotransmitter_predicted, peripheral_target_type,
cell_function)
nrow(meta)
#> [1] 1033
# (b) Match against the actual BANC color-MIP set (some efferents
# don't have a MIP rendered yet).
mips_root <- file.path(GCS_ROOT,
"neuron_colormips/template_alignment_240721",
"JRC2018_VNC_UNISEX_461") |> paste0("/")
all_mips <- system2("gsutil", c("ls", mips_root), stdout = TRUE)
mip_ids <- sub(".*/", "", sub("_in_JRC2018_VNC_UNISEX_461\\.png$", "", all_mips))
to_get <- all_mips[mip_ids %in% meta$root_888]
length(to_get)
#> [1] 782
# (c) Parallel download (~2 MB total, takes <1 minute).
writeLines(to_get, file.path(WORK, "to_get.txt"))
system(sprintf("xargs -P 16 -I {} gsutil -q cp {} %s/ < %s",
LIB_DIR, file.path(WORK, "to_get.txt")))
res <- colormip_search(
query = query_png,
library = LIB_DIR,
threshold = 100L, # channel-sum brightness cutoff (signal floor)
z_tolerance = 8L, # depth-LUT-index tolerance
xy_shift = 3L, # +/- 3 px translation grid
mirror = FALSE, # SREN is unilateral
mc.cores = 8L,
verbose = TRUE)
res$root_888 <- sub(".*/(\\d+)_in_JRC2018.*", "\\1", res$path)
head(res[, c("root_888", "score", "n_match", "dx", "dy", "mirror")], 6)
#> root_888 score n_match dx dy mirror
#> 1 720575941545785276 0.2122492 201 3 -3 FALSE
#> 2 720575941661415804 0.1774023 168 3 -3 FALSE
#> 3 720575941549868093 0.1636748 155 3 -3 FALSE
#> 4 720575941513645891 0.1499472 142 3 -3 FALSE
#> 5 720575941686473228 0.1488912 141 3 -3 FALSE
#> 6 720575941494055038 0.1478353 140 3 -3 FALSENeuropil-masking the query pushed the top cMIP score from ~0.072 (without the mask) to 0.21 here — three times more pixel matches because the noise floor that previously dominated the MIP is gone.
We score the SREN dotprops against the full ~1,033-cell BANC efferent
library using nat.nblast::nblast(..., normalised = TRUE)
both directions and averaging the two — the standard NBLAST scorer.
Points are bridged from JRC2018VNCF µm into BANC µm via
bancr::banc_to_JRC2018F with
method = "elastix" (the full manual-affine + elastix-affine
+ coarse-B-spline + fine-B-spline chain shipped at
gs://lee-lab_brain-and-nerve-cord-fly-connectome/registrations/vnc_240721/),
which preserves the bilateral SREN signal at the tip. Earlier revisions
used bancr::jrcvnc2018f_to_banc_tpsreg (a TPS approximation
of the same registration) and reported forward-only NBLAST because the
TPS placed the bridged signal on one side of the cord — that shortcut is
no longer needed.
library(nat.nblast)
# (a) Intensity-weighted SREN dotprops in BANC microns. Use ALL non-zero
# neuropil-masked voxels (no median filter, no largest-CC selection) so
# the bilateral pattern in the LSM survives.
ser <- nat::read.nrrd("gfp_PCAonly_neuropilmasked_JRC2018VNCF.nrrd")
storage.mode(ser) <- "integer"
idx <- which(ser > 0, arr.ind = TRUE)
ser_full <- data.frame(
X = (idx[,1] - 1L) * 0.461122 + 0.461122/2,
Y = (idx[,2] - 1L) * 0.461122 + 0.461122/2,
Z = (idx[,3] - 1L) * 0.7 + 0.35,
I = as.integer(ser[ser > 0]))
set.seed(11L); N <- 50000L
keep <- sample.int(nrow(ser_full), min(N, nrow(ser_full)),
prob = ser_full$I / sum(ser_full$I))
pts_F <- as.matrix(ser_full[keep, c("X","Y","Z")])
# Full elastix JRC2018F -> BANC (points in um).
pts_BANC_um <- bancr::banc_to_JRC2018F(
pts_F, region = "vnc", banc.units = "um",
inverse = TRUE, method = "elastix")
ser_dp <- nat::dotprops(pts_BANC_um, k = 5L)
# (b) Full-library NBLAST (~1,033 BANC efferents). The L2 dotprops are
# pre-cached locally as an .rds: fetch each cell's L2 skeleton with
# `bancr::banc_read_l2dp(meta$root_888)` once, drop NULLs/duplicates,
# and `saveRDS()` the resulting `neuronlist` so this step is offline.
banc_dps <- readRDS("banc_l2dp_efferent_full.rds")
banc_dps <- banc_dps[!sapply(banc_dps, is.null)]
banc_dps <- banc_dps[!duplicated(names(banc_dps))]
fwd <- nat.nblast::nblast(ser_dp, nat::as.neuronlist(banc_dps),
smat = nat.nblast::smat.fcwb, normalised = TRUE)
rev <- nat.nblast::nblast(nat::as.neuronlist(banc_dps), ser_dp,
smat = nat.nblast::smat.fcwb, normalised = TRUE)
nb <- data.frame(root_888 = names(fwd),
nblast_mean = (as.numeric(fwd) + as.numeric(rev)) / 2)
nb <- merge(nb, meta, by = "root_888")
nb <- nb[order(-nb$nblast_mean), ]
head(nb[, c("root_888", "nblast_mean", "cell_type", "manc_cell_type",
"cell_sub_class", "side", "neurotransmitter_predicted")], 6)Under this pipeline, the top-6 NBLAST hits are dominated by BANC
cell_type = EFFabg40 and its close relatives
(EFFabg43, EFFabg45), all sitting inside
cell_sub_class = "abdomen_neurosecretory_cell" or
"abd_Dsx_OA". These are doublesex-expressing
sex-dimorphic abdominal neurosecretory cells, largely
octopaminergic — a much better biological match for
“sex-peptide-receptor rectal enteric neuron” than the motor-neuron
interpretation an earlier revision of this vignette proposed. The MANC
bridge (manc_cell_type) resolves these to legacy MANC names
like MNad17, MNad48, MNad04,
MNad69.

Neither the cMIP top nor the NBLAST top puts specifically
the canonical SPR pair at rank 1, but both consistently rank a bilateral
pair with manc_cell_type = "EN00B016" (exactly 2 cells, one
per side in BANC) in the top ~10 % of the library. These are the
biologically motivated SREN candidates:
720575941680133053, BANC
cell_type = EFFabg09, MANC EN00B016,
abdomen_motor_neuron, ACh (hemilineage 06A);720575941659397456, BANC
cell_type = EN00B016, MANC EN00B016,
ventral_nerve_cord_neurosecretory_cell /
abdomen_neurosecretory_cell, octopamine (hemilineage
00B).They meet the bilateral-pair expectation for a sex-peptide-receptor
population and land in the abd_Dsx_OA family the search
prefers. Their MANC bridge to a single EN00B016 label
suggests MANC groups them as one cell type across sides even though BANC
currently annotates them as distinct cell_type names.

Under PCA pre-rotation + neuropil mask + full-elastix JRC2018F→BANC
bridge + NBLAST fwd+rev mean, the top-6 BANC efferents are all in the
abd_Dsx_OA / abdomen-neurosecretory family
(cell_class = ventral_nerve_cord_neurosecretory_cell),
spread across a handful of BANC EFFabg* cell_types and
their MANC bridges:
| rank | BANC cell_type
|
MANC manc_cell_type
|
side | NT | cell_sub_class |
|---|---|---|---|---|---|
| 1 | EFFabg40 | MNad17 | right | octopamine | abdomen_neurosecretory |
| 2 | EFFabg40 | MNad17 | right | octopamine | abdomen_neurosecretory |
| 3 | EFFabg43 | MNad48 | right | octopamine | abd_Dsx_OA |
| 4 | EFFabg39 | MNad04 | left | octopamine | abd_Dsx_OA |
| 5 | EFFabg40 | MNad17 | right | dopamine | abdomen_neurosecretory |
| 6 | EFFabg40 | MNad48 | left | — | abdomen_neurosecretory |
EFFabg40 (7 cells total in v888) accounts for most of
the top hits; its MANC bridge is MNad17. The precise
identity is most likely a member of the abd_Dsx_OA
/ abdomen-neurosecretory family; the best specific candidate
for the SREN bilateral pair is manc_cell_type = EN00B016 (2
cells, one per side). Both L and R members appear in the top ~10 % of
the ranking.
Two animated illustrations. Both are the kind of view Neuroglancer can show interactively but that a static vignette benefits from as a gif — one for the registration deformation itself, one for the candidate list.
A slow cycle through the top-6 NBLAST hits + the two hypothesised
SREN candidates (manc_cell_type EN00B016), overlaid on the
elastix-aligned SREN LM cloud in BANC µm. This is the fastest way to
“look through” the ranked list and see whether a given candidate’s
dendritic tree traces the LM signal.

deformetricar
The full-elastix chain (Step 7) is the recommended bridge; it does
the heavy lifting from JRC2018F voxel intensity to BANC synapse density.
As a separate experiment we tried an alternative registration using deformetricar —
fitting a diffeomorphism between the two VNC neuropil hulls (the
JRC2018VNCF outer surface and
bancr::banc_vnc_neuropil.surf), driven by shape alone, no
image content.
library(deformetricar)
# Source: JRC2018VNCF hull (isosurface of the female VNC template)
jrc_hull <- Rvcg::vcgIsosurface(unclass(tpl >= thr), threshold = 0.5,
spacing = c(0.461, 0.461, 0.7))
# Target: BANC VNC neuropil hull (bancr::banc_vnc_neuropil.surf, in um)
banc_hull <- bancr::banc_vnc_neuropil.surf
pre <- affine_prealign(jrc_hull, banc_hull, type = "similarity")
fit <- deformetrica_register(
source = pre$apply(jrc_hull),
target = banc_hull,
kernel_width = 50, # 50 um kernel
object_type = "SurfaceMesh",
workdir = "dfca_out",
device = "auto")The flow gif shows the JRC hull morphing into the BANC hull; the top BANC NBLAST hit is drawn as the purple mesh for reference:

Bottom line: hull-only isn’t enough for this
problem. The deformetrica warp fit hull → hull very well (~20 s
on CPU), but when we apply the resulting deformation to the SREN cloud,
the candidate ranks collapse (our hypothesised pair drops to rank
~700–800, and the top-15 is dominated by hind_leg_motor_neuron
cells rather than abd_Dsx_OA neurosecretory ones). The reason is that
the outer hull constrains only the perimeter; the interior gets whatever
deformation the RKHS kernel prefers, which happens to shift the tip
signal into the wrong internal compartment. To make deformetrica
competitive here we’d need to feed it internal landmarks too — a family
of matched neuropil sub-surfaces on both sides, similarly to what the
mosquito-to-fly vignette does. The follow-up experiment
below (MANC- cognate driven refinement) tries exactly that.
Following the pattern of the malecns-to-banc vignette in
deformetricar, we tried to improve the JRC → BANC bridge in
the abdominal region specifically by using cognate cell
pairs from the male VNC connectome (MANC) that BANC’s SeaTable annotates
via its manc_match column. There are ~102
abd_Dsx_OA / abdomen_neurosecretory_cell BANC
cells with a MANC match.
Two headline pieces of work were needed to get this off the ground:
nat.jrcbrains graph carries
MANC ↔︎ JRCFIB2022M, JRCFIB2022M ↔︎ JRC2018M and
JRCVNC2018F ↔︎ JRCVNC2018U/M, but no chain from MANC
to any JRCVNC template. We built one from the cognate pairs
themselves: for each of the 102 BANC-MANC pairs, bridge the BANC
skeleton into JRC2018VNCF via the elastix chain and pair with the MANC
skeleton in its native 8-nm-voxel space, then fit a thin-plate spline
(nat::tpsreg) on the centroid landmarks.deformetrica_register_multi() per
(cell_type, side) using core-frame skeletons (Varifold), plus a weak
whole-VNC-hull landmark anchor. Kernel = target-diagonal / 12 per the
updated deformetricar
README. Both sources and targets were placed in BANC µm (MANC
bridged through JRC then through elastix) so the fit is a small
refinement on top of the elastix bridge — that’s what the malecns
pattern does for the brain.
library(deformetricar)
# ~102 BANC-MANC cognate pairs in abd_Dsx_OA / abdomen_neurosecretory
r <- bancr::banctable_query("SELECT root_id, cell_type, manc_match, side FROM banc_meta
WHERE (cell_sub_class='abd_Dsx_OA' OR
cell_sub_class='abdomen_neurosecretory_cell')
AND manc_match IS NOT NULL AND manc_match != ''")
# Bootstrap a MANC -> JRCVNC2018F TPS from cognate-pair centroids.
# (BANC side goes native -> JRC via banc_to_JRC2018F(method="elastix");
# MANC side comes from neuprintr in 8-nm voxels, scaled to um.)
# ... build src (MANC um centroid) + tgt (JRC um centroid) matrices ...
manc_to_jrc <- nat::tpsreg(sample = src_centroids, reference = tgt_centroids)
# Bring MANC skels into JRC then BANC (composed with the elastix bridge)
manc_in_banc <- nat::nlapply(manc_sk, function(x) {
pts <- nat::xform(nat::xyzmatrix(x), manc_to_jrc)
nat::xyzmatrix(x) <- bancr::banc_to_JRC2018F(
pts, region = "vnc", banc.units = "um",
inverse = TRUE, method = "elastix"); x
})
# Multi-object fit, per (cell_type, side) group, on core-frame skels
fit <- deformetrica_register_multi(
srcs = manc_cores_by_group, tgts = banc_cores_by_group,
kernel_width = diag / 12, # ~50 um
data_sigma = 0.5,
landmarks = list(source = banc_hull_pts, target = banc_hull_pts),
landmark_sigma = 5,
timepoints = 10L, max_iterations = 40L, device = "cpu")
# Compose for the SREN cloud: elastix JRC -> BANC first, then refine.
pts_banc_elx <- bancr::banc_to_JRC2018F(sren_pts_jrc, region = "vnc",
banc.units = "um",
inverse = TRUE, method = "elastix")
pts_banc_refined <- nat::xform(pts_banc_elx, fit)Under this refinement, the SREN’s hypothesised EN00B016 pair sits at rank_fwd = 67 / rank_rev = 57 for the right cell, and 90 / 84 for the left — essentially unchanged from the elastix-only baseline (58/59 for right, 88/86 for left). The refinement doesn’t materially move the candidate ranks.
But the top-10 under the refinement is much more biologically
coherent than under elastix alone — nearly every top entry is a
EFFabg40/EFFabg43/EFFabg45 cell
in the abd_Dsx_OA or
abdomen_neurosecretory_cell family, i.e. the correct broad
identity for a sex-peptide-receptor abdominal neurosecretory
population.
There is also a useful diagnostic that would be hidden by the
fwd+rev mean: several top candidates by forward-NBLAST are far
worse by reverse. E.g. the rank-1 forward hit (EN00B010)
ranks 47 by reverse — a signature that the warped SREN cloud partially
maps inside that neuron’s arbour but the neuron extends much further
than the LM signal covers (or vice-versa). Scoring both directions
separately catches this; the mean would flatten it. This is the reason
the vignette now reports fwd and rev as independent columns.

Verdict: The MANC-cognate refinement is a working malecns-to-banc-style pattern for the VNC and produces a top-hit list that’s more biologically consistent than the elastix bridge alone, but for this specific SREN identification it doesn’t rescue the candidates that the elastix bridge already puts in the top ~10 %. That’s an honest result: the elastix chain is already doing most of the alignment work for this cell family; the refinement is polish. It would matter more for cells with sparser cognate coverage or where the elastix bridge has bigger local errors — a fair use case for future runs.
Note also that a followup approach would be to keep going with the
The originally-experimented “just fit the two VNC outer hulls” case
is documented above and remains as a cautionary tale — a nice
demonstration of the READMEs “hull alone is not enough; add matched
interior landmarks” rule. To skip through it: to make deformetrica
competitive here we’d need to feed it internal landmarks too — a family
of matched neuropil sub-surfaces on both sides, similarly to what the
mosquito-to-fly vignette in deformetricar
does. For now the full image-driven elastix chain is the better bridge;
this experiment is a useful negative result and a template for a future
improvement.
Four Neuroglancer precomputed layers ship the SREN LM signal in BANC voxel space, corresponding to successive refinements of the JRC2018F→BANC bridge:
| GCS path | registration | voxel size |
|---|---|---|
light_level/kdrc/SREN_female_aligned240721_to_BANC.ng |
bancr TPS (npmasked GFP) | 800 nm |
light_level/kdrc/SREN_female_aligned240721_to_BANC_elastix.ng |
bancr TPS + per-specimen elastix | 800 nm |
light_level/kdrc/SREN_female_aligned240721_to_BANC_elastix_400nm.ng |
bancr TPS + per-specimen elastix | 400 nm |
light_level/kdrc/SREN_female_aligned240721_to_BANC_fullElastix_400nm.ng |
full elastix chain (recommended) | 400 nm |
Prefer ..._fullElastix_400nm.ng — that’s the
registration-correct version, bilateral signal preserved, matching the
NBLAST bridge used in Step 7.
LM_GS <- paste0("precomputed://", GCS_ROOT,
"/light_level/kdrc/",
"SREN_female_aligned240721_to_BANC_fullElastix_400nm.ng")
BASE <- paste0("https://spelunker.cave-explorer.org/#!middleauth+",
"https://global.daf-apis.com/nglstate/api/v1/",
"6450802162925568")
top <- head(nb, 6)
top$ngl_url <- vapply(top$root_888, function(rid)
bancr::banc_lm_scene(
lm_url = LM_GS,
layer_name = "KDRC SREN (female, full elastix, 400 nm)",
range = c(1, 60),
opacity = 0.55,
blend = "additive",
ids = as.character(rid),
url = BASE,
shorten = TRUE),
character(1))lsm_pca_rotate.py (default
--align-source nc82_mask against a Y > 350 µm posterior
tip mask) and accept a few µm of placement drift.--pc2-sign +1 works for this acquisition; if your data
lands on the wrong hemisphere, re-run with
--pc2-sign -1.bancr::jrcvnc2018f_to_banc_tpsreg (a 5710-landmark TPS
approximation of the elastix chain) to bridge JRC2018F→BANC. It
measurably collapses bilateral signal to one side at the
abdominal tip — the SREN X distribution in JRC2018VNCF was clearly
bimodal around midline (X = 132 µm) but landed at X ∈ [308, 472] µm in
BANC, entirely left of the BANC-tip midline (~X = 394 µm). We now use
the full elastix chain via
bancr::banc_to_JRC2018F(method="elastix"), which preserves
the bilateral pattern. The bancr TPS is still fine for the
brain bridge or for coarse-scale localisation, just not for the
abdominal-tip topology.lm_to_jrc2018u_elastix for the brain).flow == "efferent" BANC v888 cells (782 / 1033) had a
colour-MIP rendered at the time of writing. Re-run Step 5 as more get
rendered.mean(fwd, rev) is the right
scorer. The earlier forward-only recipe was a workaround
for the TPS-collapsed unilateral query and is no longer needed.mirror = FALSE. The SREN GFP labelling is asymmetric
(dominant left + dim right in this acquisition), so bilateral candidates
surface as two individual unilateral hits (a left cell + a right cell of
the same type family) rather than a single symmetric match. A
mirror-aware second pass would lift a contralateral counterpart if you
want the pairing explicit.root_888. The live SeaTable’s default root_id
column is the current post-proofreading ID and may differ.
Always join by root_888 when going between the frozen
assets (cMIP filenames, GCS banc_888_meta.feather) and the
live SeaTable.MNadxxF, and the current annotation uses the
EFFabg** prefix for VNC efferents with
manc_cell_type as the bridge to MANC nomenclature. Prefer
manc_cell_type as a reasonably stable pivot; treat any BANC
cell_type as a moving target.EFFabg40 / EFFabg43 / EFFabg45 cluster that dominates the
top hits is annotated as
cell_class = ventral_nerve_cord_neurosecretory_cell
(specifically in the abd_Dsx_OA — doublesex-expressing
sex-dimorphic — family) rather than as motor neurons. Earlier revisions
of this vignette described these cells as “abdominal MNs”; that framing
is superseded by the current curation.Independent of morphology, we can ask the BANC connectome directly: which efferent cells receive the strongest input, one or two hops away, from the curated sex-peptide sensory neurons (SPSNs)? If the SRENs express the sex peptide receptor and gate ROD production in response to mating, they should sit downstream of the SPSN pathway.
suppressMessages(library(bancr))
edges <- bancr::banc_edgelist(version = "v3", source = "gcs")
edges$pre <- as.character(edges$pre); edges$post <- as.character(edges$post)
spsn <- bancr::banctable_query(
"SELECT root_id, side FROM banc_meta
WHERE cell_sub_class = 'sex_peptide_sensory_neuron'")
# 6 curated SPSNs: reproductive-tract afferents, cell_class =
# reproductive_tract_sensory_neuron, peripheral_target_type =
# sex_peptide_receptor, cell_function = visceral_chemosensation.
# 1-hop: direct SPSN synapses.
downstream <- edges[edges$pre %in% spsn$root_id, ]
recip <- downstream |>
dplyr::group_by(post) |>
dplyr::summarise(spsn_in_syn = sum(count),
spsn_in_norm = sum(norm),
n_spsn_partners = dplyr::n()) |>
dplyr::arrange(dplyr::desc(spsn_in_syn))
# 2-hop: SPSN -> intrinsic interneurons -> efferents, weighted by
# path strength (norm_from_spsn * norm_from_intrinsic).
top30 <- head(recip$post, 30)
top30_wt <- setNames(head(recip$spsn_in_norm, 30), top30)
h2 <- edges[edges$pre %in% top30, ]
h2$path_wt <- h2$norm * top30_wt[h2$pre]Two things pop out:
cell_class = ... unset or
intrinsic; flow = intrinsic;
cell_sub_class = ventral_nerve_cord_bilateral_ restricted / ipsilateral_restricted).
Only one efferent abd_Dsx_OA cell sits in
the top-15 (720575941518535347, EFFabg50
MNad12 left) — and it is explicitly annotated
cell_function = "ovulation".abd_Dsx_OA /
abdomen_neurosecretory_cell cells by 2-hop reach through
those SPSN-driven intrinsics produces a very tight cluster
whose top members are almost all annotated
cell_function = "diuresis, stress_tolerance, pain_threshold".
Diuresis matches Chae et al.’s rectal-ROD phenotype
(RODs are the fluid-content-dependent fecal pellet morphology).Top 10 connectivity + function-supported SREN
candidates (BANC v888, by 2-hop SPSN reach, all
cell_function = diuresis,stress_tolerance, pain_threshold):
| rank | root_id | BANC cell_type | MANC | side | hemilineage | NT | 2-hop weight |
|---|---|---|---|---|---|---|---|
| 1 | 720575941575458056 | EFFabg49 | MNad13 | left | 00B | serotonin | 0.0120 |
| 2 | 720575941582278265 | EFFabg48 | MNad03 | left | 06A | acetylcholine | 0.0084 |
| 3 | 720575941652774421 | EFFabg48 | MNad03 | left | 06A | octopamine | 0.0084 |
| 4 | 720575941550051873 | EFFabg47 | MNad52 | left | 13B | — | 0.0083 |
| 5 | 720575941577258088 | EFFabg44 | MNad13 | left | — | octopamine | 0.0072 |
| 6 | 720575941594396267 | EFFabg47 | MNad03 | right | 13B | acetylcholine | 0.0071 |
| 7 | 720575941482782659 | EFFabg44 | MNad13 | right | — | octopamine | 0.0071 |
| 8 | 720575941586627462 | EFFabg47 | MNad52 | right | 13B | gaba | 0.0070 |
| 9 | 720575941563946038 | EFFabg44 | MNad13 | left | — | acetylcholine | 0.0070 |
| 10 | 720575941650150129 | EFFabg49 | MNad13 | right | 00B | gaba | 0.0069 |
Bilateral pairs match the SREN morphology
description. The EFFabg44, EFFabg47,
EFFabg48, EFFabg49 cell types each contain
matched left+right cells in this diuresis-annotated cluster.
This retracts the earlier EN00B016-pair
hypothesis. The morphology + hull-registration analyses in the
sections above put the hypothesised EN00B016 pair
(720575941680133053 L + 720575941659397456 R)
at rank ~59–90 by NBLAST fwd+rev — a plausible morphology score. But by
connectivity:
9397456) sits at
abd-rank 18 by 2-hop SPSN reach (2-hop weight 0.006),
with no functional annotation.0133053) has no
2-hop path from the SPSN cluster at all.Neither cell is in the diuresis-annotated cluster. That’s not a
tie-breaker in favour of EN00B016; it’s a signal against
it, at least under the “SRENs are on the SPSN pathway and drive rectal
diuresis” reading of Chae et al.
Recommendation for follow-up: focus on the
EFFabg44 / EFFabg47 / EFFabg48 / EFFabg49
diuresis-annotated bilateral pairs as the current best SREN candidates.
EFFabg49 (MNad13) is the top-scoring pair on 2-hop SPSN
reach and worth inspecting first (left 720575941575458056,
right 720575941650150129).
Caveat: the “diuresis, stress_tolerance,
pain_threshold” cell_function label appears to be a
family-level tag on the abd_Dsx_OA cluster; several cells outside the
top-10 above (further down the 2-hop ranking) also carry it.
Confirm-by-inspection is still needed — but the ranking + the functional
label + the bilateral EFFabg47/48/49 pattern together are much stronger
evidence than morphology alone.
flybrains Python package (Saalfeldlab
JRC VNC H5 distribution): Schlegel et al.,
navis-org/navis-flybrains.