forked from jamieboyd/AutoHeadFix
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathAHF_DataLogger.py
More file actions
178 lines (158 loc) · 6.96 KB
/
AHF_DataLogger.py
File metadata and controls
178 lines (158 loc) · 6.96 KB
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
#! /usr/bin/python
# -*-coding: utf-8 -*-
from abc import ABCMeta, abstractmethod
from AHF_Base import AHF_Base
from collections import deque
class AHF_DataLogger(AHF_Base, metaclass=ABCMeta):
"""
A Data Logger provides an interface to save task data, and to save
and load mouse configuration data. This can be either to text files, or to
a database, or hd5 files, or some combination thereof. The data logger
should also print status updates to the shell, but these don't need to contain
as much information. The brain imaging data is saved
separately, but references to movie files should be saved by data logger.
Similarly, other binary data(lever positons, anyone?) can be saved separately,
by the Stimulator class, but binary file/posiiton can be saved as an event.
"""
TO_SHELL = 1
TO_FILE = 2
trackingDict = {}
isChild = False
BUFFER_SIZE = 25
@abstractmethod
def makeLogFile(self):
"""
Makes or opens a text log file, or a datbase, or whatever else needs doing. Called once before
entering main loop of program. DataLogger may make a new file every day in NewDay function, if desired
"""
pass
@abstractmethod
def readFromLogFile(self, index):
"""
Reads the log statement *index* lines prior to the current line.
Returns the event and associated dictionary in a tuple.
"""
pass
def startTracking(self, eventKind, dictKey, trackingType, size = 0):
"""
Begins tracking of the specified key for the specified event.
Tracks as a circular buffer or daily totals.
"""
self.trackingDict.update({eventKind: {dictKey: {"type": trackingType, "values": {}, "size": size}}})
def clearTrackedValues(self, tag, eventKind, dictKey):
keyTracking = self.trackingDict.get(eventKind, {}).get(dictKey)
if keyTracking is None:
return
if keyTracking["type"] is "buffer":
if tag in keyTracking["values"].keys():
keyTracking["values"][tag] = deque(maxlen = keyTracking["size"])
elif keyTracking["type"] is "totals":
if tag in keyTracking["values"].keys():
keyTracking["values"][tag] = 0
def getTrackedEvent(self, tag, eventKind, dictKey):
"""
Returns the current value for the specified mouse, event, and key.
"""
print(self.trackingDict)
try:
return self.trackingDict.get(eventKind).get(dictKey).get("values").get(tag)
except Exception as e:
return None
def stopTracking(self, eventKind, dictKey):
"""
Halts previously started tracking.
"""
try:
type = self.trackingDict.get(eventKind).get(dictKey).get("type")
self.trackingDict.get(eventKind).get(dictKey).update({"type": type + "Stopped"})
except Exception as e:
pass
def resumeTracking(self, eventKind, dictKey):
"""
Resumes previously started tracking.
"""
try:
type = self.trackingDict.get(eventKind).get(dictKey).get("type")
if type[-7:] is "Stopped":
self.trackingDict.get(eventKind).get(dictKey).update({"type": type[:-7]})
except Exception as e:
pass
@abstractmethod
def writeToLogFile(self, tag, eventKind, eventDict, timeStamp, toShellOrFile = 3):
"""
The original standard text file method was 4 tab-separated columns, mouse tag, or 0
if no single tag was applicaple, unix time stamp, ISO formatted time, and event. Event
could be anything. Now, every event has a kind, and every kind of event defines a
dictionary. Main program calls writeToLogFile, as well as the Stimulator object
For text based methods, event should be a dictionary for more complicated stimulator
results, so an event can be more easily parsed during data analysis.
"""
if self.isChild:
return
eventTracking = self.trackingDict.get(eventKind, None)
if eventKind is "ConsumedReward":
#Special case :/
self.trackingDict["Reward"]["consumed"]["values"][tag].pop()
self.trackingDict["Reward"]["consumed"]["values"][tag].append(True)
if eventTracking is not None:
for key in eventDict.keys():
keyTracking = eventTracking.get(key, None)
if keyTracking is not None:
if keyTracking["type"] is "buffer":
if tag not in keyTracking["values"].keys():
keyTracking["values"][tag] = deque(maxlen = keyTracking["size"])
keyTracking["values"][tag].append(eventDict[key])
elif keyTracking["type"] is "totals":
if tag not in keyTracking["values"].keys():
keyTracking["values"][tag] = 0
keyTracking["values"][tag] += eventDict[key]
@abstractmethod
def newDay(self):
"""
At the start of a new day, it was customary for the text-based data logging to start new text files,
and to make a precis of the day's results into a a separate text file for easy human reading.
This "quickStats" file should contain info for each mouse with rewards, head fixes,
or tasks, and other Stimulator specific data, which Stimulator object will provide for each mouse
just call the Stimulator class functions for each mouse to get a dictionary of results
"""
pass
@abstractmethod
def getMice(self):
"""
returns a list of mice that are in the dictionary/database
"""
pass
@abstractmethod
def configGenerator(self):
"""
generates configuration data for each subject as(IDtag, dictionary) tuples from some kind of permanent storage
such as a JSON file, or a database. Will be called when program is started, or restarted and settings
need to be reloaded.
"""
pass
@abstractmethod
def getConfigData(self, tag):
"""
returns a dictionary of data that was saved for this reference tag, in some permanent storage such as a JSON file
Will be called when program is started, or restarted and settings need to be reloaded
"""
pass
@abstractmethod
def saveNewMouse(self, tag, note, dictionary):
"""
store a new mouse entry in a referenced file
"""
pass
@abstractmethod
def retireMouse(self, tag,reason):
"""
store information about a mouse retirement in a referenced file
"""
pass
@abstractmethod
def storeConfig(self, tag, dictionary, source = ""):
"""
Stores configuration data, given as an IDtag, and dictionary for that tag, in some more permanent storage
as a JSON text file, or a database or hd5 file, so it can be later retrieved by IDtag
"""
pass