-
Notifications
You must be signed in to change notification settings - Fork 51
Standardize nwb ingestion #1377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #1377 +/- ##
==========================================
+ Coverage 71.02% 71.24% +0.21%
==========================================
Files 113 114 +1
Lines 13088 13243 +155
==========================================
+ Hits 9296 9435 +139
- Misses 3792 3808 +16 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking this on - great start!
| "SpyglassIngestion", | ||
| "SpyglassMixinPart", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do we want the eventual list to be? I'm thinking through the naming convention here...
- It seems like we're mixing type nouns with process verbs:
- Type noun:
SpyglassMixin,SpyglassMixinPart(eventuallySpyglassMixinParams?) - Verb process:
SpyglassIngestion - So change to
SpyglassImported?1SpyglassIngested?
- What do we want the common prefix to be?
SpyglassMixin(orSpyglassMixinTable),SpyglassMixinPart,SpyglassMixinIngestionSpyglassMixin,SpyglassPart,SpyglassIngestion- I prefer the latter, where
Mixinimplies general
I would ideally like to ...
class SpyglassParams(SpyglassMixin, dj.Lookup):
...
class SomeParams(SpyglassParams):
...But this wouldn't work retroactively. If you change a table type, it just declares a new one. Related issue
Footnotes
-
I don't love this because it implies
dj.Importedtables, which these are not ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To match #1435 we can make this IngestionMixin and move to it's own .py file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the pattern I went with in 1435 was to have (1) private mixins called XMixin: ExportMixin, FetchMixin, etc., and (2) public mixins called SpyglassX: SpyglassMixin, SpyglassAnalysis
Up to you how much you want to put in utils/mixins/ingestion.py versus utils/dj_mixin.py
src/spyglass/utils/dj_mixin.py
Outdated
| The reserved key "self" refers to the original object. | ||
| Additional keys can be added to access data from other nwb objects that are | ||
| attributes of the object (e.g. device.model). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't see a case that had additional keys. Could you flush out the 'device.model' example a bit more?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some tables need to access information in an attribute of an attribute which this gives a format to do.
Alternatively, Since I later added the option for functions as the value those cases could be handled that way instead
src/spyglass/utils/dj_mixin.py
Outdated
| def _source_nwb_object_type(self): | ||
| """The type of NWB object to import from the NWB file. | ||
| If None, the table is either incompatible with NWB ingestion or must implement |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like an odd case to me: a table that inherits this class but is incompatible with nwb ingestion? Could that happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, that was the docstring before I pulled it out into it's own class (rather than part of SpyglassMixin). Should be removed
* Auto-gen mapping doc * Refactor SpyglassIngestion * execute_inserts -> dry_run * Handle inserts as dict for parts. Quiet ingest logging * Minor edits * Add load config * Prevent prompt on equivalent entries. Always handle entries as dict * Fix ingestion order * Add DIOEvents * Remove debug print * Pluralize 'adjust_keys'. Add Raw * Update changelog * Fix tests * Remove comment * Apply suggestions from code review Co-authored-by: Copilot <[email protected]> * Apply suggestions from code review 2 * IntervalList.insert -> cautious_insert * Fix recursive call * Fix restrict by UUID --------- Co-authored-by: Copilot <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR standardizes NWB ingestion across Spyglass by implementing a centralized SpyglassIngestion class. The new approach replaces individual insert_from_nwbfile methods with a unified framework that maps NWB object attributes to table keys using declarative properties.
Key changes:
- Introduces
SpyglassIngestionmixin class with standardized ingestion workflow - Converts 14+ tables to use the new ingestion system (Institution, Lab, LabMember, Subject, Device tables, Session, Raw, DIOEvents, etc.)
- Replaces legacy
makemethods withinsert_from_nwbfilecalls and deprecation warnings
Reviewed Changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/spyglass/utils/dj_mixin.py | Adds SpyglassIngestion class with core ingestion framework |
| src/spyglass/common/common_*.py | Converts multiple tables to use SpyglassIngestion pattern |
| src/spyglass/data_import/insert_sessions.py | Updates session insertion to support reinsert parameter |
| tests/ | Updates tests to use new ingestion methods |
| docs/ | Adds auto-generated ingestion mapping documentation |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| # Avoids triggering 'accept_divergence' on reinsert | ||
| adjusted = [] | ||
| for key in keys.copy(): | ||
| key["sex"] = self.standardized_sex_string(key, warn=False) |
Copilot
AI
Oct 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The standardized_sex_string method expects a subject object as first parameter, but key is a dictionary. This will cause a runtime error.
| warnings.warn( | ||
| f"Electrode ID {nwbfile_elect_id} exists in the NWB file " | ||
| + "but has no corresponding config YAML entry." | ||
| ) |
Copilot
AI
Oct 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The logic is inverted from the original. Consider adding a comment explaining why electrodes without config entries are skipped to improve code clarity.
…yglass into standard_import
…to standard_import
00a52b7 to
6e6c3a5
Compare
src/spyglass/common/common_device.py
Outdated
|
|
||
| @schema | ||
| class DataAcquisitionDeviceSystem(SpyglassIngestion, dj.Manual): | ||
| class DataAcquisitionDeviceSystem(SpyglassMixin, IngestionMixin, dj.Manual): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I prefer SpyglassIngestion as the interface for consistency....
class SomeTable(SpyglassMixin, dj.Something)
class OtherTable(SpyglassAnalysis, dj.Something)
Class ThirdTable(SpyglassIngestion, dj.Something)
Currently, the analysis code is organized as ...
utils/mixins/analysis.py::AnalysisMixin
utils/dj_mixin.py::SpyglassAnalysis
It makes sense to me that this would be ...
utils/mixins/analysis.py::IngestionMixin
utils/dj_mixin.py::SpyglassIngestion
Keep the custom class inheritance confined to dj.Mixin for simplicity? Allows us to manage the MRO behind the utils curtain, and prevent able classes from bloating out at the declaration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's reasonable. I'll make the switch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just one minor thing of adding the new docs to mkdocs.yml
| - Understanding a Schema: ForDevelopers/Schema.md | ||
| - Custom Pipelines: ForDevelopers/CustomPipelines.md | ||
| - Using NWB: ForDevelopers/UsingNWB.md | ||
| - Ingestion Mapping: ForDevelopers/ingestion_mapping.md |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also add features/Ingestion.md

Description
Resolves #1375, partially resolves #1326
Known Tables for Ingestion
Checklist:
CITATION.cffaltersnippet for release notes.CHANGELOG.mdwith PR number and description.