Skip to content

Conversation

@samuelbray32
Copy link
Collaborator

@samuelbray32 samuelbray32 commented Aug 20, 2025

Description

Resolves #1375, partially resolves #1326

  • Implements a standard set of functions and properties for tables that ingest from nwb files

Known Tables for Ingestion

  • Institution
  • Lab,
  • LabMember,
  • LabTeam,
  • Subject,
  • DataAcquisitionDeviceAmplifier,
  • DataAcquisitionDeviceSystem,
  • DataAcquisitionDevice,
  • CameraDevice,
  • ProbeType,
  • Probe,
  • Probe.Shank,
  • Probe.Electrode,
  • Session,
  • Session.Experimenter,
  • Session.DataAcquisitionDevice,
  • ElectrodeGroup,
  • Raw,
  • SampleCount,
  • DIOEvents,
  • TaskEpoch,
  • ImportedSpikeSorting,
  • SensorData,
  • IntervalList
  • Electrode
  • PositionSource
  • VideoFile
  • StateScriptFile
  • ImportedPose
  • ImportedLFP
  • RawPosition

Checklist:

  • N/a. If this PR should be accompanied by a release, I have updated the CITATION.cff
  • N/a. If this PR edits table definitions, I have included an alter snippet for release notes.
  • N/a. If this PR makes changes to position, I ran the relevant tests locally.
  • If this PR makes user-facing changes, I have added/edited docs/notebooks to reflect the changes
  • I have updated the CHANGELOG.md with PR number and description.

@codecov
Copy link

codecov bot commented Aug 20, 2025

Codecov Report

❌ Patch coverage is 85.99641% with 78 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.24%. Comparing base (8c6ad08) to head (87c15b2).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/spyglass/utils/mixins/ingestion.py 83.45% 23 Missing ⚠️
src/spyglass/common/common_ephys.py 57.69% 22 Missing ⚠️
src/spyglass/spikesorting/imported.py 62.50% 9 Missing ⚠️
src/spyglass/common/populate_all_common.py 81.25% 6 Missing ⚠️
src/spyglass/common/common_device.py 96.39% 4 Missing ⚠️
src/spyglass/common/common_dio.py 83.33% 4 Missing ⚠️
src/spyglass/common/common_interval.py 89.18% 4 Missing ⚠️
src/spyglass/common/common_subject.py 86.95% 3 Missing ⚠️
src/spyglass/common/common_lab.py 98.59% 1 Missing ⚠️
src/spyglass/common/common_nwbfile.py 66.66% 1 Missing ⚠️
... and 1 more
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@edeno edeno requested a review from CBroz1 August 21, 2025 11:58
@CBroz1 CBroz1 mentioned this pull request Aug 21, 2025
7 tasks
Copy link
Member

@CBroz1 CBroz1 left a 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!

Comment on lines 12 to 13
"SpyglassIngestion",
"SpyglassMixinPart",
Copy link
Member

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...

  1. It seems like we're mixing type nouns with process verbs:
  • Type noun: SpyglassMixin, SpyglassMixinPart (eventually SpyglassMixinParams?)
  • Verb process: SpyglassIngestion
  • So change to SpyglassImported?1 SpyglassIngested?
  1. What do we want the common prefix to be?
  • SpyglassMixin (or SpyglassMixinTable), SpyglassMixinPart, SpyglassMixinIngestion
  • SpyglassMixin, SpyglassPart, SpyglassIngestion
  • I prefer the latter, where Mixin implies 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

  1. I don't love this because it implies dj.Imported tables, which these are not

Copy link
Collaborator Author

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

Copy link
Member

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

Comment on lines 1031 to 1033
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).
Copy link
Member

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?

Copy link
Collaborator Author

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

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
Copy link
Member

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?

Copy link
Collaborator Author

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

@samuelbray32
Copy link
Collaborator Author

image

Benefits of standardization: Can auto-generate doc table of the ingestion mapping

CBroz1 and others added 4 commits October 10, 2025 13:41
* 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]>
@edeno edeno marked this pull request as ready for review October 14, 2025 18:25
@edeno edeno requested review from CBroz1, Copilot and edeno October 14, 2025 18:25
Copy link
Contributor

Copilot AI left a 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 SpyglassIngestion mixin 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 make methods with insert_from_nwbfile calls 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)
Copy link

Copilot AI Oct 14, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines 241 to 244
warnings.warn(
f"Electrode ID {nwbfile_elect_id} exists in the NWB file "
+ "but has no corresponding config YAML entry."
)
Copy link

Copilot AI Oct 14, 2025

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.

Copilot uses AI. Check for mistakes.
@samuelbray32 samuelbray32 changed the title WIP: Standardize nwb ingestion Standardize nwb ingestion Oct 30, 2025

@schema
class DataAcquisitionDeviceSystem(SpyglassIngestion, dj.Manual):
class DataAcquisitionDeviceSystem(SpyglassMixin, IngestionMixin, dj.Manual):
Copy link
Member

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

Copy link
Collaborator Author

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

Copy link
Member

@CBroz1 CBroz1 left a 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
Copy link
Member

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Data import conventions: Imported vs insert_from_nwb Nwb-object oriented ingestion

4 participants