Skip to content

Commit c0e0ee9

Browse files
authored
Fault-permit insert and remove mutual exclusivity protections on Merge (#824)
* #719, #804, #212 * #768 * Add merge delete and populate * Changes following PR review @edeno * Replace delayed import of ImportedSpikeSorting
1 parent 5842ba6 commit c0e0ee9

File tree

7 files changed

+180
-109
lines changed

7 files changed

+180
-109
lines changed

CHANGELOG.md

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,38 @@
44

55
### Infrastructure
66

7-
- Additional documentation. #690
8-
- Clean up following pre-commit checks. #688
9-
- Add Mixin class to centralize `fetch_nwb` functionality. #692, #734
10-
- Refactor restriction use in `delete_downstream_merge` #703
11-
- Add `cautious_delete` to Mixin class
12-
- Initial implementation. #711, #762
13-
- More robust caching of join to downstream tables. #806
14-
- Overwrite datajoint `delete` method to use `cautious_delete`. #806
15-
- Reverse join order for session summary. #821
16-
- Add temporary logging of use to `common_usage`. #811, #821
17-
- Add `deprecation_factory` to facilitate table migration. #717
18-
- Add Spyglass logger. #730
19-
- IntervalList: Add secondary key `pipeline` #742
20-
- Increase pytest coverage for `common`, `lfp`, and `utils`. #743
21-
- Update docs to reflect new notebooks. #776
22-
- Add overview of Spyglass to docs. #779
23-
- Update linting for Black 24. #808
24-
- Steamline dependency management. #822
7+
- Docs:
8+
- Additional documentation. #690
9+
- Add overview of Spyglass to docs. #779
10+
- Update docs to reflect new notebooks. #776
11+
- Mixin:
12+
- Add Mixin class to centralize `fetch_nwb` functionality. #692, #734
13+
- Refactor restriction use in `delete_downstream_merge` #703
14+
- Add `cautious_delete` to Mixin class
15+
- Initial implementation. #711, #762
16+
- More robust caching of join to downstream tables. #806
17+
- Overwrite datajoint `delete` method to use `cautious_delete`. #806
18+
- Reverse join order for session summary. #821
19+
- Add temporary logging of use to `common_usage`. #811, #821
20+
- Merge Tables:
21+
- UUIDs: Revise Merge table uuid generation to include source. #824
22+
- UUIDs: Remove mutual exclusivity logic due to new UUID generation. #824
23+
- Add method for `merge_populate`. #824
24+
- Linting:
25+
- Clean up following pre-commit checks. #688
26+
- Update linting for Black 24. #808
27+
- Misc:
28+
- Add `deprecation_factory` to facilitate table migration. #717
29+
- Add Spyglass logger. #730
30+
- Increase pytest coverage for `common`, `lfp`, and `utils`. #743
31+
- Steamline dependency management. #822
2532

2633
### Pipelines
2734

35+
- Common:
36+
- `IntervalList`: Add secondary key `pipeline` #742
37+
- Add `common_usage` table. #811, #821, #824
38+
- Add catch errors during `populate_all_common`. #824
2839
- Spike sorting:
2940
- Add SpikeSorting V1 pipeline. #651
3041
- Move modules into spikesorting.v0 #807

src/spyglass/common/common_behav.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,25 @@ class SpatialSeries(SpyglassMixin, dj.Part):
4242
name=null: varchar(32) # name of spatial series
4343
"""
4444

45+
def populate(self, keys=None):
46+
"""Insert position source data from NWB file.
47+
48+
WARNING: populate method on Manual table is not protected by transaction
49+
protections like other DataJoint tables.
50+
"""
51+
if not isinstance(keys, list):
52+
keys = [keys]
53+
if isinstance(keys[0], dj.Table):
54+
keys = [k for tbl in keys for k in tbl.fetch("KEY", as_dict=True)]
55+
for key in keys:
56+
nwb_file_name = key.get("nwb_file_name")
57+
if not nwb_file_name:
58+
raise ValueError(
59+
"PositionSource.populate is an alias for a non-computed table "
60+
+ "and must be passed a key with nwb_file_name"
61+
)
62+
self.insert_from_nwbfile(nwb_file_name)
63+
4564
@classmethod
4665
def insert_from_nwbfile(cls, nwb_file_name):
4766
"""Add intervals to ItervalList and PositionSource.
@@ -482,6 +501,7 @@ def _no_transaction_make(self, key):
482501

483502
# Skip populating if no pos interval list names
484503
if len(pos_intervals) == 0:
504+
# TODO: Now that populate_all accept errors, raise here?
485505
logger.error(f"NO POS INTERVALS FOR {key}; {no_pop_msg}")
486506
return
487507

@@ -519,6 +539,7 @@ def _no_transaction_make(self, key):
519539

520540
# Check that each pos interval was matched to only one epoch
521541
if len(matching_pos_intervals) != 1:
542+
# TODO: Now that populate_all accept errors, raise here?
522543
logger.error(
523544
f"Found {len(matching_pos_intervals)} pos intervals for {key}; "
524545
+ f"{no_pop_msg}\n{matching_pos_intervals}"

src/spyglass/common/common_usage.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""A schema to store the usage of advanced Spyglass features.
22
3-
Records show usage of features such as table chains, which will be used to
3+
Records show usage of features such as cautious delete and fault-permitting
4+
insert, which will be used to
45
determine which features are used, how often, and by whom. This will help
56
plan future development of Spyglass.
67
"""
@@ -21,3 +22,18 @@ class CautiousDelete(dj.Manual):
2122
restriction: varchar(255)
2223
merge_deletes = null: blob
2324
"""
25+
26+
27+
@schema
28+
class InsertError(dj.Manual):
29+
definition = """
30+
id: int auto_increment
31+
---
32+
dj_user: varchar(64)
33+
connection_id: int # MySQL CONNECTION_ID()
34+
nwb_file_name: varchar(64)
35+
table: varchar(64)
36+
error_type: varchar(64)
37+
error_message: varchar(255)
38+
error_raw = null: blob
39+
"""
Lines changed: 51 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import datajoint as dj
2+
13
from spyglass.common.common_behav import (
24
PositionSource,
35
RawPosition,
@@ -14,51 +16,58 @@
1416
from spyglass.common.common_nwbfile import Nwbfile
1517
from spyglass.common.common_session import Session
1618
from spyglass.common.common_task import TaskEpoch
19+
from spyglass.common.common_usage import InsertError
1720
from spyglass.utils import logger
1821

1922

2023
def populate_all_common(nwb_file_name):
21-
# Insert session one by one
22-
fp = [(Nwbfile & {"nwb_file_name": nwb_file_name}).proj()]
23-
logger.info("Populate Session...")
24-
Session.populate(fp)
25-
26-
# If we use Kachery for data sharing we can uncomment the following two lines. TBD
27-
# logger.info('Populate NwbfileKachery...')
28-
# NwbfileKachery.populate()
29-
30-
logger.info("Populate ElectrodeGroup...")
31-
ElectrodeGroup.populate(fp)
32-
33-
logger.info("Populate Electrode...")
34-
Electrode.populate(fp)
35-
36-
logger.info("Populate Raw...")
37-
Raw.populate(fp)
38-
39-
logger.info("Populate SampleCount...")
40-
SampleCount.populate(fp)
41-
42-
logger.info("Populate DIOEvents...")
43-
DIOEvents.populate(fp)
44-
45-
# sensor data (from analog ProcessingModule) is temporarily removed from NWBFile
46-
# to reduce file size while it is not being used. add it back in by commenting out
47-
# the removal code in spyglass/data_import/insert_sessions.py when ready
48-
# logger.info('Populate SensorData')
49-
# SensorData.populate(fp)
50-
51-
logger.info("Populate TaskEpochs")
52-
TaskEpoch.populate(fp)
53-
logger.info("Populate StateScriptFile")
54-
StateScriptFile.populate(fp)
55-
logger.info("Populate VideoFile")
56-
VideoFile.populate(fp)
57-
logger.info("RawPosition...")
58-
PositionSource.insert_from_nwbfile(nwb_file_name)
59-
RawPosition.populate(fp)
60-
61-
logger.info("Populate ImportedSpikeSorting...")
24+
"""Insert all common tables for a given NWB file."""
6225
from spyglass.spikesorting.imported import ImportedSpikeSorting
6326

64-
ImportedSpikeSorting.populate(fp)
27+
key = [(Nwbfile & f"nwb_file_name LIKE '{nwb_file_name}'").proj()]
28+
tables = [
29+
Session,
30+
# NwbfileKachery, # Not used by default
31+
ElectrodeGroup,
32+
Electrode,
33+
Raw,
34+
SampleCount,
35+
DIOEvents,
36+
# SensorData, # Not used by default. Generates large files
37+
RawPosition,
38+
TaskEpoch,
39+
StateScriptFile,
40+
VideoFile,
41+
PositionSource,
42+
RawPosition,
43+
ImportedSpikeSorting,
44+
]
45+
error_constants = dict(
46+
dj_user=dj.config["database.user"],
47+
connection_id=dj.conn().connection_id,
48+
nwb_file_name=nwb_file_name,
49+
)
50+
51+
for table in tables:
52+
logger.info(f"Populating {table.__name__}...")
53+
try:
54+
table.populate(key)
55+
except Exception as e:
56+
InsertError.insert1(
57+
dict(
58+
**error_constants,
59+
table=table.__name__,
60+
error_type=type(e).__name__,
61+
error_message=str(e),
62+
error_raw=str(e),
63+
)
64+
)
65+
query = InsertError & error_constants
66+
if query:
67+
err_tables = query.fetch("table")
68+
logger.error(
69+
f"Errors occurred during population for {nwb_file_name}:\n\t"
70+
+ f"Failed tables {err_tables}\n\t"
71+
+ "See common_usage.InsertError for more details"
72+
)
73+
return query.fetch("KEY")
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
from spyglass.linearization.merge import LinearizedPositionOutput
1+
# CB: Circular import if only importing PositionOutput
2+
3+
# from spyglass.linearization.merge import LinearizedPositionOutput

0 commit comments

Comments
 (0)