Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

blink velocity hz? #13

Open
bzuck-temple opened this issue Jan 27, 2021 · 14 comments
Open

blink velocity hz? #13

bzuck-temple opened this issue Jan 27, 2021 · 14 comments

Comments

@bzuck-temple
Copy link

bzuck-temple commented Jan 27, 2021

Hi! Happy to see this implemented! I am wondering if "hz=250" on line 34 should be set to "hz=hz"?

mutate(extendpupil=extend_blinks(blinks_pupil, fillback=fillback, fillforward=fillforward, hz=250)) %>%

mutate(extendpupil=extend_blinks(blinks_pupil, fillback=fillback, fillforward=fillforward, hz=hz)) %>%

If not. what is the rational for using a different sample rate for extending blinks?

Moreover, I am wondering if blink extension and interpolation need to be incorporated in this function at all... I find it odd not to give the choice of interpolation type (linear or cubic) or max gap which you have incorporated into the interpolation function.

Best,
Bonnie

@jgeller112
Copy link
Collaborator

jgeller112 commented Jan 27, 2021

Hi Bonnie,

I would actually hold off on using the velocity function. I am going to incorporate Ronen's blink algo which does not require thresholds to be passed: https://link.springer.com/article/10.3758/s13428-017-1008-1. I will probably push that sometime in the next week or so.

mutate(extendpupil=extend_blinks(blinks_pupil, fillback=fillback, fillforward=fillforward, hz=hz)) %>%

You are correct! I should not have defined it again :)

Moreover, I am wondering if blink extension and interpolation need to be incorporated in this function at all... I find it odd not to give the choice of interpolation type (linear or cubic) or max gap which you have incorporated into the interpolation function.

This was roughly based on something Sebastiaan did in his blink paper. I could not quite capture what he did in that paper so I opted for a basic velocity measure. It is not the most elegant and I think Ronen's algo is a bit better and does not require interpolation or extending the blinks.

I do agree with you that interpolation should probably be taken out.

@jgeller112
Copy link
Collaborator

How is a basic velocity measure working for your group? I would be interested to know.

@jgeller112
Copy link
Collaborator

In this vignette I do separate them: https://github.com/dmirman/gazer/blob/master/vignettes/blink_detection.Rmd.
Not sure why I combined them.

@bzuck-temple
Copy link
Author

oh great! Thanks for the clarity. I looked over Sebastiaan's paper and couldn't quite understand how you got here. We have not implemented velocity in our pipeline. I just starting working today to compare all of the options out there to see which we want to use moving forward. I started with what was implemented in Gazer (NA+extension, blink velocity+extension, MAD+extension) and then moved on to other options out there (Hampel filter, Ronens, etc.). I have the R code from osf for the noised based algo. Do you think you will change it substantially (other than fitting it to gazer format)?

I'm get justing the code down and then will run some tests. I'll let you know how they go!

@jgeller112
Copy link
Collaborator

When did you last download Ronen's code? I actually worked with him and Paul a month ago to fix some errors. I also wrote a bit of code to actually use the function.

@jgeller112
Copy link
Collaborator

jgeller112 commented Jan 27, 2021

I do not plan to change the function much. I will actually probably share my vignette/blog on how to use it with gazeR as well.

@bzuck-temple
Copy link
Author

Just checked- I have the most up to date version of the code. Let me know when you post it. I was able to test it for one trial but I haven't had the chance to edit it to be able to run it grouped by subject x trial. Although I do not have an example to point to, I imagine there may be times where blinks are recorded with these sharp changes without having missing data. I think one draw back to their approach is the reliance on missing data.

@jgeller112
Copy link
Collaborator

Below is some code that will help you on your journey.

#this applies the noise function to each trail by subject
df_blinks<-interp_graph %>%
  dplyr::group_by(subject, trial) %>%
  dplyr::summarise(Blink_Index = list(noise_based_blink_detection(as.matrix(interp_graph$pupil), 250))) %>%
  unnest(c(Blink_Index))

The noise_based_blink_detection should be included in gazeR now.

#this will merge the blink data to your main df/tibble which is interp_graph above
i1 <- match(interp_graph$time, df_blinks$Blink_Index) # need to  match on time and not index to correspond to sampling rate
interp_graph[names(df_blinks)[1:2]] <- lapply(df_blinks[1:2], `[`, i1)

# once merged onsets must be duplicated across until offset in order to mark as blinks
blinks <- interp_graph %>%
        dplyr::group_by(grp = cumsum(!is.na(Label))) %>%
        dplyr::mutate(Label = replace(Label, first(Label) == 'Onset', 'Onset')) %>%
        dplyr::mutate(Blinks_New=ifelse(Label=="Onset" | Label=="Offset", 1, 0)) %>% #turn Onset and Offset 1
        mutate(Blinks_New=ifelse(is.na(Blinks_New), 0, Blinks_New)) %>% # turn rest of values 0 
        dplyr::ungroup()

