"use strict"; var urlvar = jsPsych.data.urlVariables(); var user_agent_string = navigator.userAgent; function saveData(name, data) { var xhr = new XMLHttpRequest(); xhr.open("POST", "PCstatic/write_data.php"); xhr.setRequestHeader("Content-Type", "application/json"); xhr.send(JSON.stringify({ filename: name, filedata: data })); } var string_final = urlvar.campaign + urlvar.worker + urlvar.randkey; var verification = "mw-" + sha256(string_final); // definiting two different response scales that can be used on the rating scales. var comp_option = ["No", "Small", "Visible", "Large", "Huge"]; var total_option = ["Bad", "Poor", "Fair", "Good", "Excellent"]; var better_option = ["Left", "Similar", "Right"]; var no_data = { type: "html-keyboard-response", stimulus: "

This task is only available with a valid workerID and campaignID.

", choices: jsPsych.NO_KEYS, }; var full_data = { type: "html-keyboard-response", stimulus: "

We are sorry, the maximum amount of workers for this task has already been reached.

", choices: jsPsych.NO_KEYS, }; var small_screen = { type: "html-keyboard-response", stimulus: "

Your browser screen size is only " + $(window).width() + "x" + $(window).height() + " pixels. For this study is must be at least 920x680 pixels. Try to switch your browser to fullscreen (press F11) or increase your screen resolution. Then reload this page.

