TitleAuthorReplicatorGoalInput DataOutput
Creating BIDS Events TSVs from ITC EPrime FilesTinashe TaperaTBDConvert EPrime data into BIDSEPrime filesBIDS Valid Events TSVs

Introduction

Alongside your imaging data, BIDS also allows for the storage and sharing of events data. Here, we demonstrate how to transform a common event file known as EPrime into a BIDS valid events file.

EPrime

EPrime is a software suite for experimental psychology. Without saying too much, EPrime software can be linked to an fMRI scanner and be used to record responses to questionnaires, run experiments with participants, and so on. The output of these experiments is an EPrime file:

ff <- read.delim("RTG1_1ITCscanner1LLA-04888-1.txt", fileEncoding="UCS-2LE")
# eprime files are already so esoteric that we have to use specific file encoding
# in R for them to work

This is one EPrime file from a scan session for one subject:

ff$X....Header.Start....[1:50]
 [1] "VersionPersist: 1"                "LevelName: Session"              
 [3] "LevelName: Block"                 "LevelName: Trial"                
 [5] "LevelName: SubTrial"              "LevelName: LogLevel5"            
 [7] "LevelName: LogLevel6"             "LevelName: LogLevel7"            
 [9] "LevelName: LogLevel8"             "LevelName: LogLevel9"            
[11] "LevelName: LogLevel10"            "Experiment: RTG1_1ITCscanner1LLA"
[13] "SessionDate: 04-26-2011"          "SessionTime: 14:30:53"           
[15] "SessionTimeUtc: 6:30:53 PM"       "Subject: 04888"                  
[17] "Session: 1"                       "RandomSeed: 711119607"           
[19] "Group: 1"                         "Display.RefreshRate: 60.052"     
[21] "*** Header End ***"               ""                                
[23] ""                                 "Level: 3"                        
[25] ""                                 ""                                
[27] "*** LogFrame Start ***"           ""                                
[29] ""                                 "Procedure: TrialProc"            
[31] ""                                 ""                                
[33] "TrialList: 1"                     ""                                
[35] ""                                 "Offer: 23"                       
[37] ""                                 ""                                
[39] "delay: 21"                        ""                                
[41] ""                                 "NullDuration: 5000"              
[43] ""                                 ""                                
[45] "LeftRight: 0"                     ""                                
[47] ""                                 "FeedbackDur: 0"                  
[49] ""                                 ""

This is not a very useful representation of data. Fortunately, the rprime package exists for parsing this file.

rprime Package

# install.packages("rprime")
library(rprime)
library(tidyverse) # it pairs well with the tidyverse
── Attaching packages ─────────────────────────────────────── tidyverse 1.3.1 ──

✓ ggplot2 3.3.5     ✓ purrr   0.3.4
✓ tibble  3.1.6     ✓ dplyr   1.0.8
✓ tidyr   1.2.0     ✓ stringr 1.4.0
✓ readr   2.0.0     ✓ forcats 0.5.1

Warning: package 'tidyr' was built under R version 4.1.2

Warning: package 'dplyr' was built under R version 4.1.2

── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()

It’s straightforward to read in an EPrime file, using the following:

dat <- rprime::read_eprime("RTG1_1ITCscanner1LLA-04888-1.txt")
dat <- dat %>%
  rprime::FrameList()

preview_frames(dat)
 Eprime.Level Running Procedure
            1  Header    Header
List of 17
 $ Eprime.Level       : num 1
 $ Eprime.LevelName   : chr "Header_"
 $ Eprime.Basename    : chr "RTG1_1ITCscanner1LLA-04888-1"
 $ Eprime.FrameNumber : chr "1"
 $ Procedure          : chr "Header"
 $ Running            : chr "Header"
 $ VersionPersist     : chr "1"
 $ LevelName          : chr "LogLevel10"
 $ Experiment         : chr "RTG1_1ITCscanner1LLA"
 $ SessionDate        : chr "04-26-2011"
 $ SessionTime        : chr "14:30:53"
 $ SessionTimeUtc     : chr "6:30:53 PM"
 $ Subject            : chr "04888"
 $ Session            : chr "1"
 $ RandomSeed         : chr "711119607"
 $ Group              : chr "1"
 $ Display.RefreshRate: chr "60.052"
 - attr(*, "class")= chr [1:2] "EprimeFrame" "list"

 Eprime.Level   Running Procedure
            3 TrialList TrialProc
