neu-morpho

From neu-morpho. Meshes and skeletons from a segmentation volume.

python -m neu_morpho is equivalent.

neu-morpho: index -> allowlist -> meshes, locally or on SLURM.

The command-line entry point for this package.

::

neu-morpho run ...              # the pipeline
neu-morpho progress <work-dir>  # live per-stage counts
neu-morpho run-report <work-dir># self-contained HTML summary
neu-morpho measure ...          # morphometry over PUBLISHED output

Run run ON A WORKSTATION, in a session that outlives your terminal: it starts a dask cluster whose workers are SLURM jobs (dask submits the sbatch itself via scale()), then runs the requested stages against it. Every stage is idempotent, so re-running the same command resumes.

::

# 0. look at the pyramid first, and pick your scales from real metadata
neu-morpho run --src /path/to/seg --describe

# 1. small ROI, locally, to see it work end to end
neu-morpho run --src ... \
    --dst /path/to/morpho/segmentation --work-dir /path/to/morpho \
    --config dask-local --workers 4 \
    --roi 0,0,0,512,2048,2048 --roi-scale 2 --stages index,mesh

# 2. the same ROI on SLURM, surviving logout, publishing to s3
nohup env PYTHONUNBUFFERED=1 neu-morpho run --src ... \
    --dst s3://bucket/sample3/segmentation --work-dir /path/to/morpho \
    --config dask-slurm-example --config ~/my-site.yaml --workers 48 \
    --roi 0,0,0,512,2048,2048 --roi-scale 2 --stages index,mesh > run.log 2>&1 &
squeue -u "$USER"        # watch your jobs (read-only; don't poll in a tight loop)

python -m neu_morpho is equivalent to neu-morpho. PYTHONUNBUFFERED=1 is the console-script equivalent of python -u; without it the log lags the run. Keep that log OFF a shared network filesystem such as CephFS: unbuffered, every line is its own write, and once another node reads the file (tail -f) each one becomes a synchronous round trip. A local disk or your home directory is fine.

–config takes a bundled template name or a path, and is repeatable, deep-merged left to right — so a site config carries only the keys that differ. The templates ship with blockrun, next to start_dask.

–dst is the published volume; –work-dir is everything else. –dst holds the segmentation scales with meshes and skeletons inside it, and may be an s3:// URL. –work-dir must be an ordinary filesystem path: it holds the stage-1 fragments, the sqlite metrics DB and the appended JSONL manifests, none of which work over an object store.

Because the two are separate, a manifest can outlive the data it describes — clear –dst and a resumed run would skip everything and report success having written nothing. Each stage refuses to resume when its manifest records completed work but its output info is gone; –no-resume is the explicit override.

The ROI is in the voxels of –roi-scale, which is required with –roi — the same six numbers name a box 4x smaller per pyramid level, and the wrong scale silently processes the wrong region rather than failing. It filters blocks on the global grid, so widening it later re-uses everything already done rather than redoing it (see neu_morpho/roi.py). Drop –roi for the whole volume.

Scales are integers (–mesh-scale / –index-scale); the voxel size of each is read from the source metadata, never assumed to be 2**scale — that assumption is what misaligns meshes against skeletons on non-standard pyramids.

Skeletons come from neu-skel, a separate GPL-licensed package: neu-skel run adds them to a work dir this command has indexed, with the same –src, –dst and –work-dir.

usage: neu-morpho [-h] {run,progress,measure,run-report,help} ...

Positional Arguments

command

Possible choices: run, progress, measure, run-report, help

Sub-commands

run

neu-morpho: index -> allowlist -> meshes, locally or on SLURM.

The command-line entry point for this package.

neu-morpho run ...              # the pipeline
neu-morpho progress <work-dir>  # live per-stage counts
neu-morpho run-report <work-dir># self-contained HTML summary
neu-morpho measure ...          # morphometry over PUBLISHED output

Run run ON A WORKSTATION, in a session that outlives your terminal: it starts a dask cluster whose workers are SLURM jobs (dask submits the sbatch itself via scale()), then runs the requested stages against it. Every stage is idempotent, so re-running the same command resumes.

# 0. look at the pyramid first, and pick your scales from real metadata
neu-morpho run --src /path/to/seg --describe

# 1. small ROI, locally, to see it work end to end
neu-morpho run --src ... \
    --dst /path/to/morpho/segmentation --work-dir /path/to/morpho \
    --config dask-local --workers 4 \
    --roi 0,0,0,512,2048,2048 --roi-scale 2 --stages index,mesh

# 2. the same ROI on SLURM, surviving logout, publishing to s3
nohup env PYTHONUNBUFFERED=1 neu-morpho run --src ... \
    --dst s3://bucket/sample3/segmentation --work-dir /path/to/morpho \
    --config dask-slurm-example --config ~/my-site.yaml --workers 48 \
    --roi 0,0,0,512,2048,2048 --roi-scale 2 --stages index,mesh > run.log 2>&1 &
