| Title: | Analyse Activity and Sleep Behaviour |
|---|---|
| Description: | Use behavioural variables to score activity and infer sleep from bouts of immobility. It is primarily designed to score sleep in fruit flies from Drosophila Activity Monitor (TriKinetics) and Ethoscope data. It implements sleep scoring using the "five-minute rule" (Hendricks et al. (2000) <DOI:10.1016/S0896-6273(00)80877-6>), activity classification for Ethoscopes (Geissmann et al. (2017) <DOI:10.1371/journal.pbio.2003026>) and a new algorithm to detect when animals are dead. |
| Authors: | Quentin Geissmann [aut, cre] |
| Maintainer: | Quentin Geissmann <[email protected]> |
| License: | GPL-3 |
| Version: | 0.4.0 |
| Built: | 2026-10-07 23:50:13 UTC |
| Source: | https://github.com/rethomics/sleepr |
This function is used to find contiguous regions of unique value in a – potentially irregular/heterogeneous – univariate categorical time series.
bout_analysis(var, data)bout_analysis(var, data)
var |
name of the variable to use from |
data |
data.table::data.table containing behavioural variable from or one multiple animals.
When it has a key, unique values, are assumed to represent unique individuals (e.g. in a behavr::behavr table).
Otherwise, it analysis the data as coming from a single animal. |
an object of the same type as data (i.e. data.table::data.table or behavr::behavr).
Each row is a specific bout characterised by three columns.
t – its onset
duration – its length
<var> – a column with the same name as var. The value of var for this bout.
The relevant rethomic tutorial section – on sleep analysis
sleep_annotation – to generate a binary sleep variable
rle run length encoding function – on which this analysis is based
# Bout analysis on binary variable: dt <- toy_dam_data() dt[, moving := activity > 0] bdt <- bout_analysis(moving,dt) print(bdt) # With multiple states dt <- toy_ethoscope_data() # we discretise x position in three states: left, middle and right (1/3 each) dt[, location := as.character( cut(x, breaks = c(0.0, .33, .67, 1.0), labels = c("left", "middle", "right")))] bdt <- bout_analysis(location, dt)# Bout analysis on binary variable: dt <- toy_dam_data() dt[, moving := activity > 0] bdt <- bout_analysis(moving,dt) print(bdt) # With multiple states dt <- toy_ethoscope_data() # we discretise x position in three states: left, middle and right (1/3 each) dt[, location := as.character( cut(x, breaks = c(0.0, .33, .67, 1.0), labels = c("left", "middle", "right")))] bdt <- bout_analysis(location, dt)
This function detects when individuals have died based on their first (very) long bout of immobility. Following data (which may include spurious artefact of movement) are removed.
curate_dead_animals( data, moving_var = moving, time_window = hours(24), prop_immobile = 0.01, resolution = 24 )curate_dead_animals( data, moving_var = moving, time_window = hours(24), prop_immobile = 0.01, resolution = 24 )
data |
data.table::data.table containing behavioural variable from or one multiple animals.
When it has a key, unique values, are assumed to represent unique individuals (e.g. in a behavr::behavr table).
Otherwise, it analysis the data as coming from a single animal. |
moving_var |
logical variable in |
time_window |
window during which to define death (default is one day) |
prop_immobile |
proportion of immobility that counts as "dead" during time_window (see details) |
resolution |
how much scanning windows overlap. Expressed as a factor (see details). |
This function scans the time series looking for "death" in the right (future) data, within time_window.
Death is defined as mean(moving_var) < prop_immobile within a time window.
Moving window start every time_window/resolution. resolution = 1 is fast but means no overlap.
The default would score an animal as dead it does not move more than one percent of the time for at least one day.
All data following a "death" event are removed.
an object of the same type as data (i.e. data.table::data.table or behavr::behavr).
sleep_annotation – to score movement and sleep
dt1 <- toy_activity_data() #all movement after day 3 is set at 0 dt1[t > days(3), moving := FALSE] # one artefact of movement is detected at day 3.5 dt1[t == days(3.5), moving := TRUE] dt2 <- curate_dead_animals(dt1) dt3 <- curate_dead_animals(dt1,prop_immobile = 0) ## Not run: library(ggplot2) ggplot(data=dt1[,test:=1],aes(t, as.numeric(moving))) + geom_line(data=dt1[,test:=1]) + geom_line(data=dt2[, test:=2])+ geom_line(data=dt3[, test:=3])+ facet_grid(test ~ .)+ scale_x_time() ## End(Not run)dt1 <- toy_activity_data() #all movement after day 3 is set at 0 dt1[t > days(3), moving := FALSE] # one artefact of movement is detected at day 3.5 dt1[t == days(3.5), moving := TRUE] dt2 <- curate_dead_animals(dt1) dt3 <- curate_dead_animals(dt1,prop_immobile = 0) ## Not run: library(ggplot2) ggplot(data=dt1[,test:=1],aes(t, as.numeric(moving))) + geom_line(data=dt1[,test:=1]) + geom_line(data=dt2[, test:=2])+ geom_line(data=dt3[, test:=3])+ facet_grid(test ~ .)+ scale_x_time() ## End(Not run)
max_velocity_detector calls a time window "moving" when its peak corrected
velocity exceeds a threshold. When a motionless animal's tracking noise sits
near it, spurious "movements" fragment every immobility bout: a bout of 5 min
needs 30 consecutive immobile 10-s windows, so a per-window false positive rate
p leaves only (1 - p)^30 of true rests intact.
pixel_size(x) default_still_shift(x) light_phase(t, day_length = 24, lights_off = 12) find_spikes( t, x, y, max_frames = 2, min_jump = NULL, tolerance = NULL, max_span = 2 ) find_still_bins( d_small, time_window_length = 10, context_bins = 3, max_shift = NULL )pixel_size(x) default_still_shift(x) light_phase(t, day_length = 24, lights_off = 12) find_spikes( t, x, y, max_frames = 2, min_jump = NULL, tolerance = NULL, max_span = 2 ) find_still_bins( d_small, time_window_length = 10, context_bins = 3, max_shift = NULL )
x |
x positions of one animal. |
t |
time in seconds, 0 at lights on. |
day_length |
length of the day in hours. |
lights_off |
hour of lights off. |
y |
y positions. |
max_frames |
longest displaced run treated as a spike. |
min_jump |
smallest displacement treated as a jump; |
tolerance |
how close the return must be; |
max_span |
longest time from the last good frame to the return, in seconds. |
d_small |
binned data of a single animal, with columns |
time_window_length |
window size in seconds. |
context_bins |
windows on each side used to judge stillness (3, i.e. 30 s). |
max_shift |
largest positional change still counted as still, in the
units of |
These helpers support motion_qc, which reports how often a still animal
crosses the fixed threshold. Windows in which the animal is still are found
from position alone – its position over the half-minute either side of the
window stays within one pixel – so the check does not depend on the velocity
it checks. Where the fixed threshold fails, score sleep with
sleep_annotation(rule = "k") (sleep_rules). The algorithms and defaults are
identical to ethoscopy's motion_calibration module.
pixel_size(): one pixel in the units of x: positions
normalised to the ROI width (as scopr loads them) or in pixels.
default_still_shift(): positional tolerance for a still window, 1 pixel in
the units of x. Validated against tracker-independent pixel motion on two
recordings: at 1 px the calibrated threshold matched the one chosen from the
pixels; at 0.5 px it skipped still flies whose tracked position jitters and came
out too low.
light_phase(): "light" / "dark" label for each time point.
find_spikes(): tracking spikes: the centroid jumps more than
min_jump away for up to max_frames frames and lands back within
tolerance of where it was, within max_span seconds – the tracker briefly
locking onto something other than the animal. Returns a list of two logical
vectors over frames: velocity (displaced frames and the return frame) and
position (displaced frames only).
find_still_bins(): logical vector, TRUE for windows in which the
animal did not change position. The median positions before and after the
window must agree, and the positions across the whole window must agree once
the single highest and lowest window are set aside, so that a glitch in one
window is forgiven but pacing back and forth is not.
max_velocity_detector, motion_qc, sleep_rules
Defines whether a single animal is moving according to:
max_velocity_detector( data, time_window_length, velocity_correction_coef = 0.003, masking_duration = 6 ) max_velocity_detector_legacy(data, velocity_threshold = 0.006) virtual_beam_cross_detector(data, time_window_length)max_velocity_detector( data, time_window_length, velocity_correction_coef = 0.003, masking_duration = 6 ) max_velocity_detector_legacy(data, velocity_threshold = 0.006) virtual_beam_cross_detector(data, time_window_length)
data |
data.table::data.table containing behavioural variables of a single animal (no id).
It must have the columns |
time_window_length |
number of seconds to be used by the motion classifier. This corresponds to the sampling period of the output data. |
velocity_correction_coef |
an empirical coefficient to correct velocity with respect to variable framerate. |
masking_duration |
number of seconds during which any movement is ignored (velocity is set to 0) after a stimulus is delivered (a.k.a. interaction). |
velocity_threshold |
uncorrected velocity above which an animal is classified as ‘moving’ (for the legacy version). |
Validated and corrected subpixel velocity (max_velocity_detector), the most rigorous
Uncorrected subpixel velocity (max_velocity_detector_legacy)
Crossing a virtual beam in the middle of the region of interest (virtual_beam_cross_detector)
max_velocity_detector is the default movement classification for real-time ethoscope experiments. It is benchmarked against human-generated ground truth.
These functions are rarely called directly, but typically used is in the context of sleep_annotation.
an object of the same type as data (i.e. data.table::data.table or behavr::behavr) with additional columns:
moving Logical, TRUE iff. motion was detected.
beam_crosses The number of beam crosses
(when the animal crosses x = 0.5 – that is the midpoint of the region of interest) within the time window
max_velocity The maximal velocity within the time window.
The resulting data is sampled at a period equals to time_window_length.
sleep_annotation – which requieres a motion detector
Run on raw ethoscope data before scoring sleep. For each animal and light
phase it reports how often a still animal crosses the fixed threshold
(fp_rate_fixed) and what fraction of genuine rests of min_time_immobile
would survive that (rest_survival_fixed, (1 - fp)^windows).
fp_rate_fixed above about 0.01 means classic sleep estimates are unreliable;
score sleep with sleep_annotation(rule = "k") instead. A large
untracked_fraction matters under either rule: with the default
untracked = "immobile", windows where the animal was lost count as still.
motion_qc( data, time_window_length = 10, velocity_correction_coef = 0.003, velocity_threshold = 1, min_time_immobile = 300, day_length = 24, lights_off = 12 )motion_qc( data, time_window_length = 10, velocity_correction_coef = 0.003, velocity_threshold = 1, min_time_immobile = 300, day_length = 24, lights_off = 12 )
data |
behavr::behavr table (or keyed data.table::data.table) of raw ethoscope data, or the data of a single animal. |
time_window_length |
window size in seconds. |
velocity_correction_coef |
an empirical coefficient to correct velocity with respect to variable framerate. |
velocity_threshold |
the fixed threshold being checked. |
min_time_immobile |
sleep criterion in seconds. |
day_length |
length of the day in hours. |
lights_off |
hour of lights off. |
a data.table::data.table with one row per animal and phase:
phase, n_bins, untracked_fraction, spike_fraction (frames that are
tracking spikes, see find_spikes), n_still, fp_rate_fixed,
rest_survival_fixed.
motion_calibration, sleep_rules
This function first uses a motion classifier to decide whether an animal is moving during a given time window. Then, it defines sleep as contiguous immobility for a minimum duration.
sleep_annotation( data, time_window_length = 10, min_time_immobile = 300, motion_detector_FUN = max_velocity_detector, untracked = c("immobile", "break"), rule = NULL, k = NULL, pixel = NULL, ... ) sleep_dam_annotation(data, min_time_immobile = 300)sleep_annotation( data, time_window_length = 10, min_time_immobile = 300, motion_detector_FUN = max_velocity_detector, untracked = c("immobile", "break"), rule = NULL, k = NULL, pixel = NULL, ... ) sleep_dam_annotation(data, min_time_immobile = 300)
data |
data.table::data.table containing behavioural variable from or one multiple animals.
When it has a key, unique values, are assumed to represent unique individuals (e.g. in a behavr::behavr table).
Otherwise, it analysis the data as coming from a single animal. |
time_window_length |
number of seconds to be used by the motion classifier. This corresponds to the sampling period of the output data. |
min_time_immobile |
Minimal duration (in s) of a sleep bout. Immobility bouts longer or equal to this value are considered as sleep. |
motion_detector_FUN |
function used to classify movement |
untracked |
how windows with no tracked data enter sleep scoring. |
rule |
|
k |
with |
pixel |
with |
... |
extra arguments to be passed to |
The default time_window_length is 300 seconds – it is also known as the "5-minute rule".
sleep_annotation is typically used for ethoscope data, whilst sleep_dam_annotation only works on DAM2 data.
These functions are rarely used directly, but rather passed as an argument to a data loading function,
so that analysis can be performed on the go.
a behavr::behavr table similar to data with additional variables/annotations (i.e. moving and asleep).
The resulting data will only have one data point every time_window_length seconds.
The relevant rethomic tutorial section – on sleep analysis
motion_detectors – options for the motion_detector_FUN argument
bout_analysis – to further analyse sleep bouts in terms of onset and length
dt_one_animal <- toy_ethoscope_data(seed=2) ####### Ethoscope, corrected velocity classification ######### sleep_dt <- sleep_annotation(dt_one_animal, rule = "classic", masking_duration=0) print(sleep_dt) # We could make a sleep `barecode' ## Not run: library(ggplot2) ggplot(sleep_dt, aes(t,y="Animal 1",fill=asleep)) + geom_tile() + scale_x_time() ## End(Not run) ####### Ethoscope, virutal beam cross classification ######### sleep_dt2 <- sleep_annotation(dt_one_animal, rule = "classic", motion_detector_FUN=virtual_beam_cross_detector) ## Not run: library(ggplot2) ggplot(sleep_dt2, aes(t,y="Animal 1",fill=asleep)) + geom_tile() + scale_x_time() ## End(Not run) ####### DAM data, de facto beam cross classification ###### dt_one_animal <- toy_dam_data(seed=7) sleep_dt <- sleep_dam_annotation(dt_one_animal) ## Not run: library(ggplot2) ggplot(sleep_dt, aes(t,y="Animal 1",fill=asleep)) + geom_tile() + scale_x_time() ## End(Not run)dt_one_animal <- toy_ethoscope_data(seed=2) ####### Ethoscope, corrected velocity classification ######### sleep_dt <- sleep_annotation(dt_one_animal, rule = "classic", masking_duration=0) print(sleep_dt) # We could make a sleep `barecode' ## Not run: library(ggplot2) ggplot(sleep_dt, aes(t,y="Animal 1",fill=asleep)) + geom_tile() + scale_x_time() ## End(Not run) ####### Ethoscope, virutal beam cross classification ######### sleep_dt2 <- sleep_annotation(dt_one_animal, rule = "classic", motion_detector_FUN=virtual_beam_cross_detector) ## Not run: library(ggplot2) ggplot(sleep_dt2, aes(t,y="Animal 1",fill=asleep)) + geom_tile() + scale_x_time() ## End(Not run) ####### DAM data, de facto beam cross classification ###### dt_one_animal <- toy_dam_data(seed=7) sleep_dt <- sleep_dam_annotation(dt_one_animal) ## Not run: library(ggplot2) ggplot(sleep_dt, aes(t,y="Animal 1",fill=asleep)) + geom_tile() + scale_x_time() ## End(Not run)
The classic rule calls a 10-s window "moving" when any frame's velocity
exceeds a threshold, so a single noisy frame breaks a sleep bout. The k-rule
asks two questions instead: did the animal walk (its median position moved
more than 10 px from the previous window), and did it move in place (at least
k sustained movement events started in a centred 60-s window)?
classify_events( x, y, moving, tolerance = RETURN_TOLERANCE_PIXELS, max_flicker_frames = MAX_FLICKER_FRAMES ) k_rule_bins( t, x, y, xy_dist_log10x1000, k = 3, pixel = 1, velocity_correction_coef = 0.003, min_sleep_bins = 30, untracked = c("immobile", "break") )classify_events( x, y, moving, tolerance = RETURN_TOLERANCE_PIXELS, max_flicker_frames = MAX_FLICKER_FRAMES ) k_rule_bins( t, x, y, xy_dist_log10x1000, k = 3, pixel = 1, velocity_correction_coef = 0.003, min_sleep_bins = 30, untracked = c("immobile", "break") )
x, y
|
positions of one animal, one per frame, in recording order. |
moving |
logical, |
tolerance |
how close a flicker must return, in the units of |
max_flicker_frames |
longest run of moving frames that can be a flicker. |
t |
frame times in seconds, in recording order. |
xy_dist_log10x1000 |
the tracker's per-frame displacement. |
k |
sustained events in the centred 60-s window that make a window awake. |
pixel |
one pixel in the units of |
velocity_correction_coef |
as in max_velocity_detector; a frame moves when its corrected velocity exceeds 1. |
min_sleep_bins |
shortest sleep bout, in 10-s windows. |
untracked |
|
A movement event is a run of consecutive frames above the classic velocity test. Events in which the position never leaves the pixel it started from ("subpixel"), and those that jump out and land back within a pixel in one or two frames ("flicker"), are tracking noise and are ignored; only the remaining "sustained" events count. Sleep is then 5 minutes or more of windows with neither walking nor such movement.
Windows without frames are handled as in the classic rule. By default
(untracked = "immobile") they count as still, and the first window with frames
after them is walking only if the animal is found more than 10 px from where it
was last seen. Background-subtraction tracking loses still flies, so this is
what keeps their sleep. untracked = "break" never scores such windows as sleep
and reproduces the reference rule exactly.
Evidence: against pixel-motion truth from video, night-time error per animal was 0.03-0.07 with either tracker; and in 136 air-puff runs (1,319 flies), flies the rule scores asleep, by day too, respond to puffs like sleeping flies (real minus sham response +1.2 percentage points asleep by every rule, +3.2 asleep only at k = 3, +5.2 awake).
Choose it with sleep_annotation(rule = "k"), or declare it once with
options(sleepr.sleep_rule = "k"); sleep_annotation has no default rule.
The algorithm and its defaults are identical to ethoscopy's sleep_rules
module, so both packages score the same data alike.
classify_events(): split runs of moving frames into events. The frame
before a run is its anchor, so a run on the first frame is not an event. An
event is subpixel if every frame is exactly at the anchor's position, else a
flicker if it has at most max_flicker_frames frames and the next frame is
back within tolerance of the anchor, else sustained. Returns a list with
start (row of the first frame) and class (0 subpixel, 1 flicker, 2 sustained).
k_rule_bins(): score one animal's frames window by window. Returns a
data.table::data.table with one row per 10-s window from the first to the
last window with frames: t (window start, s), has_data, walking,
sustained (events starting in the window), micro_awake and asleep.