List of 66
 $ Eprime.Level                     : num 3
 $ Eprime.LevelName                 : chr "TrialList_1"
 $ Eprime.Basename                  : chr "RTG1_1ITCscanner1LLA-04888-1"
 $ Eprime.FrameNumber               : chr "2"
 $ Procedure                        : chr "TrialProc"
 $ Running                          : chr "TrialList"
 $ Offer                            : chr "23"
 $ delay                            : chr "21"
 $ NullDuration                     : chr "5000"
 $ LeftRight                        : chr "0"
 $ FeedbackDur                      : chr "0"
 $ Cycle                            : chr "1"
 $ Sample                           : chr "1"
 $ nullscreen.OnsetDelay            : chr "71"
 $ nullscreen.OnsetTime             : chr "102383"
 $ nullscreen.DurationError         : chr "-71"
 $ nullscreen.StartTime             : chr "102314"
 $ nullscreen.OffsetTime            : chr "107112"
 $ nullscreen.FinishTime            : chr "107112"
 $ nullscreen.ActionDelay           : chr "0"
 $ nullscreen.ActionTime            : chr "102383"
 $ nullscreen.OffsetDelay           : chr "0"
 $ Choice1.OnsetDelay               : chr "0"
 $ Choice1.OnsetTime                : chr "107312"
 $ Choice1.DurationError            : chr "0"
 $ Choice1.Duration                 : chr "4000"
 $ Choice1.StartTime                : chr "107113"
 $ Choice1.OffsetTime               : chr "111112"
 $ Choice1.FinishTime               : chr "111112"
 $ Choice1.OffsetDelay              : chr "0"
 $ Choice1.RTTime                   : chr "111192"
 $ Choice1.RT                       : chr "3880"
 $ Choice1.RESP                     : chr "y"
 $ Choice1.CRESP                    : chr "r"
 $ FeedbackDisplay6.OnsetDelay      : chr "0"
 $ FeedbackDisplay6.OnsetTime       : chr "0"
 $ FeedbackDisplay6.DurationError   : chr "-999999"
 $ FeedbackDisplay6.Duration        : chr "0"
 $ FeedbackDisplay6.StartTime       : chr "111182"
 $ FeedbackDisplay6.OffsetTime      : chr "0"
 $ FeedbackDisplay6.FinishTime      : chr "111192"
 $ FeedbackDisplay6.OffsetDelay     : chr "-999999"
 $ Choice.OnsetDelay                : chr "0"
 $ Choice.OnsetTime                 : chr "0"
 $ Choice.DurationError             : chr "0"
 $ Choice.Duration                  : chr "4000"
 $ Choice.StartTime                 : chr "0"
 $ Choice.OffsetTime                : chr "0"
 $ Choice.FinishTime                : chr "0"
 $ Choice.TargetOffsetTime          : chr "0"
 $ Choice.TargetOnsetTime           : chr "0"
 $ Choice.OffsetDelay               : chr "0"
 $ Choice.RTTime                    : chr "0"
 $ Choice.RT                        : chr "0"
 $ Choice.RESP                      : chr ""
 $ Choice.CRESP                     : chr ""
 $ FeedbackDisplay3.OnsetDelay      : chr "0"
 $ FeedbackDisplay3.OnsetTime       : chr "0"
 $ FeedbackDisplay3.DurationError   : chr "0"
 $ FeedbackDisplay3.Duration        : chr "0"
 $ FeedbackDisplay3.StartTime       : chr "0"
 $ FeedbackDisplay3.OffsetTime      : chr "0"
 $ FeedbackDisplay3.FinishTime      : chr "0"
 $ FeedbackDisplay3.TargetOffsetTime: chr "0"
 $ FeedbackDisplay3.TargetOnsetTime : chr "0"
 $ FeedbackDisplay3.OffsetDelay     : chr "0"
 - attr(*, "class")= chr [1:2] "EprimeFrame" "list"

 Eprime.Level   Running Procedure
            2 BlockList BlockProc