squeue -u "$USER"        # watch your jobs (read-only; don't poll in a tight loop)

python -m neu_morpho is equivalent to neu-morpho. PYTHONUNBUFFERED=1 is the console-script equivalent of python -u; without it the log lags the run. Keep that log OFF a shared network filesystem such as CephFS: unbuffered, every line is its own write, and once another node reads the file (tail -f) each one becomes a synchronous round trip. A local disk or your home directory is fine.

–config takes a bundled template name or a path, and is repeatable, deep-merged left to right — so a site config carries only the keys that differ. The templates ship with blockrun, next to start_dask.

–dst is the published volume; –work-dir is everything else. –dst holds the segmentation scales with meshes and skeletons inside it, and may be an s3:// URL. –work-dir must be an ordinary filesystem path: it holds the stage-1 fragments, the sqlite metrics DB and the appended JSONL manifests, none of which work over an object store.

Because the two are separate, a manifest can outlive the data it describes — clear –dst and a resumed run would skip everything and report success having written nothing. Each stage refuses to resume when its manifest records completed work but its output info is gone; –no-resume is the explicit override.

The ROI is in the voxels of –roi-scale, which is required with –roi — the same six numbers name a box 4x smaller per pyramid level, and the wrong scale silently processes the wrong region rather than failing. It filters blocks on the global grid, so widening it later re-uses everything already done rather than redoing it (see neu_morpho/roi.py). Drop –roi for the whole volume.

Scales are integers (–mesh-scale / –index-scale); the voxel size of each is read from the source metadata, never assumed to be 2**scale — that assumption is what misaligns meshes against skeletons on non-standard pyramids.

Skeletons come from neu-skel, a separate GPL-licensed package: neu-skel run adds them to a work dir this command has indexed, with the same –src, –dst and –work-dir.

Stages, in the order they run (–stages):

seg    copy the ROI's labels out as a precomputed volume, so the meshes and
       skeletons can be viewed against the segmentation they came from.
       Optional, and off by default — it is pure I/O and usually the most
       expensive stage.

index  scan the volume once and record every body's bounding box and voxel
       count in a SQLite metrics DB. Needed before --min-voxels or --limit-
       bodies, whose size filter builds the allowlist from these counts; mesh
       and skel are block-based and read no bounding box.

mesh   multi-resolution Draco meshes, per body. Two phases — fragments per
       (body, block), then one mesh assembled per body — so a re-run with
       only this stage reuses the fragments already on disk.
neu-morpho run [-h] --src SRC [--dst DST] [--work-dir WORK_DIR] [--describe]
               [--stages LIST] [--seg-encoding SEG_ENCODING]
               [--seg-scales SEG_SCALES] [--roi ROI] [--roi-scale ROI_SCALE]
               [--index-scale INDEX_SCALE] [--mesh-scale MESH_SCALE]
               [--smoothing N] [--block BLOCK] [--min-voxels MIN_VOXELS]
               [--limit-bodies LIMIT_BODIES] [--allowlist ALLOWLIST]
               [--no-metrics-db] [--occupancy-scale OCCUPANCY_SCALE]
               [--occupancy-dilate OCCUPANCY_DILATE] [--config NAME_OR_PATH]
               [--workers WORKERS] [--serial] [--no-resume]
               [--allow-body-failures]
               [--max-consecutive-failures MAX_CONSECUTIVE_FAILURES]
               [--dry-run] [--store-logs]

Named Arguments

--src

