-
Notifications
You must be signed in to change notification settings - Fork 151
/
Copy pathbehavior_session.py
1679 lines (1528 loc) · 65.2 KB
/
behavior_session.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import datetime
import pathlib
import warnings
from typing import Any, Dict, List, Optional, Type
import numpy as np
import pandas as pd
import pynwb
import pytz
from allensdk import OneResultExpectedError
from allensdk.brain_observatory import sync_utilities
from allensdk.brain_observatory.behavior.data_files import (
BehaviorStimulusFile,
MappingStimulusFile,
ReplayStimulusFile,
SyncFile,
)
from allensdk.brain_observatory.behavior.data_files.eye_tracking_file import (
EyeTrackingFile,
)
from allensdk.brain_observatory.behavior.data_files.eye_tracking_metadata_file import ( # noqa: E501
EyeTrackingMetadataFile,
)
from allensdk.brain_observatory.behavior.data_files.eye_tracking_video import (
EyeTrackingVideo,
)
from allensdk.brain_observatory.behavior.data_files.stimulus_file import (
StimulusFileLookup,
stimulus_lookup_from_json,
)
from allensdk.brain_observatory.behavior.data_objects import (
BehaviorSessionId,
RunningAcquisition,
RunningSpeed,
StimulusTimestamps,
)
from allensdk.brain_observatory.behavior.data_objects.eye_tracking.eye_tracking_table import ( # noqa: E501
EyeTrackingTable,
)
from allensdk.brain_observatory.behavior.data_objects.eye_tracking.rig_geometry import ( # noqa: E501
RigGeometry as EyeTrackingRigGeometry,
)
from allensdk.brain_observatory.behavior.data_objects.licks import Licks
from allensdk.brain_observatory.behavior.data_objects.metadata.behavior_metadata.behavior_metadata import ( # noqa: E501
BehaviorMetadata,
get_expt_description,
)
from allensdk.brain_observatory.behavior.data_objects.metadata.behavior_metadata.date_of_acquisition import ( # noqa: E501
DateOfAcquisition,
)
from allensdk.brain_observatory.behavior.data_objects.metadata.behavior_metadata.project_code import ( # noqa: E501
ProjectCode,
)
from allensdk.brain_observatory.behavior.data_objects.rewards import Rewards
from allensdk.brain_observatory.behavior.data_objects.stimuli.presentations import ( # noqa: E501
Presentations,
)
from allensdk.brain_observatory.behavior.data_objects.stimuli.stimuli import (
Stimuli,
)
from allensdk.brain_observatory.behavior.data_objects.stimuli.templates import ( # noqa: E501
Templates,
)
from allensdk.brain_observatory.behavior.data_objects.task_parameters import (
TaskParameters,
)
from allensdk.brain_observatory.behavior.data_objects.trials.trials import (
Trials,
)
from allensdk.brain_observatory.sync_dataset import Dataset as SyncDataset
from allensdk.core import (
DataObject,
JsonReadableInterface,
LimsReadableInterface,
NwbReadableInterface,
NwbWritableInterface,
)
from allensdk.core.auth_config import LIMS_DB_CREDENTIAL_MAP
from allensdk.internal.api import PostgresQueryMixin, db_connection_creator
from pynwb import NWBFile
class BehaviorSession(
DataObject,
LimsReadableInterface,
NwbReadableInterface,
JsonReadableInterface,
NwbWritableInterface,
):
"""Represents data from a single Visual Behavior behavior session.
Initialize by using class methods `from_lims` or `from_nwb_path`.
"""
def __init__(
self,
behavior_session_id: BehaviorSessionId,
stimulus_timestamps: StimulusTimestamps,
running_acquisition: RunningAcquisition,
raw_running_speed: RunningSpeed,
running_speed: RunningSpeed,
licks: Licks,
rewards: Rewards,
stimuli: Stimuli,
task_parameters: TaskParameters,
trials: Trials,
metadata: BehaviorMetadata,
date_of_acquisition: DateOfAcquisition,
eye_tracking_table: Optional[EyeTrackingTable] = None,
eye_tracking_rig_geometry: Optional[EyeTrackingRigGeometry] = None,
):
super().__init__(
name="behavior_session", value=None, is_value_self=True
)
self._behavior_session_id = behavior_session_id
self._licks = licks
self._rewards = rewards
self._running_acquisition = running_acquisition
self._running_speed = running_speed
self._raw_running_speed = raw_running_speed
self._stimuli = stimuli
self._stimulus_timestamps = stimulus_timestamps
self._task_parameters = task_parameters
self._trials = trials
self._metadata = metadata
self._date_of_acquisition = date_of_acquisition
self._eye_tracking = eye_tracking_table
self._eye_tracking_rig_geometry = eye_tracking_rig_geometry
# ==================== class and utility methods ======================
@classmethod
def from_json(
cls,
session_data: dict,
read_stimulus_presentations_table_from_file=False,
stimulus_presentation_columns: Optional[List[str]] = None,
stimulus_presentation_exclude_columns: Optional[List[str]] = None,
eye_tracking_z_threshold: float = 3.0,
eye_tracking_dilation_frames: int = 2,
eye_tracking_drop_frames: bool = False,
sync_file_permissive: bool = False,
running_speed_load_from_multiple_stimulus_files: bool = False,
) -> "BehaviorSession":
"""
Parameters
----------
session_data
Dict of input data necessary to construct a session
read_stimulus_presentations_table_from_file
Whether to read the stimulus table from a file rather than
construct it here
stimulus_presentation_columns
Columns to include in the stimulus presentation table. This also
specifies the order of the columns.
stimulus_presentation_exclude_columns
Optional list of columns to exclude from stimulus presentations
table
eye_tracking_z_threshold
See `BehaviorSession.from_nwb`
eye_tracking_dilation_frames
See `BehaviorSession.from_nwb`
eye_tracking_drop_frames
See `drop_frames` arg in `allensdk.brain_observatory.behavior.
data_objects.eye_tracking.eye_tracking_table.EyeTrackingTable.
from_data_file`
sync_file_permissive
See `permissive` arg in `SyncFile` constructor
running_speed_load_from_multiple_stimulus_files
Whether to load running speed from multiple stimulus files
If False, will just load from a single behavior stimulus file
Returns
-------
`BehaviorSession` instance
"""
if "monitor_delay" not in session_data:
monitor_delay = cls._get_monitor_delay()
else:
monitor_delay = session_data["monitor_delay"]
behavior_session_id = BehaviorSessionId.from_json(
dict_repr=session_data
)
stimulus_file_lookup = stimulus_lookup_from_json(
dict_repr=session_data
)
if "sync_file" in session_data:
sync_file = SyncFile.from_json(
dict_repr=session_data, permissive=sync_file_permissive
)
else:
sync_file = None
if running_speed_load_from_multiple_stimulus_files:
running_acquisition = (
RunningAcquisition.from_multiple_stimulus_files(
behavior_stimulus_file=(
BehaviorStimulusFile.from_json(dict_repr=session_data)
),
mapping_stimulus_file=MappingStimulusFile.from_json(
dict_repr=session_data
),
replay_stimulus_file=ReplayStimulusFile.from_json(
dict_repr=session_data
),
sync_file=SyncFile.from_json(dict_repr=session_data),
)
)
raw_running_speed = RunningSpeed.from_multiple_stimulus_files(
behavior_stimulus_file=(
BehaviorStimulusFile.from_json(dict_repr=session_data)
),
mapping_stimulus_file=MappingStimulusFile.from_json(
dict_repr=session_data
),
replay_stimulus_file=ReplayStimulusFile.from_json(
dict_repr=session_data
),
sync_file=SyncFile.from_json(dict_repr=session_data),
filtered=False,
)
running_speed = RunningSpeed.from_multiple_stimulus_files(
behavior_stimulus_file=(
BehaviorStimulusFile.from_json(dict_repr=session_data)
),
mapping_stimulus_file=MappingStimulusFile.from_json(
dict_repr=session_data
),
replay_stimulus_file=ReplayStimulusFile.from_json(
dict_repr=session_data
),
sync_file=SyncFile.from_json(dict_repr=session_data),
filtered=True,
)
else:
behavior_stimulus_file = (
stimulus_file_lookup.behavior_stimulus_file
)
running_acquisition = RunningAcquisition.from_stimulus_file(
behavior_stimulus_file=behavior_stimulus_file,
sync_file=sync_file,
)
raw_running_speed = RunningSpeed.from_stimulus_file(
behavior_stimulus_file=behavior_stimulus_file,
sync_file=sync_file,
filtered=False,
)
running_speed = RunningSpeed.from_stimulus_file(
behavior_stimulus_file=behavior_stimulus_file,
sync_file=sync_file,
)
metadata = BehaviorMetadata.from_json(dict_repr=session_data)
(
stimulus_timestamps,
licks,
rewards,
stimuli,
task_parameters,
trials,
) = cls._read_data_from_stimulus_file(
stimulus_file_lookup=stimulus_file_lookup,
behavior_session_id=behavior_session_id.value,
sync_file=sync_file,
monitor_delay=monitor_delay,
include_stimuli=(not read_stimulus_presentations_table_from_file),
stimulus_presentation_columns=stimulus_presentation_columns,
)
if read_stimulus_presentations_table_from_file:
stimuli = Stimuli(
presentations=Presentations.from_path(
path=session_data["stim_table_file"],
behavior_session_id=session_data["behavior_session_id"],
exclude_columns=stimulus_presentation_exclude_columns,
trials=trials,
),
templates=Templates.from_stimulus_file(
stimulus_file=stimulus_file_lookup.behavior_stimulus_file
),
)
date_of_acquisition = DateOfAcquisition.from_json(
dict_repr=session_data
).validate(
stimulus_file=stimulus_file_lookup.behavior_stimulus_file,
behavior_session_id=behavior_session_id.value,
)
try:
eye_tracking_file = EyeTrackingFile.from_json(
dict_repr=session_data
)
except KeyError:
eye_tracking_file = None
if eye_tracking_file is None:
# Return empty data to match what is returned by from_nwb.
eye_tracking_table = EyeTrackingTable(
eye_tracking=EyeTrackingTable._get_empty_df()
)
eye_tracking_rig_geometry = None
else:
try:
eye_tracking_metadata_file = EyeTrackingMetadataFile.from_json(
dict_repr=session_data
)
except KeyError:
eye_tracking_metadata_file = None
eye_tracking_table = cls._read_eye_tracking_table(
eye_tracking_file=eye_tracking_file,
eye_tracking_metadata_file=eye_tracking_metadata_file,
sync_file=sync_file,
z_threshold=eye_tracking_z_threshold,
dilation_frames=eye_tracking_dilation_frames,
)
eye_tracking_rig_geometry = EyeTrackingRigGeometry.from_json(
dict_repr=session_data
)
return cls(
behavior_session_id=behavior_session_id,
stimulus_timestamps=stimulus_timestamps,
running_acquisition=running_acquisition,
raw_running_speed=raw_running_speed,
running_speed=running_speed,
metadata=metadata,
licks=licks,
rewards=rewards,
stimuli=stimuli,
task_parameters=task_parameters,
trials=trials,
date_of_acquisition=date_of_acquisition,
eye_tracking_table=eye_tracking_table,
eye_tracking_rig_geometry=eye_tracking_rig_geometry,
)
@classmethod
def from_lims(
cls,
behavior_session_id: int,
lims_db: Optional[PostgresQueryMixin] = None,
sync_file: Optional[SyncFile] = None,
monitor_delay: Optional[float] = None,
date_of_acquisition: Optional[DateOfAcquisition] = None,
eye_tracking_z_threshold: float = 3.0,
eye_tracking_dilation_frames: int = 2,
) -> "BehaviorSession":
"""
Parameters
----------
behavior_session_id : int
Behavior session id
lims_db : PostgresQueryMixin, Optional
Database connection. If not provided will create a new one.
sync_file : SyncFile, Optional
If provided, will be used to compute the stimulus timestamps
associated with this session. Otherwise, the stimulus timestamps
will be computed from the stimulus file.
monitor_delay : float, Optional
Monitor delay. If not provided, will use an estimate.
To provide this value, see for example
allensdk.brain_observatory.behavior.data_objects.stimuli.util.
calculate_monitor_delay
date_of_acquisition : DateOfAcquisition, Optional
Date of acquisition. If not provided, will read from
behavior_sessions table.
eye_tracking_z_threshold : float
See `BehaviorSession.from_nwb`, default 3.0
eye_tracking_dilation_frames : int
See `BehaviorSession.from_nwb`, default 2
Returns
-------
`BehaviorSession` instance
"""
if lims_db is None:
lims_db = db_connection_creator(
fallback_credentials=LIMS_DB_CREDENTIAL_MAP
)
if monitor_delay is None:
monitor_delay = cls._get_monitor_delay()
if sync_file is None:
try:
sync_file = SyncFile.from_lims(
db=lims_db, behavior_session_id=behavior_session_id
)
except OneResultExpectedError:
sync_file = None
behavior_session_id = BehaviorSessionId(behavior_session_id)
stimulus_file_lookup = StimulusFileLookup()
stimulus_file_lookup.behavior_stimulus_file = (
BehaviorStimulusFile.from_lims(
db=lims_db, behavior_session_id=behavior_session_id.value
)
)
running_acquisition = RunningAcquisition.from_stimulus_file(
behavior_stimulus_file=stimulus_file_lookup.behavior_stimulus_file,
sync_file=sync_file,
)
raw_running_speed = RunningSpeed.from_stimulus_file(
behavior_stimulus_file=stimulus_file_lookup.behavior_stimulus_file,
sync_file=sync_file,
filtered=False,
)
running_speed = RunningSpeed.from_stimulus_file(
behavior_stimulus_file=stimulus_file_lookup.behavior_stimulus_file,
sync_file=sync_file,
filtered=True,
)
behavior_metadata = BehaviorMetadata.from_lims(
behavior_session_id=behavior_session_id, lims_db=lims_db
)
(
stimulus_timestamps,
licks,
rewards,
stimuli,
task_parameters,
trials,
) = cls._read_data_from_stimulus_file(
behavior_session_id=behavior_session_id.value,
stimulus_file_lookup=stimulus_file_lookup,
sync_file=sync_file,
monitor_delay=monitor_delay,
project_code=ProjectCode.from_lims(
behavior_session_id=behavior_session_id.value, lims_db=lims_db
),
)
if date_of_acquisition is None:
date_of_acquisition = DateOfAcquisition.from_lims(
behavior_session_id=behavior_session_id.value, lims_db=lims_db
)
date_of_acquisition = date_of_acquisition.validate(
stimulus_file=stimulus_file_lookup.behavior_stimulus_file,
behavior_session_id=behavior_session_id.value,
)
eye_tracking_file = EyeTrackingFile.from_lims(
db=lims_db, behavior_session_id=behavior_session_id.value
)
if eye_tracking_file is None:
# Return empty data to match what is returned by from_nwb.
eye_tracking_table = EyeTrackingTable(
eye_tracking=EyeTrackingTable._get_empty_df()
)
eye_tracking_rig_geometry = None
else:
eye_tracking_video = EyeTrackingVideo.from_lims(
db=lims_db, behavior_session_id=behavior_session_id.value
)
eye_tracking_metadata_file = None
eye_tracking_table = cls._read_eye_tracking_table(
eye_tracking_file=eye_tracking_file,
eye_tracking_metadata_file=eye_tracking_metadata_file,
eye_tracking_video=eye_tracking_video,
sync_file=sync_file,
z_threshold=eye_tracking_z_threshold,
dilation_frames=eye_tracking_dilation_frames,
)
eye_tracking_rig_geometry = EyeTrackingRigGeometry.from_lims(
behavior_session_id=behavior_session_id.value, lims_db=lims_db
)
return BehaviorSession(
behavior_session_id=behavior_session_id,
stimulus_timestamps=stimulus_timestamps,
running_acquisition=running_acquisition,
raw_running_speed=raw_running_speed,
running_speed=running_speed,
metadata=behavior_metadata,
licks=licks,
rewards=rewards,
stimuli=stimuli,
task_parameters=task_parameters,
trials=trials,
date_of_acquisition=date_of_acquisition,
eye_tracking_table=eye_tracking_table,
eye_tracking_rig_geometry=eye_tracking_rig_geometry,
)
@classmethod
def from_nwb(
cls,
nwbfile: NWBFile,
add_is_change_to_stimulus_presentations_table=True,
eye_tracking_z_threshold: float = 3.0,
eye_tracking_dilation_frames: int = 2,
) -> "BehaviorSession":
"""
Parameters
----------
nwbfile
add_is_change_to_stimulus_presentations_table: Whether to add a column
denoting whether the stimulus presentation represented a change
event. May not be needed in case this column is precomputed
eye_tracking_z_threshold : float, optional
The z-threshold when determining which frames likely contain
outliers for eye or pupil areas. Influences which frames
are considered 'likely blinks'. By default 3.0
eye_tracking_dilation_frames : int, optional
Determines the number of adjacent frames that will be marked
as 'likely_blink' when performing blink detection for
`eye_tracking` data, by default 2
Returns
-------
"""
behavior_session_id = BehaviorSessionId.from_nwb(nwbfile)
stimulus_timestamps = StimulusTimestamps.from_nwb(nwbfile)
running_acquisition = RunningAcquisition.from_nwb(nwbfile)
raw_running_speed = RunningSpeed.from_nwb(nwbfile, filtered=False)
running_speed = RunningSpeed.from_nwb(nwbfile)
metadata = BehaviorMetadata.from_nwb(nwbfile)
licks = Licks.from_nwb(nwbfile=nwbfile)
rewards = Rewards.from_nwb(nwbfile=nwbfile)
stimuli = Stimuli.from_nwb(
nwbfile=nwbfile,
add_is_change_to_presentations_table=(
add_is_change_to_stimulus_presentations_table
),
)
task_parameters = TaskParameters.from_nwb(nwbfile=nwbfile)
trials = cls._trials_class().from_nwb(nwbfile=nwbfile)
date_of_acquisition = DateOfAcquisition.from_nwb(nwbfile=nwbfile)
with warnings.catch_warnings():
warnings.filterwarnings(
action="ignore",
message="This nwb file with identifier ",
category=UserWarning,
)
eye_tracking_rig_geometry = EyeTrackingRigGeometry.from_nwb(
nwbfile=nwbfile
)
with warnings.catch_warnings():
warnings.filterwarnings(
action="ignore",
message="This nwb file with identifier ",
category=UserWarning,
)
eye_tracking_table = EyeTrackingTable.from_nwb(
nwbfile=nwbfile,
z_threshold=eye_tracking_z_threshold,
dilation_frames=eye_tracking_dilation_frames,
)
return cls(
behavior_session_id=behavior_session_id,
stimulus_timestamps=stimulus_timestamps,
running_acquisition=running_acquisition,
raw_running_speed=raw_running_speed,
running_speed=running_speed,
metadata=metadata,
licks=licks,
rewards=rewards,
stimuli=stimuli,
task_parameters=task_parameters,
trials=trials,
date_of_acquisition=date_of_acquisition,
eye_tracking_table=eye_tracking_table,
eye_tracking_rig_geometry=eye_tracking_rig_geometry,
)
@classmethod
def from_nwb_path(cls, nwb_path: str, **kwargs) -> "BehaviorSession":
"""
Parameters
----------
nwb_path
Path to nwb file
kwargs
Kwargs to be passed to `from_nwb`
Returns
-------
An instantiation of a `BehaviorSession`
"""
nwb_path = str(nwb_path)
with pynwb.NWBHDF5IO(nwb_path, "r", load_namespaces=True) as read_io:
nwbfile = read_io.read()
return cls.from_nwb(nwbfile=nwbfile, **kwargs)
def to_nwb(
self,
add_metadata=True,
include_experiment_description=True,
stimulus_presentations_stimulus_column_name: str = "stimulus_name",
) -> NWBFile:
"""
Parameters
----------
add_metadata
Set this to False to prevent adding metadata to the nwb
instance.
include_experiment_description: Whether to include a description of the
experiment in the nwbfile
stimulus_presentations_stimulus_column_name: Name of the column
denoting the stimulus name in the presentations table
"""
if include_experiment_description:
experiment_description = get_expt_description(
session_type=self._get_session_type()
)
else:
experiment_description = None
nwbfile = NWBFile(
session_description=self._get_session_type(),
identifier=self._get_identifier(),
session_start_time=self._date_of_acquisition.value,
file_create_date=pytz.utc.localize(datetime.datetime.now()),
institution="Allen Institute for Brain Science",
keywords=self._get_keywords(),
experiment_description=experiment_description,
)
self._stimulus_timestamps.to_nwb(nwbfile=nwbfile)
self._running_acquisition.to_nwb(nwbfile=nwbfile)
self._raw_running_speed.to_nwb(nwbfile=nwbfile)
self._running_speed.to_nwb(nwbfile=nwbfile)
if add_metadata:
self._metadata.to_nwb(nwbfile=nwbfile)
self._licks.to_nwb(nwbfile=nwbfile)
self._rewards.to_nwb(nwbfile=nwbfile)
self._stimuli.to_nwb(
nwbfile=nwbfile,
presentations_stimulus_column_name=(
stimulus_presentations_stimulus_column_name
),
)
self._task_parameters.to_nwb(nwbfile=nwbfile)
self._trials.to_nwb(nwbfile=nwbfile)
if self._eye_tracking is not None:
self._eye_tracking.to_nwb(nwbfile=nwbfile)
if self._eye_tracking_rig_geometry is not None:
self._eye_tracking_rig_geometry.to_nwb(nwbfile=nwbfile)
return nwbfile
def list_data_attributes_and_methods(self) -> List[str]:
"""Convenience method for end-users to list attributes and methods
that can be called to access data for a BehaviorSession.
NOTE: Because BehaviorOphysExperiment inherits from BehaviorSession,
this method will also be available there.
Returns
-------
List[str]
A list of attributes and methods that end-users can access or call
to get data.
"""
attrs_and_methods_to_ignore: set = {
"from_json",
"from_lims",
"from_nwb_path",
"list_data_attributes_and_methods",
}
attrs_and_methods_to_ignore.update(dir(NwbReadableInterface))
attrs_and_methods_to_ignore.update(dir(NwbWritableInterface))
attrs_and_methods_to_ignore.update(dir(DataObject))
class_dir = dir(self)
attrs_and_methods = [
r
for r in class_dir
if (r not in attrs_and_methods_to_ignore and not r.startswith("_"))
]
return attrs_and_methods
# ========================= 'get' methods ==========================
def get_reward_rate(self) -> np.ndarray:
"""Get the reward rate of the subject for the task calculated over a
25 trial rolling window and provides a measure of the rewards
earned per unit time (in units of rewards/minute).
Returns
-------
np.ndarray
The reward rate (rewards/minute) of the subject for the
task calculated over a 25 trial rolling window.
"""
return self._trials.calculate_reward_rate()
def get_rolling_performance_df(self) -> pd.DataFrame:
"""Return a DataFrame containing trial by trial behavior response
performance metrics.
Returns
-------
pd.DataFrame
A pandas DataFrame containing:
trials_id [index]: (int)
Index of the trial. All trials, including aborted trials,
are assigned an index starting at 0 for the first trial.
reward_rate: (float)
Rewards earned in the previous 25 trials, normalized by
the elapsed time of the same 25 trials. Units are
rewards/minute.
hit_rate_raw: (float)
Fraction of go trials where the mouse licked in the
response window, calculated over the previous 100
non-aborted trials. Without trial count correction applied.
hit_rate: (float)
Fraction of go trials where the mouse licked in the
response window, calculated over the previous 100
non-aborted trials. With trial count correction applied.
false_alarm_rate_raw: (float)
Fraction of catch trials where the mouse licked in the
response window, calculated over the previous 100
non-aborted trials. Without trial count correction applied.
false_alarm_rate: (float)
Fraction of catch trials where the mouse licked in
the response window, calculated over the previous 100
non-aborted trials. Without trial count correction applied.
rolling_dprime: (float)
d prime calculated using the rolling hit_rate and
rolling false_alarm _rate.
"""
return self._trials.rolling_performance
def get_performance_metrics(
self, engaged_trial_reward_rate_threshold: float = 2.0
) -> dict:
"""Get a dictionary containing a subject's behavior response
summary data.
Parameters
----------
engaged_trial_reward_rate_threshold : float, optional
The number of rewards per minute that needs to be attained
before a subject is considered 'engaged', by default 2.0
Returns
-------
dict
Returns a dict of performance metrics with the following fields:
trial_count: (int)
The length of the trial dataframe
(including all 'go', 'catch', and 'aborted' trials)
go_trial_count: (int)
Number of 'go' trials in a behavior session
catch_trial_count: (int)
Number of 'catch' trial types during a behavior session
hit_trial_count: (int)
Number of trials with a hit behavior response
type in a behavior session
miss_trial_count: (int)
Number of trials with a miss behavior response
type in a behavior session
false_alarm_trial_count: (int)
Number of trials where the mouse had a false alarm
behavior response
correct_reject_trial_count: (int)
Number of trials with a correct reject behavior
response during a behavior session
auto_reward_count:
Number of trials where the mouse received an auto
reward of water.
earned_reward_count:
Number of trials where the mouse was eligible to receive a
water reward ('go' trials) and did receive an earned
water reward
total_reward_count:
Number of trials where the mouse received a
water reward (earned or auto rewarded)
total_reward_volume: (float)
Volume of all water rewards received during a
behavior session (earned and auto rewarded)
maximum_reward_rate: (float)
The peak of the rolling reward rate (rewards/minute)
engaged_trial_count: (int)
Number of trials where the mouse is engaged
(reward rate > 2 rewards/minute)
mean_hit_rate: (float)
The mean of the rolling hit_rate
mean_hit_rate_uncorrected:
The mean of the rolling hit_rate_raw
mean_hit_rate_engaged: (float)
The mean of the rolling hit_rate, excluding epochs
when the rolling reward rate was below 2 rewards/minute
mean_false_alarm_rate: (float)
The mean of the rolling false_alarm_rate, excluding
epochs when the rolling reward rate was below 2
rewards/minute
mean_false_alarm_rate_uncorrected: (float)
The mean of the rolling false_alarm_rate_raw
mean_false_alarm_rate_engaged: (float)
The mean of the rolling false_alarm_rate,
excluding epochs when the rolling reward rate
was below 2 rewards/minute
mean_dprime: (float)
The mean of the rolling d_prime
mean_dprime_engaged: (float)
The mean of the rolling d_prime, excluding
epochs when the rolling reward rate was
below 2 rewards/minute
max_dprime: (float)
The peak of the rolling d_prime
max_dprime_engaged: (float)
The peak of the rolling d_prime, excluding epochs
when the rolling reward rate was below 2 rewards/minute
"""
performance_metrics = {
"trial_count": self._trials.trial_count,
"go_trial_count": self._trials.go_trial_count,
"catch_trial_count": self._trials.catch_trial_count,
"hit_trial_count": self._trials.hit_trial_count,
"miss_trial_count": self._trials.miss_trial_count,
"false_alarm_trial_count": self._trials.false_alarm_trial_count,
"correct_reject_trial_count": self._trials.correct_reject_trial_count, # noqa: E501
"auto_reward_count": self.trials.auto_rewarded.sum(),
"earned_reward_count": self.trials.hit.sum(),
"total_reward_count": len(self.rewards),
"total_reward_volume": self.rewards.volume.sum(),
}
# Although 'earned_reward_count' will currently have the same value as
# 'hit_trial_count', in the future there may be variants of the
# task where rewards are withheld. In that case the
# 'earned_reward_count' will be smaller than (and different from)
# the 'hit_trial_count'.
rpdf = self.get_rolling_performance_df()
engaged_trial_mask = (
rpdf["reward_rate"] > engaged_trial_reward_rate_threshold
)
performance_metrics["maximum_reward_rate"] = np.nanmax(
rpdf["reward_rate"].values
)
performance_metrics[
"engaged_trial_count"
] = self._trials.get_engaged_trial_count(
engaged_trial_reward_rate_threshold=(
engaged_trial_reward_rate_threshold
)
)
performance_metrics["mean_hit_rate"] = rpdf["hit_rate"].mean()
performance_metrics["mean_hit_rate_uncorrected"] = rpdf[
"hit_rate_raw"
].mean()
performance_metrics["mean_hit_rate_engaged"] = rpdf["hit_rate"][
engaged_trial_mask
].mean()
performance_metrics["mean_false_alarm_rate"] = rpdf[
"false_alarm_rate"
].mean()
performance_metrics["mean_false_alarm_rate_uncorrected"] = rpdf[
"false_alarm_rate_raw"
].mean()
performance_metrics["mean_false_alarm_rate_engaged"] = rpdf[
"false_alarm_rate"
][engaged_trial_mask].mean()
performance_metrics["mean_dprime"] = rpdf["rolling_dprime"].mean()
performance_metrics["mean_dprime_engaged"] = rpdf["rolling_dprime"][
engaged_trial_mask
].mean()
performance_metrics["max_dprime"] = rpdf["rolling_dprime"].max()
performance_metrics["max_dprime_engaged"] = rpdf["rolling_dprime"][
engaged_trial_mask
].max()
return performance_metrics
# ====================== properties ========================
@property
def behavior_session_id(self) -> int:
"""Unique identifier for a behavioral session.
:rtype: int
"""
return self._behavior_session_id.value
@property
def eye_tracking(self) -> Optional[pd.DataFrame]:
"""A dataframe containing ellipse fit parameters for the eye, pupil
and corneal reflection (cr). Fits are derived from tracking points
from a DeepLabCut model applied to video frames of a subject's
right eye. Raw tracking points and raw video frames are not exposed
by the SDK.
Notes:
- All columns starting with 'pupil_' represent ellipse fit parameters
relating to the pupil.
- All columns starting with 'eye_' represent ellipse fit parameters
relating to the eyelid.
- All columns starting with 'cr_' represent ellipse fit parameters
relating to the corneal reflection, which is caused by an infrared
LED positioned near the eye tracking camera.
- All positions are in units of pixels.
- All areas are in units of pixels^2
- All values are in the coordinate space of the eye tracking camera,
NOT the coordinate space of the stimulus display (i.e. this is not
gaze location), with (0, 0) being the upper-left corner of the
eye-tracking image.
- The 'likely_blink' column is True for any row (frame) where the pupil
fit failed OR eye fit failed OR an outlier fit was identified on the
pupil or eye fit.
- The pupil_area, cr_area, eye_area, and pupil/eye_width, height, phi
columns are set to NaN wherever 'likely_blink' == True.
- The pupil_area_raw, cr_area_raw, eye_area_raw columns contains all
pupil fit values (including where 'likely_blink' == True).
- All ellipse fits are derived from tracking points that were output by
a DeepLabCut model that was trained on hand-annotated data from a
subset of imaging sessions on optical physiology rigs.
- Raw DeepLabCut tracking points are not publicly available.
:rtype: pandas.DataFrame
"""
return (
self._eye_tracking.value
if self._eye_tracking is not None
else None
)
@property
def eye_tracking_rig_geometry(self) -> dict:
"""the eye tracking equipment geometry associated with a
given behavior session.
Returns
-------
dict
dictionary with the following keys:
camera_eye_position_mm (array of float)
camera_rotation_deg (array of float)
equipment (string)
led_position (array of float)
monitor_position_mm (array of float)
monitor_rotation_deg (array of float)
"""
if self._eye_tracking_rig_geometry is None:
return dict()
return self._eye_tracking_rig_geometry.to_dict()["rig_geometry"]
@property
def licks(self) -> pd.DataFrame:
"""A dataframe containing lick timestmaps and frames, sampled
at 60 Hz.
NOTE: For BehaviorSessions, returned timestamps are not
aligned to external 'synchronization' reference timestamps.
Synchronized timestamps are only available for
BehaviorOphysExperiments.
Returns
-------
np.ndarray
A dataframe containing lick timestamps.
dataframe columns:
timestamps: (float)
time of lick, in seconds
frame: (int)
frame of lick
"""
return self._licks.value
@property
def rewards(self) -> pd.DataFrame:
"""Retrieves rewards from data file saved at the end of the
behavior session.
NOTE: For BehaviorSessions, returned timestamps are not
aligned to external 'synchronization' reference timestamps.
Synchronized timestamps are only available for
BehaviorOphysExperiments.