List of 9
 $ Eprime.Level      : num 2
 $ Eprime.LevelName  : chr "BlockList_1"
 $ Eprime.Basename   : chr "RTG1_1ITCscanner1LLA-04888-1"
 $ Eprime.FrameNumber: chr "52"
 $ Procedure         : chr "BlockProc"
 $ Running           : chr "BlockList"
 $ PracticeMode      : chr "?"
 $ Cycle             : chr "1"
 $ Sample            : chr "1"
 - attr(*, "class")= chr [1:2] "EprimeFrame" "list"

 Eprime.Level Running Procedure
            1    <NA>      <NA>
List of 65
 $ Eprime.Level            : num 1
 $ Eprime.LevelName        : logi NA
 $ Eprime.Basename         : chr "RTG1_1ITCscanner1LLA-04888-1"
 $ Eprime.FrameNumber      : chr "53"
 $ Procedure               : logi NA
 $ Running                 : logi NA
 $ Experiment              : chr "RTG1_1ITCscanner1LLA"
 $ SessionDate             : chr "04-26-2011"
 $ SessionTime             : chr "14:30:53"
 $ SessionTimeUtc          : chr "6:30:53 PM"
 $ Subject                 : chr "04888"
 $ Session                 : chr "1"
 $ RandomSeed              : chr "711119607"
 $ Group                   : chr "1"
 $ Display.RefreshRate     : chr "60.052"
 $ Slide2.OnsetDelay       : chr "5"
 $ Slide2.OnsetTime        : chr "16024"
 $ Slide2.DurationError    : chr "-999999"
 $ Slide2.PreRelease       : chr "0"
 $ Slide2.Duration         : chr "-1"
 $ Slide2.StartTime        : chr "16019"
 $ Slide2.OffsetTime       : chr "80941"
 $ Slide2.FinishTime       : chr "80941"
 $ Slide2.TimingMode       : chr "0"
 $ Slide2.CustomOnsetTime  : chr "0"
 $ Slide2.CustomOffsetTime : chr "0"
 $ Slide2.ActionDelay      : chr "0"
 $ Slide2.ActionTime       : chr "16024"
 $ Slide2.TargetOffsetTime : chr "-1"
 $ Slide2.TargetOnsetTime  : chr "16019"
 $ Slide2.OffsetDelay      : chr "-999999"
 $ Slide2.RTTime           : chr "80940"
 $ Slide2.ACC              : chr "0"
 $ Slide2.RT               : chr "64916"
 $ Slide2.RESP             : chr "n"
 $ Slide2.CRESP            : chr ""
 $ Slide1.OnsetTime        : chr "80945"
 $ Slide1.DurationError    : chr "-999999"
 $ Slide1.Duration         : chr "-1"
 $ Slide1.StartTime        : chr "80943"
 $ Slide1.OffsetTime       : chr "102312"
 $ Slide1.FinishTime       : chr "102312"
 $ Slide1.RT               : chr "21367"
 $ Goodbye.OnsetDelay      : chr "1"
 $ Goodbye.OnsetTime       : chr "605961"
 $ Goodbye.DurationError   : chr "0"
 $ Goodbye.PreRelease      : chr "0"
 $ Goodbye.Duration        : chr "6000"
 $ Goodbye.StartTime       : chr "605770"
 $ Goodbye.OffsetTime      : chr "611961"
 $ Goodbye.FinishTime      : chr "611961"
 $ Goodbye.TimingMode      : chr "0"
 $ Goodbye.CustomOnsetTime : chr "0"
 $ Goodbye.CustomOffsetTime: chr "0"
 $ Goodbye.ActionDelay     : chr "1"
 $ Goodbye.ActionTime      : chr "605962"
 $ Goodbye.TargetOffsetTime: chr "611961"
 $ Goodbye.TargetOnsetTime : chr "605960"
 $ Goodbye.OffsetDelay     : chr "0"
 $ Goodbye.RTTime          : chr "0"
 $ Goodbye.ACC             : chr "0"
 $ Goodbye.RT              : chr "0"
 $ Goodbye.RESP            : chr ""
 $ Goodbye.CRESP           : chr ""
 $ Clock.StartTimeOfDay    : chr "4/26/2011 2:30:53 PM"
 - attr(*, "class")= chr [1:2] "EprimeFrame" "list"