segmentation volume (path or s3://…)

--dst

the published precomputed VOLUME: labels with meshes/skeletons inside it. Local path or s3://…; bookkeeping goes to –work-dir

--work-dir

run directory: stage-1 fragments, manifests, metrics DB, failures, run summary. Must be a filesystem path. Defaults to the PARENT of a local –dst; required when –dst is remote

--describe

print the source pyramid and exit (no cluster, no writes)

Default: False

--stages

comma-separated subset of seg,index,mesh, described under ‘Stages’ above. They run in that order, and each is idempotent and resumable, so re-running a subset continues rather than redoing it (default: index,mesh)

Default: 'index,mesh'

--seg-encoding

encoding for the exported segmentation (or ‘raw’)

Default: 'compressed_segmentation'

--seg-scales

source scales to copy in the seg stage: ‘all’, a range like ‘1-6’, or a comma list. Scale 0 usually dominates the cost by an order of magnitude — check the log line

Default: 'all'

--roi

z0,y0,x0,z1,y1,x1 voxels, at –roi-scale

--roi-scale

scale the –roi values are given in. REQUIRED with –roi: the same six numbers name a different box at every scale, and guessing wrong silently processes the wrong region

--index-scale

scale to scan for bboxes

Default: 2

--mesh-scale

Default: 2

--smoothing

Laplacian smoothing passes on each block’s mesh before it is decimated, for a blocky segmentation such as ROI layers; each pass shrinks a surface slightly. A block’s mesh is closed at the block faces, so smoothing also moves the seams between blocks: use it with a –block covering the volume (default 0, none)

Default: 0

--block

block shape (z,y,x) voxels

Default: '256,256,256'

--min-voxels

only process bodies with at least this many voxels, counted at the index scale by the index stage

Default: 0

--limit-bodies

cap the allowlist to the N largest bodies (needs the index stage too)

--allowlist

explicit body-id file (overrides –min-voxels)

--no-metrics-db

skip the per-body metrics DB (its enrichment is observational; with an explicit –allowlist the index stage is unnecessary too)

Default: False

--occupancy-scale

coarse scale used to skip empty blocks; -1 disables

Default: 5

--occupancy-dilate

grow the occupied set by N blocks. Keep >=1: a coarse scale misses sparse blocks and the miss does not converge, so an un-dilated filter silently skips real data

Default: 1

--config

dask config: a bundled template (dask-local, dask-slurm-example) or a path to your own YAML. Repeatable, deep-merged left to right, so an overlay need only carry what differs from the template. Default: dask-local

--workers

Default: 4

--serial

no dask at all — run in this process (smallest smoke test)

Default: False

--no-resume

start over, ignoring manifests

Default: False

--allow-body-failures

exit 0 when the only failures are individual bodies; they are still logged, listed in the work dir’s failures file and retried by the next run. Without it such a run exits 1 so a script notices. A failed block, a store error or –max-consecutive-failures still exits non-zero

Default: False

--max-consecutive-failures

stop a stage after this many per-body failures in a row (0 disables; systemic errors abort immediately regardless)

Default: 10

--dry-run

report the plan (scales, ROI, block counts) and exit

Default: False

--store-logs

keep TensorStore’s benign S3 credential-chain logging (suppressed by default; real errors are never suppressed)

Default: False

progress

Per-stage task counts against the plan, for a run in flight. Reads only; re-run to refresh.

neu-morpho progress [-h] [--sample SECONDS] work_dir

Positional Arguments

work_dir

Named Arguments

--sample

sample the manifests twice this far apart to estimate a rate

measure

Per-body morphometry over PUBLISHED output.

Separate from run on purpose: run reads a source and writes a volume, while these read a finished volume and write tables. The thing being measured is often not the thing this pipeline produced.

neu-morpho measure volumes  --src ... --db ...   # per-body voxel count + volume
neu-morpho measure progress <db>                 # how far along that is
neu-morpho measure [-h] {volumes,skeletons,compartments,progress} ...

Positional Arguments

measure_command

Possible choices: volumes, skeletons, compartments, progress

Sub-commands

volumes

Count each body’s voxels and accumulate per-body volume into a metrics DB.

Reads EVERY block by default. An ROI built from a compartment mask omits somata that sit outside it, and dividing a truncated volume by a complete cable length is wrong in a way nothing downstream can see; an empty block is cheap enough that reading all of them is the affordable choice as well as the safe one. Pass –roi only when the region really is the subject.

–keep restricts which bodies are RECORDED, never which blocks are read, so the rows it writes are complete. Without it a fragmented segmentation records every single-voxel speck, and each one costs a driver-side upsert.

–with-bbox switches to the index scan, which also records bounding boxes. That costs an argwhere plus an argsort over every labelled voxel — ~30 s against ~3 s per dense block — so it is opt-in. The two paths both accumulate into voxel_count and refuse to share a DB, since running both would double every count.

neu-morpho measure volumes [-h] --src SRC --db DB [--level LEVEL]
                           [--block BLOCK] [--keep SRC] [--with-bbox]
                           [--roi ROI] [--background BACKGROUND]
                           [--config NAME_OR_PATH] [--workers WORKERS]
                           [--serial] [--no-resume] [--dry-run] [--store-logs]
Named Arguments
--src

segmentation to read (path or URL)

--db

metrics DB to accumulate into

--level

pyramid level to read. Its voxel size comes from the source metadata, never from 2**level

Default: 2

--block

cube block edge, in voxels

Default: 512

--keep

restrict which bodies are RECORDED (not which blocks are read). A file of ids, or a neuroglancer_segment_properties URL. Repeatable; the id sets are unioned

--with-bbox

also record each body’s bounding box. Roughly 10x the cost on a dense block, and only worth it if something reads the bbox

Default: False

--roi

z0,y0,x0,z1,y1,x1 in –level voxels

--background

label to treat as background. Annotation tools that number from 0 make this 1

Default: 0

--config
--workers

Default: 4

--serial

no dask; run in this process

Default: False

--no-resume

Default: False

--dry-run

Default: False

--store-logs

Default: False

skeletons

Cable length, topology and (where the source has radii) diameter statistics, per body.

The per-BODY pass, where volumes is per-block, and the differences are deliberate. Bodies are batched into tasks because each read is one small object and per-task dispatch would otherwise dominate; within a batch, reads run on threads, and they all share one opened store. A body that fails is RECORDED and skipped rather than killing the run — the opposite of the block sweep’s fail-fast, because one unreadable skeleton says nothing about the others, whereas one skipped block truncates every body in it.

A body with no published skeleton is recorded absent and never retried. A body that raised is recorded failed and IS retried next run. Those are different states and collapsing them would make a transient error permanent.

neu-morpho measure skeletons [-h] --volume VOLUME --db DB
                             [--skeleton-dir SKELETON_DIR] --bodies SRC
                             [--require-radii] [--batch BATCH]
                             [--threads THREADS] [--config NAME_OR_PATH]
                             [--workers WORKERS] [--serial] [--no-resume]
                             [--no-retry-failed] [--dry-run] [--store-logs]
Named Arguments
--volume

volume the skeletons live under (path or URL)

--db
--skeleton-dir

subdirectory named by the volume’s info ‘skeletons’ key

Default: 'skeleton'

--bodies

which bodies to measure: an id file or a neuroglancer_segment_properties URL. Repeatable, unioned

--require-radii

fail on a body whose skeleton carries no radius attribute, instead of recording NaN diameters. Use on a volume this package wrote, where missing radii mean something went wrong

Default: False

--batch

bodies per task

Default: 200

--threads

concurrent reads per task

Default: 8

--config
--workers

Default: 4

--serial

Default: False

--no-resume

Default: False

--no-retry-failed

treat a previously failed body as done

Default: False

--dry-run

Default: False

--store-logs

Default: False

compartments

Split each body’s volume by semantic compartment (nucleus, soma, neuropil, …).

Reads the segmentation block and the semantic block over the SAME voxel indices and counts (body, label) pairs in one pass. Requires the two volumes to share a grid at the chosen level – same voxel size, same origin – which is checked, not assumed.

Label 0 is counted too, so sum over labels == the body’s total voxel count is an exact identity. –verify checks that against the totals a previous measure volumes run recorded. That is what makes –occupancy-scale safe to use: a coarse grid CAN drop a block that holds data, and normally you would never learn which, but the volume sweep read every block, so its totals are ground truth and any deficit is named rather than silent.

Note the distinction from an anatomical ROI, which is NOT safe here: a compartment mask omits somata lying outside it, whereas a soma is segmentation and so is non-zero at any coarse level of the segmentation itself.

non_somatic = total - (nucleus + soma), so nothing needs measuring twice.

neu-morpho measure compartments [-h] --src SRC --semantic SEMANTIC --db DB
                                [--level LEVEL]
                                [--semantic-level SEMANTIC_LEVEL]
                                [--block BLOCK] [--keep SRC]
                                [--occupancy-scale LEVEL]
                                [--occupancy-dilate OCCUPANCY_DILATE]
                                [--roi ROI] [--background BACKGROUND]
                                [--verify] [--config NAME_OR_PATH]
                                [--workers WORKERS] [--serial] [--no-resume]
                                [--dry-run] [--store-logs]
Named Arguments
--src

segmentation

--semantic

semantic label volume, on the same grid at –level

--db
--level

segmentation level to read

Default: 2

--semantic-level

semantic level with the SAME voxel size; found by matching the voxel size if omitted, which is safer than assuming an index

--block

Default: 512

--keep
--occupancy-scale

restrict to blocks non-empty at this coarse level OF THE SEGMENTATION. Safe in a way an anatomical ROI is not, and –verify proves afterwards that nothing was missed

--occupancy-dilate

Default: 1

--roi
--background

Default: 0

--verify

after the run, check each body’s per-label sum against the total the volume sweep recorded

Default: False

--config
--workers

Default: 4

--serial

Default: False

--no-resume

Default: False

--dry-run

Default: False

--store-logs

Default: False

progress

Blocks done against the total each stage recorded before dispatch. Reads only; re-run to refresh.

neu-morpho measure progress [-h] db
Positional Arguments
db

run-report

Write a human-readable HTML summary of a run from its work dir. Works on an in-flight run too.

neu-morpho run-report [-h] [-o OUT] work_dir

Positional Arguments

work_dir

Named Arguments

-o, --out

output HTML (default: <work-dir>/report.html)

help

Print the help for the tool, or for one of its subcommands.

neu-morpho help run and neu-morpho run –help are the same thing; nested subcommands work too (help measure run).

neu-morpho help [-h] [COMMAND ...]

Positional Arguments

COMMAND

the subcommand to describe (default: the whole tool)