Binocular combination in the autonomic nervous system.
The 16 matches
- [1] § Materials and methods › Apparatus and stimuli ↔ manuscript.Rmd, lines 2265–2299 · score 1.00 · depth optical cylinders, circular neutral density, appropriately threaded adapters, guarantee safe illumination, white glass discs, highly diffusive white
- [2] § Materials and methods › Apparatus and stimuli ↔ manuscript.Rmd, lines 2265–2299 · score 1.00 · elicit visible percepts, penumbral cone activation, whilst keeping activity, nominally silenced photoreceptors, whilst keeping cone, intrinsically photoreceptive retinal
- [3] § Materials and methods › Apparatus and stimuli ↔ manuscript.Rmd, lines 2235–2263 · score 1.00 · Ocean Optics Jaz, NIST traceable light, degree cone fundamentals, distinct spectral tuning, Silent substitution exploits, Argon lamp
- [4] § Materials and methods › Computational model and parameter estimation ↔ manuscript.Rmd, lines 2301–2361 · score 1.00 · Target eye assignment, consensual response matched, hierarchical Bayesian framework, Gaussian priors, suppression parameters reached, noise parameter
- [5] § Results ↔ manuscript.Rmd, lines 2409–2415 · score 0.99 · downstream temporal filtering, close phase correspondence, rod activity relatively, cone signals differed, quadrature phase, phase arrangement
- [6] § Results ↔ manuscript.Rmd, lines 2378–2397 · score 0.99 · Bayesian hierarchical implementation, weakest suppression corresponds, posterior predictive check, fitted model parameters, compare posterior distributions, strongest suppressive weight
- [7] § Materials and methods › Apparatus and stimuli ↔ manuscript.Rmd, lines 2235–2263 · score 0.99 · supports synchronous launch, customised binocular viewing, synchronised stimulus modulations, spectral sequences prepared, LEDMOTIVE Technologies, active cooling
- [8] § Materials and methods › Procedure ↔ manuscript.Rmd, lines 2301–2361 · score 0.99 · unmodulated background luminance, modified VR headset, black fixation dot, windowless room, 15–17 minutes, 45–60 minutes
- [9] § Results ↔ manuscript.Rmd, lines 2378–2397 · score 0.98 · yellow diamond symbols, strongest dichoptic masking, dichoptic cross response, progressive decline, binocular signal combination, strong suppression reducing
- [10] § Results ↔ manuscript.Rmd, lines 2399–2407 · score 0.97 · periodic response magnitude, root sum square, extreme ratios, Binocular stimulation exceeded, harmonics moderated, qualitative pattern
- [11] § Results ↔ manuscript.Rmd, lines 2364–2376 · score 0.94 · Fourier amplitude spectrum, pupil waveforms, axis scale, light flux modulation, harmonic response, strong modulation
- [12] § Results ↔ manuscript.Rmd, lines 2364–2376 · score 0.92 · purely linear system, nonlinear temporal processing, separate oscillatory mechanism, sinusoid responds, nonlinear transformation, harmonic distortion
- [13] § Results ↔ manuscript.Rmd, lines 2399–2407 · score 0.81 · weakest suppressive weight, opposite pattern, substantial overlap, lower rows, strongest binocular facilitation, posterior distributions
- [14] § Materials and methods › Apparatus and stimuli ↔ manuscript.Rmd, lines 1091–1126 · score 0.61 · opic irradiances, left eye, right eye, power, spectrum, photoreceptor
- [15] § Materials and methods › Computational model and parameter estimation ↔ manuscript.Rmd, lines 2417–2452 · score 0.56 · stronger binocular facilitation, ocularity invariance, strong suppression, interocular suppression, binocular response, strength
- [16] § Materials and methods › Data analysis ↔ manuscript.Rmd, lines 26–70 · score 0.51 · Mahalanobis distance, analysed, bootstrapping, Fourier, model
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
R Markdown · 2,467 lines · 143 KB · no license · 16 matches
- ---
- title: 'Binocular combination in the autonomic nervous system'
- author: |
- | Federico G. Segala$^1$*, Aurelio Bruno$^{1,2}$, Joel T. Martin$^{1,3}$,
- | Anisa Y. Morsi$^1$, Alex R. Wade$^{1,4}$ & Daniel H. Baker$^{1,4}$
- output:
- bookdown::pdf_document2:
- fig_caption: yes
- toc: no
- keep_tex: true
- html_document: default
- bibliography: references.bib
- link-citations: yes
- linkcolor: black
- csl: plos-one.csl
- header-includes:
- \usepackage{caption}
- \usepackage{setspace}\doublespacing
- \usepackage{lineno} \linenumbers
- \newcommand{\beginsupplement}{\setcounter{table}{0}
- \renewcommand{\thetable}{S\arabic{table}}
- \setcounter{figure}{0}
- \renewcommand{\thefigure}{S\arabic{figure}}}
- ---
- ```{r setup, include=FALSE}
- processdata <- 0 # this flag determines the amount of processing, with 4 levels:
- # 0 - do no processing, generate the pdf using existing versions of all figures
- # 1 - generate figures using the processed group data
- # 2 - average data across participants, do bootstrapping
- # 3 - download all raw data and analyse (requires 56GB of storage)
- domodelling <- 0 # if set to 1, run the Stan models (takes ~24 hours)
- # all files are stored in the project directory /local/ which git is told to ignore
- # create a local directory to store data and outputs
- localdir <- 'local/'
- if (!file.exists(localdir)){dir.create(localdir)}
- # create a local directory to store raw data
- rawdir <- 'local/rawdata/'
- if (!file.exists(rawdir)){dir.create(rawdir)}
- # create a local directory to store processed data
- datadir <- 'local/processeddata/'
- if (!file.exists(datadir)){dir.create(datadir)}
- # create a local directory to store figures
- figdir <- 'Figures/'
- if (!file.exists(figdir)){dir.create(figdir)}
- # cut off for data inclusion (Mahalanobis distance for complex data)
- SDthresh <<- 3
- nbootstraps <<- 10000
- # check which packages are installed, install the missing ones, and activate
- packagelist <- c('knitr','remotes','tictoc','R.matlab','bookdown','grImport','png','tiff','pals','ez','gtools','signal','boot','rstan','bayestestR','rstatix','coda','parallel','utils','osfr','grid','gridExtra','cowplot','ggsignif') # list of CRAN packages
- missingpackages <- packagelist[!packagelist %in% installed.packages()[,1]]
- if (length(missingpackages)>0){install.packages(missingpackages,repos = "http://cran.us.r-project.org")}
- if (!'FourierStats' %in% installed.packages()[,1]){remotes::install_github("bakerdh/FourierStats")}
- packagelist <- c(packagelist,'FourierStats')
- toinstall <- packagelist[which(!packagelist %in% (.packages()))]
- invisible(lapply(toinstall,library,character.only=TRUE))
- devtools::install_github("kupietz/kableExtra")
- library(kableExtra)
- knitr::opts_chunk$set(echo = TRUE)
- # settings for sampler
- numSavedSteps <- 100000 # total number of MCMC steps
- adaptSteps <- 1000 # Number of steps to "tune" the samplers
- burnInSteps <- 2000
- thinSteps <- 10
- nChains <- 16
- ncores <- 8
- options(mc.cores=ncores)
- # helper function to make colours transparent
- addalpha <- function(col, alpha=1){apply(sapply(col, col2rgb)/255, 2, function(x) rgb(x[1], x[2], x[3], alpha=alpha))}
- anovacirc2way <- function(data, groupA=NULL, groupB=NULL, participant=NULL){
- grouplabelsA <- groupA
- grouplabelsB <- groupB
- participantlabels <- participant
- datavals <- data
- grouplabelsA <- as.factor(grouplabelsA)
- factorlistA <- levels(grouplabelsA)
- grouplabelsB <- as.factor(grouplabelsB)
- factorlistB <- levels(grouplabelsB)
- participantlabels <- as.factor(participantlabels)
- participantlist <- levels(participantlabels)
- grandmean <- mean(datavals)
- grandvar <- sum(abs(datavals - grandmean)^2)/(length(datavals)-1)
- SST <- grandvar*(length(datavals) - 1)
- SSW <- 0
- for (n in 1:nlevels(participantlabels)){SSW <- SSW + sum(abs(datavals[which(participantlabels==participantlist[n])] - mean(datavals[which(participantlabels==participantlist[n])]))^2)}
- dfW <- 2*(nlevels(participantlabels)*(nlevels(grouplabelsA)*nlevels(grouplabelsB)-1))
- SSBet <- SST - SSW # between subjects SS - not actually used
- SSM <- 0
- for (a in 1:nlevels(grouplabelsA)){
- for (b in 1:nlevels(grouplabelsB)){
- i <- as.numeric(((grouplabelsA==grouplabelsA[a])))
- j <- as.numeric(((grouplabelsB==grouplabelsB[b])))
- indices <- which((i*j)>0)
- SSM <- SSM + (nlevels(participantlabels)*sum(abs(datavals[indices] - grandmean)^2))
- }
- }
- dfM <- 2*((nlevels(grouplabelsA)*nlevels(grouplabelsB))-1)
- SSR <- 0
- for (a in 1:nlevels(grouplabelsA)){
- for (b in 1:nlevels(grouplabelsB)){
- i <- as.numeric(((grouplabelsA==grouplabelsA[a])))
- j <- as.numeric(((grouplabelsB==grouplabelsB[b])))
- indices <- which((i*j)>0)
- condvar <- sum(abs(datavals[indices] - mean(datavals[indices]))^2)/(length(indices)-1)
- SSR <- SSR + (nlevels(participantlabels)-1)*condvar
- }
- }
- dfR <- 2*(nlevels(participantlabels)-1)*(nlevels(grouplabelsA)*nlevels(grouplabelsB))
- groupmeansA <- NULL
- for (n in 1:nlevels(grouplabelsA)){groupmeansA[n] <- mean(datavals[which(grouplabelsA==factorlistA[n])])}
- SSA <- 0
- for (n in 1:nlevels(grouplabelsA)){SSA <- SSA + length(which(grouplabelsA==factorlistA[n]))*abs(groupmeansA[n]-grandmean)^2}
- dfA <- 2*(nlevels(grouplabelsA)-1)
- groupmeansB <- NULL
- for (n in 1:nlevels(grouplabelsB)){groupmeansB[n] <- mean(datavals[which(grouplabelsB==factorlistB[n])])}
- SSB <- 0
- for (n in 1:nlevels(grouplabelsB)){SSB <- SSB + length(which(grouplabelsB==factorlistB[n]))*abs(groupmeansB[n]-grandmean)^2}
- dfB <- 2*(nlevels(grouplabelsB)-1)
- SSAB <- SSW - (SSA + SSB)
- dfAB <- 2 * (nlevels(grouplabelsA)-1) * (nlevels(grouplabelsB)-1)
- MSA <- SSA/dfA
- MSB <- SSB/dfB
- MSAB <- SSAB/dfAB
- MSR <- SSR/dfR
- FratioA <- MSA/MSR
- pvalA <- pf(FratioA,df1=dfA,df2=dfR,lower.tail=FALSE)
- FratioB <- MSB/MSR
- pvalB <- pf(FratioB,df1=dfB,df2=dfR,lower.tail=FALSE)
- FratioAB <- MSAB/MSR
- pvalAB <- pf(FratioAB,df1=dfAB,df2=dfR,lower.tail=FALSE)
- method <- 'Factorial repeated measures ANOVA^2-circ'
- output <- data.frame(FratioA,pvalA,FratioB,pvalB,FratioAB,pvalAB,dfA,dfB,dfAB,dfR,method)
- return(output)
- }
- cleanup2D <- function(data){
- # helper function to clean up complex data by removing any data points more than
- # SDthresh standard deviations from the mean (in Mahalanobis distance units)
- cartdata <- data.frame(Re(data),Im(data))
- output <- 0
- if (sum(cartdata)!=0){
- D <- sqrt(stats::mahalanobis(cartdata, colMeans(cartdata), cov(cartdata)))
- i <- which(D<SDthresh)
- output <- mean(temp[i])}
- return(output)}
- bootstrap2D <- function(data){
- mbs <- data.frame(meanamp=0,lowerCI=0,upperCI=0)
- cartdata <- data.frame(Re(data),Im(data))
- if (sum(cartdata)!=0){
- D <- sqrt(stats::mahalanobis(cartdata, colMeans(cartdata), cov(cartdata)))
- i <- which(D<SDthresh)
- mbs <- amperrors(data[i],method='boot',quantiles=68,nresamples=nbootstraps)}
- return(mbs)}
- contrastsdB <- 20*log10(c(6,12,24,48,96))
- colpal <- c('#FE5000','#8783CF','#228B22','#808080','#6d008b','#8B8000')
- colpalmod <- c('#808080','#8B8000','#FE5000','#8783CF')
- # these are approximate values for isolumiant red, green, blue and yellow
- stimcolours <- c(rgb(0.02,0.02,0.02),rgb(0.98,0.98,0.98),rgb(0,0.61,0.5),rgb(1,0.39,0.5),rgb(0.35,0.63,0),rgb(0.65,0.37,1),'grey','cornflowerblue')
- if (processdata>2){
- # check if raw data are present locally, and download if not
- nodelist <- c('6u7db','xkgd9','pexyk','k9mdq') # list of OSF nodes containing raw data
- for (expt in 1:4){
- osfproject <- osf_retrieve_node(nodelist[expt])
- osffiles <- osf_ls_files(osfproject,n_max=300)
- for (f in 1:nrow(osffiles)){
- fname <- osffiles$name[f]
- foldername <- strsplit(fname,'.',fixed=TRUE)[[1]][1]
- if (!dir.exists(paste0(rawdir,foldername))){
- osf_download(osffiles[f,],localdir,progress=TRUE)
- dir.create(paste0(rawdir,foldername))
- unzip(paste0(localdir,fname),exdir=paste0(rawdir,foldername))
- file.remove(paste0(localdir,fname))
- }
- }
- }
- }
- if (processdata==2 || domodelling==1){
- # if we're going to need the pre-processed files, extract from zip file
- if (!file.exists(paste0(datadir,'P301_summary.RData'))){
- if (!file.exists(paste0(localdir,'IndividualSummary.zip'))){
- osfnode <- '9ebtd'
- osfproject <- osf_retrieve_node(osfnode)
- osffiles <- osf_ls_files(osfproject,n_max=300)
- fid <- which(osffiles$name=='IndividualSummary.zip')
- osf_download(osffiles[fid,],localdir,progress=TRUE)
- }
- unzip(paste0(localdir,'IndividualSummary.zip'),exdir=datadir)
- }
- }
- if (processdata<2){
- # if we're using the pre-processed data, check it's present and download if not
- exptnames <- c('AveragedataLum.RData','AveragedataLM.RData','AveragedataS.RData','AveragedataMel.RData','included.RData')
- for (f in 1:length(exptnames)){
- if (!file.exists(paste0(datadir,exptnames[f]))){
- if (!exists('osffiles')){
- osfnode <- '9ebtd'
- osfproject <- osf_retrieve_node(osfnode)
- osffiles <- osf_ls_files(osfproject,n_max=300)
- }
- fid <- which(osffiles$name==exptnames[f])
- osf_download(osffiles[fid,],datadir,progress=TRUE)
- }}}
- if (domodelling==0){
- # if we're not doing the modelling, check if the model outputs are present and download if not
- exptnames <- c('modeloutputLum.RData','modeloutputLM.RData','modeloutputS.RData','modeloutputMel.RData')
- for (f in 1:length(exptnames)){
- if (!file.exists(paste0(datadir,exptnames[f]))){
- if (!exists('osffiles')){
- osfnode <- '9ebtd'
- osfproject <- osf_retrieve_node(osfnode)
- osffiles <- osf_ls_files(osfproject,n_max=300)
- }
- fid <- which(osffiles$name==exptnames[f])
- osf_download(osffiles[fid,],datadir,progress=TRUE)
- }
- }
- }
- ```
- $^1$Department of Psychology, University of York, Heslington, York, YO10 5DD, United Kingdom.
- $^2$School of Psychology and Vision Sciences, University of Leicester, Leicester, LE1 7RH, United Kingdom.
- $^3$School of Philosophy, Psychology and Language Sciences, University of Edinburgh, Edinburgh, EH8 9AD, United Kingdom.
- $^4$York Biomedical Research Institute, University of York, Heslington, York, YO10 5NG, United Kingdom.
- *Corresponding author
- E-mail:[[email hidden]]([email hidden]) (FGS)
- \pagebreak
- ```{r individualanalyses, include=FALSE, results='hide'}
- # analyse individual participant data for all conditions
- if (processdata > 2){
- allsubjects <- dir(rawdir)
- for (p in 1:length(allsubjects)){
- participant <- allsubjects[p]
- pupiltargets <- array(0,dim=c(3,2,60))
- pupilmasks <- pupiltargets
- pupiltargets2 <- pupiltargets
- pupilmasks2 <- pupiltargets
- pupilwaveforms <- array(0,dim=c(3,2,60,120*14))
- pupilspectra <- array(0,dim=c(3,2,60,300))
- timeseq <- seq(1/120,10,length.out=120*10)
- timeseq2 <- seq(1/120,14,length.out=120*14)
- targetindex <- (0.5*10)+1
- maskindex <- (0.4*10)+1
- targetindex2 <- (0.5*2*10)+1
- maskindex2 <- (2*0.4*10)+1
- repcounterP <- (1:60)*0
- annfiles <- dir(paste0(rawdir,participant),pattern='*annotations.csv',full.names=TRUE)
- pupilfiles <- dir(paste0(rawdir,participant),pattern='*pupil_positions.csv',full.names=TRUE)
- for (block in 1:length(annfiles)){
- adata <- read.csv(annfiles[block])
- trialtimes <- NULL
- trialtypes <- NULL
- for (t in 1:(nrow(adata)/2)){
- trialtimes[t] <- mean(adata$timestamp[(t*2 - 1):(t*2)])
- trialtypes[t] <- adata$condition_code[t*2]
- }
- trialtypes[which(trialtypes>15)] <- trialtypes[which(trialtypes>15)] + 5
- inclist <- NULL
- for (t in 11:15){
- i <- which(trialtypes==t)
- inclist[t-10] <- i[2]
- }
- inclist <- inclist[which(!is.na(inclist))]
- trialtypes[inclist] <- trialtypes[inclist] + 5
- pdata <- read.csv(pupilfiles[block])
- pdata2 <- pdata[,c(1,3,4,14)]
- pdata2 <- pdata2[which(pdata2[,3]>0.2),]
- for (trialno in 1:length(trialtypes)){
- repcounterP[trialtypes[trialno]] <- repcounterP[trialtypes[trialno]] + 1
- for (eye in 1:2){
- eyedata <- pdata2[which(pdata2[,2]==(2-eye)),]
- a <- which(eyedata[,1]>trialtimes[trialno]+2)
- b <- which(eyedata[,1]<(trialtimes[trialno]+12))
- i <- intersect(a,b)
- trial <- eyedata[i,]
- trial <- trial[which(!is.na(trial[,4])),]
- trial[,1] <- trial[,1] - trial[1,1]
- if (nrow(trial)>3){
- resampled <- interp1(trial[,1],trial[,4],timeseq,method='linear',extrap=TRUE)
- fspec <- (fft(resampled)/length(resampled))
- pupiltargets[repcounterP[trialtypes[trialno]],eye,trialtypes[trialno]] <- fspec[targetindex]
- pupilmasks[repcounterP[trialtypes[trialno]],eye,trialtypes[trialno]] <- fspec[maskindex]
- pupiltargets2[repcounterP[trialtypes[trialno]],eye,trialtypes[trialno]] <- fspec[targetindex2]
- pupilmasks2[repcounterP[trialtypes[trialno]],eye,trialtypes[trialno]] <- fspec[maskindex2]
- pupilspectra[repcounterP[trialtypes[trialno]],eye,trialtypes[trialno],1:300] <- fspec[1:300]
- }
- a <- which(eyedata[,1]>(trialtimes[trialno]-1))
- b <- which(eyedata[,1]<(trialtimes[trialno]+13))
- i <- intersect(a,b)
- trial <- eyedata[i,]
- trial[,1] <- trial[,1] - trial[1,1]
- if (nrow(trial)>3){
- trial <- trial[which(!is.na(trial[,4])),]
- resampled <- interp1(trial[,1],trial[,4],timeseq2,method='linear',extrap=TRUE)
- pupilwaveforms[repcounterP[trialtypes[trialno]],eye,trialtypes[trialno],] <- resampled - mean(resampled[1:120])
- }
- }
- }
- }
- cleanmeansP <- matrix(0,nrow=6,ncol=5)
- cleanmasksP <- matrix(0,nrow=6,ncol=5)
- cleanmeansP2 <- matrix(0,nrow=6,ncol=5)
- cleanmasksP2 <- matrix(0,nrow=6,ncol=5)
- for (cond in 1:6){
- startindex <- (10*(cond-1))
- for (level in 1:5){
- temp <- c(pupiltargets[,,startindex+level],pupiltargets[,,startindex+level+5])
- cartdata <- data.frame(Re(temp),Im(temp))
- if (sum(cartdata)!=0){
- D <- sqrt(stats::mahalanobis(cartdata, colMeans(cartdata), cov(cartdata)))
- i <- which(D<SDthresh)
- cleanmeansP[cond,level] <- mean(temp[i])}
- temp <- c(pupiltargets2[,,startindex+level],pupiltargets2[,,startindex+level+5])
- cartdata <- data.frame(Re(temp),Im(temp))
- if (sum(cartdata)!=0){
- D <- sqrt(stats::mahalanobis(cartdata, colMeans(cartdata), cov(cartdata)))
- i <- which(D<SDthresh)
- cleanmeansP2[cond,level] <- mean(temp[i])}
- temp <- c(pupilmasks[,,startindex+level],pupilmasks[,,startindex+level+5])
- cartdata <- data.frame(Re(temp),Im(temp))
- if (sum(cartdata)!=0){
- D <- sqrt(stats::mahalanobis(cartdata, colMeans(cartdata), cov(cartdata)))
- i <- which(D<SDthresh)
- cleanmasksP[cond,level] <- mean(temp[i])}
- temp <- c(pupilmasks2[,,startindex+level],pupilmasks2[,,startindex+level+5])
- cartdata <- data.frame(Re(temp),Im(temp))
- if (sum(cartdata)!=0){
- D <- sqrt(stats::mahalanobis(cartdata, colMeans(cartdata), cov(cartdata)))
- i <- which(D<SDthresh)
- cleanmasksP2[cond,level] <- mean(temp[i])}
- }
- }
- meanwavesP <- apply(pupilwaveforms[,,c(15,20),],4,mean,na.rm=TRUE)
- meanspectraP <- apply(pupilspectra[,,c(15,20),],4,mean,na.rm=TRUE)
- save(file=paste(datadir,participant,'_summary.RData',sep=''),list=c('cleanmeansP','cleanmasksP','cleanmeansP2','cleanmasksP2','meanspectraP','meanwavesP'))
- }
- }
- ```
- ```{r groupanalyses, include=FALSE, results='hide'}
- # files from P301-P324 are for S-cone stimulation
- # files from P501-P524 are for melanopsin stimulation
- # files from P601-P624 are for luminance stimulation
- # files from P701-P724 are for (L-M) cone stimulation
- # load in individual participant data and average, save group data
- if (processdata > 1){
- prefixlist <- c('P6','P7','P3','P5')
- exptnames <- c('AveragedataLum.RData','AveragedataLM.RData','AveragedataS.RData','AveragedataMel.RData')
- includedvalues <- array(0,dim=c(4,24,6,5))
- includedvalues2 <- array(0,dim=c(4,24,6,5))
- includedmasks <- array(0,dim=c(4,24,6,5))
- includedmasks2 <- array(0,dim=c(4,24,6,5))
- for (expt in 1:4){
- d <- dir(datadir,pattern=prefixlist[expt], full.names = TRUE)
- tempmasksP <- array(0,dim=c(length(d),6,5))
- tempmeansP <- tempmasksP
- tempmeansP2 <- tempmasksP
- tempmasksP2 <- tempmasksP
- tempspectraP <- array(0,dim=c(length(d),300))
- tempwavesP <- array(0,dim=c(length(d),1680))
- for (s in 1:length(d)){
- load(d[s])
- tempmasksP[s,,] <- cleanmasksP
- tempmeansP[s,,] <- cleanmeansP
- tempmasksP2[s,,] <- cleanmasksP2
- tempmeansP2[s,,] <- cleanmeansP2
- tempspectraP[s,] <- meanspectraP
- tempwavesP[s,] <- meanwavesP
- }
- # tempspectraP <- tempspectraP[-c(2,4,13,20,26),]
- cleanmeansP <- matrix(0,nrow=6,ncol=5)
- cleanmeansPCI <- array(0,c(2,6,5))
- cleanmasksP <- matrix(0,nrow=6,ncol=5)
- cleanmasksPCI <- array(0,c(2,6,5))
- cleanmeansP2 <- matrix(0,nrow=6,ncol=5)
- cleanmeansP2CI <- array(0,c(2,6,5))
- cleanmasksP2 <- matrix(0,nrow=6,ncol=5)
- cleanmasksP2CI <- array(0,c(2,6,5))
- for (cond in 1:6){
- for (level in 1:5){
- temp <- tempmeansP[,cond,level]
- cartdata <- data.frame(Re(temp),Im(temp))
- if (sum(cartdata)!=0){
- D <- sqrt(stats::mahalanobis(cartdata, colMeans(cartdata), cov(cartdata)))
- i <- which(D<SDthresh)
- includedvalues[expt,i,cond,level] <- 1
- mbs <- amperrors(temp[i],method='boot',quantiles=68,nresamples=nbootstraps)
- cleanmeansP[cond,level] <- mean(temp[i])
- cleanmeansPCI[1,cond,level] <- mbs$lowerCI
- cleanmeansPCI[2,cond,level] <- mbs$upperCI
- }
- temp <- tempmasksP[,cond,level]
- cartdata <- data.frame(Re(temp),Im(temp))
- if (sum(cartdata)!=0){
- D <- sqrt(stats::mahalanobis(cartdata, colMeans(cartdata), cov(cartdata)))
- i <- which(D<SDthresh)
- includedmasks[expt,i,cond,level] <- 1
- mbs <- amperrors(temp[i],method='boot',quantiles=68,nresamples=nbootstraps)
- cleanmasksP[cond,level] <- mean(temp[i])
- cleanmasksPCI[1,cond,level] <- mbs$lowerCI
- cleanmasksPCI[2,cond,level] <- mbs$upperCI
- }
- temp <- tempmeansP2[,cond,level]
- cartdata <- data.frame(Re(temp),Im(temp))
- if (sum(cartdata)!=0){
- D <- sqrt(stats::mahalanobis(cartdata, colMeans(cartdata), cov(cartdata)))
- i <- which(D<SDthresh)
- includedvalues2[expt,i,cond,level] <- 1
- mbs <- amperrors(temp[i],method='boot',quantiles=68,nresamples=nbootstraps)
- cleanmeansP2[cond,level] <- mean(temp[i])
- cleanmeansP2CI[1,cond,level] <- mbs$lowerCI
- cleanmeansP2CI[2,cond,level] <- mbs$upperCI
- }
- temp <- tempmasksP2[,cond,level]
- cartdata <- data.frame(Re(temp),Im(temp))
- if (sum(cartdata)!=0){
- D <- sqrt(stats::mahalanobis(cartdata, colMeans(cartdata), cov(cartdata)))
- i <- which(D<SDthresh)
- includedmasks2[expt,i,cond,level] <- 1
- mbs <- amperrors(temp[i],method='boot',quantiles=68,nresamples=nbootstraps)
- cleanmasksP2[cond,level] <- mean(temp[i])
- cleanmasksP2CI[1,cond,level] <- mbs$lowerCI
- cleanmasksP2CI[2,cond,level] <- mbs$upperCI
- }
- }
- }
- meanspectraP <- (1:300)*0
- meanspectraPCI <- array(0,c(2,300))
- for (f in 2:300){
- temp <- tempspectraP[,f]
- cartdata <- data.frame(Re(temp),Im(temp))
- if (sum(cartdata)!=0){
- D <- sqrt(stats::mahalanobis(cartdata, colMeans(cartdata), cov(cartdata)))
- i <- which(D<SDthresh)
- mbs <- amperrors(temp[i],method='boot',quantiles=68,nresamples=nbootstraps)
- meanspectraP[f] <- mbs$meanamp
- meanspectraPCI[1,f] <- mbs$lowerCI
- meanspectraPCI[2,f] <- mbs$upperCI
- }
- }
- meanwavesP <- NULL
- meanwavesPCI <- array(0,dim=c(2,dim(tempwavesP)[2]))
- nsubjs <- dim(tempwavesP)[1]
- for (t in 1:dim(tempwavesP)[2]){
- temp <- tempwavesP[,t]
- i <- which(abs(temp-mean(temp))<(SDthresh*sd(temp)))
- meanwavesP[t] <- mean(temp[i])
- bspop <- NULL
- for (s in 1:nbootstraps){bspop[s] <- mean(sample(temp[i],length(i),replace=TRUE))}
- meanwavesPCI[,t] <- quantile(bspop,c(0.16,0.84),na.rm=TRUE)
- }
- save(file=paste0(datadir,exptnames[expt]),list=c('meanwavesP','meanwavesPCI','cleanmeansP','cleanmeansPCI','cleanmasksP','cleanmasksPCI','cleanmeansP2','cleanmeansP2CI','cleanmasksP2','cleanmasksP2CI','meanspectraP','meanspectraPCI','tempmeansP','tempmeansP2','tempmasksP','tempmasksP2'))
- }
- save(file=paste0(datadir,'included.RData'),list=c('includedvalues','includedvalues2','includedmasks','includedmasks2'))
- }
- ```
- ```{r makestanmodels, include=FALSE, results='hide'}
- # create text objects for Stan models
- if (domodelling > 0){
- CRFmodelstring = "
- data {
- int<lower=1> Nsubj ;
- int<lower=1> Ntotal ;
- real y[Ntotal] ;
- real x[Ntotal] ;
- int<lower=1> c[Ntotal] ;
- int<lower=1> s[Ntotal] ;
- }
- parameters {
- real<lower=1> Z[Nsubj] ;
- real<lower=0> Rmax[Nsubj] ;
- real<lower=0> wt[Nsubj] ;
- real<lower=0> k[Nsubj] ;
- real<lower=0> p[Nsubj] ;
- real<lower=0> q[Nsubj] ;
- real<lower=0> sigma ;
- real<lower=1> Zmu ;
- real<lower=0> Rmaxmu ;
- real<lower=0> wtmu ;
- real<lower=0> kmu ;
- real<lower=0> pmu ;
- real<lower=0> qmu ;
- real<lower=0> Zsigma ;
- real<lower=0> Rmaxsigma ;
- real<lower=0> wtsigma ;
- real<lower=0> ksigma ;
- real<lower=0> psigma ;
- real<lower=0> qsigma ;
- real<lower=0> nu ;
- }
- model {
- Zmu ~ normal( 40 , 20 ) ;
- Rmaxmu ~ normal( 0.5 , 0.3 ) ;
- wtmu ~ normal( 1 , 0.5 ) ;
- kmu ~ normal( 0.002 , 0.001 ) ;
- pmu ~ normal( 0.4 , 0.2 ) ;
- qmu ~ normal( 2 , 0.5 ) ;
- sigma ~ uniform( 1.0E-3 , 1.0E+3 ) ;
- Zsigma ~ uniform( 1.0E-3 , 1.0E+3 ) ;
- Rmaxsigma ~ uniform( 1.0E-3 , 1.0E+3 ) ;
- wtsigma ~ uniform( 1.0E-3 , 1.0E+3 ) ;
- ksigma ~ uniform( 1.0E-3 , 1.0E+3 ) ;
- psigma ~ uniform( 1.0E-3 , 1.0E+3 ) ;
- qsigma ~ uniform( 1.0E-3 , 1.0E+3 ) ;
- nu ~ exponential(1/30.0) ;
- Z ~ normal( Zmu , Zsigma ) ; // vectorized
- Rmax ~ normal( Rmaxmu , Rmaxsigma ) ; // vectorized
- wt ~ normal( wtmu , wtsigma ) ; // vectorized
- k ~ normal( kmu , ksigma ) ; // vectorized
- p ~ normal( pmu, psigma ) ;
- q ~ normal( qmu, qsigma ) ;
- for ( i in 1:Ntotal ) {
- if (c[i]==1) // monocular condition F1
- y[i] ~ student_t(
- nu ,
- k[s[i]] + (Rmax[s[i]]*(pow(x[i],p[s[i]]+q[s[i]]))/(Z[s[i]] + (pow(x[i],q[s[i]])))) ,
- sigma ) ;
- else if (c[i]==2) // binocular condition F1
- y[i] ~ student_t(
- nu ,
- k[s[i]] + (Rmax[s[i]]*((pow(x[i],p[s[i]]+q[s[i]]))/(Z[s[i]] + (pow(x[i],q[s[i]])) + wt[s[i]]*(pow(x[i],q[s[i]]))) + (pow(x[i],p[s[i]]+q[s[i]]))/(Z[s[i]] + (pow(x[i],q[s[i]])) + wt[s[i]]*(pow(x[i],q[s[i]]))))),
- sigma ) ;
- else if (c[i]==3) // dichoptic condition F1
- y[i] ~ student_t(
- nu ,
- k[s[i]] + (Rmax[s[i]]*((pow(x[i],p[s[i]]+q[s[i]]))/(Z[s[i]] + (pow(x[i],q[s[i]])) + wt[s[i]]*(pow(48,q[s[i]]))) + (pow(48,p[s[i]]+q[s[i]]))/(Z[s[i]] + (pow(48,q[s[i]])) + wt[s[i]]*(pow(x[i],q[s[i]]))))),
- sigma ) ;
- else if (c[i]==4) // binocular cross condition F1
- y[i] ~ student_t(
- nu ,
- k[s[i]] + (Rmax[s[i]]*(pow(x[i],p[s[i]]+q[s[i]]))/(Z[s[i]] + (pow(x[i],q[s[i]])) + wt[s[i]]*(pow(x[i],q[s[i]])))) ,
- sigma ) ;
- else if (c[i]==5) // dichoptic cross condition F1
- y[i] ~ student_t(
- nu ,
- k[s[i]] + (Rmax[s[i]]*(pow(x[i],p[s[i]]+q[s[i]]))/(Z[s[i]] + (pow(x[i],q[s[i]])) + wt[s[i]]*(pow(48,q[s[i]])))) ,
- sigma ) ;
- else if (c[i]==6) // monocular condition F2
- y[i] ~ student_t(
- nu ,
- k[s[i]] + (Rmax[s[i]]*(pow(x[i],p[s[i]]+q[s[i]]))/(Z[s[i]] + (pow(x[i],q[s[i]])))) ,
- sigma ) ;
- else if (c[i]==7) // binocular cross condition F2
- y[i] ~ student_t(
- nu ,
- k[s[i]] + (Rmax[s[i]]*(pow(x[i],p[s[i]]+q[s[i]]))/(Z[s[i]] + (pow(x[i],q[s[i]])) + wt[s[i]]*(pow(x[i],q[s[i]])))) ,
- sigma ) ;
- else if (c[i]==8) // dichoptic cross condition F2
- y[i] ~ student_t(
- nu ,
- k[s[i]] + (Rmax[s[i]]*(pow(48,p[s[i]]+q[s[i]]))/(Z[s[i]] + (pow(48,q[s[i]])) + wt[s[i]]*(pow(x[i],q[s[i]])))) ,
- sigma ) ;
- }
- }
- " # close quote for CRFmodelstring
- # Translate Stan models to C++ and compile to DSO:
- CRFmodel <- stan_model(model_code=CRFmodelstring)
- }
- ```
- ```{r runstanmodels, include=FALSE, results='hide'}
- # do computational modelling for all experiments
- # files from P301-P324 are for S-cone stimulation
- # files from P501-P524 are for melanopsin stimulation
- # files from P601-P624 are for luminance stimulation
- # files from P701-P724 are for (L-M) cone stimulation
- if (domodelling > 0){
- prefixlist <- c('P6','P7','P3','P5')
- exptnames <- c('modeloutputLum.RData','modeloutputLM.RData','modeloutputS.RData','modeloutputMel.RData')
- load(paste0(datadir,'included.RData'))
- for (expt in 1:length(prefixlist)){
- d <- dir(datadir,pattern=prefixlist[expt])
- allmasksP <- array(0,dim=c(length(d),6,5))
- allmeansP <- allmasksP
- allmasksP2 <- array(0,dim=c(length(d),6,5))
- allmeansP2 <- allmasksP
- for (s in 1:length(d)){
- load(paste0(datadir,d[s]))
- allmasksP[s,,] <- cleanmasksP
- allmeansP[s,,] <- cleanmeansP
- allmasksP2[s,,] <- cleanmasksP2
- allmeansP2[s,,] <- cleanmeansP2
- }
- # 1st harmonic F1
- condlist <- c(1,2,3,5,6)
- contlist <- c(6,12,24,48,96)
- y <- NULL
- x <- NULL
- c <- NULL
- s <- NULL
- i <- 0
- for (subj in 1:dim(allmeansP)[1]){
- for (cond in 1:length(condlist)){
- for (cont in 1:length(contlist)){
- dp <- abs(allmeansP[subj,condlist[cond],cont])
- if (includedvalues[expt,subj,condlist[cond],cont]==1){
- if (is.na(dp)==0){
- if (is.infinite(dp)==0){
- i <- i + 1
- x[i] <- contlist[cont]
- y[i] <- dp
- c[i] <- cond
- s[i] <- subj
- }
- }
- }
- }
- }
- }
- # 1st harmonic F2
- condlist <- c(4,5,6)
- for (subj in 1:dim(allmasksP)[1]){
- for (cond in 1:length(condlist)){
- for (cont in 1:length(contlist)){
- dp <- abs(allmasksP[subj,condlist[cond],cont])
- if (includedmasks[expt,subj,condlist[cond],cont]==1){
- if (is.na(dp)==0){
- if (is.infinite(dp)==0){
- i <- i + 1
- x[i] <- contlist[cont]
- y[i] <- dp
- c[i] <- cond+5
- s[i] <- subj
- }
- }
- }
- }
- }
- }
- pupildatalist = list(x = x, y = y, s = s, c = c, Nsubj = max(s), Ntotal = length(y))
- # Get MC sample of posterior:
- pupilsamples <- sampling(object=CRFmodel,
- data = pupildatalist,
- chains = nChains,
- iter = (ceiling(numSavedSteps/nChains)*thinSteps+burnInSteps),
- warmup = burnInSteps,
- thin = thinSteps,
- cores = getOption("mc.cores", 1L))
- # 2nd harmonic
- condlist <- c(1,2,3,5,6)
- contlist <- c(6,12,24,48,96)
- y <- NULL
- x <- NULL
- c <- NULL
- s <- NULL
- i <- 0
- for (subj in 1:dim(allmeansP2)[1]){
- for (cond in 1:length(condlist)){
- for (cont in 1:length(contlist)){
- dp <- abs(allmeansP2[subj,condlist[cond],cont])
- if (includedvalues2[expt,subj,condlist[cond],cont]==1){
- if (is.na(dp)==0){
- if (is.infinite(dp)==0){
- i <- i + 1
- x[i] <- contlist[cont]
- y[i] <- dp
- c[i] <- cond
- s[i] <- subj
- }
- }
- }
- }
- }
- }
- # 2nd harmonic F2
- condlist <- c(4,5,6)
- for (subj in 1:dim(allmasksP)[1]){
- for (cond in 1:length(condlist)){
- for (cont in 1:length(contlist)){
- dp <- abs(allmasksP2[subj,condlist[cond],cont])
- if (includedmasks2[expt,subj,condlist[cond],cont]==1){
- if (is.na(dp)==0){
- if (is.infinite(dp)==0){
- i <- i + 1
- x[i] <- contlist[cont]
- y[i] <- dp
- c[i] <- cond+5
- s[i] <- subj
- }
- }
- }
- }
- }
- }
- pupildatalist = list(x = x, y = y, s = s, c = c, Nsubj = max(s), Ntotal = length(y))
- # Get MC sample of posterior:
- pupilsamples2 <- sampling(object=CRFmodel,
- data = pupildatalist,
- chains = nChains,
- iter = (ceiling(numSavedSteps/nChains)*thinSteps+burnInSteps),
- warmup = burnInSteps,
- thin = thinSteps,
- cores = getOption("mc.cores", 1L))
- save(file=paste0(datadir,exptnames[expt]),list=c('pupilsamples','pupilsamples2'))
- }
- }
- ```
- ```{r getmodelpredictions, include=FALSE, results='hide'}
- # get predictions of the model based on earlier fits
- load(paste0(datadir,'modeloutputLum.RData'))
- draws <- extract(pupilsamples)
- pupilweightpopLum <- draws$wtmu
- pupilZpopLum <- draws$Zmu
- pupilKpopLum <- draws$kmu
- pupilPpopLum <- draws$pmu
- pupilQpopLum <- draws$qmu
- pupilRpopLum <- draws$Rmaxmu
- pupilWLum <- 10^as.numeric(map_estimate(log10(pupilweightpopLum)))
- pupilZLum <- 10^as.numeric(map_estimate(log10(pupilZpopLum)))
- pupilKLum <- 10^as.numeric(map_estimate(log10(pupilKpopLum)))/sqrt(24)
- pupilPLum <- 10^as.numeric(map_estimate(log10(pupilPpopLum+pupilQpopLum)))
- pupilQLum <- 10^as.numeric(map_estimate(log10(pupilQpopLum)))
- pupilRmaxLum <- 10^as.numeric(map_estimate(log10(pupilRpopLum)))
- load(paste0(datadir,'modeloutputMel.RData'))
- draws <- extract(pupilsamples)
- pupilweightpopMel <- draws$wtmu
- pupilZpopMel <- draws$Zmu
- pupilKpopMel <- draws$kmu
- pupilPpopMel <- draws$pmu
- pupilQpopMel <- draws$qmu
- pupilRpopMel <- draws$Rmaxmu
- pupilWMel <- 10^as.numeric(map_estimate(log10(pupilweightpopMel)))
- pupilZMel <- 10^as.numeric(map_estimate(log10(pupilZpopMel)))
- pupilKMel <- 10^as.numeric(map_estimate(log10(pupilKpopMel)))/sqrt(24)
- pupilPMel <- 10^as.numeric(map_estimate(log10(pupilPpopMel+pupilQpopMel)))
- pupilQMel <- 10^as.numeric(map_estimate(log10(pupilQpopMel)))
- pupilRmaxMel <- 10^as.numeric(map_estimate(log10(pupilRpopMel)))
- load(paste0(datadir,'modeloutputLM.RData'))
- draws <- extract(pupilsamples)
- pupilweightpopLM <- draws$wtmu
- pupilZpopLM <- draws$Zmu
- pupilKpopLM <- draws$kmu
- pupilPpopLM <- draws$pmu
- pupilQpopLM <- draws$qmu
- pupilRpopLM <- draws$Rmaxmu
- pupilWLM <- 10^as.numeric(map_estimate(log10(pupilweightpopLM)))
- pupilZLM <- 10^as.numeric(map_estimate(log10(pupilZpopLM)))
- pupilKLM <- 10^as.numeric(map_estimate(log10(pupilKpopLM)))/sqrt(24)
- pupilPLM <- 10^as.numeric(map_estimate(log10(pupilPpopLM+pupilQpopLM)))
- pupilQLM <- 10^as.numeric(map_estimate(log10(pupilQpopLM)))
- pupilRmaxLM <- 10^as.numeric(map_estimate(log10(pupilRpopLM)))
- load(paste0(datadir,'modeloutputS.RData'))
- draws <- extract(pupilsamples)
- pupilweightpopS <- draws$wtmu
- pupilZpopS <- draws$Zmu
- pupilKpopS <- draws$kmu
- pupilPpopS <- draws$pmu
- pupilQpopS <- draws$qmu
- pupilRpopS <- draws$Rmaxmu
- pupilWS <- 10^as.numeric(map_estimate(log10(pupilweightpopS)))
- pupilZS <- 10^as.numeric(map_estimate(log10(pupilZpopS)))
- pupilKS <- 10^as.numeric(map_estimate(log10(pupilKpopS)))/sqrt(24)
- pupilPS <- 10^as.numeric(map_estimate(log10(pupilPpopS+pupilQpopS)))
- pupilQS <- 10^as.numeric(map_estimate(log10(pupilQpopS)))
- pupilRmaxS <- 10^as.numeric(map_estimate(log10(pupilRpopS)))
- load(paste0(datadir,'modeloutputLum.RData'))
- draws <- extract(pupilsamples2)
- pupilweightpopLum2 <- draws$wtmu
- pupilZpopLum2 <- draws$Zmu
- pupilKpopLum2 <- draws$kmu
- pupilPpopLum2 <- draws$pmu
- pupilQpopLum2 <- draws$qmu
- pupilRpopLum2 <- draws$Rmaxmu
- pupilWLum2 <- 10^as.numeric(map_estimate(log10(pupilweightpopLum2)))
- pupilZLum2 <- 10^as.numeric(map_estimate(log10(pupilZpopLum2)))
- pupilKLum2 <- 10^as.numeric(map_estimate(log10(pupilKpopLum2)))/sqrt(24)
- pupilPLum2 <- 10^as.numeric(map_estimate(log10(pupilPpopLum2+pupilQpopLum2)))
- pupilQLum2 <- 10^as.numeric(map_estimate(log10(pupilQpopLum2)))
- pupilRmaxLum2 <- 10^as.numeric(map_estimate(log10(pupilRpopLum2)))
- load(paste0(datadir,'modeloutputMel.RData'))
- draws <- extract(pupilsamples2)
- pupilweightpopMel2 <- draws$wtmu
- pupilZpopMel2 <- draws$Zmu
- pupilKpopMel2 <- draws$kmu
- pupilPpopMel2 <- draws$pmu
- pupilQpopMel2 <- draws$qmu
- pupilRpopMel2 <- draws$Rmaxmu
- pupilWMel2 <- 10^as.numeric(map_estimate(log10(pupilweightpopMel2)))
- pupilZMel2 <- 10^as.numeric(map_estimate(log10(pupilZpopMel2)))
- pupilKMel2 <- 10^as.numeric(map_estimate(log10(pupilKpopMel2)))/sqrt(24)
- pupilPMel2 <- 10^as.numeric(map_estimate(log10(pupilPpopMel2+pupilQpopMel2)))
- pupilQMel2 <- 10^as.numeric(map_estimate(log10(pupilQpopMel2)))
- pupilRmaxMel2 <- 10^as.numeric(map_estimate(log10(pupilRpopMel2)))
- load(paste0(datadir,'modeloutputLM.RData'))
- draws <- extract(pupilsamples2)
- pupilweightpopLM2 <- draws$wtmu
- pupilZpopLM2 <- draws$Zmu
- pupilKpopLM2 <- draws$kmu
- pupilPpopLM2 <- draws$pmu
- pupilQpopLM2 <- draws$qmu
- pupilRpopLM2 <- draws$Rmaxmu
- pupilWLM2 <- 10^as.numeric(map_estimate(log10(pupilweightpopLM2)))
- pupilZLM2 <- 10^as.numeric(map_estimate(log10(pupilZpopLM2)))
- pupilKLM2 <- 10^as.numeric(map_estimate(log10(pupilKpopLM2)))/sqrt(24)
- pupilPLM2 <- 10^as.numeric(map_estimate(log10(pupilPpopLM2+pupilQpopLM2)))
- pupilQLM2 <- 10^as.numeric(map_estimate(log10(pupilQpopLM2)))
- pupilRmaxLM2 <- 10^as.numeric(map_estimate(log10(pupilRpopLM2)))
- load(paste0(datadir,'modeloutputS.RData'))
- draws <- extract(pupilsamples2)
- pupilweightpopS2 <- draws$wtmu
- pupilZpopS2 <- draws$Zmu
- pupilKpopS2 <- draws$kmu
- pupilPpopS2 <- draws$pmu
- pupilQpopS2 <- draws$qmu
- pupilRpopS2 <- draws$Rmaxmu
- pupilWS2 <- 10^as.numeric(map_estimate(log10(pupilweightpopS2)))
- pupilZS2 <- 10^as.numeric(map_estimate(log10(pupilZpopS2)))
- pupilKS2 <- 10^as.numeric(map_estimate(log10(pupilKpopS2)))/sqrt(24)
- pupilPS2 <- 10^as.numeric(map_estimate(log10(pupilPpopS2+pupilQpopS2)))
- pupilQS2 <- 10^as.numeric(map_estimate(log10(pupilQpopS2)))
- pupilRmaxS2 <- 10^as.numeric(map_estimate(log10(pupilRpopS2)))
- getmodresps <- function(params){
- contrastsfinedB <- 2:40
- contrastsfine <- 10^(contrastsfinedB/20)
- modresps <- matrix(0,nrow=5,ncol=length(contrastsfine))
- modresps[1,] <- params[1]*(contrastsfine^params[2])/(params[3] + contrastsfine^params[6]) + params[5]
- binresp <- 2*((contrastsfine^params[2])/(params[3] + contrastsfine^params[6] + params[4]*contrastsfine^params[6]))
- modresps[2,] <- params[1]*binresp + params[5]
- dichresp <- ((contrastsfine^params[2])/(params[3] + contrastsfine^params[6] + params[4]*48^params[6])) + ((48^params[2])/(params[3] + 48^params[6] + params[4]*contrastsfine^params[6]))
- modresps[3,] <- params[1]*dichresp + params[5]
- dichresp2 <- ((contrastsfine^params[2])/(params[3] + contrastsfine^params[6] + params[4]*48^params[6]))
- modresps[4,] <- params[1]*dichresp2 + params[5]
- dichresp3 <- ((48^params[2])/(params[3] + 48^params[6] + params[4]*contrastsfine^params[6]))
- modresps[5,] <- params[1]*dichresp3 + params[5]
- return(modresps)}
- params <- c(pupilRmaxLum,pupilPLum,pupilZLum,pupilWLum,pupilKLum,pupilQLum)
- modrespsLum <- getmodresps(params)
- params <- c(pupilRmaxMel,pupilPMel,pupilZMel,pupilWMel,pupilKMel,pupilQMel)
- modrespsMel <- getmodresps(params)
- params <- c(pupilRmaxLM,pupilPLM,pupilZLM,pupilWLM,pupilKLM,pupilQLM)
- modrespsLM <- getmodresps(params)
- params <- c(pupilRmaxS,pupilPS,pupilZS,pupilWS,pupilKS,pupilQS)
- modrespsS <- getmodresps(params)
- params <- c(pupilRmaxLum2,pupilPLum2,pupilZLum2,pupilWLum2,pupilKLum2,pupilQLum2)
- modrespsLum2 <- getmodresps(params)
- params <- c(pupilRmaxMel2,pupilPMel2,pupilZMel2,pupilWMel2,pupilKMel2,pupilQMel2)
- modrespsMel2 <- getmodresps(params)
- params <- c(pupilRmaxLM2,pupilPLM2,pupilZLM2,pupilWLM2,pupilKLM2,pupilQLM2)
- modrespsLM2 <- getmodresps(params)
- params <- c(pupilRmaxS2,pupilPS2,pupilZS2,pupilWS2,pupilKS2,pupilQS2)
- modrespsS2 <- getmodresps(params)
- ```
- ```{r makemethodsfig, include=FALSE, results='hide'}
- # create plots of spectra, cone absorption, waveforms and Fourier spectra
- if (processdata > 0){
- plotcontrasts <- 1 # either plot photoreceptor contrasts (1) or absolute irradiance change (0)
- times <- seq((1/120)-1,13,1/120)
- frequencies <- (0:299)/10
- sinewave <- sin(0.5*times * 2*pi)
- sinewave[1:120] <- 0
- sinewave[1560:1680] <- 0
- coneab <- read.csv('resources/coneabsorptions.csv',header=FALSE)
- spectra <- read.csv('resources/spectra.csv',header=FALSE)
- # calculate delta photoreceptor contrast
- pcontrast <- coneab
- for (n in 4:11){pcontrast[,n] <- 100*coneab[,n]/coneab[,2]}
- pdf(paste0(figdir,"methodsfigrods.pdf"), bg="transparent", height = 13, width = 12)
- layout(matrix(1:20, 5, 4, byrow = TRUE))
- par(mar=c(1.5,1,0.1,0.1))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=c(0,1), ylim=c(0,1))
- schematic <- readTIFF('resources/schematic.tiff')
- aspratio <- 15/12 # this is the aspect ratio of the output pdf
- imwidth <- 0.85
- xstart <- 0
- ystart <- 0
- rasterImage(schematic,xstart,ystart,xstart+imwidth*aspratio,ystart+imwidth)
- text(0,0.88,'(a)',pos=4,cex=2)
- par(mar=c(3.5,6,1.5,2))
- lspec <- read.csv('resources/STLAB_1_oo_irrad_spectra.csv',header=FALSE)
- rspec <- read.csv('resources/STLAB_2_oo_irrad_spectra.csv',header=FALSE)
- lall <- matrix(0,nrow=11,ncol=401)
- lall[1,] <- unlist(lspec[1,3:403])
- rall <- matrix(0,nrow=11,ncol=401)
- rall[1,] <- unlist(rspec[1,3:403])
- for (primary in 1:10){
- temp <- subset(lspec,V1==(primary-1) & V2=='4095')
- lall[primary+1,] <- unlist(temp[1,3:403])
- temp <- subset(rspec,V1==(primary-1) & V2=='4095')
- rall[primary+1,] <- unlist(temp[1,3:403])
- }
- primaryrgbvals <- c(rgb(128/255,50/255,218/255),rgb(74/255,104/255,218/255),rgb(0,0,133/255),rgb(0,0,245/255),rgb(117/255,252/255,253/255),rgb(55/255,126/255,33/255),rgb(117/255,252/255,76/255),rgb(242/255,169/255,59/255),rgb(234/255,51/255,35/255),rgb(127/255,23/255,14/255))
- plotlims <- c(380,800,0,0.2)
- ticklocsx <- seq(400,800,100) # locations of tick marks on x axis
- ticklocsy <- seq(0,0.2,0.1) # locations of tick marks on y axis
- ticklabelsx <- ticklocsx # set labels for x ticks
- ticklabelsy <- ticklocsy # set labels for y ticks
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Wavelength (nm)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab=expression(paste("Power (",mu,"W/cm"^"2","/nm)")), col.lab=rgb(0,0,0), line=2, cex.lab=1.5)
- # for (primary in 1:10){polygon(c(lall[1,],780),c(lall[primary+1,],0),col=addalpha(primaryrgbvals[primary],0.5),border=NA)}
- for (primary in 1:10){polygon(c(rall[1,],780),c(rall[primary+1,],0),col=addalpha(primaryrgbvals[primary],0.5),border=NA)}
- legend(680,0.2,1:10,col=primaryrgbvals,lwd=2,box.lwd=2,title='Primary')
- text(380,0.19,'(b)',pos=4,cex=2)
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Wavelength (nm)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab=expression(paste("Power (",mu,"W/cm"^"2","/nm)")), col.lab=rgb(0,0,0), line=2, cex.lab=1.5)
- lines(spectra[,1],spectra[,2],lwd=2,col=colpal[2])
- lines(spectra[,1],spectra[,3],lwd=2,lty=2)
- legend(380,0.2,c('Left eye', 'Right eye'),lty=1:2,lwd=2,col=c(colpal[2],'black'),box.lwd=2)
- text(720,0.19,'(c)',pos=4,cex=2)
- plotlims <- c(0.5,5.5,0,15)
- ticklocsx <- 1:5 # locations of tick marks on x axis
- ticklocsy <- seq(0,15,5) # locations of tick marks on y axis
- ticklabelsx <- c('sc','mc','lc','rods','mel') # set labels for x ticks
- ticklabelsy <- ticklocsy # set labels for y ticks
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Photoreceptor class", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab=expression(paste(alpha,"-opic irradiance")), col.lab=rgb(0,0,0), line=1.8, cex.lab=1.5)
- # coneab <- coneab[c(1:3,5),] # don't plot rod predictions
- for (b in 1:5){
- polygon(c(b-0.3,b,b,b-0.3),c(0,0,coneab[b,2],coneab[b,2]),col=colpal[2],border=NA)
- polygon(c(b+0.3,b,b,b+0.3),c(0,0,coneab[b,3],coneab[b,3]),col='black',border=NA)
- }
- text(4.5,14,'(d)',pos=4,cex=2)
- plotlims <- c(380,800,-0.2,0.2)
- ticklocsx <- seq(400,800,100) # locations of tick marks on x axis
- ticklocsy <- seq(-0.2,0.2,0.1) # locations of tick marks on y axis
- ticklabelsx <- ticklocsx # set labels for x ticks
- ticklabelsy <- ticklocsy # set labels for y ticks
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Wavelength (nm)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Difference power", col.lab=rgb(0,0,0), line=2.8, cex.lab=1.5)
- title(main='Light flux',cex.main=2)
- polygon(c(spectra[,1],spectra[401,1]),c(spectra[,4],0),border=NA,col=stimcolours[1])
- polygon(c(spectra[,1],spectra[401,1]),c(spectra[,5],0),border=NA,col=stimcolours[2])
- lines(spectra[,1],spectra[,4],lwd=2)
- lines(spectra[,1],spectra[,5],lwd=2)
- lines(c(380,800),c(0,0),lwd=2)
- text(720,0.18,'(e)',pos=4,cex=2)
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Wavelength (nm)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Difference power", col.lab=rgb(0,0,0), line=2.8, cex.lab=1.5)
- title(main='L-M cone',cex.main=2)
- polygon(spectra[,1],spectra[,6],border=NA,col=stimcolours[3])
- polygon(spectra[,1],spectra[,7],border=NA,col=stimcolours[4])
- lines(spectra[,1],spectra[,6],lwd=2)
- lines(spectra[,1],spectra[,7],lwd=2)
- lines(c(380,800),c(0,0),lwd=2)
- text(720,0.18,'(f)',pos=4,cex=2)
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Wavelength (nm)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Difference power", col.lab=rgb(0,0,0), line=2.8, cex.lab=1.5)
- title(main='S-cone',cex.main=2)
- polygon(spectra[,1],spectra[,8],border=NA,col=stimcolours[5])
- polygon(spectra[,1],spectra[,9],border=NA,col=stimcolours[6])
- lines(spectra[,1],spectra[,8],lwd=2)
- lines(spectra[,1],spectra[,9],lwd=2)
- lines(c(380,800),c(0,0),lwd=2)
- text(720,0.18,'(g)',pos=4,cex=2)
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Wavelength (nm)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Difference power", col.lab=rgb(0,0,0), line=2.8, cex.lab=1.5)
- title(main='Melanopsin',cex.main=2)
- polygon(spectra[,1],spectra[,10],border=NA,col=stimcolours[7])
- polygon(spectra[,1],spectra[,11],border=NA,col=stimcolours[8])
- lines(spectra[,1],spectra[,10],lwd=2)
- lines(spectra[,1],spectra[,11],lwd=2)
- lines(c(380,800),c(0,0),lwd=2)
- text(720,0.18,'(h)',pos=4,cex=2)
- if (plotcontrasts==1){
- # pcontrast <- pcontrast[c(1,2,3,5),] # don't plot rods
- plotlims <- c(0.5,5.5,-100,100)
- ticklocsx <- 1:5 # locations of tick marks on x axis
- ticklocsy <- seq(-100,100,50) # locations of tick marks on y axis
- ticklabelsx <- c('sc','mc','lc','rods','mel') # set labels for x ticks
- ticklabelsy <- ticklocsy # set labels for y ticks
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Photoreceptor class", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Contrast (%)", col.lab=rgb(0,0,0), line=2, cex.lab=1.5)
- for (b in 1:5){
- polygon(c(b-0.3,b+0.3,b+0.3,b-0.3),c(0,0,pcontrast[b,4],pcontrast[b,4]),col=stimcolours[1],border='black',lwd=2)
- polygon(c(b-0.3,b+0.3,b+0.3,b-0.3),c(0,0,pcontrast[b,5],pcontrast[b,5]),col=stimcolours[2],border='black',lwd=2)
- }
- lines(c(0.5,5.5),c(0,0),lwd=2)
- text(4.65,80,'(i)',pos=4,cex=2)
- plotlims <- c(0.5,5.5,-20,20)
- ticklocsx <- 1:5 # locations of tick marks on x axis
- ticklocsy <- seq(-20,20,10) # locations of tick marks on y axis
- ticklabelsy <- ticklocsy # set labels for y ticks
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Photoreceptor class", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Contrast (%)", col.lab=rgb(0,0,0), line=2, cex.lab=1.5)
- for (b in 1:5){
- polygon(c(b-0.3,b+0.3,b+0.3,b-0.3),c(0,0,pcontrast[b,6],pcontrast[b,6]),col=stimcolours[3],border='black',lwd=2)
- polygon(c(b-0.3,b+0.3,b+0.3,b-0.3),c(0,0,pcontrast[b,7],pcontrast[b,7]),col=stimcolours[4],border='black',lwd=2)
- }
- lines(c(0.5,5.5),c(0,0),lwd=2)
- text(4.6,18,'(j)',pos=4,cex=2)
- plotlims <- c(0.5,5.5,-50,50)
- ticklocsx <- 1:5 # locations of tick marks on x axis
- ticklocsy <- seq(-50,50,25) # locations of tick marks on y axis
- ticklabelsy <- ticklocsy # set labels for y ticks
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Photoreceptor class", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Contrast (%)", col.lab=rgb(0,0,0), line=2, cex.lab=1.5)
- for (b in 1:5){
- polygon(c(b-0.3,b+0.3,b+0.3,b-0.3),c(0,0,pcontrast[b,8],pcontrast[b,8]),col=stimcolours[5],border='black',lwd=2)
- polygon(c(b-0.3,b+0.3,b+0.3,b-0.3),c(0,0,pcontrast[b,9],pcontrast[b,9]),col=stimcolours[6],border='black',lwd=2)
- }
- lines(c(0.5,5.5),c(0,0),lwd=2)
- text(4.6,45,'(k)',pos=4,cex=2)
- plotlims <- c(0.5,5.5,-20,20)
- ticklocsx <- 1:5 # locations of tick marks on x axis
- ticklocsy <- seq(-20,20,10) # locations of tick marks on y axis
- ticklabelsy <- ticklocsy # set labels for y ticks
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Photoreceptor class", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Contrast (%)", col.lab=rgb(0,0,0), line=2, cex.lab=1.5)
- for (b in 1:5){
- polygon(c(b-0.3,b+0.3,b+0.3,b-0.3),c(0,0,pcontrast[b,10],pcontrast[b,10]),col=stimcolours[7],border='black',lwd=2)
- polygon(c(b-0.3,b+0.3,b+0.3,b-0.3),c(0,0,pcontrast[b,11],pcontrast[b,11]),col=stimcolours[8],border='black',lwd=2)
- }
- lines(c(0.5,5.5),c(0,0),lwd=2)
- text(0.5,18,'(l)',pos=4,cex=2)
- }
- if (plotcontrasts==0){
- plotlims <- c(0.5,4.5,-15,15)
- ticklocsx <- 1:4 # locations of tick marks on x axis
- ticklocsy <- seq(-15,15,5) # locations of tick marks on y axis
- ticklabelsx <- c('sc','mc','lc','mel') # set labels for x ticks
- ticklabelsy <- ticklocsy # set labels for y ticks
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Photoreceptor class", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Irradiance change", col.lab=rgb(0,0,0), line=2.8, cex.lab=1.5)
- for (b in 1:4){
- polygon(c(b-0.3,b+0.3,b+0.3,b-0.3),c(0,0,coneab[b,4],coneab[b,4]),col=stimcolours[1],border='black',lwd=2)
- polygon(c(b-0.3,b+0.3,b+0.3,b-0.3),c(0,0,coneab[b,5],coneab[b,5]),col=stimcolours[2],border='black',lwd=2)
- }
- lines(c(0.5,4.5),c(0,0),lwd=2)
- text(3.5,14,'(i)',pos=4,cex=2)
- plotlims <- c(0.5,4.5,-4,4)
- ticklocsx <- 1:4 # locations of tick marks on x axis
- ticklocsy <- seq(-4,4,2) # locations of tick marks on y axis
- ticklabelsy <- ticklocsy # set labels for y ticks
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Photoreceptor class", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Irradiance change", col.lab=rgb(0,0,0), line=1.8, cex.lab=1.5)
- for (b in 1:4){
- polygon(c(b-0.3,b+0.3,b+0.3,b-0.3),c(0,0,coneab[b,6],coneab[b,6]),col=stimcolours[3],border='black',lwd=2)
- polygon(c(b-0.3,b+0.3,b+0.3,b-0.3),c(0,0,coneab[b,7],coneab[b,7]),col=stimcolours[4],border='black',lwd=2)
- }
- lines(c(0.5,4.5),c(0,0),lwd=2)
- text(3.5,3.6,'(j)',pos=4,cex=2)
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Photoreceptor class", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Irradiance change", col.lab=rgb(0,0,0), line=1.8, cex.lab=1.5)
- for (b in 1:4){
- polygon(c(b-0.3,b+0.3,b+0.3,b-0.3),c(0,0,coneab[b,8],coneab[b,8]),col=stimcolours[5],border='black',lwd=2)
- polygon(c(b-0.3,b+0.3,b+0.3,b-0.3),c(0,0,coneab[b,9],coneab[b,9]),col=stimcolours[6],border='black',lwd=2)
- }
- lines(c(0.5,4.5),c(0,0),lwd=2)
- text(3.5,3.6,'(k)',pos=4,cex=2)
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Photoreceptor class", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Irradiance change", col.lab=rgb(0,0,0), line=1.8, cex.lab=1.5)
- for (b in 1:4){
- polygon(c(b-0.3,b+0.3,b+0.3,b-0.3),c(0,0,coneab[b,10],coneab[b,10]),col=stimcolours[7],border='black',lwd=2)
- polygon(c(b-0.3,b+0.3,b+0.3,b-0.3),c(0,0,coneab[b,11],coneab[b,11]),col=stimcolours[8],border='black',lwd=2)
- }
- lines(c(0.5,4.5),c(0,0),lwd=2)
- text(3.5,3.6,'(l)',pos=4,cex=2)
- }
- prefixlist <- c('P6','P7','P3','P5')
- d <- dir(datadir,pattern=prefixlist[1], full.names = TRUE)
- load(file=paste0(datadir,'AveragedataLum.RData'))
- plotlims <- c(-1,13,-1,0.5)
- ticklocsx <- c(-1,seq(0,12,2),13) # locations of tick marks on x axis
- ticklocsy <- seq(-1,0.5,0.5) # locations of tick marks on y axis
- ticklabelsx <- c('',seq(0,12,2),'') # set labels for x ticks
- ticklabelsy <- ticklocsy # set labels for y ticks
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Time (s)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Diameter change (mm)", col.lab=rgb(0,0,0), line=2.8, cex.lab=1.5)
- filt <- butter(3,0.083)
- filtwaveP <- signal::filter(filt, meanwavesP)
- filtU <- signal::filter(filt,meanwavesPCI[1,])
- filtL <- signal::filter(filt,meanwavesPCI[2,])
- polygon(times[c(1:length(times),length(times):1)],c(filtL,filtU[length(filtU):1]),col=addalpha(colpal[2],0.4),border=NA)
- lines(times, filtwaveP, col=colpal[2], lwd=1.5)
- lines(times, (sinewave/10)-0.9, col='black', lwd=2)
- for (part in 1:24) {
- load(file=paste0(d[part]))
- filt <- butter(3,0.083)
- filtwaveP <- signal::filter(filt, meanwavesP)
- lines(times, filtwaveP, col=addalpha(colpal[2],0.7), lwd=0.2)
- }
- text(-1,0.4,'(m)',pos=4,cex=2)
- load(file=paste0(datadir,'AveragedataLM.RData'))
- d <- dir(datadir,pattern=prefixlist[2], full.names = TRUE)
- plotlims <- c(-1,13,-1,0.5)
- ticklocsx <- c(-1,seq(0,12,2),13) # locations of tick marks on x axis
- ticklocsy <- seq(-1,0.5,0.5) # locations of tick marks on y axis
- ticklabelsx <- c('',seq(0,12,2),'') # set labels for x ticks
- ticklabelsy <- ticklocsy # set labels for y ticks
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Time (s)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Diameter change (mm)", col.lab=rgb(0,0,0), line=2.8, cex.lab=1.5)
- filt <- butter(3,0.083)
- filtwaveP <- signal::filter(filt, meanwavesP)
- filtU <- signal::filter(filt,meanwavesPCI[1,])
- filtL <- signal::filter(filt,meanwavesPCI[2,])
- polygon(times[c(1:length(times),length(times):1)],c(filtL,filtU[length(filtU):1]),col=addalpha(colpal[2],0.4),border=NA)
- lines(times, filtwaveP, col=colpal[2], lwd=1.5)
- lines(times, (sinewave/10)-0.9, col='black', lwd=2)
- for (part in 1:24) {
- load(file=paste0(d[part]))
- filt <- butter(3,0.083)
- filtwaveP <- signal::filter(filt, meanwavesP)
- lines(times, filtwaveP, col=addalpha(colpal[2],0.7), lwd=0.2)
- }
- text(-1,0.4,'(n)',pos=4,cex=2)
- load(file=paste0(datadir,'AveragedataS.RData'))
- d <- dir(datadir,pattern=prefixlist[3], full.names = TRUE)
- plotlims <- c(-1,13,-1,0.5)
- ticklocsx <- c(-1,seq(0,12,2),13) # locations of tick marks on x axis
- ticklocsy <- seq(-1,0.5,0.5) # locations of tick marks on y axis
- ticklabelsx <- c('',seq(0,12,2),'') # set labels for x ticks
- ticklabelsy <- ticklocsy # set labels for y ticks
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Time (s)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Diameter change (mm)", col.lab=rgb(0,0,0), line=2.8, cex.lab=1.5)
- filt <- butter(3,0.083)
- filtwaveP <- signal::filter(filt, meanwavesP)
- filtU <- signal::filter(filt,meanwavesPCI[1,])
- filtL <- signal::filter(filt,meanwavesPCI[2,])
- polygon(times[c(1:length(times),length(times):1)],c(filtL,filtU[length(filtU):1]),col=addalpha(colpal[2],0.4),border=NA)
- lines(times, filtwaveP, col=colpal[2], lwd=1.5)
- lines(times, (sinewave/10)-0.9, col='black', lwd=2)
- for (part in 1:24) {
- load(file=paste0(d[part]))
- filt <- butter(3,0.083)
- filtwaveP <- signal::filter(filt, meanwavesP)
- lines(times, filtwaveP, col=addalpha(colpal[2],0.7), lwd=0.2)
- }
- text(-1,0.4,'(o)',pos=4,cex=2)
- load(file=paste0(datadir,'AveragedataMel.RData'))
- d <- dir(datadir,pattern=prefixlist[4], full.names = TRUE)
- plotlims <- c(-1,13,-1,0.5)
- ticklocsx <- c(-1,seq(0,12,2),13) # locations of tick marks on x axis
- ticklocsy <- seq(-1,0.5,0.5) # locations of tick marks on y axis
- ticklabelsx <- c('',seq(0,12,2),'') # set labels for x ticks
- ticklabelsy <- ticklocsy # set labels for y ticks
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Time (s)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Diameter change (mm)", col.lab=rgb(0,0,0), line=2.8, cex.lab=1.5)
- filt <- butter(3,0.083)
- filtwaveP <- signal::filter(filt, meanwavesP)
- filtU <- signal::filter(filt,meanwavesPCI[1,])
- filtL <- signal::filter(filt,meanwavesPCI[2,])
- polygon(times[c(1:length(times),length(times):1)],c(filtL,filtU[length(filtU):1]),col=addalpha(colpal[2],0.4),border=NA)
- lines(times, filtwaveP, col=colpal[2], lwd=1.5)
- lines(times, (sinewave/10)-0.9, col='black', lwd=2)
- for (part in 1:24) {
- load(file=paste0(d[part]))
- filt <- butter(3,0.083)
- filtwaveP <- signal::filter(filt, meanwavesP)
- lines(times, filtwaveP, col=addalpha(colpal[2],0.7), lwd=0.2)
- }
- text(-1,0.4,'(p)',pos=4,cex=2)
- load(file=paste0(datadir,'AveragedataLum.RData'))
- plotlims <- c(0,2,0,0.15)
- ticklocsx <- seq(0,2,0.5) # locations of tick marks on x axis
- ticklocsy <- seq(0,0.15,0.05) # locations of tick marks on y axis
- ticklabelsx <-ticklocsx # set labels for x ticks
- ticklabelsy <- ticklocsy # set labels for y ticks
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Frequency (Hz)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Amplitude (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- polygon(frequencies[c(3:21,21:3)],c(meanspectraPCI[1,3:21],meanspectraPCI[2,21:3]),col=addalpha(colpal[2],0.3),border=NA)
- lines(frequencies[3:21],abs(meanspectraP[3:21]), col=colpal[2], lwd=1.5, cex=0.5)
- text(0,0.14,'(q)',pos=4,cex=2)
- load(file=paste0(datadir,'AveragedataLM.RData'))
- plotlims <- c(0,2,0,0.015)
- ticklocsx <- seq(0,2,0.5) # locations of tick marks on x axis
- ticklocsy <- seq(0,0.015,0.005) # locations of tick marks on y axis
- ticklabelsx <-ticklocsx # set labels for x ticks
- ticklabelsy <- ticklocsy # set labels for y ticks
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Frequency (Hz)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Amplitude (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- polygon(frequencies[c(3:21,21:3)],c(meanspectraPCI[1,3:21],meanspectraPCI[2,21:3]),col=addalpha(colpal[2],0.3),border=NA)
- lines(frequencies[3:21],abs(meanspectraP[3:21]), col=colpal[2], lwd=1.5, cex=0.5)
- text(0,0.014,'(r)',pos=4,cex=2)
- load(file=paste0(datadir,'AveragedataS.RData'))
- plotlims <- c(0,2,0,0.015)
- ticklocsx <- seq(0,2,0.5) # locations of tick marks on x axis
- ticklocsy <- seq(0,0.015,0.005) # locations of tick marks on y axis
- ticklabelsx <-ticklocsx # set labels for x ticks
- ticklabelsy <- ticklocsy # set labels for y ticks
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Frequency (Hz)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Amplitude (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- polygon(frequencies[c(3:21,21:3)],c(meanspectraPCI[1,3:21],meanspectraPCI[2,21:3]),col=addalpha(colpal[2],0.3),border=NA)
- lines(frequencies[3:21],abs(meanspectraP[3:21]), col=colpal[2], lwd=1.5, cex=0.5)
- text(0,0.014,'(s)',pos=4,cex=2)
- load(file=paste0(datadir,'AveragedataMel.RData'))
- plotlims <- c(0,2,0,0.015)
- ticklocsx <- seq(0,2,0.5) # locations of tick marks on x axis
- ticklocsy <- seq(0,0.015,0.005) # locations of tick marks on y axis
- ticklabelsx <-ticklocsx # set labels for x ticks
- ticklabelsy <- ticklocsy # set labels for y ticks
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Frequency (Hz)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5)
- title(ylab="Amplitude (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- polygon(frequencies[c(3:21,21:3)],c(meanspectraPCI[1,3:21],meanspectraPCI[2,21:3]),col=addalpha(colpal[2],0.3),border=NA)
- lines(frequencies[3:21],abs(meanspectraP[3:21]), col=colpal[2], lwd=1.5, cex=0.5)
- text(0,0.014,'(t)',pos=4,cex=2)
- dev.off()
- }
- ```
- ```{r plot1Fdata, include=FALSE, results='hide'}
- if (processdata > 0){
- contrastsfinedB <- 2:40
- pdf(paste0(figdir,"FirstHarmonic.pdf"), bg="transparent", height = 24, width = 8)
- layout(matrix(c(1,2,3,4,5,5,5,5,6,7,8,9),6,2,byrow=TRUE))
- par(mar=c(3,6,2,1))
- condlist <- c(1,2,6)
- condlist2 <- c(1,2,4)
- plotlims <- c(15,40,0,0.15)
- plotlims2 <- c(15,40,0,0.015)
- ticklocsx <- contrastsdB # locations of tick marks on x axis
- ticklocsy <- seq(0,0.15,0.05)
- ticklocsy2 <- seq(0,0.015,0.005) # locations of tick marks on y axis
- ticklabelsx <-c(6,12,24,48,96) # set labels for x ticks
- ticklabelsy <- ticklocsy
- ticklabelsy2 <- ticklocsy2 # set labels for y ticks
- load(file=paste0(datadir,'AveragedataLum.RData'))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Target contrast (%)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5) # titles for axes
- title(ylab="Amplitude at 0.5Hz (mm)", col.lab=rgb(0,0,0), line=3, cex.lab=1.5)
- title(main='Light flux',cex.main=2)
- for (cond in 1:3){
- lines(contrastsfinedB,modrespsLum[condlist2[cond],], col=colpal[condlist[cond]], lwd=3, cex=0.5)
- # lines(contrastsfinedB,abs(cleanmeansP[condlist[cond],]), col=colpal[condlist[cond]], lwd=3, cex=0.5)
- arrows(contrastsdB,abs(cleanmeansP[condlist[cond],]),contrastsdB,cleanmeansPCI[1,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- arrows(contrastsdB,abs(cleanmeansP[condlist[cond],]),contrastsdB,cleanmeansPCI[2,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- points(contrastsdB,abs(cleanmeansP[condlist[cond],]), pch = 20+cond, col='black', bg=colpal[condlist[cond]], cex=1.6, lwd=3)
- }
- legend(15,0.125,c('Monocular','Binocular','Dichoptic cross'),pch=21:23,pt.bg=colpal[condlist],pt.lwd=3,pt.cex=1.6,box.lwd=2,cex=1.2)
- text(17,0.14,'(a)',adj=0.5,cex=2.5)
- load(file=paste0(datadir,'AveragedataLM.RData'))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims2[1:2], ylim=plotlims2[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy2, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy2, side = 2, at=ticklocsy2, line=0.2, las=1)
- title(xlab="Target contrast (% of max)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5) # titles for axes
- title(ylab="Amplitude at 0.5Hz (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- title(main='L-M cone',cex.main=2)
- for (cond in 1:3){
- lines(contrastsfinedB,modrespsLM[condlist2[cond],], col=colpal[condlist[cond]], lwd=3, cex=0.5)
- # lines(contrastsdB,abs(cleanmeansP[condlist[cond],]), col=colpal[condlist[cond]], lwd=3, cex=0.5)
- arrows(contrastsdB,abs(cleanmeansP[condlist[cond],]),contrastsdB,cleanmeansPCI[1,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- arrows(contrastsdB,abs(cleanmeansP[condlist[cond],]),contrastsdB,cleanmeansPCI[2,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- points(contrastsdB,abs(cleanmeansP[condlist[cond],]), pch = 20+cond, col='black', bg=colpal[condlist[cond]], cex=1.6, lwd=3)
- }
- text(17,0.014,'(b)',adj=0.5,cex=2.5)
- load(file=paste0(datadir,'AveragedataS.RData'))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims2[1:2], ylim=plotlims2[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy2, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy2, side = 2, at=ticklocsy2, line=0.2, las=1)
- title(xlab="Target contrast (% of max)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5) # titles for axes
- title(ylab="Amplitude at 0.5Hz (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- title(main='S cone',cex.main=2)
- for (cond in 1:3){
- lines(contrastsfinedB,modrespsS[condlist2[cond],], col=colpal[condlist[cond]], lwd=3, cex=0.5)
- # lines(contrastsdB,abs(cleanmeansP[condlist[cond],]), col=colpal[condlist[cond]], lwd=3, cex=0.5)
- arrows(contrastsdB,abs(cleanmeansP[condlist[cond],]),contrastsdB,cleanmeansPCI[1,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- arrows(contrastsdB,abs(cleanmeansP[condlist[cond],]),contrastsdB,cleanmeansPCI[2,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- points(contrastsdB,abs(cleanmeansP[condlist[cond],]), pch = 20+cond, col='black', bg=colpal[condlist[cond]], cex=1.6, lwd=3)
- }
- text(17,0.014,'(c)',adj=0.5,cex=2.5)
- load(file=paste0(datadir,'AveragedataMel.RData'))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims2[1:2], ylim=plotlims2[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy2, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy2, side = 2, at=ticklocsy2, line=0.2, las=1)
- title(xlab="Target contrast (% of max)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5) # titles for axes
- title(ylab="Amplitude at 0.5Hz (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- title(main='Melanopsin',cex.main=2)
- for (cond in 1:3){
- lines(contrastsfinedB,modrespsMel[condlist2[cond],], col=colpal[condlist[cond]], lwd=3, cex=0.5)
- # lines(contrastsdB,abs(cleanmeansP[condlist[cond],]), col=colpal[condlist[cond]], lwd=3, cex=0.5)
- arrows(contrastsdB,abs(cleanmeansP[condlist[cond],]),contrastsdB,cleanmeansPCI[1,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- arrows(contrastsdB,abs(cleanmeansP[condlist[cond],]),contrastsdB,cleanmeansPCI[2,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- points(contrastsdB,abs(cleanmeansP[condlist[cond],]), pch = 20+cond, col='black', bg=colpal[condlist[cond]], cex=1.6, lwd=3)
- }
- text(17,0.014,'(d)',adj=0.5,cex=2.5)
- par(mar=c(8,12,8,4))
- plotlims <- c(-24,12,0,0.6)
- ticklocsx <- seq(-24,12,12)
- ticklocsy <- seq(0,0.6,0.1)
- ticklabelsx <- c('1/16','1/4','1','4')
- ticklabelsy <- ticklocsy # set labels for y ticks
- par(pty="s") # make axis square
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4]) # create an empty axis of the correct dimensions
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=1, cex=1.5)
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1, cex=1.5)
- title(xlab=expression(omega), col.lab=rgb(0,0,0), line=4, cex.lab=6)
- title(ylab="Probability", col.lab=rgb(0,0,0), line=3, cex.lab=3)
- title(main='Suppressive weight 1F',cex.main=3)
- a <- density(20*log10(pupilweightpopLum),from=-48,to=12)
- polygon(c(-48,a$x,12), c(0,a$y,0), col=addalpha(stimcolours[1],0.5),border=NA)
- a<- density(20*log10(pupilweightpopLM),from=-48,to=12)
- polygon(c(-48,a$x,12), c(0,a$y,0), col=addalpha(stimcolours[4],0.5),border=NA)
- a <- density(20*log10(pupilweightpopS),from=-48,to=12)
- polygon(c(-48,a$x,12), c(0,a$y,0), col=addalpha(stimcolours[6],0.5),border=NA)
- a <- density(20*log10(pupilweightpopMel),from=-48,to=12)
- polygon(c(-48,a$x,12), c(0,a$y,0), col=addalpha(stimcolours[7],0.5),border=NA)
- prior <- dnorm(10^((-48:12)/20),1,0.5)
- prior <- 0.5*prior/max(prior)
- lines(-48:12,prior,lwd=2)
- lines(20*log10(c(mean(pupilWLum),mean(pupilWLum))),c(0,0.6),lwd=6,col=stimcolours[1])
- lines(20*log10(c(mean(pupilWLM),mean(pupilWLM))),c(0,0.6),lwd=6,col=stimcolours[4])
- lines(20*log10(c(mean(pupilWS),mean(pupilWS))),c(0,0.6),lwd=6,col=stimcolours[6])
- lines(20*log10(c(mean(pupilWMel),mean(pupilWMel))),c(0,0.6),lwd=6,col=stimcolours[7])
- legend(-24,0.6,c('Prior','LF 1F', 'L-M 1F', 'S 1F', 'Mel 1F'), col=c('black',stimcolours[c(1,4,6,7)]),lwd=c(2,6,6,6,6),lty=1, cex=1.8, box.lwd=2, bg='white')
- text(10,0.58,'(e)',adj=0.5,cex=2.5)
- par(mar=c(3,6,2,1))
- condlist <- c(4,5,6)
- condlist2 <- c(1,4,5)
- plotlims <- c(15,40,0,0.15)
- plotlims2 <- c(15,40,0,0.015)
- ticklocsx <- contrastsdB # locations of tick marks on x axis
- ticklocsy <- seq(0,0.15,0.05)
- ticklocsy2 <- seq(0,0.015,0.005) # locations of tick marks on y axis
- ticklabelsx <-c(6,12,24,48,96) # set labels for x ticks
- ticklabelsy <- ticklocsy
- ticklabelsy2 <- ticklocsy2 # set labels for y ticks
- load(file=paste0(datadir,'AveragedataLum.RData'))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1)
- title(xlab="Target contrast (%)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5) # titles for axes
- title(ylab="Amplitude at 0.4Hz (mm)", col.lab=rgb(0,0,0), line=3, cex.lab=1.5)
- title(main='Light flux',cex.main=2)
- for (cond in 1:3){
- lines(contrastsfinedB,modrespsLum[condlist2[cond],], col=colpal[condlist[cond]], lwd=3, cex=0.5)
- # lines(contrastsfinedB,abs(cleanmeansP[condlist[cond],]), col=colpal[condlist[cond]], lwd=3, cex=0.5)
- arrows(contrastsdB,abs(cleanmasksP[condlist[cond],]),contrastsdB,cleanmasksPCI[1,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- arrows(contrastsdB,abs(cleanmasksP[condlist[cond],]),contrastsdB,cleanmasksPCI[2,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- points(contrastsdB,abs(cleanmasksP[condlist[cond],]), pch = 20+cond, col='black', bg=colpal[condlist[cond]], cex=1.6, lwd=3)
- }
- legend(15,0.125,c('Monocular','Binocular cross','Dichoptic cross'),pch=21:23,pt.bg=colpal[condlist],pt.lwd=3,pt.cex=1.6,box.lwd=2,cex=1.2)
- text(17,0.14,'(f)',adj=0.5,cex=2.5)
- load(file=paste0(datadir,'AveragedataLM.RData'))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims2[1:2], ylim=plotlims2[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy2, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy2, side = 2, at=ticklocsy2, line=0.2, las=1)
- title(xlab="Target contrast (% of max)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5) # titles for axes
- title(ylab="Amplitude at 0.4Hz (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- title(main='L-M cone',cex.main=2)
- for (cond in 1:3){
- lines(contrastsfinedB,modrespsLM[condlist2[cond],], col=colpal[condlist[cond]], lwd=3, cex=0.5)
- # lines(contrastsdB,abs(cleanmeansP[condlist[cond],]), col=colpal[condlist[cond]], lwd=3, cex=0.5)
- arrows(contrastsdB,abs(cleanmasksP[condlist[cond],]),contrastsdB,cleanmasksPCI[1,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- arrows(contrastsdB,abs(cleanmasksP[condlist[cond],]),contrastsdB,cleanmasksPCI[2,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- points(contrastsdB,abs(cleanmasksP[condlist[cond],]), pch = 20+cond, col='black', bg=colpal[condlist[cond]], cex=1.6, lwd=3)
- }
- text(17,0.014,'(g)',adj=0.5,cex=2.5)
- load(file=paste0(datadir,'AveragedataS.RData'))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims2[1:2], ylim=plotlims2[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy2, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy2, side = 2, at=ticklocsy2, line=0.2, las=1)
- title(xlab="Target contrast (% of max)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5) # titles for axes
- title(ylab="Amplitude at 0.4Hz (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- title(main='S cone',cex.main=2)
- for (cond in 1:3){
- lines(contrastsfinedB,modrespsS[condlist2[cond],], col=colpal[condlist[cond]], lwd=3, cex=0.5)
- # lines(contrastsdB,abs(cleanmeansP[condlist[cond],]), col=colpal[condlist[cond]], lwd=3, cex=0.5)
- arrows(contrastsdB,abs(cleanmasksP[condlist[cond],]),contrastsdB,cleanmasksPCI[1,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- arrows(contrastsdB,abs(cleanmasksP[condlist[cond],]),contrastsdB,cleanmasksPCI[2,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- points(contrastsdB,abs(cleanmasksP[condlist[cond],]), pch = 20+cond, col='black', bg=colpal[condlist[cond]], cex=1.6, lwd=3)
- }
- text(17,0.014,'(h)',adj=0.5,cex=2.5)
- load(file=paste0(datadir,'AveragedataMel.RData'))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims2[1:2], ylim=plotlims2[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy2, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy2, side = 2, at=ticklocsy2, line=0.2, las=1)
- title(xlab="Target contrast (% of max)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5) # titles for axes
- title(ylab="Amplitude at 0.4Hz (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- title(main='Melanopsin',cex.main=2)
- for (cond in 1:3){
- lines(contrastsfinedB,modrespsMel[condlist2[cond],], col=colpal[condlist[cond]], lwd=3, cex=0.5)
- # lines(contrastsdB,abs(cleanmeansP[condlist[cond],]), col=colpal[condlist[cond]], lwd=3, cex=0.5)
- arrows(contrastsdB,abs(cleanmasksP[condlist[cond],]),contrastsdB,cleanmasksPCI[1,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- arrows(contrastsdB,abs(cleanmasksP[condlist[cond],]),contrastsdB,cleanmasksPCI[2,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- points(contrastsdB,abs(cleanmasksP[condlist[cond],]), pch = 20+cond, col='black', bg=colpal[condlist[cond]], cex=1.6, lwd=3)
- }
- text(17,0.014,'(i)',adj=0.5,cex=2.5)
- dev.off()
- }
- ```
- ```{r plot2Fdata, include=FALSE, results='hide'}
- if (processdata > 0){
- contrastsfinedB <- 2:40
- pdf(paste0(figdir,"SecondHarmonic.pdf"), bg="transparent", height = 24, width = 8)
- layout(matrix(c(1,2,3,4,5,5,5,5,6,7,8,9),6,2,byrow=TRUE))
- par(mar=c(3,6,2,1))
- condlist <- c(1,2,6)
- condlist2 <- c(1,2,4)
- plotlims <- c(15,40,0,0.15)
- plotlims2 <- c(15,40,0,0.015)
- ticklocsx <- contrastsdB # locations of tick marks on x axis
- ticklocsy <- seq(0,0.15,0.05)
- ticklocsy2 <- seq(0,0.015,0.005) # locations of tick marks on y axis
- ticklabelsx <-c(6,12,24,48,96) # set labels for x ticks
- ticklabelsy <- ticklocsy
- ticklabelsy2 <- ticklocsy2 # set labels for y ticks
- load(file=paste0(datadir,'AveragedataLum.RData'))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims2[1:2], ylim=plotlims2[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy2, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy2, side = 2, at=ticklocsy2, line=0.2, las=1)
- title(xlab="Target contrast (%)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5) # titles for axes
- title(ylab="Amplitude at 1Hz (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- title(main='Light flux',cex.main=2)
- for (cond in 1:3){
- lines(contrastsfinedB,modrespsLum2[condlist2[cond],], col=colpal[condlist[cond]], lwd=3, cex=0.5)
- # lines(contrastsfinedB,abs(cleanmeansP[condlist[cond],]), col=colpal[condlist[cond]], lwd=3, cex=0.5)
- arrows(contrastsdB,abs(cleanmeansP2[condlist[cond],]),contrastsdB,cleanmeansP2CI[1,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- arrows(contrastsdB,abs(cleanmeansP2[condlist[cond],]),contrastsdB,cleanmeansP2CI[2,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- points(contrastsdB,abs(cleanmeansP2[condlist[cond],]), pch = 20+cond, col='black', bg=colpal[condlist[cond]], cex=1.6, lwd=3)
- }
- legend(15,0.0125,c('Monocular','Binocular','Dichoptic cross'),pch=21:23,pt.bg=colpal[condlist],pt.lwd=3,pt.cex=1.6,box.lwd=2,cex=1.2)
- text(17,0.014,'(a)',adj=0.5,cex=2.5)
- load(file=paste0(datadir,'AveragedataLM.RData'))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims2[1:2], ylim=plotlims2[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy2, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy2, side = 2, at=ticklocsy2, line=0.2, las=1)
- title(xlab="Target contrast (% of max)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5) # titles for axes
- title(ylab="Amplitude at 1Hz (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- title(main='L-M cone',cex.main=2)
- for (cond in 1:3){
- lines(contrastsfinedB,modrespsLM2[condlist2[cond],], col=colpal[condlist[cond]], lwd=3, cex=0.5)
- # lines(contrastsdB,abs(cleanmeansP[condlist[cond],]), col=colpal[condlist[cond]], lwd=3, cex=0.5)
- arrows(contrastsdB,abs(cleanmeansP2[condlist[cond],]),contrastsdB,cleanmeansP2CI[1,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- arrows(contrastsdB,abs(cleanmeansP2[condlist[cond],]),contrastsdB,cleanmeansP2CI[2,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- points(contrastsdB,abs(cleanmeansP2[condlist[cond],]), pch = 20+cond, col='black', bg=colpal[condlist[cond]], cex=1.6, lwd=3)
- }
- text(17,0.014,'(b)',adj=0.5,cex=2.5)
- load(file=paste0(datadir,'AveragedataS.RData'))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims2[1:2], ylim=plotlims2[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy2, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy2, side = 2, at=ticklocsy2, line=0.2, las=1)
- title(xlab="Target contrast (% of max)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5) # titles for axes
- title(ylab="Amplitude at 1Hz (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- title(main='S cone',cex.main=2)
- for (cond in 1:3){
- lines(contrastsfinedB,modrespsS2[condlist2[cond],], col=colpal[condlist[cond]], lwd=3, cex=0.5)
- # lines(contrastsdB,abs(cleanmeansP[condlist[cond],]), col=colpal[condlist[cond]], lwd=3, cex=0.5)
- arrows(contrastsdB,abs(cleanmeansP2[condlist[cond],]),contrastsdB,cleanmeansP2CI[1,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- arrows(contrastsdB,abs(cleanmeansP2[condlist[cond],]),contrastsdB,cleanmeansP2CI[2,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- points(contrastsdB,abs(cleanmeansP2[condlist[cond],]), pch = 20+cond, col='black', bg=colpal[condlist[cond]], cex=1.6, lwd=3)
- }
- text(17,0.014,'(c)',adj=0.5,cex=2.5)
- load(file=paste0(datadir,'AveragedataMel.RData'))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims2[1:2], ylim=plotlims2[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy2, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy2, side = 2, at=ticklocsy2, line=0.2, las=1)
- title(xlab="Target contrast (% of max)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5) # titles for axes
- title(ylab="Amplitude at 1Hz (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- title(main='Melanopsin',cex.main=2)
- for (cond in 1:3){
- lines(contrastsfinedB,modrespsMel2[condlist2[cond],], col=colpal[condlist[cond]], lwd=3, cex=0.5)
- # lines(contrastsdB,abs(cleanmeansP[condlist[cond],]), col=colpal[condlist[cond]], lwd=3, cex=0.5)
- arrows(contrastsdB,abs(cleanmeansP2[condlist[cond],]),contrastsdB,cleanmeansP2CI[1,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- arrows(contrastsdB,abs(cleanmeansP2[condlist[cond],]),contrastsdB,cleanmeansP2CI[2,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- points(contrastsdB,abs(cleanmeansP2[condlist[cond],]), pch = 20+cond, col='black', bg=colpal[condlist[cond]], cex=1.6, lwd=3)
- }
- text(17,0.014,'(d)',adj=0.5,cex=2.5)
- par(mar=c(8,12,8,4))
- plotlims <- c(-24,12,0,0.6)
- ticklocsx <- seq(-24,12,12)
- ticklocsy <- seq(0,0.6,0.1)
- ticklabelsx <- c('1/16','1/4','1','4')
- ticklabelsy <- ticklocsy # set labels for y ticks
- par(pty="s") # make axis square
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims[1:2], ylim=plotlims[3:4]) # create an empty axis of the correct dimensions
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2)
- axis(2, at=ticklocsy, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=1, cex=1.5)
- mtext(text = ticklabelsy, side = 2, at=ticklocsy, line=0.2, las=1, cex=1.5)
- title(xlab=expression(omega), col.lab=rgb(0,0,0), line=4, cex.lab=6)
- title(ylab="Probability", col.lab=rgb(0,0,0), line=3, cex.lab=3)
- title(main='Suppressive weight 2F',cex.main=3)
- a <- density(20*log10(pupilweightpopLum2),from=-48,to=12)
- polygon(c(-48,a$x,12), c(0,a$y,0), col=addalpha(stimcolours[1],0.5),border=NA)
- a<- density(20*log10(pupilweightpopLM2),from=-48,to=12)
- polygon(c(-48,a$x,12), c(0,a$y,0), col=addalpha(stimcolours[4],0.5),border=NA)
- a <- density(20*log10(pupilweightpopS2),from=-48,to=12)
- polygon(c(-48,a$x,12), c(0,a$y,0), col=addalpha(stimcolours[6],0.5),border=NA)
- a <- density(20*log10(pupilweightpopMel2),from=-48,to=12)
- polygon(c(-48,a$x,12), c(0,a$y,0), col=addalpha(stimcolours[7],0.5),border=NA)
- prior <- dnorm(10^((-48:12)/20),1,0.5)
- prior <- 0.5*prior/max(prior)
- lines(-48:12,prior,lwd=2)
- lines(20*log10(c(mean(pupilWLum2),mean(pupilWLum2))),c(0,0.6),lwd=6,col=stimcolours[1])
- lines(20*log10(c(mean(pupilWLM2),mean(pupilWLM2))),c(0,0.6),lwd=6,col=stimcolours[4])
- lines(20*log10(c(mean(pupilWS2),mean(pupilWS2))),c(0,0.6),lwd=6,col=stimcolours[6])
- lines(20*log10(c(mean(pupilWMel2),mean(pupilWMel2))),c(0,0.6),lwd=6,col=stimcolours[7])
- legend(-24,0.6,c('Prior','LF 2F', 'L-M 2F', 'S 2F', 'Mel 2F'), col=c('black',stimcolours[c(1,4,6,7)]),lwd=c(2,6,6,6,6),lty=1, cex=1.8, box.lwd=2, bg='white')
- text(10,0.58,'(e)',adj=0.5,cex=2.5)
- par(mar=c(3,6,2,1))
- condlist <- c(4,5,6)
- condlist2 <- c(1,4,5)
- plotlims <- c(15,40,0,0.15)
- plotlims2 <- c(15,40,0,0.015)
- ticklocsx <- contrastsdB # locations of tick marks on x axis
- ticklocsy <- seq(0,0.15,0.05)
- ticklocsy2 <- seq(0,0.015,0.005) # locations of tick marks on y axis
- ticklabelsx <-c(6,12,24,48,96) # set labels for x ticks
- ticklabelsy <- ticklocsy
- ticklabelsy2 <- ticklocsy2 # set labels for y ticks
- load(file=paste0(datadir,'AveragedataLum.RData'))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims2[1:2], ylim=plotlims2[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy2, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy2, side = 2, at=ticklocsy2, line=0.2, las=1)
- title(xlab="Target contrast (%)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5) # titles for axes
- title(ylab="Amplitude at 0.8Hz (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- title(main='Light flux',cex.main=2)
- for (cond in 1:3){
- lines(contrastsfinedB,modrespsLum2[condlist2[cond],], col=colpal[condlist[cond]], lwd=3, cex=0.5)
- # lines(contrastsfinedB,abs(cleanmeansP[condlist[cond],]), col=colpal[condlist[cond]], lwd=3, cex=0.5)
- arrows(contrastsdB,abs(cleanmasksP2[condlist[cond],]),contrastsdB,cleanmasksP2CI[1,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- arrows(contrastsdB,abs(cleanmasksP2[condlist[cond],]),contrastsdB,cleanmasksP2CI[2,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- points(contrastsdB,abs(cleanmasksP2[condlist[cond],]), pch = 20+cond, col='black', bg=colpal[condlist[cond]], cex=1.6, lwd=3)
- }
- legend(15,0.0125,c('Monocular','Binocular cross','Dichoptic cross'),pch=21:23,pt.bg=colpal[condlist],pt.lwd=3,pt.cex=1.6,box.lwd=2,cex=1.2)
- text(17,0.014,'(f)',adj=0.5,cex=2.5)
- load(file=paste0(datadir,'AveragedataLM.RData'))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims2[1:2], ylim=plotlims2[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy2, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy2, side = 2, at=ticklocsy2, line=0.2, las=1)
- title(xlab="Target contrast (% of max)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5) # titles for axes
- title(ylab="Amplitude at 0.8Hz (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- title(main='L-M cone',cex.main=2)
- for (cond in 1:3){
- lines(contrastsfinedB,modrespsLM2[condlist2[cond],], col=colpal[condlist[cond]], lwd=3, cex=0.5)
- # lines(contrastsdB,abs(cleanmeansP[condlist[cond],]), col=colpal[condlist[cond]], lwd=3, cex=0.5)
- arrows(contrastsdB,abs(cleanmasksP2[condlist[cond],]),contrastsdB,cleanmasksP2CI[1,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- arrows(contrastsdB,abs(cleanmasksP2[condlist[cond],]),contrastsdB,cleanmasksP2CI[2,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- points(contrastsdB,abs(cleanmasksP2[condlist[cond],]), pch = 20+cond, col='black', bg=colpal[condlist[cond]], cex=1.6, lwd=3)
- }
- text(17,0.014,'(g)',adj=0.5,cex=2.5)
- load(file=paste0(datadir,'AveragedataS.RData'))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims2[1:2], ylim=plotlims2[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy2, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy2, side = 2, at=ticklocsy2, line=0.2, las=1)
- title(xlab="Target contrast (% of max)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5) # titles for axes
- title(ylab="Amplitude at 0.8Hz (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- title(main='S cone',cex.main=2)
- for (cond in 1:3){
- lines(contrastsfinedB,modrespsS2[condlist2[cond],], col=colpal[condlist[cond]], lwd=3, cex=0.5)
- # lines(contrastsdB,abs(cleanmeansP[condlist[cond],]), col=colpal[condlist[cond]], lwd=3, cex=0.5)
- arrows(contrastsdB,abs(cleanmasksP2[condlist[cond],]),contrastsdB,cleanmasksP2CI[1,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- arrows(contrastsdB,abs(cleanmasksP2[condlist[cond],]),contrastsdB,cleanmasksP2CI[2,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- points(contrastsdB,abs(cleanmasksP2[condlist[cond],]), pch = 20+cond, col='black', bg=colpal[condlist[cond]], cex=1.6, lwd=3)
- }
- text(17,0.014,'(h)',adj=0.5,cex=2.5)
- load(file=paste0(datadir,'AveragedataMel.RData'))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=plotlims2[1:2], ylim=plotlims2[3:4])
- axis(1, at=ticklocsx, tck=0.01, lab=F, lwd=2) # plot tick marks (no labels)
- axis(2, at=ticklocsy2, tck=0.01, lab=F, lwd=2)
- mtext(text = ticklabelsx, side = 1, at=ticklocsx, line=0.3) # add the tick labels
- mtext(text = ticklabelsy2, side = 2, at=ticklocsy2, line=0.2, las=1)
- title(xlab="Target contrast (% of max)", col.lab=rgb(0,0,0), line=1.5, cex.lab=1.5) # titles for axes
- title(ylab="Amplitude at 0.8Hz (mm)", col.lab=rgb(0,0,0), line=3.5, cex.lab=1.5)
- title(main='Melanopsin',cex.main=2)
- for (cond in 1:3){
- lines(contrastsfinedB,modrespsMel2[condlist2[cond],], col=colpal[condlist[cond]], lwd=3, cex=0.5)
- # lines(contrastsdB,abs(cleanmeansP[condlist[cond],]), col=colpal[condlist[cond]], lwd=3, cex=0.5)
- arrows(contrastsdB,abs(cleanmasksP2[condlist[cond],]),contrastsdB,cleanmasksP2CI[1,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- arrows(contrastsdB,abs(cleanmasksP2[condlist[cond],]),contrastsdB,cleanmasksP2CI[2,condlist[cond],],lwd=3,col=colpal[condlist[cond]],length=0.05,angle=90)
- points(contrastsdB,abs(cleanmasksP2[condlist[cond],]), pch = 20+cond, col='black', bg=colpal[condlist[cond]], cex=1.6, lwd=3)
- }
- text(17,0.014,'(i)',adj=0.5,cex=2.5)
- dev.off()
- }
- ```
- ```{r makephaseplots, include=FALSE, results='hide'}
- # generate plots showing the phase angles
- if (processdata > 0){
- load(file=paste0(datadir,'AveragedataLum.RData'))
- xlum <- Re(cleanmeansP)
- ylum <- Im(cleanmeansP)
- xlum2 <- Re(cleanmeansP2)
- ylum2 <- Im(cleanmeansP2)
- load(file=paste0(datadir,'AveragedataMel.RData'))
- xmel <- Re(cleanmeansP)
- ymel <- Im(cleanmeansP)
- xmel2 <- Re(cleanmeansP2)
- ymel2 <- Im(cleanmeansP2)
- load(file=paste0(datadir,'AveragedataLM.RData'))
- xlm <- Re(cleanmeansP)
- ylm <- Im(cleanmeansP)
- xlm2 <- Re(cleanmeansP2)
- ylm2 <- Im(cleanmeansP2)
- load(file=paste0(datadir,'AveragedataS.RData'))
- xs <- Re(cleanmeansP)
- ys <- Im(cleanmeansP)
- xs2 <- Re(cleanmeansP2)
- ys2 <- Im(cleanmeansP2)
- pdf(paste(figdir,"phaseplots.pdf",sep=''), bg="transparent", height = 5, width = 10)
- par(mfrow=c(1,2),mar=c(1,1,1,1))
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=c(-0.022,0.022), ylim=c(-0.022,0.022))
- rangles <- seq(0,360)*(pi/180)
- r <- seq(0.005,0.02,0.005)
- for (n in 1:length(r)){
- lines(r[n]*cos(rangles),r[n]*sin(rangles),col='gray')
- }
- lines(c(0,0),c(-0.02,0.02),lwd=2)
- lines(c(-0.02,0.02),c(0,0),lwd=2)
- text(0.015,-0.001,'Amplitude',cex=1.25,adj=0.5)
- text(0.021,0,'Phase',cex=1.25,adj=0.5,srt=-90)
- lines(xlum[2,]/10,ylum[2,]/10,col=stimcolours[1],lwd=3)
- points(xlum[2,]/10,ylum[2,]/10,pch=21,col=stimcolours[1],bg=stimcolours[2],cex=2,lwd=3)
- lines(xmel[2,],ymel[2,],col=stimcolours[7],lwd=3)
- points(xmel[2,],ymel[2,],pch=21,col=stimcolours[7],bg=stimcolours[8],cex=2,lwd=3)
- lines(xlm[2,],ylm[2,],col=stimcolours[3],lwd=3)
- points(xlm[2,],ylm[2,],pch=21,col=stimcolours[3],bg=stimcolours[4],cex=2,lwd=3)
- lines(xs[2,],ys[2,],col=stimcolours[6],lwd=3)
- points(xs[2,],ys[2,],pch=21,col=stimcolours[6],bg=stimcolours[5],cex=2,lwd=3)
- text(-0.019,0.019,'(a)',cex=3)
- text(0.016,0.019,'1F',cex=2.5)
- legend(-0.019,0.0155,c('Light flux','L-M','S','Melanopsin'),lty=1,col=stimcolours[c(1,3,6,7)],lwd=3,pch=21,pt.cex=2,pt.bg=stimcolours[c(2,4,5,8)],cex=1.1,box.lwd=3,bg='white')
- plot(x=NULL,y=NULL,axes=FALSE, ann=FALSE, xlim=c(-0.022,0.022), ylim=c(-0.022,0.022))
- rangles <- seq(0,360)*(pi/180)
- r <- seq(0.005,0.02,0.005)
- for (n in 1:length(r)){
- lines(r[n]*cos(rangles),r[n]*sin(rangles),col='gray')
- }
- lines(c(0,0),c(-0.02,0.02),lwd=2)
- lines(c(-0.02,0.02),c(0,0),lwd=2)
- text(0.015,-0.001,'Amplitude',cex=1.25,adj=0.5)
- text(0.021,0,'Phase',cex=1.25,adj=0.5,srt=-90)
- lines(xlum2[2,],ylum2[2,],col=stimcolours[1],lwd=3)
- points(xlum2[2,],ylum2[2,],pch=21,col=stimcolours[1],bg=stimcolours[2],cex=2,lwd=3)
- lines(xmel2[2,],ymel2[2,],col=stimcolours[7],lwd=3)
- points(xmel2[2,],ymel2[2,],pch=21,col=stimcolours[7],bg=stimcolours[8],cex=2,lwd=3)
- lines(xlm2[2,],ylm2[2,],col=stimcolours[3],lwd=3)
- points(xlm2[2,],ylm2[2,],pch=21,col=stimcolours[3],bg=stimcolours[4],cex=2,lwd=3)
- lines(xs2[2,],ys2[2,],col=stimcolours[6],lwd=3)
- points(xs2[2,],ys2[2,],pch=21,col=stimcolours[6],bg=stimcolours[5],cex=2,lwd=3)
- text(-0.019,0.019,'(b)',cex=3)
- text(0.016,0.019,'2F',cex=2.5)
- dev.off()
- }
- ```
- # Abstract
- Pupil diameters are regulated by the autonomic nervous system, which combines light signals across the eyes independently of the visual cortex. Distinct classes of retinal photoreceptor are involved in this process, with cones and rods driving the initial constriction and intrinsically photosensitive retinal ganglion cells maintaining diameter over prolonged time periods. We investigated binocular combination by targeting different photoreceptor pathways using a novel binocular multiprimary system to modulate the input spectra via silent substitution. At the first harmonic of the modulation frequency, luminance and S-cone responses showed strong binocular facilitation, and weak interocular suppression. Melanopsin responses were invariant to the number of eyes stimulated. The L-M pathway involved binocular inhibition, whereby responses to binocular stimulation were weaker than for monocular stimulation. The second harmonic involved strong interocular suppression in all pathways, but with some evidence of binocular facilitation. Our results are consistent with a computational model of binocular signal combination (implemented in a Bayesian hierarchical framework), in which the weight of interocular suppression differs across pathways. We also find pathway differences in response phase, consistent with different lag times for phototransduction. These results provide evidence that binocular interactions in the pupillary pathway differ across photoreceptor-directed modulations and across harmonic components of the response.
- # Introduction
- The autonomic nervous system regulates many involuntary bodily processes, including the constriction and dilation of the pupils in response to light [@McDougal2015]. The anatomical pathway from the retina to the subcortical nuclei controlling the pupillary light response (PLR) is well established: it includes the olivary pretectal nucleus (OPN), the Superior Cervical ganglion and the Edinger-Westphal nucleus, which project to the iris sphincter muscles that directly control the pupil size [@McDougal2015]. Evidence of a binocular component to the PLR is shown by the consensual response of the pupil (stimulation of one eye will cause constriction of the other eye) [@Wyatt1981]. The anatomical segregation of the subcortical pathway from the rest of the brain means that this binocular combination of signals must occur independently of the cortical processes of binocular integration required for visual perception. Our recent work [@Segala2023] has shown that the algorithm underlying binocular combination of light in the pupil pathway differs from that in the cortex. Here we extend this paradigm to compare binocular combination across signals from different photoreceptor classes that feed into the autonomic nervous system.
- Different classes of retinal photoreceptors, including cones, rods and melanopsin-containing intrinsically photosensitive retinal ganglion cells (ipRGCs), are directly involved in controlling and maintaining the size of the pupils [e.g. @McDougal2010; @Barrionuevo2016; @Murray2018; @Spitschan2014; @Woelders2018]. Cones drive the initial rapid constriction of the pupils [@Mathot2018], while the slower and longer activation of the ipRGCs maintains constriction over a prolonged period of time and regulates the post-illumination pupillary response [@Markwell2010; @McDougal2010]. The ipRGCs are a recently discovered photoreceptor class [@Provencio2000] that express the photopigment melanopsin, and are involved in the regulation of the circadian rhythm [@Panda2002; @Ruby2002], forming a major input to the OPN [@Dacey2003]. The first direct evidence of the involvement of the ipRGCs in the PLR was shown in melanopsin knockout mice [@Lucas2003], resulting in the loss of the intrinsic photosensitivity of the cells and a reduced pupil constriction. Similar behaviour was later observed in primates and humans [@Gamlin2007] using silent substitution (see Methods), where it was shown that the PLR continues during light presentation even when cone and rod signalling is blocked, indicating the primary role of the ipRGCs in maintaining pupil constriction over a prolonged time.
- Binocular combination has been extensively studied in visual perception, where it is mediated by neurons in primary visual cortex [@Hubel1962]. For pattern vision, binocular summation occurs at threshold, such that a lower contrast is required to detect a target shown to both eyes than a target shown to one eye [@Baker2018; @Campbell1965]. At higher contrasts, the phenomenon of 'ocularity invariance' is observed, in which the response to monocularly- and binocularly-presented patterns is equal [@Baker2007; @Moradi2009]. This is explained by a process of interocular suppression that cancels out the additional excitatory drive caused by stimulating two eyes. Our recent work [@Segala2023] showed that cortical signal combination for luminance flicker is substantially more linear than for spatial patterns, whereas there is evidence of interocular suppression in the pupil pathway. Here we measure the amplitude of pupil modulations in response to flickering stimuli presented as light flux, or directed towards the L-M cones, S-cones and melanopsin (see Figure \@ref(fig:spectraplots)e-l). Our design uses three comparisons, each estimating a distinct quantity. To obtain an objective measure of pupil responses, we presented the two eyes with slightly different temporal frequencies (0.5 and 0.4 Hz). Responses at these two frequencies fall in separate bins of the Fourier spectrum and so this 'frequency tagging' lets us recover the response driven by one eye while the other eye supplies a concurrent, separable signal. Comparing monocular and binocular stimulation measures the net effect of adding a second eye, but cannot by itself separate additional excitation from interocular suppression. Therefore we used a dichoptic mask, in which the target eye is stimulated while the other eye views a fixed-contrast modulation. This allows us to isolate the suppression that one eye exerts on the response to the other. Together these comparisons allow us to test whether binocular facilitation and interocular suppression differ across photoreceptor-directed modulations. We interpret the results using a contemporary model of binocular vision [@Meese2006], fitted within a hierarchical Bayesian framework.
- # Materials and methods
- ## Participants
- Twenty-four participants were recruited for each of the four experiments for a total of ninety-six adult participants (28 male, 68 female), whose ages ranged from 18 to 41. All participants had normal or corrected to normal vision, no known abnormalities of binocular or colour vision, and gave written informed consent. Our procedures were approved by the Ethics Committee of the Department of Psychology at the University of York (identification number 184). Participant recruitment began on 3 March 2023 and concluded on 8 Decemeber 2023.
- ## Apparatus & stimuli
- To present synchronised stimulus modulations independently to each eye, two light engines (SpectraTuneLAB, which are thermally stable across time with active cooling according to the manufacturers: LEDMOTIVE Technologies, LLC, Barcelona, Spain), each with 10 independently addressable LED colour channels, were integrated into a customised binocular viewing system. The light engines were operated via a Python interface to their REST API [@Martin2022], which supports synchronous launch and playback of spectral sequences prepared in advance and stored in JSON format. A special command was commissioned from LEDMOTIVE to allow two different stimulus files to be launched simultaneously from the two devices. The synchronisation of the spectra from the two devices was tested and showed that they were synchronised to within about 3ms.
- When preparing the spectral sequences, the age of participants was used to account for the yellowing of the lenses. We used the silent substitution technique [@Estevez1982] to selectively stimulate specific photoreceptor classes. Silent substitution exploits the fact that each photoreceptor class has a distinct spectral tuning that overlaps with the others. Using a multiprimary system, in which the primaries (i.e. LEDs) have different spectra, it is possible to target one class of photoreceptors while maintaining the others at a constant activity level, effectively silencing them [@Shapiro1996; @Spitschan2018, this paper also offers a clear explanation of how to implement silent substitution]. We calculated silent substitution solutions using the _PySilSub_ toolbox [@Martin2023], using linear algebra. The outputs of the two light engines (see Figure \@ref(fig:spectraplots)b,c) were calibrated using an Ocean Optics Jaz spectroradiometer, which was wavelength-calibrated to an Argon lamp and intensity calibrated using a NIST-traceable light source. Each primary was also linearised using a polynomial fit (see Figure \@ref(fig:primarylinearity) for details). We used the 10-degree cone fundamentals [@Stockman2000], and estimates of melanopsin absorbance spectra from CIE S 026 (discussed in a previous paper [@Martin2023]) to calculate $\alpha$-opic irradiance.
- ```{r spectraplots, fig.cap="Summary of the spectral power distributions and alpha-opic irradiances for the background and each condition, as well as averaged pupil diameters and Fourier spectra. Panel (a) shows a schematic of the binocular stimulation system for presenting spectrally tuned modulations independently to each eye. The VR headset was attached to a clamp stand that the experimenters could use to adjust the height and align the headset with the eyes of the participant. The participant's head was supported by a chin rest to keep it in position throughout the experiment. Panel (b) shows the outputs of each LED primary at maximum intensity, and panels (c) and (d) show the overall spectral power distributions and the alpha-opic irradiances of the background spectra used for both eyes. The subsequent rows show the power differences (e-h), and photoreceptor contrasts (i-l) relative to the background, averaged pupil diameter waveforms (m-p) and Fourier spectra (q-t) for binocular stimulation. Column headings indicate the pathway stimulated, and shaded regions in panels m-t indicate bootstrapped 95\\% confidence intervals.", fig.align="center", out.width = '80%', echo=FALSE}
- knitr::include_graphics(paste0(figdir,'methodsfigrods.pdf'))
- ```
- The output from the light engines was directed through liquid light guides (LLG3-8H: Thorlabs Ltd, Cambridgeshire, UK) and diffused onto semi-opaque and highly diffusive white glass discs with a diameter of 50 mm for even illumination (34-473: Edmund Optics, York, UK). The light guide gaskets were butt-coupled to the light engine diffusers with threaded adapters (SM1A9, AD3LLG: Thorlabs Ltd, Cambridgeshire, UK) and the exiting ends of the light guides were mated with 51 mm depth optical cylinders (SM2L20: Thorlabs Ltd, Cambridgeshire, UK) via appropriately threaded adapters (AD3LLG, SM2A6: Thorlabs Ltd, Cambridgeshire, UK). The stimulus diffuser discs were retained at the front end of the optical cylinders about 51 mm from the light source, at which distance the output beam was sufficiently dispersed to afford even illumination of the diffuser when viewed from the front. To guarantee safe illumination levels, a circular neutral-density filter with the same diameter as the white glass discs (50 mm) and an optical density of 0.6 log units was placed in the optical path between the light source and the diffusers. A small circular piece of blackout material with a diameter of about 8 degrees (10 mm) was positioned centrally on the front of each diffuser disc to aid as a fusion lock, as a fixation point, and to occlude the fovea.
- The diffuser discs were positioned in the objective planes of the lenses of a modified VR headset (SHINECON SC-G01, Dongguan Shinecon Industrial Co. Ltd., Guangdong, China), which was used by the participants to view the stimuli. The stimuli were two discs of flickering light with a diameter of about 30 degrees, which were fused together into a cyclopean percept resembling a donut-shaped ring of light, similar to that used in other studies [e.g., @Barrionuevo2016; @Murray2018; @Spitschan2014; @Spitschan2015; @Zele2018]. The VR headset modifications allowed for small adjustments to account for individual differences in interpupillary distance and focal length. The use of this set up allowed us to modulate the stimuli in three different ocular configurations, similar to the ones we used in our previous study [@Segala2023]: monocular, binocular and dichoptic. In the monocular configuration, the unstimulated eye still saw a non-flickering disc of mean light flux. A schematic of the stimulation system is shown in Figure \@ref(fig:spectraplots)a. Pupillometry data were collected using a binocular Pupil Core eye-tracker headset (Pupil Labs GmbH, Berlin, Germany [@Kassner2014]) running at 120 Hz, and the signals were recorded with the Pupil Capture software.
- Our previous study [@Segala2023] used a temporal frequency of 2Hz for foveal luminance flicker, and recorded EEG data simultaneously with pupillometry. Initial pilot experiments indicated that this frequency was too high to elicit measurable responses when stimulating individual photoreceptor pathways. For all experiments, we therefore used a primary flicker frequency of 0.5Hz, as previous literature showed that this was slow enough to elicit a pupil response from all photoreceptor classes [@Spitschan2014]. We also focussed on only recording pupillometry data as this frequency would be too slow to elicit steady-state EEG responses [@Norcia2015].
- For all experiments, sinusoidal temporal modulations were presented against the same background spectrum (matched between the eyes), which was used to achieve silent substitution in the three photoreceptor modulation experiments. The background spectra were defined by setting all channels to half maximum output for the brighter of the two devices (STLab 1, left eye) and then using the STLab 1/STLab 2 calibration ratio to find the equivalent settings for the companion device (STLab 2, right eye). The background spectrum illuminance was about 74 lux, or 68.5 cd/m$^2$. The spectral power distributions and $\alpha$-opic irradiances of the background spectra for both eyes are shown in Figure \@ref(fig:spectraplots)c-d.
- Silent substitution stimuli were prepared and calibrated for each participant with custom Python software [@Martin2023] and Python scripts. Estimates of photoreceptor spectral sensitivities for each participant were constructed from the known photopigment absorbance spectra [@Stockman2000], taking account of the peak axial density of the respective photopigments, as well as lens [@Pokorny1987; @Stockman1999; @Stockman2000] and macular pigment density [@Bone1988; @Stockman1999], in accordance with the field size and age-dependent CIEPO06 observer model [@CIE2006]. The melanopic and rhodopic action spectra of the 32-year-old standard observer were taken from CIE S 026 [@CIE2018] and then adjusted for age-related lens transmittance with a spectral correction function, in line with the standard. Macular pigment correction was not applied to the rhodopic and melanopic action spectra because rods are not present at the fovea and ipRGCs sit above the retinal pigment layer [@Trieschmann2007].
- In the light flux experiment, the stimulus intensity was increased and decreased relative to the background, which we expected to modulate all photoreceptor classes (see Figure \@ref(fig:spectraplots)e,i). In the L-M cone modulation experiment, we used silent substitution to increase the L-cone activity, and simultaneously decrease the M-cone activity, during the first half-cycle of the sine wave. In the second half-cycle the polarity of the modulation reversed (see Figure \@ref(fig:spectraplots)f,j). The maximum available L-M contrast was about 10\%. In the S-cone modulation experiment, we increased and decreased S-cone-directed signals, whilst keeping activity in the other photoreceptors constant (see Figure \@ref(fig:spectraplots)g,k). Our system allowed a maximum contrast of 45\%. Finally, in the melanopsin experiment, we modulated the activity of the melanopsin-containing intrinsically photoreceptive retinal ganglion cells, whilst keeping cone activity constant (see Figure \@ref(fig:spectraplots)h,l). The maximum available melanopsin contrast was 22\%. We assume that the activity of rods was constant at the high background luminance intensity used here, and so did not attempt to silence rod activity in any condition, as this would have greatly reduced the available dynamic range. Splatter on nominally silenced photoreceptors was very small (see Figure \@ref(fig:spectraplots)j-l), well below the levels that would be expected to generate measurable pupil modulations, although we can observe that the rods may not be saturated in the L-M and melanopsin-directed stimuli (Figure \@ref(fig:spectraplots)j and \@ref(fig:spectraplots)l) and could intrude in these two experiments. We also estimated activation of penumbral L and M cones [@Spitschan2015; @Barrionuevo2016; @Zele2019], which was minimal ($\le 1.5%$\% contrast; less than splatter on the open-field cones) for melanopsin-directed stimuli. We note that the temporal frequency of our modulation (0.5Hz) is well below the range where penumbral cone activation can elicit visible percepts, and that such percepts fade after around 1 second [@Spitschan2015], and do not typically affect pupil responses [@Spitschan2014].
- ## Procedure
- Before the start of each experiment, participants adjusted the objective planes of the lenses with the help of the experimenter until the stimulus was in focus and they perceived the two pieces of blackout material as one fused disc. Pupil responses to binocular temporal contrast modulations were examined in a factorial design that combined six ocular conditions and five temporal contrast levels: 6, 12, 24, 48 and 96% of the available dynamic range. This design, similar to that used in our previous studies [@Segala2023; @Baker2017; @Baker2020], was applied in four separate experiments, each with a different mode of photoreceptor stimulation. In the first three conditions, the discs flickered at 0.5 Hz, in either a monocular, binocular or dichoptic arrangement. In the dichoptic condition the non-target eye saw a flickering fixed contrast of 48% of the available dynamic range. In the remaining three conditions (the cross-frequency conditions) one eye’s disc flickered at 0.4 Hz, and the other eye’s disc flickered at 0.5 Hz. This included monocular responses at 0.4 Hz, as well as binocular (one eye sees each frequency at the target contrast) and dichoptic (target stimulus flickering at 0.5 Hz, mask contrast of 48% at 0.4 Hz in the other eye) arrangements. We counterbalanced presentation of the target stimulus across the left and right eyes. The full set of conditions is summarised in Table \@ref(tab:diagnosticstable).
- ```{r diagnosticstable, echo=FALSE}
- diagnostics <- data.frame(
- Fit = c("Light flux 1F", "L-M cone 1F", "S cone 1F", "Melanopsin 1F",
- "Light flux 2F", "L-M cone 2F", "S cone 2F", "Melanopsin 2F"),
- Rhat = c(1.00, 1.01, 1.01, 1.06, 1.01, 1.02, 1.02, 1.00),
- ESS = c(17700, 9800, 6200, 10300, 5700, 3100, 3000, 11300),
- RMSE = c(0.0043, 0.0016, 0.0028, 0.0030, 0.0014, 0.0021, 0.0020, 0.0035)
- )
- kable(diagnostics,
- caption = "Convergence diagnostics and fit statistics for the eight model fits. Maximum split-$\\hat{R}$ is taken across all parameters; minimum effective sample size (ESS) is taken across the group-level parameters; RMSE is the root-mean-square error between the group-mean data and the model prediction.",
- col.names = c("Fit", "max $\\hat{R}$", "min ESS", "RMSE (mm)"),
- align = "lrrr",
- digits = c(NA, 2, 0, 4),
- booktabs = TRUE,
- escape = FALSE)%>%
- kableExtra::kable_styling(latex_options = "HOLD_position") %>% row_spec(4,hline_after=TRUE)
- ```
- The experiments were conducted in a windowless room, in which the only source of light was the modified VR headset. The participants sat as close as possible to the VR headset, leaving enough space for the eye-tracker to record the eyes. Each experiment was carried out in a single session of around 45-60 minutes, divided into three blocks of 15-17 minutes each. In each block, there were a total of 60 trials lasting 15 seconds each (12s of stimulus presentation, followed by 3s of interstimulus interval). The participants were given no task other than look at the black fixation dot while trying to minimise their blinking during the presentation period. For all experiments other than the light flux condition, participants adapted to the unmodulated background luminance for two minutes before stimulation began.
- Before the start of the L-M experiment, participants completed a luminance nulling perceptual calibration procedure in L-M cone space on an Iiyama $\textrm{VisionMaster}^{\textrm{TM}}$ Pro 510 display (800 x 600 pixels, 60 Hz refresh rate). During the task, participants were presented with a disc flickering within the L-M cone space (between magenta and cyan). Using a trackball, participants adjusted the angle in cone space to find their subjective isoluminant point, which resulted in changing the flickering intensity of the stimulus until the amplitude of the flicker appeared to be minimised. The result was used to modify the requested contrasts during stimulus preparation so as to account for individual differences affecting perceived illuminance, principally the L:M cone ratio [@Carroll2002; @Hofer2005].
- ## Data analysis
- The pupillometry data were analysed using the same method we used in our previous study [@Segala2023]. The data were converted from mp4 videos to a csv text file using the Pupil Player software [@Kassner2014], which estimated pupil diameter for each eye on each frame using a 3D model of the eyeball. The individual data were then loaded into R for analysis, where a ten-second waveform for each trial in each eye was extracted (excluding the first two seconds after stimulus onset). We interpolated across any dropped or missing frames to ensure regular and continuous sampling over time. The Fourier transform was calculated for each waveform, and all repetitions of each condition were pooled across eye and then averaged. Finally, data were averaged across all participants to obtain the group results. We used coherent averaging and at each stage we excluded data points with a Mahalanobis distance exceeding $D$ = `r SDthresh` from the complex-valued mean [@Baker2021]. For monocular stimulation, we confirmed that the consensual response was equivalent to the response in the stimulated eye.
- For all experiments, we used a bootstrapping procedure with `r nbootstraps` iterations to estimate standard errors across participants. All analysis and figure construction was conducted using a single R-script, available online, making this study fully computationally reproducible: https://osf.io/gdvt4/.
- ## Computational model and parameter estimation
- To quantitatively summarise our data, we used the same model described in our previous study [@Segala2023]. The model has the same general form as the first stage of the contrast gain control model proposed by Meese and colleagues [@Meese2006], but omits the second stage. For the previous model that we used [@Segala2023], the exponent of the numerator and denominator had fixed values of 2 and (implicitly) 1. Here, we allow these parameters (called _p_ and _q_) to be free, to allow different shapes of contrast response function, e.g. accelerating or saturating.
- In words, the model works as follows. Each eye produces an excitatory response that grows with the contrast in that eye. This response is then divided by a term that includes the contrast in the other eye, so that stimulating one eye reduces the response to the other. The two suppressed monocular signals are summed to give the binocular response. The weight $\omega$ sets the strength of this interocular suppression: values below 1 give weak suppression, and therefore stronger binocular facilitation, whereas values near or above 1 give strong suppression, which can produce ocularity invariance or, when suppression is strong enough, a binocular response that is smaller than the monocular one. The responses of the left eye and right eye channels are as follows:
- \begin{equation}
- \label{eq:respL}
- resp_L = \frac{L^p}{Z + L^q + \omega R^q},
- \end{equation}
- \begin{equation}
- \label{eq:respR}
- resp_R = \frac{R^p}{Z + R^q + \omega L^q},
- \end{equation}
- \noindent where _L_ and _R_ are the contrast signals from the left and right eyes, _p_ and _q_ are exponents, _Z_ is a saturation constant that shifts the contrast-response function laterally, and $\omega$ is the weight of suppression from the other eye.
- The responses from the two eyes are then summed binocularly:
- \begin{equation}
- \label{eq:respB}
- resp_B = R_{max}(resp_L + resp_R) + k,
- \end{equation}
- \noindent where _k_ is a noise parameter, and $R_{max}$ scales the overall response amplitude.
- The models were fit using a hierarchical Bayesian framework implemented in Stan [@Carpenter2017]. The data for each photoreceptor type and response frequency was fit separately, for a total of 8 model fits. We did not fit the two eyes separately. Target-eye assignment was counterbalanced across participants, and responses were pooled across eye before fitting, after confirming that the consensual response matched the response in the stimulated eye (see Data analysis). The prior for the $\omega$ parameter was Gaussian, with a mean of 1 and standard deviation of 0.5. Priors for the other free parameters were also Gaussian, with mean values based on previous work [@Segala2023]. All free parameters were constrained to be positive (and the saturation constant _Z_ to be at least 1), so these Gaussian priors were truncated at those bounds. The model was hierarchical and was fitted to the individual-participant amplitudes rather than to the group averages: each participant had their own set of parameters, drawn from group-level distributions whose means we report and compare across conditions. We ran 16 chains of 64,500 iterations each, discarded the first 2,000 iterations of each as warm-up, and thinned by a factor of 10, giving 103,200 retained samples per fit (over `r numSavedSteps*thinSteps` samples before thinning). The group-level parameters converged well in every fit (split-\(\hat{R} < 1.02\) and effective sample size \(> 3{,}000\)). In the melanopsin first-harmonic fit a few participant-level exponent and suppression parameters reached \(\hat{R} = 1.06\); the group-level suppression weight, which is our parameter of interest, had \(\hat{R} = 1.00\). As a summary of fit, we report the root-mean-square error between the group-mean data and the model prediction for each fit in Table \@ref(tab:diagnosticstable).
- ```{r designtable, echo=FALSE}
- design <- data.frame(
- Condition = c("Monocular (0.5 Hz)", "Binocular (0.5 Hz)", "Dichoptic (0.5 Hz)",
- "Monocular (0.4 Hz)", "Binocular cross", "Dichoptic cross"),
- TargetEye = c("0.5 Hz, variable", "0.5 Hz, variable", "0.5 Hz, variable",
- "0.4 Hz, variable", "0.5 Hz, variable", "0.5 Hz, variable"),
- OtherEye = c("Steady background", "0.5 Hz, variable", "0.5 Hz, fixed 48%",
- "Steady background", "0.4 Hz, variable", "0.4 Hz, fixed 48%"),
- AnalysedAt = c("0.5 Hz", "0.5 Hz", "0.5 Hz", "0.4 Hz", "0.5 & 0.4 Hz", "0.5 & 0.4 Hz"),
- Purpose = c("Response to one eye", "Binocular facilitation", "Interocular suppression",
- "Frequency control", "Cross-frequency interaction", "Cross-frequency suppression")
- )
- kable(design,
- caption = "The six ocular conditions used in each of the four experiments. The target eye was modulated at the target contrast (one of five levels); the response was measured at the frequency or frequencies indicated. Target-eye assignment was counterbalanced across the left and right eyes.",
- col.names = c("Condition", "Target eye", "Other eye", "Analysed at", "Purpose"),
- align = "lllll",
- booktabs = TRUE,
- escape = TRUE) %>%
- kableExtra::kable_styling(latex_options = "HOLD_position") %>% row_spec(4,hline_after=TRUE)
- ```
- # Results
- Figure \@ref(fig:spectraplots)m-p shows averaged waveforms of pupil diameter in response to binocular stimulation. For light flux stimuli (Figure \@ref(fig:spectraplots)m) a strong modulation is apparent at the stimulation frequency (0.5Hz), which is also clear in the Fourier amplitude spectrum (Figure \@ref(fig:spectraplots)q). For binocular stimulation of the L-M pathway, S-cone pathway and melanopsin pathway, pupil modulations were less than 10\% of the amplitude of the light flux modulation (note the change in y-axis scale for the Fourier spectra), but still apparent at 0.5Hz in the Fourier spectra (Figure \@ref(fig:spectraplots)n-p). We also observed a second harmonic response (at 1Hz) for all conditions, which was weaker than the first harmonic for light flux and melanopsin stimulation, but stronger for L-M and S-cone stimulation. The second harmonic is also apparent in the pupil waveforms shown in Figure \@ref(fig:spectraplots)n-p. Our main analysis therefore focuses on the amplitude of the pupil modulations at both the first and second harmonic frequencies across different stimulus conditions.
- A response at twice the stimulus frequency tells us that the pupil does not simply follow the sinusoidal modulation. A purely linear system driven by a sinusoid responds only at the stimulus frequency, so energy at the second harmonic must reflect a nonlinear transformation of the input. Such harmonic distortion can arise from many sources including rectification or asymmetry between constriction and redilation. We therefore treat the second harmonic as a complementary measure of nonlinear temporal processing in the pupil response, rather than as a separate oscillatory mechanism. This analysis was exploratory, as we had no specific prediction about the size of the second harmonic in each condition.
- Figure \@ref(fig:FirstHarmonicPlots)a-d shows contrast response functions across stimulation conditions for responses at the first harmonic of the main stimulation frequency (0.5Hz). In each plot, the response to monocular stimulation is given by the red circles and typically increases monotonically as a function of stimulus (temporal) contrast. Relative to monocular stimulation, binocular stimulation led to higher response amplitudes, indicating a binocular facilitation effect, for the light flux and S-cone conditions (Figure \@ref(fig:FirstHarmonicPlots)a,c), and to some extent for the melanopsin condition (Figure \@ref(fig:FirstHarmonicPlots)d). But the L-M cone condition (Figure \@ref(fig:FirstHarmonicPlots)b) produced a binocular suppression effect, where the response to binocular stimulation was weaker than the response to monocular stimulation (blue squares below red circles). These results indicate that the magnitude of binocular facilitation differs across photoreceptor pathway, suggesting heterogeneity in the underlying neural computation.
- ```{r FirstHarmonicPlots, fig.cap="Contrast response functions for pupil modualations in response to flicker at 0.5Hz (panels a-d), and 0.4Hz (panels f-i), and posterior parameter distributions for the weight of interocular suppression (panel e). Within each panel (except panel e), data points are the coherently averaged amplitudes for each condition, and error bars indicate bootstrapped 95\\% confidence intervals. Curves show model fits using the maximum a posteriori (MAP) parameter values (see Table 3). In panel (e), vertical lines show the MAP estimates, and the thin black curve indicates the prior (note the logarithmic x-axis).", fig.align="center", out.width="40%", echo=FALSE}
- knitr::include_graphics(paste0(figdir,'FirstHarmonic.pdf'))
- ```
- In contemporary models of binocular signal combination, the amount of binocular facilitation is determined by the magnitude of interocular suppression, with strong suppression reducing facilitation [@Meese2006]. We can estimate the strength of interocular suppression by measuring how much monocular responses are reduced when a dichoptic 'mask' is shown to the other eye. In our paradigm, the two components flickered at different frequencies (0.5 and 0.4Hz) so that their responses remained distinct in the Fourier spectrum [e.g. @Busse2009] (all conditions are summarised in Table \@ref(tab:diagnosticstable)). The yellow diamond symbols in Figure \@ref(fig:FirstHarmonicPlots)a-d show the target responses in this condition, and in most cases were weaker than the monocular responses (red circles). The strongest dichoptic masking is found in the L-M condition, where we also observed the binocular suppression effect. Suppression can also be estimated from the responses at 0.4Hz (Figure \@ref(fig:FirstHarmonicPlots)f-i). The reduced amplitude in the binocular cross condition (where the two eyes received different temporal frequencies; purple squares) relative to the 0.4Hz monocular condition (grey circles), and the progressive decline in amplitude of the dichoptic cross response (yellow diamonds) also differ across photoreceptor conditions, showing similar differences to those observed at 0.5Hz.
- To estimate the extent of interocular suppression for each photoreceptor pathway, we fitted each data set using a Bayesian hierarchical implementation of a simple binocular combination model [@Meese2006; @Segala2023]. Our primary objective was to compare posterior distributions of the weight of interocular suppression, which are shown in Figure \@ref(fig:FirstHarmonicPlots)e. Consistent with our earlier observations, the strongest suppressive weight corresponds to the L-M and Melanopsin conditions, and the weakest suppression corresponds to the light flux and S-cone conditions, with virtually no overlap between the posterior distributions for weak and strong suppression. The model captured the main features of the contrast response functions (Figure \@ref(fig:FirstHarmonicPlots)a-d,f-i), fitting the light flux data most closely and it fitted the noisier S-cone data least well; it did not capture every data point, and some scatter remains in the weaker responses. Fit statistics and convergence diagnostics for all eight fits are given in TableTable \@ref(tab:diagnosticstable), and a posterior predictive check (Figure \@ref(fig:FS3)) plots the model's 95\% predictive intervals against the data for every condition: most points fall within these intervals, with the widest intervals and the largest deviations in the noisier, low-amplitude conditions. The fitted model parameters are given in Table \@ref(tab:paramtable).
- ```{r paramtable, echo=FALSE}
- condnames <- c('Light flux 1F','L-M cone 1F','S cone 1F','Melanopsin 1F','Light flux 2F','L-M cone 2F','S cone 2F','Melanopsin 2F')
- zlist <- c(pupilZLum, pupilZLM, pupilZS, pupilZMel, pupilZLum2, pupilZLM2, pupilZS2, pupilZMel2)
- klist <- c(pupilKLum, pupilKLM, pupilKS, pupilKMel, pupilKLum2, pupilKLM2, pupilKS2, pupilKMel2)
- wlist <- c(pupilWLum, pupilWLM, pupilWS, pupilWMel, pupilWLum2, pupilWLM2, pupilWS2, pupilWMel2)
- plist <- c(pupilPLum, pupilPLM, pupilPS, pupilPMel, pupilPLum2, pupilPLM2, pupilPS2, pupilPMel2)
- qlist <- c(pupilQLum, pupilQLM, pupilQS, pupilQMel, pupilQLum2, pupilQLM2, pupilQS2, pupilQMel2)
- Rlist <- c(pupilRmaxLum, pupilRmaxLM, pupilRmaxS, pupilRmaxMel, pupilRmaxLum2, pupilRmaxLM2, pupilRmaxS2, pupilRmaxMel2)
- tabledata <- data.frame(condnames,round(zlist,digits=2),round(klist,digits=5),round(wlist,digits=2),round(plist,digits=2),round(qlist,digits=2),round(Rlist,digits=4))
- colnames(tabledata) <- c('Experiment','Z','k','w','p','q','Rmax')
- kable(tabledata, align='lcccccc',booktabs=TRUE,linesep='',caption = 'Summary of maximum a posteriori (MAP) parameter estimates for each data set.') %>%
- kableExtra::kable_styling(latex_options = "HOLD_position") %>% row_spec(4,hline_after=TRUE)
- ```
- At the second harmonic frequencies, the levels of suppression were more uniform across different photoreceptor pathways (see Figure \@ref(fig:SecondHarmonicPlots)). In general, suppression estimates were near or above 1 (see lower rows of Table \@ref(tab:paramtable)), with substantial overlap between the posterior distributions (Figure \@ref(fig:SecondHarmonicPlots)e). The contrast response functions also looked more uniform, and generally involved less binocular facilitation and more interocular suppression than were seen at the first harmonics. The L-M condition now featured the weakest suppressive weight, and the strongest binocular facilitation, which was the opposite pattern seen at the first harmonic.
- Because response energy can shift between the first and second harmonics depending on the exact shape of the transducer, either harmonic on its own could misrepresent the total periodic response. As a check, we combined the two harmonics into a single measure of periodic response magnitude: the root-sum-square of the first- and second-harmonic amplitudes (Figure \@ref(fig:FS2)). The combined measure preserved the main qualitative pattern. Binocular stimulation exceeded monocular stimulation for the light flux and S-cone conditions (binocular facilitation), whereas the L-M and melanopsin conditions showed little facilitation and sat close to ocularity invariance. Combining the harmonics moderated the most extreme ratios, in particular the large S-cone facilitation and the L-M binocular suppression seen at the first harmonic alone, but did not fundamentally alter our conclusions.
- ```{r SecondHarmonicPlots, fig.cap="Responses at the second harmonic frequencies (1Hz and 0.8Hz), in the same format as Figure 2.", fig.align="center", out.width="40%", echo=FALSE}
- knitr::include_graphics(paste0(figdir,'SecondHarmonic.pdf'))
- ```
- Finally, we inspected the response phase of our four stimulation conditions, given previous reports that these differ across pathways [@Spitschan2014], and may be in antiphase for melanopsin and S-cone signals. Figure \@ref(fig:phaseplots) shows the phase angles for the first (a) and second (b) harmonic frequencies for binocular stimulation (monocular stimulation produced very similar results). At the first harmonic, melanopsin and S-cone signals differed in phase by more than 90 degrees, though they were not fully in antiphase. The light flux and L-M responses were approximately in antiphase to each other, however this is likely due to our choice to modulate L+M- in the first half-cycle of the sine wave (corresponding to a luminance increase in the light flux condition), and L-M+ in the second half-cycle (corresponding to a luminance decrease in the light flux condition). Had we reversed this phase arrangement, we would likely have seen a close phase correspondence between the L-M and light flux conditions (another way to think about this is that L-cone decreases and M-cone increases are processed like luminance increases). Phase differences between the light flux, S-cone and melanopsin conditions are likely to reflect, at least in part, different lags in phototransduction at the earliest stage (i.e. in the retina), although downstream temporal filtering within the pupillary pathway could also contribute. At the second harmonic frequency (Figure \@ref(fig:phaseplots)b), the light flux condition was again out of phase with the other three, and was approximately in quadrature phase with the L-M condition, and in antiphase with both the S-cone and melanopsin conditions (which were in phase with each other). We note that the marked phase differences between conditions make the possibility that our results are dominated by rod activity relatively unlikely (e.g. in the L-M and melanopsin conditions, which have very different phase profiles).
- ```{r phaseplots, fig.cap="Pupil phase plots at the first and second harmonic frequencies for the light flux, melanopsin, L-M pathway and the S-cone pathway conditions. Panel (a) shows the pupil response at the first harmonic frequency during binocular stimulation. Panel (b) shows the pupil response at the second harmonic frequency during binocular stimulation. The five data points for each pathway correspond to the five contrast levels displayed. In panel (a) the light flux amplitudes have been scaled down by a factor of 10 to enable comparison with the other conditions.", fig.align="center", echo=FALSE}
- knitr::include_graphics(paste0(figdir,'phaseplots.pdf'))
- ```
- # Discussion
- We used binocular pupillometry and silent substitution to measure monocular and binocular responses of the pupils to flickering stimuli when stimulating specific photoreceptor pathways. In all four experiments, we were able to record contrast response functions at both the first and the second harmonic frequencies. All experiments showed that binocular combination in the autonomic nervous system happens in a non-linear manner, with evidence of different magnitudes of interocular suppression depending on the photoreceptor pathway. This pattern of results was confirmed by a computational model, which allowed us to compare the weight of interocular suppression for each pathway. We found that at the first harmonic frequency the L-M and melanopsin pathways involved strong suppression, whereas the light flux and S-cone pathways involved weaker suppression. Suppression was strong in all four pathways at the second harmonic frequency. Finally, the phase of the pupil response revealed different lag times for the different pathways at the first and second harmonic frequencies.
- This is the first study to investigate binocular interactions in the melanopsin pathway directly. A previous analysis [@Spitschan2019] indicated that there may be a substantial binocular facilitation effect in the circadian pathway, as indexed by melatonin suppression (melatonin is a hormone released by the pineal gland; its production is suppressed by exposure to bright light, particularly when the melanopsin-containing ipRGCs are stimulated). In brief, monocular stimulation of the ipRGCs requires up to ten times the signal strength to produce an equivalent effect to binocular stimulation. This superadditive effect implies an absence of interocular suppression, and perhaps the presence of either a highly compressive nonlinearity, or an AND-style neural operation. Our findings here are different - we do not see substantial binocular facilitation effects in response to melanopsin modulation, and our data indicate that interocular suppression is strong. By measuring the full contrast response function, we can also rule out compressive nonlinearities, because the functions accelerate at both the first and second harmonic frequencies. The explanation for this difference could be due to different anatomical pathways. Binocular combination in the circadian system likely takes place in the suprachiasmatic nucleus, whereas in the pupil constriction circuit the Edinger-Westphal nucleus is the most likely site of binocular integration [@Mathot2018]. Presumably these anatomical differences, and the practical constraints of the two systems, lead to differences in response.
- Our recent psychophysical work [@Baker2024] has looked at binocular interactions in the L-M and S-cone pathways, and compared these to the light flux pathway. Using a contrast discrimination paradigm with spatial modulations of light flux and colour, we found equally strong interocular suppression in all three pathways [@Baker2024]. This is rather different from our pupillometry results here, which show weaker suppression in the light flux and S-cone pathways than in the L-M pathway (see Figure \@ref(fig:FirstHarmonicPlots)). But the current experiments involve temporal modulations, which are quite distinct from the spatial modulations used in our previous work [@Baker2024]. Our other recent work [@Segala2023] has shown that temporal luminance modulations involve much weaker interocular suppression in the cortical response than do spatial luminance modulations [@Baker2017]. These different normalisation processes might reflect different priorities for spatial and temporal vision. Spatial vision aims to fuse images to provide binocular single vision, and benefits from 'ocularity invariance' [@Baker2007], in which visual appearance is constant when viewed with one eye or two. Temporal vision is critical for motion perception, which can involve alternative binocular computations, such as calculating velocity differences between the eyes [@Kaestner2019]. Of course the present measurements were of pupil size, which may be subject to different anatomical and functional constraints from those in the cortex. Future research should aim to extend our current findings to perception using psychophysical approaches.
- ## Physiological interpretation of the pathway and harmonic differences
- Our four experiments modulate different photoreceptor classes, and the differences between them are open to physiological interpretation. We are cautious about attributing responses to discrete physiological mechamisms because silent substitution isolates photoreceptor contrast rather than an anatomically separate neural pathway: the targeted photoreceptor signals converge with others downstream, and we did not explicitly silence the rods although their contribution at the mean luminance levels used is expected to be small.
- The light flux stimulus modulates several photoreceptor classes together and produced by far the largest pupil response. Its weak first-harmonic suppression is consistent with strong binocular facilitation, but because several receptors are co-modulated this condition cannot tell us which retinal input dominates. The isoluminant L-M stimulus targets a cone-opponent signal rather than luminance, and showed the strongest first-harmonic suppression, with a binocular response weaker than the monocular one. This may indicate stronger binocular normalisation for cone-opponent input, though our data cannot localise whether this interaction arises in retinal, pretectal or other subcortical circuitry.
- The S-cone stimulus produced a relatively large second harmonic. This could reflect nonlinearity or asymmetry in how S-cone increments and decrements are converted into a pupil response, rather than a separate oscillatory mechanism. For example, it is well known that there are asymmetries in the number of S-cone on- and off- bipolar cells in the retina [@Klug2003, @LeeGrunert2007]. The melanopsin-directed responses were delayed in phase relative to the cone-directed responses, which is broadly consistent with the slower phototransduction of the ipRGCs. But the ipRGCs also receive cone and rod input, and our stimulus isolates melanopsin contrast rather than an anatomically independent pathway, so the delay need not arise from melanopsin phototransduction alone.
- The meaning of the differences we observe between the 0.4 and 0.5 Hz responses is unclear. The pupil is a temporally filtered and nonlinear system, so a modest change in frequency can alter response phase, waveform asymmetry and harmonic content. Some of the difference may also reflect noisier estimates or imperfect model fit. We therefore do not attribute the 0.4 versus 0.5 Hz differences to distinct physiological mechanisms. A limitation that applies across all of these comparisons is that the four experiments used separate groups of 24 participants, so differences between the photoreceptor-directed conditions are partly between-group differences.
- # Conclusions
- We have shown that binocular combination of temporal flickering light in the autonomic nervous system depends on which photoreceptors are stimulated. We were able to elicit pupil responses by stimulating the periphery of the retina, and to record contrast response functions for all four photoreceptor-directed conditions. While all conditions showed non-linear combination, they varied in how the signals are combined, particularly in the weight of interocular suppression. This was strong ($\omega \ge 1$) for L-M and melanopsin signals at the first harmonic, and all pathways at the second harmonic. Suppression was weaker ($\omega < 1$) in the light flux pathway (consistent with previous work), and also for S-cone directed modulations.
- # References
- <div id='refs'> </div>
- \pagebreak
- # Supplementary material
- \beginsupplement
- ```{r primarylinearity, fig.cap="Linearity of primaries for each device. The calibration measures were summed (i.e., total unweighted irradiance), and the input-output relationship summarised by a 7th order polynomial reverse curve fit. By applying the coefficients of the regression, it is possible to achieve a linear output in the primaries.", fig.align="center", echo=FALSE}
- knitr::include_graphics(paste0(figdir,'gamma_figure.pdf'))
- ```
- ```{r FS2, fig.cap="Combined first- and second-harmonic pupil responses. For each photoreceptor-directed condition, the root-sum-square amplitude of the first (0.5~Hz) and second (1~Hz) harmonic is plotted against target contrast, for monocular, binocular and dichoptic stimulation. Points show the coherently averaged group amplitude and error bars show bootstrapped 95% confidence intervals (10000 iterations). Combining the harmonics preserves the binocular facilitation seen for light flux and S-cone stimulation, and the near-invariance seen for L-M and melanopsin stimulation.", fig.align="center", echo=FALSE}
- knitr::include_graphics(paste0(figdir,'FigureS2.pdf'))
- ```
- ```{r FS3, fig.cap="Posterior predictive check for the eight model fits. Each panel plots the observed group-mean amplitudes (points, with bootstrapped 95% confidence intervals) for the monocular, binocular and dichoptic-cross conditions against target contrast, at the first harmonic (top row) and second harmonic (bottom row) for each photoreceptor-directed condition. Solid lines show the posterior predictive median and shaded regions the 95% posterior predictive interval, computed from the group-level parameters. Most data points fall within the predictive intervals; the intervals are widest, and deviations largest, for the noisier low-amplitude conditions.", fig.align="center", echo=FALSE}
- knitr::include_graphics(paste0(figdir,'FigureS3.pdf'))
- ```
manuscript.Rmd, no license · at the source
Overview
- Department of Psychology, University of York, York, United Kingdom
- School of Psychology and Vision Sciences, University of Leicester, Leicester, United Kingdom
- School of Philosophy, Psychology and Language Sciences, University of Edinburgh, Edinburgh, United Kingdom
- York Biomedical Research Institute, University of York, York, United Kingdom
Abstract
Pupil diameters are regulated by the autonomic nervous system, which combines light signals across the eyes independently of the visual cortex. Distinct classes of retinal photoreceptor are involved in this process, with cones and rods driving the initial constriction and intrinsically photosensitive retinal ganglion cells maintaining diameter over prolonged time periods. We investigated binocular combination by targeting different photoreceptor pathways using a novel binocular multiprimary system to modulate the input spectra via silent substitution. At the first harmonic of the modulation frequency, luminance and S-cone responses showed strong binocular facilitation, and weak interocular suppression. Melanopsin responses were invariant to the number of eyes stimulated. The L-M pathway involved binocular inhibition, whereby responses to binocular stimulation were weaker than for monocular stimulation. The second harmonic involved strong interocular suppression in all pathways, but with some evidence of binocular facilitation. Our results are consistent with a computational model of binocular signal combination (implemented in a Bayesian hierarchical framework), in which the weight of interocular suppression differs across pathways. We also find pathway differences in response phase, consistent with different lag times for phototransduction. These results provide evidence that binocular interactions in the pupillary pathway differ across photoreceptor-directed modulations and across harmonic components of the response.
Reproduced under the paper's license (CC BY), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 16 matches between paragraphs and lines of code.
OSF gdvt4
Availability: 1 check, the latest on 26 September 2026: the link answers (HTTP 200)
- 26 September 2026: the link answers (HTTP 200)
12 files
- ExperimentCode/
analyisis.py , Python, 66 lines - ExperimentCode/
calibration/ , Python, 116 linescalibration.py - ExperimentCode/
change_data_source.py , Python, 26 lines - ExperimentCode/
make_conditions.py , Python, 30 lines - ExperimentCode/
make_luminance_stims.py , Python, 263 lines - ExperimentCode/
make_stimuli.py , Python, 307 lines - ExperimentCode/
make_stimuli_luminance_2 , Python, 208 lines.py - ExperimentCode/
monbin.py , Python, 391 lines - ExperimentCode/
psychonulltest_mouse.py , Python, 263 lines - manuscript.Rmd, R, 2,467 lines, 16 matches
- LICENSE, License, 674 lines
- README.md, Text, 15 lines
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 10 scripts, each with its path and the digest of its content;
- 16 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data Availability
All relevant data is available on OSF at https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 3, 28 September 2026
- Authors: added Joel T Martin (0000-0002-4475-3835); removed Joel T Martin
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 6 authors, 7 MeSH terms, 1 funder, 51 references.
Cite
This paper
Segala, F. G., Bruno, A., Martin, J. T., Morsi, A. Y., Wade, A. R., & Baker, D. H. (2026). Binocular combination in the autonomic nervous system. PloS one, 21(9), e0356765. https://
BibTeX
@article{segala2026binoc
author = {Segala, Federico G and Bruno, Aurelio and Martin, Joel T and Morsi, Anisa Y and Wade, Alex R and Baker, Daniel H},
title = {{Binocular combination in the autonomic nervous system}},
journal = {PloS one},
year = {2026},
month = sep,
volume = {21},
number = {9},
pages = {e0356765},
publisher = {PLOS},
issn = {1932-6203},
doi = {10.1371/
url = {https://
pmid = {42679018},
pmcid = {PMC13533428}
}
RIS
TY - JOUR
AU - Segala, Federico G
AU - Bruno, Aurelio
AU - Martin, Joel T
AU - Morsi, Anisa Y
AU - Wade, Alex R
AU - Baker, Daniel H
TI - Binocular combination in the autonomic nervous system
T2 - PloS one
J2 - PLoS One
PY - 2026
DA - 2026/
VL - 21
IS - 9
SP - e0356765
SN - 1932-6203
PB - PLOS
DO - 10.1371/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1371/
"type": "article-journal",
"title": "Binocular combination in the autonomic nervous system",
"container-title": "PloS one",
"author": [
{
"family": "Segala",
"given": "Federico G"
},
{
"family": "Bruno",
"given": "Aurelio"
},
{
"family": "Martin",
"given": "Joel T"
},
{
"family": "Morsi",
"given": "Anisa Y"
},
{
"family": "Wade",
"given": "Alex R"
},
{
"family": "Baker",
"given": "Daniel H"
}
],
"container-title-short":
"volume": "21",
"issue": "9",
"page": "e0356765",
"DOI": "10.1371/
"PMID": "42679018",
"PMCID": "PMC13533428",
"ISSN": "1932-6203",
"publisher": "PLOS",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
9,
1
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1371/journal.pone.0350140 [code]
- Signal combination in flutter vibration perception.Journal: PloS oneIn common: pandas, SciPy, Matplotlib, 1 other tool, 8 references, 2 authors
- [2] doi:10.2147/opth.s590186 [code]
- Differential Effects of Balanced and Imbalanced Binocular Stimulation on Visual Cortex Responses in Amblyopic Children.Journal: Clinical ophthalmology (Auckland, N.Z.)In common: pandas, SciPy, Matplotlib, 1 other tool, 2 references
- [3] doi:10.1073/pnas.2538139123
- Cognition does not automatically influence perception: Evidence from neural encoding of colors belonging to different categories.Journal: Proceedings of the National Academy of Sciences of the United States of AmericaIn common: author Joel T Martin
- [4] doi:10.3390/bioengineering13070820 [code]
- Benchmarking Multimodal Workload Classification: Effects of Modality, Validation Protocol, and Segmentation Contrast on an Open Graded-Arithmetic Dataset.Journal: Bioengineering (Basel, Switzerland)In common: pandas, SciPy, Matplotlib, 1 other tool, 1 reference
- [5] doi:10.7554/elife.108223 [code]
- Two time scales of adaptation in human learning rates.Journal: eLifeIn common: pandas, SciPy, Matplotlib, 1 other tool, 1 reference
- [6] doi:10.3758/s13428-026-03122-w [code]
- Modeling intraindividual variability in affect (MIVA): Formalized theoretical approach, computational model, and parameter recovery study.Journal: Behavior research methodsIn common: pandas, SciPy, Matplotlib, 1 other tool, 1 reference
- [7] doi:10.1093/braincomms/fcag218 [code]
- Magnetoencephalography biomarkers for assessing myelin content and neuronal function in acute optic neuritis.Journal: Brain communicationsIn common: pandas, SciPy, Matplotlib, 1 other tool, 1 reference
- [8] doi:10.1126/sciadv.aea3919 [code]
- Hierarchical brain dynamics supporting visual perceptual transitions.Journal: Science advancesIn common: pandas, SciPy, Matplotlib, 1 other tool, 1 reference
- [9] doi:10.7554/elife.105953 [code]
- Top-down feedback in deep neural networks leads to functional differences during audiovisual integration.Journal: eLifeIn common: pandas, SciPy, Matplotlib, 1 other tool, 1 reference
- [10] doi:10.1038/s41467-026-76098-y [code]
- A single computational objective can produce specialization of streams in visual cortex.Journal: Nature communicationsIn common: pandas, SciPy, Matplotlib, 1 other tool, 1 reference
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 10 scripts, and 16 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:414eb7ccbf82b913…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[.
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
