Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Update Visual Oddball Experiment protocol #238

Merged
merged 2 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
269 changes: 207 additions & 62 deletions frontend/public/experiments/visual_oddball.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
<script src="https://unpkg.com/@jspsych/[email protected]"></script>
<script src="https://unpkg.com/@jspsych/[email protected]"></script>
<script src="https://unpkg.com/@jspsych/[email protected]"></script>
<script src="https://unpkg.com/@jspsych/[email protected].2"></script>
<script src="https://unpkg.com/@jspsych/[email protected].3"></script>
<script src="https://unpkg.com/@jspsych/[email protected]"></script>
<script src="https://unpkg.com/@jspsych/[email protected]"></script>
<script src="https://unpkg.com/@jspsych/[email protected]"></script>
<script src="https://unpkg.com/@jspsych/[email protected]"></script>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/jspsych.css" />
<style>
.jspsych-btn {
Expand Down Expand Up @@ -42,25 +43,49 @@
</style>
</head>
<body>
<div id="countDown"><p></p></div>
<div id="jspsych-container"></div>
<button id="fullscreen-btn" style="position: absolute; top: 10px; right: 10px">Toggle Fullscreen</button>
<script>
function mapElapsedToUnix(data) {
let startStamp = -1;
data.trials.forEach((element) => {
if (startStamp === -1) {
if ("unixTimestamp" in element) startStamp = element.unixTimestamp - element.time_elapsed;
} else {
element.unixTimestamp = startStamp + element.time_elapsed;
document.getElementById("fullscreen-btn").addEventListener("click", function () {
if (!document.fullscreenElement) {
if (document.documentElement.requestFullscreen) {
document.documentElement.requestFullscreen();
} else if (document.documentElement.mozRequestFullScreen) {
// Firefox
document.documentElement.mozRequestFullScreen();
} else if (document.documentElement.webkitRequestFullscreen) {
// Chrome, Safari and Opera
document.documentElement.webkitRequestFullscreen();
} else if (document.documentElement.msRequestFullscreen) {
// IE/Edge
document.documentElement.msRequestFullscreen();
}
});
return data;
}

} else {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
// Firefox
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
// Chrome, Safari and Opera
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
// IE/Edge
document.msExitFullscreen();
}
}
});
</script>
<script>
const jsPsych = initJsPsych({
on_finish: function () {
window.parent.postMessage(mapElapsedToUnix(jsPsych.data.get()), "*");
console.log(mapElapsedToUnix(jsPsych.data.get()));
window.parent.postMessage(jsPsych.data.get(), "*");
},
on_trial_start: function (trial) {
if (!trial.data) {
trial.data = {};
}
trial.data.unixTimestamp = Date.now();
},
display_element: "jspsych-container",
});
Expand All @@ -70,70 +95,190 @@
pages: [
"Welcome to the Visual Oddball Experiment!",
"Get Ready: Find a quiet place and put on your headphones.</br> Make sure you're comfortable and ready to focus.",
"Spot the Oddball: Count the number of BLUE and GREEN circles you see in your head.",
"Start EEG Recording: If you have your EEG device connected",
],
show_clickable_nav: true,
};

