It's about time: specifying annotation intervals
As described here, Luna supports a number of formats for specifying the
start/stop times (and dates) of annotations (in either an
.xml annotation file). Here we detail these formats and provide a few examples.
When specifying formats below, the following abbreviations are used:
x: elapsed seconds
hh: hours (clock 24hr: 00 to 23; clock 12hr: 1-12; elapsed: 0+)
mm: minutes (0 - 59) or month (1 - 12, or Jan-Dec)
ss: seconds (0 to 60) and can include fractional components
dd: day (1 - 31)
yy: year (85-99 = 1985 - 1999; 00-84 = 2000 - 2084)
yyyy: year (from 1985 onwards)
There are four basic ways to specify start-times:
elapsed seconds past the EDF start, e.g.
absolute clock-times (pref. 24hr based as per EDF headers), e.g.
elapsed times in hh:mm:ss format starting
0+, e.g. two hours from the EDF start
e:10for the 10th epoch
Stop-times can be specified in a similar manner as for start-times, but there are two additional options:
specifying the duration of the event in seconds, by starting with
+30for a 30-second interval
specifying an event lasting until the start of the next annotation in the file, by writing an ellipsis (
For example, if the EDF start time is
22:28:15, then all the examples below are identical (i.e. a 30-second interval starting
from 2 minutes into the recording until 2 minutes and 30 seconds):
|Start time field||Stop time field||Example (start, stop)||Description|
||Elapsed seconds from EDF start for both start & stop|
||Using duration to specify a 30-second interval starting at 120 seconds past EDF start|
||Using clock-time to specify start & stop (note: a
||As above, but using a duration rather than explicit stop time|
||Explicit date-time strings (date delimiters
||Elapsed hh:mm:ss times from EDF start (rather than clock-times)|
||Duration is up until the next annotation in the same file (assumed to be at 150s here); this can be used with any format of start-time (i.e. absolute or relative clock-time)|
For annotation files, Luna requires a colon
:) as the delimiter for times, e.g.
hh:mm:ss. (For EDF
: can be used; and Luna may output times
with period delimiters in some cases because of this.) For dates,
- is the preferred delimiter in annotation files, although
is also accepted: for a date-time string
the canonical format. (Note that Luna uses European date
formats with day, then month, then year as per EDF spec.
Consider we have an EDF
f.edf with the following start time and date in the EDF headers:
duration 08.56.46, 32206s | time 21.23.23 - 06.20.09 | date 29.07.16
Also consider this four-column
.annot annotation file:
a01 . e:5 . a02 . e:5 e:5 a03 . 120 150 a04 . 120 +30 a05 . 21:25:23 21:25:53 a06 . 9:25:23pm 9:25:53pm a07 . 9:25:23 pm 9:25:53 pm a08 . 21:25:23 +30 a09 . 0+00:02:00 0+00:02:30 a10 . 0+00:02:00 +30 a11 . 29-07-16-21:25:23 29-07-16-21:25:53 a12 . 29-07-16-21:25:23 +30 a13 . 21:25:23 ... a00 . 21:25:53 21:25:53 a98 . 28-07-16-21:25:23 28-07-16-21:25:53 a99 . 30-07-16-21:25:23 30-07-16-21:25:53
Here we have 16 annotations (each of a distinct class labelled
a99). Of the main annotations (
a13), all specify
the identical period in the recording.
a02use epoch-encoding; assuming 30-second epochs starting at the EDF start, the 5th epoch spans from 120 to 150 seconds from the start of the EDF; when specifying a single epoch, the stop field can be left as missing (
a03explicitly gives the start time in seconds (120) past the EDF start;
a04is similar but specifies a duration
+30rather than an explicit stop time (
a05uses a 24-hr clock-time - as the EDF start is
21:25:23is two minutes (120 seconds) past the EDF start
a07illustrate the use of 12-hour AM/PM encoding formats
a08combines absolute clock-time to specify the start, with a duration (in seconds) in the second field
a09uses elapsed time in hh:mm:ss format, i.e. 120 seconds (
02:00) and 150 seconds (
02:30) past the EDF start;
a10is similar but uses a duration (
a11gives explicit absolute date-time strings in
dd-mm-yy-hh:mm:ssformat for both start and stop times;
a12is similar but uses the duration encoding of
+30instead of a stop date-time
a13uses clock-time encoding combined with an ellipsis to specify the duration as up to the next observed annotation in the file (or EDF end, if this is the last annotation in the file). We therefore add a dummy event
a00after it, which is a zero-duration timestamp at 150 seconds past the EDF start (i.e. purely to define
a13). In practice, this type of encoding might be used for sleep stages, or body positions, where we only record the change of state, and we wish for the entire recording to be covered by some type of these annotations.
The file contains two other events that test the effect of shifting
the date field either a day earlier (
a98) or later (
a98 occurs before the start of the EDF (and so should be
ignored); in contrast,
a99 occurs 24 hours after all the other
To test this, we will load the annotations and use
WRITE-ANNOTS to output the file using a uniform format (by default, elapsed seconds) called
luna f.edf annot-file=a1.annot -s WRITE-ANNOTS file=a2.annot
a2.annot file (removing extra blank columns and re-ordering rows for clarity) - of the primary events (
a13) we see
they all resolve to the identical interval:
a01 . 120.000 150.000 a02 . 120.000 150.000 a03 . 120.000 150.000 a04 . 120.000 150.000 a05 . 120.000 150.000 a06 . 120.000 150.000 a07 . 120.000 150.000 a08 . 120.000 150.000 a09 . 120.000 150.000 a10 . 120.000 150.000 a11 . 120.000 150.000 a12 . 120.000 150.000 a13 . 120.000 150.000
a00is at 150 seconds exactly:
a00 . 150.000 150.000 .
a98is not present - it is ignored as it occurred prior to the EDF start (one day earlier). In contrast,
a99(starting one day later) is:
a99 . 86520.000 86550.000
Modifying the EDF start
To further explore and confirm the behavior of Luna's handling of times and dates, here we perform the same command but first modifying the EDF headers to 1) set the start date to null, 2) alter the start time, and in the section below, 3) alter the start dates.
First, we'll set the EDF start date to null (i.e.
anon=T to the
command line modifies the EDF header before attaching annotations, and so annotations will be processed differently
depending on whether they have absolute or relative times. In fact, if the EDF header has a null start date, then Luna will not
allow any dates to be specified in annotation files - i.e. as it would not be possible to correctly place the annotation relative
to the start of the EDF, as the duration between that and the annotation is unknown.
luna f.edf annot-file=a1.annot anon=T -s WRITE-ANNOTS file=a2.annot
error : cannot specify annotations with date-times if the EDF start date is null (1.1.85)
As the message states, specifying explicit dates in an annotation file
is only allowed if the EDF header has a non-null date defined.
If we drop the offending lines from the file (i.e.
a12 as well
a99) then this will run and give identical results (120
to 150 seconds past the EDF start) for all remaining primary intervals.
Next, we'll alter the start time of the EDF header by setting the
starttime special variable:
luna f.edf annot-file=a1.annot starttime=19:00:00 -s WRITE-ANNOTS file=a2.annot
This sets the EDF start to 7:00:00pm instead of 9:23:23pm, i.e. 2 hours, 23 minutes and 23 seconds earlier (or 2 x 3600 + 23 x 60 + 23 = 8603 seconds). Considering the output, we now see that all annotations using relative start times (or epoch-encoding) have the same outputs as before when generating a new file that uses elapsed seconds, i.e. as these do not depend on absolute times:
a01 . 120 150 a02 . 120 150 a03 . 120 150 a04 . 120 150 a09 . 120 150 a10 . 120 150
In contrast, annotations that used absolute times are delayed w.r.t. to the EDF start, e.g. below, 8723 = 8603 + 120 seconds.
a05 . 8723 8753 a06 . 8723 8753 a07 . 8723 8753 a08 . 8723 8753 a11 . 8723 8753 a12 . 8723 8753 a13 . 8723 8753
Note that this is not a problem per se - we're merely altering the times to check the behavior of Luna is as expected under these different encodings.
Dates and long recordings
By default, annotations are only specified by times (rather than dates and times) which can impose some limitations on annotation encoding for long (>24 hour) recordings.
Specifying annotations in elapsed seconds or elapsed hh:mm:ss formats is acceptable for recordings over 24 hours. For example, here we can define two (identical) events that begin at 9:25:23pm (the same time as all the annotations in the example above, or 120 seconds past the EDF start) but alter the annotation such that it occurs exactly three days later. In elapsed seconds, this would be 259320 = 120 + 3 x 86,400 (where 86,400 is the number of seconds in a day):
b01 . 259320 259350
b02 . 0+72:02:00 0+72:02:30
Using the above
WRITE-ANNOTS command after reading in these events,
then with no further options except
file, the command emits times in
elapsed seconds, with both events correctly aligned as overlapping:
b01 . 259320 259350 b02 . 259320 259350
However, when dealing with long recordings, using
hms clock-time encoding would yield ambiguous/incorrect results
when writing a new annotation file, i.e. as we obviously lose information about the day:
WRITE-ANNOTS file=a2.annot hms
b01 . 21:25:23 21:25:53 b02 . 21:25:23 21:25:53
In this scenario, if we want to retain absolute clock-times in the created annotation file, we must specify
dhms instead, to emit date-time strings:
WRITE-ANNOTS file=a2.annot dhms
b01 . 1-8-2016-21:25:23 1-8-2016-21:25:53 b02 . 1-8-2016-21:25:23 1-8-2016-21:25:53
Dates in Luna
Luna understands calendar conventions for advancing
X number of days (i.e. correctly counting 3 days from 28th of July to be the 1st August as above, and
it will respect leap year conventions. When parsing dates, it is acceptable to use string codes for months
as well as numbers 1 - 12: e.g.
01-Aug-2016. Month strings are the standard three-letter codes
and are case insensitive.
The null date is
01.01.85 as per the EDF specification
1.1.85 or in annotation files
, meaning that no earlier dates can be specified; internally, Luna
represents dates as the number of days past
1.1.85. A value of
1.1.85 (i.e. 0 or null) is a special case, taken to mean that
no date is specified (which impacts how Luna parses dates, as
shown above). If you want to have multi-day recordings and use
absolute clock-times/dates in annotations when working with an
anonymized EDF (i.e. a null start date), we suggest you use
2.1.85 (or any other arbitrary date) instead (or simply use
elapsed time encodings to specify annotations).
Internal representation of time and long recordings
Internally, all annotations are mapped to elapsed time-points which are unsigned (positive) integers from 0 (the EDF start) to a very large number (8,446,744,073,709,551,615). Each time-point is 0.000000001 seconds (1e-9 seconds) which theoretically allows for very long recordings (i.e. over 260 years). The important constraint to note is that no negative times can be specified, meaning that no annotations can be represented as occurring before the EDF start.
To confirm the treatment of dates, we will use
startdate to force
the EDF header to different dates (in place of the original
29.07.16) but keeping the same
a1.annot. First, we'll set
the EDF start time to one year later, i.e. 2017:
luna f.edf annot-file=a1.annot startdate=29.07.17 -s WRITE-ANNOTS file=a2.annot
a01 . 120 150 a02 . 120 150 a03 . 120 150 a04 . 120 150 a05 . 120 150 a06 . 120 150 a07 . 120 150 a08 . 120 150 a09 . 120 150 a10 . 120 150 a13 . 120 150 a00 . 150 150
Note that in the above output, we're missing
a12 (as well
a99). This because those annotations used an absolute
date in 2016, i.e. before the EDF start-date. When reading the
annotations in, these are therefore skipped automatically.
Second, we'll now set the EDF start date to one year earlier, i.e. 2015:
luna f.edf annot-file=a1.annot startdate=29.07.15 -s WRITE-ANNOTS file=a2.annot
a01 . 120 150 a02 . 120 150 a03 . 120 150 a04 . 120 150 a05 . 120 150 a06 . 120 150 a07 . 120 150 a08 . 120 150 a09 . 120 150 a10 . 120 150 a11 . 31622520 31622550 a12 . 31622520 31622550 a13 . 120 150
Considering the main set of annotations,
a12 are now included. Because those annotations used an absolute 2016 date, Luna
correctly writes the number of seconds as 31622520 = 120 + 3600 x 24 x 366 (as 2016 is a leap year), with a stop time 30 seconds later.
a99 annotations are one year later (but
either a day behind or a day ahead, i.e. 31536120 = 120 +
3600 x 24 x (366-1) and 31708920 = 120 + 3600 x 24 x (366+1):
a98 . 31536120 31536150 a99 . 31708920 31708950
Analysis of multi-day recordings
Although Luna can represent multi-day recordings, please remember that certain commands (most notably
are not explicitly tailored to this context, and so it would be desirable to create multiple single-day EDFs
prior to running such analyses.
We've seen that Luna can accept times and dates in a number of
.annot format is quite flexible: by default, it has header rows and
six tab-delimited fields, but several of these can be omitted, spaces can
be used instead of tabs, and the header rows are optional. Together with the
alternative options for specifying times, this should enable most text-based
annotation files to be reformatted, with only minimal effort, into a form that Luna can accept.
We've also seen that Luna can handle long (>24 hr) recordings, but in this case, it is necessary to use elapsed time metrics (seconds or hh:mm:ss) or absolute clock-times that also include dates. As we've also seen in other contexts (e.g. exporting EDF+D files or merging EDFs), working with absolute annotation time-stamps can be convenient (over times that are relative to the EDF start) and so the date-time representation support by Luna is useful here.