This creates a more workable data structure. The section with the expriment is called the trial frame.rprime allows you to filter these with built-ins:

trial <- filter_in(dat, "Running", "TrialList")
block <- filter_in(dat, "Running", "BlockList")
slides <- filter_in(dat, "Eprime.Level", 1)

slides_df <- to_data_frame(slides) %>%
  readr::type_convert()
trial_df <- to_data_frame(trial) %>%
  readr::type_convert()

head(trial_df)
Eprime.LevelEprime.LevelNameEprime.BasenameEprime.FrameNumberProcedureRunningOfferdelayNullDurationLeftRightFeedbackDurCycleSamplenullscreen.OnsetDelaynullscreen.OnsetTimenullscreen.DurationErrornullscreen.StartTimenullscreen.OffsetTimenullscreen.FinishTimenullscreen.ActionDelaynullscreen.ActionTimenullscreen.OffsetDelayChoice1.OnsetDelayChoice1.OnsetTimeChoice1.DurationErrorChoice1.DurationChoice1.StartTimeChoice1.OffsetTimeChoice1.FinishTimeChoice1.OffsetDelayChoice1.RTTimeChoice1.RTChoice1.RESPChoice1.CRESPFeedbackDisplay6.OnsetDelayFeedbackDisplay6.OnsetTimeFeedbackDisplay6.DurationErrorFeedbackDisplay6.DurationFeedbackDisplay6.StartTimeFeedbackDisplay6.OffsetTimeFeedbackDisplay6.FinishTimeFeedbackDisplay6.OffsetDelayChoice.OnsetDelayChoice.OnsetTimeChoice.DurationErrorChoice.DurationChoice.StartTimeChoice.OffsetTimeChoice.FinishTimeChoice.TargetOffsetTimeChoice.TargetOnsetTimeChoice.OffsetDelayChoice.RTTimeChoice.RTChoice.RESPChoice.CRESPFeedbackDisplay3.OnsetDelayFeedbackDisplay3.OnsetTimeFeedbackDisplay3.DurationErrorFeedbackDisplay3.DurationFeedbackDisplay3.StartTimeFeedbackDisplay3.OffsetTimeFeedbackDisplay3.FinishTimeFeedbackDisplay3.TargetOffsetTimeFeedbackDisplay3.TargetOnsetTimeFeedbackDisplay3.OffsetDelay
3TrialList_1RTG1_1ITCscanner1LLA-04888-12TrialProcTrialList23.0215000001171102383-711023141071121071120102383001073120400010711311111211111201111923880yr00-99999901111820111192-999999000400000000000NANA0000000000
3TrialList_2RTG1_1ITCscanner1LLA-04888-13TrialProcTrialList28.0550001760128111200-81111991159921159920111200001073120400010711311111211111201111923880yr00-99999901111820111192-9999990116192-9999994000115993119432119432119992116192-9999991194323240yr3119435-37601194341199921199921199921194320
3TrialList_3RTG1_1ITCscanner1LLA-04888-14TrialProcTrialList30.55550001136113012019201199991249921249920120192001073120400010711311111211111201111923880yr00-99999901111820111192-9999990125192-9999994000124993127832127832128992125192-9999991278312639rr3127835-313611278341289931289931289931278320
3TrialList_4RTG1_1ITCscanner1LLA-04888-15TrialProcTrialList39.034170001014012919301290001459931459930129193001073120400010711311111211111201111923880yr00-99999901111820111192-999999014619304000145994149993149993149993146193000NAr01501932000149995150193150193149993150193200
3TrialList_5RTG1_1ITCscanner1LLA-04888-16TrialProcTrialList25.0780000993159150202-9150201157993157993015020200158193-9999994000157994161200161200-9999991612003007rr3161203-39931612021619931619930014619304000145994149993149993149993146193000NAr01501932000149995150193150193149993150193200
3TrialList_6RTG1_1ITCscanner1LLA-04888-17TrialProcTrialList22.5452000113291601621930162000163993163993016219300158193-9999994000157994161200161200-9999991612003007rr3161203-399316120216199316199300164193-9999994000163994166864166864167993164193-9999991668642671rr3166867-313291668661679931679931679931668640

