neu_mark.segprops

Building a neuroglancer segment_properties source from the body annotations.

The format is one inline JSON document, written through neu_vol.location. What the spec allows shapes it:

  • At most one label, one description and one tags property, so every facet pools into the one tags property as prefix:value (side:L, type:Mi1).

  • A segment’s tags values are indices into the tags array, in increasing order.

  • A tag may contain no spaces (they are hyphenated here) and no leading #. Matching is case-insensitive, so values differing only in case show as one chip in the viewer (case_collisions() reports them); case is preserved.

Facets come from a dataset’s rules module (neu_mark.rules.Rule); nothing is built in, so with no rules the document carries labels and numbers only. The label is the rules’ label column (a module’s LABEL, or label=); a reserved label rule replaces it, and with neither, segments are known by id.

The document is inline, so every viewer downloads all of it: max_tags and max_facets refuse a vocabulary large enough to make the layer unusable (resolve_tag_limit()).

Attributes

Functions

as_table(bodies, outcomes, ruleset, *[, counts, ...])

One row per body: what the document says about it, then the record it came from.

build(bodies, *[, rules, label, drop, counts, sizes, ...]) → dict[str, Any]

The segment_properties info document, plus a report of what went into it.

case_collisions(vocabulary) → dict[str, list[str]]

{lowercased: [tags]} for tags that differ only in case.

check_vocabulary(vocabulary, distinct, *, segments[, ...]) → int

Refuse a vocabulary big enough to make the layer unusable. Returns the limit applied.

facet_order(rules) → Callable[[str], tuple]

A sort key for tags: facets in the order their rules are declared, a prefix no rule

facets(record, ruleset) → dict[str, list[str]]

Every facet ruleset gives one body, as {rule name: [tags]}, already

format_report(report) → list[str]

The report as lines, for a CLI to print.

natural_key(tag) → tuple

Sort key for tags: case-insensitive, with runs of digits compared as numbers, and

normalize_tag(value) → str | None

A tag as the format requires: spaces hyphenated, no leading #, case preserved.

resolve_tag_limit(max_tags, segments) → tuple[int, str]

max_tags as a concrete tag ceiling: (limit, how).

tag_for(rule, value) → str | None

One rule's value as a tag: prefix:value, or a bare tag where prefix is empty.

write(dst, info, *[, subdir]) → str

Write the document to <dst>/<subdir>/info through the kvstore.

Module Contents

neu_mark.segprops.as_table(bodies, outcomes, ruleset, *, counts=None, sizes=None, requested: Sequence[int] | None = None, described: bool = False)

One row per body: what the document says about it, then the record it came from.

Columns: body, label, description (when a rule writes one), excluded (the drop rule’s reason, "no record", or null for a segment in the document), tags (the full list), tag:<prefix> per facet in rule order (a list for a multi rule, else a value or null), flag:<tag> per bare tag, a column per tag=False rule named after it, the numbers, then every record column. A record column whose name is taken gets record: in front, and dict values (the synapse neuropil shares) are JSON text.

neu_mark.segprops.build(bodies, *, rules=None, label: str | None = None, drop: Sequence[str] = (), counts=None, sizes=None, max_tags: float = DEFAULT_MAX_TAGS, max_facets: int = MAX_FACETS, table: bool = False, requested: Sequence[int] | None = None) → dict[str, Any]

The segment_properties info document, plus a report of what went into it.

bodies is a frame with body and whatever property fields exist; counts an optional frame with body/pre/post/syn; sizes an optional {body: voxels}.

rules is a RuleSet (or anything it accepts); without it the document has labels and numbers only. label names the label column, overriding the rules’ own; drop names rules whose bodies are excluded rather than tagged. Evaluation order: drop rules, then tag rules, then the reserved label/description rules, then any other tag=False rule (reported, and a column of the table, but not in the document).

Returns {"info": …, "report": …}. Check the report’s coverage: a facet that fired on 2% of bodies looks the same in the viewer as one the data does not have.

table=True adds "table", a DataFrame with a row per body (as_table()); requested names bodies to list there as excluded="no record" when bodies has no row for them.

neu_mark.segprops.case_collisions(vocabulary: Iterable[str]) → dict[str, list[str]]

{lowercased: [tags]} for tags that differ only in case.

The viewer matches tags case-insensitively, so type:Tm2 and type:TM2 show as one chip while remaining two values in the table.

neu_mark.segprops.check_vocabulary(vocabulary: Sequence[str], distinct: Mapping[str, set], *, segments: int, max_tags: float = DEFAULT_MAX_TAGS, max_facets: int = MAX_FACETS, facets: Sequence[str] | None = None) → int

Refuse a vocabulary big enough to make the layer unusable. Returns the limit applied.

Two checks: too many tags (typically a rule echoing the whole instance string) and too many facets (a rule minting a separate facet per value). See resolve_tag_limit().

neu_mark.segprops.facet_order(rules: Iterable[Any]) → Callable[[str], tuple]

A sort key for tags: facets in the order their rules are declared, a prefix no rule declares after those, bare flags last, and natural_key() within each.

neu_mark.segprops.facets(record: Mapping[str, Any], ruleset) → dict[str, list[str]]

Every facet ruleset gives one body, as {rule name: [tags]}, already tag-normalized. Drop rules are not consulted; exclusion happens in build().

neu_mark.segprops.format_report(report: Mapping[str, Any]) → list[str]

The report as lines, for a CLI to print.

neu_mark.segprops.natural_key(tag: str) → tuple

Sort key for tags: case-insensitive, with runs of digits compared as numbers, and the original text last so the order is total.

neu_mark.segprops.normalize_tag(value: Any) → str | None

A tag as the format requires: spaces hyphenated, no leading #, case preserved. Missing values (including pd.NA) give None, via neu_mark.explore.normalize().

neu_mark.segprops.resolve_tag_limit(max_tags: float, segments: int) → tuple[int, str]

max_tags as a concrete tag ceiling: (limit, how).

Below 1 is a fraction of the segment count, 1 or above an absolute count, decided by value (1 and 1.0 are the same to TOML and JSON). MIN_TAG_ALLOWANCE floors a fraction only; max_tags=25 means 25.

neu_mark.segprops.tag_for(rule, value: Any) → str | None

One rule’s value as a tag: prefix:value, or a bare tag where prefix is empty.

A trailing colon on the prefix is tolerated (prefix="col:" gives col:C2).

neu_mark.segprops.write(dst: str, info: Mapping[str, Any], *, subdir: str = SUBDIR) → str

Write the document to <dst>/<subdir>/info through the kvstore.

neu_mark.segprops.AT_TYPE = 'neuroglancer_segment_properties'
neu_mark.segprops.DEFAULT_MAX_TAGS = 0.1
neu_mark.segprops.MAX_FACETS = 24
neu_mark.segprops.MIN_TAG_ALLOWANCE = 100
neu_mark.segprops.NO_RECORD = 'no record'
neu_mark.segprops.SEPARATOR = ':'
neu_mark.segprops.SUBDIR = 'segment_properties'
neu_mark.segprops.logger