From 7dee90a9562c8505fb76dc5cb19aac31829f87c8 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Sun, 19 Sep 2021 13:00:44 -0500 Subject: [PATCH 01/59] accommodate different ephys modes (chronic, no-curation, etc.) --- tests/__init__.py | 28 +++++++++++++++++++--------- tests/test_populate.py | 26 +++++++++++++++++++++----- workflow_array_ephys/ingest.py | 20 ++++++++++++++------ workflow_array_ephys/pipeline.py | 14 +++++++++++++- 4 files changed, 67 insertions(+), 21 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 86f9d5bf..495a33e4 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -13,7 +13,7 @@ # ------------------- SOME CONSTANTS ------------------- -_tear_down = True +_tear_down = False test_user_data_dir = pathlib.Path('./tests/user_data') test_user_data_dir.mkdir(exist_ok=True) @@ -84,7 +84,8 @@ def pipeline(): 'ephys': pipeline.ephys, 'probe': pipeline.probe, 'session': pipeline.session, - 'get_ephys_root_data_dir': pipeline.get_ephys_root_data_dir} + 'get_ephys_root_data_dir': pipeline.get_ephys_root_data_dir, + 'ephys_mode': pipeline.ephys_mode} if _tear_down: pipeline.subject.Subject.delete() @@ -196,7 +197,10 @@ def kilosort_paramset(pipeline): # doing the insert here as well, since most of the test will require this paramset inserted ephys.ClusteringParamSet.insert_new_params( - 'kilosort2', 0, 'Spike sorting using Kilosort2', params_ks) + processing_method='kilosort2', + paramset_desc='Spike sorting using Kilosort2', + params=params_ks, + paramset_idx=0) yield params_ks @@ -230,6 +234,7 @@ def clustering_tasks(pipeline, kilosort_paramset, ephys_recordings): kilosort_dir = next(recording_dir.rglob('spike_times.npy')).parent ephys.ClusteringTask.insert1({**ephys_rec_key, 'paramset_idx': 0, + 'task_mode': 'load', 'clustering_output_dir': kilosort_dir.as_posix()}, skip_duplicates=True) @@ -253,12 +258,17 @@ def clustering(clustering_tasks, pipeline): @pytest.fixture def curations(clustering, pipeline): - ephys = pipeline['ephys'] + ephys_mode = pipeline['ephys_mode'] - for key in (ephys.ClusteringTask - ephys.Curation).fetch('KEY'): - ephys.Curation().create1_from_clustering_task(key) + if ephys_mode == 'no-curation': + return + else: + ephys = pipeline['ephys'] - yield + for key in (ephys.ClusteringTask - ephys.Curation).fetch('KEY'): + ephys.Curation().create1_from_clustering_task(key) - if _tear_down: - ephys.Curation.delete() + yield + + if _tear_down: + ephys.Curation.delete() diff --git a/tests/test_populate.py b/tests/test_populate.py index aaac1520..b840cd3e 100644 --- a/tests/test_populate.py +++ b/tests/test_populate.py @@ -80,19 +80,19 @@ def test_curated_clustering_populate(curations, pipeline, testdata_paths): ephys = pipeline['ephys'] rel_path = testdata_paths['npx3A-p1-ks'] - curation_key = (ephys.Curation & f'curation_output_dir LIKE "%{rel_path}"').fetch1('KEY') + curation_key = _get_curation_key(rel_path, pipeline) ephys.CuratedClustering.populate(curation_key) assert len(ephys.CuratedClustering.Unit & curation_key & 'cluster_quality_label = "good"') == 76 rel_path = testdata_paths['oe_npx3B-ks'] - curation_key = (ephys.Curation & f'curation_output_dir LIKE "%{rel_path}"').fetch1('KEY') + curation_key = _get_curation_key(rel_path, pipeline) ephys.CuratedClustering.populate(curation_key) assert len(ephys.CuratedClustering.Unit & curation_key & 'cluster_quality_label = "good"') == 68 rel_path = testdata_paths['npx3B-p1-ks'] - curation_key = (ephys.Curation & f'curation_output_dir LIKE "%{rel_path}"').fetch1('KEY') + curation_key = _get_curation_key(rel_path, pipeline) ephys.CuratedClustering.populate(curation_key) assert len(ephys.CuratedClustering.Unit & curation_key & 'cluster_quality_label = "good"') == 55 @@ -102,7 +102,7 @@ def test_waveform_populate_npx3B_OpenEphys(curations, pipeline, testdata_paths): ephys = pipeline['ephys'] rel_path = testdata_paths['oe_npx3B-ks'] - curation_key = (ephys.Curation & f'curation_output_dir LIKE "%{rel_path}"').fetch1('KEY') + curation_key = _get_curation_key(rel_path, pipeline) ephys.CuratedClustering.populate(curation_key) ephys.WaveformSet.populate(curation_key) @@ -116,7 +116,7 @@ def test_waveform_populate_npx3B_SpikeGLX(curations, pipeline, testdata_paths): ephys = pipeline['ephys'] rel_path = testdata_paths['npx3B-p1-ks'] - curation_key = (ephys.Curation & f'curation_output_dir LIKE "%{rel_path}"').fetch1('KEY') + curation_key = _get_curation_key(rel_path, pipeline) ephys.CuratedClustering.populate(curation_key) ephys.WaveformSet.populate(curation_key) @@ -124,3 +124,19 @@ def test_waveform_populate_npx3B_SpikeGLX(curations, pipeline, testdata_paths): 'peak_electrode_waveform')) assert waveforms.shape == (150, 64) + + +# ---- HELPER FUNCTIONS ---- + +def _get_curation_key(output_relative_path, pipeline): + ephys = pipeline['ephys'] + ephys_mode = pipeline['ephys_mode'] + + if ephys_mode == 'no-curation': + EphysCuration = ephys.Clustering + output_dir_attr_name = 'clustering_output_dir' + else: + EphysCuration = ephys.Curation + output_dir_attr_name = 'curation_output_dir' + + return (EphysCuration & f'{output_dir_attr_name} LIKE "%{output_relative_path}"').fetch1('KEY') diff --git a/workflow_array_ephys/ingest.py b/workflow_array_ephys/ingest.py index cbd0405f..2fb74fb5 100644 --- a/workflow_array_ephys/ingest.py +++ b/workflow_array_ephys/ingest.py @@ -4,6 +4,7 @@ from workflow_array_ephys.pipeline import subject, ephys, probe, session from workflow_array_ephys.paths import get_ephys_root_data_dir +from workflow_array_ephys.pipeline import ephys_mode from element_array_ephys.readers import spikeglx, openephys @@ -72,15 +73,22 @@ def ingest_sessions(session_csv_path='./user_data/sessions.csv'): session_dir_list.append({**session_key, 'session_dir': sess_dir.relative_to(root_data_dir).as_posix()}) probe_insertion_list.extend([{**session_key, **insertion} for insertion in insertions]) - print(f'\n---- Insert {len(session_list)} entry(s) into session.Session ----') - session.Session.insert(session_list) - session.SessionDirectory.insert(session_dir_list) - print(f'\n---- Insert {len(probe_list)} entry(s) into probe.Probe ----') probe.Probe.insert(probe_list) - print(f'\n---- Insert {len(probe_insertion_list)} entry(s) into ephys.ProbeInsertion ----') - ephys.ProbeInsertion.insert(probe_insertion_list) + if ephys_mode == 'chronic': + print(f'\n---- Insert {len(probe_insertion_list)} entry(s) into ephys.ProbeInsertion ----') + ephys.ProbeInsertion.insert(probe_insertion_list, + ignore_extra_fields=True, skip_duplicates=True) + print(f'\n---- Insert {len(session_list)} entry(s) into session.Session ----') + session.Session.insert(session_list) + session.SessionDirectory.insert(session_dir_list) + else: + print(f'\n---- Insert {len(session_list)} entry(s) into session.Session ----') + session.Session.insert(session_list) + session.SessionDirectory.insert(session_dir_list) + print(f'\n---- Insert {len(probe_insertion_list)} entry(s) into ephys.ProbeInsertion ----') + ephys.ProbeInsertion.insert(probe_insertion_list) print('\n---- Successfully completed workflow_array_ephys/ingest.py ----') diff --git a/workflow_array_ephys/pipeline.py b/workflow_array_ephys/pipeline.py index 93bbeea6..a411483e 100644 --- a/workflow_array_ephys/pipeline.py +++ b/workflow_array_ephys/pipeline.py @@ -1,8 +1,9 @@ import datajoint as dj +import os from element_animal import subject from element_lab import lab from element_session import session -from element_array_ephys import probe, ephys +from element_array_ephys import probe from element_animal.subject import Subject from element_lab.lab import Source, Lab, Protocol, User, Project @@ -15,6 +16,17 @@ db_prefix = dj.config['custom'].get('database.prefix', '') +# ------------- Import the configured "ephys mode" ------------- +ephys_mode = os.getenv('EPHYS_MODE', + dj.config['custom'].get('ephys_mode', 'acute')) +if ephys_mode == 'acute': + from element_array_ephys import ephys +elif ephys_mode == 'chronic': + from element_array_ephys import ephys_chronic as ephys +elif ephys_mode == 'no-curation': + from element_array_ephys import ephys_no_curation as ephys +else: + raise ValueError(f'Unknown ephys mode: {ephys_mode}') # ------------- Activate "lab", "subject", "session" schema ------------- From 582380d5873999215370d311c3698150d5f0a765 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 30 Sep 2021 13:42:08 -0500 Subject: [PATCH 02/59] minor bugfix --- tests/__init__.py | 4 +++- tests/test_populate.py | 2 +- workflow_array_ephys/ingest.py | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 495a33e4..e37b202f 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -36,6 +36,8 @@ def dj_config(): dj.config['safemode'] = False dj.config['custom'] = { + 'ephys_mode': (os.environ.get('EPHYS_MODE') + or dj.config['custom']['ephys_mode']), 'database.prefix': (os.environ.get('DATABASE_PREFIX') or dj.config['custom']['database.prefix']), 'ephys_root_data_dir': (os.environ.get('EPHYS_ROOT_DATA_DIR') @@ -261,7 +263,7 @@ def curations(clustering, pipeline): ephys_mode = pipeline['ephys_mode'] if ephys_mode == 'no-curation': - return + yield else: ephys = pipeline['ephys'] diff --git a/tests/test_populate.py b/tests/test_populate.py index b840cd3e..58f49942 100644 --- a/tests/test_populate.py +++ b/tests/test_populate.py @@ -133,7 +133,7 @@ def _get_curation_key(output_relative_path, pipeline): ephys_mode = pipeline['ephys_mode'] if ephys_mode == 'no-curation': - EphysCuration = ephys.Clustering + EphysCuration = ephys.ClusteringTask output_dir_attr_name = 'clustering_output_dir' else: EphysCuration = ephys.Curation diff --git a/workflow_array_ephys/ingest.py b/workflow_array_ephys/ingest.py index 2fb74fb5..bf720ce9 100644 --- a/workflow_array_ephys/ingest.py +++ b/workflow_array_ephys/ingest.py @@ -11,7 +11,7 @@ def ingest_subjects(subject_csv_path='./user_data/subjects.csv'): # -------------- Insert new "Subject" -------------- - with open(subject_csv_path, newline= '') as f: + with open(subject_csv_path, newline='') as f: input_subjects = list(csv.DictReader(f, delimiter=',')) print(f'\n---- Insert {len(input_subjects)} entry(s) into subject.Subject ----') @@ -33,7 +33,7 @@ def ingest_sessions(session_csv_path='./user_data/sessions.csv'): session_datetimes, insertions = [], [] # search session dir and determine acquisition software - for ephys_pattern, ephys_acq_type in zip(['*.ap.meta', '*.oebin'], ['SpikeGLX', 'OpenEphys']): + for ephys_pattern, ephys_acq_type in zip(['*.ap.meta', '*.oebin'], ['SpikeGLX', 'Open Ephys']): ephys_meta_filepaths = [fp for fp in sess_dir.rglob(ephys_pattern)] if len(ephys_meta_filepaths): acq_software = ephys_acq_type @@ -55,7 +55,7 @@ def ingest_sessions(session_csv_path='./user_data/sessions.csv'): insertions.append({'probe': spikeglx_meta.probe_SN, 'insertion_number': int(probe_number)}) session_datetimes.append(spikeglx_meta.recording_time) - elif acq_software == 'OpenEphys': + elif acq_software == 'Open Ephys': loaded_oe = openephys.OpenEphys(sess_dir) session_datetimes.append(loaded_oe.experiment.datetime) for probe_idx, oe_probe in enumerate(loaded_oe.probes.values()): From 8e8ec4e1a546357e38453058f6a3d8b7690fc029 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Tue, 15 Mar 2022 17:48:38 -0500 Subject: [PATCH 03/59] add trialized CSVs and ingestion functions --- .gitignore | 3 +- docker/Dockerfile.dev | 3 + docker/docker-compose-dev.yaml | 1 + docker/docker-compose-test.yaml | 1 + tests/__init__.py | 3 +- trial_diagram.svg | 242 ++++++++++++++++++++++++++++++ trial_diagram2.svg | 242 ++++++++++++++++++++++++++++++ user_data/alignments.csv | 4 + user_data/behavior_recordings.csv | 3 + user_data/blocks.csv | 5 + user_data/events.csv | 53 +++++++ user_data/sessions.csv | 3 +- user_data/subjects.csv | 1 + user_data/trials.csv | 41 +++++ workflow_array_ephys/ingest.py | 77 ++++++++-- workflow_array_ephys/pipeline.py | 7 + 16 files changed, 672 insertions(+), 17 deletions(-) create mode 100644 trial_diagram.svg create mode 100644 trial_diagram2.svg create mode 100644 user_data/alignments.csv create mode 100644 user_data/behavior_recordings.csv create mode 100644 user_data/blocks.csv create mode 100644 user_data/events.csv create mode 100644 user_data/trials.csv diff --git a/.gitignore b/.gitignore index 2e13d1eb..fc60462d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ __pycache__/ *.py[cod] *$py.class +.pytest_ca*/ # C extensions *.so @@ -123,4 +124,4 @@ Diagram.ipynb .vscode/settings.json # notes -temp* \ No newline at end of file +temp* diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index c5d54534..433282b6 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -8,6 +8,7 @@ RUN /entrypoint.sh echo "Installed dependencies." RUN mkdir /main/element-lab \ /main/element-animal \ /main/element-session \ + /main/element-trial \ /main/element-array-ephys \ /main/workflow-array-ephys @@ -15,6 +16,7 @@ RUN mkdir /main/element-lab \ COPY --chown=anaconda:anaconda ./element-lab /main/element-lab COPY --chown=anaconda:anaconda ./element-animal /main/element-animal COPY --chown=anaconda:anaconda ./element-session /main/element-session +COPY --chown=anaconda:anaconda ./element-trial /main/element-trial COPY --chown=anaconda:anaconda ./element-array-ephys /main/element-array-ephys COPY --chown=anaconda:anaconda ./workflow-array-ephys /main/workflow-array-ephys @@ -22,6 +24,7 @@ COPY --chown=anaconda:anaconda ./workflow-array-ephys /main/workflow-array-ephys RUN pip install -e /main/element-lab RUN pip install -e /main/element-animal RUN pip install -e /main/element-session +RUN pip install -e /main/element-trial RUN pip install -e /main/element-array-ephys RUN pip install -e /main/workflow-array-ephys RUN pip install -r /main/workflow-array-ephys/requirements_test.txt diff --git a/docker/docker-compose-dev.yaml b/docker/docker-compose-dev.yaml index 9420f662..43986ab2 100644 --- a/docker/docker-compose-dev.yaml +++ b/docker/docker-compose-dev.yaml @@ -26,6 +26,7 @@ services: - ../../element-lab:/main/element-lab - ../../element-animal:/main/element-animal - ../../element-session:/main/element-session + - ../../element-trial:/main/element-trial - ../../element-array-ephys:/main/element-array-ephys - ..:/main/workflow-array-ephys depends_on: diff --git a/docker/docker-compose-test.yaml b/docker/docker-compose-test.yaml index 08c94d0f..1688e000 100644 --- a/docker/docker-compose-test.yaml +++ b/docker/docker-compose-test.yaml @@ -40,6 +40,7 @@ services: - ../../element-lab:/main/element-lab - ../../element-animal:/main/element-animal - ../../element-session:/main/element-session + - ../../element-trial:/main/element-trial - ../../element-array-ephys:/main/element-array-ephys - ..:/main/workflow-array-ephys depends_on: diff --git a/tests/__init__.py b/tests/__init__.py index 219f8545..bf3d5cdb 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -10,14 +10,13 @@ import pathlib import datajoint as dj -import workflow_array_ephys from workflow_array_ephys.paths import get_ephys_root_data_dir from element_interface.utils import find_full_path # ------------------- SOME CONSTANTS ------------------- -_tear_down = True +_tear_down = False verbose = False test_user_data_dir = pathlib.Path('./tests/user_data') diff --git a/trial_diagram.svg b/trial_diagram.svg new file mode 100644 index 00000000..ac85be52 --- /dev/null +++ b/trial_diagram.svg @@ -0,0 +1,242 @@ + + + + + + + + + +2 + +2 + + + +event.AlignmentEvent + + +event.AlignmentEvent + + + + + +2->event.AlignmentEvent + + + + +1 + +1 + + + +1->event.AlignmentEvent + + + + +0 + +0 + + + +0->event.AlignmentEvent + + + + +trial.Block.Attribute + + +trial.Block.Attribute + + + + + +event.BehaviorRecording.File + + +event.BehaviorRecording.File + + + + + +trial.TrialType + + +trial.TrialType + + + + + +trial.Trial + + +trial.Trial + + + + + +trial.TrialType->trial.Trial + + + + +trial.Trial.Attribute + + +trial.Trial.Attribute + + + + + +trial.Trial->trial.Trial.Attribute + + + + +trial.BlockTrial + + +trial.BlockTrial + + + + + +trial.Trial->trial.BlockTrial + + + + +trial.TrialEvent + + +trial.TrialEvent + + + + + +trial.Trial->trial.TrialEvent + + + + +trial.Block + + +trial.Block + + + + + +trial.Block->trial.Block.Attribute + + + + +trial.Block->trial.BlockTrial + + + + +event.EventType + + +event.EventType + + + + + +event.EventType->2 + + + + +event.EventType->1 + + + + +event.EventType->0 + + + + +event.Event + + +event.Event + + + + + +event.EventType->event.Event + + + + +event.Event->trial.TrialEvent + + + + +Session + + +Session + + + + + +event.BehaviorRecording + + +event.BehaviorRecording + + + + + +Session->event.BehaviorRecording + + + + +event.BehaviorRecording->event.BehaviorRecording.File + + + + +event.BehaviorRecording->trial.Trial + + + + +event.BehaviorRecording->trial.Block + + + + +event.BehaviorRecording->event.Event + + + + diff --git a/trial_diagram2.svg b/trial_diagram2.svg new file mode 100644 index 00000000..683e9f2f --- /dev/null +++ b/trial_diagram2.svg @@ -0,0 +1,242 @@ + + + + + + + + + +1 + +1 + + + +AlignmentEvent + + +AlignmentEvent + + + + + +1->AlignmentEvent + + + + +0 + +0 + + + +0->AlignmentEvent + + + + +2 + +2 + + + +2->AlignmentEvent + + + + +TrialEvent + + +TrialEvent + + + + + +Trial.TrialAttribute + + +Trial.TrialAttribute + + + + + +Session + + +Session + + + + + +BehaviorRecording + + +BehaviorRecording + + + + + +Session->BehaviorRecording + + + + +Block + + +Block + + + + + +BehaviorRecording->Block + + + + +Trial + + +Trial + + + + + +BehaviorRecording->Trial + + + + +BehaviorRecording.File + + +BehaviorRecording.File + + + + + +BehaviorRecording->BehaviorRecording.File + + + + +Event + + +Event + + + + + +BehaviorRecording->Event + + + + +BlockTrial + + +BlockTrial + + + + + +Block->BlockTrial + + + + +Block.BlockAttribute + + +Block.BlockAttribute + + + + + +Block->Block.BlockAttribute + + + + +Trial->TrialEvent + + + + +Trial->Trial.TrialAttribute + + + + +Trial->BlockTrial + + + + +Event->TrialEvent + + + + +TrialType + + +TrialType + + + + + +TrialType->Trial + + + + +EventType + + +EventType + + + + + +EventType->1 + + + + +EventType->0 + + + + +EventType->2 + + + + +EventType->Event + + + + diff --git a/user_data/alignments.csv b/user_data/alignments.csv new file mode 100644 index 00000000..e2f1621f --- /dev/null +++ b/user_data/alignments.csv @@ -0,0 +1,4 @@ +alignment_name,alignment_event_type,alignment_time_shift,start_event_type,start_time_shift,end_event_type,end_time_shift +left_button,left,0,left,-3,left,3 +center_button,center,0,center,-3,center,3 +right_button,right,0,right,-3,right,3 diff --git a/user_data/behavior_recordings.csv b/user_data/behavior_recordings.csv new file mode 100644 index 00000000..4ce21f1c --- /dev/null +++ b/user_data/behavior_recordings.csv @@ -0,0 +1,3 @@ +subject,session_datetime,filepath +subject5,2018-07-03 20:32:28,./user_data/trials.csv +subject5,2018-07-03 20:32:28,./user_data/events.csv diff --git a/user_data/blocks.csv b/user_data/blocks.csv new file mode 100644 index 00000000..b371e9e9 --- /dev/null +++ b/user_data/blocks.csv @@ -0,0 +1,5 @@ +subject,session_datetime,block_id,block_start_time,block_stop_time,attribute_name,attribute_value +subject5,2018-07-03 20:32:28,1,2,86,type,light +subject5,2018-07-03 20:32:28,2,86,170,type,dark +subject5,2018-07-03 20:32:28,3,170,254,type,light +subject5,2018-07-03 20:32:28,4,254,338,type,dark diff --git a/user_data/events.csv b/user_data/events.csv new file mode 100644 index 00000000..3e74d6bb --- /dev/null +++ b/user_data/events.csv @@ -0,0 +1,53 @@ +subject,session_datetime,trial_id,event_id,event_start_time,event_type +subject5,2018-07-03 20:32:28,1,1,8.812,center +subject5,2018-07-03 20:32:28,1,2,4.647,left +subject5,2018-07-03 20:32:28,1,3,3.733,right +subject5,2018-07-03 20:32:28,3,1,21.198,left +subject5,2018-07-03 20:32:28,3,2,19.121,left +subject5,2018-07-03 20:32:28,3,3,25.344,right +subject5,2018-07-03 20:32:28,4,1,26.769,right +subject5,2018-07-03 20:32:28,4,2,27.073,right +subject5,2018-07-03 20:32:28,4,3,32.411,center +subject5,2018-07-03 20:32:28,5,1,35.048,right +subject5,2018-07-03 20:32:28,5,2,35.091,left +subject5,2018-07-03 20:32:28,9,1,72.032,right +subject5,2018-07-03 20:32:28,10,1,83.279,left +subject5,2018-07-03 20:32:28,11,1,90.027,right +subject5,2018-07-03 20:32:28,14,1,115.413,center +subject5,2018-07-03 20:32:28,14,2,111.274,center +subject5,2018-07-03 20:32:28,15,1,125.791,left +subject5,2018-07-03 20:32:28,16,1,135.069,left +subject5,2018-07-03 20:32:28,16,2,128.56,center +subject5,2018-07-03 20:32:28,16,3,133.69,center +subject5,2018-07-03 20:32:28,19,1,158.744,center +subject5,2018-07-03 20:32:28,19,2,152.561,right +subject5,2018-07-03 20:32:28,22,1,186.771,center +subject5,2018-07-03 20:32:28,22,2,185.484,right +subject5,2018-07-03 20:32:28,22,3,182.107,left +subject5,2018-07-03 20:32:28,23,1,192.072,right +subject5,2018-07-03 20:32:28,23,2,187.923,center +subject5,2018-07-03 20:32:28,24,1,198.2,right +subject5,2018-07-03 20:32:28,24,2,198.436,left +subject5,2018-07-03 20:32:28,26,1,213.439,right +subject5,2018-07-03 20:32:28,26,2,214.028,center +subject5,2018-07-03 20:32:28,26,3,215.391,right +subject5,2018-07-03 20:32:28,29,1,237.248,left +subject5,2018-07-03 20:32:28,31,1,261.215,left +subject5,2018-07-03 20:32:28,33,1,277.311,center +subject5,2018-07-03 20:32:28,33,2,271.487,center +subject5,2018-07-03 20:32:28,33,3,277.392,center +subject5,2018-07-03 20:32:28,34,1,282.147,left +subject5,2018-07-03 20:32:28,34,2,283.893,right +subject5,2018-07-03 20:32:28,35,1,288.407,center +subject5,2018-07-03 20:32:28,35,2,291.126,right +subject5,2018-07-03 20:32:28,35,3,287.522,center +subject5,2018-07-03 20:32:28,36,1,301.118,left +subject5,2018-07-03 20:32:28,36,2,298.515,right +subject5,2018-07-03 20:32:28,37,1,312.081,right +subject5,2018-07-03 20:32:28,37,2,305.579,left +subject5,2018-07-03 20:32:28,37,3,309.113,left +subject5,2018-07-03 20:32:28,39,1,324.653,left +subject5,2018-07-03 20:32:28,39,2,322.778,left +subject5,2018-07-03 20:32:28,40,1,336.635,center +subject5,2018-07-03 20:32:28,40,2,332.538,right +subject5,2018-07-03 20:32:28,40,3,331.889,right diff --git a/user_data/sessions.csv b/user_data/sessions.csv index 1983068b..4e2b93c2 100644 --- a/user_data/sessions.csv +++ b/user_data/sessions.csv @@ -1,2 +1,3 @@ subject,session_dir -subject6,/tmp/test_data/workflow-array-ephys-test-set/subject6/session1/ +subject5,subject5/session1/ +subject6,subject6/session1/ diff --git a/user_data/subjects.csv b/user_data/subjects.csv index f5d2dc25..390d04a6 100644 --- a/user_data/subjects.csv +++ b/user_data/subjects.csv @@ -1,2 +1,3 @@ subject,sex,subject_birth_date,subject_description +subject5,F,2020-01-01,rich subject6,M,2020-01-03,hneih_E105 diff --git a/user_data/trials.csv b/user_data/trials.csv new file mode 100644 index 00000000..b8bf8975 --- /dev/null +++ b/user_data/trials.csv @@ -0,0 +1,41 @@ +subject,session_datetime,block_id,trial_id,trial_start_time,trial_stop_time,trial_type,attribute_name,attribute_value +subject5,2018-07-03 20:32:28,1,1,2.043,10.043,stim,lumen,633 +subject5,2018-07-03 20:32:28,1,2,10.508,18.508,ctrl,lumen,840 +subject5,2018-07-03 20:32:28,1,3,18.7,26.7,ctrl,lumen,872 +subject5,2018-07-03 20:32:28,1,4,26.707,34.707,ctrl,lumen,539 +subject5,2018-07-03 20:32:28,1,5,34.715,42.715,stim,lumen,929 +subject5,2018-07-03 20:32:28,1,6,42.806,50.806,stim,lumen,991 +subject5,2018-07-03 20:32:28,1,7,50.839,58.839,ctrl,lumen,970 +subject5,2018-07-03 20:32:28,1,8,59.196,67.196,ctrl,lumen,990 +subject5,2018-07-03 20:32:28,1,9,67.31,75.31,stim,lumen,745 +subject5,2018-07-03 20:32:28,1,10,75.772,83.772,ctrl,lumen,818 +subject5,2018-07-03 20:32:28,2,11,86.082,94.082,stim,lumen,0 +subject5,2018-07-03 20:32:28,2,12,94.087,102.087,stim,lumen,0 +subject5,2018-07-03 20:32:28,2,13,102.183,110.183,stim,lumen,0 +subject5,2018-07-03 20:32:28,2,14,110.526,118.526,stim,lumen,0 +subject5,2018-07-03 20:32:28,2,15,118.844,126.844,stim,lumen,0 +subject5,2018-07-03 20:32:28,2,16,127.22,135.22,stim,lumen,0 +subject5,2018-07-03 20:32:28,2,17,135.319,143.319,ctrl,lumen,0 +subject5,2018-07-03 20:32:28,2,18,143.357,151.357,stim,lumen,0 +subject5,2018-07-03 20:32:28,2,19,151.54,159.54,ctrl,lumen,0 +subject5,2018-07-03 20:32:28,2,20,159.8,167.8,stim,lumen,0 +subject5,2018-07-03 20:32:28,3,21,170.146,178.146,ctrl,lumen,551 +subject5,2018-07-03 20:32:28,3,22,178.548,186.548,ctrl,lumen,701 +subject5,2018-07-03 20:32:28,3,23,186.909,194.909,stim,lumen,665 +subject5,2018-07-03 20:32:28,3,24,195.124,203.124,ctrl,lumen,745 +subject5,2018-07-03 20:32:28,3,25,203.344,211.344,ctrl,lumen,695 +subject5,2018-07-03 20:32:28,3,26,211.788,219.788,stim,lumen,684 +subject5,2018-07-03 20:32:28,3,27,220.009,228.009,ctrl,lumen,608 +subject5,2018-07-03 20:32:28,3,28,228.359,236.359,stim,lumen,913 +subject5,2018-07-03 20:32:28,3,29,236.768,244.768,stim,lumen,650 +subject5,2018-07-03 20:32:28,3,30,244.884,252.884,ctrl,lumen,571 +subject5,2018-07-03 20:32:28,4,31,254.437,262.437,stim,lumen,0 +subject5,2018-07-03 20:32:28,4,32,262.918,270.918,stim,lumen,0 +subject5,2018-07-03 20:32:28,4,33,270.95,278.95,ctrl,lumen,0 +subject5,2018-07-03 20:32:28,4,34,279.121,287.121,stim,lumen,0 +subject5,2018-07-03 20:32:28,4,35,287.194,295.194,ctrl,lumen,0 +subject5,2018-07-03 20:32:28,4,36,295.661,303.661,ctrl,lumen,0 +subject5,2018-07-03 20:32:28,4,37,304.029,312.029,stim,lumen,0 +subject5,2018-07-03 20:32:28,4,38,312.486,320.486,stim,lumen,0 +subject5,2018-07-03 20:32:28,4,39,320.576,328.576,ctrl,lumen,0 +subject5,2018-07-03 20:32:28,4,40,328.971,336.971,stim,lumen,0 diff --git a/workflow_array_ephys/ingest.py b/workflow_array_ephys/ingest.py index 9ced0519..d39589cd 100644 --- a/workflow_array_ephys/ingest.py +++ b/workflow_array_ephys/ingest.py @@ -1,28 +1,47 @@ import csv import re -from workflow_array_ephys.pipeline import subject, ephys, probe, session +from workflow_array_ephys.pipeline import subject, ephys, probe, session, trial, event from workflow_array_ephys.paths import get_ephys_root_data_dir from element_array_ephys.readers import spikeglx, openephys from element_interface.utils import find_root_directory, find_full_path -def ingest_subjects(subject_csv_path='./user_data/subjects.csv', verbose=True): +def ingest_general(csvs, tables, skip_duplicates=True, verbose=True, + allow_direct_insert=False): + """ + Inserts data from a series of csvs into their corresponding table: + e.g., ingest_general(['./lab_data.csv', './proj_data.csv'], + [lab.Lab(),lab.Project()] + ingest_general(csvs, tables, skip_duplicates=True) + :param csvs: list of relative paths to CSV files. CSV are delimited by commas. + :param tables: list of datajoint tables with () + :param verbose: print number inserted (i.e., table length change) + """ + for csv_filepath, table in zip(csvs, tables): + with open(csv_filepath, newline='') as f: + data = list(csv.DictReader(f, delimiter=',')) + if verbose: + prev_len = len(table) + table.insert(data, skip_duplicates=skip_duplicates, + # Ignore extra fields because some CSVs feed multiple tables + ignore_extra_fields=True, allow_direct_insert=allow_direct_insert) + if verbose: + insert_len = len(table) - prev_len # report length change + print(f'\n---- Inserting {insert_len} entry(s) ' + + f'into {table.table_name} ----') + + +def ingest_subjects(subject_csv_path='./user_data/subjects.csv', + skip_duplicates=True, verbose=True): """ Ingest subjects listed in the subject column of ./user_data/subjects.csv """ - # -------------- Insert new "Subject" -------------- - with open(subject_csv_path, newline='') as f: - input_subjects = list(csv.DictReader(f, delimiter=',')) - if verbose: - previous_length = len(subject.Subject.fetch()) - subject.Subject.insert(input_subjects, skip_duplicates=True) - if verbose: - insert_length = len(subject.Subject.fetch()) - previous_length - print(f'\n---- Insert {insert_length} entry(s) into ' - + 'subject.Subject ----') - print('\n---- Successfully completed ingest_subjects ----') + csvs = [subject_csv_path] + tables = [subject.Subject()] + + ingest_general(csvs, tables, skip_duplicates=skip_duplicates, verbose=verbose) def ingest_sessions(session_csv_path='./user_data/sessions.csv', verbose=True): @@ -115,6 +134,38 @@ def ingest_sessions(session_csv_path='./user_data/sessions.csv', verbose=True): print('\n---- Successfully completed ingest_subjects ----') +def ingest_events(recording_csv_path='./user_data/behavior_recordings.csv', + block_csv_path='./user_data/blocks.csv', + trial_csv_path='./user_data/trials.csv', + event_csv_path='./user_data/events.csv', + skip_duplicates=True, verbose=True): + csvs = [recording_csv_path, recording_csv_path, + block_csv_path, block_csv_path, + trial_csv_path, trial_csv_path, trial_csv_path, + trial_csv_path, + event_csv_path] + tables = [event.BehaviorRecording(), event.BehaviorRecording.File(), + trial.Block(), trial.Block.Attribute(), + trial.TrialType(), trial.Trial(), trial.Trial.Attribute(), + trial.BlockTrial(), + event.EventType(), event.Event()] + + # Allow direct insert required bc element-trial has Imported that should be Manual + ingest_general(csvs, tables, skip_duplicates=skip_duplicates, verbose=verbose, + allow_direct_insert=True) + + +def ingest_alignment(alignment_csv_path='./user_data/alignments.csv', + skip_duplicates=True, verbose=True): + + csvs = [alignment_csv_path] + tables = [event.AlignmentEvent()] + + ingest_general(csvs, tables, skip_duplicates=skip_duplicates, verbose=verbose) + + if __name__ == '__main__': ingest_subjects() ingest_sessions() + ingest_events() + ingest_alignment() diff --git a/workflow_array_ephys/pipeline.py b/workflow_array_ephys/pipeline.py index eecd2878..bdab68f4 100644 --- a/workflow_array_ephys/pipeline.py +++ b/workflow_array_ephys/pipeline.py @@ -2,6 +2,7 @@ from element_animal import subject from element_lab import lab from element_session import session +from element_trial import trial, event from element_array_ephys import probe, ephys from element_animal.subject import Subject @@ -15,6 +16,10 @@ db_prefix = dj.config['custom'].get('database.prefix', '') +__all__ = ['subject', 'lab', 'session', 'trial', 'event', 'probe', 'ephys', 'Subject', + 'Source', 'Lab', 'Protocol', 'User', 'Project', 'Session', + 'get_ephys_root_data_dir', 'get_session_directory'] + # Activate "lab", "subject", "session" schema --------------------------------- @@ -25,6 +30,8 @@ Experimenter = lab.User session.activate(db_prefix + 'session', linking_module=__name__) +trial.activate(db_prefix + 'trial', db_prefix + 'event', linking_module= __name__) + # Declare table "SkullReference" for use in element-array-ephys --------------- From c5bbaa1bf39f8730836dc4e079f4000b84c34b81 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Tue, 15 Mar 2022 17:50:43 -0500 Subject: [PATCH 04/59] remove trial diagrams --- trial_diagram.svg | 242 --------------------------------------------- trial_diagram2.svg | 242 --------------------------------------------- 2 files changed, 484 deletions(-) delete mode 100644 trial_diagram.svg delete mode 100644 trial_diagram2.svg diff --git a/trial_diagram.svg b/trial_diagram.svg deleted file mode 100644 index ac85be52..00000000 --- a/trial_diagram.svg +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - - - - -2 - -2 - - - -event.AlignmentEvent - - -event.AlignmentEvent - - - - - -2->event.AlignmentEvent - - - - -1 - -1 - - - -1->event.AlignmentEvent - - - - -0 - -0 - - - -0->event.AlignmentEvent - - - - -trial.Block.Attribute - - -trial.Block.Attribute - - - - - -event.BehaviorRecording.File - - -event.BehaviorRecording.File - - - - - -trial.TrialType - - -trial.TrialType - - - - - -trial.Trial - - -trial.Trial - - - - - -trial.TrialType->trial.Trial - - - - -trial.Trial.Attribute - - -trial.Trial.Attribute - - - - - -trial.Trial->trial.Trial.Attribute - - - - -trial.BlockTrial - - -trial.BlockTrial - - - - - -trial.Trial->trial.BlockTrial - - - - -trial.TrialEvent - - -trial.TrialEvent - - - - - -trial.Trial->trial.TrialEvent - - - - -trial.Block - - -trial.Block - - - - - -trial.Block->trial.Block.Attribute - - - - -trial.Block->trial.BlockTrial - - - - -event.EventType - - -event.EventType - - - - - -event.EventType->2 - - - - -event.EventType->1 - - - - -event.EventType->0 - - - - -event.Event - - -event.Event - - - - - -event.EventType->event.Event - - - - -event.Event->trial.TrialEvent - - - - -Session - - -Session - - - - - -event.BehaviorRecording - - -event.BehaviorRecording - - - - - -Session->event.BehaviorRecording - - - - -event.BehaviorRecording->event.BehaviorRecording.File - - - - -event.BehaviorRecording->trial.Trial - - - - -event.BehaviorRecording->trial.Block - - - - -event.BehaviorRecording->event.Event - - - - diff --git a/trial_diagram2.svg b/trial_diagram2.svg deleted file mode 100644 index 683e9f2f..00000000 --- a/trial_diagram2.svg +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - - - - -1 - -1 - - - -AlignmentEvent - - -AlignmentEvent - - - - - -1->AlignmentEvent - - - - -0 - -0 - - - -0->AlignmentEvent - - - - -2 - -2 - - - -2->AlignmentEvent - - - - -TrialEvent - - -TrialEvent - - - - - -Trial.TrialAttribute - - -Trial.TrialAttribute - - - - - -Session - - -Session - - - - - -BehaviorRecording - - -BehaviorRecording - - - - - -Session->BehaviorRecording - - - - -Block - - -Block - - - - - -BehaviorRecording->Block - - - - -Trial - - -Trial - - - - - -BehaviorRecording->Trial - - - - -BehaviorRecording.File - - -BehaviorRecording.File - - - - - -BehaviorRecording->BehaviorRecording.File - - - - -Event - - -Event - - - - - -BehaviorRecording->Event - - - - -BlockTrial - - -BlockTrial - - - - - -Block->BlockTrial - - - - -Block.BlockAttribute - - -Block.BlockAttribute - - - - - -Block->Block.BlockAttribute - - - - -Trial->TrialEvent - - - - -Trial->Trial.TrialAttribute - - - - -Trial->BlockTrial - - - - -Event->TrialEvent - - - - -TrialType - - -TrialType - - - - - -TrialType->Trial - - - - -EventType - - -EventType - - - - - -EventType->1 - - - - -EventType->0 - - - - -EventType->2 - - - - -EventType->Event - - - - From b8b79a394afdcd494487be614197492ba4cae3c8 Mon Sep 17 00:00:00 2001 From: Kabilar Gunalan Date: Wed, 16 Mar 2022 23:05:28 -0500 Subject: [PATCH 05/59] Update service names --- docker/docker-compose-dev.yaml | 6 +++--- docker/docker-compose-test.yaml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/docker-compose-dev.yaml b/docker/docker-compose-dev.yaml index 43986ab2..f6652cfc 100644 --- a/docker/docker-compose-dev.yaml +++ b/docker/docker-compose-dev.yaml @@ -6,12 +6,12 @@ x-net: &net networks: - main services: - db: + array-ephys-dev-db: <<: *net image: datajoint/mysql:5.7 environment: - MYSQL_ROOT_PASSWORD=simple - workflow-array-ephys-dev: + array-ephys-dev-workflow: <<: *net build: context: ../../ @@ -30,7 +30,7 @@ services: - ../../element-array-ephys:/main/element-array-ephys - ..:/main/workflow-array-ephys depends_on: - db: + array-ephys-dev-db: condition: service_healthy networks: main: diff --git a/docker/docker-compose-test.yaml b/docker/docker-compose-test.yaml index 1688e000..9c27bb52 100644 --- a/docker/docker-compose-test.yaml +++ b/docker/docker-compose-test.yaml @@ -9,12 +9,12 @@ x-net: &net networks: - main services: - db: + array-ephys-test-db: <<: *net image: datajoint/mysql:5.7 environment: - MYSQL_ROOT_PASSWORD=simple - workflow: + array-ephys-test-workflow: <<: *net build: context: ../../ @@ -44,7 +44,7 @@ services: - ../../element-array-ephys:/main/element-array-ephys - ..:/main/workflow-array-ephys depends_on: - db: + array-ephys-test-db: condition: service_healthy networks: main: From d196645407b4ba7fa5dc76409b3f82f8fa609fca Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 17 Mar 2022 12:51:42 -0500 Subject: [PATCH 06/59] minor update tests --- tests/__init__.py | 6 +++--- tests/test_ingest.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index ee2a2d5a..d8b3a0f0 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -13,7 +13,7 @@ # ------------------- SOME CONSTANTS ------------------- -_tear_down = True +_tear_down = False test_user_data_dir = pathlib.Path('./tests/user_data') test_user_data_dir.mkdir(exist_ok=True) @@ -198,8 +198,8 @@ def kilosort_paramset(pipeline): # doing the insert here as well, since most of the test will require this paramset inserted ephys.ClusteringParamSet.insert_new_params( - processing_method='kilosort2', - paramset_desc='Spike sorting using Kilosort2', + clustering_method='kilosort2.5', + paramset_desc='Spike sorting using Kilosort2.5', params=params_ks, paramset_idx=0) diff --git a/tests/test_ingest.py b/tests/test_ingest.py index 20f2fa1f..155a10ec 100644 --- a/tests/test_ingest.py +++ b/tests/test_ingest.py @@ -77,7 +77,7 @@ def test_paramset_insert(kilosort_paramset, pipeline): method, desc, paramset_hash = (ephys.ClusteringParamSet & {'paramset_idx': 0}).fetch1( 'clustering_method', 'paramset_desc', 'param_set_hash') - assert method == 'kilosort2' - assert desc == 'Spike sorting using Kilosort2' + assert method == 'kilosort2.5' + assert desc == 'Spike sorting using Kilosort2.5' assert dict_to_uuid(kilosort_paramset) == paramset_hash From 9dddf106860865584c4c4f343592c4a37d0c6d46 Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Thu, 17 Mar 2022 14:02:17 -0500 Subject: [PATCH 07/59] Apply suggestions from code review Co-authored-by: Kabilar Gunalan --- workflow_array_ephys/ingest.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/workflow_array_ephys/ingest.py b/workflow_array_ephys/ingest.py index d39589cd..c60fc0e4 100644 --- a/workflow_array_ephys/ingest.py +++ b/workflow_array_ephys/ingest.py @@ -14,10 +14,12 @@ def ingest_general(csvs, tables, skip_duplicates=True, verbose=True, Inserts data from a series of csvs into their corresponding table: e.g., ingest_general(['./lab_data.csv', './proj_data.csv'], [lab.Lab(),lab.Project()] - ingest_general(csvs, tables, skip_duplicates=True) + ingest_general(csvs, tables, skip_duplicates=True, verbose=True, allow_direct_insert=False) :param csvs: list of relative paths to CSV files. CSV are delimited by commas. :param tables: list of datajoint tables with () :param verbose: print number inserted (i.e., table length change) + :param skip_duplicates: + :param allow_direct_insert: """ for csv_filepath, table in zip(csvs, tables): with open(csv_filepath, newline='') as f: From 011c207de598a00f728fac14a851236db7128577 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 17 Mar 2022 14:19:43 -0500 Subject: [PATCH 08/59] remove event_id; fix table list --- user_data/events.csv | 106 +++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/user_data/events.csv b/user_data/events.csv index 3e74d6bb..daf559d4 100644 --- a/user_data/events.csv +++ b/user_data/events.csv @@ -1,53 +1,53 @@ -subject,session_datetime,trial_id,event_id,event_start_time,event_type -subject5,2018-07-03 20:32:28,1,1,8.812,center -subject5,2018-07-03 20:32:28,1,2,4.647,left -subject5,2018-07-03 20:32:28,1,3,3.733,right -subject5,2018-07-03 20:32:28,3,1,21.198,left -subject5,2018-07-03 20:32:28,3,2,19.121,left -subject5,2018-07-03 20:32:28,3,3,25.344,right -subject5,2018-07-03 20:32:28,4,1,26.769,right -subject5,2018-07-03 20:32:28,4,2,27.073,right -subject5,2018-07-03 20:32:28,4,3,32.411,center -subject5,2018-07-03 20:32:28,5,1,35.048,right -subject5,2018-07-03 20:32:28,5,2,35.091,left -subject5,2018-07-03 20:32:28,9,1,72.032,right -subject5,2018-07-03 20:32:28,10,1,83.279,left -subject5,2018-07-03 20:32:28,11,1,90.027,right -subject5,2018-07-03 20:32:28,14,1,115.413,center -subject5,2018-07-03 20:32:28,14,2,111.274,center -subject5,2018-07-03 20:32:28,15,1,125.791,left -subject5,2018-07-03 20:32:28,16,1,135.069,left -subject5,2018-07-03 20:32:28,16,2,128.56,center -subject5,2018-07-03 20:32:28,16,3,133.69,center -subject5,2018-07-03 20:32:28,19,1,158.744,center -subject5,2018-07-03 20:32:28,19,2,152.561,right -subject5,2018-07-03 20:32:28,22,1,186.771,center -subject5,2018-07-03 20:32:28,22,2,185.484,right -subject5,2018-07-03 20:32:28,22,3,182.107,left -subject5,2018-07-03 20:32:28,23,1,192.072,right -subject5,2018-07-03 20:32:28,23,2,187.923,center -subject5,2018-07-03 20:32:28,24,1,198.2,right -subject5,2018-07-03 20:32:28,24,2,198.436,left -subject5,2018-07-03 20:32:28,26,1,213.439,right -subject5,2018-07-03 20:32:28,26,2,214.028,center -subject5,2018-07-03 20:32:28,26,3,215.391,right -subject5,2018-07-03 20:32:28,29,1,237.248,left -subject5,2018-07-03 20:32:28,31,1,261.215,left -subject5,2018-07-03 20:32:28,33,1,277.311,center -subject5,2018-07-03 20:32:28,33,2,271.487,center -subject5,2018-07-03 20:32:28,33,3,277.392,center -subject5,2018-07-03 20:32:28,34,1,282.147,left -subject5,2018-07-03 20:32:28,34,2,283.893,right -subject5,2018-07-03 20:32:28,35,1,288.407,center -subject5,2018-07-03 20:32:28,35,2,291.126,right -subject5,2018-07-03 20:32:28,35,3,287.522,center -subject5,2018-07-03 20:32:28,36,1,301.118,left -subject5,2018-07-03 20:32:28,36,2,298.515,right -subject5,2018-07-03 20:32:28,37,1,312.081,right -subject5,2018-07-03 20:32:28,37,2,305.579,left -subject5,2018-07-03 20:32:28,37,3,309.113,left -subject5,2018-07-03 20:32:28,39,1,324.653,left -subject5,2018-07-03 20:32:28,39,2,322.778,left -subject5,2018-07-03 20:32:28,40,1,336.635,center -subject5,2018-07-03 20:32:28,40,2,332.538,right -subject5,2018-07-03 20:32:28,40,3,331.889,right +subject,session_datetime,trial_id,event_start_time,event_type +subject5,2018-07-03 20:32:28,1,8.812,center +subject5,2018-07-03 20:32:28,1,4.647,left +subject5,2018-07-03 20:32:28,1,3.733,right +subject5,2018-07-03 20:32:28,3,21.198,left +subject5,2018-07-03 20:32:28,3,19.121,left +subject5,2018-07-03 20:32:28,3,25.344,right +subject5,2018-07-03 20:32:28,4,26.769,right +subject5,2018-07-03 20:32:28,4,27.073,right +subject5,2018-07-03 20:32:28,4,32.411,center +subject5,2018-07-03 20:32:28,5,35.048,right +subject5,2018-07-03 20:32:28,5,35.091,left +subject5,2018-07-03 20:32:28,9,72.032,right +subject5,2018-07-03 20:32:28,10,83.279,left +subject5,2018-07-03 20:32:28,11,90.027,right +subject5,2018-07-03 20:32:28,14,115.413,center +subject5,2018-07-03 20:32:28,14,111.274,center +subject5,2018-07-03 20:32:28,15,125.791,left +subject5,2018-07-03 20:32:28,16,135.069,left +subject5,2018-07-03 20:32:28,16,128.56,center +subject5,2018-07-03 20:32:28,16,133.69,center +subject5,2018-07-03 20:32:28,19,158.744,center +subject5,2018-07-03 20:32:28,19,152.561,right +subject5,2018-07-03 20:32:28,22,186.771,center +subject5,2018-07-03 20:32:28,22,185.484,right +subject5,2018-07-03 20:32:28,22,182.107,left +subject5,2018-07-03 20:32:28,23,192.072,right +subject5,2018-07-03 20:32:28,23,187.923,center +subject5,2018-07-03 20:32:28,24,198.2,right +subject5,2018-07-03 20:32:28,24,198.436,left +subject5,2018-07-03 20:32:28,26,213.439,right +subject5,2018-07-03 20:32:28,26,214.028,center +subject5,2018-07-03 20:32:28,26,215.391,right +subject5,2018-07-03 20:32:28,29,237.248,left +subject5,2018-07-03 20:32:28,31,261.215,left +subject5,2018-07-03 20:32:28,33,277.311,center +subject5,2018-07-03 20:32:28,33,271.487,center +subject5,2018-07-03 20:32:28,33,277.392,center +subject5,2018-07-03 20:32:28,34,282.147,left +subject5,2018-07-03 20:32:28,34,283.893,right +subject5,2018-07-03 20:32:28,35,288.407,center +subject5,2018-07-03 20:32:28,35,291.126,right +subject5,2018-07-03 20:32:28,35,287.522,center +subject5,2018-07-03 20:32:28,36,301.118,left +subject5,2018-07-03 20:32:28,36,298.515,right +subject5,2018-07-03 20:32:28,37,312.081,right +subject5,2018-07-03 20:32:28,37,305.579,left +subject5,2018-07-03 20:32:28,37,309.113,left +subject5,2018-07-03 20:32:28,39,324.653,left +subject5,2018-07-03 20:32:28,39,322.778,left +subject5,2018-07-03 20:32:28,40,336.635,center +subject5,2018-07-03 20:32:28,40,332.538,right +subject5,2018-07-03 20:32:28,40,331.889,right From 2a6c15b91aeac9f0b229ad9c638661786c28adc3 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 17 Mar 2022 14:22:44 -0500 Subject: [PATCH 09/59] ingest.py: fix table list --- workflow_array_ephys/ingest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflow_array_ephys/ingest.py b/workflow_array_ephys/ingest.py index c60fc0e4..fd807296 100644 --- a/workflow_array_ephys/ingest.py +++ b/workflow_array_ephys/ingest.py @@ -145,7 +145,7 @@ def ingest_events(recording_csv_path='./user_data/behavior_recordings.csv', block_csv_path, block_csv_path, trial_csv_path, trial_csv_path, trial_csv_path, trial_csv_path, - event_csv_path] + event_csv_path,event_csv_path] tables = [event.BehaviorRecording(), event.BehaviorRecording.File(), trial.Block(), trial.Block.Attribute(), trial.TrialType(), trial.Trial(), trial.Trial.Attribute(), From e0b5c84884a26cba378acedaa1cf20f21e5a0233 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 17 Mar 2022 17:30:39 -0500 Subject: [PATCH 10/59] Create analysis.py --- workflow_array_ephys/analysis.py | 118 +++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 workflow_array_ephys/analysis.py diff --git a/workflow_array_ephys/analysis.py b/workflow_array_ephys/analysis.py new file mode 100644 index 00000000..e5a02b36 --- /dev/null +++ b/workflow_array_ephys/analysis.py @@ -0,0 +1,118 @@ +import datajoint as dj +import numpy as np + +from .pipeline import db_prefix, session, ephys, trial, event + + +schema = dj.schema(db_prefix + 'analysis') + + +@schema +class SpikesAlignmentCondition(dj.Manual): + definition = """ + -> ephys.CuratedClustering + -> event.AlignmentEvent + trial_condition: varchar(128) # user-friendly name of condition + --- + bin_size=0.04: float # bin-size (in second) used to compute the PSTH + """ + + class Trial(dj.Part): + definition = """ # Trials (or subset of trials) to computed event-aligned spikes and PSTH on + -> master + -> trial.Trial + """ + + +@schema +class SpikesAlignment(dj.Computed): + definition = """ + -> SpikesAlignmentCondition + -> ephys.CuratedClustering.Unit + """ + + class AlignedTrialSpikes(dj.Part): + definition = """ + -> master + -> SpikesAlignmentCondition.Trial + --- + aligned_spike_times: longblob # (s) spike times relative to the alignment event time + """ + + class UnitPSTH(dj.Part): + definition = """ + -> master + -> ephys.CuratedClustering.Unit + --- + psth: longblob # event-aligned spike peristimulus time histogram (PSTH) + psth_edges: longblob + """ + + def make(self, key): + unit_keys, unit_spike_times = (ephys.CuratedClustering.Unit & key).fetch('KEY', 'unit_spike_times', order_by='unit') + + trial_keys, trial_starts, trial_ends = (trial.Trial & (SpikesAlignmentCondition.Trial & key)).fetch( + 'KEY', 'trial_start_time', 'trial_stop_time', order_by='trial_id') + + bin_size = (SpikesAlignmentCondition & key).fetch1('bin_size') + + alignment_spec = (event.AlignmentEvent & key).fetch1() + + # Spike raster + aligned_trial_spikes = [] + units_spike_raster = {u['unit']: {'KEY': {}, 'aligned_spikes': []} for u in unit_keys} + min_limit, max_limit = np.Inf, -np.Inf + for trial_key, trial_start, trial_stop in zip(trial_keys, trial_starts, trial_ends): + alignment_event_time = (event.Event & key & {'event_type': alignment_spec['alignment_event_type']} + & f'event_start_time BETWEEN {trial_start} AND {trial_stop}') + if alignment_event_time: + # if there are multiple of such alignment event, pick the last one in the trial + alignment_event_time = alignment_event_time.fetch( + 'event_start_time', order_by='event_start_time DESC', limit=1)[0] + else: + continue + + alignment_start_time = (event.Event & key & {'event_type': alignment_spec['start_event_type']} + & f'event_start_time < {alignment_event_time}') + if alignment_start_time: + # if there are multiple of such start event, pick the most immediate one prior to the alignment event + alignment_start_time = alignment_start_time.fetch( + 'event_start_time', order_by='event_start_time DESC', limit=1)[0] + alignment_start_time = max(alignment_start_time, trial_start) + else: + alignment_start_time = trial_start + + alignment_end_time = (event.Event & key & {'event_type': alignment_spec['end_event_type']} + & f'event_start_time > {alignment_event_time}') + if alignment_end_time: + # if there are multiple of such start event, pick the most immediate one following the alignment event + alignment_end_time = alignment_end_time.fetch( + 'event_start_time', order_by='event_start_time', limit=1)[0] + alignment_end_time = min(alignment_end_time, trial_stop) + else: + alignment_end_time = trial_stop + + alignment_event_time += alignment_spec['alignment_time_shift'] + alignment_start_time += alignment_spec['start_time_shift'] + alignment_end_time += alignment_spec['end_time_shift'] + + min_limit = min(alignment_start_time, min_limit) + max_limit = max(alignment_end_time, max_limit) + + for unit_key, spikes in zip(unit_keys, unit_spike_times): + aligned_spikes = spikes[(alignment_start_time <= spikes) + & (spikes < alignment_end_time)] - alignment_event_time + aligned_trial_spikes.append({**key, **unit_key, **trial_key, 'aligned_spike_times': aligned_spikes}) + units_spike_raster[unit_key['unit']]['aligned_spikes'].append(aligned_spikes) + + # PSTH + for unit_spike_raster in units_spike_raster.values(): + spikes = np.concatenate(unit_spike_raster['aligned_spikes']) + + psth, edges = np.histogram(spikes, bins=np.arange(min_limit, max_limit, bin_size)) + unit_spike_raster['psth'] = psth / len(unit_spike_raster.pop('aligned_spikes')) / bin_size + unit_spike_raster['psth_edges'] = edges[1:] + + self.insert1(key) + self.AlignedTrialSpikes.insert(aligned_trial_spikes) + self.UnitPSTH.insert(list(units_spike_raster.values())) From 7637b3fa512b5a2ab0bd067596823981202f9820 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 17 Mar 2022 18:41:25 -0500 Subject: [PATCH 11/59] AlignedSpikes calculation and plotting --- workflow_array_ephys/analysis.py | 34 ++++++++++++++--- workflow_array_ephys/plotting/__init__.py | 0 workflow_array_ephys/plotting/plot_psth.py | 43 ++++++++++++++++++++++ 3 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 workflow_array_ephys/plotting/__init__.py create mode 100644 workflow_array_ephys/plotting/plot_psth.py diff --git a/workflow_array_ephys/analysis.py b/workflow_array_ephys/analysis.py index e5a02b36..23ce5978 100644 --- a/workflow_array_ephys/analysis.py +++ b/workflow_array_ephys/analysis.py @@ -1,7 +1,7 @@ import datajoint as dj import numpy as np -from .pipeline import db_prefix, session, ephys, trial, event +from workflow_array_ephys.pipeline import db_prefix, session, ephys, trial, event schema = dj.schema(db_prefix + 'analysis') @@ -14,6 +14,7 @@ class SpikesAlignmentCondition(dj.Manual): -> event.AlignmentEvent trial_condition: varchar(128) # user-friendly name of condition --- + condition_description='': varchar(1000) bin_size=0.04: float # bin-size (in second) used to compute the PSTH """ @@ -28,12 +29,13 @@ class Trial(dj.Part): class SpikesAlignment(dj.Computed): definition = """ -> SpikesAlignmentCondition - -> ephys.CuratedClustering.Unit + -> ephys.CuratedClustering """ class AlignedTrialSpikes(dj.Part): definition = """ -> master + -> ephys.CuratedClustering.Unit -> SpikesAlignmentCondition.Trial --- aligned_spike_times: longblob # (s) spike times relative to the alignment event time @@ -49,7 +51,7 @@ class UnitPSTH(dj.Part): """ def make(self, key): - unit_keys, unit_spike_times = (ephys.CuratedClustering.Unit & key).fetch('KEY', 'unit_spike_times', order_by='unit') + unit_keys, unit_spike_times = (ephys.CuratedClustering.Unit & key).fetch('KEY', 'spike_times', order_by='unit') trial_keys, trial_starts, trial_ends = (trial.Trial & (SpikesAlignmentCondition.Trial & key)).fetch( 'KEY', 'trial_start_time', 'trial_stop_time', order_by='trial_id') @@ -60,7 +62,7 @@ def make(self, key): # Spike raster aligned_trial_spikes = [] - units_spike_raster = {u['unit']: {'KEY': {}, 'aligned_spikes': []} for u in unit_keys} + units_spike_raster = {u['unit']: {**key, **u, 'aligned_spikes': []} for u in unit_keys} min_limit, max_limit = np.Inf, -np.Inf for trial_key, trial_start, trial_stop in zip(trial_keys, trial_starts, trial_ends): alignment_event_time = (event.Event & key & {'event_type': alignment_spec['alignment_event_type']} @@ -96,8 +98,8 @@ def make(self, key): alignment_start_time += alignment_spec['start_time_shift'] alignment_end_time += alignment_spec['end_time_shift'] - min_limit = min(alignment_start_time, min_limit) - max_limit = max(alignment_end_time, max_limit) + min_limit = min(alignment_start_time - alignment_event_time, min_limit) + max_limit = max(alignment_end_time - alignment_event_time, max_limit) for unit_key, spikes in zip(unit_keys, unit_spike_times): aligned_spikes = spikes[(alignment_start_time <= spikes) @@ -116,3 +118,23 @@ def make(self, key): self.insert1(key) self.AlignedTrialSpikes.insert(aligned_trial_spikes) self.UnitPSTH.insert(list(units_spike_raster.values())) + + def plot_raster(self, key, unit, axs=None): + import matplotlib.pyplot as plt + from .plotting import plot_psth + + fig = None + if axs is None: + fig, axs = plt.subplots(2, 1) + + trial_ids, aligned_spikes = (self.AlignedTrialSpikes + & key & {'unit': unit}).fetch('trial_id', 'aligned_spike_times') + psth, psth_edges = (self.UnitPSTH & key & {'unit': unit}).fetch( + 'psth', 'psth_edges') + + plot_psth._plot_spike_raster(aligned_spikes, trial_ids=trial_ids, ax=axs[0], + title=f'{{**key, "unit": unit}}', xlim=None) + plot_psth._plot_psth(psth, psth_edges, ax=axs[1], + title=f'{{**key, "unit": unit}}', xlim=None) + + return fig diff --git a/workflow_array_ephys/plotting/__init__.py b/workflow_array_ephys/plotting/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/workflow_array_ephys/plotting/plot_psth.py b/workflow_array_ephys/plotting/plot_psth.py new file mode 100644 index 00000000..d5398b48 --- /dev/null +++ b/workflow_array_ephys/plotting/plot_psth.py @@ -0,0 +1,43 @@ +import numpy as np +import matplotlib.pyplot as plt + + +def _plot_spike_raster(aligned_spikes, trial_ids=None, vlines=[0], ax=None, title='', xlim=None): + if not ax: + fig, ax = plt.subplots(1, 1) + + raster = np.concatenate(aligned_spikes) + if trial_ids is None: + trial_ids = np.concatenate([[t] * len(s) + for t, s in enumerate(aligned_spikes)]) + + assert len(raster) == len(trial_ids) + + ax.plot(raster, trial_ids, 'r.', markersize=1) + + for x in vlines: + ax.axvline(x=x, linestyle='--', color='k') + + if xlim: + ax.set_xlim(xlim) + ax.set_axis_off() + ax.set_title(title) + + +def _plot_psth(psth, psth_edges, vlines=[0], ax=None, title='', xlim=None): + if not ax: + fig, ax = plt.subplots(1, 1) + + ax.plot(psth, psth_edges, 'b') + + for x in vlines: + ax.axvline(x=x, linestyle='--', color='k') + + ax.set_ylabel('spikes/s') + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + + if xlim: + ax.set_xlim(xlim) + ax.set_xlabel('Time (s)') + ax.set_title(title) From 0e68d4f7745fc5ee61cdd43a50d84d3bc2121cb0 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Fri, 18 Mar 2022 10:14:13 -0500 Subject: [PATCH 12/59] bugfix in plotting functions, add notebook 7 for analysis examples --- notebooks/07-Downstream analysis.ipynb | 1037 ++++++++++++++++++++ workflow_array_ephys/analysis.py | 10 +- workflow_array_ephys/plotting/plot_psth.py | 8 +- 3 files changed, 1048 insertions(+), 7 deletions(-) create mode 100644 notebooks/07-Downstream analysis.ipynb diff --git a/notebooks/07-Downstream analysis.ipynb b/notebooks/07-Downstream analysis.ipynb new file mode 100644 index 00000000..f4bce16e --- /dev/null +++ b/notebooks/07-Downstream analysis.ipynb @@ -0,0 +1,1037 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "921a4a03", + "metadata": {}, + "outputs": [], + "source": [ + "cd .." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "79cef246", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting ttngu207@dss-db.datajoint.io:3306\n" + ] + } + ], + "source": [ + "from workflow_array_ephys.pipeline import session, ephys, trial, event" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "0678d202", + "metadata": {}, + "outputs": [], + "source": [ + "from workflow_array_ephys import analysis" + ] + }, + { + "cell_type": "markdown", + "id": "4936a1e8", + "metadata": {}, + "source": [ + "# Event-aligned trialized unit spike times" + ] + }, + { + "cell_type": "markdown", + "id": "a2e97d75", + "metadata": {}, + "source": [ + "The `analysis` schema provides example tables to perform event-aligned spike-times analysis.\n", + "+ ***SpikesAlignmentCondition*** - a manual table to specify the inputs and condition for the analysis\n", + "+ ***SpikesAlignment*** - a computed table to extract event-aligned spikes and compute unit PSTH" + ] + }, + { + "cell_type": "markdown", + "id": "ba4607a1", + "metadata": {}, + "source": [ + "Let's start by creating several analyses configuration - i.e. inserting into ***SpikesAlignmentCondition***" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9a36c342", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Clustering results of the spike sorting step.\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_id

\n", + " \n", + "
\n", + "

insertion_number

\n", + " \n", + "
\n", + "

paramset_idx

\n", + " \n", + "
subject5110
subject5120
\n", + " \n", + "

Total: 2

\n", + " " + ], + "text/plain": [ + "*subject *session_id *insertion_number *paramset_idx \n", + "+----------+ +------------+ +------------------+ +--------------+\n", + "subject5 1 1 0 \n", + "subject5 1 2 0 \n", + " (Total: 2)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ephys.CuratedClustering()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "8642f010", + "metadata": {}, + "outputs": [], + "source": [ + "clustering_key = (ephys.CuratedClustering & {'subject': 'subject5', 'session_id': 1, 'insertion_number': 1}).fetch1('KEY')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "50a2c99f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " time_shift is seconds to shift with respect to (WRT) a variable\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

alignment_name

\n", + " \n", + "
\n", + "

alignment_description

\n", + " \n", + "
\n", + "

alignment_event_type

\n", + " \n", + "
\n", + "

alignment_time_shift

\n", + " (s) WRT alignment_event_type\n", + "
\n", + "

start_event_type

\n", + " \n", + "
\n", + "

start_time_shift

\n", + " (s) WRT start_event_type\n", + "
\n", + "

end_event_type

\n", + " \n", + "
\n", + "

end_time_shift

\n", + " (s) WRT end_event_type\n", + "
center_buttoncenter0.0center-3.0center3.0
left_buttonleft0.0left-3.0left3.0
right_buttonright0.0right-3.0right3.0
\n", + " \n", + "

Total: 3

\n", + " " + ], + "text/plain": [ + "*alignment_name alignment_description alignment_event_type alignment_time_shift start_event_type start_time_shift end_event_type end_time_shift \n", + "+----------------+ +-----------------------+ +----------------------+ +----------------------+ +------------------+ +------------------+ +----------------+ +----------------+\n", + "center_button center 0.0 center -3.0 center 3.0 \n", + "left_button left 0.0 left -3.0 left 3.0 \n", + "right_button right 0.0 right -3.0 right 3.0 \n", + " (Total: 3)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "event.AlignmentEvent()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d859203f", + "metadata": {}, + "outputs": [], + "source": [ + "alignment_key = (event.AlignmentEvent & 'alignment_name = \"center_button\"').fetch1('KEY')" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c9c95806", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Experimental trials\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_id

\n", + " \n", + "
\n", + "

trial_id

\n", + " trial number (1-based indexing)\n", + "
\n", + "

trial_type

\n", + " \n", + "
\n", + "

trial_start_time

\n", + " (second) relative to recording start\n", + "
\n", + "

trial_stop_time

\n", + " (second) relative to recording start\n", + "
subject511stim2.04310.043
subject512ctrl10.50818.508
subject513ctrl18.726.7
subject514ctrl26.70734.707
subject515stim34.71542.715
subject516stim42.80650.806
subject517ctrl50.83958.839
subject518ctrl59.19667.196
subject519stim67.3175.31
subject5110ctrl75.77283.772
subject5111stim86.08294.082
subject5112stim94.087102.087
subject5113stim102.183110.183
subject5114stim110.526118.526
subject5115stim118.844126.844
\n", + "

...

\n", + "

Total: 40

\n", + " " + ], + "text/plain": [ + "*subject *session_id *trial_id trial_type trial_start_time trial_stop_time \n", + "+----------+ +------------+ +----------+ +------------+ +------------------+ +-----------------+\n", + "subject5 1 1 stim 2.043 10.043 \n", + "subject5 1 2 ctrl 10.508 18.508 \n", + "subject5 1 3 ctrl 18.7 26.7 \n", + "subject5 1 4 ctrl 26.707 34.707 \n", + "subject5 1 5 stim 34.715 42.715 \n", + "subject5 1 6 stim 42.806 50.806 \n", + "subject5 1 7 ctrl 50.839 58.839 \n", + "subject5 1 8 ctrl 59.196 67.196 \n", + "subject5 1 9 stim 67.31 75.31 \n", + "subject5 1 10 ctrl 75.772 83.772 \n", + "subject5 1 11 stim 86.082 94.082 \n", + "subject5 1 12 stim 94.087 102.087 \n", + "subject5 1 13 stim 102.183 110.183 \n", + "subject5 1 14 stim 110.526 118.526 \n", + "subject5 1 15 stim 118.844 126.844 \n", + " ...\n", + " (Total: 40)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trial.Trial & clustering_key" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "1851ad2b", + "metadata": {}, + "outputs": [], + "source": [ + "ctrl_trials = trial.Trial & clustering_key & 'trial_type = \"ctrl\"'" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "01474336", + "metadata": {}, + "outputs": [], + "source": [ + "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'ctrl_center_button'}" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "8bc824cb", + "metadata": {}, + "outputs": [], + "source": [ + "analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "8994e8f5", + "metadata": {}, + "outputs": [], + "source": [ + "analysis.SpikesAlignmentCondition.Trial.insert(\n", + " (analysis.SpikesAlignmentCondition * ctrl_trials & alignment_condition).proj(),\n", + " skip_duplicates=True)" + ] + }, + { + "cell_type": "markdown", + "id": "4ddd0345", + "metadata": {}, + "source": [ + "With the steps above, we have create a new spike alignment condition for analysis, named `ctrl_center_button`, which specifies:\n", + "+ a CuratedClustering of interest for analysis\n", + "+ an event of interest to align the spikes to - `center_button`\n", + "+ a set of trials of interest to perform the analysis on - `ctrl` trials" + ] + }, + { + "cell_type": "markdown", + "id": "2a23b206", + "metadata": {}, + "source": [ + "Now, let's create another set with:\n", + "+ the same CuratedClustering of interest for analysis\n", + "+ an event of interest to align the spikes to - `center_button`\n", + "+ a set of trials of interest to perform the analysis on - `stim` trials" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "39d4d423", + "metadata": {}, + "outputs": [], + "source": [ + "stim_trials = trial.Trial & clustering_key & 'trial_type = \"stim\"'\n", + "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", + "analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True)\n", + "analysis.SpikesAlignmentCondition.Trial.insert(\n", + " (analysis.SpikesAlignmentCondition * stim_trials & alignment_condition).proj(),\n", + " skip_duplicates=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "6452c508", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_id

\n", + " \n", + "
\n", + "

insertion_number

\n", + " \n", + "
\n", + "

paramset_idx

\n", + " \n", + "
\n", + "

alignment_name

\n", + " \n", + "
\n", + "

trial_condition

\n", + " user-friendly name of condition\n", + "
\n", + "

bin_size

\n", + " bin-size (in second) used to compute the PSTH\n", + "
subject5110center_buttonctrl_center_button0.04
subject5110center_buttonstim_center_button0.04
\n", + " \n", + "

Total: 2

\n", + " " + ], + "text/plain": [ + "*subject *session_id *insertion_number *paramset_idx *alignment_name *trial_condition bin_size \n", + "+----------+ +------------+ +------------------+ +--------------+ +----------------+ +--------------------+ +----------+\n", + "subject5 1 1 0 center_button ctrl_center_button 0.04 \n", + "subject5 1 1 0 center_button stim_center_button 0.04 \n", + " (Total: 2)" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "analysis.SpikesAlignmentCondition()" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "7a9ef2fb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Trials (or subset of trials) to computed event-aligned spikes and PSTH on\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_id

\n", + " \n", + "
\n", + "

insertion_number

\n", + " \n", + "
\n", + "

paramset_idx

\n", + " \n", + "
\n", + "

alignment_name

\n", + " \n", + "
\n", + "

trial_condition

\n", + " user-friendly name of condition\n", + "
\n", + "

trial_id

\n", + " trial number (1-based indexing)\n", + "
subject5110center_buttonctrl_center_button2
subject5110center_buttonctrl_center_button3
subject5110center_buttonctrl_center_button4
subject5110center_buttonctrl_center_button7
subject5110center_buttonctrl_center_button8
subject5110center_buttonctrl_center_button10
subject5110center_buttonctrl_center_button17
subject5110center_buttonctrl_center_button19
subject5110center_buttonctrl_center_button21
subject5110center_buttonctrl_center_button22
subject5110center_buttonctrl_center_button24
subject5110center_buttonctrl_center_button25
subject5110center_buttonctrl_center_button27
subject5110center_buttonctrl_center_button30
subject5110center_buttonctrl_center_button33
\n", + "

...

\n", + "

Total: 18

\n", + " " + ], + "text/plain": [ + "*subject *session_id *insertion_number *paramset_idx *alignment_name *trial_condition *trial_id \n", + "+----------+ +------------+ +------------------+ +--------------+ +----------------+ +--------------------+ +----------+\n", + "subject5 1 1 0 center_button ctrl_center_button 2 \n", + "subject5 1 1 0 center_button ctrl_center_button 3 \n", + "subject5 1 1 0 center_button ctrl_center_button 4 \n", + "subject5 1 1 0 center_button ctrl_center_button 7 \n", + "subject5 1 1 0 center_button ctrl_center_button 8 \n", + "subject5 1 1 0 center_button ctrl_center_button 10 \n", + "subject5 1 1 0 center_button ctrl_center_button 17 \n", + "subject5 1 1 0 center_button ctrl_center_button 19 \n", + "subject5 1 1 0 center_button ctrl_center_button 21 \n", + "subject5 1 1 0 center_button ctrl_center_button 22 \n", + "subject5 1 1 0 center_button ctrl_center_button 24 \n", + "subject5 1 1 0 center_button ctrl_center_button 25 \n", + "subject5 1 1 0 center_button ctrl_center_button 27 \n", + "subject5 1 1 0 center_button ctrl_center_button 30 \n", + "subject5 1 1 0 center_button ctrl_center_button 33 \n", + " ...\n", + " (Total: 18)" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "analysis.SpikesAlignmentCondition.Trial & 'trial_condition = \"ctrl_center_button\"'" + ] + }, + { + "cell_type": "markdown", + "id": "1486add8", + "metadata": {}, + "source": [ + "### Now let's run the computation on these" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "f467d3f7", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "SpikesAlignment: 100%|███████████████████████████████████████████████████████████████████| 2/2 [00:13<00:00, 6.97s/it]\n" + ] + } + ], + "source": [ + "analysis.SpikesAlignment.populate(display_progress=True)" + ] + }, + { + "cell_type": "markdown", + "id": "c4320cdd", + "metadata": {}, + "source": [ + "### Let's visualize the results" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2c4962b7", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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2vQ0to3EmWTvxPkHP5h3NbDP0zgvodbanbbw8Gsl+Yeq2i/dFguvba29eZjKAGCbDX06qkXex1nSuNzGE+eLL553NbDP0zgvoap6WGvtpKr67dVub6395VqPba39V7pNvAFg3zNQ4pyka5wXcR9SiTOJfZxhYBrn5XSl/v+D51oFAMzakInzaBqLluxKnEns4wwD0zgvodbaC/v/nz7vWgBgpiTOLAuJMwzKUbWXWFXdsKreVFXnVdXXquqfq+qG864LALZFa6ufjmoTadyDHvSgPOjAAxcz0ZM4k0icYWAa5+X2yiSvTXfAr2sneV2SV821IgDYLq11/w+QOD/2sY/NYy9/+cVM9Eap+qLVxWxJnGFQGufldlBr7eWttUv6fyclucK8iwKAbTFqHgZInC+66KJcdMkli5nojfbjXrS6mC2JMwzKPs7L7W1V9aQkr053/uYHJ3lrVV0jSVpr35hncQAwqFHzMEDifM973jP57ndzyiImehJnEokzDEzjvNwe1P//q+ka5ySpJA/pL9vfGYCdY8DEOUm36fciJnoSZxKJMwzMptrL7feSHNVau0GSv09yepKfb63doLWmaQZgZxkwcU6y2EfVXsS6mC2JMwxK47zcntpa+3ZV3SHJnZO8OMkL5lwTAGyPoRPn0TQWLdGTOJNInGFgGuflNvr58V5J/q619pYkB86xHgDYPmslzlvZVHuU6C1SYzKeOC9SXcyWcQCD0jgvty9X1Quz56Bgl48xAcBOtVbivInNWI877rgcN5rGfvst1qaw44nzItXFbBkHMChN0nJ7UJJ3JLlba+2CJNdI8jtzrQgAtsuAifNxj3xk1zgvYqInaSQxDmBgjqq9xFprFyX5x7HL5yY5d34VAcA2GjUPAyTO53/ta0mSQxfx4EujfVsXrS5my8HBYFAaZwBgOYyahwES5wc8qDuj4ymLePClUcO0aHUxWw4OBoOyqTYAsByGPB1Va3umsWiJnsSZROIMA9M4AwDLYcjTUY0a50VM9CTOJBJnGJjGGQBYDkMmziOLmOhJnEkkzjAwjTMAsBwkziwTiTMMSuMMACyHARPnX3vUo/Jro2ksWqIncSaROMPAHFUbAFgOAybOD77vffdMY9ESPYkzicQZBiZxZipVdURV/VtVfaKqzqiqx827JgDYkAET57PPPjtnj6axaImexJlE4gwD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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'ctrl_center_button'}\n", + "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "af466c68", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", + "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c0f1691", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:wt-ephys_no_curation]", + "language": "python", + "name": "conda-env-wt-ephys_no_curation-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/workflow_array_ephys/analysis.py b/workflow_array_ephys/analysis.py index 23ce5978..3d047a7f 100644 --- a/workflow_array_ephys/analysis.py +++ b/workflow_array_ephys/analysis.py @@ -125,16 +125,18 @@ def plot_raster(self, key, unit, axs=None): fig = None if axs is None: - fig, axs = plt.subplots(2, 1) + fig, axs = plt.subplots(2, 1, figsize=(12, 8)) trial_ids, aligned_spikes = (self.AlignedTrialSpikes & key & {'unit': unit}).fetch('trial_id', 'aligned_spike_times') - psth, psth_edges = (self.UnitPSTH & key & {'unit': unit}).fetch( + psth, psth_edges = (self.UnitPSTH & key & {'unit': unit}).fetch1( 'psth', 'psth_edges') + xlim = psth_edges[0], psth_edges[-1] + plot_psth._plot_spike_raster(aligned_spikes, trial_ids=trial_ids, ax=axs[0], - title=f'{{**key, "unit": unit}}', xlim=None) + title=f'{dict(**key, unit=unit)}', xlim=xlim) plot_psth._plot_psth(psth, psth_edges, ax=axs[1], - title=f'{{**key, "unit": unit}}', xlim=None) + title='', xlim=xlim) return fig diff --git a/workflow_array_ephys/plotting/plot_psth.py b/workflow_array_ephys/plotting/plot_psth.py index d5398b48..50c1a3b9 100644 --- a/workflow_array_ephys/plotting/plot_psth.py +++ b/workflow_array_ephys/plotting/plot_psth.py @@ -8,8 +8,10 @@ def _plot_spike_raster(aligned_spikes, trial_ids=None, vlines=[0], ax=None, titl raster = np.concatenate(aligned_spikes) if trial_ids is None: - trial_ids = np.concatenate([[t] * len(s) - for t, s in enumerate(aligned_spikes)]) + trial_ids = range(len(aligned_spikes)) + + trial_ids = np.concatenate([[t] * len(s) + for t, s in zip(trial_ids, aligned_spikes)]).astype(int) assert len(raster) == len(trial_ids) @@ -28,7 +30,7 @@ def _plot_psth(psth, psth_edges, vlines=[0], ax=None, title='', xlim=None): if not ax: fig, ax = plt.subplots(1, 1) - ax.plot(psth, psth_edges, 'b') + ax.plot(psth_edges, psth, 'r') for x in vlines: ax.axvline(x=x, linestyle='--', color='k') From c99ca155252d02f31bf0b918a1b7d33553401efa Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Fri, 4 Jun 2021 13:05:57 -0500 Subject: [PATCH 13/59] Rebase ephys modes w/event-related. See Details add tests for NWB export accommodate different ephys modes (chronic, no-curation, etc.) Create analysis.py add trialized CSVs and ingestion functions remove trial diagrams Update docker service names Code review co-authored-by: Kabilar Gunalan ingest.py: fix table list AlignedSpikes calculation and plotting add notebook 7 for analysis examples add trials up to 100 --- .gitignore | 3 +- docker/Dockerfile.dev | 3 + docker/docker-compose-dev.yaml | 5 +- docker/docker-compose-test.yaml | 7 +- notebooks/07-Downstream analysis.ipynb | 1037 ++++++++++++++++++++ requirements.txt | 8 + test_export | Bin 0 -> 800 bytes tests/__init__.py | 34 +- tests/test_export.py | 85 ++ tests/test_ingest.py | 4 +- tests/test_populate.py | 31 +- user_data/alignments.csv | 4 + user_data/behavior_recordings.csv | 3 + user_data/blocks.csv | 5 + user_data/events.csv | 154 +++ user_data/sessions.csv | 3 +- user_data/subjects.csv | 1 + user_data/trials.csv | 101 ++ workflow_array_ephys/analysis.py | 142 +++ workflow_array_ephys/ingest.py | 106 +- workflow_array_ephys/pipeline.py | 21 + workflow_array_ephys/plotting/__init__.py | 0 workflow_array_ephys/plotting/plot_psth.py | 45 + 23 files changed, 1752 insertions(+), 50 deletions(-) create mode 100644 notebooks/07-Downstream analysis.ipynb create mode 100644 test_export create mode 100644 user_data/alignments.csv create mode 100644 user_data/behavior_recordings.csv create mode 100644 user_data/blocks.csv create mode 100644 user_data/events.csv create mode 100644 user_data/trials.csv create mode 100644 workflow_array_ephys/analysis.py create mode 100644 workflow_array_ephys/plotting/__init__.py create mode 100644 workflow_array_ephys/plotting/plot_psth.py diff --git a/.gitignore b/.gitignore index 2e13d1eb..fc60462d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ __pycache__/ *.py[cod] *$py.class +.pytest_ca*/ # C extensions *.so @@ -123,4 +124,4 @@ Diagram.ipynb .vscode/settings.json # notes -temp* \ No newline at end of file +temp* diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index c5d54534..433282b6 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -8,6 +8,7 @@ RUN /entrypoint.sh echo "Installed dependencies." RUN mkdir /main/element-lab \ /main/element-animal \ /main/element-session \ + /main/element-trial \ /main/element-array-ephys \ /main/workflow-array-ephys @@ -15,6 +16,7 @@ RUN mkdir /main/element-lab \ COPY --chown=anaconda:anaconda ./element-lab /main/element-lab COPY --chown=anaconda:anaconda ./element-animal /main/element-animal COPY --chown=anaconda:anaconda ./element-session /main/element-session +COPY --chown=anaconda:anaconda ./element-trial /main/element-trial COPY --chown=anaconda:anaconda ./element-array-ephys /main/element-array-ephys COPY --chown=anaconda:anaconda ./workflow-array-ephys /main/workflow-array-ephys @@ -22,6 +24,7 @@ COPY --chown=anaconda:anaconda ./workflow-array-ephys /main/workflow-array-ephys RUN pip install -e /main/element-lab RUN pip install -e /main/element-animal RUN pip install -e /main/element-session +RUN pip install -e /main/element-trial RUN pip install -e /main/element-array-ephys RUN pip install -e /main/workflow-array-ephys RUN pip install -r /main/workflow-array-ephys/requirements_test.txt diff --git a/docker/docker-compose-dev.yaml b/docker/docker-compose-dev.yaml index 62c4d787..c7144ac8 100644 --- a/docker/docker-compose-dev.yaml +++ b/docker/docker-compose-dev.yaml @@ -6,7 +6,7 @@ x-net: &net networks: - main services: - db: + array-ephys-dev-db: <<: *net image: datajoint/mysql:5.7 container_name: workflow-array-ephys-dev-db @@ -28,10 +28,11 @@ services: - ../../element-lab:/main/element-lab - ../../element-animal:/main/element-animal - ../../element-session:/main/element-session + - ../../element-trial:/main/element-trial - ../../element-array-ephys:/main/element-array-ephys - ..:/main/workflow-array-ephys depends_on: - db: + array-ephys-dev-db: condition: service_healthy networks: main: diff --git a/docker/docker-compose-test.yaml b/docker/docker-compose-test.yaml index e27cb3c2..02945739 100644 --- a/docker/docker-compose-test.yaml +++ b/docker/docker-compose-test.yaml @@ -9,13 +9,13 @@ x-net: &net networks: - main services: - db: + array-ephys-test-db: <<: *net image: datajoint/mysql:5.7 container_name: workflow-array-ephys-test-db environment: - MYSQL_ROOT_PASSWORD=simple - workflow: + array-ephys-test-workflow: <<: *net build: context: ../../ @@ -42,10 +42,11 @@ services: - ../../element-lab:/main/element-lab - ../../element-animal:/main/element-animal - ../../element-session:/main/element-session + - ../../element-trial:/main/element-trial - ../../element-array-ephys:/main/element-array-ephys - ..:/main/workflow-array-ephys depends_on: - db: + array-ephys-test-db: condition: service_healthy networks: main: diff --git a/notebooks/07-Downstream analysis.ipynb b/notebooks/07-Downstream analysis.ipynb new file mode 100644 index 00000000..f4bce16e --- /dev/null +++ b/notebooks/07-Downstream analysis.ipynb @@ -0,0 +1,1037 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "921a4a03", + "metadata": {}, + "outputs": [], + "source": [ + "cd .." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "79cef246", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting ttngu207@dss-db.datajoint.io:3306\n" + ] + } + ], + "source": [ + "from workflow_array_ephys.pipeline import session, ephys, trial, event" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "0678d202", + "metadata": {}, + "outputs": [], + "source": [ + "from workflow_array_ephys import analysis" + ] + }, + { + "cell_type": "markdown", + "id": "4936a1e8", + "metadata": {}, + "source": [ + "# Event-aligned trialized unit spike times" + ] + }, + { + "cell_type": "markdown", + "id": "a2e97d75", + "metadata": {}, + "source": [ + "The `analysis` schema provides example tables to perform event-aligned spike-times analysis.\n", + "+ ***SpikesAlignmentCondition*** - a manual table to specify the inputs and condition for the analysis\n", + "+ ***SpikesAlignment*** - a computed table to extract event-aligned spikes and compute unit PSTH" + ] + }, + { + "cell_type": "markdown", + "id": "ba4607a1", + "metadata": {}, + "source": [ + "Let's start by creating several analyses configuration - i.e. inserting into ***SpikesAlignmentCondition***" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9a36c342", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Clustering results of the spike sorting step.\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_id

\n", + " \n", + "
\n", + "

insertion_number

\n", + " \n", + "
\n", + "

paramset_idx

\n", + " \n", + "
subject5110
subject5120
\n", + " \n", + "

Total: 2

\n", + " " + ], + "text/plain": [ + "*subject *session_id *insertion_number *paramset_idx \n", + "+----------+ +------------+ +------------------+ +--------------+\n", + "subject5 1 1 0 \n", + "subject5 1 2 0 \n", + " (Total: 2)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ephys.CuratedClustering()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "8642f010", + "metadata": {}, + "outputs": [], + "source": [ + "clustering_key = (ephys.CuratedClustering & {'subject': 'subject5', 'session_id': 1, 'insertion_number': 1}).fetch1('KEY')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "50a2c99f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " time_shift is seconds to shift with respect to (WRT) a variable\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

alignment_name

\n", + " \n", + "
\n", + "

alignment_description

\n", + " \n", + "
\n", + "

alignment_event_type

\n", + " \n", + "
\n", + "

alignment_time_shift

\n", + " (s) WRT alignment_event_type\n", + "
\n", + "

start_event_type

\n", + " \n", + "
\n", + "

start_time_shift

\n", + " (s) WRT start_event_type\n", + "
\n", + "

end_event_type

\n", + " \n", + "
\n", + "

end_time_shift

\n", + " (s) WRT end_event_type\n", + "
center_buttoncenter0.0center-3.0center3.0
left_buttonleft0.0left-3.0left3.0
right_buttonright0.0right-3.0right3.0
\n", + " \n", + "

Total: 3

\n", + " " + ], + "text/plain": [ + "*alignment_name alignment_description alignment_event_type alignment_time_shift start_event_type start_time_shift end_event_type end_time_shift \n", + "+----------------+ +-----------------------+ +----------------------+ +----------------------+ +------------------+ +------------------+ +----------------+ +----------------+\n", + "center_button center 0.0 center -3.0 center 3.0 \n", + "left_button left 0.0 left -3.0 left 3.0 \n", + "right_button right 0.0 right -3.0 right 3.0 \n", + " (Total: 3)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "event.AlignmentEvent()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d859203f", + "metadata": {}, + "outputs": [], + "source": [ + "alignment_key = (event.AlignmentEvent & 'alignment_name = \"center_button\"').fetch1('KEY')" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c9c95806", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Experimental trials\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_id

\n", + " \n", + "
\n", + "

trial_id

\n", + " trial number (1-based indexing)\n", + "
\n", + "

trial_type

\n", + " \n", + "
\n", + "

trial_start_time

\n", + " (second) relative to recording start\n", + "
\n", + "

trial_stop_time

\n", + " (second) relative to recording start\n", + "
subject511stim2.04310.043
subject512ctrl10.50818.508
subject513ctrl18.726.7
subject514ctrl26.70734.707
subject515stim34.71542.715
subject516stim42.80650.806
subject517ctrl50.83958.839
subject518ctrl59.19667.196
subject519stim67.3175.31
subject5110ctrl75.77283.772
subject5111stim86.08294.082
subject5112stim94.087102.087
subject5113stim102.183110.183
subject5114stim110.526118.526
subject5115stim118.844126.844
\n", + "

...

\n", + "

Total: 40

\n", + " " + ], + "text/plain": [ + "*subject *session_id *trial_id trial_type trial_start_time trial_stop_time \n", + "+----------+ +------------+ +----------+ +------------+ +------------------+ +-----------------+\n", + "subject5 1 1 stim 2.043 10.043 \n", + "subject5 1 2 ctrl 10.508 18.508 \n", + "subject5 1 3 ctrl 18.7 26.7 \n", + "subject5 1 4 ctrl 26.707 34.707 \n", + "subject5 1 5 stim 34.715 42.715 \n", + "subject5 1 6 stim 42.806 50.806 \n", + "subject5 1 7 ctrl 50.839 58.839 \n", + "subject5 1 8 ctrl 59.196 67.196 \n", + "subject5 1 9 stim 67.31 75.31 \n", + "subject5 1 10 ctrl 75.772 83.772 \n", + "subject5 1 11 stim 86.082 94.082 \n", + "subject5 1 12 stim 94.087 102.087 \n", + "subject5 1 13 stim 102.183 110.183 \n", + "subject5 1 14 stim 110.526 118.526 \n", + "subject5 1 15 stim 118.844 126.844 \n", + " ...\n", + " (Total: 40)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trial.Trial & clustering_key" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "1851ad2b", + "metadata": {}, + "outputs": [], + "source": [ + "ctrl_trials = trial.Trial & clustering_key & 'trial_type = \"ctrl\"'" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "01474336", + "metadata": {}, + "outputs": [], + "source": [ + "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'ctrl_center_button'}" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "8bc824cb", + "metadata": {}, + "outputs": [], + "source": [ + "analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "8994e8f5", + "metadata": {}, + "outputs": [], + "source": [ + "analysis.SpikesAlignmentCondition.Trial.insert(\n", + " (analysis.SpikesAlignmentCondition * ctrl_trials & alignment_condition).proj(),\n", + " skip_duplicates=True)" + ] + }, + { + "cell_type": "markdown", + "id": "4ddd0345", + "metadata": {}, + "source": [ + "With the steps above, we have create a new spike alignment condition for analysis, named `ctrl_center_button`, which specifies:\n", + "+ a CuratedClustering of interest for analysis\n", + "+ an event of interest to align the spikes to - `center_button`\n", + "+ a set of trials of interest to perform the analysis on - `ctrl` trials" + ] + }, + { + "cell_type": "markdown", + "id": "2a23b206", + "metadata": {}, + "source": [ + "Now, let's create another set with:\n", + "+ the same CuratedClustering of interest for analysis\n", + "+ an event of interest to align the spikes to - `center_button`\n", + "+ a set of trials of interest to perform the analysis on - `stim` trials" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "39d4d423", + "metadata": {}, + "outputs": [], + "source": [ + "stim_trials = trial.Trial & clustering_key & 'trial_type = \"stim\"'\n", + "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", + "analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True)\n", + "analysis.SpikesAlignmentCondition.Trial.insert(\n", + " (analysis.SpikesAlignmentCondition * stim_trials & alignment_condition).proj(),\n", + " skip_duplicates=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "6452c508", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_id

\n", + " \n", + "
\n", + "

insertion_number

\n", + " \n", + "
\n", + "

paramset_idx

\n", + " \n", + "
\n", + "

alignment_name

\n", + " \n", + "
\n", + "

trial_condition

\n", + " user-friendly name of condition\n", + "
\n", + "

bin_size

\n", + " bin-size (in second) used to compute the PSTH\n", + "
subject5110center_buttonctrl_center_button0.04
subject5110center_buttonstim_center_button0.04
\n", + " \n", + "

Total: 2

\n", + " " + ], + "text/plain": [ + "*subject *session_id *insertion_number *paramset_idx *alignment_name *trial_condition bin_size \n", + "+----------+ +------------+ +------------------+ +--------------+ +----------------+ +--------------------+ +----------+\n", + "subject5 1 1 0 center_button ctrl_center_button 0.04 \n", + "subject5 1 1 0 center_button stim_center_button 0.04 \n", + " (Total: 2)" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "analysis.SpikesAlignmentCondition()" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "7a9ef2fb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Trials (or subset of trials) to computed event-aligned spikes and PSTH on\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_id

\n", + " \n", + "
\n", + "

insertion_number

\n", + " \n", + "
\n", + "

paramset_idx

\n", + " \n", + "
\n", + "

alignment_name

\n", + " \n", + "
\n", + "

trial_condition

\n", + " user-friendly name of condition\n", + "
\n", + "

trial_id

\n", + " trial number (1-based indexing)\n", + "
subject5110center_buttonctrl_center_button2
subject5110center_buttonctrl_center_button3
subject5110center_buttonctrl_center_button4
subject5110center_buttonctrl_center_button7
subject5110center_buttonctrl_center_button8
subject5110center_buttonctrl_center_button10
subject5110center_buttonctrl_center_button17
subject5110center_buttonctrl_center_button19
subject5110center_buttonctrl_center_button21
subject5110center_buttonctrl_center_button22
subject5110center_buttonctrl_center_button24
subject5110center_buttonctrl_center_button25
subject5110center_buttonctrl_center_button27
subject5110center_buttonctrl_center_button30
subject5110center_buttonctrl_center_button33
\n", + "

...

\n", + "

Total: 18

\n", + " " + ], + "text/plain": [ + "*subject *session_id *insertion_number *paramset_idx *alignment_name *trial_condition *trial_id \n", + "+----------+ +------------+ +------------------+ +--------------+ +----------------+ +--------------------+ +----------+\n", + "subject5 1 1 0 center_button ctrl_center_button 2 \n", + "subject5 1 1 0 center_button ctrl_center_button 3 \n", + "subject5 1 1 0 center_button ctrl_center_button 4 \n", + "subject5 1 1 0 center_button ctrl_center_button 7 \n", + "subject5 1 1 0 center_button ctrl_center_button 8 \n", + "subject5 1 1 0 center_button ctrl_center_button 10 \n", + "subject5 1 1 0 center_button ctrl_center_button 17 \n", + "subject5 1 1 0 center_button ctrl_center_button 19 \n", + "subject5 1 1 0 center_button ctrl_center_button 21 \n", + "subject5 1 1 0 center_button ctrl_center_button 22 \n", + "subject5 1 1 0 center_button ctrl_center_button 24 \n", + "subject5 1 1 0 center_button ctrl_center_button 25 \n", + "subject5 1 1 0 center_button ctrl_center_button 27 \n", + "subject5 1 1 0 center_button ctrl_center_button 30 \n", + "subject5 1 1 0 center_button ctrl_center_button 33 \n", + " ...\n", + " (Total: 18)" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "analysis.SpikesAlignmentCondition.Trial & 'trial_condition = \"ctrl_center_button\"'" + ] + }, + { + "cell_type": "markdown", + "id": "1486add8", + "metadata": {}, + "source": [ + "### Now let's run the computation on these" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "f467d3f7", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "SpikesAlignment: 100%|███████████████████████████████████████████████████████████████████| 2/2 [00:13<00:00, 6.97s/it]\n" + ] + } + ], + "source": [ + "analysis.SpikesAlignment.populate(display_progress=True)" + ] + }, + { + "cell_type": "markdown", + "id": "c4320cdd", + "metadata": {}, + "source": [ + "### Let's visualize the results" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2c4962b7", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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2vQ0to3EmWTvxPkHP5h3NbDP0zgvodbanbbw8Gsl+Yeq2i/dFguvba29eZjKAGCbDX06qkXex1nSuNzGE+eLL553NbDP0zgvoap6WGvtpKr67dVub6395VqPba39V7pNvAFg3zNQ4pyka5wXcR9SiTOJfZxhYBrn5XSl/v+D51oFAMzakInzaBqLluxKnEns4wwD0zgvodbaC/v/nz7vWgBgpiTOLAuJMwzKUbWXWFXdsKreVFXnVdXXquqfq+qG864LALZFa6ufjmoTadyDHvSgPOjAAxcz0ZM4k0icYWAa5+X2yiSvTXfAr2sneV2SV821IgDYLq11/w+QOD/2sY/NYy9/+cVM9Eap+qLVxWxJnGFQGufldlBr7eWttUv6fyclucK8iwKAbTFqHgZInC+66KJcdMkli5nojfbjXrS6mC2JMwzKPs7L7W1V9aQkr053/uYHJ3lrVV0jSVpr35hncQAwqFHzMEDifM973jP57ndzyiImehJnEokzDEzjvNwe1P//q+ka5ySpJA/pL9vfGYCdY8DEOUm36fciJnoSZxKJMwzMptrL7feSHNVau0GSv09yepKfb63doLWmaQZgZxkwcU6y2EfVXsS6mC2JMwxK47zcntpa+3ZV3SHJnZO8OMkL5lwTAGyPoRPn0TQWLdGTOJNInGFgGuflNvr58V5J/q619pYkB86xHgDYPmslzlvZVHuU6C1SYzKeOC9SXcyWcQCD0jgvty9X1Quz56Bgl48xAcBOtVbivInNWI877rgcN5rGfvst1qaw44nzItXFbBkHMChN0nJ7UJJ3JLlba+2CJNdI8jtzrQgAtsuAifNxj3xk1zgvYqInaSQxDmBgjqq9xFprFyX5x7HL5yY5d34VAcA2GjUPAyTO53/ta0mSQxfx4EujfVsXrS5my8HBYFAaZwBgOYyahwES5wc8qDuj4ymLePClUcO0aHUxWw4OBoOyqTYAsByGPB1Va3umsWiJnsSZROIMA9M4AwDLYcjTUY0a50VM9CTOJBJnGJjGGQBYDkMmziOLmOhJnEkkzjAwjTMAsBwkziwTiTMMSuMMACyHARPnX3vUo/Jro2ksWqIncSaROMPAHFUbAFgOAybOD77vffdMY9ESPYkzicQZBiZxZipVdURV/VtVfaKqzqiqx827JgDYkAET57PPPjtnj6axaImexJlE4gwD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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'ctrl_center_button'}\n", + "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "af466c68", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", + "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c0f1691", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:wt-ephys_no_curation]", + "language": "python", + "name": "conda-env-wt-ephys_no_curation-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/requirements.txt b/requirements.txt index f5a04193..6ffbc8ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,15 @@ datajoint>=0.13.0 +<<<<<<< HEAD element-array-ephys==0.1.0b0 element-lab>=0.1.0b0 element-animal==0.1.0b0 element-session==0.1.0b0 element-interface @ git+https://github.com/datajoint/element-interface.git ipykernel==6.0.1 +======= +element-array-ephys +element-lab +element-animal +element-session +ipykernel +>>>>>>> 579683d (add tests for NWB export) diff --git a/test_export b/test_export new file mode 100644 index 0000000000000000000000000000000000000000..82daee09f5d9bab4d4ef6cf1c24d6067e7457f8a GIT binary patch literal 800 zcmeD5aB<`1lHy_j0S*oZ76t(@6Gr@p0tIG>2#gPtPk=HQp>zk7Ucm%mFfxE31A_!q zTo7tLy1I}cS67e{nE5aos%?}S;UVDR>KFhDf(U3ha6su3&~ygng3}s^4OR>jq<{th Dq@*Z_ literal 0 HcmV?d00001 diff --git a/tests/__init__.py b/tests/__init__.py index 219f8545..0b8feb5f 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -10,14 +10,13 @@ import pathlib import datajoint as dj -import workflow_array_ephys from workflow_array_ephys.paths import get_ephys_root_data_dir from element_interface.utils import find_full_path # ------------------- SOME CONSTANTS ------------------- -_tear_down = True +_tear_down = False verbose = False test_user_data_dir = pathlib.Path('./tests/user_data') @@ -54,6 +53,8 @@ def dj_config(): dj.config.load('./dj_local_conf.json') dj.config['safemode'] = False dj.config['custom'] = { + 'ephys_mode': (os.environ.get('EPHYS_MODE') + or dj.config['custom']['ephys_mode']), 'database.prefix': (os.environ.get('DATABASE_PREFIX') or dj.config['custom']['database.prefix']), 'ephys_root_data_dir': (os.environ.get('EPHYS_ROOT_DATA_DIR').split(',') if os.environ.get('EPHYS_ROOT_DATA_DIR') else dj.config['custom']['ephys_root_data_dir']) @@ -112,7 +113,9 @@ def pipeline(): 'ephys': pipeline.ephys, 'probe': pipeline.probe, 'session': pipeline.session, - 'get_ephys_root_data_dir': pipeline.get_ephys_root_data_dir} + 'get_ephys_root_data_dir': pipeline.get_ephys_root_data_dir, + 'ephys_mode': pipeline.ephys_mode} + if verbose and _tear_down: pipeline.subject.Subject.delete() elif not verbose and _tear_down: @@ -201,7 +204,7 @@ def testdata_paths(): def ephys_insertionlocation(pipeline, ingest_sessions): """Insert probe location into ephys.InsertionLocation""" ephys = pipeline['ephys'] - + for probe_insertion_key in ephys.ProbeInsertion.fetch('KEY'): ephys.InsertionLocation.insert1(dict(**probe_insertion_key, skull_reference='Bregma', @@ -252,7 +255,10 @@ def kilosort_paramset(pipeline): # Insert here, since most of the test will require this paramset inserted ephys.ClusteringParamSet.insert_new_params( - 'kilosort2', 0, 'Spike sorting using Kilosort2', params_ks) + clustering_method='kilosort2.5', + paramset_desc='Spike sorting using Kilosort2.5', + params=params_ks, + paramset_idx=0) yield params_ks @@ -280,7 +286,6 @@ def ephys_recordings(pipeline, ingest_sessions): with QuietStdOut(): ephys.EphysRecording.delete() - @pytest.fixture def clustering_tasks(pipeline, kilosort_paramset, ephys_recordings): """Insert keys from ephys.EphysRecording into ephys.Clustering""" @@ -294,9 +299,9 @@ def clustering_tasks(pipeline, kilosort_paramset, ephys_recordings): kilosort_dir = next(recording_dir.rglob('spike_times.npy')).parent ephys.ClusteringTask.insert1({**ephys_rec_key, 'paramset_idx': 0, - 'clustering_output_dir': - kilosort_dir.as_posix() - }, skip_duplicates=True) + 'task_mode': 'load', + 'clustering_output_dir': kilosort_dir.as_posix()}, + skip_duplicates=True) yield @@ -328,12 +333,15 @@ def clustering(clustering_tasks, pipeline): @pytest.fixture def curations(clustering, pipeline): """Insert keys from ephys.ClusteringTask into ephys.Curation""" - ephys = pipeline['ephys'] + ephys_mode = pipeline['ephys_mode'] - for key in (ephys.ClusteringTask - ephys.Curation).fetch('KEY'): - ephys.Curation().create1_from_clustering_task(key) + if ephys_mode == 'no-curation': + yield + else: + ephys = pipeline['ephys'] - yield + for key in (ephys.ClusteringTask - ephys.Curation).fetch('KEY'): + ephys.Curation().create1_from_clustering_task(key) if _tear_down: if verbose: diff --git a/tests/test_export.py b/tests/test_export.py index e69de29b..741fcd8d 100644 --- a/tests/test_export.py +++ b/tests/test_export.py @@ -0,0 +1,85 @@ +import sys +import pathlib +import numpy as np + +from . import (dj_config, pipeline, test_data, + subjects_csv, ingest_subjects, + sessions_csv, ingest_sessions, + testdata_paths, kilosort_paramset, + ephys_recordings, clustering_tasks, clustering, curations) + + +def test_subject_nwb_export(ingest_subjects, pipeline): + subject = pipeline['subject'] + subject_key = {'subject': 'subject1'} + nwb_subject = subject.Subject.make_nwb(subject_key) + + subject_info = (subject.Subject & subject_key).fetch1() + + assert nwb_subject.subject_id == subject_info['subject'] + assert nwb_subject.sex == subject_info['sex'] + assert nwb_subject.date_of_birth.date() == subject_info['subject_birth_date'] + + +def test_session_nwb_export(ingest_sessions, pipeline): + session = pipeline['session'] + session_key = {'subject': 'subject1', 'session_datetime': '2018-11-22 18:51:26'} + nwb_session = session.Session.make_nwb(session_key) + + session_info = (session.Session & session_key).fetch1() + + assert nwb_session.session_start_time.strftime('%Y%m%d_%H%M%S') == session_info['session_datetime'].strftime('%Y%m%d_%H%M%S') + assert nwb_session.subject.subject_id == session_info['subject'] + assert nwb_session.experimenter == list(session.SessionExperimenter.fetch('user')) + + +def test_ephys_nwb_export(curations, pipeline, testdata_paths): + ephys = pipeline['ephys'] + probe = pipeline['probe'] + + rel_path = testdata_paths['npx3B-p1-ks'] + curation_key = (ephys.Curation & f'curation_output_dir LIKE "%{rel_path}"').fetch1('KEY') + ephys.CuratedClustering.populate(curation_key) + ephys.LFP.populate(curation_key) + ephys.WaveformSet.populate(curation_key) + + nwb_ephys = ephys.CuratedClustering.make_nwb(curation_key) + + probe_name, probe_type = (ephys.ProbeInsertion * probe.Probe * probe.ProbeType + & curation_key).fetch1('probe', 'probe_type') + + device_name = f'{probe_name} ({probe_type})' + assert device_name in nwb_ephys.devices + + # check LFP + lfp_name = f'probe_{probe_name} - LFP' + assert lfp_name in nwb_ephys.processing['ecephys'].data_interfaces + + lfp_timestamps = (ephys.LFP & curation_key).fetch1('lfp_time_stamps') + lfp_channel_count = len((ephys.LFP.Electrode & curation_key)) + + nwb_lfp = nwb_ephys.processing['ecephys'].data_interfaces[lfp_name].electrical_series['processed_electrical_series'] + assert nwb_lfp.data.shape == (len(lfp_timestamps), lfp_channel_count) + + # check electrodes + nwb_electrodes = nwb_ephys.electrodes.to_dataframe() + electrodes = (ephys.EphysRecording * probe.ElectrodeConfig.Electrode + * probe.ProbeType.Electrode & curation_key).fetch( + format='frame').reset_index() + assert np.array_equal(nwb_electrodes.index, electrodes.index) + assert np.array_equal(nwb_electrodes.rel_x, electrodes.x_coord) + assert np.array_equal(nwb_electrodes.rel_y, electrodes.y_coord) + + # check Unit + nwb_units = nwb_ephys.units.to_dataframe() + + assert len(ephys.CuratedClustering.Unit & curation_key) == len(nwb_units) + assert len(ephys.CuratedClustering.Unit & curation_key & 'cluster_quality_label = "good"') == sum(nwb_units.cluster_quality_label == 'good') + + # check waveform + + assert np.array_equal( + nwb_units.iloc[15].waveform_mean, + (ephys.WaveformSet.PeakWaveform & curation_key + & 'unit = 15').fetch1('peak_electrode_waveform') + ) diff --git a/tests/test_ingest.py b/tests/test_ingest.py index e8d19923..b75bf3da 100644 --- a/tests/test_ingest.py +++ b/tests/test_ingest.py @@ -97,6 +97,6 @@ def test_paramset_insert(kilosort_paramset, pipeline): method, desc, paramset_hash = (ephys.ClusteringParamSet & {'paramset_idx': 0}).fetch1( 'clustering_method', 'paramset_desc', 'param_set_hash') - assert method == 'kilosort2' - assert desc == 'Spike sorting using Kilosort2' + assert method == 'kilosort2.5' + assert desc == 'Spike sorting using Kilosort2.5' assert dict_to_uuid(kilosort_paramset) == paramset_hash diff --git a/tests/test_populate.py b/tests/test_populate.py index c24350bc..c082cbbe 100644 --- a/tests/test_populate.py +++ b/tests/test_populate.py @@ -98,22 +98,19 @@ def test_curated_clustering_populate(curations, pipeline, testdata_paths): ephys = pipeline['ephys'] rel_path = testdata_paths['npx3A-p1-ks'] - curation_key = (ephys.Curation & f'curation_output_dir LIKE "%{rel_path}"' - ).fetch1('KEY') + curation_key = _get_curation_key(rel_path, pipeline) ephys.CuratedClustering.populate(curation_key) assert len(ephys.CuratedClustering.Unit & curation_key & 'cluster_quality_label = "good"') == 76 rel_path = testdata_paths['oe_npx3B-ks'] - curation_key = (ephys.Curation & f'curation_output_dir LIKE "%{rel_path}"' - ).fetch1('KEY') + curation_key = _get_curation_key(rel_path, pipeline) ephys.CuratedClustering.populate(curation_key) assert len(ephys.CuratedClustering.Unit & curation_key & 'cluster_quality_label = "good"') == 68 rel_path = testdata_paths['npx3B-p1-ks'] - curation_key = (ephys.Curation & f'curation_output_dir LIKE "%{rel_path}"' - ).fetch1('KEY') + curation_key = _get_curation_key(rel_path, pipeline) ephys.CuratedClustering.populate(curation_key) assert len(ephys.CuratedClustering.Unit & curation_key & 'cluster_quality_label = "good"') == 55 @@ -126,8 +123,7 @@ def test_waveform_populate_npx3B_OpenEphys(curations, pipeline, testdata_paths): """ ephys = pipeline['ephys'] rel_path = testdata_paths['oe_npx3B-ks'] - curation_key = (ephys.Curation & f'curation_output_dir LIKE "%{rel_path}"' - ).fetch1('KEY') + curation_key = _get_curation_key(rel_path, pipeline) ephys.CuratedClustering.populate(curation_key) ephys.WaveformSet.populate(curation_key) @@ -146,8 +142,7 @@ def test_waveform_populate_npx3B_SpikeGLX(curations, pipeline, testdata_paths): ephys = pipeline['ephys'] rel_path = testdata_paths['npx3B-p1-ks'] - curation_key = (ephys.Curation & f'curation_output_dir LIKE "%{rel_path}"' - ).fetch1('KEY') + curation_key = _get_curation_key(rel_path, pipeline) ephys.CuratedClustering.populate(curation_key) ephys.WaveformSet.populate(curation_key) @@ -155,3 +150,19 @@ def test_waveform_populate_npx3B_SpikeGLX(curations, pipeline, testdata_paths): & curation_key).fetch('peak_electrode_waveform')) assert waveforms.shape == (150, 64) + + +# ---- HELPER FUNCTIONS ---- + +def _get_curation_key(output_relative_path, pipeline): + ephys = pipeline['ephys'] + ephys_mode = pipeline['ephys_mode'] + + if ephys_mode == 'no-curation': + EphysCuration = ephys.ClusteringTask + output_dir_attr_name = 'clustering_output_dir' + else: + EphysCuration = ephys.Curation + output_dir_attr_name = 'curation_output_dir' + + return (EphysCuration & f'{output_dir_attr_name} LIKE "%{output_relative_path}"').fetch1('KEY') diff --git a/user_data/alignments.csv b/user_data/alignments.csv new file mode 100644 index 00000000..e2f1621f --- /dev/null +++ b/user_data/alignments.csv @@ -0,0 +1,4 @@ +alignment_name,alignment_event_type,alignment_time_shift,start_event_type,start_time_shift,end_event_type,end_time_shift +left_button,left,0,left,-3,left,3 +center_button,center,0,center,-3,center,3 +right_button,right,0,right,-3,right,3 diff --git a/user_data/behavior_recordings.csv b/user_data/behavior_recordings.csv new file mode 100644 index 00000000..4ce21f1c --- /dev/null +++ b/user_data/behavior_recordings.csv @@ -0,0 +1,3 @@ +subject,session_datetime,filepath +subject5,2018-07-03 20:32:28,./user_data/trials.csv +subject5,2018-07-03 20:32:28,./user_data/events.csv diff --git a/user_data/blocks.csv b/user_data/blocks.csv new file mode 100644 index 00000000..dd378d8a --- /dev/null +++ b/user_data/blocks.csv @@ -0,0 +1,5 @@ +subject,session_datetime,block_id,block_start_time,block_stop_time,attribute_name,attribute_value +subject5,2018-07-03 20:32:28,1,0,84,type,light +subject5,2018-07-03 20:32:28,2,84,168,type,dark +subject5,2018-07-03 20:32:28,3,168,252,type,light +subject5,2018-07-03 20:32:28,4,252,336,type,dark diff --git a/user_data/events.csv b/user_data/events.csv new file mode 100644 index 00000000..073b62e5 --- /dev/null +++ b/user_data/events.csv @@ -0,0 +1,154 @@ +subject,session_datetime,trial_id,event_start_time,event_type +subject5,2018-07-03 20:32:28,1,0.241,center +subject5,2018-07-03 20:32:28,1,1.529,left +subject5,2018-07-03 20:32:28,2,3.33,right +subject5,2018-07-03 20:32:28,2,4.265,center +subject5,2018-07-03 20:32:28,3,6.622,center +subject5,2018-07-03 20:32:28,3,7.498,right +subject5,2018-07-03 20:32:28,4,10.02,right +subject5,2018-07-03 20:32:28,5,13.094,center +subject5,2018-07-03 20:32:28,6,16.588,center +subject5,2018-07-03 20:32:28,6,17.627,right +subject5,2018-07-03 20:32:28,7,19.782,center +subject5,2018-07-03 20:32:28,7,20.494,center +subject5,2018-07-03 20:32:28,8,22.63,right +subject5,2018-07-03 20:32:28,8,23.59,left +subject5,2018-07-03 20:32:28,9,26.28,right +subject5,2018-07-03 20:32:28,9,27.263,left +subject5,2018-07-03 20:32:28,10,29.394,center +subject5,2018-07-03 20:32:28,11,32.592,center +subject5,2018-07-03 20:32:28,11,33.674,center +subject5,2018-07-03 20:32:28,12,35.861,right +subject5,2018-07-03 20:32:28,13,39.319,right +subject5,2018-07-03 20:32:28,14,42.204,left +subject5,2018-07-03 20:32:28,14,43.163,center +subject5,2018-07-03 20:32:28,15,45.662,center +subject5,2018-07-03 20:32:28,16,48.655,left +subject5,2018-07-03 20:32:28,16,49.668,left +subject5,2018-07-03 20:32:28,17,51.937,left +subject5,2018-07-03 20:32:28,18,55.48,left +subject5,2018-07-03 20:32:28,18,56.667,left +subject5,2018-07-03 20:32:28,19,58.883,right +subject5,2018-07-03 20:32:28,19,59.619,left +subject5,2018-07-03 20:32:28,20,62.054,center +subject5,2018-07-03 20:32:28,21,65.023,right +subject5,2018-07-03 20:32:28,21,65.82,left +subject5,2018-07-03 20:32:28,22,68.202,center +subject5,2018-07-03 20:32:28,23,72.049,right +subject5,2018-07-03 20:32:28,23,72.903,center +subject5,2018-07-03 20:32:28,24,75.05,left +subject5,2018-07-03 20:32:28,25,84.49,left +subject5,2018-07-03 20:32:28,25,85.095,center +subject5,2018-07-03 20:32:28,26,87.504,left +subject5,2018-07-03 20:32:28,26,88.368,left +subject5,2018-07-03 20:32:28,27,90.834,right +subject5,2018-07-03 20:32:28,28,93.849,right +subject5,2018-07-03 20:32:28,29,97.098,left +subject5,2018-07-03 20:32:28,29,98.38,center +subject5,2018-07-03 20:32:28,30,100.62,right +subject5,2018-07-03 20:32:28,31,103.931,center +subject5,2018-07-03 20:32:28,32,107.541,right +subject5,2018-07-03 20:32:28,32,108.2,right +subject5,2018-07-03 20:32:28,33,110.765,center +subject5,2018-07-03 20:32:28,34,113.891,left +subject5,2018-07-03 20:32:28,34,114.741,left +subject5,2018-07-03 20:32:28,35,117.006,center +subject5,2018-07-03 20:32:28,35,118.084,right +subject5,2018-07-03 20:32:28,36,120.256,left +subject5,2018-07-03 20:32:28,36,121.212,center +subject5,2018-07-03 20:32:28,37,123.553,left +subject5,2018-07-03 20:32:28,37,124.465,right +subject5,2018-07-03 20:32:28,38,126.973,left +subject5,2018-07-03 20:32:28,39,130.357,left +subject5,2018-07-03 20:32:28,39,131.638,center +subject5,2018-07-03 20:32:28,40,133.661,left +subject5,2018-07-03 20:32:28,40,134.764,right +subject5,2018-07-03 20:32:28,41,137.35,right +subject5,2018-07-03 20:32:28,42,140.573,left +subject5,2018-07-03 20:32:28,43,143.71,left +subject5,2018-07-03 20:32:28,44,146.95,right +subject5,2018-07-03 20:32:28,44,147.981,right +subject5,2018-07-03 20:32:28,45,150.1,center +subject5,2018-07-03 20:32:28,46,153.394,right +subject5,2018-07-03 20:32:28,47,156.783,right +subject5,2018-07-03 20:32:28,47,157.856,center +subject5,2018-07-03 20:32:28,48,160.237,right +subject5,2018-07-03 20:32:28,49,163.41,center +subject5,2018-07-03 20:32:28,50,168.543,right +subject5,2018-07-03 20:32:28,50,169.562,right +subject5,2018-07-03 20:32:28,51,171.455,center +subject5,2018-07-03 20:32:28,52,175.002,left +subject5,2018-07-03 20:32:28,52,176.259,left +subject5,2018-07-03 20:32:28,53,178.429,center +subject5,2018-07-03 20:32:28,53,179.629,center +subject5,2018-07-03 20:32:28,54,181.39,left +subject5,2018-07-03 20:32:28,55,184.639,right +subject5,2018-07-03 20:32:28,55,186.014,right +subject5,2018-07-03 20:32:28,56,188.182,left +subject5,2018-07-03 20:32:28,57,191.348,center +subject5,2018-07-03 20:32:28,57,192.091,center +subject5,2018-07-03 20:32:28,58,194.804,left +subject5,2018-07-03 20:32:28,58,195.743,left +subject5,2018-07-03 20:32:28,59,197.978,right +subject5,2018-07-03 20:32:28,60,200.774,left +subject5,2018-07-03 20:32:28,60,202.206,center +subject5,2018-07-03 20:32:28,61,204.198,left +subject5,2018-07-03 20:32:28,61,205.417,left +subject5,2018-07-03 20:32:28,62,207.538,center +subject5,2018-07-03 20:32:28,63,210.839,center +subject5,2018-07-03 20:32:28,63,211.747,center +subject5,2018-07-03 20:32:28,64,214.213,right +subject5,2018-07-03 20:32:28,64,215.096,left +subject5,2018-07-03 20:32:28,65,217.684,left +subject5,2018-07-03 20:32:28,66,220.714,center +subject5,2018-07-03 20:32:28,66,221.4,center +subject5,2018-07-03 20:32:28,67,224.075,center +subject5,2018-07-03 20:32:28,67,224.846,left +subject5,2018-07-03 20:32:28,68,227.159,right +subject5,2018-07-03 20:32:28,68,228.137,center +subject5,2018-07-03 20:32:28,69,230.632,left +subject5,2018-07-03 20:32:28,69,231.505,center +subject5,2018-07-03 20:32:28,70,233.681,center +subject5,2018-07-03 20:32:28,71,237.067,right +subject5,2018-07-03 20:32:28,72,240.201,right +subject5,2018-07-03 20:32:28,73,243.41,center +subject5,2018-07-03 20:32:28,74,246.725,left +subject5,2018-07-03 20:32:28,75,252.418,right +subject5,2018-07-03 20:32:28,76,256.161,right +subject5,2018-07-03 20:32:28,77,259.277,right +subject5,2018-07-03 20:32:28,78,262.736,left +subject5,2018-07-03 20:32:28,78,263.526,right +subject5,2018-07-03 20:32:28,79,265.636,right +subject5,2018-07-03 20:32:28,79,266.654,center +subject5,2018-07-03 20:32:28,80,268.96,center +subject5,2018-07-03 20:32:28,81,272.152,right +subject5,2018-07-03 20:32:28,81,272.954,right +subject5,2018-07-03 20:32:28,82,275.033,left +subject5,2018-07-03 20:32:28,83,278.411,left +subject5,2018-07-03 20:32:28,84,281.852,center +subject5,2018-07-03 20:32:28,85,284.743,left +subject5,2018-07-03 20:32:28,86,288.422,right +subject5,2018-07-03 20:32:28,86,289.326,left +subject5,2018-07-03 20:32:28,87,291.666,left +subject5,2018-07-03 20:32:28,87,292.366,left +subject5,2018-07-03 20:32:28,88,295.061,center +subject5,2018-07-03 20:32:28,88,295.665,center +subject5,2018-07-03 20:32:28,89,297.889,left +subject5,2018-07-03 20:32:28,89,299.087,right +subject5,2018-07-03 20:32:28,90,301.612,center +subject5,2018-07-03 20:32:28,90,302.257,left +subject5,2018-07-03 20:32:28,91,304.82,left +subject5,2018-07-03 20:32:28,92,307.794,left +subject5,2018-07-03 20:32:28,92,308.817,left +subject5,2018-07-03 20:32:28,93,310.856,center +subject5,2018-07-03 20:32:28,94,314.304,center +subject5,2018-07-03 20:32:28,95,317.795,left +subject5,2018-07-03 20:32:28,95,318.518,center +subject5,2018-07-03 20:32:28,96,320.594,right +subject5,2018-07-03 20:32:28,97,323.87,right +subject5,2018-07-03 20:32:28,97,325.213,right +subject5,2018-07-03 20:32:28,98,327.498,center +subject5,2018-07-03 20:32:28,98,328.386,center +subject5,2018-07-03 20:32:28,99,330.959,center +subject5,2018-07-03 20:32:28,99,332.02,left +subject5,2018-07-03 20:32:28,100,334.177,center diff --git a/user_data/sessions.csv b/user_data/sessions.csv index 1983068b..4e2b93c2 100644 --- a/user_data/sessions.csv +++ b/user_data/sessions.csv @@ -1,2 +1,3 @@ subject,session_dir -subject6,/tmp/test_data/workflow-array-ephys-test-set/subject6/session1/ +subject5,subject5/session1/ +subject6,subject6/session1/ diff --git a/user_data/subjects.csv b/user_data/subjects.csv index f5d2dc25..390d04a6 100644 --- a/user_data/subjects.csv +++ b/user_data/subjects.csv @@ -1,2 +1,3 @@ subject,sex,subject_birth_date,subject_description +subject5,F,2020-01-01,rich subject6,M,2020-01-03,hneih_E105 diff --git a/user_data/trials.csv b/user_data/trials.csv new file mode 100644 index 00000000..9e6195a8 --- /dev/null +++ b/user_data/trials.csv @@ -0,0 +1,101 @@ +subject,session_datetime,block_id,trial_id,trial_start_time,trial_stop_time,trial_type,attribute_name,attribute_value, +subject5,2018-07-03 20:32:28,1,1,0.044,3.044,stim,lumen,882, +subject5,2018-07-03 20:32:28,1,2,3.261,6.261,ctrl,lumen,855, +subject5,2018-07-03 20:32:28,1,3,6.483,9.483,ctrl,lumen,558, +subject5,2018-07-03 20:32:28,1,4,9.639,12.639,ctrl,lumen,705, +subject5,2018-07-03 20:32:28,1,5,12.763,15.763,stim,lumen,660, +subject5,2018-07-03 20:32:28,1,6,16.176,19.176,stim,lumen,807, +subject5,2018-07-03 20:32:28,1,7,19.441,22.441,ctrl,lumen,712, +subject5,2018-07-03 20:32:28,1,8,22.544,25.544,ctrl,lumen,974, +subject5,2018-07-03 20:32:28,1,9,25.848,28.848,ctrl,lumen,947, +subject5,2018-07-03 20:32:28,1,10,28.996,31.996,stim,lumen,836, +subject5,2018-07-03 20:32:28,1,11,32.294,35.294,stim,lumen,510, +subject5,2018-07-03 20:32:28,1,12,35.59,38.59,stim,lumen,805, +subject5,2018-07-03 20:32:28,1,13,38.892,41.892,ctrl,lumen,613, +subject5,2018-07-03 20:32:28,1,14,42.061,45.061,ctrl,lumen,578, +subject5,2018-07-03 20:32:28,1,15,45.414,48.414,ctrl,lumen,783, +subject5,2018-07-03 20:32:28,1,16,48.563,51.563,ctrl,lumen,596, +subject5,2018-07-03 20:32:28,1,17,51.759,54.759,stim,lumen,532, +subject5,2018-07-03 20:32:28,1,18,55.168,58.168,stim,lumen,636, +subject5,2018-07-03 20:32:28,1,19,58.49,61.49,stim,lumen,717, +subject5,2018-07-03 20:32:28,1,20,61.637,64.637,ctrl,lumen,546, +subject5,2018-07-03 20:32:28,1,21,64.799,67.799,stim,lumen,651, +subject5,2018-07-03 20:32:28,1,22,68.139,71.139,stim,lumen,602, +subject5,2018-07-03 20:32:28,1,23,71.571,74.571,stim,lumen,967, +subject5,2018-07-03 20:32:28,1,24,74.757,77.757,stim,lumen,570, +subject5,2018-07-03 20:32:28,1,25,84.005,87.005,stim,lumen,929, +subject5,2018-07-03 20:32:28,2,26,87.264,90.264,stim,lumen,0, +subject5,2018-07-03 20:32:28,2,27,90.696,93.696,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,2,28,93.838,96.838,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,2,29,97.057,100.057,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,2,30,100.386,103.386,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,2,31,103.8,106.8,stim,lumen,0, +subject5,2018-07-03 20:32:28,2,32,107.05,110.05,stim,lumen,0, +subject5,2018-07-03 20:32:28,2,33,110.445,113.445,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,2,34,113.593,116.593,stim,lumen,0, +subject5,2018-07-03 20:32:28,2,35,116.813,119.813,stim,lumen,0, +subject5,2018-07-03 20:32:28,2,36,120.184,123.184,stim,lumen,0, +subject5,2018-07-03 20:32:28,2,37,123.387,126.387,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,2,38,126.784,129.784,stim,lumen,0, +subject5,2018-07-03 20:32:28,2,39,130.151,133.151,stim,lumen,0, +subject5,2018-07-03 20:32:28,2,40,133.538,136.538,stim,lumen,0, +subject5,2018-07-03 20:32:28,2,41,136.907,139.907,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,2,42,140.308,143.308,stim,lumen,0, +subject5,2018-07-03 20:32:28,2,43,143.6,146.6,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,2,44,146.717,149.717,stim,lumen,0, +subject5,2018-07-03 20:32:28,2,45,150.061,153.061,stim,lumen,0, +subject5,2018-07-03 20:32:28,2,46,153.302,156.302,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,2,47,156.495,159.495,stim,lumen,0, +subject5,2018-07-03 20:32:28,2,48,159.838,162.838,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,2,49,163.089,166.089,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,2,50,168.263,171.263,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,3,51,171.43,174.43,ctrl,lumen,848, +subject5,2018-07-03 20:32:28,3,52,174.814,177.814,ctrl,lumen,609, +subject5,2018-07-03 20:32:28,3,53,178.174,181.174,stim,lumen,940, +subject5,2018-07-03 20:32:28,3,54,181.303,184.303,ctrl,lumen,748, +subject5,2018-07-03 20:32:28,3,55,184.631,187.631,stim,lumen,645, +subject5,2018-07-03 20:32:28,3,56,187.822,190.822,ctrl,lumen,604, +subject5,2018-07-03 20:32:28,3,57,190.971,193.971,ctrl,lumen,602, +subject5,2018-07-03 20:32:28,3,58,194.359,197.359,ctrl,lumen,965, +subject5,2018-07-03 20:32:28,3,59,197.559,200.559,ctrl,lumen,574, +subject5,2018-07-03 20:32:28,3,60,200.71,203.71,ctrl,lumen,752, +subject5,2018-07-03 20:32:28,3,61,204.074,207.074,ctrl,lumen,974, +subject5,2018-07-03 20:32:28,3,62,207.442,210.442,ctrl,lumen,512, +subject5,2018-07-03 20:32:28,3,63,210.617,213.617,ctrl,lumen,908, +subject5,2018-07-03 20:32:28,3,64,213.958,216.958,stim,lumen,700, +subject5,2018-07-03 20:32:28,3,65,217.205,220.205,ctrl,lumen,566, +subject5,2018-07-03 20:32:28,3,66,220.364,223.364,ctrl,lumen,552, +subject5,2018-07-03 20:32:28,3,67,223.68,226.68,ctrl,lumen,619, +subject5,2018-07-03 20:32:28,3,68,226.878,229.878,stim,lumen,677, +subject5,2018-07-03 20:32:28,3,69,230.224,233.224,ctrl,lumen,953, +subject5,2018-07-03 20:32:28,3,70,233.51,236.51,stim,lumen,644, +subject5,2018-07-03 20:32:28,3,71,236.615,239.615,stim,lumen,911, +subject5,2018-07-03 20:32:28,3,72,239.776,242.776,ctrl,lumen,697, +subject5,2018-07-03 20:32:28,3,73,243.016,246.016,ctrl,lumen,608, +subject5,2018-07-03 20:32:28,3,74,246.252,249.252,stim,lumen,680, +subject5,2018-07-03 20:32:28,3,75,252.329,255.329,ctrl,lumen,899, +subject5,2018-07-03 20:32:28,4,76,255.744,258.744,stim,lumen,0, +subject5,2018-07-03 20:32:28,4,77,259.006,262.006,stim,lumen,0, +subject5,2018-07-03 20:32:28,4,78,262.345,265.345,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,4,79,265.623,268.623,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,4,80,268.763,271.763,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,4,81,271.883,274.883,stim,lumen,0, +subject5,2018-07-03 20:32:28,4,82,275.032,278.032,stim,lumen,0, +subject5,2018-07-03 20:32:28,4,83,278.185,281.185,stim,lumen,0, +subject5,2018-07-03 20:32:28,4,84,281.449,284.449,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,4,85,284.664,287.664,stim,lumen,0, +subject5,2018-07-03 20:32:28,4,86,288.03,291.03,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,4,87,291.282,294.282,stim,lumen,0, +subject5,2018-07-03 20:32:28,4,88,294.622,297.622,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,4,89,297.762,300.762,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,4,90,301.183,304.183,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,4,91,304.353,307.353,stim,lumen,0, +subject5,2018-07-03 20:32:28,4,92,307.502,310.502,stim,lumen,0, +subject5,2018-07-03 20:32:28,4,93,310.775,313.775,stim,lumen,0, +subject5,2018-07-03 20:32:28,4,94,314.205,317.205,ctrl,lumen,0, +subject5,2018-07-03 20:32:28,4,95,317.348,320.348,stim,lumen,0, +subject5,2018-07-03 20:32:28,4,96,320.572,323.572,stim,lumen,0, +subject5,2018-07-03 20:32:28,4,97,323.8,326.8,stim,lumen,0, +subject5,2018-07-03 20:32:28,4,98,327.151,330.151,stim,lumen,0, +subject5,2018-07-03 20:32:28,4,99,330.53,333.53,stim,lumen,0, +subject5,2018-07-03 20:32:28,4,100,333.72,336.72,stim,lumen,0, diff --git a/workflow_array_ephys/analysis.py b/workflow_array_ephys/analysis.py new file mode 100644 index 00000000..3d047a7f --- /dev/null +++ b/workflow_array_ephys/analysis.py @@ -0,0 +1,142 @@ +import datajoint as dj +import numpy as np + +from workflow_array_ephys.pipeline import db_prefix, session, ephys, trial, event + + +schema = dj.schema(db_prefix + 'analysis') + + +@schema +class SpikesAlignmentCondition(dj.Manual): + definition = """ + -> ephys.CuratedClustering + -> event.AlignmentEvent + trial_condition: varchar(128) # user-friendly name of condition + --- + condition_description='': varchar(1000) + bin_size=0.04: float # bin-size (in second) used to compute the PSTH + """ + + class Trial(dj.Part): + definition = """ # Trials (or subset of trials) to computed event-aligned spikes and PSTH on + -> master + -> trial.Trial + """ + + +@schema +class SpikesAlignment(dj.Computed): + definition = """ + -> SpikesAlignmentCondition + -> ephys.CuratedClustering + """ + + class AlignedTrialSpikes(dj.Part): + definition = """ + -> master + -> ephys.CuratedClustering.Unit + -> SpikesAlignmentCondition.Trial + --- + aligned_spike_times: longblob # (s) spike times relative to the alignment event time + """ + + class UnitPSTH(dj.Part): + definition = """ + -> master + -> ephys.CuratedClustering.Unit + --- + psth: longblob # event-aligned spike peristimulus time histogram (PSTH) + psth_edges: longblob + """ + + def make(self, key): + unit_keys, unit_spike_times = (ephys.CuratedClustering.Unit & key).fetch('KEY', 'spike_times', order_by='unit') + + trial_keys, trial_starts, trial_ends = (trial.Trial & (SpikesAlignmentCondition.Trial & key)).fetch( + 'KEY', 'trial_start_time', 'trial_stop_time', order_by='trial_id') + + bin_size = (SpikesAlignmentCondition & key).fetch1('bin_size') + + alignment_spec = (event.AlignmentEvent & key).fetch1() + + # Spike raster + aligned_trial_spikes = [] + units_spike_raster = {u['unit']: {**key, **u, 'aligned_spikes': []} for u in unit_keys} + min_limit, max_limit = np.Inf, -np.Inf + for trial_key, trial_start, trial_stop in zip(trial_keys, trial_starts, trial_ends): + alignment_event_time = (event.Event & key & {'event_type': alignment_spec['alignment_event_type']} + & f'event_start_time BETWEEN {trial_start} AND {trial_stop}') + if alignment_event_time: + # if there are multiple of such alignment event, pick the last one in the trial + alignment_event_time = alignment_event_time.fetch( + 'event_start_time', order_by='event_start_time DESC', limit=1)[0] + else: + continue + + alignment_start_time = (event.Event & key & {'event_type': alignment_spec['start_event_type']} + & f'event_start_time < {alignment_event_time}') + if alignment_start_time: + # if there are multiple of such start event, pick the most immediate one prior to the alignment event + alignment_start_time = alignment_start_time.fetch( + 'event_start_time', order_by='event_start_time DESC', limit=1)[0] + alignment_start_time = max(alignment_start_time, trial_start) + else: + alignment_start_time = trial_start + + alignment_end_time = (event.Event & key & {'event_type': alignment_spec['end_event_type']} + & f'event_start_time > {alignment_event_time}') + if alignment_end_time: + # if there are multiple of such start event, pick the most immediate one following the alignment event + alignment_end_time = alignment_end_time.fetch( + 'event_start_time', order_by='event_start_time', limit=1)[0] + alignment_end_time = min(alignment_end_time, trial_stop) + else: + alignment_end_time = trial_stop + + alignment_event_time += alignment_spec['alignment_time_shift'] + alignment_start_time += alignment_spec['start_time_shift'] + alignment_end_time += alignment_spec['end_time_shift'] + + min_limit = min(alignment_start_time - alignment_event_time, min_limit) + max_limit = max(alignment_end_time - alignment_event_time, max_limit) + + for unit_key, spikes in zip(unit_keys, unit_spike_times): + aligned_spikes = spikes[(alignment_start_time <= spikes) + & (spikes < alignment_end_time)] - alignment_event_time + aligned_trial_spikes.append({**key, **unit_key, **trial_key, 'aligned_spike_times': aligned_spikes}) + units_spike_raster[unit_key['unit']]['aligned_spikes'].append(aligned_spikes) + + # PSTH + for unit_spike_raster in units_spike_raster.values(): + spikes = np.concatenate(unit_spike_raster['aligned_spikes']) + + psth, edges = np.histogram(spikes, bins=np.arange(min_limit, max_limit, bin_size)) + unit_spike_raster['psth'] = psth / len(unit_spike_raster.pop('aligned_spikes')) / bin_size + unit_spike_raster['psth_edges'] = edges[1:] + + self.insert1(key) + self.AlignedTrialSpikes.insert(aligned_trial_spikes) + self.UnitPSTH.insert(list(units_spike_raster.values())) + + def plot_raster(self, key, unit, axs=None): + import matplotlib.pyplot as plt + from .plotting import plot_psth + + fig = None + if axs is None: + fig, axs = plt.subplots(2, 1, figsize=(12, 8)) + + trial_ids, aligned_spikes = (self.AlignedTrialSpikes + & key & {'unit': unit}).fetch('trial_id', 'aligned_spike_times') + psth, psth_edges = (self.UnitPSTH & key & {'unit': unit}).fetch1( + 'psth', 'psth_edges') + + xlim = psth_edges[0], psth_edges[-1] + + plot_psth._plot_spike_raster(aligned_spikes, trial_ids=trial_ids, ax=axs[0], + title=f'{dict(**key, unit=unit)}', xlim=xlim) + plot_psth._plot_psth(psth, psth_edges, ax=axs[1], + title='', xlim=xlim) + + return fig diff --git a/workflow_array_ephys/ingest.py b/workflow_array_ephys/ingest.py index 9ced0519..23f11518 100644 --- a/workflow_array_ephys/ingest.py +++ b/workflow_array_ephys/ingest.py @@ -1,28 +1,50 @@ import csv import re -from workflow_array_ephys.pipeline import subject, ephys, probe, session +from workflow_array_ephys.pipeline import subject, ephys, probe, session, trial, event from workflow_array_ephys.paths import get_ephys_root_data_dir +from workflow_array_ephys.pipeline import ephys_mode from element_array_ephys.readers import spikeglx, openephys from element_interface.utils import find_root_directory, find_full_path -def ingest_subjects(subject_csv_path='./user_data/subjects.csv', verbose=True): +def ingest_general(csvs, tables, skip_duplicates=True, verbose=True, + allow_direct_insert=False): + """ + Inserts data from a series of csvs into their corresponding table: + e.g., ingest_general(['./lab_data.csv', './proj_data.csv'], + [lab.Lab(),lab.Project()] + ingest_general(csvs, tables, skip_duplicates=True, verbose=True, allow_direct_insert=False) + :param csvs: list of relative paths to CSV files. CSV are delimited by commas. + :param tables: list of datajoint tables with () + :param verbose: print number inserted (i.e., table length change) + :param skip_duplicates: + :param allow_direct_insert: + """ + for csv_filepath, table in zip(csvs, tables): + with open(csv_filepath, newline='') as f: + data = list(csv.DictReader(f, delimiter=',')) + if verbose: + prev_len = len(table) + table.insert(data, skip_duplicates=skip_duplicates, + # Ignore extra fields because some CSVs feed multiple tables + ignore_extra_fields=True, allow_direct_insert=allow_direct_insert) + if verbose: + insert_len = len(table) - prev_len # report length change + print(f'\n---- Inserting {insert_len} entry(s) ' + + f'into {table.table_name} ----') + + +def ingest_subjects(subject_csv_path='./user_data/subjects.csv', + skip_duplicates=True, verbose=True): """ Ingest subjects listed in the subject column of ./user_data/subjects.csv """ - # -------------- Insert new "Subject" -------------- - with open(subject_csv_path, newline='') as f: - input_subjects = list(csv.DictReader(f, delimiter=',')) - if verbose: - previous_length = len(subject.Subject.fetch()) - subject.Subject.insert(input_subjects, skip_duplicates=True) - if verbose: - insert_length = len(subject.Subject.fetch()) - previous_length - print(f'\n---- Insert {insert_length} entry(s) into ' - + 'subject.Subject ----') - print('\n---- Successfully completed ingest_subjects ----') + csvs = [subject_csv_path] + tables = [subject.Subject()] + + ingest_general(csvs, tables, skip_duplicates=skip_duplicates, verbose=verbose) def ingest_sessions(session_csv_path='./user_data/sessions.csv', verbose=True): @@ -108,13 +130,61 @@ def ingest_sessions(session_csv_path='./user_data/sessions.csv', verbose=True): if verbose: print(f'\n---- Insert {len(probe_list)} entry(s) into probe.Probe ----') - ephys.ProbeInsertion.insert(probe_insertion_list) - if verbose: - print(f'\n---- Insert {len(probe_insertion_list)} entry(s) into ' - + 'ephys.ProbeInsertion ----') - print('\n---- Successfully completed ingest_subjects ----') + if ephys_mode == 'chronic': + ephys.ProbeInsertion.insert(probe_insertion_list, + ignore_extra_fields=True, skip_duplicates=True) + session.Session.insert(session_list) + session.SessionDirectory.insert(session_dir_list) + if verbose: + print(f'\n---- Insert {len(probe_insertion_list)} entry(s) into ' + + 'ephys.ProbeInsertion ----') + print(f'\n---- Insert {len(session_list)} entry(s) into ' + + 'session.Session ----') + else: + session.Session.insert(session_list) + session.SessionDirectory.insert(session_dir_list) + ephys.ProbeInsertion.insert(probe_insertion_list) + if verbose: + print(f'\n---- Insert {len(probe_insertion_list)} entry(s) into ' + + 'ephys.ProbeInsertion ----') + print(f'\n---- Insert {len(session_list)} entry(s) into ' + + 'session.Session ----') + + print('\n---- Successfully completed workflow_array_ephys/ingest.py ----') + + +def ingest_events(recording_csv_path='./user_data/behavior_recordings.csv', + block_csv_path='./user_data/blocks.csv', + trial_csv_path='./user_data/trials.csv', + event_csv_path='./user_data/events.csv', + skip_duplicates=True, verbose=True): + csvs = [recording_csv_path, recording_csv_path, + block_csv_path, block_csv_path, + trial_csv_path, trial_csv_path, trial_csv_path, + trial_csv_path, + event_csv_path,event_csv_path] + tables = [event.BehaviorRecording(), event.BehaviorRecording.File(), + trial.Block(), trial.Block.Attribute(), + trial.TrialType(), trial.Trial(), trial.Trial.Attribute(), + trial.BlockTrial(), + event.EventType(), event.Event()] + + # Allow direct insert required bc element-trial has Imported that should be Manual + ingest_general(csvs, tables, skip_duplicates=skip_duplicates, verbose=verbose, + allow_direct_insert=True) + + +def ingest_alignment(alignment_csv_path='./user_data/alignments.csv', + skip_duplicates=True, verbose=True): + + csvs = [alignment_csv_path] + tables = [event.AlignmentEvent()] + + ingest_general(csvs, tables, skip_duplicates=skip_duplicates, verbose=verbose) if __name__ == '__main__': ingest_subjects() ingest_sessions() + ingest_events() + ingest_alignment() diff --git a/workflow_array_ephys/pipeline.py b/workflow_array_ephys/pipeline.py index eecd2878..f5a079be 100644 --- a/workflow_array_ephys/pipeline.py +++ b/workflow_array_ephys/pipeline.py @@ -1,7 +1,10 @@ import datajoint as dj +import os from element_animal import subject from element_lab import lab from element_session import session +from element_array_ephys import probe +from element_trial import trial, event from element_array_ephys import probe, ephys from element_animal.subject import Subject @@ -15,6 +18,22 @@ db_prefix = dj.config['custom'].get('database.prefix', '') +__all__ = ['subject', 'lab', 'session', 'trial', 'event', 'probe', 'ephys', 'Subject', + 'Source', 'Lab', 'Protocol', 'User', 'Project', 'Session', + 'get_ephys_root_data_dir', 'get_session_directory'] + + +# ------------- Import the configured "ephys mode" ------------- +ephys_mode = os.getenv('EPHYS_MODE', + dj.config['custom'].get('ephys_mode', 'acute')) +if ephys_mode == 'acute': + from element_array_ephys import ephys +elif ephys_mode == 'chronic': + from element_array_ephys import ephys_chronic as ephys +elif ephys_mode == 'no-curation': + from element_array_ephys import ephys_no_curation as ephys +else: + raise ValueError(f'Unknown ephys mode: {ephys_mode}') # Activate "lab", "subject", "session" schema --------------------------------- @@ -25,6 +44,8 @@ Experimenter = lab.User session.activate(db_prefix + 'session', linking_module=__name__) +trial.activate(db_prefix + 'trial', db_prefix + 'event', linking_module= __name__) + # Declare table "SkullReference" for use in element-array-ephys --------------- diff --git a/workflow_array_ephys/plotting/__init__.py b/workflow_array_ephys/plotting/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/workflow_array_ephys/plotting/plot_psth.py b/workflow_array_ephys/plotting/plot_psth.py new file mode 100644 index 00000000..50c1a3b9 --- /dev/null +++ b/workflow_array_ephys/plotting/plot_psth.py @@ -0,0 +1,45 @@ +import numpy as np +import matplotlib.pyplot as plt + + +def _plot_spike_raster(aligned_spikes, trial_ids=None, vlines=[0], ax=None, title='', xlim=None): + if not ax: + fig, ax = plt.subplots(1, 1) + + raster = np.concatenate(aligned_spikes) + if trial_ids is None: + trial_ids = range(len(aligned_spikes)) + + trial_ids = np.concatenate([[t] * len(s) + for t, s in zip(trial_ids, aligned_spikes)]).astype(int) + + assert len(raster) == len(trial_ids) + + ax.plot(raster, trial_ids, 'r.', markersize=1) + + for x in vlines: + ax.axvline(x=x, linestyle='--', color='k') + + if xlim: + ax.set_xlim(xlim) + ax.set_axis_off() + ax.set_title(title) + + +def _plot_psth(psth, psth_edges, vlines=[0], ax=None, title='', xlim=None): + if not ax: + fig, ax = plt.subplots(1, 1) + + ax.plot(psth_edges, psth, 'r') + + for x in vlines: + ax.axvline(x=x, linestyle='--', color='k') + + ax.set_ylabel('spikes/s') + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + + if xlim: + ax.set_xlim(xlim) + ax.set_xlabel('Time (s)') + ax.set_title(title) From e8bfc602fad5c432b351cbd9ad285e717d543ebb Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Fri, 18 Mar 2022 19:48:14 -0500 Subject: [PATCH 14/59] Trialized notebook. See details Dockerfile.test: add element-trial Needs corresponding docker dev update Docker-compose-test: update to new name system (rebasing overwrote it when merging branches unfortunately) Add Ephys mode to environment !! Future integration tests will neeed to check all options of ephys mode requirements: fix conflict errors Update csvs for subject6 notebook - downstream analysis - flush out for increased number of events analysis: PEP8 --- docker/Dockerfile.test | 16 +- docker/docker-compose-test.yaml | 9 +- notebooks/07-Downstream analysis.ipynb | 1037 ------------- notebooks/07-downstream-analysis.ipynb | 1837 ++++++++++++++++++++++++ requirements.txt | 8 - test_export | Bin 800 -> 0 bytes user_data/behavior_recordings.csv | 4 +- user_data/blocks.csv | 8 +- user_data/events.csv | 306 ++-- user_data/trials.csv | 202 +-- workflow_array_ephys/analysis.py | 54 +- workflow_array_ephys/ingest.py | 7 +- workflow_array_ephys/pipeline.py | 5 +- 13 files changed, 2154 insertions(+), 1339 deletions(-) delete mode 100644 notebooks/07-Downstream analysis.ipynb create mode 100644 notebooks/07-downstream-analysis.ipynb delete mode 100644 test_export diff --git a/docker/Dockerfile.test b/docker/Dockerfile.test index dfdd7542..e4185050 100644 --- a/docker/Dockerfile.test +++ b/docker/Dockerfile.test @@ -15,27 +15,31 @@ WORKDIR /main/workflow-array-ephys # RUN pip install git+https://github.com/element-lab.git # RUN pip install git+https://github.com//element-animal.git # RUN pip install git+https://github.com//element-session.git +# RUN pip install git+https://github.com//element-trial.git # RUN pip install git+https://github.com//element-array-ephys.git # RUN git clone https://github.com//workflow-array-ephys.git /main/workflow-array-ephys # Option 3 - Install user's local fork of element and workflow -RUN mkdir /main/element-lab \ - /main/element-animal \ - /main/element-session \ - /main/element-array-ephys \ - /main/workflow-array-ephys +RUN mkdir -p /main/element-lab \ + /main/element-animal \ + /main/element-session \ + /main/element-trial \ + /main/element-array-ephys \ + /main/workflow-array-ephys COPY --chown=anaconda:anaconda ./element-lab /main/element-lab COPY --chown=anaconda:anaconda ./element-animal /main/element-animal COPY --chown=anaconda:anaconda ./element-session /main/element-session +COPY --chown=anaconda:anaconda ./element-trial /main/element-trial COPY --chown=anaconda:anaconda ./element-array-ephys /main/element-array-ephys COPY --chown=anaconda:anaconda ./workflow-array-ephys /main/workflow-array-ephys RUN pip install -e /main/element-lab RUN pip install -e /main/element-animal RUN pip install -e /main/element-session +RUN pip install -e /main/element-trial RUN pip install -e /main/element-array-ephys -RUN rm -f /main/workflow-array-ephys/dj_local_conf.json +# RUN rm -f /main/workflow-array-ephys/dj_local_conf.json # Install the workflow RUN pip install /main/workflow-array-ephys diff --git a/docker/docker-compose-test.yaml b/docker/docker-compose-test.yaml index 02945739..8c53e2c6 100644 --- a/docker/docker-compose-test.yaml +++ b/docker/docker-compose-test.yaml @@ -1,7 +1,7 @@ # export COMPOSE_DOCKER_CLI_BUILD=0 # some machines need for smooth --build # .env file: TEST_DATA_DIR= # docker-compose -f ./docker/docker-compose-test.yaml up --build -# docker exec -it workflow-array-ephys_workflow_1 /bin/bash +# docker exec -it workflow-array-ephys-test /bin/bash # docker-compose -f ./docker/docker-compose-test.yaml down version: "2.4" @@ -9,13 +9,13 @@ x-net: &net networks: - main services: - array-ephys-test-db: + db: <<: *net image: datajoint/mysql:5.7 container_name: workflow-array-ephys-test-db environment: - MYSQL_ROOT_PASSWORD=simple - array-ephys-test-workflow: + workflow: <<: *net build: context: ../../ @@ -28,6 +28,7 @@ services: - DJ_USER=root - DJ_PASS=simple - EPHYS_ROOT_DATA_DIR=/main/test_data/workflow_ephys_data1/,/main/test_data/workflow_ephys_data2/ + - EPHYS_MODE=no-curation - DATABASE_PREFIX=test_ command: - bash @@ -46,7 +47,7 @@ services: - ../../element-array-ephys:/main/element-array-ephys - ..:/main/workflow-array-ephys depends_on: - array-ephys-test-db: + db: condition: service_healthy networks: main: diff --git a/notebooks/07-Downstream analysis.ipynb b/notebooks/07-Downstream analysis.ipynb deleted file mode 100644 index f4bce16e..00000000 --- a/notebooks/07-Downstream analysis.ipynb +++ /dev/null @@ -1,1037 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "921a4a03", - "metadata": {}, - "outputs": [], - "source": [ - "cd .." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "79cef246", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Connecting ttngu207@dss-db.datajoint.io:3306\n" - ] - } - ], - "source": [ - "from workflow_array_ephys.pipeline import session, ephys, trial, event" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "0678d202", - "metadata": {}, - "outputs": [], - "source": [ - "from workflow_array_ephys import analysis" - ] - }, - { - "cell_type": "markdown", - "id": "4936a1e8", - "metadata": {}, - "source": [ - "# Event-aligned trialized unit spike times" - ] - }, - { - "cell_type": "markdown", - "id": "a2e97d75", - "metadata": {}, - "source": [ - "The `analysis` schema provides example tables to perform event-aligned spike-times analysis.\n", - "+ ***SpikesAlignmentCondition*** - a manual table to specify the inputs and condition for the analysis\n", - "+ ***SpikesAlignment*** - a computed table to extract event-aligned spikes and compute unit PSTH" - ] - }, - { - "cell_type": "markdown", - "id": "ba4607a1", - "metadata": {}, - "source": [ - "Let's start by creating several analyses configuration - i.e. inserting into ***SpikesAlignmentCondition***" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "9a36c342", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " Clustering results of the spike sorting step.\n", - "
\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

subject

\n", - " \n", - "
\n", - "

session_id

\n", - " \n", - "
\n", - "

insertion_number

\n", - " \n", - "
\n", - "

paramset_idx

\n", - " \n", - "
subject5110
subject5120
\n", - " \n", - "

Total: 2

\n", - " " - ], - "text/plain": [ - "*subject *session_id *insertion_number *paramset_idx \n", - "+----------+ +------------+ +------------------+ +--------------+\n", - "subject5 1 1 0 \n", - "subject5 1 2 0 \n", - " (Total: 2)" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ephys.CuratedClustering()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "8642f010", - "metadata": {}, - "outputs": [], - "source": [ - "clustering_key = (ephys.CuratedClustering & {'subject': 'subject5', 'session_id': 1, 'insertion_number': 1}).fetch1('KEY')" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "50a2c99f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " time_shift is seconds to shift with respect to (WRT) a variable\n", - "
\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

alignment_name

\n", - " \n", - "
\n", - "

alignment_description

\n", - " \n", - "
\n", - "

alignment_event_type

\n", - " \n", - "
\n", - "

alignment_time_shift

\n", - " (s) WRT alignment_event_type\n", - "
\n", - "

start_event_type

\n", - " \n", - "
\n", - "

start_time_shift

\n", - " (s) WRT start_event_type\n", - "
\n", - "

end_event_type

\n", - " \n", - "
\n", - "

end_time_shift

\n", - " (s) WRT end_event_type\n", - "
center_buttoncenter0.0center-3.0center3.0
left_buttonleft0.0left-3.0left3.0
right_buttonright0.0right-3.0right3.0
\n", - " \n", - "

Total: 3

\n", - " " - ], - "text/plain": [ - "*alignment_name alignment_description alignment_event_type alignment_time_shift start_event_type start_time_shift end_event_type end_time_shift \n", - "+----------------+ +-----------------------+ +----------------------+ +----------------------+ +------------------+ +------------------+ +----------------+ +----------------+\n", - "center_button center 0.0 center -3.0 center 3.0 \n", - "left_button left 0.0 left -3.0 left 3.0 \n", - "right_button right 0.0 right -3.0 right 3.0 \n", - " (Total: 3)" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "event.AlignmentEvent()" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "d859203f", - "metadata": {}, - "outputs": [], - "source": [ - "alignment_key = (event.AlignmentEvent & 'alignment_name = \"center_button\"').fetch1('KEY')" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "c9c95806", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " Experimental trials\n", - "
\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

subject

\n", - " \n", - "
\n", - "

session_id

\n", - " \n", - "
\n", - "

trial_id

\n", - " trial number (1-based indexing)\n", - "
\n", - "

trial_type

\n", - " \n", - "
\n", - "

trial_start_time

\n", - " (second) relative to recording start\n", - "
\n", - "

trial_stop_time

\n", - " (second) relative to recording start\n", - "
subject511stim2.04310.043
subject512ctrl10.50818.508
subject513ctrl18.726.7
subject514ctrl26.70734.707
subject515stim34.71542.715
subject516stim42.80650.806
subject517ctrl50.83958.839
subject518ctrl59.19667.196
subject519stim67.3175.31
subject5110ctrl75.77283.772
subject5111stim86.08294.082
subject5112stim94.087102.087
subject5113stim102.183110.183
subject5114stim110.526118.526
subject5115stim118.844126.844
\n", - "

...

\n", - "

Total: 40

\n", - " " - ], - "text/plain": [ - "*subject *session_id *trial_id trial_type trial_start_time trial_stop_time \n", - "+----------+ +------------+ +----------+ +------------+ +------------------+ +-----------------+\n", - "subject5 1 1 stim 2.043 10.043 \n", - "subject5 1 2 ctrl 10.508 18.508 \n", - "subject5 1 3 ctrl 18.7 26.7 \n", - "subject5 1 4 ctrl 26.707 34.707 \n", - "subject5 1 5 stim 34.715 42.715 \n", - "subject5 1 6 stim 42.806 50.806 \n", - "subject5 1 7 ctrl 50.839 58.839 \n", - "subject5 1 8 ctrl 59.196 67.196 \n", - "subject5 1 9 stim 67.31 75.31 \n", - "subject5 1 10 ctrl 75.772 83.772 \n", - "subject5 1 11 stim 86.082 94.082 \n", - "subject5 1 12 stim 94.087 102.087 \n", - "subject5 1 13 stim 102.183 110.183 \n", - "subject5 1 14 stim 110.526 118.526 \n", - "subject5 1 15 stim 118.844 126.844 \n", - " ...\n", - " (Total: 40)" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "trial.Trial & clustering_key" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "1851ad2b", - "metadata": {}, - "outputs": [], - "source": [ - "ctrl_trials = trial.Trial & clustering_key & 'trial_type = \"ctrl\"'" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "01474336", - "metadata": {}, - "outputs": [], - "source": [ - "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'ctrl_center_button'}" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "8bc824cb", - "metadata": {}, - "outputs": [], - "source": [ - "analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "8994e8f5", - "metadata": {}, - "outputs": [], - "source": [ - "analysis.SpikesAlignmentCondition.Trial.insert(\n", - " (analysis.SpikesAlignmentCondition * ctrl_trials & alignment_condition).proj(),\n", - " skip_duplicates=True)" - ] - }, - { - "cell_type": "markdown", - "id": "4ddd0345", - "metadata": {}, - "source": [ - "With the steps above, we have create a new spike alignment condition for analysis, named `ctrl_center_button`, which specifies:\n", - "+ a CuratedClustering of interest for analysis\n", - "+ an event of interest to align the spikes to - `center_button`\n", - "+ a set of trials of interest to perform the analysis on - `ctrl` trials" - ] - }, - { - "cell_type": "markdown", - "id": "2a23b206", - "metadata": {}, - "source": [ - "Now, let's create another set with:\n", - "+ the same CuratedClustering of interest for analysis\n", - "+ an event of interest to align the spikes to - `center_button`\n", - "+ a set of trials of interest to perform the analysis on - `stim` trials" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "39d4d423", - "metadata": {}, - "outputs": [], - "source": [ - "stim_trials = trial.Trial & clustering_key & 'trial_type = \"stim\"'\n", - "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", - "analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True)\n", - "analysis.SpikesAlignmentCondition.Trial.insert(\n", - " (analysis.SpikesAlignmentCondition * stim_trials & alignment_condition).proj(),\n", - " skip_duplicates=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "6452c508", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - "
\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

subject

\n", - " \n", - "
\n", - "

session_id

\n", - " \n", - "
\n", - "

insertion_number

\n", - " \n", - "
\n", - "

paramset_idx

\n", - " \n", - "
\n", - "

alignment_name

\n", - " \n", - "
\n", - "

trial_condition

\n", - " user-friendly name of condition\n", - "
\n", - "

bin_size

\n", - " bin-size (in second) used to compute the PSTH\n", - "
subject5110center_buttonctrl_center_button0.04
subject5110center_buttonstim_center_button0.04
\n", - " \n", - "

Total: 2

\n", - " " - ], - "text/plain": [ - "*subject *session_id *insertion_number *paramset_idx *alignment_name *trial_condition bin_size \n", - "+----------+ +------------+ +------------------+ +--------------+ +----------------+ +--------------------+ +----------+\n", - "subject5 1 1 0 center_button ctrl_center_button 0.04 \n", - "subject5 1 1 0 center_button stim_center_button 0.04 \n", - " (Total: 2)" - ] - }, - "execution_count": 38, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "analysis.SpikesAlignmentCondition()" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "7a9ef2fb", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " Trials (or subset of trials) to computed event-aligned spikes and PSTH on\n", - "
\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

subject

\n", - " \n", - "
\n", - "

session_id

\n", - " \n", - "
\n", - "

insertion_number

\n", - " \n", - "
\n", - "

paramset_idx

\n", - " \n", - "
\n", - "

alignment_name

\n", - " \n", - "
\n", - "

trial_condition

\n", - " user-friendly name of condition\n", - "
\n", - "

trial_id

\n", - " trial number (1-based indexing)\n", - "
subject5110center_buttonctrl_center_button2
subject5110center_buttonctrl_center_button3
subject5110center_buttonctrl_center_button4
subject5110center_buttonctrl_center_button7
subject5110center_buttonctrl_center_button8
subject5110center_buttonctrl_center_button10
subject5110center_buttonctrl_center_button17
subject5110center_buttonctrl_center_button19
subject5110center_buttonctrl_center_button21
subject5110center_buttonctrl_center_button22
subject5110center_buttonctrl_center_button24
subject5110center_buttonctrl_center_button25
subject5110center_buttonctrl_center_button27
subject5110center_buttonctrl_center_button30
subject5110center_buttonctrl_center_button33
\n", - "

...

\n", - "

Total: 18

\n", - " " - ], - "text/plain": [ - "*subject *session_id *insertion_number *paramset_idx *alignment_name *trial_condition *trial_id \n", - "+----------+ +------------+ +------------------+ +--------------+ +----------------+ +--------------------+ +----------+\n", - "subject5 1 1 0 center_button ctrl_center_button 2 \n", - "subject5 1 1 0 center_button ctrl_center_button 3 \n", - "subject5 1 1 0 center_button ctrl_center_button 4 \n", - "subject5 1 1 0 center_button ctrl_center_button 7 \n", - "subject5 1 1 0 center_button ctrl_center_button 8 \n", - "subject5 1 1 0 center_button ctrl_center_button 10 \n", - "subject5 1 1 0 center_button ctrl_center_button 17 \n", - "subject5 1 1 0 center_button ctrl_center_button 19 \n", - "subject5 1 1 0 center_button ctrl_center_button 21 \n", - "subject5 1 1 0 center_button ctrl_center_button 22 \n", - "subject5 1 1 0 center_button ctrl_center_button 24 \n", - "subject5 1 1 0 center_button ctrl_center_button 25 \n", - "subject5 1 1 0 center_button ctrl_center_button 27 \n", - "subject5 1 1 0 center_button ctrl_center_button 30 \n", - "subject5 1 1 0 center_button ctrl_center_button 33 \n", - " ...\n", - " (Total: 18)" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "analysis.SpikesAlignmentCondition.Trial & 'trial_condition = \"ctrl_center_button\"'" - ] - }, - { - "cell_type": "markdown", - "id": "1486add8", - "metadata": {}, - "source": [ - "### Now let's run the computation on these" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "f467d3f7", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "SpikesAlignment: 100%|███████████████████████████████████████████████████████████████████| 2/2 [00:13<00:00, 6.97s/it]\n" - ] - } - ], - "source": [ - "analysis.SpikesAlignment.populate(display_progress=True)" - ] - }, - { - "cell_type": "markdown", - "id": "c4320cdd", - "metadata": {}, - "source": [ - "### Let's visualize the results" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "2c4962b7", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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2vQ0to3EmWTvxPkHP5h3NbDP0zgvodbanbbw8Gsl+Yeq2i/dFguvba29eZjKAGCbDX06qkXex1nSuNzGE+eLL553NbDP0zgvoap6WGvtpKr67dVub6395VqPba39V7pNvAFg3zNQ4pyka5wXcR9SiTOJfZxhYBrn5XSl/v+D51oFAMzakInzaBqLluxKnEns4wwD0zgvodbaC/v/nz7vWgBgpiTOLAuJMwzKUbWXWFXdsKreVFXnVdXXquqfq+qG864LALZFa6ufjmoTadyDHvSgPOjAAxcz0ZM4k0icYWAa5+X2yiSvTXfAr2sneV2SV821IgDYLq11/w+QOD/2sY/NYy9/+cVM9Eap+qLVxWxJnGFQGufldlBr7eWttUv6fyclucK8iwKAbTFqHgZInC+66KJcdMkli5nojfbjXrS6mC2JMwzKPs7L7W1V9aQkr053/uYHJ3lrVV0jSVpr35hncQAwqFHzMEDifM973jP57ndzyiImehJnEokzDEzjvNwe1P//q+ka5ySpJA/pL9vfGYCdY8DEOUm36fciJnoSZxKJMwzMptrL7feSHNVau0GSv09yepKfb63doLWmaQZgZxkwcU6y2EfVXsS6mC2JMwxK47zcntpa+3ZV3SHJnZO8OMkL5lwTAGyPoRPn0TQWLdGTOJNInGFgGuflNvr58V5J/q619pYkB86xHgDYPmslzlvZVHuU6C1SYzKeOC9SXcyWcQCD0jgvty9X1Quz56Bgl48xAcBOtVbivInNWI877rgcN5rGfvst1qaw44nzItXFbBkHMChN0nJ7UJJ3JLlba+2CJNdI8jtzrQgAtsuAifNxj3xk1zgvYqInaSQxDmBgjqq9xFprFyX5x7HL5yY5d34VAcA2GjUPAyTO53/ta0mSQxfx4EujfVsXrS5my8HBYFAaZwBgOYyahwES5wc8qDuj4ymLePClUcO0aHUxWw4OBoOyqTYAsByGPB1Va3umsWiJnsSZROIMA9M4AwDLYcjTUY0a50VM9CTOJBJnGJjGGQBYDkMmziOLmOhJnEkkzjAwjTMAsBwkziwTiTMMSuMMACyHARPnX3vUo/Jro2ksWqIncSaROMPAHFUbAFgOAybOD77vffdMY9ESPYkzicQZBiZxZipVdURV/VtVfaKqzqiqx827JgDYkAET57PPPjtnj6axaImexJlE4gwD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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'ctrl_center_button'}\n", - "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "af466c68", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", - "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6c0f1691", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:wt-ephys_no_curation]", - "language": "python", - "name": "conda-env-wt-ephys_no_curation-py" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/07-downstream-analysis.ipynb b/notebooks/07-downstream-analysis.ipynb new file mode 100644 index 00000000..c4f9dda3 --- /dev/null +++ b/notebooks/07-downstream-analysis.ipynb @@ -0,0 +1,1837 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "15bdef0d-bd52-49e6-87a0-d569006149a0", + "metadata": { + "tags": [] + }, + "source": [ + "# DataJoint U24 - Workflow Array Electrophysiology" + ] + }, + { + "cell_type": "markdown", + "id": "15ba9ad8-f0e4-48ec-b959-324c35c7581b", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "markdown", + "id": "fdb8265a-129e-4ccb-b995-bb4ff30d756d", + "metadata": {}, + "source": [ + "First, let's change directories to find the `dj_local_conf` file." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "921a4a03", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "# change to the upper level folder to detect dj_local_conf.json\n", + "if os.path.basename(os.getcwd())=='notebooks': os.chdir('..')\n", + "assert os.path.basename(os.getcwd())=='workflow-array-ephys', (\"Please move to the \"\n", + " + \"workflow directory\")\n", + "# We'll be working with long tables, so we'll make visualization easier with a limit\n", + "import datajoint as dj; dj.config['display.limit']=10" + ] + }, + { + "cell_type": "markdown", + "id": "84b2c6ae-b8cd-47b8-af38-812f65032933", + "metadata": {}, + "source": [ + "Next, we populate the python namespace with the required schemas" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "79cef246", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting cbroz@tutorial-db.datajoint.io:3306\n" + ] + } + ], + "source": [ + "from workflow_array_ephys.pipeline import session, ephys, trial, event" + ] + }, + { + "cell_type": "markdown", + "id": "04616e30-c9f8-468c-bed1-0d233ab76617", + "metadata": {}, + "source": [ + "## Trial and Event schemas" + ] + }, + { + "cell_type": "markdown", + "id": "a1451602-df01-4c33-b4bf-e280d3d0742c", + "metadata": {}, + "source": [ + "Tables in the `trial` and `event` schemas specify the structure of your experiment, including block, trial and event timing. \n", + "- Session has a 1-to-1 mapping with a behavior recording\n", + "- A block is a continuous phase of an experiment that contains repeated instances of a condition, or trials. \n", + "- Events may occur within or outside of conditions, either instantaneous or continuous.\n", + "\n", + "The diagram below shows (a) the levels of hierarchy and (b) how the bounds may not completely overlap. A block may not fully capure trials and events may occur outside both blocks/trials." + ] + }, + { + "cell_type": "markdown", + "id": "d5bc66f4-4a30-467a-829d-2e3b05decdd0", + "metadata": {}, + "source": [ + "```\n", + "|----------------------------------------------------------------------------|\n", + "|-------------------------------- Session ---------------------------------|__\n", + "|-------------------------- BehaviorRecording ---------------------------|____\n", + "|----- Block 1 -----|______|----- Block 2 -----|______|----- Block 3 -----|___\n", + "| trial 1 || trial 2 |____| trial 3 || trial 4 |____| trial 5 |____| trial 6 |\n", + "|_|e1|_|e2||e3|_|e4|__|e5|__|e6||e7||e8||e9||e10||e11|____|e12||e13|_________|\n", + "|----------------------------------------------------------------------------|\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "2e9a8cfd-8188-4d69-9c3a-b91a1ba4876a", + "metadata": {}, + "source": [ + "Let's load some example data. The `ingest.py` script has a series of loaders to help." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "dab268a4-ae07-4c8b-b999-536d9e5b3e2e", + "metadata": {}, + "outputs": [], + "source": [ + "from workflow_array_ephys.ingest import ingest_subjects, ingest_sessions,\\\n", + " ingest_events, ingest_alignment" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5b6eaecd-a823-4649-9b81-63f8f2b4dc21", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "---- Inserting 0 entry(s) into subject ----\n", + "\n", + "---- Insert 0 entry(s) into session.Session ----\n", + "\n", + "---- Insert 0 entry(s) into probe.Probe ----\n", + "\n", + "---- Insert 0 entry(s) into ephys.ProbeInsertion ----\n", + "\n", + "---- Insert 0 entry(s) into session.Session ----\n", + "\n", + "---- Successfully completed workflow_array_ephys/ingest.py ----\n", + "\n", + "---- Inserting 1 entry(s) into behavior_recording ----\n", + "\n", + "---- Inserting 2 entry(s) into behavior_recording__file ----\n", + "\n", + "---- Inserting 4 entry(s) into _block ----\n", + "\n", + "---- Inserting 4 entry(s) into _block__attribute ----\n", + "\n", + "---- Inserting 2 entry(s) into #trial_type ----\n", + "\n", + "---- Inserting 100 entry(s) into _trial ----\n", + "\n", + "---- Inserting 100 entry(s) into _trial__attribute ----\n", + "\n", + "---- Inserting 100 entry(s) into _block_trial ----\n", + "\n", + "---- Inserting 3 entry(s) into #event_type ----\n", + "\n", + "---- Inserting 153 entry(s) into _event ----\n", + "\n", + "---- Inserting 153 entry(s) into _trial_event ----\n" + ] + } + ], + "source": [ + "ingest_subjects(); ingest_sessions(); ingest_events()" + ] + }, + { + "cell_type": "markdown", + "id": "3a51de8b-3ddd-473e-b930-b0bc0c50d05f", + "metadata": {}, + "source": [ + "We have 100 total trials, either 'stim' or 'ctrl', with start and stop time" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "4cdf4879-cd05-43c3-89fa-cacc1b1474a6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Experimental trials\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_datetime

\n", + " \n", + "
\n", + "

trial_id

\n", + " trial number (1-based indexing)\n", + "
\n", + "

trial_type

\n", + " \n", + "
\n", + "

trial_start_time

\n", + " (second) relative to recording start\n", + "
\n", + "

trial_stop_time

\n", + " (second) relative to recording start\n", + "
subject62021-01-15 11:16:381stim0.12317.123
subject62021-01-15 11:16:382ctrl17.5434.54
subject62021-01-15 11:16:383ctrl34.8151.81
subject62021-01-15 11:16:384ctrl52.20269.202
subject62021-01-15 11:16:385stim69.61186.611
subject62021-01-15 11:16:386stim87.03104.03
subject62021-01-15 11:16:387ctrl104.165121.165
subject62021-01-15 11:16:388ctrl121.502138.502
subject62021-01-15 11:16:389ctrl138.612155.612
subject62021-01-15 11:16:3810stim155.741172.741
\n", + "

...

\n", + "

Total: 100

\n", + " " + ], + "text/plain": [ + "*subject *session_datet *trial_id trial_type trial_start_ti trial_stop_tim\n", + "+----------+ +------------+ +----------+ +------------+ +------------+ +------------+\n", + "subject6 2021-01-15 11: 1 stim 0.123 17.123 \n", + "subject6 2021-01-15 11: 2 ctrl 17.54 34.54 \n", + "subject6 2021-01-15 11: 3 ctrl 34.81 51.81 \n", + "subject6 2021-01-15 11: 4 ctrl 52.202 69.202 \n", + "subject6 2021-01-15 11: 5 stim 69.611 86.611 \n", + "subject6 2021-01-15 11: 6 stim 87.03 104.03 \n", + "subject6 2021-01-15 11: 7 ctrl 104.165 121.165 \n", + "subject6 2021-01-15 11: 8 ctrl 121.502 138.502 \n", + "subject6 2021-01-15 11: 9 ctrl 138.612 155.612 \n", + "subject6 2021-01-15 11: 10 stim 155.741 172.741 \n", + " ...\n", + " (Total: 100)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trial.Trial()" + ] + }, + { + "cell_type": "markdown", + "id": "ea3ed128-cb2c-400f-866c-1337d6608d2b", + "metadata": {}, + "source": [ + "Each trial is paired with one or more events that take place during the trial window." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "7fe42898-3ff9-4394-a811-a4cb85320f04", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_datetime

\n", + " \n", + "
\n", + "

trial_id

\n", + " trial number (1-based indexing)\n", + "
\n", + "

event_type

\n", + " \n", + "
\n", + "

event_start_time

\n", + " (second) relative to recording start\n", + "
subject62021-01-15 11:16:381center10.58
subject62021-01-15 11:16:382center21.647
subject62021-01-15 11:16:383center37.044
subject62021-01-15 11:16:384center55.259
subject62021-01-15 11:16:381left4.498
subject62021-01-15 11:16:383left41.892
subject62021-01-15 11:16:382right23.9
\n", + " \n", + "

Total: 7

\n", + " " + ], + "text/plain": [ + "*subject *session_datet *trial_id *event_type *event_start_t\n", + "+----------+ +------------+ +----------+ +------------+ +------------+\n", + "subject6 2021-01-15 11: 1 center 10.58 \n", + "subject6 2021-01-15 11: 2 center 21.647 \n", + "subject6 2021-01-15 11: 3 center 37.044 \n", + "subject6 2021-01-15 11: 4 center 55.259 \n", + "subject6 2021-01-15 11: 1 left 4.498 \n", + "subject6 2021-01-15 11: 3 left 41.892 \n", + "subject6 2021-01-15 11: 2 right 23.9 \n", + " (Total: 7)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trial.TrialEvent() & 'trial_id<5'" + ] + }, + { + "cell_type": "markdown", + "id": "1b2a40b8-8104-4fba-b99c-355162e4c8a9", + "metadata": {}, + "source": [ + "Finally, the `AlignmentEvent` describes the event of interest and the window we'd like to see around it." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "5122d831-48b2-4214-bd43-a42d67a5dc2d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "---- Inserting 3 entry(s) into alignment_event ----\n" + ] + } + ], + "source": [ + "ingest_alignment()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "50a2c99f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " time_shift is seconds to shift with respect to (WRT) a variable\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

alignment_name

\n", + " \n", + "
\n", + "

alignment_description

\n", + " \n", + "
\n", + "

alignment_event_type

\n", + " \n", + "
\n", + "

alignment_time_shift

\n", + " (s) WRT alignment_event_type\n", + "
\n", + "

start_event_type

\n", + " \n", + "
\n", + "

start_time_shift

\n", + " (s) WRT start_event_type\n", + "
\n", + "

end_event_type

\n", + " \n", + "
\n", + "

end_time_shift

\n", + " (s) WRT end_event_type\n", + "
center_buttoncenter0.0center-3.0center3.0
left_buttonleft0.0left-3.0left3.0
right_buttonright0.0right-3.0right3.0
\n", + " \n", + "

Total: 3

\n", + " " + ], + "text/plain": [ + "*alignment_nam alignment_desc alignment_even alignment_time start_event_ty start_time_shi end_event_type end_time_shift\n", + "+------------+ +------------+ +------------+ +------------+ +------------+ +------------+ +------------+ +------------+\n", + "center_button center 0.0 center -3.0 center 3.0 \n", + "left_button left 0.0 left -3.0 left 3.0 \n", + "right_button right 0.0 right -3.0 right 3.0 \n", + " (Total: 3)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "event.AlignmentEvent()" + ] + }, + { + "cell_type": "markdown", + "id": "4936a1e8", + "metadata": {}, + "source": [ + "## Event-aligned trialized unit spike times" + ] + }, + { + "cell_type": "markdown", + "id": "48907760-54b8-485f-b2eb-8c03a5cc2839", + "metadata": {}, + "source": [ + "First, we'll check that the data is still properly inserted from the previous notebooks." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "9a36c342", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Clustering results of a curation.\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_datetime

\n", + " \n", + "
\n", + "

insertion_number

\n", + " \n", + "
\n", + "

paramset_idx

\n", + " \n", + "
\n", + "

curation_id

\n", + " \n", + "
subject62021-01-15 11:16:38001
\n", + " \n", + "

Total: 1

\n", + " " + ], + "text/plain": [ + "*subject *session_datet *insertion_num *paramset_idx *curation_id \n", + "+----------+ +------------+ +------------+ +------------+ +------------+\n", + "subject6 2021-01-15 11: 0 0 1 \n", + " (Total: 1)" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ephys.CuratedClustering()" + ] + }, + { + "cell_type": "markdown", + "id": "cc6c1e0c-ad56-4152-a200-873150aafb17", + "metadata": {}, + "source": [ + "For this example, we'll be looking at `subject6`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "8642f010", + "metadata": {}, + "outputs": [], + "source": [ + "clustering_key = (ephys.CuratedClustering \n", + " & {'subject': 'subject6', 'session_datetime': '2021-01-15 11:16:38',\n", + " 'insertion_number': 0}\n", + " ).fetch1('KEY')" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "c9c95806", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Experimental trials\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_datetime

\n", + " \n", + "
\n", + "

trial_id

\n", + " trial number (1-based indexing)\n", + "
\n", + "

trial_type

\n", + " \n", + "
\n", + "

trial_start_time

\n", + " (second) relative to recording start\n", + "
\n", + "

trial_stop_time

\n", + " (second) relative to recording start\n", + "
subject62021-01-15 11:16:381stim0.12317.123
subject62021-01-15 11:16:382ctrl17.5434.54
subject62021-01-15 11:16:383ctrl34.8151.81
subject62021-01-15 11:16:384ctrl52.20269.202
subject62021-01-15 11:16:385stim69.61186.611
subject62021-01-15 11:16:386stim87.03104.03
subject62021-01-15 11:16:387ctrl104.165121.165
subject62021-01-15 11:16:388ctrl121.502138.502
subject62021-01-15 11:16:389ctrl138.612155.612
subject62021-01-15 11:16:3810stim155.741172.741
\n", + "

...

\n", + "

Total: 100

\n", + " " + ], + "text/plain": [ + "*subject *session_datet *trial_id trial_type trial_start_ti trial_stop_tim\n", + "+----------+ +------------+ +----------+ +------------+ +------------+ +------------+\n", + "subject6 2021-01-15 11: 1 stim 0.123 17.123 \n", + "subject6 2021-01-15 11: 2 ctrl 17.54 34.54 \n", + "subject6 2021-01-15 11: 3 ctrl 34.81 51.81 \n", + "subject6 2021-01-15 11: 4 ctrl 52.202 69.202 \n", + "subject6 2021-01-15 11: 5 stim 69.611 86.611 \n", + "subject6 2021-01-15 11: 6 stim 87.03 104.03 \n", + "subject6 2021-01-15 11: 7 ctrl 104.165 121.165 \n", + "subject6 2021-01-15 11: 8 ctrl 121.502 138.502 \n", + "subject6 2021-01-15 11: 9 ctrl 138.612 155.612 \n", + "subject6 2021-01-15 11: 10 stim 155.741 172.741 \n", + " ...\n", + " (Total: 100)" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trial.Trial & clustering_key" + ] + }, + { + "cell_type": "markdown", + "id": "a2947c6b-e58b-4dd8-b6ac-23717244634b", + "metadata": {}, + "source": [ + "And we can narrow our focus on `ctrl` trials." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "1851ad2b", + "metadata": {}, + "outputs": [], + "source": [ + "ctrl_trials = trial.Trial & clustering_key & 'trial_type = \"ctrl\"'" + ] + }, + { + "cell_type": "markdown", + "id": "be1e7935-7a12-4753-9132-e311f9c3fadd", + "metadata": {}, + "source": [ + "The `analysis` schema provides example tables to perform event-aligned spike-times analysis." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "0678d202", + "metadata": {}, + "outputs": [], + "source": [ + "from workflow_array_ephys import analysis" + ] + }, + { + "cell_type": "markdown", + "id": "a2e97d75", + "metadata": {}, + "source": [ + "+ ***SpikesAlignmentCondition*** - a manual table to specify the inputs and condition for the analysis\n", + "+ ***SpikesAlignment*** - a computed table to extract event-aligned spikes and compute unit PSTH" + ] + }, + { + "cell_type": "markdown", + "id": "ba4607a1", + "metadata": {}, + "source": [ + "Let's start by creating several analyses configuration - i.e. inserting into ***SpikesAlignmentCondition*** for the `center` event, called `center_button` in the `AlignmentEvent` table." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "cb866198-ca64-4fd3-a5d9-eb03f19e61e3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " time_shift is seconds to shift with respect to (WRT) a variable\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

alignment_name

\n", + " \n", + "
\n", + "

alignment_description

\n", + " \n", + "
\n", + "

alignment_event_type

\n", + " \n", + "
\n", + "

alignment_time_shift

\n", + " (s) WRT alignment_event_type\n", + "
\n", + "

start_event_type

\n", + " \n", + "
\n", + "

start_time_shift

\n", + " (s) WRT start_event_type\n", + "
\n", + "

end_event_type

\n", + " \n", + "
\n", + "

end_time_shift

\n", + " (s) WRT end_event_type\n", + "
center_buttoncenter0.0center-3.0center3.0
left_buttonleft0.0left-3.0left3.0
right_buttonright0.0right-3.0right3.0
\n", + " \n", + "

Total: 3

\n", + " " + ], + "text/plain": [ + "*alignment_nam alignment_desc alignment_even alignment_time start_event_ty start_time_shi end_event_type end_time_shift\n", + "+------------+ +------------+ +------------+ +------------+ +------------+ +------------+ +------------+ +------------+\n", + "center_button center 0.0 center -3.0 center 3.0 \n", + "left_button left 0.0 left -3.0 left 3.0 \n", + "right_button right 0.0 right -3.0 right 3.0 \n", + " (Total: 3)" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "event.AlignmentEvent()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "d859203f", + "metadata": {}, + "outputs": [], + "source": [ + "alignment_key = (event.AlignmentEvent & 'alignment_name = \"center_button\"'\n", + " ).fetch1('KEY')\n", + "alignment_condition = {**clustering_key, **alignment_key, \n", + " 'trial_condition': 'ctrl_center_button'}\n", + "analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "8994e8f5", + "metadata": {}, + "outputs": [], + "source": [ + "analysis.SpikesAlignmentCondition.Trial.insert(\n", + " (analysis.SpikesAlignmentCondition * ctrl_trials & alignment_condition).proj(),\n", + " skip_duplicates=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "0c0d093a-f2ae-4474-941c-fae5fcf74f83", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Trials (or subset of trials) to computed event-aligned spikes and PSTH on\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_datetime

\n", + " \n", + "
\n", + "

insertion_number

\n", + " \n", + "
\n", + "

paramset_idx

\n", + " \n", + "
\n", + "

curation_id

\n", + " \n", + "
\n", + "

alignment_name

\n", + " \n", + "
\n", + "

trial_condition

\n", + " user-friendly name of condition\n", + "
\n", + "

trial_id

\n", + " trial number (1-based indexing)\n", + "
subject62021-01-15 11:16:38001center_buttonctrl_center_button2
subject62021-01-15 11:16:38001center_buttonctrl_center_button3
subject62021-01-15 11:16:38001center_buttonctrl_center_button4
subject62021-01-15 11:16:38001center_buttonctrl_center_button7
subject62021-01-15 11:16:38001center_buttonctrl_center_button8
subject62021-01-15 11:16:38001center_buttonctrl_center_button9
subject62021-01-15 11:16:38001center_buttonctrl_center_button13
subject62021-01-15 11:16:38001center_buttonctrl_center_button14
subject62021-01-15 11:16:38001center_buttonctrl_center_button15
subject62021-01-15 11:16:38001center_buttonctrl_center_button16
\n", + "

...

\n", + "

Total: 50

\n", + " " + ], + "text/plain": [ + "*subject *session_datet *insertion_num *paramset_idx *curation_id *alignment_nam *trial_conditi *trial_id \n", + "+----------+ +------------+ +------------+ +------------+ +------------+ +------------+ +------------+ +----------+\n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 2 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 3 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 4 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 7 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 8 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 9 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 13 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 14 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 15 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 16 \n", + " ...\n", + " (Total: 50)" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "analysis.SpikesAlignmentCondition.Trial()" + ] + }, + { + "cell_type": "markdown", + "id": "4ddd0345", + "metadata": {}, + "source": [ + "With the steps above, we have create a new spike alignment condition for analysis, named `ctrl_center_button`, which specifies:\n", + "+ a CuratedClustering of interest for analysis\n", + "+ an event of interest to align the spikes to - `center_button`\n", + "+ a set of trials of interest to perform the analysis on - `ctrl` trials" + ] + }, + { + "cell_type": "markdown", + "id": "2a23b206", + "metadata": {}, + "source": [ + "Now, let's create another set with:\n", + "+ the same CuratedClustering of interest for analysis\n", + "+ an event of interest to align the spikes to - `center_button`\n", + "+ a set of trials of interest to perform the analysis on - `stim` trials" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "39d4d423", + "metadata": {}, + "outputs": [], + "source": [ + "stim_trials = trial.Trial & clustering_key & 'trial_type = \"stim\"'\n", + "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", + "analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True)\n", + "analysis.SpikesAlignmentCondition.Trial.insert(\n", + " (analysis.SpikesAlignmentCondition * stim_trials & alignment_condition).proj(),\n", + " skip_duplicates=True)" + ] + }, + { + "cell_type": "markdown", + "id": "bbdd4984-2439-44a4-a72f-ecf52b64d17b", + "metadata": {}, + "source": [ + "We can compare conditions in the `SpikesAlignmentCondition` table." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "6452c508", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_datetime

\n", + " \n", + "
\n", + "

insertion_number

\n", + " \n", + "
\n", + "

paramset_idx

\n", + " \n", + "
\n", + "

curation_id

\n", + " \n", + "
\n", + "

alignment_name

\n", + " \n", + "
\n", + "

trial_condition

\n", + " user-friendly name of condition\n", + "
\n", + "

condition_description

\n", + " \n", + "
\n", + "

bin_size

\n", + " bin-size (in second) used to compute the PSTH\n", + "
subject62021-01-15 11:16:38001center_buttonctrl_center_button0.04
subject62021-01-15 11:16:38001center_buttonstim_center_button0.04
\n", + " \n", + "

Total: 2

\n", + " " + ], + "text/plain": [ + "*subject *session_datet *insertion_num *paramset_idx *curation_id *alignment_nam *trial_conditi condition_desc bin_size \n", + "+----------+ +------------+ +------------+ +------------+ +------------+ +------------+ +------------+ +------------+ +----------+\n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 0.04 \n", + "subject6 2021-01-15 11: 0 0 1 center_button stim_center_bu 0.04 \n", + " (Total: 2)" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "analysis.SpikesAlignmentCondition()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "7a9ef2fb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Trials (or subset of trials) to computed event-aligned spikes and PSTH on\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_datetime

\n", + " \n", + "
\n", + "

insertion_number

\n", + " \n", + "
\n", + "

paramset_idx

\n", + " \n", + "
\n", + "

curation_id

\n", + " \n", + "
\n", + "

alignment_name

\n", + " \n", + "
\n", + "

trial_condition

\n", + " user-friendly name of condition\n", + "
\n", + "

trial_id

\n", + " trial number (1-based indexing)\n", + "
subject62021-01-15 11:16:38001center_buttonctrl_center_button2
subject62021-01-15 11:16:38001center_buttonctrl_center_button3
subject62021-01-15 11:16:38001center_buttonctrl_center_button4
subject62021-01-15 11:16:38001center_buttonctrl_center_button7
subject62021-01-15 11:16:38001center_buttonctrl_center_button8
subject62021-01-15 11:16:38001center_buttonctrl_center_button9
subject62021-01-15 11:16:38001center_buttonctrl_center_button13
subject62021-01-15 11:16:38001center_buttonctrl_center_button14
subject62021-01-15 11:16:38001center_buttonctrl_center_button15
subject62021-01-15 11:16:38001center_buttonctrl_center_button16
\n", + "

...

\n", + "

Total: 50

\n", + " " + ], + "text/plain": [ + "*subject *session_datet *insertion_num *paramset_idx *curation_id *alignment_nam *trial_conditi *trial_id \n", + "+----------+ +------------+ +------------+ +------------+ +------------+ +------------+ +------------+ +----------+\n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 2 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 3 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 4 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 7 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 8 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 9 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 13 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 14 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 15 \n", + "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 16 \n", + " ...\n", + " (Total: 50)" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "analysis.SpikesAlignmentCondition.Trial & 'trial_condition = \"ctrl_center_button\"'" + ] + }, + { + "cell_type": "markdown", + "id": "1486add8", + "metadata": {}, + "source": [ + "### Computation" + ] + }, + { + "cell_type": "markdown", + "id": "7f0f7c3f-88a1-4930-9abe-b2fc7f9fe3c3", + "metadata": {}, + "source": [ + "Now let's run the computation on these." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "f467d3f7", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "SpikesAlignment: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 2/2 [00:16<00:00, 8.11s/it]\n" + ] + } + ], + "source": [ + "analysis.SpikesAlignment.populate(display_progress=True)" + ] + }, + { + "cell_type": "markdown", + "id": "c4320cdd", + "metadata": {}, + "source": [ + "### Vizualize" + ] + }, + { + "cell_type": "markdown", + "id": "7ba0a59e-cd84-48ae-a7fd-9324e0a73af7", + "metadata": {}, + "source": [ + "We can visualize the results with the `plot_raster` function." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "2c4962b7", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'ctrl_center_button'}\n", + "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "af466c68", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", + "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv-nwb", + "language": "python", + "name": "venv-nwb" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/requirements.txt b/requirements.txt index 6ffbc8ad..f5a04193 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,7 @@ datajoint>=0.13.0 -<<<<<<< HEAD element-array-ephys==0.1.0b0 element-lab>=0.1.0b0 element-animal==0.1.0b0 element-session==0.1.0b0 element-interface @ git+https://github.com/datajoint/element-interface.git ipykernel==6.0.1 -======= -element-array-ephys -element-lab -element-animal -element-session -ipykernel ->>>>>>> 579683d (add tests for NWB export) diff --git a/test_export b/test_export deleted file mode 100644 index 82daee09f5d9bab4d4ef6cf1c24d6067e7457f8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 800 zcmeD5aB<`1lHy_j0S*oZ76t(@6Gr@p0tIG>2#gPtPk=HQp>zk7Ucm%mFfxE31A_!q zTo7tLy1I}cS67e{nE5aos%?}S;UVDR>KFhDf(U3ha6su3&~ygng3}s^4OR>jq<{th Dq@*Z_ diff --git a/user_data/behavior_recordings.csv b/user_data/behavior_recordings.csv index 4ce21f1c..7ff004a6 100644 --- a/user_data/behavior_recordings.csv +++ b/user_data/behavior_recordings.csv @@ -1,3 +1,3 @@ subject,session_datetime,filepath -subject5,2018-07-03 20:32:28,./user_data/trials.csv -subject5,2018-07-03 20:32:28,./user_data/events.csv +subject6,2021-01-15 11:16:38,./user_data/trials.csv +subject6,2021-01-15 11:16:38,./user_data/events.csv diff --git a/user_data/blocks.csv b/user_data/blocks.csv index dd378d8a..4214e0ce 100644 --- a/user_data/blocks.csv +++ b/user_data/blocks.csv @@ -1,5 +1,5 @@ subject,session_datetime,block_id,block_start_time,block_stop_time,attribute_name,attribute_value -subject5,2018-07-03 20:32:28,1,0,84,type,light -subject5,2018-07-03 20:32:28,2,84,168,type,dark -subject5,2018-07-03 20:32:28,3,168,252,type,light -subject5,2018-07-03 20:32:28,4,252,336,type,dark +subject6,2021-01-15 11:16:38,1,0,476,type,light +subject6,2021-01-15 11:16:38,2,476,952,type,dark +subject6,2021-01-15 11:16:38,3,952,1428,type,light +subject6,2021-01-15 11:16:38,4,1428,1904,type,dark diff --git a/user_data/events.csv b/user_data/events.csv index 073b62e5..b30fa7c4 100644 --- a/user_data/events.csv +++ b/user_data/events.csv @@ -1,154 +1,154 @@ subject,session_datetime,trial_id,event_start_time,event_type -subject5,2018-07-03 20:32:28,1,0.241,center -subject5,2018-07-03 20:32:28,1,1.529,left -subject5,2018-07-03 20:32:28,2,3.33,right -subject5,2018-07-03 20:32:28,2,4.265,center -subject5,2018-07-03 20:32:28,3,6.622,center -subject5,2018-07-03 20:32:28,3,7.498,right -subject5,2018-07-03 20:32:28,4,10.02,right -subject5,2018-07-03 20:32:28,5,13.094,center -subject5,2018-07-03 20:32:28,6,16.588,center -subject5,2018-07-03 20:32:28,6,17.627,right -subject5,2018-07-03 20:32:28,7,19.782,center -subject5,2018-07-03 20:32:28,7,20.494,center -subject5,2018-07-03 20:32:28,8,22.63,right -subject5,2018-07-03 20:32:28,8,23.59,left -subject5,2018-07-03 20:32:28,9,26.28,right -subject5,2018-07-03 20:32:28,9,27.263,left -subject5,2018-07-03 20:32:28,10,29.394,center -subject5,2018-07-03 20:32:28,11,32.592,center -subject5,2018-07-03 20:32:28,11,33.674,center -subject5,2018-07-03 20:32:28,12,35.861,right -subject5,2018-07-03 20:32:28,13,39.319,right -subject5,2018-07-03 20:32:28,14,42.204,left -subject5,2018-07-03 20:32:28,14,43.163,center -subject5,2018-07-03 20:32:28,15,45.662,center -subject5,2018-07-03 20:32:28,16,48.655,left -subject5,2018-07-03 20:32:28,16,49.668,left -subject5,2018-07-03 20:32:28,17,51.937,left -subject5,2018-07-03 20:32:28,18,55.48,left -subject5,2018-07-03 20:32:28,18,56.667,left -subject5,2018-07-03 20:32:28,19,58.883,right -subject5,2018-07-03 20:32:28,19,59.619,left -subject5,2018-07-03 20:32:28,20,62.054,center -subject5,2018-07-03 20:32:28,21,65.023,right -subject5,2018-07-03 20:32:28,21,65.82,left -subject5,2018-07-03 20:32:28,22,68.202,center -subject5,2018-07-03 20:32:28,23,72.049,right -subject5,2018-07-03 20:32:28,23,72.903,center -subject5,2018-07-03 20:32:28,24,75.05,left -subject5,2018-07-03 20:32:28,25,84.49,left -subject5,2018-07-03 20:32:28,25,85.095,center -subject5,2018-07-03 20:32:28,26,87.504,left -subject5,2018-07-03 20:32:28,26,88.368,left -subject5,2018-07-03 20:32:28,27,90.834,right -subject5,2018-07-03 20:32:28,28,93.849,right -subject5,2018-07-03 20:32:28,29,97.098,left -subject5,2018-07-03 20:32:28,29,98.38,center -subject5,2018-07-03 20:32:28,30,100.62,right -subject5,2018-07-03 20:32:28,31,103.931,center -subject5,2018-07-03 20:32:28,32,107.541,right -subject5,2018-07-03 20:32:28,32,108.2,right -subject5,2018-07-03 20:32:28,33,110.765,center -subject5,2018-07-03 20:32:28,34,113.891,left -subject5,2018-07-03 20:32:28,34,114.741,left -subject5,2018-07-03 20:32:28,35,117.006,center -subject5,2018-07-03 20:32:28,35,118.084,right -subject5,2018-07-03 20:32:28,36,120.256,left -subject5,2018-07-03 20:32:28,36,121.212,center -subject5,2018-07-03 20:32:28,37,123.553,left -subject5,2018-07-03 20:32:28,37,124.465,right -subject5,2018-07-03 20:32:28,38,126.973,left -subject5,2018-07-03 20:32:28,39,130.357,left -subject5,2018-07-03 20:32:28,39,131.638,center -subject5,2018-07-03 20:32:28,40,133.661,left -subject5,2018-07-03 20:32:28,40,134.764,right -subject5,2018-07-03 20:32:28,41,137.35,right -subject5,2018-07-03 20:32:28,42,140.573,left -subject5,2018-07-03 20:32:28,43,143.71,left -subject5,2018-07-03 20:32:28,44,146.95,right -subject5,2018-07-03 20:32:28,44,147.981,right -subject5,2018-07-03 20:32:28,45,150.1,center -subject5,2018-07-03 20:32:28,46,153.394,right -subject5,2018-07-03 20:32:28,47,156.783,right -subject5,2018-07-03 20:32:28,47,157.856,center -subject5,2018-07-03 20:32:28,48,160.237,right -subject5,2018-07-03 20:32:28,49,163.41,center -subject5,2018-07-03 20:32:28,50,168.543,right -subject5,2018-07-03 20:32:28,50,169.562,right -subject5,2018-07-03 20:32:28,51,171.455,center -subject5,2018-07-03 20:32:28,52,175.002,left -subject5,2018-07-03 20:32:28,52,176.259,left -subject5,2018-07-03 20:32:28,53,178.429,center -subject5,2018-07-03 20:32:28,53,179.629,center -subject5,2018-07-03 20:32:28,54,181.39,left -subject5,2018-07-03 20:32:28,55,184.639,right -subject5,2018-07-03 20:32:28,55,186.014,right -subject5,2018-07-03 20:32:28,56,188.182,left -subject5,2018-07-03 20:32:28,57,191.348,center -subject5,2018-07-03 20:32:28,57,192.091,center -subject5,2018-07-03 20:32:28,58,194.804,left -subject5,2018-07-03 20:32:28,58,195.743,left -subject5,2018-07-03 20:32:28,59,197.978,right -subject5,2018-07-03 20:32:28,60,200.774,left -subject5,2018-07-03 20:32:28,60,202.206,center -subject5,2018-07-03 20:32:28,61,204.198,left -subject5,2018-07-03 20:32:28,61,205.417,left -subject5,2018-07-03 20:32:28,62,207.538,center -subject5,2018-07-03 20:32:28,63,210.839,center -subject5,2018-07-03 20:32:28,63,211.747,center -subject5,2018-07-03 20:32:28,64,214.213,right -subject5,2018-07-03 20:32:28,64,215.096,left -subject5,2018-07-03 20:32:28,65,217.684,left -subject5,2018-07-03 20:32:28,66,220.714,center -subject5,2018-07-03 20:32:28,66,221.4,center -subject5,2018-07-03 20:32:28,67,224.075,center -subject5,2018-07-03 20:32:28,67,224.846,left -subject5,2018-07-03 20:32:28,68,227.159,right -subject5,2018-07-03 20:32:28,68,228.137,center -subject5,2018-07-03 20:32:28,69,230.632,left -subject5,2018-07-03 20:32:28,69,231.505,center -subject5,2018-07-03 20:32:28,70,233.681,center -subject5,2018-07-03 20:32:28,71,237.067,right -subject5,2018-07-03 20:32:28,72,240.201,right -subject5,2018-07-03 20:32:28,73,243.41,center -subject5,2018-07-03 20:32:28,74,246.725,left -subject5,2018-07-03 20:32:28,75,252.418,right -subject5,2018-07-03 20:32:28,76,256.161,right -subject5,2018-07-03 20:32:28,77,259.277,right -subject5,2018-07-03 20:32:28,78,262.736,left -subject5,2018-07-03 20:32:28,78,263.526,right -subject5,2018-07-03 20:32:28,79,265.636,right -subject5,2018-07-03 20:32:28,79,266.654,center -subject5,2018-07-03 20:32:28,80,268.96,center -subject5,2018-07-03 20:32:28,81,272.152,right -subject5,2018-07-03 20:32:28,81,272.954,right -subject5,2018-07-03 20:32:28,82,275.033,left -subject5,2018-07-03 20:32:28,83,278.411,left -subject5,2018-07-03 20:32:28,84,281.852,center -subject5,2018-07-03 20:32:28,85,284.743,left -subject5,2018-07-03 20:32:28,86,288.422,right -subject5,2018-07-03 20:32:28,86,289.326,left -subject5,2018-07-03 20:32:28,87,291.666,left -subject5,2018-07-03 20:32:28,87,292.366,left -subject5,2018-07-03 20:32:28,88,295.061,center -subject5,2018-07-03 20:32:28,88,295.665,center -subject5,2018-07-03 20:32:28,89,297.889,left -subject5,2018-07-03 20:32:28,89,299.087,right -subject5,2018-07-03 20:32:28,90,301.612,center -subject5,2018-07-03 20:32:28,90,302.257,left -subject5,2018-07-03 20:32:28,91,304.82,left -subject5,2018-07-03 20:32:28,92,307.794,left -subject5,2018-07-03 20:32:28,92,308.817,left -subject5,2018-07-03 20:32:28,93,310.856,center -subject5,2018-07-03 20:32:28,94,314.304,center -subject5,2018-07-03 20:32:28,95,317.795,left -subject5,2018-07-03 20:32:28,95,318.518,center -subject5,2018-07-03 20:32:28,96,320.594,right -subject5,2018-07-03 20:32:28,97,323.87,right -subject5,2018-07-03 20:32:28,97,325.213,right -subject5,2018-07-03 20:32:28,98,327.498,center -subject5,2018-07-03 20:32:28,98,328.386,center -subject5,2018-07-03 20:32:28,99,330.959,center -subject5,2018-07-03 20:32:28,99,332.02,left -subject5,2018-07-03 20:32:28,100,334.177,center +subject6,2021-01-15 11:16:38,1,4.498,left +subject6,2021-01-15 11:16:38,1,10.58,center +subject6,2021-01-15 11:16:38,2,21.647,center +subject6,2021-01-15 11:16:38,2,23.9,right +subject6,2021-01-15 11:16:38,3,37.044,center +subject6,2021-01-15 11:16:38,3,41.892,left +subject6,2021-01-15 11:16:38,4,55.259,center +subject6,2021-01-15 11:16:38,5,74.072,right +subject6,2021-01-15 11:16:38,6,91.062,right +subject6,2021-01-15 11:16:38,6,96.453,left +subject6,2021-01-15 11:16:38,7,104.462,left +subject6,2021-01-15 11:16:38,7,111.576,left +subject6,2021-01-15 11:16:38,8,125.868,left +subject6,2021-01-15 11:16:38,8,127.549,left +subject6,2021-01-15 11:16:38,9,142.886,left +subject6,2021-01-15 11:16:38,9,144.982,right +subject6,2021-01-15 11:16:38,10,158.177,center +subject6,2021-01-15 11:16:38,11,177.522,left +subject6,2021-01-15 11:16:38,11,179.519,right +subject6,2021-01-15 11:16:38,12,194.551,center +subject6,2021-01-15 11:16:38,13,208.223,left +subject6,2021-01-15 11:16:38,14,227.622,left +subject6,2021-01-15 11:16:38,14,234.657,center +subject6,2021-01-15 11:16:38,15,242.766,center +subject6,2021-01-15 11:16:38,16,259.943,center +subject6,2021-01-15 11:16:38,16,265.695,left +subject6,2021-01-15 11:16:38,17,278.033,right +subject6,2021-01-15 11:16:38,18,295.436,center +subject6,2021-01-15 11:16:38,18,301.092,right +subject6,2021-01-15 11:16:38,19,312.168,center +subject6,2021-01-15 11:16:38,19,320.269,left +subject6,2021-01-15 11:16:38,20,328.678,left +subject6,2021-01-15 11:16:38,21,350.125,left +subject6,2021-01-15 11:16:38,21,355.114,left +subject6,2021-01-15 11:16:38,22,365.729,left +subject6,2021-01-15 11:16:38,23,381.802,center +subject6,2021-01-15 11:16:38,23,388.785,center +subject6,2021-01-15 11:16:38,24,397.983,center +subject6,2021-01-15 11:16:38,25,480.351,right +subject6,2021-01-15 11:16:38,25,483.584,right +subject6,2021-01-15 11:16:38,26,496.797,left +subject6,2021-01-15 11:16:38,26,500.623,center +subject6,2021-01-15 11:16:38,27,513.88,center +subject6,2021-01-15 11:16:38,28,530.146,center +subject6,2021-01-15 11:16:38,29,548.123,left +subject6,2021-01-15 11:16:38,29,551.419,center +subject6,2021-01-15 11:16:38,30,565.787,right +subject6,2021-01-15 11:16:38,31,581.927,right +subject6,2021-01-15 11:16:38,32,597.105,center +subject6,2021-01-15 11:16:38,32,605.174,center +subject6,2021-01-15 11:16:38,33,617.296,center +subject6,2021-01-15 11:16:38,34,631.519,center +subject6,2021-01-15 11:16:38,34,639.799,center +subject6,2021-01-15 11:16:38,35,648.943,left +subject6,2021-01-15 11:16:38,35,655.128,right +subject6,2021-01-15 11:16:38,36,666.147,center +subject6,2021-01-15 11:16:38,36,672.215,left +subject6,2021-01-15 11:16:38,37,684.363,right +subject6,2021-01-15 11:16:38,37,691.55,right +subject6,2021-01-15 11:16:38,38,701.364,right +subject6,2021-01-15 11:16:38,39,720.031,right +subject6,2021-01-15 11:16:38,39,725.79,right +subject6,2021-01-15 11:16:38,40,736.141,right +subject6,2021-01-15 11:16:38,40,741.894,right +subject6,2021-01-15 11:16:38,41,756.252,right +subject6,2021-01-15 11:16:38,42,770.854,left +subject6,2021-01-15 11:16:38,43,789.026,left +subject6,2021-01-15 11:16:38,44,806.311,right +subject6,2021-01-15 11:16:38,44,814.348,left +subject6,2021-01-15 11:16:38,45,824.353,center +subject6,2021-01-15 11:16:38,46,841.581,center +subject6,2021-01-15 11:16:38,47,855.822,left +subject6,2021-01-15 11:16:38,47,861.736,left +subject6,2021-01-15 11:16:38,48,873.726,center +subject6,2021-01-15 11:16:38,49,893.056,center +subject6,2021-01-15 11:16:38,50,954.241,left +subject6,2021-01-15 11:16:38,50,959.362,right +subject6,2021-01-15 11:16:38,51,972.74,right +subject6,2021-01-15 11:16:38,52,990.811,center +subject6,2021-01-15 11:16:38,52,995.781,left +subject6,2021-01-15 11:16:38,53,1003.789,right +subject6,2021-01-15 11:16:38,53,1014.044,right +subject6,2021-01-15 11:16:38,54,1023.039,right +subject6,2021-01-15 11:16:38,55,1042.303,left +subject6,2021-01-15 11:16:38,55,1045.455,center +subject6,2021-01-15 11:16:38,56,1055.932,right +subject6,2021-01-15 11:16:38,57,1075.973,left +subject6,2021-01-15 11:16:38,57,1079.937,center +subject6,2021-01-15 11:16:38,58,1094.502,right +subject6,2021-01-15 11:16:38,58,1096.454,right +subject6,2021-01-15 11:16:38,59,1111.624,right +subject6,2021-01-15 11:16:38,60,1125.798,center +subject6,2021-01-15 11:16:38,60,1132.081,left +subject6,2021-01-15 11:16:38,61,1143.288,right +subject6,2021-01-15 11:16:38,61,1148.102,center +subject6,2021-01-15 11:16:38,62,1161.495,left +subject6,2021-01-15 11:16:38,63,1180.847,right +subject6,2021-01-15 11:16:38,63,1185.941,center +subject6,2021-01-15 11:16:38,64,1198.098,left +subject6,2021-01-15 11:16:38,64,1201.006,left +subject6,2021-01-15 11:16:38,65,1213.625,right +subject6,2021-01-15 11:16:38,66,1230.984,center +subject6,2021-01-15 11:16:38,66,1236.597,center +subject6,2021-01-15 11:16:38,67,1250.275,center +subject6,2021-01-15 11:16:38,67,1253.086,center +subject6,2021-01-15 11:16:38,68,1266.502,right +subject6,2021-01-15 11:16:38,68,1269.087,right +subject6,2021-01-15 11:16:38,69,1282.504,right +subject6,2021-01-15 11:16:38,69,1287.335,left +subject6,2021-01-15 11:16:38,70,1297.615,left +subject6,2021-01-15 11:16:38,71,1318.076,center +subject6,2021-01-15 11:16:38,72,1332.045,left +subject6,2021-01-15 11:16:38,73,1353.319,left +subject6,2021-01-15 11:16:38,74,1368.471,left +subject6,2021-01-15 11:16:38,75,1432.293,left +subject6,2021-01-15 11:16:38,76,1448.608,left +subject6,2021-01-15 11:16:38,77,1463.869,center +subject6,2021-01-15 11:16:38,78,1482.217,center +subject6,2021-01-15 11:16:38,78,1487.081,right +subject6,2021-01-15 11:16:38,79,1500.456,right +subject6,2021-01-15 11:16:38,79,1503.5,left +subject6,2021-01-15 11:16:38,80,1515.845,center +subject6,2021-01-15 11:16:38,81,1533.975,center +subject6,2021-01-15 11:16:38,81,1540.17,center +subject6,2021-01-15 11:16:38,82,1552.464,left +subject6,2021-01-15 11:16:38,83,1570.591,right +subject6,2021-01-15 11:16:38,84,1583.92,left +subject6,2021-01-15 11:16:38,85,1604.533,right +subject6,2021-01-15 11:16:38,86,1620.502,left +subject6,2021-01-15 11:16:38,86,1625.613,right +subject6,2021-01-15 11:16:38,87,1637.768,right +subject6,2021-01-15 11:16:38,87,1641.88,right +subject6,2021-01-15 11:16:38,88,1654.531,center +subject6,2021-01-15 11:16:38,88,1659.364,center +subject6,2021-01-15 11:16:38,89,1673.598,right +subject6,2021-01-15 11:16:38,89,1680.487,left +subject6,2021-01-15 11:16:38,90,1691.872,center +subject6,2021-01-15 11:16:38,90,1696.875,right +subject6,2021-01-15 11:16:38,91,1705.346,right +subject6,2021-01-15 11:16:38,92,1722.876,right +subject6,2021-01-15 11:16:38,92,1732.6,center +subject6,2021-01-15 11:16:38,93,1740.822,right +subject6,2021-01-15 11:16:38,94,1757.562,right +subject6,2021-01-15 11:16:38,95,1774.462,right +subject6,2021-01-15 11:16:38,95,1783.568,left +subject6,2021-01-15 11:16:38,96,1795.55,center +subject6,2021-01-15 11:16:38,97,1810.663,left +subject6,2021-01-15 11:16:38,97,1815.738,center +subject6,2021-01-15 11:16:38,98,1825.994,center +subject6,2021-01-15 11:16:38,98,1831.876,left +subject6,2021-01-15 11:16:38,99,1845.095,right +subject6,2021-01-15 11:16:38,99,1851.139,left +subject6,2021-01-15 11:16:38,100,1863.219,left diff --git a/user_data/trials.csv b/user_data/trials.csv index 9e6195a8..4cc10cc3 100644 --- a/user_data/trials.csv +++ b/user_data/trials.csv @@ -1,101 +1,101 @@ -subject,session_datetime,block_id,trial_id,trial_start_time,trial_stop_time,trial_type,attribute_name,attribute_value, -subject5,2018-07-03 20:32:28,1,1,0.044,3.044,stim,lumen,882, -subject5,2018-07-03 20:32:28,1,2,3.261,6.261,ctrl,lumen,855, -subject5,2018-07-03 20:32:28,1,3,6.483,9.483,ctrl,lumen,558, -subject5,2018-07-03 20:32:28,1,4,9.639,12.639,ctrl,lumen,705, -subject5,2018-07-03 20:32:28,1,5,12.763,15.763,stim,lumen,660, -subject5,2018-07-03 20:32:28,1,6,16.176,19.176,stim,lumen,807, -subject5,2018-07-03 20:32:28,1,7,19.441,22.441,ctrl,lumen,712, -subject5,2018-07-03 20:32:28,1,8,22.544,25.544,ctrl,lumen,974, -subject5,2018-07-03 20:32:28,1,9,25.848,28.848,ctrl,lumen,947, -subject5,2018-07-03 20:32:28,1,10,28.996,31.996,stim,lumen,836, -subject5,2018-07-03 20:32:28,1,11,32.294,35.294,stim,lumen,510, -subject5,2018-07-03 20:32:28,1,12,35.59,38.59,stim,lumen,805, -subject5,2018-07-03 20:32:28,1,13,38.892,41.892,ctrl,lumen,613, -subject5,2018-07-03 20:32:28,1,14,42.061,45.061,ctrl,lumen,578, -subject5,2018-07-03 20:32:28,1,15,45.414,48.414,ctrl,lumen,783, -subject5,2018-07-03 20:32:28,1,16,48.563,51.563,ctrl,lumen,596, -subject5,2018-07-03 20:32:28,1,17,51.759,54.759,stim,lumen,532, -subject5,2018-07-03 20:32:28,1,18,55.168,58.168,stim,lumen,636, -subject5,2018-07-03 20:32:28,1,19,58.49,61.49,stim,lumen,717, -subject5,2018-07-03 20:32:28,1,20,61.637,64.637,ctrl,lumen,546, -subject5,2018-07-03 20:32:28,1,21,64.799,67.799,stim,lumen,651, -subject5,2018-07-03 20:32:28,1,22,68.139,71.139,stim,lumen,602, -subject5,2018-07-03 20:32:28,1,23,71.571,74.571,stim,lumen,967, -subject5,2018-07-03 20:32:28,1,24,74.757,77.757,stim,lumen,570, -subject5,2018-07-03 20:32:28,1,25,84.005,87.005,stim,lumen,929, -subject5,2018-07-03 20:32:28,2,26,87.264,90.264,stim,lumen,0, -subject5,2018-07-03 20:32:28,2,27,90.696,93.696,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,2,28,93.838,96.838,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,2,29,97.057,100.057,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,2,30,100.386,103.386,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,2,31,103.8,106.8,stim,lumen,0, -subject5,2018-07-03 20:32:28,2,32,107.05,110.05,stim,lumen,0, -subject5,2018-07-03 20:32:28,2,33,110.445,113.445,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,2,34,113.593,116.593,stim,lumen,0, -subject5,2018-07-03 20:32:28,2,35,116.813,119.813,stim,lumen,0, -subject5,2018-07-03 20:32:28,2,36,120.184,123.184,stim,lumen,0, -subject5,2018-07-03 20:32:28,2,37,123.387,126.387,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,2,38,126.784,129.784,stim,lumen,0, -subject5,2018-07-03 20:32:28,2,39,130.151,133.151,stim,lumen,0, -subject5,2018-07-03 20:32:28,2,40,133.538,136.538,stim,lumen,0, -subject5,2018-07-03 20:32:28,2,41,136.907,139.907,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,2,42,140.308,143.308,stim,lumen,0, -subject5,2018-07-03 20:32:28,2,43,143.6,146.6,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,2,44,146.717,149.717,stim,lumen,0, -subject5,2018-07-03 20:32:28,2,45,150.061,153.061,stim,lumen,0, -subject5,2018-07-03 20:32:28,2,46,153.302,156.302,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,2,47,156.495,159.495,stim,lumen,0, -subject5,2018-07-03 20:32:28,2,48,159.838,162.838,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,2,49,163.089,166.089,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,2,50,168.263,171.263,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,3,51,171.43,174.43,ctrl,lumen,848, -subject5,2018-07-03 20:32:28,3,52,174.814,177.814,ctrl,lumen,609, -subject5,2018-07-03 20:32:28,3,53,178.174,181.174,stim,lumen,940, -subject5,2018-07-03 20:32:28,3,54,181.303,184.303,ctrl,lumen,748, -subject5,2018-07-03 20:32:28,3,55,184.631,187.631,stim,lumen,645, -subject5,2018-07-03 20:32:28,3,56,187.822,190.822,ctrl,lumen,604, -subject5,2018-07-03 20:32:28,3,57,190.971,193.971,ctrl,lumen,602, -subject5,2018-07-03 20:32:28,3,58,194.359,197.359,ctrl,lumen,965, -subject5,2018-07-03 20:32:28,3,59,197.559,200.559,ctrl,lumen,574, -subject5,2018-07-03 20:32:28,3,60,200.71,203.71,ctrl,lumen,752, -subject5,2018-07-03 20:32:28,3,61,204.074,207.074,ctrl,lumen,974, -subject5,2018-07-03 20:32:28,3,62,207.442,210.442,ctrl,lumen,512, -subject5,2018-07-03 20:32:28,3,63,210.617,213.617,ctrl,lumen,908, -subject5,2018-07-03 20:32:28,3,64,213.958,216.958,stim,lumen,700, -subject5,2018-07-03 20:32:28,3,65,217.205,220.205,ctrl,lumen,566, -subject5,2018-07-03 20:32:28,3,66,220.364,223.364,ctrl,lumen,552, -subject5,2018-07-03 20:32:28,3,67,223.68,226.68,ctrl,lumen,619, -subject5,2018-07-03 20:32:28,3,68,226.878,229.878,stim,lumen,677, -subject5,2018-07-03 20:32:28,3,69,230.224,233.224,ctrl,lumen,953, -subject5,2018-07-03 20:32:28,3,70,233.51,236.51,stim,lumen,644, -subject5,2018-07-03 20:32:28,3,71,236.615,239.615,stim,lumen,911, -subject5,2018-07-03 20:32:28,3,72,239.776,242.776,ctrl,lumen,697, -subject5,2018-07-03 20:32:28,3,73,243.016,246.016,ctrl,lumen,608, -subject5,2018-07-03 20:32:28,3,74,246.252,249.252,stim,lumen,680, -subject5,2018-07-03 20:32:28,3,75,252.329,255.329,ctrl,lumen,899, -subject5,2018-07-03 20:32:28,4,76,255.744,258.744,stim,lumen,0, -subject5,2018-07-03 20:32:28,4,77,259.006,262.006,stim,lumen,0, -subject5,2018-07-03 20:32:28,4,78,262.345,265.345,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,4,79,265.623,268.623,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,4,80,268.763,271.763,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,4,81,271.883,274.883,stim,lumen,0, -subject5,2018-07-03 20:32:28,4,82,275.032,278.032,stim,lumen,0, -subject5,2018-07-03 20:32:28,4,83,278.185,281.185,stim,lumen,0, -subject5,2018-07-03 20:32:28,4,84,281.449,284.449,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,4,85,284.664,287.664,stim,lumen,0, -subject5,2018-07-03 20:32:28,4,86,288.03,291.03,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,4,87,291.282,294.282,stim,lumen,0, -subject5,2018-07-03 20:32:28,4,88,294.622,297.622,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,4,89,297.762,300.762,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,4,90,301.183,304.183,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,4,91,304.353,307.353,stim,lumen,0, -subject5,2018-07-03 20:32:28,4,92,307.502,310.502,stim,lumen,0, -subject5,2018-07-03 20:32:28,4,93,310.775,313.775,stim,lumen,0, -subject5,2018-07-03 20:32:28,4,94,314.205,317.205,ctrl,lumen,0, -subject5,2018-07-03 20:32:28,4,95,317.348,320.348,stim,lumen,0, -subject5,2018-07-03 20:32:28,4,96,320.572,323.572,stim,lumen,0, -subject5,2018-07-03 20:32:28,4,97,323.8,326.8,stim,lumen,0, -subject5,2018-07-03 20:32:28,4,98,327.151,330.151,stim,lumen,0, -subject5,2018-07-03 20:32:28,4,99,330.53,333.53,stim,lumen,0, -subject5,2018-07-03 20:32:28,4,100,333.72,336.72,stim,lumen,0, +subject,session_datetime,block_id,trial_id,trial_start_time,trial_stop_time,trial_type,attribute_name,attribute_value +subject6,2021-01-15 11:16:38,1,1,0.123,17.123,stim,lumen,851 +subject6,2021-01-15 11:16:38,1,2,17.54,34.54,ctrl,lumen,762 +subject6,2021-01-15 11:16:38,1,3,34.81,51.81,ctrl,lumen,634 +subject6,2021-01-15 11:16:38,1,4,52.202,69.202,ctrl,lumen,707 +subject6,2021-01-15 11:16:38,1,5,69.611,86.611,stim,lumen,507 +subject6,2021-01-15 11:16:38,1,6,87.03,104.03,stim,lumen,541 +subject6,2021-01-15 11:16:38,1,7,104.165,121.165,ctrl,lumen,543 +subject6,2021-01-15 11:16:38,1,8,121.502,138.502,ctrl,lumen,791 +subject6,2021-01-15 11:16:38,1,9,138.612,155.612,ctrl,lumen,696 +subject6,2021-01-15 11:16:38,1,10,155.741,172.741,stim,lumen,973 +subject6,2021-01-15 11:16:38,1,11,173.101,190.101,stim,lumen,504 +subject6,2021-01-15 11:16:38,1,12,190.433,207.433,stim,lumen,985 +subject6,2021-01-15 11:16:38,1,13,207.788,224.788,ctrl,lumen,803 +subject6,2021-01-15 11:16:38,1,14,225.163,242.163,ctrl,lumen,942 +subject6,2021-01-15 11:16:38,1,15,242.351,259.351,ctrl,lumen,835 +subject6,2021-01-15 11:16:38,1,16,259.577,276.577,ctrl,lumen,996 +subject6,2021-01-15 11:16:38,1,17,276.695,293.695,stim,lumen,631 +subject6,2021-01-15 11:16:38,1,18,293.985,310.985,stim,lumen,621 +subject6,2021-01-15 11:16:38,1,19,311.152,328.152,stim,lumen,767 +subject6,2021-01-15 11:16:38,1,20,328.523,345.523,ctrl,lumen,529 +subject6,2021-01-15 11:16:38,1,21,345.944,362.944,stim,lumen,886 +subject6,2021-01-15 11:16:38,1,22,363.362,380.362,stim,lumen,810 +subject6,2021-01-15 11:16:38,1,23,380.503,397.503,stim,lumen,664 +subject6,2021-01-15 11:16:38,1,24,397.887,414.887,stim,lumen,612 +subject6,2021-01-15 11:16:38,1,25,476.155,493.155,stim,lumen,950 +subject6,2021-01-15 11:16:38,2,26,493.517,510.517,stim,lumen,0 +subject6,2021-01-15 11:16:38,2,27,510.72,527.72,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,2,28,527.866,544.866,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,2,29,545.114,562.114,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,2,30,562.331,579.331,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,2,31,579.437,596.437,stim,lumen,0 +subject6,2021-01-15 11:16:38,2,32,596.811,613.811,stim,lumen,0 +subject6,2021-01-15 11:16:38,2,33,614.125,631.125,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,2,34,631.516,648.516,stim,lumen,0 +subject6,2021-01-15 11:16:38,2,35,648.63,665.63,stim,lumen,0 +subject6,2021-01-15 11:16:38,2,36,665.929,682.929,stim,lumen,0 +subject6,2021-01-15 11:16:38,2,37,683.141,700.141,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,2,38,700.313,717.313,stim,lumen,0 +subject6,2021-01-15 11:16:38,2,39,717.586,734.586,stim,lumen,0 +subject6,2021-01-15 11:16:38,2,40,734.714,751.714,stim,lumen,0 +subject6,2021-01-15 11:16:38,2,41,752.075,769.075,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,2,42,769.458,786.458,stim,lumen,0 +subject6,2021-01-15 11:16:38,2,43,786.765,803.765,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,2,44,803.884,820.884,stim,lumen,0 +subject6,2021-01-15 11:16:38,2,45,821.245,838.245,stim,lumen,0 +subject6,2021-01-15 11:16:38,2,46,838.408,855.408,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,2,47,855.549,872.549,stim,lumen,0 +subject6,2021-01-15 11:16:38,2,48,872.713,889.713,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,2,49,889.942,906.942,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,2,50,952.031,969.031,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,3,51,969.387,986.387,ctrl,lumen,529 +subject6,2021-01-15 11:16:38,3,52,986.578,1003.578,ctrl,lumen,601 +subject6,2021-01-15 11:16:38,3,53,1003.788,1020.788,stim,lumen,962 +subject6,2021-01-15 11:16:38,3,54,1021.001,1038.001,ctrl,lumen,617 +subject6,2021-01-15 11:16:38,3,55,1038.129,1055.129,stim,lumen,541 +subject6,2021-01-15 11:16:38,3,56,1055.529,1072.529,ctrl,lumen,894 +subject6,2021-01-15 11:16:38,3,57,1072.875,1089.875,ctrl,lumen,849 +subject6,2021-01-15 11:16:38,3,58,1090.057,1107.057,ctrl,lumen,911 +subject6,2021-01-15 11:16:38,3,59,1107.354,1124.354,ctrl,lumen,807 +subject6,2021-01-15 11:16:38,3,60,1124.679,1141.679,ctrl,lumen,717 +subject6,2021-01-15 11:16:38,3,61,1141.983,1158.983,ctrl,lumen,844 +subject6,2021-01-15 11:16:38,3,62,1159.267,1176.267,ctrl,lumen,953 +subject6,2021-01-15 11:16:38,3,63,1176.454,1193.454,ctrl,lumen,974 +subject6,2021-01-15 11:16:38,3,64,1193.837,1210.837,stim,lumen,820 +subject6,2021-01-15 11:16:38,3,65,1211.187,1228.187,ctrl,lumen,763 +subject6,2021-01-15 11:16:38,3,66,1228.556,1245.556,ctrl,lumen,860 +subject6,2021-01-15 11:16:38,3,67,1245.909,1262.909,ctrl,lumen,947 +subject6,2021-01-15 11:16:38,3,68,1263.059,1280.059,stim,lumen,542 +subject6,2021-01-15 11:16:38,3,69,1280.222,1297.222,ctrl,lumen,845 +subject6,2021-01-15 11:16:38,3,70,1297.399,1314.399,stim,lumen,715 +subject6,2021-01-15 11:16:38,3,71,1314.593,1331.593,stim,lumen,825 +subject6,2021-01-15 11:16:38,3,72,1331.785,1348.785,ctrl,lumen,672 +subject6,2021-01-15 11:16:38,3,73,1349.079,1366.079,ctrl,lumen,512 +subject6,2021-01-15 11:16:38,3,74,1366.183,1383.183,stim,lumen,742 +subject6,2021-01-15 11:16:38,3,75,1428.237,1445.237,ctrl,lumen,741 +subject6,2021-01-15 11:16:38,4,76,1445.393,1462.393,stim,lumen,0 +subject6,2021-01-15 11:16:38,4,77,1462.722,1479.722,stim,lumen,0 +subject6,2021-01-15 11:16:38,4,78,1479.996,1496.996,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,4,79,1497.224,1514.224,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,4,80,1514.559,1531.559,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,4,81,1531.955,1548.955,stim,lumen,0 +subject6,2021-01-15 11:16:38,4,82,1549.3,1566.3,stim,lumen,0 +subject6,2021-01-15 11:16:38,4,83,1566.43,1583.43,stim,lumen,0 +subject6,2021-01-15 11:16:38,4,84,1583.819,1600.819,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,4,85,1601.127,1618.127,stim,lumen,0 +subject6,2021-01-15 11:16:38,4,86,1618.378,1635.378,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,4,87,1635.718,1652.718,stim,lumen,0 +subject6,2021-01-15 11:16:38,4,88,1653.111,1670.111,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,4,89,1670.435,1687.435,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,4,90,1687.721,1704.721,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,4,91,1705.036,1722.036,stim,lumen,0 +subject6,2021-01-15 11:16:38,4,92,1722.447,1739.447,stim,lumen,0 +subject6,2021-01-15 11:16:38,4,93,1739.749,1756.749,stim,lumen,0 +subject6,2021-01-15 11:16:38,4,94,1757.081,1774.081,ctrl,lumen,0 +subject6,2021-01-15 11:16:38,4,95,1774.191,1791.191,stim,lumen,0 +subject6,2021-01-15 11:16:38,4,96,1791.426,1808.426,stim,lumen,0 +subject6,2021-01-15 11:16:38,4,97,1808.53,1825.53,stim,lumen,0 +subject6,2021-01-15 11:16:38,4,98,1825.691,1842.691,stim,lumen,0 +subject6,2021-01-15 11:16:38,4,99,1842.868,1859.868,stim,lumen,0 +subject6,2021-01-15 11:16:38,4,100,1860.118,1877.118,stim,lumen,0 diff --git a/workflow_array_ephys/analysis.py b/workflow_array_ephys/analysis.py index 3d047a7f..3ac1cf59 100644 --- a/workflow_array_ephys/analysis.py +++ b/workflow_array_ephys/analysis.py @@ -38,7 +38,7 @@ class AlignedTrialSpikes(dj.Part): -> ephys.CuratedClustering.Unit -> SpikesAlignmentCondition.Trial --- - aligned_spike_times: longblob # (s) spike times relative to the alignment event time + aligned_spike_times: longblob # (s) spike times relative to alignment event time """ class UnitPSTH(dj.Part): @@ -51,9 +51,12 @@ class UnitPSTH(dj.Part): """ def make(self, key): - unit_keys, unit_spike_times = (ephys.CuratedClustering.Unit & key).fetch('KEY', 'spike_times', order_by='unit') + unit_keys, unit_spike_times = (ephys.CuratedClustering.Unit & key + ).fetch('KEY', 'spike_times', order_by='unit') - trial_keys, trial_starts, trial_ends = (trial.Trial & (SpikesAlignmentCondition.Trial & key)).fetch( + trial_keys, trial_starts, trial_ends = (trial.Trial + & (SpikesAlignmentCondition.Trial & key) + ).fetch( 'KEY', 'trial_start_time', 'trial_stop_time', order_by='trial_id') bin_size = (SpikesAlignmentCondition & key).fetch1('bin_size') @@ -62,32 +65,40 @@ def make(self, key): # Spike raster aligned_trial_spikes = [] - units_spike_raster = {u['unit']: {**key, **u, 'aligned_spikes': []} for u in unit_keys} + units_spike_raster = {u['unit']: {**key, **u, 'aligned_spikes': [] + } for u in unit_keys} min_limit, max_limit = np.Inf, -np.Inf - for trial_key, trial_start, trial_stop in zip(trial_keys, trial_starts, trial_ends): - alignment_event_time = (event.Event & key & {'event_type': alignment_spec['alignment_event_type']} - & f'event_start_time BETWEEN {trial_start} AND {trial_stop}') + for trial_key, trial_start, trial_stop in zip(trial_keys, trial_starts, + trial_ends): + alignment_event_time = (event.Event & key + & {'event_type': + alignment_spec['alignment_event_type']} + & (f'event_start_time BETWEEN {trial_start} ' + + f'AND {trial_stop}') + ) if alignment_event_time: - # if there are multiple of such alignment event, pick the last one in the trial + # if multiple of such alignment event, pick the last one in the trial alignment_event_time = alignment_event_time.fetch( 'event_start_time', order_by='event_start_time DESC', limit=1)[0] else: continue - alignment_start_time = (event.Event & key & {'event_type': alignment_spec['start_event_type']} + alignment_start_time = (event.Event & key + & {'event_type': alignment_spec['start_event_type']} & f'event_start_time < {alignment_event_time}') if alignment_start_time: - # if there are multiple of such start event, pick the most immediate one prior to the alignment event + # if multiple start events, pick immediately prior to alignment event alignment_start_time = alignment_start_time.fetch( 'event_start_time', order_by='event_start_time DESC', limit=1)[0] alignment_start_time = max(alignment_start_time, trial_start) else: alignment_start_time = trial_start - alignment_end_time = (event.Event & key & {'event_type': alignment_spec['end_event_type']} + alignment_end_time = (event.Event & key + & {'event_type': alignment_spec['end_event_type']} & f'event_start_time > {alignment_event_time}') if alignment_end_time: - # if there are multiple of such start event, pick the most immediate one following the alignment event + # if multiple start events, pick immediatly following alignment event alignment_end_time = alignment_end_time.fetch( 'event_start_time', order_by='event_start_time', limit=1)[0] alignment_end_time = min(alignment_end_time, trial_stop) @@ -103,16 +114,22 @@ def make(self, key): for unit_key, spikes in zip(unit_keys, unit_spike_times): aligned_spikes = spikes[(alignment_start_time <= spikes) - & (spikes < alignment_end_time)] - alignment_event_time - aligned_trial_spikes.append({**key, **unit_key, **trial_key, 'aligned_spike_times': aligned_spikes}) - units_spike_raster[unit_key['unit']]['aligned_spikes'].append(aligned_spikes) + & (spikes < alignment_end_time) + ] - alignment_event_time + aligned_trial_spikes.append({**key, **unit_key, **trial_key, + 'aligned_spike_times': aligned_spikes}) + units_spike_raster[unit_key['unit']]['aligned_spikes' + ].append(aligned_spikes) # PSTH for unit_spike_raster in units_spike_raster.values(): spikes = np.concatenate(unit_spike_raster['aligned_spikes']) - psth, edges = np.histogram(spikes, bins=np.arange(min_limit, max_limit, bin_size)) - unit_spike_raster['psth'] = psth / len(unit_spike_raster.pop('aligned_spikes')) / bin_size + psth, edges = np.histogram(spikes, + bins=np.arange(min_limit, max_limit, bin_size)) + unit_spike_raster['psth'] = (psth / + len(unit_spike_raster.pop('aligned_spikes') + ) / bin_size) unit_spike_raster['psth_edges'] = edges[1:] self.insert1(key) @@ -128,7 +145,8 @@ def plot_raster(self, key, unit, axs=None): fig, axs = plt.subplots(2, 1, figsize=(12, 8)) trial_ids, aligned_spikes = (self.AlignedTrialSpikes - & key & {'unit': unit}).fetch('trial_id', 'aligned_spike_times') + & key & {'unit': unit} + ).fetch('trial_id', 'aligned_spike_times') psth, psth_edges = (self.UnitPSTH & key & {'unit': unit}).fetch1( 'psth', 'psth_edges') diff --git a/workflow_array_ephys/ingest.py b/workflow_array_ephys/ingest.py index 23f11518..62412a7f 100644 --- a/workflow_array_ephys/ingest.py +++ b/workflow_array_ephys/ingest.py @@ -47,7 +47,8 @@ def ingest_subjects(subject_csv_path='./user_data/subjects.csv', ingest_general(csvs, tables, skip_duplicates=skip_duplicates, verbose=verbose) -def ingest_sessions(session_csv_path='./user_data/sessions.csv', verbose=True): +def ingest_sessions(session_csv_path='./user_data/sessions.csv', verbose=True, + skip_duplicates=False): """ Ingests SpikeGLX and OpenEphys files from directories listed in the session_dir column of ./user_data/sessions.csv @@ -162,12 +163,12 @@ def ingest_events(recording_csv_path='./user_data/behavior_recordings.csv', block_csv_path, block_csv_path, trial_csv_path, trial_csv_path, trial_csv_path, trial_csv_path, - event_csv_path,event_csv_path] + event_csv_path, event_csv_path, event_csv_path] tables = [event.BehaviorRecording(), event.BehaviorRecording.File(), trial.Block(), trial.Block.Attribute(), trial.TrialType(), trial.Trial(), trial.Trial.Attribute(), trial.BlockTrial(), - event.EventType(), event.Event()] + event.EventType(), event.Event(), trial.TrialEvent()] # Allow direct insert required bc element-trial has Imported that should be Manual ingest_general(csvs, tables, skip_duplicates=skip_duplicates, verbose=verbose, diff --git a/workflow_array_ephys/pipeline.py b/workflow_array_ephys/pipeline.py index f5a079be..8783a42b 100644 --- a/workflow_array_ephys/pipeline.py +++ b/workflow_array_ephys/pipeline.py @@ -2,10 +2,9 @@ import os from element_animal import subject from element_lab import lab -from element_session import session +from element_session import session_with_datetime as session from element_array_ephys import probe from element_trial import trial, event -from element_array_ephys import probe, ephys from element_animal.subject import Subject from element_lab.lab import Source, Lab, Protocol, User, Project @@ -44,7 +43,7 @@ Experimenter = lab.User session.activate(db_prefix + 'session', linking_module=__name__) -trial.activate(db_prefix + 'trial', db_prefix + 'event', linking_module= __name__) +trial.activate(db_prefix + 'trial', db_prefix + 'event', linking_module=__name__) # Declare table "SkullReference" for use in element-array-ephys --------------- From 7be78e4e6e7d156f30b0c57c1cf74bb0b3a76ce9 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 21 Mar 2022 11:09:31 -0500 Subject: [PATCH 15/59] update readme for trialized --- README.md | 19 ++- images/attached_trial_analysis.svg | 195 +++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+), 6 deletions(-) create mode 100644 images/attached_trial_analysis.svg diff --git a/README.md b/README.md index 24182e1d..52de56d3 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ A complete electrophysiology workflow can be built using the DataJoint Elements. + [element-animal](https://github.com/datajoint/element-animal) + [element-session](https://github.com/datajoint/element-session) + [element-array-ephys](https://github.com/datajoint/element-array-ephys) ++ Optionally, [element-event](https://github.com/datajoint/element-event) This repository provides demonstrations for: 1. Set up a workflow using DataJoint Elements (see @@ -17,11 +18,12 @@ This repository provides demonstrations for: convention, and directory lookup methods (see [workflow_array_ephys/paths.py](workflow_array_ephys/paths.py)). 3. Ingestion of clustering results. +4. Ingestion of experimental condition information and downstream [PSTH analysis](https://www.sciencedirect.com/topics/neuroscience/peristimulus-time-histogram). ## Workflow architecture -The electrophysiology workflow presented here uses components from 4 DataJoint -Elements (`element-lab`, `element-animal`, `element-session`, +The electrophysiology workflow presented here uses components from 5 DataJoint +Elements (`element-lab`, `element-animal`, `element-session`, `element-event`, `element-array-ephys`) assembled together to form a fully functional workflow. ### element-lab @@ -38,6 +40,10 @@ https://github.com/datajoint/element-animal/raw/main/images/subject_diagram.svg) ![element-array-ephys](images/attached_array_ephys_element.svg) +### assembled with element-event and workflow analysis + +![worklow-trial-analysis](attached_trial_analysis.svg) + ## Installation instructions + The installation instructions can be found at the @@ -45,7 +51,8 @@ https://github.com/datajoint/element-animal/raw/main/images/subject_diagram.svg) ## Interacting with the DataJoint workflow -+ Please refer to the following workflow-specific - [Jupyter notebooks](/notebooks) for an in-depth explanation of how to run the - workflow ([03-process.ipynb](notebooks/03-process.ipynb)) and explore the data - ([05-explore.ipynb](notebooks/05-explore.ipynb)). +Please refer to the following workflow-specific + [Jupyter notebooks](/notebooks) for an in-depth explanation of how to ... ++ run the workflow ([03-process.ipynb](notebooks/03-process.ipynb)) ++ explore the data ([05-explore.ipynb](notebooks/05-explore.ipynb)) ++ visualize trial-based analyses ([07-downstream-analysis.ipynb](notebooks/07-downstream-analysis.ipynb)) diff --git a/images/attached_trial_analysis.svg b/images/attached_trial_analysis.svg new file mode 100644 index 00000000..ff085085 --- /dev/null +++ b/images/attached_trial_analysis.svg @@ -0,0 +1,195 @@ + + + + + + + + + +subject.Subject + + +subject.Subject + + + + + +session.Session + + +session.Session + + + + + +subject.Subject->session.Session + + + + +event.BehaviorRecording + + +event.BehaviorRecording + + + + + +session.Session->event.BehaviorRecording + + + + +trial.Trial + + +trial.Trial + + + + + +event.BehaviorRecording->trial.Trial + + + + +analysis.SpikesAlignmentCondition + + +analysis.SpikesAlignmentCondition + + + + + +analysis.SpikesAlignment + + +analysis.SpikesAlignment + + + + + +analysis.SpikesAlignmentCondition->analysis.SpikesAlignment + + + + +analysis.SpikesAlignmentCondition.Trial + + +analysis.SpikesAlignmentCondition.Trial + + + + + +analysis.SpikesAlignmentCondition->analysis.SpikesAlignmentCondition.Trial + + + + +analysis.SpikesAlignment.AlignedTrialSpikes + + +analysis.SpikesAlignment.AlignedTrialSpikes + + + + + +analysis.SpikesAlignment->analysis.SpikesAlignment.AlignedTrialSpikes + + + + +analysis.SpikesAlignment.UnitPSTH + + +analysis.SpikesAlignment.UnitPSTH + + + + + +analysis.SpikesAlignment->analysis.SpikesAlignment.UnitPSTH + + + + +analysis.SpikesAlignmentCondition.Trial->analysis.SpikesAlignment.AlignedTrialSpikes + + + + +trial.Trial->analysis.SpikesAlignmentCondition.Trial + + + + +ephys.CuratedClustering + + +ephys.CuratedClustering + + + + + +ephys.CuratedClustering->analysis.SpikesAlignmentCondition + + + + +ephys.CuratedClustering->analysis.SpikesAlignment + + + + +ephys.CuratedClustering.Unit + + +ephys.CuratedClustering.Unit + + + + + +ephys.CuratedClustering->ephys.CuratedClustering.Unit + + + + +ephys.CuratedClustering.Unit->analysis.SpikesAlignment.AlignedTrialSpikes + + + + +ephys.CuratedClustering.Unit->analysis.SpikesAlignment.UnitPSTH + + + + +event.AlignmentEvent + + +event.AlignmentEvent + + + + + +event.AlignmentEvent->analysis.SpikesAlignmentCondition + + + + From a6d551d65d5fdc5cfbfc0ac69b9ecdcc27d12d65 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Wed, 23 Mar 2022 18:30:54 -0500 Subject: [PATCH 16/59] activate electrode-localization --- workflow_array_ephys/paths.py | 17 +++++++++++++++++ workflow_array_ephys/pipeline.py | 21 ++++++++++++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/workflow_array_ephys/paths.py b/workflow_array_ephys/paths.py index 2c775287..300650b1 100644 --- a/workflow_array_ephys/paths.py +++ b/workflow_array_ephys/paths.py @@ -1,4 +1,6 @@ import datajoint as dj +import pathlib +from element_interface.utils import find_full_path def get_ephys_root_data_dir(): @@ -9,3 +11,18 @@ def get_session_directory(session_key: dict) -> str: from .pipeline import session session_dir = (session.SessionDirectory & session_key).fetch1('session_dir') return session_dir + + +def get_electrode_localization_dir(probe_insertion_key: dict) -> str: + from .pipeline import ephys + acq_software = (ephys.EphysRecording & probe_insertion_key).fetch1('acq_software') + + if acq_software == 'SpikeGLX': + spikeglx_meta_filepath = pathlib.Path((ephys.EphysRecording.EphysFile & probe_insertion_key + & 'file_path LIKE "%.ap.meta"').fetch1('file_path')) + probe_dir = find_full_path(get_ephys_root_data_dir(), spikeglx_meta_filepath.parent) + elif acq_software == 'Open Ephys': + probe_path = (ephys.EphysRecording.EphysFile & probe_insertion_key).fetch1('file_path') + probe_dir = find_full_path(get_ephys_root_data_dir(), probe_path) + + return probe_dir diff --git a/workflow_array_ephys/pipeline.py b/workflow_array_ephys/pipeline.py index 194ea0ae..29a7c9c8 100644 --- a/workflow_array_ephys/pipeline.py +++ b/workflow_array_ephys/pipeline.py @@ -5,12 +5,13 @@ from element_session import session from element_trial import trial, event from element_array_ephys import probe +from element_electrode_localization import coordinate_framework, electrode_localization from element_animal.subject import Subject from element_lab.lab import Source, Lab, Protocol, User, Project from element_session.session_with_datetime import Session -from .paths import get_ephys_root_data_dir, get_session_directory +from .paths import get_ephys_root_data_dir, get_session_directory, get_electrode_localization_dir if 'custom' not in dj.config: dj.config['custom'] = {} @@ -30,8 +31,8 @@ raise ValueError(f'Unknown ephys mode: {ephys_mode}') __all__ = ['subject', 'lab', 'session', 'trial', 'event', 'probe', 'ephys', 'Subject', - 'Source', 'Lab', 'Protocol', 'User', 'Project', 'Session', - 'get_ephys_root_data_dir', 'get_session_directory'] + 'Source', 'Lab', 'Protocol', 'User', 'Project', 'Session', 'coordinate_framework', 'electrode_localization', + 'get_ephys_root_data_dir', 'get_session_directory', 'get_electrode_localization_dir'] # Activate "lab", "subject", "session" schema --------------------------------- @@ -61,3 +62,17 @@ class SkullReference(dj.Lookup): ephys.activate(db_prefix + 'ephys', db_prefix + 'probe', linking_module=__name__) + +# Activate "electrode-localization" schema ------------------------------------ + +ProbeInsertion = ephys.ProbeInsertion +Electrode = probe.ProbeType.Electrode + +electrode_localization.activate(db_prefix + 'electrode_localization', + db_prefix + 'ccf', + linking_module=__name__) + +coordinate_framework.load_ccf_annotation( + ccf_id=0, version_name='ccf_2017', voxel_resolution=25, + nrrd_filepath='./data/annotation_25.nrrd', + ontology_csv_filepath='./data/query.csv') From d94f36c2bd4ffdb2660085144badccf063fbdc17 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 24 Mar 2022 10:16:25 -0500 Subject: [PATCH 17/59] jupytext, histology updates --- docker/Dockerfile.dev | 2 + docker/Dockerfile.test | 3 + docker/docker-compose-dev.yaml | 3 +- docker/docker-compose-test.yaml | 1 + notebooks/00-data-download-optional.ipynb | 3 + notebooks/01-configure.ipynb | 3 + .../02-workflow-structure-optional.ipynb | 2382 ++++++++++++++++- notebooks/03-process.ipynb | 3 +- notebooks/04-automate-optional.ipynb | 3 + notebooks/05-explore.ipynb | 455 +++- notebooks/06-drop-optional.ipynb | 3 + notebooks/07-downstream-analysis.ipynb | 3 + notebooks/08-electrode-localization.ipynb | 85 + .../py_scripts/00-data-download-optional.py | 69 + notebooks/py_scripts/01-configure.py | 104 + .../02-workflow-structure-optional.py | 137 + notebooks/py_scripts/03-process.py | 298 +++ notebooks/py_scripts/04-automate-optional.py | 113 + notebooks/py_scripts/05-explore.py | 137 + notebooks/py_scripts/06-drop-optional.py | 34 + .../py_scripts/07-downstream-analysis.py | 155 ++ .../py_scripts/08-electrode-localization.py | 40 + workflow_array_ephys/ingest.py | 8 +- workflow_array_ephys/localization.py | 17 + workflow_array_ephys/pipeline.py | 7 +- 25 files changed, 4049 insertions(+), 19 deletions(-) create mode 100644 notebooks/08-electrode-localization.ipynb create mode 100644 notebooks/py_scripts/00-data-download-optional.py create mode 100644 notebooks/py_scripts/01-configure.py create mode 100644 notebooks/py_scripts/02-workflow-structure-optional.py create mode 100644 notebooks/py_scripts/03-process.py create mode 100644 notebooks/py_scripts/04-automate-optional.py create mode 100644 notebooks/py_scripts/05-explore.py create mode 100644 notebooks/py_scripts/06-drop-optional.py create mode 100644 notebooks/py_scripts/07-downstream-analysis.py create mode 100644 notebooks/py_scripts/08-electrode-localization.py create mode 100644 workflow_array_ephys/localization.py diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index 433282b6..09897612 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -17,6 +17,7 @@ COPY --chown=anaconda:anaconda ./element-lab /main/element-lab COPY --chown=anaconda:anaconda ./element-animal /main/element-animal COPY --chown=anaconda:anaconda ./element-session /main/element-session COPY --chown=anaconda:anaconda ./element-trial /main/element-trial +COPY --chown=anaconda:anaconda ./element-electrode-localization /main/element-electrode-localization COPY --chown=anaconda:anaconda ./element-array-ephys /main/element-array-ephys COPY --chown=anaconda:anaconda ./workflow-array-ephys /main/workflow-array-ephys @@ -25,6 +26,7 @@ RUN pip install -e /main/element-lab RUN pip install -e /main/element-animal RUN pip install -e /main/element-session RUN pip install -e /main/element-trial +RUN pip install -e /main/element-electrode-localization RUN pip install -e /main/element-array-ephys RUN pip install -e /main/workflow-array-ephys RUN pip install -r /main/workflow-array-ephys/requirements_test.txt diff --git a/docker/Dockerfile.test b/docker/Dockerfile.test index e4185050..28b25b2b 100644 --- a/docker/Dockerfile.test +++ b/docker/Dockerfile.test @@ -16,6 +16,7 @@ WORKDIR /main/workflow-array-ephys # RUN pip install git+https://github.com//element-animal.git # RUN pip install git+https://github.com//element-session.git # RUN pip install git+https://github.com//element-trial.git +# RUN pip install git+https://github.com//element-electrode-localization.git # RUN pip install git+https://github.com//element-array-ephys.git # RUN git clone https://github.com//workflow-array-ephys.git /main/workflow-array-ephys @@ -31,6 +32,7 @@ COPY --chown=anaconda:anaconda ./element-lab /main/element-lab COPY --chown=anaconda:anaconda ./element-animal /main/element-animal COPY --chown=anaconda:anaconda ./element-session /main/element-session COPY --chown=anaconda:anaconda ./element-trial /main/element-trial +COPY --chown=anaconda:anaconda ./element-electrode-localization /main/element-electrode-localization COPY --chown=anaconda:anaconda ./element-array-ephys /main/element-array-ephys COPY --chown=anaconda:anaconda ./workflow-array-ephys /main/workflow-array-ephys @@ -38,6 +40,7 @@ RUN pip install -e /main/element-lab RUN pip install -e /main/element-animal RUN pip install -e /main/element-session RUN pip install -e /main/element-trial +RUN pip install -e /main/element-electrode-localization RUN pip install -e /main/element-array-ephys # RUN rm -f /main/workflow-array-ephys/dj_local_conf.json diff --git a/docker/docker-compose-dev.yaml b/docker/docker-compose-dev.yaml index c7144ac8..77531413 100644 --- a/docker/docker-compose-dev.yaml +++ b/docker/docker-compose-dev.yaml @@ -6,7 +6,7 @@ x-net: &net networks: - main services: - array-ephys-dev-db: + db: <<: *net image: datajoint/mysql:5.7 container_name: workflow-array-ephys-dev-db @@ -29,6 +29,7 @@ services: - ../../element-animal:/main/element-animal - ../../element-session:/main/element-session - ../../element-trial:/main/element-trial + - ../../element-electrode-localization:/main/element-electrode-localization - ../../element-array-ephys:/main/element-array-ephys - ..:/main/workflow-array-ephys depends_on: diff --git a/docker/docker-compose-test.yaml b/docker/docker-compose-test.yaml index 8c53e2c6..c8d6490d 100644 --- a/docker/docker-compose-test.yaml +++ b/docker/docker-compose-test.yaml @@ -44,6 +44,7 @@ services: - ../../element-animal:/main/element-animal - ../../element-session:/main/element-session - ../../element-trial:/main/element-trial + - ../../element-electrode-localization:/main/element-electrode-localization - ../../element-array-ephys:/main/element-array-ephys - ..:/main/workflow-array-ephys depends_on: diff --git a/notebooks/00-data-download-optional.ipynb b/notebooks/00-data-download-optional.ipynb index 8bc210ff..c21cea39 100644 --- a/notebooks/00-data-download-optional.ipynb +++ b/notebooks/00-data-download-optional.ipynb @@ -182,6 +182,9 @@ } ], "metadata": { + "jupytext": { + "formats": "ipynb,py" + }, "kernelspec": { "display_name": "ephys_workflow_runner", "language": "python", diff --git a/notebooks/01-configure.ipynb b/notebooks/01-configure.ipynb index 5fc00572..f6c54c16 100644 --- a/notebooks/01-configure.ipynb +++ b/notebooks/01-configure.ipynb @@ -247,6 +247,9 @@ } ], "metadata": { + "jupytext": { + "formats": "ipynb,py" + }, "kernelspec": { "display_name": "bl_dev", "language": "python", diff --git a/notebooks/02-workflow-structure-optional.ipynb b/notebooks/02-workflow-structure-optional.ipynb index 34fa7f1a..981caedc 100644 --- a/notebooks/02-workflow-structure-optional.ipynb +++ b/notebooks/02-workflow-structure-optional.ipynb @@ -282,7 +282,315 @@ "outputs": [ { "data": { - "image/svg+xml": "\n\n%3\n\n\nephys.AcquisitionSoftware\n\n\nephys.AcquisitionSoftware\n\n\n\n\nephys.EphysRecording\n\n\nephys.EphysRecording\n\n\n\n\nephys.AcquisitionSoftware->ephys.EphysRecording\n\n\n\nephys.EphysRecording.EphysFile\n\n\nephys.EphysRecording.EphysFile\n\n\n\n\nephys.EphysRecording->ephys.EphysRecording.EphysFile\n\n\n\nephys.LFP\n\n\nephys.LFP\n\n\n\n\nephys.EphysRecording->ephys.LFP\n\n\n\nephys.ClusteringTask\n\n\nephys.ClusteringTask\n\n\n\n\nephys.EphysRecording->ephys.ClusteringTask\n\n\n\nephys.ClusterQualityLabel\n\n\nephys.ClusterQualityLabel\n\n\n\n\nephys.CuratedClustering.Unit\n\n\nephys.CuratedClustering.Unit\n\n\n\n\nephys.ClusterQualityLabel->ephys.CuratedClustering.Unit\n\n\n\nephys.ClusteringParamSet\n\n\nephys.ClusteringParamSet\n\n\n\n\nephys.ClusteringParamSet->ephys.ClusteringTask\n\n\n\nephys.WaveformSet.Waveform\n\n\nephys.WaveformSet.Waveform\n\n\n\n\nephys.CuratedClustering.Unit->ephys.WaveformSet.Waveform\n\n\n\nephys.WaveformSet.PeakWaveform\n\n\nephys.WaveformSet.PeakWaveform\n\n\n\n\nephys.CuratedClustering.Unit->ephys.WaveformSet.PeakWaveform\n\n\n\nephys.ProbeInsertion\n\n\nephys.ProbeInsertion\n\n\n\n\nephys.ProbeInsertion->ephys.EphysRecording\n\n\n\nephys.InsertionLocation\n\n\nephys.InsertionLocation\n\n\n\n\nephys.ProbeInsertion->ephys.InsertionLocation\n\n\n\nephys.Curation\n\n\nephys.Curation\n\n\n\n\nephys.CuratedClustering\n\n\nephys.CuratedClustering\n\n\n\n\nephys.Curation->ephys.CuratedClustering\n\n\n\nephys.LFP.Electrode\n\n\nephys.LFP.Electrode\n\n\n\n\nephys.LFP->ephys.LFP.Electrode\n\n\n\nephys.WaveformSet\n\n\nephys.WaveformSet\n\n\n\n\nephys.WaveformSet->ephys.WaveformSet.Waveform\n\n\n\nephys.WaveformSet->ephys.WaveformSet.PeakWaveform\n\n\n\nephys.Clustering\n\n\nephys.Clustering\n\n\n\n\nephys.ClusteringTask->ephys.Clustering\n\n\n\nephys.CuratedClustering->ephys.CuratedClustering.Unit\n\n\n\nephys.CuratedClustering->ephys.WaveformSet\n\n\n\nephys.Clustering->ephys.Curation\n\n\n\nephys.ClusteringMethod\n\n\nephys.ClusteringMethod\n\n\n\n\nephys.ClusteringMethod->ephys.ClusteringParamSet\n\n\n\n", + "image/svg+xml": [ + "\n", + "\n", + "%3\n", + "\n", + "\n", + "ephys.AcquisitionSoftware\n", + "\n", + "\n", + "ephys.AcquisitionSoftware\n", + "\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording\n", + "\n", + "\n", + "ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "\n", + "ephys.AcquisitionSoftware->ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording.EphysFile\n", + "\n", + "\n", + "ephys.EphysRecording.EphysFile\n", + "\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording->ephys.EphysRecording.EphysFile\n", + "\n", + "\n", + "\n", + "ephys.LFP\n", + "\n", + "\n", + "ephys.LFP\n", + "\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording->ephys.LFP\n", + "\n", + "\n", + "\n", + "ephys.ClusteringTask\n", + "\n", + "\n", + "ephys.ClusteringTask\n", + "\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording->ephys.ClusteringTask\n", + "\n", + "\n", + "\n", + "ephys.ClusterQualityLabel\n", + "\n", + "\n", + "ephys.ClusterQualityLabel\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusterQualityLabel->ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "\n", + "ephys.ClusteringParamSet\n", + "\n", + "\n", + "ephys.ClusteringParamSet\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringParamSet->ephys.ClusteringTask\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit->ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit->ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "\n", + "ephys.ProbeInsertion\n", + "\n", + "\n", + "ephys.ProbeInsertion\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ProbeInsertion->ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "ephys.InsertionLocation\n", + "\n", + "\n", + "ephys.InsertionLocation\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ProbeInsertion->ephys.InsertionLocation\n", + "\n", + "\n", + "\n", + "ephys.Curation\n", + "\n", + "\n", + "ephys.Curation\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering\n", + "\n", + "\n", + "ephys.CuratedClustering\n", + "\n", + "\n", + "\n", + "\n", + "ephys.Curation->ephys.CuratedClustering\n", + "\n", + "\n", + "\n", + "ephys.LFP.Electrode\n", + "\n", + "\n", + "ephys.LFP.Electrode\n", + "\n", + "\n", + "\n", + "\n", + "ephys.LFP->ephys.LFP.Electrode\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet\n", + "\n", + "\n", + "ephys.WaveformSet\n", + "\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet->ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet->ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "\n", + "ephys.Clustering\n", + "\n", + "\n", + "ephys.Clustering\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringTask->ephys.Clustering\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering->ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering->ephys.WaveformSet\n", + "\n", + "\n", + "\n", + "ephys.Clustering->ephys.Curation\n", + "\n", + "\n", + "\n", + "ephys.ClusteringMethod\n", + "\n", + "\n", + "ephys.ClusteringMethod\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringMethod->ephys.ClusteringParamSet\n", + "\n", + "\n", + "\n", + "" + ], "text/plain": [ "" ] @@ -325,7 +633,594 @@ "outputs": [ { "data": { - "image/svg+xml": "\n\n%3\n\n\nephys.AcquisitionSoftware\n\n\nephys.AcquisitionSoftware\n\n\n\n\nephys.EphysRecording\n\n\nephys.EphysRecording\n\n\n\n\nephys.AcquisitionSoftware->ephys.EphysRecording\n\n\n\nephys.ClusteringParamSet\n\n\nephys.ClusteringParamSet\n\n\n\n\nephys.ClusteringTask\n\n\nephys.ClusteringTask\n\n\n\n\nephys.ClusteringParamSet->ephys.ClusteringTask\n\n\n\nsubject.Line\n\n\nsubject.Line\n\n\n\n\nsubject.Subject.Line\n\n\nsubject.Subject.Line\n\n\n\n\nsubject.Line->subject.Subject.Line\n\n\n\nsubject.Line.Allele\n\n\nsubject.Line.Allele\n\n\n\n\nsubject.Line->subject.Line.Allele\n\n\n\nsubject.Subject.Protocol\n\n\nsubject.Subject.Protocol\n\n\n\n\nsubject.SubjectCullMethod\n\n\nsubject.SubjectCullMethod\n\n\n\n\nsubject.Allele.Source\n\n\nsubject.Allele.Source\n\n\n\n\nephys.Curation\n\n\nephys.Curation\n\n\n\n\nephys.CuratedClustering\n\n\nephys.CuratedClustering\n\n\n\n\nephys.Curation->ephys.CuratedClustering\n\n\n\nsubject.Strain\n\n\nsubject.Strain\n\n\n\n\nsubject.Subject.Strain\n\n\nsubject.Subject.Strain\n\n\n\n\nsubject.Strain->subject.Subject.Strain\n\n\n\nsubject.Allele\n\n\nsubject.Allele\n\n\n\n\nsubject.Allele->subject.Allele.Source\n\n\n\nsubject.Allele->subject.Line.Allele\n\n\n\nsubject.Zygosity\n\n\nsubject.Zygosity\n\n\n\n\nsubject.Allele->subject.Zygosity\n\n\n\nsession.Session\n\n\nsession.Session\n\n\n\n\nsession.ProjectSession\n\n\nsession.ProjectSession\n\n\n\n\nsession.Session->session.ProjectSession\n\n\n\nsession.SessionDirectory\n\n\nsession.SessionDirectory\n\n\n\n\nsession.Session->session.SessionDirectory\n\n\n\nephys.ProbeInsertion\n\n\nephys.ProbeInsertion\n\n\n\n\nsession.Session->ephys.ProbeInsertion\n\n\n\nephys.ClusteringMethod\n\n\nephys.ClusteringMethod\n\n\n\n\nephys.ClusteringMethod->ephys.ClusteringParamSet\n\n\n\nsubject.Subject.User\n\n\nsubject.Subject.User\n\n\n\n\nephys.CuratedClustering.Unit\n\n\nephys.CuratedClustering.Unit\n\n\n\n\nephys.WaveformSet.Waveform\n\n\nephys.WaveformSet.Waveform\n\n\n\n\nephys.CuratedClustering.Unit->ephys.WaveformSet.Waveform\n\n\n\nephys.WaveformSet.PeakWaveform\n\n\nephys.WaveformSet.PeakWaveform\n\n\n\n\nephys.CuratedClustering.Unit->ephys.WaveformSet.PeakWaveform\n\n\n\nephys.LFP.Electrode\n\n\nephys.LFP.Electrode\n\n\n\n\nephys.WaveformSet\n\n\nephys.WaveformSet\n\n\n\n\nephys.WaveformSet->ephys.WaveformSet.Waveform\n\n\n\nephys.WaveformSet->ephys.WaveformSet.PeakWaveform\n\n\n\nsubject.Subject\n\n\nsubject.Subject\n\n\n\n\nsubject.Subject->subject.Subject.Protocol\n\n\n\nsubject.Subject->subject.SubjectCullMethod\n\n\n\nsubject.Subject->subject.Subject.Line\n\n\n\nsubject.Subject->session.Session\n\n\n\nsubject.Subject->subject.Subject.User\n\n\n\nsubject.Subject->subject.Subject.Strain\n\n\n\nsubject.Subject.Source\n\n\nsubject.Subject.Source\n\n\n\n\nsubject.Subject->subject.Subject.Source\n\n\n\nsubject.SubjectDeath\n\n\nsubject.SubjectDeath\n\n\n\n\nsubject.Subject->subject.SubjectDeath\n\n\n\nsubject.Subject.Lab\n\n\nsubject.Subject.Lab\n\n\n\n\nsubject.Subject->subject.Subject.Lab\n\n\n\nsubject.Subject->subject.Zygosity\n\n\n\nephys.EphysRecording.EphysFile\n\n\nephys.EphysRecording.EphysFile\n\n\n\n\nephys.EphysRecording->ephys.EphysRecording.EphysFile\n\n\n\nephys.LFP\n\n\nephys.LFP\n\n\n\n\nephys.EphysRecording->ephys.LFP\n\n\n\nephys.EphysRecording->ephys.ClusteringTask\n\n\n\nephys.ClusterQualityLabel\n\n\nephys.ClusterQualityLabel\n\n\n\n\nephys.ClusterQualityLabel->ephys.CuratedClustering.Unit\n\n\n\nephys.InsertionLocation\n\n\nephys.InsertionLocation\n\n\n\n\nephys.LFP->ephys.LFP.Electrode\n\n\n\nephys.ProbeInsertion->ephys.EphysRecording\n\n\n\nephys.ProbeInsertion->ephys.InsertionLocation\n\n\n\nephys.Clustering\n\n\nephys.Clustering\n\n\n\n\nephys.ClusteringTask->ephys.Clustering\n\n\n\nephys.CuratedClustering->ephys.CuratedClustering.Unit\n\n\n\nephys.CuratedClustering->ephys.WaveformSet\n\n\n\nephys.Clustering->ephys.Curation\n\n\n\n", + "image/svg+xml": [ + "\n", + "\n", + "%3\n", + "\n", + "\n", + "ephys.AcquisitionSoftware\n", + "\n", + "\n", + "ephys.AcquisitionSoftware\n", + "\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording\n", + "\n", + "\n", + "ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "\n", + "ephys.AcquisitionSoftware->ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "ephys.ClusteringParamSet\n", + "\n", + "\n", + "ephys.ClusteringParamSet\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringTask\n", + "\n", + "\n", + "ephys.ClusteringTask\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringParamSet->ephys.ClusteringTask\n", + "\n", + "\n", + "\n", + "subject.Line\n", + "\n", + "\n", + "subject.Line\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.Line\n", + "\n", + "\n", + "subject.Subject.Line\n", + "\n", + "\n", + "\n", + "\n", + "subject.Line->subject.Subject.Line\n", + "\n", + "\n", + "\n", + "subject.Line.Allele\n", + "\n", + "\n", + "subject.Line.Allele\n", + "\n", + "\n", + "\n", + "\n", + "subject.Line->subject.Line.Allele\n", + "\n", + "\n", + "\n", + "subject.Subject.Protocol\n", + "\n", + "\n", + "subject.Subject.Protocol\n", + "\n", + "\n", + "\n", + "\n", + "subject.SubjectCullMethod\n", + "\n", + "\n", + "subject.SubjectCullMethod\n", + "\n", + "\n", + "\n", + "\n", + "subject.Allele.Source\n", + "\n", + "\n", + "subject.Allele.Source\n", + "\n", + "\n", + "\n", + "\n", + "ephys.Curation\n", + "\n", + "\n", + "ephys.Curation\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering\n", + "\n", + "\n", + "ephys.CuratedClustering\n", + "\n", + "\n", + "\n", + "\n", + "ephys.Curation->ephys.CuratedClustering\n", + "\n", + "\n", + "\n", + "subject.Strain\n", + "\n", + "\n", + "subject.Strain\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.Strain\n", + "\n", + "\n", + "subject.Subject.Strain\n", + "\n", + "\n", + "\n", + "\n", + "subject.Strain->subject.Subject.Strain\n", + "\n", + "\n", + "\n", + "subject.Allele\n", + "\n", + "\n", + "subject.Allele\n", + "\n", + "\n", + "\n", + "\n", + "subject.Allele->subject.Allele.Source\n", + "\n", + "\n", + "\n", + "subject.Allele->subject.Line.Allele\n", + "\n", + "\n", + "\n", + "subject.Zygosity\n", + "\n", + "\n", + "subject.Zygosity\n", + "\n", + "\n", + "\n", + "\n", + "subject.Allele->subject.Zygosity\n", + "\n", + "\n", + "\n", + "session.Session\n", + "\n", + "\n", + "session.Session\n", + "\n", + "\n", + "\n", + "\n", + "session.ProjectSession\n", + "\n", + "\n", + "session.ProjectSession\n", + "\n", + "\n", + "\n", + "\n", + "session.Session->session.ProjectSession\n", + "\n", + "\n", + "\n", + "session.SessionDirectory\n", + "\n", + "\n", + "session.SessionDirectory\n", + "\n", + "\n", + "\n", + "\n", + "session.Session->session.SessionDirectory\n", + "\n", + "\n", + "\n", + "ephys.ProbeInsertion\n", + "\n", + "\n", + "ephys.ProbeInsertion\n", + "\n", + "\n", + "\n", + "\n", + "session.Session->ephys.ProbeInsertion\n", + "\n", + "\n", + "\n", + "ephys.ClusteringMethod\n", + "\n", + "\n", + "ephys.ClusteringMethod\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringMethod->ephys.ClusteringParamSet\n", + "\n", + "\n", + "\n", + "subject.Subject.User\n", + "\n", + "\n", + "subject.Subject.User\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit->ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit->ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "\n", + "ephys.LFP.Electrode\n", + "\n", + "\n", + "ephys.LFP.Electrode\n", + "\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet\n", + "\n", + "\n", + "ephys.WaveformSet\n", + "\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet->ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet->ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "\n", + "subject.Subject\n", + "\n", + "\n", + "subject.Subject\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Protocol\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.SubjectCullMethod\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Line\n", + "\n", + "\n", + "\n", + "subject.Subject->session.Session\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.User\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Strain\n", + "\n", + "\n", + "\n", + "subject.Subject.Source\n", + "\n", + "\n", + "subject.Subject.Source\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Source\n", + "\n", + "\n", + "\n", + "subject.SubjectDeath\n", + "\n", + "\n", + "subject.SubjectDeath\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.SubjectDeath\n", + "\n", + "\n", + "\n", + "subject.Subject.Lab\n", + "\n", + "\n", + "subject.Subject.Lab\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Lab\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Zygosity\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording.EphysFile\n", + "\n", + "\n", + "ephys.EphysRecording.EphysFile\n", + "\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording->ephys.EphysRecording.EphysFile\n", + "\n", + "\n", + "\n", + "ephys.LFP\n", + "\n", + "\n", + "ephys.LFP\n", + "\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording->ephys.LFP\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording->ephys.ClusteringTask\n", + "\n", + "\n", + "\n", + "ephys.ClusterQualityLabel\n", + "\n", + "\n", + "ephys.ClusterQualityLabel\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusterQualityLabel->ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "\n", + "ephys.InsertionLocation\n", + "\n", + "\n", + "ephys.InsertionLocation\n", + "\n", + "\n", + "\n", + "\n", + "ephys.LFP->ephys.LFP.Electrode\n", + "\n", + "\n", + "\n", + "ephys.ProbeInsertion->ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "ephys.ProbeInsertion->ephys.InsertionLocation\n", + "\n", + "\n", + "\n", + "ephys.Clustering\n", + "\n", + "\n", + "ephys.Clustering\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringTask->ephys.Clustering\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering->ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering->ephys.WaveformSet\n", + "\n", + "\n", + "\n", + "ephys.Clustering->ephys.Curation\n", + "\n", + "\n", + "\n", + "" + ], "text/plain": [ "" ] @@ -347,7 +1242,346 @@ "outputs": [ { "data": { - "image/svg+xml": "\n\n%3\n\n\nephys.AcquisitionSoftware\n\n\nephys.AcquisitionSoftware\n\n\n\n\nephys.EphysRecording\n\n\nephys.EphysRecording\n\n\n\n\nephys.AcquisitionSoftware->ephys.EphysRecording\n\n\n\nephys.ClusteringParamSet\n\n\nephys.ClusteringParamSet\n\n\n\n\nephys.ClusteringTask\n\n\nephys.ClusteringTask\n\n\n\n\nephys.ClusteringParamSet->ephys.ClusteringTask\n\n\n\nephys.Curation\n\n\nephys.Curation\n\n\n\n\nephys.CuratedClustering\n\n\nephys.CuratedClustering\n\n\n\n\nephys.Curation->ephys.CuratedClustering\n\n\n\nsession.Session\n\n\nsession.Session\n\n\n\n\nephys.ProbeInsertion\n\n\nephys.ProbeInsertion\n\n\n\n\nsession.Session->ephys.ProbeInsertion\n\n\n\nephys.ClusteringMethod\n\n\nephys.ClusteringMethod\n\n\n\n\nephys.ClusteringMethod->ephys.ClusteringParamSet\n\n\n\nephys.CuratedClustering.Unit\n\n\nephys.CuratedClustering.Unit\n\n\n\n\nephys.WaveformSet.Waveform\n\n\nephys.WaveformSet.Waveform\n\n\n\n\nephys.CuratedClustering.Unit->ephys.WaveformSet.Waveform\n\n\n\nephys.WaveformSet.PeakWaveform\n\n\nephys.WaveformSet.PeakWaveform\n\n\n\n\nephys.CuratedClustering.Unit->ephys.WaveformSet.PeakWaveform\n\n\n\nephys.LFP.Electrode\n\n\nephys.LFP.Electrode\n\n\n\n\nephys.WaveformSet\n\n\nephys.WaveformSet\n\n\n\n\nephys.WaveformSet->ephys.WaveformSet.Waveform\n\n\n\nephys.WaveformSet->ephys.WaveformSet.PeakWaveform\n\n\n\nsubject.Subject\n\n\nsubject.Subject\n\n\n\n\nsubject.Subject->session.Session\n\n\n\nephys.EphysRecording.EphysFile\n\n\nephys.EphysRecording.EphysFile\n\n\n\n\nephys.EphysRecording->ephys.EphysRecording.EphysFile\n\n\n\nephys.LFP\n\n\nephys.LFP\n\n\n\n\nephys.EphysRecording->ephys.LFP\n\n\n\nephys.EphysRecording->ephys.ClusteringTask\n\n\n\nephys.ClusterQualityLabel\n\n\nephys.ClusterQualityLabel\n\n\n\n\nephys.ClusterQualityLabel->ephys.CuratedClustering.Unit\n\n\n\nephys.InsertionLocation\n\n\nephys.InsertionLocation\n\n\n\n\nephys.LFP->ephys.LFP.Electrode\n\n\n\nephys.ProbeInsertion->ephys.EphysRecording\n\n\n\nephys.ProbeInsertion->ephys.InsertionLocation\n\n\n\nephys.Clustering\n\n\nephys.Clustering\n\n\n\n\nephys.ClusteringTask->ephys.Clustering\n\n\n\nephys.CuratedClustering->ephys.CuratedClustering.Unit\n\n\n\nephys.CuratedClustering->ephys.WaveformSet\n\n\n\nephys.Clustering->ephys.Curation\n\n\n\n", + "image/svg+xml": [ + "\n", + "\n", + "%3\n", + "\n", + "\n", + "ephys.AcquisitionSoftware\n", + "\n", + "\n", + "ephys.AcquisitionSoftware\n", + "\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording\n", + "\n", + "\n", + "ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "\n", + "ephys.AcquisitionSoftware->ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "ephys.ClusteringParamSet\n", + "\n", + "\n", + "ephys.ClusteringParamSet\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringTask\n", + "\n", + "\n", + "ephys.ClusteringTask\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringParamSet->ephys.ClusteringTask\n", + "\n", + "\n", + "\n", + "ephys.Curation\n", + "\n", + "\n", + "ephys.Curation\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering\n", + "\n", + "\n", + "ephys.CuratedClustering\n", + "\n", + "\n", + "\n", + "\n", + "ephys.Curation->ephys.CuratedClustering\n", + "\n", + "\n", + "\n", + "session.Session\n", + "\n", + "\n", + "session.Session\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ProbeInsertion\n", + "\n", + "\n", + "ephys.ProbeInsertion\n", + "\n", + "\n", + "\n", + "\n", + "session.Session->ephys.ProbeInsertion\n", + "\n", + "\n", + "\n", + "ephys.ClusteringMethod\n", + "\n", + "\n", + "ephys.ClusteringMethod\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringMethod->ephys.ClusteringParamSet\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit->ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit->ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "\n", + "ephys.LFP.Electrode\n", + "\n", + "\n", + "ephys.LFP.Electrode\n", + "\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet\n", + "\n", + "\n", + "ephys.WaveformSet\n", + "\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet->ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet->ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "\n", + "subject.Subject\n", + "\n", + "\n", + "subject.Subject\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->session.Session\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording.EphysFile\n", + "\n", + "\n", + "ephys.EphysRecording.EphysFile\n", + "\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording->ephys.EphysRecording.EphysFile\n", + "\n", + "\n", + "\n", + "ephys.LFP\n", + "\n", + "\n", + "ephys.LFP\n", + "\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording->ephys.LFP\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording->ephys.ClusteringTask\n", + "\n", + "\n", + "\n", + "ephys.ClusterQualityLabel\n", + "\n", + "\n", + "ephys.ClusterQualityLabel\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusterQualityLabel->ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "\n", + "ephys.InsertionLocation\n", + "\n", + "\n", + "ephys.InsertionLocation\n", + "\n", + "\n", + "\n", + "\n", + "ephys.LFP->ephys.LFP.Electrode\n", + "\n", + "\n", + "\n", + "ephys.ProbeInsertion->ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "ephys.ProbeInsertion->ephys.InsertionLocation\n", + "\n", + "\n", + "\n", + "ephys.Clustering\n", + "\n", + "\n", + "ephys.Clustering\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringTask->ephys.Clustering\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering->ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering->ephys.WaveformSet\n", + "\n", + "\n", + "\n", + "ephys.Clustering->ephys.Curation\n", + "\n", + "\n", + "\n", + "" + ], "text/plain": [ "" ] @@ -369,7 +1603,176 @@ "outputs": [ { "data": { - "image/svg+xml": "\n\n%3\n\n\nsubject.Subject.Protocol\n\n\nsubject.Subject.Protocol\n\n\n\n\nsubject.SubjectCullMethod\n\n\nsubject.SubjectCullMethod\n\n\n\n\nsubject.Subject.Source\n\n\nsubject.Subject.Source\n\n\n\n\nsubject.Subject.Lab\n\n\nsubject.Subject.Lab\n\n\n\n\nsubject.SubjectDeath\n\n\nsubject.SubjectDeath\n\n\n\n\nsubject.Zygosity\n\n\nsubject.Zygosity\n\n\n\n\nsubject.Subject.User\n\n\nsubject.Subject.User\n\n\n\n\nsubject.Subject.Strain\n\n\nsubject.Subject.Strain\n\n\n\n\nsubject.Subject.Line\n\n\nsubject.Subject.Line\n\n\n\n\nsubject.Subject\n\n\nsubject.Subject\n\n\n\n\nsubject.Subject->subject.Subject.Protocol\n\n\n\nsubject.Subject->subject.SubjectCullMethod\n\n\n\nsubject.Subject->subject.Subject.Source\n\n\n\nsubject.Subject->subject.Subject.Lab\n\n\n\nsubject.Subject->subject.SubjectDeath\n\n\n\nsubject.Subject->subject.Zygosity\n\n\n\nsubject.Subject->subject.Subject.User\n\n\n\nsubject.Subject->subject.Subject.Strain\n\n\n\nsubject.Subject->subject.Subject.Line\n\n\n\nsession.Session\n\n\nsession.Session\n\n\n\n\nsubject.Subject->session.Session\n\n\n\n", + "image/svg+xml": [ + "\n", + "\n", + "%3\n", + "\n", + "\n", + "subject.Subject.Protocol\n", + "\n", + "\n", + "subject.Subject.Protocol\n", + "\n", + "\n", + "\n", + "\n", + "subject.SubjectCullMethod\n", + "\n", + "\n", + "subject.SubjectCullMethod\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.Source\n", + "\n", + "\n", + "subject.Subject.Source\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.Lab\n", + "\n", + "\n", + "subject.Subject.Lab\n", + "\n", + "\n", + "\n", + "\n", + "subject.SubjectDeath\n", + "\n", + "\n", + "subject.SubjectDeath\n", + "\n", + "\n", + "\n", + "\n", + "subject.Zygosity\n", + "\n", + "\n", + "subject.Zygosity\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.User\n", + "\n", + "\n", + "subject.Subject.User\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.Strain\n", + "\n", + "\n", + "subject.Subject.Strain\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.Line\n", + "\n", + "\n", + "subject.Subject.Line\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject\n", + "\n", + "\n", + "subject.Subject\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Protocol\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.SubjectCullMethod\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Source\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Lab\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.SubjectDeath\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Zygosity\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.User\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Strain\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Line\n", + "\n", + "\n", + "\n", + "session.Session\n", + "\n", + "\n", + "session.Session\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->session.Session\n", + "\n", + "\n", + "\n", + "" + ], "text/plain": [ "" ] @@ -391,7 +1794,119 @@ "outputs": [ { "data": { - "image/svg+xml": "\n\n%3\n\n\nephys.AcquisitionSoftware\n\n\nephys.AcquisitionSoftware\n\n\n\n\nephys.EphysRecording\n\n\nephys.EphysRecording\n\n\n\n\nephys.AcquisitionSoftware->ephys.EphysRecording\n\n\n\nephys.ProbeInsertion\n\n\nephys.ProbeInsertion\n\n\n\n\nephys.ProbeInsertion->ephys.EphysRecording\n\n\n\nprobe.ElectrodeConfig\n\n\nprobe.ElectrodeConfig\n\n\n\n\nprobe.ElectrodeConfig->ephys.EphysRecording\n\n\n\nprobe.ProbeType\n\n\nprobe.ProbeType\n\n\n\n\nprobe.ProbeType->probe.ElectrodeConfig\n\n\n\nprobe.Probe\n\n\nprobe.Probe\n\n\n\n\nprobe.ProbeType->probe.Probe\n\n\n\nsession.Session\n\n\nsession.Session\n\n\n\n\nsession.Session->ephys.ProbeInsertion\n\n\n\nprobe.Probe->ephys.ProbeInsertion\n\n\n\n", + "image/svg+xml": [ + "\n", + "\n", + "%3\n", + "\n", + "\n", + "ephys.AcquisitionSoftware\n", + "\n", + "\n", + "ephys.AcquisitionSoftware\n", + "\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording\n", + "\n", + "\n", + "ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "\n", + "ephys.AcquisitionSoftware->ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "ephys.ProbeInsertion\n", + "\n", + "\n", + "ephys.ProbeInsertion\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ProbeInsertion->ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "probe.ElectrodeConfig\n", + "\n", + "\n", + "probe.ElectrodeConfig\n", + "\n", + "\n", + "\n", + "\n", + "probe.ElectrodeConfig->ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "probe.ProbeType\n", + "\n", + "\n", + "probe.ProbeType\n", + "\n", + "\n", + "\n", + "\n", + "probe.ProbeType->probe.ElectrodeConfig\n", + "\n", + "\n", + "\n", + "probe.Probe\n", + "\n", + "\n", + "probe.Probe\n", + "\n", + "\n", + "\n", + "\n", + "probe.ProbeType->probe.Probe\n", + "\n", + "\n", + "\n", + "session.Session\n", + "\n", + "\n", + "session.Session\n", + "\n", + "\n", + "\n", + "\n", + "session.Session->ephys.ProbeInsertion\n", + "\n", + "\n", + "\n", + "probe.Probe->ephys.ProbeInsertion\n", + "\n", + "\n", + "\n", + "" + ], "text/plain": [ "" ] @@ -501,7 +2016,161 @@ "outputs": [ { "data": { - "image/svg+xml": "\n\n%3\n\n\n`neuro_lab`.`#skull_reference`\n\n`neuro_lab`.`#skull_reference`\n\n\nlab.LabMembership\n\n\nlab.LabMembership\n\n\n\n\nlab.Location\n\n\nlab.Location\n\n\n\n\nlab.Source\n\n\nlab.Source\n\n\n\n\nlab.Protocol\n\n\nlab.Protocol\n\n\n\n\nlab.Project\n\n\nlab.Project\n\n\n\n\nlab.ProjectUser\n\n\nlab.ProjectUser\n\n\n\n\nlab.Project->lab.ProjectUser\n\n\n\nlab.Lab\n\n\nlab.Lab\n\n\n\n\nlab.Lab->lab.LabMembership\n\n\n\nlab.Lab->lab.Location\n\n\n\nlab.User\n\n\nlab.User\n\n\n\n\nlab.User->lab.LabMembership\n\n\n\nlab.User->lab.ProjectUser\n\n\n\nlab.UserRole\n\n\nlab.UserRole\n\n\n\n\nlab.UserRole->lab.LabMembership\n\n\n\nlab.ProtocolType\n\n\nlab.ProtocolType\n\n\n\n\nlab.ProtocolType->lab.Protocol\n\n\n\n", + "image/svg+xml": [ + "\n", + "\n", + "%3\n", + "\n", + "\n", + "`neuro_lab`.`#skull_reference`\n", + "\n", + "`neuro_lab`.`#skull_reference`\n", + "\n", + "\n", + "lab.LabMembership\n", + "\n", + "\n", + "lab.LabMembership\n", + "\n", + "\n", + "\n", + "\n", + "lab.Location\n", + "\n", + "\n", + "lab.Location\n", + "\n", + "\n", + "\n", + "\n", + "lab.Source\n", + "\n", + "\n", + "lab.Source\n", + "\n", + "\n", + "\n", + "\n", + "lab.Protocol\n", + "\n", + "\n", + "lab.Protocol\n", + "\n", + "\n", + "\n", + "\n", + "lab.Project\n", + "\n", + "\n", + "lab.Project\n", + "\n", + "\n", + "\n", + "\n", + "lab.ProjectUser\n", + "\n", + "\n", + "lab.ProjectUser\n", + "\n", + "\n", + "\n", + "\n", + "lab.Project->lab.ProjectUser\n", + "\n", + "\n", + "\n", + "lab.Lab\n", + "\n", + "\n", + "lab.Lab\n", + "\n", + "\n", + "\n", + "\n", + "lab.Lab->lab.LabMembership\n", + "\n", + "\n", + "\n", + "lab.Lab->lab.Location\n", + "\n", + "\n", + "\n", + "lab.User\n", + "\n", + "\n", + "lab.User\n", + "\n", + "\n", + "\n", + "\n", + "lab.User->lab.LabMembership\n", + "\n", + "\n", + "\n", + "lab.User->lab.ProjectUser\n", + "\n", + "\n", + "\n", + "lab.UserRole\n", + "\n", + "\n", + "lab.UserRole\n", + "\n", + "\n", + "\n", + "\n", + "lab.UserRole->lab.LabMembership\n", + "\n", + "\n", + "\n", + "lab.ProtocolType\n", + "\n", + "\n", + "lab.ProtocolType\n", + "\n", + "\n", + "\n", + "\n", + "lab.ProtocolType->lab.Protocol\n", + "\n", + "\n", + "\n", + "" + ], "text/plain": [ "" ] @@ -529,7 +2198,246 @@ "outputs": [ { "data": { - "image/svg+xml": "\n\n%3\n\n\nsubject.Line\n\n\nsubject.Line\n\n\n\n\nsubject.Line.Allele\n\n\nsubject.Line.Allele\n\n\n\n\nsubject.Line->subject.Line.Allele\n\n\n\nsubject.Subject.Line\n\n\nsubject.Subject.Line\n\n\n\n\nsubject.Line->subject.Subject.Line\n\n\n\nsubject.Subject.Protocol\n\n\nsubject.Subject.Protocol\n\n\n\n\nsubject.SubjectCullMethod\n\n\nsubject.SubjectCullMethod\n\n\n\n\nsubject.Subject.Source\n\n\nsubject.Subject.Source\n\n\n\n\nsubject.Allele.Source\n\n\nsubject.Allele.Source\n\n\n\n\nsubject.Subject.Lab\n\n\nsubject.Subject.Lab\n\n\n\n\nsubject.SubjectDeath\n\n\nsubject.SubjectDeath\n\n\n\n\nsubject.Zygosity\n\n\nsubject.Zygosity\n\n\n\n\nsubject.Strain\n\n\nsubject.Strain\n\n\n\n\nsubject.Subject.Strain\n\n\nsubject.Subject.Strain\n\n\n\n\nsubject.Strain->subject.Subject.Strain\n\n\n\nsubject.Subject.User\n\n\nsubject.Subject.User\n\n\n\n\nsubject.Allele\n\n\nsubject.Allele\n\n\n\n\nsubject.Allele->subject.Allele.Source\n\n\n\nsubject.Allele->subject.Zygosity\n\n\n\nsubject.Allele->subject.Line.Allele\n\n\n\nsubject.Subject\n\n\nsubject.Subject\n\n\n\n\nsubject.Subject->subject.Subject.Protocol\n\n\n\nsubject.Subject->subject.SubjectCullMethod\n\n\n\nsubject.Subject->subject.Subject.Source\n\n\n\nsubject.Subject->subject.Subject.Lab\n\n\n\nsubject.Subject->subject.SubjectDeath\n\n\n\nsubject.Subject->subject.Zygosity\n\n\n\nsubject.Subject->subject.Subject.User\n\n\n\nsubject.Subject->subject.Subject.Strain\n\n\n\nsubject.Subject->subject.Subject.Line\n\n\n\n", + "image/svg+xml": [ + "\n", + "\n", + "%3\n", + "\n", + "\n", + "subject.Line\n", + "\n", + "\n", + "subject.Line\n", + "\n", + "\n", + "\n", + "\n", + "subject.Line.Allele\n", + "\n", + "\n", + "subject.Line.Allele\n", + "\n", + "\n", + "\n", + "\n", + "subject.Line->subject.Line.Allele\n", + "\n", + "\n", + "\n", + "subject.Subject.Line\n", + "\n", + "\n", + "subject.Subject.Line\n", + "\n", + "\n", + "\n", + "\n", + "subject.Line->subject.Subject.Line\n", + "\n", + "\n", + "\n", + "subject.Subject.Protocol\n", + "\n", + "\n", + "subject.Subject.Protocol\n", + "\n", + "\n", + "\n", + "\n", + "subject.SubjectCullMethod\n", + "\n", + "\n", + "subject.SubjectCullMethod\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.Source\n", + "\n", + "\n", + "subject.Subject.Source\n", + "\n", + "\n", + "\n", + "\n", + "subject.Allele.Source\n", + "\n", + "\n", + "subject.Allele.Source\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.Lab\n", + "\n", + "\n", + "subject.Subject.Lab\n", + "\n", + "\n", + "\n", + "\n", + "subject.SubjectDeath\n", + "\n", + "\n", + "subject.SubjectDeath\n", + "\n", + "\n", + "\n", + "\n", + "subject.Zygosity\n", + "\n", + "\n", + "subject.Zygosity\n", + "\n", + "\n", + "\n", + "\n", + "subject.Strain\n", + "\n", + "\n", + "subject.Strain\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.Strain\n", + "\n", + "\n", + "subject.Subject.Strain\n", + "\n", + "\n", + "\n", + "\n", + "subject.Strain->subject.Subject.Strain\n", + "\n", + "\n", + "\n", + "subject.Subject.User\n", + "\n", + "\n", + "subject.Subject.User\n", + "\n", + "\n", + "\n", + "\n", + "subject.Allele\n", + "\n", + "\n", + "subject.Allele\n", + "\n", + "\n", + "\n", + "\n", + "subject.Allele->subject.Allele.Source\n", + "\n", + "\n", + "\n", + "subject.Allele->subject.Zygosity\n", + "\n", + "\n", + "\n", + "subject.Allele->subject.Line.Allele\n", + "\n", + "\n", + "\n", + "subject.Subject\n", + "\n", + "\n", + "subject.Subject\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Protocol\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.SubjectCullMethod\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Source\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Lab\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.SubjectDeath\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Zygosity\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.User\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Strain\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Line\n", + "\n", + "\n", + "\n", + "" + ], "text/plain": [ "" ] @@ -582,7 +2490,53 @@ "outputs": [ { "data": { - "image/svg+xml": "\n\n%3\n\n\nsession.SessionDirectory\n\n\nsession.SessionDirectory\n\n\n\n\nsession.ProjectSession\n\n\nsession.ProjectSession\n\n\n\n\nsession.Session\n\n\nsession.Session\n\n\n\n\nsession.Session->session.SessionDirectory\n\n\n\nsession.Session->session.ProjectSession\n\n\n\n", + "image/svg+xml": [ + "\n", + "\n", + "%3\n", + "\n", + "\n", + "session.SessionDirectory\n", + "\n", + "\n", + "session.SessionDirectory\n", + "\n", + "\n", + "\n", + "\n", + "session.ProjectSession\n", + "\n", + "\n", + "session.ProjectSession\n", + "\n", + "\n", + "\n", + "\n", + "session.Session\n", + "\n", + "\n", + "session.Session\n", + "\n", + "\n", + "\n", + "\n", + "session.Session->session.SessionDirectory\n", + "\n", + "\n", + "\n", + "session.Session->session.ProjectSession\n", + "\n", + "\n", + "\n", + "" + ], "text/plain": [ "" ] @@ -633,7 +2587,414 @@ "outputs": [ { "data": { - "image/svg+xml": "\n\n%3\n\n\nephys.AcquisitionSoftware\n\n\nephys.AcquisitionSoftware\n\n\n\n\nephys.EphysRecording\n\n\nephys.EphysRecording\n\n\n\n\nephys.AcquisitionSoftware->ephys.EphysRecording\n\n\n\nephys.ClusteringParamSet\n\n\nephys.ClusteringParamSet\n\n\n\n\nephys.ClusteringTask\n\n\nephys.ClusteringTask\n\n\n\n\nephys.ClusteringParamSet->ephys.ClusteringTask\n\n\n\nephys.Curation\n\n\nephys.Curation\n\n\n\n\nephys.CuratedClustering\n\n\nephys.CuratedClustering\n\n\n\n\nephys.Curation->ephys.CuratedClustering\n\n\n\nephys.ClusteringMethod\n\n\nephys.ClusteringMethod\n\n\n\n\nephys.ClusteringMethod->ephys.ClusteringParamSet\n\n\n\nephys.CuratedClustering.Unit\n\n\nephys.CuratedClustering.Unit\n\n\n\n\nephys.WaveformSet.Waveform\n\n\nephys.WaveformSet.Waveform\n\n\n\n\nephys.CuratedClustering.Unit->ephys.WaveformSet.Waveform\n\n\n\nephys.WaveformSet.PeakWaveform\n\n\nephys.WaveformSet.PeakWaveform\n\n\n\n\nephys.CuratedClustering.Unit->ephys.WaveformSet.PeakWaveform\n\n\n\nephys.LFP.Electrode\n\n\nephys.LFP.Electrode\n\n\n\n\nephys.WaveformSet\n\n\nephys.WaveformSet\n\n\n\n\nephys.WaveformSet->ephys.WaveformSet.Waveform\n\n\n\nephys.WaveformSet->ephys.WaveformSet.PeakWaveform\n\n\n\nprobe.Probe\n\n\nprobe.Probe\n\n\n\n\nephys.ProbeInsertion\n\n\nephys.ProbeInsertion\n\n\n\n\nprobe.Probe->ephys.ProbeInsertion\n\n\n\nephys.EphysRecording.EphysFile\n\n\nephys.EphysRecording.EphysFile\n\n\n\n\nephys.EphysRecording->ephys.EphysRecording.EphysFile\n\n\n\nephys.LFP\n\n\nephys.LFP\n\n\n\n\nephys.EphysRecording->ephys.LFP\n\n\n\nephys.EphysRecording->ephys.ClusteringTask\n\n\n\nephys.ClusterQualityLabel\n\n\nephys.ClusterQualityLabel\n\n\n\n\nephys.ClusterQualityLabel->ephys.CuratedClustering.Unit\n\n\n\nprobe.ElectrodeConfig\n\n\nprobe.ElectrodeConfig\n\n\n\n\nprobe.ElectrodeConfig->ephys.EphysRecording\n\n\n\nprobe.ElectrodeConfig.Electrode\n\n\nprobe.ElectrodeConfig.Electrode\n\n\n\n\nprobe.ElectrodeConfig->probe.ElectrodeConfig.Electrode\n\n\n\nephys.InsertionLocation\n\n\nephys.InsertionLocation\n\n\n\n\nephys.LFP->ephys.LFP.Electrode\n\n\n\nprobe.ElectrodeConfig.Electrode->ephys.CuratedClustering.Unit\n\n\n\nprobe.ElectrodeConfig.Electrode->ephys.WaveformSet.Waveform\n\n\n\nprobe.ElectrodeConfig.Electrode->ephys.LFP.Electrode\n\n\n\nprobe.ProbeType\n\n\nprobe.ProbeType\n\n\n\n\nprobe.ProbeType->probe.Probe\n\n\n\nprobe.ProbeType->probe.ElectrodeConfig\n\n\n\nprobe.ProbeType.Electrode\n\n\nprobe.ProbeType.Electrode\n\n\n\n\nprobe.ProbeType->probe.ProbeType.Electrode\n\n\n\nephys.ProbeInsertion->ephys.EphysRecording\n\n\n\nephys.ProbeInsertion->ephys.InsertionLocation\n\n\n\nprobe.ProbeType.Electrode->probe.ElectrodeConfig.Electrode\n\n\n\nephys.Clustering\n\n\nephys.Clustering\n\n\n\n\nephys.ClusteringTask->ephys.Clustering\n\n\n\nephys.CuratedClustering->ephys.CuratedClustering.Unit\n\n\n\nephys.CuratedClustering->ephys.WaveformSet\n\n\n\nephys.Clustering->ephys.Curation\n\n\n\n", + "image/svg+xml": [ + "\n", + "\n", + "%3\n", + "\n", + "\n", + "ephys.AcquisitionSoftware\n", + "\n", + "\n", + "ephys.AcquisitionSoftware\n", + "\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording\n", + "\n", + "\n", + "ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "\n", + "ephys.AcquisitionSoftware->ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "ephys.ClusteringParamSet\n", + "\n", + "\n", + "ephys.ClusteringParamSet\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringTask\n", + "\n", + "\n", + "ephys.ClusteringTask\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringParamSet->ephys.ClusteringTask\n", + "\n", + "\n", + "\n", + "ephys.Curation\n", + "\n", + "\n", + "ephys.Curation\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering\n", + "\n", + "\n", + "ephys.CuratedClustering\n", + "\n", + "\n", + "\n", + "\n", + "ephys.Curation->ephys.CuratedClustering\n", + "\n", + "\n", + "\n", + "ephys.ClusteringMethod\n", + "\n", + "\n", + "ephys.ClusteringMethod\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringMethod->ephys.ClusteringParamSet\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit->ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit->ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "\n", + "ephys.LFP.Electrode\n", + "\n", + "\n", + "ephys.LFP.Electrode\n", + "\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet\n", + "\n", + "\n", + "ephys.WaveformSet\n", + "\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet->ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet->ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "\n", + "probe.Probe\n", + "\n", + "\n", + "probe.Probe\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ProbeInsertion\n", + "\n", + "\n", + "ephys.ProbeInsertion\n", + "\n", + "\n", + "\n", + "\n", + "probe.Probe->ephys.ProbeInsertion\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording.EphysFile\n", + "\n", + "\n", + "ephys.EphysRecording.EphysFile\n", + "\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording->ephys.EphysRecording.EphysFile\n", + "\n", + "\n", + "\n", + "ephys.LFP\n", + "\n", + "\n", + "ephys.LFP\n", + "\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording->ephys.LFP\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording->ephys.ClusteringTask\n", + "\n", + "\n", + "\n", + "ephys.ClusterQualityLabel\n", + "\n", + "\n", + "ephys.ClusterQualityLabel\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusterQualityLabel->ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "\n", + "probe.ElectrodeConfig\n", + "\n", + "\n", + "probe.ElectrodeConfig\n", + "\n", + "\n", + "\n", + "\n", + "probe.ElectrodeConfig->ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "probe.ElectrodeConfig.Electrode\n", + "\n", + "\n", + "probe.ElectrodeConfig.Electrode\n", + "\n", + "\n", + "\n", + "\n", + "probe.ElectrodeConfig->probe.ElectrodeConfig.Electrode\n", + "\n", + "\n", + "\n", + "ephys.InsertionLocation\n", + "\n", + "\n", + "ephys.InsertionLocation\n", + "\n", + "\n", + "\n", + "\n", + "ephys.LFP->ephys.LFP.Electrode\n", + "\n", + "\n", + "\n", + "probe.ElectrodeConfig.Electrode->ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "\n", + "probe.ElectrodeConfig.Electrode->ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "\n", + "probe.ElectrodeConfig.Electrode->ephys.LFP.Electrode\n", + "\n", + "\n", + "\n", + "probe.ProbeType\n", + "\n", + "\n", + "probe.ProbeType\n", + "\n", + "\n", + "\n", + "\n", + "probe.ProbeType->probe.Probe\n", + "\n", + "\n", + "\n", + "probe.ProbeType->probe.ElectrodeConfig\n", + "\n", + "\n", + "\n", + "probe.ProbeType.Electrode\n", + "\n", + "\n", + "probe.ProbeType.Electrode\n", + "\n", + "\n", + "\n", + "\n", + "probe.ProbeType->probe.ProbeType.Electrode\n", + "\n", + "\n", + "\n", + "ephys.ProbeInsertion->ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "ephys.ProbeInsertion->ephys.InsertionLocation\n", + "\n", + "\n", + "\n", + "probe.ProbeType.Electrode->probe.ElectrodeConfig.Electrode\n", + "\n", + "\n", + "\n", + "ephys.Clustering\n", + "\n", + "\n", + "ephys.Clustering\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringTask->ephys.Clustering\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering->ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering->ephys.WaveformSet\n", + "\n", + "\n", + "\n", + "ephys.Clustering->ephys.Curation\n", + "\n", + "\n", + "\n", + "" + ], "text/plain": [ "" ] @@ -661,7 +3022,8 @@ ], "metadata": { "jupytext": { - "encoding": "# -*- coding: utf-8 -*-" + "encoding": "# -*- coding: utf-8 -*-", + "formats": "ipynb,py" }, "kernelspec": { "display_name": "ephys_workflow_runner", diff --git a/notebooks/03-process.ipynb b/notebooks/03-process.ipynb index fdec3659..0db15aa8 100644 --- a/notebooks/03-process.ipynb +++ b/notebooks/03-process.ipynb @@ -3523,7 +3523,8 @@ ], "metadata": { "jupytext": { - "encoding": "# -*- coding: utf-8 -*-" + "encoding": "# -*- coding: utf-8 -*-", + "formats": "ipynb,py" }, "kernelspec": { "display_name": "ephys_workflow_runner", diff --git a/notebooks/04-automate-optional.ipynb b/notebooks/04-automate-optional.ipynb index 1dd6f6c8..0dfdd34d 100644 --- a/notebooks/04-automate-optional.ipynb +++ b/notebooks/04-automate-optional.ipynb @@ -391,6 +391,9 @@ } ], "metadata": { + "jupytext": { + "formats": "ipynb,py" + }, "kernelspec": { "display_name": "ephys_workflow_runner", "language": "python", diff --git a/notebooks/05-explore.ipynb b/notebooks/05-explore.ipynb index b897b778..230c1f24 100644 --- a/notebooks/05-explore.ipynb +++ b/notebooks/05-explore.ipynb @@ -68,7 +68,457 @@ "outputs": [ { "data": { - "image/svg+xml": "\n\n%3\n\n\n`u24_ephys_lab`.`#skull_reference`\n\n`u24_ephys_lab`.`#skull_reference`\n\n\nephys.InsertionLocation\n\n\nephys.InsertionLocation\n\n\n\n\n`u24_ephys_lab`.`#skull_reference`->ephys.InsertionLocation\n\n\n\nephys.AcquisitionSoftware\n\n\nephys.AcquisitionSoftware\n\n\n\n\nephys.EphysRecording\n\n\nephys.EphysRecording\n\n\n\n\nephys.AcquisitionSoftware->ephys.EphysRecording\n\n\n\nephys.ClusterQualityLabel\n\n\nephys.ClusterQualityLabel\n\n\n\n\nephys.CuratedClustering.Unit\n\n\nephys.CuratedClustering.Unit\n\n\n\n\nephys.ClusterQualityLabel->ephys.CuratedClustering.Unit\n\n\n\nephys.ClusteringMethod\n\n\nephys.ClusteringMethod\n\n\n\n\nephys.ClusteringParamSet\n\n\nephys.ClusteringParamSet\n\n\n\n\nephys.ClusteringMethod->ephys.ClusteringParamSet\n\n\n\nephys.ClusteringTask\n\n\nephys.ClusteringTask\n\n\n\n\nephys.ClusteringParamSet->ephys.ClusteringTask\n\n\n\nephys.Clustering\n\n\nephys.Clustering\n\n\n\n\nephys.Curation\n\n\nephys.Curation\n\n\n\n\nephys.Clustering->ephys.Curation\n\n\n\nephys.CuratedClustering\n\n\nephys.CuratedClustering\n\n\n\n\nephys.CuratedClustering->ephys.CuratedClustering.Unit\n\n\n\nephys.WaveformSet\n\n\nephys.WaveformSet\n\n\n\n\nephys.CuratedClustering->ephys.WaveformSet\n\n\n\nephys.WaveformSet.PeakWaveform\n\n\nephys.WaveformSet.PeakWaveform\n\n\n\n\nephys.CuratedClustering.Unit->ephys.WaveformSet.PeakWaveform\n\n\n\nephys.WaveformSet.Waveform\n\n\nephys.WaveformSet.Waveform\n\n\n\n\nephys.CuratedClustering.Unit->ephys.WaveformSet.Waveform\n\n\n\nephys.EphysRecording.EphysFile\n\n\nephys.EphysRecording.EphysFile\n\n\n\n\nephys.EphysRecording->ephys.EphysRecording.EphysFile\n\n\n\nephys.LFP\n\n\nephys.LFP\n\n\n\n\nephys.EphysRecording->ephys.LFP\n\n\n\nephys.EphysRecording->ephys.ClusteringTask\n\n\n\nephys.LFP.Electrode\n\n\nephys.LFP.Electrode\n\n\n\n\nephys.LFP->ephys.LFP.Electrode\n\n\n\nephys.WaveformSet->ephys.WaveformSet.PeakWaveform\n\n\n\nephys.WaveformSet->ephys.WaveformSet.Waveform\n\n\n\nephys.ClusteringTask->ephys.Clustering\n\n\n\nephys.Curation->ephys.CuratedClustering\n\n\n\nephys.ProbeInsertion\n\n\nephys.ProbeInsertion\n\n\n\n\nephys.ProbeInsertion->ephys.EphysRecording\n\n\n\nephys.ProbeInsertion->ephys.InsertionLocation\n\n\n\nlab.Project\n\n\nlab.Project\n\n\n\n\nsession.ProjectSession\n\n\nsession.ProjectSession\n\n\n\n\nlab.Project->session.ProjectSession\n\n\n\nephys.probe.ElectrodeConfig\n\n\nephys.probe.ElectrodeConfig\n\n\n\n\nephys.probe.ElectrodeConfig->ephys.EphysRecording\n\n\n\nephys.probe.ElectrodeConfig.Electrode\n\n\nephys.probe.ElectrodeConfig.Electrode\n\n\n\n\nephys.probe.ElectrodeConfig->ephys.probe.ElectrodeConfig.Electrode\n\n\n\nephys.probe.ElectrodeConfig.Electrode->ephys.CuratedClustering.Unit\n\n\n\nephys.probe.ElectrodeConfig.Electrode->ephys.LFP.Electrode\n\n\n\nephys.probe.ElectrodeConfig.Electrode->ephys.WaveformSet.Waveform\n\n\n\nephys.probe.Probe\n\n\nephys.probe.Probe\n\n\n\n\nephys.probe.Probe->ephys.ProbeInsertion\n\n\n\nsession.Session\n\n\nsession.Session\n\n\n\n\nsession.Session->ephys.ProbeInsertion\n\n\n\nsession.Session->session.ProjectSession\n\n\n\nsession.SessionDirectory\n\n\nsession.SessionDirectory\n\n\n\n\nsession.Session->session.SessionDirectory\n\n\n\nsubject.Subject\n\n\nsubject.Subject\n\n\n\n\nsubject.Subject->session.Session\n\n\n\n", + "image/svg+xml": [ + "\n", + "\n", + "%3\n", + "\n", + "\n", + "`u24_ephys_lab`.`#skull_reference`\n", + "\n", + "`u24_ephys_lab`.`#skull_reference`\n", + "\n", + "\n", + "ephys.InsertionLocation\n", + "\n", + "\n", + "ephys.InsertionLocation\n", + "\n", + "\n", + "\n", + "\n", + "`u24_ephys_lab`.`#skull_reference`->ephys.InsertionLocation\n", + "\n", + "\n", + "\n", + "ephys.AcquisitionSoftware\n", + "\n", + "\n", + "ephys.AcquisitionSoftware\n", + "\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording\n", + "\n", + "\n", + "ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "\n", + "ephys.AcquisitionSoftware->ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "ephys.ClusterQualityLabel\n", + "\n", + "\n", + "ephys.ClusterQualityLabel\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusterQualityLabel->ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "\n", + "ephys.ClusteringMethod\n", + "\n", + "\n", + "ephys.ClusteringMethod\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringParamSet\n", + "\n", + "\n", + "ephys.ClusteringParamSet\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringMethod->ephys.ClusteringParamSet\n", + "\n", + "\n", + "\n", + "ephys.ClusteringTask\n", + "\n", + "\n", + "ephys.ClusteringTask\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ClusteringParamSet->ephys.ClusteringTask\n", + "\n", + "\n", + "\n", + "ephys.Clustering\n", + "\n", + "\n", + "ephys.Clustering\n", + "\n", + "\n", + "\n", + "\n", + "ephys.Curation\n", + "\n", + "\n", + "ephys.Curation\n", + "\n", + "\n", + "\n", + "\n", + "ephys.Clustering->ephys.Curation\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering\n", + "\n", + "\n", + "ephys.CuratedClustering\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering->ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet\n", + "\n", + "\n", + "ephys.WaveformSet\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering->ephys.WaveformSet\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit->ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "\n", + "\n", + "ephys.CuratedClustering.Unit->ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording.EphysFile\n", + "\n", + "\n", + "ephys.EphysRecording.EphysFile\n", + "\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording->ephys.EphysRecording.EphysFile\n", + "\n", + "\n", + "\n", + "ephys.LFP\n", + "\n", + "\n", + "ephys.LFP\n", + "\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording->ephys.LFP\n", + "\n", + "\n", + "\n", + "ephys.EphysRecording->ephys.ClusteringTask\n", + "\n", + "\n", + "\n", + "ephys.LFP.Electrode\n", + "\n", + "\n", + "ephys.LFP.Electrode\n", + "\n", + "\n", + "\n", + "\n", + "ephys.LFP->ephys.LFP.Electrode\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet->ephys.WaveformSet.PeakWaveform\n", + "\n", + "\n", + "\n", + "ephys.WaveformSet->ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "\n", + "ephys.ClusteringTask->ephys.Clustering\n", + "\n", + "\n", + "\n", + "ephys.Curation->ephys.CuratedClustering\n", + "\n", + "\n", + "\n", + "ephys.ProbeInsertion\n", + "\n", + "\n", + "ephys.ProbeInsertion\n", + "\n", + "\n", + "\n", + "\n", + "ephys.ProbeInsertion->ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "ephys.ProbeInsertion->ephys.InsertionLocation\n", + "\n", + "\n", + "\n", + "lab.Project\n", + "\n", + "\n", + "lab.Project\n", + "\n", + "\n", + "\n", + "\n", + "session.ProjectSession\n", + "\n", + "\n", + "session.ProjectSession\n", + "\n", + "\n", + "\n", + "\n", + "lab.Project->session.ProjectSession\n", + "\n", + "\n", + "\n", + "ephys.probe.ElectrodeConfig\n", + "\n", + "\n", + "ephys.probe.ElectrodeConfig\n", + "\n", + "\n", + "\n", + "\n", + "ephys.probe.ElectrodeConfig->ephys.EphysRecording\n", + "\n", + "\n", + "\n", + "ephys.probe.ElectrodeConfig.Electrode\n", + "\n", + "\n", + "ephys.probe.ElectrodeConfig.Electrode\n", + "\n", + "\n", + "\n", + "\n", + "ephys.probe.ElectrodeConfig->ephys.probe.ElectrodeConfig.Electrode\n", + "\n", + "\n", + "\n", + "ephys.probe.ElectrodeConfig.Electrode->ephys.CuratedClustering.Unit\n", + "\n", + "\n", + "\n", + "ephys.probe.ElectrodeConfig.Electrode->ephys.LFP.Electrode\n", + "\n", + "\n", + "\n", + "ephys.probe.ElectrodeConfig.Electrode->ephys.WaveformSet.Waveform\n", + "\n", + "\n", + "\n", + "ephys.probe.Probe\n", + "\n", + "\n", + "ephys.probe.Probe\n", + "\n", + "\n", + "\n", + "\n", + "ephys.probe.Probe->ephys.ProbeInsertion\n", + "\n", + "\n", + "\n", + "session.Session\n", + "\n", + "\n", + "session.Session\n", + "\n", + "\n", + "\n", + "\n", + "session.Session->ephys.ProbeInsertion\n", + "\n", + "\n", + "\n", + "session.Session->session.ProjectSession\n", + "\n", + "\n", + "\n", + "session.SessionDirectory\n", + "\n", + "\n", + "session.SessionDirectory\n", + "\n", + "\n", + "\n", + "\n", + "session.Session->session.SessionDirectory\n", + "\n", + "\n", + "\n", + "subject.Subject\n", + "\n", + "\n", + "subject.Subject\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->session.Session\n", + "\n", + "\n", + "\n", + "" + ], "text/plain": [ "" ] @@ -1821,6 +2271,9 @@ } ], "metadata": { + "jupytext": { + "formats": "ipynb,py" + }, "kernelspec": { "display_name": "ephys_workflow_runner", "language": "python", diff --git a/notebooks/06-drop-optional.ipynb b/notebooks/06-drop-optional.ipynb index 652c3aac..d1eca9af 100644 --- a/notebooks/06-drop-optional.ipynb +++ b/notebooks/06-drop-optional.ipynb @@ -53,6 +53,9 @@ } ], "metadata": { + "jupytext": { + "formats": "ipynb,py" + }, "kernelspec": { "display_name": "Python [conda env:workflow-ephys]", "language": "python", diff --git a/notebooks/07-downstream-analysis.ipynb b/notebooks/07-downstream-analysis.ipynb index c4f9dda3..f04d9985 100644 --- a/notebooks/07-downstream-analysis.ipynb +++ b/notebooks/07-downstream-analysis.ipynb @@ -1814,6 +1814,9 @@ } ], "metadata": { + "jupytext": { + "formats": "ipynb,py" + }, "kernelspec": { "display_name": "venv-nwb", "language": "python", diff --git a/notebooks/08-electrode-localization.ipynb b/notebooks/08-electrode-localization.ipynb new file mode 100644 index 00000000..bd89bbe8 --- /dev/null +++ b/notebooks/08-electrode-localization.ipynb @@ -0,0 +1,85 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Electrode Localization" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Change into the parent directory to find the `dj_local_conf.json` file." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import datajoint as dj\n", + "# change to the upper level folder to detect dj_local_conf.json\n", + "if os.path.basename(os.getcwd())=='notebooks': os.chdir('..')\n", + "assert os.path.basename(os.getcwd())=='workflow-array-ephys', (\"Please move to the \"\n", + " + \"workflow directory\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from workflow_array_ephys.localization import electrode_localization as eloc\n", + "from workflow_array_ephys.localization import coordinate_framework as ccf\n", + "from element_electrode_localization.coordinate_framework import load_ccf_annotation" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "limbic_ids = [972] #, 171, 195, 304, 363, 84, 132, 44, 707, 747, 556, 827, 1054, 1081]\n", + "from element_interface.utils import find_full_path\n", + "from workflow_array_ephys.paths import get_ephys_root_data_dir\n", + "nrrd_filepath = find_full_path(get_ephys_root_data_dir(), 'annotation_10.nrrd')\n", + "ontology_csv_filepath = find_full_path(get_ephys_root_data_dir(), 'query.csv')\n", + "for n in limbic_ids:\n", + " load_ccf_annotation(\n", + " ccf_id=n, version_name='ccf_2017', voxel_resolution=10,\n", + " nrrd_filepath=nrrd_filepath,\n", + " ontology_csv_filepath=ontology_csv_filepath)" + ] + } + ], + "metadata": { + "jupytext": { + "formats": "ipynb,py" + }, + "kernelspec": { + "display_name": "venv-nwb", + "language": "python", + "name": "venv-nwb" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/py_scripts/00-data-download-optional.py b/notebooks/py_scripts/00-data-download-optional.py new file mode 100644 index 00000000..253d889b --- /dev/null +++ b/notebooks/py_scripts/00-data-download-optional.py @@ -0,0 +1,69 @@ +# --- +# jupyter: +# jupytext: +# formats: ipynb,py +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.13.7 +# kernelspec: +# display_name: ephys_workflow_runner +# language: python +# name: ephys_workflow_runner +# --- + +# This workflow will need Ephys data collected from either SpikeGLX or OpenEphys and the output from kilosort2. We provided an example dataset to be downloaded to run through the pipeline. This notebook walks you through the process to download the dataset. + +# ## Install djarchive-client + +# The example dataset was hosted on djarchive, an AWS storage. We provide a client package to download the data.[djarchive-client](https://github.com/datajoint/djarchive-client), which could be installed with pip: + +pip install git+https://github.com/datajoint/djarchive-client.git + +# ## Download ephys test datasets using `djarchive-client` + +import os +import djarchive_client +client = djarchive_client.client() + +# To browse the datasets that are available in djarchive: + +list(client.datasets()) + +# Each of the datasets have different versions associated with the version of workflow package. To browse the revisions: + +list(client.revisions()) + +# To download the dataset, let's prepare a root directory, for example in `/tmp`: + +os.mkdir('/tmp/test_data') + +# Get the dataset revision with the current version of the workflow: + +from workflow_array_ephys import version +revision = version.__version__.replace('.', '_') +revision + +# Then run download for a given set and the revision: + +client.download('workflow-array-ephys-test-set', target_directory='/tmp/test_data', revision=revision) + +# ## Directory organization +# After downloading, the directory will be organized as follows: + +# ``` +# /tmp/test_data/ +# - subject6 +# - session1 +# - towersTask_g0_imec0 +# - towersTask_g0_t0_nidq.meta +# - towersTask_g0_t0.nidq.bin +# ``` + +# We will use this dataset as an example for the rest of the notebooks. If you use for own dataset for the workflow, change the path accordingly. +# +# The example dataset `subject6/session1` is a dataset recorded with SpikeGLX and processed with Kilosort2. The workflow also supports the processing of dataset recorded with OpenEphys. + +# ## Next step +# In the [next notebook](01-configure.ipynb), we will set up the configuration file for the workflow. diff --git a/notebooks/py_scripts/01-configure.py b/notebooks/py_scripts/01-configure.py new file mode 100644 index 00000000..e49fe0a4 --- /dev/null +++ b/notebooks/py_scripts/01-configure.py @@ -0,0 +1,104 @@ +# --- +# jupyter: +# jupytext: +# formats: ipynb,py +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.13.7 +# kernelspec: +# display_name: bl_dev +# language: python +# name: bl_dev +# --- + +# To run the array ephys workflow, we need to properly set up the DataJoint configuration. The configuration will be saved in a file called `dj_local_conf.json` on each machine and this notebook walks you through the process. +# +# +# **The configuration only needs to be set up once**, if you have gone through the configuration before, directly go to [02-workflow-structure](02-workflow-structure-optional.ipynb). + +# # Set up configuration in root directory of this package +# +# As a convention, we set the configuration up in the root directory of the workflow package and always starts importing datajoint and pipeline modules from there. + +import os +os.chdir('..') + +pwd + +import datajoint as dj + +# # Configure database host address and credentials + +# Now let's set up the host, user and password in the `dj.config` global variable + +import getpass +dj.config['database.host'] = '{YOUR_HOST}' +dj.config['database.user'] = '{YOUR_USERNAME}' +dj.config['database.password'] = getpass.getpass() # enter the password securily + +# You should be able to connect to the database at this stage. + +dj.conn() + +# # Configure the `custom` field in `dj.config` for the element-array-ephys + +# The major component of the current workflow is the [DataJoint Array Ephys Element](https://github.com/datajoint/element-array-ephys). Array Ephys Element requires configurations in the field `custom` in `dj.config`: + +# ## Database prefix +# +# Giving a prefix to schema could help on the configuration of privilege settings. For example, if we set prefix `neuro_`, every schema created with the current workflow will start with `neuro_`, e.g. `neuro_lab`, `neuro_subject`, `neuro_ephys` etc. +# +# The prefix could be configurated as follows in `dj.config`: + +dj.config['custom'] = {'database.prefix': 'neuro_'} + +# ## Root directories for raw ephys data and kilosort 2 processed results +# +# + `ephys_root_data_dir` field indicates the root directory for the **ephys raw data** from SpikeGLX or OpenEphys (e.g. `*imec0.ap.bin`, `*imec0.ap.meta`, `*imec0.lf.bin`, `imec0.lf.meta`) or the **clustering results** from kilosort2 (e.g. `spike_times.npy`, `spike_clusters.npy`). The root path typically **do not** contain information of subjects or sessions, all data from subjects/sessions should be subdirectories in the root path. +# +# In the example dataset downloaded with [this instruction](00-data-download-optional.ipynb), `/tmp/test_data` will be the root +# +# ``` +# /tmp/test_data/ +# - subject6 +# - session1 +# - towersTask_g0_imec0 +# - towersTask_g0_t0_nidq.meta +# - towersTask_g0_t0.nidq.bin +# ``` + +# If there is only one root path. +dj.config['custom']['ephys_root_data_dir'] = '/tmp/test_data' +# If there are multiple possible root paths: +dj.config['custom']['ephys_root_data_dir'] = ['/tmp/test_data'] + +dj.config + +# + In the database, every path for the ephys raw data is **relative to this root path**. The benefit is that the absolute path could be configured for each machine, and when data transfer happens, we just need to change the root directory in the config file. +# + The workflow supports **multiple root directories**. If there are multiple possible root directories, specify the `ephys_root_data_dir` as a list. +# + The root path(s) are **specific to each machine**, as the name of drive mount could be different for different operating systems or machines. +# + In the context of the workflow, all the paths saved into the database or saved in the config file need to be in the **POSIX standards** (Unix/Linux), with `/`. The path conversion for machines of any operating system is taken care of inside the elements. + +# # Save the configuration as a json file + +# With the proper configurations, we could save this as a file, either as a local json file, or a global file. + +dj.config.save_local() + +# ls + +# Local configuration file is saved as `dj_local_conf.json` in the root directory of this package `workflow-array-ephys`. Next time if you change your directory to `workflow-array-ephys` before importing datajoint and the pipeline packages, the configurations will get properly loaded. +# +# If saved globally, there will be a hidden configuration file saved in your root directory. The configuration will be loaded no matter where the directory is. + +# + +# dj.config.save_global() +# - + +# # Next Step + +# After the configuration, we will be able to review the workflow structure with [02-workflow-structure-optional](02-workflow-structure-optional.ipynb). + + diff --git a/notebooks/py_scripts/02-workflow-structure-optional.py b/notebooks/py_scripts/02-workflow-structure-optional.py new file mode 100644 index 00000000..e847505a --- /dev/null +++ b/notebooks/py_scripts/02-workflow-structure-optional.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +# --- +# jupyter: +# jupytext: +# formats: ipynb,py +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.13.7 +# kernelspec: +# display_name: ephys_workflow_runner +# language: python +# name: ephys_workflow_runner +# --- + +# # Introduction to the workflow structure + +# This notebook gives a brief overview of the workflow structure and introduce some useful DataJoint tools to facilitate the exploration. +# + DataJoint needs to be pre-configured before running this notebook, if you haven't set up the configuration, refer to notebook [01-configure](01-configure.ipynb). +# + If you are familar with DataJoint and the workflow structure, proceed to the next notebook [03-process](03-process.ipynb) directly to run the workflow. +# + For a more thorough introduction of DataJoint functionings, please visit our [general tutorial site](https://playground.datajoint.io) + +# To load the local configuration, we will change the directory to the package root. + +import os +os.chdir('..') + +# ## Schemas and tables + +# The current workflow is composed of multiple database schemas, each of them corresponds to a module within `workflow_array_ephys.pipeline` + +import datajoint as dj +from workflow_array_ephys.pipeline import lab, subject, session, probe, ephys + +# + Each module contains a schema object that enables interaction with the schema in the database. + +probe.schema + +# + Each module imported above corresponds to one schema inside the database. For example, `ephys` corresponds to `neuro_ephys` schema in the database. +ephys.schema + +# + The table classes in the module corresponds to a table in the schema in the database. + +# + Each datajoint table class inside the module corresponds to a table inside the schema. For example, the class `ephys.EphysRecording` correponds to the table `_ephys_recording` in the schema `neuro_ephys` in the database. +# preview table columns and contents in a table +ephys.EphysRecording() + +# + The first time importing the modules, empty schemas and tables will be created in the database. [markdown] +# # + By importing the modules for the first time, the schemas and tables will be created inside the database. +# # + Once created, importing modules will not create schemas and tables again, but the existing schemas/tables can be accessed and manipulated by the modules. +# - +# ## DataJoint tools to explore schemas and tables + +# + The schemas and tables will not be re-created when importing modules if they have existed. [markdown] +# # + `dj.list_schemas()`: list all schemas a user has access to in the current database +# + `dj.list_schemas()`: list all schemas a user could access. +dj.list_schemas() + +# + `dj.Diagram()`: plot tables and dependencies. + +# + `dj.Diagram()`: plot tables and dependencies +# plot diagram for all tables in a schema +dj.Diagram(ephys) +# - + +# **Table tiers**: +# +# Manual table: green box, manually inserted table, expect new entries daily, e.g. Subject, ProbeInsertion. +# Lookup table: gray box, pre inserted table, commonly used for general facts or parameters. e.g. Strain, ClusteringMethod, ClusteringParamSet. +# Imported table: blue oval, auto-processing table, the processing depends on the importing of external files. e.g. process of Clustering requires output files from kilosort2. +# Computed table: red circle, auto-processing table, the processing does not depend on files external to the database, commonly used for +# Part table: plain text, as an appendix to the master table, all the part entries of a given master entry represent a intact set of the master entry. e.g. Unit of a CuratedClustering. +# +# **Dependencies**: +# +# One-to-one primary: thick solid line, share the exact same primary key, meaning the child table inherits all the primary key fields from the parent table as its own primary key. +# One-to-many primary: thin solid line, inherit the primary key from the parent table, but have additional field(s) as part of the primary key as well +# secondary dependency: dashed line, the child table inherits the primary key fields from parent table as its own secondary attribute. + +# + `dj.Diagram()`: plot the diagram of the tables and dependencies. It could be used to plot tables in a schema or selected tables. +# plot diagram of tables in multiple schemas +dj.Diagram(subject) + dj.Diagram(session) + dj.Diagram(ephys) +# - + +# plot diagram of selected tables and schemas +dj.Diagram(subject.Subject) + dj.Diagram(session.Session) + dj.Diagram(ephys) + +# plot diagram with 1 additional level of dependency downstream +dj.Diagram(subject.Subject) + 1 + +# plot diagram with 2 additional levels of dependency upstream +dj.Diagram(ephys.EphysRecording) - 2 + +# + `heading`: [markdown] +# # + `describe()`: show table definition with foreign key references. +# - +ephys.EphysRecording.describe(); + +# + `heading`: show attribute definitions regardless of foreign key references + +# + `heading`: show table attributes regardless of foreign key references. +ephys.EphysRecording.heading + +# + probe [markdown] +# # Major DataJoint Elements installed in the current workflow +# + ephys [markdown] +# # + [`lab`](https://github.com/datajoint/element-lab): lab management related information, such as Lab, User, Project, Protocol, Source. +# - + +dj.Diagram(lab) + +# + [`animal`](https://github.com/datajoint/element-animal): general animal information, User, Genetic background, Death etc. + +dj.Diagram(subject) + +# + [subject](https://github.com/datajoint/element-animal): contains the basic information of subject, including Strain, Line, Subject, Zygosity, and SubjectDeath information. +subject.Subject.describe(); + +# + [`session`](https://github.com/datajoint/element-session): General information of experimental sessions. + +dj.Diagram(session) + +# + [session](https://github.com/datajoint/element-session): experimental session information +session.Session.describe(); + +# + [`ephys`](https://github.com/datajoint/element-array-ephys): Neuropixel based probe and ephys information + +# + [probe and ephys](https://github.com/datajoint/element-array-ephys): Neuropixel based probe and ephys tables +dj.Diagram(probe) + dj.Diagram(ephys) +# - + +# ## Summary and next step +# +# + This notebook introduced the overall structures of the schemas and tables in the workflow and relevant tools to explore the schema structure and table definitions. +# +# + In the next notebook [03-process](03-process.ipynb), we will further introduce the detailed steps running through the pipeline and table contents accordingly. diff --git a/notebooks/py_scripts/03-process.py b/notebooks/py_scripts/03-process.py new file mode 100644 index 00000000..c8ee604e --- /dev/null +++ b/notebooks/py_scripts/03-process.py @@ -0,0 +1,298 @@ +# -*- coding: utf-8 -*- +# --- +# jupyter: +# jupytext: +# formats: ipynb,py +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.13.7 +# kernelspec: +# display_name: ephys_workflow_runner +# language: python +# name: ephys_workflow_runner +# --- + +# # Interatively run array ephys workflow + +# This notebook walks you through the steps in detail to run the ephys workflow. +# +# + If you need a more automatic approach to run the workflow, refer to [03-automate](03-automate.ipynb) +# + The workflow requires neuropixels meta file and kilosort output data. If you haven't configure the paths, refer to [01-configure](01-configure.ipynb) +# + To overview the schema structures, refer to [02-workflow-structure](02-workflow-structure.ipynb) + +# Let's will change the directory to the package root to load configuration and also import relevant schemas. + +import os +os.chdir('..') + +import datajoint as dj +from workflow_array_ephys.pipeline import lab, subject, session, probe, ephys + +# ## Ingestion of metadata: subjects, sessions, probes, and probe insertions +# +# The first step to run through the workflow is to insert metadata into the following tables: +# +# + subject.Subject: an animal subject for experiments +# + session.Session: an experimental session performed on an animal subject +# + session.SessionDirectory: directory to the data for a given experimental session +# + probe.Probe: probe information +# + ephys.ProbeInsertion: probe insertion into an animal subject during a given experimental session + +dj.Diagram(subject.Subject) + dj.Diagram(session.Session) + dj.Diagram(probe.Probe) + dj.Diagram(ephys.ProbeInsertion) + +# Our example dataset is for subject6, session1. + +# ### Ingest Subject + +subject.Subject.heading + +# insert entries with insert1() or insert(), with all required attributes specified in a dictionary +subject.Subject.insert1( + dict(subject='subject6', sex='M', subject_birth_date='2020-01-04'), + skip_duplicates=True) # skip_duplicates avoids error when inserting entries with duplicated primary keys +subject.Subject() + +# ### Ingest Session + +session.Session.describe(); + +session.Session.heading + +session_key = dict(subject='subject6', session_datetime='2021-01-15 11:16:38') +session.Session.insert1(session_key, skip_duplicates=True) +session.Session() + +# ### Ingest SessionDirectory + +session.SessionDirectory.describe(); + +session.SessionDirectory.heading + +session.SessionDirectory.insert1( + dict(subject='subject6', session_datetime='2021-01-15 11:16:38', + session_dir='subject6/session1'), + skip_duplicates=True) +session.SessionDirectory() + +# **Note**: +# +# the `session_dir` needs to be: +# + a directory **relative to** the `ephys_root_path` in the configuration file, refer to [01-configure](01-configure.ipynb) for more information. +# + a directory in POSIX format (Unix/Linux), with `/`, the difference in Operating system will be taken care of by the elements. + +# ### Ingest Probe + +probe.Probe.heading + +probe.Probe.insert1( + dict(probe='17131311651', probe_type='neuropixels 1.0 - 3B'), + skip_duplicates=True) # this info could be achieve from neuropixels meta file. +probe.Probe() + +# ### Ingest ProbeInsertion + +ephys.ProbeInsertion.describe(); + +ephys.ProbeInsertion.heading + +ephys.ProbeInsertion.insert1( + dict(subject='subject6', session_datetime="2021-01-15 11:16:38", + insertion_number=0, probe='17131311651'), + skip_duplicates=True) # probe, subject, session_datetime needs to follow the restrictions of foreign keys. +ephys.ProbeInsertion() + +# ## Automate this manual step +# +# In this workflow, these manual steps could be automated by: +# 1. Insert entries in files `/user_data/subjects.csv` and `/user_data/session.csv` +# 2. Extract user-specified information from `/user_data/subjects.csv` and `/user_data/sessions.csv` and insert to Subject and Session tables by running: +# ``` +# from workflow_array_ephys.ingest import ingest_subjects, ingest_sessions +# ingest_subjects() +# ingest_sessions() +# ``` +# `ingest_sessions` also extracts probe and probe insertion information automatically from the meta file. +# +# This is the regular routine for daily data processing, illustrated in notebook [04-automate](04-automate[optional].ipynb). + +# ## Populate EphysRecording + +# Now we are ready to populate EphysRecording, a table for entries of ephys recording in a particular session. + +dj.Diagram(session.Session) + \ +(dj.Diagram(probe.ElectrodeConfig) + 1) + \ +dj.Diagram(ephys.EphysRecording) + dj.Diagram(ephys.EphysRecording.EphysFile) +# # +1 means plotting 1 more layer of the child tables + +# The first argument specify a particular session to populate +ephys.EphysRecording.populate(session_key, display_progress=True) + +# Populate EphysRecording extracts the following information from .ap.meta file from SpikeGLX: +# +# 1. **probe.EelectrodeConfig**: this procedure detects new ElectrodeConfig, i.e. which 384 electrodes out of the total 960 on the probe were used in this ephys session, and save the results into the table `probe.EelectrodeConfig`. Each entry in table `ephys.EphysRecording` specifies which ElectrodeConfig is used in a particular ephys session. + +# For this ephys session we just populated, Electrodes 0-383 was used. + +probe.ElectrodeConfig() + +probe.ElectrodeConfig.Electrode() + +# 2. **ephys.EphysRecording**: note here that it refers to a particular electrode_config identified with a hash. + +ephys.EphysRecording() & session_key + +# 3. **ephys_element.EphysRecording.EphysFile** +# +# The table `EphysFile` only saves the meta file from the recording + +ephys.EphysRecording.EphysFile() & session_key + +# ## Create ClusteringTask and run/validate Clustering + +dj.Diagram(ephys.EphysRecording) + ephys.ClusteringParamSet + ephys.ClusteringTask + \ +ephys.Clustering + +# The next major table in the ephys pipeline is the `ClusteringTask`. +# +# + An entry in `ClusteringTask` indicates a set of clustering results generated from Kilosort2 outside `workflow-array-ephys` are ready be ingested. In a future release, an entry in `ClusteringTask` can also indicate a new Kilosort2 clustering job is ready to be triggered. +# +# + The `ClusteringTask` table depends on the table `ClusteringParamSet`, which are the parameters of the clustering task and needed to be ingested first. + +# A method of the class `ClusteringParamSet` called `insert_new_params` helps on the insertion of a parameter set and ensures the inserted one is not duplicated with existing parameter sets in the database. +# +# The following parameters' values are set based on [Kilosort StandardConfig file](https://github.com/MouseLand/Kilosort/tree/main/configFiles) + +# insert clustering task manually +params_ks = { + "fs": 30000, + "fshigh": 150, + "minfr_goodchannels": 0.1, + "Th": [10, 4], + "lam": 10, + "AUCsplit": 0.9, + "minFR": 0.02, + "momentum": [20, 400], + "sigmaMask": 30, + "ThPr": 8, + "spkTh": -6, + "reorder": 1, + "nskip": 25, + "GPU": 1, + "Nfilt": 1024, + "nfilt_factor": 4, + "ntbuff": 64, + "whiteningRange": 32, + "nSkipCov": 25, + "scaleproc": 200, + "nPCs": 3, + "useRAM": 0 +} +ephys.ClusteringParamSet.insert_new_params( + processing_method='kilosort2', + paramset_idx=0, + params=params_ks, + paramset_desc='Spike sorting using Kilosort2') +ephys.ClusteringParamSet() + +# We are then able to insert an entry into the `ClusteringTask` table. One important field of the table is `clustering_output_dir`, which specifies the Kilosort2 output directory for the later processing. +# **Note**: this output dir is a relative path to be combined with `ephys_root_directory` in the config file. + +ephys.ClusteringTask.describe(); + +ephys.ClusteringTask.heading + +ephys.ClusteringTask.insert1( + dict(session_key, insertion_number=0, paramset_idx=0, + clustering_output_dir='subject6/session1/towersTask_g0_imec0'), + skip_duplicates=True) + +ephys.ClusteringTask() & session_key + +# We are then able to populate the clustering results. The `Clustering` table now validates the Kilosort2 outcomes before ingesting the spike sorted results. In a future release of `element-array-ephys`, this table may be used to trigger a Kilosort2 process. A record in the `Clustering` indicates that Kilosort2 job is done successfully and the results are ready to be processed. + +ephys.Clustering.populate(display_progress=True) + +ephys.Clustering() & session_key + +# ## Import clustering results and manually curated results + +# We are now ready to ingest the clustering results (spike times etc.) into the database. These clustering results are either directly from Kilosort2 or with manual curation. Both ways share the same format of files. In the element, there is a `Curation` table that saves this information. + +dj.Diagram(ephys.ClusteringTask) + dj.Diagram(ephys.Clustering) + dj.Diagram(ephys.Curation) + \ +dj.Diagram(ephys.CuratedClustering) + dj.Diagram(ephys.CuratedClustering.Unit) + +ephys.Curation.describe(); + +ephys.Curation.heading + +ephys.Curation.insert1( + dict(session_key, insertion_number=0, paramset_idx=0, + curation_id=1, + curation_time='2021-04-28 15:47:01', + curation_output_dir='subject6/session1/towersTask_g0_imec0', + quality_control=0, + manual_curation=0 + )) + +# In this case, the curation results are directly from Kilosort2 outputs, so the `curation_output_dir` is identical to `clustering_output_dir` in the table `ephys.ClusteringTask`. The `element-array-ephys` provides a helper function `ephys.Curation().create1_from_clustering_task` to conveniently insert an entry without manual curation. +# +# Example usage: +# +# ```python +# key = (ephys.ClusteringTask & session_key).fetch1('KEY') +# ephys.Curation().create1_from_clustering_task(key) +# ``` + +# Then we could populate table `CuratedClustering`, ingesting either the output of Kilosort2 or the curated results. + +ephys.CuratedClustering.populate(session_key, display_progress=True) + +# The part table `CuratedClustering.Unit` contains the spike sorted units + +ephys.CuratedClustering.Unit() + +# ## Populate LFP + +# + `LFP`: mean LFP across all electrodes [markdown] +# # + `LFP`: Mean local field potential across different electrodes. +# # + `LFP.Electrode`: Local field potential of a given electrode. +# + LFP and LFP.Electrode: By populating LFP, LFP of every other 9 electrode on the probe will be saved into table `ephys_element.LFP.Electrode` and an average LFP saved into table `ephys_element.LFP` +dj.Diagram(ephys.EphysRecording) + dj.Diagram(ephys.LFP) + dj.Diagram(ephys.LFP.Electrode) +# - + +# Takes a few minutes to populate +ephys.LFP.populate(session_key, display_progress=True) +ephys.LFP & session_key + +ephys.LFP.Electrode & session_key + +# ## Populate Spike Waveform + +# The current workflow also contain tables to save spike waveforms: +# + `WaveformSet`: a table to drive the processing of all spikes waveforms resulting from a CuratedClustering. +# + `WaveformSet.Waveform`: mean waveform across spikes for a given unit and electrode. +# + `WaveformSet.PeakWaveform`: mean waveform across spikes for a given unit at the electrode with peak spike amplitude. + +dj.Diagram(ephys.CuratedClustering) + dj.Diagram(ephys.WaveformSet) + 1 + +# + The `probe_element.EelectrodeConfig` table conains the configuration information of the electrodes used, i.e. which 384 electrodes out of the total 960 on the probe were used in this ephys session, while the table `ephys_element.EphysRecording` specify which ElectrodeConfig is used in a particular ephys session. +# Takes ~1h to populate for the test dataset +ephys.WaveformSet.populate(session_key, display_progress=True) +# - + +ephys.WaveformSet & session_key + +ephys.WaveformSet.Waveform & session_key + +ephys.WaveformSet.PeakWaveform & session_key + +# ## Summary and next step + +# This notebook walks through the detailed steps running the workflow. +# +# + For an more automated way running the workflow, refer to [04-automate](04-automate-optional.ipynb) +# + In the next notebook [05-explore](05-explore.ipynb), we will introduce DataJoint methods to explore and visualize the ingested data. + + diff --git a/notebooks/py_scripts/04-automate-optional.py b/notebooks/py_scripts/04-automate-optional.py new file mode 100644 index 00000000..de4282d4 --- /dev/null +++ b/notebooks/py_scripts/04-automate-optional.py @@ -0,0 +1,113 @@ +# --- +# jupyter: +# jupytext: +# formats: ipynb,py +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.13.7 +# kernelspec: +# display_name: ephys_workflow_runner +# language: python +# name: ephys_workflow_runner +# --- + +# + [markdown] pycharm={"name": "#%% md\n"} +# # Run workflow in an automatic way +# +# In the previous notebook [03-process](03-process.ipynb), we ran through the workflow in detailed steps. For daily running routines, the current notebook provides a more succinct and automatic approach to run through the pipeline using some utility functions in the workflow. +# - + +import os +os.chdir('..') +import numpy as np +from workflow_array_ephys.pipeline import lab, subject, session, probe, ephys + +# ## Ingestion of subjects, sessions, probes, probe insertions +# +# 1. Fill subject and session information in files `/user_data/subjects.csv` and `/user_data/sessions.csv` +# 2. Run automatic scripts prepared in `workflow_array_ephys.ingest` for ingestion + +from workflow_array_ephys.ingest import ingest_subjects, ingest_sessions + +# ##### Insert new entries for subject.Subject from the `subjects.csv` file + +ingest_subjects() + +# ##### Insert new entries for session.Session, session.SessionDirectory, probe.Probe, ephys.ProbeInsertions from the `sessions.csv` file + +ingest_sessions() + +# ## [Optional] Insert new ClusteringParamSet for Kilosort +# +# This is not needed if keep using the existing ClusteringParamSet + +# + jupyter={"outputs_hidden": false} pycharm={"name": "#%%\n"} +params_ks = { + "fs": 30000, + "fshigh": 150, + "minfr_goodchannels": 0.1, + "Th": [10, 4], + "lam": 10, + "AUCsplit": 0.9, + "minFR": 0.02, + "momentum": [20, 400], + "sigmaMask": 30, + "ThPr": 8, + "spkTh": -6, + "reorder": 1, + "nskip": 25, + "GPU": 1, + "Nfilt": 1024, + "nfilt_factor": 4, + "ntbuff": 64, + "whiteningRange": 32, + "nSkipCov": 25, + "scaleproc": 200, + "nPCs": 3, + "useRAM": 0 +} +ephys.ClusteringParamSet.insert_new_params( + processing_method='kilosort2', + paramset_idx=0, + params=params_ks, + paramset_desc='Spike sorting using Kilosort2') +# - + +# ## Trigger autoprocessing of the remaining ephys pipeline + +from workflow_array_ephys import process + +# The `process.run()` function in the workflow populates every auto-processing table in the workflow. If a table is dependent on a manual table upstream, it will not get populated until the manual table is inserted. + +# At this stage, process script populates through the table upstream of `ClusteringTask` +process.run() + +# ## Insert new ClusteringTask to trigger ingestion of clustering results +# +# To populate the rest of the tables in the workflow, an entry in the `ClusteringTask` needs to be added to trigger the ingestion of the clustering results, with the two pieces of information specified: +# + the `paramset_idx` used for the clustering job +# + the output directory storing the clustering results + +session_key = session.Session.fetch1('KEY') +ephys.ClusteringTask.insert1( + dict(session_key, insertion_number=0, paramset_idx=0, + clustering_output_dir='subject6/session1/towersTask_g0_imec0'), skip_duplicates=True) + +# run populate again for table Clustering +process.run() + +# ## Insert new Curation to trigger ingestion of curated results + +key = (ephys.ClusteringTask & session_key).fetch1('KEY') +ephys.Curation().create1_from_clustering_task(key) + +# run populate for the rest of the tables in the workflow, takes a while +process.run() + +# ## Summary and next step +# +# + This notebook runs through the workflow in an automatic manner. +# +# + In the next notebook [05-explore](05-explore.ipynb), we will introduce how to query, fetch and visualize the contents we ingested into the tables. diff --git a/notebooks/py_scripts/05-explore.py b/notebooks/py_scripts/05-explore.py new file mode 100644 index 00000000..a6054b97 --- /dev/null +++ b/notebooks/py_scripts/05-explore.py @@ -0,0 +1,137 @@ +# --- +# jupyter: +# jupytext: +# formats: ipynb,py +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.13.7 +# kernelspec: +# display_name: ephys_workflow_runner +# language: python +# name: ephys_workflow_runner +# --- + +# # DataJoint Workflow Array Ephys +# +# This notebook will describe the steps for interacting with the data ingested into `workflow-array-ephys`. + +import os +os.chdir('..') + +# + +import datajoint as dj +import matplotlib.pyplot as plt +import numpy as np + +from workflow_array_ephys.pipeline import lab, subject, session, ephys + +# + [markdown] pycharm={"name": "#%% md\n"} +# ## Workflow architecture +# +# This workflow is assembled from 4 DataJoint elements: +# # + [element-lab](https://github.com/datajoint/element-lab) +# # + [element-animal](https://github.com/datajoint/element-animal) +# # + [element-session](https://github.com/datajoint/element-session) +# # + [element-array-ephys](https://github.com/datajoint/element-array-ephys) +# +# For the architecture and detailed descriptions for each of those elements, please visit the respective links. +# +# Below is the diagram describing the core components of the fully assembled pipeline. +# +# - + +dj.Diagram(ephys) + (dj.Diagram(session.Session) + 1) - 1 + +# ## Browsing the data with DataJoint query and fetch +# +# +# DataJoint provides abundant functions to query data and fetch. For a detailed tutorials, visit our [general tutorial site](https://playground.datajoint.io/) +# + +# Running through the pipeline, we have ingested data of subject6 session1 into the database. Here are some highlights of the important tables. + +# ### `Subject` and `Session` tables + +subject.Subject() + +session.Session() + +session_key = (session.Session & 'subject="subject6"' & 'session_datetime = "2021-01-15 11:16:38"').fetch1('KEY') + +# ### `ephys.ProbeInsertion` and `ephys.EphysRecording` tables +# +# These tables stores the probe recordings within a particular session from one or more probes. + +ephys.ProbeInsertion & session_key + +ephys.EphysRecording & session_key + +# ### `ephys.ClusteringTask` , `ephys.Clustering`, `ephys.Curation`, and `ephys.CuratedClustering` +# +# + Spike-sorting is performed on a per-probe basis with the details stored in `ClusteringTask` and `Clustering` +# +# + After the spike sorting, the results may go through curation process. +# + If it did not go through curation, a copy of `ClusteringTask` entry was inserted into table `ephys.Curation` with the `curation_ouput_dir` identicial to the `clustering_output_dir`. +# + If it did go through a curation, a new entry will be inserted into `ephys.Curation`, with a `curation_output_dir` specified. +# + `ephys.Curation` supports multiple curations of a clustering task. + +ephys.ClusteringTask * ephys.Clustering & session_key + +# In our example workflow, `curation_output_dir` is the same as `clustering_output_dir` + +ephys.Curation * ephys.CuratedClustering & session_key + +# ### Spike-sorting results are stored in `ephys.CuratedClustering`, `ephys.WaveformSet.Waveform` + +ephys.CuratedClustering.Unit & session_key + +# Let's pick one probe insertion and one `curation_id`, and further inspect the clustering results. + +curation_key = (ephys.CuratedClustering & session_key & 'insertion_number = 0' & 'curation_id=1').fetch1('KEY') + +ephys.CuratedClustering.Unit & curation_key + +# ### Generate a raster plot + +# Let's try a raster plot - just the "good" units + +ephys.CuratedClustering.Unit & curation_key & 'cluster_quality_label = "good"' + +units, unit_spiketimes = (ephys.CuratedClustering.Unit + & curation_key + & 'cluster_quality_label = "good"').fetch('unit', 'spike_times') + +x = np.hstack(unit_spiketimes) +y = np.hstack([np.full_like(s, u) for u, s in zip(units, unit_spiketimes)]) + +fig, ax = plt.subplots(1, 1, figsize=(32, 16)) +ax.plot(x, y, '|') +ax.set_xlabel('Time (s)'); +ax.set_ylabel('Unit'); + +# ### Plot waveform of a unit + +# Let's pick one unit and further inspect + +unit_key = (ephys.CuratedClustering.Unit & curation_key & 'unit = 15').fetch1('KEY') + +ephys.CuratedClustering.Unit * ephys.WaveformSet.Waveform & unit_key + +unit_data = (ephys.CuratedClustering.Unit * ephys.WaveformSet.PeakWaveform & unit_key).fetch1() + +unit_data + +sampling_rate = (ephys.EphysRecording & curation_key).fetch1('sampling_rate')/1000 # in kHz +plt.plot(np.r_[:unit_data['peak_electrode_waveform'].size] * 1/sampling_rate, unit_data['peak_electrode_waveform']) +plt.xlabel('Time (ms)'); +plt.ylabel(r'Voltage ($\mu$V)'); + +# ## Summary and Next Step + +# This notebook highlights the major tables in the workflow and visualize some of the ingested results. +# +# The next notebook [06-drop](06-drop-optional.ipynb) shows how to drop schemas and tables if needed. + + diff --git a/notebooks/py_scripts/06-drop-optional.py b/notebooks/py_scripts/06-drop-optional.py new file mode 100644 index 00000000..239cf70e --- /dev/null +++ b/notebooks/py_scripts/06-drop-optional.py @@ -0,0 +1,34 @@ +# --- +# jupyter: +# jupytext: +# formats: ipynb,py +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.13.7 +# kernelspec: +# display_name: Python [conda env:workflow-ephys] +# language: python +# name: conda-env-workflow-ephys-py +# --- + +# # Drop schemas +# +# + Schemas are not typically dropped in a production workflow with real data in it. +# + At the developmental phase, it might be required for the table redesign. +# + When dropping all schemas is needed, the following is the dependency order. + +# Change into the parent directory to find the `dj_local_conf.json` file. + +import os +os.chdir('..') + +# + +from workflow_array_ephys.pipeline import * + +# ephys.schema.drop() +# probe.schema.drop() +# session.schema.drop() +# subject.schema.drop() +# lab.schema.drop() diff --git a/notebooks/py_scripts/07-downstream-analysis.py b/notebooks/py_scripts/07-downstream-analysis.py new file mode 100644 index 00000000..c5a05a64 --- /dev/null +++ b/notebooks/py_scripts/07-downstream-analysis.py @@ -0,0 +1,155 @@ +# --- +# jupyter: +# jupytext: +# formats: ipynb,py +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.13.7 +# kernelspec: +# display_name: venv-nwb +# language: python +# name: venv-nwb +# --- + +# + [markdown] tags=[] +# # DataJoint U24 - Workflow Array Electrophysiology +# - + +# ## Setup + +# First, let's change directories to find the `dj_local_conf` file. + +import os +# change to the upper level folder to detect dj_local_conf.json +if os.path.basename(os.getcwd())=='notebooks': os.chdir('..') +assert os.path.basename(os.getcwd())=='workflow-array-ephys', ("Please move to the " + + "workflow directory") +# We'll be working with long tables, so we'll make visualization easier with a limit +import datajoint as dj; dj.config['display.limit']=10 + +# Next, we populate the python namespace with the required schemas + +from workflow_array_ephys.pipeline import session, ephys, trial, event + +# ## Trial and Event schemas + +# Tables in the `trial` and `event` schemas specify the structure of your experiment, including block, trial and event timing. +# - Session has a 1-to-1 mapping with a behavior recording +# - A block is a continuous phase of an experiment that contains repeated instances of a condition, or trials. +# - Events may occur within or outside of conditions, either instantaneous or continuous. +# +# The diagram below shows (a) the levels of hierarchy and (b) how the bounds may not completely overlap. A block may not fully capure trials and events may occur outside both blocks/trials. + +# ``` +# |----------------------------------------------------------------------------| +# |-------------------------------- Session ---------------------------------|__ +# |-------------------------- BehaviorRecording ---------------------------|____ +# |----- Block 1 -----|______|----- Block 2 -----|______|----- Block 3 -----|___ +# | trial 1 || trial 2 |____| trial 3 || trial 4 |____| trial 5 |____| trial 6 | +# |_|e1|_|e2||e3|_|e4|__|e5|__|e6||e7||e8||e9||e10||e11|____|e12||e13|_________| +# |----------------------------------------------------------------------------| +# ``` + +# Let's load some example data. The `ingest.py` script has a series of loaders to help. + +from workflow_array_ephys.ingest import ingest_subjects, ingest_sessions,\ + ingest_events, ingest_alignment + +ingest_subjects(); ingest_sessions(); ingest_events() + +# We have 100 total trials, either 'stim' or 'ctrl', with start and stop time + +trial.Trial() + +# Each trial is paired with one or more events that take place during the trial window. + +trial.TrialEvent() & 'trial_id<5' + +# Finally, the `AlignmentEvent` describes the event of interest and the window we'd like to see around it. + +ingest_alignment() + +event.AlignmentEvent() + +# ## Event-aligned trialized unit spike times + +# First, we'll check that the data is still properly inserted from the previous notebooks. + +ephys.CuratedClustering() + +# For this example, we'll be looking at `subject6`. + +clustering_key = (ephys.CuratedClustering + & {'subject': 'subject6', 'session_datetime': '2021-01-15 11:16:38', + 'insertion_number': 0} + ).fetch1('KEY') + +trial.Trial & clustering_key + +# And we can narrow our focus on `ctrl` trials. + +ctrl_trials = trial.Trial & clustering_key & 'trial_type = "ctrl"' + +# The `analysis` schema provides example tables to perform event-aligned spike-times analysis. + +from workflow_array_ephys import analysis + +# + ***SpikesAlignmentCondition*** - a manual table to specify the inputs and condition for the analysis +# + ***SpikesAlignment*** - a computed table to extract event-aligned spikes and compute unit PSTH + +# Let's start by creating several analyses configuration - i.e. inserting into ***SpikesAlignmentCondition*** for the `center` event, called `center_button` in the `AlignmentEvent` table. + +event.AlignmentEvent() + +alignment_key = (event.AlignmentEvent & 'alignment_name = "center_button"' + ).fetch1('KEY') +alignment_condition = {**clustering_key, **alignment_key, + 'trial_condition': 'ctrl_center_button'} +analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True) + +analysis.SpikesAlignmentCondition.Trial.insert( + (analysis.SpikesAlignmentCondition * ctrl_trials & alignment_condition).proj(), + skip_duplicates=True) + +analysis.SpikesAlignmentCondition.Trial() + +# With the steps above, we have create a new spike alignment condition for analysis, named `ctrl_center_button`, which specifies: +# + a CuratedClustering of interest for analysis +# + an event of interest to align the spikes to - `center_button` +# + a set of trials of interest to perform the analysis on - `ctrl` trials + +# Now, let's create another set with: +# + the same CuratedClustering of interest for analysis +# + an event of interest to align the spikes to - `center_button` +# + a set of trials of interest to perform the analysis on - `stim` trials + +stim_trials = trial.Trial & clustering_key & 'trial_type = "stim"' +alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'} +analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True) +analysis.SpikesAlignmentCondition.Trial.insert( + (analysis.SpikesAlignmentCondition * stim_trials & alignment_condition).proj(), + skip_duplicates=True) + +# We can compare conditions in the `SpikesAlignmentCondition` table. + +analysis.SpikesAlignmentCondition() + +analysis.SpikesAlignmentCondition.Trial & 'trial_condition = "ctrl_center_button"' + +# ### Computation + +# Now let's run the computation on these. + +analysis.SpikesAlignment.populate(display_progress=True) + +# ### Vizualize + +# We can visualize the results with the `plot_raster` function. + +alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'ctrl_center_button'} +analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2); + +alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'} +analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2); diff --git a/notebooks/py_scripts/08-electrode-localization.py b/notebooks/py_scripts/08-electrode-localization.py new file mode 100644 index 00000000..b94eb54e --- /dev/null +++ b/notebooks/py_scripts/08-electrode-localization.py @@ -0,0 +1,40 @@ +# --- +# jupyter: +# jupytext: +# formats: ipynb,py +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.13.7 +# kernelspec: +# display_name: venv-nwb +# language: python +# name: venv-nwb +# --- + +# # Electrode Localization + +# Change into the parent directory to find the `dj_local_conf.json` file. + +import os +import datajoint as dj +# change to the upper level folder to detect dj_local_conf.json +if os.path.basename(os.getcwd())=='notebooks': os.chdir('..') +assert os.path.basename(os.getcwd())=='workflow-array-ephys', ("Please move to the " + + "workflow directory") + +from workflow_array_ephys.localization import electrode_localization as eloc +from workflow_array_ephys.localization import coordinate_framework as ccf +from element_electrode_localization.coordinate_framework import load_ccf_annotation + +limbic_ids = [972] #, 171, 195, 304, 363, 84, 132, 44, 707, 747, 556, 827, 1054, 1081] +from element_interface.utils import find_full_path +from workflow_array_ephys.paths import get_ephys_root_data_dir +nrrd_filepath = find_full_path(get_ephys_root_data_dir(), 'annotation_10.nrrd') +ontology_csv_filepath = find_full_path(get_ephys_root_data_dir(), 'query.csv') +for n in limbic_ids: + load_ccf_annotation( + ccf_id=n, version_name='ccf_2017', voxel_resolution=10, + nrrd_filepath=nrrd_filepath, + ontology_csv_filepath=ontology_csv_filepath) diff --git a/workflow_array_ephys/ingest.py b/workflow_array_ephys/ingest.py index 62412a7f..b40d5a28 100644 --- a/workflow_array_ephys/ingest.py +++ b/workflow_array_ephys/ingest.py @@ -15,12 +15,14 @@ def ingest_general(csvs, tables, skip_duplicates=True, verbose=True, Inserts data from a series of csvs into their corresponding table: e.g., ingest_general(['./lab_data.csv', './proj_data.csv'], [lab.Lab(),lab.Project()] - ingest_general(csvs, tables, skip_duplicates=True, verbose=True, allow_direct_insert=False) + ingest_general(csvs, tables, skip_duplicates=True, verbose=True, + allow_direct_insert=False) :param csvs: list of relative paths to CSV files. CSV are delimited by commas. :param tables: list of datajoint tables with () :param verbose: print number inserted (i.e., table length change) - :param skip_duplicates: - :param allow_direct_insert: + :param skip_duplicates: skip items that are either (a) duplicates within the csv + or (b) already exist in the corresponding table + :param allow_direct_insert: Permit insertion directly into calculated tables """ for csv_filepath, table in zip(csvs, tables): with open(csv_filepath, newline='') as f: diff --git a/workflow_array_ephys/localization.py b/workflow_array_ephys/localization.py new file mode 100644 index 00000000..1824fd55 --- /dev/null +++ b/workflow_array_ephys/localization.py @@ -0,0 +1,17 @@ +import datajoint as dj +from element_electrode_localization import coordinate_framework, electrode_localization +from .pipeline import ephys, probe + + +if 'custom' not in dj.config: + dj.config['custom'] = {} + +db_prefix = dj.config['custom'].get('database.prefix', '') + +__all__ = ['ephys', 'probe', 'coordinate_framework', 'electrode_localization', + 'ProbeInsertion'] + +ProbeInsertion = ephys.ProbeInsertion +electrode_localization.activate(db_prefix + 'eloc', + db_prefix + 'ccf', + linking_module=__name__) diff --git a/workflow_array_ephys/pipeline.py b/workflow_array_ephys/pipeline.py index 8783a42b..f2c76173 100644 --- a/workflow_array_ephys/pipeline.py +++ b/workflow_array_ephys/pipeline.py @@ -17,8 +17,8 @@ db_prefix = dj.config['custom'].get('database.prefix', '') -__all__ = ['subject', 'lab', 'session', 'trial', 'event', 'probe', 'ephys', 'Subject', - 'Source', 'Lab', 'Protocol', 'User', 'Project', 'Session', +__all__ = ['subject', 'lab', 'session', 'trial', 'event', 'probe', 'ephys', + 'Subject', 'Source', 'Lab', 'Protocol', 'User', 'Project', 'Session', 'get_ephys_root_data_dir', 'get_session_directory'] @@ -26,7 +26,7 @@ ephys_mode = os.getenv('EPHYS_MODE', dj.config['custom'].get('ephys_mode', 'acute')) if ephys_mode == 'acute': - from element_array_ephys import ephys + from element_array_ephys import ephys_acute as ephys elif ephys_mode == 'chronic': from element_array_ephys import ephys_chronic as ephys elif ephys_mode == 'no-curation': @@ -34,6 +34,7 @@ else: raise ValueError(f'Unknown ephys mode: {ephys_mode}') + # Activate "lab", "subject", "session" schema --------------------------------- lab.activate(db_prefix + 'lab') From b8065313bbd61a66f7763007346887c30d5bdd4b Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Fri, 4 Jun 2021 13:05:57 -0500 Subject: [PATCH 18/59] Rebase ttngu207:event with cbroz1:histology add tests for NWB export accommodate different ephys modes (chronic, no-curation, etc.) Create analysis.py add trialized CSVs and ingestion functions remove trial diagrams Update service names AlignedSpikes calculation and plotting bugfix in plotting functions, add notebook 7 for analysis examples activate electrode-localization in localization.py --- docker/docker-compose-dev.yaml | 2 +- docker/docker-compose-test.yaml | 6 +-- notebooks/08-electrode-localization.ipynb | 42 +++++++++++++++---- .../py_scripts/08-electrode-localization.py | 21 ++++++---- tests/__init__.py | 1 + workflow_array_ephys/analysis.py | 6 +-- workflow_array_ephys/ingest.py | 11 ++--- workflow_array_ephys/localization.py | 15 ++++++- workflow_array_ephys/paths.py | 21 ++++++++++ workflow_array_ephys/pipeline.py | 10 ++--- workflow_array_ephys/plotting/plot_psth.py | 3 +- 11 files changed, 98 insertions(+), 40 deletions(-) diff --git a/docker/docker-compose-dev.yaml b/docker/docker-compose-dev.yaml index 77531413..1a40310b 100644 --- a/docker/docker-compose-dev.yaml +++ b/docker/docker-compose-dev.yaml @@ -6,7 +6,7 @@ x-net: &net networks: - main services: - db: + array-ephys-dev-db: <<: *net image: datajoint/mysql:5.7 container_name: workflow-array-ephys-dev-db diff --git a/docker/docker-compose-test.yaml b/docker/docker-compose-test.yaml index c8d6490d..4bb32c77 100644 --- a/docker/docker-compose-test.yaml +++ b/docker/docker-compose-test.yaml @@ -9,13 +9,13 @@ x-net: &net networks: - main services: - db: + array-ephys-test-db: <<: *net image: datajoint/mysql:5.7 container_name: workflow-array-ephys-test-db environment: - MYSQL_ROOT_PASSWORD=simple - workflow: + array-ephys-test-workflow: <<: *net build: context: ../../ @@ -48,7 +48,7 @@ services: - ../../element-array-ephys:/main/element-array-ephys - ..:/main/workflow-array-ephys depends_on: - db: + array-ephys-test-db: condition: service_healthy networks: main: diff --git a/notebooks/08-electrode-localization.ipynb b/notebooks/08-electrode-localization.ipynb index bd89bbe8..1cd9b2eb 100644 --- a/notebooks/08-electrode-localization.ipynb +++ b/notebooks/08-electrode-localization.ipynb @@ -28,14 +28,42 @@ " + \"workflow directory\")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Allen Institute hosts [brain atlases](http://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/annotation/ccf_2017) and [ontology trees](https://community.brain-map.org/t/allen-mouse-ccf-accessing-and-using-related-data-and-tools/359) that we'll use in the next section.\n", + "1. Download the `nrrd` and `csv` files that correspond too your desired resolution\n", + "2. Move these files to your ephys root directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "resolution = 100\n", + "from element_interface.utils import find_full_path\n", + "from workflow_array_ephys.paths import get_ephys_root_data_dir\n", + "nrrd_filepath = find_full_path(get_ephys_root_data_dir(), \n", + " f'annotation_{resolution}.nrrd')\n", + "ontology_csv_filepath = find_full_path(get_ephys_root_data_dir(), 'query.csv')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we'll load ccf annotation information from some of the IDs defined by the above CSV. This tutorial highlights IDs in the limbic system." + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "from workflow_array_ephys.localization import electrode_localization as eloc\n", - "from workflow_array_ephys.localization import coordinate_framework as ccf\n", "from element_electrode_localization.coordinate_framework import load_ccf_annotation" ] }, @@ -45,14 +73,10 @@ "metadata": {}, "outputs": [], "source": [ - "limbic_ids = [972] #, 171, 195, 304, 363, 84, 132, 44, 707, 747, 556, 827, 1054, 1081]\n", - "from element_interface.utils import find_full_path\n", - "from workflow_array_ephys.paths import get_ephys_root_data_dir\n", - "nrrd_filepath = find_full_path(get_ephys_root_data_dir(), 'annotation_10.nrrd')\n", - "ontology_csv_filepath = find_full_path(get_ephys_root_data_dir(), 'query.csv')\n", - "for n in limbic_ids:\n", + "ids = [972] #, 171, 195, 304, 363, 84, 132, 44, 707, 747, 556, 827, 1054, 1081]\n", + "for n in ids:\n", " load_ccf_annotation(\n", - " ccf_id=n, version_name='ccf_2017', voxel_resolution=10,\n", + " ccf_id=n, version_name='ccf_2017', voxel_resolution=resolution,\n", " nrrd_filepath=nrrd_filepath,\n", " ontology_csv_filepath=ontology_csv_filepath)" ] diff --git a/notebooks/py_scripts/08-electrode-localization.py b/notebooks/py_scripts/08-electrode-localization.py index b94eb54e..2367111a 100644 --- a/notebooks/py_scripts/08-electrode-localization.py +++ b/notebooks/py_scripts/08-electrode-localization.py @@ -24,17 +24,24 @@ assert os.path.basename(os.getcwd())=='workflow-array-ephys', ("Please move to the " + "workflow directory") -from workflow_array_ephys.localization import electrode_localization as eloc -from workflow_array_ephys.localization import coordinate_framework as ccf -from element_electrode_localization.coordinate_framework import load_ccf_annotation +# The Allen Institute hosts [brain atlases](http://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/annotation/ccf_2017) and [ontology trees](https://community.brain-map.org/t/allen-mouse-ccf-accessing-and-using-related-data-and-tools/359) that we'll use in the next section. +# 1. Download the `nrrd` and `csv` files that correspond too your desired resolution +# 2. Move these files to your ephys root directory. -limbic_ids = [972] #, 171, 195, 304, 363, 84, 132, 44, 707, 747, 556, 827, 1054, 1081] +resolution = 100 from element_interface.utils import find_full_path from workflow_array_ephys.paths import get_ephys_root_data_dir -nrrd_filepath = find_full_path(get_ephys_root_data_dir(), 'annotation_10.nrrd') +nrrd_filepath = find_full_path(get_ephys_root_data_dir(), + f'annotation_{resolution}.nrrd') ontology_csv_filepath = find_full_path(get_ephys_root_data_dir(), 'query.csv') -for n in limbic_ids: + +# Next, we'll load ccf annotation information from some of the IDs defined by the above CSV. This tutorial highlights IDs in the limbic system. + +from element_electrode_localization.coordinate_framework import load_ccf_annotation + +ids = [972] #, 171, 195, 304, 363, 84, 132, 44, 707, 747, 556, 827, 1054, 1081] +for n in ids: load_ccf_annotation( - ccf_id=n, version_name='ccf_2017', voxel_resolution=10, + ccf_id=n, version_name='ccf_2017', voxel_resolution=resolution, nrrd_filepath=nrrd_filepath, ontology_csv_filepath=ontology_csv_filepath) diff --git a/tests/__init__.py b/tests/__init__.py index 0b8feb5f..a3501ecb 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -286,6 +286,7 @@ def ephys_recordings(pipeline, ingest_sessions): with QuietStdOut(): ephys.EphysRecording.delete() + @pytest.fixture def clustering_tasks(pipeline, kilosort_paramset, ephys_recordings): """Insert keys from ephys.EphysRecording into ephys.Clustering""" diff --git a/workflow_array_ephys/analysis.py b/workflow_array_ephys/analysis.py index 3ac1cf59..1212ee29 100644 --- a/workflow_array_ephys/analysis.py +++ b/workflow_array_ephys/analysis.py @@ -1,7 +1,7 @@ import datajoint as dj import numpy as np -from workflow_array_ephys.pipeline import db_prefix, session, ephys, trial, event +from .pipeline import db_prefix, ephys, trial, event schema = dj.schema(db_prefix + 'analysis') @@ -19,7 +19,7 @@ class SpikesAlignmentCondition(dj.Manual): """ class Trial(dj.Part): - definition = """ # Trials (or subset of trials) to computed event-aligned spikes and PSTH on + definition = """ # Trials on which to compute event-aligned spikes and PSTH -> master -> trial.Trial """ @@ -38,7 +38,7 @@ class AlignedTrialSpikes(dj.Part): -> ephys.CuratedClustering.Unit -> SpikesAlignmentCondition.Trial --- - aligned_spike_times: longblob # (s) spike times relative to alignment event time + aligned_spike_times: longblob # (s) spike times relative to alignment event time """ class UnitPSTH(dj.Part): diff --git a/workflow_array_ephys/ingest.py b/workflow_array_ephys/ingest.py index b40d5a28..c203cab2 100644 --- a/workflow_array_ephys/ingest.py +++ b/workflow_array_ephys/ingest.py @@ -124,14 +124,9 @@ def ingest_sessions(session_csv_path='./user_data/sessions.csv', verbose=True, probe_insertion_list.extend([{**session_key, **insertion } for insertion in insertions]) - session.Session.insert(session_list) - session.SessionDirectory.insert(session_dir_list) - if verbose: - print(f'\n---- Insert {len(session_list)} entry(s) into session.Session ----') - - probe.Probe.insert(probe_list) if verbose: print(f'\n---- Insert {len(probe_list)} entry(s) into probe.Probe ----') + probe.Probe.insert(probe_list) if ephys_mode == 'chronic': ephys.ProbeInsertion.insert(probe_insertion_list, @@ -152,8 +147,8 @@ def ingest_sessions(session_csv_path='./user_data/sessions.csv', verbose=True, + 'ephys.ProbeInsertion ----') print(f'\n---- Insert {len(session_list)} entry(s) into ' + 'session.Session ----') - - print('\n---- Successfully completed workflow_array_ephys/ingest.py ----') + if verbose: + print('\n---- Successfully completed workflow_array_ephys/ingest.py ----') def ingest_events(recording_csv_path='./user_data/behavior_recordings.csv', diff --git a/workflow_array_ephys/localization.py b/workflow_array_ephys/localization.py index 1824fd55..ab2d6337 100644 --- a/workflow_array_ephys/localization.py +++ b/workflow_array_ephys/localization.py @@ -1,6 +1,8 @@ import datajoint as dj from element_electrode_localization import coordinate_framework, electrode_localization from .pipeline import ephys, probe +from .paths import get_ephys_root_data_dir, get_session_directory, \ + get_electrode_localization_dir if 'custom' not in dj.config: @@ -9,9 +11,20 @@ db_prefix = dj.config['custom'].get('database.prefix', '') __all__ = ['ephys', 'probe', 'coordinate_framework', 'electrode_localization', - 'ProbeInsertion'] + 'ProbeInsertion', + 'get_ephys_root_data_dir', 'get_session_directory', + 'get_electrode_localization_dir'] ProbeInsertion = ephys.ProbeInsertion electrode_localization.activate(db_prefix + 'eloc', db_prefix + 'ccf', linking_module=__name__) + +# Activate "electrode-localization" schema ------------------------------------ + +ProbeInsertion = ephys.ProbeInsertion +Electrode = probe.ProbeType.Electrode + +electrode_localization.activate(db_prefix + 'electrode_localization', + db_prefix + 'ccf', + linking_module=__name__) diff --git a/workflow_array_ephys/paths.py b/workflow_array_ephys/paths.py index 2c775287..fec1f38b 100644 --- a/workflow_array_ephys/paths.py +++ b/workflow_array_ephys/paths.py @@ -1,4 +1,6 @@ import datajoint as dj +import pathlib +from element_interface.utils import find_full_path def get_ephys_root_data_dir(): @@ -9,3 +11,22 @@ def get_session_directory(session_key: dict) -> str: from .pipeline import session session_dir = (session.SessionDirectory & session_key).fetch1('session_dir') return session_dir + + +def get_electrode_localization_dir(probe_insertion_key: dict) -> str: + from .pipeline import ephys + acq_software = (ephys.EphysRecording & probe_insertion_key).fetch1('acq_software') + + if acq_software == 'SpikeGLX': + spikeglx_meta_filepath = pathlib.Path((ephys.EphysRecording.EphysFile + & probe_insertion_key + & 'file_path LIKE "%.ap.meta"' + ).fetch1('file_path')) + probe_dir = find_full_path(get_ephys_root_data_dir(), + spikeglx_meta_filepath.parent) + elif acq_software == 'Open Ephys': + probe_path = (ephys.EphysRecording.EphysFile & probe_insertion_key + ).fetch1('file_path') + probe_dir = find_full_path(get_ephys_root_data_dir(), probe_path) + + return probe_dir diff --git a/workflow_array_ephys/pipeline.py b/workflow_array_ephys/pipeline.py index f2c76173..3910e07e 100644 --- a/workflow_array_ephys/pipeline.py +++ b/workflow_array_ephys/pipeline.py @@ -10,17 +10,13 @@ from element_lab.lab import Source, Lab, Protocol, User, Project from element_session.session_with_datetime import Session -from .paths import get_ephys_root_data_dir, get_session_directory - if 'custom' not in dj.config: dj.config['custom'] = {} db_prefix = dj.config['custom'].get('database.prefix', '') __all__ = ['subject', 'lab', 'session', 'trial', 'event', 'probe', 'ephys', - 'Subject', 'Source', 'Lab', 'Protocol', 'User', 'Project', 'Session', - 'get_ephys_root_data_dir', 'get_session_directory'] - + 'Subject', 'Source', 'Lab', 'Protocol', 'User', 'Project', 'Session'] # ------------- Import the configured "ephys mode" ------------- ephys_mode = os.getenv('EPHYS_MODE', @@ -59,6 +55,6 @@ class SkullReference(dj.Lookup): # Activate "ephys" schema ----------------------------------------------------- -ephys.activate(db_prefix + 'ephys', - db_prefix + 'probe', +ephys.activate(db_prefix + 'ephys', + db_prefix + 'probe', linking_module=__name__) diff --git a/workflow_array_ephys/plotting/plot_psth.py b/workflow_array_ephys/plotting/plot_psth.py index 50c1a3b9..b73faea3 100644 --- a/workflow_array_ephys/plotting/plot_psth.py +++ b/workflow_array_ephys/plotting/plot_psth.py @@ -2,7 +2,8 @@ import matplotlib.pyplot as plt -def _plot_spike_raster(aligned_spikes, trial_ids=None, vlines=[0], ax=None, title='', xlim=None): +def _plot_spike_raster(aligned_spikes, trial_ids=None, vlines=[0], ax=None, title='', + xlim=None): if not ax: fig, ax = plt.subplots(1, 1) From ddb6b5653ccc6324c78f58f772832300866a1a8b Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 24 Mar 2022 16:14:43 -0500 Subject: [PATCH 19/59] Localization NB, DummyTable for altering database case sensitivity --- 0217_ccf/db.opt | 0 notebooks/08-electrode-localization.ipynb | 66 ++++++++++++++++--- .../py_scripts/08-electrode-localization.py | 20 ++++-- workflow_array_ephys/localization.py | 29 ++++++-- 4 files changed, 92 insertions(+), 23 deletions(-) create mode 100644 0217_ccf/db.opt diff --git a/0217_ccf/db.opt b/0217_ccf/db.opt new file mode 100644 index 00000000..e69de29b diff --git a/notebooks/08-electrode-localization.ipynb b/notebooks/08-electrode-localization.ipynb index 1cd9b2eb..1077eeb9 100644 --- a/notebooks/08-electrode-localization.ipynb +++ b/notebooks/08-electrode-localization.ipynb @@ -34,7 +34,9 @@ "source": [ "The Allen Institute hosts [brain atlases](http://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/annotation/ccf_2017) and [ontology trees](https://community.brain-map.org/t/allen-mouse-ccf-accessing-and-using-related-data-and-tools/359) that we'll use in the next section.\n", "1. Download the `nrrd` and `csv` files that correspond too your desired resolution\n", - "2. Move these files to your ephys root directory." + "2. Move these files to your ephys root directory.\n", + "\n", + "Note that the higher resolution (i.e., lower number) files are quite large when loaded. Depending on the python environment, the terminal may be killed when loading so much information into memory." ] }, { @@ -55,7 +57,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Next, we'll load ccf annotation information from some of the IDs defined by the above CSV. This tutorial highlights IDs in the limbic system." + "Next, we'll load annotation information from the files above to generate a set of lookup tables for the entire brain volume." ] }, { @@ -64,21 +66,67 @@ "metadata": {}, "outputs": [], "source": [ - "from element_electrode_localization.coordinate_framework import load_ccf_annotation" + "from workflow_array_ephys.localization import coordinate_framework as ccf\n", + "from element_electrode_localization.coordinate_framework import load_ccf_annotation\n", + "load_ccf_annotation(\n", + " ccf_id=n, version_name='ccf_2017', voxel_resolution=resolution,\n", + " nrrd_filepath=nrrd_filepath,\n", + " ontology_csv_filepath=ontology_csv_filepath)" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting cbroz@tutorial-db.datajoint.io:3306\n" + ] + }, + { + "ename": "OperationalError", + "evalue": "(3780, \"Referencing column 'subject' and referenced column 'subject' in foreign key constraint '_electrode_position_ibfk_1' are incompatible.\")", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mOperationalError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_25332/1683904897.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0mworkflow_array_ephys\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlocalization\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mcoordinate_framework\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mccf\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mccf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mBrainRegionAnnotation\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/Volumes/GoogleDrive/My Drive/NWB/workflow-array-ephys/workflow_array_ephys/localization.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 41\u001b[0m \u001b[0mProbeInsertion\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mephys\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mProbeInsertion\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 42\u001b[0m \u001b[0mElectrode\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mprobe\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mProbeType\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mElectrode\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 43\u001b[0;31m electrode_localization.activate(db_prefix + 'eloc',\n\u001b[0m\u001b[1;32m 44\u001b[0m \u001b[0mdb_prefix\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m'ccf'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 45\u001b[0m linking_module=__name__)\n", + "\u001b[0;32m/Volumes/GoogleDrive/My Drive/NWB/element-electrode-localization/element_electrode_localization/electrode_localization.py\u001b[0m in \u001b[0;36mactivate\u001b[0;34m(electrode_localization_schema_name, coordinate_framework_schema_name, create_schema, create_tables, linking_module)\u001b[0m\n\u001b[1;32m 53\u001b[0m \u001b[0mcreate_schema\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcreate_schema\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 54\u001b[0m create_tables=create_tables)\n\u001b[0;32m---> 55\u001b[0;31m schema.activate(electrode_localization_schema_name, create_schema=create_schema,\n\u001b[0m\u001b[1;32m 56\u001b[0m create_tables=create_tables, add_objects=_linking_module.__dict__)\n\u001b[1;32m 57\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/schemas.py\u001b[0m in \u001b[0;36mactivate\u001b[0;34m(self, schema_name, connection, create_schema, create_tables, add_objects)\u001b[0m\n\u001b[1;32m 128\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0madd_objects\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 129\u001b[0m \u001b[0mcontext\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcontext\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0madd_objects\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 130\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_decorate_master\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcontext\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 131\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 132\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_assert_exists\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmessage\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/schemas.py\u001b[0m in \u001b[0;36m_decorate_master\u001b[0;34m(self, cls, context)\u001b[0m\n\u001b[1;32m 155\u001b[0m \u001b[0;34m:\u001b[0m\u001b[0mparam\u001b[0m \u001b[0mcontext\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mthe\u001b[0m \u001b[0;32mclass\u001b[0m\u001b[0;31m'\u001b[0m \u001b[0mdeclaration\u001b[0m \u001b[0mcontext\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 156\u001b[0m \"\"\"\n\u001b[0;32m--> 157\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_decorate_table\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcontext\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcontext\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0;34m{\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__name__\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mcls\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 158\u001b[0m \u001b[0;31m# Process part tables\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 159\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mpart\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mordered_dir\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/schemas.py\u001b[0m in \u001b[0;36m_decorate_table\u001b[0;34m(self, table_class, context, assert_declared)\u001b[0m\n\u001b[1;32m 186\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreate_tables\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0massert_declared\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 187\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mDataJointError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Table `%s` not declared'\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0minstance\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtable_name\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 188\u001b[0;31m \u001b[0minstance\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdeclare\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcontext\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 189\u001b[0m \u001b[0mis_declared\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mis_declared\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0minstance\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_declared\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 190\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/table.py\u001b[0m in \u001b[0;36mdeclare\u001b[0;34m(self, context)\u001b[0m\n\u001b[1;32m 83\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mstore\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mexternal_stores\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 84\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconnection\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mschemas\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdatabase\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexternal\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstore\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 85\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconnection\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mquery\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msql\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 86\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mAccessError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 87\u001b[0m \u001b[0;31m# skip if no create privilege\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/connection.py\u001b[0m in \u001b[0;36mquery\u001b[0;34m(self, query, args, as_dict, suppress_warnings, reconnect)\u001b[0m\n\u001b[1;32m 298\u001b[0m \u001b[0mcursor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_conn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcursor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcursor\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcursor_class\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 299\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 300\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_execute_query\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcursor\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mquery\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msuppress_warnings\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 301\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0merrors\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mLostConnectionError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 302\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mreconnect\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/connection.py\u001b[0m in \u001b[0;36m_execute_query\u001b[0;34m(cursor, query, args, suppress_warnings)\u001b[0m\n\u001b[1;32m 264\u001b[0m \u001b[0mcursor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexecute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mquery\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 265\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mclient\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0merr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 266\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mtranslate_query_error\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0merr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mquery\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 267\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 268\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mquery\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mquery\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mas_dict\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msuppress_warnings\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreconnect\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/connection.py\u001b[0m in \u001b[0;36m_execute_query\u001b[0;34m(cursor, query, args, suppress_warnings)\u001b[0m\n\u001b[1;32m 262\u001b[0m \u001b[0;31m# suppress all warnings arising from underlying SQL library\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 263\u001b[0m \u001b[0mwarnings\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msimplefilter\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"ignore\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 264\u001b[0;31m \u001b[0mcursor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexecute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mquery\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 265\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mclient\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0merr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 266\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mtranslate_query_error\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0merr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mquery\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pymysql/cursors.py\u001b[0m in \u001b[0;36mexecute\u001b[0;34m(self, query, args)\u001b[0m\n\u001b[1;32m 146\u001b[0m \u001b[0mquery\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmogrify\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mquery\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 147\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 148\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_query\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mquery\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 149\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_executed\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mquery\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 150\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pymysql/cursors.py\u001b[0m in \u001b[0;36m_query\u001b[0;34m(self, q)\u001b[0m\n\u001b[1;32m 308\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_last_executed\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mq\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 309\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_clear_result\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 310\u001b[0;31m \u001b[0mconn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mquery\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mq\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 311\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_do_get_result\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 312\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrowcount\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pymysql/connections.py\u001b[0m in \u001b[0;36mquery\u001b[0;34m(self, sql, unbuffered)\u001b[0m\n\u001b[1;32m 546\u001b[0m \u001b[0msql\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msql\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mencode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mencoding\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"surrogateescape\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 547\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_execute_command\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mCOMMAND\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCOM_QUERY\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msql\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 548\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_affected_rows\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_read_query_result\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0munbuffered\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0munbuffered\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 549\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_affected_rows\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 550\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pymysql/connections.py\u001b[0m in \u001b[0;36m_read_query_result\u001b[0;34m(self, unbuffered)\u001b[0m\n\u001b[1;32m 773\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 774\u001b[0m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mMySQLResult\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 775\u001b[0;31m \u001b[0mresult\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 776\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_result\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 777\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mserver_status\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pymysql/connections.py\u001b[0m in \u001b[0;36mread\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 1154\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mread\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1155\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1156\u001b[0;31m \u001b[0mfirst_packet\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconnection\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_read_packet\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1157\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1158\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mfirst_packet\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_ok_packet\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pymysql/connections.py\u001b[0m in \u001b[0;36m_read_packet\u001b[0;34m(self, packet_type)\u001b[0m\n\u001b[1;32m 723\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_result\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_result\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0munbuffered_active\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 724\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_result\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0munbuffered_active\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 725\u001b[0;31m \u001b[0mpacket\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mraise_for_error\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 726\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mpacket\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 727\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pymysql/protocol.py\u001b[0m in \u001b[0;36mraise_for_error\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 219\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mDEBUG\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 220\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"errno =\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0merrno\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 221\u001b[0;31m \u001b[0merr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mraise_mysql_exception\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_data\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 222\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 223\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mdump\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pymysql/err.py\u001b[0m in \u001b[0;36mraise_mysql_exception\u001b[0;34m(data)\u001b[0m\n\u001b[1;32m 141\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0merrorclass\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 142\u001b[0m \u001b[0merrorclass\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mInternalError\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0merrno\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0;36m1000\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0mOperationalError\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 143\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0merrorclass\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0merrno\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0merrval\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mOperationalError\u001b[0m: (3780, \"Referencing column 'subject' and referenced column 'subject' in foreign key constraint '_electrode_position_ibfk_1' are incompatible.\")" + ] + } + ], + "source": [ + "from workflow_array_ephys.localization import coordinate_framework as ccf\n", + "ccf.BrainRegionAnnotation()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ - "ids = [972] #, 171, 195, 304, 363, 84, 132, 44, 707, 747, 556, 827, 1054, 1081]\n", - "for n in ids:\n", - " load_ccf_annotation(\n", - " ccf_id=n, version_name='ccf_2017', voxel_resolution=resolution,\n", - " nrrd_filepath=nrrd_filepath,\n", - " ontology_csv_filepath=ontology_csv_filepath)" + "ccf.BrainRe" ] } ], diff --git a/notebooks/py_scripts/08-electrode-localization.py b/notebooks/py_scripts/08-electrode-localization.py index 2367111a..4bbb8d1d 100644 --- a/notebooks/py_scripts/08-electrode-localization.py +++ b/notebooks/py_scripts/08-electrode-localization.py @@ -27,6 +27,8 @@ # The Allen Institute hosts [brain atlases](http://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/annotation/ccf_2017) and [ontology trees](https://community.brain-map.org/t/allen-mouse-ccf-accessing-and-using-related-data-and-tools/359) that we'll use in the next section. # 1. Download the `nrrd` and `csv` files that correspond too your desired resolution # 2. Move these files to your ephys root directory. +# +# Note that the higher resolution (i.e., lower number) files are quite large when loaded. Depending on the python environment, the terminal may be killed when loading so much information into memory. resolution = 100 from element_interface.utils import find_full_path @@ -35,13 +37,17 @@ f'annotation_{resolution}.nrrd') ontology_csv_filepath = find_full_path(get_ephys_root_data_dir(), 'query.csv') -# Next, we'll load ccf annotation information from some of the IDs defined by the above CSV. This tutorial highlights IDs in the limbic system. +# Next, we'll load annotation information from the files above to generate a set of lookup tables for the entire brain volume. +from workflow_array_ephys.localization import coordinate_framework as ccf from element_electrode_localization.coordinate_framework import load_ccf_annotation +load_ccf_annotation( + ccf_id=n, version_name='ccf_2017', voxel_resolution=resolution, + nrrd_filepath=nrrd_filepath, + ontology_csv_filepath=ontology_csv_filepath) -ids = [972] #, 171, 195, 304, 363, 84, 132, 44, 707, 747, 556, 827, 1054, 1081] -for n in ids: - load_ccf_annotation( - ccf_id=n, version_name='ccf_2017', voxel_resolution=resolution, - nrrd_filepath=nrrd_filepath, - ontology_csv_filepath=ontology_csv_filepath) +from workflow_array_ephys.localization import coordinate_framework as ccf +ccf.BrainRegionAnnotation() + + +ccf.BrainRe diff --git a/workflow_array_ephys/localization.py b/workflow_array_ephys/localization.py index ab2d6337..725f437c 100644 --- a/workflow_array_ephys/localization.py +++ b/workflow_array_ephys/localization.py @@ -1,5 +1,7 @@ import datajoint as dj from element_electrode_localization import coordinate_framework, electrode_localization +from element_electrode_localization.coordinate_framework import load_ccf_annotation + from .pipeline import ephys, probe from .paths import get_ephys_root_data_dir, get_session_directory, \ get_electrode_localization_dir @@ -13,18 +15,31 @@ __all__ = ['ephys', 'probe', 'coordinate_framework', 'electrode_localization', 'ProbeInsertion', 'get_ephys_root_data_dir', 'get_session_directory', - 'get_electrode_localization_dir'] + 'get_electrode_localization_dir', 'load_ccf_annotation'] + + +# Dummy table for case sensitivity in MySQL------------------------------------ + +coordinate_framework_schema = dj.schema(db_prefix + 'ccf') + +@coordinate_framework_schema +class DummyTable(dj.Manual): + definition = """ + id : varchar(1) + """ + contents = zip(['1', '2']) + + +ccf_schema_name = db_prefix + 'ccf' +dj.conn().query(f'ALTER DATABASE `{ccf_schema_name}` CHARACTER SET utf8 COLLATE ' + + 'utf8_bin;') + -ProbeInsertion = ephys.ProbeInsertion -electrode_localization.activate(db_prefix + 'eloc', - db_prefix + 'ccf', - linking_module=__name__) # Activate "electrode-localization" schema ------------------------------------ ProbeInsertion = ephys.ProbeInsertion Electrode = probe.ProbeType.Electrode - -electrode_localization.activate(db_prefix + 'electrode_localization', +electrode_localization.activate(db_prefix + 'eloc', db_prefix + 'ccf', linking_module=__name__) From 8d8272f4fee25bdddc7ccd217993a77c63a07d7d Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 24 Mar 2022 16:26:46 -0500 Subject: [PATCH 20/59] notebook update --- 0217_ccf/db.opt | 0 notebooks/08-electrode-localization.ipynb | 80 +++++++++---------- .../py_scripts/08-electrode-localization.py | 15 +++- 3 files changed, 49 insertions(+), 46 deletions(-) delete mode 100644 0217_ccf/db.opt diff --git a/0217_ccf/db.opt b/0217_ccf/db.opt deleted file mode 100644 index e69de29b..00000000 diff --git a/notebooks/08-electrode-localization.ipynb b/notebooks/08-electrode-localization.ipynb index 1077eeb9..e5dfd19b 100644 --- a/notebooks/08-electrode-localization.ipynb +++ b/notebooks/08-electrode-localization.ipynb @@ -69,55 +69,51 @@ "from workflow_array_ephys.localization import coordinate_framework as ccf\n", "from element_electrode_localization.coordinate_framework import load_ccf_annotation\n", "load_ccf_annotation(\n", - " ccf_id=n, version_name='ccf_2017', voxel_resolution=resolution,\n", + " ccf_id=0, version_name='ccf_2017', voxel_resolution=resolution,\n", " nrrd_filepath=nrrd_filepath,\n", " ontology_csv_filepath=ontology_csv_filepath)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, to explore the data we just loaded." + ] + }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Connecting cbroz@tutorial-db.datajoint.io:3306\n" - ] - }, - { - "ename": "OperationalError", - "evalue": "(3780, \"Referencing column 'subject' and referenced column 'subject' in foreign key constraint '_electrode_position_ibfk_1' are incompatible.\")", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mOperationalError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_25332/1683904897.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0mworkflow_array_ephys\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlocalization\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mcoordinate_framework\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mccf\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mccf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mBrainRegionAnnotation\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/Volumes/GoogleDrive/My Drive/NWB/workflow-array-ephys/workflow_array_ephys/localization.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 41\u001b[0m \u001b[0mProbeInsertion\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mephys\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mProbeInsertion\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 42\u001b[0m \u001b[0mElectrode\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mprobe\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mProbeType\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mElectrode\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 43\u001b[0;31m electrode_localization.activate(db_prefix + 'eloc',\n\u001b[0m\u001b[1;32m 44\u001b[0m \u001b[0mdb_prefix\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m'ccf'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 45\u001b[0m linking_module=__name__)\n", - "\u001b[0;32m/Volumes/GoogleDrive/My Drive/NWB/element-electrode-localization/element_electrode_localization/electrode_localization.py\u001b[0m in \u001b[0;36mactivate\u001b[0;34m(electrode_localization_schema_name, coordinate_framework_schema_name, create_schema, create_tables, linking_module)\u001b[0m\n\u001b[1;32m 53\u001b[0m \u001b[0mcreate_schema\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcreate_schema\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 54\u001b[0m create_tables=create_tables)\n\u001b[0;32m---> 55\u001b[0;31m schema.activate(electrode_localization_schema_name, create_schema=create_schema,\n\u001b[0m\u001b[1;32m 56\u001b[0m create_tables=create_tables, add_objects=_linking_module.__dict__)\n\u001b[1;32m 57\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/schemas.py\u001b[0m in \u001b[0;36mactivate\u001b[0;34m(self, schema_name, connection, create_schema, create_tables, add_objects)\u001b[0m\n\u001b[1;32m 128\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0madd_objects\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 129\u001b[0m \u001b[0mcontext\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcontext\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0madd_objects\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 130\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_decorate_master\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcontext\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 131\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 132\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_assert_exists\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmessage\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/schemas.py\u001b[0m in \u001b[0;36m_decorate_master\u001b[0;34m(self, cls, context)\u001b[0m\n\u001b[1;32m 155\u001b[0m \u001b[0;34m:\u001b[0m\u001b[0mparam\u001b[0m \u001b[0mcontext\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mthe\u001b[0m \u001b[0;32mclass\u001b[0m\u001b[0;31m'\u001b[0m \u001b[0mdeclaration\u001b[0m \u001b[0mcontext\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 156\u001b[0m \"\"\"\n\u001b[0;32m--> 157\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_decorate_table\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcontext\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcontext\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0;34m{\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__name__\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mcls\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 158\u001b[0m \u001b[0;31m# Process part tables\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 159\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mpart\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mordered_dir\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/schemas.py\u001b[0m in \u001b[0;36m_decorate_table\u001b[0;34m(self, table_class, context, assert_declared)\u001b[0m\n\u001b[1;32m 186\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreate_tables\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0massert_declared\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 187\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mDataJointError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Table `%s` not declared'\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0minstance\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtable_name\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 188\u001b[0;31m \u001b[0minstance\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdeclare\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcontext\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 189\u001b[0m \u001b[0mis_declared\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mis_declared\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0minstance\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_declared\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 190\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/table.py\u001b[0m in \u001b[0;36mdeclare\u001b[0;34m(self, context)\u001b[0m\n\u001b[1;32m 83\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mstore\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mexternal_stores\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 84\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconnection\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mschemas\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdatabase\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexternal\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstore\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 85\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconnection\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mquery\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msql\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 86\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mAccessError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 87\u001b[0m \u001b[0;31m# skip if no create privilege\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/connection.py\u001b[0m in \u001b[0;36mquery\u001b[0;34m(self, query, args, as_dict, suppress_warnings, reconnect)\u001b[0m\n\u001b[1;32m 298\u001b[0m \u001b[0mcursor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_conn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcursor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcursor\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcursor_class\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 299\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 300\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_execute_query\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcursor\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mquery\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msuppress_warnings\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 301\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0merrors\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mLostConnectionError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 302\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mreconnect\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/connection.py\u001b[0m in \u001b[0;36m_execute_query\u001b[0;34m(cursor, query, args, suppress_warnings)\u001b[0m\n\u001b[1;32m 264\u001b[0m \u001b[0mcursor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexecute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mquery\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 265\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mclient\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0merr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 266\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mtranslate_query_error\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0merr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mquery\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 267\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 268\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mquery\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mquery\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mas_dict\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msuppress_warnings\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreconnect\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/connection.py\u001b[0m in \u001b[0;36m_execute_query\u001b[0;34m(cursor, query, args, suppress_warnings)\u001b[0m\n\u001b[1;32m 262\u001b[0m \u001b[0;31m# suppress all warnings arising from underlying SQL library\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 263\u001b[0m \u001b[0mwarnings\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msimplefilter\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"ignore\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 264\u001b[0;31m \u001b[0mcursor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexecute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mquery\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 265\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mclient\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0merr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 266\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mtranslate_query_error\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0merr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mquery\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pymysql/cursors.py\u001b[0m in \u001b[0;36mexecute\u001b[0;34m(self, query, args)\u001b[0m\n\u001b[1;32m 146\u001b[0m \u001b[0mquery\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmogrify\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mquery\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 147\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 148\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_query\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mquery\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 149\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_executed\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mquery\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 150\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pymysql/cursors.py\u001b[0m in \u001b[0;36m_query\u001b[0;34m(self, q)\u001b[0m\n\u001b[1;32m 308\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_last_executed\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mq\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 309\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_clear_result\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 310\u001b[0;31m \u001b[0mconn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mquery\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mq\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 311\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_do_get_result\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 312\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrowcount\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pymysql/connections.py\u001b[0m in \u001b[0;36mquery\u001b[0;34m(self, sql, unbuffered)\u001b[0m\n\u001b[1;32m 546\u001b[0m \u001b[0msql\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msql\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mencode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mencoding\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"surrogateescape\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 547\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_execute_command\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mCOMMAND\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mCOM_QUERY\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msql\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 548\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_affected_rows\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_read_query_result\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0munbuffered\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0munbuffered\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 549\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_affected_rows\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 550\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pymysql/connections.py\u001b[0m in \u001b[0;36m_read_query_result\u001b[0;34m(self, unbuffered)\u001b[0m\n\u001b[1;32m 773\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 774\u001b[0m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mMySQLResult\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 775\u001b[0;31m \u001b[0mresult\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 776\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_result\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 777\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mserver_status\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pymysql/connections.py\u001b[0m in \u001b[0;36mread\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 1154\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mread\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1155\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1156\u001b[0;31m \u001b[0mfirst_packet\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconnection\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_read_packet\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1157\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1158\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mfirst_packet\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_ok_packet\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pymysql/connections.py\u001b[0m in \u001b[0;36m_read_packet\u001b[0;34m(self, packet_type)\u001b[0m\n\u001b[1;32m 723\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_result\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_result\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0munbuffered_active\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 724\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_result\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0munbuffered_active\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 725\u001b[0;31m \u001b[0mpacket\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mraise_for_error\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 726\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mpacket\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 727\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pymysql/protocol.py\u001b[0m in \u001b[0;36mraise_for_error\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 219\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mDEBUG\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 220\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"errno =\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0merrno\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 221\u001b[0;31m \u001b[0merr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mraise_mysql_exception\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_data\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 222\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 223\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mdump\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pymysql/err.py\u001b[0m in \u001b[0;36mraise_mysql_exception\u001b[0;34m(data)\u001b[0m\n\u001b[1;32m 141\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0merrorclass\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 142\u001b[0m \u001b[0merrorclass\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mInternalError\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0merrno\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0;36m1000\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0mOperationalError\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 143\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0merrorclass\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0merrno\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0merrval\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;31mOperationalError\u001b[0m: (3780, \"Referencing column 'subject' and referenced column 'subject' in foreign key constraint '_electrode_position_ibfk_1' are incompatible.\")" - ] - } - ], + "outputs": [], "source": [ - "from workflow_array_ephys.localization import coordinate_framework as ccf\n", - "ccf.BrainRegionAnnotation()\n" + "ccf.BrainRegionAnnotation.BrainRegion()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ccf.BrainRegionAnnotation.Voxel()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you have `channel_location` json files for your data, you can look at the position and regions associated with each electrode." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from workflow_array_ephys.localization import electrode_localization as eloc\n", + "eloc.ElectrodePosition.populate()" ] }, { @@ -126,7 +122,7 @@ "metadata": {}, "outputs": [], "source": [ - "ccf.BrainRe" + "eloc.ElectrodePosition.Electrode()" ] } ], diff --git a/notebooks/py_scripts/08-electrode-localization.py b/notebooks/py_scripts/08-electrode-localization.py index 4bbb8d1d..69a7d005 100644 --- a/notebooks/py_scripts/08-electrode-localization.py +++ b/notebooks/py_scripts/08-electrode-localization.py @@ -42,12 +42,19 @@ from workflow_array_ephys.localization import coordinate_framework as ccf from element_electrode_localization.coordinate_framework import load_ccf_annotation load_ccf_annotation( - ccf_id=n, version_name='ccf_2017', voxel_resolution=resolution, + ccf_id=0, version_name='ccf_2017', voxel_resolution=resolution, nrrd_filepath=nrrd_filepath, ontology_csv_filepath=ontology_csv_filepath) -from workflow_array_ephys.localization import coordinate_framework as ccf -ccf.BrainRegionAnnotation() +# Now, to explore the data we just loaded. + +ccf.BrainRegionAnnotation.BrainRegion() + +ccf.BrainRegionAnnotation.Voxel() + +# If you have `channel_location` json files for your data, you can look at the position and regions associated with each electrode. +from workflow_array_ephys.localization import electrode_localization as eloc +eloc.ElectrodePosition.populate() -ccf.BrainRe +eloc.ElectrodePosition.Electrode() From 5338c9ceec73ddc31055d471c33060e88e10eab0 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 24 Mar 2022 16:43:57 -0500 Subject: [PATCH 21/59] improve trialized event-aligned unit spikes calculation --- requirements.txt | 1 + workflow_array_ephys/analysis.py | 56 +++++++------------------------- workflow_array_ephys/pipeline.py | 19 +++++++---- 3 files changed, 25 insertions(+), 51 deletions(-) diff --git a/requirements.txt b/requirements.txt index f5a04193..3820b94f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,6 @@ element-array-ephys==0.1.0b0 element-lab>=0.1.0b0 element-animal==0.1.0b0 element-session==0.1.0b0 +element-event @ git+https://github.com/datajoint/element-event.git element-interface @ git+https://github.com/datajoint/element-interface.git ipykernel==6.0.1 diff --git a/workflow_array_ephys/analysis.py b/workflow_array_ephys/analysis.py index 3d047a7f..718d31b3 100644 --- a/workflow_array_ephys/analysis.py +++ b/workflow_array_ephys/analysis.py @@ -29,7 +29,6 @@ class Trial(dj.Part): class SpikesAlignment(dj.Computed): definition = """ -> SpikesAlignmentCondition - -> ephys.CuratedClustering """ class AlignedTrialSpikes(dj.Part): @@ -52,59 +51,26 @@ class UnitPSTH(dj.Part): def make(self, key): unit_keys, unit_spike_times = (ephys.CuratedClustering.Unit & key).fetch('KEY', 'spike_times', order_by='unit') - - trial_keys, trial_starts, trial_ends = (trial.Trial & (SpikesAlignmentCondition.Trial & key)).fetch( - 'KEY', 'trial_start_time', 'trial_stop_time', order_by='trial_id') - bin_size = (SpikesAlignmentCondition & key).fetch1('bin_size') - alignment_spec = (event.AlignmentEvent & key).fetch1() + trialized_event_times = trial.get_trialized_alignment_event_times( + key, trial.Trial & (SpikesAlignmentCondition.Trial & key)) + + min_limit = (trialized_event_times.event - trialized_event_times.start).max() + max_limit = (trialized_event_times.end - trialized_event_times.event).max() # Spike raster aligned_trial_spikes = [] units_spike_raster = {u['unit']: {**key, **u, 'aligned_spikes': []} for u in unit_keys} - min_limit, max_limit = np.Inf, -np.Inf - for trial_key, trial_start, trial_stop in zip(trial_keys, trial_starts, trial_ends): - alignment_event_time = (event.Event & key & {'event_type': alignment_spec['alignment_event_type']} - & f'event_start_time BETWEEN {trial_start} AND {trial_stop}') - if alignment_event_time: - # if there are multiple of such alignment event, pick the last one in the trial - alignment_event_time = alignment_event_time.fetch( - 'event_start_time', order_by='event_start_time DESC', limit=1)[0] - else: + for _, r in trialized_event_times.iterrows(): + if np.isnan(r.event): continue - - alignment_start_time = (event.Event & key & {'event_type': alignment_spec['start_event_type']} - & f'event_start_time < {alignment_event_time}') - if alignment_start_time: - # if there are multiple of such start event, pick the most immediate one prior to the alignment event - alignment_start_time = alignment_start_time.fetch( - 'event_start_time', order_by='event_start_time DESC', limit=1)[0] - alignment_start_time = max(alignment_start_time, trial_start) - else: - alignment_start_time = trial_start - - alignment_end_time = (event.Event & key & {'event_type': alignment_spec['end_event_type']} - & f'event_start_time > {alignment_event_time}') - if alignment_end_time: - # if there are multiple of such start event, pick the most immediate one following the alignment event - alignment_end_time = alignment_end_time.fetch( - 'event_start_time', order_by='event_start_time', limit=1)[0] - alignment_end_time = min(alignment_end_time, trial_stop) - else: - alignment_end_time = trial_stop - - alignment_event_time += alignment_spec['alignment_time_shift'] - alignment_start_time += alignment_spec['start_time_shift'] - alignment_end_time += alignment_spec['end_time_shift'] - - min_limit = min(alignment_start_time - alignment_event_time, min_limit) - max_limit = max(alignment_end_time - alignment_event_time, max_limit) - + alignment_start_time = r.event - min_limit + alignment_end_time = r.event + max_limit for unit_key, spikes in zip(unit_keys, unit_spike_times): aligned_spikes = spikes[(alignment_start_time <= spikes) - & (spikes < alignment_end_time)] - alignment_event_time - aligned_trial_spikes.append({**key, **unit_key, **trial_key, 'aligned_spike_times': aligned_spikes}) + & (spikes < alignment_end_time)] - r.event + aligned_trial_spikes.append({**key, **unit_key, **r.trial_key, 'aligned_spike_times': aligned_spikes}) units_spike_raster[unit_key['unit']]['aligned_spikes'].append(aligned_spikes) # PSTH diff --git a/workflow_array_ephys/pipeline.py b/workflow_array_ephys/pipeline.py index 29a7c9c8..e11c0022 100644 --- a/workflow_array_ephys/pipeline.py +++ b/workflow_array_ephys/pipeline.py @@ -3,7 +3,7 @@ from element_animal import subject from element_lab import lab from element_session import session -from element_trial import trial, event +from element_event import trial, event from element_array_ephys import probe from element_electrode_localization import coordinate_framework, electrode_localization @@ -44,7 +44,10 @@ Experimenter = lab.User session.activate(db_prefix + 'session', linking_module=__name__) -trial.activate(db_prefix + 'trial', db_prefix + 'event', linking_module= __name__) + +# Activate "event" and "trial" schema --------------------------------- + +trial.activate(db_prefix + 'trial', db_prefix + 'event', linking_module=__name__) # Declare table "SkullReference" for use in element-array-ephys --------------- @@ -72,7 +75,11 @@ class SkullReference(dj.Lookup): db_prefix + 'ccf', linking_module=__name__) -coordinate_framework.load_ccf_annotation( - ccf_id=0, version_name='ccf_2017', voxel_resolution=25, - nrrd_filepath='./data/annotation_25.nrrd', - ontology_csv_filepath='./data/query.csv') +ccf_id = 0 +voxel_resolution = 100 + +if not (coordinate_framework.CCF & {'ccf_id': ccf_id}): + coordinate_framework.load_ccf_annotation( + ccf_id=ccf_id, version_name='ccf_2017', voxel_resolution=voxel_resolution, + nrrd_filepath=f'./data/annotation_{voxel_resolution}.nrrd', + ontology_csv_filepath='./data/query.csv') From 3c9f7f180efde3a914c20a01bcba3a118cd4534d Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 24 Mar 2022 16:59:51 -0500 Subject: [PATCH 22/59] bugfix --- workflow_array_ephys/analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflow_array_ephys/analysis.py b/workflow_array_ephys/analysis.py index 718d31b3..cfc5a73f 100644 --- a/workflow_array_ephys/analysis.py +++ b/workflow_array_ephys/analysis.py @@ -77,7 +77,7 @@ def make(self, key): for unit_spike_raster in units_spike_raster.values(): spikes = np.concatenate(unit_spike_raster['aligned_spikes']) - psth, edges = np.histogram(spikes, bins=np.arange(min_limit, max_limit, bin_size)) + psth, edges = np.histogram(spikes, bins=np.arange(-min_limit, max_limit, bin_size)) unit_spike_raster['psth'] = psth / len(unit_spike_raster.pop('aligned_spikes')) / bin_size unit_spike_raster['psth_edges'] = edges[1:] From 6419bc11688821b026ce1a277421441484417a0e Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 24 Mar 2022 17:09:29 -0500 Subject: [PATCH 23/59] rename docker images --- docker/docker-compose-dev.yaml | 2 +- docker/docker-compose-test.yaml | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docker/docker-compose-dev.yaml b/docker/docker-compose-dev.yaml index 1a40310b..77531413 100644 --- a/docker/docker-compose-dev.yaml +++ b/docker/docker-compose-dev.yaml @@ -6,7 +6,7 @@ x-net: &net networks: - main services: - array-ephys-dev-db: + db: <<: *net image: datajoint/mysql:5.7 container_name: workflow-array-ephys-dev-db diff --git a/docker/docker-compose-test.yaml b/docker/docker-compose-test.yaml index 4bb32c77..caf390ec 100644 --- a/docker/docker-compose-test.yaml +++ b/docker/docker-compose-test.yaml @@ -1,7 +1,7 @@ # export COMPOSE_DOCKER_CLI_BUILD=0 # some machines need for smooth --build # .env file: TEST_DATA_DIR= # docker-compose -f ./docker/docker-compose-test.yaml up --build -# docker exec -it workflow-array-ephys-test /bin/bash +# docker exec -it workflow-array-ephys_workflow_1 /bin/bash # docker-compose -f ./docker/docker-compose-test.yaml down version: "2.4" @@ -9,13 +9,13 @@ x-net: &net networks: - main services: - array-ephys-test-db: + db: <<: *net image: datajoint/mysql:5.7 container_name: workflow-array-ephys-test-db environment: - MYSQL_ROOT_PASSWORD=simple - array-ephys-test-workflow: + workflow: <<: *net build: context: ../../ @@ -27,8 +27,8 @@ services: - DJ_HOST=db - DJ_USER=root - DJ_PASS=simple - - EPHYS_ROOT_DATA_DIR=/main/test_data/workflow_ephys_data1/,/main/test_data/workflow_ephys_data2/ - EPHYS_MODE=no-curation + - EPHYS_ROOT_DATA_DIR=/main/test_data/workflow_ephys_data1/,/main/test_data/workflow_ephys_data2/ - DATABASE_PREFIX=test_ command: - bash @@ -43,12 +43,11 @@ services: - ../../element-lab:/main/element-lab - ../../element-animal:/main/element-animal - ../../element-session:/main/element-session - - ../../element-trial:/main/element-trial - ../../element-electrode-localization:/main/element-electrode-localization - ../../element-array-ephys:/main/element-array-ephys - ..:/main/workflow-array-ephys depends_on: - array-ephys-test-db: + db: condition: service_healthy networks: main: From 3544809d121980368cde4f231481d12a880d1ab4 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 24 Mar 2022 19:28:35 -0500 Subject: [PATCH 24/59] localization NB; snake_case acronyms --- notebooks/08-electrode-localization.ipynb | 565 +++++++++++++++++- .../py_scripts/08-electrode-localization.py | 42 +- workflow_array_ephys/localization.py | 35 +- workflow_array_ephys/pipeline.py | 44 -- 4 files changed, 578 insertions(+), 108 deletions(-) diff --git a/notebooks/08-electrode-localization.ipynb b/notebooks/08-electrode-localization.ipynb index e5dfd19b..b19d5a3f 100644 --- a/notebooks/08-electrode-localization.ipynb +++ b/notebooks/08-electrode-localization.ipynb @@ -30,34 +30,30 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true, + "tags": [] + }, "source": [ - "The Allen Institute hosts [brain atlases](http://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/annotation/ccf_2017) and [ontology trees](https://community.brain-map.org/t/allen-mouse-ccf-accessing-and-using-related-data-and-tools/359) that we'll use in the next section.\n", - "1. Download the `nrrd` and `csv` files that correspond too your desired resolution\n", - "2. Move these files to your ephys root directory.\n", - "\n", - "Note that the higher resolution (i.e., lower number) files are quite large when loaded. Depending on the python environment, the terminal may be killed when loading so much information into memory." + "## Coordinate Framework" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "resolution = 100\n", - "from element_interface.utils import find_full_path\n", - "from workflow_array_ephys.paths import get_ephys_root_data_dir\n", - "nrrd_filepath = find_full_path(get_ephys_root_data_dir(), \n", - " f'annotation_{resolution}.nrrd')\n", - "ontology_csv_filepath = find_full_path(get_ephys_root_data_dir(), 'query.csv')" + "The Allen Institute hosts [brain atlases](http://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/annotation/ccf_2017) and [ontology trees](https://community.brain-map.org/t/allen-mouse-ccf-accessing-and-using-related-data-and-tools/359) that we'll use in the next section. The `localization.py` script assumes this is your first atlas, and that you'll use the 100μm resolution. For finer resolutions, edit `voxel_resolution` in `localization.py`. Higher resolution `nrrd` files are quite large when loaded. Depending on the python environment, the terminal may be killed when loading so much information into memory. To load multiple atlases, increment `ccf_id` for each unique atlas.\n", + "\n", + "To run this pipeline ...\n", + "1. Download the 100μm `nrrd` and `csv` files from the links above.\n", + "2. Move these files to your ephys root directory." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Next, we'll load annotation information from the files above to generate a set of lookup tables for the entire brain volume." + "Next, we'll populate the coordinate framework schema simply by loading it." ] }, { @@ -66,12 +62,7 @@ "metadata": {}, "outputs": [], "source": [ - "from workflow_array_ephys.localization import coordinate_framework as ccf\n", - "from element_electrode_localization.coordinate_framework import load_ccf_annotation\n", - "load_ccf_annotation(\n", - " ccf_id=0, version_name='ccf_2017', voxel_resolution=resolution,\n", - " nrrd_filepath=nrrd_filepath,\n", - " ontology_csv_filepath=ontology_csv_filepath)" + "from workflow_array_ephys.localization import coordinate_framework as ccf" ] }, { @@ -83,37 +74,541 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

ccf_id

\n", + " CCF ID, a.k.a atlas ID\n", + "
\n", + "

acronym

\n", + " CHARACTER SET utf8 COLLATE utf8_bin\n", + "
\n", + "

region_name

\n", + " \n", + "
\n", + "

region_id

\n", + " \n", + "
\n", + "

color_code

\n", + " hexcode of the color code of this region\n", + "
06bLayer 6b isocortex168ADA87
0A13Dopaminergic A13 group796F2483B
0AAAAnterior amygdalar area2380C0E2
0ACAAnterior cingulate area3140A666
0ACA1Anterior cingulate area layer 157240A666
0ACA2/3Anterior cingulate area layer 2/3105340A666
0ACA5Anterior cingulate area layer 573940A666
0ACA6aAnterior cingulate area layer 6a17940A666
0ACA6bAnterior cingulate area layer 6b22740A666
0ACAdAnterior cingulate area dorsal part3940A666
0ACAd1Anterior cingulate area dorsal part layer 193540A666
0ACAd2/3Anterior cingulate area dorsal part layer 2/321140A666
0ACAd5Anterior cingulate area dorsal part layer 5101540A666
0ACAd6aAnterior cingulate area dorsal part layer 6a91940A666
0ACAd6bAnterior cingulate area dorsal part layer 6b92740A666
0ACAvAnterior cingulate area ventral part4840A666
0ACAv1Anterior cingulate area ventral part layer 158840A666
0ACAv2/3Anterior cingulate area ventral part layer 2/329640A666
0ACAv5Anterior cingulate area ventral part layer 577240A666
0ACAv6aAnterior cingulate area ventral part 6a81040A666
0ACAv6bAnterior cingulate area ventral part 6b81940A666
0ACBNucleus accumbens5680CDF8
0ACVIAccessory abducens nucleus568FFB3D9
0ACVIIAccessory facial motor nucleus576FFB3D9
0ADAnterodorsal nucleus64FF909F
0ADPAnterodorsal preoptic nucleus72FF5547
0AHAAnterior hypothalamic area80FF5547
0AHNAnterior hypothalamic nucleus88FF4C3E
0AHNaAnterior hypothalamic nucleus anterior part700FF4C3E
0AHNcAnterior hypothalamic nucleus central part708FF4C3E
\n", + "

...

\n", + "

Total: 1327

\n", + " " + ], + "text/plain": [ + "*ccf_id *acronym region_name region_id color_code \n", + "+--------+ +---------+ +------------+ +-----------+ +------------+\n", + "0 6b Layer 6b isoco 16 8ADA87 \n", + "0 A13 Dopaminergic A 796 F2483B \n", + "0 AAA Anterior amygd 23 80C0E2 \n", + "0 ACA Anterior cingu 31 40A666 \n", + "0 ACA1 Anterior cingu 572 40A666 \n", + "0 ACA2/3 Anterior cingu 1053 40A666 \n", + "0 ACA5 Anterior cingu 739 40A666 \n", + "0 ACA6a Anterior cingu 179 40A666 \n", + "0 ACA6b Anterior cingu 227 40A666 \n", + "0 ACAd Anterior cingu 39 40A666 \n", + "0 ACAd1 Anterior cingu 935 40A666 \n", + "0 ACAd2/3 Anterior cingu 211 40A666 \n", + "0 ACAd5 Anterior cingu 1015 40A666 \n", + "0 ACAd6a Anterior cingu 919 40A666 \n", + "0 ACAd6b Anterior cingu 927 40A666 \n", + "0 ACAv Anterior cingu 48 40A666 \n", + "0 ACAv1 Anterior cingu 588 40A666 \n", + "0 ACAv2/3 Anterior cingu 296 40A666 \n", + "0 ACAv5 Anterior cingu 772 40A666 \n", + "0 ACAv6a Anterior cingu 810 40A666 \n", + "0 ACAv6b Anterior cingu 819 40A666 \n", + "0 ACB Nucleus accumb 56 80CDF8 \n", + "0 ACVI Accessory abdu 568 FFB3D9 \n", + "0 ACVII Accessory faci 576 FFB3D9 \n", + "0 AD Anterodorsal n 64 FF909F \n", + "0 ADP Anterodorsal p 72 FF5547 \n", + "0 AHA Anterior hypot 80 FF5547 \n", + "0 AHN Anterior hypot 88 FF4C3E \n", + "0 AHNa Anterior hypot 700 FF4C3E \n", + "0 AHNc Anterior hypot 708 FF4C3E \n", + " ...\n", + " (Total: 1327)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "ccf.BrainRegionAnnotation.BrainRegion()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's look at the dimensions of the primary somatosensory cortex, which has the acronym `SSp1`. To look at other regions, open the CSV you downloaded and search for your desired region." + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

ccf_id

\n", + " CCF ID, a.k.a atlas ID\n", + "
\n", + "

acronym

\n", + " CHARACTER SET utf8 COLLATE utf8_bin\n", + "
\n", + "

x

\n", + " (um) Anterior-to-Posterior (AP axis)\n", + "
\n", + "

y

\n", + " (um) Superior-to-Inferior (DV axis)\n", + "
\n", + "

z

\n", + " (um) Left-to-Right (ML axis)\n", + "
0SSp1570054005400
0SSp1570054005500
0SSp1570054005600
0SSp1570054005800
0SSp1570054005900
0SSp1570055005500
0SSp1570055005600
0SSp1570055005800
0SSp1570055005900
0SSp1570056005600
0SSp1570056005800
0SSp1570057005600
0SSp1570057005800
0SSp1580054005400
0SSp1580054005500
0SSp1580054005600
0SSp1580054005800
0SSp1580054005900
0SSp1580054006000
0SSp1580055005400
0SSp1580055005500
0SSp1580055005600
0SSp1580055005800
0SSp1580055005900
0SSp1580055006000
0SSp1580056005500
0SSp1580056005600
0SSp1580056005800
0SSp1580056005900
0SSp1580057005600
\n", + "

...

\n", + "

Total: 179

\n", + " " + ], + "text/plain": [ + "*ccf_id *acronym *x *y *z \n", + "+--------+ +---------+ +------+ +------+ +------+\n", + "0 SSp1 5700 5400 5400 \n", + "0 SSp1 5700 5400 5500 \n", + "0 SSp1 5700 5400 5600 \n", + "0 SSp1 5700 5400 5800 \n", + "0 SSp1 5700 5400 5900 \n", + "0 SSp1 5700 5500 5500 \n", + "0 SSp1 5700 5500 5600 \n", + "0 SSp1 5700 5500 5800 \n", + "0 SSp1 5700 5500 5900 \n", + "0 SSp1 5700 5600 5600 \n", + "0 SSp1 5700 5600 5800 \n", + "0 SSp1 5700 5700 5600 \n", + "0 SSp1 5700 5700 5800 \n", + "0 SSp1 5800 5400 5400 \n", + "0 SSp1 5800 5400 5500 \n", + "0 SSp1 5800 5400 5600 \n", + "0 SSp1 5800 5400 5800 \n", + "0 SSp1 5800 5400 5900 \n", + "0 SSp1 5800 5400 6000 \n", + "0 SSp1 5800 5500 5400 \n", + "0 SSp1 5800 5500 5500 \n", + "0 SSp1 5800 5500 5600 \n", + "0 SSp1 5800 5500 5800 \n", + "0 SSp1 5800 5500 5900 \n", + "0 SSp1 5800 5500 6000 \n", + "0 SSp1 5800 5600 5500 \n", + "0 SSp1 5800 5600 5600 \n", + "0 SSp1 5800 5600 5800 \n", + "0 SSp1 5800 5600 5900 \n", + "0 SSp1 5800 5700 5600 \n", + " ...\n", + " (Total: 179)" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ccf.BrainRegionAnnotation.Voxel() & 'acronym=\"SSp1\"'" + ] + }, + { + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "ccf.BrainRegionAnnotation.Voxel()" + "## Electrode Localization" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "If you have `channel_location` json files for your data, you can look at the position and regions associated with each electrode." + "If you have `channel_location` json files for your data, you can look at the position and regions associated with each electrode. Here, we've added an example file to our pre-existing `subject6` for demonstration purposes." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ - "from workflow_array_ephys.localization import electrode_localization as eloc\n", - "eloc.ElectrodePosition.populate()" + "dj.config.load('dj_local_conf.json')\n", + "from workflow_array_ephys.pipeline import subject, session, ephys, probe\n", + "from workflow_array_ephys.localization import electrode_localization as eloc" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "from workflow_array_ephys.localization import coordinate_framework as ccf" ] }, { @@ -122,8 +617,16 @@ "metadata": {}, "outputs": [], "source": [ + "eloc.ElectrodePosition.populate()\n", "eloc.ElectrodePosition.Electrode()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/notebooks/py_scripts/08-electrode-localization.py b/notebooks/py_scripts/08-electrode-localization.py index 69a7d005..87121c8f 100644 --- a/notebooks/py_scripts/08-electrode-localization.py +++ b/notebooks/py_scripts/08-electrode-localization.py @@ -24,37 +24,39 @@ assert os.path.basename(os.getcwd())=='workflow-array-ephys', ("Please move to the " + "workflow directory") -# The Allen Institute hosts [brain atlases](http://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/annotation/ccf_2017) and [ontology trees](https://community.brain-map.org/t/allen-mouse-ccf-accessing-and-using-related-data-and-tools/359) that we'll use in the next section. -# 1. Download the `nrrd` and `csv` files that correspond too your desired resolution -# 2. Move these files to your ephys root directory. -# -# Note that the higher resolution (i.e., lower number) files are quite large when loaded. Depending on the python environment, the terminal may be killed when loading so much information into memory. +# + [markdown] jp-MarkdownHeadingCollapsed=true tags=[] +# ## Coordinate Framework +# - -resolution = 100 -from element_interface.utils import find_full_path -from workflow_array_ephys.paths import get_ephys_root_data_dir -nrrd_filepath = find_full_path(get_ephys_root_data_dir(), - f'annotation_{resolution}.nrrd') -ontology_csv_filepath = find_full_path(get_ephys_root_data_dir(), 'query.csv') +# The Allen Institute hosts [brain atlases](http://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/annotation/ccf_2017) and [ontology trees](https://community.brain-map.org/t/allen-mouse-ccf-accessing-and-using-related-data-and-tools/359) that we'll use in the next section. The `localization.py` script assumes this is your first atlas, and that you'll use the 100μm resolution. For finer resolutions, edit `voxel_resolution` in `localization.py`. Higher resolution `nrrd` files are quite large when loaded. Depending on the python environment, the terminal may be killed when loading so much information into memory. To load multiple atlases, increment `ccf_id` for each unique atlas. +# +# To run this pipeline ... +# 1. Download the 100μm `nrrd` and `csv` files from the links above. +# 2. Move these files to your ephys root directory. -# Next, we'll load annotation information from the files above to generate a set of lookup tables for the entire brain volume. +# Next, we'll populate the coordinate framework schema simply by loading it. from workflow_array_ephys.localization import coordinate_framework as ccf -from element_electrode_localization.coordinate_framework import load_ccf_annotation -load_ccf_annotation( - ccf_id=0, version_name='ccf_2017', voxel_resolution=resolution, - nrrd_filepath=nrrd_filepath, - ontology_csv_filepath=ontology_csv_filepath) # Now, to explore the data we just loaded. ccf.BrainRegionAnnotation.BrainRegion() -ccf.BrainRegionAnnotation.Voxel() +# Let's look at the dimensions of the primary somatosensory cortex, which has the acronym `SSp1`. To look at other regions, open the CSV you downloaded and search for your desired region. + +ccf.BrainRegionAnnotation.Voxel() & 'acronym="SSp1"' + +# ## Electrode Localization -# If you have `channel_location` json files for your data, you can look at the position and regions associated with each electrode. +# If you have `channel_location` json files for your data, you can look at the position and regions associated with each electrode. Here, we've added an example file to our pre-existing `subject6` for demonstration purposes. +dj.config.load('dj_local_conf.json') +from workflow_array_ephys.pipeline import subject, session, ephys, probe from workflow_array_ephys.localization import electrode_localization as eloc -eloc.ElectrodePosition.populate() +from workflow_array_ephys.localization import coordinate_framework as ccf + +eloc.ElectrodePosition.populate() eloc.ElectrodePosition.Electrode() + + diff --git a/workflow_array_ephys/localization.py b/workflow_array_ephys/localization.py index 066e2ff8..ac85a458 100644 --- a/workflow_array_ephys/localization.py +++ b/workflow_array_ephys/localization.py @@ -1,4 +1,5 @@ import datajoint as dj +from element_interface.utils import find_full_path from element_electrode_localization import coordinate_framework, electrode_localization from element_electrode_localization.coordinate_framework import load_ccf_annotation @@ -6,6 +7,7 @@ from .paths import get_ephys_root_data_dir, get_session_directory, \ get_electrode_localization_dir + if 'custom' not in dj.config: dj.config['custom'] = {} @@ -21,34 +23,41 @@ voxel_resolution = 100 -# Dummy table for case sensitivity in MySQL------------------------------------ +# # Dummy table for case sensitivity in MySQL------------------------------------ +# # Without DummyTable, the schema activates with a case-insensitive character set +# # which cannot ingest all CCF standard acronyms -coordinate_framework_schema = dj.schema(db_prefix + 'ccf') +# coordinate_framework_schema = dj.schema(db_prefix + 'ccf') -@coordinate_framework_schema -class DummyTable(dj.Manual): - definition = """ - id : varchar(1) - """ - contents = zip(['1', '2']) +# @coordinate_framework_schema +# class DummyTable(dj.Manual): +# definition = """ +# id : varchar(1) +# """ +# contents = zip(['1', '2']) -ccf_schema_name = db_prefix + 'ccf' -dj.conn().query(f'ALTER DATABASE `{ccf_schema_name}` CHARACTER SET utf8 COLLATE ' - + 'utf8_bin;') +# ccf_schema_name = db_prefix + 'ccf' +# dj.conn().query(f'ALTER DATABASE `{ccf_schema_name}` CHARACTER SET utf8 COLLATE ' +# + 'utf8_bin;') # Activate "electrode-localization" schema ------------------------------------ ProbeInsertion = ephys.ProbeInsertion Electrode = probe.ProbeType.Electrode + electrode_localization.activate(db_prefix + 'eloc', db_prefix + 'ccf', linking_module=__name__) +nrrd_filepath = find_full_path(get_ephys_root_data_dir(), + f'annotation_{voxel_resolution}.nrrd') +ontology_csv_filepath = find_full_path(get_ephys_root_data_dir(), 'query.csv') + if not (coordinate_framework.CCF & {'ccf_id': ccf_id}): coordinate_framework.load_ccf_annotation( ccf_id=ccf_id, version_name='ccf_2017', voxel_resolution=voxel_resolution, - nrrd_filepath=f'./data/annotation_{voxel_resolution}.nrrd', - ontology_csv_filepath='./data/query.csv') + nrrd_filepath=nrrd_filepath, + ontology_csv_filepath=ontology_csv_filepath) diff --git a/workflow_array_ephys/pipeline.py b/workflow_array_ephys/pipeline.py index 94bc5b44..6b0c9d22 100644 --- a/workflow_array_ephys/pipeline.py +++ b/workflow_array_ephys/pipeline.py @@ -2,59 +2,33 @@ import os from element_animal import subject from element_lab import lab -<<<<<<< HEAD from element_session import session_with_datetime as session from element_array_ephys import probe from element_trial import trial, event -======= -from element_session import session -from element_event import trial, event -from element_array_ephys import probe -from element_electrode_localization import coordinate_framework, electrode_localization ->>>>>>> 3c9f7f180efde3a914c20a01bcba3a118cd4534d from element_animal.subject import Subject from element_lab.lab import Source, Lab, Protocol, User, Project from element_session.session_with_datetime import Session -<<<<<<< HEAD -======= -from .paths import get_ephys_root_data_dir, get_session_directory, get_electrode_localization_dir - ->>>>>>> 3c9f7f180efde3a914c20a01bcba3a118cd4534d if 'custom' not in dj.config: dj.config['custom'] = {} db_prefix = dj.config['custom'].get('database.prefix', '') -<<<<<<< HEAD __all__ = ['subject', 'lab', 'session', 'trial', 'event', 'probe', 'ephys', 'Subject', 'Source', 'Lab', 'Protocol', 'User', 'Project', 'Session'] -======= ->>>>>>> 3c9f7f180efde3a914c20a01bcba3a118cd4534d # ------------- Import the configured "ephys mode" ------------- ephys_mode = os.getenv('EPHYS_MODE', dj.config['custom'].get('ephys_mode', 'acute')) if ephys_mode == 'acute': -<<<<<<< HEAD from element_array_ephys import ephys_acute as ephys -======= - from element_array_ephys import ephys ->>>>>>> 3c9f7f180efde3a914c20a01bcba3a118cd4534d elif ephys_mode == 'chronic': from element_array_ephys import ephys_chronic as ephys elif ephys_mode == 'no-curation': from element_array_ephys import ephys_no_curation as ephys else: raise ValueError(f'Unknown ephys mode: {ephys_mode}') -<<<<<<< HEAD -======= - -__all__ = ['subject', 'lab', 'session', 'trial', 'event', 'probe', 'ephys', 'Subject', - 'Source', 'Lab', 'Protocol', 'User', 'Project', 'Session', 'coordinate_framework', 'electrode_localization', - 'get_ephys_root_data_dir', 'get_session_directory', 'get_electrode_localization_dir'] ->>>>>>> 3c9f7f180efde3a914c20a01bcba3a118cd4534d # Activate "lab", "subject", "session" schema --------------------------------- @@ -89,21 +63,3 @@ class SkullReference(dj.Lookup): ephys.activate(db_prefix + 'ephys', db_prefix + 'probe', linking_module=__name__) - -# Activate "electrode-localization" schema ------------------------------------ - -ProbeInsertion = ephys.ProbeInsertion -Electrode = probe.ProbeType.Electrode - -electrode_localization.activate(db_prefix + 'electrode_localization', - db_prefix + 'ccf', - linking_module=__name__) - -ccf_id = 0 -voxel_resolution = 100 - -if not (coordinate_framework.CCF & {'ccf_id': ccf_id}): - coordinate_framework.load_ccf_annotation( - ccf_id=ccf_id, version_name='ccf_2017', voxel_resolution=voxel_resolution, - nrrd_filepath=f'./data/annotation_{voxel_resolution}.nrrd', - ontology_csv_filepath='./data/query.csv') From 9ae3071583d3750784f8500e179b156385bcdb8b Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Fri, 25 Mar 2022 10:28:54 -0500 Subject: [PATCH 25/59] add attached localization diagram; README edit; analysis import --- README.md | 12 +- images/attached_electrode_localization.svg | 25 ++++ notebooks/08-electrode-localization.ipynb | 163 ++++++++++++++++++++- workflow_array_ephys/analysis.py | 4 +- 4 files changed, 197 insertions(+), 7 deletions(-) create mode 100644 images/attached_electrode_localization.svg diff --git a/README.md b/README.md index 52de56d3..d4257be8 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,17 @@ https://github.com/datajoint/element-lab/raw/main/images/lab_diagram.svg) ![element-animal]( https://github.com/datajoint/element-animal/raw/main/images/subject_diagram.svg) -### assembled with element-array-ephys +### Assembled with element-array-ephys -![element-array-ephys](images/attached_array_ephys_element.svg) +![attached-element-array-ephys](images/attached_array_ephys_element.svg) -### assembled with element-event and workflow analysis +### Assembled with element-event and workflow analysis -![worklow-trial-analysis](attached_trial_analysis.svg) +![attached-trial-analysis](./images/attached_trial_analysis.svg) + +## Assembled with element-electrode-localization + +![attached-electrode-localization](./images/attached_electrode_localization.svg) ## Installation instructions diff --git a/images/attached_electrode_localization.svg b/images/attached_electrode_localization.svg new file mode 100644 index 00000000..5d82d9fb --- /dev/null +++ b/images/attached_electrode_localization.svg @@ -0,0 +1,25 @@ + + diagram_electrode_localization_attached + + + + + + + + Ephys + + Workflow + + + + Electrode + + Localization + + + \ No newline at end of file diff --git a/notebooks/08-electrode-localization.ipynb b/notebooks/08-electrode-localization.ipynb index b19d5a3f..4c832302 100644 --- a/notebooks/08-electrode-localization.ipynb +++ b/notebooks/08-electrode-localization.ipynb @@ -593,7 +593,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -604,10 +604,11 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ + "from workflow_array_ephys.localization import electrode_localization as eloc\n", "from workflow_array_ephys.localization import coordinate_framework as ccf" ] }, @@ -621,6 +622,164 @@ "eloc.ElectrodePosition.Electrode()" ] }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting cbroz@tutorial-db.datajoint.io:3306\n" + ] + }, + { + "data": { + "text/plain": [ + "DataJoint connection (connected) cbroz@tutorial-db.datajoint.io:3306" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dj.conn()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "801" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mydiagram=dj.Diagram(subject).make_dot()\n", + "mydiagram.write(path='mydiagram_dotfile',format='dot')" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "((dj.Di(ccf) + dj.Di(eloc) -1) + ephys.CuratedClustering + ephys.CuratedClustering.Unit \n", + " + ephys.EphysRecording\n", + " + ephys.Clustering).make_dot().write(path='temp_eloc_full', format='dot')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "from workflow_array_ephys import analysis \n", + "from workflow_array_ephys.pipeline import trial, event\n", + "from element_trial.event import *\n", + "from element_trial.trial import *\n", + "from element_session.session_with_datetime import Session\n", + "from element_array_ephys.ephys_no_curation import *" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "ename": "DataJointError", + "evalue": "Class CuratedClustering is not properly declared (schema decorator not applied?)", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mDataJointError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/IPython/core/formatters.py\u001b[0m in \u001b[0;36m__call__\u001b[0;34m(self, obj)\u001b[0m\n\u001b[1;32m 343\u001b[0m \u001b[0mmethod\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_real_method\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mprint_method\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 344\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mmethod\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 345\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 346\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 347\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/diagram.py\u001b[0m in \u001b[0;36m_repr_svg_\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 324\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 325\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_repr_svg_\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 326\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmake_svg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_repr_svg_\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 327\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 328\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mdraw\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/diagram.py\u001b[0m in \u001b[0;36mmake_svg\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 312\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmake_svg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 313\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mIPython\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdisplay\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mSVG\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 314\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mSVG\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmake_dot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreate_svg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 315\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 316\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmake_png\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/diagram.py\u001b[0m in \u001b[0;36mmake_dot\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 251\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmake_dot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 252\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 253\u001b[0;31m \u001b[0mgraph\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_make_graph\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 254\u001b[0m \u001b[0mgraph\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnodes\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 255\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/diagram.py\u001b[0m in \u001b[0;36m_make_graph\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 240\u001b[0m for n in graph})\n\u001b[1;32m 241\u001b[0m \u001b[0;31m# relabel nodes to class names\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 242\u001b[0;31m mapping = {node: lookup_class_name(node, self.context) or node\n\u001b[0m\u001b[1;32m 243\u001b[0m for node in graph.nodes()}\n\u001b[1;32m 244\u001b[0m \u001b[0mnew_names\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mmapping\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalues\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/diagram.py\u001b[0m in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 240\u001b[0m for n in graph})\n\u001b[1;32m 241\u001b[0m \u001b[0;31m# relabel nodes to class names\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 242\u001b[0;31m mapping = {node: lookup_class_name(node, self.context) or node\n\u001b[0m\u001b[1;32m 243\u001b[0m for node in graph.nodes()}\n\u001b[1;32m 244\u001b[0m \u001b[0mnew_names\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mmapping\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalues\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/table.py\u001b[0m in \u001b[0;36mlookup_class_name\u001b[0;34m(name, context, depth)\u001b[0m\n\u001b[1;32m 719\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mmember_name\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstartswith\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'_'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# skip IPython's implicit variables\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 720\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0minspect\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misclass\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmember\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0missubclass\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmember\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTable\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 721\u001b[0;31m \u001b[0;32mif\u001b[0m \u001b[0mmember\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfull_table_name\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# found it!\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 722\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;34m'.'\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mnode\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'context_name'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmember_name\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlstrip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'.'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 723\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# look for part tables\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/user_tables.py\u001b[0m in \u001b[0;36m__getattribute__\u001b[0;34m(cls, name)\u001b[0m\n\u001b[1;32m 28\u001b[0m \u001b[0;31m# trigger instantiation for supported class attrs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 29\u001b[0m return (cls().__getattribute__(name) if name in supported_class_attrs\n\u001b[0;32m---> 30\u001b[0;31m else super().__getattribute__(name))\n\u001b[0m\u001b[1;32m 31\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 32\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__and__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/utils.py\u001b[0m in \u001b[0;36m__get__\u001b[0;34m(self, obj, owner)\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 13\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__get__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mowner\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 14\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mf\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mowner\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 15\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/user_tables.py\u001b[0m in \u001b[0;36mfull_table_name\u001b[0;34m(cls)\u001b[0m\n\u001b[1;32m 95\u001b[0m \u001b[0;31m# for derived classes only\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 96\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcls\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdatabase\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 97\u001b[0;31m raise DataJointError(\n\u001b[0m\u001b[1;32m 98\u001b[0m \u001b[0;34m'Class %s is not properly declared (schema decorator not applied?)'\u001b[0m \u001b[0;34m%\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 99\u001b[0m cls.__name__)\n", + "\u001b[0;31mDataJointError\u001b[0m: Class CuratedClustering is not properly declared (schema decorator not applied?)" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#dj.Di(trial) + dj.Di(event) -1 + \n", + "(dj.Di(analysis) - 1 + BehaviorRecording + Session)#.make_dot().write(path='temp_ev', format='dot')" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [], + "source": [ + "from workflow_array_ephys import analysis" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['spikes_alignment_condition',\n", + " 'spikes_alignment_condition__trial',\n", + " '__spikes_alignment',\n", + " '__spikes_alignment__aligned_trial_spikes',\n", + " '__spikes_alignment__unit_p_s_t_h']" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dj.schema('cbroz_analysis').list_tables()" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/workflow_array_ephys/analysis.py b/workflow_array_ephys/analysis.py index edf0da3c..10981f79 100644 --- a/workflow_array_ephys/analysis.py +++ b/workflow_array_ephys/analysis.py @@ -1,11 +1,13 @@ import datajoint as dj import numpy as np -from .pipeline import db_prefix, ephys, trial +from .pipeline import db_prefix, ephys, trial, event schema = dj.schema(db_prefix + 'analysis') +AlignmentEvent = event.AlignmentEvent + @schema class SpikesAlignmentCondition(dj.Manual): From f4d175c4026bd3749ffff4971e4823a501ed3377 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 28 Mar 2022 17:47:38 -0500 Subject: [PATCH 26/59] minor fixes --- notebooks/08-electrode-localization.ipynb | 604 +++++------------- .../py_scripts/08-electrode-localization.py | 33 +- workflow_array_ephys/pipeline.py | 5 +- 3 files changed, 174 insertions(+), 468 deletions(-) diff --git a/notebooks/08-electrode-localization.ipynb b/notebooks/08-electrode-localization.ipynb index 4c832302..32b312b3 100644 --- a/notebooks/08-electrode-localization.ipynb +++ b/notebooks/08-electrode-localization.ipynb @@ -21,16 +21,18 @@ "outputs": [], "source": [ "import os\n", - "import datajoint as dj\n", "# change to the upper level folder to detect dj_local_conf.json\n", "if os.path.basename(os.getcwd())=='notebooks': os.chdir('..')\n", "assert os.path.basename(os.getcwd())=='workflow-array-ephys', (\"Please move to the \"\n", - " + \"workflow directory\")" + " + \"workflow directory\")\n", + "# We'll be working with long tables, so we'll make visualization easier with a limit\n", + "import datajoint as dj; dj.config['display.limit']=10" ] }, { "cell_type": "markdown", "metadata": { + "incorrectly_encoded_metadata": "tags=[] jp-MarkdownHeadingCollapsed=true", "jp-MarkdownHeadingCollapsed": true, "tags": [] }, @@ -53,14 +55,22 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Next, we'll populate the coordinate framework schema simply by loading it." + "Next, we'll populate the coordinate framework schema simply by loading it. Because we are loading the whole brain volume, this may take 25 minutes or more." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting cbroz@dss-db.datajoint.io:3306\n" + ] + } + ], "source": [ "from workflow_array_ephys.localization import coordinate_framework as ccf" ] @@ -74,7 +84,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -154,165 +164,65 @@ "Layer 6b isocortex\n", "16\n", "8ADA870\n", - "A13\n", - "Dopaminergic A13 group\n", - "796\n", - "F2483B0\n", - "AAA\n", + "a_a_a\n", "Anterior amygdalar area\n", "23\n", "80C0E20\n", - "ACA\n", + "a_c_a\n", "Anterior cingulate area\n", "31\n", "40A6660\n", - "ACA1\n", + "a_c_a1\n", "Anterior cingulate area layer 1\n", "572\n", "40A6660\n", - "ACA2/3\n", + "a_c_a2/3\n", "Anterior cingulate area layer 2/3\n", "1053\n", "40A6660\n", - "ACA5\n", + "a_c_a5\n", "Anterior cingulate area layer 5\n", "739\n", "40A6660\n", - "ACA6a\n", + "a_c_a6a\n", "Anterior cingulate area layer 6a\n", "179\n", "40A6660\n", - "ACA6b\n", + "a_c_a6b\n", "Anterior cingulate area layer 6b\n", "227\n", "40A6660\n", - "ACAd\n", + "a_c_ad\n", "Anterior cingulate area dorsal part\n", "39\n", "40A6660\n", - "ACAd1\n", + "a_c_ad1\n", "Anterior cingulate area dorsal part layer 1\n", "935\n", - "40A6660\n", - "ACAd2/3\n", - "Anterior cingulate area dorsal part layer 2/3\n", - "211\n", - "40A6660\n", - "ACAd5\n", - "Anterior cingulate area dorsal part layer 5\n", - "1015\n", - "40A6660\n", - "ACAd6a\n", - "Anterior cingulate area dorsal part layer 6a\n", - "919\n", - "40A6660\n", - "ACAd6b\n", - "Anterior cingulate area dorsal part layer 6b\n", - "927\n", - "40A6660\n", - "ACAv\n", - "Anterior cingulate area ventral part\n", - "48\n", - "40A6660\n", - "ACAv1\n", - "Anterior cingulate area ventral part layer 1\n", - "588\n", - "40A6660\n", - "ACAv2/3\n", - "Anterior cingulate area ventral part layer 2/3\n", - "296\n", - "40A6660\n", - "ACAv5\n", - "Anterior cingulate area ventral part layer 5\n", - "772\n", - "40A6660\n", - "ACAv6a\n", - "Anterior cingulate area ventral part 6a\n", - "810\n", - "40A6660\n", - "ACAv6b\n", - "Anterior cingulate area ventral part 6b\n", - "819\n", - "40A6660\n", - "ACB\n", - "Nucleus accumbens\n", - "56\n", - "80CDF80\n", - "ACVI\n", - "Accessory abducens nucleus\n", - "568\n", - "FFB3D90\n", - "ACVII\n", - "Accessory facial motor nucleus\n", - "576\n", - "FFB3D90\n", - "AD\n", - "Anterodorsal nucleus\n", - "64\n", - "FF909F0\n", - "ADP\n", - "Anterodorsal preoptic nucleus\n", - "72\n", - "FF55470\n", - "AHA\n", - "Anterior hypothalamic area\n", - "80\n", - "FF55470\n", - "AHN\n", - "Anterior hypothalamic nucleus\n", - "88\n", - "FF4C3E0\n", - "AHNa\n", - "Anterior hypothalamic nucleus anterior part\n", - "700\n", - "FF4C3E0\n", - "AHNc\n", - "Anterior hypothalamic nucleus central part\n", - "708\n", - "FF4C3E \n", + "40A666 \n", " \n", "

...

\n", "

Total: 1327

\n", " " ], "text/plain": [ - "*ccf_id *acronym region_name region_id color_code \n", - "+--------+ +---------+ +------------+ +-----------+ +------------+\n", - "0 6b Layer 6b isoco 16 8ADA87 \n", - "0 A13 Dopaminergic A 796 F2483B \n", - "0 AAA Anterior amygd 23 80C0E2 \n", - "0 ACA Anterior cingu 31 40A666 \n", - "0 ACA1 Anterior cingu 572 40A666 \n", - "0 ACA2/3 Anterior cingu 1053 40A666 \n", - "0 ACA5 Anterior cingu 739 40A666 \n", - "0 ACA6a Anterior cingu 179 40A666 \n", - "0 ACA6b Anterior cingu 227 40A666 \n", - "0 ACAd Anterior cingu 39 40A666 \n", - "0 ACAd1 Anterior cingu 935 40A666 \n", - "0 ACAd2/3 Anterior cingu 211 40A666 \n", - "0 ACAd5 Anterior cingu 1015 40A666 \n", - "0 ACAd6a Anterior cingu 919 40A666 \n", - "0 ACAd6b Anterior cingu 927 40A666 \n", - "0 ACAv Anterior cingu 48 40A666 \n", - "0 ACAv1 Anterior cingu 588 40A666 \n", - "0 ACAv2/3 Anterior cingu 296 40A666 \n", - "0 ACAv5 Anterior cingu 772 40A666 \n", - "0 ACAv6a Anterior cingu 810 40A666 \n", - "0 ACAv6b Anterior cingu 819 40A666 \n", - "0 ACB Nucleus accumb 56 80CDF8 \n", - "0 ACVI Accessory abdu 568 FFB3D9 \n", - "0 ACVII Accessory faci 576 FFB3D9 \n", - "0 AD Anterodorsal n 64 FF909F \n", - "0 ADP Anterodorsal p 72 FF5547 \n", - "0 AHA Anterior hypot 80 FF5547 \n", - "0 AHN Anterior hypot 88 FF4C3E \n", - "0 AHNa Anterior hypot 700 FF4C3E \n", - "0 AHNc Anterior hypot 708 FF4C3E \n", + "*ccf_id *acronym region_name region_id color_code \n", + "+--------+ +----------+ +------------+ +-----------+ +------------+\n", + "0 6b Layer 6b isoco 16 8ADA87 \n", + "0 a_a_a Anterior amygd 23 80C0E2 \n", + "0 a_c_a Anterior cingu 31 40A666 \n", + "0 a_c_a1 Anterior cingu 572 40A666 \n", + "0 a_c_a2/3 Anterior cingu 1053 40A666 \n", + "0 a_c_a5 Anterior cingu 739 40A666 \n", + "0 a_c_a6a Anterior cingu 179 40A666 \n", + "0 a_c_a6b Anterior cingu 227 40A666 \n", + "0 a_c_ad Anterior cingu 39 40A666 \n", + "0 a_c_ad1 Anterior cingu 935 40A666 \n", " ...\n", " (Total: 1327)" ] }, - "execution_count": 9, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -325,12 +235,41 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Let's look at the dimensions of the primary somatosensory cortex, which has the acronym `SSp1`. To look at other regions, open the CSV you downloaded and search for your desired region." + "The acronyms listed in the DataJoint table differ slightly from the CCF standard by substituting case-sensitive differences with [snake case](https://en.wikipedia.org/wiki/Snake_case). To lookup the snake case equivalent, use the `retrieve_acronym` function." ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CM: c_m\n", + "cm: cm\n" + ] + } + ], + "source": [ + "central_thalamus = ccf.BrainRegionAnnotation.retrieve_acronym('CM')\n", + "cranial_nerves = ccf.BrainRegionAnnotation.retrieve_acronym('cm')\n", + "print(f'CM: {central_thalamus}\\ncm: {cranial_nerves}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If your work requires the case-sensitive columns please contact get in touch with the DataJoint team via [StackOverflow](https://stackoverflow.com/questions/tagged/datajoint).\n", + "\n", + "For this demo, let's look at the dimensions of the central thalamus. To look at other regions, open the CSV you downloaded and search for your desired region." + ] + }, + { + "cell_type": "code", + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -406,386 +345,141 @@ " (um) Left-to-Right (ML axis)\n", " \n", " 0\n", - "SSp1\n", - "5700\n", - "5400\n", - "54000\n", - "SSp1\n", - "5700\n", - "5400\n", - "55000\n", - "SSp1\n", - "5700\n", - "5400\n", - "56000\n", - "SSp1\n", - "5700\n", - "5400\n", - "58000\n", - "SSp1\n", - "5700\n", - "5400\n", - "59000\n", - "SSp1\n", - "5700\n", - "5500\n", - "55000\n", - "SSp1\n", - "5700\n", - "5500\n", - "56000\n", - "SSp1\n", - "5700\n", - "5500\n", - "58000\n", - "SSp1\n", - "5700\n", - "5500\n", - "59000\n", - "SSp1\n", - "5700\n", - "5600\n", - "56000\n", - "SSp1\n", - "5700\n", - "5600\n", - "58000\n", - "SSp1\n", - "5700\n", - "5700\n", - "56000\n", - "SSp1\n", - "5700\n", - "5700\n", - "58000\n", - "SSp1\n", - "5800\n", - "5400\n", - "54000\n", - "SSp1\n", - "5800\n", - "5400\n", - "55000\n", - "SSp1\n", - "5800\n", - "5400\n", - "56000\n", - "SSp1\n", - "5800\n", - "5400\n", - "58000\n", - "SSp1\n", - "5800\n", - "5400\n", - "59000\n", - "SSp1\n", - "5800\n", - "5400\n", - "60000\n", - "SSp1\n", - "5800\n", - "5500\n", - "54000\n", - "SSp1\n", - "5800\n", - "5500\n", - "55000\n", - "SSp1\n", - "5800\n", - "5500\n", - "56000\n", - "SSp1\n", - "5800\n", - "5500\n", - "58000\n", - "SSp1\n", - "5800\n", - "5500\n", - "59000\n", - "SSp1\n", - "5800\n", - "5500\n", - "60000\n", - "SSp1\n", - "5800\n", - "5600\n", - "55000\n", - "SSp1\n", - "5800\n", - "5600\n", - "56000\n", - "SSp1\n", - "5800\n", - "5600\n", - "58000\n", - "SSp1\n", - "5800\n", - "5600\n", - "59000\n", - "SSp1\n", - "5800\n", - "5700\n", - "5600 \n", + "c_m\n", + "100\n", + "3300\n", + "49000\n", + "c_m\n", + "100\n", + "3300\n", + "50000\n", + "c_m\n", + "100\n", + "3300\n", + "51000\n", + "c_m\n", + "100\n", + "3300\n", + "52000\n", + "c_m\n", + "100\n", + "3300\n", + "53000\n", + "c_m\n", + "100\n", + "3300\n", + "61000\n", + "c_m\n", + "100\n", + "3300\n", + "62000\n", + "c_m\n", + "100\n", + "3300\n", + "63000\n", + "c_m\n", + "100\n", + "3300\n", + "64000\n", + "c_m\n", + "100\n", + "3300\n", + "6500 \n", " \n", "

...

\n", - "

Total: 179

\n", + "

Total: 4911

\n", " " ], "text/plain": [ - "*ccf_id *acronym *x *y *z \n", - "+--------+ +---------+ +------+ +------+ +------+\n", - "0 SSp1 5700 5400 5400 \n", - "0 SSp1 5700 5400 5500 \n", - "0 SSp1 5700 5400 5600 \n", - "0 SSp1 5700 5400 5800 \n", - "0 SSp1 5700 5400 5900 \n", - "0 SSp1 5700 5500 5500 \n", - "0 SSp1 5700 5500 5600 \n", - "0 SSp1 5700 5500 5800 \n", - "0 SSp1 5700 5500 5900 \n", - "0 SSp1 5700 5600 5600 \n", - "0 SSp1 5700 5600 5800 \n", - "0 SSp1 5700 5700 5600 \n", - "0 SSp1 5700 5700 5800 \n", - "0 SSp1 5800 5400 5400 \n", - "0 SSp1 5800 5400 5500 \n", - "0 SSp1 5800 5400 5600 \n", - "0 SSp1 5800 5400 5800 \n", - "0 SSp1 5800 5400 5900 \n", - "0 SSp1 5800 5400 6000 \n", - "0 SSp1 5800 5500 5400 \n", - "0 SSp1 5800 5500 5500 \n", - "0 SSp1 5800 5500 5600 \n", - "0 SSp1 5800 5500 5800 \n", - "0 SSp1 5800 5500 5900 \n", - "0 SSp1 5800 5500 6000 \n", - "0 SSp1 5800 5600 5500 \n", - "0 SSp1 5800 5600 5600 \n", - "0 SSp1 5800 5600 5800 \n", - "0 SSp1 5800 5600 5900 \n", - "0 SSp1 5800 5700 5600 \n", + "*ccf_id *acronym *x *y *z \n", + "+--------+ +---------+ +-----+ +------+ +------+\n", + "0 c_m 100 3300 4900 \n", + "0 c_m 100 3300 5000 \n", + "0 c_m 100 3300 5100 \n", + "0 c_m 100 3300 5200 \n", + "0 c_m 100 3300 5300 \n", + "0 c_m 100 3300 6100 \n", + "0 c_m 100 3300 6200 \n", + "0 c_m 100 3300 6300 \n", + "0 c_m 100 3300 6400 \n", + "0 c_m 100 3300 6500 \n", " ...\n", - " (Total: 179)" + " (Total: 4911)" ] }, - "execution_count": 11, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "ccf.BrainRegionAnnotation.Voxel() & 'acronym=\"SSp1\"'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Electrode Localization" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you have `channel_location` json files for your data, you can look at the position and regions associated with each electrode. Here, we've added an example file to our pre-existing `subject6` for demonstration purposes." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "dj.config.load('dj_local_conf.json')\n", - "from workflow_array_ephys.pipeline import subject, session, ephys, probe\n", - "from workflow_array_ephys.localization import electrode_localization as eloc" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "from workflow_array_ephys.localization import electrode_localization as eloc\n", - "from workflow_array_ephys.localization import coordinate_framework as ccf" + "cm_voxels = ccf.BrainRegionAnnotation.Voxel() & f'acronym=\\\"{central_thalamus}\\\"'\n", + "cm_voxels" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "eloc.ElectrodePosition.populate()\n", - "eloc.ElectrodePosition.Electrode()" - ] - }, - { - "cell_type": "code", - "execution_count": 2, + "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Connecting cbroz@tutorial-db.datajoint.io:3306\n" + "The central thalamus extends from \n", + "\tx = 100 to x = 8400\n", + "\ty = 2600 to y = 7500\n", + "\tz = 1700 to z = 9700\n" ] - }, - { - "data": { - "text/plain": [ - "DataJoint connection (connected) cbroz@tutorial-db.datajoint.io:3306" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dj.conn()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "801" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ - "mydiagram=dj.Diagram(subject).make_dot()\n", - "mydiagram.write(path='mydiagram_dotfile',format='dot')" + "cm_x, cm_y, cm_z = cm_voxels.fetch('x', 'y', 'z')\n", + "print(f'The central thalamus extends from \\n\\tx = {min(cm_x)} to x = {max(cm_x)}\\n\\t'\n", + " + f'y = {min(cm_y)} to y = {max(cm_y)}\\n\\tz = {min(cm_z)} to z = {max(cm_z)}')" ] }, { - "cell_type": "code", - "execution_count": 25, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ - "((dj.Di(ccf) + dj.Di(eloc) -1) + ephys.CuratedClustering + ephys.CuratedClustering.Unit \n", - " + ephys.EphysRecording\n", - " + ephys.Clustering).make_dot().write(path='temp_eloc_full', format='dot')" + "## Electrode Localization" ] }, { - "cell_type": "code", - "execution_count": 6, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "from workflow_array_ephys import analysis \n", - "from workflow_array_ephys.pipeline import trial, event\n", - "from element_trial.event import *\n", - "from element_trial.trial import *\n", - "from element_session.session_with_datetime import Session\n", - "from element_array_ephys.ephys_no_curation import *" + "If you have `channel_location` json files for your data, you can look at the position and regions associated with each electrode. Here, we've added an example file to our pre-existing `subject6` for demonstration purposes." ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 2, "metadata": {}, "outputs": [ { - "ename": "DataJointError", - "evalue": "Class CuratedClustering is not properly declared (schema decorator not applied?)", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mDataJointError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/IPython/core/formatters.py\u001b[0m in \u001b[0;36m__call__\u001b[0;34m(self, obj)\u001b[0m\n\u001b[1;32m 343\u001b[0m \u001b[0mmethod\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_real_method\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mprint_method\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 344\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mmethod\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 345\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 346\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 347\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/diagram.py\u001b[0m in \u001b[0;36m_repr_svg_\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 324\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 325\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_repr_svg_\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 326\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmake_svg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_repr_svg_\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 327\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 328\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mdraw\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/diagram.py\u001b[0m in \u001b[0;36mmake_svg\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 312\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmake_svg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 313\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mIPython\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdisplay\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mSVG\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 314\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mSVG\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmake_dot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreate_svg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 315\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 316\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmake_png\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/diagram.py\u001b[0m in \u001b[0;36mmake_dot\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 251\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmake_dot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 252\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 253\u001b[0;31m \u001b[0mgraph\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_make_graph\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 254\u001b[0m \u001b[0mgraph\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnodes\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 255\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/diagram.py\u001b[0m in \u001b[0;36m_make_graph\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 240\u001b[0m for n in graph})\n\u001b[1;32m 241\u001b[0m \u001b[0;31m# relabel nodes to class names\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 242\u001b[0;31m mapping = {node: lookup_class_name(node, self.context) or node\n\u001b[0m\u001b[1;32m 243\u001b[0m for node in graph.nodes()}\n\u001b[1;32m 244\u001b[0m \u001b[0mnew_names\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mmapping\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalues\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/diagram.py\u001b[0m in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 240\u001b[0m for n in graph})\n\u001b[1;32m 241\u001b[0m \u001b[0;31m# relabel nodes to class names\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 242\u001b[0;31m mapping = {node: lookup_class_name(node, self.context) or node\n\u001b[0m\u001b[1;32m 243\u001b[0m for node in graph.nodes()}\n\u001b[1;32m 244\u001b[0m \u001b[0mnew_names\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mmapping\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalues\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/table.py\u001b[0m in \u001b[0;36mlookup_class_name\u001b[0;34m(name, context, depth)\u001b[0m\n\u001b[1;32m 719\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mmember_name\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstartswith\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'_'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# skip IPython's implicit variables\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 720\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0minspect\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misclass\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmember\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0missubclass\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmember\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTable\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 721\u001b[0;31m \u001b[0;32mif\u001b[0m \u001b[0mmember\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfull_table_name\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# found it!\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 722\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;34m'.'\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mnode\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'context_name'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmember_name\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlstrip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'.'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 723\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# look for part tables\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/user_tables.py\u001b[0m in \u001b[0;36m__getattribute__\u001b[0;34m(cls, name)\u001b[0m\n\u001b[1;32m 28\u001b[0m \u001b[0;31m# trigger instantiation for supported class attrs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 29\u001b[0m return (cls().__getattribute__(name) if name in supported_class_attrs\n\u001b[0;32m---> 30\u001b[0;31m else super().__getattribute__(name))\n\u001b[0m\u001b[1;32m 31\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 32\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__and__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/utils.py\u001b[0m in \u001b[0;36m__get__\u001b[0;34m(self, obj, owner)\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 13\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__get__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mowner\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 14\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mf\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mowner\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 15\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/user_tables.py\u001b[0m in \u001b[0;36mfull_table_name\u001b[0;34m(cls)\u001b[0m\n\u001b[1;32m 95\u001b[0m \u001b[0;31m# for derived classes only\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 96\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcls\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdatabase\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 97\u001b[0;31m raise DataJointError(\n\u001b[0m\u001b[1;32m 98\u001b[0m \u001b[0;34m'Class %s is not properly declared (schema decorator not applied?)'\u001b[0m \u001b[0;34m%\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 99\u001b[0m cls.__name__)\n", - "\u001b[0;31mDataJointError\u001b[0m: Class CuratedClustering is not properly declared (schema decorator not applied?)" + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting cbroz@dss-db.datajoint.io:3306\n" ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ - "#dj.Di(trial) + dj.Di(event) -1 + \n", - "(dj.Di(analysis) - 1 + BehaviorRecording + Session)#.make_dot().write(path='temp_ev', format='dot')" + "from workflow_array_ephys.localization import coordinate_framework as ccf\n", + "from workflow_array_ephys.localization import electrode_localization as eloc" ] }, { "cell_type": "code", - "execution_count": 32, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "from workflow_array_ephys import analysis" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['spikes_alignment_condition',\n", - " 'spikes_alignment_condition__trial',\n", - " '__spikes_alignment',\n", - " '__spikes_alignment__aligned_trial_spikes',\n", - " '__spikes_alignment__unit_p_s_t_h']" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dj.schema('cbroz_analysis').list_tables()" + "elocBrainRegionAnnotationodePosition.populate()\n", + "eloc.ElectrodePosition.Electrode()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/notebooks/py_scripts/08-electrode-localization.py b/notebooks/py_scripts/08-electrode-localization.py index 87121c8f..fb759658 100644 --- a/notebooks/py_scripts/08-electrode-localization.py +++ b/notebooks/py_scripts/08-electrode-localization.py @@ -18,13 +18,14 @@ # Change into the parent directory to find the `dj_local_conf.json` file. import os -import datajoint as dj # change to the upper level folder to detect dj_local_conf.json if os.path.basename(os.getcwd())=='notebooks': os.chdir('..') assert os.path.basename(os.getcwd())=='workflow-array-ephys', ("Please move to the " + "workflow directory") +# We'll be working with long tables, so we'll make visualization easier with a limit +import datajoint as dj; dj.config['display.limit']=10 -# + [markdown] jp-MarkdownHeadingCollapsed=true tags=[] +# + [markdown] tags=[] jp-MarkdownHeadingCollapsed=true jp-MarkdownHeadingCollapsed=true tags=[] # ## Coordinate Framework # - @@ -34,7 +35,7 @@ # 1. Download the 100μm `nrrd` and `csv` files from the links above. # 2. Move these files to your ephys root directory. -# Next, we'll populate the coordinate framework schema simply by loading it. +# Next, we'll populate the coordinate framework schema simply by loading it. Because we are loading the whole brain volume, this may take 25 minutes or more. from workflow_array_ephys.localization import coordinate_framework as ccf @@ -42,21 +43,29 @@ ccf.BrainRegionAnnotation.BrainRegion() -# Let's look at the dimensions of the primary somatosensory cortex, which has the acronym `SSp1`. To look at other regions, open the CSV you downloaded and search for your desired region. +# The acronyms listed in the DataJoint table differ slightly from the CCF standard by substituting case-sensitive differences with [snake case](https://en.wikipedia.org/wiki/Snake_case). To lookup the snake case equivalent, use the `retrieve_acronym` function. -ccf.BrainRegionAnnotation.Voxel() & 'acronym="SSp1"' +central_thalamus = ccf.BrainRegionAnnotation.retrieve_acronym('CM') +cranial_nerves = ccf.BrainRegionAnnotation.retrieve_acronym('cm') +print(f'CM: {central_thalamus}\ncm: {cranial_nerves}') + +# If your work requires the case-sensitive columns please contact get in touch with the DataJoint team via [StackOverflow](https://stackoverflow.com/questions/tagged/datajoint). +# +# For this demo, let's look at the dimensions of the central thalamus. To look at other regions, open the CSV you downloaded and search for your desired region. + +cm_voxels = ccf.BrainRegionAnnotation.Voxel() & f'acronym=\"{central_thalamus}\"' +cm_voxels + +cm_x, cm_y, cm_z = cm_voxels.fetch('x', 'y', 'z') +print(f'The central thalamus extends from \n\tx = {min(cm_x)} to x = {max(cm_x)}\n\t' + + f'y = {min(cm_y)} to y = {max(cm_y)}\n\tz = {min(cm_z)} to z = {max(cm_z)}') # ## Electrode Localization # If you have `channel_location` json files for your data, you can look at the position and regions associated with each electrode. Here, we've added an example file to our pre-existing `subject6` for demonstration purposes. -dj.config.load('dj_local_conf.json') -from workflow_array_ephys.pipeline import subject, session, ephys, probe -from workflow_array_ephys.localization import electrode_localization as eloc - from workflow_array_ephys.localization import coordinate_framework as ccf +from workflow_array_ephys.localization import electrode_localization as eloc -eloc.ElectrodePosition.populate() +elocBrainRegionAnnotationodePosition.populate() eloc.ElectrodePosition.Electrode() - - diff --git a/workflow_array_ephys/pipeline.py b/workflow_array_ephys/pipeline.py index 6b0c9d22..c974bef4 100644 --- a/workflow_array_ephys/pipeline.py +++ b/workflow_array_ephys/pipeline.py @@ -10,13 +10,16 @@ from element_lab.lab import Source, Lab, Protocol, User, Project from element_session.session_with_datetime import Session +from .paths import get_ephys_root_data_dir, get_session_directory + if 'custom' not in dj.config: dj.config['custom'] = {} db_prefix = dj.config['custom'].get('database.prefix', '') __all__ = ['subject', 'lab', 'session', 'trial', 'event', 'probe', 'ephys', - 'Subject', 'Source', 'Lab', 'Protocol', 'User', 'Project', 'Session'] + 'Subject', 'Source', 'Lab', 'Protocol', 'User', 'Project', 'Session', + 'get_ephys_root_data_dir', 'get_session_directory'] # ------------- Import the configured "ephys mode" ------------- ephys_mode = os.getenv('EPHYS_MODE', From 6f72f79c673e2f9faa6977d1de7e85e8f5ee9506 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 31 Mar 2022 13:20:14 -0500 Subject: [PATCH 27/59] finalize eloc demo --- notebooks/07-Downstream analysis.ipynb | 1037 ----------------- notebooks/07-downstream-analysis.ipynb | 907 ++------------ notebooks/08-electrode-localization.ipynb | 221 +++- .../py_scripts/07-downstream-analysis.py | 15 +- .../py_scripts/08-electrode-localization.py | 34 +- workflow_array_ephys/pipeline.py | 2 +- 6 files changed, 371 insertions(+), 1845 deletions(-) delete mode 100644 notebooks/07-Downstream analysis.ipynb diff --git a/notebooks/07-Downstream analysis.ipynb b/notebooks/07-Downstream analysis.ipynb deleted file mode 100644 index f4bce16e..00000000 --- a/notebooks/07-Downstream analysis.ipynb +++ /dev/null @@ -1,1037 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "921a4a03", - "metadata": {}, - "outputs": [], - "source": [ - "cd .." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "79cef246", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Connecting ttngu207@dss-db.datajoint.io:3306\n" - ] - } - ], - "source": [ - "from workflow_array_ephys.pipeline import session, ephys, trial, event" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "0678d202", - "metadata": {}, - "outputs": [], - "source": [ - "from workflow_array_ephys import analysis" - ] - }, - { - "cell_type": "markdown", - "id": "4936a1e8", - "metadata": {}, - "source": [ - "# Event-aligned trialized unit spike times" - ] - }, - { - "cell_type": "markdown", - "id": "a2e97d75", - "metadata": {}, - "source": [ - "The `analysis` schema provides example tables to perform event-aligned spike-times analysis.\n", - "+ ***SpikesAlignmentCondition*** - a manual table to specify the inputs and condition for the analysis\n", - "+ ***SpikesAlignment*** - a computed table to extract event-aligned spikes and compute unit PSTH" - ] - }, - { - "cell_type": "markdown", - "id": "ba4607a1", - "metadata": {}, - "source": [ - "Let's start by creating several analyses configuration - i.e. inserting into ***SpikesAlignmentCondition***" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "9a36c342", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " Clustering results of the spike sorting step.\n", - "
\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

subject

\n", - " \n", - "
\n", - "

session_id

\n", - " \n", - "
\n", - "

insertion_number

\n", - " \n", - "
\n", - "

paramset_idx

\n", - " \n", - "
subject5110
subject5120
\n", - " \n", - "

Total: 2

\n", - " " - ], - "text/plain": [ - "*subject *session_id *insertion_number *paramset_idx \n", - "+----------+ +------------+ +------------------+ +--------------+\n", - "subject5 1 1 0 \n", - "subject5 1 2 0 \n", - " (Total: 2)" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ephys.CuratedClustering()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "8642f010", - "metadata": {}, - "outputs": [], - "source": [ - "clustering_key = (ephys.CuratedClustering & {'subject': 'subject5', 'session_id': 1, 'insertion_number': 1}).fetch1('KEY')" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "50a2c99f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " time_shift is seconds to shift with respect to (WRT) a variable\n", - "
\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

alignment_name

\n", - " \n", - "
\n", - "

alignment_description

\n", - " \n", - "
\n", - "

alignment_event_type

\n", - " \n", - "
\n", - "

alignment_time_shift

\n", - " (s) WRT alignment_event_type\n", - "
\n", - "

start_event_type

\n", - " \n", - "
\n", - "

start_time_shift

\n", - " (s) WRT start_event_type\n", - "
\n", - "

end_event_type

\n", - " \n", - "
\n", - "

end_time_shift

\n", - " (s) WRT end_event_type\n", - "
center_buttoncenter0.0center-3.0center3.0
left_buttonleft0.0left-3.0left3.0
right_buttonright0.0right-3.0right3.0
\n", - " \n", - "

Total: 3

\n", - " " - ], - "text/plain": [ - "*alignment_name alignment_description alignment_event_type alignment_time_shift start_event_type start_time_shift end_event_type end_time_shift \n", - "+----------------+ +-----------------------+ +----------------------+ +----------------------+ +------------------+ +------------------+ +----------------+ +----------------+\n", - "center_button center 0.0 center -3.0 center 3.0 \n", - "left_button left 0.0 left -3.0 left 3.0 \n", - "right_button right 0.0 right -3.0 right 3.0 \n", - " (Total: 3)" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "event.AlignmentEvent()" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "d859203f", - "metadata": {}, - "outputs": [], - "source": [ - "alignment_key = (event.AlignmentEvent & 'alignment_name = \"center_button\"').fetch1('KEY')" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "c9c95806", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " Experimental trials\n", - "
\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

subject

\n", - " \n", - "
\n", - "

session_id

\n", - " \n", - "
\n", - "

trial_id

\n", - " trial number (1-based indexing)\n", - "
\n", - "

trial_type

\n", - " \n", - "
\n", - "

trial_start_time

\n", - " (second) relative to recording start\n", - "
\n", - "

trial_stop_time

\n", - " (second) relative to recording start\n", - "
subject511stim2.04310.043
subject512ctrl10.50818.508
subject513ctrl18.726.7
subject514ctrl26.70734.707
subject515stim34.71542.715
subject516stim42.80650.806
subject517ctrl50.83958.839
subject518ctrl59.19667.196
subject519stim67.3175.31
subject5110ctrl75.77283.772
subject5111stim86.08294.082
subject5112stim94.087102.087
subject5113stim102.183110.183
subject5114stim110.526118.526
subject5115stim118.844126.844
\n", - "

...

\n", - "

Total: 40

\n", - " " - ], - "text/plain": [ - "*subject *session_id *trial_id trial_type trial_start_time trial_stop_time \n", - "+----------+ +------------+ +----------+ +------------+ +------------------+ +-----------------+\n", - "subject5 1 1 stim 2.043 10.043 \n", - "subject5 1 2 ctrl 10.508 18.508 \n", - "subject5 1 3 ctrl 18.7 26.7 \n", - "subject5 1 4 ctrl 26.707 34.707 \n", - "subject5 1 5 stim 34.715 42.715 \n", - "subject5 1 6 stim 42.806 50.806 \n", - "subject5 1 7 ctrl 50.839 58.839 \n", - "subject5 1 8 ctrl 59.196 67.196 \n", - "subject5 1 9 stim 67.31 75.31 \n", - "subject5 1 10 ctrl 75.772 83.772 \n", - "subject5 1 11 stim 86.082 94.082 \n", - "subject5 1 12 stim 94.087 102.087 \n", - "subject5 1 13 stim 102.183 110.183 \n", - "subject5 1 14 stim 110.526 118.526 \n", - "subject5 1 15 stim 118.844 126.844 \n", - " ...\n", - " (Total: 40)" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "trial.Trial & clustering_key" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "1851ad2b", - "metadata": {}, - "outputs": [], - "source": [ - "ctrl_trials = trial.Trial & clustering_key & 'trial_type = \"ctrl\"'" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "01474336", - "metadata": {}, - "outputs": [], - "source": [ - "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'ctrl_center_button'}" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "8bc824cb", - "metadata": {}, - "outputs": [], - "source": [ - "analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "8994e8f5", - "metadata": {}, - "outputs": [], - "source": [ - "analysis.SpikesAlignmentCondition.Trial.insert(\n", - " (analysis.SpikesAlignmentCondition * ctrl_trials & alignment_condition).proj(),\n", - " skip_duplicates=True)" - ] - }, - { - "cell_type": "markdown", - "id": "4ddd0345", - "metadata": {}, - "source": [ - "With the steps above, we have create a new spike alignment condition for analysis, named `ctrl_center_button`, which specifies:\n", - "+ a CuratedClustering of interest for analysis\n", - "+ an event of interest to align the spikes to - `center_button`\n", - "+ a set of trials of interest to perform the analysis on - `ctrl` trials" - ] - }, - { - "cell_type": "markdown", - "id": "2a23b206", - "metadata": {}, - "source": [ - "Now, let's create another set with:\n", - "+ the same CuratedClustering of interest for analysis\n", - "+ an event of interest to align the spikes to - `center_button`\n", - "+ a set of trials of interest to perform the analysis on - `stim` trials" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "39d4d423", - "metadata": {}, - "outputs": [], - "source": [ - "stim_trials = trial.Trial & clustering_key & 'trial_type = \"stim\"'\n", - "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", - "analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True)\n", - "analysis.SpikesAlignmentCondition.Trial.insert(\n", - " (analysis.SpikesAlignmentCondition * stim_trials & alignment_condition).proj(),\n", - " skip_duplicates=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "6452c508", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - "
\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

subject

\n", - " \n", - "
\n", - "

session_id

\n", - " \n", - "
\n", - "

insertion_number

\n", - " \n", - "
\n", - "

paramset_idx

\n", - " \n", - "
\n", - "

alignment_name

\n", - " \n", - "
\n", - "

trial_condition

\n", - " user-friendly name of condition\n", - "
\n", - "

bin_size

\n", - " bin-size (in second) used to compute the PSTH\n", - "
subject5110center_buttonctrl_center_button0.04
subject5110center_buttonstim_center_button0.04
\n", - " \n", - "

Total: 2

\n", - " " - ], - "text/plain": [ - "*subject *session_id *insertion_number *paramset_idx *alignment_name *trial_condition bin_size \n", - "+----------+ +------------+ +------------------+ +--------------+ +----------------+ +--------------------+ +----------+\n", - "subject5 1 1 0 center_button ctrl_center_button 0.04 \n", - "subject5 1 1 0 center_button stim_center_button 0.04 \n", - " (Total: 2)" - ] - }, - "execution_count": 38, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "analysis.SpikesAlignmentCondition()" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "7a9ef2fb", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " Trials (or subset of trials) to computed event-aligned spikes and PSTH on\n", - "
\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

subject

\n", - " \n", - "
\n", - "

session_id

\n", - " \n", - "
\n", - "

insertion_number

\n", - " \n", - "
\n", - "

paramset_idx

\n", - " \n", - "
\n", - "

alignment_name

\n", - " \n", - "
\n", - "

trial_condition

\n", - " user-friendly name of condition\n", - "
\n", - "

trial_id

\n", - " trial number (1-based indexing)\n", - "
subject5110center_buttonctrl_center_button2
subject5110center_buttonctrl_center_button3
subject5110center_buttonctrl_center_button4
subject5110center_buttonctrl_center_button7
subject5110center_buttonctrl_center_button8
subject5110center_buttonctrl_center_button10
subject5110center_buttonctrl_center_button17
subject5110center_buttonctrl_center_button19
subject5110center_buttonctrl_center_button21
subject5110center_buttonctrl_center_button22
subject5110center_buttonctrl_center_button24
subject5110center_buttonctrl_center_button25
subject5110center_buttonctrl_center_button27
subject5110center_buttonctrl_center_button30
subject5110center_buttonctrl_center_button33
\n", - "

...

\n", - "

Total: 18

\n", - " " - ], - "text/plain": [ - "*subject *session_id *insertion_number *paramset_idx *alignment_name *trial_condition *trial_id \n", - "+----------+ +------------+ +------------------+ +--------------+ +----------------+ +--------------------+ +----------+\n", - "subject5 1 1 0 center_button ctrl_center_button 2 \n", - "subject5 1 1 0 center_button ctrl_center_button 3 \n", - "subject5 1 1 0 center_button ctrl_center_button 4 \n", - "subject5 1 1 0 center_button ctrl_center_button 7 \n", - "subject5 1 1 0 center_button ctrl_center_button 8 \n", - "subject5 1 1 0 center_button ctrl_center_button 10 \n", - "subject5 1 1 0 center_button ctrl_center_button 17 \n", - "subject5 1 1 0 center_button ctrl_center_button 19 \n", - "subject5 1 1 0 center_button ctrl_center_button 21 \n", - "subject5 1 1 0 center_button ctrl_center_button 22 \n", - "subject5 1 1 0 center_button ctrl_center_button 24 \n", - "subject5 1 1 0 center_button ctrl_center_button 25 \n", - "subject5 1 1 0 center_button ctrl_center_button 27 \n", - "subject5 1 1 0 center_button ctrl_center_button 30 \n", - "subject5 1 1 0 center_button ctrl_center_button 33 \n", - " ...\n", - " (Total: 18)" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "analysis.SpikesAlignmentCondition.Trial & 'trial_condition = \"ctrl_center_button\"'" - ] - }, - { - "cell_type": "markdown", - "id": "1486add8", - "metadata": {}, - "source": [ - "### Now let's run the computation on these" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "f467d3f7", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "SpikesAlignment: 100%|███████████████████████████████████████████████████████████████████| 2/2 [00:13<00:00, 6.97s/it]\n" - ] - } - ], - "source": [ - "analysis.SpikesAlignment.populate(display_progress=True)" - ] - }, - { - "cell_type": "markdown", - "id": "c4320cdd", - "metadata": {}, - "source": [ - "### Let's visualize the results" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "2c4962b7", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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2vQ0to3EmWTvxPkHP5h3NbDP0zgvodbanbbw8Gsl+Yeq2i/dFguvba29eZjKAGCbDX06qkXex1nSuNzGE+eLL553NbDP0zgvoap6WGvtpKr67dVub6395VqPba39V7pNvAFg3zNQ4pyka5wXcR9SiTOJfZxhYBrn5XSl/v+D51oFAMzakInzaBqLluxKnEns4wwD0zgvodbaC/v/nz7vWgBgpiTOLAuJMwzKUbWXWFXdsKreVFXnVdXXquqfq+qG864LALZFa6ufjmoTadyDHvSgPOjAAxcz0ZM4k0icYWAa5+X2yiSvTXfAr2sneV2SV821IgDYLq11/w+QOD/2sY/NYy9/+cVM9Eap+qLVxWxJnGFQGufldlBr7eWttUv6fyclucK8iwKAbTFqHgZInC+66KJcdMkli5nojfbjXrS6mC2JMwzKPs7L7W1V9aQkr053/uYHJ3lrVV0jSVpr35hncQAwqFHzMEDifM973jP57ndzyiImehJnEokzDEzjvNwe1P//q+ka5ySpJA/pL9vfGYCdY8DEOUm36fciJnoSZxKJMwzMptrL7feSHNVau0GSv09yepKfb63doLWmaQZgZxkwcU6y2EfVXsS6mC2JMwxK47zcntpa+3ZV3SHJnZO8OMkL5lwTAGyPoRPn0TQWLdGTOJNInGFgGuflNvr58V5J/q619pYkB86xHgDYPmslzlvZVHuU6C1SYzKeOC9SXcyWcQCD0jgvty9X1Quz56Bgl48xAcBOtVbivInNWI877rgcN5rGfvst1qaw44nzItXFbBkHMChN0nJ7UJJ3JLlba+2CJNdI8jtzrQgAtsuAifNxj3xk1zgvYqInaSQxDmBgjqq9xFprFyX5x7HL5yY5d34VAcA2GjUPAyTO53/ta0mSQxfx4EujfVsXrS5my8HBYFAaZwBgOYyahwES5wc8qDuj4ymLePClUcO0aHUxWw4OBoOyqTYAsByGPB1Va3umsWiJnsSZROIMA9M4AwDLYcjTUY0a50VM9CTOJBJnGJjGGQBYDkMmziOLmOhJnEkkzjAwjTMAsBwkziwTiTMMSuMMACyHARPnX3vUo/Jro2ksWqIncSaROMPAHFUbAFgOAybOD77vffdMY9ESPYkzicQZBiZxZipVdURV/VtVfaKqzqiqx827JgDYkAET57PPPjtnj6axaImexJlE4gwD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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'ctrl_center_button'}\n", - "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "af466c68", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", - "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6c0f1691", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:wt-ephys_no_curation]", - "language": "python", - "name": "conda-env-wt-ephys_no_curation-py" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/07-downstream-analysis.ipynb b/notebooks/07-downstream-analysis.ipynb index f04d9985..75ee442b 100644 --- a/notebooks/07-downstream-analysis.ipynb +++ b/notebooks/07-downstream-analysis.ipynb @@ -13,7 +13,9 @@ { "cell_type": "markdown", "id": "15ba9ad8-f0e4-48ec-b959-324c35c7581b", - "metadata": {}, + "metadata": { + "tags": [] + }, "source": [ "## Setup" ] @@ -52,15 +54,20 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "id": "79cef246", "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Connecting cbroz@tutorial-db.datajoint.io:3306\n" + "ename": "ImportError", + "evalue": "cannot import name 'trial' from 'element_trial' (unknown location)", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mImportError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_13886/1603087128.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0mworkflow_array_ephys\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpipeline\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0msession\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mephys\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtrial\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mevent\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m/Volumes/GoogleDrive/My Drive/NWB/workflow-array-ephys/workflow_array_ephys/pipeline.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0melement_session\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0msession_with_datetime\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0msession\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0melement_array_ephys\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mprobe\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0melement_trial\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mtrial\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mevent\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 8\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0melement_animal\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msubject\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mSubject\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mImportError\u001b[0m: cannot import name 'trial' from 'element_trial' (unknown location)" ] } ], @@ -71,7 +78,10 @@ { "cell_type": "markdown", "id": "04616e30-c9f8-468c-bed1-0d233ab76617", - "metadata": {}, + "metadata": { + "incorrectly_encoded_metadata": "jp-MarkdownHeadingCollapsed=true", + "tags": [] + }, "source": [ "## Trial and Event schemas" ] @@ -987,827 +997,136 @@ "from workflow_array_ephys import analysis" ] }, - { - "cell_type": "markdown", - "id": "a2e97d75", - "metadata": {}, - "source": [ - "+ ***SpikesAlignmentCondition*** - a manual table to specify the inputs and condition for the analysis\n", - "+ ***SpikesAlignment*** - a computed table to extract event-aligned spikes and compute unit PSTH" - ] - }, - { - "cell_type": "markdown", - "id": "ba4607a1", - "metadata": {}, - "source": [ - "Let's start by creating several analyses configuration - i.e. inserting into ***SpikesAlignmentCondition*** for the `center` event, called `center_button` in the `AlignmentEvent` table." - ] - }, { "cell_type": "code", - "execution_count": 16, - "id": "cb866198-ca64-4fd3-a5d9-eb03f19e61e3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " time_shift is seconds to shift with respect to (WRT) a variable\n", - "
\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

alignment_name

\n", - " \n", - "
\n", - "

alignment_description

\n", - " \n", - "
\n", - "

alignment_event_type

\n", - " \n", - "
\n", - "

alignment_time_shift

\n", - " (s) WRT alignment_event_type\n", - "
\n", - "

start_event_type

\n", - " \n", - "
\n", - "

start_time_shift

\n", - " (s) WRT start_event_type\n", - "
\n", - "

end_event_type

\n", - " \n", - "
\n", - "

end_time_shift

\n", - " (s) WRT end_event_type\n", - "
center_buttoncenter0.0center-3.0center3.0
left_buttonleft0.0left-3.0left3.0
right_buttonright0.0right-3.0right3.0
\n", - " \n", - "

Total: 3

\n", - " " - ], - "text/plain": [ - "*alignment_nam alignment_desc alignment_even alignment_time start_event_ty start_time_shi end_event_type end_time_shift\n", - "+------------+ +------------+ +------------+ +------------+ +------------+ +------------+ +------------+ +------------+\n", - "center_button center 0.0 center -3.0 center 3.0 \n", - "left_button left 0.0 left -3.0 left 3.0 \n", - "right_button right 0.0 right -3.0 right 3.0 \n", - " (Total: 3)" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "event.AlignmentEvent()" - ] + "execution_count": null, + "id": "be6d368a", + "metadata": { + "title": "***SpikesAlignmentCondition*** - a manual table to specify the inputs and condition for the analysis" + }, + "outputs": [], + "source": [] }, { "cell_type": "code", - "execution_count": 18, - "id": "d859203f", - "metadata": {}, + "execution_count": null, + "id": "6b113f1e", + "metadata": { + "lines_to_next_cell": 0, + "title": "***SpikesAlignment*** - a computed table to extract event-aligned spikes and compute unit PSTH" + }, "outputs": [], "source": [ + "\n", + "# Let's start by creating several analyses configuration - i.e. inserting into ***SpikesAlignmentCondition*** for the `center` event, called `center_button` in the `AlignmentEvent` table.\n", + "\n", + "event.AlignmentEvent()\n", + "\n", "alignment_key = (event.AlignmentEvent & 'alignment_name = \"center_button\"'\n", " ).fetch1('KEY')\n", "alignment_condition = {**clustering_key, **alignment_key, \n", " 'trial_condition': 'ctrl_center_button'}\n", - "analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True)" + "analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True)\n", + "\n", + "analysis.SpikesAlignmentCondition.Trial.insert(\n", + " (analysis.SpikesAlignmentCondition * ctrl_trials & alignment_condition).proj(),\n", + " skip_duplicates=True)\n", + "\n", + "analysis.SpikesAlignmentCondition.Trial()\n", + "\n", + "# With the steps above, we have create a new spike alignment condition for analysis, named `ctrl_center_button`, which specifies:" ] }, { "cell_type": "code", - "execution_count": 19, - "id": "8994e8f5", - "metadata": {}, + "execution_count": null, + "id": "071fc809", + "metadata": { + "title": "a CuratedClustering of interest for analysis" + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27558ed5", + "metadata": { + "title": "an event of interest to align the spikes to - `center_button`" + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f5f3fec8", + "metadata": { + "lines_to_next_cell": 0, + "title": "a set of trials of interest to perform the analysis on - `ctrl` trials" + }, "outputs": [], "source": [ - "analysis.SpikesAlignmentCondition.Trial.insert(\n", - " (analysis.SpikesAlignmentCondition * ctrl_trials & alignment_condition).proj(),\n", - " skip_duplicates=True)" + "\n", + "# Now, let's create another set with:" ] }, { "cell_type": "code", - "execution_count": 20, - "id": "0c0d093a-f2ae-4474-941c-fae5fcf74f83", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " Trials (or subset of trials) to computed event-aligned spikes and PSTH on\n", - "
\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

subject

\n", - " \n", - "
\n", - "

session_datetime

\n", - " \n", - "
\n", - "

insertion_number

\n", - " \n", - "
\n", - "

paramset_idx

\n", - " \n", - "
\n", - "

curation_id

\n", - " \n", - "
\n", - "

alignment_name

\n", - " \n", - "
\n", - "

trial_condition

\n", - " user-friendly name of condition\n", - "
\n", - "

trial_id

\n", - " trial number (1-based indexing)\n", - "
subject62021-01-15 11:16:38001center_buttonctrl_center_button2
subject62021-01-15 11:16:38001center_buttonctrl_center_button3
subject62021-01-15 11:16:38001center_buttonctrl_center_button4
subject62021-01-15 11:16:38001center_buttonctrl_center_button7
subject62021-01-15 11:16:38001center_buttonctrl_center_button8
subject62021-01-15 11:16:38001center_buttonctrl_center_button9
subject62021-01-15 11:16:38001center_buttonctrl_center_button13
subject62021-01-15 11:16:38001center_buttonctrl_center_button14
subject62021-01-15 11:16:38001center_buttonctrl_center_button15
subject62021-01-15 11:16:38001center_buttonctrl_center_button16
\n", - "

...

\n", - "

Total: 50

\n", - " " - ], - "text/plain": [ - "*subject *session_datet *insertion_num *paramset_idx *curation_id *alignment_nam *trial_conditi *trial_id \n", - "+----------+ +------------+ +------------+ +------------+ +------------+ +------------+ +------------+ +----------+\n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 2 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 3 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 4 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 7 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 8 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 9 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 13 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 14 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 15 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 16 \n", - " ...\n", - " (Total: 50)" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "analysis.SpikesAlignmentCondition.Trial()" - ] - }, - { - "cell_type": "markdown", - "id": "4ddd0345", - "metadata": {}, - "source": [ - "With the steps above, we have create a new spike alignment condition for analysis, named `ctrl_center_button`, which specifies:\n", - "+ a CuratedClustering of interest for analysis\n", - "+ an event of interest to align the spikes to - `center_button`\n", - "+ a set of trials of interest to perform the analysis on - `ctrl` trials" - ] + "execution_count": null, + "id": "c4ea0bd1", + "metadata": { + "title": "the same CuratedClustering of interest for analysis" + }, + "outputs": [], + "source": [] }, { - "cell_type": "markdown", - "id": "2a23b206", - "metadata": {}, - "source": [ - "Now, let's create another set with:\n", - "+ the same CuratedClustering of interest for analysis\n", - "+ an event of interest to align the spikes to - `center_button`\n", - "+ a set of trials of interest to perform the analysis on - `stim` trials" - ] + "cell_type": "code", + "execution_count": null, + "id": "598fce99", + "metadata": { + "title": "an event of interest to align the spikes to - `center_button`" + }, + "outputs": [], + "source": [] }, { "cell_type": "code", - "execution_count": 21, - "id": "39d4d423", - "metadata": {}, + "execution_count": null, + "id": "4c66992f", + "metadata": { + "title": "a set of trials of interest to perform the analysis on - `stim` trials" + }, "outputs": [], "source": [ + "\n", "stim_trials = trial.Trial & clustering_key & 'trial_type = \"stim\"'\n", "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", "analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True)\n", "analysis.SpikesAlignmentCondition.Trial.insert(\n", " (analysis.SpikesAlignmentCondition * stim_trials & alignment_condition).proj(),\n", - " skip_duplicates=True)" - ] - }, - { - "cell_type": "markdown", - "id": "bbdd4984-2439-44a4-a72f-ecf52b64d17b", - "metadata": {}, - "source": [ - "We can compare conditions in the `SpikesAlignmentCondition` table." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "6452c508", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - "
\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

subject

\n", - " \n", - "
\n", - "

session_datetime

\n", - " \n", - "
\n", - "

insertion_number

\n", - " \n", - "
\n", - "

paramset_idx

\n", - " \n", - "
\n", - "

curation_id

\n", - " \n", - "
\n", - "

alignment_name

\n", - " \n", - "
\n", - "

trial_condition

\n", - " user-friendly name of condition\n", - "
\n", - "

condition_description

\n", - " \n", - "
\n", - "

bin_size

\n", - " bin-size (in second) used to compute the PSTH\n", - "
subject62021-01-15 11:16:38001center_buttonctrl_center_button0.04
subject62021-01-15 11:16:38001center_buttonstim_center_button0.04
\n", - " \n", - "

Total: 2

\n", - " " - ], - "text/plain": [ - "*subject *session_datet *insertion_num *paramset_idx *curation_id *alignment_nam *trial_conditi condition_desc bin_size \n", - "+----------+ +------------+ +------------+ +------------+ +------------+ +------------+ +------------+ +------------+ +----------+\n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 0.04 \n", - "subject6 2021-01-15 11: 0 0 1 center_button stim_center_bu 0.04 \n", - " (Total: 2)" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "analysis.SpikesAlignmentCondition()" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "7a9ef2fb", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " Trials (or subset of trials) to computed event-aligned spikes and PSTH on\n", - "
\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

subject

\n", - " \n", - "
\n", - "

session_datetime

\n", - " \n", - "
\n", - "

insertion_number

\n", - " \n", - "
\n", - "

paramset_idx

\n", - " \n", - "
\n", - "

curation_id

\n", - " \n", - "
\n", - "

alignment_name

\n", - " \n", - "
\n", - "

trial_condition

\n", - " user-friendly name of condition\n", - "
\n", - "

trial_id

\n", - " trial number (1-based indexing)\n", - "
subject62021-01-15 11:16:38001center_buttonctrl_center_button2
subject62021-01-15 11:16:38001center_buttonctrl_center_button3
subject62021-01-15 11:16:38001center_buttonctrl_center_button4
subject62021-01-15 11:16:38001center_buttonctrl_center_button7
subject62021-01-15 11:16:38001center_buttonctrl_center_button8
subject62021-01-15 11:16:38001center_buttonctrl_center_button9
subject62021-01-15 11:16:38001center_buttonctrl_center_button13
subject62021-01-15 11:16:38001center_buttonctrl_center_button14
subject62021-01-15 11:16:38001center_buttonctrl_center_button15
subject62021-01-15 11:16:38001center_buttonctrl_center_button16
\n", - "

...

\n", - "

Total: 50

\n", - " " - ], - "text/plain": [ - "*subject *session_datet *insertion_num *paramset_idx *curation_id *alignment_nam *trial_conditi *trial_id \n", - "+----------+ +------------+ +------------+ +------------+ +------------+ +------------+ +------------+ +----------+\n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 2 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 3 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 4 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 7 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 8 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 9 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 13 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 14 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 15 \n", - "subject6 2021-01-15 11: 0 0 1 center_button ctrl_center_bu 16 \n", - " ...\n", - " (Total: 50)" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "analysis.SpikesAlignmentCondition.Trial & 'trial_condition = \"ctrl_center_button\"'" - ] - }, - { - "cell_type": "markdown", - "id": "1486add8", - "metadata": {}, - "source": [ - "### Computation" - ] - }, - { - "cell_type": "markdown", - "id": "7f0f7c3f-88a1-4930-9abe-b2fc7f9fe3c3", - "metadata": {}, - "source": [ - "Now let's run the computation on these." - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "f467d3f7", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "SpikesAlignment: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 2/2 [00:16<00:00, 8.11s/it]\n" - ] - } - ], - "source": [ - "analysis.SpikesAlignment.populate(display_progress=True)" - ] - }, - { - "cell_type": "markdown", - "id": "c4320cdd", - "metadata": {}, - "source": [ - "### Vizualize" - ] - }, - { - "cell_type": "markdown", - "id": "7ba0a59e-cd84-48ae-a7fd-9324e0a73af7", - "metadata": {}, - "source": [ - "We can visualize the results with the `plot_raster` function." - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "2c4962b7", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ + " skip_duplicates=True)\n", + "\n", + "# We can compare conditions in the `SpikesAlignmentCondition` table.\n", + "\n", + "analysis.SpikesAlignmentCondition()\n", + "\n", + "analysis.SpikesAlignmentCondition.Trial & 'trial_condition = \"ctrl_center_button\"'\n", + "\n", + "# ### Computation\n", + "\n", + "# Now let's run the computation on these.\n", + "\n", + "analysis.SpikesAlignment.populate(display_progress=True)\n", + "\n", + "# ### Vizualize\n", + "\n", + "# We can visualize the results with the `plot_raster` function.\n", + "\n", "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'ctrl_center_button'}\n", - "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "af466c68", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ + "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);\n", + "\n", "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);" ] diff --git a/notebooks/08-electrode-localization.ipynb b/notebooks/08-electrode-localization.ipynb index 32b312b3..759e6051 100644 --- a/notebooks/08-electrode-localization.ipynb +++ b/notebooks/08-electrode-localization.ipynb @@ -32,8 +32,7 @@ { "cell_type": "markdown", "metadata": { - "incorrectly_encoded_metadata": "tags=[] jp-MarkdownHeadingCollapsed=true", - "jp-MarkdownHeadingCollapsed": true, + "incorrectly_encoded_metadata": "tags=[] jp-MarkdownHeadingCollapsed=true jp-MarkdownHeadingCollapsed=true", "tags": [] }, "source": [ @@ -455,20 +454,225 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from workflow_array_ephys.localization import coordinate_framework as ccf\n", + "from workflow_array_ephys.localization import electrode_localization as eloc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Because the probe may not be fully inserted, there will be some electrode positions that occur outside the brain. We register these instances with an `IntegrityError` warning because we're trying to register a coorinate position with no corresponding location in the `ccf.CCF.Voxel` table. We can silence these warnings by setting the log level before running `populate()` on the `ElectrodePosition` table." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "logging.getLogger().setLevel(logging.ERROR) # or logging.INFO" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "eloc.ElectrodePosition.populate()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By calling the `ElectrodePosition` table, we can see the keys the `populate()` method has already processed." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_datetime

\n", + " \n", + "
\n", + "

insertion_number

\n", + " \n", + "
\n", + "

ccf_id

\n", + " CCF ID, a.k.a atlas ID\n", + "
subject52018-07-03 20:32:2810
subject52018-07-03 20:32:2820
subject62021-01-15 11:16:3800
\n", + " \n", + "

Total: 3

\n", + " " + ], + "text/plain": [ + "*subject *session_datet *insertion_num *ccf_id \n", + "+----------+ +------------+ +------------+ +--------+\n", + "subject5 2018-07-03 20: 1 0 \n", + "subject5 2018-07-03 20: 2 0 \n", + "subject6 2021-01-15 11: 0 0 \n", + " (Total: 3)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eloc.ElectrodePosition()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's focus on `subject5`, insertion `1`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "373" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from workflow_array_ephys.pipeline import ephys\n", + "key=(ephys.EphysRecording & 'subject=\"subject5\"' & 'insertion_number=1').fetch1('KEY')\n", + "len(eloc.ElectrodePosition.Electrode & key)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With a resolution of 100μm, adjacent electrodes will very likely be in the same region. Let's look at every 38th electrode to sample 10 across the probe.\n", + "\n", + "If you're interested in more electrodes, decrease the number next to the `%` modulo operator." + ] + }, + { + "cell_type": "code", + "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Connecting cbroz@dss-db.datajoint.io:3306\n" + "Electrode 0 (x=11900, y=6300, z=6900) is in Primary auditory area\n", + "Electrode 38 (x=11900, y=6300, z=6500) is in Primary auditory area\n", + "Electrode 76 (x=11800, y=6300, z=6100) is in Declive (VI) Purkinje layer\n", + "Electrode 114 (x=11700, y=6400, z=5600) is in Lobules IV-V molecular layer\n", + "Electrode 152 (x=11700, y=6400, z=5200) is in Declive (VI) Purkinje layer\n", + "Electrode 190 (x=11600, y=6500, z=4700) is in Primary auditory area\n", + "Electrode 228 (x=11600, y=6500, z=4300) is in Granular lamina of the cochlear nuclei\n", + "Electrode 266 (x=11600, y=6600, z=3900) is in Cuneiform nucleus\n", + "Electrode 304 (x=11600, y=6600, z=3500) is in Lateral hypothalamic area\n", + "Electrode 342 (x=11600, y=6700, z=3100) is in root\n", + "Electrode 380 (x=11600, y=6700, z=2800) is in root\n" ] } ], "source": [ - "from workflow_array_ephys.localization import coordinate_framework as ccf\n", - "from workflow_array_ephys.localization import electrode_localization as eloc" + "electrode_coordinates = (eloc.ElectrodePosition.Electrode & 'electrode%38=0' \n", + " & key).fetch('electrode', 'x', 'y', 'z', as_dict=True)\n", + "for e in electrode_coordinates:\n", + " x, y, z = [ e[k] for k in ('x','y', 'z')]\n", + " acronym = (ccf.BrainRegionAnnotation.Voxel & f'x={x}' & f'y={y}' & f'z={z}'\n", + " ).fetch1('acronym')\n", + " e['region'] = (ccf.BrainRegionAnnotation.BrainRegion & f'acronym=\\\"{acronym}\\\"'\n", + " ).fetch1('region_name')\n", + " print('Electrode {electrode} (x={x}, y={y}, z={z}) is in {region}'.format(**e))" ] }, { @@ -476,10 +680,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "elocBrainRegionAnnotationodePosition.populate()\n", - "eloc.ElectrodePosition.Electrode()" - ] + "source": [] } ], "metadata": { diff --git a/notebooks/py_scripts/07-downstream-analysis.py b/notebooks/py_scripts/07-downstream-analysis.py index c5a05a64..cc524f37 100644 --- a/notebooks/py_scripts/07-downstream-analysis.py +++ b/notebooks/py_scripts/07-downstream-analysis.py @@ -15,9 +15,10 @@ # + [markdown] tags=[] # # DataJoint U24 - Workflow Array Electrophysiology -# - +# + [markdown] tags=[] # ## Setup +# - # First, let's change directories to find the `dj_local_conf` file. @@ -33,7 +34,9 @@ from workflow_array_ephys.pipeline import session, ephys, trial, event +# + [markdown] jp-MarkdownHeadingCollapsed=true tags=[] # ## Trial and Event schemas +# - # Tables in the `trial` and `event` schemas specify the structure of your experiment, including block, trial and event timing. # - Session has a 1-to-1 mapping with a behavior recording @@ -97,6 +100,8 @@ from workflow_array_ephys import analysis # + ***SpikesAlignmentCondition*** - a manual table to specify the inputs and condition for the analysis + + # + ***SpikesAlignment*** - a computed table to extract event-aligned spikes and compute unit PSTH # Let's start by creating several analyses configuration - i.e. inserting into ***SpikesAlignmentCondition*** for the `center` event, called `center_button` in the `AlignmentEvent` table. @@ -117,12 +122,20 @@ # With the steps above, we have create a new spike alignment condition for analysis, named `ctrl_center_button`, which specifies: # + a CuratedClustering of interest for analysis + + # + an event of interest to align the spikes to - `center_button` + + # + a set of trials of interest to perform the analysis on - `ctrl` trials # Now, let's create another set with: # + the same CuratedClustering of interest for analysis + + # + an event of interest to align the spikes to - `center_button` + + # + a set of trials of interest to perform the analysis on - `stim` trials stim_trials = trial.Trial & clustering_key & 'trial_type = "stim"' diff --git a/notebooks/py_scripts/08-electrode-localization.py b/notebooks/py_scripts/08-electrode-localization.py index fb759658..d4d91d2c 100644 --- a/notebooks/py_scripts/08-electrode-localization.py +++ b/notebooks/py_scripts/08-electrode-localization.py @@ -67,5 +67,35 @@ from workflow_array_ephys.localization import coordinate_framework as ccf from workflow_array_ephys.localization import electrode_localization as eloc -elocBrainRegionAnnotationodePosition.populate() -eloc.ElectrodePosition.Electrode() +# Because the probe may not be fully inserted, there will be some electrode positions that occur outside the brain. We register these instances with an `IntegrityError` warning because we're trying to register a coorinate position with no corresponding location in the `ccf.CCF.Voxel` table. We can silence these warnings by setting the log level before running `populate()` on the `ElectrodePosition` table. + +import logging +logging.getLogger().setLevel(logging.ERROR) # or logging.INFO + +eloc.ElectrodePosition.populate() + +# By calling the `ElectrodePosition` table, we can see the keys the `populate()` method has already processed. + +eloc.ElectrodePosition() + +# Let's focus on `subject5`, insertion `1`. + +from workflow_array_ephys.pipeline import ephys +key=(ephys.EphysRecording & 'subject="subject5"' & 'insertion_number=1').fetch1('KEY') +len(eloc.ElectrodePosition.Electrode & key) + +# With a resolution of 100μm, adjacent electrodes will very likely be in the same region. Let's look at every 38th electrode to sample 10 across the probe. +# +# If you're interested in more electrodes, decrease the number next to the `%` modulo operator. + +electrode_coordinates = (eloc.ElectrodePosition.Electrode & 'electrode%38=0' + & key).fetch('electrode', 'x', 'y', 'z', as_dict=True) +for e in electrode_coordinates: + x, y, z = [ e[k] for k in ('x','y', 'z')] + acronym = (ccf.BrainRegionAnnotation.Voxel & f'x={x}' & f'y={y}' & f'z={z}' + ).fetch1('acronym') + e['region'] = (ccf.BrainRegionAnnotation.BrainRegion & f'acronym=\"{acronym}\"' + ).fetch1('region_name') + print('Electrode {electrode} (x={x}, y={y}, z={z}) is in {region}'.format(**e)) + + diff --git a/workflow_array_ephys/pipeline.py b/workflow_array_ephys/pipeline.py index c974bef4..be632ec6 100644 --- a/workflow_array_ephys/pipeline.py +++ b/workflow_array_ephys/pipeline.py @@ -4,7 +4,7 @@ from element_lab import lab from element_session import session_with_datetime as session from element_array_ephys import probe -from element_trial import trial, event +from element_event import trial, event from element_animal.subject import Subject from element_lab.lab import Source, Lab, Protocol, User, Project From e3afb7befd8c9127d5bbf7f96f3090c578be9731 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 25 Apr 2022 11:11:07 -0500 Subject: [PATCH 28/59] update event notebook --- notebooks/04-automate-optional.ipynb | 188 ++-- notebooks/07-downstream-analysis.ipynb | 1001 +++++++++++++---- notebooks/08-electrode-localization.ipynb | 215 +++- notebooks/py_scripts/04-automate-optional.py | 6 +- .../py_scripts/07-downstream-analysis.py | 62 +- .../py_scripts/08-electrode-localization.py | 2 + 6 files changed, 1096 insertions(+), 378 deletions(-) diff --git a/notebooks/04-automate-optional.ipynb b/notebooks/04-automate-optional.ipynb index 0dfdd34d..3bb8eeae 100644 --- a/notebooks/04-automate-optional.ipynb +++ b/notebooks/04-automate-optional.ipynb @@ -22,7 +22,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Connecting root@localhost:3306\n" + "Connecting cbroz@dss-db.datajoint.io:3306\n" ] } ], @@ -45,7 +45,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -61,7 +61,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -69,7 +69,7 @@ "output_type": "stream", "text": [ "\n", - "---- Insert 1 entry(s) into subject.Subject ----\n" + "---- Inserting 0 entry(s) into subject ----\n" ] } ], @@ -86,20 +86,20 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\n", - "---- Insert 0 entry(s) into session.Session ----\n", "\n", "---- Insert 0 entry(s) into probe.Probe ----\n", "\n", "---- Insert 0 entry(s) into ephys.ProbeInsertion ----\n", "\n", + "---- Insert 0 entry(s) into session.Session ----\n", + "\n", "---- Successfully completed workflow_array_ephys/ingest.py ----\n" ] } @@ -121,6 +121,7 @@ "cell_type": "code", "execution_count": 7, "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false }, @@ -128,7 +129,20 @@ "name": "#%%\n" } }, - "outputs": [], + "outputs": [ + { + "ename": "DataJointError", + "evalue": "The specified paramset_idx 0 already exists, please pick a different one.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mDataJointError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_29486/3976263307.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 23\u001b[0m \u001b[0;34m\"useRAM\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 24\u001b[0m }\n\u001b[0;32m---> 25\u001b[0;31m ephys.ClusteringParamSet.insert_new_params(\n\u001b[0m\u001b[1;32m 26\u001b[0m \u001b[0mclustering_method\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'kilosort2'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 27\u001b[0m \u001b[0mparamset_idx\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/Volumes/GoogleDrive/My Drive/NWB/element-array-ephys/element_array_ephys/ephys_no_curation.py\u001b[0m in \u001b[0;36minsert_new_params\u001b[0;34m(cls, clustering_method, paramset_desc, params, paramset_idx)\u001b[0m\n\u001b[1;32m 470\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 471\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m'paramset_idx'\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mparamset_idx\u001b[0m\u001b[0;34m}\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mcls\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mproj\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 472\u001b[0;31m raise dj.DataJointError(\n\u001b[0m\u001b[1;32m 473\u001b[0m \u001b[0;34mf'The specified paramset_idx {paramset_idx} already exists,'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 474\u001b[0m f' please pick a different one.')\n", + "\u001b[0;31mDataJointError\u001b[0m: The specified paramset_idx 0 already exists, please pick a different one." + ] + } + ], "source": [ "params_ks = {\n", " \"fs\": 30000,\n", @@ -155,7 +169,7 @@ " \"useRAM\": 0\n", "}\n", "ephys.ClusteringParamSet.insert_new_params(\n", - " processing_method='kilosort2',\n", + " clustering_method='kilosort2',\n", " paramset_idx=0,\n", " params=params_ks,\n", " paramset_desc='Spike sorting using Kilosort2')" @@ -186,7 +200,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -201,11 +215,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "EphysRecording: 0it [00:00, ?it/s]\n", - "LFP: 0it [00:00, ?it/s]\n", - "Clustering: 0it [00:00, ?it/s]\n", - "CuratedClustering: 0it [00:00, ?it/s]\n", - "WaveformSet: 0it [00:00, ?it/s]" + "EphysRecording: 0it [00:00, ?it/s]\n" ] }, { @@ -213,11 +223,50 @@ "output_type": "stream", "text": [ "\n", - "---- Populate ephys.LFP ----\n", + "---- Populate ephys.LFP ----\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "LFP: 0it [00:00, ?it/s]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "\n", - "---- Populate ephys.Clustering ----\n", + "---- Populate ephys.Clustering ----\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Clustering: 0it [00:00, ?it/s]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "\n", - "---- Populate ephys.CuratedClustering ----\n", + "---- Populate ephys.CuratedClustering ----\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "CuratedClustering: 100%|██████████████████████████| 1/1 [00:07<00:00, 7.99s/it]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "\n", "---- Populate ephys.WaveformSet ----\n" ] @@ -226,7 +275,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "\n" + "WaveformSet: 0%| | 0/1 [00:00\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0mworkflow_array_ephys\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpipeline\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0msession\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mephys\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtrial\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mevent\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m/Volumes/GoogleDrive/My Drive/NWB/workflow-array-ephys/workflow_array_ephys/pipeline.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0melement_session\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0msession_with_datetime\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0msession\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0melement_array_ephys\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mprobe\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0melement_trial\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mtrial\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mevent\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 8\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0melement_animal\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msubject\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mSubject\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mImportError\u001b[0m: cannot import name 'trial' from 'element_trial' (unknown location)" - ] - } - ], + "outputs": [], "source": [ - "from workflow_array_ephys.pipeline import session, ephys, trial, event" + "from workflow_array_ephys.pipeline import session, ephys, trial, event\n", + "from workflow_array_ephys import analysis" ] }, { @@ -80,6 +68,7 @@ "id": "04616e30-c9f8-468c-bed1-0d233ab76617", "metadata": { "incorrectly_encoded_metadata": "jp-MarkdownHeadingCollapsed=true", + "jp-MarkdownHeadingCollapsed": true, "tags": [] }, "source": [ @@ -125,7 +114,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "dab268a4-ae07-4c8b-b999-536d9e5b3e2e", "metadata": {}, "outputs": [], @@ -147,8 +136,6 @@ "\n", "---- Inserting 0 entry(s) into subject ----\n", "\n", - "---- Insert 0 entry(s) into session.Session ----\n", - "\n", "---- Insert 0 entry(s) into probe.Probe ----\n", "\n", "---- Insert 0 entry(s) into ephys.ProbeInsertion ----\n", @@ -165,7 +152,7 @@ "\n", "---- Inserting 4 entry(s) into _block__attribute ----\n", "\n", - "---- Inserting 2 entry(s) into #trial_type ----\n", + "---- Inserting 0 entry(s) into #trial_type ----\n", "\n", "---- Inserting 100 entry(s) into _trial ----\n", "\n", @@ -173,7 +160,7 @@ "\n", "---- Inserting 100 entry(s) into _block_trial ----\n", "\n", - "---- Inserting 3 entry(s) into #event_type ----\n", + "---- Inserting 0 entry(s) into #event_type ----\n", "\n", "---- Inserting 153 entry(s) into _event ----\n", "\n", @@ -274,77 +261,77 @@ "

trial_stop_time

\n", " (second) relative to recording start\n", " \n", - " subject6\n", - "2021-01-15 11:16:38\n", + " subject3\n", + "2021-10-25 13:06:40\n", "1\n", "stim\n", - "0.123\n", - "17.123subject6\n", - "2021-01-15 11:16:38\n", + "0.285\n", + "20.285subject3\n", + "2021-10-25 13:06:40\n", "2\n", "ctrl\n", - "17.54\n", - "34.54subject6\n", - "2021-01-15 11:16:38\n", + "23.927\n", + "43.927subject3\n", + "2021-10-25 13:06:40\n", "3\n", - "ctrl\n", - "34.81\n", - "51.81subject6\n", - "2021-01-15 11:16:38\n", + "stim\n", + "47.622\n", + "67.622subject3\n", + "2021-10-25 13:06:40\n", "4\n", "ctrl\n", - "52.202\n", - "69.202subject6\n", - "2021-01-15 11:16:38\n", + "71.443\n", + "91.443subject3\n", + "2021-10-25 13:06:40\n", "5\n", - "stim\n", - "69.611\n", - "86.611subject6\n", - "2021-01-15 11:16:38\n", + "ctrl\n", + "95.385\n", + "115.385subject3\n", + "2021-10-25 13:06:40\n", "6\n", - "stim\n", - "87.03\n", - "104.03subject6\n", - "2021-01-15 11:16:38\n", + "ctrl\n", + "119.331\n", + "139.331subject3\n", + "2021-10-25 13:06:40\n", "7\n", "ctrl\n", - "104.165\n", - "121.165subject6\n", - "2021-01-15 11:16:38\n", + "143.254\n", + "163.254subject3\n", + "2021-10-25 13:06:40\n", "8\n", - "ctrl\n", - "121.502\n", - "138.502subject6\n", - "2021-01-15 11:16:38\n", + "stim\n", + "166.86\n", + "186.86subject3\n", + "2021-10-25 13:06:40\n", "9\n", "ctrl\n", - "138.612\n", - "155.612subject6\n", - "2021-01-15 11:16:38\n", + "190.465\n", + "210.465subject3\n", + "2021-10-25 13:06:40\n", "10\n", - "stim\n", - "155.741\n", - "172.741 \n", + "ctrl\n", + "214.395\n", + "234.395 \n", " \n", "

...

\n", - "

Total: 100

\n", + "

Total: 140

\n", " " ], "text/plain": [ "*subject *session_datet *trial_id trial_type trial_start_ti trial_stop_tim\n", "+----------+ +------------+ +----------+ +------------+ +------------+ +------------+\n", - "subject6 2021-01-15 11: 1 stim 0.123 17.123 \n", - "subject6 2021-01-15 11: 2 ctrl 17.54 34.54 \n", - "subject6 2021-01-15 11: 3 ctrl 34.81 51.81 \n", - "subject6 2021-01-15 11: 4 ctrl 52.202 69.202 \n", - "subject6 2021-01-15 11: 5 stim 69.611 86.611 \n", - "subject6 2021-01-15 11: 6 stim 87.03 104.03 \n", - "subject6 2021-01-15 11: 7 ctrl 104.165 121.165 \n", - "subject6 2021-01-15 11: 8 ctrl 121.502 138.502 \n", - "subject6 2021-01-15 11: 9 ctrl 138.612 155.612 \n", - "subject6 2021-01-15 11: 10 stim 155.741 172.741 \n", + "subject3 2021-10-25 13: 1 stim 0.285 20.285 \n", + "subject3 2021-10-25 13: 2 ctrl 23.927 43.927 \n", + "subject3 2021-10-25 13: 3 stim 47.622 67.622 \n", + "subject3 2021-10-25 13: 4 ctrl 71.443 91.443 \n", + "subject3 2021-10-25 13: 5 ctrl 95.385 115.385 \n", + "subject3 2021-10-25 13: 6 ctrl 119.331 139.331 \n", + "subject3 2021-10-25 13: 7 ctrl 143.254 163.254 \n", + "subject3 2021-10-25 13: 8 stim 166.86 186.86 \n", + "subject3 2021-10-25 13: 9 ctrl 190.465 210.465 \n", + "subject3 2021-10-25 13: 10 ctrl 214.395 234.395 \n", " ...\n", - " (Total: 100)" + " (Total: 140)" ] }, "execution_count": 5, @@ -442,51 +429,67 @@ "

event_start_time

\n", " (second) relative to recording start\n", " \n", - " subject6\n", - "2021-01-15 11:16:38\n", + " subject3\n", + "2021-10-25 13:06:40\n", "1\n", "center\n", - "10.58subject6\n", - "2021-01-15 11:16:38\n", - "2\n", + "2.794subject3\n", + "2021-10-25 13:06:40\n", + "1\n", "center\n", - "21.647subject6\n", - "2021-01-15 11:16:38\n", + "13.252subject3\n", + "2021-10-25 13:06:40\n", "3\n", "center\n", - "37.044subject6\n", - "2021-01-15 11:16:38\n", + "59.234subject3\n", + "2021-10-25 13:06:40\n", "4\n", "center\n", - "55.259subject6\n", - "2021-01-15 11:16:38\n", + "76.33subject3\n", + "2021-10-25 13:06:40\n", + "4\n", + "center\n", + "82.185subject3\n", + "2021-10-25 13:06:40\n", + "4\n", + "center\n", + "89.928subject3\n", + "2021-10-25 13:06:40\n", "1\n", "left\n", - "4.498subject6\n", - "2021-01-15 11:16:38\n", + "16.014subject3\n", + "2021-10-25 13:06:40\n", + "3\n", + "left\n", + "52.128subject3\n", + "2021-10-25 13:06:40\n", "3\n", "left\n", - "41.892subject6\n", + "65.688subject6\n", "2021-01-15 11:16:38\n", - "2\n", - "right\n", - "23.9 \n", + "1\n", + "center\n", + "10.58 \n", " \n", - " \n", - "

Total: 7

\n", + "

...

\n", + "

Total: 16

\n", " " ], "text/plain": [ "*subject *session_datet *trial_id *event_type *event_start_t\n", "+----------+ +------------+ +----------+ +------------+ +------------+\n", + "subject3 2021-10-25 13: 1 center 2.794 \n", + "subject3 2021-10-25 13: 1 center 13.252 \n", + "subject3 2021-10-25 13: 3 center 59.234 \n", + "subject3 2021-10-25 13: 4 center 76.33 \n", + "subject3 2021-10-25 13: 4 center 82.185 \n", + "subject3 2021-10-25 13: 4 center 89.928 \n", + "subject3 2021-10-25 13: 1 left 16.014 \n", + "subject3 2021-10-25 13: 3 left 52.128 \n", + "subject3 2021-10-25 13: 3 left 65.688 \n", "subject6 2021-01-15 11: 1 center 10.58 \n", - "subject6 2021-01-15 11: 2 center 21.647 \n", - "subject6 2021-01-15 11: 3 center 37.044 \n", - "subject6 2021-01-15 11: 4 center 55.259 \n", - "subject6 2021-01-15 11: 1 left 4.498 \n", - "subject6 2021-01-15 11: 3 left 41.892 \n", - "subject6 2021-01-15 11: 2 right 23.9 \n", - " (Total: 7)" + " ...\n", + " (Total: 16)" ] }, "execution_count": 6, @@ -517,7 +520,7 @@ "output_type": "stream", "text": [ "\n", - "---- Inserting 3 entry(s) into alignment_event ----\n" + "---- Inserting 0 entry(s) into alignment_event ----\n" ] } ], @@ -617,23 +620,23 @@ "center\n", "0.0\n", "center\n", - "-3.0\n", + "-5.0\n", "center\n", - "3.0left_button\n", + "5.0left_button\n", "\n", "left\n", "0.0\n", "left\n", - "-3.0\n", + "-5.0\n", "left\n", - "3.0right_button\n", + "5.0right_button\n", "\n", "right\n", "0.0\n", "right\n", - "-3.0\n", + "-5.0\n", "right\n", - "3.0 \n", + "5.0 \n", " \n", " \n", "

Total: 3

\n", @@ -642,9 +645,9 @@ "text/plain": [ "*alignment_nam alignment_desc alignment_even alignment_time start_event_ty start_time_shi end_event_type end_time_shift\n", "+------------+ +------------+ +------------+ +------------+ +------------+ +------------+ +------------+ +------------+\n", - "center_button center 0.0 center -3.0 center 3.0 \n", - "left_button left 0.0 left -3.0 left 3.0 \n", - "right_button right 0.0 right -3.0 right 3.0 \n", + "center_button center 0.0 center -5.0 center 5.0 \n", + "left_button left 0.0 left -5.0 left 5.0 \n", + "right_button right 0.0 right -5.0 right 5.0 \n", " (Total: 3)" ] }, @@ -660,9 +663,12 @@ { "cell_type": "markdown", "id": "4936a1e8", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true, + "tags": [] + }, "source": [ - "## Event-aligned trialized unit spike times" + "## Event-aligned spike times" ] }, { @@ -675,7 +681,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "9a36c342", "metadata": {}, "outputs": [ @@ -732,7 +738,7 @@ " }\n", " \n", " \n", - " Clustering results of a curation.\n", + " Clustering results of the spike sorting step.\n", "
\n", " \n", " \n", " \n", "\n", "\n", - "\n", - "\n", + "\n", "
\n", @@ -747,28 +753,24 @@ "
\n", "

paramset_idx

\n", " \n", - "
\n", - "

curation_id

\n", - " \n", "
subject62021-01-15 11:16:38001
0
\n", " \n", "

Total: 1

\n", " " ], "text/plain": [ - "*subject *session_datet *insertion_num *paramset_idx *curation_id \n", - "+----------+ +------------+ +------------+ +------------+ +------------+\n", - "subject6 2021-01-15 11: 0 0 1 \n", + "*subject *session_datet *insertion_num *paramset_idx \n", + "+----------+ +------------+ +------------+ +------------+\n", + "subject6 2021-01-15 11: 0 0 \n", " (Total: 1)" ] }, - "execution_count": 10, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -787,7 +789,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 7, "id": "8642f010", "metadata": {}, "outputs": [], @@ -800,7 +802,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 11, "id": "c9c95806", "metadata": {}, "outputs": [ @@ -952,7 +954,7 @@ " (Total: 100)" ] }, - "execution_count": 13, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -971,7 +973,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 10, "id": "1851ad2b", "metadata": {}, "outputs": [], @@ -989,39 +991,53 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 18, "id": "0678d202", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "['spikes_alignment_condition',\n", + " 'spikes_alignment_condition__trial',\n", + " 'activity_alignment_condition',\n", + " 'activity_alignment_condition__trial',\n", + " '__spikes_alignment',\n", + " '__spikes_alignment__aligned_trial_spikes',\n", + " '__spikes_alignment__unit_p_s_t_h',\n", + " '__activity_alignment',\n", + " '__activity_alignment__aligned_trial_activity']" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "from workflow_array_ephys import analysis" + "analysis.schema.list_tables()" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "be6d368a", + "cell_type": "markdown", + "id": "0fdb3f63-6dda-4489-b2a1-8a9073ff290a", "metadata": { "title": "***SpikesAlignmentCondition*** - a manual table to specify the inputs and condition for the analysis" }, - "outputs": [], - "source": [] + "source": [ + "Let's start by creating several analyses configuration - i.e. inserting into ***SpikesAlignmentCondition*** for the `center` event, called `center_button` in the `AlignmentEvent` table." + ] }, { "cell_type": "code", - "execution_count": null, - "id": "6b113f1e", + "execution_count": 11, + "id": "f76cc104-4925-4e71-822e-81f3643e5625", "metadata": { "lines_to_next_cell": 0, "title": "***SpikesAlignment*** - a computed table to extract event-aligned spikes and compute unit PSTH" }, "outputs": [], "source": [ - "\n", - "# Let's start by creating several analyses configuration - i.e. inserting into ***SpikesAlignmentCondition*** for the `center` event, called `center_button` in the `AlignmentEvent` table.\n", - "\n", - "event.AlignmentEvent()\n", - "\n", "alignment_key = (event.AlignmentEvent & 'alignment_name = \"center_button\"'\n", " ).fetch1('KEY')\n", "alignment_condition = {**clustering_key, **alignment_key, \n", @@ -1030,106 +1046,643 @@ "\n", "analysis.SpikesAlignmentCondition.Trial.insert(\n", " (analysis.SpikesAlignmentCondition * ctrl_trials & alignment_condition).proj(),\n", - " skip_duplicates=True)\n", - "\n", - "analysis.SpikesAlignmentCondition.Trial()\n", - "\n", - "# With the steps above, we have create a new spike alignment condition for analysis, named `ctrl_center_button`, which specifies:" + " skip_duplicates=True)" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "071fc809", + "cell_type": "markdown", + "id": "8d095e0a-207d-4641-86eb-bc25a0a09018", "metadata": { "title": "a CuratedClustering of interest for analysis" }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "27558ed5", - "metadata": { - "title": "an event of interest to align the spikes to - `center_button`" - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f5f3fec8", - "metadata": { - "lines_to_next_cell": 0, - "title": "a set of trials of interest to perform the analysis on - `ctrl` trials" - }, - "outputs": [], "source": [ - "\n", - "# Now, let's create another set with:" + "With the steps above, we have create a new spike alignment condition for analysis, named `ctrl_center_button`, which retains all spiking information related to control trials during which the center button was pressed." ] }, { "cell_type": "code", - "execution_count": null, - "id": "c4ea0bd1", - "metadata": { - "title": "the same CuratedClustering of interest for analysis" - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "598fce99", - "metadata": { - "title": "an event of interest to align the spikes to - `center_button`" - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4c66992f", + "execution_count": 19, + "id": "5e2c96c5-9382-4d57-a1c9-0124611a3e08", "metadata": { - "title": "a set of trials of interest to perform the analysis on - `stim` trials" + "lines_to_next_cell": 0, + "title": "***SpikesAlignment*** - a computed table to extract event-aligned spikes and compute unit PSTH" }, - "outputs": [], - "source": [ - "\n", - "stim_trials = trial.Trial & clustering_key & 'trial_type = \"stim\"'\n", - "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", - "analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True)\n", - "analysis.SpikesAlignmentCondition.Trial.insert(\n", - " (analysis.SpikesAlignmentCondition * stim_trials & alignment_condition).proj(),\n", - " skip_duplicates=True)\n", - "\n", - "# We can compare conditions in the `SpikesAlignmentCondition` table.\n", - "\n", - "analysis.SpikesAlignmentCondition()\n", - "\n", - "analysis.SpikesAlignmentCondition.Trial & 'trial_condition = \"ctrl_center_button\"'\n", - "\n", - "# ### Computation\n", - "\n", - "# Now let's run the computation on these.\n", - "\n", - "analysis.SpikesAlignment.populate(display_progress=True)\n", - "\n", - "# ### Vizualize\n", - "\n", - "# We can visualize the results with the `plot_raster` function.\n", - "\n", - "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'ctrl_center_button'}\n", - "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);\n", - "\n", - "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", - "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);" - ] + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Trials on which to compute event-aligned spikes and PSTH\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_datetime

\n", + " \n", + "
\n", + "

insertion_number

\n", + " \n", + "
\n", + "

paramset_idx

\n", + " \n", + "
\n", + "

alignment_name

\n", + " \n", + "
\n", + "

trial_condition

\n", + " user-friendly name of condition\n", + "
\n", + "

trial_id

\n", + " trial number (1-based indexing)\n", + "
subject62021-01-15 11:16:3800center_buttonctrl_center_button2
subject62021-01-15 11:16:3800center_buttonctrl_center_button3
subject62021-01-15 11:16:3800center_buttonctrl_center_button4
subject62021-01-15 11:16:3800center_buttonctrl_center_button7
subject62021-01-15 11:16:3800center_buttonctrl_center_button8
subject62021-01-15 11:16:3800center_buttonctrl_center_button9
subject62021-01-15 11:16:3800center_buttonctrl_center_button13
subject62021-01-15 11:16:3800center_buttonctrl_center_button14
subject62021-01-15 11:16:3800center_buttonctrl_center_button15
subject62021-01-15 11:16:3800center_buttonctrl_center_button16
\n", + "

...

\n", + "

Total: 50

\n", + " " + ], + "text/plain": [ + "*subject *session_datet *insertion_num *paramset_idx *alignment_nam *trial_conditi *trial_id \n", + "+----------+ +------------+ +------------+ +------------+ +------------+ +------------+ +----------+\n", + "subject6 2021-01-15 11: 0 0 center_button ctrl_center_bu 2 \n", + "subject6 2021-01-15 11: 0 0 center_button ctrl_center_bu 3 \n", + "subject6 2021-01-15 11: 0 0 center_button ctrl_center_bu 4 \n", + "subject6 2021-01-15 11: 0 0 center_button ctrl_center_bu 7 \n", + "subject6 2021-01-15 11: 0 0 center_button ctrl_center_bu 8 \n", + "subject6 2021-01-15 11: 0 0 center_button ctrl_center_bu 9 \n", + "subject6 2021-01-15 11: 0 0 center_button ctrl_center_bu 13 \n", + "subject6 2021-01-15 11: 0 0 center_button ctrl_center_bu 14 \n", + "subject6 2021-01-15 11: 0 0 center_button ctrl_center_bu 15 \n", + "subject6 2021-01-15 11: 0 0 center_button ctrl_center_bu 16 \n", + " ...\n", + " (Total: 50)" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "analysis.SpikesAlignmentCondition.Trial()" + ] + }, + { + "cell_type": "markdown", + "id": "68d6a35b-41be-404c-b39d-7df813d2a9de", + "metadata": { + "lines_to_next_cell": 0, + "title": "a set of trials of interest to perform the analysis on - `ctrl` trials" + }, + "source": [ + "Now, let's create another set for the stimulus condition." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "1ea69400-75a0-4421-9310-8eec465b4775", + "metadata": { + "title": "a set of trials of interest to perform the analysis on - `stim` trials" + }, + "outputs": [], + "source": [ + "stim_trials = trial.Trial & clustering_key & 'trial_type = \"stim\"'\n", + "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", + "analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True)\n", + "analysis.SpikesAlignmentCondition.Trial.insert(\n", + " (analysis.SpikesAlignmentCondition * stim_trials & alignment_condition).proj(),\n", + " skip_duplicates=True)" + ] + }, + { + "cell_type": "markdown", + "id": "b77f92ae-d2ba-4894-a439-286efd5d13f6", + "metadata": { + "title": "a set of trials of interest to perform the analysis on - `stim` trials" + }, + "source": [ + "We can compare conditions in the `SpikesAlignmentCondition` table." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "10abfa77-a30c-4d4f-b905-51adaa6de54c", + "metadata": { + "title": "a set of trials of interest to perform the analysis on - `stim` trials" + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_datetime

\n", + " \n", + "
\n", + "

insertion_number

\n", + " \n", + "
\n", + "

paramset_idx

\n", + " \n", + "
\n", + "

alignment_name

\n", + " \n", + "
\n", + "

trial_condition

\n", + " user-friendly name of condition\n", + "
\n", + "

condition_description

\n", + " \n", + "
\n", + "

bin_size

\n", + " bin-size (in second) used to compute the PSTH\n", + "
subject62021-01-15 11:16:3800center_buttonctrl_center_button0.04
subject62021-01-15 11:16:3800center_buttonstim_center_button0.04
\n", + " \n", + "

Total: 2

\n", + " " + ], + "text/plain": [ + "*subject *session_datet *insertion_num *paramset_idx *alignment_nam *trial_conditi condition_desc bin_size \n", + "+----------+ +------------+ +------------+ +------------+ +------------+ +------------+ +------------+ +----------+\n", + "subject6 2021-01-15 11: 0 0 center_button ctrl_center_bu 0.04 \n", + "subject6 2021-01-15 11: 0 0 center_button stim_center_bu 0.04 \n", + " (Total: 2)" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "analysis.SpikesAlignmentCondition()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "8125c8d8-006d-41af-9c87-5c13973cdc3f", + "metadata": { + "title": "a set of trials of interest to perform the analysis on - `stim` trials" + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Trials on which to compute event-aligned spikes and PSTH\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_datetime

\n", + " \n", + "
\n", + "

insertion_number

\n", + " \n", + "
\n", + "

paramset_idx

\n", + " \n", + "
\n", + "

alignment_name

\n", + " \n", + "
\n", + "

trial_condition

\n", + " user-friendly name of condition\n", + "
\n", + "

trial_id

\n", + " trial number (1-based indexing)\n", + "
subject62021-01-15 11:16:3800center_buttonstim_center_button1
subject62021-01-15 11:16:3800center_buttonstim_center_button5
subject62021-01-15 11:16:3800center_buttonstim_center_button6
subject62021-01-15 11:16:3800center_buttonstim_center_button10
subject62021-01-15 11:16:3800center_buttonstim_center_button11
subject62021-01-15 11:16:3800center_buttonstim_center_button12
subject62021-01-15 11:16:3800center_buttonstim_center_button17
subject62021-01-15 11:16:3800center_buttonstim_center_button18
subject62021-01-15 11:16:3800center_buttonstim_center_button19
subject62021-01-15 11:16:3800center_buttonstim_center_button21
\n", + "

...

\n", + "

Total: 50

\n", + " " + ], + "text/plain": [ + "*subject *session_datet *insertion_num *paramset_idx *alignment_nam *trial_conditi *trial_id \n", + "+----------+ +------------+ +------------+ +------------+ +------------+ +------------+ +----------+\n", + "subject6 2021-01-15 11: 0 0 center_button stim_center_bu 1 \n", + "subject6 2021-01-15 11: 0 0 center_button stim_center_bu 5 \n", + "subject6 2021-01-15 11: 0 0 center_button stim_center_bu 6 \n", + "subject6 2021-01-15 11: 0 0 center_button stim_center_bu 10 \n", + "subject6 2021-01-15 11: 0 0 center_button stim_center_bu 11 \n", + "subject6 2021-01-15 11: 0 0 center_button stim_center_bu 12 \n", + "subject6 2021-01-15 11: 0 0 center_button stim_center_bu 17 \n", + "subject6 2021-01-15 11: 0 0 center_button stim_center_bu 18 \n", + "subject6 2021-01-15 11: 0 0 center_button stim_center_bu 19 \n", + "subject6 2021-01-15 11: 0 0 center_button stim_center_bu 21 \n", + " ...\n", + " (Total: 50)" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "analysis.SpikesAlignmentCondition.Trial & 'trial_condition = \"stim_center_button\"'" + ] + }, + { + "cell_type": "markdown", + "id": "fa873de6-8e37-4616-996a-967682a896fb", + "metadata": { + "title": "a set of trials of interest to perform the analysis on - `stim` trials" + }, + "source": [ + "## Computation\n", + "\n", + "Now let's run the computation on these." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "623998dd-0c08-4c75-b5ad-2766f11bda6b", + "metadata": { + "title": "a set of trials of interest to perform the analysis on - `stim` trials" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "SpikesAlignment: 100%|██████████████████████████████████████████████████████████████████████| 2/2 [00:23<00:00, 11.78s/it]\n" + ] + } + ], + "source": [ + "analysis.SpikesAlignment.populate(display_progress=True)" + ] + }, + { + "cell_type": "markdown", + "id": "e80e0b70-1b75-4645-87d3-519e7f1a5ff6", + "metadata": { + "title": "a set of trials of interest to perform the analysis on - `stim` trials" + }, + "source": [ + "## Visualize\n", + "\n", + "We can visualize the results with the `plot_raster` function." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "03132026-55e3-4522-9f58-ce86b94c7842", + "metadata": { + "title": "a set of trials of interest to perform the analysis on - `stim` trials" + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'ctrl_center_button'}\n", + "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "e144df4c-87c1-4646-9d4b-b0009216bca1", + "metadata": { + "title": "a set of trials of interest to perform the analysis on - `stim` trials" + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", + "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "93bf7940-a3bb-4ab9-abd3-95f6a2282775", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/notebooks/08-electrode-localization.ipynb b/notebooks/08-electrode-localization.ipynb index 759e6051..5d93b7e7 100644 --- a/notebooks/08-electrode-localization.ipynb +++ b/notebooks/08-electrode-localization.ipynb @@ -59,7 +59,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -83,7 +83,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -221,7 +221,7 @@ " (Total: 1327)" ] }, - "execution_count": 5, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -239,7 +239,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -454,7 +454,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -497,7 +497,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -593,7 +593,7 @@ " (Total: 3)" ] }, - "execution_count": 3, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -675,6 +675,207 @@ " print('Electrode {electrode} (x={x}, y={y}, z={z}) is in {region}'.format(**e))" ] }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_datetime

\n", + " \n", + "
\n", + "

insertion_number

\n", + " \n", + "
\n", + "

ccf_id

\n", + " CCF ID, a.k.a atlas ID\n", + "
\n", + "

probe_type

\n", + " e.g. neuropixels_1.0\n", + "
\n", + "

electrode

\n", + " electrode index, starts at 0\n", + "
\n", + "

x

\n", + " (um) Anterior-to-Posterior (AP axis)\n", + "
\n", + "

y

\n", + " (um) Superior-to-Inferior (DV axis)\n", + "
\n", + "

z

\n", + " (um) Left-to-Right (ML axis)\n", + "
subject52018-07-03 20:32:2820neuropixels 1.0 - 3A2061100046004600
subject52018-07-03 20:32:2820neuropixels 1.0 - 3A2071100046004600
subject62021-01-15 11:16:3800neuropixels 1.0 - 3B2061100046004600
subject62021-01-15 11:16:3800neuropixels 1.0 - 3B2071100046004600
subject52018-07-03 20:32:2820neuropixels 1.0 - 3A1961100046004700
subject52018-07-03 20:32:2820neuropixels 1.0 - 3A1971100046004700
subject52018-07-03 20:32:2820neuropixels 1.0 - 3A1981100046004700
subject52018-07-03 20:32:2820neuropixels 1.0 - 3A1991100046004700
subject52018-07-03 20:32:2820neuropixels 1.0 - 3A2001100046004700
subject52018-07-03 20:32:2820neuropixels 1.0 - 3A2011100046004700
\n", + "

...

\n", + "

Total: 1113

\n", + " " + ], + "text/plain": [ + "*subject *session_datet *insertion_num *ccf_id *probe_type *electrode x y z \n", + "+----------+ +------------+ +------------+ +--------+ +------------+ +-----------+ +-------+ +------+ +------+\n", + "subject5 2018-07-03 20: 2 0 neuropixels 1. 206 11000 4600 4600 \n", + "subject5 2018-07-03 20: 2 0 neuropixels 1. 207 11000 4600 4600 \n", + "subject6 2021-01-15 11: 0 0 neuropixels 1. 206 11000 4600 4600 \n", + "subject6 2021-01-15 11: 0 0 neuropixels 1. 207 11000 4600 4600 \n", + "subject5 2018-07-03 20: 2 0 neuropixels 1. 196 11000 4600 4700 \n", + "subject5 2018-07-03 20: 2 0 neuropixels 1. 197 11000 4600 4700 \n", + "subject5 2018-07-03 20: 2 0 neuropixels 1. 198 11000 4600 4700 \n", + "subject5 2018-07-03 20: 2 0 neuropixels 1. 199 11000 4600 4700 \n", + "subject5 2018-07-03 20: 2 0 neuropixels 1. 200 11000 4600 4700 \n", + "subject5 2018-07-03 20: 2 0 neuropixels 1. 201 11000 4600 4700 \n", + " ...\n", + " (Total: 1113)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eloc.ElectrodePosition.Electrode()" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/notebooks/py_scripts/04-automate-optional.py b/notebooks/py_scripts/04-automate-optional.py index de4282d4..8615f616 100644 --- a/notebooks/py_scripts/04-automate-optional.py +++ b/notebooks/py_scripts/04-automate-optional.py @@ -8,9 +8,9 @@ # format_version: '1.5' # jupytext_version: 1.13.7 # kernelspec: -# display_name: ephys_workflow_runner +# display_name: venv-nwb # language: python -# name: ephys_workflow_runner +# name: venv-nwb # --- # + [markdown] pycharm={"name": "#%% md\n"} @@ -69,7 +69,7 @@ "useRAM": 0 } ephys.ClusteringParamSet.insert_new_params( - processing_method='kilosort2', + clustering_method='kilosort2', paramset_idx=0, params=params_ks, paramset_desc='Spike sorting using Kilosort2') diff --git a/notebooks/py_scripts/07-downstream-analysis.py b/notebooks/py_scripts/07-downstream-analysis.py index cc524f37..0009f4e0 100644 --- a/notebooks/py_scripts/07-downstream-analysis.py +++ b/notebooks/py_scripts/07-downstream-analysis.py @@ -33,8 +33,9 @@ # Next, we populate the python namespace with the required schemas from workflow_array_ephys.pipeline import session, ephys, trial, event +from workflow_array_ephys import analysis -# + [markdown] jp-MarkdownHeadingCollapsed=true tags=[] +# + [markdown] jp-MarkdownHeadingCollapsed=true jp-MarkdownHeadingCollapsed=true tags=[] # ## Trial and Event schemas # - @@ -76,7 +77,9 @@ event.AlignmentEvent() -# ## Event-aligned trialized unit spike times +# + [markdown] jp-MarkdownHeadingCollapsed=true tags=[] +# ## Event-aligned spike times +# - # First, we'll check that the data is still properly inserted from the previous notebooks. @@ -97,17 +100,12 @@ # The `analysis` schema provides example tables to perform event-aligned spike-times analysis. -from workflow_array_ephys import analysis - -# + ***SpikesAlignmentCondition*** - a manual table to specify the inputs and condition for the analysis - - -# + ***SpikesAlignment*** - a computed table to extract event-aligned spikes and compute unit PSTH +analysis.schema.list_tables() +# + ***SpikesAlignmentCondition*** - a manual table to specify the inputs and condition for the analysis [markdown] # Let's start by creating several analyses configuration - i.e. inserting into ***SpikesAlignmentCondition*** for the `center` event, called `center_button` in the `AlignmentEvent` table. -event.AlignmentEvent() - +# + ***SpikesAlignment*** - a computed table to extract event-aligned spikes and compute unit PSTH alignment_key = (event.AlignmentEvent & 'alignment_name = "center_button"' ).fetch1('KEY') alignment_condition = {**clustering_key, **alignment_key, @@ -117,27 +115,14 @@ analysis.SpikesAlignmentCondition.Trial.insert( (analysis.SpikesAlignmentCondition * ctrl_trials & alignment_condition).proj(), skip_duplicates=True) +# + a CuratedClustering of interest for analysis [markdown] +# With the steps above, we have create a new spike alignment condition for analysis, named `ctrl_center_button`, which retains all spiking information related to control trials during which the center button was pressed. +# + ***SpikesAlignment*** - a computed table to extract event-aligned spikes and compute unit PSTH analysis.SpikesAlignmentCondition.Trial() - -# With the steps above, we have create a new spike alignment condition for analysis, named `ctrl_center_button`, which specifies: -# + a CuratedClustering of interest for analysis - - -# + an event of interest to align the spikes to - `center_button` - - -# + a set of trials of interest to perform the analysis on - `ctrl` trials - -# Now, let's create another set with: -# + the same CuratedClustering of interest for analysis - - -# + an event of interest to align the spikes to - `center_button` - - +# + a set of trials of interest to perform the analysis on - `ctrl` trials [markdown] +# Now, let's create another set for the stimulus condition. # + a set of trials of interest to perform the analysis on - `stim` trials - stim_trials = trial.Trial & clustering_key & 'trial_type = "stim"' alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'} analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True) @@ -145,24 +130,35 @@ (analysis.SpikesAlignmentCondition * stim_trials & alignment_condition).proj(), skip_duplicates=True) +# + a set of trials of interest to perform the analysis on - `stim` trials [markdown] # We can compare conditions in the `SpikesAlignmentCondition` table. +# + a set of trials of interest to perform the analysis on - `stim` trials analysis.SpikesAlignmentCondition() -analysis.SpikesAlignmentCondition.Trial & 'trial_condition = "ctrl_center_button"' - -# ### Computation +# + a set of trials of interest to perform the analysis on - `stim` trials +analysis.SpikesAlignmentCondition.Trial & 'trial_condition = "stim_center_button"' +# + a set of trials of interest to perform the analysis on - `stim` trials [markdown] +# ## Computation +# # Now let's run the computation on these. +# + a set of trials of interest to perform the analysis on - `stim` trials analysis.SpikesAlignment.populate(display_progress=True) -# ### Vizualize - +# + a set of trials of interest to perform the analysis on - `stim` trials [markdown] +# ## Visualize +# # We can visualize the results with the `plot_raster` function. +# + a set of trials of interest to perform the analysis on - `stim` trials alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'ctrl_center_button'} analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2); +# + a set of trials of interest to perform the analysis on - `stim` trials alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'} analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2); +# - + + diff --git a/notebooks/py_scripts/08-electrode-localization.py b/notebooks/py_scripts/08-electrode-localization.py index d4d91d2c..3b3595de 100644 --- a/notebooks/py_scripts/08-electrode-localization.py +++ b/notebooks/py_scripts/08-electrode-localization.py @@ -98,4 +98,6 @@ ).fetch1('region_name') print('Electrode {electrode} (x={x}, y={y}, z={z}) is in {region}'.format(**e)) +eloc.ElectrodePosition.Electrode() + From a4c6d32f64ec2b6a5e08bd0ee5d03a0e11000398 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 25 Apr 2022 12:34:00 -0500 Subject: [PATCH 29/59] event activation in pipeline; minor pep8 --- workflow_array_ephys/export.py | 5 ++++- workflow_array_ephys/ingest.py | 8 +++++--- workflow_array_ephys/localization.py | 22 ---------------------- workflow_array_ephys/pipeline.py | 12 +++++++++++- 4 files changed, 20 insertions(+), 27 deletions(-) diff --git a/workflow_array_ephys/export.py b/workflow_array_ephys/export.py index a6ff2bc7..930060da 100644 --- a/workflow_array_ephys/export.py +++ b/workflow_array_ephys/export.py @@ -4,4 +4,7 @@ from element_session.export.nwb import session_to_nwb # Import NWB export functions -from element_array_ephys.export.nwb import ecephys_session_to_nwb, write_nwb \ No newline at end of file +from element_array_ephys.export.nwb import ecephys_session_to_nwb, write_nwb + +__all__ = ['element_lab_to_nwb_dict', 'subject_to_nwb', 'session_to_nwb', + 'ecephys_session_to_nwb', 'write_nwb'] diff --git a/workflow_array_ephys/ingest.py b/workflow_array_ephys/ingest.py index 6d088f03..3ec88fd0 100644 --- a/workflow_array_ephys/ingest.py +++ b/workflow_array_ephys/ingest.py @@ -5,7 +5,8 @@ from workflow_array_ephys.paths import get_ephys_root_data_dir from element_array_ephys.readers import spikeglx, openephys -from element_interface.utils import find_root_directory, find_full_path, ingest_csv_to_table +from element_interface.utils import find_root_directory, find_full_path, \ + ingest_csv_to_table def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', @@ -46,6 +47,7 @@ def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', ingest_csv_to_table(csvs, tables, skip_duplicates=skip_duplicates, verbose=verbose) + def ingest_subjects(subject_csv_path='./user_data/subjects.csv', verbose=True): """ Ingest subjects listed in the subject column of ./user_data/subjects.csv @@ -136,9 +138,9 @@ def ingest_sessions(session_csv_path='./user_data/sessions.csv', verbose=True): session_dir_list.append({**session_key, 'session_dir': session_dir.relative_to(root_dir).as_posix()}) session_note_list.append({**session_key, 'session_note': - sess['session_note']}) + sess['session_note']}) session_experimenter_list.append({**session_key, 'user': - sess['user']}) + sess['user']}) probe_insertion_list.extend([{**session_key, **insertion } for insertion in insertions]) diff --git a/workflow_array_ephys/localization.py b/workflow_array_ephys/localization.py index ac85a458..27c56acc 100644 --- a/workflow_array_ephys/localization.py +++ b/workflow_array_ephys/localization.py @@ -18,31 +18,9 @@ 'get_ephys_root_data_dir', 'get_session_directory', 'get_electrode_localization_dir', 'load_ccf_annotation'] - ccf_id = 0 voxel_resolution = 100 - -# # Dummy table for case sensitivity in MySQL------------------------------------ -# # Without DummyTable, the schema activates with a case-insensitive character set -# # which cannot ingest all CCF standard acronyms - -# coordinate_framework_schema = dj.schema(db_prefix + 'ccf') - - -# @coordinate_framework_schema -# class DummyTable(dj.Manual): -# definition = """ -# id : varchar(1) -# """ -# contents = zip(['1', '2']) - - -# ccf_schema_name = db_prefix + 'ccf' -# dj.conn().query(f'ALTER DATABASE `{ccf_schema_name}` CHARACTER SET utf8 COLLATE ' -# + 'utf8_bin;') - - # Activate "electrode-localization" schema ------------------------------------ ProbeInsertion = ephys.ProbeInsertion diff --git a/workflow_array_ephys/pipeline.py b/workflow_array_ephys/pipeline.py index b7dc28d7..9fed7353 100644 --- a/workflow_array_ephys/pipeline.py +++ b/workflow_array_ephys/pipeline.py @@ -5,6 +5,7 @@ from element_session import session_with_datetime as session from element_array_ephys import probe from element_array_ephys import ephys_acute as ephys +from element_event import trial, event from element_lab.lab import Source, Lab, Protocol, User, Project from element_animal.subject import Subject @@ -17,6 +18,9 @@ db_prefix = dj.config['custom'].get('database.prefix', '') +__all__ = ['lab', 'subject', 'session', 'probe', 'ephys', 'trial', 'event', + 'Source', 'Lab', 'Protocol', 'User', 'Project', 'Subject', 'Session', + 'get_ephys_root_data_dir', 'get_session_directory'] # Activate "lab", "subject", "session" schema ------------------------------------------ @@ -42,4 +46,10 @@ class SkullReference(dj.Lookup): ephys.activate(db_prefix + 'ephys', db_prefix + 'probe', - linking_module=__name__) \ No newline at end of file + linking_module=__name__) + +# Activate "trial" schema -------------------------------------------------------------- + +trial.activate(db_prefix + 'trial', + db_prefix + 'event', + linking_module=__name__) From 22bf2a46acc344988ebb3d4097b3787dc9fce2c2 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 25 Apr 2022 13:32:59 -0500 Subject: [PATCH 30/59] add diagrams to README --- README.md | 27 +- notebooks/07-downstream-analysis.ipynb | 41 +-- notebooks/08-electrode-localization.ipynb | 379 +++++++++++++++++++++- 3 files changed, 413 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 2d0530d9..4524e09c 100644 --- a/README.md +++ b/README.md @@ -33,21 +33,34 @@ assembled together to form a fully functional workflow. ![element-array-ephys](images/attached_array_ephys_element.svg) +Optionally, this can be used in conjunction with +[element-event](https://github.com/datajoint/element-event) +and [element-electrode-localization](https://github.com/datajoint/element-electrode-localization/). + +![element-event_attached](images/attached_trial_analysis) + +![element-electrode-localization_attached](images/attached_electrode_localization) + ## Installation instructions -+ The installation instructions can be found at the +The installation instructions can be found at the [DataJoint Elements documentation](https://elements.datajoint.org/usage/install/). ## Interacting with the DataJoint workflow -+ Please refer to the following workflow-specific - [Jupyter notebooks](/notebooks) for an in-depth explanation of how to run the - workflow ([03-process.ipynb](notebooks/03-process.ipynb)) and explore the data - ([05-explore.ipynb](notebooks/05-explore.ipynb)). +Please refer to the workflow-specific +[Jupyter notebooks](/notebooks) +for an in-depth explanation of how to run the +workflow ([03-process.ipynb](notebooks/03-process.ipynb)) and explore the data +([05-explore.ipynb](notebooks/05-explore.ipynb)). +[07-downstream-analysis.ipynb](notebooks/07-downstream-analysis.ipynb) +and [08-electrode-localization.ipynb](notebooks/08-electrode-localization.ipynb) +explore how to (a) look at trialized analyses, and (b) locate probes within the +[Common Coordinate Framework](https://www.sciencedirect.com/science/article/pii/S0092867420304025). ## Citation -+ If your work uses DataJoint and DataJoint Elements, please cite the respective Research Resource Identifiers (RRIDs) and manuscripts. +If your work uses DataJoint and DataJoint Elements, please cite the respective Research Resource Identifiers (RRIDs) and manuscripts. + DataJoint for Python or MATLAB + Yatsenko D, Reimer J, Ecker AS, Walker EY, Sinz F, Berens P, Hoenselaar A, Cotton RJ, Siapas AS, Tolias AS. DataJoint: managing big scientific data using MATLAB or Python. bioRxiv. 2015 Jan 1:031658. doi: https://doi.org/10.1101/031658 @@ -57,4 +70,4 @@ assembled together to form a fully functional workflow. + DataJoint Elements + Yatsenko D, Nguyen T, Shen S, Gunalan K, Turner CA, Guzman R, Sasaki M, Sitonic D, Reimer J, Walker EY, Tolias AS. DataJoint Elements: Data Workflows for Neurophysiology. bioRxiv. 2021 Jan 1. doi: https://doi.org/10.1101/2021.03.30.437358 - + DataJoint Elements ([RRID:SCR_021894](https://scicrunch.org/resolver/SCR_021894)) - Element Array Electrophysiology (version ``) \ No newline at end of file + + DataJoint Elements ([RRID:SCR_021894](https://scicrunch.org/resolver/SCR_021894)) - Element Array Electrophysiology (version ``) diff --git a/notebooks/07-downstream-analysis.ipynb b/notebooks/07-downstream-analysis.ipynb index 2528d8d1..df2f3ce7 100644 --- a/notebooks/07-downstream-analysis.ipynb +++ b/notebooks/07-downstream-analysis.ipynb @@ -54,10 +54,18 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 2, "id": "79cef246", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting cbroz@dss-db.datajoint.io:3306\n" + ] + } + ], "source": [ "from workflow_array_ephys.pipeline import session, ephys, trial, event\n", "from workflow_array_ephys import analysis" @@ -664,7 +672,6 @@ "cell_type": "markdown", "id": "4936a1e8", "metadata": { - "jp-MarkdownHeadingCollapsed": true, "tags": [] }, "source": [ @@ -991,31 +998,13 @@ }, { "cell_type": "code", - "execution_count": 18, - "id": "0678d202", + "execution_count": null, + "id": "937383de-b313-45ec-9239-8f6a8e604ac1", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['spikes_alignment_condition',\n", - " 'spikes_alignment_condition__trial',\n", - " 'activity_alignment_condition',\n", - " 'activity_alignment_condition__trial',\n", - " '__spikes_alignment',\n", - " '__spikes_alignment__aligned_trial_spikes',\n", - " '__spikes_alignment__unit_p_s_t_h',\n", - " '__activity_alignment',\n", - " '__activity_alignment__aligned_trial_activity']" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "analysis.schema.list_tables()" + "(dj.Diagram(analysis) + dj.Diagram(event.AlignmentEvent) + dj.Diagram(trial.Trial) + \n", + " dj.Diagram(ephys.CuratedClustering))" ] }, { diff --git a/notebooks/08-electrode-localization.ipynb b/notebooks/08-electrode-localization.ipynb index 5d93b7e7..9d48e700 100644 --- a/notebooks/08-electrode-localization.ipynb +++ b/notebooks/08-electrode-localization.ipynb @@ -74,6 +74,155 @@ "from workflow_array_ephys.localization import coordinate_framework as ccf" ] }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "3\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "ccf.ParentBrainRegion\n", + "\n", + "\n", + "ccf.ParentBrainRegion\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "3->ccf.ParentBrainRegion\n", + "\n", + "\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation.BrainRegion\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation.BrainRegion\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation.BrainRegion->3\n", + "\n", + "\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation.BrainRegion->ccf.ParentBrainRegion\n", + "\n", + "\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation.Voxel\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation.Voxel\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation.BrainRegion->ccf.BrainRegionAnnotation.Voxel\n", + "\n", + "\n", + "\n", + "\n", + "ccf.CCF.Voxel\n", + "\n", + "\n", + "ccf.CCF.Voxel\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "ccf.CCF.Voxel->ccf.BrainRegionAnnotation.Voxel\n", + "\n", + "\n", + "\n", + "\n", + "ccf.CCF\n", + "\n", + "\n", + "ccf.CCF\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "ccf.CCF->ccf.CCF.Voxel\n", + "\n", + "\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "ccf.CCF->ccf.BrainRegionAnnotation\n", + "\n", + "\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation->ccf.BrainRegionAnnotation.BrainRegion\n", + "\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dj.Diagram(ccf)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -454,7 +603,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -462,6 +611,234 @@ "from workflow_array_ephys.localization import electrode_localization as eloc" ] }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "35\n", + "\n", + "35\n", + "\n", + "\n", + "\n", + "ccf.ParentBrainRegion\n", + "\n", + "\n", + "ccf.ParentBrainRegion\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "35->ccf.ParentBrainRegion\n", + "\n", + "\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation.BrainRegion\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation.BrainRegion\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation.BrainRegion->35\n", + "\n", + "\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation.BrainRegion->ccf.ParentBrainRegion\n", + "\n", + "\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation.Voxel\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation.Voxel\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation.BrainRegion->ccf.BrainRegionAnnotation.Voxel\n", + "\n", + "\n", + "\n", + "\n", + "eloc.probe.ProbeType.Electrode\n", + "\n", + "\n", + "eloc.probe.ProbeType.Electrode\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "eloc.ElectrodePosition.Electrode\n", + "\n", + "\n", + "eloc.ElectrodePosition.Electrode\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "eloc.probe.ProbeType.Electrode->eloc.ElectrodePosition.Electrode\n", + "\n", + "\n", + "\n", + "\n", + "ccf.CCF.Voxel\n", + "\n", + "\n", + "ccf.CCF.Voxel\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "ccf.CCF.Voxel->eloc.ElectrodePosition.Electrode\n", + "\n", + "\n", + "\n", + "\n", + "ccf.CCF.Voxel->ccf.BrainRegionAnnotation.Voxel\n", + "\n", + "\n", + "\n", + "\n", + "eloc.ProbeInsertion\n", + "\n", + "\n", + "eloc.ProbeInsertion\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "eloc.ElectrodePosition\n", + "\n", + "\n", + "eloc.ElectrodePosition\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "eloc.ProbeInsertion->eloc.ElectrodePosition\n", + "\n", + "\n", + "\n", + "\n", + "ccf.CCF\n", + "\n", + "\n", + "ccf.CCF\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "ccf.CCF->ccf.CCF.Voxel\n", + "\n", + "\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "ccf.CCF->ccf.BrainRegionAnnotation\n", + "\n", + "\n", + "\n", + "\n", + "ccf.CCF->eloc.ElectrodePosition\n", + "\n", + "\n", + "\n", + "\n", + "ccf.BrainRegionAnnotation->ccf.BrainRegionAnnotation.BrainRegion\n", + "\n", + "\n", + "\n", + "\n", + "eloc.ElectrodePosition->eloc.ElectrodePosition.Electrode\n", + "\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(dj.Diagram(eloc) + dj.Diagram(ccf) - 1)" + ] + }, { "cell_type": "markdown", "metadata": {}, From ce184d62c6e02d43ad48bd69add40d2947ca0046 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 25 Apr 2022 13:34:14 -0500 Subject: [PATCH 31/59] typo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4524e09c..025cb426 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,9 @@ Optionally, this can be used in conjunction with [element-event](https://github.com/datajoint/element-event) and [element-electrode-localization](https://github.com/datajoint/element-electrode-localization/). -![element-event_attached](images/attached_trial_analysis) +![element-event_attached](images/attached_trial_analysis.svg) -![element-electrode-localization_attached](images/attached_electrode_localization) +![element-electrode-localization_attached](images/attached_electrode_localization.svg) ## Installation instructions From e6ef9869475a1faf7ae1e6ffdd0402833442292e Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Mon, 2 May 2022 11:29:03 -0500 Subject: [PATCH 32/59] Apply suggestions from code review Co-authored-by: Kabilar Gunalan --- docker/Dockerfile.dev | 7 ++++--- docker/Dockerfile.test | 7 ++++--- docker/docker-compose-dev.yaml | 4 ++-- docker/docker-compose-test.yaml | 2 +- workflow_array_ephys/analysis.py | 1 - workflow_array_ephys/localization.py | 1 - 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index e7cadfc2..83b33226 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -8,16 +8,17 @@ RUN /entrypoint.sh echo "Installed dependencies." RUN mkdir /main/element-lab \ /main/element-animal \ /main/element-session \ - /main/element-trial \ + /main/element-event \ /main/element-array-ephys \ /main/element-interface \ + /main/element-electrode-localization \ /main/workflow-array-ephys # Copy user's local fork of elements and workflow COPY --chown=anaconda:anaconda ./element-lab /main/element-lab COPY --chown=anaconda:anaconda ./element-animal /main/element-animal COPY --chown=anaconda:anaconda ./element-session /main/element-session -COPY --chown=anaconda:anaconda ./element-trial /main/element-trial +COPY --chown=anaconda:anaconda ./element-event /main/element-event COPY --chown=anaconda:anaconda ./element-electrode-localization /main/element-electrode-localization COPY --chown=anaconda:anaconda ./element-array-ephys /main/element-array-ephys COPY --chown=anaconda:anaconda ./element-interface /main/element-interface @@ -27,7 +28,7 @@ COPY --chown=anaconda:anaconda ./workflow-array-ephys /main/workflow-array-ephys RUN pip install -e /main/element-lab RUN pip install -e /main/element-animal RUN pip install -e /main/element-session -RUN pip install -e /main/element-trial +RUN pip install -e /main/element-event RUN pip install -e /main/element-electrode-localization RUN pip install -e /main/element-array-ephys RUN pip install -e /main/workflow-array-ephys diff --git a/docker/Dockerfile.test b/docker/Dockerfile.test index 28b25b2b..5ea6a839 100644 --- a/docker/Dockerfile.test +++ b/docker/Dockerfile.test @@ -15,7 +15,7 @@ WORKDIR /main/workflow-array-ephys # RUN pip install git+https://github.com/element-lab.git # RUN pip install git+https://github.com//element-animal.git # RUN pip install git+https://github.com//element-session.git -# RUN pip install git+https://github.com//element-trial.git +# RUN pip install git+https://github.com//element-event.git # RUN pip install git+https://github.com//element-electrode-localization.git # RUN pip install git+https://github.com//element-array-ephys.git # RUN git clone https://github.com//workflow-array-ephys.git /main/workflow-array-ephys @@ -26,12 +26,13 @@ RUN mkdir -p /main/element-lab \ /main/element-session \ /main/element-trial \ /main/element-array-ephys \ + /main/element-electrode-localization \ /main/workflow-array-ephys COPY --chown=anaconda:anaconda ./element-lab /main/element-lab COPY --chown=anaconda:anaconda ./element-animal /main/element-animal COPY --chown=anaconda:anaconda ./element-session /main/element-session -COPY --chown=anaconda:anaconda ./element-trial /main/element-trial +COPY --chown=anaconda:anaconda ./element-event /main/element-event COPY --chown=anaconda:anaconda ./element-electrode-localization /main/element-electrode-localization COPY --chown=anaconda:anaconda ./element-array-ephys /main/element-array-ephys COPY --chown=anaconda:anaconda ./workflow-array-ephys /main/workflow-array-ephys @@ -39,7 +40,7 @@ COPY --chown=anaconda:anaconda ./workflow-array-ephys /main/workflow-array-ephys RUN pip install -e /main/element-lab RUN pip install -e /main/element-animal RUN pip install -e /main/element-session -RUN pip install -e /main/element-trial +RUN pip install -e /main/element-event RUN pip install -e /main/element-electrode-localization RUN pip install -e /main/element-array-ephys # RUN rm -f /main/workflow-array-ephys/dj_local_conf.json diff --git a/docker/docker-compose-dev.yaml b/docker/docker-compose-dev.yaml index 262223a9..a69776a8 100644 --- a/docker/docker-compose-dev.yaml +++ b/docker/docker-compose-dev.yaml @@ -28,13 +28,13 @@ services: - ../../element-lab:/main/element-lab - ../../element-animal:/main/element-animal - ../../element-session:/main/element-session - - ../../element-trial:/main/element-trial + - ../../element-event:/main/element-event - ../../element-electrode-localization:/main/element-electrode-localization - ../../element-array-ephys:/main/element-array-ephys - ../../element-interface:/main/element-interface - ..:/main/workflow-array-ephys depends_on: - array-ephys-dev-db: + db: condition: service_healthy networks: main: diff --git a/docker/docker-compose-test.yaml b/docker/docker-compose-test.yaml index 78d28ac7..1f24ca09 100644 --- a/docker/docker-compose-test.yaml +++ b/docker/docker-compose-test.yaml @@ -44,7 +44,7 @@ services: - ../../element-animal:/main/element-animal - ../../element-session:/main/element-session - ../../element-electrode-localization:/main/element-electrode-localization - - ../../element-trial:/main/element-trial + - ../../element-event:/main/element-event - ../../element-array-ephys:/main/element-array-ephys - ..:/main/workflow-array-ephys depends_on: diff --git a/workflow_array_ephys/analysis.py b/workflow_array_ephys/analysis.py index 10981f79..5f5236fd 100644 --- a/workflow_array_ephys/analysis.py +++ b/workflow_array_ephys/analysis.py @@ -6,7 +6,6 @@ schema = dj.schema(db_prefix + 'analysis') -AlignmentEvent = event.AlignmentEvent @schema diff --git a/workflow_array_ephys/localization.py b/workflow_array_ephys/localization.py index 27c56acc..c73ead50 100644 --- a/workflow_array_ephys/localization.py +++ b/workflow_array_ephys/localization.py @@ -24,7 +24,6 @@ # Activate "electrode-localization" schema ------------------------------------ ProbeInsertion = ephys.ProbeInsertion -Electrode = probe.ProbeType.Electrode electrode_localization.activate(db_prefix + 'eloc', db_prefix + 'ccf', From 6fd692acfb8e5b01357ee4251aec740ac1caf2f7 Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Mon, 2 May 2022 11:29:41 -0500 Subject: [PATCH 33/59] Apply suggestions from code review Co-authored-by: Kabilar Gunalan --- workflow_array_ephys/analysis.py | 1 - 1 file changed, 1 deletion(-) diff --git a/workflow_array_ephys/analysis.py b/workflow_array_ephys/analysis.py index 5f5236fd..af8bede3 100644 --- a/workflow_array_ephys/analysis.py +++ b/workflow_array_ephys/analysis.py @@ -30,7 +30,6 @@ class Trial(dj.Part): class SpikesAlignment(dj.Computed): definition = """ -> SpikesAlignmentCondition - -> ephys.CuratedClustering """ class AlignedTrialSpikes(dj.Part): From 4f3d600dc2b246d0a611a9de4d9ab9d2a87a3692 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Wed, 4 May 2022 11:41:25 -0500 Subject: [PATCH 34/59] Add element-event items to ingest.py --- workflow_array_ephys/ingest.py | 42 +++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/workflow_array_ephys/ingest.py b/workflow_array_ephys/ingest.py index 3ec88fd0..0a7238e0 100644 --- a/workflow_array_ephys/ingest.py +++ b/workflow_array_ephys/ingest.py @@ -1,7 +1,8 @@ import csv import re -from workflow_array_ephys.pipeline import lab, subject, ephys, probe, session +from workflow_array_ephys.pipeline import lab, subject, ephys, probe, session, trial, \ + event from workflow_array_ephys.paths import get_ephys_root_data_dir from element_array_ephys.readers import spikeglx, openephys @@ -162,6 +163,45 @@ def ingest_sessions(session_csv_path='./user_data/sessions.csv', verbose=True): print('\n---- Successfully completed ingest_subjects ----') +def ingest_events(recording_csv_path='./user_data/behavior_recordings.csv', + block_csv_path='./user_data/blocks.csv', + trial_csv_path='./user_data/trials.csv', + event_csv_path='./user_data/events.csv', + skip_duplicates=True, verbose=True): + """ + Ingest each level of experiment heirarchy for element-trial: + recording, block (i.e., phases of trials), trials (repeated units), + events (optionally 0-duration occurances within trial). + This ingestion function is duplicated across wf-array-ephys and wf-calcium-imaging + """ + csvs = [recording_csv_path, recording_csv_path, + block_csv_path, block_csv_path, + trial_csv_path, trial_csv_path, trial_csv_path, + trial_csv_path, + event_csv_path, event_csv_path, event_csv_path] + tables = [event.BehaviorRecording(), event.BehaviorRecording.File(), + trial.Block(), trial.Block.Attribute(), + trial.TrialType(), trial.Trial(), trial.Trial.Attribute(), + trial.BlockTrial(), + event.EventType(), event.Event(), trial.TrialEvent()] + + # Allow direct insert required bc element-trial has Imported that should be Manual + ingest_csv_to_table(csvs, tables, skip_duplicates=skip_duplicates, verbose=verbose, + allow_direct_insert=True) + + +def ingest_alignment(alignment_csv_path='./user_data/alignments.csv', + skip_duplicates=True, verbose=True): + """This is duplicated across wf-array-ephys and wf-calcium-imaging""" + + csvs = [alignment_csv_path] + tables = [event.AlignmentEvent()] + + ingest_csv_to_table(csvs, tables, skip_duplicates=skip_duplicates, verbose=verbose) + + if __name__ == '__main__': ingest_subjects() ingest_sessions() + ingest_events() + ingest_alignment() From 4628a626a3819c4dbbd307d94f4974fc550c8b0b Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Wed, 4 May 2022 16:39:34 -0500 Subject: [PATCH 35/59] Apply suggestions from code review --- docker/Dockerfile.test | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile.test b/docker/Dockerfile.test index 5ea6a839..5e2b3d4d 100644 --- a/docker/Dockerfile.test +++ b/docker/Dockerfile.test @@ -24,7 +24,8 @@ WORKDIR /main/workflow-array-ephys RUN mkdir -p /main/element-lab \ /main/element-animal \ /main/element-session \ - /main/element-trial \ + /main/element-event \ + /main/element-interface \ /main/element-array-ephys \ /main/element-electrode-localization \ /main/workflow-array-ephys From f55d8e257bf38cb88319f90f2a6cbd1362f4ed2b Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Wed, 25 May 2022 15:29:59 -0500 Subject: [PATCH 36/59] WIP: fix timezone for nwb --- requirements.txt | 8 +-- tests/__init__.py | 12 ++-- tests/test_export.py | 96 ++++++++++++++++++++----------- workflow_array_ephys/pipeline.py | 97 +++++++++++++++++++++++++------- workflow_array_ephys/process.py | 6 +- 5 files changed, 154 insertions(+), 65 deletions(-) diff --git a/requirements.txt b/requirements.txt index e48bde7b..e5cc9a7a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ datajoint>=0.13.0 -element-array-ephys==0.1.0b0 +element-array-ephys>=0.1.0b0 element-lab>=0.1.0b0 -element-animal==0.1.0b0 -element-session==0.1.0b0 +element-animal>=0.1.0b0 +element-session>=0.1.0b0 element-event @ git+https://github.com/datajoint/element-event.git element-interface @ git+https://github.com/datajoint/element-interface.git -ipykernel==6.0.1 \ No newline at end of file +ipykernel>=6.0.1 diff --git a/tests/__init__.py b/tests/__init__.py index 621865bc..96344269 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -128,7 +128,6 @@ def pipeline(): 'session': pipeline.session, 'get_ephys_root_data_dir': pipeline.get_ephys_root_data_dir, 'ephys_mode': pipeline.ephys_mode} - 'get_ephys_root_data_dir': pipeline.get_ephys_root_data_dir} if verbose and _tear_down: pipeline.subject.Subject.delete() @@ -488,16 +487,21 @@ def clustering(clustering_tasks, pipeline): """Populate ephys.Clustering""" ephys = pipeline['ephys'] - ephys.Clustering.populate() + if pipeline['ephys_mode'] == "no-curation": + clustering_table = ephys.CuratedClustering + else: + clustering_table = ephys.Clustering + + clustering_table.populate() yield if _tear_down: if verbose: - ephys.Clustering.delete() + clustering_table.delete() else: with QuietStdOut(): - ephys.Clustering.delete() + clustering_table.delete() @pytest.fixture diff --git a/tests/test_export.py b/tests/test_export.py index ac030402..e814c48f 100644 --- a/tests/test_export.py +++ b/tests/test_export.py @@ -9,22 +9,50 @@ testdata_paths, ephys_insertionlocation, kilosort_paramset, ephys_recordings, clustering_tasks, clustering, curations) -from workflow_array_ephys.export import ecephys_session_to_nwb, session_to_nwb, write_nwb +__all__ = [ + "dj_config", + "pipeline", + "test_data", + "lab_csv", + "lab_project_csv", + "lab_user_csv", + "lab_publications_csv", + "lab_keywords_csv", + "lab_protocol_csv", + "lab_project_users_csv", + "ingest_lab", + "subjects_csv", + "ingest_subjects", + "sessions_csv", + "ingest_sessions", + "testdata_paths", + "ephys_insertionlocation", + "kilosort_paramset", + "ephys_recordings", + "clustering_tasks", + "clustering", + "curations", +] +from workflow_array_ephys.export import (ecephys_session_to_nwb, + session_to_nwb, write_nwb) -def test_session_to_nwb(pipeline, ingest_lab, ingest_subjects, ingest_sessions): - nwbfile = session_to_nwb(session_key={"subject": "subject5", - "session_datetime": - datetime.datetime(2018, 7, 3, 20, 32, 28),}, - lab_key={"lab": "LabA"}, - protocol_key={"protocol": "ProtA"}, - project_key={"project": "ProjA"}) +def test_session_to_nwb(pipeline, ingest_lab, ingest_subjects, ingest_sessions): + nwbfile = session_to_nwb( + session_key={ + "subject": "subject5", + "session_datetime": datetime.datetime(2018, 7, 3, 20, 32, 28), + }, + lab_key={"lab": "LabA"}, + protocol_key={"protocol": "ProtA"}, + project_key={"project": "ProjA"}, + ) assert nwbfile.session_id == "subject5_2018-07-03T20:32:28" assert nwbfile.session_description == "Successful data collection" + # when saved in NWB, converts local to UTC assert nwbfile.session_start_time == datetime.datetime( - 2018, 7, 3, 20, 32, 28, tzinfo=datetime.timezone.utc - ) + 2018, 7, 3, 20, 32, 28).astimezone(datetime.timezone.utc) assert nwbfile.experimenter == ["User1"] assert nwbfile.subject.subject_id == "subject5" @@ -42,27 +70,25 @@ def test_session_to_nwb(pipeline, ingest_lab, ingest_subjects, ingest_sessions): def test_write_to_nwb(pipeline, ingest_lab, ingest_subjects, ingest_sessions, ephys_insertionlocation, kilosort_paramset, ephys_recordings, clustering_tasks, clustering, curations): - session = pipeline['session'] - ephys = pipeline['ephys'] - - session_key = dict(subject='subject5', session_datetime='2018-07-03 20:32:28') - - ephys.LFP.populate(session_key, display_progress=True) - ephys.CuratedClustering.populate(session_key, display_progress=True) - ephys.WaveformSet.populate(session_key, display_progress=True) - - nwbfile=ecephys_session_to_nwb(session_key=session_key, - raw=True, - spikes=True, - lfp="dj", - end_frame=None, - lab_key=None, - project_key=None, - protocol_key=None, - nwbfile_kwargs=None, - ) - - write_nwb(nwbfile,'/main/test_data/test1.nwb') + ephys = pipeline['ephys'] + + session_key = dict(subject='subject5', session_datetime='2018-07-03 20:32:28') + + ephys.LFP.populate(session_key, display_progress=True) + ephys.CuratedClustering.populate(session_key, display_progress=True) + ephys.WaveformSet.populate(session_key, display_progress=True) + + nwbfile = ecephys_session_to_nwb(session_key=session_key, + raw=True, + spikes=True, + lfp="dj", + end_frame=None, + lab_key=None, + project_key=None, + protocol_key=None, + nwbfile_kwargs=None) + + write_nwb(nwbfile, '/main/test_data/test1.nwb') def test_convert_to_nwb(pipeline, ingest_lab, ingest_subjects, ingest_sessions, @@ -76,6 +102,7 @@ def test_convert_to_nwb(pipeline, ingest_lab, ingest_subjects, ingest_sessions, ephys.WaveformSet.populate(session_key, display_progress=True) nwbfile = ecephys_session_to_nwb(session_key=session_key, end_frame=1000, + spikes=True, lab_key=dict(lab='LabA'), protocol_key=dict(protocol='ProtA'), project_key=dict(project='ProjA')) @@ -93,9 +120,12 @@ def test_convert_to_nwb(pipeline, ingest_lab, ingest_subjects, ingest_sessions, assert es.conversion == 2.34375e-06 # make sure the ElectricalSeries objects don't share electrodes - assert not set(nwbfile.acquisition["ElectricalSeries1"].electrodes.data) & set(nwbfile.acquisition["ElectricalSeries2"].electrodes.data) + assert not set(nwbfile.acquisition["ElectricalSeries1"].electrodes.data) & set( + nwbfile.acquisition["ElectricalSeries2"].electrodes.data + ) - assert len(nwbfile.units) == 499 + assert len(nwbfile.units) == 499 # TODO: fails bc tests/__init__ isn't buidling right + for col in ("cluster_quality_label", "spike_depths"): assert col in nwbfile.units diff --git a/workflow_array_ephys/pipeline.py b/workflow_array_ephys/pipeline.py index 9fed7353..5701bf22 100644 --- a/workflow_array_ephys/pipeline.py +++ b/workflow_array_ephys/pipeline.py @@ -1,38 +1,75 @@ import datajoint as dj - -from element_lab import lab +import os +from pathlib import Path from element_animal import subject -from element_session import session_with_datetime as session -from element_array_ephys import probe -from element_array_ephys import ephys_acute as ephys +from element_lab import lab +from element_session import session from element_event import trial, event +from element_array_ephys import probe +from element_electrode_localization import coordinate_framework, electrode_localization -from element_lab.lab import Source, Lab, Protocol, User, Project from element_animal.subject import Subject +from element_lab.lab import Source, Lab, Protocol, User, Project from element_session.session_with_datetime import Session -from .paths import get_ephys_root_data_dir, get_session_directory +from .paths import (get_ephys_root_data_dir, + get_session_directory, + get_electrode_localization_dir) + +# session and ephys nwb exports check for these in linking_module +from .export import element_lab_to_nwb_dict, subject_to_nwb, session_to_nwb if 'custom' not in dj.config: dj.config['custom'] = {} db_prefix = dj.config['custom'].get('database.prefix', '') -__all__ = ['lab', 'subject', 'session', 'probe', 'ephys', 'trial', 'event', - 'Source', 'Lab', 'Protocol', 'User', 'Project', 'Subject', 'Session', - 'get_ephys_root_data_dir', 'get_session_directory'] - -# Activate "lab", "subject", "session" schema ------------------------------------------ +# ------------- Import the configured "ephys mode" ------------- +ephys_mode = os.getenv('EPHYS_MODE', + dj.config['custom'].get('ephys_mode', 'acute')) +if ephys_mode == 'acute': + from element_array_ephys import ephys +elif ephys_mode == 'chronic': + from element_array_ephys import ephys_chronic as ephys +elif ephys_mode == 'no-curation': + from element_array_ephys import ephys_no_curation as ephys +else: + raise ValueError(f'Unknown ephys mode: {ephys_mode}') + + +# ---------------- All items in namespace for linter ----------- + +__all__ = [ + # schemas + 'subject', 'lab', 'session', 'trial', 'event', 'probe', 'ephys', + 'coordinate_framework', 'electrode_localization', + # tables + 'Subject', 'Source', 'Lab', 'Protocol', 'User', 'Project', 'Session', + # paths + 'get_ephys_root_data_dir', 'get_session_directory', + 'get_electrode_localization_dir', + # export + 'subject_to_nwb', 'session_to_nwb', "element_lab_to_nwb_dict" + ] + + +# Activate "lab", "subject", "session" schema --------------------------------- lab.activate(db_prefix + 'lab') subject.activate(db_prefix + 'subject', linking_module=__name__) Experimenter = lab.User + session.activate(db_prefix + 'session', linking_module=__name__) -# Declare table "SkullReference" for use in element-array-ephys ------------------------ +# Activate "event" and "trial" schema --------------------------------- + +trial.activate(db_prefix + "trial", db_prefix + "event", linking_module=__name__) + + +# Declare table "SkullReference" for use in element-array-ephys --------------- @lab.schema class SkullReference(dj.Lookup): @@ -42,14 +79,32 @@ class SkullReference(dj.Lookup): contents = zip(['Bregma', 'Lambda']) -# Activate "ephys" schema -------------------------------------------------------------- +# Activate "ephys" schema ----------------------------------------------------- -ephys.activate(db_prefix + 'ephys', - db_prefix + 'probe', +ephys.activate(db_prefix + 'ephys', + db_prefix + 'probe', linking_module=__name__) -# Activate "trial" schema -------------------------------------------------------------- - -trial.activate(db_prefix + 'trial', - db_prefix + 'event', - linking_module=__name__) +# Activate "electrode-localization" schema ------------------------------------ + +ProbeInsertion = ephys.ProbeInsertion +Electrode = probe.ProbeType.Electrode + +electrode_localization.activate(db_prefix + 'electrode_localization', + db_prefix + 'ccf', + linking_module=__name__) + +ccf_id = 0 # Atlas ID +voxel_resolution = 100 + +if ( + not (coordinate_framework.CCF & {"ccf_id": ccf_id}) + and Path(f"./data/annotation_{voxel_resolution}.nrrd").exists() +): + coordinate_framework.load_ccf_annotation( + ccf_id=ccf_id, + version_name="ccf_2017", + voxel_resolution=voxel_resolution, + nrrd_filepath=f"./data/annotation_{voxel_resolution}.nrrd", + ontology_csv_filepath="./data/query.csv", + ) diff --git a/workflow_array_ephys/process.py b/workflow_array_ephys/process.py index 1f3769a2..91568b1d 100644 --- a/workflow_array_ephys/process.py +++ b/workflow_array_ephys/process.py @@ -1,11 +1,11 @@ from workflow_array_ephys.pipeline import ephys -def run(display_progress=True): +def run(display_progress=True, reserve_jobs=False, suppress_errors=False): populate_settings = {'display_progress': display_progress, - 'reserve_jobs': False, - 'suppress_errors': False} + 'reserve_jobs': reserve_jobs, + 'suppress_errors': suppress_errors} print('\n---- Populate ephys.EphysRecording ----') ephys.EphysRecording.populate(**populate_settings) From 4d6e5df03667e0e7a438176c635fe0de336fbf98 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 26 May 2022 17:56:54 -0500 Subject: [PATCH 37/59] WIP: NWB fixes - automate ingest with export test --- tests/__init__.py | 2 ++ tests/test_export.py | 1 + user_data/sessions.csv | 6 +++--- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 96344269..f9a53f01 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -525,3 +525,5 @@ def curations(clustering, pipeline): else: with QuietStdOut(): ephys.Curation.delete() + + diff --git a/tests/test_export.py b/tests/test_export.py index e814c48f..56db5c4c 100644 --- a/tests/test_export.py +++ b/tests/test_export.py @@ -98,6 +98,7 @@ def test_convert_to_nwb(pipeline, ingest_lab, ingest_subjects, ingest_sessions, session_key = dict(subject='subject5', session_datetime='2018-07-03 20:32:28') + ephys.Clustering.populate(session_key,display_progress=True) ephys.CuratedClustering.populate(session_key, display_progress=True) ephys.WaveformSet.populate(session_key, display_progress=True) nwbfile = ecephys_session_to_nwb(session_key=session_key, diff --git a/user_data/sessions.csv b/user_data/sessions.csv index 4e2b93c2..5cd95cd1 100644 --- a/user_data/sessions.csv +++ b/user_data/sessions.csv @@ -1,3 +1,3 @@ -subject,session_dir -subject5,subject5/session1/ -subject6,subject6/session1/ +subject,session_dir,session_note,user +subject5,subject5/session1/,Successful data collection,User1 +subject6,subject6/session1/,Ambient temp abnormally low,User2 From 8162ecfa0fd18bf4ab4e9e390da22438468b0461 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Fri, 27 May 2022 10:42:55 -0500 Subject: [PATCH 38/59] WIP: success in nwb tests --- tests/test_export.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/test_export.py b/tests/test_export.py index 56db5c4c..891fa345 100644 --- a/tests/test_export.py +++ b/tests/test_export.py @@ -1,5 +1,9 @@ from pynwb.ecephys import ElectricalSeries import datetime +import time + +from element_interface.utils import find_root_directory, find_full_path + from . import (dj_config, pipeline, test_data, lab_csv, lab_project_csv, lab_user_csv, lab_publications_csv, lab_keywords_csv, lab_protocol_csv, @@ -88,7 +92,17 @@ def test_write_to_nwb(pipeline, ingest_lab, ingest_subjects, ingest_sessions, protocol_key=None, nwbfile_kwargs=None) - write_nwb(nwbfile, '/main/test_data/test1.nwb') + root_dirs = pipeline["get_ephys_root_data_dir"]() + root_dir = find_root_directory( + root_dirs, + find_full_path( + root_dirs, + (pipeline["session"].SessionDirectory & session_key).fetch1("session_dir"), + ), + ) + + write_nwb(nwbfile, root_dir / time.strftime("_test_%Y%m%d-%H%M%S.nwb")) + def test_convert_to_nwb(pipeline, ingest_lab, ingest_subjects, ingest_sessions, @@ -125,7 +139,7 @@ def test_convert_to_nwb(pipeline, ingest_lab, ingest_subjects, ingest_sessions, nwbfile.acquisition["ElectricalSeries2"].electrodes.data ) - assert len(nwbfile.units) == 499 # TODO: fails bc tests/__init__ isn't buidling right + assert len(nwbfile.units) == 499 for col in ("cluster_quality_label", "spike_depths"): assert col in nwbfile.units From c6449bf20962c4b985e0f753065e780d051c0769 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Tue, 31 May 2022 18:41:26 -0500 Subject: [PATCH 39/59] WIP: Add ephys_mode to README, add NWB notebook --- .gitignore | 62 +- README.md | 33 +- notebooks/01-configure.ipynb | 94 ++- notebooks/09-NWB-export.ipynb | 547 ++++++++++++++++++ notebooks/py_scripts/01-configure.py | 55 +- .../py_scripts/07-downstream-analysis.py | 5 +- .../py_scripts/08-electrode-localization.py | 4 + notebooks/py_scripts/09-NWB-export.py | 125 ++++ workflow_array_ephys/export.py | 2 +- 9 files changed, 798 insertions(+), 129 deletions(-) create mode 100644 notebooks/09-NWB-export.ipynb create mode 100644 notebooks/py_scripts/09-NWB-export.py diff --git a/.gitignore b/.gitignore index e77169f2..92c0b9e4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,6 @@ __pycache__/ *$py.class .pytest_ca*/ -# C extensions -*.so - # Distribution / packaging .Python env/ @@ -30,11 +27,12 @@ wheels/ *.egg .idea/ -# PyInstaller +# PyInstaller, PyBuilder # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec +target/ # Installer logs pip-log.txt @@ -52,77 +50,55 @@ coverage.xml .hypothesis/ /tests/user_data -# Translations +# Translations, C extensions *.mo *.pot +*.so -# Django stuff: -*.log -local_settings.py - -# Flask stuff: +# Flask/Scrapy.Django stuff: instance/ .webassets-cache - -# Scrapy stuff: .scrapy scratchpaper.* +*.log +local_settings.py -# Sphinx documentation +# Sphinx/mkdocs documentation docs/_build/ - -# PyBuilder -target/ +/site # Jupyter Notebook .ipynb_checkpoints -# pyenv -.python-version - -# celery beat schedule file +# celery beat schedule file, SageMath parsed files celerybeat-schedule - -# SageMath parsed files *.sage.py -# dotenv -.env - -# virtualenv +# virtualenv, pyenv, dotenv .venv venv/ ENV/ +.python-version +.env -# Spyder project settings +# Spyder/Rope project, mypy .spyderproject .spyproject - -# Rope project settings .ropeproject - -# mkdocs documentation -/site - -# mypy .mypy_cache/ -# datajoint +# datajoint, nwb, notes dj_local_con*.json +*nwb +temp* # emacs **/*~ **/#*# **/.#* -docker-compose.yml Diagram.ipynb -# docker +# docker, vscode .env - -# vscode +docker-compose.yml .vscode/settings.json - - -# notes -temp* diff --git a/README.md b/README.md index 025cb426..a9e77fe0 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ This repository provides demonstrations for: convention, and directory lookup methods (see [workflow_array_ephys/paths.py](workflow_array_ephys/paths.py)). 3. Ingestion of clustering results. +4. Export of `no_curation` schema to NWB and DANDI (see (notebooks/09-NWB-export.ipynb)[notebooks/09-NWB-export.ipynb] See the [Element Array Electrophysiology documentation](https://elements.datajoint.org/description/array_ephys/) for the background information and development timeline. @@ -24,12 +25,17 @@ For more information on the DataJoint Elements project, please visit https://ele ## Workflow architecture -The electrophysiology workflow presented here uses components from 4 DataJoint -Elements ([element-lab](https://github.com/datajoint/element-lab), -[element-animal](https://github.com/datajoint/element-animal), +The electrophysiology workflow presented here uses components from 4 DataJoint Elements +([element-lab](https://github.com/datajoint/element-lab), +[element-animal](https://github.com/datajoint/element-animal), [element-session](https://github.com/datajoint/element-session), -[element-array-ephys](https://github.com/datajoint/element-array-ephys)) -assembled together to form a fully functional workflow. +[element-array-ephys](https://github.com/datajoint/element-array-ephys)) assembled +together to form a fully functional workflow. Note that element-array-ephys offers three +schema options, selected via the DataJoint config file, with +`dj.config['custom']['ephys_mode']` ++ `acute` probe insertion, with curated clustering ++ `chronic` probe insertion, with curated clustering ++ `no-curation`, with kilosort triggered clustering and supported NWB export ![element-array-ephys](images/attached_array_ephys_element.svg) @@ -50,13 +56,16 @@ The installation instructions can be found at the Please refer to the workflow-specific [Jupyter notebooks](/notebooks) -for an in-depth explanation of how to run the -workflow ([03-process.ipynb](notebooks/03-process.ipynb)) and explore the data -([05-explore.ipynb](notebooks/05-explore.ipynb)). -[07-downstream-analysis.ipynb](notebooks/07-downstream-analysis.ipynb) -and [08-electrode-localization.ipynb](notebooks/08-electrode-localization.ipynb) -explore how to (a) look at trialized analyses, and (b) locate probes within the -[Common Coordinate Framework](https://www.sciencedirect.com/science/article/pii/S0092867420304025). +for an in-depth explanation of how to ... +1. Run the workflow ([03-process.ipynb](notebooks/03-process.ipynb)). +2. Explore the data ([05-explore.ipynb](notebooks/05-explore.ipynb)). +3. Establish downstream analyses +([07-downstream-analysis.ipynb](notebooks/07-downstream-analysis.ipynb)) +4. Examine trialized analyses, and locate probes within the +[Common Coordinate Framework](https://www.sciencedirect.com/science/article/pii/S0092867420304025) +([08-electrode-localization.ipynb](notebooks/08-electrode-localization.ipynb)) +5. Export to NWB and DANDI ([09-NWB-export.ipynb](notebooks/09-NWB-export.ipynb)) + ## Citation diff --git a/notebooks/01-configure.ipynb b/notebooks/01-configure.ipynb index f6c54c16..61271b9a 100644 --- a/notebooks/01-configure.ipynb +++ b/notebooks/01-configure.ipynb @@ -2,21 +2,20 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "id": "15bdef0d-bd52-49e6-87a0-d569006149a0", + "metadata": { + "tags": [] + }, "source": [ - "To run the array ephys workflow, we need to properly set up the DataJoint configuration. The configuration will be saved in a file called `dj_local_conf.json` on each machine and this notebook walks you through the process.\n", + "# DataJoint U24 - Workflow Array Electrophysiology\n", "\n", + "## Setup - Working Directory\n", "\n", - "**The configuration only needs to be set up once**, if you have gone through the configuration before, directly go to [02-workflow-structure](02-workflow-structure-optional.ipynb)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Set up configuration in root directory of this package\n", + "To run the array ephys workflow, we need to properly set up the DataJoint configuration. The configuration will be saved in a file called `dj_local_conf.json` on each machine and this notebook walks you through the process.\n", "\n", - "As a convention, we set the configuration up in the root directory of the workflow package and always starts importing datajoint and pipeline modules from there." + "**The configuration only needs to be set up once**, if you have gone through the configuration before, directly go to [02-workflow-structure](02-workflow-structure-optional.ipynb).\n", + "\n", + "As a convention, we set the configuration up in the root directory of the workflow package and always starts importing datajoint and pipeline modules from ther" ] }, { @@ -26,16 +25,11 @@ "outputs": [], "source": [ "import os\n", - "os.chdir('..')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pwd" + "# change to the upper level folder\n", + "if os.path.basename(os.getcwd())=='notebooks': os.chdir('..')\n", + "assert os.path.basename(os.getcwd())=='workflow-array-ephys', (\"Please move to the \"\n", + " + \"workflow directory\")\n", + "import datajoint as dj" ] }, { @@ -51,13 +45,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Configure database host address and credentials" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "## Setup - Credentials\n", + "\n", "Now let's set up the host, user and password in the `dj.config` global variable" ] }, @@ -93,13 +82,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Configure the `custom` field in `dj.config` for the element-array-ephys" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "## Setup - `dj.config['custom']`\n", + "\n", "The major component of the current workflow is the [DataJoint Array Ephys Element](https://github.com/datajoint/element-array-ephys). Array Ephys Element requires configurations in the field `custom` in `dj.config`:" ] }, @@ -107,7 +91,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Database prefix\n", + "### Database prefix\n", "\n", "Giving a prefix to schema could help on the configuration of privilege settings. For example, if we set prefix `neuro_`, every schema created with the current workflow will start with `neuro_`, e.g. `neuro_lab`, `neuro_subject`, `neuro_ephys` etc.\n", "\n", @@ -127,11 +111,15 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Root directories for raw ephys data and kilosort 2 processed results\n", + "### Root directories for raw/processed data\n", + "\n", + "`ephys_root_data_dir` field indicates the root directory for \n", + "+ The **ephys raw data** from SpikeGLX or OpenEphys, including `*{.ap,lf}.{bin,meta}`\n", + "+ The **clustering results** from kilosort2 (e.g. `spike_{times,clusters}.npy`\n", "\n", - "+ `ephys_root_data_dir` field indicates the root directory for the **ephys raw data** from SpikeGLX or OpenEphys (e.g. `*imec0.ap.bin`, `*imec0.ap.meta`, `*imec0.lf.bin`, `imec0.lf.meta`) or the **clustering results** from kilosort2 (e.g. `spike_times.npy`, `spike_clusters.npy`). The root path typically **do not** contain information of subjects or sessions, all data from subjects/sessions should be subdirectories in the root path.\n", + "The root path typically **do not** contain information of subjects or sessions, all data from subjects/sessions should be subdirectories in the root path.\n", "\n", - "In the example dataset downloaded with [this instruction](00-data-download-optional.ipynb), `/tmp/test_data` will be the root\n", + "In the example dataset downloaded with [these instructions](00-data-download-optional.ipynb), `/tmp/test_data` will be the root\n", "\n", "```\n", "/tmp/test_data/\n", @@ -152,7 +140,7 @@ "# If there is only one root path.\n", "dj.config['custom']['ephys_root_data_dir'] = '/tmp/test_data'\n", "# If there are multiple possible root paths:\n", - "dj.config['custom']['ephys_root_data_dir'] = ['/tmp/test_data']" + "dj.config['custom']['ephys_root_data_dir'] = ['/tmp/test_data1', '/tmp/test_data2']" ] }, { @@ -168,7 +156,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "+ In the database, every path for the ephys raw data is **relative to this root path**. The benefit is that the absolute path could be configured for each machine, and when data transfer happens, we just need to change the root directory in the config file.\n", + "+ In the database, every path for the ephys raw data is **relative to root path(s)**. The benefit is that the absolute path could be configured for each machine, and when data transfer happens, we just need to change the root directory in the config file.\n", "+ The workflow supports **multiple root directories**. If there are multiple possible root directories, specify the `ephys_root_data_dir` as a list.\n", "+ The root path(s) are **specific to each machine**, as the name of drive mount could be different for different operating systems or machines.\n", "+ In the context of the workflow, all the paths saved into the database or saved in the config file need to be in the **POSIX standards** (Unix/Linux), with `/`. The path conversion for machines of any operating system is taken care of inside the elements." @@ -178,13 +166,26 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Save the configuration as a json file" + "### Ephys Mode\n", + "\n", + "`element-array-ephys` offers 3 different schemas: `acute`, `chronic`, and `no-curation`. For more information about each, please visit the [electrophysiology description page](https://elements.datajoint.org/description/array_ephys/). This decision should be made before first activating the schema. Note: only `no-curation` is supported for export to NWB directly from the Element." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dj.config['custom']['ephys_mode']='no-curation' # or acute or chronic" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## Save configuration\n", + "\n", "With the proper configurations, we could save this as a file, either as a local json file, or a global file." ] }, @@ -228,7 +229,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Next Step" + "## Next Step" ] }, { @@ -237,13 +238,6 @@ "source": [ "After the configuration, we will be able to review the workflow structure with [02-workflow-structure-optional](02-workflow-structure-optional.ipynb)." ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/notebooks/09-NWB-export.ipynb b/notebooks/09-NWB-export.ipynb new file mode 100644 index 00000000..7edc6923 --- /dev/null +++ b/notebooks/09-NWB-export.ipynb @@ -0,0 +1,547 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "15bdef0d-bd52-49e6-87a0-d569006149a0", + "metadata": { + "tags": [] + }, + "source": [ + "# DataJoint U24 - Workflow Array Electrophysiology" + ] + }, + { + "cell_type": "markdown", + "id": "4ad5b737", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "First, let's change directories to find the `dj_local_conf` file." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "921a4a03", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "# change to the upper level folder to detect dj_local_conf.json\n", + "if os.path.basename(os.getcwd())=='notebooks': os.chdir('..')\n", + "assert os.path.basename(os.getcwd())=='workflow-array-ephys', (\"Please move to the \"\n", + " + \"workflow directory\")\n", + "# We'll be working with long tables, so we'll make visualization easier with a limit\n", + "import datajoint as dj; dj.config['display.limit']=10" + ] + }, + { + "cell_type": "markdown", + "id": "84b2c6ae-b8cd-47b8-af38-812f65032933", + "metadata": {}, + "source": [ + "If you haven't already populated the `lab`, `subject`, `session`, `probe`, and `ephys` schemas, please do so now with [04-automate](./04-automate-optional.ipynb). Note: exporting `ephys` data is currently only supported on the `no_curation` schema. " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "79cef246", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting cbroz@dss-db.datajoint.io:3306\n" + ] + } + ], + "source": [ + "from workflow_array_ephys.pipeline import lab, subject, session, probe, ephys\n", + "from workflow_array_ephys.export import (element_lab_to_nwb_dict, subject_to_nwb, \n", + " session_to_nwb, ecephys_session_to_nwb, \n", + " write_nwb)\n", + "from element_interface.dandi import upload_to_dandi" + ] + }, + { + "cell_type": "markdown", + "id": "bafd4a7c", + "metadata": {}, + "source": [ + "## Export to NWB\n", + "\n", + "Each of the following elements has tools for interacting with NWB files: `element-lab`, `element-session`, `element-array-ephys`, and `element-interface`. We'll use the following keys for testing these functions:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e6cebd98", + "metadata": {}, + "outputs": [], + "source": [ + "lab_key={\"lab\": \"LabA\"}\n", + "protocol_key={\"protocol\": \"ProtA\"}\n", + "project_key={\"project\": \"ProjA\"}\n", + "session_key={\"subject\": \"subject5\",\n", + " \"session_datetime\": \"2018-07-03 20:32:28\"}" + ] + }, + { + "cell_type": "markdown", + "id": "fc2d028a", + "metadata": {}, + "source": [ + "\n", + "### Element Lab\n", + "\n", + "Because an NWB file must include session information, `element_lab_to_nwb_dict` can only help package information from the Lab schema into `dict` format. This would be helpful for a team using Element Lab, but not others. " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "a6a3306b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on function element_lab_to_nwb_dict in module element_lab.export.nwb:\n", + "\n", + "element_lab_to_nwb_dict(lab_key=None, project_key=None, protocol_key=None)\n", + " Generate a dictionary object containing all relevant lab information used\n", + " when generating an NWB file at the session level.\n", + " All parameters optional, but should only specify one of respective type\n", + " Use: mynwbfile = pynwb.NWBFile(identifier=\"your identifier\",\n", + " session_description=\"your description\",\n", + " session_start_time=session_datetime,\n", + " **element_lab_to_nwb_dict(\n", + " lab_key=key1,\n", + " project_key=key2,\n", + " protocol_key=key3))\n", + " \n", + " :param lab_key: Key specifying one entry in element_lab.lab.Lab\n", + " :param project_key: Key specifying one entry in element_lab.lab.Project\n", + " :param protocol_key: Key specifying one entry in element_lab.lab.Protocol\n", + " :return: dictionary with NWB parameters\n", + "\n" + ] + } + ], + "source": [ + "help(element_lab_to_nwb_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "61ba0714", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'institution': 'Example Uni',\n", + " 'lab': 'The Example Lab',\n", + " 'experiment_description': 'Example project to populate element-lab',\n", + " 'keywords': ['Example', 'Study'],\n", + " 'related_publications': ['arXiv:1807.11104', 'arXiv:1807.11104v1'],\n", + " 'protocol': 'ProtA',\n", + " 'notes': 'Protocol for managing data ingestion'}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "element_lab_to_nwb_dict(lab_key=lab_key, protocol_key=protocol_key, \n", + " project_key=project_key)" + ] + }, + { + "cell_type": "markdown", + "id": "66af900a", + "metadata": {}, + "source": [ + "### Element Animal\n", + "\n", + "`subject_to_nwb` can use a session key to retrieve subject information, and will return an nwb file with a number of sections specified. When packaging into an NWB file, `pynwb` will display a warning regarding timezone information - datetime fields are assumed to be in local time, and will be converted to UTC." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "71433b2e", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/cb/miniconda3/envs/ele/lib/python3.8/site-packages/pynwb/file.py:1037: UserWarning: Date is missing timezone information. Updating to local timezone.\n", + " warn(\"Date is missing timezone information. Updating to local timezone.\")\n" + ] + }, + { + "data": { + "text/plain": [ + "subject pynwb.file.Subject at 0x140613566479040\n", + "Fields:\n", + " date_of_birth: 2020-01-01 00:00:00-06:00\n", + " description: {\"subject\": \"subject5\", \"sex\": \"F\", \"subject_birth_date\": \"2020-01-01\", \"subject_description\": \"rich\", \"line\": null, \"strain\": null, \"source\": null}\n", + " sex: F\n", + " subject_id: subject5" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "subject_to_nwb(session_key=session_key)" + ] + }, + { + "cell_type": "markdown", + "id": "b1d2c7e3", + "metadata": {}, + "source": [ + "### Element Session\n", + "\n", + "`session_to_nwb` pulls the same information as above, while also including information about session experimenter and session time. The export process provides the same warning about timezone conversion to UTC.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "cc1b52b7", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/cb/miniconda3/envs/ele/lib/python3.8/site-packages/pynwb/file.py:1037: UserWarning: Date is missing timezone information. Updating to local timezone.\n", + " warn(\"Date is missing timezone information. Updating to local timezone.\")\n" + ] + }, + { + "data": { + "text/plain": [ + "root pynwb.file.NWBFile at 0x140613434487232\n", + "Fields:\n", + " experimenter: ['User1']\n", + " file_create_date: [datetime.datetime(2022, 5, 31, 13, 58, 25, 578725, tzinfo=tzlocal())]\n", + " identifier: 9d2131bb-5747-4bf1-95f0-4b24b54b1968\n", + " session_description: Successful data collection\n", + " session_id: subject5_2018-07-03T20:32:28\n", + " session_start_time: 2018-07-04 01:32:28+00:00\n", + " subject: subject pynwb.file.Subject at 0x140613566479712\n", + "Fields:\n", + " date_of_birth: 2020-01-01 00:00:00-06:00\n", + " description: {\"subject\": \"subject5\", \"sex\": \"F\", \"subject_birth_date\": \"2020-01-01\", \"subject_description\": \"rich\", \"line\": null, \"strain\": null, \"source\": null}\n", + " sex: F\n", + " subject_id: subject5\n", + "\n", + " timestamps_reference_time: 2018-07-04 01:32:28+00:00" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "session_to_nwb(session_key=session_key)" + ] + }, + { + "cell_type": "markdown", + "id": "bb5fba81", + "metadata": {}, + "source": [ + "\n", + "### Element Array Electrophysiology\n", + "\n", + "`ecephys_session_to_nwb` provides a full export mechanism, returning an NWB file with raw, data, spikes, and LFP. Optional arguments determine which pieces are exported. For demonstration purposes, we recommend limiting `end_frame`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "7c2f913c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on function ecephys_session_to_nwb in module element_array_ephys.export.nwb.nwb:\n", + "\n", + "ecephys_session_to_nwb(session_key, raw=True, spikes=True, lfp='source', end_frame=None, lab_key=None, project_key=None, protocol_key=None, nwbfile_kwargs=None)\n", + " Main function for converting ephys data to NWB\n", + " \n", + " Parameters\n", + " ----------\n", + " session_key: dict\n", + " raw: bool\n", + " Whether to include the raw data from source. SpikeGLX and OpenEphys are supported\n", + " spikes: bool\n", + " Whether to include CuratedClustering\n", + " lfp:\n", + " \"dj\" - read LFP data from ephys.LFP\n", + " \"source\" - read LFP data from source (SpikeGLX supported)\n", + " False - do not convert LFP\n", + " end_frame: int, optional\n", + " Used to create small test conversions where large datasets are truncated.\n", + " lab_key, project_key, and protocol_key: dictionaries used to look up optional additional metadata\n", + " nwbfile_kwargs: dict, optional\n", + " - If element-session is not being used, this argument is required and must be a dictionary containing\n", + " 'session_description' (str), 'identifier' (str), and 'session_start_time' (datetime),\n", + " the minimal data for instantiating an NWBFile object.\n", + " \n", + " - If element-session is being used, this argument can optionally be used to add over overwrite NWBFile fields.\n", + "\n" + ] + } + ], + "source": [ + "help(ecephys_session_to_nwb)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5edf9615", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/cb/miniconda3/envs/ele/lib/python3.8/site-packages/pynwb/file.py:1037: UserWarning: Date is missing timezone information. Updating to local timezone.\n", + " warn(\"Date is missing timezone information. Updating to local timezone.\")\n", + "creating units table for paramset 0: 100%|██████████| 499/499 [00:41<00:00, 12.11it/s]\n" + ] + } + ], + "source": [ + "nwbfile = ecephys_session_to_nwb(session_key=session_key,\n", + " raw=True,\n", + " spikes=True,\n", + " lfp=\"dj\",\n", + " end_frame=100,\n", + " lab_key=lab_key,\n", + " project_key=project_key,\n", + " protocol_key=protocol_key,\n", + " nwbfile_kwargs=None)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "1131e149", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "root pynwb.file.NWBFile at 0x140297891486016\n", + "Fields:\n", + " acquisition: {\n", + " ElectricalSeries1 ,\n", + " ElectricalSeries2 \n", + " }\n", + " devices: {\n", + " 262716621 ,\n", + " 714000838 \n", + " }\n", + " electrode_groups: {\n", + " probe262716621_shank0 ,\n", + " probe714000838_shank0 \n", + " }\n", + " electrodes: electrodes \n", + " experiment_description: Example project to populate element-lab\n", + " experimenter: ['User1']\n", + " file_create_date: [datetime.datetime(2022, 5, 31, 15, 47, 41, 270996, tzinfo=tzlocal())]\n", + " identifier: 172f2d3b-44c1-4ae1-8785-2d20d3df3db1\n", + " institution: Example Uni\n", + " keywords: ['Example' 'Study']\n", + " lab: The Example Lab\n", + " notes: Protocol for managing data ingestion\n", + " processing: {\n", + " ecephys \n", + " }\n", + " protocol: ProtA\n", + " related_publications: ['arXiv:1807.11104' 'arXiv:1807.11104v1']\n", + " session_description: Successful data collection\n", + " session_id: subject5_2018-07-03T20:32:28\n", + " session_start_time: 2018-07-04 01:32:28+00:00\n", + " subject: subject pynwb.file.Subject at 0x140297891485200\n", + "Fields:\n", + " date_of_birth: 2020-01-01 00:00:00-06:00\n", + " description: {\"subject\": \"subject5\", \"sex\": \"F\", \"subject_birth_date\": \"2020-01-01\", \"subject_description\": \"rich\", \"line\": null, \"strain\": null, \"source\": null}\n", + " sex: F\n", + " subject_id: subject5\n", + "\n", + " timestamps_reference_time: 2018-07-04 01:32:28+00:00\n", + " units: units " + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "nwbfile" + ] + }, + { + "cell_type": "markdown", + "id": "2d6393fc", + "metadata": {}, + "source": [ + "`write_nwb` can then be used to write this file to disk. The following cell will include a timestamp in the filename." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "eb3f1030", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "from workflow_array_ephys.paths import get_ephys_root_data_dir\n", + " \n", + "write_nwb(nwbfile, f'./temp_nwb/{time.strftime(\"_test_%Y%m%d-%H%M%S.nwb\")}')" + ] + }, + { + "cell_type": "markdown", + "id": "f717baf8", + "metadata": {}, + "source": [ + "## DANDI Export\n", + "\n", + "`element-interface.dandi` includes the `upload_to_dandi` utility to support direct uploads. For more information, see [DANDI documentation](https://www.dandiarchive.org/handbook/10_using_dandi/).\n", + "\n", + "In order to upload, you'll need...\n", + "1. A DANDI account\n", + "2. A `DANDI_API_KEY`\n", + "3. A `dandiset_id`\n", + "\n", + "These values can be added to your `dj.config` as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3a75d1e4", + "metadata": {}, + "outputs": [], + "source": [ + "dj.config['custom']['dandiset_id']=\"\" \n", + "dj.config['custom']['dandi.api']=\"<40-character alphanumeric string>\"" + ] + }, + { + "cell_type": "markdown", + "id": "26f65d15", + "metadata": {}, + "source": [ + "This would facilitate routine updating of your dandiset." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "84dbce9f-825e-49a4-b49f-58b406873430", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "A newer version (0.40.0) of dandi/dandi-cli is available. You are using 0.39.4\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PATH SIZE DONE DONE% CHECKSUM STATUS MESSAGE \n", + "dandiset.yaml done updated \n", + "Summary: 0 Bytes 1 done 1 updated \n", + " <0.00% \n" + ] + }, + { + "ename": "FileExistsError", + "evalue": "File './temp_nwb/200178/test1.nwb' already exists", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mFileExistsError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_70901/2105288079.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m upload_to_dandi(\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mdata_directory\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"./temp_nwb/\"\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0mdandiset_id\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mdj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'custom'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'dandiset_id'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mstaging\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mworking_directory\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"./temp_nwb/\"\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/Documents/dev/element-interface/element_interface/dandi.py\u001b[0m in \u001b[0;36mupload_to_dandi\u001b[0;34m(data_directory, dandiset_id, staging, working_directory, api_key, sync)\u001b[0m\n\u001b[1;32m 40\u001b[0m \u001b[0mdandiset_directory\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mworking_directory\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdandiset_id\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m#enforce str\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 41\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 42\u001b[0;31m download(\n\u001b[0m\u001b[1;32m 43\u001b[0m \u001b[0;34mf\"https://gui-staging.dandiarchive.org/#/dandiset/{dandiset_id}\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 44\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mstaging\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/ele/lib/python3.8/site-packages/dandi/download.py\u001b[0m in \u001b[0;36mdownload\u001b[0;34m(urls, output_dir, format, existing, jobs, jobs_per_zarr, get_metadata, get_assets, sync)\u001b[0m\n\u001b[1;32m 146\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mformat\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"pyout\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 147\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mout\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 148\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mrec\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mgen_\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 149\u001b[0m \u001b[0mout\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrec\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 150\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/ele/lib/python3.8/site-packages/dandi/download.py\u001b[0m in \u001b[0;36mdownload_generator\u001b[0;34m(parsed_url, output_path, assets_it, yield_generator_for_fields, existing, get_metadata, get_assets, jobs_per_zarr)\u001b[0m\n\u001b[1;32m 317\u001b[0m \u001b[0;32myield\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m\"path\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mpath\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0myield_generator_for_fields\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0m_download_generator\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 318\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 319\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mresp\u001b[0m \u001b[0;32min\u001b[0m \u001b[0m_download_generator\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 320\u001b[0m \u001b[0;32myield\u001b[0m \u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mresp\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpath\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 321\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/ele/lib/python3.8/site-packages/dandi/download.py\u001b[0m in \u001b[0;36m_download_file\u001b[0;34m(downloader, path, toplevel_path, lock, size, mtime, existing, digests, digest_callback)\u001b[0m\n\u001b[1;32m 523\u001b[0m \u001b[0mannex_path\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mop\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtoplevel_path\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\".git\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"annex\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 524\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mexisting\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"error\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 525\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mFileExistsError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"File {path!r} already exists\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 526\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mexisting\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"skip\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 527\u001b[0m \u001b[0;32myield\u001b[0m \u001b[0m_skip_file\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"already exists\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mFileExistsError\u001b[0m: File './temp_nwb/200178/test1.nwb' already exists" + ] + } + ], + "source": [ + "upload_to_dandi(\n", + " data_directory=\"./temp_nwb/\",\n", + " dandiset_id=dj.config['custom']['dandiset_id'],\n", + " staging=True,\n", + " working_directory=\"./temp_nwb/\",\n", + " api_key=dj.config['custom']['dandi.api'],\n", + " sync=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c0dd56de-ecf3-469b-8aed-e2484402bcdc", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "interpreter": { + "hash": "61456c693db5d9aa6731701ec9a9b08ab88a172bee0780139a3679beb166da16" + }, + "kernelspec": { + "display_name": "Python 3.8.11 ('ele')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/py_scripts/01-configure.py b/notebooks/py_scripts/01-configure.py index e49fe0a4..64868022 100644 --- a/notebooks/py_scripts/01-configure.py +++ b/notebooks/py_scripts/01-configure.py @@ -13,24 +13,29 @@ # name: bl_dev # --- -# To run the array ephys workflow, we need to properly set up the DataJoint configuration. The configuration will be saved in a file called `dj_local_conf.json` on each machine and this notebook walks you through the process. +# + [markdown] tags=[] +# # DataJoint U24 - Workflow Array Electrophysiology +# +# ## Setup - Working Directory # +# To run the array ephys workflow, we need to properly set up the DataJoint configuration. The configuration will be saved in a file called `dj_local_conf.json` on each machine and this notebook walks you through the process. # # **The configuration only needs to be set up once**, if you have gone through the configuration before, directly go to [02-workflow-structure](02-workflow-structure-optional.ipynb). - -# # Set up configuration in root directory of this package # -# As a convention, we set the configuration up in the root directory of the workflow package and always starts importing datajoint and pipeline modules from there. +# As a convention, we set the configuration up in the root directory of the workflow package and always starts importing datajoint and pipeline modules from ther +# - import os -os.chdir('..') - -pwd - +# change to the upper level folder +if os.path.basename(os.getcwd())=='notebooks': os.chdir('..') +assert os.path.basename(os.getcwd())=='workflow-array-ephys', ("Please move to the " + + "workflow directory") import datajoint as dj -# # Configure database host address and credentials +import datajoint as dj +# ## Setup - Credentials +# # Now let's set up the host, user and password in the `dj.config` global variable import getpass @@ -42,11 +47,11 @@ dj.conn() -# # Configure the `custom` field in `dj.config` for the element-array-ephys - +# ## Setup - `dj.config['custom']` +# # The major component of the current workflow is the [DataJoint Array Ephys Element](https://github.com/datajoint/element-array-ephys). Array Ephys Element requires configurations in the field `custom` in `dj.config`: -# ## Database prefix +# ### Database prefix # # Giving a prefix to schema could help on the configuration of privilege settings. For example, if we set prefix `neuro_`, every schema created with the current workflow will start with `neuro_`, e.g. `neuro_lab`, `neuro_subject`, `neuro_ephys` etc. # @@ -54,11 +59,15 @@ dj.config['custom'] = {'database.prefix': 'neuro_'} -# ## Root directories for raw ephys data and kilosort 2 processed results +# ### Root directories for raw/processed data # -# + `ephys_root_data_dir` field indicates the root directory for the **ephys raw data** from SpikeGLX or OpenEphys (e.g. `*imec0.ap.bin`, `*imec0.ap.meta`, `*imec0.lf.bin`, `imec0.lf.meta`) or the **clustering results** from kilosort2 (e.g. `spike_times.npy`, `spike_clusters.npy`). The root path typically **do not** contain information of subjects or sessions, all data from subjects/sessions should be subdirectories in the root path. +# `ephys_root_data_dir` field indicates the root directory for +# + The **ephys raw data** from SpikeGLX or OpenEphys, including `*{.ap,lf}.{bin,meta}` +# + The **clustering results** from kilosort2 (e.g. `spike_{times,clusters}.npy` # -# In the example dataset downloaded with [this instruction](00-data-download-optional.ipynb), `/tmp/test_data` will be the root +# The root path typically **do not** contain information of subjects or sessions, all data from subjects/sessions should be subdirectories in the root path. +# +# In the example dataset downloaded with [these instructions](00-data-download-optional.ipynb), `/tmp/test_data` will be the root # # ``` # /tmp/test_data/ @@ -72,17 +81,23 @@ # If there is only one root path. dj.config['custom']['ephys_root_data_dir'] = '/tmp/test_data' # If there are multiple possible root paths: -dj.config['custom']['ephys_root_data_dir'] = ['/tmp/test_data'] +dj.config['custom']['ephys_root_data_dir'] = ['/tmp/test_data1', '/tmp/test_data2'] dj.config -# + In the database, every path for the ephys raw data is **relative to this root path**. The benefit is that the absolute path could be configured for each machine, and when data transfer happens, we just need to change the root directory in the config file. +# + In the database, every path for the ephys raw data is **relative to root path(s)**. The benefit is that the absolute path could be configured for each machine, and when data transfer happens, we just need to change the root directory in the config file. # + The workflow supports **multiple root directories**. If there are multiple possible root directories, specify the `ephys_root_data_dir` as a list. # + The root path(s) are **specific to each machine**, as the name of drive mount could be different for different operating systems or machines. # + In the context of the workflow, all the paths saved into the database or saved in the config file need to be in the **POSIX standards** (Unix/Linux), with `/`. The path conversion for machines of any operating system is taken care of inside the elements. -# # Save the configuration as a json file +# ### Ephys Mode +# +# `element-array-ephys` offers 3 different schemas: `acute`, `chronic`, and `no-curation`. For more information about each, please visit the [electrophysiology description page](https://elements.datajoint.org/description/array_ephys/). This decision should be made before first activating the schema. Note: only `no-curation` is supported for export to NWB directly from the Element. + +dj.config['custom']['ephys_mode']='no-curation' # or acute or chronic +# ## Save configuration +# # With the proper configurations, we could save this as a file, either as a local json file, or a global file. dj.config.save_local() @@ -97,8 +112,6 @@ # dj.config.save_global() # - -# # Next Step +# ## Next Step # After the configuration, we will be able to review the workflow structure with [02-workflow-structure-optional](02-workflow-structure-optional.ipynb). - - diff --git a/notebooks/py_scripts/07-downstream-analysis.py b/notebooks/py_scripts/07-downstream-analysis.py index 0009f4e0..73369ef5 100644 --- a/notebooks/py_scripts/07-downstream-analysis.py +++ b/notebooks/py_scripts/07-downstream-analysis.py @@ -77,7 +77,7 @@ event.AlignmentEvent() -# + [markdown] jp-MarkdownHeadingCollapsed=true tags=[] +# + [markdown] tags=[] # ## Event-aligned spike times # - @@ -100,7 +100,8 @@ # The `analysis` schema provides example tables to perform event-aligned spike-times analysis. -analysis.schema.list_tables() +(dj.Diagram(analysis) + dj.Diagram(event.AlignmentEvent) + dj.Diagram(trial.Trial) + + dj.Diagram(ephys.CuratedClustering)) # + ***SpikesAlignmentCondition*** - a manual table to specify the inputs and condition for the analysis [markdown] # Let's start by creating several analyses configuration - i.e. inserting into ***SpikesAlignmentCondition*** for the `center` event, called `center_button` in the `AlignmentEvent` table. diff --git a/notebooks/py_scripts/08-electrode-localization.py b/notebooks/py_scripts/08-electrode-localization.py index 3b3595de..ea6ef78d 100644 --- a/notebooks/py_scripts/08-electrode-localization.py +++ b/notebooks/py_scripts/08-electrode-localization.py @@ -39,6 +39,8 @@ from workflow_array_ephys.localization import coordinate_framework as ccf +dj.Diagram(ccf) + # Now, to explore the data we just loaded. ccf.BrainRegionAnnotation.BrainRegion() @@ -67,6 +69,8 @@ from workflow_array_ephys.localization import coordinate_framework as ccf from workflow_array_ephys.localization import electrode_localization as eloc +(dj.Diagram(eloc) + dj.Diagram(ccf) - 1) + # Because the probe may not be fully inserted, there will be some electrode positions that occur outside the brain. We register these instances with an `IntegrityError` warning because we're trying to register a coorinate position with no corresponding location in the `ccf.CCF.Voxel` table. We can silence these warnings by setting the log level before running `populate()` on the `ElectrodePosition` table. import logging diff --git a/notebooks/py_scripts/09-NWB-export.py b/notebooks/py_scripts/09-NWB-export.py new file mode 100644 index 00000000..c9d7da0e --- /dev/null +++ b/notebooks/py_scripts/09-NWB-export.py @@ -0,0 +1,125 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.13.7 +# kernelspec: +# display_name: Python 3.8.11 ('ele') +# language: python +# name: python3 +# --- + +# + [markdown] tags=[] +# # DataJoint U24 - Workflow Array Electrophysiology +# - + +# ## Setup +# +# First, let's change directories to find the `dj_local_conf` file. + +import os +# change to the upper level folder to detect dj_local_conf.json +if os.path.basename(os.getcwd())=='notebooks': os.chdir('..') +assert os.path.basename(os.getcwd())=='workflow-array-ephys', ("Please move to the " + + "workflow directory") +# We'll be working with long tables, so we'll make visualization easier with a limit +import datajoint as dj; dj.config['display.limit']=10 + +# If you haven't already populated the `lab`, `subject`, `session`, `probe`, and `ephys` schemas, please do so now with [04-automate](./04-automate-optional.ipynb). Note: exporting `ephys` data is currently only supported on the `no_curation` schema. + +from workflow_array_ephys.pipeline import lab, subject, session, probe, ephys +from workflow_array_ephys.export import (element_lab_to_nwb_dict, subject_to_nwb, + session_to_nwb, ecephys_session_to_nwb, + write_nwb) +from element_interface.dandi import upload_to_dandi + +# ## Export to NWB +# +# Each of the following elements has tools for interacting with NWB files: `element-lab`, `element-session`, `element-array-ephys`, and `element-interface`. We'll use the following keys for testing these functions: + +lab_key={"lab": "LabA"} +protocol_key={"protocol": "ProtA"} +project_key={"project": "ProjA"} +session_key={"subject": "subject5", + "session_datetime": "2018-07-03 20:32:28"} + +# +# ### Element Lab +# +# Because an NWB file must include session information, `element_lab_to_nwb_dict` can only help package information from the Lab schema into `dict` format. This would be helpful for a team using Element Lab, but not others. + +help(element_lab_to_nwb_dict) + +element_lab_to_nwb_dict(lab_key=lab_key, protocol_key=protocol_key, + project_key=project_key) + +# ### Element Animal +# +# `subject_to_nwb` can use a session key to retrieve subject information, and will return an nwb file with a number of sections specified. When packaging into an NWB file, `pynwb` will display a warning regarding timezone information - datetime fields are assumed to be in local time, and will be converted to UTC. + +subject_to_nwb(session_key=session_key) + +# ### Element Session +# +# `session_to_nwb` pulls the same information as above, while also including information about session experimenter and session time. The export process provides the same warning about timezone conversion to UTC. +# + +session_to_nwb(session_key=session_key) + +# +# ### Element Array Electrophysiology +# +# `ecephys_session_to_nwb` provides a full export mechanism, returning an NWB file with raw, data, spikes, and LFP. Optional arguments determine which pieces are exported. For demonstration purposes, we recommend limiting `end_frame`. +# + +help(ecephys_session_to_nwb) + +nwbfile = ecephys_session_to_nwb(session_key=session_key, + raw=True, + spikes=True, + lfp="dj", + end_frame=100, + lab_key=lab_key, + project_key=project_key, + protocol_key=protocol_key, + nwbfile_kwargs=None) + +nwbfile + +# `write_nwb` can then be used to write this file to disk. The following cell will include a timestamp in the filename. + +# + +import time +from workflow_array_ephys.paths import get_ephys_root_data_dir + +write_nwb(nwbfile, f'./temp_nwb/{time.strftime("_test_%Y%m%d-%H%M%S.nwb")}') +# - + +# ## DANDI Export +# +# `element-interface.dandi` includes the `upload_to_dandi` utility to support direct uploads. For more information, see [DANDI documentation](https://www.dandiarchive.org/handbook/10_using_dandi/). +# +# In order to upload, you'll need... +# 1. A DANDI account +# 2. A `DANDI_API_KEY` +# 3. A `dandiset_id` +# +# These values can be added to your `dj.config` as follows: + +dj.config['custom']['dandiset_id']="" +dj.config['custom']['dandi.api']="<40-character alphanumeric string>" + +# This would facilitate routine updating of your dandiset. + +upload_to_dandi( + data_directory="./temp_nwb/", + dandiset_id=dj.config['custom']['dandiset_id'], + staging=True, + working_directory="./temp_nwb/", + api_key=dj.config['custom']['dandi.api'], + sync=False) + + diff --git a/workflow_array_ephys/export.py b/workflow_array_ephys/export.py index 930060da..1f8307e3 100644 --- a/workflow_array_ephys/export.py +++ b/workflow_array_ephys/export.py @@ -3,7 +3,7 @@ from element_animal.export.nwb import subject_to_nwb from element_session.export.nwb import session_to_nwb -# Import NWB export functions +# Import ephys NWB export functions from element_array_ephys.export.nwb import ecephys_session_to_nwb, write_nwb __all__ = ['element_lab_to_nwb_dict', 'subject_to_nwb', 'session_to_nwb', From 38baa4efd88fe7fc26f318329e09f0d6b7bc5dcb Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Wed, 1 Jun 2022 14:48:50 -0500 Subject: [PATCH 40/59] Validated dandi upload --- notebooks/09-NWB-export.ipynb | 100 +++++++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 20 deletions(-) diff --git a/notebooks/09-NWB-export.ipynb b/notebooks/09-NWB-export.ipynb index 7edc6923..88e42668 100644 --- a/notebooks/09-NWB-export.ipynb +++ b/notebooks/09-NWB-export.ipynb @@ -464,40 +464,100 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 5, "id": "84dbce9f-825e-49a4-b49f-58b406873430", "metadata": {}, "outputs": [ { - "name": "stderr", + "name": "stdout", "output_type": "stream", "text": [ - "A newer version (0.40.0) of dandi/dandi-cli is available. You are using 0.39.4\n" + "PATH SIZE DONE DONE% CHECKSUM STATUS MESSAGE \n", + "dandiset.yaml done updated \n", + "test1.nwb 109.8 MB 109.8 MB 100% ok done \n", + "Summary: 109.8 MB 109.8 MB 2 done 1 updated \n", + " 100.00% \n", + "Usage: dandi [OPTIONS] COMMAND [ARGS]...\n", + "\n", + " A client to support interactions with DANDI archive\n", + " (http://dandiarchive.org).\n", + "\n", + " To see help for a specific command, run\n", + "\n", + " dandi COMMAND --help\n", + "\n", + " e.g. dandi upload --help\n", + "\n", + "Options:\n", + " --version\n", + " -l, --log-level [DEBUG|INFO|WARNING|ERROR|CRITICAL]\n", + " Log level (case insensitive). May be\n", + " specified as an integer. [default: INFO]\n", + " --pdb Fall into pdb if errors out\n", + " --help Show this message and exit.\n", + "\n", + "Commands:\n", + " delete Delete dandisets and assets from the server.\n", + " digest Calculate file digests\n", + " download Download a file or entire folder from DANDI.\n", + " instances List known Dandi Archive instances that the CLI can...\n", + " ls List .nwb files and dandisets metadata.\n", + " organize (Re)organize files according to the metadata.\n", + " shell-completion Emit shell script for enabling command completion.\n", + " upload Upload Dandiset files to DANDI Archive.\n", + " validate Validate files for NWB and DANDI compliance.\n", + " validate-bids Validate BIDS paths.\n", + "Usage: dandi [OPTIONS] COMMAND [ARGS]...\n", + "\n", + " A client to support interactions with DANDI archive\n", + " (http://dandiarchive.org).\n", + "\n", + " To see help for a specific command, run\n", + "\n", + " dandi COMMAND --help\n", + "\n", + " e.g. dandi upload --help\n", + "\n", + "Options:\n", + " --version\n", + " -l, --log-level [DEBUG|INFO|WARNING|ERROR|CRITICAL]\n", + " Log level (case insensitive). May be\n", + " specified as an integer. [default: INFO]\n", + " --pdb Fall into pdb if errors out\n", + " --help Show this message and exit.\n", + "\n", + "Commands:\n", + " delete Delete dandisets and assets from the server.\n", + " digest Calculate file digests\n", + " download Download a file or entire folder from DANDI.\n", + " instances List known Dandi Archive instances that the CLI can...\n", + " ls List .nwb files and dandisets metadata.\n", + " organize (Re)organize files according to the metadata.\n", + " shell-completion Emit shell script for enabling command completion.\n", + " upload Upload Dandiset files to DANDI Archive.\n", + " validate Validate files for NWB and DANDI compliance.\n", + " validate-bids Validate BIDS paths.\n", + "work_dir: ./temp_nwb/\n", + "data_dir: ./temp_nwb/\n", + "dand_dir: ./temp_nwb/200178\n" ] }, { - "name": "stdout", + "name": "stderr", "output_type": "stream", "text": [ - "PATH SIZE DONE DONE% CHECKSUM STATUS MESSAGE \n", - "dandiset.yaml done updated \n", - "Summary: 0 Bytes 1 done 1 updated \n", - " <0.00% \n" + "pynwb validation errors for /Users/cb/Documents/dev/workflow-array-ephys/temp_nwb/200178/test1.nwb: []\n" ] }, { - "ename": "FileExistsError", - "evalue": "File './temp_nwb/200178/test1.nwb' already exists", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mFileExistsError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_70901/2105288079.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m upload_to_dandi(\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mdata_directory\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"./temp_nwb/\"\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0mdandiset_id\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mdj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfig\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'custom'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'dandiset_id'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mstaging\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mworking_directory\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"./temp_nwb/\"\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/Documents/dev/element-interface/element_interface/dandi.py\u001b[0m in \u001b[0;36mupload_to_dandi\u001b[0;34m(data_directory, dandiset_id, staging, working_directory, api_key, sync)\u001b[0m\n\u001b[1;32m 40\u001b[0m \u001b[0mdandiset_directory\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mworking_directory\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdandiset_id\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m#enforce str\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 41\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 42\u001b[0;31m download(\n\u001b[0m\u001b[1;32m 43\u001b[0m \u001b[0;34mf\"https://gui-staging.dandiarchive.org/#/dandiset/{dandiset_id}\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 44\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mstaging\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/ele/lib/python3.8/site-packages/dandi/download.py\u001b[0m in \u001b[0;36mdownload\u001b[0;34m(urls, output_dir, format, existing, jobs, jobs_per_zarr, get_metadata, get_assets, sync)\u001b[0m\n\u001b[1;32m 146\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mformat\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"pyout\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 147\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mout\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 148\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mrec\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mgen_\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 149\u001b[0m \u001b[0mout\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrec\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 150\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/ele/lib/python3.8/site-packages/dandi/download.py\u001b[0m in \u001b[0;36mdownload_generator\u001b[0;34m(parsed_url, output_path, assets_it, yield_generator_for_fields, existing, get_metadata, get_assets, jobs_per_zarr)\u001b[0m\n\u001b[1;32m 317\u001b[0m \u001b[0;32myield\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m\"path\"\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mpath\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0myield_generator_for_fields\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0m_download_generator\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 318\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 319\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mresp\u001b[0m \u001b[0;32min\u001b[0m \u001b[0m_download_generator\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 320\u001b[0m \u001b[0;32myield\u001b[0m \u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mresp\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpath\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 321\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/ele/lib/python3.8/site-packages/dandi/download.py\u001b[0m in \u001b[0;36m_download_file\u001b[0;34m(downloader, path, toplevel_path, lock, size, mtime, existing, digests, digest_callback)\u001b[0m\n\u001b[1;32m 523\u001b[0m \u001b[0mannex_path\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mop\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtoplevel_path\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\".git\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"annex\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 524\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mexisting\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"error\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 525\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mFileExistsError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"File {path!r} already exists\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 526\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mexisting\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"skip\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 527\u001b[0m \u001b[0;32myield\u001b[0m \u001b[0m_skip_file\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"already exists\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mFileExistsError\u001b[0m: File './temp_nwb/200178/test1.nwb' already exists" + "name": "stdout", + "output_type": "stream", + "text": [ + "PATH SIZE ERRORS UPLOAD STATUS MESSAGE \n", + "test1.nwb 109.8 MB 0 skipped file exists \n", + "dandiset.yaml 2.0 kB skipped should be edited online \n", + "Summary: 109.8 MB 2 skipped 1 file exists \n", + " 1 should be edited online\n" ] } ], From 8d9eebd0e8a23a0a1cdd2961b299202c0dacbe56 Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Fri, 3 Jun 2022 10:01:34 -0500 Subject: [PATCH 41/59] Apply suggestions from code review Co-authored-by: Kabilar Gunalan --- docker/Dockerfile.test | 5 ++++- workflow_array_ephys/ingest.py | 7 +++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docker/Dockerfile.test b/docker/Dockerfile.test index 5e2b3d4d..cdc45ee7 100644 --- a/docker/Dockerfile.test +++ b/docker/Dockerfile.test @@ -12,10 +12,11 @@ WORKDIR /main/workflow-array-ephys # Option 2 - Install user's remote fork of element and workflow # or an unreleased version of the element -# RUN pip install git+https://github.com/element-lab.git +# RUN pip install git+https://github.com//element-lab.git # RUN pip install git+https://github.com//element-animal.git # RUN pip install git+https://github.com//element-session.git # RUN pip install git+https://github.com//element-event.git +# RUN pip install git+https://github.com//element-interface.git # RUN pip install git+https://github.com//element-electrode-localization.git # RUN pip install git+https://github.com//element-array-ephys.git # RUN git clone https://github.com//workflow-array-ephys.git /main/workflow-array-ephys @@ -34,6 +35,7 @@ COPY --chown=anaconda:anaconda ./element-lab /main/element-lab COPY --chown=anaconda:anaconda ./element-animal /main/element-animal COPY --chown=anaconda:anaconda ./element-session /main/element-session COPY --chown=anaconda:anaconda ./element-event /main/element-event +COPY --chown=anaconda:anaconda ./element-interface /main/element-interface COPY --chown=anaconda:anaconda ./element-electrode-localization /main/element-electrode-localization COPY --chown=anaconda:anaconda ./element-array-ephys /main/element-array-ephys COPY --chown=anaconda:anaconda ./workflow-array-ephys /main/workflow-array-ephys @@ -42,6 +44,7 @@ RUN pip install -e /main/element-lab RUN pip install -e /main/element-animal RUN pip install -e /main/element-session RUN pip install -e /main/element-event +RUN pip install -e /main/element-interface RUN pip install -e /main/element-electrode-localization RUN pip install -e /main/element-array-ephys # RUN rm -f /main/workflow-array-ephys/dj_local_conf.json diff --git a/workflow_array_ephys/ingest.py b/workflow_array_ephys/ingest.py index 0a7238e0..21a64b90 100644 --- a/workflow_array_ephys/ingest.py +++ b/workflow_array_ephys/ingest.py @@ -20,7 +20,7 @@ def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', skip_duplicates=True, verbose=True): """ Inserts data from a CSVs into their corresponding lab schema tables. - By default, uses data from workflow_session/user_data/lab/ + By default, uses data from workflow/user_data/lab/ :param lab_csv_path: relative path of lab csv :param project_csv_path: relative path of project csv :param publication_csv_path: relative path of publication csv @@ -172,7 +172,6 @@ def ingest_events(recording_csv_path='./user_data/behavior_recordings.csv', Ingest each level of experiment heirarchy for element-trial: recording, block (i.e., phases of trials), trials (repeated units), events (optionally 0-duration occurances within trial). - This ingestion function is duplicated across wf-array-ephys and wf-calcium-imaging """ csvs = [recording_csv_path, recording_csv_path, block_csv_path, block_csv_path, @@ -185,14 +184,13 @@ def ingest_events(recording_csv_path='./user_data/behavior_recordings.csv', trial.BlockTrial(), event.EventType(), event.Event(), trial.TrialEvent()] - # Allow direct insert required bc element-trial has Imported that should be Manual + # Allow direct insert required because element-event has Imported that should be Manual ingest_csv_to_table(csvs, tables, skip_duplicates=skip_duplicates, verbose=verbose, allow_direct_insert=True) def ingest_alignment(alignment_csv_path='./user_data/alignments.csv', skip_duplicates=True, verbose=True): - """This is duplicated across wf-array-ephys and wf-calcium-imaging""" csvs = [alignment_csv_path] tables = [event.AlignmentEvent()] @@ -201,6 +199,7 @@ def ingest_alignment(alignment_csv_path='./user_data/alignments.csv', if __name__ == '__main__': + ingest_lab() ingest_subjects() ingest_sessions() ingest_events() From dd71df2c87d75d348f1194c124c1326fe1c96227 Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Fri, 3 Jun 2022 10:23:39 -0500 Subject: [PATCH 42/59] Apply suggestion from code review Co-authored-by: Kabilar Gunalan --- workflow_array_ephys/plotting/plot_psth.py | 1 + 1 file changed, 1 insertion(+) diff --git a/workflow_array_ephys/plotting/plot_psth.py b/workflow_array_ephys/plotting/plot_psth.py index b73faea3..4d8182a7 100644 --- a/workflow_array_ephys/plotting/plot_psth.py +++ b/workflow_array_ephys/plotting/plot_psth.py @@ -21,6 +21,7 @@ def _plot_spike_raster(aligned_spikes, trial_ids=None, vlines=[0], ax=None, titl for x in vlines: ax.axvline(x=x, linestyle='--', color='k') + ax.set_ylabel('Trial (#)') if xlim: ax.set_xlim(xlim) ax.set_axis_off() From 5d267e9f590488f85fac8bc460e18ff3bfe2bde5 Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Fri, 3 Jun 2022 10:24:28 -0500 Subject: [PATCH 43/59] Apply suggestion from code review Co-authored-by: Kabilar Gunalan --- workflow_array_ephys/ingest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflow_array_ephys/ingest.py b/workflow_array_ephys/ingest.py index 21a64b90..0b2c6b35 100644 --- a/workflow_array_ephys/ingest.py +++ b/workflow_array_ephys/ingest.py @@ -169,7 +169,7 @@ def ingest_events(recording_csv_path='./user_data/behavior_recordings.csv', event_csv_path='./user_data/events.csv', skip_duplicates=True, verbose=True): """ - Ingest each level of experiment heirarchy for element-trial: + Ingest each level of experiment heirarchy for element-event: recording, block (i.e., phases of trials), trials (repeated units), events (optionally 0-duration occurances within trial). """ From 961fcfc4a7f6d5d0b8cf3b2712849c36d5f72982 Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Mon, 6 Jun 2022 08:59:20 -0500 Subject: [PATCH 44/59] Apply suggestions from code review Co-authored-by: Kabilar Gunalan --- README.md | 6 +++--- docker/Dockerfile.dev | 1 + notebooks/py_scripts/01-configure.py | 3 +-- notebooks/py_scripts/07-downstream-analysis.py | 8 ++++---- notebooks/py_scripts/08-electrode-localization.py | 4 ++-- notebooks/py_scripts/09-NWB-export.py | 5 ++--- 6 files changed, 13 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index a9e77fe0..b3189dbe 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ schema options, selected via the DataJoint config file, with `dj.config['custom']['ephys_mode']` + `acute` probe insertion, with curated clustering + `chronic` probe insertion, with curated clustering -+ `no-curation`, with kilosort triggered clustering and supported NWB export ++ `no-curation`, acute probe insertion with kilosort triggered clustering and supported NWB export ![element-array-ephys](images/attached_array_ephys_element.svg) @@ -59,9 +59,9 @@ Please refer to the workflow-specific for an in-depth explanation of how to ... 1. Run the workflow ([03-process.ipynb](notebooks/03-process.ipynb)). 2. Explore the data ([05-explore.ipynb](notebooks/05-explore.ipynb)). -3. Establish downstream analyses +3. Examine trialized analyses, and establish downstream analyses ([07-downstream-analysis.ipynb](notebooks/07-downstream-analysis.ipynb)) -4. Examine trialized analyses, and locate probes within the +4. Locate probes within the [Common Coordinate Framework](https://www.sciencedirect.com/science/article/pii/S0092867420304025) ([08-electrode-localization.ipynb](notebooks/08-electrode-localization.ipynb)) 5. Export to NWB and DANDI ([09-NWB-export.ipynb](notebooks/09-NWB-export.ipynb)) diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index 91656731..945f7403 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -30,6 +30,7 @@ RUN pip install -e /main/element-animal RUN pip install -e /main/element-session RUN pip install -e /main/element-event RUN pip install -e /main/element-electrode-localization +RUN pip install -e /main/element-interface RUN pip install -e /main/element-array-ephys RUN pip install -e /main/workflow-array-ephys RUN pip install -r /main/workflow-array-ephys/requirements_test.txt diff --git a/notebooks/py_scripts/01-configure.py b/notebooks/py_scripts/01-configure.py index 64868022..e984e9f9 100644 --- a/notebooks/py_scripts/01-configure.py +++ b/notebooks/py_scripts/01-configure.py @@ -22,7 +22,7 @@ # # **The configuration only needs to be set up once**, if you have gone through the configuration before, directly go to [02-workflow-structure](02-workflow-structure-optional.ipynb). # -# As a convention, we set the configuration up in the root directory of the workflow package and always starts importing datajoint and pipeline modules from ther +# As a convention, we set the configuration up in the root directory of the workflow package and always starts importing datajoint and pipeline modules from there. # - import os @@ -32,7 +32,6 @@ + "workflow directory") import datajoint as dj -import datajoint as dj # ## Setup - Credentials # diff --git a/notebooks/py_scripts/07-downstream-analysis.py b/notebooks/py_scripts/07-downstream-analysis.py index 73369ef5..d9e0375e 100644 --- a/notebooks/py_scripts/07-downstream-analysis.py +++ b/notebooks/py_scripts/07-downstream-analysis.py @@ -44,12 +44,12 @@ # - A block is a continuous phase of an experiment that contains repeated instances of a condition, or trials. # - Events may occur within or outside of conditions, either instantaneous or continuous. # -# The diagram below shows (a) the levels of hierarchy and (b) how the bounds may not completely overlap. A block may not fully capure trials and events may occur outside both blocks/trials. +# The diagram below shows (a) the levels of hierarchy and (b) how the bounds may not completely overlap. A block may not fully capture trials and events may occur outside both blocks/trials. # ``` # |----------------------------------------------------------------------------| # |-------------------------------- Session ---------------------------------|__ -# |-------------------------- BehaviorRecording ---------------------------|____ +# |-------------------------- BehaviorRecording -----------------------------|__ # |----- Block 1 -----|______|----- Block 2 -----|______|----- Block 3 -----|___ # | trial 1 || trial 2 |____| trial 3 || trial 4 |____| trial 5 |____| trial 6 | # |_|e1|_|e2||e3|_|e4|__|e5|__|e6||e7||e8||e9||e10||e11|____|e12||e13|_________| @@ -104,7 +104,7 @@ dj.Diagram(ephys.CuratedClustering)) # + ***SpikesAlignmentCondition*** - a manual table to specify the inputs and condition for the analysis [markdown] -# Let's start by creating several analyses configuration - i.e. inserting into ***SpikesAlignmentCondition*** for the `center` event, called `center_button` in the `AlignmentEvent` table. +# Let's start by creating an analysis configuration - i.e. inserting into ***SpikesAlignmentCondition*** for the `center` event, called `center_button` in the `AlignmentEvent` table. # + ***SpikesAlignment*** - a computed table to extract event-aligned spikes and compute unit PSTH alignment_key = (event.AlignmentEvent & 'alignment_name = "center_button"' @@ -117,7 +117,7 @@ (analysis.SpikesAlignmentCondition * ctrl_trials & alignment_condition).proj(), skip_duplicates=True) # + a CuratedClustering of interest for analysis [markdown] -# With the steps above, we have create a new spike alignment condition for analysis, named `ctrl_center_button`, which retains all spiking information related to control trials during which the center button was pressed. +# With the steps above, we have created a new spike alignment condition for analysis, named `ctrl_center_button`, which retains all spiking information related to control trials during which the center button was pressed. # + ***SpikesAlignment*** - a computed table to extract event-aligned spikes and compute unit PSTH analysis.SpikesAlignmentCondition.Trial() diff --git a/notebooks/py_scripts/08-electrode-localization.py b/notebooks/py_scripts/08-electrode-localization.py index ea6ef78d..05d575cc 100644 --- a/notebooks/py_scripts/08-electrode-localization.py +++ b/notebooks/py_scripts/08-electrode-localization.py @@ -51,7 +51,7 @@ cranial_nerves = ccf.BrainRegionAnnotation.retrieve_acronym('cm') print(f'CM: {central_thalamus}\ncm: {cranial_nerves}') -# If your work requires the case-sensitive columns please contact get in touch with the DataJoint team via [StackOverflow](https://stackoverflow.com/questions/tagged/datajoint). +# If your work requires the case-sensitive columns please get in touch with the DataJoint team via [StackOverflow](https://stackoverflow.com/questions/tagged/datajoint). # # For this demo, let's look at the dimensions of the central thalamus. To look at other regions, open the CSV you downloaded and search for your desired region. @@ -71,7 +71,7 @@ (dj.Diagram(eloc) + dj.Diagram(ccf) - 1) -# Because the probe may not be fully inserted, there will be some electrode positions that occur outside the brain. We register these instances with an `IntegrityError` warning because we're trying to register a coorinate position with no corresponding location in the `ccf.CCF.Voxel` table. We can silence these warnings by setting the log level before running `populate()` on the `ElectrodePosition` table. +# Because the probe may not be fully inserted, there will be some electrode positions that occur outside the brain. We register these instances with an `IntegrityError` warning because we're trying to register a coordinate position with no corresponding location in the `ccf.CCF.Voxel` table. We can silence these warnings by setting the log level before running `populate()` on the `ElectrodePosition` table. import logging logging.getLogger().setLevel(logging.ERROR) # or logging.INFO diff --git a/notebooks/py_scripts/09-NWB-export.py b/notebooks/py_scripts/09-NWB-export.py index c9d7da0e..9a7473a0 100644 --- a/notebooks/py_scripts/09-NWB-export.py +++ b/notebooks/py_scripts/09-NWB-export.py @@ -72,7 +72,7 @@ # # ### Element Array Electrophysiology # -# `ecephys_session_to_nwb` provides a full export mechanism, returning an NWB file with raw, data, spikes, and LFP. Optional arguments determine which pieces are exported. For demonstration purposes, we recommend limiting `end_frame`. +# `ecephys_session_to_nwb` provides a full export mechanism, returning an NWB file with raw data, spikes, and LFP. Optional arguments determine which pieces are exported. For demonstration purposes, we recommend limiting `end_frame`. # help(ecephys_session_to_nwb) @@ -93,12 +93,11 @@ # + import time -from workflow_array_ephys.paths import get_ephys_root_data_dir write_nwb(nwbfile, f'./temp_nwb/{time.strftime("_test_%Y%m%d-%H%M%S.nwb")}') # - -# ## DANDI Export +# ## DANDI Upload # # `element-interface.dandi` includes the `upload_to_dandi` utility to support direct uploads. For more information, see [DANDI documentation](https://www.dandiarchive.org/handbook/10_using_dandi/). # From d2df0900b0f5af369497f977d17f2f3a04ca6bb9 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 6 Jun 2022 09:25:01 -0500 Subject: [PATCH 45/59] Mirror code review on NB files --- notebooks/01-configure.ipynb | 11 +- notebooks/07-downstream-analysis.ipynb | 12 +- notebooks/08-electrode-localization.ipynb | 128 +++++++++++----------- notebooks/09-NWB-export.ipynb | 7 +- 4 files changed, 74 insertions(+), 84 deletions(-) diff --git a/notebooks/01-configure.ipynb b/notebooks/01-configure.ipynb index 61271b9a..126973cc 100644 --- a/notebooks/01-configure.ipynb +++ b/notebooks/01-configure.ipynb @@ -15,7 +15,7 @@ "\n", "**The configuration only needs to be set up once**, if you have gone through the configuration before, directly go to [02-workflow-structure](02-workflow-structure-optional.ipynb).\n", "\n", - "As a convention, we set the configuration up in the root directory of the workflow package and always starts importing datajoint and pipeline modules from ther" + "As a convention, we set the configuration up in the root directory of the workflow package and always starts importing datajoint and pipeline modules from there." ] }, { @@ -32,15 +32,6 @@ "import datajoint as dj" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import datajoint as dj" - ] - }, { "cell_type": "markdown", "metadata": {}, diff --git a/notebooks/07-downstream-analysis.ipynb b/notebooks/07-downstream-analysis.ipynb index df2f3ce7..ac1046be 100644 --- a/notebooks/07-downstream-analysis.ipynb +++ b/notebooks/07-downstream-analysis.ipynb @@ -93,7 +93,7 @@ "- A block is a continuous phase of an experiment that contains repeated instances of a condition, or trials. \n", "- Events may occur within or outside of conditions, either instantaneous or continuous.\n", "\n", - "The diagram below shows (a) the levels of hierarchy and (b) how the bounds may not completely overlap. A block may not fully capure trials and events may occur outside both blocks/trials." + "The diagram below shows (a) the levels of hierarchy and (b) how the bounds may not completely overlap. A block may not fully capture trials and events may occur outside both blocks/trials." ] }, { @@ -104,7 +104,7 @@ "```\n", "|----------------------------------------------------------------------------|\n", "|-------------------------------- Session ---------------------------------|__\n", - "|-------------------------- BehaviorRecording ---------------------------|____\n", + "|-------------------------- BehaviorRecording -----------------------------|__\n", "|----- Block 1 -----|______|----- Block 2 -----|______|----- Block 3 -----|___\n", "| trial 1 || trial 2 |____| trial 3 || trial 4 |____| trial 5 |____| trial 6 |\n", "|_|e1|_|e2||e3|_|e4|__|e5|__|e6||e7||e8||e9||e10||e11|____|e12||e13|_________|\n", @@ -1014,7 +1014,7 @@ "title": "***SpikesAlignmentCondition*** - a manual table to specify the inputs and condition for the analysis" }, "source": [ - "Let's start by creating several analyses configuration - i.e. inserting into ***SpikesAlignmentCondition*** for the `center` event, called `center_button` in the `AlignmentEvent` table." + "Let's start by creating an analysis configuration - i.e. inserting into ***SpikesAlignmentCondition*** for the `center` event, called `center_button` in the `AlignmentEvent` table." ] }, { @@ -1045,7 +1045,7 @@ "title": "a CuratedClustering of interest for analysis" }, "source": [ - "With the steps above, we have create a new spike alignment condition for analysis, named `ctrl_center_button`, which retains all spiking information related to control trials during which the center button was pressed." + "With the steps above, we have created a new spike alignment condition for analysis, named `ctrl_center_button`, which retains all spiking information related to control trials during which the center button was pressed." ] }, { @@ -1623,7 +1623,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] @@ -1649,7 +1649,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] diff --git a/notebooks/08-electrode-localization.ipynb b/notebooks/08-electrode-localization.ipynb index 9d48e700..d2b1f667 100644 --- a/notebooks/08-electrode-localization.ipynb +++ b/notebooks/08-electrode-localization.ipynb @@ -94,9 +94,9 @@ "\n", "\n", "ccf.ParentBrainRegion\n", - "\n", "\n", "ccf.ParentBrainRegion\n", @@ -111,12 +111,12 @@ "\n", "\n", "ccf.BrainRegionAnnotation.BrainRegion\n", - "\n", "\n", "ccf.BrainRegionAnnotation.BrainRegion\n", @@ -136,8 +136,8 @@ "\n", "\n", "ccf.BrainRegionAnnotation.Voxel\n", - "\n", "\n", "ccf.BrainRegionAnnotation.Voxel\n", @@ -152,11 +152,11 @@ "\n", "\n", "ccf.CCF.Voxel\n", - "\n", "\n", "ccf.CCF.Voxel\n", @@ -171,11 +171,11 @@ "\n", "\n", "ccf.CCF\n", - "\n", "\n", "ccf.CCF\n", @@ -190,7 +190,7 @@ "\n", "\n", "ccf.BrainRegionAnnotation\n", - "\n", "\n", "ccf.BrainRegionAnnotation\n", @@ -410,7 +410,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "If your work requires the case-sensitive columns please contact get in touch with the DataJoint team via [StackOverflow](https://stackoverflow.com/questions/tagged/datajoint).\n", + "If your work requires the case-sensitive columns please get in touch with the DataJoint team via [StackOverflow](https://stackoverflow.com/questions/tagged/datajoint).\n", "\n", "For this demo, let's look at the dimensions of the central thalamus. To look at other regions, open the CSV you downloaded and search for your desired region." ] @@ -631,9 +631,9 @@ "\n", "\n", "ccf.ParentBrainRegion\n", - "\n", "\n", "ccf.ParentBrainRegion\n", @@ -648,12 +648,12 @@ "\n", "\n", "ccf.BrainRegionAnnotation.BrainRegion\n", - "\n", "\n", "ccf.BrainRegionAnnotation.BrainRegion\n", @@ -673,8 +673,8 @@ "\n", "\n", "ccf.BrainRegionAnnotation.Voxel\n", - "\n", "\n", "ccf.BrainRegionAnnotation.Voxel\n", @@ -689,14 +689,14 @@ "\n", "\n", "eloc.probe.ProbeType.Electrode\n", - "\n", "\n", "eloc.probe.ProbeType.Electrode\n", @@ -706,10 +706,10 @@ "\n", "\n", "eloc.ElectrodePosition.Electrode\n", - "\n", "\n", "eloc.ElectrodePosition.Electrode\n", @@ -724,11 +724,11 @@ "\n", "\n", "ccf.CCF.Voxel\n", - "\n", "\n", "ccf.CCF.Voxel\n", @@ -748,10 +748,10 @@ "\n", "\n", "eloc.ProbeInsertion\n", - "\n", "\n", "eloc.ProbeInsertion\n", @@ -761,8 +761,8 @@ "\n", "\n", "eloc.ElectrodePosition\n", - "\n", "\n", "eloc.ElectrodePosition\n", @@ -777,11 +777,11 @@ "\n", "\n", "ccf.CCF\n", - "\n", "\n", "ccf.CCF\n", @@ -796,7 +796,7 @@ "\n", "\n", "ccf.BrainRegionAnnotation\n", - "\n", "\n", "ccf.BrainRegionAnnotation\n", @@ -843,7 +843,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Because the probe may not be fully inserted, there will be some electrode positions that occur outside the brain. We register these instances with an `IntegrityError` warning because we're trying to register a coorinate position with no corresponding location in the `ccf.CCF.Voxel` table. We can silence these warnings by setting the log level before running `populate()` on the `ElectrodePosition` table." + "Because the probe may not be fully inserted, there will be some electrode positions that occur outside the brain. We register these instances with an `IntegrityError` warning because we're trying to register a coordinate position with no corresponding location in the `ccf.CCF.Voxel` table. We can silence these warnings by setting the log level before running `populate()` on the `ElectrodePosition` table." ] }, { diff --git a/notebooks/09-NWB-export.ipynb b/notebooks/09-NWB-export.ipynb index 88e42668..61d984b2 100644 --- a/notebooks/09-NWB-export.ipynb +++ b/notebooks/09-NWB-export.ipynb @@ -271,7 +271,7 @@ "\n", "### Element Array Electrophysiology\n", "\n", - "`ecephys_session_to_nwb` provides a full export mechanism, returning an NWB file with raw, data, spikes, and LFP. Optional arguments determine which pieces are exported. For demonstration purposes, we recommend limiting `end_frame`.\n" + "`ecephys_session_to_nwb` provides a full export mechanism, returning an NWB file with raw data, spikes, and LFP. Optional arguments determine which pieces are exported. For demonstration purposes, we recommend limiting `end_frame`.\n" ] }, { @@ -415,13 +415,12 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "eb3f1030", "metadata": {}, "outputs": [], "source": [ "import time\n", - "from workflow_array_ephys.paths import get_ephys_root_data_dir\n", " \n", "write_nwb(nwbfile, f'./temp_nwb/{time.strftime(\"_test_%Y%m%d-%H%M%S.nwb\")}')" ] @@ -431,7 +430,7 @@ "id": "f717baf8", "metadata": {}, "source": [ - "## DANDI Export\n", + "## DANDI Upload\n", "\n", "`element-interface.dandi` includes the `upload_to_dandi` utility to support direct uploads. For more information, see [DANDI documentation](https://www.dandiarchive.org/handbook/10_using_dandi/).\n", "\n", From 718ced46ffbc5801ebb949c0d653e8699a1a520f Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 6 Jun 2022 09:33:34 -0500 Subject: [PATCH 46/59] Jupytext sync --- notebooks/py_scripts/01-configure.py | 1 - notebooks/py_scripts/02-workflow-structure-optional.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/notebooks/py_scripts/01-configure.py b/notebooks/py_scripts/01-configure.py index e984e9f9..a0dd0254 100644 --- a/notebooks/py_scripts/01-configure.py +++ b/notebooks/py_scripts/01-configure.py @@ -32,7 +32,6 @@ + "workflow directory") import datajoint as dj - # ## Setup - Credentials # # Now let's set up the host, user and password in the `dj.config` global variable diff --git a/notebooks/py_scripts/02-workflow-structure-optional.py b/notebooks/py_scripts/02-workflow-structure-optional.py index e847505a..e71ccd49 100644 --- a/notebooks/py_scripts/02-workflow-structure-optional.py +++ b/notebooks/py_scripts/02-workflow-structure-optional.py @@ -24,7 +24,7 @@ # To load the local configuration, we will change the directory to the package root. import os -os.chdir('..') +if os.path.basename(os.getcwd())=='notebooks': os.chdir('..') # ## Schemas and tables From 765516f264b836be2ffe909b6386c4bc82501777 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 6 Jun 2022 11:45:35 -0500 Subject: [PATCH 47/59] Add links for notebook 09-NWB --- notebooks/09-NWB-export.ipynb | 22 +- notebooks/py_scripts/09-NWB-export.py | 16 +- tests/__init__.py | 506 +++++++++++++++----------- 3 files changed, 326 insertions(+), 218 deletions(-) diff --git a/notebooks/09-NWB-export.ipynb b/notebooks/09-NWB-export.ipynb index 61d984b2..a1fbc552 100644 --- a/notebooks/09-NWB-export.ipynb +++ b/notebooks/09-NWB-export.ipynb @@ -73,7 +73,7 @@ "source": [ "## Export to NWB\n", "\n", - "Each of the following elements has tools for interacting with NWB files: `element-lab`, `element-session`, `element-array-ephys`, and `element-interface`. We'll use the following keys for testing these functions:" + "Several Elements have tools for generating NWB materials. We'll use the following keys for testing these functions:" ] }, { @@ -90,12 +90,25 @@ " \"session_datetime\": \"2018-07-03 20:32:28\"}" ] }, + { + "cell_type": "markdown", + "id": "5cb8b7f2", + "metadata": {}, + "source": [ + "To skip ahead, use any of the following links:\n", + "- [Element Lab](#Element-Lab)\n", + "- [Element Animal](#Element-Animal)\n", + "- [Element Session](#Element-Session)\n", + "- [Element Array Electrophysiology](#Element-Array-Electrophysiology) - If you are using all upstream Elements, you can skip to this\n", + "- [Element Interface for DANDI Upload](#DANDI-Upload)" + ] + }, { "cell_type": "markdown", "id": "fc2d028a", "metadata": {}, "source": [ - "\n", + "\n", "### Element Lab\n", "\n", "Because an NWB file must include session information, `element_lab_to_nwb_dict` can only help package information from the Lab schema into `dict` format. This would be helpful for a team using Element Lab, but not others. " @@ -170,6 +183,7 @@ "id": "66af900a", "metadata": {}, "source": [ + "\n", "### Element Animal\n", "\n", "`subject_to_nwb` can use a session key to retrieve subject information, and will return an nwb file with a number of sections specified. When packaging into an NWB file, `pynwb` will display a warning regarding timezone information - datetime fields are assumed to be in local time, and will be converted to UTC." @@ -214,6 +228,7 @@ "id": "b1d2c7e3", "metadata": {}, "source": [ + "\n", "### Element Session\n", "\n", "`session_to_nwb` pulls the same information as above, while also including information about session experimenter and session time. The export process provides the same warning about timezone conversion to UTC.\n" @@ -268,7 +283,7 @@ "id": "bb5fba81", "metadata": {}, "source": [ - "\n", + "\n", "### Element Array Electrophysiology\n", "\n", "`ecephys_session_to_nwb` provides a full export mechanism, returning an NWB file with raw data, spikes, and LFP. Optional arguments determine which pieces are exported. For demonstration purposes, we recommend limiting `end_frame`.\n" @@ -430,6 +445,7 @@ "id": "f717baf8", "metadata": {}, "source": [ + "\n", "## DANDI Upload\n", "\n", "`element-interface.dandi` includes the `upload_to_dandi` utility to support direct uploads. For more information, see [DANDI documentation](https://www.dandiarchive.org/handbook/10_using_dandi/).\n", diff --git a/notebooks/py_scripts/09-NWB-export.py b/notebooks/py_scripts/09-NWB-export.py index 9a7473a0..f2c52442 100644 --- a/notebooks/py_scripts/09-NWB-export.py +++ b/notebooks/py_scripts/09-NWB-export.py @@ -38,7 +38,7 @@ # ## Export to NWB # -# Each of the following elements has tools for interacting with NWB files: `element-lab`, `element-session`, `element-array-ephys`, and `element-interface`. We'll use the following keys for testing these functions: +# Several Elements have tools for generating NWB materials. We'll use the following keys for testing these functions: lab_key={"lab": "LabA"} protocol_key={"protocol": "ProtA"} @@ -46,7 +46,14 @@ session_key={"subject": "subject5", "session_datetime": "2018-07-03 20:32:28"} -# +# To skip ahead, use any of the following links: +# - [Element Lab](#Element-Lab) +# - [Element Animal](#Element-Animal) +# - [Element Session](#Element-Session) +# - [Element Array Electrophysiology](#Element-Array-Electrophysiology) - If you are using all upstream Elements, you can skip to this +# - [Element Interface for DANDI Upload](#DANDI-Upload) + +# # ### Element Lab # # Because an NWB file must include session information, `element_lab_to_nwb_dict` can only help package information from the Lab schema into `dict` format. This would be helpful for a team using Element Lab, but not others. @@ -56,12 +63,14 @@ element_lab_to_nwb_dict(lab_key=lab_key, protocol_key=protocol_key, project_key=project_key) +# # ### Element Animal # # `subject_to_nwb` can use a session key to retrieve subject information, and will return an nwb file with a number of sections specified. When packaging into an NWB file, `pynwb` will display a warning regarding timezone information - datetime fields are assumed to be in local time, and will be converted to UTC. subject_to_nwb(session_key=session_key) +# # ### Element Session # # `session_to_nwb` pulls the same information as above, while also including information about session experimenter and session time. The export process provides the same warning about timezone conversion to UTC. @@ -69,7 +78,7 @@ session_to_nwb(session_key=session_key) -# +# # ### Element Array Electrophysiology # # `ecephys_session_to_nwb` provides a full export mechanism, returning an NWB file with raw data, spikes, and LFP. Optional arguments determine which pieces are exported. For demonstration purposes, we recommend limiting `end_frame`. @@ -97,6 +106,7 @@ write_nwb(nwbfile, f'./temp_nwb/{time.strftime("_test_%Y%m%d-%H%M%S.nwb")}') # - +# # ## DANDI Upload # # `element-interface.dandi` includes the `upload_to_dandi` utility to support direct uploads. For more information, see [DANDI documentation](https://www.dandiarchive.org/handbook/10_using_dandi/). diff --git a/tests/__init__.py b/tests/__init__.py index f9a53f01..224ba0cb 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -19,16 +19,18 @@ _tear_down = False verbose = False -pathlib.Path('./tests/user_data').mkdir(exist_ok=True) -pathlib.Path('./tests/user_data/lab').mkdir(exist_ok=True) - -sessions_dirs = ['subject1/session1', - 'subject2/session1', - 'subject2/session2', - 'subject3/session1', - 'subject4/experiment1', - 'subject5/session1', - 'subject6/session1'] +pathlib.Path("./tests/user_data").mkdir(exist_ok=True) +pathlib.Path("./tests/user_data/lab").mkdir(exist_ok=True) + +sessions_dirs = [ + "subject1/session1", + "subject2/session1", + "subject2/session2", + "subject3/session1", + "subject4/experiment1", + "subject5/session1", + "subject6/session1", +] # -------------------- HELPER CLASS -------------------- @@ -39,38 +41,44 @@ def write_csv(content, path): :param path: pathlib PosixPath :param content: list of strings, each as row of CSV """ - with open(path, 'w') as f: + with open(path, "w") as f: for line in content: - f.write(line+'\n') + f.write(line + "\n") class QuietStdOut: """If verbose set to false, used to quiet tear_down table.delete prints""" + def __enter__(self): self._original_stdout = sys.stdout - sys.stdout = open(os.devnull, 'w') + sys.stdout = open(os.devnull, "w") def __exit__(self, exc_type, exc_val, exc_tb): sys.stdout.close() sys.stdout = self._original_stdout + # ---------------------- FIXTURES ---------------------- @pytest.fixture(autouse=True) def dj_config(): - """ If dj_local_config exists, load""" - if pathlib.Path('./dj_local_conf.json').exists(): - dj.config.load('./dj_local_conf.json') - dj.config['safemode'] = False - dj.config['custom'] = { - 'ephys_mode': (os.environ.get('EPHYS_MODE') - or dj.config['custom']['ephys_mode']), - 'database.prefix': (os.environ.get('DATABASE_PREFIX') - or dj.config['custom']['database.prefix']), - 'ephys_root_data_dir': (os.environ.get('EPHYS_ROOT_DATA_DIR').split(',') - if os.environ.get('EPHYS_ROOT_DATA_DIR') - else dj.config['custom']['ephys_root_data_dir']) + """If dj_local_config exists, load""" + if pathlib.Path("./dj_local_conf.json").exists(): + dj.config.load("./dj_local_conf.json") + dj.config["safemode"] = False + dj.config["custom"] = { + "ephys_mode": ( + os.environ.get("EPHYS_MODE") or dj.config["custom"]["ephys_mode"] + ), + "database.prefix": ( + os.environ.get("DATABASE_PREFIX") or dj.config["custom"]["database.prefix"] + ), + "ephys_root_data_dir": ( + os.environ.get("EPHYS_ROOT_DATA_DIR").split(",") + if os.environ.get("EPHYS_ROOT_DATA_DIR") + else dj.config["custom"]["ephys_root_data_dir"] + ), } return @@ -84,50 +92,62 @@ def test_data(dj_config): try: find_full_path(get_ephys_root_data_dir(), p) except FileNotFoundError: - test_data_exists = False # If data not found + test_data_exists = False # If data not found - if not test_data_exists: # attempt to djArchive dowload + if not test_data_exists: # attempt to djArchive dowload try: - dj.config['custom'].update({ - 'djarchive.client.endpoint': - os.environ['DJARCHIVE_CLIENT_ENDPOINT'], - 'djarchive.client.bucket': - os.environ['DJARCHIVE_CLIENT_BUCKET'], - 'djarchive.client.access_key': - os.environ['DJARCHIVE_CLIENT_ACCESSKEY'], - 'djarchive.client.secret_key': - os.environ['DJARCHIVE_CLIENT_SECRETKEY'] - }) + dj.config["custom"].update( + { + "djarchive.client.endpoint": os.environ[ + "DJARCHIVE_CLIENT_ENDPOINT" + ], + "djarchive.client.bucket": os.environ["DJARCHIVE_CLIENT_BUCKET"], + "djarchive.client.access_key": os.environ[ + "DJARCHIVE_CLIENT_ACCESSKEY" + ], + "djarchive.client.secret_key": os.environ[ + "DJARCHIVE_CLIENT_SECRETKEY" + ], + } + ) except KeyError as e: raise FileNotFoundError( - f' Full test data not available.' - f'\nAttempting to download from DJArchive,' - f' but no credentials found in environment variables.' - f'\nError: {str(e)}') + f" Full test data not available." + f"\nAttempting to download from DJArchive," + f" but no credentials found in environment variables." + f"\nError: {str(e)}" + ) import djarchive_client + client = djarchive_client.client() test_data_dir = get_ephys_root_data_dir() if isinstance(test_data_dir, list): # if multiple root dirs, first test_data_dir = test_data_dir[0] - client.download('workflow-array-ephys-benchmark', - 'v2', - str(test_data_dir), create_target=False) + client.download( + "workflow-array-ephys-benchmark", + "v2", + str(test_data_dir), + create_target=False, + ) return @pytest.fixture def pipeline(): from workflow_array_ephys import pipeline - yield {'subject': pipeline.subject, - 'lab': pipeline.lab, - 'ephys': pipeline.ephys, - 'probe': pipeline.probe, - 'session': pipeline.session, - 'get_ephys_root_data_dir': pipeline.get_ephys_root_data_dir, - 'ephys_mode': pipeline.ephys_mode} + + yield { + "subject": pipeline.subject, + "lab": pipeline.lab, + "ephys": pipeline.ephys, + "probe": pipeline.probe, + "session": pipeline.session, + "get_ephys_root_data_dir": pipeline.get_ephys_root_data_dir, + "ephys_mode": pipeline.ephys_mode, + } if verbose and _tear_down: pipeline.subject.Subject.delete() @@ -138,18 +158,19 @@ def pipeline(): @pytest.fixture def lab_csv(): - """ Create a 'labs.csv' file""" - lab_content = ["lab,lab_name,institution,address," - + "time_zone,location,location_description", - "LabA,The Example Lab,Example Uni," - + "'221B Baker St,London NW1 6XE,UK',UTC+0," - + "Example Building,'2nd floor lab dedicated to all " - + "fictional experiments.'", - "LabB,The Other Lab,Other Uni," - + "'Oxford OX1 2JD, United Kingdom',UTC+0," - + "Other Building,'fictional campus dedicated to imaginary" - + "experiments.'"] - lab_csv_path = pathlib.Path('./tests/user_data/lab/labs.csv') + """Create a 'labs.csv' file""" + lab_content = [ + "lab,lab_name,institution,address," + "time_zone,location,location_description", + "LabA,The Example Lab,Example Uni," + + "'221B Baker St,London NW1 6XE,UK',UTC+0," + + "Example Building,'2nd floor lab dedicated to all " + + "fictional experiments.'", + "LabB,The Other Lab,Other Uni," + + "'Oxford OX1 2JD, United Kingdom',UTC+0," + + "Other Building,'fictional campus dedicated to imaginary" + + "experiments.'", + ] + lab_csv_path = pathlib.Path("./tests/user_data/lab/labs.csv") write_csv(lab_content, lab_csv_path) yield lab_content, lab_csv_path @@ -158,18 +179,19 @@ def lab_csv(): @pytest.fixture def lab_project_csv(): - """ Create a 'projects.csv' file""" - lab_project_content = ["project,project_description,repository_url," - + "repository_name,codeurl", - "ProjA,Example project to populate element-lab," - + "https://github.com/datajoint/element-lab/," - + "element-lab,https://github.com/datajoint/element" - + "-lab/tree/main/element_lab", - "ProjB,Other example project to populate element-" - + "lab,https://github.com/datajoint/element-session" - + "/,element-session,https://github.com/datajoint/" - + "element-session/tree/main/element_session"] - lab_project_csv_path = pathlib.Path('./tests/user_data/lab/projects.csv') + """Create a 'projects.csv' file""" + lab_project_content = [ + "project,project_description,repository_url," + "repository_name,codeurl", + "ProjA,Example project to populate element-lab," + + "https://github.com/datajoint/element-lab/," + + "element-lab,https://github.com/datajoint/element" + + "-lab/tree/main/element_lab", + "ProjB,Other example project to populate element-" + + "lab,https://github.com/datajoint/element-session" + + "/,element-session,https://github.com/datajoint/" + + "element-session/tree/main/element_session", + ] + lab_project_csv_path = pathlib.Path("./tests/user_data/lab/projects.csv") write_csv(lab_project_content, lab_project_csv_path) yield lab_project_content, lab_project_csv_path @@ -178,15 +200,19 @@ def lab_project_csv(): @pytest.fixture def lab_project_users_csv(): - """ Create a 'project_users.csv' file""" - lab_project_user_content = ["user,project", - "Sherlock,ProjA", - "Sherlock,ProjB", - "Watson,ProjB", - "Dr. Candace Pert,ProjA", - "User1,ProjA"] - lab_project_user_csv_path = pathlib.Path('./tests/user_data/lab/\ - project_users.csv') + """Create a 'project_users.csv' file""" + lab_project_user_content = [ + "user,project", + "Sherlock,ProjA", + "Sherlock,ProjB", + "Watson,ProjB", + "Dr. Candace Pert,ProjA", + "User1,ProjA", + ] + lab_project_user_csv_path = pathlib.Path( + "./tests/user_data/lab/\ + project_users.csv" + ) write_csv(lab_project_user_content, lab_project_user_csv_path) yield lab_project_user_content, lab_project_user_csv_path @@ -195,12 +221,16 @@ def lab_project_users_csv(): @pytest.fixture def lab_publications_csv(): - """ Create a 'publications.csv' file""" - lab_publication_content = ["project,publication", - "ProjA,arXiv:1807.11104", - "ProjA,arXiv:1807.11104v1"] - lab_publication_csv_path = pathlib.Path('./tests/user_data/lab/\ - publications.csv') + """Create a 'publications.csv' file""" + lab_publication_content = [ + "project,publication", + "ProjA,arXiv:1807.11104", + "ProjA,arXiv:1807.11104v1", + ] + lab_publication_csv_path = pathlib.Path( + "./tests/user_data/lab/\ + publications.csv" + ) write_csv(lab_publication_content, lab_publication_csv_path) yield lab_publication_content, lab_publication_csv_path @@ -209,12 +239,14 @@ def lab_publications_csv(): @pytest.fixture def lab_keywords_csv(): - """ Create a 'keywords.csv' file""" - lab_keyword_content = ["project,keyword", - "ProjA,Study", - "ProjA,Example", - "ProjB,Alternate"] - lab_keyword_csv_path = pathlib.Path('./tests/user_data/lab/keywords.csv') + """Create a 'keywords.csv' file""" + lab_keyword_content = [ + "project,keyword", + "ProjA,Study", + "ProjA,Example", + "ProjB,Alternate", + ] + lab_keyword_csv_path = pathlib.Path("./tests/user_data/lab/keywords.csv") write_csv(lab_keyword_content, lab_keyword_csv_path) yield lab_keyword_content, lab_keyword_csv_path @@ -223,13 +255,13 @@ def lab_keywords_csv(): @pytest.fixture def lab_protocol_csv(): - """ Create a 'protocols.csv' file""" - lab_protocol_content = ["protocol,protocol_type,protocol_description", - "ProtA,IRB expedited review,Protocol for managing " - + "data ingestion", - "ProtB,Alternative Method,Limited protocol for " - + "piloting only"] - lab_protocol_csv_path = pathlib.Path('./tests/user_data/lab/protocols.csv') + """Create a 'protocols.csv' file""" + lab_protocol_content = [ + "protocol,protocol_type,protocol_description", + "ProtA,IRB expedited review,Protocol for managing " + "data ingestion", + "ProtB,Alternative Method,Limited protocol for " + "piloting only", + ] + lab_protocol_csv_path = pathlib.Path("./tests/user_data/lab/protocols.csv") write_csv(lab_protocol_content, lab_protocol_csv_path) yield lab_protocol_content, lab_protocol_csv_path @@ -238,16 +270,16 @@ def lab_protocol_csv(): @pytest.fixture def lab_user_csv(): - """ Create a 'users.csv' file""" - lab_user_content = ["lab,user,user_role,user_email,user_cellphone", - "LabA,Sherlock,PI,Sherlock@BakerSt.com," - + "+44 20 7946 0344", - "LabA,Watson,Dr,DrWatson@BakerSt.com,+44 73 8389 1763", - "LabB,Dr. Candace Pert,PI,Pert@gmail.com," - + "+44 74 4046 5899", - "LabA,User1,Lab Tech,fake@email.com,+44 1632 960103", - "LabB,User2,Lab Tech,fake2@email.com,+44 1632 960102"] - lab_user_csv_path = pathlib.Path('./tests/user_data/lab/users.csv') + """Create a 'users.csv' file""" + lab_user_content = [ + "lab,user,user_role,user_email,user_cellphone", + "LabA,Sherlock,PI,Sherlock@BakerSt.com," + "+44 20 7946 0344", + "LabA,Watson,Dr,DrWatson@BakerSt.com,+44 73 8389 1763", + "LabB,Dr. Candace Pert,PI,Pert@gmail.com," + "+44 74 4046 5899", + "LabA,User1,Lab Tech,fake@email.com,+44 1632 960103", + "LabB,User2,Lab Tech,fake2@email.com,+44 1632 960102", + ] + lab_user_csv_path = pathlib.Path("./tests/user_data/lab/users.csv") write_csv(lab_user_content, lab_user_csv_path) yield lab_user_content, lab_user_csv_path @@ -255,11 +287,19 @@ def lab_user_csv(): @pytest.fixture -def ingest_lab(pipeline, lab_csv, lab_project_csv, lab_publications_csv, - lab_keywords_csv, lab_protocol_csv, lab_user_csv, - lab_project_users_csv): - """ From workflow_array_ephys ingest.py, import ingest_lab, run """ +def ingest_lab( + pipeline, + lab_csv, + lab_project_csv, + lab_publications_csv, + lab_keywords_csv, + lab_protocol_csv, + lab_user_csv, + lab_project_users_csv, +): + """From workflow_array_ephys ingest.py, import ingest_lab, run""" from workflow_array_ephys.ingest import ingest_lab + _, lab_csv_path = lab_csv _, lab_project_csv_path = lab_project_csv _, lab_publication_csv_path = lab_publications_csv @@ -267,47 +307,64 @@ def ingest_lab(pipeline, lab_csv, lab_project_csv, lab_publications_csv, _, lab_protocol_csv_path = lab_protocol_csv _, lab_user_csv_path = lab_user_csv _, lab_project_user_csv_path = lab_project_users_csv - ingest_lab(lab_csv_path=lab_csv_path, - project_csv_path=lab_project_csv_path, - publication_csv_path=lab_publication_csv_path, - keyword_csv_path=lab_keyword_csv_path, - protocol_csv_path=lab_protocol_csv_path, - users_csv_path=lab_user_csv_path, - project_user_csv_path=lab_project_user_csv_path, verbose=verbose) + ingest_lab( + lab_csv_path=lab_csv_path, + project_csv_path=lab_project_csv_path, + publication_csv_path=lab_publication_csv_path, + keyword_csv_path=lab_keyword_csv_path, + protocol_csv_path=lab_protocol_csv_path, + users_csv_path=lab_user_csv_path, + project_user_csv_path=lab_project_user_csv_path, + verbose=verbose, + ) return @pytest.fixture def subjects_csv(): - """ Create a 'subjects.csv' file""" - input_subjects = pd.DataFrame(columns=['subject', 'sex', - 'subject_birth_date', - 'subject_description']) - input_subjects.subject = ['subject1', 'subject2', - 'subject3', 'subject4', - 'subject5', 'subject6'] - input_subjects.sex = ['F', 'M', 'M', 'M', 'F', 'F'] - input_subjects.subject_birth_date = ['2020-01-01 00:00:01', - '2020-01-01 00:00:01', - '2020-01-01 00:00:01', - '2020-01-01 00:00:01', - '2020-01-01 00:00:01', - '2020-01-01 00:00:01'] - input_subjects.subject_description = ['dl56', 'SC035', 'SC038', - 'oe_talab', 'rich', 'manuel'] - input_subjects = input_subjects.set_index('subject') - - subjects_csv_path = pathlib.Path('./tests/user_data/subjects.csv') - input_subjects.to_csv(subjects_csv_path) # write csv file + """Create a 'subjects.csv' file""" + input_subjects = pd.DataFrame( + columns=["subject", "sex", "subject_birth_date", "subject_description"] + ) + input_subjects.subject = [ + "subject1", + "subject2", + "subject3", + "subject4", + "subject5", + "subject6", + ] + input_subjects.sex = ["F", "M", "M", "M", "F", "F"] + input_subjects.subject_birth_date = [ + "2020-01-01 00:00:01", + "2020-01-01 00:00:01", + "2020-01-01 00:00:01", + "2020-01-01 00:00:01", + "2020-01-01 00:00:01", + "2020-01-01 00:00:01", + ] + input_subjects.subject_description = [ + "dl56", + "SC035", + "SC038", + "oe_talab", + "rich", + "manuel", + ] + input_subjects = input_subjects.set_index("subject") + + subjects_csv_path = pathlib.Path("./tests/user_data/subjects.csv") + input_subjects.to_csv(subjects_csv_path) # write csv file yield input_subjects, subjects_csv_path - subjects_csv_path.unlink() # delete csv file after use + subjects_csv_path.unlink() # delete csv file after use @pytest.fixture def ingest_subjects(pipeline, subjects_csv): from workflow_array_ephys.ingest import ingest_subjects + _, subjects_csv_path = subjects_csv ingest_subjects(subjects_csv_path, verbose=verbose) return @@ -315,27 +372,42 @@ def ingest_subjects(pipeline, subjects_csv): @pytest.fixture def sessions_csv(test_data): - """ Create a 'sessions.csv' file""" - input_sessions = pd.DataFrame(columns=['subject', 'session_dir', 'session_note', - 'user']) - input_sessions.subject = ['subject1', 'subject2', 'subject2', - 'subject3', 'subject4', 'subject5', - 'subject6'] + """Create a 'sessions.csv' file""" + input_sessions = pd.DataFrame( + columns=["subject", "session_dir", "session_note", "user"] + ) + input_sessions.subject = [ + "subject1", + "subject2", + "subject2", + "subject3", + "subject4", + "subject5", + "subject6", + ] input_sessions.session_dir = sessions_dirs - input_sessions.session_note = ['Data collection notes', - 'Data collection notes', - 'Interrupted session', - 'Data collection notes', - 'Successful data collection', - 'Successful data collection', - 'Ambient temp abnormally low'] - input_sessions.user = ['User2', 'User2', 'User2', - 'User1', 'User2', 'User1', - 'User2'] - - input_sessions = input_sessions.set_index('subject') - - sessions_csv_path = pathlib.Path('./tests/user_data/sessions.csv') + input_sessions.session_note = [ + "Data collection notes", + "Data collection notes", + "Interrupted session", + "Data collection notes", + "Successful data collection", + "Successful data collection", + "Ambient temp abnormally low", + ] + input_sessions.user = [ + "User2", + "User2", + "User2", + "User1", + "User2", + "User1", + "User2", + ] + + input_sessions = input_sessions.set_index("subject") + + sessions_csv_path = pathlib.Path("./tests/user_data/sessions.csv") input_sessions.to_csv(sessions_csv_path) # write csv file yield input_sessions, sessions_csv_path @@ -346,6 +418,7 @@ def sessions_csv(test_data): @pytest.fixture def ingest_sessions(ingest_subjects, sessions_csv): from workflow_array_ephys.ingest import ingest_sessions + _, sessions_csv_path = sessions_csv ingest_sessions(sessions_csv_path, verbose=verbose) return @@ -353,34 +426,39 @@ def ingest_sessions(ingest_subjects, sessions_csv): @pytest.fixture def testdata_paths(): - """ Paths for test data 'subjectX/sessionY/probeZ/etc'""" + """Paths for test data 'subjectX/sessionY/probeZ/etc'""" return { - 'npx3A-p1-ks': 'subject5/session1/probe_1/ks2.1_01', - 'npx3A-p2-ks': 'subject5/session1/probe_2/ks2.1_01', - 'oe_npx3B-ks': 'subject4/experiment1/recording1/continuous/' - + 'Neuropix-PXI-100.0/ks', - 'sglx_npx3A-p1': 'subject5/session1/probe_1', - 'oe_npx3B': 'subject4/experiment1/recording1/continuous/' - + 'Neuropix-PXI-100.0', - 'sglx_npx3B-p1': 'subject6/session1/towersTask_g0_imec0', - 'npx3B-p1-ks': 'subject6/session1/towersTask_g0_imec0' + "npx3A-p1-ks": "subject5/session1/probe_1/ks2.1_01", + "npx3A-p2-ks": "subject5/session1/probe_2/ks2.1_01", + "oe_npx3B-ks": "subject4/experiment1/recording1/continuous/" + + "Neuropix-PXI-100.0/ks", + "sglx_npx3A-p1": "subject5/session1/probe_1", + "oe_npx3B": "subject4/experiment1/recording1/continuous/" + + "Neuropix-PXI-100.0", + "sglx_npx3B-p1": "subject6/session1/towersTask_g0_imec0", + "npx3B-p1-ks": "subject6/session1/towersTask_g0_imec0", } @pytest.fixture def ephys_insertionlocation(pipeline, ingest_sessions): """Insert probe location into ephys.InsertionLocation""" - ephys = pipeline['ephys'] - - for probe_insertion_key in ephys.ProbeInsertion.fetch('KEY'): - ephys.InsertionLocation.insert1(dict(**probe_insertion_key, - skull_reference='Bregma', - ap_location=0, - ml_location=0, - depth=0, - theta=0, - phi=0, - beta=0), skip_duplicates=True) + ephys = pipeline["ephys"] + + for probe_insertion_key in ephys.ProbeInsertion.fetch("KEY"): + ephys.InsertionLocation.insert1( + dict( + **probe_insertion_key, + skull_reference="Bregma", + ap_location=0, + ml_location=0, + depth=0, + theta=0, + phi=0, + beta=0, + ), + skip_duplicates=True, + ) yield if _tear_down: @@ -394,7 +472,7 @@ def ephys_insertionlocation(pipeline, ingest_sessions): @pytest.fixture def kilosort_paramset(pipeline): """Insert kilosort parameters into ephys.ClusteringParamset""" - ephys = pipeline['ephys'] + ephys = pipeline["ephys"] params_ks = { "fs": 30000, @@ -418,30 +496,31 @@ def kilosort_paramset(pipeline): "nSkipCov": 25, "scaleproc": 200, "nPCs": 3, - "useRAM": 0 + "useRAM": 0, } # Insert here, since most of the test will require this paramset inserted ephys.ClusteringParamSet.insert_new_params( - clustering_method='kilosort2.5', - paramset_desc='Spike sorting using Kilosort2.5', + clustering_method="kilosort2.5", + paramset_desc="Spike sorting using Kilosort2.5", params=params_ks, - paramset_idx=0) + paramset_idx=0, + ) yield params_ks if _tear_down: if verbose: - (ephys.ClusteringParamSet & 'paramset_idx = 0').delete() + (ephys.ClusteringParamSet & "paramset_idx = 0").delete() else: with QuietStdOut(): - (ephys.ClusteringParamSet & 'paramset_idx = 0').delete() + (ephys.ClusteringParamSet & "paramset_idx = 0").delete() @pytest.fixture def ephys_recordings(pipeline, ingest_sessions): """Populate ephys.EphysRecording""" - ephys = pipeline['ephys'] + ephys = pipeline["ephys"] ephys.EphysRecording.populate() @@ -458,19 +537,24 @@ def ephys_recordings(pipeline, ingest_sessions): @pytest.fixture def clustering_tasks(pipeline, kilosort_paramset, ephys_recordings): """Insert keys from ephys.EphysRecording into ephys.Clustering""" - ephys = pipeline['ephys'] + ephys = pipeline["ephys"] - for ephys_rec_key in (ephys.EphysRecording - ephys.ClusteringTask).fetch('KEY'): - ephys_file_path = pathlib.Path(((ephys.EphysRecording.EphysFile & ephys_rec_key - ).fetch('file_path'))[0]) + for ephys_rec_key in (ephys.EphysRecording - ephys.ClusteringTask).fetch("KEY"): + ephys_file_path = pathlib.Path( + ((ephys.EphysRecording.EphysFile & ephys_rec_key).fetch("file_path"))[0] + ) ephys_file = find_full_path(get_ephys_root_data_dir(), ephys_file_path) recording_dir = ephys_file.parent - kilosort_dir = next(recording_dir.rglob('spike_times.npy')).parent - ephys.ClusteringTask.insert1({**ephys_rec_key, - 'paramset_idx': 0, - 'task_mode': 'load', - 'clustering_output_dir': kilosort_dir.as_posix()}, - skip_duplicates=True) + kilosort_dir = next(recording_dir.rglob("spike_times.npy")).parent + ephys.ClusteringTask.insert1( + { + **ephys_rec_key, + "paramset_idx": 0, + "task_mode": "load", + "clustering_output_dir": kilosort_dir.as_posix(), + }, + skip_duplicates=True, + ) yield @@ -485,9 +569,9 @@ def clustering_tasks(pipeline, kilosort_paramset, ephys_recordings): @pytest.fixture def clustering(clustering_tasks, pipeline): """Populate ephys.Clustering""" - ephys = pipeline['ephys'] + ephys = pipeline["ephys"] - if pipeline['ephys_mode'] == "no-curation": + if pipeline["ephys_mode"] == "no-curation": clustering_table = ephys.CuratedClustering else: clustering_table = ephys.Clustering @@ -507,14 +591,14 @@ def clustering(clustering_tasks, pipeline): @pytest.fixture def curations(clustering, pipeline): """Insert keys from ephys.ClusteringTask into ephys.Curation""" - ephys_mode = pipeline['ephys_mode'] + ephys_mode = pipeline["ephys_mode"] - if ephys_mode == 'no-curation': + if ephys_mode == "no-curation": yield else: - ephys = pipeline['ephys'] + ephys = pipeline["ephys"] - for key in (ephys.ClusteringTask - ephys.Curation).fetch('KEY'): + for key in (ephys.ClusteringTask - ephys.Curation).fetch("KEY"): ephys.Curation().create1_from_clustering_task(key) yield @@ -525,5 +609,3 @@ def curations(clustering, pipeline): else: with QuietStdOut(): ephys.Curation.delete() - - From 228952da2550775d643a5a1c2a6d8211e9be4fbc Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 6 Jun 2022 11:46:56 -0500 Subject: [PATCH 48/59] Revert "Add links for notebook 09-NWB" This reverts commit 765516f264b836be2ffe909b6386c4bc82501777. --- notebooks/09-NWB-export.ipynb | 22 +- notebooks/py_scripts/09-NWB-export.py | 16 +- tests/__init__.py | 506 +++++++++++--------------- 3 files changed, 218 insertions(+), 326 deletions(-) diff --git a/notebooks/09-NWB-export.ipynb b/notebooks/09-NWB-export.ipynb index a1fbc552..61d984b2 100644 --- a/notebooks/09-NWB-export.ipynb +++ b/notebooks/09-NWB-export.ipynb @@ -73,7 +73,7 @@ "source": [ "## Export to NWB\n", "\n", - "Several Elements have tools for generating NWB materials. We'll use the following keys for testing these functions:" + "Each of the following elements has tools for interacting with NWB files: `element-lab`, `element-session`, `element-array-ephys`, and `element-interface`. We'll use the following keys for testing these functions:" ] }, { @@ -90,25 +90,12 @@ " \"session_datetime\": \"2018-07-03 20:32:28\"}" ] }, - { - "cell_type": "markdown", - "id": "5cb8b7f2", - "metadata": {}, - "source": [ - "To skip ahead, use any of the following links:\n", - "- [Element Lab](#Element-Lab)\n", - "- [Element Animal](#Element-Animal)\n", - "- [Element Session](#Element-Session)\n", - "- [Element Array Electrophysiology](#Element-Array-Electrophysiology) - If you are using all upstream Elements, you can skip to this\n", - "- [Element Interface for DANDI Upload](#DANDI-Upload)" - ] - }, { "cell_type": "markdown", "id": "fc2d028a", "metadata": {}, "source": [ - "\n", + "\n", "### Element Lab\n", "\n", "Because an NWB file must include session information, `element_lab_to_nwb_dict` can only help package information from the Lab schema into `dict` format. This would be helpful for a team using Element Lab, but not others. " @@ -183,7 +170,6 @@ "id": "66af900a", "metadata": {}, "source": [ - "\n", "### Element Animal\n", "\n", "`subject_to_nwb` can use a session key to retrieve subject information, and will return an nwb file with a number of sections specified. When packaging into an NWB file, `pynwb` will display a warning regarding timezone information - datetime fields are assumed to be in local time, and will be converted to UTC." @@ -228,7 +214,6 @@ "id": "b1d2c7e3", "metadata": {}, "source": [ - "\n", "### Element Session\n", "\n", "`session_to_nwb` pulls the same information as above, while also including information about session experimenter and session time. The export process provides the same warning about timezone conversion to UTC.\n" @@ -283,7 +268,7 @@ "id": "bb5fba81", "metadata": {}, "source": [ - "\n", + "\n", "### Element Array Electrophysiology\n", "\n", "`ecephys_session_to_nwb` provides a full export mechanism, returning an NWB file with raw data, spikes, and LFP. Optional arguments determine which pieces are exported. For demonstration purposes, we recommend limiting `end_frame`.\n" @@ -445,7 +430,6 @@ "id": "f717baf8", "metadata": {}, "source": [ - "\n", "## DANDI Upload\n", "\n", "`element-interface.dandi` includes the `upload_to_dandi` utility to support direct uploads. For more information, see [DANDI documentation](https://www.dandiarchive.org/handbook/10_using_dandi/).\n", diff --git a/notebooks/py_scripts/09-NWB-export.py b/notebooks/py_scripts/09-NWB-export.py index f2c52442..9a7473a0 100644 --- a/notebooks/py_scripts/09-NWB-export.py +++ b/notebooks/py_scripts/09-NWB-export.py @@ -38,7 +38,7 @@ # ## Export to NWB # -# Several Elements have tools for generating NWB materials. We'll use the following keys for testing these functions: +# Each of the following elements has tools for interacting with NWB files: `element-lab`, `element-session`, `element-array-ephys`, and `element-interface`. We'll use the following keys for testing these functions: lab_key={"lab": "LabA"} protocol_key={"protocol": "ProtA"} @@ -46,14 +46,7 @@ session_key={"subject": "subject5", "session_datetime": "2018-07-03 20:32:28"} -# To skip ahead, use any of the following links: -# - [Element Lab](#Element-Lab) -# - [Element Animal](#Element-Animal) -# - [Element Session](#Element-Session) -# - [Element Array Electrophysiology](#Element-Array-Electrophysiology) - If you are using all upstream Elements, you can skip to this -# - [Element Interface for DANDI Upload](#DANDI-Upload) - -# +# # ### Element Lab # # Because an NWB file must include session information, `element_lab_to_nwb_dict` can only help package information from the Lab schema into `dict` format. This would be helpful for a team using Element Lab, but not others. @@ -63,14 +56,12 @@ element_lab_to_nwb_dict(lab_key=lab_key, protocol_key=protocol_key, project_key=project_key) -# # ### Element Animal # # `subject_to_nwb` can use a session key to retrieve subject information, and will return an nwb file with a number of sections specified. When packaging into an NWB file, `pynwb` will display a warning regarding timezone information - datetime fields are assumed to be in local time, and will be converted to UTC. subject_to_nwb(session_key=session_key) -# # ### Element Session # # `session_to_nwb` pulls the same information as above, while also including information about session experimenter and session time. The export process provides the same warning about timezone conversion to UTC. @@ -78,7 +69,7 @@ session_to_nwb(session_key=session_key) -# +# # ### Element Array Electrophysiology # # `ecephys_session_to_nwb` provides a full export mechanism, returning an NWB file with raw data, spikes, and LFP. Optional arguments determine which pieces are exported. For demonstration purposes, we recommend limiting `end_frame`. @@ -106,7 +97,6 @@ write_nwb(nwbfile, f'./temp_nwb/{time.strftime("_test_%Y%m%d-%H%M%S.nwb")}') # - -# # ## DANDI Upload # # `element-interface.dandi` includes the `upload_to_dandi` utility to support direct uploads. For more information, see [DANDI documentation](https://www.dandiarchive.org/handbook/10_using_dandi/). diff --git a/tests/__init__.py b/tests/__init__.py index 224ba0cb..f9a53f01 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -19,18 +19,16 @@ _tear_down = False verbose = False -pathlib.Path("./tests/user_data").mkdir(exist_ok=True) -pathlib.Path("./tests/user_data/lab").mkdir(exist_ok=True) - -sessions_dirs = [ - "subject1/session1", - "subject2/session1", - "subject2/session2", - "subject3/session1", - "subject4/experiment1", - "subject5/session1", - "subject6/session1", -] +pathlib.Path('./tests/user_data').mkdir(exist_ok=True) +pathlib.Path('./tests/user_data/lab').mkdir(exist_ok=True) + +sessions_dirs = ['subject1/session1', + 'subject2/session1', + 'subject2/session2', + 'subject3/session1', + 'subject4/experiment1', + 'subject5/session1', + 'subject6/session1'] # -------------------- HELPER CLASS -------------------- @@ -41,44 +39,38 @@ def write_csv(content, path): :param path: pathlib PosixPath :param content: list of strings, each as row of CSV """ - with open(path, "w") as f: + with open(path, 'w') as f: for line in content: - f.write(line + "\n") + f.write(line+'\n') class QuietStdOut: """If verbose set to false, used to quiet tear_down table.delete prints""" - def __enter__(self): self._original_stdout = sys.stdout - sys.stdout = open(os.devnull, "w") + sys.stdout = open(os.devnull, 'w') def __exit__(self, exc_type, exc_val, exc_tb): sys.stdout.close() sys.stdout = self._original_stdout - # ---------------------- FIXTURES ---------------------- @pytest.fixture(autouse=True) def dj_config(): - """If dj_local_config exists, load""" - if pathlib.Path("./dj_local_conf.json").exists(): - dj.config.load("./dj_local_conf.json") - dj.config["safemode"] = False - dj.config["custom"] = { - "ephys_mode": ( - os.environ.get("EPHYS_MODE") or dj.config["custom"]["ephys_mode"] - ), - "database.prefix": ( - os.environ.get("DATABASE_PREFIX") or dj.config["custom"]["database.prefix"] - ), - "ephys_root_data_dir": ( - os.environ.get("EPHYS_ROOT_DATA_DIR").split(",") - if os.environ.get("EPHYS_ROOT_DATA_DIR") - else dj.config["custom"]["ephys_root_data_dir"] - ), + """ If dj_local_config exists, load""" + if pathlib.Path('./dj_local_conf.json').exists(): + dj.config.load('./dj_local_conf.json') + dj.config['safemode'] = False + dj.config['custom'] = { + 'ephys_mode': (os.environ.get('EPHYS_MODE') + or dj.config['custom']['ephys_mode']), + 'database.prefix': (os.environ.get('DATABASE_PREFIX') + or dj.config['custom']['database.prefix']), + 'ephys_root_data_dir': (os.environ.get('EPHYS_ROOT_DATA_DIR').split(',') + if os.environ.get('EPHYS_ROOT_DATA_DIR') + else dj.config['custom']['ephys_root_data_dir']) } return @@ -92,62 +84,50 @@ def test_data(dj_config): try: find_full_path(get_ephys_root_data_dir(), p) except FileNotFoundError: - test_data_exists = False # If data not found + test_data_exists = False # If data not found - if not test_data_exists: # attempt to djArchive dowload + if not test_data_exists: # attempt to djArchive dowload try: - dj.config["custom"].update( - { - "djarchive.client.endpoint": os.environ[ - "DJARCHIVE_CLIENT_ENDPOINT" - ], - "djarchive.client.bucket": os.environ["DJARCHIVE_CLIENT_BUCKET"], - "djarchive.client.access_key": os.environ[ - "DJARCHIVE_CLIENT_ACCESSKEY" - ], - "djarchive.client.secret_key": os.environ[ - "DJARCHIVE_CLIENT_SECRETKEY" - ], - } - ) + dj.config['custom'].update({ + 'djarchive.client.endpoint': + os.environ['DJARCHIVE_CLIENT_ENDPOINT'], + 'djarchive.client.bucket': + os.environ['DJARCHIVE_CLIENT_BUCKET'], + 'djarchive.client.access_key': + os.environ['DJARCHIVE_CLIENT_ACCESSKEY'], + 'djarchive.client.secret_key': + os.environ['DJARCHIVE_CLIENT_SECRETKEY'] + }) except KeyError as e: raise FileNotFoundError( - f" Full test data not available." - f"\nAttempting to download from DJArchive," - f" but no credentials found in environment variables." - f"\nError: {str(e)}" - ) + f' Full test data not available.' + f'\nAttempting to download from DJArchive,' + f' but no credentials found in environment variables.' + f'\nError: {str(e)}') import djarchive_client - client = djarchive_client.client() test_data_dir = get_ephys_root_data_dir() if isinstance(test_data_dir, list): # if multiple root dirs, first test_data_dir = test_data_dir[0] - client.download( - "workflow-array-ephys-benchmark", - "v2", - str(test_data_dir), - create_target=False, - ) + client.download('workflow-array-ephys-benchmark', + 'v2', + str(test_data_dir), create_target=False) return @pytest.fixture def pipeline(): from workflow_array_ephys import pipeline - - yield { - "subject": pipeline.subject, - "lab": pipeline.lab, - "ephys": pipeline.ephys, - "probe": pipeline.probe, - "session": pipeline.session, - "get_ephys_root_data_dir": pipeline.get_ephys_root_data_dir, - "ephys_mode": pipeline.ephys_mode, - } + yield {'subject': pipeline.subject, + 'lab': pipeline.lab, + 'ephys': pipeline.ephys, + 'probe': pipeline.probe, + 'session': pipeline.session, + 'get_ephys_root_data_dir': pipeline.get_ephys_root_data_dir, + 'ephys_mode': pipeline.ephys_mode} if verbose and _tear_down: pipeline.subject.Subject.delete() @@ -158,19 +138,18 @@ def pipeline(): @pytest.fixture def lab_csv(): - """Create a 'labs.csv' file""" - lab_content = [ - "lab,lab_name,institution,address," + "time_zone,location,location_description", - "LabA,The Example Lab,Example Uni," - + "'221B Baker St,London NW1 6XE,UK',UTC+0," - + "Example Building,'2nd floor lab dedicated to all " - + "fictional experiments.'", - "LabB,The Other Lab,Other Uni," - + "'Oxford OX1 2JD, United Kingdom',UTC+0," - + "Other Building,'fictional campus dedicated to imaginary" - + "experiments.'", - ] - lab_csv_path = pathlib.Path("./tests/user_data/lab/labs.csv") + """ Create a 'labs.csv' file""" + lab_content = ["lab,lab_name,institution,address," + + "time_zone,location,location_description", + "LabA,The Example Lab,Example Uni," + + "'221B Baker St,London NW1 6XE,UK',UTC+0," + + "Example Building,'2nd floor lab dedicated to all " + + "fictional experiments.'", + "LabB,The Other Lab,Other Uni," + + "'Oxford OX1 2JD, United Kingdom',UTC+0," + + "Other Building,'fictional campus dedicated to imaginary" + + "experiments.'"] + lab_csv_path = pathlib.Path('./tests/user_data/lab/labs.csv') write_csv(lab_content, lab_csv_path) yield lab_content, lab_csv_path @@ -179,19 +158,18 @@ def lab_csv(): @pytest.fixture def lab_project_csv(): - """Create a 'projects.csv' file""" - lab_project_content = [ - "project,project_description,repository_url," + "repository_name,codeurl", - "ProjA,Example project to populate element-lab," - + "https://github.com/datajoint/element-lab/," - + "element-lab,https://github.com/datajoint/element" - + "-lab/tree/main/element_lab", - "ProjB,Other example project to populate element-" - + "lab,https://github.com/datajoint/element-session" - + "/,element-session,https://github.com/datajoint/" - + "element-session/tree/main/element_session", - ] - lab_project_csv_path = pathlib.Path("./tests/user_data/lab/projects.csv") + """ Create a 'projects.csv' file""" + lab_project_content = ["project,project_description,repository_url," + + "repository_name,codeurl", + "ProjA,Example project to populate element-lab," + + "https://github.com/datajoint/element-lab/," + + "element-lab,https://github.com/datajoint/element" + + "-lab/tree/main/element_lab", + "ProjB,Other example project to populate element-" + + "lab,https://github.com/datajoint/element-session" + + "/,element-session,https://github.com/datajoint/" + + "element-session/tree/main/element_session"] + lab_project_csv_path = pathlib.Path('./tests/user_data/lab/projects.csv') write_csv(lab_project_content, lab_project_csv_path) yield lab_project_content, lab_project_csv_path @@ -200,19 +178,15 @@ def lab_project_csv(): @pytest.fixture def lab_project_users_csv(): - """Create a 'project_users.csv' file""" - lab_project_user_content = [ - "user,project", - "Sherlock,ProjA", - "Sherlock,ProjB", - "Watson,ProjB", - "Dr. Candace Pert,ProjA", - "User1,ProjA", - ] - lab_project_user_csv_path = pathlib.Path( - "./tests/user_data/lab/\ - project_users.csv" - ) + """ Create a 'project_users.csv' file""" + lab_project_user_content = ["user,project", + "Sherlock,ProjA", + "Sherlock,ProjB", + "Watson,ProjB", + "Dr. Candace Pert,ProjA", + "User1,ProjA"] + lab_project_user_csv_path = pathlib.Path('./tests/user_data/lab/\ + project_users.csv') write_csv(lab_project_user_content, lab_project_user_csv_path) yield lab_project_user_content, lab_project_user_csv_path @@ -221,16 +195,12 @@ def lab_project_users_csv(): @pytest.fixture def lab_publications_csv(): - """Create a 'publications.csv' file""" - lab_publication_content = [ - "project,publication", - "ProjA,arXiv:1807.11104", - "ProjA,arXiv:1807.11104v1", - ] - lab_publication_csv_path = pathlib.Path( - "./tests/user_data/lab/\ - publications.csv" - ) + """ Create a 'publications.csv' file""" + lab_publication_content = ["project,publication", + "ProjA,arXiv:1807.11104", + "ProjA,arXiv:1807.11104v1"] + lab_publication_csv_path = pathlib.Path('./tests/user_data/lab/\ + publications.csv') write_csv(lab_publication_content, lab_publication_csv_path) yield lab_publication_content, lab_publication_csv_path @@ -239,14 +209,12 @@ def lab_publications_csv(): @pytest.fixture def lab_keywords_csv(): - """Create a 'keywords.csv' file""" - lab_keyword_content = [ - "project,keyword", - "ProjA,Study", - "ProjA,Example", - "ProjB,Alternate", - ] - lab_keyword_csv_path = pathlib.Path("./tests/user_data/lab/keywords.csv") + """ Create a 'keywords.csv' file""" + lab_keyword_content = ["project,keyword", + "ProjA,Study", + "ProjA,Example", + "ProjB,Alternate"] + lab_keyword_csv_path = pathlib.Path('./tests/user_data/lab/keywords.csv') write_csv(lab_keyword_content, lab_keyword_csv_path) yield lab_keyword_content, lab_keyword_csv_path @@ -255,13 +223,13 @@ def lab_keywords_csv(): @pytest.fixture def lab_protocol_csv(): - """Create a 'protocols.csv' file""" - lab_protocol_content = [ - "protocol,protocol_type,protocol_description", - "ProtA,IRB expedited review,Protocol for managing " + "data ingestion", - "ProtB,Alternative Method,Limited protocol for " + "piloting only", - ] - lab_protocol_csv_path = pathlib.Path("./tests/user_data/lab/protocols.csv") + """ Create a 'protocols.csv' file""" + lab_protocol_content = ["protocol,protocol_type,protocol_description", + "ProtA,IRB expedited review,Protocol for managing " + + "data ingestion", + "ProtB,Alternative Method,Limited protocol for " + + "piloting only"] + lab_protocol_csv_path = pathlib.Path('./tests/user_data/lab/protocols.csv') write_csv(lab_protocol_content, lab_protocol_csv_path) yield lab_protocol_content, lab_protocol_csv_path @@ -270,16 +238,16 @@ def lab_protocol_csv(): @pytest.fixture def lab_user_csv(): - """Create a 'users.csv' file""" - lab_user_content = [ - "lab,user,user_role,user_email,user_cellphone", - "LabA,Sherlock,PI,Sherlock@BakerSt.com," + "+44 20 7946 0344", - "LabA,Watson,Dr,DrWatson@BakerSt.com,+44 73 8389 1763", - "LabB,Dr. Candace Pert,PI,Pert@gmail.com," + "+44 74 4046 5899", - "LabA,User1,Lab Tech,fake@email.com,+44 1632 960103", - "LabB,User2,Lab Tech,fake2@email.com,+44 1632 960102", - ] - lab_user_csv_path = pathlib.Path("./tests/user_data/lab/users.csv") + """ Create a 'users.csv' file""" + lab_user_content = ["lab,user,user_role,user_email,user_cellphone", + "LabA,Sherlock,PI,Sherlock@BakerSt.com," + + "+44 20 7946 0344", + "LabA,Watson,Dr,DrWatson@BakerSt.com,+44 73 8389 1763", + "LabB,Dr. Candace Pert,PI,Pert@gmail.com," + + "+44 74 4046 5899", + "LabA,User1,Lab Tech,fake@email.com,+44 1632 960103", + "LabB,User2,Lab Tech,fake2@email.com,+44 1632 960102"] + lab_user_csv_path = pathlib.Path('./tests/user_data/lab/users.csv') write_csv(lab_user_content, lab_user_csv_path) yield lab_user_content, lab_user_csv_path @@ -287,19 +255,11 @@ def lab_user_csv(): @pytest.fixture -def ingest_lab( - pipeline, - lab_csv, - lab_project_csv, - lab_publications_csv, - lab_keywords_csv, - lab_protocol_csv, - lab_user_csv, - lab_project_users_csv, -): - """From workflow_array_ephys ingest.py, import ingest_lab, run""" +def ingest_lab(pipeline, lab_csv, lab_project_csv, lab_publications_csv, + lab_keywords_csv, lab_protocol_csv, lab_user_csv, + lab_project_users_csv): + """ From workflow_array_ephys ingest.py, import ingest_lab, run """ from workflow_array_ephys.ingest import ingest_lab - _, lab_csv_path = lab_csv _, lab_project_csv_path = lab_project_csv _, lab_publication_csv_path = lab_publications_csv @@ -307,64 +267,47 @@ def ingest_lab( _, lab_protocol_csv_path = lab_protocol_csv _, lab_user_csv_path = lab_user_csv _, lab_project_user_csv_path = lab_project_users_csv - ingest_lab( - lab_csv_path=lab_csv_path, - project_csv_path=lab_project_csv_path, - publication_csv_path=lab_publication_csv_path, - keyword_csv_path=lab_keyword_csv_path, - protocol_csv_path=lab_protocol_csv_path, - users_csv_path=lab_user_csv_path, - project_user_csv_path=lab_project_user_csv_path, - verbose=verbose, - ) + ingest_lab(lab_csv_path=lab_csv_path, + project_csv_path=lab_project_csv_path, + publication_csv_path=lab_publication_csv_path, + keyword_csv_path=lab_keyword_csv_path, + protocol_csv_path=lab_protocol_csv_path, + users_csv_path=lab_user_csv_path, + project_user_csv_path=lab_project_user_csv_path, verbose=verbose) return @pytest.fixture def subjects_csv(): - """Create a 'subjects.csv' file""" - input_subjects = pd.DataFrame( - columns=["subject", "sex", "subject_birth_date", "subject_description"] - ) - input_subjects.subject = [ - "subject1", - "subject2", - "subject3", - "subject4", - "subject5", - "subject6", - ] - input_subjects.sex = ["F", "M", "M", "M", "F", "F"] - input_subjects.subject_birth_date = [ - "2020-01-01 00:00:01", - "2020-01-01 00:00:01", - "2020-01-01 00:00:01", - "2020-01-01 00:00:01", - "2020-01-01 00:00:01", - "2020-01-01 00:00:01", - ] - input_subjects.subject_description = [ - "dl56", - "SC035", - "SC038", - "oe_talab", - "rich", - "manuel", - ] - input_subjects = input_subjects.set_index("subject") - - subjects_csv_path = pathlib.Path("./tests/user_data/subjects.csv") - input_subjects.to_csv(subjects_csv_path) # write csv file + """ Create a 'subjects.csv' file""" + input_subjects = pd.DataFrame(columns=['subject', 'sex', + 'subject_birth_date', + 'subject_description']) + input_subjects.subject = ['subject1', 'subject2', + 'subject3', 'subject4', + 'subject5', 'subject6'] + input_subjects.sex = ['F', 'M', 'M', 'M', 'F', 'F'] + input_subjects.subject_birth_date = ['2020-01-01 00:00:01', + '2020-01-01 00:00:01', + '2020-01-01 00:00:01', + '2020-01-01 00:00:01', + '2020-01-01 00:00:01', + '2020-01-01 00:00:01'] + input_subjects.subject_description = ['dl56', 'SC035', 'SC038', + 'oe_talab', 'rich', 'manuel'] + input_subjects = input_subjects.set_index('subject') + + subjects_csv_path = pathlib.Path('./tests/user_data/subjects.csv') + input_subjects.to_csv(subjects_csv_path) # write csv file yield input_subjects, subjects_csv_path - subjects_csv_path.unlink() # delete csv file after use + subjects_csv_path.unlink() # delete csv file after use @pytest.fixture def ingest_subjects(pipeline, subjects_csv): from workflow_array_ephys.ingest import ingest_subjects - _, subjects_csv_path = subjects_csv ingest_subjects(subjects_csv_path, verbose=verbose) return @@ -372,42 +315,27 @@ def ingest_subjects(pipeline, subjects_csv): @pytest.fixture def sessions_csv(test_data): - """Create a 'sessions.csv' file""" - input_sessions = pd.DataFrame( - columns=["subject", "session_dir", "session_note", "user"] - ) - input_sessions.subject = [ - "subject1", - "subject2", - "subject2", - "subject3", - "subject4", - "subject5", - "subject6", - ] + """ Create a 'sessions.csv' file""" + input_sessions = pd.DataFrame(columns=['subject', 'session_dir', 'session_note', + 'user']) + input_sessions.subject = ['subject1', 'subject2', 'subject2', + 'subject3', 'subject4', 'subject5', + 'subject6'] input_sessions.session_dir = sessions_dirs - input_sessions.session_note = [ - "Data collection notes", - "Data collection notes", - "Interrupted session", - "Data collection notes", - "Successful data collection", - "Successful data collection", - "Ambient temp abnormally low", - ] - input_sessions.user = [ - "User2", - "User2", - "User2", - "User1", - "User2", - "User1", - "User2", - ] - - input_sessions = input_sessions.set_index("subject") - - sessions_csv_path = pathlib.Path("./tests/user_data/sessions.csv") + input_sessions.session_note = ['Data collection notes', + 'Data collection notes', + 'Interrupted session', + 'Data collection notes', + 'Successful data collection', + 'Successful data collection', + 'Ambient temp abnormally low'] + input_sessions.user = ['User2', 'User2', 'User2', + 'User1', 'User2', 'User1', + 'User2'] + + input_sessions = input_sessions.set_index('subject') + + sessions_csv_path = pathlib.Path('./tests/user_data/sessions.csv') input_sessions.to_csv(sessions_csv_path) # write csv file yield input_sessions, sessions_csv_path @@ -418,7 +346,6 @@ def sessions_csv(test_data): @pytest.fixture def ingest_sessions(ingest_subjects, sessions_csv): from workflow_array_ephys.ingest import ingest_sessions - _, sessions_csv_path = sessions_csv ingest_sessions(sessions_csv_path, verbose=verbose) return @@ -426,39 +353,34 @@ def ingest_sessions(ingest_subjects, sessions_csv): @pytest.fixture def testdata_paths(): - """Paths for test data 'subjectX/sessionY/probeZ/etc'""" + """ Paths for test data 'subjectX/sessionY/probeZ/etc'""" return { - "npx3A-p1-ks": "subject5/session1/probe_1/ks2.1_01", - "npx3A-p2-ks": "subject5/session1/probe_2/ks2.1_01", - "oe_npx3B-ks": "subject4/experiment1/recording1/continuous/" - + "Neuropix-PXI-100.0/ks", - "sglx_npx3A-p1": "subject5/session1/probe_1", - "oe_npx3B": "subject4/experiment1/recording1/continuous/" - + "Neuropix-PXI-100.0", - "sglx_npx3B-p1": "subject6/session1/towersTask_g0_imec0", - "npx3B-p1-ks": "subject6/session1/towersTask_g0_imec0", + 'npx3A-p1-ks': 'subject5/session1/probe_1/ks2.1_01', + 'npx3A-p2-ks': 'subject5/session1/probe_2/ks2.1_01', + 'oe_npx3B-ks': 'subject4/experiment1/recording1/continuous/' + + 'Neuropix-PXI-100.0/ks', + 'sglx_npx3A-p1': 'subject5/session1/probe_1', + 'oe_npx3B': 'subject4/experiment1/recording1/continuous/' + + 'Neuropix-PXI-100.0', + 'sglx_npx3B-p1': 'subject6/session1/towersTask_g0_imec0', + 'npx3B-p1-ks': 'subject6/session1/towersTask_g0_imec0' } @pytest.fixture def ephys_insertionlocation(pipeline, ingest_sessions): """Insert probe location into ephys.InsertionLocation""" - ephys = pipeline["ephys"] - - for probe_insertion_key in ephys.ProbeInsertion.fetch("KEY"): - ephys.InsertionLocation.insert1( - dict( - **probe_insertion_key, - skull_reference="Bregma", - ap_location=0, - ml_location=0, - depth=0, - theta=0, - phi=0, - beta=0, - ), - skip_duplicates=True, - ) + ephys = pipeline['ephys'] + + for probe_insertion_key in ephys.ProbeInsertion.fetch('KEY'): + ephys.InsertionLocation.insert1(dict(**probe_insertion_key, + skull_reference='Bregma', + ap_location=0, + ml_location=0, + depth=0, + theta=0, + phi=0, + beta=0), skip_duplicates=True) yield if _tear_down: @@ -472,7 +394,7 @@ def ephys_insertionlocation(pipeline, ingest_sessions): @pytest.fixture def kilosort_paramset(pipeline): """Insert kilosort parameters into ephys.ClusteringParamset""" - ephys = pipeline["ephys"] + ephys = pipeline['ephys'] params_ks = { "fs": 30000, @@ -496,31 +418,30 @@ def kilosort_paramset(pipeline): "nSkipCov": 25, "scaleproc": 200, "nPCs": 3, - "useRAM": 0, + "useRAM": 0 } # Insert here, since most of the test will require this paramset inserted ephys.ClusteringParamSet.insert_new_params( - clustering_method="kilosort2.5", - paramset_desc="Spike sorting using Kilosort2.5", + clustering_method='kilosort2.5', + paramset_desc='Spike sorting using Kilosort2.5', params=params_ks, - paramset_idx=0, - ) + paramset_idx=0) yield params_ks if _tear_down: if verbose: - (ephys.ClusteringParamSet & "paramset_idx = 0").delete() + (ephys.ClusteringParamSet & 'paramset_idx = 0').delete() else: with QuietStdOut(): - (ephys.ClusteringParamSet & "paramset_idx = 0").delete() + (ephys.ClusteringParamSet & 'paramset_idx = 0').delete() @pytest.fixture def ephys_recordings(pipeline, ingest_sessions): """Populate ephys.EphysRecording""" - ephys = pipeline["ephys"] + ephys = pipeline['ephys'] ephys.EphysRecording.populate() @@ -537,24 +458,19 @@ def ephys_recordings(pipeline, ingest_sessions): @pytest.fixture def clustering_tasks(pipeline, kilosort_paramset, ephys_recordings): """Insert keys from ephys.EphysRecording into ephys.Clustering""" - ephys = pipeline["ephys"] + ephys = pipeline['ephys'] - for ephys_rec_key in (ephys.EphysRecording - ephys.ClusteringTask).fetch("KEY"): - ephys_file_path = pathlib.Path( - ((ephys.EphysRecording.EphysFile & ephys_rec_key).fetch("file_path"))[0] - ) + for ephys_rec_key in (ephys.EphysRecording - ephys.ClusteringTask).fetch('KEY'): + ephys_file_path = pathlib.Path(((ephys.EphysRecording.EphysFile & ephys_rec_key + ).fetch('file_path'))[0]) ephys_file = find_full_path(get_ephys_root_data_dir(), ephys_file_path) recording_dir = ephys_file.parent - kilosort_dir = next(recording_dir.rglob("spike_times.npy")).parent - ephys.ClusteringTask.insert1( - { - **ephys_rec_key, - "paramset_idx": 0, - "task_mode": "load", - "clustering_output_dir": kilosort_dir.as_posix(), - }, - skip_duplicates=True, - ) + kilosort_dir = next(recording_dir.rglob('spike_times.npy')).parent + ephys.ClusteringTask.insert1({**ephys_rec_key, + 'paramset_idx': 0, + 'task_mode': 'load', + 'clustering_output_dir': kilosort_dir.as_posix()}, + skip_duplicates=True) yield @@ -569,9 +485,9 @@ def clustering_tasks(pipeline, kilosort_paramset, ephys_recordings): @pytest.fixture def clustering(clustering_tasks, pipeline): """Populate ephys.Clustering""" - ephys = pipeline["ephys"] + ephys = pipeline['ephys'] - if pipeline["ephys_mode"] == "no-curation": + if pipeline['ephys_mode'] == "no-curation": clustering_table = ephys.CuratedClustering else: clustering_table = ephys.Clustering @@ -591,14 +507,14 @@ def clustering(clustering_tasks, pipeline): @pytest.fixture def curations(clustering, pipeline): """Insert keys from ephys.ClusteringTask into ephys.Curation""" - ephys_mode = pipeline["ephys_mode"] + ephys_mode = pipeline['ephys_mode'] - if ephys_mode == "no-curation": + if ephys_mode == 'no-curation': yield else: - ephys = pipeline["ephys"] + ephys = pipeline['ephys'] - for key in (ephys.ClusteringTask - ephys.Curation).fetch("KEY"): + for key in (ephys.ClusteringTask - ephys.Curation).fetch('KEY'): ephys.Curation().create1_from_clustering_task(key) yield @@ -609,3 +525,5 @@ def curations(clustering, pipeline): else: with QuietStdOut(): ephys.Curation.delete() + + From 5f03d172df7ed2d3a87a0f70b10133fcd0434fd6 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 6 Jun 2022 11:51:01 -0500 Subject: [PATCH 49/59] Add links for notebook 09-NWB --- notebooks/09-NWB-export.ipynb | 22 +++++++++++++++++++--- notebooks/py_scripts/09-NWB-export.py | 16 +++++++++++++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/notebooks/09-NWB-export.ipynb b/notebooks/09-NWB-export.ipynb index 61d984b2..2cfd96c7 100644 --- a/notebooks/09-NWB-export.ipynb +++ b/notebooks/09-NWB-export.ipynb @@ -73,7 +73,7 @@ "source": [ "## Export to NWB\n", "\n", - "Each of the following elements has tools for interacting with NWB files: `element-lab`, `element-session`, `element-array-ephys`, and `element-interface`. We'll use the following keys for testing these functions:" + "Several Elements have tools for generating NWB materials. We'll use the following keys for testing these functions:" ] }, { @@ -90,12 +90,25 @@ " \"session_datetime\": \"2018-07-03 20:32:28\"}" ] }, + { + "cell_type": "markdown", + "id": "764f5a00", + "metadata": {}, + "source": [ + "To skip ahead, use any of the following links:\n", + "- [Element Lab](#Element-Lab)\n", + "- [Element Animal](#Element-Animal)\n", + "- [Element Session](#Element-Session)\n", + "- [Element Array Electrophysiology](#Element-Array-Electrophysiology) - If you are using all upstream Elements, you can skip to this\n", + "- [Element Interface for DANDI Upload](#DANDI-Upload)" + ] + }, { "cell_type": "markdown", "id": "fc2d028a", "metadata": {}, "source": [ - "\n", + "\n", "### Element Lab\n", "\n", "Because an NWB file must include session information, `element_lab_to_nwb_dict` can only help package information from the Lab schema into `dict` format. This would be helpful for a team using Element Lab, but not others. " @@ -170,6 +183,7 @@ "id": "66af900a", "metadata": {}, "source": [ + "\n", "### Element Animal\n", "\n", "`subject_to_nwb` can use a session key to retrieve subject information, and will return an nwb file with a number of sections specified. When packaging into an NWB file, `pynwb` will display a warning regarding timezone information - datetime fields are assumed to be in local time, and will be converted to UTC." @@ -214,6 +228,7 @@ "id": "b1d2c7e3", "metadata": {}, "source": [ + "\n", "### Element Session\n", "\n", "`session_to_nwb` pulls the same information as above, while also including information about session experimenter and session time. The export process provides the same warning about timezone conversion to UTC.\n" @@ -268,7 +283,7 @@ "id": "bb5fba81", "metadata": {}, "source": [ - "\n", + "\n", "### Element Array Electrophysiology\n", "\n", "`ecephys_session_to_nwb` provides a full export mechanism, returning an NWB file with raw data, spikes, and LFP. Optional arguments determine which pieces are exported. For demonstration purposes, we recommend limiting `end_frame`.\n" @@ -430,6 +445,7 @@ "id": "f717baf8", "metadata": {}, "source": [ + "\n", "## DANDI Upload\n", "\n", "`element-interface.dandi` includes the `upload_to_dandi` utility to support direct uploads. For more information, see [DANDI documentation](https://www.dandiarchive.org/handbook/10_using_dandi/).\n", diff --git a/notebooks/py_scripts/09-NWB-export.py b/notebooks/py_scripts/09-NWB-export.py index 9a7473a0..f2c52442 100644 --- a/notebooks/py_scripts/09-NWB-export.py +++ b/notebooks/py_scripts/09-NWB-export.py @@ -38,7 +38,7 @@ # ## Export to NWB # -# Each of the following elements has tools for interacting with NWB files: `element-lab`, `element-session`, `element-array-ephys`, and `element-interface`. We'll use the following keys for testing these functions: +# Several Elements have tools for generating NWB materials. We'll use the following keys for testing these functions: lab_key={"lab": "LabA"} protocol_key={"protocol": "ProtA"} @@ -46,7 +46,14 @@ session_key={"subject": "subject5", "session_datetime": "2018-07-03 20:32:28"} -# +# To skip ahead, use any of the following links: +# - [Element Lab](#Element-Lab) +# - [Element Animal](#Element-Animal) +# - [Element Session](#Element-Session) +# - [Element Array Electrophysiology](#Element-Array-Electrophysiology) - If you are using all upstream Elements, you can skip to this +# - [Element Interface for DANDI Upload](#DANDI-Upload) + +# # ### Element Lab # # Because an NWB file must include session information, `element_lab_to_nwb_dict` can only help package information from the Lab schema into `dict` format. This would be helpful for a team using Element Lab, but not others. @@ -56,12 +63,14 @@ element_lab_to_nwb_dict(lab_key=lab_key, protocol_key=protocol_key, project_key=project_key) +# # ### Element Animal # # `subject_to_nwb` can use a session key to retrieve subject information, and will return an nwb file with a number of sections specified. When packaging into an NWB file, `pynwb` will display a warning regarding timezone information - datetime fields are assumed to be in local time, and will be converted to UTC. subject_to_nwb(session_key=session_key) +# # ### Element Session # # `session_to_nwb` pulls the same information as above, while also including information about session experimenter and session time. The export process provides the same warning about timezone conversion to UTC. @@ -69,7 +78,7 @@ session_to_nwb(session_key=session_key) -# +# # ### Element Array Electrophysiology # # `ecephys_session_to_nwb` provides a full export mechanism, returning an NWB file with raw data, spikes, and LFP. Optional arguments determine which pieces are exported. For demonstration purposes, we recommend limiting `end_frame`. @@ -97,6 +106,7 @@ write_nwb(nwbfile, f'./temp_nwb/{time.strftime("_test_%Y%m%d-%H%M%S.nwb")}') # - +# # ## DANDI Upload # # `element-interface.dandi` includes the `upload_to_dandi` utility to support direct uploads. For more information, see [DANDI documentation](https://www.dandiarchive.org/handbook/10_using_dandi/). From 287de730129b40dd3e6e5611818398a5fadc375a Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 6 Jun 2022 11:56:31 -0500 Subject: [PATCH 50/59] WIP: attempt fix link bug --- notebooks/09-NWB-export.ipynb | 15 +++++---------- notebooks/py_scripts/09-NWB-export.py | 15 +++++---------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/notebooks/09-NWB-export.ipynb b/notebooks/09-NWB-export.ipynb index 2cfd96c7..dd76f302 100644 --- a/notebooks/09-NWB-export.ipynb +++ b/notebooks/09-NWB-export.ipynb @@ -108,8 +108,7 @@ "id": "fc2d028a", "metadata": {}, "source": [ - "\n", - "### Element Lab\n", + "### Element Lab \n", "\n", "Because an NWB file must include session information, `element_lab_to_nwb_dict` can only help package information from the Lab schema into `dict` format. This would be helpful for a team using Element Lab, but not others. " ] @@ -183,8 +182,7 @@ "id": "66af900a", "metadata": {}, "source": [ - "\n", - "### Element Animal\n", + "### Element Animal \n", "\n", "`subject_to_nwb` can use a session key to retrieve subject information, and will return an nwb file with a number of sections specified. When packaging into an NWB file, `pynwb` will display a warning regarding timezone information - datetime fields are assumed to be in local time, and will be converted to UTC." ] @@ -228,8 +226,7 @@ "id": "b1d2c7e3", "metadata": {}, "source": [ - "\n", - "### Element Session\n", + "### Element Session \n", "\n", "`session_to_nwb` pulls the same information as above, while also including information about session experimenter and session time. The export process provides the same warning about timezone conversion to UTC.\n" ] @@ -283,8 +280,7 @@ "id": "bb5fba81", "metadata": {}, "source": [ - "\n", - "### Element Array Electrophysiology\n", + "### Element Array Electrophysiology \n", "\n", "`ecephys_session_to_nwb` provides a full export mechanism, returning an NWB file with raw data, spikes, and LFP. Optional arguments determine which pieces are exported. For demonstration purposes, we recommend limiting `end_frame`.\n" ] @@ -445,8 +441,7 @@ "id": "f717baf8", "metadata": {}, "source": [ - "\n", - "## DANDI Upload\n", + "## DANDI Upload \n", "\n", "`element-interface.dandi` includes the `upload_to_dandi` utility to support direct uploads. For more information, see [DANDI documentation](https://www.dandiarchive.org/handbook/10_using_dandi/).\n", "\n", diff --git a/notebooks/py_scripts/09-NWB-export.py b/notebooks/py_scripts/09-NWB-export.py index f2c52442..46d8476e 100644 --- a/notebooks/py_scripts/09-NWB-export.py +++ b/notebooks/py_scripts/09-NWB-export.py @@ -53,8 +53,7 @@ # - [Element Array Electrophysiology](#Element-Array-Electrophysiology) - If you are using all upstream Elements, you can skip to this # - [Element Interface for DANDI Upload](#DANDI-Upload) -# -# ### Element Lab +# ### Element Lab # # Because an NWB file must include session information, `element_lab_to_nwb_dict` can only help package information from the Lab schema into `dict` format. This would be helpful for a team using Element Lab, but not others. @@ -63,23 +62,20 @@ element_lab_to_nwb_dict(lab_key=lab_key, protocol_key=protocol_key, project_key=project_key) -# -# ### Element Animal +# ### Element Animal # # `subject_to_nwb` can use a session key to retrieve subject information, and will return an nwb file with a number of sections specified. When packaging into an NWB file, `pynwb` will display a warning regarding timezone information - datetime fields are assumed to be in local time, and will be converted to UTC. subject_to_nwb(session_key=session_key) -# -# ### Element Session +# ### Element Session # # `session_to_nwb` pulls the same information as above, while also including information about session experimenter and session time. The export process provides the same warning about timezone conversion to UTC. # session_to_nwb(session_key=session_key) -# -# ### Element Array Electrophysiology +# ### Element Array Electrophysiology # # `ecephys_session_to_nwb` provides a full export mechanism, returning an NWB file with raw data, spikes, and LFP. Optional arguments determine which pieces are exported. For demonstration purposes, we recommend limiting `end_frame`. # @@ -106,8 +102,7 @@ write_nwb(nwbfile, f'./temp_nwb/{time.strftime("_test_%Y%m%d-%H%M%S.nwb")}') # - -# -# ## DANDI Upload +# ## DANDI Upload # # `element-interface.dandi` includes the `upload_to_dandi` utility to support direct uploads. For more information, see [DANDI documentation](https://www.dandiarchive.org/handbook/10_using_dandi/). # From 5a5fb89fb5b0e10e349bdb04ca013e5899a13d2d Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 6 Jun 2022 11:57:59 -0500 Subject: [PATCH 51/59] WIP attempt fix 2 --- notebooks/09-NWB-export.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebooks/09-NWB-export.ipynb b/notebooks/09-NWB-export.ipynb index dd76f302..f0c207c5 100644 --- a/notebooks/09-NWB-export.ipynb +++ b/notebooks/09-NWB-export.ipynb @@ -108,7 +108,7 @@ "id": "fc2d028a", "metadata": {}, "source": [ - "### Element Lab \n", + "### Element Lab\n", "\n", "Because an NWB file must include session information, `element_lab_to_nwb_dict` can only help package information from the Lab schema into `dict` format. This would be helpful for a team using Element Lab, but not others. " ] From 500e837d3d6a16804701c23f3c566593ac5c3b36 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 6 Jun 2022 12:50:57 -0500 Subject: [PATCH 52/59] De-emphasize upstream NWB functions --- notebooks/09-NWB-export.ipynb | 189 +++----------------------- notebooks/py_scripts/09-NWB-export.py | 40 ++---- 2 files changed, 33 insertions(+), 196 deletions(-) diff --git a/notebooks/09-NWB-export.ipynb b/notebooks/09-NWB-export.ipynb index f0c207c5..099246f6 100644 --- a/notebooks/09-NWB-export.ipynb +++ b/notebooks/09-NWB-export.ipynb @@ -73,7 +73,7 @@ "source": [ "## Export to NWB\n", "\n", - "Several Elements have tools for generating NWB materials. We'll use the following keys for testing these functions:" + "We'll use the following keys to demonstrate export functions." ] }, { @@ -90,188 +90,35 @@ " \"session_datetime\": \"2018-07-03 20:32:28\"}" ] }, - { - "cell_type": "markdown", - "id": "764f5a00", - "metadata": {}, - "source": [ - "To skip ahead, use any of the following links:\n", - "- [Element Lab](#Element-Lab)\n", - "- [Element Animal](#Element-Animal)\n", - "- [Element Session](#Element-Session)\n", - "- [Element Array Electrophysiology](#Element-Array-Electrophysiology) - If you are using all upstream Elements, you can skip to this\n", - "- [Element Interface for DANDI Upload](#DANDI-Upload)" - ] - }, { "cell_type": "markdown", "id": "fc2d028a", "metadata": {}, "source": [ - "### Element Lab\n", + "### Upstream Elements\n", "\n", - "Because an NWB file must include session information, `element_lab_to_nwb_dict` can only help package information from the Lab schema into `dict` format. This would be helpful for a team using Element Lab, but not others. " - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "a6a3306b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Help on function element_lab_to_nwb_dict in module element_lab.export.nwb:\n", - "\n", - "element_lab_to_nwb_dict(lab_key=None, project_key=None, protocol_key=None)\n", - " Generate a dictionary object containing all relevant lab information used\n", - " when generating an NWB file at the session level.\n", - " All parameters optional, but should only specify one of respective type\n", - " Use: mynwbfile = pynwb.NWBFile(identifier=\"your identifier\",\n", - " session_description=\"your description\",\n", - " session_start_time=session_datetime,\n", - " **element_lab_to_nwb_dict(\n", - " lab_key=key1,\n", - " project_key=key2,\n", - " protocol_key=key3))\n", - " \n", - " :param lab_key: Key specifying one entry in element_lab.lab.Lab\n", - " :param project_key: Key specifying one entry in element_lab.lab.Project\n", - " :param protocol_key: Key specifying one entry in element_lab.lab.Protocol\n", - " :return: dictionary with NWB parameters\n", - "\n" - ] - } - ], - "source": [ - "help(element_lab_to_nwb_dict)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "61ba0714", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'institution': 'Example Uni',\n", - " 'lab': 'The Example Lab',\n", - " 'experiment_description': 'Example project to populate element-lab',\n", - " 'keywords': ['Example', 'Study'],\n", - " 'related_publications': ['arXiv:1807.11104', 'arXiv:1807.11104v1'],\n", - " 'protocol': 'ProtA',\n", - " 'notes': 'Protocol for managing data ingestion'}" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "element_lab_to_nwb_dict(lab_key=lab_key, protocol_key=protocol_key, \n", - " project_key=project_key)" - ] - }, - { - "cell_type": "markdown", - "id": "66af900a", - "metadata": {}, - "source": [ - "### Element Animal \n", + "If you plan to use all upstream Elements, you can skip to the following section. To combine with other schemas, the following functions may be helpful.\n", "\n", - "`subject_to_nwb` can use a session key to retrieve subject information, and will return an nwb file with a number of sections specified. When packaging into an NWB file, `pynwb` will display a warning regarding timezone information - datetime fields are assumed to be in local time, and will be converted to UTC." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "71433b2e", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/cb/miniconda3/envs/ele/lib/python3.8/site-packages/pynwb/file.py:1037: UserWarning: Date is missing timezone information. Updating to local timezone.\n", - " warn(\"Date is missing timezone information. Updating to local timezone.\")\n" - ] - }, - { - "data": { - "text/plain": [ - "subject pynwb.file.Subject at 0x140613566479040\n", - "Fields:\n", - " date_of_birth: 2020-01-01 00:00:00-06:00\n", - " description: {\"subject\": \"subject5\", \"sex\": \"F\", \"subject_birth_date\": \"2020-01-01\", \"subject_description\": \"rich\", \"line\": null, \"strain\": null, \"source\": null}\n", - " sex: F\n", - " subject_id: subject5" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "subject_to_nwb(session_key=session_key)" - ] - }, - { - "cell_type": "markdown", - "id": "b1d2c7e3", - "metadata": {}, - "source": [ - "### Element Session \n", + "- **Element Lab** `element_lab_to_nwb_dict` exports NWB-relevant items to `dict` format.\n", + "- **Element Animal** `subject_to_nwb` returns an NWB file with subject information.\n", + "- **Element Session** `session_to_nwb` returns an NWB files with subject and session information.\n", "\n", - "`session_to_nwb` pulls the same information as above, while also including information about session experimenter and session time. The export process provides the same warning about timezone conversion to UTC.\n" + "Note: `pynwb` will display a warning regarding timezone information - datetime fields are assumed to be in local time, and will be converted to UTC.\n" ] }, { "cell_type": "code", - "execution_count": 21, - "id": "cc1b52b7", + "execution_count": null, + "id": "61ba0714", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/cb/miniconda3/envs/ele/lib/python3.8/site-packages/pynwb/file.py:1037: UserWarning: Date is missing timezone information. Updating to local timezone.\n", - " warn(\"Date is missing timezone information. Updating to local timezone.\")\n" - ] - }, - { - "data": { - "text/plain": [ - "root pynwb.file.NWBFile at 0x140613434487232\n", - "Fields:\n", - " experimenter: ['User1']\n", - " file_create_date: [datetime.datetime(2022, 5, 31, 13, 58, 25, 578725, tzinfo=tzlocal())]\n", - " identifier: 9d2131bb-5747-4bf1-95f0-4b24b54b1968\n", - " session_description: Successful data collection\n", - " session_id: subject5_2018-07-03T20:32:28\n", - " session_start_time: 2018-07-04 01:32:28+00:00\n", - " subject: subject pynwb.file.Subject at 0x140613566479712\n", - "Fields:\n", - " date_of_birth: 2020-01-01 00:00:00-06:00\n", - " description: {\"subject\": \"subject5\", \"sex\": \"F\", \"subject_birth_date\": \"2020-01-01\", \"subject_description\": \"rich\", \"line\": null, \"strain\": null, \"source\": null}\n", - " sex: F\n", - " subject_id: subject5\n", - "\n", - " timestamps_reference_time: 2018-07-04 01:32:28+00:00" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ + "print('Lab:\\n')\n", + "element_lab_to_nwb_dict(lab_key=lab_key, protocol_key=protocol_key, \n", + " project_key=project_key)\n", + "print('\\nAnimal:\\n')\n", + "subject_to_nwb(session_key=session_key)\n", + "print('\\nSession:\\n')\n", "session_to_nwb(session_key=session_key)" ] }, @@ -280,7 +127,7 @@ "id": "bb5fba81", "metadata": {}, "source": [ - "### Element Array Electrophysiology \n", + "### Element Array Electrophysiology\n", "\n", "`ecephys_session_to_nwb` provides a full export mechanism, returning an NWB file with raw data, spikes, and LFP. Optional arguments determine which pieces are exported. For demonstration purposes, we recommend limiting `end_frame`.\n" ] @@ -441,7 +288,7 @@ "id": "f717baf8", "metadata": {}, "source": [ - "## DANDI Upload \n", + "## DANDI Upload\n", "\n", "`element-interface.dandi` includes the `upload_to_dandi` utility to support direct uploads. For more information, see [DANDI documentation](https://www.dandiarchive.org/handbook/10_using_dandi/).\n", "\n", diff --git a/notebooks/py_scripts/09-NWB-export.py b/notebooks/py_scripts/09-NWB-export.py index 46d8476e..cb8604b9 100644 --- a/notebooks/py_scripts/09-NWB-export.py +++ b/notebooks/py_scripts/09-NWB-export.py @@ -38,7 +38,7 @@ # ## Export to NWB # -# Several Elements have tools for generating NWB materials. We'll use the following keys for testing these functions: +# We'll use the following keys to demonstrate export functions. lab_key={"lab": "LabA"} protocol_key={"protocol": "ProtA"} @@ -46,36 +46,26 @@ session_key={"subject": "subject5", "session_datetime": "2018-07-03 20:32:28"} -# To skip ahead, use any of the following links: -# - [Element Lab](#Element-Lab) -# - [Element Animal](#Element-Animal) -# - [Element Session](#Element-Session) -# - [Element Array Electrophysiology](#Element-Array-Electrophysiology) - If you are using all upstream Elements, you can skip to this -# - [Element Interface for DANDI Upload](#DANDI-Upload) - -# ### Element Lab +# ### Upstream Elements # -# Because an NWB file must include session information, `element_lab_to_nwb_dict` can only help package information from the Lab schema into `dict` format. This would be helpful for a team using Element Lab, but not others. - -help(element_lab_to_nwb_dict) - -element_lab_to_nwb_dict(lab_key=lab_key, protocol_key=protocol_key, - project_key=project_key) - -# ### Element Animal +# If you plan to use all upstream Elements, you can skip to the following section. To combine with other schemas, the following functions may be helpful. # -# `subject_to_nwb` can use a session key to retrieve subject information, and will return an nwb file with a number of sections specified. When packaging into an NWB file, `pynwb` will display a warning regarding timezone information - datetime fields are assumed to be in local time, and will be converted to UTC. - -subject_to_nwb(session_key=session_key) - -# ### Element Session +# - **Element Lab** `element_lab_to_nwb_dict` exports NWB-relevant items to `dict` format. +# - **Element Animal** `subject_to_nwb` returns an NWB file with subject information. +# - **Element Session** `session_to_nwb` returns an NWB files with subject and session information. # -# `session_to_nwb` pulls the same information as above, while also including information about session experimenter and session time. The export process provides the same warning about timezone conversion to UTC. +# Note: `pynwb` will display a warning regarding timezone information - datetime fields are assumed to be in local time, and will be converted to UTC. # +print('Lab:\n') +element_lab_to_nwb_dict(lab_key=lab_key, protocol_key=protocol_key, + project_key=project_key) +print('\nAnimal:\n') +subject_to_nwb(session_key=session_key) +print('\nSession:\n') session_to_nwb(session_key=session_key) -# ### Element Array Electrophysiology +# ### Element Array Electrophysiology # # `ecephys_session_to_nwb` provides a full export mechanism, returning an NWB file with raw data, spikes, and LFP. Optional arguments determine which pieces are exported. For demonstration purposes, we recommend limiting `end_frame`. # @@ -102,7 +92,7 @@ write_nwb(nwbfile, f'./temp_nwb/{time.strftime("_test_%Y%m%d-%H%M%S.nwb")}') # - -# ## DANDI Upload +# ## DANDI Upload # # `element-interface.dandi` includes the `upload_to_dandi` utility to support direct uploads. For more information, see [DANDI documentation](https://www.dandiarchive.org/handbook/10_using_dandi/). # From e72ac90ab8571175e327cd3e544ec31fa36d8814 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 6 Jun 2022 14:39:25 -0500 Subject: [PATCH 53/59] Revise verbosity on test_export. Revise pipeline build in tests/__init__ --- tests/__init__.py | 13 +++----- tests/test_export.py | 75 +++++++++++++++++++++++++++----------------- 2 files changed, 50 insertions(+), 38 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index f9a53f01..0b1ded47 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -17,7 +17,7 @@ # ------------------- SOME CONSTANTS ------------------- _tear_down = False -verbose = False +verbose = True pathlib.Path('./tests/user_data').mkdir(exist_ok=True) pathlib.Path('./tests/user_data/lab').mkdir(exist_ok=True) @@ -487,21 +487,16 @@ def clustering(clustering_tasks, pipeline): """Populate ephys.Clustering""" ephys = pipeline['ephys'] - if pipeline['ephys_mode'] == "no-curation": - clustering_table = ephys.CuratedClustering - else: - clustering_table = ephys.Clustering - - clustering_table.populate() + ephys.Clustering.populate() yield if _tear_down: if verbose: - clustering_table.delete() + ephys.Clustering.delete() else: with QuietStdOut(): - clustering_table.delete() + ephys.Clustering.delete() @pytest.fixture diff --git a/tests/test_export.py b/tests/test_export.py index 891fa345..af393cc5 100644 --- a/tests/test_export.py +++ b/tests/test_export.py @@ -4,7 +4,7 @@ from element_interface.utils import find_root_directory, find_full_path -from . import (dj_config, pipeline, test_data, +from . import (dj_config, verbose, QuietStdOut, pipeline, test_data, lab_csv, lab_project_csv, lab_user_csv, lab_publications_csv, lab_keywords_csv, lab_protocol_csv, lab_project_users_csv, ingest_lab, @@ -43,15 +43,22 @@ def test_session_to_nwb(pipeline, ingest_lab, ingest_subjects, ingest_sessions): - nwbfile = session_to_nwb( - session_key={ + session_kwargs = { + 'session_key':{ "subject": "subject5", "session_datetime": datetime.datetime(2018, 7, 3, 20, 32, 28), }, - lab_key={"lab": "LabA"}, - protocol_key={"protocol": "ProtA"}, - project_key={"project": "ProjA"}, - ) + 'lab_key': {"lab": "LabA"}, + 'protocol_key':{"protocol": "ProtA"}, + 'project_key':{"project": "ProjA"}, + } + + if verbose: + nwbfile = session_to_nwb(**session_kwargs) + else: + with QuietStdOut(): + nwbfile = session_to_nwb(**session_kwargs) + assert nwbfile.session_id == "subject5_2018-07-03T20:32:28" assert nwbfile.session_description == "Successful data collection" # when saved in NWB, converts local to UTC @@ -78,19 +85,22 @@ def test_write_to_nwb(pipeline, ingest_lab, ingest_subjects, ingest_sessions, session_key = dict(subject='subject5', session_datetime='2018-07-03 20:32:28') - ephys.LFP.populate(session_key, display_progress=True) - ephys.CuratedClustering.populate(session_key, display_progress=True) - ephys.WaveformSet.populate(session_key, display_progress=True) + ephys.LFP.populate(session_key, display_progress=verbose) + ephys.CuratedClustering.populate(session_key, display_progress=verbose) + ephys.WaveformSet.populate(session_key, display_progress=verbose) - nwbfile = ecephys_session_to_nwb(session_key=session_key, - raw=True, - spikes=True, - lfp="dj", - end_frame=None, - lab_key=None, - project_key=None, - protocol_key=None, - nwbfile_kwargs=None) + ecephys_kwargs = { + 'session_key':session_key, + 'raw':True, + 'spikes':True, + 'lfp':"dj", + } + + if verbose: + nwbfile = ecephys_session_to_nwb(**ecephys_kwargs) + else: + with QuietStdOut(): + nwbfile = ecephys_session_to_nwb(**ecephys_kwargs) root_dirs = pipeline["get_ephys_root_data_dir"]() root_dir = find_root_directory( @@ -104,7 +114,6 @@ def test_write_to_nwb(pipeline, ingest_lab, ingest_subjects, ingest_sessions, write_nwb(nwbfile, root_dir / time.strftime("_test_%Y%m%d-%H%M%S.nwb")) - def test_convert_to_nwb(pipeline, ingest_lab, ingest_subjects, ingest_sessions, ephys_insertionlocation, kilosort_paramset, ephys_recordings, clustering_tasks, clustering, curations): @@ -112,16 +121,24 @@ def test_convert_to_nwb(pipeline, ingest_lab, ingest_subjects, ingest_sessions, session_key = dict(subject='subject5', session_datetime='2018-07-03 20:32:28') - ephys.Clustering.populate(session_key,display_progress=True) - ephys.CuratedClustering.populate(session_key, display_progress=True) - ephys.WaveformSet.populate(session_key, display_progress=True) - nwbfile = ecephys_session_to_nwb(session_key=session_key, - end_frame=1000, - spikes=True, - lab_key=dict(lab='LabA'), - protocol_key=dict(protocol='ProtA'), - project_key=dict(project='ProjA')) + ephys.CuratedClustering.populate(session_key, display_progress=verbose) + ephys.WaveformSet.populate(session_key, display_progress=verbose) + ecephys_kwargs = { + 'session_key':session_key, + 'end_frame':1000, + 'spikes':True, + 'lab_key': {"lab": "LabA"}, + 'protocol_key':{"protocol": "ProtA"}, + 'project_key':{"project": "ProjA"}, + } + + if verbose: + nwbfile = ecephys_session_to_nwb(**ecephys_kwargs) + else: + with QuietStdOut(): + nwbfile = ecephys_session_to_nwb(**ecephys_kwargs) + for x in ("262716621", "714000838"): assert x in nwbfile.devices From 5f1ef690921ef634e519a5d746d26ba6bf32052b Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 6 Jun 2022 17:18:31 -0500 Subject: [PATCH 54/59] WIP `plot_raster` -> `plot` --- notebooks/07-downstream-analysis.ipynb | 18 ++++++++++++------ notebooks/py_scripts/07-downstream-analysis.py | 6 +++--- workflow_array_ephys/analysis.py | 5 ++--- workflow_array_ephys/pipeline.py | 1 - 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/notebooks/07-downstream-analysis.ipynb b/notebooks/07-downstream-analysis.ipynb index ac1046be..9747843d 100644 --- a/notebooks/07-downstream-analysis.ipynb +++ b/notebooks/07-downstream-analysis.ipynb @@ -1610,7 +1610,7 @@ "source": [ "## Visualize\n", "\n", - "We can visualize the results with the `plot_raster` function." + "We can visualize the results with the `plot` function." ] }, { @@ -1636,7 +1636,7 @@ ], "source": [ "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'ctrl_center_button'}\n", - "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);" + "analysis.SpikesAlignment().plot(alignment_condition, unit=2);" ] }, { @@ -1644,6 +1644,7 @@ "execution_count": 13, "id": "e144df4c-87c1-4646-9d4b-b0009216bca1", "metadata": { + "lines_to_next_cell": 0, "title": "a set of trials of interest to perform the analysis on - `stim` trials" }, "outputs": [ @@ -1662,26 +1663,31 @@ ], "source": [ "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", - "analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2);" + "analysis.SpikesAlignment().plot(alignment_condition, unit=2);" ] }, { "cell_type": "code", "execution_count": null, "id": "93bf7940-a3bb-4ab9-abd3-95f6a2282775", - "metadata": {}, + "metadata": { + "lines_to_next_cell": 2 + }, "outputs": [], "source": [] } ], "metadata": { + "interpreter": { + "hash": "61456c693db5d9aa6731701ec9a9b08ab88a172bee0780139a3679beb166da16" + }, "jupytext": { "formats": "ipynb,py" }, "kernelspec": { - "display_name": "venv-nwb", + "display_name": "Python 3.8.11 ('ele')", "language": "python", - "name": "venv-nwb" + "name": "python3" }, "language_info": { "codemirror_mode": { diff --git a/notebooks/py_scripts/07-downstream-analysis.py b/notebooks/py_scripts/07-downstream-analysis.py index d9e0375e..d2a3f853 100644 --- a/notebooks/py_scripts/07-downstream-analysis.py +++ b/notebooks/py_scripts/07-downstream-analysis.py @@ -151,15 +151,15 @@ # + a set of trials of interest to perform the analysis on - `stim` trials [markdown] # ## Visualize # -# We can visualize the results with the `plot_raster` function. +# We can visualize the results with the `plot` function. # + a set of trials of interest to perform the analysis on - `stim` trials alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'ctrl_center_button'} -analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2); +analysis.SpikesAlignment().plot(alignment_condition, unit=2); # + a set of trials of interest to perform the analysis on - `stim` trials alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'} -analysis.SpikesAlignment().plot_raster(alignment_condition, unit=2); +analysis.SpikesAlignment().plot(alignment_condition, unit=2); # - diff --git a/workflow_array_ephys/analysis.py b/workflow_array_ephys/analysis.py index af8bede3..62d3a487 100644 --- a/workflow_array_ephys/analysis.py +++ b/workflow_array_ephys/analysis.py @@ -1,13 +1,12 @@ import datajoint as dj import numpy as np -from .pipeline import db_prefix, ephys, trial, event +from .pipeline import db_prefix, ephys, trial schema = dj.schema(db_prefix + 'analysis') - @schema class SpikesAlignmentCondition(dj.Manual): definition = """ @@ -94,7 +93,7 @@ def make(self, key): self.AlignedTrialSpikes.insert(aligned_trial_spikes) self.UnitPSTH.insert(list(units_spike_raster.values())) - def plot_raster(self, key, unit, axs=None): + def plot(self, key, unit, axs=None): import matplotlib.pyplot as plt from .plotting import plot_psth diff --git a/workflow_array_ephys/pipeline.py b/workflow_array_ephys/pipeline.py index 5701bf22..d2c9f8a6 100644 --- a/workflow_array_ephys/pipeline.py +++ b/workflow_array_ephys/pipeline.py @@ -88,7 +88,6 @@ class SkullReference(dj.Lookup): # Activate "electrode-localization" schema ------------------------------------ ProbeInsertion = ephys.ProbeInsertion -Electrode = probe.ProbeType.Electrode electrode_localization.activate(db_prefix + 'electrode_localization', db_prefix + 'ccf', From 08cc600532261f49137c71a496bd4348d4d75dec Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Tue, 7 Jun 2022 16:05:44 -0500 Subject: [PATCH 55/59] revise PSTH plot --- notebooks/07-downstream-analysis.ipynb | 47 +++++++++++++------ .../py_scripts/07-downstream-analysis.py | 25 +++++++--- workflow_array_ephys/analysis.py | 4 +- workflow_array_ephys/plotting/plot_psth.py | 8 ++-- 4 files changed, 57 insertions(+), 27 deletions(-) diff --git a/notebooks/07-downstream-analysis.ipynb b/notebooks/07-downstream-analysis.ipynb index 9747843d..17956efd 100644 --- a/notebooks/07-downstream-analysis.ipynb +++ b/notebooks/07-downstream-analysis.ipynb @@ -1030,9 +1030,10 @@ "alignment_key = (event.AlignmentEvent & 'alignment_name = \"center_button\"'\n", " ).fetch1('KEY')\n", "alignment_condition = {**clustering_key, **alignment_key, \n", - " 'trial_condition': 'ctrl_center_button'}\n", + " 'trial_condition': 'ctrl_center_button',\n", + " 'bin_size':.2}\n", "analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True)\n", - "\n", + "alignment_condition.pop('bin_size')\n", "analysis.SpikesAlignmentCondition.Trial.insert(\n", " (analysis.SpikesAlignmentCondition * ctrl_trials & alignment_condition).proj(),\n", " skip_duplicates=True)" @@ -1240,7 +1241,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 8, "id": "1ea69400-75a0-4421-9310-8eec465b4775", "metadata": { "title": "a set of trials of interest to perform the analysis on - `stim` trials" @@ -1248,8 +1249,11 @@ "outputs": [], "source": [ "stim_trials = trial.Trial & clustering_key & 'trial_type = \"stim\"'\n", - "alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'}\n", + "alignment_condition = {**clustering_key, **alignment_key, \n", + " 'trial_condition': 'stim_center_button',\n", + " 'bin_size':.2}\n", "analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True)\n", + "alignment_condition.pop('bin_size')\n", "analysis.SpikesAlignmentCondition.Trial.insert(\n", " (analysis.SpikesAlignmentCondition * stim_trials & alignment_condition).proj(),\n", " skip_duplicates=True)" @@ -1583,7 +1587,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 11, "id": "623998dd-0c08-4c75-b5ad-2766f11bda6b", "metadata": { "title": "a set of trials of interest to perform the analysis on - `stim` trials" @@ -1593,7 +1597,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "SpikesAlignment: 100%|██████████████████████████████████████████████████████████████████████| 2/2 [00:23<00:00, 11.78s/it]\n" + "SpikesAlignment: 100%|██████████| 1/1 [00:11<00:00, 11.26s/it]\n" ] } ], @@ -1610,12 +1614,27 @@ "source": [ "## Visualize\n", "\n", - "We can visualize the results with the `plot` function." + "We can visualize the results with the `plot` function with our keys." ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 3, + "id": "b1f8bd26", + "metadata": {}, + "outputs": [], + "source": [ + "clustering_key = (ephys.CuratedClustering \n", + " & {'subject': 'subject6', 'session_datetime': '2021-01-15 11:16:38',\n", + " 'insertion_number': 0}\n", + " ).fetch1('KEY')\n", + "alignment_key = (event.AlignmentEvent & 'alignment_name = \"center_button\"'\n", + " ).fetch1('KEY')" + ] + }, + { + "cell_type": "code", + "execution_count": 4, "id": "03132026-55e3-4522-9f58-ce86b94c7842", "metadata": { "title": "a set of trials of interest to perform the analysis on - `stim` trials" @@ -1623,7 +1642,7 @@ "outputs": [ { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ "
" ] @@ -1641,7 +1660,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "id": "e144df4c-87c1-4646-9d4b-b0009216bca1", "metadata": { "lines_to_next_cell": 0, @@ -1650,7 +1669,7 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABPwAAAHwCAYAAAAyx3EqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8/fFQqAAAACXBIWXMAAAsTAAALEwEAmpwYAABgO0lEQVR4nO3debwkVXn/8e9Xhm0QNyAGZBAXJAEVNCiucVRUQJH8ghEXVOKCxqAhagygKApqYjTBJILgkhFQAUEJIKhBve4KaAYUECSIAoIssjrI4jy/P05dpqdv7911q+rU5/16zWv6dlefes5T1VXVT5+qckQIAAAAAAAAQB7uU3UAAAAAAAAAAGaHgh8AAAAAAACQEQp+AAAAAAAAQEYo+AEAAAAAAAAZoeAHAAAAAAAAZISCHwAAAAAAAJCRkQt+tre3/b+2b7X95FkGYfsK27v0ee3pti+Z5fyq0pZ+zprt220/fBHnd6jt4xdxfmfZftVizW9Ug9bXEua1VbGc11mM+XXNe4XtwxdxfhfaXl5S25vZ/pntDctoH/mx/WHbfzPheyv73PaI5WO2D6k6jlmwva/t71QdB6Yz6Nil6cu48/jA9sG2P1F1TMiH7a1th+0lVcdShXH2Z7bnbL+27JhG0RmL7Zfb/uqAaRv1vZftXFKn9a0uFvN7vO23277J9ldsLx31feOM8Hu1pMslPSAivl/MdGvbV4wX6ngi4tsRse207RQ7jkd2/L3c9tyI721sP4vnlto+0vYNtm+x/a2O11bY3nfEtufKKlQMEhH3jYjLF3u+oxi3WNSrmBgRu0XEp0uIbeR1fAbzWrDeDZl+rWJiRPyqWM5/KCfC2Rh3R9dr/YiI7SNibubBJQdKWhERdxTz/5Dtn9u+rSgEvrIrvh1t/8j2quL/HTte+wfbPy3e+wvb/9D13sNs/8T2PbYPHSdI2y+2/b1ivnNjvvfRxY7uBtvR4/U5278vvmzfPs4B5bC4bB9j+xLbq0fdbna8d3/b59m+0/aKHq/33U6P0PY3bF/v9IPc+bb37Hr9TcUyvLWI4WkdL39I0sG21xtxXvvOx1/V57ZXsSQi3hARhy1mHE3RucxGnP7ZxfZiVbFuPXTE95V+rFS1cfYBszp2KfaXW4847fq2P1V81q+1/ZYx5jPy8WCniHh/RDTqC6AbWFAa9/PVa/84S8Ux5lVlzmNS3ceYs17exbH8oSNOe+/nqte+q5cc9mcR8ZmIeO78393fE2b1vXe+7TGmHbo97bVuN2k7173+10Wv78Djfq8a0v7I20jP8Dty5/f4Xp/xcfattl9VfB+71fZVtj/Yud2KiA9K2lLSn0h6bt+GuoxT8HuQpIsjYvUY70E9HKO0/P60+P/vqw0HwKzZXl/SqyR17kx/J2kPSfcvXvuI7acU068n6b+L6R8o6dOS/ruj8GNJryxe21XS/rZf0tH2ZZLeLulLE4T7W0lHSPqnCd57t6STJL1mwDT7F1+27zvmAeWwuM6X9EZJPx6jzXm/lnS4pE/1eX2a7fTfSdo8Iu4naT9Jx9veXJJs76zUnxcprQeflPRFF6PyIuIaST+T9MJxO1SFJn1Br4Nx82V7U0lfkHSI0np4nqQTSwhtnJhsm0vQjOZQSdtIeqikZ0p6u+1dK40I6IFteW+uwYh5tBf728otlXSApE0l7Szp2ZLe1jlBRPxO0i8kbTJqo+Ms0CWSBhb7bP+j7audRoRcYvvZxfNrjXLp86vQE2xf5DRM8b9sb9BrWttb2D6lGM3wC9tv7nhtHacht/9XxPAj28u8ZqTE+U4jPvYeo9+N7qftP1H6IrdfRFwfEX+IiB9N0/8ihic6jRS51fZvbP9rx2tPcholc7PTaJPlHa/ta/tyrxk19PLi+Ufa/qbTyJYbbJ/Y8Z57fxWyfX/bxxZ5+aXtd85vmIq2v+M0qummov3dRujLw4p532b7f5Q+ZJ2vf97pl/JbbH/L9vbF8/tJernSAfXttk8vnu+57IqD7oMl7V1Mf37x/L2/bhR9+K7tfyvyd7ntpxTPX2n7OncMG3b6Nf9Dtn9VLIePecLTOW2/osjpjbbf0fXaE21/v4jpGtv/6aIw1O/zZfsFtlcW7/me7ccWzx8naStJpxfTv91dv74WOTm8eN/ttk+3vYntzxTr3Lnu+IXO9p/Y/h/bv3X6TL54QD8fZ/vHxfI+UdIGHa890PYZxbK7qXi8ZfHa+yQ9XdJ/FjH956B5D1g/7v3lzenXrs/bPr6I5ye2H2X7oGJZX2m78xfS+9v+ZLEMri5yNH9wuLOkmyPi3u1IRLw7In4WEasj4oeSvi1p/pIMy5W260dExJ0R8e9KRb5nFe/9YET8OCLuiYhLlIqDT+1o+9MRcZak2/rlup+IODsiTlIqgo373ksi4pOSLhz3vdPGFREfjYivSfr9BG1/ISJOlXRj92uecjsdERdExD3zf0paV9Ky4u+tJV0YET+KiJB0rNI27o86mpiT9PzxerRw1ETxuT2s2IbdZvurTgUk2d6gWM9vLLYJ59p+cPFa3/W6a5t4o1Lx6WOSnlx8rm4upuve/77O9mXF5/I021t0vBa23+A0+vVm2x+17SF9Hbh/8cIRJff+kt2Rp78uPtM3FfN/gu0Lihj+c+Es/Z9O+52fuTjOmCBfh468QJO/VFpfPh8Rvy/ev0Oxjo6lyMlB7n3M03dbW7w+Z/t9tr8raZWkhxf5u7hYty63/fqO6Zc7/Rr+9mLbeY3tv7C9u+1Li/Xg4I7pxz6GcZ99wID+dx67bFKsh7faPkfSIzqme4rTcc+y4u8dipyMnXOlH3YOi4ibIuJiSR+XtO+4jdh+hO2vF5/XG5z2vQ/oM+2963rx9yu95ljiEC/c553kdBx3m9MlLnbqeO8VTqPLL7D9u2I9f7DTKVO32T7b9gM7ph90vNl3eyRp/rjl5mJZ9r1UkYd/9me5Xt7H9oFOx/c3Frl60NAFNoTtBxWfv18XfTi147Wex2rFa1fYfluxPG6xfaLTtnwjSWdJ2sJrRtNvMSh+r9kOvsb2ryR9fYTQX13EfI3te7/0esD3Lfc4xlSP5V3E+s5iXb2uWCfv3xXrq5yOr29w13HxuGz/qfrvu46yfabt30l6Zmf/PGRbOcb8X9exnl5k+/HzcRWflZudPo8v7HjPCqf945eK9/3Qdue26zlO+6dbnLaH7nhtXxcjndz7++m9y2zaOCZVfA4vKtq8uljX+63b0+7T+8XQb7kMqgP03Y72Wf9H2Vautb8dEvYjbJ/jtD/7b6/5jK+1TIvnrrC9i3t8B3b/71VPcTpGvKX4/yldsfbbrk/EPUYAe+F380H7gDnbr3Wfz/g4IuKoYvTrXRFxtaTPqOO7V4fVSt/hRm546D8Vo/skvXbANNtKulLSFsXfW0t6RPF4haTDO6ZdLumqjr+vkPRTpS8oD5L03fnpO6dVKlD+SNK7JK2ntEJeLul5xev/IOknRSyWtIOkTYrXQtIjB8R/hqQDR8hFo/qpNELnJ5L+TdINxeO9RlnuQ/LwfUmvKB7fV9KTiscPUfpCu3vRj+cUf28maSNJt0ratph2c0nbF48/J+kdxXs2kPS0jnnd2yelL6v/LWnjIveXSnpN8dq+SqN/XidpHUl/o/TF3SP05V8lrS/pz5UKGMd3vP7qYn7rK43+WdnxWvcyH7bsDu1su3huTsVnq+jDPZL+uujD4ZJ+JemjxfyfW8R332L6f5N0WrE+bSzpdEkfGHcdl7SdpNuL/q9f5OMeSbsUr/+ZpCcpbVy2VtoeHNBrGRV/P07SdUpFqHWUvoRcIWn9js/CLh3Tb120saQjJ5cpfSm6v6SLimW9SxHDsZL+q5h2I6XP5F8Xrz1OaV3frkc/15P0S6XRU+sqjXq6W2s+h5tI2kvpF5aNJX1e0qm9ltUo8+5eP7r7XqwPv5f0vI5+/ULps7Cu0rr8i473flHS0cV8/0jSOZJeX7z2t5K+NGA931DSNZJ2Lf7+e0ln9VhH3trjvZb0v5Le0OO14yUdOuF25LWS5iZ87yMlRY/n5yRdXyyH70paPuu4JH1H0r4Txn240mnXnc9NvZ0ult3vi8/RlyXdp3j+fkrbpPnP4puKZemO9/6lpB9P0JettfBz+3+SHlWsb3OS/ql47fVK26elRRx/Jul+I6zX+ypti95UfEY2LJ77TlcsK7Tmc/ysIo+PV9qe/Yekb3VMG0W+HqB0YHz9/OdiQF/31YD9ixZu0w5Vsa3vyNPHlPZvzy2W1alFfx+itL18Rlef57dTe0u6RdKDJslXj75cIOllffr5EUlHdT33U01w3KDBxzyjbGt/JWn7oh/rKhWlH6G0PXqG0heTxxfTLy/6/S6t2XZeL+mzRfvbS7pD0sOK6cc+humIq++xcFf/O49dTlAambyRpEdLulod67Ck9ykVQDZU+vzv36fNl0m6oM9rDyzm+eCO514k6ScTLLtHFn1fX+n47VtKPw51LtvO/dj8uj5/LPE0pf3th5Q+N937vN2VPkcfkPSDrnZ/IOnBWvO5+LHSvnWDIkfvHmNZ9dseba2ObdeUn/1Zrpd/V/R/yyL3R0v6XJ+4jpR05IjL80tKP5Y8sIhjflszyrHaOZK20Jrvgm/o6NtVXfPpG39Hzo9V+hws2DZ1tDM/7eeKaR9T5G1+PVqh4d+3+h5jFs+9Wuk48+FK24AvSDqua/qPF+vODpLulPSn436WeqxLvfZdtyh9qZ//DnRv/zTmcWmf+f6V0jbnCUrr6SOVRgGvW+TgYKXP67OUvmNs2xHbjZKeqLQd/oykE4rXNi2mfVHRzt8rreuv7dVXLfyecO8ymyaOHn09UNIZIy6PayQ9vWP7+fju2DqmPVQT7tMnWC6jfJccth3tXP9H2Vautb8dEPNcEfOjlT6bp3TkpVfe7o1FQ74DF38/SNJNkl5RxPLS4u9NOqbvuV2f4nM5vzyX9IpLw/cB3dN+Z8C8tpJ0s6StRozt1F79Uzor63OS1hupnRFm9KYiCT8YsgI8Umnl3qV7Oo22YX5Dx9+7S/q/HhuEnSX9qqvtg7Tmi/8lkvbsE99aG5opVopG9VNp4xlKH7L1lA5Gbtf0O61vSXqPpE27nv9HFTvMjue+onQQsVGxku+lrh290gHAMZK27NcnpQ/ZXeoo5Ch9iZwrHu8r6bKO15YW7/3jAf3YSmkHtVHHc59V1wap47UHFG3ev88yH7bsDu1uWws3FD/veO0xWngAf6OkHZV2Dr9TUXAuXnuyOgpEYyzPd6lj51ksq7vUscPomv4ASV8csN4dpTTKoPM9l2jNQeYVGl7we0fH6x9WR3FK6TTVlcXjvSV9u2teR6v4UtD1/J+rqwgs6XvqKsp1vLajpJt6LatR5t29fnT3vVgf/qerX7dLWqf4e+MiLw9Q+gJ0pzo+O0o7wm8Uj9+hPgdAxeufVioEze+gDumeXukg6tAe732P0ums6/d4rW4Fv521pkD/KqWDxkfMMi7NvuA3k+200kHzbpLe0vGci/bvVtrW3SDpCV3ve46kyyfoy9Za+Ll9Z8frb5T05eLxq4vP2mO72hi2Xu+rhdvUfTW44PdJSR/seO2+Rf+3Lv4Orf2j0kka8oOfhuxfNFrB7yEdr98oae+Ov09R8SNKMa/u7dQ5SgfAY+drzGX6SXUdXCoV6sZe3zXgmKfHtDtq4bb2vUPaP1XS3xWPlysVTrq3nTt3TP8jSX9RPB77GKYjrrEKfkrHLndL+pOO196vtb8Mr1vE9xN1bKfHzPeyYp4bdDz3HElXTLo+dLTzF5L+t2vZ9ir4vUsdBSqlz8ldXdOe3fH6dpLu6Gr35V2fi6M6/n6TimLHiMuq3/Zoa41X8Bv52HLK9fJiSc/ueG3zYt0ZGueA+DdXGg3ywB6vjXKstk/Hax+U9LGOvnV/ue8bf0fOHz5CzPPT/knXvD9ZPF6h6Qt+X5P0xo6/t+0R65Ydr58j6SVTfo72Ve9917E9npvouLTPe74yv052Pf90Sdeq+IGweO5zKo7pijg+0fHa7pJ+Vjx+pdYuMlnSVZqs4DdxHFMuj18pfY+8X9fzvdbtQzXhPn2C5TLKd8lh29HO9X+UbeXA/W3X+vZPXfO+S2k/1ytv98ai0Qp+r5B0Ttc031dxDKIB2/Up1oP55Tmo4Dfo+K972r4FvzHjerXSZ2rTHq89Qukzc7eknYa1NfSU3oj4D6UN9h9L2nPAdJcpFQEOlXSd7RPccQrNCK7sePxLpV+Tuj1UaYjtzfP/lL7IPLh4fZlS1bc0DeznHVozgumuiPimpG9ojAs99vEaper6z4rhti/oiP2vumJ/mtL1pX6nVCB5g6RriqHZ86esvF1pZ3FOMTT51T3muanSQfEvO577pdIvF/OunX8QEauKh/cd0I8tlHacv+tqU5LkdPr0PzmdnnCr0oZrPpZehi27Ufym4/EdkhQR3c/dV+lX96WSftQxry8Xz49rC3Wsm0U+7j310Ok00zOcTm2+VenLyqAh1A+V9NauPCxT7/W9n+4+98rB/Lx27prXy5W2Wd22kHR1FFvLQufyXmr7aKdTPG5V+lL4APe/pso48+6nu183xJqbINxR/H9frfk19pqOeR2tNadm3qT0ZWIB2/+i9Gvcizv6frvS6K9O91PXKbq291c6sHt+RNw5Rr8qERE/jIjbIp2m/GmlYsXuVcc1xEy20xFxd6TTrJ/rNafDvEZpBOr2SsXEfSSd0bXf2ljpx5hZuLbj8Sqt+Zwep3SAeYLTKVoftL2uhq/X0tr7zVFsoY7PdUTcrrQ967mv6IpzkHH3L91G3aZJvbdTW6icfHUaabswhp7HPCNua9fqh+3dbP/A6TTIm5U+1537oRt7bDv75XjsY5ixe77GZkpFhO5c3Csi7lb6UvtoSR/uWvajur34v3P5TbTsnE6jPcHpNLdblX7YGeW0qe5jiVVaeBmD7s/eBl77em7j7PuHLatJPue99P3sz3i9fKjSNVbn+3OxpD9ovGPIbssk/TYiburx2ijHauPkcJT4x9k+jfKdaVJr7SeKx0u0dqyzWn+G6ZuTCY5Le+n3vXELSVfG2tfn7/u9SmvnoPuzHoP6McQ0cUxjL6XP6y+dLu3U99T+PsbZp/fSb7mM8l1y2Ha0u71h28ppPpfrarT9wyi6P5fz8yh7XRhm2uO/sdj+C6WRm7tFxA09Jvk7SecqFavPG9beSNfwi4hrlaqr2w2Z7rMR8TSlFSsk/XPx0u+UChPzen0ZXtbxeCv1vobSlUqjlx7Q8W/jiNi94/Wpz+kfpmH9vKBXF0Z8b18R8fOIeKnSl4x/lnSy03UPrlT6FaEz9o0i4p+K930lIp6jtJH5mdJweUXEtRHxuojYQunXliO98K6vNyh9KX5ox3NbKQ0tntQ1kh5YxN7Z5ryXKRW6d1E6tXTr4vn561R053LYsps69x1uUNqhbN8xr/tHxCQboGvUsW463ep7k47Xj1JaXttEujHAweq4VkcPV0p6X1celkbE54rXZ5mHKyV9s2te942Iv+kx7TWSHmKvdb2uzuX9VqVfeXcu+vnnxfODlvegec+6n3cq/dIzP6/7RcT2xesXKH2BXYvt9yiN+npuRNza8dKFkh7blYvHquPaeEXh/UClX+y7r0faFKHB62odzHo7vURr9hE7Kp3ecmmkazl+Welz8JSO6f9UaQRnaYpi5HsiYrti3i9QKiQPW6+lhbkYlptfq2M/UWzfN9F0+4phRtn/j6PXdurXmixf47hQ6fQ1Sffm7hGa/JqZ/Y55hm1rpY5+ON2U6BSlU0QfHBEPkHSmJvxsT3oMo8lye73S6NruXNzL9kMkvVvSf0n6cNHfsRQFnWvUsfyKx5Msu/cr9fUxxfLZR6Pl+hql0zklSU7XFN6k/+RTGbasBpnJvnnW66VSn3br6tMGka7lNKkrJT3Iva/BOOxYbZBeORwl/nFy32/7MWx7O8o+Y639hNac8fObHtPOSr++D8rJKNvKYfp9b/y1pGVe+yYNo36v6v7eYK29vMYxTRwTi4hzI2JPpf3AqUqj/aXZHrsP0m+5DPsuOUyv7yrDtpXTfC7vVvpOutbnsihKdw5C6TWP7ue6P5fz8yj7+E2azTHc1OuO0/UOPy5pj4j4SZ/J/lRpZOMdfV5fyzg37bhTaXRAv+C2tf2sYuf3e6VCxHylfqWk3Z0uGvvHSiPkuv2t7S2dLvz4DvW+K9w5km5zumnGhk6jrx5t+wnF65+QdJjtbZw81vb8gcZvNPwilEM1sJ/fUhqyfJDtJbafKumZSiMtuvs2f9HKrUfIwz62N4v0a8zNxdOrlX4F3sP284q4N3C6iOeWxS/GexYH1Xcq/Rq9umjvr7zmIrQ3KX1g1rpJTKRfSE+S9D7bG9t+qKS3aO27ko4lIn6pdBfC99hez/bTlE6rnLdxEeuNShuC93c10Z3vYcvuN5K29gzugFTk/uOS/s32H0npS4Pt503Q3MmSXmD7aU4343iv1t4+bKx0/cXbnUZldhfTuvPwcUlvsL1zsY5uZPv5tjfuM/00zpD0KKebjqxb/HuC08VTu31f6YDuzcV0f6l0TZB5Gyt9pm8uPqPv7np/d9zD5j2zfka6m+pXlb4M3s/pgtOPsP2MYpJzlH71vfdXMNsHKRWtd4mI7lEWc0q/vL/Z6eYv+xfPf71478uV1vfnRMTl3fEUfd1AaT1ZUnzW1yleG7gtmd82KBWm7lO8d92O169wn1vYF+vTBir2R8V71y8eP6DY9mxQbO9ernRw/OUZxbVe8bolrVu8Pn/ToOW2++7oi3g2UDrtYX7bOP9r7MDttNMFg6/o0+6fOI0w2bBYJvsUff5mMcm5kp5v++FF7p6jVBj+aUczz1C6SPV8m3O2D+3Xl0nYfqbtxxTryK1KB4irR1ive/mNpC295o7S3T4n6a9t71isG++X9MOIuGJ2PVpgpaSXFMtgJ6XrGk3jj7RmO/VXSgd3Z06Yr3F8UdKjbe9VrK/vUrpm3M+key8WPjdGe/2OeYZta7utp3Sa/vWS7nG6YPbEZyp4gmOYYpqxt+nFscsXJB3qNFpnO6XLDczHYqXRfZ9UGnl4jaTDJuzasZLe6XSh/z9RuubQio55hTsu1j7AxkrHaLcU+5R/GHH+Jyvl7ynF5/NQlfeDy7BlNcj1Sst72v3zTNdLpWuCvc/p+Fa2N7Pd9+yqURTbjLOUfkh/YLFNmS8aDTtWG+Q3kjZxcaOLkuI/pPjMbK80Un1++7FSg79vdX9Oey3vz0n6e6cb991XaT9xYqy5AdbIxvhcDdt39TLutrKXT0h6m+0/K5bzI4tl9EOlEVJvL9aL5UrfgU4Yoc0vSdre9l8WxzJv1uAiyaBt5zRxTKQ4nnu57ftHGmF9q9Z87+y1bpeh33IZ9l1ymO5cT7Ot7GUf29s5DQ55r6STi/3cpUojDZ/vdOz8TqXtY2dc3d+Bu2M9U+l71cucjof3Vhpwdsa4QTrd7GXFsOki4nqlguI+RX5erckHkE3yGb+X7WcpXV5pr4g4Z8Ck6yrVJ0YyTtFh9ZDp15f0T0oV3muVDlYPKl47Tmn0wBVKB6q9ilyfLV67XGl46+HdExQr0wuURiz8opjXJ5RGXknpRgMnFe3cqnTwtGHx2qGSPu00lHXBHTyd7gB28ID+NbKfxUZsT6Uhy7co7dxfOX/w3mWZ0rDZUarou0q60PbtShf5fklE3BERVxbzO1hpB3ul0oHifYp/b1Gq3v9W6UvmfOHoCZJ+WLR3mtI1DRYUGZSu3/I7pfx9Rymfnxoh3kFepnS9hN8q7UiP7XjtWK3JyUVK17Ls9ElJ2xX5PnWEZff54v8bbf94yrildF2GyyT9wGmo/9lKvwQuMGgdj4gLlW768FmlLxs3KV03YN7blPJ0m9I61L1uH6q117vzlL5o/GfR1mVa+06BH1D6UnKzO+68NomIuE3pAPslSuvWtUojNuaLQAfbPquY9i6lGxTsq7S891b6IjbvCKXP0g1Ky/rLXbP7iKQXOd2l6d+HzVtd68c0/Sy8UukLxkVKeT1ZxZD8om8rlEZizHu/0i9jl3nN3cYO7pj+L4o2b1a6VsRfFM9Ladu0iaRzO977sY62P650EPpSpS/ydyhde0Mavi15RTH9UUrXbrmjaE/FTnITLfyszXtoMf38qJU7lK45JKUd4OFac9OONxV9unTauApfLZ57itI1R+/Qml/blyldo66fdxbTH6i0jO4ontMI2+llSqcm92IVl5go+v13SteRmd++HKt00DyntL/4d6UbPMwXcDZXOpg6taPNQfOb1B8rra+3Kp3m9U2lfaY0YL3u4+tKy/9a2wtOdYiIs5WuUXmK0vbsEUqf0TIdUsznJqVrw312yvZ+KGkbpfX4fZJe1FG0Hzdfa3G6bMbLe71WHPjuVczzJqV9Y2fuxl03+h3zHKHB29ruuG5T+kJ5UhHXy5SOFSY1yTGM1LUPGGN++yud/nOt0nb6vzpee7PSseQhERFKhY2/tv307kaKL6iDRuy9WynPv1T6jP1LMapXTncBvk3pOoHDvEfppje3KH2x/8LgyZPiWOJNStuca5SKhtdpjC8moxphWQ167yqldfy7xf75SRPGMOv18iPF+79q+zalz8bOvSa0/bGuffIgr1D6keVnSsvjgCL+YcdqfRX7kM9JurzI4RbjxD+ibxYxfU3ShyLiq8Xzw75vrXWM2Wd5f6po51tKx+u/V1p3xzLm52rgvquPIzTGtrKXiPi8Uv8/W8R6qtJNoO5SKqztVrR/pPp/R+xu8walm078k9KgiG00eN9wqPp8D58mjm6dx/wjeIWkK4rvT29QuiRPv3V75gYsl2HfJYfpXv8n3lb2cZzSfuxapZuWvLnozy1K19T7hNJx9u+09nfJXt+Bu79X3ajU97cqrVdvl/SC6H1a6zDjHK+8TiknNypdAmfQ8fwgAz/jtrcqvk9ttfCtktKx5P0lndnx3avX+ryOugZGDTJ/8fbhE9rvV7qb0wuLLyeLoqh0fiIiZjUaqJbq0E/b75R0fUQcXVUMACZnezNJ35b0uFGHeZcUx8TbEqdRtn9bnG5Xm7hGaPsTkj4fEQtGT8+g7a8q/QhycQltf1jpRgpHFn9vKemkiHjK4HeijWyvVDrFv3vEcK9pr1C6kPXZZceF4ZxG/24fEQcNnXh287yv0g9K20TELxZrvsBiqeJzBWCwYvDA+Uo3ilu0utVicLo8w0VKx1dnjvSeMQp+D1f6NWR7pXOK+42+mCnbb1a6I+2zF2N+VWlLPwEAQP4o+LWT7T2URmRZ0oeVRng9Pkb9wgEAABYozop7m9KN/faNEW+mOPJQzoi4PCKWR8Rmi1js+4ikv1c6tSBbbelnlTqGxXb/W3DKDACgfYpT5HrtJ0Y9bQ4tYvvp/Y4tqo6tYnsqXeLi10qn+b2k7sU+PvuLrzg9vVfOJ71BUKuxDtdTE5cL35nrKyI+FBF/HBEvHbXYJ40xwg8AAAAAAABA/U19p1AAAAAAAAAA9UHBDwAAAAAAAMgIBT8AAAAAAAAgIxT8AABA7dnexfYuVccBAAAANAE37QAAALVne06SImJ5tZEAAAAA9ccIPwAAAAAAACAjFPwAAAAAAACAjFDwAwAAAAAAADJCwQ8AAAAAAADICDftAAAAtWd7W0mKiEuqjgUAAACoO0b4AUBObMveUbarDgWYpYi4hGIfgEqwbwUANBAFPwDIyw6STin+B7Jhew/be1QdB4BWYt8KAGgcTukFgJyk0Qc7SDpfbOCREdtzkhQRy6uNBEDrsG8FADTQkqoDAADMUPoisrLqMAAAyAb7VgBAA3FKLwAAAAAAAJARCn4AAAAAAABARij4AQAAAAAAABnhph0AAKD2bC+TpIi4supYAAAAgLqj4AcAAAAAAABkhFN6AQBA7dne2/beVccBAAAANAEj/AAAQO3ZnpOkiFhebSQAAABA/THCDwAAAAAAAMgIBT8AAAAAAAAgIxT8AAAAAAAAgIxQ8AMAAAAAAAAywk07AABA7dneVJIi4oaqYwEAAADqjoIfAAAAAAAAkBFO6QUAALVne1/b+1YdBwAAANAEjPADAAC1Z3tOkiJiebWRAAAAAPXHCD8AAAAAAAAgIxT8AAAAAAAAgIxQ8AMAAAAAAAAyQsEPAAAAAAAAyAg37QAAALVne6kkRcSqqmMBAAAA6o6CHwAAAAAAAJARTukFAAC1Z/uNtt9YdRwAAABAEzDCDwAA1J7tOUmKiOXVRgIAAADUHyP8AAAAAAAAgIxQ8AMAAAAAAAAyQsEPAAAAAAAAyAgFPwAAAAAAACAj3LQDAAAAAAAAyAgj/AAAAAAAAICMUPADAAC1Z/tttt9WdRwAAABAE3BKLwAAqD3bc5IUEcurjQQAAACoP0b4AQAAAAAAABmh4AcAAAAAAABkhIIfAAAAAAAAkJElVQcAAAAwgjuqDgAAAABoCm7aAQAAAAAAAGSEU3oBAAAAAACAjFDwAwAAtWf7ENuHVB0HAAAA0AQU/AAAQBM8u/gHAAAAYAgKfgAAAAAAAEBGKPgBAAAAAAAAGaHgBwAAAAAAAGRkSdUBAAAAjODGqgMAAAAAmsIRUXUMAAAAAAAAAGaEU3oBAAAAAACAjFDwAwAAtWf7A7Y/UHUcAAAAQBNwDT8AANAET646AAAAAKApGOEHAAAAAAAAZISCHwAAAAAAAJARCn4AAAAAAABARriGHwAAaIKrqg4AAAAAaApHRNUxAAAAAAAAAJgRTukFAAAAAAAAMkLBDwAA1J7tI2wfUXUcAAAAQBNwDT8AANAEO1YdAAAAANAUjPADAAAAAAAAMkLBDwAAAAAAAMgIBT8AAAAAAAAgI1zDDwAANMGlVQcAAAAANIUjouoYAAAAAAAAAMwIp/QCAAAAAAAAGaHgBwAAas/2MbaPqToOAAAAoAm4hh8AAGiCR1UdAAAAANAUjPADgGnZlr2jbFcdCtAT6ygAAADQKhT8AGB6O0g6pfgfqCPWUQAAAKBFuEsvAEwrjZraQdL5YqOKOspgHbU9J0kRsbzaSAAAAID64xp+ADCtVEBZWXUYQF95rKMrqw4AAAAAaApG+AEAAAAAAAAZ4Rp+AAAAAAAAQEYo+AEAgNqzfbzt46uOAwAAAGgCruEHAACaYMuqAwAAAACaghF+AAAAAAAAQEYo+AEAAAAAAAAZoeAHAAAAAAAAZIRr+AEAgCb4ftUBAAAAAE3hiKg6BgAAAAAAAAAzwim9AAAAAAAAQEYo+AEAgNqzfYrtU6qOAwAAAGgCruEHAACaYJOqAwAAAACaghF+AAAAAAAAQEYo+AEAAAAAAAAZoeAHAAAAAAAAZIRr+AEAgCb4WtUBAAAAAE3hiKg6BgAAAAAAAAAzwim9AAAAAAAAQEYo+AEAgNqzfZbts6qOAwAAAGgCruEHAACaYMOqAwAAAACaghF+AAAAAAAAQEYo+AEAAAAAAAAZoeAHAAAAAAAAZIRr+AEAgCY4o+oAAAAAgKZwRFQdAwAAAAAAAIAZ4ZReAAAAAAAAICMU/AAAQO3ZnrM9V3UcAAAAQBNQ8AMAAAAAAAAyQsEPAAAAAAAAyAgFPwAAmsy27B1lu+pQAAAAANQDBT8AAJptB0mnFP8DAAAAgJZUHQAAAJjK+ZL2Kv7P2UlVBwAAAAA0hSOi6hgAAAAAAAAAzAin9AIAgNqzvdT20qrjAAAAAJqAEX4AAKD2bM9JUkQsrzYSAAAAoP4Y4QcAAAAAAABkhIIfAAAAAAAAkBEKfgAAAAAAAEBGKPgBAAAAAAAAGVlSdQAAAAAjWFF1AAAAAEBTcJdeAAAAAAAAICOc0gsAAGrP9qa2N606DgAAAKAJGOEHAABqz/acJEXE8mojAQAAAOqPEX4AAAAAAABARij4AUDd2Za9o2xXHQoWCcscAAAAwBQo+AFA/e0g6ZTif7QDyxwAAADAxLiGHwDUXRrltYOk88VGux1Y5gtwDT8AAABgdBT8AABA7dneW5Ii4sSqYwEAAADqjoIfAAAAAAAAkBGu4QcAAGrP9jLby6qOAwAAAGgCCn4AUAbusgrM2nHFP6A92JcAAIAJUfADgHJwl1UAwLTYlwAAgIlQ8APQTuWPmjhf0l7F/0CzMKoIqAv2JWgu9iUAUCkKfgDaqtxRExGhiJXizkhoJkYVAXXAvgTNxr4EACrEXXoBtFP6tXkHSefzRQroUsPPh+05SYqI5dVGAgAYSQ33JQDQJhT8AABA7dneQ5Ii4vSqYwEAAADqjoIfAAAAAAAAkBGu4QcAAGrP9ra2t606DgAAAKAJGOEHAABqj2v4AQAAAKNjhB8AAAAAAACQEQp+AAAAAAAAQEYo+AEAAAAAAAAZoeAHADmyLXtH2a46FAAAAADA4lpSdQAAgFLsIOkUSXtJWlltKMBMHF51AAAAAEBTcJdeAMhRGtm3g6TzxYYeAAAAAFqFgh8AAKg92ztKUkSsrDYS1B4/eAAAAHANPwAA0AhHFP+AYeYvabBD1YEAAABUhYIfAAAAcnK+0vVLz686EAAAgKpw0w4AAADkI53Gu7LqMAAAAKrECD8AAAAAAAAgIxT8AAAAAAAAgIxwSi8AAGiCg6sOAAAAAGgKp8ucAAAAAAAAAMgBp/QCAIDas/0U20+pOg4AAACgCRjhBwAAas/2nCRFxPJqIwEAAADqjxF+AAAAAAAAQEYo+AEAAAAAAAAZoeAHAAAAAAAAZISCHwAAAAAAAJARbtoBAABqz/aOkhQRK6uNBAAAAKg/Cn4AAAAAAABARjilFwAA1J7tXWzvUnUcAAAAQBMwwg8AANSe7TlJiojl1UYCAAAA1B8j/AAAAAAAAICMUPADAAAAAAAAMkLBDwAAAAAAAMgIBT8AqIpt2TvKdtWhAKgY2wMgH3yeAQA1QMEPAKqzg6RTiv8BDPb64l+u2B4A+eDzDACoHHfpBYCqpF/+d5B0vtgYA+3G9gDIB59nAEANUPADAAC1Z3sPSYqI06uOBQAAAKg7Cn4AAKD2bM9JUkQsrzYSAAAAoP64hh8AAAAAAACQEQp+AAAAAAAAQEYo+AEAAAAAAAAZoeAHAAAAAAAAZISbdgAAgNqzvUySIuLKqmMBAAAA6o6CHwAAAAAAAJARTukFAAC1Z3tv23tXHQcAAADQBIzwAwAAtWd7TpIiYnm1kQAAAAD1xwg/AAAAAAAAICMU/AAAAAAAAICMUPADAAAAAAAAMkLBDwAAAAAAAMgIN+0AAAC1Z3tTSYqIG6qOBQAAAKg7Cn4AAAAAAABARjilFwAA1J7tfW3vW3UcAAAAQBMwwg8AANSe7TlJiojl1UYCAAAA1B8j/AAAAAAAAICMUPADAAAAAAAAMkLBDwAAAAB6sS17R9muOpSZyrVfAIB7UfADAAAAgN52kHRK8X9Ocu0XAKDATTsAAEDt2V4qSRGxqupYALRIGgG3g6TzldMXp1z7BQC4FwU/AAAAAAAAICOc0gsAAGrP9httv7HqOAAAAIAmYIQfAACoPdtzkhQRy6uNBAAAAKg/RvgBAAAAAAAAGaHgBwAAAAAAAGSEgh8AAAAAAACQEQp+AAAAAAAAQEa4aQcAAAAAAACQEUb4AQAAAAAAABmh4AcAAAAAAABkhIIfAAAAAAAAkBEKfgAAAAAAAEBGKPgBAAAAAAAAGaHgBwAAAAAAAGSEgh8AAAAAAACQEQp+AAAAAAAAQEYo+AEAAAAAAAAZoeAHAAAAAAAAZISCHwAAAAAAAJARCn4AAAAAAABARij4AQAAAAAAABmh4AcAAAAAAABkhIIfAAAAAAAAkBEKfgAAAAAAAEBGKPgBAAAAAAAAGaHgBwAAAAAAAGSEgh8AAAAAAACQEQp+AAAAAAAAQEYo+AEAAAAAAAAZoeAHAAAAAAAAZISCHwAAAAAAAJARCn4AAAAAAABARij4AQAAAAAAABmh4AcAAAAAAABkhIIfAAAAAAAAkBEKfgAAAAAAAEBGKPgBAAAAAAAAGaHgBwAAAAAAAGSEgh8AAAAAAACQEQp+AAAAAAAAQEYo+AEAAAAAAAAZoeAHAAAAAAAAZISCHwAAAAAAAJARCn4AAAAAAABARij4AQAAAAAAABmh4AcAAAAAAABkZEnVAQDAJHbdddf48pe/XHUYAAAAyJ+rDgAAxsUIPwCNdMMNN1QdAoBFdPbZZ+vss8+uOgwAAACgERjhBwAAau/www+XJO2yyy4VRwIAAADUHyP8AAAAAAAAgIxQ8AMAAAAAAAAyQsEPAAAAAAAAyAgFPwAAAAAAACAj3LQDAADU3tFHH111CAAAAEBjMMIPQHt95jPSD34wfTs/+5n00Y+u/dzq1dJ73iP99reD3/uxj0kXXjh8Hh/5iHTZZcOnu+026V3vkr74RemrXx0+Perpd7+TDjlEuuuu0ab/4helr3+9/+vXXiu9731SxGjtffrT0nnnjTbtItl222217bbbVh1GeS64QDrmmKqjyMOxx0rnnlt1FLPx/e+nfRWq88tfSv/yL+NtF3/1K+mDHxx9m1uFCOn970/7h7L88IfS8ceX1z4AYCAKfgDa6x3vSAW3aX32s9Kb3rT2cz//uXToodJXvjL4vQccIB133OBp7r47TXfiicNj+cY3pMMOk176UunDHx4+Perp29+WDj9c+vGPR5v+sMOkI47o//qXviS9853SVVeN1t4//qP0iU+MNu0iOf3003X66adXHUZ5jj1Westbqo4iDwceKH3841VHMRtHH532VajOF78ovf3t0hveMPp28dRT03b0pptKDW0qv/51WrfK3K4efbR08MHltQ8AGIhTegG01+rVs/n1vVc7q1en/4e1P0oMo7bVOe0999R7ZAEGG2eZz08/aNpZt1eBDxcF7D322KPiSEpSw5w3Vk65zKkvTTXJfnXcbW4VFiNG1l8AqBQj/AC01+rVaw54p21HWvugdv65Ye2PEsOobXVPO4u+oRrjLPP56QZNO+v2MHvkfHZyymVOfWmq+fz/4Q/jbUM7/6+jxYiR9RcAKkXBD0B7Rczml+f5Njrb6vXcpDGM2lb3tPyq3lzjLPP56QZNO+v2MHvkfHZyymVOfWmqSfar425zq7AYMbL+AkClKPgBKJXtT9m+zvZP+7z+ctsX2P6J7e/Z3mHRgsu54Dfq9KgnCn7tQ85nJ6dc5tSXpppkv0rBb03bdc4BAGSOgh+Asq2QtOuA138h6RkR8RhJh0lavNtUUvBDXVHwax9yPjs55TKnvjQVBb/p5lHnHABA5rhpB4BSRcS3bG894PXvdfz5A0lblh7UmplXX/AbZRoKfu1DwW+B44bdzbrpapjzxsoplzn1pako+E03jzrnAAAyR8EPQJ28RtJZ/V60vZ+k/SRpq622mn5uVRf8xhkFOMp0/WJA81DwW2DZsmVVh1CuGua8sXLKZU59aSoKftPNo845AIDMcUovgFqw/Uylgt8/9psmIo6JiJ0iYqfNNtts+plS8ENdUfBb4MQTT9SJJ55YdRjlqWHOGyunXObUl6ai4DfdPOqcAwDIHCP8AFTO9mMlfULSbhFx46LNmIIf6oqC3wJHHXWUJGnvvfeuOJKS1DDnjZVTLnPqS1NR8JtuHnXOAQBkjhF+ACpleytJX5D0ioi4dFFnTsEPdUXBr33I+ezklMuc+tJUFPymm0edcwAAmWOEH4BS2f6cpOWSNrV9laR3S1pXkiLiY5LeJWkTSUfalqR7ImKnRQmOgh/qioJf+5Dv2clp/c2pL01FwW+6edQ5BwCQOQp+AEoVES8d8vprJb12kcLpnnneBb/Vq4dPj3qi4Nc+ncso/fiBSeW0/ubUl6ai4DfdPOqcAwDIHKf0Amiv1atnUxSbb6OzrV7PjfK+aabrnoaD7OYaZ5nPTzeLdW3U9jB74y4j9JfT+ptTX5qq17591PfUedktRoysvwBQKUb4AWiv3Ef4UfBrLkb4LXDyySdXHUK5mjAiqClquP5OLKe+NBUj/KabR51zAACZo+AHoL0o+KGuKPgtsOmmm1YdQrmaUCBoihquvxPLqS9NRcFvunnUOQcAkDlO6QXQXhT8UFcU/BZYsWKFVqxYUXUY5WlCgaAparj+TiynvjQVBb/p5lHnHABA5ij4AWgvCn6oKwp+C1Dww8hquP5OLKe+NBUFv+nmUeccAEDmKPgBaC8KfqgrCn7t04QCQVPktP7m1JemouA33TzqnAMAyBwFPwDtRcEPdUXBr32aUCBoipzW35z60lQU/KabR51zAACZo+AHoL0o+KGuKPi1TxMKBE2R0/qbU1+aioLfdPOocw4AIHMU/AC0FwU/1FXVBb9xp8X0mlAgaIqcigw59aWpKPhNN4865wAAMrek6gAAoDIU/FBXVRb8avpF9cwzz6w6hHLVNO+NlFORIae+NBUFv+nmUeccAEDmKPgBaK/cC36rVw+fHvVEwW+BpUuXVh1CuWqa98bJLY8UTKpHwW+6edQ5BwCQOU7pBdBeq1fPpig230ZnW72eG+V900zXPQ0H2c01zjKfn24W69ok814kRx55pI488siqwyhPTfPeOPPbvVzyOKv9FCbXa98+6nvqvOwWI0bWXwCoFAU/AO2V+wg/Cn7NxQi/BU466SSddNJJVYdRnprmvXFyyyMjpKrHCL/p5lHnHABA5ij4AWgvCn6oKwp+7UPeZyO3PFIwqR4Fv+nmUeccAEDmKPgBaC8KfqgrCn7tQ95nI7c8UjCpHgW/6ecBAKgEBT8A7UbBD3VEwa99yPts5JZHCn7Vo+BX73kAAPqi4AegnWZ5EErBD7NGwa99yPts5JbHXPrRZBT86j0PAEBfS6oOAAAqQcEPdUbBb4G5ubmqQyhXTfPeOLnlsbM/drWxtBUFv3rPAwDQFyP8ALQTBT/UGQW/9iHvs5FbHnPrTxNR8Kv3PAAAfVHwA1A625+yfZ3tn/Z53bb/3fZlti+w/fjSg6Lghzqj4LfAhz70IX3oQx+qOozy1DTvjZNbHnPrTxNR8Kv3PAAAfVHwA7AYVkjadcDru0napvi3n6SjSo+oDQW/1auHT496ouC3wBlnnKEzzjij6jDKU9O8N05uecytP01Ewa/e8wAA9EXBD0DpIuJbkn47YJI9JR0byQ8kPcD25iUHtfb/s26rDgU/DrCbi4Jf+5D32cgtj7n1p4ko+NV7HgCAvij4AaiDh0i6suPvq4rnyjM/+m0Wo+B6tTVK+6PGME6sndNwgN1c466fq1fPZl2bZN6YDfI+G7nlMbf+NFGvffuo76nzcluMGJuQBwDIGAU/AI1hez/b59k+7/rrr5+uMUb4oc4Y4dc+5H02cstjbv1pIkb41XseAIC+KPgBqIOrJS3r+HvL4rm1RMQxEbFTROy02WabTTdHCn6oMwp+C2y44YbacMMNqw6jPDXNe+Pklsfc+tNEFPzqPQ8AQF9Lqg4AACSdJml/2ydI2lnSLRFxTalzpOCHOqPgt8BZZ51VdQjlqmneGye3PObWnyai4FfveQAA+qLgB6B0tj8nabmkTW1fJendktaVpIj4mKQzJe0u6TJJqyT9delBUfBDnVHwax/yPhu55TG3/jQRBb96zwMA0BcFPwCli4iXDnk9JP3tIoUzP9O1/591WxT8MA0KfgscdthhkqRDDjmk4khKUtO8N05uecytP01Ewa/e8wAA9MU1/AC0EwU/1BkFvwW+9rWv6Wtf+1rVYZSnpnlvnNzymFt/moiCX73nAQDoi4IfgHai4Ic6o+DXPuR9NnLLY279aSIKfvWeBwCgLwp+ANqJgh/qjIJf+5D32cgtj7n1p4ko+NV7HgCAvij4AWinNhT8Vq8ePj3qiYJf+5D32cgtj7n1p4ko+NV7HgCAvrhpB4B2akPBjwPs5pp1gS6Dgt8mm2xSdQjlqmneGye3PObWnyai4FfveQAA+qLgB6Cd5ke/zWIUXK+2Rml/1BjGibVzGg6wm2ucZT6/nGexro077SI65ZRTqg6hXDXNe+Pklsfc+tNEvfbto76nzsttMWJsQh4AIGOc0gugnRjhhzpjhF/7kPfZyC2PufWniRjhV+95AAD6ouAHoJ0o+KHOKPgtcNBBB+mggw6qOozy1DTvjZNbHnPrTxNR8Kv3PAAAfXFKL4B2ouCHOqPgt8D3v//9qkMoV03z3ji55TG3/jQRBb96zwMA0Bcj/AC0EwU/1BkFv/Yh77ORWx5z608TUfCr9zwAAH1R8APQThT8UGcU/NqHvM9GbnnMrT9NRMGv3vMAAPRFwQ9AO1HwQ51R8Gsf8j4bueUxt/40EQW/es8DANAX1/AD0E4U/FBnFPwW2HLLLasOoVw1zXvj5JbH3PrTRBT86j0PAEBfFPwAtFMbCn6rVw+fHvVEwW+B448/vuoQylXTvDdObnnMrT9NRMGv3vMAAPTFKb0A2qkNBT8OsJuLgl/7kPfZyC2PufWniSj41XseAIC+KPgBaCcKfqgzCn4LHHDAATrggAOqDqM8Nc174+SWx9z600QU/Oo9DwBAX5zSC6CdKPihzij4LbBy5cqqQyhXTfPeOLnlMbf+NBEFv3rPAwDQFyP8ALTT/PXtZnGdu15tjdL+qDGME2vnNBxgN9cky3wW69q402J2yPts5JbH3PrTRL327aO+p87LbTFibEIeACBjFPwAtBMj/FBnjPBrH/I+G7nlMbf+NBEj/Oo9DwBAXxT8ALQTBT/UGQW/9iHvs5FbHnPrTxNR8Kv3PAAAfVHwA1A627vavsT2ZbYP7PH6Vra/Yft/bV9ge/fSg6Lghzqj4LfAox71KD3qUY+qOozy1DTvjZNbHnPrTxNR8Kv3PAAAfXHTDgClsr2OpI9Keo6kqySda/u0iLioY7J3SjopIo6yvZ2kMyVtXWpgFPxQZxT8FjjmmGOqDqFcNc174+SWx9z600QU/Oo9DwBAX4zwA1C2J0q6LCIuj4i7JJ0gac+uaULS/YrH95f069KjouCHOqPg1z7kfTZyy2Nu/WkiCn71ngcAoC9G+AEo20MkXdnx91WSdu6a5lBJX7X9JkkbSdql9Kgo+KHOKPgtsN9++0nKeKRfTfPeOLnlMbf+NBEFv3rPAwDQFyP8ANTBSyWtiIgtJe0u6TjbC7ZPtvezfZ7t866//vrp5tiGgt+o70H9UPBb4NJLL9Wll15adRjlqWneGye3PObWnyai4FfveQAA+qLgB6BsV0ta1vH3lsVznV4j6SRJiojvS9pA0qbdDUXEMRGxU0TstNlmm00XFQU/1BkFv/Yh77ORWx5z608TUfCr9zwAAH1R8ANQtnMlbWP7YbbXk/QSSad1TfMrSc+WJNt/qlTwm3II3xAU/FBnFPzah7zPRm55zK0/TUTBr97zAAD0RcEPQKki4h5J+0v6iqSLle7Ge6Ht99p+YTHZWyW9zvb5kj4nad+Iko8OKfihzij4tQ95n43c8phbf5qIgl+95wEA6IubdgAoXUScKenMrufe1fH4IklPXdSgVq9e+/9ZtzVK+6PGME6s3dNwkN1MkyzzWaxr4067iHbccceqQyhXTfPeOLnlMbf+NFGvffuo76nzcluMGJuQBwDIGAU/AO3ECD/UGSP8FjjiiCOqDqFcNc174+SWx9z600SM8Kv3PAAAfXFKL4B2ouCHOptmmc+qPdadxUXeZyO3PObWnyai4FfveQAA+qLgB2Aith9o+7FVxzExCn6os0mXeb/pMyj47bPPPtpnn32qDqM8Nc174+SWx9z600QU/Oo9DwBAX5zSC2BktuckvVBp2/EjSdfZ/m5EvKXSwCZBwQ91Nk3Bz55NezVbd6666qqqQyhXTfPeOLnlMbf+NBEFv3rPAwDQFyP8AIzj/hFxq6S/lHRsROwsaZeKY5oMBT/UGSP82oe8z0ZuecytP01Ewa/e8wAA9EXBD8A4ltjeXNKLJZ1RdTBTaUvBjzvjNRMFv/Yh77ORWx5z608TUfCr9zwAAH1R8AMwjvdK+oqkyyLiXNsPl/TzimOaTFsKfhxkNxMFv/Yh77ORWx5z608TUfCr9zwAAH1xDT8AQ9l+qaSvRsTnJX1+/vmIuFzSXpUFNg0KfqgzCn4LPPnJT646hHLVNO+Nk1sec+tPE1Hwq/c8AAB9UfADMIqtJH3e9rqSvibpLEnnRDT4CI6CH+qMgt8CH/jAB6oOoVw1zXvj5JbH3PrTRBT86j0PAEBfnNILYKiI+OeIeJak3SWdL+nVkn5s+7O2X2n7wdVGOAEKfqgzCn7tQ95nI7c85tafJqLgV+95AAD6YoQfgJFFxG2Svlj8k+3tJO0m6VhJz6swtPHN38xiFje16NXWKO2PGsM4sXZPw0F2M026zPtNP0l7Nbvhy157pasHnHLKKRVHUpKa5r1xcstjbv1polG2sf3eU+flthgxNiEPAJAxRvgBGJntp9reqHi8j6TXSjo5IppV7JMY4Yd6Y4TfAjfeeKNuvPHGqsMoT03z3ji55TG3/jQRI/zqPQ8AQF8U/ACM4yhJq2zvIOmtkv5PaXRf81DwQ51R8Gsf8j4bueUxt/40EQW/es8DANAXBT8A47inuFHHnpL+MyI+KmnjimOaDAU/1BkFv/Yh77ORWx5z608TUfCr9zwAAH1xDT8A47jN9kGSXiHp6bbvI2ndimOaDAU/1BkFv/Yh77ORWx5z608TUfCr9zwAAH1R8AMwjr0lvUzSqyPiWttbSfqXimOaDAU/1BkFvwWe/exnVx1CuWqa98bJLY+59aeJKPjVex4AgL4o+AEYWVHkO0XSNsVTN6i4Y2/jtKXgx53xmomC3wKHHHJI1SGUq6Z5b5zc8phbf5qIgl+95wEA6Itr+AEYme3XSTpZ0tHFUw+RdGplAU2jLQU/DrKbiYJf+5D32cg1j7n1p0ko+NV7HgCAvij4ARjH30p6qqRbJSkifi7pjyqNaFKzPPik4IdZo+C3wG677abddtut6jDKU9O8N05OeZyk0ITZo+BX73kAAPrilF4A47gzIu6yLUmyvURSM4/iug/giz5N1RYFP8wKBb8F7rjjjqpDKFdN8944OeWRgl89UPCb3bwAAIuKEX4AxvFN2wdL2tD2cyR9XtLpw95ke1fbl9i+zPaBfaZ5se2LbF9o+7MzjnuhWX6RouCHWaPg1z7kfTZyyiMFv3qg4Ddd+2XOAwAwECP8AIzjQEmvkfQTSa+XdGZEfHzQG2yvI+mjkp4j6SpJ59o+LSIu6phmG0kHSXpqRNxku/zThDtvZrF6tXSfKX7/mG+ru83u50Z53zTT9ZqGg+xmmnSZ95t+kva44cviIu+zkVMeR/lso3yTLIcmrIdlx8j6CwCVo+AHYByHRsS7JH1cSsU825+JiJcPeM8TJV0WEZcX7zlB0p6SLuqY5nWSPhoRN0lSRFxXSvSdGOGHOmOEX/uQ99nIKY+MkKoHRvhN136Z8wAADETBD8A4ltk+KCI+YHs9SSdJWjnkPQ+RdGXH31dJ2rlrmkdJku3vSlpHqbD45dmE3AcFP9QZBb8FXvCCF1QdQrlqmvfGySl/FEzqgYLfdO2XOQ8AwEAU/ACM49WSPmP7IEnPlHRWRPzbDNpdImkbScslbSnpW7YfExE3d05kez9J+0nSVlttNd0cKfihzij4LfC2t72t6hDKVdO8N07352GaGzJVjYJJPVDwm679MucBABiIm3YAGMr2420/XtLjJH1E0t6Sfq50E4/HD3n71ZKWdfy9ZfFcp6sknRYRd0fELyRdqlQAXEtEHBMRO0XETpttttmEvbm3sd6Pp2mLgh9mpQ4FPyyuJhQImiCnIkNOfWmySXLfhM8zBT8AyB4j/ACM4sNdf98kabvi+ZD0rAHvPVfSNrYfplToe4mkl3VNc6qkl0r6L9ubKp3ie/n0YQ/QloIfF8puproU/Go0Qmr58uWSpLm5uUrjKE0TCgRNkFORIae+NFmvH9KGbReb8Hmm4AcA2aPgB2CoiHjmFO+9x/b+kr6idH2+T0XEhbbfK+m8iDiteO25ti+S9AdJ/xARN84i9gGB9X48TVt1LPhxkN1MFPzapwkFgibIqciQU1+ajILfdO2XOQ8AwEAU/AAMZXufiDje9lt6vR4R/zro/RFxpqQzu557V8fjkPSW4t/ioOCHOqtTwQ+LowkFgibIaf3NqS9NNsl+tQmfZwp+AJA9Cn4ARrFR8f/GlUYxSxT8UGcU/NqnCQWCJshp/c2pL01GwW+69sucBwBgIAp+AIaKiKOL/99TdSwzQ8EPdUbBr32aUCBogpzW35z60mQU/KZrv8x5AAAGouAHYGS2H650l94nSQpJ35f09xFR7g02ykDBD3VGwW+BF7/4xVWHUK4mFAiaoKbr70Ry6kuTUfCbrv0y5wEAGIiCH4BxfFbSRyX9v+Lvl0j6nKSdK4toUp13r532Trbz7+/V5qC2R5lmnOl6TcNBdjNNusz7TT/r9irwxje+seoQyjXOMkJ/NV1/J5JTX5qsO/fjbEfrvNzKjpH1FwAqd5+qAwDQKEsj4riIuKf4d7ykDaoOaiKM8EOdMcJvgVWrVmnVqlVVh1GeJowIaoKarr8TyakvTcYIv+naL3MeAICBGOEHYBxn2T5Q0glKp/TuLelM2w+SpIj4bZXBjYWCH+qMgt8Cu+++uyRpbm6u2kDK0oQCQRPUdP2dSE59aTIKftO1X+Y8AAADUfADMI75i2i9XqngJ0lWOrU3JD28iqAmQsEPdUbBr73I+XRyWn9z6kuTUfCbrv0y5wEAGIhTegGM4x8l7RARD5P0X5LOl7RXRDwsIppT7JMo+KHeKPi1CzmfnZxymVNfmoyC33TtlzkPAMBAFPwAjOOdEXGr7adJepakT0g6quKYJtOWgh8Xym4mCn7tQs5nJ6dc5tSXJqPgN137Zc4DADAQBT8A4/hD8f/zJX08Ir4kab0K45lcWwp+HGQ3EwW/diHns5NTLnPqS5NR8Juu/TLnAQAYiGv4ARjH1baPlvQcSf9se3019YcDCn6oMwp+C+y7775Vh1Cemua8kXLKZU59aTIKftO1X+Y8AAADUfADMI4XS9pV0oci4mbbm0v6h4pjmkydCn6TtD9qmxxkNxMFvwUo+GEkOeUyp740GQW/6dovcx4AgIEo+AEYWUSskvSFjr+vkXRNdRFNoW4FvwjJHr39Udoc9T2oHwp+C9xwww2SpE033bTiSEpQ05w3Uk65zKkvTUbBb7r2y5wHAGAgCn4A2omCH+qMgt8CL3rRiyRJc3Nz1QZShprmvJFyymVOfWkyCn7TtV/mPAAAAzXz2lsAMK3Ou9dOeyfb+ff3anNQ26PGMEpb/drhILuZJl3m/aafdXuYLXI+OznlMqe+NFl37sfZjtZ5uZUdI+svAFSOgh+Adirjl+dpR/gNm44Rfu3BCL92Ieezk1Muc+pLkzHCb7r2y5wHAGAgCn4A2mlWB6L92qHgh2lQ8GsXcj47OeUyp740GQW/6dovcx4AgIEo+AFoJwp+qDMKfu1Czmcnp1zm1Jcmo+A3XftlzgMAMBA37QDQTm0p+HHdnGai4LfA3/zN31QdQnlqmvNGyimXOfWlySj4Tdd+mfMAAAxEwQ9AO7Wl4MdBdjNR8Ftg7733rjqE8tQ0542UUy5z6kuTUfCbrv0y5wEAGIhTegGUzvauti+xfZntAwdMt5ftsL1T6UFR8EOdUfBb4Morr9SVV15ZdRjlqGnOGymnXObUlyaj4Ddd+2XOAwAwECP8AJTK9jqSPirpOZKuknSu7dMi4qKu6TaW9HeSfrgogVHwQ51R8FvgFa94hSRpbm6u2kDKUNOcN1JOucypL01GwW+69sucBwBgIEb4ASjbEyVdFhGXR8Rdkk6QtGeP6Q6T9M+Sfr8oUVHwQ51R8GsXcj47OeUyp740GQW/6dovcx4AgIEo+AEo20MkdZ6Hd1Xx3L1sP17Ssoj40qCGbO9n+zzb511//fXTRdXEgt+48Yz7XtQHBb92Ieezk1Muc+pLk1Hwm679MucBABiIgh+AStm+j6R/lfTWYdNGxDERsVNE7LTZZptNN+OmFvyGxUrBLw8U/NqFnM9OTrnMqS9NRsFvuvbLnAcAYCAKfgDKdrWkZR1/b1k8N29jSY+WNGf7CklPknRa6TfuoOCHOqPg1y7kfHZyymVOfWkyCn7TtV/mPAAAA3HTDgBlO1fSNrYfplToe4mkl82/GBG3SNp0/m/bc5LeFhHnlRrV6tW9H8+qnfnHg9oeNYbu6e4z4Lea7nY4yG6mUdaf7mkHTT/r9irw1rcOHQTcXDXNeSPllMuc+tJk3bkfZzta5+VWdoysvwBQOQp+AEoVEffY3l/SVyStI+lTEXGh7fdKOi8iTqsosN6PZ9UOI/wwDUb4LbDHHntUHUJ5aprzRsoplzn1pckY4Tdd+2XOAwAwEAU/AKWLiDMlndn13Lv6TLt8MWKi4Idao+C3wCWXXCJJ2nbbbSuOpAQ1zXkj5ZTLnPrSZBT8pmu/zHkAAAai4AegndpS8OM0mmai4LfA61//eknS3NxctYGUoaY5b6SccplTX5qMgt907Zc5DwDAQNy0A0A7taXgx0F2M1HwaxdyPjs55TKnvjQZBb/p2i9zHgCAgSj4AWgnCn6oMwp+7ULOZyenXObUlyaj4Ddd+2XOAwAwEAU/AO1EwQ91RsGvXcj57OSUy5z6khMKfuO1X+Y8AAADUfAD0E4U/FBnFPzahZzPTk65zKkvTdUr7xT8xmu/zHkAAAbiph0A2omCH+qMgt8C73znO6sOoTw1zXkj5ZTLnPrSVJMW/CaZdrFR8AOA7FHwA9BOFPxQZxT8Fthll12qDqE8Nc15I+WUy5z60lSTFPyastwo+AFA9jilF0A7UfBDnVHwW2DlypVauXJl1WGUo6Y5b6SccplTX5qKgt/07Zc5DwDAQIzwA9BOq1f3fjyrduYfD2p71BjGibX7dQ6ym2mU9ad72kHTz7q9ChxwwAGSpLm5uUrjKEVNc95IOeUyp740Va+8j7MfrvNyG2e/ME37Zc4DADAQI/wAtBMj/FBnjPBrF3I+OznlMqe+NBUj/KZvv8x5AAAGouAHoJ3aUvDjV/VmouDXLuR8dnLKZU59aSoKftO3X+Y8AAADUfAD0E5tKfhxkN1MFPzahZzPTk65zKkvTUXBb/r2y5wHAGAgCn4A2omCH+qMgl+7kPPZySmXOfWlqSj4Td9+mfMAAAzETTsAtBMFP9QZBb8F3v/+91cdQnlqmvNGyimXOfWlqSj4Td9+mfMAAAxEwQ9AO1HwQ51R8FvgKU95StUhlKemOW+knHKZU1+aqg0Fv8Vov855AICMcUovgHai4Ic6K6tA1+CC3/e+9z1973vfqzqMctQ0542UUy5z6ktTtaXgV0acTckDAGSMEX4A2omCH+pslgW/cdf1mn5JO/jggyVJc3Nz1QZShprmvJFyymVOfWmqNhX87HLbBwAsOkb4AWgnCn6oMwp+7ULOZyenXObUl6ZqU8Gvae0DAIai4AegnSj4oc4o+LULOZ+dnHKZU1+aioJffdsHAAxFwQ9A6WzvavsS25fZPrDH62+xfZHtC2x/zfZDSw9q9erej2fVzvzjQW2PGsM4sXa/zkF2M42y/nRP22/6cdf1WX02MDpyPjs55TKnvjTVsG3qsPfUebmVHWdT8gAAGaPgB6BUtteR9FFJu0naTtJLbW/XNdn/StopIh4r6WRJHyw9MEb4oc4Y4dcu5Hx2csplTn1pKkb41bd9AMBQ3LQDQNmeKOmyiLhckmyfIGlPSRfNTxAR3+iY/geS9ik9qrYU/PhVvZko+C1wxBFHVB1CeWqa80bKKZc59aWpKPjVt30AwFAU/ACU7SGSruz4+ypJOw+Y/jWSzio1Iqk9BT8OspuJgt8CO+64Y9UhlKemOW+knHKZU1+aioJffdsHAAxFwQ9AbdjeR9JOkp7R5/X9JO0nSVtttdV0M6Pghzqj4LfA2WefLUnaZZddKo6kBDXNeSPllMuc+tJUFPzq2z4AYCgKfgDKdrWkZR1/b1k8txbbu0h6h6RnRMSdvRqKiGMkHSNJO+2003RHjxT8UGcU/BY4/PDDJVHwwxA55TKnvjQVBb/6tg8AGIqbdgAo27mStrH9MNvrSXqJpNM6J7D9OElHS3phRFy3KFEtVsFvFjFQ8GsfCn7tQs5nJ6dc5tSXpqLgV9/2AQBDUfADUKqIuEfS/pK+IuliSSdFxIW232v7hcVk/yLpvpI+b3ul7dP6NDfLwHo/nlU7o7RPwQ/9UPBrF3I+OznlMqe+NBUFv/q2DwAYilN6AZQuIs6UdGbXc+/qeLz45+gtdsHPnjwGCn7tQ8GvXcj57OSUy5z60lQU/OrbPgBgKEb4AWgnRvihzij4tQs5n52ccplTX5qKgl992wcADMUIPwDtRMEPdUbBb4Gjjz666hDKU9OcN1JOucypL01Fwa++7QMAhqLgB6CdVq/u/XhW7YzS/qgxjBNr9+scZDfT/HIcZd0ctn6Mu67P6rMxY9tuu23VIZSnpjlvpJxymVNfmmrYNnXYe+q83MqOsyl5AICMcUovgHZqywg/DrKbiRF+C5x++uk6/fTTqw6jHDXNeSPllMuc+tJUjPCrb/sAgKEY4QegndpS8OMgu5ko+C3w4Q9/WJK0xx57VBxJCWqa80bKKZc59aWpKPjVt30AwFCM8APQThT8UGcU/NqFnM9OTrnMqS9NRcGvvu0DAIai4AegnXIt+A16L5qDgl+7kPPZySmXOfWlqSj41bd9AMBQFPwAtFOOBb9Jvpignij4tQs5n52ccplTX5qKgl992wcADEXBD0A7UfBDnVHwaxdyPjs55TKnvjQVBb/6tg8AGIqbdgBoJwp+qDMKfgscd9xxVYdQnprmvJFyymVOfWkqCn71bR8AMBQFPwDtRMEPdUbBb4Fly5ZVHUJ5aprzRsoplzn1pako+NW3fQDAUJzSC6CdKPihzij4LXDiiSfqxBNPrDqMctQ0542UUy5z6ktTUfCrb/sAgKEY4QegnSj4oa5mXaDLpOB31FFHSZL23nvviiMpQU1z3kg55TKnvjQVBb/6tg8AGIoRfgDaafXq3o9n1c4o7Y8awyTTzeMgu3k6l9ko6+aw9WPcdX1Wnw2MjpzPTk65zKkvTTVsmzrsPXVebmXH2ZQ8AEDGKPgBaKe2jPDjILt5GOHXPuR8dnLKZU59aSpG+NW3fQDAUBT8ALRTWwp+HGQ3DwW/9iHns5NTLnPqS1NR8Ktv+wCAoSj4AWgnCn6oKwp+7UPOZyenXObUl6ai4Fff9gEAQ3HTDgDtRMEPdUXBr6eTTz656hDKU9OcN1JOucypL01Fwa++7QMAhqLgB6CdKPihrij49bTppptWHUJ5aprzRsoplzn1pako+NW3fQDAUJzSC6CdKPihrij49bRixQqtWLGi6jDKUdOcN1JOucypL01Fwa++7QMAhqLgB6B0tne1fYnty2wf2OP19W2fWLz+Q9tblx4UBT/UFQW/nij4YSQ55TKnvjQVBb/6tg8AGIqCH4BS2V5H0kcl7SZpO0kvtb1d12SvkXRTRDxS0r9J+ufSA4uQ7DWPp2lHSm11H9wOa3/UGMaZbj6WWfQN1ei3Tg2aftDynnV7mL1xlxH6y2n9Zb2o3iT71aYst7I/K03JAwBkjIIfgLI9UdJlEXF5RNwl6QRJe3ZNs6ekTxePT5b0bHv+KLQkEdI666x5PE07Umqru+A3rP1RYxhnuvlY7nOf4dOjnvqtU4OmH7R+zLo9zN64ywj95bT+sl5Ur3MZjLpeNWW5lf1ZaUoeACBjDjbAAEpk+0WSdo2I1xZ/v0LSzhGxf8c0Py2muar4+/+KaW7o1+5OS5bEeRtvPHlgv/udtO660qpV0oYbSuuvP1k7d9+d2lq6VLrzTmk+pptvTs+tWiXd735rCnCdbrstzXfVKmmjjVI8vdx6q7TBBsOnW706Tbt0aXr8+9+n9jfccLK+oRoR0i23rFl/HvCAwdOvWpW+UN1xR1pPNthg7df/8Ie0ri1dmqa5//0Ht3f77dJ6603/2Zix5bfdJkmam+ZzX1d33ZXy3b0dwfhquv5O5M4702d26dK0r9loo6ojap977knr1NKl6e9R1qvO94yyza3KLbekvqxalbY588W/Wclt/b3ppnJ/iAaAEnCXXgCNYXs/SftJ0nYbbyy98pXTNfi0p0mXXSZde+107dzvftKTniR99atrnrOl5z9f+p//SQe9/TznOdI556QD70F23VX67ndT4WaQddeV9thD+s1vpOuvly69dPR+oD6WLJF220360pdS8XaYxz8+FZkvv7z36xtsID372am9USxfLl14YVqH6uKUU9L/e+1VbRxlecADpJ12ks4+u+pImu8Zz5Auvli67rqqI5ne5ptLj3iE9J3vVB1Je220UdpX33WXdO65o20Xly5N29Ezzyw9vKk8//nS17+eCnNlePCDpUc9Svr2t8tpHwAwECP8AJTK9pMlHRoRzyv+PkiSIuIDHdN8pZjm+7aXSLpW0mYxYAO10047xXnnnVdu8ABqY9WqVZKkpfMjbQAAWDyM8APQOFzDD0DZzpW0je2H2V5P0kskndY1zWmSXlU8fpGkrw8q9gFon6VLl1LsAwAAAEbEKb0AShUR99jeX9JXJK0j6VMRcaHt90o6LyJOk/RJScfZvkzSb5WKggBwryOPPFKS9MY3vrHiSAAAAID645ReAI3EKb1AuyxfvlySNDc3V2kcAIBW4pReAI3DKb0AAAAAAABARij4AQAAAAAAABmh4AcAAAAAAABkhIIfAAAAAAAAkBFu2gGgkWxfL+mXVcdRgU0l3VB1EC1G/qtF/qtD7qtF/qtF/qtTl9zfEBG7Vh0EAIyDgh8ANIjt8yJip6rjaCvyXy3yXx1yXy3yXy3yXx1yDwCT45ReAAAAAAAAICMU/AAAAAAAAICMUPADgGY5puoAWo78V4v8V4fcV4v8V4v8V4fcA8CEuIYfAAAAAAAAkBFG+AEAAAAAAAAZoeAHAA1g+69sX2h7te2dul47yPZlti+x/byqYmwL24favtr2yuLf7lXHlDvbuxbr92W2D6w6nraxfYXtnxTr+3lVx5M725+yfZ3tn3Y89yDb/2P758X/D6wyxlz1yT3b/EVie5ntb9i+qDjm+bviedZ/AJgABT8AaIafSvpLSd/qfNL2dpJeIml7SbtKOtL2OosfXuv8W0TsWPw7s+pgclaszx+VtJuk7SS9tFjvsbieWazvOw2fFFNaobQ973SgpK9FxDaSvlb8jdlboYW5l9jmL5Z7JL01IraT9CRJf1ts71n/AWACFPwAoAEi4uKIuKTHS3tKOiEi7oyIX0i6TNITFzc6oFRPlHRZRFweEXdJOkFpvQeyFBHfkvTbrqf3lPTp4vGnJf3FYsbUFn1yj0USEddExI+Lx7dJuljSQ8T6DwAToeAHAM32EElXdvx9VfEcyrW/7QuK0784tahcrOPVC0lftf0j2/tVHUxLPTgirikeXyvpwVUG00Js8xeZ7a0lPU7SD8X6DwAToeAHADVh+2zbP+3xj9FMi2zIsjhK0iMk7SjpGkkfrjJWYBE8LSIer3Ra9d/a/vOqA2qziAilIiwWB9v8RWb7vpJOkXRARNza+RrrPwCMbknVAQAAkojYZYK3XS1pWcffWxbPYQqjLgvbH5d0RsnhtB3reMUi4uri/+tsf1HpNOtvDX4XZuw3tjePiGtsby7puqoDaouI+M38Y7b55bO9rlKx7zMR8YXiadZ/AJgAI/wAoNlOk/QS2+vbfpikbSSdU3FMWSu+bMz7f0o3VEF5zpW0je2H2V5P6SY1p1UcU2vY3sj2xvOPJT1XrPNVOE3Sq4rHr5L03xXG0ips8xePbUv6pKSLI+JfO15i/QeACTiNigYA1Jnt/yfpPyRtJulmSSsj4nnFa++Q9Gqlu9sdEBFnVRVnG9g+TunUrpB0haTXd1xbCCWwvbukIyStI+lTEfG+aiNqD9sPl/TF4s8lkj5L/stl+3OSlkvaVNJvJL1b0qmSTpK0laRfSnpxRHBziRnrk/vlYpu/KGw/TdK3Jf1E0uri6YOVruPH+g8AY6LgBwAAAAAAAGSEU3oBAAAAAACAjFDwAwAAAAAAADJCwQ8AAAAAAADICAU/AAAAAAAAICMU/AAAAAAAAICMUPADAAAAAAAAMkLBDwAAoKZsb2J7ZfHvWttXF49vt31kSfM8wPYrB7z+AtvvLWPeAAAAmA1HRNUxAAAAYAjbh0q6PSI+VOI8lkj6saTHR8Q9faZxMc1TI2JVWbEAAABgcozwAwAAaBjby22fUTw+1PanbX/b9i9t/6XtD9r+ie0v2163mO7PbH/T9o9sf8X25j2afpakH88X+2y/2fZFti+wfYIkRfq1eE7SCxalswAAABgbBT8AAIDme4RSse6Fko6X9I2IeIykOyQ9vyj6/YekF0XEn0n6lKT39WjnqZJ+1PH3gZIeFxGPlfSGjufPk/T0mfcCAAAAM7Gk6gAAAAAwtbMi4m7bP5G0jqQvF8//RNLWkraV9GhJ/5POyNU6kq7p0c7mki7u+PsCSZ+xfaqkUzuev07SFrMLHwAAALNEwQ8AAKD57pSkiFht++5Yc5Hm1UrHe5Z0YUQ8eUg7d0jaoOPv50v6c0l7SHqH7ccUp/tuUEwLAACAGuKUXgAAgPxdImkz20+WJNvr2t6+x3QXS3pkMc19JC2LiG9I+kdJ95d032K6R0n6aelRAwAAYCIU/AAAADIXEXdJepGkf7Z9vqSVkp7SY9KzlEb0Sem03+OL04T/V9K/R8TNxWvPlPSlMmMGAADA5LzmjA8AAAC0ne0vSnp7RPy8z+sPlvTZiHj24kYGAACAUVHwAwAAwL1sbyvpwRHxrT6vP0HS3RGxclEDAwAAwMgo+AEAAAAAAAAZ4Rp+AAAAAAAAQEYo+AEAAAAAAAAZoeAHAAAAAAAAZISCHwAAAAAAAJARCn4AAAAAAABARv4/ZudQJ3i8dg8AAAAASUVORK5CYII=", + "image/png": "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", "text/plain": [ "
" ] @@ -1669,10 +1688,8 @@ { "cell_type": "code", "execution_count": null, - "id": "93bf7940-a3bb-4ab9-abd3-95f6a2282775", - "metadata": { - "lines_to_next_cell": 2 - }, + "id": "1e6407b3", + "metadata": {}, "outputs": [], "source": [] } diff --git a/notebooks/py_scripts/07-downstream-analysis.py b/notebooks/py_scripts/07-downstream-analysis.py index d2a3f853..0281755c 100644 --- a/notebooks/py_scripts/07-downstream-analysis.py +++ b/notebooks/py_scripts/07-downstream-analysis.py @@ -8,9 +8,9 @@ # format_version: '1.5' # jupytext_version: 1.13.7 # kernelspec: -# display_name: venv-nwb +# display_name: Python 3.8.11 ('ele') # language: python -# name: venv-nwb +# name: python3 # --- # + [markdown] tags=[] @@ -110,9 +110,10 @@ alignment_key = (event.AlignmentEvent & 'alignment_name = "center_button"' ).fetch1('KEY') alignment_condition = {**clustering_key, **alignment_key, - 'trial_condition': 'ctrl_center_button'} + 'trial_condition': 'ctrl_center_button', + 'bin_size':.2} analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True) - +alignment_condition.pop('bin_size') analysis.SpikesAlignmentCondition.Trial.insert( (analysis.SpikesAlignmentCondition * ctrl_trials & alignment_condition).proj(), skip_duplicates=True) @@ -125,8 +126,11 @@ # Now, let's create another set for the stimulus condition. # + a set of trials of interest to perform the analysis on - `stim` trials stim_trials = trial.Trial & clustering_key & 'trial_type = "stim"' -alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'stim_center_button'} +alignment_condition = {**clustering_key, **alignment_key, + 'trial_condition': 'stim_center_button', + 'bin_size':.2} analysis.SpikesAlignmentCondition.insert1(alignment_condition, skip_duplicates=True) +alignment_condition.pop('bin_size') analysis.SpikesAlignmentCondition.Trial.insert( (analysis.SpikesAlignmentCondition * stim_trials & alignment_condition).proj(), skip_duplicates=True) @@ -151,7 +155,15 @@ # + a set of trials of interest to perform the analysis on - `stim` trials [markdown] # ## Visualize # -# We can visualize the results with the `plot` function. +# We can visualize the results with the `plot` function with our keys. +# - + +clustering_key = (ephys.CuratedClustering + & {'subject': 'subject6', 'session_datetime': '2021-01-15 11:16:38', + 'insertion_number': 0} + ).fetch1('KEY') +alignment_key = (event.AlignmentEvent & 'alignment_name = "center_button"' + ).fetch1('KEY') # + a set of trials of interest to perform the analysis on - `stim` trials alignment_condition = {**clustering_key, **alignment_key, 'trial_condition': 'ctrl_center_button'} @@ -162,4 +174,3 @@ analysis.SpikesAlignment().plot(alignment_condition, unit=2); # - - diff --git a/workflow_array_ephys/analysis.py b/workflow_array_ephys/analysis.py index 62d3a487..ecae583e 100644 --- a/workflow_array_ephys/analysis.py +++ b/workflow_array_ephys/analysis.py @@ -3,6 +3,7 @@ from .pipeline import db_prefix, ephys, trial +__all__ = ["db_prefix", "ephys", "trial", "event"] schema = dj.schema(db_prefix + 'analysis') @@ -101,6 +102,7 @@ def plot(self, key, unit, axs=None): if axs is None: fig, axs = plt.subplots(2, 1, figsize=(12, 8)) + bin_size = (SpikesAlignmentCondition & key).fetch1("bin_size") trial_ids, aligned_spikes = (self.AlignedTrialSpikes & key & {'unit': unit} ).fetch('trial_id', 'aligned_spike_times') @@ -111,7 +113,7 @@ def plot(self, key, unit, axs=None): plot_psth._plot_spike_raster(aligned_spikes, trial_ids=trial_ids, ax=axs[0], title=f'{dict(**key, unit=unit)}', xlim=xlim) - plot_psth._plot_psth(psth, psth_edges, ax=axs[1], + plot_psth._plot_psth(psth, psth_edges, bin_size, ax=axs[1], title='', xlim=xlim) return fig diff --git a/workflow_array_ephys/plotting/plot_psth.py b/workflow_array_ephys/plotting/plot_psth.py index 4d8182a7..30297dbd 100644 --- a/workflow_array_ephys/plotting/plot_psth.py +++ b/workflow_array_ephys/plotting/plot_psth.py @@ -16,7 +16,7 @@ def _plot_spike_raster(aligned_spikes, trial_ids=None, vlines=[0], ax=None, titl assert len(raster) == len(trial_ids) - ax.plot(raster, trial_ids, 'r.', markersize=1) + ax.plot(raster, trial_ids, 'ro', markersize=4) for x in vlines: ax.axvline(x=x, linestyle='--', color='k') @@ -24,15 +24,15 @@ def _plot_spike_raster(aligned_spikes, trial_ids=None, vlines=[0], ax=None, titl ax.set_ylabel('Trial (#)') if xlim: ax.set_xlim(xlim) - ax.set_axis_off() + # ax.set_axis_off() ax.set_title(title) -def _plot_psth(psth, psth_edges, vlines=[0], ax=None, title='', xlim=None): +def _plot_psth(psth, psth_edges, bin_size, vlines=[0], ax=None, title='', xlim=None): if not ax: fig, ax = plt.subplots(1, 1) - ax.plot(psth_edges, psth, 'r') + ax.bar(psth_edges, psth, width=bin_size, edgecolor="black", align="edge") for x in vlines: ax.axvline(x=x, linestyle='--', color='k') From 6ee408a366a8c35d28eaad0df940e3d98203a393 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Wed, 15 Jun 2022 11:50:36 -0500 Subject: [PATCH 56/59] Add issue template, #8 --- .github/ISSUE_TEMPLATE/bug_report.md | 39 ++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 ++ .github/ISSUE_TEMPLATE/feature_request.md | 57 +++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..31fe9fcf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,39 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: 'bug' +assignees: '' + +--- + +## Bug Report + +### Description + +A clear and concise description of what is the overall operation that is intended to be +performed that resulted in an error. + +### Reproducibility +Include: +- OS (WIN | MACOS | Linux) +- DataJoint Element Version +- MySQL Version +- MySQL Deployment Strategy (local-native | local-docker | remote) +- Minimum number of steps to reliably reproduce the issue +- Complete error stack as a result of evaluating the above steps + +### Expected Behavior +A clear and concise description of what you expected to happen. + +### Screenshots +If applicable, add screenshots to help explain your problem. + +### Additional Research and Context +Add any additional research or context that was conducted in creating this report. + +For example: +- Related GitHub issues and PR's either within this repository or in other relevant + repositories. +- Specific links to specific lines or a focus within source code. +- Relevant summary of Maintainers development meetings, milestones, projects, etc. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..d31fbace --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: DataJoint Contribution Guideline + url: https://docs.datajoint.org/python/community/02-Contribute.html + about: Please make sure to review the DataJoint Contribution Guidelines \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..1f2b784e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,57 @@ +--- +name: Feature request +about: Suggest an idea for a new feature +title: '' +labels: 'enhancement' +assignees: '' + +--- + +## Feature Request + +### Problem + +A clear and concise description how this idea has manifested and the context. Elaborate +on the need for this feature and/or what could be improved. Ex. I'm always frustrated +when [...] + +### Requirements + +A clear and concise description of the requirements to satisfy the new feature. Detail +what you expect from a successful implementation of the feature. Ex. When using this +feature, it should [...] + +### Justification + +Provide the key benefits in making this a supported feature. Ex. Adding support for this +feature would ensure [...] + +### Alternative Considerations + +Do you currently have a work-around for this? Provide any alternative solutions or +features you've considered. + +### Related Errors +Add any errors as a direct result of not exposing this feature. + +Please include steps to reproduce provided errors as follows: +- OS (WIN | MACOS | Linux) +- DataJoint Element Version +- MySQL Version +- MySQL Deployment Strategy (local-native | local-docker | remote) +- Minimum number of steps to reliably reproduce the issue +- Complete error stack as a result of evaluating the above steps + +### Screenshots +If applicable, add screenshots to help explain your feature. + +### Additional Research and Context +Add any additional research or context that was conducted in creating this feature request. + +For example: +- Related GitHub issues and PR's either within this repository or in other relevant + repositories. +- Specific links to specific lines or a focus within source code. +- Relevant summary of Maintainers development meetings, milestones, projects, etc. +- Any additional supplemental web references or links that would further justify this + feature request. From 80892b28b5210ce4574d4c196edc83df994b214a Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 16 Jun 2022 07:31:20 -0500 Subject: [PATCH 57/59] Add Code of Conduct --- CODE_OF_CONDUCT.md | 133 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..45d257b2 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,133 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[INSERT CONTACT METHOD]. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations From 4cb9a699e75b838a9e6d2fd1f5f1df793a44d5bd Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Fri, 17 Jun 2022 08:52:45 -0500 Subject: [PATCH 58/59] Add contact info to Code of Conduct --- CODE_OF_CONDUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 45d257b2..684cf81d 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -61,7 +61,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -[INSERT CONTACT METHOD]. +[Support@DataJoint.com](mailto:support@datajoint.com). All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the From 637e50f73403f8c2acd804426da540f109ee8d14 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 23 Jun 2022 16:41:48 -0500 Subject: [PATCH 59/59] Remove order constraints in test_pipeline_generation --- tests/test_pipeline_generation.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/tests/test_pipeline_generation.py b/tests/test_pipeline_generation.py index f4c32470..12d35cd7 100644 --- a/tests/test_pipeline_generation.py +++ b/tests/test_pipeline_generation.py @@ -2,19 +2,15 @@ def test_generate_pipeline(pipeline): - subject = pipeline['subject'] - ephys = pipeline['ephys'] - probe = pipeline['probe'] - session = pipeline['session'] - - subject_tbl, *_ = session.Session.parents(as_objects=True) + subject = pipeline["subject"] + ephys = pipeline["ephys"] + probe = pipeline["probe"] + session = pipeline["session"] # test elements connection from lab, subject to Session - assert subject_tbl.full_table_name == subject.Subject.full_table_name + assert subject.Subject.full_table_name in session.Session.parents() # test elements connection from Session to probe, ephys - session_tbl, probe_tbl = ephys.ProbeInsertion.parents(as_objects=True) - assert session_tbl.full_table_name == session.Session.full_table_name - assert probe_tbl.full_table_name == probe.Probe.full_table_name - assert 'spike_times' in (ephys.CuratedClustering.Unit.heading. - secondary_attributes) + assert session.Session.full_table_name in ephys.ProbeInsertion.parents() + assert probe.Probe.full_table_name in ephys.ProbeInsertion.parents() + assert "spike_times" in (ephys.CuratedClustering.Unit.heading.secondary_attributes)