This is far more useful, as we can see some of the encoded experimental variables like offer, delay, and choice.

You can generally expect this method to be useful for parsing any EPrime file. To export this to BIDS, one could simply write this dataframe to a BIDS-valid events file:

trial_df %>%
  write_delim("sub-X/ses-1/func/sub-X_ses-1_task-ITC_events.tsv")

In this case, we have to do some more data wrangling for the ITC experimental design.

ITC Experimental Design

Each line of this table indicates an event (trial) happening in the experiment. In an ITC task, participants are shown a value of money and a delay. They’re asked, “would you rather receive $20 now, or wait X number of days to receive $Y later?” Hence, each line is an offer in this paradigm. To convert this to BIDS, we expect an output

Mandatory in BIDS:

onset: time that the trial happened from the start of the scan

duration: how long that event happened for

Required for ITC

choice: did they choose the delayed offer or not

button_press: did they go left or right

amount: how much they were offered

delay: what was the delay on the offer

reaction_time: how long did they take to make their decision

Data Wrangling

The first thing we need to define is what choice they made. The column LeftRight indicates whether the instant $20 option is on the left or right of the screen. If LeftRight == 1, the delayed option was presented on the left.

We’ll code this as a factor delay_position with two levels, 1 to indicate it was on the right, and 0 to indicate it was on the left:

trial_df_proc <- trial_df %>%
  select(-contains("null"), -contains("Eprime"), -contains("Feedback")) %>%
  mutate(delay_position = ifelse(LeftRight == 0, 1, 0))

Next, we define which button they pressed, left (0) or right (1):

We’ll also define the motor response they made, button_press, as either 1 (right) or 0 (left). We use CHOICE.RESP and CHOICE1.RESP as the indicator of which side they picked. If the delayed option was on the left, use CHOICE.RESP; r is right, y is left.

Then, we encode the choice as either 1=delayed (the delayed position and the chosen button press where the same) or 0=now (the delayed position and the chosen button press where different):

trial_df_proc <- trial_df_proc %>%
  mutate(
    button_press = case_when(
      
      LeftRight == 1 & Choice.RESP == "y" ~ 0,
      LeftRight == 1 & Choice.RESP == "r" ~ 1,
      LeftRight == 0 & Choice1.RESP == "y" ~ 0,
      LeftRight == 0 & Choice1.RESP == "r" ~ 1
      
    ),
    choice = case_when(
      
      LeftRight == 1 & Choice.RESP == "y" ~ 1,
      LeftRight == 1 & Choice.RESP == "r" ~ 0,
      LeftRight == 0 & Choice1.RESP == "y" ~ 0,
      LeftRight == 0 & Choice1.RESP == "r" ~ 1
      
      )
    )

We also include the amount and delay:

trial_df_proc <- trial_df_proc %>%
  rename(offer = Offer) %>%
  select(offer, delay, choice, button_press, delay_position, everything())

The duration of the trial is uniform at 4000ms. There are two columns for event timings: Choice1.OnsetTime and Choice.OnsetTime. If LeftRight==1 the task proceeds to Choice.*. If they clicked right, the time for this gets recorded in Choice.OnsetTime, and the value in Choice1.OnsetTime is duplicated from the previous row:

