This R Notebook steps through some important aspects of pupillometry
data visualization, in the context of condition-averaged responses.
Developed by Lauren Fink with much external inspiration!
Explore relevant links:
https://www.research.autodesk.com/publications/same-stats-different-graphs/
http://www.thefunctionalart.com/2016/08/download-datasaurus-never-trust-summary.html
http://robertgrantstats.co.uk/drawmydata.html https://cran.r-project.org/web/packages/datasauRus/vignettes/Datasaurus.html
Install required packages, if not already installed
The code below checks if each package we need in the list.of.packages
variable exists in the user’s installed packages. If the user is missing
any packages, we install them.
# define list of necessary packages
list.of.packages <- c("dplyr", "here", "ggplot2")
# define list of packages that will need to be installed
new.packages <- list.of.packages[!(list.of.packages %in% installed.packages()[,"Package"])]
# insall required packages, if any
if(length(new.packages)) install.packages(new.packages)
Load data
We use the here package so that all file paths are relative.
As long as the data .csv file lives in the same directory as the current
script you are reading, it will be found. Users should be sure not to
change the directory structure of the repository.
By default, figures will be plotted in line. If the user wants to
save the figures to a folder on their local machine, set
op = 1
. Then, all figures will save into the file path
defined as fig_path
.
# Use the here package to locate this script on the users' machine
here::i_am("KonstanzPupilWorkshop.Rmd")
here() starts at /Users/laurenfink/Desktop
library(here) # establish all filepaths relative to this script
# Read in our data
df <- read.csv(here("KonstanzWorkshop_pupilData_byCondition.csv"))
# df$Y = df$Y + 50
# df2 <- read.csv(here("dataVisualisation_dataset.csv"))
# df2 <- df2[!grepl("condition_3", df2$condition), ]
# df2$condition[df2$condition == "condition_1"] <- "condition_3"
# df2$y[df2$condition == "condition_4"] <- df2$y[df2$condition == "condition_4"] - 30
# colnames(df2) <- c("condition", "time", "pupil")
# df$condition <- "condition_1"
# colnames(df)[colnames(df) == "X"] <- "time"
# colnames(df)[colnames(df) == "Y"] <- "pupil"
# new_df <- rbind(df, df2)
# df <- new_df
write.csv(df, file = "KonstanzWorkshop_pupilData_byCondition.csv", row.names = FALSE)
# Define file I/O for figures
# Create an output folder for figures, if it does not already exist in this directory
fig_path <- here("figures/") # path to save generated figures to
ifelse(!dir.exists(fig_path), dir.create(fig_path), FALSE) # return FALSE if the directory already exists or can't be created. TRUE if it has been successfully created.
[1] FALSE
# Define whether to output the plot inline (op=0) or save to file (op=1).
# Inline by default
op = 0
Print summary stats, by condition, to a table
Below we output min, max, mean, std. Feel free to add others!
if(requireNamespace("dplyr")){ # make sure our required package is loaded
suppressPackageStartupMessages(library(dplyr))}
Loading required namespace: dplyr
# specify stats to print
condstats <- df %>%
group_by(condition) %>% # print for each condition separately
summarize(
min_pupil = min(pupil), # define stats we care about
max_pupil = max(pupil),
mean_pupil = mean(pupil),
std_dev_pupil = sd(pupil),
)
condstats # print inline
Plot mean and std of pupil size by condition
Define plotting constants
NOTE: I am randomly choosing 4 color-blind-friendly colors and 4
different marker shapes. That means that those two features will change
every time that the plot is generated. To make the color and marker
choices constant, I provide an example in the commented code below.
# define axis limits
# Best to set them dynamically in case we might want to use this same code for a different dataset, which might have different limits. Let's find our min and max and round them to the nearest whole number.
axisMin =floor(min(df$time, df$pupil)) # min across both our columns of interest
axisMax = ceiling(max(df$time, df$pupil)) # min across both our columns of interest
# choose a unique, color-blind-friendly color for each condition
nconds = length(unique(df$condition)) # determin number of conditions
colorBlindFriendlyPallette = c("#999999", "#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7") # define color-blind-friendly palette
ourColors = sample(colorBlindFriendlyPallette, nconds, replace=FALSE) # randomly choose the number of colors we need, without replacement
# The reason for choosing colors dynamically (using nconds) is so that if the number of conditions in our dataset changes, our code will not break.
# NOTE. You could set colors manually by doing something like this:
# cond1 = "#009E73"
# cond2 = "#0072B2"
# cond3 = "#D55E00"
# cond4 = "#CC79A7"
# ourColors = c(cond1, cond2, cond3, cond4)
# We can do the same thing for marker shapes
# Marker shapes are defined by number. See here for the full list of options: https://r-graphics.org/recipe-scatter-shapes
ourMarkers = sample(1:20, nconds, replace=FALSE)
# Pre-set option
# ourMarkers = c(17, 1, 5, 8)
Create bar plot
Errors bars represent std dev
if(requireNamespace("ggplot2")){ # make sure our required package is loaded
suppressPackageStartupMessages(library(ggplot2))}
Loading required namespace: ggplot2
if (op) {png(paste(fig_path, "pupilBarPlot.png", sep=""), units="in", width=11, height=11, res=300)}
pupilbarplot <- ggplot(condstats, aes(x = condition, fill = condition, y = mean_pupil)) +
geom_col(position = "dodge") +
scale_fill_manual(values=ourColors) +
geom_errorbar(aes(ymin = mean_pupil - std_dev_pupil, ymax = mean_pupil + std_dev_pupil), position = position_dodge(0.9), width = .2) + labs(fill = "Condition")
pupilbarplot + xlab("Condition") + ylab("Avg. Pupil Size (au)")
Take-aways so far?
Plenty of pupil papers would only show the results up until this
point. But. There is often useful information in the pupil trajectory.
Let’s plot the pupil trace over time.
Plot pupil trace over time, by condition
Create scatterplot
# Initialize output file if we want to save the generated figure to file
if (op) {png(paste(fig_path, "pupilOverTime.png", sep=""), units="in", width=11, height=11, res=300)}
# create plots
if(requireNamespace("ggplot2")){ # make sure required package is loaded
library(ggplot2)}
ggplot(df, aes(x = time, y = pupil, colour=condition, shape=condition))+
geom_point(size=4) + # create scatter plot
scale_colour_manual(values=ourColors) +
scale_shape_manual(values=ourMarkers) +
theme_classic() +
theme(aspect.ratio=1, axis.text = element_text(size = 12), axis.title = element_text(size = 12), strip.text = element_text(size = 12), legend.position = "none") +
xlim(axisMin, axisMax) +
ylim(axisMin, axisMax) +
facet_wrap(~condition, ncol = 2) # create one scatter plot per condition
Main take-away
Hopefully, stepping through the exercises above has made you realize
the importance of data visualization – you might not even be looking at
real pupil data!!
LS0tCnRpdGxlOiAiS29uc3RhbnogUHVwaWxsb21ldHJ5IFdvcmtzaG9wIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKYXV0aG9yOiBMYXVyZW4gRmluawpjb250YWN0OiBmaW5rbDFAbWNtYXN0ZXIuY2EKLS0tClRoaXMgUiBOb3RlYm9vayBzdGVwcyB0aHJvdWdoIHNvbWUgaW1wb3J0YW50IGFzcGVjdHMgb2YgcHVwaWxsb21ldHJ5IGRhdGEgdmlzdWFsaXphdGlvbiwgaW4gdGhlIGNvbnRleHQgb2YgY29uZGl0aW9uLWF2ZXJhZ2VkIHJlc3BvbnNlcy4gCgpEZXZlbG9wZWQgYnkgTGF1cmVuIEZpbmsgd2l0aCBtdWNoIGV4dGVybmFsIGluc3BpcmF0aW9uISAKCkV4cGxvcmUgcmVsZXZhbnQgbGlua3M6IAoKPiBodHRwczovL3d3dy5yZXNlYXJjaC5hdXRvZGVzay5jb20vcHVibGljYXRpb25zL3NhbWUtc3RhdHMtZGlmZmVyZW50LWdyYXBocy8KPiBodHRwOi8vd3d3LnRoZWZ1bmN0aW9uYWxhcnQuY29tLzIwMTYvMDgvZG93bmxvYWQtZGF0YXNhdXJ1cy1uZXZlci10cnVzdC1zdW1tYXJ5Lmh0bWwgCj4gaHR0cDovL3JvYmVydGdyYW50c3RhdHMuY28udWsvZHJhd215ZGF0YS5odG1sIAo+IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9kYXRhc2F1UnVzL3ZpZ25ldHRlcy9EYXRhc2F1cnVzLmh0bWwgCgoKIyBJbnN0YWxsIHJlcXVpcmVkIHBhY2thZ2VzLCBpZiBub3QgYWxyZWFkeSBpbnN0YWxsZWQKVGhlIGNvZGUgYmVsb3cgY2hlY2tzIGlmIGVhY2ggcGFja2FnZSB3ZSBuZWVkIGluIHRoZSBsaXN0Lm9mLnBhY2thZ2VzIHZhcmlhYmxlIGV4aXN0cyBpbiB0aGUgdXNlcidzIGluc3RhbGxlZCBwYWNrYWdlcy4gSWYgdGhlIHVzZXIgaXMgbWlzc2luZyBhbnkgcGFja2FnZXMsIHdlIGluc3RhbGwgdGhlbS4gIApgYGB7cn0KIyBkZWZpbmUgbGlzdCBvZiBuZWNlc3NhcnkgcGFja2FnZXMKbGlzdC5vZi5wYWNrYWdlcyA8LSBjKCJkcGx5ciIsICJoZXJlIiwgImdncGxvdDIiKSAKCiMgZGVmaW5lIGxpc3Qgb2YgcGFja2FnZXMgdGhhdCB3aWxsIG5lZWQgdG8gYmUgaW5zdGFsbGVkCm5ldy5wYWNrYWdlcyA8LSBsaXN0Lm9mLnBhY2thZ2VzWyEobGlzdC5vZi5wYWNrYWdlcyAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpWywiUGFja2FnZSJdKV0KCiMgaW5zYWxsIHJlcXVpcmVkIHBhY2thZ2VzLCBpZiBhbnkKaWYobGVuZ3RoKG5ldy5wYWNrYWdlcykpIGluc3RhbGwucGFja2FnZXMobmV3LnBhY2thZ2VzKSAKYGBgCgojIExvYWQgZGF0YQpXZSB1c2UgdGhlICpoZXJlKiBwYWNrYWdlIHNvIHRoYXQgYWxsIGZpbGUgcGF0aHMgYXJlIHJlbGF0aXZlLiBBcyBsb25nIGFzIHRoZSBkYXRhIC5jc3YgZmlsZSBsaXZlcyBpbiB0aGUgc2FtZSBkaXJlY3RvcnkgYXMgdGhlIGN1cnJlbnQgc2NyaXB0IHlvdSBhcmUgcmVhZGluZywgaXQgd2lsbCBiZSBmb3VuZC4gVXNlcnMgc2hvdWxkIGJlIHN1cmUgbm90IHRvIGNoYW5nZSB0aGUgZGlyZWN0b3J5IHN0cnVjdHVyZSBvZiB0aGUgcmVwb3NpdG9yeS4gCgpCeSBkZWZhdWx0LCBmaWd1cmVzIHdpbGwgYmUgcGxvdHRlZCBpbiBsaW5lLiBJZiB0aGUgdXNlciB3YW50cyB0byBzYXZlIHRoZSBmaWd1cmVzIHRvIGEgZm9sZGVyIG9uIHRoZWlyIGxvY2FsIG1hY2hpbmUsIHNldCBgb3AgPSAxYC4gVGhlbiwgYWxsIGZpZ3VyZXMgd2lsbCBzYXZlIGludG8gdGhlIGZpbGUgcGF0aCBkZWZpbmVkIGFzIGBmaWdfcGF0aGAuCmBgYHtyfQojIFVzZSB0aGUgaGVyZSBwYWNrYWdlIHRvIGxvY2F0ZSB0aGlzIHNjcmlwdCBvbiB0aGUgdXNlcnMnIG1hY2hpbmUKaGVyZTo6aV9hbSgiS29uc3RhbnpQdXBpbFdvcmtzaG9wLlJtZCIpCmxpYnJhcnkoaGVyZSkgIyBlc3RhYmxpc2ggYWxsIGZpbGVwYXRocyByZWxhdGl2ZSB0byB0aGlzIHNjcmlwdAoKIyBSZWFkIGluIG91ciBkYXRhCmRmIDwtIHJlYWQuY3N2KGhlcmUoIktvbnN0YW56V29ya3Nob3BfcHVwaWxEYXRhX2J5Q29uZGl0aW9uLmNzdiIpKSAKIyBkZiRZID0gZGYkWSArIDUwCiMgZGYyIDwtIHJlYWQuY3N2KGhlcmUoImRhdGFWaXN1YWxpc2F0aW9uX2RhdGFzZXQuY3N2IikpCiMgZGYyIDwtIGRmMlshZ3JlcGwoImNvbmRpdGlvbl8zIiwgZGYyJGNvbmRpdGlvbiksIF0KIyBkZjIkY29uZGl0aW9uW2RmMiRjb25kaXRpb24gPT0gImNvbmRpdGlvbl8xIl0gPC0gImNvbmRpdGlvbl8zIgojIGRmMiR5W2RmMiRjb25kaXRpb24gPT0gImNvbmRpdGlvbl80Il0gPC0gZGYyJHlbZGYyJGNvbmRpdGlvbiA9PSAiY29uZGl0aW9uXzQiXSAtIDMwCiMgY29sbmFtZXMoZGYyKSA8LSBjKCJjb25kaXRpb24iLCAidGltZSIsICJwdXBpbCIpCiMgZGYkY29uZGl0aW9uIDwtICJjb25kaXRpb25fMSIKIyBjb2xuYW1lcyhkZilbY29sbmFtZXMoZGYpID09ICJYIl0gPC0gInRpbWUiCiMgY29sbmFtZXMoZGYpW2NvbG5hbWVzKGRmKSA9PSAiWSJdIDwtICJwdXBpbCIKIyBuZXdfZGYgPC0gcmJpbmQoZGYsIGRmMikKIyBkZiA8LSBuZXdfZGYKd3JpdGUuY3N2KGRmLCBmaWxlID0gIktvbnN0YW56V29ya3Nob3BfcHVwaWxEYXRhX2J5Q29uZGl0aW9uLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQoKCiMgRGVmaW5lIGZpbGUgSS9PIGZvciBmaWd1cmVzCiMgQ3JlYXRlIGFuIG91dHB1dCBmb2xkZXIgZm9yIGZpZ3VyZXMsIGlmIGl0IGRvZXMgbm90IGFscmVhZHkgZXhpc3QgaW4gdGhpcyBkaXJlY3RvcnkKZmlnX3BhdGggPC0gaGVyZSgiZmlndXJlcy8iKSAjIHBhdGggdG8gc2F2ZSBnZW5lcmF0ZWQgZmlndXJlcyB0bwppZmVsc2UoIWRpci5leGlzdHMoZmlnX3BhdGgpLCBkaXIuY3JlYXRlKGZpZ19wYXRoKSwgRkFMU0UpICMgcmV0dXJuIEZBTFNFIGlmIHRoZSBkaXJlY3RvcnkgYWxyZWFkeSBleGlzdHMgb3IgY2FuJ3QgYmUgY3JlYXRlZC4gVFJVRSBpZiBpdCBoYXMgYmVlbiBzdWNjZXNzZnVsbHkgY3JlYXRlZC4KCiMgRGVmaW5lIHdoZXRoZXIgdG8gb3V0cHV0IHRoZSBwbG90IGlubGluZSAob3A9MCkgb3Igc2F2ZSB0byBmaWxlIChvcD0xKS4gCiMgSW5saW5lIGJ5IGRlZmF1bHQKb3AgPSAwIApgYGAKCgojIFByaW50IHN1bW1hcnkgc3RhdHMsIGJ5IGNvbmRpdGlvbiwgdG8gYSB0YWJsZQpCZWxvdyB3ZSBvdXRwdXQgbWluLCBtYXgsIG1lYW4sIHN0ZC4gRmVlbCBmcmVlIHRvIGFkZCBvdGhlcnMhCmBgYHtyfQppZihyZXF1aXJlTmFtZXNwYWNlKCJkcGx5ciIpKXsgIyBtYWtlIHN1cmUgb3VyIHJlcXVpcmVkIHBhY2thZ2UgaXMgbG9hZGVkCiAgc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkoZHBseXIpKX0KICAKICAjIHNwZWNpZnkgc3RhdHMgdG8gcHJpbnQKY29uZHN0YXRzIDwtICBkZiAlPiUgCiAgICBncm91cF9ieShjb25kaXRpb24pICU+JSAgIyBwcmludCBmb3IgZWFjaCBjb25kaXRpb24gc2VwYXJhdGVseQogICAgc3VtbWFyaXplKAogICAgICBtaW5fcHVwaWwgICAgID0gbWluKHB1cGlsKSwgIyBkZWZpbmUgc3RhdHMgd2UgY2FyZSBhYm91dAogICAgICBtYXhfcHVwaWwgICAgPSBtYXgocHVwaWwpLAogICAgICBtZWFuX3B1cGlsICAgID0gbWVhbihwdXBpbCksCiAgICAgIHN0ZF9kZXZfcHVwaWwgPSBzZChwdXBpbCksCiAgICApCgpjb25kc3RhdHMgIyBwcmludCBpbmxpbmUKYGBgCgoKIyBQbG90IG1lYW4gYW5kIHN0ZCBvZiBwdXBpbCBzaXplIGJ5IGNvbmRpdGlvbgoKIyMjIyBEZWZpbmUgcGxvdHRpbmcgY29uc3RhbnRzCk5PVEU6IEkgYW0gcmFuZG9tbHkgY2hvb3NpbmcgNCBjb2xvci1ibGluZC1mcmllbmRseSBjb2xvcnMgYW5kIDQgZGlmZmVyZW50IG1hcmtlciBzaGFwZXMuIFRoYXQgbWVhbnMgdGhhdCB0aG9zZSB0d28gZmVhdHVyZXMgd2lsbCBjaGFuZ2UgZXZlcnkgdGltZSB0aGF0IHRoZSBwbG90IGlzIGdlbmVyYXRlZC4gVG8gbWFrZSB0aGUgY29sb3IgYW5kIG1hcmtlciBjaG9pY2VzIGNvbnN0YW50LCBJIHByb3ZpZGUgYW4gZXhhbXBsZSBpbiB0aGUgY29tbWVudGVkIGNvZGUgYmVsb3cuIApgYGB7cn0KIyBkZWZpbmUgYXhpcyBsaW1pdHMKIyBCZXN0IHRvIHNldCB0aGVtIGR5bmFtaWNhbGx5IGluIGNhc2Ugd2UgbWlnaHQgd2FudCB0byB1c2UgdGhpcyBzYW1lIGNvZGUgZm9yIGEgZGlmZmVyZW50IGRhdGFzZXQsIHdoaWNoIG1pZ2h0IGhhdmUgZGlmZmVyZW50IGxpbWl0cy4gTGV0J3MgZmluZCBvdXIgbWluIGFuZCBtYXggYW5kIHJvdW5kIHRoZW0gdG8gdGhlIG5lYXJlc3Qgd2hvbGUgbnVtYmVyLiAgCmF4aXNNaW4gPWZsb29yKG1pbihkZiR0aW1lLCBkZiRwdXBpbCkpICMgbWluIGFjcm9zcyBib3RoIG91ciBjb2x1bW5zIG9mIGludGVyZXN0CmF4aXNNYXggPSBjZWlsaW5nKG1heChkZiR0aW1lLCBkZiRwdXBpbCkpICMgbWluIGFjcm9zcyBib3RoIG91ciBjb2x1bW5zIG9mIGludGVyZXN0CgojIGNob29zZSBhIHVuaXF1ZSwgY29sb3ItYmxpbmQtZnJpZW5kbHkgY29sb3IgZm9yIGVhY2ggY29uZGl0aW9uCm5jb25kcyA9IGxlbmd0aCh1bmlxdWUoZGYkY29uZGl0aW9uKSkgIyBkZXRlcm1pbiBudW1iZXIgb2YgY29uZGl0aW9ucwpjb2xvckJsaW5kRnJpZW5kbHlQYWxsZXR0ZSA9IGMoIiM5OTk5OTkiLCAiI0U2OUYwMCIsICIjNTZCNEU5IiwgIiMwMDlFNzMiLCAiI0YwRTQ0MiIsICIjMDA3MkIyIiwgIiNENTVFMDAiLCAiI0NDNzlBNyIpICMgZGVmaW5lIGNvbG9yLWJsaW5kLWZyaWVuZGx5IHBhbGV0dGUKb3VyQ29sb3JzID0gc2FtcGxlKGNvbG9yQmxpbmRGcmllbmRseVBhbGxldHRlLCBuY29uZHMsIHJlcGxhY2U9RkFMU0UpICMgcmFuZG9tbHkgY2hvb3NlIHRoZSBudW1iZXIgb2YgY29sb3JzIHdlIG5lZWQsIHdpdGhvdXQgcmVwbGFjZW1lbnQKCiMgVGhlIHJlYXNvbiBmb3IgY2hvb3NpbmcgY29sb3JzIGR5bmFtaWNhbGx5ICh1c2luZyBuY29uZHMpIGlzIHNvIHRoYXQgaWYgdGhlIG51bWJlciBvZiBjb25kaXRpb25zIGluIG91ciBkYXRhc2V0IGNoYW5nZXMsIG91ciBjb2RlIHdpbGwgbm90IGJyZWFrLiAKIyBOT1RFLiBZb3UgY291bGQgc2V0IGNvbG9ycyBtYW51YWxseSBieSBkb2luZyBzb21ldGhpbmcgbGlrZSB0aGlzOgojIGNvbmQxID0gICIjMDA5RTczIgojIGNvbmQyID0gICIjMDA3MkIyIgojIGNvbmQzID0gICIjRDU1RTAwIgojIGNvbmQ0ID0gICIjQ0M3OUE3IgojIG91ckNvbG9ycyA9IGMoY29uZDEsIGNvbmQyLCBjb25kMywgY29uZDQpCgojIFdlIGNhbiBkbyB0aGUgc2FtZSB0aGluZyBmb3IgbWFya2VyIHNoYXBlcwojIE1hcmtlciBzaGFwZXMgYXJlIGRlZmluZWQgYnkgbnVtYmVyLiBTZWUgaGVyZSBmb3IgdGhlIGZ1bGwgbGlzdCBvZiBvcHRpb25zOiBodHRwczovL3ItZ3JhcGhpY3Mub3JnL3JlY2lwZS1zY2F0dGVyLXNoYXBlcwpvdXJNYXJrZXJzID0gc2FtcGxlKDE6MjAsIG5jb25kcywgcmVwbGFjZT1GQUxTRSkKCiMgUHJlLXNldCBvcHRpb24KIyBvdXJNYXJrZXJzID0gYygxNywgMSwgNSwgOCkKYGBgCgojIyMgQ3JlYXRlIGJhciBwbG90IApFcnJvcnMgYmFycyByZXByZXNlbnQgc3RkIGRldgpgYGB7cn0KaWYocmVxdWlyZU5hbWVzcGFjZSgiZ2dwbG90MiIpKXsgIyBtYWtlIHN1cmUgb3VyIHJlcXVpcmVkIHBhY2thZ2UgaXMgbG9hZGVkCiAgc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkoZ2dwbG90MikpfQoKaWYgKG9wKSB7cG5nKHBhc3RlKGZpZ19wYXRoLCAicHVwaWxCYXJQbG90LnBuZyIsIHNlcD0iIiksIHVuaXRzPSJpbiIsIHdpZHRoPTExLCBoZWlnaHQ9MTEsIHJlcz0zMDApfQogIApwdXBpbGJhcnBsb3QgPC0gZ2dwbG90KGNvbmRzdGF0cywgYWVzKHggPSBjb25kaXRpb24sIGZpbGwgPSBjb25kaXRpb24sIHkgPSBtZWFuX3B1cGlsKSkgKwogIGdlb21fY29sKHBvc2l0aW9uID0gImRvZGdlIikgKyAKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9b3VyQ29sb3JzKSArCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbiA9IG1lYW5fcHVwaWwgLSBzdGRfZGV2X3B1cGlsLCB5bWF4ID0gbWVhbl9wdXBpbCArIHN0ZF9kZXZfcHVwaWwpLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKDAuOSksIHdpZHRoID0gLjIpICsgbGFicyhmaWxsID0gIkNvbmRpdGlvbiIpICAKCnB1cGlsYmFycGxvdCArIHhsYWIoIkNvbmRpdGlvbiIpICsgeWxhYigiQXZnLiBQdXBpbCBTaXplIChhdSkiKQpgYGAKIyBUYWtlLWF3YXlzIHNvIGZhcj8KUGxlbnR5IG9mIHB1cGlsIHBhcGVycyB3b3VsZCBvbmx5IHNob3cgdGhlIHJlc3VsdHMgdXAgdW50aWwgdGhpcyBwb2ludC4gQnV0LiBUaGVyZSBpcyBvZnRlbiB1c2VmdWwgaW5mb3JtYXRpb24gaW4gdGhlIHB1cGlsIHRyYWplY3RvcnkuIExldCdzIHBsb3QgdGhlIHB1cGlsIHRyYWNlIG92ZXIgdGltZS4gCgpfX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fXwoKIyBQbG90IHB1cGlsIHRyYWNlIG92ZXIgdGltZSwgYnkgY29uZGl0aW9uCgojIyMgQ3JlYXRlIHNjYXR0ZXJwbG90CmBgYHtyfQojIEluaXRpYWxpemUgb3V0cHV0IGZpbGUgaWYgd2Ugd2FudCB0byBzYXZlIHRoZSBnZW5lcmF0ZWQgZmlndXJlIHRvIGZpbGUKaWYgKG9wKSB7cG5nKHBhc3RlKGZpZ19wYXRoLCAicHVwaWxPdmVyVGltZS5wbmciLCBzZXA9IiIpLCB1bml0cz0iaW4iLCB3aWR0aD0xMSwgaGVpZ2h0PTExLCByZXM9MzAwKX0KCiMgY3JlYXRlIHBsb3RzCmlmKHJlcXVpcmVOYW1lc3BhY2UoImdncGxvdDIiKSl7ICMgbWFrZSBzdXJlIHJlcXVpcmVkIHBhY2thZ2UgaXMgbG9hZGVkCiAgbGlicmFyeShnZ3Bsb3QyKX0KCmdncGxvdChkZiwgYWVzKHggPSB0aW1lLCB5ID0gcHVwaWwsIGNvbG91cj1jb25kaXRpb24sIHNoYXBlPWNvbmRpdGlvbikpKyAKICAgIGdlb21fcG9pbnQoc2l6ZT00KSArICMgY3JlYXRlIHNjYXR0ZXIgcGxvdAogICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9b3VyQ29sb3JzKSArIAogICAgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcz1vdXJNYXJrZXJzKSArIAogICAgdGhlbWVfY2xhc3NpYygpICsgCiAgICB0aGVtZShhc3BlY3QucmF0aW89MSwgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLCBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICAgIHhsaW0oYXhpc01pbiwgYXhpc01heCkgKyAKICAgIHlsaW0oYXhpc01pbiwgYXhpc01heCkgKwogICAgZmFjZXRfd3JhcCh+Y29uZGl0aW9uLCBuY29sID0gMikgIyBjcmVhdGUgb25lIHNjYXR0ZXIgcGxvdCBwZXIgY29uZGl0aW9uCmBgYAoKCiMjIE1haW4gdGFrZS1hd2F5CkhvcGVmdWxseSwgc3RlcHBpbmcgdGhyb3VnaCB0aGUgZXhlcmNpc2VzIGFib3ZlIGhhcyBtYWRlIHlvdSByZWFsaXplIHRoZSBpbXBvcnRhbmNlIG9mIGRhdGEgdmlzdWFsaXphdGlvbiAtLSB5b3UgbWlnaHQgbm90IGV2ZW4gYmUgbG9va2luZyBhdCByZWFsIHB1cGlsIGRhdGEhIQo=