From e047af449bcc2badfe227281db67281d70f411cd Mon Sep 17 00:00:00 2001 From: Jerry Yang Date: Tue, 31 Oct 2023 22:10:29 -0400 Subject: [PATCH 1/2] updated garmin class --- dbdpy/garmin.py | 281 ++++++++++++++++++++++-------------------------- 1 file changed, 128 insertions(+), 153 deletions(-) diff --git a/dbdpy/garmin.py b/dbdpy/garmin.py index f745fa3a..b67a6d20 100644 --- a/dbdpy/garmin.py +++ b/dbdpy/garmin.py @@ -1,183 +1,158 @@ -import pandas -from garmin_fit_sdk import Decoder, Stream +import numpy as np +import pandas as pd +from garmin_fit_sdk import Decoder, Stream, Profile import datetime - -class GarminDataProcessor: - FIT_EPOCH_S = 631065600 +class Garmin: def __init__(self): - self.data = None - - def read_fit(self, file): - """ - Reads FIT file and stores the data in the class instance. - - Args: - file (str): The path to the FIT file to be read. - """ - stream = Stream.from_file(file) - decoder = Decoder(stream) - messages, errors = decoder.read( - apply_scale_and_offset=True, - convert_datetimes_to_dates=False, - convert_types_to_strings=True, - enable_crc_check=True, - expand_sub_fields=True, - expand_components=True, - merge_heart_rates=False, - mesg_listener=None - ) - print(errors) - self.data = messages - - def convert_time(self): - """ - Converts timestamps in the stored data to a human-readable format. - - Uses the FIT_EPOCH_S constant and adjusts for the time zone. - - Note: Requires data to be loaded using `read_fit` method first. - """ - time_delta = datetime.timedelta(hours=4) - if self.data is None: - return None - self.data['timestamp'] = [ - datetime.datetime.utcfromtimestamp( - self.FIT_EPOCH_S + self.data['timestamp'][i]) - time_delta - for i in range(len(self.data['timestamp'])) - ] - - def get_sleep_time(self): - """ - Extracts sleep start and wake times from the stored data. - - Note: Requires data to be loaded and timestamps to be converted using - `read_fit` and `convert_time` methods. - """ - if self.data is None: - return None - sleep = self.data # Replace with the actual sleep data frame - sleep_time, wake_time = None, None - for i in range(len(sleep['timestamp']) - 1): - time_difference = sleep['timestamp'][i + 1] - sleep['timestamp'][i] - if time_difference.seconds // 3600 > 1: - sleep_time = sleep['timestamp'][i] - wake_time = sleep['timestamp'][i + 1] - return sleep_time, wake_time - - def get_timestamp(self): - """ - Adjusts timestamps in the stored data, fixing inconsistencies and - converting them to a continuous sequence. - - Note: Requires data to be loaded using `read_fit` method first. - """ - if self.data is None: - return None - df = self.data # Replace with the actual data frame - for i in range(len(df) - 1): - if df.iloc[i, 0].astype(str) == 'nan': + self.messages = None + self.raw_data = None + self.stress = None + self.SpO2 = None + self.floor = None + self.hr = None + self.distance = None + self.intensity = None + self.calories = None + self.sleep_time = None + self.wake_time = None + self.rhr = None + + def convert_time(df): + df['timestamp'] = [datetime.datetime.utcfromtimestamp(631065600 + df['timestamp'][i]) - datetime.timedelta(hours=4) + for i in range(len(df['timestamp']))] + return df + + def get_timestamp(df): + for i in range(len(df)-1): + if df.iloc[i,0].astype(str) == 'nan': timestamp_16 = df['timestamp_16'][i] - df.iloc[i, 0] = df.iloc[(i - 1), 0] + df.iloc[i,0] = df.iloc[(i-1),0] for i in range(len(df)): mesgTimestamp = int(df['timestamp'][i]) - try: + try: timestamp_16 = int(df['timestamp_16'][i]) - duration = (timestamp_16 - (mesgTimestamp & 0xFFFF)) & 0xFFFF + duration = ( timestamp_16 - ( mesgTimestamp & 0xFFFF ) ) & 0xFFFF except ValueError: continue mesgTimestamp += duration - df.iloc[i, 0] = mesgTimestamp + df.iloc[i,0] = mesgTimestamp + return df - def get_floor(self): + @classmethod + def read_fit(cls, path): """ - Calculates cumulative ascent and descent values over time, - returning a data frame. + Reads FIT file and stores the data in the class instance. - Note: Requires data to be loaded using `read_fit` method first. + Args: + file (str): The path to the FIT file to be read. """ - if self.data is None: - return None - df = self.data # Replace with the actual data frame - floor = df[['timestamp', 'ascent', 'descent']] - floor.iloc[0, 1:] = 0 - for i in range(len(floor) - 1): - if str(floor['ascent'][i + 1]) == 'nan': - floor.iloc[i + 1, 1] = floor.iloc[i, 1] + stream = Stream.from_file(path) + decoder = Decoder(stream) + messages, errors = decoder.read(apply_scale_and_offset=True, + convert_datetimes_to_dates=False, + convert_types_to_strings=True, + enable_crc_check=True, + expand_sub_fields=True, + expand_components=True, + merge_heart_rates=False, + mesg_listener=None) + + cls.messages = messages + + @classmethod + def get_raw_data(cls): + raw_data = pd.DataFrame(cls.messages['monitoring_mesgs']) + raw_data = cls.get_timestamp(raw_data) + raw_data = cls.convert_time(raw_data) + cls.raw_data = raw_data + + @classmethod + def get_stress_level(cls): + stress = pd.DataFrame(cls.messages['stress_level_mesgs']) + stress['stress_level_time'] = [datetime.datetime.utcfromtimestamp(631065600 + stress['stress_level_time'][i]) - datetime.timedelta(hours=4) + for i in range(len(stress['stress_level_time']))] + cls.stress = stress + + @classmethod + def get_SpO2(cls): + SpO2 = pd.DataFrame(cls.messages['spo2_data_mesgs']) + SpO2 = cls.convert_time(SpO2) + cls.SpO2 = SpO2 + + @classmethod + def get_floor(cls): + floor = cls.raw_data[['timestamp', 'ascent', 'descent']] + floor.iloc[0,1:] = 0 + for i in range(len(floor)-1): + if str(floor['ascent'][i+1]) == 'nan': + floor.iloc[i+1,1] = floor.iloc[i,1] else: - floor.iloc[i + 1, 1] += floor.iloc[i, 1] + floor.iloc[i+1,1] += floor.iloc[i,1] - if str(floor['descent'][i + 1]) == 'nan': - floor.iloc[i + 1, 2] = floor.iloc[i, 2] + + if str(floor['descent'][i+1]) == 'nan': + floor.iloc[i+1,2] = floor.iloc[i,2] else: - floor.iloc[i + 1, 2] += floor.iloc[i, 2] - return floor - - def get_distance(self): - """ - Calculates cumulative distance values over time, - returning a data frame. + floor.iloc[i+1,2] += floor.iloc[i,2] - Note: Requires data to be loaded using `read_fit` method first. - """ - if self.data is None: - return None - df = self.data # Replace with the actual data frame - distance = df.iloc[:-5, :] + cls.floor = floor + + @classmethod + def get_distance(cls): + distance = cls.raw_data.iloc[:-5,:] distance = distance[['timestamp', 'distance']] - distance.iloc[0, 1] = 0 - for i in range(len(distance) - 1): + distance.iloc[0,1] = 0 + for i in range(len(distance)-1): i = i + 1 if str(distance['distance'][i]) == 'nan': - if str(distance['distance'][i - 1]) != 'nan': - distance.iloc[i, 1] = distance['distance'][i - 1] - - if distance.iloc[i, 1] < distance['distance'][i - 1]: - distance.iloc[i, 1] = distance['distance'][i - 1] - return distance + if str(distance['distance'][i-1]) != 'nan': + distance.iloc[i,1] = distance['distance'][i-1] + + if distance.iloc[i,1] < distance['distance'][i-1]: + distance.iloc[i,1] = distance['distance'][i-1] - def get_hr(self): - """ - Extracts heart rate data from the stored data. + cls.distance = distance - Note: Requires data to be loaded using `read_fit` method first. - """ - if self.data is None: - return None - hr_df = self.data[~self.data['heart_rate'].isna()] - hr_df = hr_df.reset_index(drop=True) + @classmethod + def get_hr(cls): + hr_df = cls.raw_data[~cls.raw_data['heart_rate'].isna()].reset_index(drop=True) hr_df = hr_df[['timestamp', 'heart_rate']] - return hr_df - - def get_intensity(self): - """ - Extracts intensity data from the stored data. + cls.hr = hr_df - Note: Requires data to be loaded using `read_fit` method first. - """ - if self.data is None: - return None - intensity = self.data[~self.data['intensity'].isna()] - intensity = intensity.reset_index(drop=True) + @classmethod + def get_intensity(cls): + intensity = cls.raw_data[~cls.raw_data['intensity'].isna()].reset_index(drop=True) intensity = intensity[['timestamp', 'intensity']] - return intensity - - def get_calories(self): - """ - Calculates cumulative active calorie values over time, - returning a data frame. + cls.intensity = intensity - Note: Requires data to be loaded using `read_fit` method first. - """ - if self.data is None: - return None - calories = self.data.iloc[:-5, :] + @classmethod + def get_calories(cls): + calories = cls.raw_data.iloc[:-5,:] calories = calories[['timestamp', 'active_calories']] - for i in range(len(calories) - 1): - if str((calories['active_calories'][i + 1]) == 'nan') or\ - (calories['active_calories'][i + 1] < - calories['active_calories'][i]): - calories.iloc[i + 1, 1] = calories.iloc[i, 1] - return calories + for i in range(len(calories)-1): + if str(calories['active_calories'][i+1]) == 'nan' or calories['active_calories'][i+1] < calories['active_calories'][i]: + calories.iloc[i+1,1] = calories.iloc[i,1] + cls.calories = calories + + @classmethod + def get_sleep_time(cls): + sleep = pd.DataFrame(cls.messages['sleep_level_mesgs']) + sleep = cls.convert_time(sleep) + for i in range(len(sleep['timestamp']) - 1): + time_difference = sleep['timestamp'][i + 1] - sleep['timestamp'][i] + if time_difference.seconds // 3600 > 1: + sleep_time = sleep['timestamp'][i] + wake_time = sleep['timestamp'][i + 1] + + cls.sleep_time = sleep_time + cls.wake_time = wake_time + + @classmethod + def get_rhr(cls): + rhr_df = pd.DataFrame(cls.messages['monitoring_hr_data_mesgs']) + rhr = rhr_df['resting_heart_rate'][0] # current_day_resting_heart_rate + + cls.rhr = rhr From 321caf6e621d26b040f3897bf67924a39d1938d4 Mon Sep 17 00:00:00 2001 From: Jerry Yang Date: Thu, 30 Nov 2023 19:11:06 -0500 Subject: [PATCH 2/2] function ready --- dbdpy/garmin.py | 160 +++++++++++++++++++++++++++++------------------- 1 file changed, 96 insertions(+), 64 deletions(-) diff --git a/dbdpy/garmin.py b/dbdpy/garmin.py index b67a6d20..4a949681 100644 --- a/dbdpy/garmin.py +++ b/dbdpy/garmin.py @@ -1,30 +1,23 @@ import numpy as np import pandas as pd +import matplotlib.pyplot as plt from garmin_fit_sdk import Decoder, Stream, Profile import datetime +# from device import CommercialDevice -class Garmin: - +# class garmin(CommercialDevice): +class garmin(): def __init__(self): + # super().__init__('garmin') self.messages = None - self.raw_data = None - self.stress = None - self.SpO2 = None - self.floor = None - self.hr = None - self.distance = None - self.intensity = None - self.calories = None - self.sleep_time = None - self.wake_time = None - self.rhr = None - def convert_time(df): + # helper functions + def convert_time(self, df): df['timestamp'] = [datetime.datetime.utcfromtimestamp(631065600 + df['timestamp'][i]) - datetime.timedelta(hours=4) for i in range(len(df['timestamp']))] return df - def get_timestamp(df): + def get_timestamp(self, df): for i in range(len(df)-1): if df.iloc[i,0].astype(str) == 'nan': timestamp_16 = df['timestamp_16'][i] @@ -40,14 +33,25 @@ def get_timestamp(df): mesgTimestamp += duration df.iloc[i,0] = mesgTimestamp return df - + @classmethod - def read_fit(cls, path): + def from_directory(cls, filepath): + newGarmin = cls() + file_type = filepath.split('.')[-1] + if file_type == 'fit': + data = newGarmin.read_fit(filepath) + else: + data = newGarmin.read_sleepData(filepath) + newobject = cls(data) + return newobject + + # read wellness file + def read_fit(self, path): """ Reads FIT file and stores the data in the class instance. Args: - file (str): The path to the FIT file to be read. + path (str): The path to the FIT file to be read. """ stream = Stream.from_file(path) decoder = Decoder(stream) @@ -60,31 +64,51 @@ def read_fit(cls, path): merge_heart_rates=False, mesg_listener=None) - cls.messages = messages + self.messages = messages - @classmethod - def get_raw_data(cls): - raw_data = pd.DataFrame(cls.messages['monitoring_mesgs']) - raw_data = cls.get_timestamp(raw_data) - raw_data = cls.convert_time(raw_data) - cls.raw_data = raw_data + # read sleep data json file + def read_sleepData(self, path): + """ + Reads sleep data in json file and stores the data in the class instance. + + Args: + file (str): The path to the json file to be read. + """ + sleepData = pd.read_json(path) + sleepData['sleepStartTimestampGMT'] = pd.to_datetime(sleepData['sleepStartTimestampGMT']) + sleepData['sleepStartTimestampGMT'] = sleepData['sleepStartTimestampGMT'] - datetime.timedelta(hours=4) + sleepData['sleepEndTimestampGMT'] = pd.to_datetime(sleepData['sleepEndTimestampGMT']) + sleepData['sleepEndTimestampGMT'] = sleepData['sleepEndTimestampGMT'] - datetime.timedelta(hours=4) + sleepData['calendarDate'] = pd.to_datetime(sleepData['calendarDate']) + sleepData.drop(sleepData[sleepData['sleepWindowConfirmationType'] == 'UNCONFIRMED'].index, inplace = True) + self.sleepData = sleepData - @classmethod - def get_stress_level(cls): - stress = pd.DataFrame(cls.messages['stress_level_mesgs']) + # get sleep stage information + def get_sleepstage(self): + sleepStage = self.sleepData[['calendarDate', 'deepSleepSeconds', 'lightSleepSeconds', 'remSleepSeconds', 'awakeSleepSeconds']] + return sleepStage + + # get raw wellness data + def get_raw_data(self): + raw_data = pd.DataFrame(self.messages['monitoring_mesgs']) + raw_data = self.get_timestamp(raw_data) + raw_data = self.convert_time(raw_data) + return raw_data + + def get_stress_level(self): + stress = pd.DataFrame(self.messages['stress_level_mesgs']) stress['stress_level_time'] = [datetime.datetime.utcfromtimestamp(631065600 + stress['stress_level_time'][i]) - datetime.timedelta(hours=4) for i in range(len(stress['stress_level_time']))] - cls.stress = stress + return stress - @classmethod - def get_SpO2(cls): - SpO2 = pd.DataFrame(cls.messages['spo2_data_mesgs']) - SpO2 = cls.convert_time(SpO2) - cls.SpO2 = SpO2 + def get_SpO2(self): + SpO2 = pd.DataFrame(self.messages['spo2_data_mesgs']) + SpO2 = self.convert_time(SpO2) + return SpO2 - @classmethod - def get_floor(cls): - floor = cls.raw_data[['timestamp', 'ascent', 'descent']] + def get_floor(self): + raw_data = self.get_raw_data() + floor = raw_data[['timestamp', 'ascent', 'descent']] floor.iloc[0,1:] = 0 for i in range(len(floor)-1): if str(floor['ascent'][i+1]) == 'nan': @@ -97,12 +121,15 @@ def get_floor(cls): floor.iloc[i+1,2] = floor.iloc[i,2] else: floor.iloc[i+1,2] += floor.iloc[i,2] + + duplicate_rows = floor[floor.duplicated()] + floor_no_duplicates = floor.drop_duplicates(keep='first') - cls.floor = floor + return floor_no_duplicates - @classmethod - def get_distance(cls): - distance = cls.raw_data.iloc[:-5,:] + def get_distance(self): + raw_data = self.get_raw_data() + distance = raw_data.iloc[:-5,:] distance = distance[['timestamp', 'distance']] distance.iloc[0,1] = 0 for i in range(len(distance)-1): @@ -114,45 +141,50 @@ def get_distance(cls): if distance.iloc[i,1] < distance['distance'][i-1]: distance.iloc[i,1] = distance['distance'][i-1] - cls.distance = distance + duplicate_rows = distance[distance.duplicated()] + distance_no_duplicates = distance.drop_duplicates(keep='first') + + return distance_no_duplicates - @classmethod - def get_hr(cls): - hr_df = cls.raw_data[~cls.raw_data['heart_rate'].isna()].reset_index(drop=True) + def get_hr(self): + raw_data = self.get_raw_data() + hr_df = raw_data[~raw_data['heart_rate'].isna()].reset_index(drop=True) hr_df = hr_df[['timestamp', 'heart_rate']] - cls.hr = hr_df + hr_df['heart_rate'] = hr_df['heart_rate'].astype(int) + return hr_df - @classmethod - def get_intensity(cls): - intensity = cls.raw_data[~cls.raw_data['intensity'].isna()].reset_index(drop=True) + def get_intensity(self): + raw_data = self.get_raw_data() + intensity = raw_data[~raw_data['intensity'].isna()].reset_index(drop=True) intensity = intensity[['timestamp', 'intensity']] - cls.intensity = intensity + return intensity - @classmethod - def get_calories(cls): - calories = cls.raw_data.iloc[:-5,:] + def get_calories(self): + raw_data = self.get_raw_data() + calories = raw_data.iloc[:-5,:] calories = calories[['timestamp', 'active_calories']] for i in range(len(calories)-1): if str(calories['active_calories'][i+1]) == 'nan' or calories['active_calories'][i+1] < calories['active_calories'][i]: calories.iloc[i+1,1] = calories.iloc[i,1] - cls.calories = calories + + duplicate_rows = calories[calories.duplicated()] + calories_no_duplicates = calories.drop_duplicates(keep='first') + + return calories_no_duplicates - @classmethod - def get_sleep_time(cls): - sleep = pd.DataFrame(cls.messages['sleep_level_mesgs']) - sleep = cls.convert_time(sleep) + def get_sleep_time(self): + sleep = pd.DataFrame(self.messages['sleep_level_mesgs']) + sleep = self.convert_time(sleep) for i in range(len(sleep['timestamp']) - 1): time_difference = sleep['timestamp'][i + 1] - sleep['timestamp'][i] if time_difference.seconds // 3600 > 1: sleep_time = sleep['timestamp'][i] wake_time = sleep['timestamp'][i + 1] - cls.sleep_time = sleep_time - cls.wake_time = wake_time + return sleep_time, wake_time - @classmethod - def get_rhr(cls): - rhr_df = pd.DataFrame(cls.messages['monitoring_hr_data_mesgs']) + def get_rhr(self): + rhr_df = pd.DataFrame(self.messages['monitoring_hr_data_mesgs']) rhr = rhr_df['resting_heart_rate'][0] # current_day_resting_heart_rate - cls.rhr = rhr + return rhr