Blink_Pupil<- blinks %>% 
        group_by(subject, trial) %>%
        mutate(blinks_pupil=ifelse(Blinks_New==1, pupil==NA, pupil)) %>%
         mutate(blinks_pupil=ifelse(blinks_pupil==0, NA, blinks_pupil))
  

@ZKorda
Copy link

ZKorda commented Feb 22, 2022

Hi!
I cannot make the code from your last comment work. The first part workers, but merging the blink data with previous data does not. There is also no column called "Label" with values "Onset" and "Offset" - I am not sure where should this come from? Also, when I try to match and make "i1" it results in NAs with length nrow(interp_graph). Is there a way around this?

Best,
Ziva

@jgeller112
Copy link
Collaborator

jgeller112 commented Feb 22, 2022

Hi,

Have a look at this document. I want to say that I did not write this code and have not had time to extensively validate it. IMHO I would probably use the forward and backward fill method to treat blinks and then interpolate.

Blink Detetcion.pdf

The algo is from here: https://pubmed.ncbi.nlm.nih.gov/29340968/

@bzuck-temple
Copy link
Author

bzuck-temple commented Feb 22, 2022

Hi Jason & Ziva

The pdf fills in a few missing pieces but it still fails when trying to apply the function across subject and trials. My issue was that blinks that occurred in one trial were repeated for every other trial. Below is my fix (see points 2 & 4).

I agree that the forward and backward fill method is a good way to treat blinks. That is the method my group uses and will likely continue to use.

bz

  1. You have to move NAs back to 0 in the pupil column (ew)
pupil_files <- pupil_files %>%  
  dplyr::mutate(pupil=ifelse(is.na(pupil), 0, pupil))
  1. DIFFERENT THAN ABOVE - Do not include the datafile when passing the pupil column in the noise detection function.
df_blinks <- pupil_files %>% 
  group_by(subject, trial) %>% 
  dplyr::summarise(Blink_Index= list(noise_based_blink_detection(as.matrix(pupil), 1000))) %>% 
  unnest(c(Blink_Index)) 
  1. Assign labels to the blink index
df_blinks$Label <- rep(c("Onset", "Offset"), length.out=nrow(df_blinks))
  1. DIFFERENT THAN ABOVE -use dplyr left_join function to match blink index and time (so to include subject and trial)
pupil_files <- left_join(pupil_files, df_blinks, by=c("subject", "trial", c("time"="Blink_Index")))
  1. The rest of the code you provided worked for me.
###----------  CODE from JGeller: WORKING  ----------###
blinks <- pupil_files %>%
  dplyr::group_by(grp = cumsum(!is.na(Label))) %>%
  dplyr::mutate(Label = replace(Label, first(Label) == 'Onset', 'Onset')) %>%
  dplyr::mutate(Blinks_New=ifelse(Label=="Onset" | Label=="Offset", 1, 0)) %>% #turn Onset and Offset 1
  mutate(Blinks_New=ifelse(is.na(Blinks_New), 0, Blinks_New)) %>% # turn rest of values 0 
  dplyr::ungroup()


Blink_Pupil<- blinks %>% 
  group_by(subject, trial) %>%
  mutate(blinks_pupil=ifelse(Blinks_New==1, pupil==NA, pupil)) %>%
  mutate(blinks_pupil=ifelse(blinks_pupil==0, NA, blinks_pupil))
  1. You can plot a trial to test if its working
###---------- Plot Blinks ----------###
blink_graph <- Blink_Pupil  %>%
  dplyr::filter(subject=="YOUR SUBJECT", trial=="YOUR TRIAL")
bold <- element_text(face = "bold", color = "black", size = 14) #axis bold

#Graph
pup_g<- ggplot(blink_graph, aes(x=time, y= pupil)) + geom_point() +
  geom_line(aes(x=time, y=blinks_pupil), colour="green") + xlab("Time (ms)") + ylab("Pupil Size (arbitrary units)") + theme_bw() + theme(axis.title.y=element_text(size = 16, face="bold"), axis.title.x = element_text(size=16, face="bold"), axis.text.x=element_text(size = 12, face="bold"), axis.text.y=element_text(size=12, face="bold")) 
print(pup_g)
  1. Move on to interpolation

@jgeller112
Copy link
Collaborator

Thanks for looking into this @bzuck-temple :). I'm glad you fixed some of the kinks. Did you ever figure out what the best method was?

@jgeller112
Copy link
Collaborator

@bzuck-temple can share how the noise algo worked on your data? In my sample data I had some artifacts and it didn’t do a great job.

@bzuck-temple
Copy link
Author

Anecdotally, I found that it struggled with marking the offset. It kept more of what I would identify as part of the blink than I would have liked.

I only compared the methods visually- never got around to doing a systematic comparison.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants