feat(herd): add a user-facing API for external resource references - #877
Draft
ehennestad wants to merge 4 commits into
Draft
feat(herd): add a user-facing API for external resource references#877ehennestad wants to merge 4 commits into
ehennestad wants to merge 4 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## fix-empty-compound-dataset-write #877 +/- ##
====================================================================
+ Coverage 95.27% 95.30% +0.03%
====================================================================
Files 234 235 +1
Lines 8318 8592 +274
====================================================================
+ Hits 7925 8189 +264
- Misses 393 403 +10 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
ehennestad
marked this pull request as draft
August 27, 2026 07:46
ehennestad
force-pushed
the
add-herd-user-api
branch
2 times, most recently
from
August 27, 2026 08:53
1be5f31 to
b0d04c6
Compare
Add matnwb.neurodata.HERDBase, a non-generated base class for the
generated HERD type, and register it with the code generator. HERD
holds its associations in six tables that reference each other by
zero-based row index; the new methods hide that layout behind terms,
entities and objects:
herd.addRef(nwb, nwb.general_subject, Key="Mus musculus", ...
EntityId="NCBITaxon:10090", ...
EntityUri="http://purl.obolibrary.org/obo/NCBITaxon_10090")
Lookups are getEntity, getKey, getObjectEntities and getObjectType.
toTable flattens the six tables into one table of references.
Semantics follow HDMF: keys are scoped to an object rather than shared
across the file, an entity keeps the URI it was first stored with, and
one key may resolve to several entities. Unlike HDMF, adding a
reference that is already recorded changes nothing instead of
appending a duplicate object_keys row; both files read the same way.
Referencing an attribute is limited to properties that are themselves
neurodata types, such as a table column. Attributes holding plain
values need relative path support, which is not implemented yet.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add nwb.addRef(container, ...) and nwb.getExternalResources() as the primary entry points for HERD: addRef lazily creates and attaches the file's HERD on first use, matching PyNWB's get_external_resources semantics (a file has at most one HERD). HERD.addRef remains available directly for building a HERD before it is attached to a file. Give HERD a custom display showing the reference-table summary counts and the flattened table instead of the six internal Data properties, which are not informative on their own. This requires HERDBase to declare matlab.mixin.CustomDisplay directly: MATLAB treats a method defined in an unrelated class as ambiguous rather than as an override unless the class sits in the same CustomDisplay lineage. getFooter is left untouched so the existing missing-required-property warning from MetaClass still fires. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
getObjectTypeName used class(container), which returns the bare
'NwbFile' for the MatNWB-only wrapper class rather than the 'NWBFile'
schema name MetaClass.export actually writes to neurodata_type. This
mismatch was invisible for every other type (their class name already
equals the schema name) but meant an external reference added directly
to the file, via addRef(nwb, nwb, ...), recorded an object_type that
getObjectType("NWBFile") and any PyNWB/HDMF consumer filtering by
neurodata_type would never match, even though the same row's object_id
still resolved correctly.
Also drop a dead isempty/else branch in findKeyForObject: intersect
already returns empty when there is no match, so the branch reassigned
empty to empty.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Asking a file for its external resources attached a HERD whose six tables were unset, and every one of them is required by the schema. Exporting that file stopped with a required-property error listing all six, so nwb.getExternalResources() followed by nwbExport could not be run. PyNWB writes an empty HERD for the same sequence. The tables were only filled in by addRef, which left the HERD unwritable until a reference was added to it. getExternalResources now fills them in too, so the HERD it hands back can be written as it is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ehennestad
force-pushed
the
add-herd-user-api
branch
from
August 27, 2026 09:03
b0d04c6 to
6063815
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Problem — A value in an NWB file is often a term that means something specific: a species
name, a brain region, a unit. HERD (HDMF External Resources Data Structure) is the schema type
that records what those terms refer to in an external resource such as an ontology, so the
values are standardized rather than free text. MatNWB generated the
HERDclass but gave noway to use it: adding one annotation meant assembling six compound tables by hand and wiring
them together with zero-based row indices, and getting an annotation back out meant reversing
that by hand.
Solution — Add the operations HERD is actually used for. One call records that an object
refers to an external entity, and a small set of lookups reads the annotations back as MATLAB
tables.
What changed
nwb.addRef(container, ...)records that an object in the file refers to an external entity.nwb.getExternalResources()returns the file's HERD, creating and attaching one on first use.A file has at most one HERD, so an existing one is reused, including after
nwbRead.HERD.toTable()flattens the six tables into one table of references, with the internal rowindices resolved into the values they point at.
HERD.getEntity,getKey,getObjectEntitiesandgetObjectTypelook annotations back up.flattened table, instead of the six
Dataproperties.Referencing an attribute of a container is supported when that attribute is itself a neurodata
type, such as a column of a
DynamicTable. Attributes holding plain values need the relativepath support that HDMF has and MatNWB does not implement yet; those raise a clear error. The
sidecar zip form for sharing one HERD across files (
to_zip/from_zipin HDMF) is also notimplemented.
Implementation notes
The handwritten behaviour lives in
matnwb.neurodata.HERDBase, an abstract base class attachedto the generated
HERDclass through thecustomBaseClassesmap infile.fillClass, followingthe existing
DynamicTableBaseandAlignedDynamicTableBasepattern.Row indices are stored zero-based on disk to match HDMF and PyNWB, and are converted at the API
boundary so no public method takes or returns one.
Semantics follow HDMF: keys are scoped to an object rather than shared across the file, so the
same term on two objects is stored as two key rows; an entity keeps the URI it was first stored
with and a differing URI warns; and one key may resolve to several entities. Adding a reference
that is already recorded changes nothing, where HDMF appends a second identical
object_keysrow; both files read the same way.
HERDBasedeclaresmatlab.mixin.CustomDisplaydirectly. MATLAB treats a display hook definedin a class outside that lineage as an ambiguous definition rather than an override, so the
mixin has to sit in it.
getFooteris left toMetaClassso its missing-property warningstill fires.
Examples
Recording and reading back an annotation
Before — neither entry point exists, so the six tables have to be built by hand:
and the result displays as its raw properties:
After
Looking up what is annotated on one object
Before — no such method; the
objects,object_keys,entity_keysandentitiestableshave to be joined by hand on their zero-based indices.
After
How to test
Unit tests:
Checklist
fix #XXwhereXXis the issue number?🤖 Generated with Claude Code