const fixation = {
type: jsPsychHtmlKeyboardResponse,
const consent = {
type: jsPsychHtmlButtonResponse,
stimulus:
'<div style="width: 200px; height: 200px; display: flex; justify-content: center; align-items: center; font-size: 150px;">+</div>',
response_ends_trial: false,
trial_duration: 500,
"Do you understand that by completing this, your brain activity will be recorded and shared with the research team for analysis?",
choices: ["Yes", "No"],
button_html: '<button class="jspsych-btn" style="display: inline-block; margin: 0 5px;">%choice%</button>',
on_finish: function (data) {
data.consent = data.response == 0 ? "Yes" : "No";
if (data.response == 1) {
jsPsych.endExperiment("Thank you for your time. The experiment has been terminated.");
}
},
};

const iti = {
type: jsPsychHtmlKeyboardResponse,
stimulus: "",
trial_duration: 250,
response_ends_trial: false,
// consent & text box for input
const userInput = {
type: jsPsychSurveyText,
questions: [
{
prompt: "How old are you?",
name: "age",
required: true,
},
{
prompt: "What is your sex at birth?",
name: "sex",
required: true,
},
{
prompt: "What is your current diagnosis?",
name: "diagnosis",
required: true,
},
],
};

// Define the standard and oddball stimuli
const standardStimulus = {
type: jsPsychHtmlButtonResponse,
stimulus: '<div style="width: 200px; height: 200px; background-color: green; border-radius: 50%;"></div>',
choices: [],
on_finish: (data) => {
console.log(data);
data.value = "standard";
},
// eyes closed instruction & task
const eyesClosedSequence = () => {
const eyes_closed_instruction = {
type: jsPsychAudioKeyboardResponse,
stimulus: "./assets/sounds/visual_oddball/task_rest_eyes_closed_msg.mp3",
prompt: "Close your eyes and relax",
choices: "NO_KEYS",
trial_ends_after_audio: true,
};
const eyes_closed = {
type: jsPsychHtmlKeyboardResponse,
stimulus: "close your eyes and relax",
trial_duration: 60000,
response_ends_trial: false,
on_finish: (data) => {
data.value = "eyes_closed";
},
};
return [eyes_closed_instruction, eyes_closed];
};
const oddballStimulus = {
type: jsPsychHtmlButtonResponse,
stimulus: '<div style="width: 200px; height: 200px; background-color: blue; border-radius: 50%;"></div>',
choices: [],
on_finish: (data) => {
console.log(data);
data.value = "oddball";
},

// eyes open instruction & task
const eyesOpenSequence = () => {
const eyes_open_instruction = {
type: jsPsychAudioKeyboardResponse,
stimulus: "./assets/sounds/visual_oddball/task_rest_eyes_open_msg.mp3",
prompt: "Look at the X sign and relax",
choices: "NO_KEYS",
trial_ends_after_audio: true,
};
const eyes_open = {
type: jsPsychHtmlKeyboardResponse,
stimulus:
'<div style="width: 200px; height: 200px; display: flex; justify-content: center; align-items: center; font-size: 150px;">x</div>',
trial_duration: 60000,
response_ends_trial: false,
on_finish: (data) => {
data.value = "eyes_open";
},
};
return [eyes_open_instruction, eyes_open];
};

const trials = [];
trials.push(instructions);
trials.push(fixation);
// oddball sequence generator
const oddballSequenceGenerator = (numberOfSequences) => {
const visual_oddball_instruction = {
type: jsPsychAudioKeyboardResponse,
stimulus: "./assets/sounds/visual_oddball/p300_visual_circles_instructions.mp3",
prompt: "Focus on the screen and silently count the number of blue circles you see",
choices: "NO_KEYS",
trial_ends_after_audio: true,
};

/**
* Generate the trial sequence
*/
const numberOfSequences = 40;
// Define the standard (green) and oddball (blue) stimuli
const standardStimulus = {
type: jsPsychHtmlButtonResponse,
stimulus: '<div style="width: 200px; height: 200px; background-color: green; border-radius: 50%;"></div>',
choices: [],
on_load: function () {
document.body.style.backgroundColor = "#808080";
},
on_finish: (data) => {
data.value = "standard";
},
};

for (let i = 0; i < numberOfSequences; i++) {
// 10% chance for deviant stimulus
if (Math.random() < 0.1) {
// set the duration for the stimulus
oddballStimulus.trial_duration = Math.floor(Math.random() * 400) + 800;
trials.push(oddballStimulus);
} else {
standardStimulus.trial_duration = Math.floor(Math.random() * 400) + 800;
trials.push(standardStimulus);
const oddballStimulus = {
type: jsPsychHtmlButtonResponse,
stimulus: '<div style="width: 200px; height: 200px; background-color: blue; border-radius: 50%;"></div>',
choices: [],
on_load: function () {
document.body.style.backgroundColor = "#808080";
},
on_finish: (data) => {
data.value = "oddball";
},
};

const iti = {
type: jsPsychHtmlKeyboardResponse,
stimulus: "",
trial_duration: 250,
response_ends_trial: false,
};

let sequences = [visual_oddball_instruction];
for (let i = 0; i < numberOfSequences; i++) {
// 10% chance for deviant stimulus
if (Math.random() < 0.1) {
// set the duration for the stimulus
oddballStimulus.trial_duration = Math.floor(Math.random() * 400) + 800;
sequences.push(oddballStimulus);
} else {
standardStimulus.trial_duration = Math.floor(Math.random() * 400) + 800;
sequences.push(standardStimulus);
}

// Push inter interval after each stimulus
sequences.push(iti);
}
return sequences;
};

// Push fixation after each stimulus
trials.push(iti);
}
// rest video
const relaxSequenceGenerator = () => {
const relax_instruction = {
type: jsPsychAudioKeyboardResponse,
stimulus: "./assets/sounds/visual_oddball/relax_1min_instructions.mp3",
prompt: "Relax for 1 minute",
choices: "NO_KEYS",
trial_ends_after_audio: true,
};
const relax_task = {
type: jsPsychAudioKeyboardResponse,
stimulus: "./assets/sounds/visual_oddball/relax_1min_background.mp3",
choices: "NO_KEYS",
trial_ends_after_audio: false,
trial_duration: 60000,
on_load: function () {
document.body.style.backgroundImage = "url('./assets/media/waves.gif')";
document.body.style.backgroundSize = "cover";
},
on_finish: (data) => {
document.body.style.backgroundImage = "none";
data.value = "relax";
},
};
return [relax_instruction, relax_task];
};

// trial sequences
// ref: https://github.com/diamandis-lab/HEROIC/blob/41da0241ea5dd300672a3f3b87268c214d2b14bf/HEROIC-core/session_config/home_session.json
const trials = [];
trials.push(instructions);
trials.push(consent);
trials.push(userInput);
trials.push(...eyesClosedSequence());
trials.push(...eyesOpenSequence());
trials.push(...oddballSequenceGenerator(40));
trials.push(...relaxSequenceGenerator());
trials.push(...oddballSequenceGenerator(40));
trials.push(...relaxSequenceGenerator());
trials.push(...oddballSequenceGenerator(40));

jsPsych.run(trials);
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const ActivityWatchModal: FC<IActivityWatchModalProps> = ({ isOpen, onClo
appInsights.trackEvent({
name: "ActivityWatchHostSelected",
properties: {
userNpub: user?.data?.user?.email, // this is actually the npub, need to cast the session type so I can use the right selection
userNpub: user?.data?.user?.name,
unixTimestamp: dayjs().unix(),
},
});
Expand Down Expand Up @@ -199,7 +199,7 @@ export const ActivityWatchModal: FC<IActivityWatchModalProps> = ({ isOpen, onClo
appInsights.trackEvent({
name: "ActivityWatchDataDownloaded",
properties: {
userNpub: user?.data?.user?.email, // this is actually the npub, need to cast the session type so I can use the right selection
userNpub: user?.data?.user?.name,
unixTimestamp: dayjs().unix(),
},
});
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/components/lab/experiment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const Experiment: FC<IExperiment> = (experiment) => {
properties: {
deviceInfo: connectedDevice,
experimentInfo: experimentInfo,
userNpub: session.data?.user?.email,
userNpub: session.data?.user?.name,
},
});
console.log(experimentInfo);
Expand All @@ -64,7 +64,7 @@ export const Experiment: FC<IExperiment> = (experiment) => {
properties: {
deviceInfo: connectedDevice,
experimentDetails: experimentInfo,
userNpub: session.data?.user?.email,
userNpub: session.data?.user?.name,
},
});
neurosityService.stopRecording();
Expand All @@ -85,7 +85,7 @@ export const Experiment: FC<IExperiment> = (experiment) => {
properties: {
deviceInfo: await museContext?.museClient?.deviceInfo(),
experimentInfo: experimentInfo,
userNpub: session.data?.user?.email,
userNpub: session.data?.user?.name,
},
});
await museEEGService.startRecording(experimentInfo);
Expand All @@ -100,7 +100,7 @@ export const Experiment: FC<IExperiment> = (experiment) => {
properties: {
deviceInfo: await museContext?.museClient?.deviceInfo(),
experimentInfo: experimentInfo,
userNpub: session.data?.user?.email,
userNpub: session.data?.user?.name,
},
});
await museEEGService.stopRecording(true);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/config/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export const experiments: IExperiment[] = [
id: 10,
name: "Visual Oddball - P300, Event Related Potential",
description:
"We want to understand how our brains react when something unexpected happens. They're particularly interested in a brain wave called the 'P300 wave'. This wave is like a signal your brain sends when it recognizes a change in the pattern of images. It usually occurs around 300 milliseconds after your brain registers the oddball sound. Start the experiment to see how your brain responds!",
"You’ll see a series of images containing circles of different colors. Your task is to count the number of blue and green circles that appear. Occasionally, an image will display a different or unexpected pattern. After the experiment, we will analyze your brain activity to look for changes in the P300 wave, a brain response that occurs about 300 milliseconds after noticing something unexpected.",
url: "/experiments/visual_oddball.html",
tags: ["visual_oddball"],
},
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/datasets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ const DatasetPage: NextPage = () => {
description: "Manage your datasets from previous recordings. You can download your data here.",
}}
/>
<h1 className="text-2xl font-bold mb-4">Datasets</h1>
<p className="mb-4">You can download your data here. You can also delete your data from the browser.</p>
<h1 className="text-4xl font-bold mb-4">Datasets</h1>
<p className="mb-4">Manage your datasets from previous recordings. You can download your data here..</p>
<ul className="space-y-2">
{files.map((name, index) => (
<li key={index} className="flex items-center justify-between">
Expand Down
Loading
Loading