1+ import pandas as pd
2+ import numpy as np
3+ import os
4+ import sqlite3
5+ import warnings
6+
7+ from ..base import Raw
8+
9+
10+ class AGD (Raw ):
11+ r"""Raw object from .agd file (recorded by Actigraph)
12+
13+ Parameters
14+ ----------
15+ input_fname: str
16+ Path to the AGD file.
17+ start_time: datetime-like, optional
18+ Read data from this time.
19+ Default is None.
20+ period: str, optional
21+ Length of the read data.
22+ Cf. #timeseries-offset-aliases in
23+ <https://pandas.pydata.org/pandas-docs/stable/timeseries.html>.
24+ Default is None (i.e., all the data).
25+ """
26+
27+ def __init__ (
28+ self ,
29+ input_fname ,
30+ start_time = None ,
31+ period = None
32+ ):
33+
34+ # get absolute file path
35+ input_fname = os .path .abspath (input_fname )
36+
37+ # create connection to the SQLITE3 .agd file in read-only mode
38+ connection = sqlite3 .connect ('file:' + input_fname + '?mode=ro' , uri = True )
39+
40+ # extract header and data size
41+ settings = pd .read_sql_query (
42+ "SELECT * FROM settings" ,
43+ connection ,
44+ index_col = 'settingName'
45+ )
46+
47+ # extract information from the header
48+ start = self ._to_timestamps (
49+ int (settings .at ['startdatetime' , 'settingValue' ])
50+ )
51+ freq = pd .to_timedelta (
52+ int (settings .at ['epochlength' , 'settingValue' ]),
53+ unit = 's'
54+ )
55+ self ._model = settings .at ['devicename' , 'settingValue' ]
56+
57+ # extract proximity (wear/no-wear) information
58+ try :
59+ capsense = pd .read_sql_query (
60+ "SELECT state, timeStamp FROM capsense" ,
61+ connection ,
62+ index_col = 'timeStamp'
63+ ).squeeze ()
64+ # convert index to a date time
65+ capsense .index = self ._to_timestamps (capsense .index )
66+ # set index frequency
67+ if 'proximityIntervalInSeconds' in settings .index :
68+ self .capsense = capsense .asfreq (
69+ pd .to_timedelta (
70+ int (
71+ settings .at [
72+ 'proximityIntervalInSeconds' ,
73+ 'settingValue'
74+ ]
75+ ),
76+ unit = 's'
77+ )
78+ )
79+ elif capsense .index .inferred_freq is not None :
80+ self .capsense = capsense .asfreq (capsense .index .inferred_freq )
81+ else :
82+ warnings .warn (
83+ 'Acquisition frequency for the wearing sensor data '
84+ + '(capsense) could neither be retrieved nor inferred from'
85+ + ' the data.\n Use this information at your own risk' ,
86+ UserWarning
87+ )
88+ self .capsense = capsense
89+ except Exception as err :
90+ # TODO: specialize exception (eg;sqlite3.OperationalError)
91+ warnings .warn (
92+ 'Could not find wearing sensor data (capsense): {}' .format (
93+ err ),
94+ UserWarning
95+ )
96+ self .capsense = None
97+
98+ # extract acceleration and light data
99+ data = pd .read_sql_query (
100+ "SELECT * FROM data" ,
101+ connection ,
102+ index_col = 'dataTimestamp'
103+ )
104+ # convert index to a date time
105+ data .index = self ._to_timestamps (data .index )
106+ # set index frequency
107+ data = data .asfreq (freq = freq )
108+
109+ # calculate the magnitude of the acceleration vector.
110+ data ['mag' ] = np .sqrt (
111+ data ['axis1' ]* data ['axis1' ]
112+ + data ['axis2' ]* data ['axis2' ]
113+ + data ['axis3' ]* data ['axis3' ]
114+ )
115+
116+ if start_time is not None :
117+ start_time = pd .to_datetime (start_time )
118+ else :
119+ start_time = start
120+
121+ if period is not None :
122+ period = pd .Timedelta (period )
123+ stop_time = start_time + period
124+ else :
125+ stop_time = data .index [- 1 ]
126+ period = stop_time - start_time
127+
128+ data = data .loc [start_time :stop_time ]
129+
130+ # Extract position info:
131+ self ._position = data .filter (regex = 'incline*' , axis = 'columns' )
132+
133+ # call __init__ function of the base class
134+ super ().__init__ (
135+ df = data ,
136+ fpath = input_fname ,
137+ start_time = start_time ,
138+ period = period ,
139+ frequency = freq ,
140+ activity = data ['mag' ],
141+ light = data ['lux' ].to_frame (name = 'whitelight' ) if 'lux' in data .columns else None
142+ )
143+
144+ # Close sqlite3 connection
145+ connection .close ()
146+
147+ def _extract_position (self , column ):
148+ if column in self ._position .columns :
149+ return self ._position .loc [:, column ]
150+ else :
151+ return None
152+
153+ def _extract_light_channel (self , channel ):
154+ if self .light is None :
155+ return None
156+ else :
157+ return self .light .get_channel (channel )
158+
159+ @property
160+ def white_light (self ):
161+ r"""White light levels (in lux.)"""
162+ return self ._extract_light_channel ("whitelight" )
163+
164+ @property
165+ def model (self ):
166+ r"""Model of the device: devicename"""
167+ return self ._model
168+
169+ @staticmethod
170+ def _to_timestamps (ticks ):
171+ return pd .to_datetime (
172+ (ticks / 10000000 ) - 62135596800 ,
173+ unit = 's'
174+ )
175+
176+ @property
177+ def incline_off (self ):
178+ r"""Hourly positional information: inclineOff """
179+ return self ._extract_position ('inclineOff' )
180+
181+ @property
182+ def incline_standing (self ):
183+ r"""Hourly positional information: inclineStanding """
184+ return self ._extract_position ('inclineStanding' )
185+
186+ @property
187+ def incline_sitting (self ):
188+ r"""Hourly positional information: inclineSitting """
189+ return self ._extract_position ('inclineSitting' )
190+
191+ @property
192+ def incline_lying (self ):
193+ r"""Hourly positional information: inclineLying """
194+ return self ._extract_position ('inclineLying' )
195+
196+ def incline_position (self , pos_map = None ):
197+ r"""Reader function for raw AWD file.
198+
199+ Parameters
200+ ----------
201+ pos_map: dict, optional
202+ Positional information map.
203+ Can be used to turn positional info (str) into int, for example.
204+ Set to None to keep original information.
205+ Default mapping is:
206+ * 'inclineOff': -1
207+ * 'inclineLying': 0
208+ * 'inclineSitting': 1
209+ * 'inclineStanding': 2
210+
211+ Returns
212+ -------
213+ pos : Pandas.Series
214+ Time series with positional information.
215+ """
216+ if pos_map is None :
217+ pos_map = {
218+ 'inclineOff' : - 1 ,
219+ 'inclineLying' : 0 ,
220+ 'inclineSitting' : 1 ,
221+ 'inclineStanding' : 2
222+ }
223+ pos = self ._position .idxmax (axis = 1 )
224+ return pos .map (pos_map ) if pos_map is not None else pos
225+
226+
227+ def read_agd (input_fname , start_time = None , period = None ):
228+ r"""Reader function for raw AWD file.
229+
230+ Parameters
231+ ----------
232+ input_fname: str
233+ Path to the AGD file.
234+ start_time: datetime-like, optional
235+ Read data from this time.
236+ Default is None.
237+ period: str, optional
238+ Length of the read data.
239+ Cf. #timeseries-offset-aliases in
240+ <https://pandas.pydata.org/pandas-docs/stable/timeseries.html>.
241+ Default is None (i.e all the data).
242+
243+ Returns
244+ -------
245+ raw : Instance of RawAWD
246+ An object containing raw AWD data
247+ """
248+
249+ return AGD (
250+ input_fname = input_fname ,
251+ start_time = start_time ,
252+ period = period
253+ )
0 commit comments