10            216687           222699  left
11            228727           222699  left
12            246744           222699  right
13            246744           264762  right
14            246744           270774  right

Here we use the duplicated function to tell us if a value in a vector is a duplicate of itself. If there’s a duplicate, take the value from the opposite column:

trial_df_proc <- trial_df_proc %>%
  mutate(
    onset = case_when(
       
      # the first row should both not be duplicates
      !duplicated(Choice.OnsetTime) & !duplicated(Choice1.OnsetTime) ~ 0,
      
      # if any value in Choice is a duplicate, choose the value from Choice1
      duplicated(Choice.OnsetTime) ~ Choice1.OnsetTime,
      
      # vice versa
      duplicated(Choice1.OnsetTime) ~ Choice.OnsetTime
    ),
    
    duration = 4000,
    
  ) %>%
  select(onset, duration, everything())

We can do the same for response time:

trial_df_proc <- trial_df_proc %>%
  mutate(
    row_num = row_number(),
    response_time = case_when(
       
      # the first row should be the one that's not zero
      row_num == 1 & Choice.RT == 0 ~ Choice1.RT,
      row_num == 1 & Choice1.RT == 0 ~ Choice.RT,
      
      # if any value in Choice is a duplicate, choose the value from Choice1
      duplicated(Choice.RT) ~ Choice1.RT,
      
      # vice versa
      duplicated(Choice1.RT) ~ Choice.RT
    )
  ) %>%
  select(onset, duration, response_time, everything(), -row_num)

Lastly, we have to account for the timepoint that the task began (as opposed to when the participant is reading instructions). This is given in the slides_df:

trial_df_proc <- trial_df_proc %>%
  mutate(onset = ifelse(onset == 0, onset, onset - slides_df$Slide1.OffsetTime[2]))

Here’s the data head so far:

trial_df_sv <- trial_df_proc %>%
  select(onset:delay_position) %>%
  mutate(IA = 20)
trial_df_sv %>%
  head()
onsetdurationresponse_timeofferdelaychoicebutton_pressdelay_positionIA
04000388023.02100120
138804000324028.0510020
228804000263930.55501020
438814000388039.034NANA020
558814000300725.0711120
618814000267122.54501020

K Parameter Estimation

The penultimate step is to estimate each participant’s k parameter. This is estimated from a discount function that you can read about in this paper. For our purposes, the code is in matlab and exists as a precompiled binary on CUBIC at /cbica/projects/wolf_satterthwaite_reward/Curation/code/itc_eprime/matlab_code/kable_itc_wrapper. It was run with /cbica/projects/wolf_satterthwaite_reward/Curation/code/itc_eprime/loop_processed_eprimes.sh, which outputs the k parameter value to /cbica/projects/wolf_satterthwaite_reward/Curation/code/itc_eprime/matlab_code/data/processed/*.txt

Output

After estimating the k parameter, we can then calculate the subjective value (the participant’s evaluation of each offer of money now or later as the experiment continues) by doing $\frac{\text{offer}} {1 + k \times \text{delay}}$:

# a fake k value
k_param <- 0.07106205
final_df <- trial_df_sv %>%
  mutate(subjective_value = offer / (1 + k_param * delay)) %>%
  mutate_if(~ is.numeric(.) && all(unique(.) %in% c(0, 1, NA)), factor) %>%
  select(-IA) 

And here’s the output for this example subject:

final_df %>%
  head(10)
onsetdurationresponse_timeofferdelaychoicebutton_pressdelay_positionsubjective_value
04000388023.0210019.228412
138804000324028.0510020.659476
228804000263930.5550106.213821
438814000388039.034NANA011.416495
558814000300725.0711116.695223
618814000267122.5450105.359960
678814000247137.51490103.236038
828814000395940.5971005.131117
948404000289631.5680015.401031
1008404000300046.54810010.541879

This procedure is reproduced for the full dataset in /cbica/projects/wolf_satterthwaite_reward/Curation/code/itc_eprime/parse_eprime.Rmd.