", choices: jsPsych.NO_KEYS, }; function checkSize() { if ($(window).width() < 920 || $(window).height() < 680) { return true; } else { return false; } } // The if's check that preconditions are met before the study can be started. var start_if = { timeline: [no_data], conditional_function: function () { if (urlvar.worker == null || urlvar.campaign == null) { return true; } else { return false; } }, } var start_if2 = { timeline: [full_data], conditional_function: function () { return false; }, } var start_if3 = { timeline: [small_screen], conditional_function: function () { return checkSize(); }, } // Predefined element position coordinates for each stimulus needed for evaluation block generation const positions_bremen = [[13, 13.5, 17], [31, 50, 2], [-65, -7, 9], [-40, -68, 3], [-35, 64, 4], [-5, 76, 1], [-18, 30, 0],[-15, -27, 0]]; const positions_randersacker = [[-6, -7, 0.6],[-4.5, -18, 4],[-13, -33, -0.5],[-6, -14.5, -0.5], [8, -0.4, 1], [1.7, -8, 7.3]]; // Build all possible test blocks, which are chosen dynamically later and actually used // Functions create based on tested condition jsTimelineObjects here: sequential with one element for each cloud const TQ0001 = generate_test_data_for_pointclouds_sequentiell("Q00", "Q01", "bremen", twoRandomPositions(positions_bremen)); const TQ0002 = generate_test_data_for_pointclouds_sequentiell("Q00", "Q02", "bremen", twoRandomPositions(positions_bremen)); const TQ0003 = generate_test_data_for_pointclouds_sequentiell("Q00", "Q03", "bremen", twoRandomPositions(positions_bremen)); const TQ0004 = generate_test_data_for_pointclouds_sequentiell("Q00", "Q04", "bremen", twoRandomPositions(positions_bremen)); const TQ0005 = generate_test_data_for_pointclouds_sequentiell("Q00", "Q05", "bremen", twoRandomPositions(positions_bremen)); const TQ0006 = generate_test_data_for_pointclouds_sequentiell("Q00", "Q06", "bremen", twoRandomPositions(positions_bremen)); const TQ1001 = generate_test_data_for_pointclouds_sequentiell("Q10", "Q11", "randersacker", twoRandomPositions(positions_randersacker)); const TQ1002 = generate_test_data_for_pointclouds_sequentiell("Q10", "Q12", "randersacker", twoRandomPositions(positions_randersacker)); const TQ1003 = generate_test_data_for_pointclouds_sequentiell("Q10", "Q13", "randersacker", twoRandomPositions(positions_randersacker)); const TQ1004 = generate_test_data_for_pointclouds_sequentiell("Q10", "Q14", "randersacker", twoRandomPositions(positions_randersacker)); const TQ1005 = generate_test_data_for_pointclouds_sequentiell("Q10", "Q15", "randersacker", twoRandomPositions(positions_randersacker)); const TQ1006 = generate_test_data_for_pointclouds_sequentiell("Q10", "Q16", "randersacker", twoRandomPositions(positions_randersacker)); /* // Example objects when testing for two elements const TQ0001 = generate_test_data_for_pointclouds_sequentiell_2_elements("Q00", "Q01", "bremen", fourRandomPositions(positions_bremen)); const TQ0002 = generate_test_data_for_pointclouds_sequentiell_2_elements("Q00", "Q02", "bremen", fourRandomPositions(positions_bremen)); const TQ0003 = generate_test_data_for_pointclouds_sequentiell_2_elements("Q00", "Q03", "bremen", fourRandomPositions(positions_bremen)); const TQ0004 = generate_test_data_for_pointclouds_sequentiell_2_elements("Q00", "Q04", "bremen", fourRandomPositions(positions_bremen)); const TQ0005 = generate_test_data_for_pointclouds_sequentiell_2_elements("Q00", "Q05", "bremen", fourRandomPositions(positions_bremen)); const TQ0006 = generate_test_data_for_pointclouds_sequentiell_2_elements("Q00", "Q06", "bremen", fourRandomPositions(positions_bremen)); const TQ1001 = generate_test_data_for_pointclouds_sequentiell_2_elements("Q00", "Q01", "randersacker", fourRandomPositions(positions_randersacker)); const TQ1002 = generate_test_data_for_pointclouds_sequentiell_2_elements("Q00", "Q01", "randersacker", fourRandomPositions(positions_randersacker)); const TQ1003 = generate_test_data_for_pointclouds_sequentiell_2_elements("Q00", "Q01", "randersacker", fourRandomPositions(positions_randersacker)); const TQ1004 = generate_test_data_for_pointclouds_sequentiell_2_elements("Q00", "Q01", "randersacker", fourRandomPositions(positions_randersacker)); const TQ1005 = generate_test_data_for_pointclouds_sequentiell_2_elements("Q00", "Q01", "randersacker", fourRandomPositions(positions_randersacker)); const TQ1006 = generate_test_data_for_pointclouds_sequentiell_2_elements("Q00", "Q01", "randersacker", fourRandomPositions(positions_randersacker)); // Example objects when testing for three elements const TQ0001 = generate_test_data_for_pointclouds_sequentiell_3_elements("Q00", "Q01", "bremen", sixRandomPositions(positions_bremen)); const TQ0002 = generate_test_data_for_pointclouds_sequentiell_3_elements("Q00", "Q02", "bremen", sixRandomPositions(positions_bremen)); const TQ0003 = generate_test_data_for_pointclouds_sequentiell_3_elements("Q00", "Q03", "bremen", sixRandomPositions(positions_bremen)); const TQ0004 = generate_test_data_for_pointclouds_sequentiell_3_elements("Q00", "Q04", "bremen", sixRandomPositions(positions_bremen)); const TQ0005 = generate_test_data_for_pointclouds_sequentiell_3_elements("Q00", "Q05", "bremen", sixRandomPositions(positions_bremen)); const TQ0006 = generate_test_data_for_pointclouds_sequentiell_3_elements("Q00", "Q06", "bremen", sixRandomPositions(positions_bremen)); const TQ1001 = generate_test_data_for_pointclouds_sequentiell_3_elements("Q00", "Q01", "randersacker", sixRandomPositions(positions_randersacker)); const TQ1002 = generate_test_data_for_pointclouds_sequentiell_3_elements("Q00", "Q01", "randersacker", sixRandomPositions(positions_randersacker)); const TQ1003 = generate_test_data_for_pointclouds_sequentiell_3_elements("Q00", "Q01", "randersacker", sixRandomPositions(positions_randersacker)); const TQ1004 = generate_test_data_for_pointclouds_sequentiell_3_elements("Q00", "Q01", "randersacker", sixRandomPositions(positions_randersacker)); const TQ1005 = generate_test_data_for_pointclouds_sequentiell_3_elements("Q00", "Q01", "randersacker", sixRandomPositions(positions_randersacker)); const TQ1006 = generate_test_data_for_pointclouds_sequentiell_3_elements("Q00", "Q01", "randersacker", sixRandomPositions(positions_randersacker)); */ // Functions that output evaluation blocks based on input. Input follows rules (see example inputs above). function generate_test_data_for_pointclouds_paired(cloudLeft, cloudRight, type, positions) { const letter1 = generateRandomLetter(); const letter2 = generateRandomLetter(); var data = [ { data: { js_url: "PCstatic/js/pik.js", controlLetter1: letter1, controlLetter2: letter2, positions: positions, cloudRight: cloudRight, cloudLeft: cloudLeft, type: type, }, timeline: [ { url: `potree/paired_comparison_template.html` }, { type: "survey-multi-choice", questions: [ { prompt: "Which point cloud looked better?", options: better_option, required: true, horizontal: true, }, { prompt: "What letter combination was displayed in the last point cloud?", options: generiereUndMischePaar(letter1 + letter2), required: true, horizontal: true, }, ], }, ], }, ]; return data; } function generate_test_data_for_pointclouds_sequentiell( cloudOne, cloudTwo, type,positions ) { const letter1 = generateRandomLetter(); const letter2 = generateRandomLetter(); var data = [ { data: { js_url: "PCstatic/js/pik.js", controlLetter1: letter1, controlLetter2: letter2, positions: positions, cloudOne: cloudOne, cloudTwo: cloudTwo, type: type, perspectives: [], tab_switch: 0, }, timeline: [ { url: `potree/sequentiell_template.html` }, { type: "survey-multi-choice", questions: [ { prompt: "How would you rate the Point Cloud?", options: total_option, required: true, horizontal: true, }, { prompt: "What letter/number was displayed in the last point cloud?", options: generateRandomLetterChoice(letter1), required: true, horizontal: true, }, ], }, { url: `potree/sequentiell_template2.html` }, { type: "survey-multi-choice", questions: [ { prompt: "How would you rate the Point Cloud?", options: total_option, required: true, horizontal: true, }, { prompt: "How would you rate the difference compared to the previous point cloud?", options: comp_option, required: true, horizontal: true, }, { prompt: "What letter/number was displayed in the last point cloud?", options: generateRandomLetterChoice(letter2), required: true, horizontal: true, }, ], }, ], }, ]; return data; } function generate_test_data_for_pointclouds_sequentiell_2_elements( cloudOne, cloudTwo, type,positions ) { const letter1 = generateRandomLetter(); const letter2 = generateRandomLetter(); const letter3 = generateRandomLetter(); const letter4 = generateRandomLetter(); var choices12 = generateRandomChoicesTwo(letter1, letter2); var choices34 = generateRandomChoicesTwo(letter3, letter4); var data = [ { data: { js_url: "PCstatic/js/pik.js", controlLetter1: letter1, controlLetter2: letter2, controlLetter3: letter3, controlLetter4: letter4, positions: positions, cloudOne: cloudOne, cloudTwo: cloudTwo, type: type, perspectives: [], tab_switch: 0, }, timeline: [ { url: `potree/sequentiell_template.html` }, { type: "survey-multi-choice", questions: [ { prompt: "How would you rate the Point Cloud?", options: total_option, required: true, horizontal: true, }, { prompt: "Which element was displayed in the last point cloud?", options: choices12[0], required: true, horizontal: true, }, { prompt: "Which element was displayed in the last point cloud?", options: choices12[1], required: true, horizontal: true, }, ], }, { url: `potree/sequentiell_template2.html` }, { type: "survey-multi-choice", questions: [ { prompt: "How would you rate the Point Cloud?", options: total_option, required: true, horizontal: true, }, { prompt: "How would you rate the difference compared to the previous point cloud?", options: comp_option, required: true, horizontal: true, }, { prompt: "Which element was displayed in the last point cloud?", options: choices34[0], required: true, horizontal: true, }, { prompt: "Which element was displayed in the last point cloud?", options: choices34[1], required: true, horizontal: true, }, ], }, ], }, ]; return data; } function generate_test_data_for_pointclouds_sequentiell_3_elements( cloudOne, cloudTwo, type,positions ) { const letter1 = generateRandomLetter(); const letter2 = generateRandomLetter(); const letter3 = generateRandomLetter(); const letter4 = generateRandomLetter(); const letter5 = generateRandomLetter(); const letter6 = generateRandomLetter(); var choices123 = generateRandomChoicesThree(letter1, letter2, letter3); var choices456 = generateRandomChoicesThree(letter4, letter5, letter6); var data = [ { data: { js_url: "PCstatic/js/pik.js", controlLetter1: letter1, controlLetter2: letter2, controlLetter3: letter3, controlLetter4: letter4, controlLetter5: letter5, controlLetter6: letter6, positions: positions, cloudOne: cloudOne, cloudTwo: cloudTwo, type: type, perspectives: [], tab_switch: 0, }, timeline: [ { url: `potree/sequentiell_template.html` }, { type: "survey-multi-choice", questions: [ { prompt: "How would you rate the Point Cloud?", options: total_option, required: true, horizontal: true, }, { prompt: "Which element was displayed in the last point cloud?", options: choices123[0], required: true, horizontal: true, }, { prompt: "Which element was displayed in the last point cloud?", options: choices123[1], required: true, horizontal: true, }, { prompt: "Which element was displayed in the last point cloud?", options: choices123[2], required: true, horizontal: true, }, ], }, { url: `potree/sequentiell_template2.html` }, { type: "survey-multi-choice", questions: [ { prompt: "How would you rate the Point Cloud?", options: total_option, required: true, horizontal: true, }, { prompt: "How would you rate the difference compared to the previous point cloud?", options: comp_option, required: true, horizontal: true, }, { prompt: "Which element was displayed in the last point cloud?", options: choices456[0], required: true, horizontal: true, }, { prompt: "Which element was displayed in the last point cloud?", options: choices456[1], required: true, horizontal: true, }, { prompt: "Which element was displayed in the last point cloud?", options: choices456[2], required: true, horizontal: true, }, ], }, ], }, ]; return data; } // Support functions for the main test object generation functions (see above) function twoRandomPositions(positions) { let randomIndex1 = Math.floor(Math.random() * positions.length); let randomIndex2 = Math.floor(Math.random() * positions.length); while (randomIndex1 == randomIndex2) { randomIndex2 = Math.floor(Math.random() * positions.length); } return [positions[randomIndex1], positions[randomIndex2]]; } function fourRandomPositions(positions) { if (positions.length < 4) { throw new Error("The positions array must contain at least 4 elements"); } let result = []; let availableIndices = [...Array(positions.length).keys()]; for (let i = 0; i < 4; i++) { let randomIndex = Math.floor(Math.random() * availableIndices.length); let selectedIndex = availableIndices[randomIndex]; result.push(positions[selectedIndex]); availableIndices.splice(randomIndex, 1); } return result; } function sixRandomPositions(positions) { if (positions.length < 6) { throw new Error("The positions array must contain at least 6 elements"); } let result = []; let availableIndices = [...Array(positions.length).keys()]; for (let i = 0; i < 6; i++) { let randomIndex = Math.floor(Math.random() * availableIndices.length); let selectedIndex = availableIndices[randomIndex]; result.push(positions[selectedIndex]); availableIndices.splice(randomIndex, 1); } return result; } function generateRandomLetter() { const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789"; const randomIndex = Math.floor(Math.random() * 35); return alphabet[randomIndex]; } function generateRandomLetterChoice(initialLetter) { let choices = [initialLetter]; while (choices.length < 10) { let newLetter = generateRandomLetter(); if (!choices.includes(newLetter)) { choices.push(newLetter); } } //durchmischen des arrays for (let i = choices.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [choices[i], choices[j]] = [choices[j], choices[i]]; } return choices; } function generateRandomChoicesTwo(initialElement1, initialElement2) { let choices1 = [initialElement1]; let choices2 = [initialElement2]; while (choices1.length < 10) { let newLetter = generateRandomLetter(); if (!choices1.includes(newLetter) && newLetter != initialElement2) { choices1.push(newLetter); } } //durchmischen des arrays for (let i = choices1.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [choices1[i], choices1[j]] = [choices1[j], choices1[i]]; } while (choices2.length < 10) { let newLetter = generateRandomLetter(); if (!choices2.includes(newLetter) && !choices1.includes(newLetter)) { choices2.push(newLetter); } } //durchmischen des arrays for (let i = choices2.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [choices2[i], choices2[j]] = [choices2[j], choices2[i]]; } return [choices1, choices2]; } function generateRandomChoicesThree(initialElement1, initialElement2, initialElement3) { let choices1 = [initialElement1]; let choices2 = [initialElement2]; let choices3 = [initialElement3]; while (choices1.length < 10) { let newLetter = generateRandomLetter(); if (!choices1.includes(newLetter) && newLetter != initialElement2 && newLetter != initialElement3) { choices1.push(newLetter); } } //durchmischen des arrays for (let i = choices1.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [choices1[i], choices1[j]] = [choices1[j], choices1[i]]; } while (choices2.length < 10) { let newLetter = generateRandomLetter(); if (!choices2.includes(newLetter) && !choices1.includes(newLetter) && newLetter != initialElement3) { choices2.push(newLetter); } } //durchmischen des arrays for (let i = choices2.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [choices2[i], choices2[j]] = [choices2[j], choices2[i]]; } while (choices3.length < 10) { let newLetter = generateRandomLetter(); if (!choices3.includes(newLetter) && !choices1.includes(newLetter) && !choices2.includes(newLetter)) { choices3.push(newLetter); } } //durchmischen des arrays for (let i = choices3.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [choices3[i], choices3[j]] = [choices3[j], choices3[i]]; } return [choices1, choices2, choices3]; } function generiereUndMischePaar(initialesPaar) { // Funktion, um ein zufälliges Paar von Buchstaben zu generieren function zufaelligesPaar() { return generateRandomLetter() + generateRandomLetter(); } // Initialisiert das Array mit dem übergebenen Paar let paare = [initialesPaar]; // Generiert 9 zufällige Paare while (paare.length < 10) { let neuesPaar = zufaelligesPaar(); // Stellt sicher, dass das generierte Paar einzigartig ist if (!paare.includes(neuesPaar)) { paare.push(neuesPaar); } } // Mischung des Arrays for (let i = paare.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [paare[i], paare[j]] = [paare[j], paare[i]]; } return paare; } // Create 4 Block timeline with 2 Randersacker and 2 Bremen // Uses previously generated test blocks function generateBlockSequence() { const test_data_bremen_seq = [ TQ0001, TQ0002, TQ0003, TQ0004, TQ0005, TQ0006, ]; const test_data_randersacker_seq = [ TQ1001, TQ1002, TQ1003, TQ1004, TQ1005, TQ1006, ]; const block_sequence = []; if (Math.random() < 0.5) { //zuerst 2 zufällige Bremen, dann 2 zufällige Randersacker let randomIndex1 = Math.floor(Math.random() * test_data_bremen_seq.length); let randomIndex2 = Math.floor(Math.random() * test_data_bremen_seq.length); while (randomIndex1 == randomIndex2) { randomIndex2 = Math.floor(Math.random() * test_data_bremen_seq.length); } block_sequence.push(test_data_bremen_seq[randomIndex1]); block_sequence.push(test_data_bremen_seq[randomIndex2]); randomIndex1 = Math.floor(Math.random() * test_data_randersacker_seq.length); randomIndex2 = Math.floor(Math.random() * test_data_randersacker_seq.length); while (randomIndex1 == randomIndex2) { randomIndex2 = Math.floor(Math.random() * test_data_randersacker_seq.length); } block_sequence.push(test_data_randersacker_seq[randomIndex1]); block_sequence.push(test_data_randersacker_seq[randomIndex2]); } else { //zuerst 2 zufällige Randersacker, dann 2 zufällige Bremen let randomIndex1 = Math.floor(Math.random() * test_data_randersacker_seq.length); let randomIndex2 = Math.floor(Math.random() * test_data_randersacker_seq.length); while (randomIndex1 == randomIndex2) { randomIndex2 = Math.floor(Math.random() * test_data_randersacker_seq.length); } block_sequence.push(test_data_randersacker_seq[randomIndex1]); block_sequence.push(test_data_randersacker_seq[randomIndex2]); randomIndex1 = Math.floor(Math.random() * test_data_bremen_seq.length); randomIndex2 = Math.floor(Math.random() * test_data_bremen_seq.length); while (randomIndex1 == randomIndex2) { randomIndex2 = Math.floor(Math.random() * test_data_bremen_seq.length); } block_sequence.push(test_data_bremen_seq[randomIndex1]); block_sequence.push(test_data_bremen_seq[randomIndex2]); } return block_sequence; } var test_data = generateBlockSequence(); // jsTimelineObjs containing pages for introduction, information and instructions var welcome = { type: "external-html", url: "PCstatic/html/welcome.html", cont_btn: "consent", executeScript: true, }; var instructions0 = { type: "external-html", url: "PCstatic/html/instructions_P0.html", cont_btn: "consent", executeScript: true, }; var instructions1 = { type: "external-html", url: "PCstatic/html/instructions_P1_no_ emphasis_quality.html", cont_btn: "consent", executeScript: true, }; var instructions2 = { type: "external-html", url: "PCstatic/html/instructions_P2.html", cont_btn: "consent", executeScript: true, }; var post_test_survey = { // still just a test type: "survey-text", preamble: "

One last question

", questions: [ { prompt: "What made a point cloud look good or bad? Please describe on what you have focused most. Also write any feedback here.", rows: 10, columns: 100, }, ], on_finish: function () { var interaction_data = jsPsych.data.getInteractionData(); jsPsych.data.get().addToLast({ interaction: interaction_data.json(), useragent: user_agent_string, campaign: urlvar.campaign, worker: urlvar.worker, randkey: urlvar.randkey, verificationcode: verification, }); }, }; var debrief_block = { type: "instructions", allow_keys: false, pages: [ '

Thank you very much for your participation.' + '

Your Microworkers verification code is: '+verification+'

' + '

Please copy this code to Microworkers to receive your payment, afterwards you can close this window.

' + '

----------------------------------------------------------------------------------------------------

' + "What have we tested?" + "

Two methods of reduction are presented alongside the original. The first, known as octree can be described as follows:

"+ "

The number of points per volume in the cloud is higher in the inner regions and lower in the outer regions. They can be saved as Octrees.

" + '' + "

The space of the cloud is divided into 8 sections, which can be subdivided again. The leaves of the tree are the points.

" + "

One method to reduce a point cloud is to subdivide the Octree to a certain size. Then only allow a certain maximum amount of points per leaf (e.g. 2). This especially reduces the cloud in very dense regions and leaves the sparse regions untouched.

" + "

Another method is to project the point cloud onto a sphere and then project the sphere onto a 2D picture (similar to 2D maps of earth). The picture is now reduced in size and then converted back into a point cloud.

" + "

These and other methods were tested in the survey together with other factors that could influence the QoE of a viewer.

" + "
" + "

Thank you again for your participation!

" + "
", ], on_load: function () { jsPsych.data .get() .addToLast({ Window: $(window).width() + "x" + $(window).height() }); saveData(taskName + "data", jsPsych.data.get().json()); }, }; // creates jsTimelineObj for the trial/test run let letter1 = generateRandomLetter(); let choices = generateRandomLetterChoice(letter1); let cloudOne = "trial"; let positions = [ [0, 0, 0], ]; let trial_run = { type: "external-html", cont_btn: "end-trial", executeScript: true, data: { js_url: "PCstatic/js/pik.js", controlLetter1: letter1, positions: positions, cloudOne: cloudOne, type: "trial", }, timeline: [ { url: `potree/sequentiell_template.html` }, { type: "survey-multi-choice", questions: [ { prompt: "How would you rate the Point Cloud?", options: total_option, required: true, horizontal: true, }, { prompt: "What element was displayed in the last point cloud?", options: choices, required: true, horizontal: true, }, ], }, ], }; let feedback_trial = { type: "external-html", url: "PCstatic/html/feedback_no_emphasis_quality.html", executeScript: true, cont_btn: "end-trial", }; const testRunLoop = { timeline: [trial_run, feedback_trial], loop_function: function (data) { var responses = JSON.parse( jsPsych.data.get().last(2).select("responses").values ); var controlLetter1 = jsPsych.data.get().last(2).select("controlLetter1") .values[0]; if (responses.Q1 != controlLetter1) { //repeat the loop new Notification("Test Run Feedback"); return true; } else { //end the loop false; } }, }; jsPsych.data.addProperties(); // Now build the timeline for the study run. Push all desired objects into it in the correct order var timeline = []; timeline.push(start_if) timeline.push(start_if2) timeline.push(start_if3) timeline.push(welcome); timeline.push(instructions0) timeline.push(instructions1) timeline.push(instructions2) timeline.push(testRunLoop); test_data.forEach(function (this_test) { var test_block = { type: "external-html", timeline: this_test, executeScript: true, cont_btn: "end-trial", force_refresh: true, }; timeline.push(test_block); }); timeline.push(post_test_survey); // When this block is reached, the captured data is saved timeline.push(debrief_block); //jsPsych init function starts test. jsPsych.init({ timeline: timeline, show_progress_bar: true, });