Class: PostRunner::DailySleepAnalyzer
- Inherits:
-
Object
- Object
- PostRunner::DailySleepAnalyzer
- Defined in:
- lib/postrunner/DailySleepAnalyzer.rb
Overview
This class extracts the sleep information from a set of monitoring files and determines when and how long the user was awake or had a light or deep sleep. Determining the sleep state of a person purely based on wrist movement data is not very accurate. It gets a lot more accurate when heart rate data is available as well. The heart rate describes a sinus-like curve that aligns with the sleep cycles. Each sinus cycle corresponds to a sleep cycle. Unfortunately, current Garmin devices only use a default sampling time of 15 minutes. Since a sleep cycle is broken down into various sleep phases that normally last 10 - 15 minutes, there is a fairly high margin of error to determine the exact timing of the sleep cycle.
HR High —–+ -------
------
HR Low ---
--------
— Mov High – -------
-----
+– Mov Low ---------
--
-----
Phase wk n1 n3 n2 rem n2 n3 n2 rem n2 n3 n2 Cycle 1 2 3
Legend: wk: wake n1: NREM1, n2: NREM2, n3: NREM3, rem: REM sleep
Too frequent or too strong movements abort the cycle to wake.
Constant Summary collapse
- TIME_WINDOW_MINUTES =
24 * 60
Instance Attribute Summary collapse
-
#deep_sleep ⇒ Object
readonly
Returns the value of attribute deep_sleep.
-
#light_sleep ⇒ Object
readonly
Returns the value of attribute light_sleep.
-
#rem_sleep ⇒ Object
readonly
Returns the value of attribute rem_sleep.
-
#resting_heart_rate ⇒ Object
readonly
Returns the value of attribute resting_heart_rate.
-
#sleep_cycles ⇒ Object
readonly
Returns the value of attribute sleep_cycles.
-
#total_sleep ⇒ Object
readonly
Returns the value of attribute total_sleep.
-
#utc_offset ⇒ Object
readonly
Returns the value of attribute utc_offset.
-
#window_end_time ⇒ Object
readonly
Returns the value of attribute window_end_time.
-
#window_start_time ⇒ Object
readonly
Returns the value of attribute window_start_time.
Instance Method Summary collapse
-
#initialize(monitoring_files, day, window_offest_secs) ⇒ DailySleepAnalyzer
constructor
Create a new DailySleepAnalyzer object to analyze the given monitoring files.
Constructor Details
#initialize(monitoring_files, day, window_offest_secs) ⇒ DailySleepAnalyzer
Create a new DailySleepAnalyzer object to analyze the given monitoring files.
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 |
# File 'lib/postrunner/DailySleepAnalyzer.rb', line 54 def initialize(monitoring_files, day, window_offest_secs) @window_start_time = @window_end_time = @utc_offset = nil # The following activity types are known: # [ :undefined, :running, :cycling, :transition, # :fitness_equipment, :swimming, :walking, :unknown7, # :resting, :unknown9 ] @activity_type = Array.new(TIME_WINDOW_MINUTES, nil) # The activity values in the FIT files can range from 0 to 7. @activity_intensity = Array.new(TIME_WINDOW_MINUTES, nil) # Wrist motion data is not very well suited to determine wake or sleep # states. A single movement can be a turning motion, a NREM1 jerk or # even a movement while you dream. The fewer motions are detected, the # more likely you are really asleep. To even out single spikes, we # average the motions over a period of time. This Array stores the # weighted activity. @weighted_sleep_activity = Array.new(TIME_WINDOW_MINUTES, 8) # We classify the sleep activity into :wake, :low_activity and # :no_activity in this Array. @sleep_activity_classification = Array.new(TIME_WINDOW_MINUTES, nil) # The data from the monitoring files is stored in Arrays that cover 24 # hours at 1 minute resolution. The algorithm currently cannot handle # time zone or DST changes. The day is always 24 hours and the local # time at noon the previous day is used for the whole window. @heart_rate = Array.new(TIME_WINDOW_MINUTES, nil) # From the wrist motion data and if available from the heart rate data, # we try to guess the sleep phase (:wake, :rem, :nrem1, :nrem2, :nrem3). # This Array will hold a minute-by-minute list of the guessed sleep # phase. @sleep_phase = Array.new(TIME_WINDOW_MINUTES, :wake) # The DailySleepAnalzyer extracts the sleep cycles from the monitoring # data. Each night usually has 5 - 6 sleep cycles. If we have heart rate # data, those cycles can be identified fairly well. If we have to rely # on wrist motion data only, we usually find more cycles than there # actually were. Each cycle is captured as SleepCycle object. @sleep_cycles = [] # The resting heart rate. @resting_heart_rate = nil # Day as Time object. Midnight UTC. day_as_time = Time.parse(day + "-00:00:00+00:00").gmtime extract_data_from_monitor_files(monitoring_files, day_as_time, window_offest_secs) # We must have information about the local time zone the data was # recorded in. Abort if not available. return unless @utc_offset fill_monitoring_data categorize_sleep_activity if categorize_sleep_heart_rate # We have usable heart rate data for the sleep periods. Correlating # wrist motion data with heart rate cycles will greatly improve the # sleep phase and sleep cycle detection. categorize_sleep_phase_by_hr_level @sleep_cycles.each do |c| # Adjust the cycle boundaries to align with REM phase. c.adjust_cycle_boundaries(@sleep_phase) # Detect sleep phases for each cycle. c.detect_phases(@sleep_phase) end else # We have no usable heart rate data. Just guess sleep phases based on # wrist motion data. categorize_sleep_phase_by_activity_level @sleep_cycles.each { |c| c.detect_phases(@sleep_phase) } end dump_data delete_wake_cycles determine_resting_heart_rate calculate_totals end |
Instance Attribute Details
#deep_sleep ⇒ Object (readonly)
Returns the value of attribute deep_sleep.
42 43 44 |
# File 'lib/postrunner/DailySleepAnalyzer.rb', line 42 def deep_sleep @deep_sleep end |
#light_sleep ⇒ Object (readonly)
Returns the value of attribute light_sleep.
42 43 44 |
# File 'lib/postrunner/DailySleepAnalyzer.rb', line 42 def light_sleep @light_sleep end |
#rem_sleep ⇒ Object (readonly)
Returns the value of attribute rem_sleep.
42 43 44 |
# File 'lib/postrunner/DailySleepAnalyzer.rb', line 42 def rem_sleep @rem_sleep end |
#resting_heart_rate ⇒ Object (readonly)
Returns the value of attribute resting_heart_rate.
42 43 44 |
# File 'lib/postrunner/DailySleepAnalyzer.rb', line 42 def resting_heart_rate @resting_heart_rate end |
#sleep_cycles ⇒ Object (readonly)
Returns the value of attribute sleep_cycles.
42 43 44 |
# File 'lib/postrunner/DailySleepAnalyzer.rb', line 42 def sleep_cycles @sleep_cycles end |
#total_sleep ⇒ Object (readonly)
Returns the value of attribute total_sleep.
42 43 44 |
# File 'lib/postrunner/DailySleepAnalyzer.rb', line 42 def total_sleep @total_sleep end |
#utc_offset ⇒ Object (readonly)
Returns the value of attribute utc_offset.
42 43 44 |
# File 'lib/postrunner/DailySleepAnalyzer.rb', line 42 def utc_offset @utc_offset end |
#window_end_time ⇒ Object (readonly)
Returns the value of attribute window_end_time.
42 43 44 |
# File 'lib/postrunner/DailySleepAnalyzer.rb', line 42 def window_end_time @window_end_time end |
#window_start_time ⇒ Object (readonly)
Returns the value of attribute window_start_time.
42 43 44 |
# File 'lib/postrunner/DailySleepAnalyzer.rb', line 42 def window_start_time @window_start_time end |