Skip to content

Commit 8c6ad08

Browse files
CBroz1samuelbray32
andauthored
Distributed Analysis Tables (LorenFrankLab#1435)
* Split Mixin * WIP: AnalysisRegistry * WIP: dynamic externals * WIP: Replace master instances with cls/self * WIP: Fix cls/self references due to refactor * WIP: Fix tests * WIP: Fix inheritance * WIP: Add tests * WIP: copy files to master analysis table on export * WIP: fix cleanup * WIP: simplify cleanup orphan tracking * WIP: Dynamic fetch_nwb table tuple * WIP: Prevent multi fks to Analysis tables * WIP: documentation * Blackify * Fix file reservation * Allow update external of custom analysis tables * Fix missing prefix var in fetch.py * Fix test scope * Remove hanging tests * Fix test issue * Error messages * Fix var name in err msg * PR comments * Fix test * Update changelog * Add analysis file context manager * blackify * Update docs/src/Features/AnalysisTables.md Co-authored-by: Samuel Bray <[email protected]> * Remove AnalysisNwbile.cleanup safeguard * `share_data_to_kachery` copies from parent to master * Update export._copy_parent_to_master docstring * 'master' -> 'common' * Add typing import * Func rename * Fix failing test * PR comments * Fix teardown issue --------- Co-authored-by: Samuel Bray <[email protected]>
1 parent 67ca666 commit 8c6ad08

30 files changed

+5510
-1610
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import all foreign key references.
1616
- Delete extra pyscripts that were renamed # 1363
1717
- Add note on fetching changes to setup notebook #1371
1818
- Revise table field docstring heading and `mermaid` diagram generation #1402
19+
- Add pages for custom analysis tables and class inheritance structure #1435
1920

2021
### Infrastructure
2122

@@ -26,7 +27,9 @@ import all foreign key references.
2627
- Fix error from unlinked object in `AnalysisNwbfile.create` #1396
2728
- Sort `UserEnvironment` dict objects by key for consistency #1380
2829
- Fix typo in VideoFile.make #1427
29-
- Fix bug in TaskEpoch.make so that it correctly handles multi-row task tables from NWB #1433
30+
- Fix bug in TaskEpoch.make so that it correctly handles multi-row task tables
31+
from NWB #1433
32+
- Split `SpyglassMixin` into task-specific mixins #1435
3033

3134
### Infrastructure
3235

@@ -40,6 +43,7 @@ import all foreign key references.
4043
- Common
4144
- Add tables for storing optogenetic experiment information #1312
4245
- Remove wildcard matching in `Nwbfile().get_abs_path` #1382
46+
- Add custom/dynamic `AnalysisNwbfile` creation #1435
4347
- Decoding
4448
- Ensure results directory is created if it doesn't exist #1362
4549
- Position

docs/mkdocs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ nav:
7575
- Overview: Features/index.md
7676
- FigURL: Features/FigURL.md
7777
- Merge Tables: Features/Merge.md
78+
- Analysis Tables: Features/AnalysisTables.md
7879
- Export: Features/Export.md
7980
- Centralized Code: Features/Mixin.md
8081
- Recompute: Features/Recompute.md
@@ -84,6 +85,7 @@ nav:
8485
- Database Management: ForDevelopers/Management.md
8586
- Code Reuse: ForDevelopers/Reuse.md
8687
- Table Types: ForDevelopers/TableTypes.md
88+
- Classes: ForDevelopers/Classes.md
8789
- Understanding a Schema: ForDevelopers/Schema.md
8890
- Custom Pipelines: ForDevelopers/CustomPipelines.md
8991
- Using NWB: ForDevelopers/UsingNWB.md
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
# Analysis File Tables
2+
3+
Spyglass uses NWB files to store both raw experimental data and analysis
4+
results. This guide explains how to create and manage analysis files.
5+
6+
---
7+
8+
## What is AnalysisNwbfile?
9+
10+
`AnalysisNwbfile` is a DataJoint table that tracks analysis files containing
11+
your results (intermediate computations, final outputs, spike sorting, etc.).
12+
13+
**Key Features**:
14+
15+
- Creates derivative NWB files from your experimental sessions
16+
- Tracks analysis files in the database with checksums
17+
- Prevents accidental file modifications
18+
- Supports custom per-user tables for better performance
19+
20+
**Lifecycle**: Analysis files follow a three-step process:
21+
22+
```
23+
CREATE → POPULATE → REGISTER
24+
```
25+
26+
Once registered, files are **checksummed and immutable** - any modification
27+
will break the checksum and cause errors.
28+
29+
---
30+
31+
## Table of Contents
32+
33+
- [How to Use (Recommended)](#how-to-use-recommended)
34+
- [Using Custom Tables](#using-custom-tables)
35+
- [Legacy Pattern Comparison](#legacy-pattern-comparison)
36+
- [Troubleshooting](#troubleshooting)
37+
38+
---
39+
40+
## How to Use (Recommended)
41+
42+
Use the `.build()` method which provides a context manager that handles the
43+
CREATE → POPULATE → REGISTER lifecycle automatically.
44+
45+
### Basic Usage
46+
47+
```python
48+
from spyglass.common import AnalysisNwbfile
49+
import datajoint as dj
50+
import pandas as pd
51+
52+
schema = dj.schema("my_schema")
53+
54+
@schema
55+
class MyAnalysis(dj.Computed):
56+
definition = """
57+
-> SomeOtherTable
58+
---
59+
-> AnalysisNwbfile
60+
"""
61+
62+
def make(self, key):
63+
64+
my_data = ... # Your analysis data here
65+
66+
nwb_file_name = key["nwb_file_name"]
67+
with AnalysisNwbfile().build(nwb_file_name) as builder:
68+
# Add your data using helper methods
69+
builder.add_nwb_object( pd.DataFrame(my_data))
70+
71+
# File automatically registered on exit!
72+
analysis_file_name = builder.analysis_file_name
73+
74+
self.insert1({**key, "analysis_file_name": analysis_file_name})
75+
```
76+
77+
### Common Operations
78+
79+
**Adding multiple objects**:
80+
81+
```python
82+
with AnalysisNwbfile().build("session.nwb") as builder:
83+
builder.add_nwb_object(position_data, "position")
84+
builder.add_nwb_object(velocity_data, "velocity")
85+
builder.add_nwb_object(metadata, "analysis_params")
86+
```
87+
88+
**Adding spike sorting units**:
89+
90+
```python
91+
with AnalysisNwbfile().build("session.nwb") as builder:
92+
builder.add_units(
93+
units={1: [0.1, 0.5, 1.2], 2: [0.2, 0.6]},
94+
units_valid_times={1: [[0, 10]], 2: [[0, 10]]},
95+
units_sort_interval={1: [[0, 5]], 2: [[0, 5]]},
96+
metrics={"snr": {1: 5.2, 2: 3.8}}
97+
)
98+
```
99+
100+
**Direct NWB I/O** (for complex operations):
101+
102+
```python
103+
with AnalysisNwbfile().build("session.nwb") as builder:
104+
with builder.open_for_write() as io:
105+
nwbf = io.read()
106+
nwbf.add_unit(spike_times=[0.1, 0.5, 1.2], id=1)
107+
io.write(nwbf)
108+
```
109+
110+
**What happens on exception**:
111+
112+
```python
113+
try:
114+
with AnalysisNwbfile().build("session.nwb") as builder:
115+
builder.add_nwb_object(my_data, "results")
116+
raise ValueError("Something went wrong!")
117+
except ValueError:
118+
# File created but NOT registered - logged for cleanup
119+
pass
120+
```
121+
122+
---
123+
124+
## Using Custom Tables
125+
126+
By default, all users share the common `AnalysisNwbfile` table. When multiple
127+
users work concurrently, this can cause database lock contention and prevent
128+
new table declarations. To avoid this, Spyglass supports custom per-user
129+
analysis tables for custom analysis pipelines.
130+
131+
### How to Use
132+
133+
Import from `custom_nwbfile` instead of `common_nwbfile`:
134+
135+
```python
136+
import datajoint as dj
137+
138+
# Standard (shared table)
139+
from spyglass.common import AnalysisNwbfile
140+
# ---------------- OR ----------------
141+
# Custom (your own table - better performance)
142+
from spyglass.common.custom_nwbfile import AnalysisNwbfile
143+
144+
schema = dj.schema("my_schema")
145+
146+
# Usage is identical
147+
@schema
148+
class MyAnalysis(dj.Computed):
149+
definition = """
150+
-> SomeOtherTable
151+
---
152+
-> AnalysisNwbfile
153+
"""
154+
```
155+
156+
**What happens**: Creates a user-specific schema `{username}_nwbfile` automatically,
157+
providing lock isolation from other users.
158+
159+
**Team sharing**: Set `dj.config["custom"]["database.prefix"] = "teamname"` to
160+
share across a team (may still have some lock contention).
161+
162+
---
163+
164+
## Legacy Pattern Comparison
165+
166+
**Old way** (manual lifecycle):
167+
168+
```python
169+
def make(self, key):
170+
# CREATE
171+
file = AnalysisNwbfile().create("session.nwb")
172+
173+
# POPULATE
174+
AnalysisNwbfile().add_nwb_object(file, data, "results")
175+
176+
# REGISTER (easy to forget!)
177+
AnalysisNwbfile().add("session.nwb", file)
178+
```
179+
180+
**New way** (automatic):
181+
182+
```python
183+
with AnalysisNwbfile().build("session.nwb") as builder:
184+
builder.add_nwb_object(data, "results")
185+
# Auto-registered on exit
186+
```
187+
188+
---
189+
190+
## Troubleshooting
191+
192+
### Error: "Cannot call add_nwb_object() in state: REGISTERED"
193+
194+
**Cause**: You tried to use a helper method after the file was registered.
195+
196+
**Solution**: Use `build()` which prevents this error:
197+
198+
```python
199+
# ❌ Old way - easy to make this mistake
200+
file = AnalysisNwbfile().create("session.nwb")
201+
AnalysisNwbfile().add("session.nwb", file) # Registered!
202+
AnalysisNwbfile().add_nwb_object(file, data) # ❌ ERROR!
203+
204+
# ✅ New way - impossible to make this mistake
205+
with AnalysisNwbfile().build("session.nwb") as builder:
206+
builder.add_nwb_object(data, "results")
207+
# Auto-registered on exit - can't call methods after
208+
```
209+
210+
### Error: "File downloaded but did not pass checksum"
211+
212+
**Cause**: The file was modified after registration.
213+
214+
**Solutions**:
215+
216+
1. Delete and recreate the file
217+
2. Discuss why the file was modified with admin to modify the checksum
218+
219+
### Error: "Cannot call add_nwb_object() before entering context manager"
220+
221+
**Cause**: You tried to use builder methods outside the `with` block.
222+
223+
**Solution**:
224+
225+
```python
226+
# ❌ Wrong
227+
builder = AnalysisNwbfile().build("session.nwb")
228+
builder.add_nwb_object(data, "results") # ❌ ERROR!
229+
230+
# ✅ Correct
231+
with AnalysisNwbfile().build("session.nwb") as builder:
232+
builder.add_nwb_object(data, "results")
233+
```
234+
235+
### When should I use the builder vs. direct NWB I/O?
236+
237+
**Use the builder** (recommended for 90% of cases):
238+
239+
- Adding DataFrames or arrays
240+
- Adding spike sorting units
241+
- Standard analysis workflows
242+
243+
**Use direct I/O** (advanced, legacy code):
244+
245+
- Custom NWB processing modules
246+
- Complex file modifications
247+
- Maintaining backward compatibility
248+
249+
Even with direct I/O, you can still use the builder:
250+
251+
```python
252+
with AnalysisNwbfile().build("session.nwb") as builder:
253+
with builder.open_for_write() as io:
254+
# Direct PyNWB operations here
255+
pass
256+
```
257+
258+
---
259+
260+
## Related Documentation
261+
262+
- [Database Management](../ForDevelopers/Management.md) - Cleanup, maintenance, and custom analysis tables
263+
- [DataJoint External Storage](https://docs.datajoint.org/python/admin/5-blob-config.html)
264+
- [PyNWB File I/O](https://pynwb.readthedocs.io/en/stable/tutorials/general/file.html)

docs/src/Features/Mixin.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ should be fetched from `Nwbfile` or an analysis file should be fetched from
4949
`AnalysisNwbfile`. If neither is foreign-key-referenced, the function will refer
5050
to a `_nwb_table` attribute.
5151

52+
**Custom Analysis File Tables**: Spyglass supports individualized
53+
`AnalysisNwbfile` tables to address transaction lock contention in multi-team
54+
environments. The `fetch_nwb()` method automatically detects whether your table
55+
references the common `AnalysisNwbfile` table or a custom team-specific table
56+
and fetches from the appropriate location. See [Custom Analysis Tables](../ForDevelopers/Management.md#custom-analysis-tables)
57+
for details on using custom analysis file tables.
58+
5259
## Long-Distance Restrictions
5360

5461
In complicated pipelines like Spyglass, there are often tables that 'bury' their

docs/src/Features/index.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
This directory contains a series of explainers on tools that have been added to
44
Spyglass.
55

6+
- [Analysis File Tables](./AnalysisTables.md) - Guide to creating and managing
7+
analysis NWB files.
68
- [Export](./Export.md) - How to export an analysis.
79
- [FigURL](./FigURL.md) - How to use FigURL to share figures.
8-
- [Merge Tables](./Merge.md) - Tables for pipeline versioning.
10+
- [Merge Tables](./Merge.md) - Tables for pipeline versioning
911
- [Mixin](./Mixin.md) - Spyglass-specific functionalities to DataJoint tables,
1012
including fetching NWB files, long-distance restrictions, and permission
1113
checks on delete operations.

0 commit comments

Comments
 (0)