├── .DS_Store ├── LICENSE ├── README.md ├── code ├── .DS_Store ├── analyses │ ├── RespiroceptionAnalysis.mlx │ └── helperFunctions │ │ ├── GetFields.m │ │ ├── ciplot.m │ │ ├── intersections.m │ │ └── new_bin_ratings.m └── suppAnalyses │ └── FDTvsRRST.jasp ├── data ├── FDTvsRRST.csv ├── NZ_constant_flow_rrst.csv ├── NZfdtRrstAccuracy.csv ├── NZfdtRrstConfidence.csv ├── NZrrstResistance.csv ├── RRST_SubLevelData_15-Apr-2021_12-59.csv ├── RRST_TrialLevelData_12-Apr-2021_14-49.csv └── allMetacogFits.mat ├── figs ├── .DS_Store ├── RRST_MP_suppfig_1.png ├── RRST_MP_suppfig_2.png ├── RRST_MP_suppfig_3.png ├── RRST_MP_suppfig_4.png ├── RRST_MP_suppfig_5.png ├── RRST_devicePhoto.png ├── figure_1.png ├── figure_2.png ├── figure_3.png ├── figure_4.png ├── figure_5.png ├── figure_6.png ├── figure_7.png ├── figure_8.png ├── figure_9.png └── graphicalAbstract_revision.png └── task ├── .DS_Store ├── RRS_2-2 ├── code │ ├── loadParams.m │ ├── main.m │ └── respiroceptionTutorial.m └── experimentLauncher.m ├── RRS_3-0 ├── code │ ├── loadParams.m │ ├── main.m │ └── respiroceptionTutorial.m └── experimentLauncher.m ├── apparatus ├── .DS_Store ├── version_2-2 │ └── 3dPrintStlFiles │ │ ├── .DS_Store │ │ ├── Vice_clamp.stl │ │ ├── Vice_top.stl │ │ └── drawer.stl └── version_3-0 │ ├── .DS_Store │ ├── 3d_print_files.zip │ ├── Connection board v10.pdf │ ├── pcb.zip │ ├── pcb │ ├── .DS_Store │ └── CAMOutputs │ │ ├── .DS_Store │ │ ├── Assembly │ │ ├── Connection board v24.txt │ │ ├── PnP_Connection board v24_back.txt │ │ └── PnP_Connection board v24_front.txt │ │ ├── DrillFiles │ │ └── drill_1_16.xln │ │ └── GerberFiles │ │ ├── copper_bottom.gbr │ │ ├── copper_top.gbr │ │ ├── gerber_job.gbrjob │ │ ├── profile.gbr │ │ ├── silkscreen_bottom.gbr │ │ ├── silkscreen_top.gbr │ │ ├── soldermask_bottom.gbr │ │ ├── soldermask_top.gbr │ │ ├── solderpaste_bottom.gbr │ │ └── solderpaste_top.gbr │ └── photos │ ├── 20220426_095017.jpg │ ├── 20220426_095023.jpg │ └── 20220520_141144 (2).jpg ├── arduino └── RRST_Arduino │ └── RRST_Arduino.ino ├── helpers ├── Dependencies.txt ├── PAL_AMUD_updateUD_NN.m ├── SetupRand.m ├── angle2pix.m ├── audio │ └── audioToPlayGoesHere.txt ├── displayConfig.m ├── drawExpandingRing.m ├── expandingRing60.avi ├── expandingRing60_hiRes.avi ├── experimentEnd.m ├── getConfidence.m ├── getPMFbasedTrials.m ├── getResponse.m ├── keyConfig.m ├── loadFiles.m ├── makeMovie.m ├── mixArray.m ├── multSubPMF.m ├── playSound.m ├── randInRange.m ├── setupNdownStaircase.m ├── showExpandingRing.m ├── showInstruction.m ├── simplePMFplot.m ├── simpleUDplot.m ├── slideScale.m ├── struct2csv.m ├── subjectReportRRS.m ├── subjectReportRRS_physio.m └── updateStaircase.m ├── respDeviceHelpers_2-2 ├── moveInIncrements.m ├── moveResp.m ├── moveResp2ITIpos.m ├── moveResp2NoLoad.m ├── resetResp.m ├── scale2motorstep.m ├── setupResp.m ├── testMovements.m └── testRespDevice.m └── respDeviceHelpers_3-0 ├── calibrateResp.m ├── moveInIncrements.m ├── moveResp.m ├── moveResp2ITIpos.m ├── moveResp2NoLoad.m ├── scale2motorstep.m ├── setupResp.m ├── testMovements.m └── testRespDevice.m /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Respiratory Resistance Sensitivity Task: An Automated Method for Quantifying Respiratory Interoception and Metacognition 2 | 3 | This repository contains data, code and supporting documents for the manuscript: 4 | 5 | https://www.biorxiv.org/content/10.1101/2021.10.14.464418v1 6 | 7 | Please cite as: 8 | 9 | >Nikolova, N., Harrison, O. K., Toohey, S., Braendholt, M., Legrand, N., Correa, C. C., Vejlo, M., Fardo, F., & Allen, M. (2021). The Respiratory Resistance Sensitivity Task: An Automated Method for Quantifying Respiratory Interoception and Metacognition. BioRxiv, 2021.10.14.464418. https://doi.org/10.1101/2021.10.14.464418 10 | 11 | # Abstract 12 | >The ability to sense, monitor, and control respiration - e.g., respiratory interoception (henceforth, respiroception) is a core homeostatic ability. Beyond the regulation of gas exchange, enhanced awareness of respiratory sensations is directly related to psychiatric symptoms such as panic and anxiety. Indeed, chronic breathlessness (dyspnea) is associated with a fourfold increase in the risk of developing depression and anxiety, and the regulation of the breath is a key aspect of many mindfulness-based approaches to the treatment of mental illness. Physiologically speaking, the ability to accurately monitor respiratory sensations is important for optimizing cardiorespiratory function during athletic exertion, and can be a key indicator of illness. Given the important role of respiroception in mental and physical health, it is unsurprising that there is increased interest in the quantification of respiratory psychophysiology across different perceptual and metacognitive levels of the psychological hierarchy. Compared to other more popular modalities of interoception, such as in the cardiac domain, there are relatively few methods available for measuring aspects of respiroception. Existing inspiratory loading tasks are difficult to administer and frequently require expensive medical equipment, or offer poor granularity in their quantification of respiratory-related perceptual ability. To facilitate the study of respiroception, we here present a new, fully automated and computer-controlled apparatus and psychophysiological method, which can flexibly and easily measure respiratory-related interoceptive sensitivity, bias and metacognition, in as little as 30 minutes of testing, using easy to make 3D printable parts. 13 | 14 | # Data 15 | All anonymized trial and group-level data associated with this manuscript can be found here: 16 | 17 | https://github.com/embodied-computation-group/RespiroceptionMethodsPaper/tree/main/data 18 | 19 | # Code 20 | 21 | This repository contains all needed code to run the RRST itself, as well as associated data preprocessing and analysis scripts. 22 | 23 | # Figures 24 | 25 | ## Figure 1 - The Respiratory Resistance Sensitivity Task - Apparatus and Design 26 | f1 27 | 28 | >Figure 1: The Respiratory Resistance Discrimination Task. A) Trial schematic depicting the 2-interval forced choice (2IFC) design of the task. On each trial participants view a circular cue instructing them to prepare to inhale. The circle then blinks and begins expanding, with the participant instructed to sharply inhale with the expansion of the circle. The participant then exhales, and a second similarly guided breath is conducted. This procedure of pacing the participant’s breathing via visual cues is a novel feature of the RRST, and is intended to reduce intra- and inter-subject variance in respiratory effort. Following the two breaths, the participant indicates by keyboard press whether the first or second breath was more difficult. B) Sample single subject data, illustrating the psychophysical procedure. On each trial, one of two breaths is randomly signal minus (s-), such that the compression wedge is at resting baseline (0% obstruction) with no added resistance, and the other signal plus (s+) with some level of compression determined by the staircase procedure. The procedure rapidly hones in on a threshold estimate using a Bayesian procedure (psi); in this example the participant threshold of approximately 80% obstruction is found within just 20 trials. C) Schematic illustrating the design of the automated resistive load apparatus (see Supplementary Material 1 - Detailed schematic for details). 29 | 30 | ## Figure 2 - Respiratory Psychophysical Functions 31 | f2 32 | 33 | >Figure 2: Psychometric task results. A) Plot depicting trial-by-trial Psi threshold estimates for all participants. Light gray lines depict individual stimulus traces indicating the % of tube obstruction on the stimulus breath for each trial, the thick green line represents grand mean stimulus on each trial +- SEM. In general, threshold estimates stabilize around trials 20-40 for all participants. B) PMF fits for all subjects. The green lines depict individuals’ PMF fits, and grey points show stimulus levels presented, where the dot size indicates the number of times presented. C) Grand mean psychometric fit (green) overlaid on individual PMF fits (grey), demonstrating that average respiratory thresholds are around 66% airway obstruction, with substantive inter-individual variance around this value. D) Raincloud plots (Allen, Poggiali, et al., 2019) depicting individual threshold (green) and slope (orange) estimates for all subjects. 34 | 35 | ## Figure 3 - Task Accuracy, Reaction Time, and Accuracy 36 | f3 37 | 38 | >Figure 3: Type 1 performance on Psi & QUEST methods. Raincloud plots of reaction times (RT, left panel) and stimulus level (right panel) by accuracy (correct vs. incorrect) for the Psi (upper panel) and QUEST (lower panel) staircase methods. A) & C) Median RTs presented for each subject, for correct (green) and incorrect (orange) trials showing that RTs on correct trials are lower than on incorrect trials. B) & D) Average stimulus levels presented for each subject, for correct and incorrect trials, showing that stimuli were higher (i.e., easier) on correct trials. These results indicate good overall convergence of estimated psychometric thresholds. 39 | 40 | ## Figure 4 - Staircase Convergence and Reliability 41 | f4 42 | 43 | >Figure 4: Staircase convergence and Task Reliability. A) Standard errors of the threshold estimate by trial number obtained with Psi indicate that reliable threshold estimates are derived within 20-50 trials. B) Standard errors of the slope estimate by trial number show that slope uncertainty drops linearly as a function of trials, indicating that slope estimates may benefit from higher trial numbers and/or hierarchical modelling. C) The difference to the final threshold estimates by trial number obtained with QUEST indicates that threshold estimates also converge within 20-50 trials. D) Correlation plot across the two (counter-balanced) QUEST and Psi threshold estimates indicates a high within-subject reliability of respiroceptive thresholds, regardless of estimation technique. 44 | 45 | ## Figure 5 - Respiratory Metacognition 46 | f5 47 | 48 | >Figure 5: Type 2 performance. A) Raincloud plot showing stimulus level by confidence rating, showing higher confidence ratings for stimuli with greater resistance. B) Histogram of binned confidence ratings for correct (green) and incorrect (orange) trials. Generally, participants showed high metacognitive sensitivity, as seen in the dissociation between the correct and incorrect trial ratings. C) Type 2 ROC curve, averaged over participants, showing good respiroceptive type 2 performance. D) Type 1 (accuracy, green) and type 2 (aROC, orange) performance, sorted by each participant’s aROC. Participants show substantial variations in metacognition while type 1 accuracy is held relatively constant by the Psi staircase procedure. 49 | 50 | ## Figure 6 - Stimulus Averiseness and other Symptoms 51 | f6 52 | 53 | >Figure 6: Task tolerance & subjective ratings. A) Plot depicting aversiveness ratings across all 10 blocks (timepoints) of testing. Mean aversiveness ratings after each block are shown in green, and the shaded gray area represents standard error of the mean (SEM). Each block comprised 20 trials, for a total testing time of approximately 45 minutes. Participants on average reported roughly 20% stimulus aversiveness (out of 100 total), which remains stable throughout the testing period. This indicates that the stimuli were mildly unpleasant and that extended testing time did not increase task adversity within these limits. B) Plot depicting mean dizziness, breathlessness and asthma symptoms across participants. Bar height represents mean ratings, error bars denote SEM, and gray circles show individual participants’ ratings. In general, participants showed low levels of these adverse effects following 1 full hour of testing, indicating good tolerability of the task. 54 | 55 | 56 | 57 | # Device specifications and parts 58 | 59 | ## May 2022 60 | We have made an update to the RRST appratus harware and software. 61 | An updated parts list for the device is available at: https://docs.google.com/spreadsheets/d/1Xm-xjyxZgqfn2RlGidUxHrNGNTMF7fd8MpfaUctbE9U/edit?usp=sharing 62 | New versions of the 3d printing files, PCB design, and code for running the task are available in ./task/RRS_3-0 63 | Instructions for setting up and running the RRST can be found here: https://docs.google.com/document/d/1KxjABdMcecnZfaXCnsQhJQzVfaNqk7DXVoDFSJKMT78/edit?usp=sharing 64 | (Note that these were created for data collection during the COVID pandemic, and therefore include more sanitisation procedures than would usually be necessary.) 65 | 66 | -------------------------------------------------------------------------------- /code/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/code/.DS_Store -------------------------------------------------------------------------------- /code/analyses/RespiroceptionAnalysis.mlx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/code/analyses/RespiroceptionAnalysis.mlx -------------------------------------------------------------------------------- /code/analyses/helperFunctions/GetFields.m: -------------------------------------------------------------------------------- 1 | function Value = GetFields(S, Name) 2 | %Value = GetFields(S, Name) 3 | % 4 | % For a structure S and fieldname 'Name', returns an array with all entries 5 | % in that field 6 | % 7 | % Niia 02/2021 8 | 9 | Value = cell(size(Name)); 10 | Data = struct2cell(S); 11 | Value = Data(1,:)'; -------------------------------------------------------------------------------- /code/analyses/helperFunctions/ciplot.m: -------------------------------------------------------------------------------- 1 | function ciplot(lower,upper,x,colour); 2 | 3 | % ciplot(lower,upper) 4 | % ciplot(lower,upper,x) 5 | % ciplot(lower,upper,x,colour) 6 | % 7 | % Plots a shaded region on a graph between specified lower and upper confidence intervals (L and U). 8 | % l and u must be vectors of the same length. 9 | % Uses the 'fill' function, not 'area'. Therefore multiple shaded plots 10 | % can be overlayed without a problem. Make them transparent for total visibility. 11 | % x data can be specified, otherwise plots against index values. 12 | % colour can be specified (eg 'k'). Defaults to blue. 13 | 14 | % Raymond Reynolds 24/11/06 15 | 16 | if length(lower)~=length(upper) 17 | error('lower and upper vectors must be same length') 18 | end 19 | 20 | if nargin<4 21 | colour='b'; 22 | end 23 | 24 | if nargin<3 25 | x=1:length(lower); 26 | end 27 | 28 | % convert to row vectors so fliplr can work 29 | if find(size(x)==(max(size(x))))<2 30 | x=x'; end 31 | if find(size(lower)==(max(size(lower))))<2 32 | lower=lower'; end 33 | if find(size(upper)==(max(size(upper))))<2 34 | upper=upper'; end 35 | 36 | fill([x fliplr(x)],[upper fliplr(lower)],colour,.... 37 | 'facecolor',colour, ... 38 | 'edgecolor','none', ... 39 | 'facealpha', 0.5) 40 | 41 | 42 | -------------------------------------------------------------------------------- /code/analyses/helperFunctions/intersections.m: -------------------------------------------------------------------------------- 1 | function [x0,y0,iout,jout] = intersections(x1,y1,x2,y2,robust) 2 | %INTERSECTIONS Intersections of curves. 3 | % Computes the (x,y) locations where two curves intersect. The curves 4 | % can be broken with NaNs or have vertical segments. 5 | % 6 | % Example: 7 | % [X0,Y0] = intersections(X1,Y1,X2,Y2,ROBUST); 8 | % 9 | % where X1 and Y1 are equal-length vectors of at least two points and 10 | % represent curve 1. Similarly, X2 and Y2 represent curve 2. 11 | % X0 and Y0 are column vectors containing the points at which the two 12 | % curves intersect. 13 | % 14 | % ROBUST (optional) set to 1 or true means to use a slight variation of the 15 | % algorithm that might return duplicates of some intersection points, and 16 | % then remove those duplicates. The default is true, but since the 17 | % algorithm is slightly slower you can set it to false if you know that 18 | % your curves don't intersect at any segment boundaries. Also, the robust 19 | % version properly handles parallel and overlapping segments. 20 | % 21 | % The algorithm can return two additional vectors that indicate which 22 | % segment pairs contain intersections and where they are: 23 | % 24 | % [X0,Y0,I,J] = intersections(X1,Y1,X2,Y2,ROBUST); 25 | % 26 | % For each element of the vector I, I(k) = (segment number of (X1,Y1)) + 27 | % (how far along this segment the intersection is). For example, if I(k) = 28 | % 45.25 then the intersection lies a quarter of the way between the line 29 | % segment connecting (X1(45),Y1(45)) and (X1(46),Y1(46)). Similarly for 30 | % the vector J and the segments in (X2,Y2). 31 | % 32 | % You can also get intersections of a curve with itself. Simply pass in 33 | % only one curve, i.e., 34 | % 35 | % [X0,Y0] = intersections(X1,Y1,ROBUST); 36 | % 37 | % where, as before, ROBUST is optional. 38 | 39 | % Version: 2.0, 25 May 2017 40 | % Author: Douglas M. Schwarz 41 | % Email: dmschwarz=ieee*org, dmschwarz=urgrad*rochester*edu 42 | % Real_email = regexprep(Email,{'=','*'},{'@','.'}) 43 | 44 | 45 | % Theory of operation: 46 | % 47 | % Given two line segments, L1 and L2, 48 | % 49 | % L1 endpoints: (x1(1),y1(1)) and (x1(2),y1(2)) 50 | % L2 endpoints: (x2(1),y2(1)) and (x2(2),y2(2)) 51 | % 52 | % we can write four equations with four unknowns and then solve them. The 53 | % four unknowns are t1, t2, x0 and y0, where (x0,y0) is the intersection of 54 | % L1 and L2, t1 is the distance from the starting point of L1 to the 55 | % intersection relative to the length of L1 and t2 is the distance from the 56 | % starting point of L2 to the intersection relative to the length of L2. 57 | % 58 | % So, the four equations are 59 | % 60 | % (x1(2) - x1(1))*t1 = x0 - x1(1) 61 | % (x2(2) - x2(1))*t2 = x0 - x2(1) 62 | % (y1(2) - y1(1))*t1 = y0 - y1(1) 63 | % (y2(2) - y2(1))*t2 = y0 - y2(1) 64 | % 65 | % Rearranging and writing in matrix form, 66 | % 67 | % [x1(2)-x1(1) 0 -1 0; [t1; [-x1(1); 68 | % 0 x2(2)-x2(1) -1 0; * t2; = -x2(1); 69 | % y1(2)-y1(1) 0 0 -1; x0; -y1(1); 70 | % 0 y2(2)-y2(1) 0 -1] y0] -y2(1)] 71 | % 72 | % Let's call that A*T = B. We can solve for T with T = A\B. 73 | % 74 | % Once we have our solution we just have to look at t1 and t2 to determine 75 | % whether L1 and L2 intersect. If 0 <= t1 < 1 and 0 <= t2 < 1 then the two 76 | % line segments cross and we can include (x0,y0) in the output. 77 | % 78 | % In principle, we have to perform this computation on every pair of line 79 | % segments in the input data. This can be quite a large number of pairs so 80 | % we will reduce it by doing a simple preliminary check to eliminate line 81 | % segment pairs that could not possibly cross. The check is to look at the 82 | % smallest enclosing rectangles (with sides parallel to the axes) for each 83 | % line segment pair and see if they overlap. If they do then we have to 84 | % compute t1 and t2 (via the A\B computation) to see if the line segments 85 | % cross, but if they don't then the line segments cannot cross. In a 86 | % typical application, this technique will eliminate most of the potential 87 | % line segment pairs. 88 | 89 | 90 | % Input checks. 91 | if verLessThan('matlab','7.13') 92 | error(nargchk(2,5,nargin)) %#ok 93 | else 94 | narginchk(2,5) 95 | end 96 | 97 | % Adjustments based on number of arguments. 98 | switch nargin 99 | case 2 100 | robust = true; 101 | x2 = x1; 102 | y2 = y1; 103 | self_intersect = true; 104 | case 3 105 | robust = x2; 106 | x2 = x1; 107 | y2 = y1; 108 | self_intersect = true; 109 | case 4 110 | robust = true; 111 | self_intersect = false; 112 | case 5 113 | self_intersect = false; 114 | end 115 | 116 | % x1 and y1 must be vectors with same number of points (at least 2). 117 | if sum(size(x1) > 1) ~= 1 || sum(size(y1) > 1) ~= 1 || ... 118 | length(x1) ~= length(y1) 119 | error('X1 and Y1 must be equal-length vectors of at least 2 points.') 120 | end 121 | % x2 and y2 must be vectors with same number of points (at least 2). 122 | if sum(size(x2) > 1) ~= 1 || sum(size(y2) > 1) ~= 1 || ... 123 | length(x2) ~= length(y2) 124 | error('X2 and Y2 must be equal-length vectors of at least 2 points.') 125 | end 126 | 127 | 128 | % Force all inputs to be column vectors. 129 | x1 = x1(:); 130 | y1 = y1(:); 131 | x2 = x2(:); 132 | y2 = y2(:); 133 | 134 | % Compute number of line segments in each curve and some differences we'll 135 | % need later. 136 | n1 = length(x1) - 1; 137 | n2 = length(x2) - 1; 138 | xy1 = [x1 y1]; 139 | xy2 = [x2 y2]; 140 | dxy1 = diff(xy1); 141 | dxy2 = diff(xy2); 142 | 143 | 144 | % Determine the combinations of i and j where the rectangle enclosing the 145 | % i'th line segment of curve 1 overlaps with the rectangle enclosing the 146 | % j'th line segment of curve 2. 147 | 148 | % Original method that works in old MATLAB versions, but is slower than 149 | % using binary singleton expansion (explicit or implicit). 150 | % [i,j] = find( ... 151 | % repmat(mvmin(x1),1,n2) <= repmat(mvmax(x2).',n1,1) & ... 152 | % repmat(mvmax(x1),1,n2) >= repmat(mvmin(x2).',n1,1) & ... 153 | % repmat(mvmin(y1),1,n2) <= repmat(mvmax(y2).',n1,1) & ... 154 | % repmat(mvmax(y1),1,n2) >= repmat(mvmin(y2).',n1,1)); 155 | 156 | % Select an algorithm based on MATLAB version and number of line 157 | % segments in each curve. We want to avoid forming large matrices for 158 | % large numbers of line segments. If the matrices are not too large, 159 | % choose the best method available for the MATLAB version. 160 | if n1 > 1000 || n2 > 1000 || verLessThan('matlab','7.4') 161 | % Determine which curve has the most line segments. 162 | if n1 >= n2 163 | % Curve 1 has more segments, loop over segments of curve 2. 164 | ijc = cell(1,n2); 165 | min_x1 = mvmin(x1); 166 | max_x1 = mvmax(x1); 167 | min_y1 = mvmin(y1); 168 | max_y1 = mvmax(y1); 169 | for k = 1:n2 170 | k1 = k + 1; 171 | ijc{k} = find( ... 172 | min_x1 <= max(x2(k),x2(k1)) & max_x1 >= min(x2(k),x2(k1)) & ... 173 | min_y1 <= max(y2(k),y2(k1)) & max_y1 >= min(y2(k),y2(k1))); 174 | ijc{k}(:,2) = k; 175 | end 176 | ij = vertcat(ijc{:}); 177 | i = ij(:,1); 178 | j = ij(:,2); 179 | else 180 | % Curve 2 has more segments, loop over segments of curve 1. 181 | ijc = cell(1,n1); 182 | min_x2 = mvmin(x2); 183 | max_x2 = mvmax(x2); 184 | min_y2 = mvmin(y2); 185 | max_y2 = mvmax(y2); 186 | for k = 1:n1 187 | k1 = k + 1; 188 | ijc{k}(:,2) = find( ... 189 | min_x2 <= max(x1(k),x1(k1)) & max_x2 >= min(x1(k),x1(k1)) & ... 190 | min_y2 <= max(y1(k),y1(k1)) & max_y2 >= min(y1(k),y1(k1))); 191 | ijc{k}(:,1) = k; 192 | end 193 | ij = vertcat(ijc{:}); 194 | i = ij(:,1); 195 | j = ij(:,2); 196 | end 197 | 198 | elseif verLessThan('matlab','9.1') 199 | % Use bsxfun. 200 | [i,j] = find( ... 201 | bsxfun(@le,mvmin(x1),mvmax(x2).') & ... 202 | bsxfun(@ge,mvmax(x1),mvmin(x2).') & ... 203 | bsxfun(@le,mvmin(y1),mvmax(y2).') & ... 204 | bsxfun(@ge,mvmax(y1),mvmin(y2).')); 205 | 206 | else 207 | % Use implicit expansion. 208 | [i,j] = find( ... 209 | mvmin(x1) <= mvmax(x2).' & mvmax(x1) >= mvmin(x2).' & ... 210 | mvmin(y1) <= mvmax(y2).' & mvmax(y1) >= mvmin(y2).'); 211 | 212 | end 213 | 214 | 215 | % Find segments pairs which have at least one vertex = NaN and remove them. 216 | % This line is a fast way of finding such segment pairs. We take 217 | % advantage of the fact that NaNs propagate through calculations, in 218 | % particular subtraction (in the calculation of dxy1 and dxy2, which we 219 | % need anyway) and addition. 220 | % At the same time we can remove redundant combinations of i and j in the 221 | % case of finding intersections of a line with itself. 222 | if self_intersect 223 | remove = isnan(sum(dxy1(i,:) + dxy2(j,:),2)) | j <= i + 1; 224 | else 225 | remove = isnan(sum(dxy1(i,:) + dxy2(j,:),2)); 226 | end 227 | i(remove) = []; 228 | j(remove) = []; 229 | 230 | % Initialize matrices. We'll put the T's and B's in matrices and use them 231 | % one column at a time. AA is a 3-D extension of A where we'll use one 232 | % plane at a time. 233 | n = length(i); 234 | T = zeros(4,n); 235 | AA = zeros(4,4,n); 236 | AA([1 2],3,:) = -1; 237 | AA([3 4],4,:) = -1; 238 | AA([1 3],1,:) = dxy1(i,:).'; 239 | AA([2 4],2,:) = dxy2(j,:).'; 240 | B = -[x1(i) x2(j) y1(i) y2(j)].'; 241 | 242 | % Loop through possibilities. Trap singularity warning and then use 243 | % lastwarn to see if that plane of AA is near singular. Process any such 244 | % segment pairs to determine if they are colinear (overlap) or merely 245 | % parallel. That test consists of checking to see if one of the endpoints 246 | % of the curve 2 segment lies on the curve 1 segment. This is done by 247 | % checking the cross product 248 | % 249 | % (x1(2),y1(2)) - (x1(1),y1(1)) x (x2(2),y2(2)) - (x1(1),y1(1)). 250 | % 251 | % If this is close to zero then the segments overlap. 252 | 253 | % If the robust option is false then we assume no two segment pairs are 254 | % parallel and just go ahead and do the computation. If A is ever singular 255 | % a warning will appear. This is faster and obviously you should use it 256 | % only when you know you will never have overlapping or parallel segment 257 | % pairs. 258 | 259 | if robust 260 | overlap = false(n,1); 261 | warning_state = warning('off','MATLAB:singularMatrix'); 262 | % Use try-catch to guarantee original warning state is restored. 263 | try 264 | lastwarn('') 265 | for k = 1:n 266 | T(:,k) = AA(:,:,k)\B(:,k); 267 | [unused,last_warn] = lastwarn; %#ok 268 | lastwarn('') 269 | if strcmp(last_warn,'MATLAB:singularMatrix') 270 | % Force in_range(k) to be false. 271 | T(1,k) = NaN; 272 | % Determine if these segments overlap or are just parallel. 273 | overlap(k) = rcond([dxy1(i(k),:);xy2(j(k),:) - xy1(i(k),:)]) < eps; 274 | end 275 | end 276 | warning(warning_state) 277 | catch err 278 | warning(warning_state) 279 | rethrow(err) 280 | end 281 | % Find where t1 and t2 are between 0 and 1 and return the corresponding 282 | % x0 and y0 values. 283 | in_range = (T(1,:) >= 0 & T(2,:) >= 0 & T(1,:) <= 1 & T(2,:) <= 1).'; 284 | % For overlapping segment pairs the algorithm will return an 285 | % intersection point that is at the center of the overlapping region. 286 | if any(overlap) 287 | ia = i(overlap); 288 | ja = j(overlap); 289 | % set x0 and y0 to middle of overlapping region. 290 | T(3,overlap) = (max(min(x1(ia),x1(ia+1)),min(x2(ja),x2(ja+1))) + ... 291 | min(max(x1(ia),x1(ia+1)),max(x2(ja),x2(ja+1)))).'/2; 292 | T(4,overlap) = (max(min(y1(ia),y1(ia+1)),min(y2(ja),y2(ja+1))) + ... 293 | min(max(y1(ia),y1(ia+1)),max(y2(ja),y2(ja+1)))).'/2; 294 | selected = in_range | overlap; 295 | else 296 | selected = in_range; 297 | end 298 | xy0 = T(3:4,selected).'; 299 | 300 | % Remove duplicate intersection points. 301 | [xy0,index] = unique(xy0,'rows'); 302 | x0 = xy0(:,1); 303 | y0 = xy0(:,2); 304 | 305 | % Compute how far along each line segment the intersections are. 306 | if nargout > 2 307 | sel_index = find(selected); 308 | sel = sel_index(index); 309 | iout = i(sel) + T(1,sel).'; 310 | jout = j(sel) + T(2,sel).'; 311 | end 312 | else % non-robust option 313 | for k = 1:n 314 | [L,U] = lu(AA(:,:,k)); 315 | T(:,k) = U\(L\B(:,k)); 316 | end 317 | 318 | % Find where t1 and t2 are between 0 and 1 and return the corresponding 319 | % x0 and y0 values. 320 | in_range = (T(1,:) >= 0 & T(2,:) >= 0 & T(1,:) < 1 & T(2,:) < 1).'; 321 | x0 = T(3,in_range).'; 322 | y0 = T(4,in_range).'; 323 | 324 | % Compute how far along each line segment the intersections are. 325 | if nargout > 2 326 | iout = i(in_range) + T(1,in_range).'; 327 | jout = j(in_range) + T(2,in_range).'; 328 | end 329 | end 330 | 331 | % Plot the results (useful for debugging). 332 | % plot(x1,y1,x2,y2,x0,y0,'ok'); 333 | 334 | function y = mvmin(x) 335 | % Faster implementation of movmin(x,k) when k = 1. 336 | y = min(x(1:end-1),x(2:end)); 337 | 338 | function y = mvmax(x) 339 | % Faster implementation of movmax(x,k) when k = 1. 340 | y = max(x(1:end-1),x(2:end)); 341 | -------------------------------------------------------------------------------- /code/analyses/helperFunctions/new_bin_ratings.m: -------------------------------------------------------------------------------- 1 | function responseConf = new_bin_ratings(slidingCon, Nbins) 2 | % function responseConf = new_bin_ratings(slidingCon, Nbins) 3 | % 4 | % converts a vector of VAS confidence ratings to discrete ratings from 5 | % 1:Nbins 6 | % 7 | % Input: 8 | % slidingCon vecor of VAS ratings *for a single subject & condition* 9 | % Nbins number of bins to divide into (usually 4) 10 | % Output: 11 | % responseConf vector of dicrete confidence rartings 12 | 13 | % Resample if quantiles are equal at high or low end to ensure proper 14 | % assignment of binned confidence 15 | % Nbins = 4; 16 | confBins = quantile(slidingCon,linspace(0,1,Nbins+1)); 17 | if confBins(1) == confBins(2) & confBins(Nbins) == confBins(Nbins+1) 18 | error('Bad bins!') 19 | elseif confBins(Nbins) == confBins(Nbins+1) 20 | disp('Lots of high confidence ratings'); 21 | % exclude high confidence trials and re-estimate 22 | hiConf = confBins(Nbins); 23 | temp{Nbins} = slidingCon == hiConf; 24 | confBins = quantile(slidingCon(~temp{Nbins}),linspace(0,1,Nbins)); 25 | for b = 1:length(confBins)-1; 26 | temp{b} = slidingCon >= confBins(b) & slidingCon <= confBins(b+1); 27 | if b==length(confBins)-1 28 | temp{b} = slidingCon >= confBins(b) & slidingCon <= confBins(b+1); 29 | end 30 | end 31 | out.confBins = [confBins hiConf]; 32 | out.rebin = 1; 33 | elseif confBins(1) == confBins(2) 34 | disp('Lots of low confidence ratings'); 35 | % exclude low confidence trials and re-estimate 36 | lowConf = confBins(2); 37 | temp{1} = slidingCon == lowConf; 38 | confBins = quantile(slidingCon(~temp{1}),linspace(0,1,Nbins)); 39 | %for b = 2:length(confBins)-1; 40 | for b = 2:length(confBins); %JR 41 | temp{b} = slidingCon >= confBins(b-1) & slidingCon <= confBins(b); 42 | if b==length(confBins) 43 | temp{b} = slidingCon >= confBins(b-1) & slidingCon <= confBins(b); 44 | end 45 | end 46 | out.confBins = [lowConf confBins]; 47 | out.rebin = 1; 48 | else 49 | for b = 1:length(confBins)-1; 50 | temp{b} = slidingCon >= confBins(b) & slidingCon <= confBins(b+1); 51 | if b==length(confBins)-1 52 | temp{b} = slidingCon >= confBins(b) & slidingCon <= confBins(b+1); 53 | end 54 | end 55 | out.confBins = confBins; 56 | out.rebin = 0; 57 | end 58 | 59 | for b = 1:Nbins 60 | responseConf(temp{b}) = b; 61 | out.binCount = sum(temp{b}); 62 | end 63 | 64 | end -------------------------------------------------------------------------------- /code/suppAnalyses/FDTvsRRST.jasp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/code/suppAnalyses/FDTvsRRST.jasp -------------------------------------------------------------------------------- /data/FDTvsRRST.csv: -------------------------------------------------------------------------------- 1 | PPID;RRST Slope;FDT Resistance;RRST Resistance;FDT Pressure;RRST Pressure;FDT Pressure;FDT Confidence;FDT AROC;RRST ConfRaw;RRST Confidence;RRST AROC;FDT accuracy;RRST accuracy 2 | 0001;1,15619226;2,015798826;2,382586792;2,53868115;0,87287954;2,53868115;6,25;0,62719704;54,92984694;5,492984694;0,589055764;0,7;0,77 3 | 0002;;1,10022859;3,360827986;0,644875901;4,468833221;0,644875901;4,7;0,671993272;38,41875;3,841875;0,617487923;0,6;0,8 4 | 0003;;0,637680362;1,491218066;1,103600756;1,542509578;1,103600756;3,616666667;0,622735507;60,33125;6,033125;0,595870827;0,683333333;0,78 5 | 0004;0,86575315;0,690408185;2,324917253;0,726013147;2,144940419;0,726013147;6,083333333;0,613264427;66,2875;6,62875;0,631656525;0,633333333;0,78 6 | 0005;0,683086155;1,278655802;1,234943607;0,752612255;0,835647961;0,752612255;3,733333333;0,611971104;48,6125;4,86125;0,59975047;0,766666667;0,76 7 | 0006;1,171386432;1,412316301;3,589803693;1,111620618;3,159208143;1,111620618;5,983333333;0,674242424;59,88125;5,988125;0,575463236;0,716666667;0,78 8 | 0007;0,867488185;4,441563438;3,450366768;0,94470548;1,557719573;0,94470548;7,266666667;0,721064815;77,2;7,72;0,679828761;0,816666667;0,81 9 | 0008;1,029004243;1,90835631;30,4596875;1,32523928;4,458497681;1,32523928;8,066666667;0,551630435;64,66666667;6,466666667;0,646365666;0,683333333;0,8 10 | 0009;0,751629523;0,805302481;1,130419486;0,597606106;1,175926139;0,597606106;5,4;0,658764368;47,7375;4,77375;0,594539001;0,883333333;0,77 11 | 0010;2,180803997;2,50524504;46,25380222;1,743686992;9,763626782;1,743686992;7,266666667;0,62833764;83,578125;8,3578125;0,708630478;0,633333333;0,8 12 | 0011;2,472833083;4,216097829;1,28579554;2,646991187;1,913683641;;;;63,4625;6,34625;0,591643765;;0,79 13 | 0012;1,028882842;;3,869974995;;2,91088169;1,756395206;4,533333333;0,433660934;61,14477041;6,114477041;0,609602407;0,533333333;0,77 14 | 0013;1,136951574;1,541115597;1,560076011;1,250372216;2,209294913;1,250372216;2,766666667;0,600852273;36,321875;3,6321875;0,532502869;0,716666667;0,78 15 | 0014;1,096521046;2,155724782;1,382911036;0,990695495;0,989519747;0,990695495;4,366666667;0,714209402;41,759375;4,1759375;0,543992392;0,783333333;0,81 16 | 0015;0,463103808;0,770806808;15,08018017;0,564379334;3,223942799;0,564379334;3,85;0,6365;44,91875;4,491875;0,594408134;0,75;0,79 -------------------------------------------------------------------------------- /data/NZ_constant_flow_rrst.csv: -------------------------------------------------------------------------------- 1 | 0.012143864,0.013205665,-0.002300191,-4.41093123,-4.327109373,-6.074763167, -0.007427525,0.018846209,0.025367153,-4.902562519,-3.971443499,-3.674300143, 0.03552855,0.0056537,0.010316426,-3.337418667,-5.175445122,-4.574017882, 0.015290994,0.009600562,-0.004707241,-4.180491276,-4.645933669,-5.358653322, 0.001674046,0.031877223,0.01049036,-6.39251184,-3.445863531,-4.557298541, 0.024145466,0.01394032,-0.009270047,-3.723658672,-4.272969924,-4.680966798, 0.039348163,0.027388156,0.038074701,-3.235305991,-3.597644621,-3.268205231, 0.034540631,0.048547235,0.061258242,-3.365618947,-3.025218029,-2.792656877, 0.079888175,0.076406344,0.071784275,-2.527127432,-2.571689555,-2.63408984, 0.127633969,0.137267341,0.140410635,-2.058588725,-1.985824863,-1.96318404, 0.159094696,0.193330484,0.185467479,-1.838255681,-1.643354202,-1.684875727, 0.26467976,0.321121027,0.281256064,-1.329234635,-1.135937196,-1.268489764, 0.422626288,0.428246261,0.478910253,-0.86126697,-0.848056874,-0.736242062, 0.752357936,0.759110227,0.844926333,-0.284543089,-0.275608285,-0.168505835, 1.242614741,1.449481636,1.500894413,0.217217822,0.371206,0.406061206, 2.598700239,3.116515883,3.243425311,0.955011412,1.136715674,1.176629966, 13.25435617,19.0669566,21.35726777,2.584326266,2.947956816,3.061392093, ,,,,,, -------------------------------------------------------------------------------- /data/NZfdtRrstAccuracy.csv: -------------------------------------------------------------------------------- 1 | FDTaccuracy,RRSTaccuracy 0.7,0.77 0.6,0.8 0.683333333,0.78 0.633333333,0.78 0.766666667,0.76 0.716666667,0.78 0.816666667,0.81 0.683333333,0.8 0.883333333,0.77 0.633333333,0.8 ,0.79 0.533333333,0.77 0.716666667,0.78 0.783333333,0.81 0.75,0.79 -------------------------------------------------------------------------------- /data/NZfdtRrstConfidence.csv: -------------------------------------------------------------------------------- 1 | FDTconfidence,RRSTconfidence 6.25,54.92984694 4.7,38.41875 3.616666667,60.33125 6.083333333,66.2875 3.733333333,48.6125 5.983333333,59.88125 7.266666667,77.2 8.066666667,64.66666667 5.4,47.7375 7.266666667,83.578125 ,63.4625 4.533333333,61.14477041 2.766666667,36.321875 4.366666667,41.759375 3.85,44.91875 -------------------------------------------------------------------------------- /data/RRST_SubLevelData_15-Apr-2021_12-59.csv: -------------------------------------------------------------------------------- 1 | ID,psi_thresh,psi_slope,psi_acc,psi_dizzy,psi_breathless,psi_asthma,quest_thresh,quest_slope,quest_acc,quest_dizzy,quest_breathless,quest_asthma,displeasure1,displeasure2,displeasure3,displeasure4,displeasure5,displeasure6,displeasure7,displeasure8,displeasure9,displeasure10,gID,order,age,sex 2 | 1,10.2077793585577,5.31995215623529,0.8,NaN,NaN,NaN,9.58489363347148,1.5,0.85,NaN,NaN,NaN,0,0,0,NaN,0,0,NaN,NaN,NaN,NaN,1,2,33,1 3 | 2,10.5245515674706,3.95996616491009,0.81,4.94791666666667,3.64583333333333,0,11.0579052108831,1.5,0.816666666666667,16.9270833333333,10.9375,0,26.3020833333333,16.9270833333333,22.65625,21.6145833333333,5.20833333333333,8.59375,13.5416666666667,19.0104166666667,35.9375,31.25,2,1,32,2 4 | 3,11.6871086783346,7.52176207706684,0.8125,44.6614583333333,25.78125,0,11.9851887608571,1.5,0.816666666666667,34.6354166666667,28.90625,0,4.42708333333333,31.640625,16.40625,29.8177083333333,5.859375,23.1770833333333,31.5104166666667,36.4583333333333,NaN,38.0208333333333,3,2,35,1 5 | 4,14.3296586163953,9.31119600815159,0.84,0,53.90625,0,14.1688959268548,1.5,0.75,0,48.6979166666667,0,26.3020833333333,26.0416666666667,19.7916666666667,45.703125,50.2604166666667,46.3541666666667,56.5104166666667,38.1510416666667,53.125,52.8645833333333,4,1,34,2 6 | 5,11.9075953071011,7.86264802820801,0.81,56.7708333333333,25,0,12.6770473134412,1.5,0.783333333333333,63.1510416666667,8.59375,0,51.4322916666667,52.2135416666667,42.7083333333333,53.515625,52.4739583333333,49.7395833333333,49.0885416666667,50.78125,52.4739583333333,53.515625,5,2,24,2 7 | 6,11.8964602451713,5.30166367272575,0.81,62.5,63.5416666666667,14.1927083333333,9.76554255525523,1.5,0.816666666666667,61.8489583333333,71.09375,29.296875,60.9375,73.046875,55.7291666666667,69.921875,70.5729166666667,51.4322916666667,24.8697916666667,32.421875,29.0364583333333,23.828125,6,1,31,1 8 | 7,11.6026023208057,5.34680586303803,0.82,4.81770833333333,33.59375,5.20833333333333,10.6076688058184,1.5,0.816666666666667,0,0,6.51041666666667,0,0,0,0,0,0,0,0,0,0,7,2,30,1 9 | 8,12.8880435862866,8.59110584057851,0.81,45.0520833333333,24.7395833333333,0,13.2118800942753,1.5,0.766666666666667,63.0208333333333,46.6145833333333,0,27.0833333333333,37.2395833333333,39.3229166666667,47.1354166666667,63.0208333333333,61.9791666666667,66.9270833333333,73.6979166666667,59.6354166666667,68.4895833333333,8,1,20,2 10 | 9,12.0499446328411,4.5331526270702,0.8,0,0,0,11.8714742494578,1.5,0.8,0,0,0,0,0,0,100,0,0,0,0,0,0,9,2,22,1 11 | 10,15.2461690569337,12.2583637079108,0.83,25,39.5833333333333,0,15.2561324730343,1.5,0.683333333333333,55.7291666666667,40.3645833333333,0,56.25,58.3333333333333,59.8958333333333,53.3854166666667,48.9583333333333,36.4583333333333,42.4479166666667,49.4791666666667,50,39.84375,10,1,24,2 12 | 11,13.6730492362825,6.38493708470951,0.8,22.65625,25.2604166666667,0,13.1777638770051,1.5,0.75,8.07291666666667,6.51041666666667,0,0,0,0,0,0,14.0625,7.29166666666667,0,0,16.9270833333333,11,1,19,2 13 | 12,13.4830882362427,9.90164619508611,0.82,5.98958333333333,0,0,14.7945804976517,1.5,0.716666666666667,48.9583333333333,0,0,0,0,0,0,0,0,0,0,0,0,12,2,22,2 14 | 13,14.2460657592773,5.4301700551418,0.77,24.7395833333333,0,0,14.2867752223604,1.5,0.75,68.2291666666667,0,0,49.7395833333333,50.5208333333333,52.34375,53.6458333333333,52.6041666666667,48.1770833333333,66.6666666666667,73.1770833333333,54.9479166666667,31.5104166666667,13,1,21,1 15 | 14,11.0765941627615,11.4338100636681,0.81,54.6875,25.5208333333333,0,12.5250756632574,1.5,0.8,28.125,14.0625,0,13.8020833333333,16.1458333333333,18.2291666666667,21.6145833333333,27.6041666666667,32.8125,35.6770833333333,44.2708333333333,52.6041666666667,14.3229166666667,14,2,22,2 16 | 15,11.3204161766981,6.91240893209377,0.79,10.15625,12.5,2.60416666666667,11.6599694204097,1.5,0.816666666666667,8.33333333333333,0,2.34375,0,0,0,0,0,0,0,0,0,0,15,1,20,2 17 | 16,12.2709662676144,7.04275814068009,0.8,51.3020833333333,0,0,12.6842407699721,1.5,0.8,50.2604166666667,0,0,30.46875,0,0,13.0208333333333,0,0,20.0520833333333,13.8020833333333,19.0104166666667,15.3645833333333,16,2,26,2 18 | 18,13.2285633449264,10.3170042948316,0.84,63.5416666666667,34.8958333333333,0,15.0207135761199,1.5,0.7,58.8541666666667,49.4791666666667,0,51.8229166666667,50.2604166666667,48.1770833333333,37.5,48.6979166666667,51.0416666666667,52.6041666666667,51.3020833333333,52.0833333333333,48.9583333333333,18,2,24,2 19 | 19,12.9111714105461,3.11528837132479,0.79,0,0,33.8541666666667,12.6852228067213,1.5,0.8,0,0,20.3125,18.75,29.1666666666667,9.89583333333333,14.0625,0,0,0,2.60416666666667,0,0,19,1,39,1 20 | 20,16.1627430571408,12.3673497221616,0.82,82.2916666666667,28.6458333333333,0,16.136185745883,1.5,0.55,0,0,0,52.8645833333333,38.8020833333333,77.34375,34.375,66.40625,69.2708333333333,67.4479166666667,72.3958333333333,100,75.2604166666667,20,2,34,2 21 | 21,12.5062998084763,9.85156186963592,0.83,37.2395833333333,32.2916666666667,5.72916666666667,12.6164239164362,1.5,0.783333333333333,5.72916666666667,14.0625,4.94791666666667,6.25,8.85416666666667,5.46875,7.29166666666667,5.98958333333333,8.85416666666667,11.1979166666667,9.11458333333333,6.51041666666667,4.6875,21,1,19,1 22 | 22,11.7755424757847,7.88104872892216,0.79,0,0,0,11.6720887183121,1.5,0.816666666666667,0,0,0,0,25.5208333333333,32.03125,32.8125,30.46875,19.7916666666667,20.8333333333333,15.8854166666667,16.6666666666667,13.28125,22,2,27,1 23 | 23,10.8573861284089,7.19412225980916,0.81,71.09375,0,0,11.0681318828783,1.5,0.816666666666667,32.8125,0,0,50,52.6041666666667,50.78125,45.3125,50.5208333333333,70.0520833333333,46.09375,53.6458333333333,55.46875,50.2604166666667,23,1,30,1 24 | 24,15.0413661643961,11.1437834459881,0.8,17.4479166666667,34.1145833333333,7.29166666666667,14.9837590639467,1.5,0.7,52.6041666666667,54.4270833333333,61.1979166666667,48.6979166666667,52.6041666666667,28.3854166666667,30.7291666666667,37.2395833333333,41.9270833333333,31.5104166666667,39.0625,25.78125,19.0104166666667,24,2,34,1 25 | 25,14.4568711358483,11.8130380501028,0.81,14.0625,12.7604166666667,0,13.3945481619895,1.5,0.766666666666667,8.85416666666667,4.94791666666667,0,0,0,0,0,0,0,0,7.8125,7.29166666666667,0,25,1,20,2 26 | 26,12.4226238284544,8.94166287309687,0.78,0,0,0,12.9171357992811,1.5,0.783333333333333,0,0,0,0,0,0,0,0,0,0,0,0,0,26,2,66,1 27 | 27,12.7266520910733,4.5568415630886,0.8,7.29166666666667,15.625,0,12.7781012351622,1.5,0.783333333333333,8.85416666666667,3.64583333333333,0,3.64583333333333,3.64583333333333,8.33333333333333,0,0,0,0,0,4.42708333333333,3.90625,27,1,22,2 28 | 28,12.6735400307429,9.14601181425344,0.84,0,0,0,12.3449799478479,1.5,0.783333333333333,0,0,0,0,0,0,0,0,0,0,0,0,0,28,2,34,2 29 | 29,13.4007251323926,6.98541063231921,0.78,80.7291666666667,2.86458333333333,0,14.3012964736507,1.5,0.733333333333333,0,0,0,50,68.4895833333333,58.59375,61.9791666666667,57.5520833333333,71.6145833333333,61.4583333333333,59.375,58.8541666666667,60.9375,29,1,39,1 30 | 30,14.9602836169944,11.0704184475233,0.8,66.1458333333333,41.40625,0,15.1940211326384,1.5,0.683333333333333,61.1979166666667,85.9375,0,79.6875,0,6.77083333333333,3.64583333333333,4.6875,0,28.3854166666667,7.03125,3.90625,5.46875,30,2,22,2 31 | 31,13.3094080473658,6.13760381742378,0.82,0,0,0,12.6418929846587,1.5,0.783333333333333,0,0,0,67.1875,23.6979166666667,14.3229166666667,13.28125,12.7604166666667,7.29166666666667,8.59375,8.59375,8.07291666666667,4.94791666666667,31,1,24,2 32 | 32,11.4570859469559,6.88313884962363,0.81,0,0,0,13.0257509902813,1.5,0.766666666666667,0,0,0,0,0,0,0,0,0,0,0,0,0,32,2,29,2 33 | 33,15.439747911037,4.99271126367346,0.76,48.4375,50.78125,0,15.4651087597437,1.5,0.666666666666667,23.9583333333333,30.7291666666667,0,62.2395833333333,60.9375,48.1770833333333,55.7291666666667,50.5208333333333,65.3645833333333,51.8229166666667,51.8229166666667,48.6979166666667,63.0208333333333,33,1,21,2 34 | -------------------------------------------------------------------------------- /data/allMetacogFits.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/data/allMetacogFits.mat -------------------------------------------------------------------------------- /figs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/figs/.DS_Store -------------------------------------------------------------------------------- /figs/RRST_MP_suppfig_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/figs/RRST_MP_suppfig_1.png -------------------------------------------------------------------------------- /figs/RRST_MP_suppfig_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/figs/RRST_MP_suppfig_2.png -------------------------------------------------------------------------------- /figs/RRST_MP_suppfig_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/figs/RRST_MP_suppfig_3.png -------------------------------------------------------------------------------- /figs/RRST_MP_suppfig_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/figs/RRST_MP_suppfig_4.png -------------------------------------------------------------------------------- /figs/RRST_MP_suppfig_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/figs/RRST_MP_suppfig_5.png -------------------------------------------------------------------------------- /figs/RRST_devicePhoto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/figs/RRST_devicePhoto.png -------------------------------------------------------------------------------- /figs/figure_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/figs/figure_1.png -------------------------------------------------------------------------------- /figs/figure_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/figs/figure_2.png -------------------------------------------------------------------------------- /figs/figure_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/figs/figure_3.png -------------------------------------------------------------------------------- /figs/figure_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/figs/figure_4.png -------------------------------------------------------------------------------- /figs/figure_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/figs/figure_5.png -------------------------------------------------------------------------------- /figs/figure_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/figs/figure_6.png -------------------------------------------------------------------------------- /figs/figure_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/figs/figure_7.png -------------------------------------------------------------------------------- /figs/figure_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/figs/figure_8.png -------------------------------------------------------------------------------- /figs/figure_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/figs/figure_9.png -------------------------------------------------------------------------------- /figs/graphicalAbstract_revision.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/figs/graphicalAbstract_revision.png -------------------------------------------------------------------------------- /task/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/.DS_Store -------------------------------------------------------------------------------- /task/RRS_2-2/experimentLauncher.m: -------------------------------------------------------------------------------- 1 | function experimentLauncher() 2 | %function experimentLauncher() 3 | % 4 | % Project: Respiratory Resistance Sensitivity (RRS) Task. 5 | % Respiratory interoception task. Staircase adjusts restrictive load 6 | % on an inhalation tube. 7 | % 8 | % Calls main.m (the main experiment script, which in turn loads parameters 9 | % from loadParams.m) 10 | % Most variables you might want to change are found in loadParams.m 11 | % Note however, that text size needs to be set after the OpenWindow call, 12 | % and is therefore set in main.m (~line 78). 13 | % 14 | % Versions: 15 | % 1-2 Task for Visceral Mind Project, Cohort 2, Fall 2020 16 | % 2-1 Version 2. Device update with new driver and screw. Change in motor step (& stim level) values 17 | % 2-2 Tasl for Visceral Mind Project 2021, Main task uses Psi single staircase. Includes variation of 2-1, 18 | % but using PAL PEST staircase method 19 | % instead of n-down as an option. Psi single staircase 20 | % ====================================================== 21 | % 22 | % Run from 'RRS_2-2' directory 23 | % -------------- PRESS ESC TO EXIT --------------------- 24 | % 25 | % ====================================================== 26 | % 27 | % Niia Nikolova 28 | % Last edit: 27/10/2021 29 | 30 | 31 | %% Initial settings 32 | % Close existing workspace 33 | close all; clc; clear all; 34 | 35 | devFlag = 0; % optional flag. Set to 1 when developing the task 36 | 37 | vars.exptName = 'RRS'; 38 | vars.exptVers = '_2-2'; 39 | 40 | %% Set-up 41 | % Check that PTB is installed 42 | PTBv = PsychtoolboxVersion; 43 | if isempty(PTBv) 44 | disp('Please install Psychtoolbox 3. Download and installation can be found here: http://psychtoolbox.org/download'); 45 | return 46 | end 47 | 48 | % Skip internal synch checks, suppress warnings 49 | oldLevel = Screen('Preference', 'Verbosity', 0); 50 | Screen('Preference', 'SkipSyncTests', 1); 51 | Screen('Preference','VisualDebugLevel', 0); 52 | 53 | % check working directory & change if necessary 54 | vars.workingDir = fullfile(vars.exptName); % make sure we're inside the RRS folder 55 | currentFolder = pwd; 56 | correctFolder = contains(currentFolder, vars.workingDir); 57 | if ~correctFolder % if we're not in the correct working directory, prompt to change 58 | disp(['Incorrect working directory. Please start from ', vars.workingDir, vars.exptVers]); return; 59 | end 60 | 61 | % check for data dir 62 | if ~exist('dataBackup', 'dir') 63 | mkdir('dataBackup') 64 | end 65 | 66 | % setup path 67 | addpath(genpath('code')); 68 | addpath(genpath('dataBackup')); 69 | addpath(genpath(fullfile('..', filesep, 'helpers'))); 70 | sPath = what((fullfile('..', filesep, 'helpers'))); 71 | vars.helpersPath = sPath.path; 72 | 73 | 74 | %% Ask for subID & language 75 | if ~devFlag % if we're testing 76 | 77 | vars.subNo = input('What is the subject ID, e.g. 0001)? '); 78 | vars.languageIn = input('Language?: (e for english, or d for danish) ', 's'); % 'E', or 'D' 79 | scr.ViewDist = 70; 80 | HideCursor; 81 | else 82 | scr.ViewDist = 40; 83 | end 84 | 85 | if ~isfield(vars,'subNo') || isempty(vars.subNo) 86 | vars.subNo = 9999; % test 87 | end 88 | 89 | %% Output 90 | vars.OutputFolder = fullfile('.', 'dataBackup', filesep); 91 | subIDstring = sprintf('%04d', vars.subNo); 92 | vars.DataFileName = strcat(vars.exptName, '_',subIDstring, '_P'); % name of data file to write to 93 | 94 | if isfile(strcat(vars.OutputFolder, vars.DataFileName, '.mat')) 95 | % File already exists in Outputdir 96 | if vars.subNo ~= 9999 97 | disp('A datafile already exists for this subject ID. Please enter a different ID.') 98 | return 99 | end 100 | end 101 | 102 | %% Start experiment 103 | main(vars, scr); 104 | 105 | if ~devFlag % if we're testing 106 | % Things to do to clean up if we're testing go here (e.g. copy data to network drive for backup) 107 | end 108 | 109 | % Clean up, reset paths 110 | Screen('Preference', 'Verbosity', oldLevel); 111 | rmpath(genpath('code')); 112 | rmpath(genpath('dataBackup')); 113 | rmpath(genpath(fullfile('..', filesep, 'helpers'))); -------------------------------------------------------------------------------- /task/RRS_3-0/experimentLauncher.m: -------------------------------------------------------------------------------- 1 | function experimentLauncher() 2 | %function experimentLauncher() 3 | % 4 | % Project: Respiratory Resistance Sensitivity (RRS) Task. 5 | % Respiratory interoception task. Staircase adjusts restrictive load 6 | % on an inhalation tube. 7 | % 8 | % Calls main.m (the main experiment script, which in turn loads parameters 9 | % from loadParams.m) 10 | % Most variables you might want to change are found in loadParams.m 11 | % Note however, that text size needs to be set after the OpenWindow call, 12 | % and is therefore set in main.m (~line 78). 13 | % 14 | % Versions: 15 | % 1-2 Task for Visceral Mind Project, Cohort 2, Fall 2020 16 | % 2-1 Version 2. Device update with new driver and screw. Change in motor step (& stim level) values 17 | % 2-2 Tasl for Visceral Mind Project 2021, Main task uses Psi single staircase. Includes variation of 2-1, 18 | % but using PAL PEST staircase method 19 | % instead of n-down as an option. Psi single staircase 20 | % ====================================================== 21 | % 22 | % Run from 'RRS_2-2' directory 23 | % -------------- PRESS ESC TO EXIT --------------------- 24 | % 25 | % ====================================================== 26 | % 27 | % Niia Nikolova 28 | % Last edit: 27/10/2021 29 | 30 | 31 | %% Initial settings 32 | % Close existing workspace 33 | close all; clc; clear all; 34 | 35 | devFlag = 0; % optional flag. Set to 1 when developing the task 36 | 37 | vars.exptName = 'RRS'; 38 | vars.exptVers = '_3-0'; 39 | 40 | %% Set-up 41 | % Check that PTB is installed 42 | PTBv = PsychtoolboxVersion; 43 | if isempty(PTBv) 44 | disp('Please install Psychtoolbox 3. Download and installation can be found here: http://psychtoolbox.org/download'); 45 | return 46 | end 47 | 48 | % Skip internal synch checks, suppress warnings 49 | oldLevel = Screen('Preference', 'Verbosity', 0); 50 | Screen('Preference', 'SkipSyncTests', 1); 51 | Screen('Preference','VisualDebugLevel', 0); 52 | 53 | % check working directory & change if necessary 54 | vars.workingDir = fullfile(vars.exptName); % make sure we're inside the RRS folder 55 | currentFolder = pwd; 56 | correctFolder = contains(currentFolder, vars.workingDir); 57 | if ~correctFolder % if we're not in the correct working directory, prompt to change 58 | disp(['Incorrect working directory. Please start from ', vars.workingDir, vars.exptVers]); return; 59 | end 60 | 61 | % check for data dir 62 | if ~exist('dataBackup', 'dir') 63 | mkdir('dataBackup') 64 | end 65 | 66 | % setup path 67 | addpath(genpath('code')); 68 | addpath(genpath('dataBackup')); 69 | addpath(genpath(fullfile('..', filesep, 'respDeviceHelpers_3-0'))); 70 | addpath(genpath(fullfile('..', filesep, 'helpers'))); 71 | sPath = what((fullfile('..', filesep, 'helpers'))); 72 | vars.helpersPath = sPath.path; 73 | 74 | 75 | %% Ask for subID & language 76 | if ~devFlag % if we're testing 77 | 78 | vars.subNo = input('What is the subject ID, e.g. 0001)? '); 79 | vars.languageIn = input('Language?: (e for english, or d for danish) ', 's'); % 'E', or 'D' 80 | scr.ViewDist = 70; 81 | HideCursor; 82 | else 83 | scr.ViewDist = 40; 84 | end 85 | 86 | if ~isfield(vars,'subNo') || isempty(vars.subNo) 87 | vars.subNo = 9999; % test 88 | end 89 | 90 | %% Output 91 | vars.OutputFolder = fullfile('.', 'dataBackup', filesep); 92 | subIDstring = sprintf('%04d', vars.subNo); 93 | vars.DataFileName = strcat(vars.exptName, '_',subIDstring, '_P'); % name of data file to write to 94 | 95 | if isfile(strcat(vars.OutputFolder, vars.DataFileName, '.mat')) 96 | % File already exists in Outputdir 97 | if vars.subNo ~= 9999 98 | disp('A datafile already exists for this subject ID. Please enter a different ID.') 99 | return 100 | end 101 | end 102 | 103 | %% Start experiment 104 | main(vars, scr); 105 | 106 | if ~devFlag % if we're testing 107 | % Things to do to clean up if we're testing go here (e.g. copy data to network drive for backup) 108 | end 109 | 110 | % Clean up, reset paths 111 | Screen('Preference', 'Verbosity', oldLevel); 112 | rmpath(genpath('code')); 113 | rmpath(genpath('dataBackup')); 114 | rmpath(genpath(fullfile('..', filesep, 'helpers'))); -------------------------------------------------------------------------------- /task/apparatus/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/apparatus/.DS_Store -------------------------------------------------------------------------------- /task/apparatus/version_2-2/3dPrintStlFiles/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/apparatus/version_2-2/3dPrintStlFiles/.DS_Store -------------------------------------------------------------------------------- /task/apparatus/version_2-2/3dPrintStlFiles/Vice_clamp.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/apparatus/version_2-2/3dPrintStlFiles/Vice_clamp.stl -------------------------------------------------------------------------------- /task/apparatus/version_2-2/3dPrintStlFiles/Vice_top.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/apparatus/version_2-2/3dPrintStlFiles/Vice_top.stl -------------------------------------------------------------------------------- /task/apparatus/version_2-2/3dPrintStlFiles/drawer.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/apparatus/version_2-2/3dPrintStlFiles/drawer.stl -------------------------------------------------------------------------------- /task/apparatus/version_3-0/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/apparatus/version_3-0/.DS_Store -------------------------------------------------------------------------------- /task/apparatus/version_3-0/3d_print_files.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/apparatus/version_3-0/3d_print_files.zip -------------------------------------------------------------------------------- /task/apparatus/version_3-0/Connection board v10.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/apparatus/version_3-0/Connection board v10.pdf -------------------------------------------------------------------------------- /task/apparatus/version_3-0/pcb.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/apparatus/version_3-0/pcb.zip -------------------------------------------------------------------------------- /task/apparatus/version_3-0/pcb/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/apparatus/version_3-0/pcb/.DS_Store -------------------------------------------------------------------------------- /task/apparatus/version_3-0/pcb/CAMOutputs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/apparatus/version_3-0/pcb/CAMOutputs/.DS_Store -------------------------------------------------------------------------------- /task/apparatus/version_3-0/pcb/CAMOutputs/Assembly/Connection board v24.txt: -------------------------------------------------------------------------------- 1 | Partlist exported from Team Hub: Martin Jensen Project: Martin's First Project Board: Connection board v24 at 18/08/2023 08.40 2 | 3 | Qty Value Device Package Parts Description CATEGORY DESCRIPTION MANUFACTURER MPN OPERATING_TEMP PART_STATUS PROD_ID ROHS_COMPLIANT SERIES SUB-CATEGORY THERMALLOSS TYPE VALUE VOLTAGE_RATING 4 | 2 C-POL_TANTALUM-1206(3216-METRIC) CAPMP3216X180N C5, C6 Capacitor Polarised - Generic Capacitor 5 | 1 C_CHIP-0603(1608-METRIC) CAPC1608X85 C4 Capacitor - Generic Capacitor 6 | 1 C_TANTALUM-2412(6032-METRIC) CAPM6032X280 C1 Capacitor - Generic Capacitor 7 | 1 1182-A4988 1182-A4988 A4988 TMC2100 A4988 Stepper Motor Driver Carrier 8 | 1 JST-XH-02-PIN-LONG-PAD JST-XH-02-PIN-LONG-PAD JST-XH-02-PACKAGE-LONG-PAD FAN JST XH Connector 2 Pin 9 | 2 JST-XH-03-PIN-LONG-PAD JST-XH-03-PIN-LONG-PAD JST-XH-03-PACKAGE-LONG-PAD AUX, ENDSTOP JST XH Connector 2 Pin 10 | 1 JST-XH-04-PIN-LONG-PAD JST-XH-04-PIN-LONG-PAD JST-XH-04-PACKAGE-LONG-PAD STEPPER JST XH Connector 2 Pin 11 | 1 NCP1117ST33T3G NCP1117ST33T3GSOT-223 SOT230P700X170-4 VR1 1.0 A Low-Dropout Positive Fixed and Adjustable Voltage Regulators IC_Power-Management ON Semiconductor NCP1117ST33T3G 0 to +125 °C Active RoHS3 Compliant NCP1117 NCV1117 Voltage Regulator Linear NCP1117ST33T3G 12 | 1 PINHD-1X1 PINHD-1X1 1X01 JP2 PIN HEADER CONNECTOR PIN-HEADER 13 | 2 PINHD-1X2 PINHD-1X2 1X02 JP1, JP3 PIN HEADER CONNECTOR PIN-HEADER 14 | 2 PINHD-1X3 PINHD-1X3 1X03 CFG1, CFG2 PIN HEADER CONNECTOR PIN-HEADER 15 | 1 POWER_JACK POWER_JACK POWER_JACK_PTH J1 Power Jack Connector CONN-08197 16 | 1 TEENSY_3.1-3.2_OUTER_ROW TEENSY_3.1-3.2_OUTER_ROW TEENSY_3.0-3.2&LC_OUTER_ROW TEENSY Footprint for Teensy 3.1 or 3.2 board using all pin connections on the outer perimeter 17 | 1 XM1584 XM1584 XM1584-TH XM1584 18 | -------------------------------------------------------------------------------- /task/apparatus/version_3-0/pcb/CAMOutputs/Assembly/PnP_Connection board v24_back.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/apparatus/version_3-0/pcb/CAMOutputs/Assembly/PnP_Connection board v24_back.txt -------------------------------------------------------------------------------- /task/apparatus/version_3-0/pcb/CAMOutputs/Assembly/PnP_Connection board v24_front.txt: -------------------------------------------------------------------------------- 1 | C1 69.85 77.80 90.00 CAPM6032X280 2 | C4 73.99 72.39 90.00 CAPC1608X85 3 | C5 76.42 72.39 90.00 CAPMP3216X180N 4 | C6 76.42 77.47 90.00 CAPMP3216X180N 5 | VR1 83.82 79.38 0.00 NCP1117ST33T3G SOT230P700X170-4 6 | -------------------------------------------------------------------------------- /task/apparatus/version_3-0/pcb/CAMOutputs/DrillFiles/drill_1_16.xln: -------------------------------------------------------------------------------- 1 | M48 2 | ;GenerationSoftware,Autodesk,EAGLE,9.7.0*% 3 | ;CreationDate,2023-08-18T06:40:39Z*% 4 | FMAT,2 5 | ICI,OFF 6 | METRIC,TZ,000.000 7 | T7C0.650 8 | T6C0.965 9 | T5C1.000 10 | T4C1.016 11 | T3C2.997 12 | T2C3.500 13 | T1C6.500 14 | % 15 | G90 16 | M71 17 | T1 18 | X110590Y93330 19 | X85590Y68330 20 | T2 21 | X44640Y61150 22 | X44640Y96330 23 | X144590Y96330 24 | X144590Y61150 25 | T3 26 | X55880Y89170 27 | X55880Y95170 28 | X51180Y92170 29 | T4 30 | X104775Y77470 31 | X104775Y80010 32 | X104775Y82550 33 | X104775Y85090 34 | X104775Y87630 35 | X104775Y90170 36 | X92075Y90170 37 | X92075Y87630 38 | X92075Y85090 39 | X92075Y82550 40 | X92075Y80010 41 | X92075Y77470 42 | X92075Y74930 43 | X92075Y72390 44 | X109220Y61036 45 | X106680Y61036 46 | X111760Y64770 47 | X109220Y64770 48 | X106680Y64770 49 | X73025Y62865 50 | X70485Y62865 51 | X142240Y89535 52 | X142240Y86995 53 | X142240Y84455 54 | X44450Y88900 55 | X83820Y93980 56 | X81280Y93980 57 | X142240Y74295 58 | X142240Y71755 59 | X142240Y69215 60 | X64770Y93980 61 | X72390Y93980 62 | X69850Y93980 63 | X67310Y93980 64 | X104775Y74930 65 | X104775Y72390 66 | X111760Y61036 67 | X67310Y71120 68 | X69850Y71120 69 | T5 70 | X62702Y68407 71 | X44100Y68378 72 | X62695Y70857 73 | X44095Y70857 74 | X62685Y79013 75 | X44095Y78993 76 | X62682Y81595 77 | X44099Y81596 78 | T6 79 | X133350Y98247 80 | X133350Y95707 81 | X133350Y93167 82 | X133350Y90627 83 | X133350Y88087 84 | X133350Y85547 85 | X133350Y83007 86 | X133350Y80467 87 | X133350Y77927 88 | X133350Y75387 89 | X133350Y72847 90 | X133350Y70307 91 | X133350Y67767 92 | X133350Y65227 93 | X130810Y65227 94 | X128270Y65227 95 | X125730Y65227 96 | X123190Y65227 97 | X120650Y65227 98 | X118110Y65227 99 | X118110Y67767 100 | X118110Y70307 101 | X118110Y72847 102 | X118110Y75387 103 | X118110Y77927 104 | X118110Y80467 105 | X118110Y83007 106 | X118110Y85547 107 | X118110Y88087 108 | X118110Y90627 109 | X118110Y93167 110 | X118110Y95707 111 | X118110Y98247 112 | T7 113 | X62575Y86070 114 | M30 -------------------------------------------------------------------------------- /task/apparatus/version_3-0/pcb/CAMOutputs/GerberFiles/copper_bottom.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INBottom Copper*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | G04 Define Apertures* 12 | %ADD10C,1.524000*% 13 | %ADD11C,1.473200*% 14 | %ADD12C,1.508000*% 15 | %ADD13C,1.524000*% 16 | %ADD14C,4.318000*% 17 | %ADD15P,1.64956X8X22.5*% 18 | %ADD16C,1.270000*% 19 | %ADD17C,0.609600*% 20 | %ADD18C,1.056400*% 21 | %ADD19C,1.016000*% 22 | %ADD20C,0.254000*% 23 | D10* 24 | X1047750Y723900D03* 25 | X1047750Y749300D03* 26 | X1047750Y774700D03* 27 | X1047750Y800100D03* 28 | X1047750Y825500D03* 29 | X1047750Y850900D03* 30 | X1047750Y876300D03* 31 | X1047750Y901700D03* 32 | X920750Y901700D03* 33 | X920750Y876300D03* 34 | X920750Y850900D03* 35 | X920750Y825500D03* 36 | X920750Y800100D03* 37 | X920750Y774700D03* 38 | X920750Y749300D03* 39 | X920750Y723900D03* 40 | D11* 41 | X1181100Y982474D03* 42 | X1181100Y957074D03* 43 | X1181100Y931674D03* 44 | X1181100Y906274D03* 45 | X1181100Y880874D03* 46 | X1181100Y855474D03* 47 | X1181100Y830074D03* 48 | X1181100Y804674D03* 49 | X1181100Y779274D03* 50 | X1181100Y753874D03* 51 | X1181100Y728474D03* 52 | X1181100Y703074D03* 53 | X1181100Y677674D03* 54 | X1181100Y652274D03* 55 | X1206500Y652274D03* 56 | X1231900Y652274D03* 57 | X1257300Y652274D03* 58 | X1282700Y652274D03* 59 | X1308100Y652274D03* 60 | X1333500Y652274D03* 61 | X1333500Y677674D03* 62 | X1333500Y703074D03* 63 | X1333500Y728474D03* 64 | X1333500Y753874D03* 65 | X1333500Y779274D03* 66 | X1333500Y804674D03* 67 | X1333500Y830074D03* 68 | X1333500Y855474D03* 69 | X1333500Y880874D03* 70 | X1333500Y906274D03* 71 | X1333500Y931674D03* 72 | X1333500Y957074D03* 73 | X1333500Y982474D03* 74 | D12* 75 | X448535Y815961D02* 76 | X433455Y815961D01* 77 | X619280Y815954D02* 78 | X634360Y815954D01* 79 | X448490Y789932D02* 80 | X433410Y789932D01* 81 | X619310Y790132D02* 82 | X634390Y790132D01* 83 | X448490Y708568D02* 84 | X433410Y708568D01* 85 | X619410Y708568D02* 86 | X634490Y708568D01* 87 | X448536Y683776D02* 88 | X433456Y683776D01* 89 | X619484Y684073D02* 90 | X634564Y684073D01* 91 | D13* 92 | X698500Y703580D02* 93 | X698500Y718820D01* 94 | X673100Y718820D02* 95 | X673100Y703580D01* 96 | D14* 97 | X558800Y891700D03* 98 | X558800Y951700D03* 99 | X511800Y921700D03* 100 | D13* 101 | X673100Y932180D02* 102 | X673100Y947420D01* 103 | X698500Y947420D02* 104 | X698500Y932180D01* 105 | X723900Y932180D02* 106 | X723900Y947420D01* 107 | X647700Y947420D02* 108 | X647700Y932180D01* 109 | X1414780Y692150D02* 110 | X1430020Y692150D01* 111 | X1430020Y717550D02* 112 | X1414780Y717550D01* 113 | X1414780Y742950D02* 114 | X1430020Y742950D01* 115 | X812800Y932180D02* 116 | X812800Y947420D01* 117 | X838200Y947420D02* 118 | X838200Y932180D01* 119 | D15* 120 | X444500Y889000D03* 121 | D13* 122 | X1414780Y844550D02* 123 | X1430020Y844550D01* 124 | X1430020Y869950D02* 125 | X1414780Y869950D01* 126 | X1414780Y895350D02* 127 | X1430020Y895350D01* 128 | X704850Y636270D02* 129 | X704850Y621030D01* 130 | X730250Y621030D02* 131 | X730250Y636270D01* 132 | X1066800Y640080D02* 133 | X1066800Y655320D01* 134 | X1092200Y655320D02* 135 | X1092200Y640080D01* 136 | X1117600Y640080D02* 137 | X1117600Y655320D01* 138 | X1066800Y617982D02* 139 | X1066800Y602742D01* 140 | X1092200Y602742D02* 141 | X1092200Y617982D01* 142 | X1117600Y617982D02* 143 | X1117600Y602742D01* 144 | D16* 145 | X558800Y951700D02* 146 | X541800Y951700D01* 147 | X511800Y921700D01* 148 | X479100Y889000D01* 149 | X444500Y889000D01* 150 | X444500Y819466D01* 151 | X440995Y815961D01* 152 | X440995Y789977D01* 153 | X440950Y789932D01* 154 | X440995Y815961D02* 155 | X626813Y815961D01* 156 | X626820Y815954D01* 157 | X626820Y790162D01* 158 | X626850Y790132D01* 159 | X812800Y939800D02* 160 | X774700Y977900D01* 161 | X585000Y977900D01* 162 | X558800Y951700D01* 163 | X920750Y901700D02* 164 | X939800Y901700D01* 165 | X952500Y889000D01* 166 | X952500Y762000D01* 167 | X939800Y749300D01* 168 | X920750Y749300D01* 169 | X920750Y901700D02* 170 | X1001524Y982474D01* 171 | X1181100Y982474D01* 172 | D17* 173 | X984250Y749300D02* 174 | X920750Y749300D01* 175 | X984250Y749300D02* 176 | X1066800Y666750D01* 177 | X1066800Y647700D01* 178 | X1066800Y610362D01* 179 | X1435100Y692150D02* 180 | X1454150Y711200D01* 181 | X1435100Y692150D02* 182 | X1422400Y692150D01* 183 | X1454150Y711200D02* 184 | X1454150Y787400D01* 185 | X1422400Y819150D01* 186 | X1422400Y844550D01* 187 | D18* 188 | X625747Y860697D03* 189 | D19* 190 | X711200Y774700D02* 191 | X920750Y774700D01* 192 | X711200Y774700D02* 193 | X625747Y860153D01* 194 | X625747Y860697D01* 195 | X755650Y850900D02* 196 | X920750Y850900D01* 197 | X755650Y850900D02* 198 | X698500Y908050D01* 199 | X698500Y939800D01* 200 | X736600Y825500D02* 201 | X920750Y825500D01* 202 | X736600Y825500D02* 203 | X673100Y889000D01* 204 | X673100Y939800D01* 205 | X717550Y800100D02* 206 | X920750Y800100D01* 207 | X717550Y800100D02* 208 | X647700Y869950D01* 209 | X647700Y939800D01* 210 | D20* 211 | X1047750Y723900D02* 212 | X1077724Y753874D01* 213 | X1181100Y753874D01* 214 | D17* 215 | X984250Y628650D02* 216 | X730250Y628650D01* 217 | X984250Y628650D02* 218 | X1027430Y585470D01* 219 | X1104900Y585470D01* 220 | X1117600Y598170D01* 221 | X1117600Y610362D01* 222 | X1117600Y647700D01* 223 | X1117600Y610362D02* 224 | X1213612Y610362D01* 225 | X1231900Y628650D01* 226 | X1231900Y652274D01* 227 | X1231900Y628650D02* 228 | X1250950Y609600D01* 229 | X1346200Y609600D01* 230 | X1377950Y641350D01* 231 | X1377950Y717550D01* 232 | X1403350Y742950D01* 233 | X1422400Y742950D01* 234 | M02* 235 | -------------------------------------------------------------------------------- /task/apparatus/version_3-0/pcb/CAMOutputs/GerberFiles/copper_top.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INTop Copper*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | G04 Define Apertures* 12 | %ADD10C,1.524000*% 13 | %ADD11C,1.473200*% 14 | %ADD12C,1.508000*% 15 | %ADD13C,1.524000*% 16 | %ADD14C,4.318000*% 17 | %ADD15P,1.64956X8X22.5*% 18 | %ADD16R,2.203600X2.368000*% 19 | %ADD17R,0.970200X0.920900*% 20 | %ADD18R,1.860300X0.897900*% 21 | %ADD19R,1.860300X3.189100*% 22 | %ADD20R,1.188700X1.795500*% 23 | %ADD21C,1.270000*% 24 | %ADD22C,0.609600*% 25 | %ADD23C,0.812800*% 26 | %ADD24C,1.056400*% 27 | %ADD25C,1.016000*% 28 | %ADD26C,0.406400*% 29 | %ADD27C,0.254000*% 30 | D10* 31 | X1047750Y723900D03* 32 | X1047750Y749300D03* 33 | X1047750Y774700D03* 34 | X1047750Y800100D03* 35 | X1047750Y825500D03* 36 | X1047750Y850900D03* 37 | X1047750Y876300D03* 38 | X1047750Y901700D03* 39 | X920750Y901700D03* 40 | X920750Y876300D03* 41 | X920750Y850900D03* 42 | X920750Y825500D03* 43 | X920750Y800100D03* 44 | X920750Y774700D03* 45 | X920750Y749300D03* 46 | X920750Y723900D03* 47 | D11* 48 | X1181100Y982474D03* 49 | X1181100Y957074D03* 50 | X1181100Y931674D03* 51 | X1181100Y906274D03* 52 | X1181100Y880874D03* 53 | X1181100Y855474D03* 54 | X1181100Y830074D03* 55 | X1181100Y804674D03* 56 | X1181100Y779274D03* 57 | X1181100Y753874D03* 58 | X1181100Y728474D03* 59 | X1181100Y703074D03* 60 | X1181100Y677674D03* 61 | X1181100Y652274D03* 62 | X1206500Y652274D03* 63 | X1231900Y652274D03* 64 | X1257300Y652274D03* 65 | X1282700Y652274D03* 66 | X1308100Y652274D03* 67 | X1333500Y652274D03* 68 | X1333500Y677674D03* 69 | X1333500Y703074D03* 70 | X1333500Y728474D03* 71 | X1333500Y753874D03* 72 | X1333500Y779274D03* 73 | X1333500Y804674D03* 74 | X1333500Y830074D03* 75 | X1333500Y855474D03* 76 | X1333500Y880874D03* 77 | X1333500Y906274D03* 78 | X1333500Y931674D03* 79 | X1333500Y957074D03* 80 | X1333500Y982474D03* 81 | D12* 82 | X448535Y815961D02* 83 | X433455Y815961D01* 84 | X619280Y815954D02* 85 | X634360Y815954D01* 86 | X448490Y789932D02* 87 | X433410Y789932D01* 88 | X619310Y790132D02* 89 | X634390Y790132D01* 90 | X448490Y708568D02* 91 | X433410Y708568D01* 92 | X619410Y708568D02* 93 | X634490Y708568D01* 94 | X448536Y683776D02* 95 | X433456Y683776D01* 96 | X619484Y684073D02* 97 | X634564Y684073D01* 98 | D13* 99 | X698500Y703580D02* 100 | X698500Y718820D01* 101 | X673100Y718820D02* 102 | X673100Y703580D01* 103 | D14* 104 | X558800Y891700D03* 105 | X558800Y951700D03* 106 | X511800Y921700D03* 107 | D13* 108 | X673100Y932180D02* 109 | X673100Y947420D01* 110 | X698500Y947420D02* 111 | X698500Y932180D01* 112 | X723900Y932180D02* 113 | X723900Y947420D01* 114 | X647700Y947420D02* 115 | X647700Y932180D01* 116 | X1414780Y692150D02* 117 | X1430020Y692150D01* 118 | X1430020Y717550D02* 119 | X1414780Y717550D01* 120 | X1414780Y742950D02* 121 | X1430020Y742950D01* 122 | X812800Y932180D02* 123 | X812800Y947420D01* 124 | X838200Y947420D02* 125 | X838200Y932180D01* 126 | D15* 127 | X444500Y889000D03* 128 | D13* 129 | X1414780Y844550D02* 130 | X1430020Y844550D01* 131 | X1430020Y869950D02* 132 | X1414780Y869950D01* 133 | X1414780Y895350D02* 134 | X1430020Y895350D01* 135 | D16* 136 | X698500Y753314D03* 137 | X698500Y802738D03* 138 | D17* 139 | X739877Y716154D03* 140 | X739877Y731646D03* 141 | D18* 142 | X807450Y816750D03* 143 | X807450Y793750D03* 144 | X807450Y770750D03* 145 | D19* 146 | X868950Y793750D03* 147 | D20* 148 | X764185Y710801D03* 149 | X764185Y736999D03* 150 | X764185Y761601D03* 151 | X764185Y787799D03* 152 | D13* 153 | X704850Y636270D02* 154 | X704850Y621030D01* 155 | X730250Y621030D02* 156 | X730250Y636270D01* 157 | X1066800Y640080D02* 158 | X1066800Y655320D01* 159 | X1092200Y655320D02* 160 | X1092200Y640080D01* 161 | X1117600Y640080D02* 162 | X1117600Y655320D01* 163 | X1066800Y617982D02* 164 | X1066800Y602742D01* 165 | X1092200Y602742D02* 166 | X1092200Y617982D01* 167 | X1117600Y617982D02* 168 | X1117600Y602742D01* 169 | D21* 170 | X650432Y790132D02* 171 | X626850Y790132D01* 172 | X650432Y790132D02* 173 | X663038Y802738D01* 174 | X698500Y802738D01* 175 | X812800Y914400D02* 176 | X812800Y939800D01* 177 | X812800Y914400D02* 178 | X825500Y901700D01* 179 | X920750Y901700D01* 180 | D22* 181 | X1066800Y610362D02* 182 | X1066800Y603250D01* 183 | X1084580Y585470D01* 184 | X1220470Y585470D01* 185 | X1257300Y622300D01* 186 | X1257300Y652274D01* 187 | X1257300Y622300D02* 188 | X1270000Y609600D01* 189 | X1352550Y609600D01* 190 | X1422400Y679450D01* 191 | X1422400Y692150D01* 192 | D23* 193 | X730250Y802738D02* 194 | X698500Y802738D01* 195 | X730250Y802738D02* 196 | X762000Y802738D01* 197 | X770988Y802738D01* 198 | X785000Y816750D01* 199 | X807450Y816750D01* 200 | X764185Y736999D02* 201 | X758832Y731646D01* 202 | X739877Y731646D01* 203 | X730250Y741273D01* 204 | X730250Y802738D01* 205 | X762000Y789984D02* 206 | X764185Y787799D01* 207 | X762000Y789984D02* 208 | X762000Y802738D01* 209 | D24* 210 | X625747Y860697D03* 211 | D25* 212 | X660400Y895350D01* 213 | X704850Y895350D01* 214 | X723900Y914400D01* 215 | X723900Y939800D01* 216 | D26* 217 | X1047750Y876300D02* 218 | X1060450Y876300D01* 219 | X1098550Y838200D01* 220 | X1098550Y774700D01* 221 | X1170176Y703074D01* 222 | X1181100Y703074D01* 223 | X1181100Y728474D02* 224 | X1117600Y791974D01* 225 | X1117600Y844550D01* 226 | X1060450Y901700D01* 227 | X1047750Y901700D01* 228 | D27* 229 | X1346200Y768350D02* 230 | X1397000Y717550D01* 231 | X1346200Y768350D02* 232 | X1219200Y768350D01* 233 | X1208276Y779274D01* 234 | X1181100Y779274D01* 235 | X1397000Y717550D02* 236 | X1422400Y717550D01* 237 | X1422400Y895350D02* 238 | X1386076Y931674D01* 239 | X1333500Y931674D01* 240 | X1422400Y774700D02* 241 | X1422400Y742950D01* 242 | X1422400Y774700D02* 243 | X1454150Y806450D01* 244 | X1454150Y889000D01* 245 | X1447800Y895350D01* 246 | X1422400Y895350D01* 247 | D23* 248 | X764584Y762000D02* 249 | X764185Y761601D01* 250 | X764584Y762000D02* 251 | X774700Y762000D01* 252 | X787400Y774700D01* 253 | X787400Y787400D01* 254 | X793750Y793750D01* 255 | X838200Y736600D02* 256 | X730250Y628650D01* 257 | X838200Y736600D02* 258 | X838200Y774700D01* 259 | X819150Y793750D01* 260 | X807450Y793750D02* 261 | X793750Y793750D01* 262 | X807450Y793750D02* 263 | X819150Y793750D01* 264 | X901700Y876300D01* 265 | X920750Y876300D01* 266 | D21* 267 | X627024Y684073D02* 268 | X626950Y684147D01* 269 | X626950Y708568D01* 270 | X629582Y711200D01* 271 | X673100Y711200D01* 272 | X558800Y891700D02* 273 | X482600Y815500D01* 274 | X482600Y736600D01* 275 | X454568Y708568D01* 276 | X440950Y708568D01* 277 | X440950Y683822D01* 278 | X440996Y683776D01* 279 | X558800Y891700D02* 280 | X596900Y929800D01* 281 | X596900Y965200D01* 282 | X615190Y983490D01* 283 | X812800Y983490D01* 284 | X838200Y958090D01* 285 | X838200Y939800D01* 286 | X927100Y939800D01* 287 | X920750Y723900D02* 288 | X939800Y723900D01* 289 | X957072Y909828D02* 290 | X927100Y939800D01* 291 | X957072Y909828D02* 292 | X957072Y741172D01* 293 | X939800Y723900D01* 294 | X698500Y711200D02* 295 | X698500Y753314D01* 296 | D23* 297 | X698500Y711200D02* 298 | X698500Y635000D01* 299 | X704850Y628650D01* 300 | X698500Y711200D02* 301 | X703454Y716154D01* 302 | X739877Y716154D01* 303 | X758832Y716154D01* 304 | X764185Y710801D01* 305 | X764584Y711200D01* 306 | X774700Y711200D01* 307 | X807450Y743950D01* 308 | X807450Y770750D01* 309 | D27* 310 | X1181100Y804674D02* 311 | X1211074Y804674D01* 312 | X1276350Y869950D01* 313 | X1422400Y869950D01* 314 | X1060450Y749300D02* 315 | X1047750Y749300D01* 316 | X1060450Y749300D02* 317 | X1092200Y717550D01* 318 | X1092200Y647700D01* 319 | X1104900Y717550D02* 320 | X1047750Y774700D01* 321 | X1104900Y717550D02* 322 | X1104900Y622300D01* 323 | X1092962Y610362D01* 324 | X1092200Y610362D01* 325 | M02* 326 | -------------------------------------------------------------------------------- /task/apparatus/version_3-0/pcb/CAMOutputs/GerberFiles/gerber_job.gbrjob: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "Comment": "All values are metric (mm)", 4 | "CreationDate": "2023-08-18T06:40:39Z", 5 | "GenerationSoftware": { 6 | "Application": "Fusion 360 Electronics", 7 | "Vendor": "Autodesk", 8 | "Version": "9.7.0" 9 | }, 10 | "Part": "Single" 11 | }, 12 | "Overall": { 13 | "BoardThickness": 1.57, 14 | "LayerNumber": 2, 15 | "Name": { 16 | "ProjectId": "Connection board v24" 17 | }, 18 | "Owner": "Martin Jensen ", 19 | "Size": { 20 | "X": 107.95, 21 | "Y": 43.18 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /task/apparatus/version_3-0/pcb/CAMOutputs/GerberFiles/profile.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %IN*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | G04 Define Apertures* 12 | %ADD10C,0.254000*% 13 | D10* 14 | X406400Y571500D02* 15 | X1485900Y571500D01* 16 | X1485900Y1003300D01* 17 | X406400Y1003300D01* 18 | X406400Y571500D01* 19 | M02* 20 | -------------------------------------------------------------------------------- /task/apparatus/version_3-0/pcb/CAMOutputs/GerberFiles/silkscreen_bottom.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INSilkscreen Bottom*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | G04 Define Apertures* 12 | M02* 13 | -------------------------------------------------------------------------------- /task/apparatus/version_3-0/pcb/CAMOutputs/GerberFiles/soldermask_bottom.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INSoldermask Bottom*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | G04 Define Apertures* 12 | %ADD10C,3.703200*% 13 | %ADD11C,6.703200*% 14 | %ADD12C,1.727200*% 15 | %ADD13C,1.676400*% 16 | %ADD14C,1.711200*% 17 | %ADD15C,1.727200*% 18 | %ADD16C,4.521200*% 19 | %ADD17P,1.8695X8X22.5*% 20 | %ADD18C,1.259600*% 21 | D10* 22 | X446400Y611500D03* 23 | X446400Y963300D03* 24 | X1445900Y963300D03* 25 | X1445900Y611500D03* 26 | D11* 27 | X1105900Y933300D03* 28 | X855900Y683300D03* 29 | D12* 30 | X1047750Y723900D03* 31 | X1047750Y749300D03* 32 | X1047750Y774700D03* 33 | X1047750Y800100D03* 34 | X1047750Y825500D03* 35 | X1047750Y850900D03* 36 | X1047750Y876300D03* 37 | X1047750Y901700D03* 38 | X920750Y901700D03* 39 | X920750Y876300D03* 40 | X920750Y850900D03* 41 | X920750Y825500D03* 42 | X920750Y800100D03* 43 | X920750Y774700D03* 44 | X920750Y749300D03* 45 | X920750Y723900D03* 46 | D13* 47 | X1181100Y982474D03* 48 | X1181100Y957074D03* 49 | X1181100Y931674D03* 50 | X1181100Y906274D03* 51 | X1181100Y880874D03* 52 | X1181100Y855474D03* 53 | X1181100Y830074D03* 54 | X1181100Y804674D03* 55 | X1181100Y779274D03* 56 | X1181100Y753874D03* 57 | X1181100Y728474D03* 58 | X1181100Y703074D03* 59 | X1181100Y677674D03* 60 | X1181100Y652274D03* 61 | X1206500Y652274D03* 62 | X1231900Y652274D03* 63 | X1257300Y652274D03* 64 | X1282700Y652274D03* 65 | X1308100Y652274D03* 66 | X1333500Y652274D03* 67 | X1333500Y677674D03* 68 | X1333500Y703074D03* 69 | X1333500Y728474D03* 70 | X1333500Y753874D03* 71 | X1333500Y779274D03* 72 | X1333500Y804674D03* 73 | X1333500Y830074D03* 74 | X1333500Y855474D03* 75 | X1333500Y880874D03* 76 | X1333500Y906274D03* 77 | X1333500Y931674D03* 78 | X1333500Y957074D03* 79 | X1333500Y982474D03* 80 | D14* 81 | X448535Y815961D02* 82 | X433455Y815961D01* 83 | X619280Y815954D02* 84 | X634360Y815954D01* 85 | X448490Y789932D02* 86 | X433410Y789932D01* 87 | X619310Y790132D02* 88 | X634390Y790132D01* 89 | X448490Y708568D02* 90 | X433410Y708568D01* 91 | X619410Y708568D02* 92 | X634490Y708568D01* 93 | X448536Y683776D02* 94 | X433456Y683776D01* 95 | X619484Y684073D02* 96 | X634564Y684073D01* 97 | D15* 98 | X698500Y703580D02* 99 | X698500Y718820D01* 100 | X673100Y718820D02* 101 | X673100Y703580D01* 102 | D16* 103 | X558800Y891700D03* 104 | X558800Y951700D03* 105 | X511800Y921700D03* 106 | D15* 107 | X673100Y932180D02* 108 | X673100Y947420D01* 109 | X698500Y947420D02* 110 | X698500Y932180D01* 111 | X723900Y932180D02* 112 | X723900Y947420D01* 113 | X647700Y947420D02* 114 | X647700Y932180D01* 115 | X1414780Y692150D02* 116 | X1430020Y692150D01* 117 | X1430020Y717550D02* 118 | X1414780Y717550D01* 119 | X1414780Y742950D02* 120 | X1430020Y742950D01* 121 | X812800Y932180D02* 122 | X812800Y947420D01* 123 | X838200Y947420D02* 124 | X838200Y932180D01* 125 | D17* 126 | X444500Y889000D03* 127 | D15* 128 | X1414780Y844550D02* 129 | X1430020Y844550D01* 130 | X1430020Y869950D02* 131 | X1414780Y869950D01* 132 | X1414780Y895350D02* 133 | X1430020Y895350D01* 134 | X704850Y636270D02* 135 | X704850Y621030D01* 136 | X730250Y621030D02* 137 | X730250Y636270D01* 138 | X1066800Y640080D02* 139 | X1066800Y655320D01* 140 | X1092200Y655320D02* 141 | X1092200Y640080D01* 142 | X1117600Y640080D02* 143 | X1117600Y655320D01* 144 | X1066800Y617982D02* 145 | X1066800Y602742D01* 146 | X1092200Y602742D02* 147 | X1092200Y617982D01* 148 | X1117600Y617982D02* 149 | X1117600Y602742D01* 150 | D18* 151 | X625747Y860697D03* 152 | M02* 153 | -------------------------------------------------------------------------------- /task/apparatus/version_3-0/pcb/CAMOutputs/GerberFiles/soldermask_top.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INSoldermask Top*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | G04 Define Apertures* 12 | %ADD10C,3.703200*% 13 | %ADD11C,6.703200*% 14 | %ADD12C,1.727200*% 15 | %ADD13C,1.676400*% 16 | %ADD14C,1.711200*% 17 | %ADD15C,1.727200*% 18 | %ADD16C,4.521200*% 19 | %ADD17P,1.8695X8X22.5*% 20 | %ADD18R,2.406800X2.571200*% 21 | %ADD19R,1.173400X1.124100*% 22 | %ADD20R,2.063500X1.101100*% 23 | %ADD21R,2.063500X3.392300*% 24 | %ADD22R,1.391900X1.998700*% 25 | %ADD23C,1.259600*% 26 | D10* 27 | X446400Y611500D03* 28 | X446400Y963300D03* 29 | X1445900Y963300D03* 30 | X1445900Y611500D03* 31 | D11* 32 | X1105900Y933300D03* 33 | X855900Y683300D03* 34 | D12* 35 | X1047750Y723900D03* 36 | X1047750Y749300D03* 37 | X1047750Y774700D03* 38 | X1047750Y800100D03* 39 | X1047750Y825500D03* 40 | X1047750Y850900D03* 41 | X1047750Y876300D03* 42 | X1047750Y901700D03* 43 | X920750Y901700D03* 44 | X920750Y876300D03* 45 | X920750Y850900D03* 46 | X920750Y825500D03* 47 | X920750Y800100D03* 48 | X920750Y774700D03* 49 | X920750Y749300D03* 50 | X920750Y723900D03* 51 | D13* 52 | X1181100Y982474D03* 53 | X1181100Y957074D03* 54 | X1181100Y931674D03* 55 | X1181100Y906274D03* 56 | X1181100Y880874D03* 57 | X1181100Y855474D03* 58 | X1181100Y830074D03* 59 | X1181100Y804674D03* 60 | X1181100Y779274D03* 61 | X1181100Y753874D03* 62 | X1181100Y728474D03* 63 | X1181100Y703074D03* 64 | X1181100Y677674D03* 65 | X1181100Y652274D03* 66 | X1206500Y652274D03* 67 | X1231900Y652274D03* 68 | X1257300Y652274D03* 69 | X1282700Y652274D03* 70 | X1308100Y652274D03* 71 | X1333500Y652274D03* 72 | X1333500Y677674D03* 73 | X1333500Y703074D03* 74 | X1333500Y728474D03* 75 | X1333500Y753874D03* 76 | X1333500Y779274D03* 77 | X1333500Y804674D03* 78 | X1333500Y830074D03* 79 | X1333500Y855474D03* 80 | X1333500Y880874D03* 81 | X1333500Y906274D03* 82 | X1333500Y931674D03* 83 | X1333500Y957074D03* 84 | X1333500Y982474D03* 85 | D14* 86 | X448535Y815961D02* 87 | X433455Y815961D01* 88 | X619280Y815954D02* 89 | X634360Y815954D01* 90 | X448490Y789932D02* 91 | X433410Y789932D01* 92 | X619310Y790132D02* 93 | X634390Y790132D01* 94 | X448490Y708568D02* 95 | X433410Y708568D01* 96 | X619410Y708568D02* 97 | X634490Y708568D01* 98 | X448536Y683776D02* 99 | X433456Y683776D01* 100 | X619484Y684073D02* 101 | X634564Y684073D01* 102 | D15* 103 | X698500Y703580D02* 104 | X698500Y718820D01* 105 | X673100Y718820D02* 106 | X673100Y703580D01* 107 | D16* 108 | X558800Y891700D03* 109 | X558800Y951700D03* 110 | X511800Y921700D03* 111 | D15* 112 | X673100Y932180D02* 113 | X673100Y947420D01* 114 | X698500Y947420D02* 115 | X698500Y932180D01* 116 | X723900Y932180D02* 117 | X723900Y947420D01* 118 | X647700Y947420D02* 119 | X647700Y932180D01* 120 | X1414780Y692150D02* 121 | X1430020Y692150D01* 122 | X1430020Y717550D02* 123 | X1414780Y717550D01* 124 | X1414780Y742950D02* 125 | X1430020Y742950D01* 126 | X812800Y932180D02* 127 | X812800Y947420D01* 128 | X838200Y947420D02* 129 | X838200Y932180D01* 130 | D17* 131 | X444500Y889000D03* 132 | D15* 133 | X1414780Y844550D02* 134 | X1430020Y844550D01* 135 | X1430020Y869950D02* 136 | X1414780Y869950D01* 137 | X1414780Y895350D02* 138 | X1430020Y895350D01* 139 | D18* 140 | X698500Y753314D03* 141 | X698500Y802738D03* 142 | D19* 143 | X739877Y716154D03* 144 | X739877Y731646D03* 145 | D20* 146 | X807450Y816750D03* 147 | X807450Y793750D03* 148 | X807450Y770750D03* 149 | D21* 150 | X868950Y793750D03* 151 | D22* 152 | X764185Y710801D03* 153 | X764185Y736999D03* 154 | X764185Y761601D03* 155 | X764185Y787799D03* 156 | D15* 157 | X704850Y636270D02* 158 | X704850Y621030D01* 159 | X730250Y621030D02* 160 | X730250Y636270D01* 161 | X1066800Y640080D02* 162 | X1066800Y655320D01* 163 | X1092200Y655320D02* 164 | X1092200Y640080D01* 165 | X1117600Y640080D02* 166 | X1117600Y655320D01* 167 | X1066800Y617982D02* 168 | X1066800Y602742D01* 169 | X1092200Y602742D02* 170 | X1092200Y617982D01* 171 | X1117600Y617982D02* 172 | X1117600Y602742D01* 173 | D23* 174 | X625747Y860697D03* 175 | M02* 176 | -------------------------------------------------------------------------------- /task/apparatus/version_3-0/pcb/CAMOutputs/GerberFiles/solderpaste_bottom.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INSolderpaste Bottom*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | G04 Define Apertures* 12 | M02* 13 | -------------------------------------------------------------------------------- /task/apparatus/version_3-0/pcb/CAMOutputs/GerberFiles/solderpaste_top.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INSolderpaste Top*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | G04 Define Apertures* 12 | %ADD10R,2.203600X2.368000*% 13 | %ADD11R,0.970200X0.920900*% 14 | %ADD12R,1.860300X0.897900*% 15 | %ADD13R,1.860300X3.189100*% 16 | %ADD14R,1.188700X1.795500*% 17 | D10* 18 | X698500Y753314D03* 19 | X698500Y802738D03* 20 | D11* 21 | X739877Y716154D03* 22 | X739877Y731646D03* 23 | D12* 24 | X807450Y816750D03* 25 | X807450Y793750D03* 26 | X807450Y770750D03* 27 | D13* 28 | X868950Y793750D03* 29 | D14* 30 | X764185Y710801D03* 31 | X764185Y736999D03* 32 | X764185Y761601D03* 33 | X764185Y787799D03* 34 | M02* 35 | -------------------------------------------------------------------------------- /task/apparatus/version_3-0/photos/20220426_095017.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/apparatus/version_3-0/photos/20220426_095017.jpg -------------------------------------------------------------------------------- /task/apparatus/version_3-0/photos/20220426_095023.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/apparatus/version_3-0/photos/20220426_095023.jpg -------------------------------------------------------------------------------- /task/apparatus/version_3-0/photos/20220520_141144 (2).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/apparatus/version_3-0/photos/20220520_141144 (2).jpg -------------------------------------------------------------------------------- /task/arduino/RRST_Arduino/RRST_Arduino.ino: -------------------------------------------------------------------------------- 1 | /*========================================================================== 2 | 3 | ===========================================================================*/ 4 | 5 | #include "TeensyStep.h" 6 | 7 | #define endstopPin 7 8 | #define enablePin 8 9 | #define dirPin 9 10 | #define stepPin 10 11 | 12 | #define MAXPOSITION 37000 13 | 14 | long initial_homing=-200; 15 | 16 | //Serial interface setup 17 | const byte numChars = 90; 18 | char receivedChars[numChars]; 19 | boolean newData = false; 20 | 21 | long stepperPosition = 0; 22 | 23 | Stepper motor(stepPin, dirPin); 24 | 25 | StepControl controller; // Use default settings 26 | 27 | void setup() 28 | { 29 | Serial.begin(9600); 30 | 31 | motor 32 | .setAcceleration(10000) // 20 000 33 | .setMaxSpeed(16000); 34 | 35 | // Setting up Endstop pin 36 | pinMode(endstopPin,INPUT_PULLUP); 37 | 38 | // Setting up enable pin and enable the driver 39 | pinMode(enablePin,OUTPUT); 40 | digitalWrite(enablePin,LOW); 41 | 42 | // Setup LED pin for debugging 43 | pinMode(LED_BUILTIN, OUTPUT); 44 | 45 | // Home the stepper 46 | homeStepper(); 47 | 48 | } 49 | 50 | void loop() 51 | { 52 | 53 | recvWithEndMarker(); 54 | showNewNumber(); 55 | 56 | 57 | 58 | motor.setTargetAbs(stepperPosition); 59 | 60 | // Set the target position: 61 | if(stepperPosition <= MAXPOSITION && stepperPosition >= 0) 62 | { 63 | motor.setTargetAbs(stepperPosition); 64 | controller.move(motor); // Do the move 65 | 66 | } 67 | else if (stepperPosition > MAXPOSITION) 68 | { 69 | stepperPosition = MAXPOSITION; 70 | Serial.print("Out of range! Setting position to MAXPOSITION: "); 71 | Serial.println(stepperPosition); 72 | motor.setTargetAbs(stepperPosition); 73 | controller.move(motor); // Do the move 74 | } 75 | else if (stepperPosition < 0) 76 | { 77 | homeStepper(); 78 | } 79 | 80 | 81 | } 82 | 83 | // Serial receive function 84 | void recvWithEndMarker() { 85 | static byte ndx = 0; 86 | char endMarker = '\n'; 87 | char rc; 88 | 89 | if (Serial.available() > 0) { 90 | rc = Serial.read(); 91 | Serial.print((char)rc); 92 | 93 | if (rc != endMarker) { 94 | receivedChars[ndx] = rc; 95 | ndx++; 96 | if (ndx >= numChars) { 97 | ndx = numChars - 1; 98 | } 99 | } 100 | else { 101 | receivedChars[ndx] = '\0'; // terminate the string 102 | ndx = 0; 103 | newData = true; 104 | } 105 | } 106 | } 107 | 108 | void showNewNumber() { 109 | if (newData == true) { 110 | Serial.print("Current position: "); 111 | Serial.println(stepperPosition); 112 | stepperPosition = 0; // new for this version 113 | stepperPosition = atol(receivedChars); // new for this version 114 | Serial.print("New position: "); 115 | Serial.println(receivedChars); 116 | newData = false; 117 | } 118 | } 119 | 120 | 121 | // Homing routine 122 | void homeStepper(){ 123 | 124 | Serial.println("Fast Homing begins:"); 125 | int endstopStatus = digitalRead(endstopPin); 126 | Serial.println(endstopStatus); 127 | 128 | while (digitalRead(endstopPin)) { // Make the Stepper move CCW until the switch is activated 129 | motor.setTargetRel(initial_homing); // Set the position to move to 130 | controller.move(motor); // Start moving the stepper 131 | delay(4); 132 | } 133 | 134 | Serial.println("Fast Homing ended"); 135 | endstopStatus = digitalRead(endstopPin); 136 | Serial.println(endstopStatus); 137 | 138 | Serial.println("Stepping forward"); 139 | Serial.println( motor.getPosition()); 140 | motor.setTargetRel(2000); 141 | controller.move(motor); 142 | delay(600); 143 | endstopStatus = digitalRead(endstopPin); 144 | Serial.println("Re-homing "); 145 | Serial.println(endstopStatus); 146 | Serial.println( motor.getPosition()); 147 | 148 | 149 | while (digitalRead(endstopPin)) { // Make the Stepper move CCW until the switch is activated 150 | motor.setTargetRel(-5); // Set the position to move to 151 | controller.move(motor); // Start moving the stepper 152 | Serial.println( motor.getPosition()); 153 | delay(3); 154 | } 155 | endstopStatus = digitalRead(endstopPin); 156 | Serial.println("Homed "); 157 | Serial.println(endstopStatus); 158 | stepperPosition = 0; 159 | motor.setPosition(0); 160 | } 161 | -------------------------------------------------------------------------------- /task/helpers/Dependencies.txt: -------------------------------------------------------------------------------- 1 | Respiratory Resistance Sensitivity Task 2 | 3 | experimentLauncher.m 4 | main.m 5 | loadParams.m 6 | respiroceptionTutorial.m 7 | 8 | moveResp.m 9 | moveResp2ITIpos.m 10 | moveResp2NoLoad.m 11 | resetResp.m 12 | scale2motorstep.m 13 | setupResp.m 14 | 15 | drawExpandingRing.m 16 | getConfidence.m 17 | getResponse.m 18 | playSound.m 19 | 20 | displayConfig.m 21 | experimentEnd.m 22 | keyConfig.m 23 | 24 | SetupRand.m 25 | angle2pix.m 26 | struct2csv.m 27 | randInRange.m 28 | 29 | subjectReportRRS.m 30 | 31 | Palamedes (in ./helpers) 32 | 33 | Psychtoolbox: 34 | Psychtoolbox-3-PTB_Beta-2019-08-07_V3.0.16 35 | 36 | Tested on: 37 | Windows 10 38 | MATLAB (R2020a), with Statistics and Machine Learning Toolbox v11.4 -------------------------------------------------------------------------------- /task/helpers/PAL_AMUD_updateUD_NN.m: -------------------------------------------------------------------------------- 1 | % 2 | %PAL_AMUD_updateUD Updates structure which contains settings for and 3 | % results of up/down adaptive method. 4 | % 5 | % syntax: UD = PAL_AMUD_updateUD(UD, response) 6 | % 7 | % After having created a structure 'UD' using PAL_AMUD_setupUD, use 8 | % something akin to the following loop to control stimulus intensity 9 | % during experimental run: 10 | % 11 | % while ~UD.stop 12 | % 13 | % %Present trial here at stimulus magnitude in 'UD.xCurrent' 14 | % %and collect response (1: correct/greater than, 0: incorrect/ 15 | % %smaller than) 16 | % 17 | % UD = PAL_AMUD_updateUD(UD, response); %update UD structure based 18 | % %on response 19 | % 20 | % end 21 | % 22 | % Introduced: Palamedes version 1.0.0 (NP) 23 | % Modified: Palamedes version 1.4.0, 1.4.5 (see History.m) 24 | % 25 | % Modified: added possibility of varying stepSize (e.g. to decrease 26 | % logarithmically), Niia Nikolova 2020 27 | 28 | function UD = PAL_AMUD_updateUD_NN(UD, response) 29 | 30 | trial = length(UD.x); 31 | UD.response(trial) = response; 32 | 33 | nStepSizes = length(UD.stepSizeDown); 34 | nReversal = UD.reversal(find(UD.reversal,1,'last')); % Cannot have 0 35 | if isempty(nReversal) 36 | nReversal = 1; 37 | elseif nReversal >= nStepSizes 38 | nReversal = nStepSizes; 39 | end 40 | currStepSizeDown = UD.stepSizeDown(nReversal); 41 | currStepSizeUp = UD.stepSizeUp(nReversal); 42 | 43 | if trial == 1 44 | UD.xStaircase(trial) = UD.x(trial); 45 | if response == 1 46 | UD.direction = -1; 47 | else 48 | UD.direction = 1; 49 | end 50 | end 51 | 52 | if response == 1 % Correct 53 | UD.d = UD.d + 1; 54 | if UD.d == UD.down || max(UD.reversal) < 1 55 | UD.xStaircase(trial+1) = UD.xStaircase(trial)-currStepSizeDown; 56 | if UD.xStaircase(trial+1) < UD.xMin && strcmp(UD.truncate,'yes') 57 | UD.xStaircase(trial+1) = UD.xMin; 58 | end 59 | UD.u = 0; 60 | UD.d = 0; 61 | UD.reversal(trial) = 0; 62 | if UD.direction == 1 63 | UD.reversal(trial) = sum(UD.reversal~=0) + 1; 64 | else 65 | UD.reversal(trial) = 0; 66 | end 67 | UD.direction = -1; 68 | else 69 | UD.xStaircase(trial+1) = UD.xStaircase(trial); 70 | end 71 | else 72 | UD.u = UD.u + 1; 73 | if UD.u == UD.up || max(UD.reversal) < 1 74 | UD.xStaircase(trial+1) = UD.xStaircase(trial)+currStepSizeUp; 75 | if UD.xStaircase(trial+1) > UD.xMax && strcmp(UD.truncate,'yes') 76 | UD.xStaircase(trial+1) = UD.xMax; 77 | end 78 | UD.u = 0; 79 | UD.d = 0; 80 | UD.reversal(trial) = 0; 81 | if UD.direction == -1 82 | UD.reversal(trial) = sum(UD.reversal~=0) + 1; 83 | else 84 | UD.reversal(trial) = 0; 85 | end 86 | UD.direction = 1; 87 | else 88 | UD.xStaircase(trial+1) = UD.xStaircase(trial); 89 | end 90 | end 91 | 92 | if strncmpi(UD.stopCriterion,'reversals',4) && sum(UD.reversal~=0) == UD.stopRule 93 | UD.stop = 1; 94 | end 95 | if strncmpi(UD.stopCriterion,'trials',4) && trial == UD.stopRule 96 | UD.stop = 1; 97 | end 98 | if ~UD.stop 99 | UD.x(trial+1) = UD.xStaircase(trial+1); 100 | if UD.x(trial+1) > UD.xMax 101 | UD.x(trial+1) = UD.xMax; 102 | elseif UD.x(trial+1) < UD.xMin 103 | UD.x(trial+1) = UD.xMin; 104 | end 105 | UD.xCurrent = UD.x(trial+1); 106 | end 107 | 108 | currStepSizeDown 109 | currStepSizeUp 110 | thisStim = UD.xCurrent -------------------------------------------------------------------------------- /task/helpers/SetupRand.m: -------------------------------------------------------------------------------- 1 | % Set up the randomizers for uniform and normal distributions. 2 | % It is of great importance to do this before anything else! 3 | 4 | % rand('twister',sum(100*clock)); 5 | rand('state',sum(100*clock)); 6 | randn('state',sum(100*clock)); 7 | -------------------------------------------------------------------------------- /task/helpers/angle2pix.m: -------------------------------------------------------------------------------- 1 | function pix = angle2pix(display,ang) 2 | %pix = angle2pix(display,ang) 3 | % 4 | %converts visual angles in degrees to pixels. 5 | % 6 | %Inputs: 7 | %display.dist (distance from screen (cm)) 8 | %display.width (width of screen (cm)) 9 | %display.resolution (number of pixels of display in horizontal direction) 10 | % 11 | %ang (visual angle) 12 | % 13 | %Warning: assumes isotropic (square) pixels 14 | 15 | %Written 11/1/07 gmb zre 16 | 17 | %Calculate pixel size 18 | pixSize = display.width/display.resolution(1); %cm/pix 19 | 20 | sz = 2*display.dist*tan(pi*ang/(2*180)); %cm 21 | 22 | pix = round(sz/pixSize); %pix 23 | 24 | 25 | return 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /task/helpers/audio/audioToPlayGoesHere.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/helpers/audio/audioToPlayGoesHere.txt -------------------------------------------------------------------------------- /task/helpers/displayConfig.m: -------------------------------------------------------------------------------- 1 | function [scr] = displayConfig(scr) 2 | %function [scr] = displayConfig(scr) 3 | % 4 | % Called by main.m 5 | % Input: 6 | % scr struct with screen / display settings 7 | % 8 | % Niia Nikolova 9 | % Last edit: 11/06/2020 10 | 11 | 12 | %% Set-up screen 13 | if length(Screen('Screens')) > 1 14 | scr.ExternalMonitor = 1;% set to 1 for secondary monitor 15 | % N.B. It's not optimal to use external monitor for newer Win systems 16 | % (Windows 7+) due to timing issues 17 | else 18 | scr.ExternalMonitor = 0; 19 | end 20 | 21 | if scr.ExternalMonitor 22 | scr.screenID = max(Screen('Screens')); 23 | [width, height]=Screen('DisplaySize', scr.screenID); % returns screen dim in mm 24 | width = width/10; % in cm 25 | height = height/10; 26 | 27 | if ~isfield(scr,'MonitorHeight') || isempty(scr.MonitorHeight) 28 | scr.MonitorHeight = height; end % in cm 29 | if ~isfield(scr,'MonitorWidth') || isempty(scr.MonitorWidth) 30 | scr.MonitorWidth = width; end 31 | if ~isfield(scr,'ViewDist') || isempty(scr.ViewDist) 32 | scr.ViewDist = 75; end 33 | else % Laptop 34 | scr.screenID = min(Screen('Screens')); 35 | [width, height]=Screen('DisplaySize', scr.screenID); 36 | width = width/10; 37 | height = height/10; 38 | 39 | if ~isfield(scr,'MonitorHeight') || isempty(scr.MonitorHeight) 40 | scr.MonitorHeight = height; end 41 | if ~isfield(scr,'MonitorWidth') || isempty(scr.MonitorWidth) 42 | scr.MonitorWidth = width; end 43 | if ~isfield(scr,'ViewDist') || isempty(scr.ViewDist) 44 | scr.ViewDist = 40; end 45 | end 46 | 47 | 48 | scr.dist = scr.ViewDist; 49 | scr.width = scr.MonitorWidth; 50 | scr.resolution = Screen('Resolution', scr.screenID); 51 | 52 | %% Colours and text params 53 | Gray = GrayIndex(scr.screenID); 54 | % White = WhiteIndex(scr.screenID); 55 | % Black = BlackIndex(scr.screenID); 56 | scr.BackgroundGray = Gray; 57 | % scr.TextColour = Black; 58 | 59 | 60 | end -------------------------------------------------------------------------------- /task/helpers/drawExpandingRing.m: -------------------------------------------------------------------------------- 1 | function [scr, vars] = drawExpandingRing(scr, vars) 2 | %[scr, vars] = drawExpandingRing(scr, vars) Draw expanding rings 3 | % Expanding / contracting rings to cue inhale/exhale for respiroception 4 | % task 5 | % Based on PTB, PsychDemos/ExpandingRingsShader 6 | % 7 | % Project: Respiroception 8 | % 9 | % Input: 10 | % vars struct with key parameters (most are defined in loadParams.m) 11 | % scr struct with screen / display settings 12 | % 13 | % Niia Nikolova 14 | % Last edit: 29/30/2021 15 | 16 | cueExhale = 0; % 1 contract ring to cue/pace exhalation 17 | ringtype = 1; 18 | global GL; 19 | 20 | %% Create movie file 21 | % movName = ['expandingRing_',num2str(vars.switchColour),'.mp4']; 22 | % moviePtr = Screen('CreateMovie', scr.win, movName); 23 | %% Setup size of expandingRing movie rect 24 | movieRectSize = [0; 0; 5; 5]; 25 | multFactorW = scr.resolution.width ./ scr.MonitorWidth; 26 | multFactorH = scr.resolution.height ./ scr.MonitorHeight; 27 | scr.movieRect(3) = movieRectSize(3) .* multFactorW; 28 | scr.movieRect(4) = movieRectSize(4) .* multFactorH; 29 | movrect = CenterRectOnPoint(scr.movieRect, scr.resolution.width/2, scr.resolution.height/2); 30 | 31 | 32 | % Duration of plux flash for stimulus (2 frames) 33 | pluxDurationSec = scr.pluxDur(1) / scr.hz; 34 | 35 | % Set screenID, win, windowSize 36 | tw = scr.winRect(3); 37 | th = scr.winRect(4); 38 | 39 | % Load the 'ExpandingRingsShader' fragment program from file, compile it, 40 | % return a handle to it: 41 | rshader = [PsychtoolboxRoot 'PsychDemos/ExpandingRingsShader.vert.txt']; 42 | 43 | if ringtype == 0 44 | expandingRingShader = LoadGLSLProgramFromFiles({ rshader, [PsychtoolboxRoot 'PsychDemos/ExpandingRingsShader.frag.txt'] }, 0); 45 | % Width of a single ring (radius) in pixels: 46 | ringwidth = 20; 47 | else 48 | expandingRingShader = LoadGLSLProgramFromFiles({ rshader, [PsychtoolboxRoot 'PsychDemos/ExpandingSinesShader.frag.txt'] }, 0); % ,1 debug flag was 1 49 | % Width of a single ring (radius) / Period of a single color sine wave in pixels: 50 | ringwidth = 400; 51 | end 52 | 53 | % Create a purely virtual texture 'ringtex' of size tw x th virtual pixels 54 | ringtex = Screen('SetOpenGLTexture', scr.win, [], 0, GL.TEXTURE_RECTANGLE_EXT, tw, th, 1, expandingRingShader); 55 | 56 | % Bind the shader: After binding it, we can setup some constant parameters 57 | % for our stimulus, so called GLSL 'uniform' variables. These are 58 | % parameters that are constant during the whole session, or at least only 59 | % change infrequently. They are set outside the fast stimulus rendering 60 | % loop and potentially optimized by the graphics driver for fast execution: 61 | glUseProgram(expandingRingShader); 62 | 63 | % Set the 'RingCenter' parameter to the center position of the ring 64 | % stimulus [tw/2, th/2]: 65 | glUniform2f(glGetUniformLocation(expandingRingShader, 'RingCenter'), tw/2, th/2); 66 | 67 | % Done with setup, disable shader. All other stimulus parameters will be 68 | % set at each Screen('DrawTexture') invocation to allow fast dynamic change 69 | % of the parameters during each stimulus redraw: 70 | glUseProgram(0); 71 | 72 | % Define first and second ring color as RGBA vector with normalized color 73 | % component range between 0.0 and 1.0 (switch colours on breath 2) 74 | if vars.switchColour 75 | 76 | % Shaded rings, grey & teal 77 | firstColor = [0.14, 0.82, 0.67, 1]; % dark teal 78 | secondColor = [.2, .2, .2, 1]; % grey 79 | 80 | 81 | else 82 | 83 | % Shaded rings, greay & teal 84 | firstColor = [0.14, 0.82, 0.67, 1]; % dark teal 85 | secondColor = [.2, .2, .2, 1]; % grey 86 | end 87 | 88 | % Initial shift- and radius value is zero: We start with a point in the 89 | % center of the screen which will then expand and scroll by one pixel at 90 | % each redraw: 91 | shiftvalue = 0; 92 | count = 0; 93 | 94 | % Retrieve monitor refresh duration: 95 | ifi = Screen('GetFlipInterval', scr.win); 96 | 97 | 98 | %% Draw a rectangle where we wan to cut the movie (x y width height) 99 | % movieRectSize = [0; 0; 5; 5]; 100 | % multFactorW = scr.resolution.width ./ scr.MonitorWidth; 101 | % multFactorH = scr.resolution.height ./ scr.MonitorHeight; 102 | % scr.movieRect(3) = movieRectSize(3) .* multFactorW; 103 | % scr.movieRect(4) = movieRectSize(4) .* multFactorH; 104 | % movrect = CenterRectOnPoint(scr.movieRect, scr.resolution.width/2, scr.resolution.height/2); 105 | % Screen('FrameRect', scr.win, [1 1 1], movrect,2); 106 | 107 | 108 | 109 | % Show a pale circle and pause briefly 110 | % We use 'firstColor' for the even rings, 'secondColor' for the odd 111 | % rings. 112 | rad = scr.rad+5; 113 | Screen('FrameOval', scr.win, firstColor, [scr.x_middle-rad scr.y_middle-rad scr.x_middle+rad scr.y_middle+rad], 3, 3); 114 | Screen('Flip', scr.win); 115 | WaitSecs(vars.breathPauseT); 116 | 117 | % Perform initial flip to gray background and sync us to the retrace: 118 | Screen('FrameOval', scr.win, firstColor, [scr.x_middle-rad scr.y_middle-rad scr.x_middle+rad scr.y_middle+rad], 3, 3); 119 | [vbl, StimOn] = Screen('Flip', scr.win); 120 | 121 | % While loop to show stimulus until StimT seconds elapsed... 122 | % Inhale 123 | stimDurFr = vars.inhaleT * scr.hz; 124 | for thisFr = 1:stimDurFr 125 | % while (GetSecs - StimOn) <= vars.inhaleT % Inhale 126 | 127 | % If using Plux for physiological measures, display a square in the 128 | % bottom right screen corner 129 | if vars.pluxSynch 130 | % if were in the first pluxDurationSec seconds, draw the rectangle 131 | if vars.stimIsHere == 1 &&((GetSecs - StimOn) <= pluxDurationSec) % stim interval 132 | Screen('FillRect', scr.win, scr.pluxWhite, scr.pluxRect); 133 | elseif vars.stimIsHere == 0 &&((GetSecs - StimOn) <= pluxDurationSec) % stim interval 134 | Screen('FillRect', scr.win, scr.pluxBlack, scr.pluxRect); 135 | 136 | end 137 | end 138 | 139 | % Increase shift and radius of stimulus: 140 | shiftvalue = shiftvalue + 5; 141 | radius = shiftvalue; 142 | count = count + 1; 143 | 144 | % We use 'firstColor' for the even rings, 'secondColor' for the odd 145 | % rings. 146 | Screen('DrawTexture', scr.win, ringtex, [], [], [], [], [], firstColor, [], [], [secondColor(1), secondColor(2), secondColor(3), secondColor(4), shiftvalue, ringwidth, radius, 0]); 147 | DrawFormattedText(scr.win, [vars.InstructionInhale], 'center', 3*th/4, scr.TextColour); 148 | Screen('FrameOval', scr.win, firstColor, [scr.x_middle-rad scr.y_middle-rad scr.x_middle+rad scr.y_middle+rad], 3, 3); 149 | 150 | % Request stimulus onset at next video refresh: 151 | vbl = Screen('Flip', scr.win, vbl + ifi/2); 152 | 153 | %% Add frame to movie 154 | %Screen('AddFrameToMovie', scr.win);% [,rect] [,bufferName] [,moviePtr=0] [,frameduration=1]) 155 | 156 | %% Save an image of the frame 157 | imageArray=Screen('GetImage', scr.win, movrect);% [,bufferName] [,floatprecision=0] [,nrchannels=3]) 158 | imString = sprintf('%02d', count); 159 | imname = ['im_',imString,'.png']; 160 | imwrite(imageArray, imname); 161 | 162 | end 163 | 164 | 165 | % Pause 166 | while (GetSecs - StimOn) <= (vars.inhaleT + vars.breathPauseT) 167 | % Do nothing 168 | 169 | end 170 | 171 | %% Close movie 172 | %Screen('FinalizeMovie', moviePtr); 173 | 174 | 175 | % Exhale 176 | if cueExhale 177 | while (GetSecs - StimOn) <= (vars.inhaleT + vars.breathPauseT + vars.exhaleT) % Exhale 178 | % Increase shift and radius of stimulus: 179 | shiftvalue = shiftvalue - 3; 180 | radius = shiftvalue; 181 | count = count + 1; 182 | 183 | % We use 'firstColor' for the even rings, 'secondColor' for the odd 184 | % rings. 185 | Screen('DrawTexture', scr.win, ringtex, [], [], [], [], [], firstColor, [], [], [secondColor(1), secondColor(2), secondColor(3), secondColor(4), shiftvalue, ringwidth, radius, 0]); 186 | DrawFormattedText(scr.win, [vars.InstructionExhale], 'center', 3*th/4, scr.TextColour); 187 | 188 | % Request stimulus onset at next video refresh: 189 | vbl = Screen('Flip', scr.win, vbl + ifi/2); 190 | end 191 | end 192 | 193 | % disp(['Max ring radius: ', num2str(radius)]); 194 | 195 | end 196 | 197 | -------------------------------------------------------------------------------- /task/helpers/expandingRing60.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/helpers/expandingRing60.avi -------------------------------------------------------------------------------- /task/helpers/expandingRing60_hiRes.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embodied-computation-group/RespiroceptionMethodsPaper/4c503786175f2ab6b473951cb080838df8bf6dff/task/helpers/expandingRing60_hiRes.avi -------------------------------------------------------------------------------- /task/helpers/experimentEnd.m: -------------------------------------------------------------------------------- 1 | function experimentEnd(vars, scr, keys, Results, stair) 2 | %function experimentEnd(vars, scr, keys, Results, stair) 3 | 4 | % Get open audio device count 5 | countAudDev = PsychPortAudio('GetOpenDeviceCount'); 6 | 7 | if vars.Aborted 8 | 9 | % Abort screen 10 | Screen('FillRect', scr.win, scr.BackgroundGray, scr.winRect); 11 | DrawFormattedText(scr.win, 'Experiment was aborted!', 'center', 'center', scr.TextColour); 12 | [~, ~] = Screen('Flip', scr.win); 13 | WaitSecs(0.5); 14 | ShowCursor; 15 | sca; 16 | disp('Experiment aborted by user!'); 17 | 18 | % Save, mark the run 19 | vars.DataFileName = ['Aborted_', vars.DataFileName]; 20 | save(strcat(vars.OutputFolder, vars.DataFileName), 'stair', 'Results', 'vars', 'scr', 'keys' ); 21 | disp(['Run was aborted. Results were saved as: ', vars.DataFileName]); 22 | 23 | % and as .csv 24 | Results.stair = stair; % Add staircase params to Results struct for the .csv 25 | csvName = strcat(vars.OutputFolder, vars.DataFileName, '.csv'); 26 | struct2csv(Results, csvName); 27 | 28 | % Stop audio playback 29 | if countAudDev ~= 0 30 | PsychPortAudio('Close', 0); 31 | end 32 | 33 | elseif vars.Error 34 | % Error 35 | vars.DataFileName = ['Error_',vars.DataFileName]; 36 | save(strcat(vars.OutputFolder, vars.DataFileName), 'stair', 'Results', 'vars', 'scr', 'keys' ); 37 | % and as .csv 38 | Results.stair = stair; % Add staircase structure to Results to save 39 | csvName = strcat(vars.OutputFolder, vars.DataFileName, '.csv'); 40 | struct2csv(Results, csvName); 41 | 42 | disp(['Run crashed. Results were saved as: ', vars.DataFileName]); 43 | disp(' ** Error!! ***') 44 | 45 | % Stop audio playback 46 | if countAudDev ~= 0 47 | PsychPortAudio('Close', 0); 48 | end 49 | 50 | % Output the error message that describes the error: 51 | psychrethrow(psychlasterror); 52 | 53 | else % Successfull run 54 | % Show end screen and clean up 55 | Screen('FillRect', scr.win, scr.BackgroundGray, scr.winRect); 56 | DrawFormattedText(scr.win, vars.InstructionEnd, 'center', 'center', scr.TextColour); 57 | [~, ~] = Screen('Flip', scr.win); 58 | WaitSecs(3); 59 | 60 | % Save the data 61 | save(strcat(vars.OutputFolder, vars.DataFileName), 'stair', 'Results', 'vars', 'scr', 'keys' ); 62 | disp(['Run complete. Results were saved as: ', vars.DataFileName]); 63 | 64 | % and as .csv 65 | Results.stair = stair; % Add staircase structure to Results to save 66 | csvName = strcat(vars.OutputFolder, vars.DataFileName, '.csv'); 67 | struct2csv(Results, csvName); 68 | 69 | % Stop audio playback 70 | if countAudDev ~= 0 71 | PsychPortAudio('Close', 0); 72 | end 73 | end 74 | 75 | 76 | sca; 77 | ShowCursor; 78 | fclose('all'); 79 | Priority(0); 80 | -------------------------------------------------------------------------------- /task/helpers/getConfidence.m: -------------------------------------------------------------------------------- 1 | function [vars] = getConfidence(keys, scr, vars) 2 | %function [vars] = getConfidence(keys, scr, vars) 3 | % 4 | % Get the participants confidence response - either keyboard or mouse 5 | % 6 | % Project: RRST task (respiratory resistance sensitivity) 7 | % 8 | % Input: 9 | % keys (struct) 10 | % scr (struct) 11 | % vars (struct) 12 | % 13 | % 14 | % Output: 15 | % vars (struct) 16 | % 17 | % Niia Nikolova 18 | % Last edit: 30/06/2020 19 | 20 | switch vars.InputDevice 21 | 22 | case 1 % Keyboard response 23 | 24 | % Rate confidence: 1 Unsure, 2 Sure, 3 Very sure 25 | 26 | Screen('FillRect', scr.win, scr.BackgroundGray, scr.winRect); 27 | DrawFormattedText(scr.win, [vars.InstructionConf], 'center', 'center', scr.TextColour); 28 | [~, StartConf] = Screen('Flip', scr.win); 29 | vars.ConfOnset = StartConf; 30 | 31 | % loop until valid key is pressed or ConfT is reached 32 | while (GetSecs - StartConf) <= vars.ConfT 33 | 34 | % KbCheck for response 35 | if keys.KeyCode(keys.One)==1 36 | % update results 37 | vars.ConfResp = 1; 38 | vars.ValidTrial(2) = 1; 39 | elseif keys.KeyCode(keys.Two)==1 40 | % update results 41 | vars.ConfResp = 2; 42 | vars.ValidTrial(2) = 1; 43 | elseif keys.KeyCode(keys.Three)==1 44 | % update results 45 | vars.ConfResp = 3; 46 | vars.ValidTrial(2) = 1; 47 | elseif keys.KeyCode(keys.Escape)==1 48 | vars.abortFlag = 1; 49 | 50 | else 51 | % DrawText: Please press a valid key... 52 | end 53 | 54 | [~, EndConf, keys.KeyCode] = KbCheck; 55 | WaitSecs(0.001); 56 | 57 | if ~vars.fixedTiming 58 | % Stop waiting when a rating is made 59 | if(vars.ValidTrial(2)), WaitSecs(0.2); break; end 60 | end 61 | 62 | % Compute response time 63 | vars.ConfRatingT = (EndConf - StartConf); 64 | 65 | end 66 | 67 | % show brief feedback 68 | if ~isnan(vars.ConfResp) 69 | switch vars.ConfResp 70 | case 1 71 | feedbackXPos = ((scr.winRect(3)/2)-350); 72 | case 2 73 | feedbackXPos = ((scr.winRect(3)/2)); 74 | case 3 75 | feedbackXPos = ((scr.winRect(3)/2)+350); 76 | end 77 | 78 | feedbackString = 'O'; 79 | Screen('FillRect', scr.win, scr.BackgroundGray, scr.winRect); 80 | DrawFormattedText(scr.win, [vars.InstructionConf], 'center', 'center', scr.TextColour); 81 | DrawFormattedText(scr.win, feedbackString, feedbackXPos, ((scr.winRect(4)/2)+200), scr.AccentColour); 82 | [~, ~] = Screen('Flip', scr.win); 83 | WaitSecs(0.5); 84 | 85 | disp(['Confidence recorded: ', num2str(vars.ConfResp)]); 86 | 87 | else 88 | disp('No confidence recorded.'); 89 | end 90 | 91 | case 2 % Mouse response 92 | answer = 0; % reset response flag 93 | 94 | vars.ConfOnset = GetSecs; 95 | % We set a time-out for conf rating, b/c otherwise it's Inf... 96 | [position, ConfTimeStamp, RT, answer] = slideScale(scr.win, ... 97 | vars.InstructionConf, ... 98 | scr.winRect, ... 99 | vars.ConfEndPoins, ... 100 | 'scalalength', 0.6,... 101 | 'linelength', 20,... 102 | 'width', 10,... 103 | 'device', 'mouse', ... 104 | 'stepsize', 4, ... 105 | 'startposition', 'shuffle', ... 106 | 'range', 2, ... 107 | 'aborttime', vars.ConfT,... 108 | 'slidercolor', [0.14, 0.82, 0.67],... 109 | 'scalacolor', [1 1 1]); 110 | % old slider color [0.14 0.42 0.38] 111 | 112 | % % If we want to allow infinite resp time 113 | % [position, ConfTimeStamp, RT, answer] = slideScale(scr.win, ... 114 | % vars.InstructionConf, ... 115 | % scr.winRect, ... 116 | % vars.ConfEndPoins, ... 117 | % 'scalalength', 0.7,... 118 | % 'linelength', 20,... 119 | % 'width', 6,... 120 | % 'device', 'mouse', ... 121 | % 'stepsize', 10, ... 122 | % 'startposition', 'shuffle', ... 123 | % 'range', 2); 124 | 125 | vars.ConfOffset = GetSecs; 126 | % update results 127 | if answer 128 | vars.ConfResp = position; 129 | vars.ValidTrial(2) = 1; 130 | end 131 | vars.ConfRatingT = ConfTimeStamp; 132 | vars.ConfRatingRT = RT; 133 | 134 | % Show rating in command window 135 | if ~isnan(vars.ConfResp) 136 | disp(['Confidence recorded: ', num2str(round(vars.ConfResp))]); 137 | else 138 | disp('No confidence recorded.'); 139 | end 140 | 141 | end -------------------------------------------------------------------------------- /task/helpers/getPMFbasedTrials.m: -------------------------------------------------------------------------------- 1 | function trialsList = getPMFbasedTrials(vars, stair) 2 | %% function trialsList = getPMFbasedTrials(vars, stair) 3 | % Create a list of trials based on a PMF (Psi method, stored in stair [structure]) 4 | % 5 | % Niia Nikolova 2020 6 | 7 | % Get the stim levels at which participant performance is 8 | % .75 +/- jitter (.05) 9 | pptPMFvals = [stair.PM.threshold(length(stair.PM.threshold)) 10.^stair.PM.slope(length(stair.PM.threshold)) 0 stair.PM.lapse(length(stair.PM.threshold))]; 10 | invPMFvals = [vars.MC.threshLevel-vars.MC.jitter, vars.MC.threshLevel, vars.MC.threshLevel+vars.MC.jitter ]; 11 | 12 | inversePMFstims = stair.PF(pptPMFvals, invPMFvals, 'inverse'); 13 | pptThreshLevels = round(inversePMFstims); 14 | 15 | % Generate repeating list & randomize order of stimuli 16 | nReps = ceil(vars.NMetacogTrials / 3); 17 | StimTrialList = repmat(pptThreshLevels', nReps, 1); 18 | randomorder = randperm(length(StimTrialList)); 19 | trialsList = StimTrialList(randomorder); 20 | 21 | trialsList(trialsList > 100) = 100; % No greater than 100 22 | 23 | end 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /task/helpers/getResponse.m: -------------------------------------------------------------------------------- 1 | function [vars] = getResponse(keys, scr, vars) 2 | %function [vars] = getResponse(keys, scr, vars) 3 | % 4 | % Get the participants response - either keyboard or mouse 5 | % 6 | % Project: Respiroception task 7 | % 8 | % Input: 9 | % keys (struct) 10 | % scr (struct) 11 | % vars (struct) 12 | % 13 | % 14 | % Output: 15 | % vars (struct) 16 | % 17 | % 18 | % Niia Nikolova 19 | % Last edit: 25/09/2020 20 | 21 | feedbackString = 'O'; 22 | outputString = ''; 23 | postResponseInt = 0.3; % pause for 300ms after response 24 | 25 | % Duration of plux flash for response (4 frames) 26 | pluxDurationSec = scr.pluxDur(2) / scr.hz; 27 | 28 | 29 | % loop until valid key is pressed or RespT is reached 30 | while ((GetSecs - vars.StartRT) <= vars.RespT) 31 | 32 | switch vars.InputDevice 33 | 34 | case 1 % Keyboard response 35 | 36 | % KbCheck for response 37 | if keys.KeyCode(keys.Left)==1 % Stim 1 38 | % update results 39 | vars.RespLR = 0; 40 | vars.ValidTrial(1) = 1; 41 | 42 | elseif keys.KeyCode(keys.Right)==1 % Stim 2 43 | % update results 44 | vars.RespLR = 1; 45 | vars.ValidTrial(1) = 1; 46 | 47 | elseif keys.KeyCode(keys.Escape)==1 48 | vars.abortFlag = 1; 49 | 50 | else 51 | % ? DrawText: Please press a valid key... 52 | end 53 | 54 | [~, vars.EndRT, keys.KeyCode] = KbCheck; 55 | WaitSecs(0.001); 56 | 57 | case 2 % Mouse 58 | 59 | [~,~,buttons] = GetMouse; 60 | while (~any(buttons)) && ((GetSecs - vars.StartRT) <= vars.RespT) % wait for press & response time 61 | [~,~,buttons] = GetMouse; % L [1 0 0], R [0 0 1] 62 | end 63 | 64 | if buttons == [1 0 0] % Left - Stim 1 65 | % update results 66 | vars.RespLR = 0; 67 | vars.ValidTrial(1) = 1; 68 | 69 | elseif buttons == [0 0 1] % Right - Stim 2 70 | % update results 71 | vars.RespLR = 1; 72 | vars.ValidTrial(1) = 1; 73 | 74 | else 75 | 76 | end 77 | vars.EndRT = GetSecs; 78 | end 79 | 80 | % %% Brief feedback 81 | % if vars.Resp == 1% happy 82 | % emotString = 'Happy'; 83 | % feedbackXPos = ((scr.winRect(3)/2)+150); 84 | % elseif vars.Resp == 0 85 | % emotString = 'Angry'; 86 | % feedbackXPos = ((scr.winRect(3)/2)-250); 87 | % else 88 | % emotString = ''; 89 | % feedbackXPos = ((scr.winRect(3)/2)); 90 | % end 91 | 92 | %% Once a response is made, (show feedback), pause for a few ms 93 | % fixed timing - wait for response interval to pass 94 | [~, stimOn] = Screen('Flip', scr.win); 95 | if vars.fixedTiming 96 | if ~isnan(vars.RespLR) && (vars.ValidTrial(1)) % valid trial 97 | Screen('FillRect', scr.win, scr.BackgroundGray, scr.winRect); 98 | DrawFormattedText(scr.win, [vars.InstructionQ], 'center', 3*scr.height/4, scr.TextColour); 99 | 100 | % % Feedback 101 | % DrawFormattedText(scr.win, feedbackString, feedbackXPos, ((scr.winRect(4)/2)+150), scr.AccentColour); 102 | 103 | [~, ~] = Screen('Flip', scr.win); 104 | 105 | % outputString = ['Response recorded: ', emotString]; 106 | else 107 | outputString = 'No response recorded'; 108 | end 109 | 110 | else % Variable timing 111 | if ~isnan(vars.RespLR) && (vars.ValidTrial(1)) 112 | 113 | while (GetSecs - stimOn) <= postResponseInt 114 | Screen('FillRect', scr.win, scr.BackgroundGray, scr.winRect); 115 | DrawFormattedText(scr.win, [vars.InstructionQ], 'center', 3*scr.height/4, scr.TextColour); 116 | 117 | % %Feedback 118 | % DrawFormattedText(scr.win, feedbackString, feedbackXPos, ((scr.winRect(4)/2)+150), scr.AccentColour); 119 | 120 | % If using Plux for physiological measures, display a square in the 121 | % bottom right screen corner 122 | if vars.pluxSynch 123 | % if were in the first pluxDurationSec seconds, draw the rectangle 124 | if vars.RespLR == 0 &&((GetSecs - stimOn) <= pluxDurationSec) % stim interval 125 | Screen('FillRect', scr.win, scr.pluxWhite, scr.pluxRect); 126 | elseif vars.RespLR == 1 &&((GetSecs - stimOn) <= pluxDurationSec) % stim interval 127 | Screen('FillRect', scr.win, scr.pluxBlack, scr.pluxRect); 128 | end 129 | end 130 | Screen('Flip', scr.win); 131 | 132 | end 133 | %WaitSecs(0.3); 134 | 135 | else 136 | outputString = 'No response recorded'; 137 | end 138 | 139 | WaitSecs(0.2); 140 | break; 141 | end 142 | 143 | 144 | end 145 | 146 | disp(outputString); 147 | 148 | end 149 | -------------------------------------------------------------------------------- /task/helpers/keyConfig.m: -------------------------------------------------------------------------------- 1 | function [keys] = keyConfig() 2 | %function [keys] = keyConfig() 3 | 4 | % Set-up keyboard 5 | KbName('UnifyKeyNames') 6 | keys.Escape = KbName('ESCAPE'); 7 | keys.Space = KbName('space'); 8 | keys.Left = KbName('LeftArrow'); 9 | keys.Right = KbName('RightArrow'); 10 | keys.One = KbName('1!'); 11 | keys.Two = KbName('2@'); 12 | keys.Three = KbName('3#'); 13 | keys.SoundStop = KbName('s'); 14 | 15 | end -------------------------------------------------------------------------------- /task/helpers/loadFiles.m: -------------------------------------------------------------------------------- 1 | function [outputArray] = loadFiles(dataFolder, searchString) 2 | % function [outputArray] = loadFiles(dataFolder, searchString) 3 | % 4 | % Load all files in inputDir and concat data into outputArray 5 | % 6 | % dataFolder path to folder from which to load files 7 | % searchString [optional] string specifyingn which files to load, 8 | % can use wildcard, e.g. '\EmotDiscrim*' 9 | % 10 | % Niia Nikolova 11 | % Last edit: 24/04/2020 12 | 13 | 14 | %% Set-up 15 | % Check input args 16 | if nargin < 1 17 | disp('loadFiles error! Please specify a directory.'); 18 | return; 19 | elseif nargin == 1 20 | searchString = '\*'; 21 | end 22 | 23 | % Find files in folder 24 | dataSpec = [dataFolder, searchString]; 25 | files = dir(dataSpec); 26 | 27 | % Get some information about the data 28 | if ~isempty(files) 29 | firstFilePath = [files(1).folder, filesep, files(1).name]; 30 | firstFile = load(firstFilePath, 'Results'); 31 | structNames = (fieldnames(firstFile.Results)); 32 | nFields = numel(structNames); 33 | else 34 | disp('Error, no files found in directory.'); 35 | return 36 | end 37 | 38 | % Set up output array 39 | dimRows = 1; 40 | dimCols = nFields; 41 | outputArray = zeros(dimRows, dimCols); 42 | 43 | 44 | %% Run through the files and extract the data 45 | for thisFile = 1 : length(files) 46 | thisFilePath = [files(thisFile).folder, filesep, files(thisFile).name]; 47 | thisFileResults = load(thisFilePath, 'Results'); % load struct 48 | thisFileVars = load(thisFilePath, 'vars'); % load struct 49 | 50 | % loop through stuct fields and populate outputArray rows 51 | for thisField = 1:nFields 52 | 53 | thisFieldData = thisFileResults.Results.(structNames{thisField}); 54 | 55 | % % code individual in stimulus <---- N.B. FIELD NUMBER SPECIFIC TO EMOT DISCRIM TASK *** 56 | % % m08 , m31 , f06 , f24 57 | % if thisField == 9 58 | % strings2replace = thisFieldData; 59 | % [~,~,thisFieldData ] = unique(strings2replace,'stable'); 60 | % end 61 | 62 | % Ignote signalInterval field, mismatch in dimensions 63 | if strcmp(structNames{thisField}, 'SignalInterval') ~= 1 64 | dataFileArray(1:size(thisFieldData,1), thisField) = thisFieldData; 65 | end 66 | 67 | end%thisField 68 | 69 | % concatenate output array 70 | outputArray = vertcat(outputArray, dataFileArray); 71 | 72 | end 73 | 74 | % clean up zero rows, NaN columns 75 | outputArray( ~any(outputArray,2), : ) = []; % for zero - rows 76 | outputArray = outputArray(:, ~all(isnan(outputArray))); % for nan - columns 77 | 78 | % done 79 | end 80 | -------------------------------------------------------------------------------- /task/helpers/makeMovie.m: -------------------------------------------------------------------------------- 1 | % Make an avi movie from a collection of PNG images in a folder. 2 | % Specify the folder. 3 | myFolder = 'C:\Users\niian\Documents\GitHub\ECG_aarhus\Respiroception\task\RRS_2-2\images'; 4 | if ~isdir(myFolder) 5 | errorMessage = sprintf('Error: The following folder does not exist:\n%s', myFolder); 6 | uiwait(warndlg(errorMessage)); 7 | return; 8 | end 9 | % Get a directory listing. 10 | filePattern = fullfile(myFolder, '*.png'); 11 | pngFiles = dir(filePattern); 12 | % Open the video writer object. 13 | writerObj = VideoWriter('expandingRing60.avi'); 14 | writerObj.FrameRate = 60; 15 | open(writerObj); 16 | % Go through image by image writing it out to the AVI file. 17 | for frameNumber = 1 : length(pngFiles) 18 | % Construct the full filename. 19 | baseFileName = pngFiles(frameNumber).name; 20 | fullFileName = fullfile(myFolder, baseFileName); 21 | % Display image name in the command window. 22 | fprintf(1, 'Now reading %s\n', fullFileName); 23 | % Display image in an axes control. 24 | thisimage = imread(fullFileName); 25 | imshow(thisimage); % Display image. 26 | drawnow; % Force display to update immediately. 27 | % Write this frame out to the AVI file. 28 | writeVideo(writerObj, thisimage); 29 | end 30 | % Close down the video writer object to finish the file. 31 | close(writerObj); -------------------------------------------------------------------------------- /task/helpers/mixArray.m: -------------------------------------------------------------------------------- 1 | function [arrayOut] = mixArray(arrayIn) 2 | %function [arrayOut] = mixArray(arrayIn) 3 | % 4 | % Mixes up the elements of an input array arrayIn using randperm 5 | % 6 | % Niia Nikolova 2020 7 | 8 | randomorder = randperm(length(arrayIn)); 9 | arrayOut = arrayIn(randomorder); 10 | 11 | end -------------------------------------------------------------------------------- /task/helpers/multSubPMF.m: -------------------------------------------------------------------------------- 1 | % 2 | % Project: Respiratory Resistance Sensitivity (RRS) Task. Respiratory 3 | % interoception 4 | % 5 | % simple PF fitting (multiple subjects/runs) using 6 | % Palamedes 7 | % 8 | % Niia Nikolova 9 | % Last edit: 03/10/2020 10 | % 11 | 12 | 13 | % Define where key data are in the results matrix (i.e. 'outputExcl'), by 14 | % column # 15 | stimIntensity = 2; 16 | responsesAccuracy = 4; 17 | subjectIDs = 8; 18 | 19 | %% Load file 20 | loadFile = uigetfile('*.mat', 'Select a .mat file to load.'); 21 | load(loadFile); 22 | ResultsArray = outputExcl; 23 | 24 | 25 | %% Emot Discrim 26 | % col_trialN = 1; 27 | % col_EmoResp = 2; 28 | % col_ConfResp = 3; 29 | % col_EmoRT = 4; 30 | % col_ConfRT = 5; 31 | % col_trialSuccess = 6; 32 | % col_MorphLevel = 7; 33 | % col_Indiv = 8; 34 | % col_subID = 9; 35 | 36 | %% Resp 37 | % col_trialN = 1; 38 | % col_Stim = 2; 39 | % col_Resp = 4; 40 | % col_RespLR = 5; 41 | % col_RT = 6; 42 | % col_trialSuccess = 7; 43 | % col_subID = 8; 44 | 45 | 46 | %% Palamedes setup 47 | ParOrNonPar = 1; % parametric (1), non-parametric (2) 48 | PF = @PAL_Weibull; %Alternatives: PAL_Gumbel, PAL_Weibull, PAL_Logistic 49 | %PAL_Quick, PAL_logQuick, 50 | %PAL_CumulativeNormal, PAL_HyperbolicSecant 51 | 52 | %Threshold and Slope are free parameters, guess and lapse rate are fixed 53 | paramsFree = [1 1 0 0]; %1: free parameter, 0: fixed parameter 54 | %Number of simulations to perform to determine standard error 55 | B=400; 56 | 57 | %Parameter grid defining parameter space through which to perform a 58 | %brute-force search for values to be used as initial guesses in iterative 59 | %parameter search. 60 | searchGrid.alpha = linspace(0,100,201); 61 | searchGrid.beta = linspace(log10(1),log10(16),201); 62 | searchGrid.gamma = 0.5; %scalar here (since fixed) but may be vector 63 | searchGrid.lambda = 0.05; %ditto 64 | 65 | % Plotting settings 66 | colours = [.08 .44 .35; .3 .89 .75; %teal 67 | .25 .12 .47; .57 .37 .90; %purple 68 | .68 .62 .13; 1 .92 .51; %yellow 69 | .68 .33 .13; 1 .59 .35]; %orange 70 | markerShape = ['ko'; 'ks'; 'k<'; 'k>'; 'k+'; 'ko'; 'k.'; 'kx'; 'kp'; 'kh']; 71 | 72 | % open a figure 73 | figure('name','Respiratory Resistance Sensitivity'); 74 | axes 75 | hold on 76 | 77 | % Define stimulus levels 78 | StimLevels = unique(ResultsArray(:,stimIntensity))'; 79 | StimLevelsFineGrain = [min(StimLevels):max(StimLevels)./1000:max(StimLevels)]; 80 | 81 | 82 | %% Subject - level 83 | % loop over subjects 84 | subIds = unique(ResultsArray(:,subjectIDs)); 85 | nSubjects = length(subIds); 86 | 87 | disp(['Number of subjects: ' num2str(nSubjects)]); 88 | 89 | for subLoop = 1:nSubjects 90 | 91 | % Define inputs 92 | thisSub = subIds(subLoop); 93 | OutOfNum = hist(ResultsArray((ResultsArray(:,subjectIDs)==thisSub), stimIntensity), StimLevels); 94 | NumPos = hist( ResultsArray(((ResultsArray(:,responsesAccuracy)==1) & (ResultsArray(:,subjectIDs)==thisSub)),stimIntensity), StimLevels ); 95 | 96 | % Fit PMFs 97 | disp(['Fitting function: subject ', num2str(subLoop), '....']); 98 | [paramsValues, LL, exitflag] = PAL_PFML_Fit(StimLevels,NumPos, ... 99 | OutOfNum,searchGrid,paramsFree,PF); 100 | 101 | disp('done:') 102 | message = sprintf('Threshold estimate: %6.4f',paramsValues(1)); 103 | disp(message); 104 | message = sprintf('Slope estimate: %6.4f\r',paramsValues(2)); 105 | disp(message); 106 | 107 | 108 | disp('Determining standard errors.....'); 109 | if ParOrNonPar == 1 110 | [SD, paramsSim, LLSim, converged] = PAL_PFML_BootstrapParametric(... 111 | StimLevels, OutOfNum, paramsValues, paramsFree, B, PF, ... 112 | 'searchGrid', searchGrid); 113 | else 114 | [SD(subLoop,:), paramsSim(subLoop,:), LLSim(subLoop,:), converged] = PAL_PFML_BootstrapNonParametric(... 115 | StimLevels(subLoop,:), NumPos(subLoop,:), OutOfNum(subLoop,:), [], paramsFree, B, PF,... 116 | 'searchGrid',searchGrid); 117 | end 118 | 119 | disp('done:'); 120 | message = sprintf('Standard error of Threshold: %6.4f',SD(1)); 121 | disp(message); 122 | message = sprintf('Standard error of Slope: %6.4f\r',SD(2)); 123 | disp(message); 124 | 125 | 126 | % Determine Goodness-of-Fit 127 | B=200;%1000; % Number of simulation to perform 128 | 129 | disp('Determining Goodness-of-fit.....'); 130 | [Dev, pDev] = PAL_PFML_GoodnessOfFit(StimLevels, NumPos, OutOfNum, ... 131 | paramsValues, paramsFree, B, PF, 'searchGrid', searchGrid); 132 | 133 | disp('done:'); 134 | % Display summary of results 135 | message = sprintf('Deviance: %6.4f',Dev); 136 | disp(message); 137 | message = sprintf('p-value: %6.4f',pDev); 138 | disp(message); 139 | 140 | % Proportion correct 141 | ProportionCorrectObserved = NumPos./OutOfNum; 142 | ProportionCorrectModel = PF(paramsValues,StimLevelsFineGrain); 143 | 144 | % Plot 145 | XaxisTicks = 0:20:100; 146 | XaxisLabels = {'0', '20', '40', '60', '80', '100'}; 147 | plot(StimLevelsFineGrain,ProportionCorrectModel,'-','color', colours(subLoop, :),'linewidth',2); 148 | plot(StimLevels,ProportionCorrectObserved, markerShape(subLoop,:),'markersize',6, 'MarkerEdgeColor', colours(subLoop, :)); 149 | set(gca, 'fontsize',16); 150 | set(gca, 'Xtick', XaxisTicks, 'XtickLabels', XaxisLabels); 151 | axis([min(StimLevels) max(StimLevels)+2 0 1]); 152 | xlabel('Respiratory Resistance'); 153 | ylabel('Percent Correct'); 154 | hold on 155 | end 156 | 157 | 158 | %% Inputs - sub. avg. 159 | StimLevels = unique(ResultsArray(:,stimIntensity))'; 160 | OutOfNum = hist(ResultsArray(:,stimIntensity), StimLevels); 161 | NumPos = hist( ResultsArray(logical(ResultsArray(:,responsesAccuracy)==1),stimIntensity), StimLevels ); 162 | 163 | 164 | %% Fit PMF 165 | disp('Fitting function.....'); 166 | [paramsValues LL exitflag] = PAL_PFML_Fit(StimLevels,NumPos, ... 167 | OutOfNum,searchGrid,paramsFree,PF); 168 | 169 | disp('done:') 170 | message = sprintf('Threshold estimate: %6.4f',paramsValues(1)); 171 | disp(message); 172 | message = sprintf('Slope estimate: %6.4f\r',paramsValues(2)); 173 | disp(message); 174 | 175 | %Number of simulations to perform to determine standard error 176 | B=400; 177 | 178 | disp('Determining standard errors.....'); 179 | 180 | if ParOrNonPar == 1 181 | [SD paramsSim LLSim converged] = PAL_PFML_BootstrapParametric(... 182 | StimLevels, OutOfNum, paramsValues, paramsFree, B, PF, ... 183 | 'searchGrid', searchGrid); 184 | else 185 | [SD paramsSim LLSim converged] = PAL_PFML_BootstrapNonParametric(... 186 | StimLevels, NumPos, OutOfNum, [], paramsFree, B, PF,... 187 | 'searchGrid',searchGrid); 188 | end 189 | 190 | disp('done:'); 191 | message = sprintf('Standard error of Threshold: %6.4f',SD(1)); 192 | disp(message); 193 | message = sprintf('Standard error of Slope: %6.4f\r',SD(2)); 194 | disp(message); 195 | 196 | %Distribution of estimated slope parameters for simulations will be skewed 197 | %(type: hist(paramsSim(:,2),40) to see this). However, distribution of 198 | %log-transformed slope estimates will be approximately symmetric 199 | %[type: hist(log10(paramsSim(:,2),40)]. This might motivate using 200 | %log-scale for slope values (uncomment next three lines to put on screen): 201 | % SElog10slope = std(log10(paramsSim(:,2))); 202 | % message = ['Estimate for log10(slope): ' num2str(log10(paramsValues(2))) ' +/- ' num2str(SElog10slope)]; 203 | % disp(message); 204 | 205 | %% determine Goodness-of-Fit 206 | B=200;%1000; % Number of simulation to perform 207 | 208 | disp('Determining Goodness-of-fit.....'); 209 | 210 | [Dev pDev] = PAL_PFML_GoodnessOfFit(StimLevels, NumPos, OutOfNum, ... 211 | paramsValues, paramsFree, B, PF, 'searchGrid', searchGrid); 212 | 213 | disp('done:'); 214 | 215 | % Display summary of results 216 | message = sprintf('Deviance: %6.4f',Dev); 217 | disp(message); 218 | message = sprintf('p-value: %6.4f',pDev); 219 | disp(message); 220 | 221 | 222 | %% Add avg to plot 223 | ProportionCorrectObserved=NumPos./OutOfNum; 224 | StimLevelsFineGrain=[min(StimLevels):max(StimLevels)./1000:max(StimLevels)]; 225 | ProportionCorrectModel = PF(paramsValues,StimLevelsFineGrain); 226 | 227 | % figure('name','Emotion Discrimination'); 228 | % axes 229 | % hold on 230 | plot(StimLevelsFineGrain,ProportionCorrectModel,'-','color',[0.93 .0 0.28],'linewidth',4); 231 | plot(StimLevels,ProportionCorrectObserved,'k.','markersize',20); 232 | set(gca, 'fontsize',16); 233 | % set(gca, 'Xtick',StimLevels); 234 | % axis([min(StimLevels) max(StimLevels) 0 1]); 235 | % xlabel('Stimulus Intensity'); 236 | % ylabel('Proportion ''Angry'' '); 237 | 238 | -------------------------------------------------------------------------------- /task/helpers/playSound.m: -------------------------------------------------------------------------------- 1 | function playSound(filename) 2 | % playSound sound engine 3 | % input: filename (.wav file) 4 | % Troubleshooting: 5 | % 6 | % If we get this error:Failed to open audio device 7 | % close the device PsychPortAudio('Close', 0) 8 | % 9 | % Niia Nikolova 12.2019 10 | 11 | %34-Rain-60min.wav 12 | 13 | printOutput = 0; % Optional flag to print playback status to command line 14 | 15 | switch nargin 16 | case 0 17 | filename = 'funk.wav'; 18 | disp('No audio (.wav) file provided.'); 19 | end 20 | 21 | InitializePsychSound;%(1); % could set optional desperate to 1? 22 | 23 | 24 | Parameters.pa.device = 4;%2 % try with default setting - NB that default may be MME --> BAD for timing! 25 | % Run PsychPortAudio('GetDevices') to get a list of all available devices 26 | Parameters.pa.mode = 1; % playback only 27 | Parameters.pa.latencyClass = 1; % 0 don't care // 1 go for low latency // 2 really agressive // 3 monster // 4 fail if can't MONSTER 28 | % Parameters.pa.freq = 48000; % INPUT DEFAULT DEVICE FREQ HERE 29 | % Parameters.pa.channels = 2;%1; % go for mono as we don't care... 30 | repetitions = 2; 31 | 32 | Parameters.pa.wavfilename = [ '..' filesep 'helpers' filesep 'audio' filesep filename]; 33 | 34 | % Read WAV file from filesystem: 35 | [y, freq] = psychwavread(Parameters.pa.wavfilename); 36 | wavedata = y'; 37 | Parameters.pa.channels = size(wavedata,1); % Number of rows == number of channels. 38 | 39 | % Make sure we have always 2 channels stereo output. 40 | % Why? Because some low-end and embedded soundcards 41 | % only support 2 channels, not 1 channel, and we want 42 | % to be robust in our demos. 43 | if Parameters.pa.channels < 2 44 | wavedata = [wavedata ; wavedata]; 45 | Parameters.pa.channels = 2; 46 | end 47 | 48 | % Parameters.pa.toneBuffer = PsychPortAudio('FillBuffer',Parameters.pa.H,cfg.Binding.tone); 49 | % Parameters.pa.volume = PsychPortAudio('Volume',Parameters.pa.H); 50 | 51 | 52 | % Open the audio device, with default mode [] (==Only playback), 53 | % and a required latencyclass of zero 0 == no low-latency mode, as well as 54 | % a frequency of freq and nrchannels sound channels. 55 | % This returns a handle to the audio device: 56 | % try 57 | % % Try with the 'freq'uency we wanted: 58 | % Parameters.pa.H = PsychPortAudio('Open',Parameters.pa.device,... 59 | % Parameters.pa.mode,... 60 | % Parameters.pa.latencyClass,... 61 | % Parameters.pa.freq,... 62 | % Parameters.pa.channels); 63 | % catch 64 | % % Failed. Retry with default frequency as suggested by device: 65 | % fprintf('\nCould not open device at wanted playback frequency of %i Hz. Will retry with device default frequency.\n', freq); 66 | % fprintf('Sound may sound a bit out of tune, ...\n\n'); 67 | % 68 | % psychlasterror('reset'); 69 | % Parameters.pa.H = PsychPortAudio('Open', Parameters.pa.device, [], 0, [], Parameters.pa.channels); 70 | % end 71 | 72 | % Open the audio device 73 | Parameters.pa.H = PsychPortAudio('Open', Parameters.pa.device, [], 0, [], Parameters.pa.channels); 74 | 75 | % Fill the audio playback buffer with the audio data 'wavedata': 76 | PsychPortAudio('FillBuffer', Parameters.pa.H, wavedata); 77 | 78 | % Start audio playback for 'repetitions' repetitions of the sound data, 79 | % start it immediately (0) and wait for the playback to start, return onset 80 | % timestamp. 81 | t1 = PsychPortAudio('Start', Parameters.pa.H, repetitions, 0, 1); 82 | 83 | % Wait for release of all keys on keyboard: 84 | KbReleaseWait; 85 | 86 | % fprintf('Audio playback started, press any key for about 1 second to quit.\n'); 87 | % 88 | % lastSample = 0; 89 | % lastTime = t1; 90 | 91 | % % Stay in a little loop until keypress: 92 | % while ~KbCheck 93 | % 94 | % % Wait a seconds... 95 | % WaitSecs(1); 96 | % 97 | % % Query current playback status and print it to the Matlab window: 98 | % s = PsychPortAudio('GetStatus', Parameters.pa.H); 99 | % tHost = GetSecs; 100 | % 101 | % if printOutput 102 | % % Print it: 103 | % fprintf('\n\nAudio playback started, press any key for about 1 second to quit.\n'); 104 | % fprintf('This is some status output of PsychPortAudio:\n'); 105 | % disp(s); 106 | % 107 | % realSampleRate = (s.ElapsedOutSamples - lastSample) / (s.CurrentStreamTime - lastTime); 108 | % fprintf('Measured average samplerate Hz: %f\n', realSampleRate); 109 | % 110 | % tHost = s.CurrentStreamTime; 111 | % clockDelta = (s.ElapsedOutSamples / s.SampleRate) - (tHost - t1); 112 | % clockRatio = (s.ElapsedOutSamples / s.SampleRate) / (tHost - t1); 113 | % fprintf('Delta between audio hw clock and host clock: %f msecs. Ratio %f.\n', 1000 * clockDelta, clockRatio); 114 | % end 115 | % end 116 | 117 | % %% If we want to wait for the sound to finish playing... 118 | % %% Find out the duration of the audio file 119 | % info = audioinfo([ '.' filesep 'audio' filesep filename]); 120 | % audioDuration = (info.Duration)+1; % wait for audio and a sec at the end 121 | % %% Wait for the sound to finish 122 | % WaitSecs(audioDuration); 123 | 124 | 125 | % % Stop playback: 126 | % PsychPortAudio('Stop', Parameters.pa.H); 127 | % 128 | % % Close the audio device: 129 | % PsychPortAudio('Close', Parameters.pa.H); 130 | % 131 | % % Done. 132 | % fprintf('Finished playing sound!\n'); -------------------------------------------------------------------------------- /task/helpers/randInRange.m: -------------------------------------------------------------------------------- 1 | function randArray = randInRange(rangeMin, rangeMax, arraySize) 2 | % function randArray = randInRange(rangeMin, rangeMax, arraySize) 3 | % 4 | % Creates an array of arraySize of random numbers from the uniform distribution in 5 | % range rangeMin - rangeMax 6 | 7 | randArray = (rangeMax - rangeMin) .* rand(arraySize) + rangeMin; 8 | 9 | end -------------------------------------------------------------------------------- /task/helpers/setupNdownStaircase.m: -------------------------------------------------------------------------------- 1 | function [stair] = setupNdownStaircase(vars) 2 | %function [stair] = setupNdownStaircase(vars) 3 | % 4 | % Set up an N-down staircase. Possible to have several interleaved 5 | % staircases by passing a nested structure, e.g. staircase.StartHigh 6 | % Input: vars structure with fields, 7 | % NumTrials % max # trials in the staircase 8 | % NumDown % N 'correct' items in a row to go down, after 1st reversal 9 | % StepSize % Step size 10 | % ThreshStart % Starting point of staircase 11 | % HappyCounter/Correct counter % Response 'happy'/correct counter 12 | % ReversalCounter % Reversal counter 13 | % ReversalStop % # of reversals to stop after 14 | % xCurrent % Current stim value 15 | % x % Vector of stims presented by trial 16 | % stop % starts as 0, switch to 1 when it's time to stop 17 | % 18 | % Niia Nikolova, 29/05/2020 19 | 20 | stair.HappyCounter = 1; % Response 'happy'/correct counter 21 | stair.ReversalCounter = 0; % Reversal counter 22 | stair.xCurrent = vars.ThreshStart; % Current stim value 23 | stair.x = []; % Vector of stims presented by trial 24 | stair.ReversalFlags = []; 25 | stair.stop = 0; 26 | 27 | % Assign general staircase variables 28 | stair.StepSize = vars.StepSize; 29 | stair.NumTrials = vars.NumTrials; 30 | stair.NumDown = vars.NumDown; 31 | stair.StepSize = vars.StepSize; 32 | stair.ReversalStop = vars.ReversalStop; 33 | 34 | if vars.ThreshStart > 100 % High start 35 | stair.Previous = -1; 36 | elseif vars.ThreshStart <100 % Low start 37 | stair.Previous = 1; 38 | end -------------------------------------------------------------------------------- /task/helpers/showExpandingRing.m: -------------------------------------------------------------------------------- 1 | function [scr, vars] = showExpandingRing(scr, vars) 2 | %[scr, vars] = showExpandingRing(scr, vars) 3 | % Plays expandingRing movie 4 | % 5 | % Project: Respiroception 6 | % 7 | % Input: 8 | % vars struct with key parameters (most are defined in loadParams.m) 9 | % scr struct with screen / display settings 10 | % 11 | % Niia Nikolova 12 | % Last edit: 27/05/2021 13 | 14 | cueExhale = 0; % NOT YET IMPLEMENTED 1 contract ring to cue/pace exhalation 15 | 16 | % Movie 17 | % movieDir needs to be full hard path 18 | movieDir = vars.helpersPath;%'C:\Users\niian\Documents\GitHub\ECG_aarhus\Respiroception\task\helpers'; 19 | movieName = 'expandingRing60.avi'; 20 | 21 | % Duration of plux flash for stimulus (2 frames) 22 | pluxDurationSec = scr.pluxDur(1) / scr.hz; 23 | 24 | % Set screenID, win, windowSize 25 | tw = scr.winRect(3); 26 | th = scr.winRect(4); 27 | 28 | % Retrieve monitor refresh duration: 29 | ifi = Screen('GetFlipInterval', scr.win); 30 | firstColor = [0.14, 0.82, 0.67, 1]; % dark teal 31 | 32 | % Show a pale circle and pause briefly 33 | % We use 'firstColor' for the even rings, 'secondColor' for the odd 34 | % rings. 35 | rad = scr.rad+5; 36 | Screen('FrameOval', scr.win, firstColor, [scr.x_middle-rad scr.y_middle-rad scr.x_middle+rad scr.y_middle+rad], 3, 3); 37 | Screen('Flip', scr.win); 38 | WaitSecs(vars.breathPauseT); 39 | 40 | % Perform initial flip to gray background and sync us to the retrace: 41 | Screen('FrameOval', scr.win, firstColor, [scr.x_middle-rad scr.y_middle-rad scr.x_middle+rad scr.y_middle+rad], 3, 3); 42 | [~, StimOn] = Screen('Flip', scr.win); 43 | 44 | % Open movie file: 45 | moviepath = fullfile(movieDir, filesep, movieName); 46 | pixelFormat=4; 47 | [movie movieduration fps imgw imgh] = Screen('OpenMovie', scr.win, moviepath, [], [], [], pixelFormat); 48 | 49 | % Start playback engine: 50 | Screen('PlayMovie', movie, 1); 51 | 52 | % While loop to show stimulus until StimT seconds elapsed... 53 | % Inhale 54 | stimDurFr = (vars.inhaleT + vars.breathPauseT) * scr.hz; 55 | 56 | for thisFr = 1:stimDurFr 57 | % while (GetSecs - StimOn) <= vars.inhaleT % Inhale 58 | 59 | % If using Plux for physiological measures, display a square in the 60 | % bottom right screen corner 61 | if vars.pluxSynch 62 | % if were in the first pluxDurationSec seconds, draw the rectangle 63 | if vars.stimIsHere == 1 &&((GetSecs - StimOn) <= pluxDurationSec) % stim interval 64 | Screen('FillRect', scr.win, scr.pluxWhite, scr.pluxRect); 65 | elseif vars.stimIsHere == 0 &&((GetSecs - StimOn) <= pluxDurationSec) % stim interval 66 | Screen('FillRect', scr.win, scr.pluxBlack, scr.pluxRect); 67 | 68 | end 69 | end 70 | 71 | DrawFormattedText(scr.win, [vars.InstructionInhale], 'center', 3*th/4, scr.TextColour); 72 | Screen('FrameOval', scr.win, firstColor, [scr.x_middle-rad scr.y_middle-rad scr.x_middle+rad scr.y_middle+rad], 3, 3); 73 | 74 | % Draw movie frame 75 | % Wait for next movie frame, retrieve texture handle to it 76 | tex = Screen('GetMovieImage', scr.win, movie); 77 | % Valid texture returned? A negative value means end of movie reached: 78 | if tex<=0 79 | % We're done, break out of loop: 80 | break; 81 | end 82 | 83 | % Draw the new texture immediately to screen: 84 | Screen('DrawTexture', scr.win, tex); 85 | 86 | % Update display: 87 | Screen('Flip', scr.win); 88 | 89 | % Release texture: 90 | Screen('Close', tex); 91 | 92 | end 93 | 94 | % % Pause 95 | % while (GetSecs - StimOn) <= (vars.inhaleT + vars.breathPauseT) 96 | % % Do nothing 97 | % end 98 | 99 | % Stop playback: 100 | Screen('PlayMovie', movie, 0); 101 | % Close movie: 102 | Screen('CloseMovie', movie); 103 | 104 | 105 | 106 | % Exhale 107 | if cueExhale 108 | while (GetSecs - StimOn) <= (vars.inhaleT + vars.breathPauseT + vars.exhaleT) % Exhale 109 | 110 | % Show exhale movie 111 | 112 | 113 | DrawFormattedText(scr.win, [vars.InstructionExhale], 'center', 3*th/4, scr.TextColour); 114 | 115 | % Request stimulus onset at next video refresh: 116 | vbl = Screen('Flip', scr.win, vbl + ifi/2); 117 | end 118 | end 119 | 120 | 121 | end 122 | 123 | -------------------------------------------------------------------------------- /task/helpers/showInstruction.m: -------------------------------------------------------------------------------- 1 | function showInstruction(scr, keys, textInput) 2 | %function showInstruction(scr, keys, textInput) 3 | % Shows a message in open PTB window and waits for SPACE press 4 | % Input: 5 | % scr structure with screen & window parameters 6 | % keys key codes 7 | % textInput text to show on the screem 8 | % 9 | % 10 | % Niia Nikolova 01/010/2020 11 | 12 | 13 | Screen('FillRect', scr.win, scr.BackgroundGray, scr.winRect); 14 | DrawFormattedText(scr.win, textInput, 'center', 'center', scr.TextColour); 15 | [~, ~] = Screen('Flip', scr.win); 16 | 17 | % Wait for space 18 | while keys.KeyCode(keys.Space) == 0 19 | [~, ~, keys.KeyCode] = KbCheck; 20 | WaitSecs(0.001); 21 | end 22 | 23 | % Show a blank screen for 200ms for flow 24 | Screen('FillRect', scr.win, scr.BackgroundGray, scr.winRect); 25 | [~, ~] = Screen('Flip', scr.win); 26 | pause(0.2); 27 | 28 | end -------------------------------------------------------------------------------- /task/helpers/simplePMFplot.m: -------------------------------------------------------------------------------- 1 | function simplePMFplot() 2 | % function simplePMFplot() 3 | % 4 | % Make a single subject analysis report, Psi 5 | % 6 | % Project: Respiratory resistance sensitivity task 7 | % 8 | % Niia Nikolova 9 | % Last edit: 08/01/2021 10 | 11 | %% Define plotting variables 12 | plotColours = {[0.85,0.33,0.10], [0.00,0.45,0.74], [], []}; 13 | lineWidth = 2; 14 | fontSize = 10; 15 | 16 | %% Select a file & load 17 | [file, path] = uigetfile('*.mat', 'Select a subject results file.'); 18 | load([path, file]); 19 | 20 | %% Check for metacog data & set up subplot figure 21 | existMcgnData = any(~isnan(Results.ConfResp)); 22 | figure; hold on; 23 | set(gcf,'color','w'); 24 | spCols = 3; 25 | if existMcgnData 26 | spRows = 3; 27 | else 28 | spRows = 2; 29 | end 30 | 31 | 32 | %% Threshold estimate by trial 33 | subplot(spRows,spCols,[1,2]); hold on; 34 | t = 1:length(stair.PM.x)-1; % trial # 35 | % Plot line 36 | plot(1:length(t),stair.PM.threshold,'Color',plotColours{2},'LineWidth',lineWidth); hold on 37 | 38 | % Plot correct responses 39 | plot(t(stair.PM.response == 1),stair.PM.x(stair.PM.response == 1),'wo',... 40 | 'MarkerSize',8,... 41 | 'MarkerEdgeColor',plotColours{2},... 42 | 'MarkerFaceColor',plotColours{2}); 43 | hold on 44 | 45 | % Plot incorrect responses 46 | plot(t(stair.PM.response == 0),stair.PM.x(stair.PM.response == 0),'wo',... 47 | 'MarkerSize',8,... 48 | 'MarkerEdgeColor',plotColours{1},... 49 | 'MarkerFaceColor',plotColours{1}); 50 | % plot(t,stair.PM.x(1:length(t)),'wo',... 51 | % 'MarkerSize',8,... 52 | % 'MarkerEdgeColor','r',... 53 | % 'MarkerFaceColor',[1 1 1]); 54 | hold on 55 | 56 | % Add axis labels 57 | hold on 58 | title('Presented Stimuli & Threshold Estimate by Trial'); 59 | axis([0 (length(stair.PM.x)+5) 0 17]); 60 | xlabel('Trial number','fontsize',fontSize); 61 | ylabel('Resistance (mm)','fontsize',fontSize); 62 | 63 | %% PMF 64 | % Calculate Threshold and Slope 65 | estA = stair.PM.threshold(t(end)); 66 | estB = 10.^stair.PM.slope(t(end)); % slope is in log10 units of beta parameter 67 | Results.estA = estA; 68 | Results.estB = estB; 69 | disp(['Threshold estimate: ', num2str(estA)]); 70 | disp(['Slope estimate: ', num2str(estB)]); 71 | 72 | % Plot pmf 73 | subplot(spRows,spCols,3); hold on; 74 | % subplot(2,1,2,'align'); 75 | title('Psi PMF fit'); 76 | hold on 77 | 78 | % SL stim levels, NP num positive, OON out of num 79 | [SL, NP, OON] = PAL_PFML_GroupTrialsbyX(stair.PM.x(1:length(stair.PM.x)-1),stair.PM.response,ones(size(stair.PM.response))); 80 | for SR = 1:length(SL(OON~=0)) 81 | plot(SL(SR),NP(SR)/OON(SR),'wo','markerfacecolor',plotColours{2},'markersize',20*sqrt(OON(SR)./sum(OON))) 82 | end 83 | axis([0 20 0.5 1]); 84 | 85 | % plot 86 | plot([min(stair.stimRange):.01:max(stair.stimRange)], stair.PF([stair.PM.threshold(length(stair.PM.threshold)) 10.^stair.PM.slope(length(stair.PM.threshold)) stair.priorGammaRange stair.PM.lapse(length(stair.PM.threshold))],min(stair.stimRange):.01:max(stair.stimRange)),'Color',plotColours{2},'linewidth',lineWidth) 87 | 88 | xlabel('Resistance (mm)','fontsize',fontSize); 89 | ylabel('\psi(\itx\rm; \alpha, \beta, \gamma, \lambda)','fontsize',fontSize); 90 | text(min(stair.stimRange)+(max(stair.stimRange)-min(stair.stimRange))/1.3, .70,['\alpha ', num2str(estA)],'color',[0 0 0],'Fontsize',fontSize) 91 | text(min(stair.stimRange)+(max(stair.stimRange)-min(stair.stimRange))/1.3, .60,['\beta ', num2str(estB)],'color',[0 0 0],'Fontsize',fontSize) 92 | % text(min(stair.stimRange)+(max(stair.stimRange)-min(stair.stimRange))/4, .75,'Bayes fit','color',[0 0 0],'Fontsize',fontSize) 93 | % set(gca,'ytick',[0:.25:1]); 94 | 95 | drawnow 96 | 97 | % Get the stim levels at which participant performane is .3 .5 and .7 98 | pptPMFvals = [stair.PM.threshold(length(stair.PM.threshold)) 10.^stair.PM.slope(length(stair.PM.threshold)) 0 stair.PM.lapse(length(stair.PM.threshold))]; 99 | inversePMFvals = [0.3, 0.5, 0.7]; 100 | inversePMFstims = stair.PF(pptPMFvals, inversePMFvals, 'inverse'); 101 | inversePMFstims = round(inversePMFstims,2); 102 | 103 | %% Calculate overall accuracy 104 | validTrials = ~isnan(Results.Resp); 105 | nValidTrials = sum(validTrials); 106 | nCorrectTrials = sum(Results.Resp(validTrials)); 107 | accuracy = nCorrectTrials./nValidTrials; 108 | 109 | disp(['Session accuracy: ', num2str(accuracy)]); 110 | Results.acc = accuracy; 111 | 112 | %% Plot accuracy x RT 113 | RT = Results.RT; 114 | correctTrials = nanmean(RT(Results.Resp == 1)); 115 | incorrectTrials = nanmean(RT(Results.Resp == 0)); 116 | 117 | subplot(spRows,spCols,4); hold on; 118 | title('RT x accuracy'); 119 | set(gcf,'color','w'); 120 | X = categorical({'Correct','Incorrect'}); 121 | Y = [correctTrials, incorrectTrials]; 122 | bar(X,Y, 0.5, 'FaceColor',plotColours{2},'LineWidth',0.1) 123 | set(gca,'yLim',[0, 1]); 124 | ylabel('RT (s)','fontsize',fontSize); 125 | 126 | 127 | %% Plot accuracy x confidence 128 | if existMcgnData % If confidence ratings were collected 129 | Conf = Results.ConfResp; 130 | correctTrials = nanmean(Conf(Results.Resp == 1)); 131 | incorrectTrials = nanmean(Conf(Results.Resp == 0)); 132 | 133 | subplot(spRows,spCols,6); hold on; 134 | title('Confidence x accuracy'); 135 | set(gcf,'color','w'); 136 | X = categorical({'Correct','Incorrect'}); 137 | Y = [correctTrials, incorrectTrials]; 138 | bar(X,Y, 0.5, 'FaceColor',plotColours{2},'LineWidth',0.1) 139 | set(gca,'yLim',[0, 100]); 140 | ylabel('Confidence ratings','fontsize',fontSize); 141 | end 142 | 143 | %% Plot unpleasantness rating x break # 144 | blahRateMat = Results.ExpRateTask; 145 | nRatigs = size(blahRateMat,2); 146 | blahRateVals = blahRateMat(1, :); 147 | 148 | subplot(spRows,spCols,(spCols+spRows)); hold on; 149 | title('Task unpleasantness'); 150 | set(gcf,'color','w'); 151 | bar(1:nRatigs, blahRateVals, 0.5, 'FaceColor',plotColours{2},'LineWidth',0.1) 152 | set(gca,'yLim',[0, 100]); 153 | ylabel('Unpleasantness rating','fontsize',fontSize); 154 | xlabel('Timepoint','fontsize',fontSize); 155 | 156 | %% Save updated results file 157 | vars.DataFileName = ['a', vars.DataFileName]; 158 | save(strcat(vars.OutputFolder, vars.DataFileName), 'stair', 'Results', 'vars', 'scr', 'keys' ); 159 | 160 | 161 | end -------------------------------------------------------------------------------- /task/helpers/simpleUDplot.m: -------------------------------------------------------------------------------- 1 | function simpleUDplot(stair) 2 | % function simpleUDplot(stair) 3 | % 4 | % Make a simple plot for up-down staircase session 5 | % Can take stair stucture fields for seperate staircases 6 | % 7 | % Project: RRS task, respiroception 8 | % 9 | % Niia Nikolova 10 | % Last edit: 01/12/2020 11 | 12 | origStairs = stair; 13 | nStaircases = size(stair,2); 14 | 15 | plotColours = {[0.85,0.33,0.10], [0.00,0.45,0.74], [], []}; 16 | 17 | %% Stim trace by trial 18 | figure; 19 | subplot(2,2,[1, 2]); 20 | 21 | % Loop over the staircases 22 | for thisStair = 1 : nStaircases 23 | 24 | stair = origStairs(thisStair); 25 | 26 | % Plot this staircase 27 | t = 1:length(stair.PM.x);%-1; % trial # 28 | % plot line 29 | plot(t,stair.PM.x, 'Color', plotColours{thisStair}); hold on; 30 | % plot stims and responses 31 | plot(t(stair.PM.response == 1),stair.PM.x((stair.PM.response == 1)),'wo',... 32 | 'MarkerSize',8,... 33 | 'MarkerEdgeColor',plotColours{thisStair},... 34 | 'MarkerFaceColor',plotColours{thisStair});%,... 35 | % 'LineStyle', '-',... 36 | % 'LineWidth',1,... 37 | % 'Color', plotColours{thisStair}); 38 | hold on 39 | 40 | plot(t(stair.PM.response == 0),stair.PM.x((stair.PM.response == 0)),'wo',... 41 | 'MarkerSize',8,... 42 | 'MarkerEdgeColor',plotColours{thisStair},... 43 | 'MarkerFaceColor',[1 1 1]);%,... 44 | % 'LineStyle', '-',... 45 | % 'LineWidth',1,... 46 | % 'Color', plotColours{thisStair}); 47 | end 48 | 49 | % Add axis labels 50 | title('Presented Stimuli by Trial'); 51 | % axis([0 (length(stair.PM.x)+5) 0 200]); 52 | xlabel('Trial number','fontsize',10); 53 | ylabel('Aperture obstruction (%)','fontsize',10); 54 | set(gcf,'color','w'); 55 | hold on 56 | 57 | %% Plot PMF 58 | 59 | % Loop over the staircases 60 | for thisStair = 1 : nStaircases 61 | 62 | % subplot(2,2,(thisStair+2)); 63 | subplot(2,2,3); 64 | 65 | stair = origStairs(thisStair); 66 | 67 | [SL, NP, OON] = PAL_PFML_GroupTrialsbyX(round(stair.PM.x(1:t)),stair.PM.response,ones(size(stair.PM.response))); 68 | % [SL, NP, OON] = PAL_PFML_GroupTrialsbyX(stair.PM.x(1:length(stair.PM.x)),stair.PM.response,ones(size(stair.PM.response))); 69 | for SR = 1:length(SL(OON~=0)) 70 | % plot stim responses 71 | plot(SL(SR),NP(SR)/OON(SR),'wo','markerfacecolor',plotColours{thisStair},'markersize',20*sqrt(OON(SR)./sum(OON))); 72 | hold on 73 | end 74 | 75 | % Plot the pmf, 10.^stair.PM.beta 76 | plot([min(SL):.01:max(SL)], stair.PM.PF([stair.PM.xStaircase(end) 10.^(stair.beta) stair.PM.gamma stair.PM.lambda],min(SL):.01:max(SL)),'Color', plotColours{thisStair},'LineWidth',2) 77 | 78 | % Add axis names 79 | xlabel('Aperture obstruction (%)','fontsize',10); 80 | ylabel('\psi(\itx\rm; \alpha, \beta, \gamma, \lambda)','fontsize',10); 81 | % text(min(SL)+(max(SL)-min(SL))/4, .75, ['\beta: ', num2str(stair.PM.beta)],'color','k','Fontsize',8) 82 | set(gca,'yLim',[.5, 1]); 83 | 84 | drawnow 85 | end 86 | 87 | -------------------------------------------------------------------------------- /task/helpers/slideScale.m: -------------------------------------------------------------------------------- 1 | function [position, ConfTimeStamp, RT, answer] = slideScale(screenPointer, question, rect, anchors, varargin) 2 | %SLIDESCALE This funtion draws a slide scale on a PSYCHTOOLOX 3 screen and returns the 3 | % position of the slider spaced between -100 and 100 as well as the rection time and if an answer was given. 4 | % 5 | % Usage: [position, secs] = slideScale(ScreenPointer, question, center, rect, endPoints, varargin) 6 | % Mandatory input: 7 | % ScreenPointer -> Pointer to the window. 8 | % question -> Text string containing the question. 9 | % rect -> Double contatining the screen size. 10 | % Obtained with [myScreen, rect] = Screen('OpenWindow', 0); 11 | % anchors -> Cell containg the two or three text strings of the left (, middle) and right 12 | % end of the scale. Exampe: anchors = {'left', 'middle', 'right'} 13 | % 14 | % Varargin: 15 | % 'linelength' -> An integer specifying the lengths of the ticks in 16 | % pixels. The default is 10. 17 | % 'width' -> An integer specifying the width of the scale line in 18 | % pixels. The default is 3. 19 | % 'range' -> An integer specifying the type of range. If 1, 20 | % then the range is from -100 to 100. If 2, then the 21 | % range is from 0 to 100. Default is 1. 22 | % 'startposition' -> Choose 'right', 'left' or 'center' start position. 23 | % Default is center. 24 | % 'scalalength' -> Double value between 0 and 1 for the length of the 25 | % scale. The default is 0.9. 26 | % 'scalaposition' -> Double value between 0 and 1 for the position of the 27 | % scale. 0 is top and 1 is bottom. Default is 0.6. 28 | % 'device' -> A string specifying the response device. Either 'mouse' 29 | % or 'keyboard'. The default is 'mouse'. 30 | % 'responsekeys' -> Vector containing keyCodes of the keys from the keyboard to log the 31 | % response and move the slider to the right and left. 32 | % The default is [KbName('return') KbName('LeftArrow') KbName('RightArrow')]. 33 | % 'stepSize' -> An integer specifying the number of pixel the 34 | % slider moves with each step. Default is 1. 35 | % 'slidercolor' -> Vector for the color value of the slider [r g b] 36 | % from 0 to 255. The default is red [255 0 0]. 37 | % 'scalacolor' -> Vector for the color value of the scale [r g b] 38 | % from 0 to 255.The default is black [0 0 0]. 39 | % 'aborttime' -> Double specifying the time in seconds after which 40 | % the function should be aborted. In this case no 41 | % answer is saved. The default is Inf. 42 | % 'image' -> An image saved in a uint8 matrix. Use 43 | % imread('image.png') to load an image file. 44 | % 'displayposition' -> If true, the position of the slider is displayed. 45 | % The default is false. 46 | % 47 | % Output: 48 | % 'position' -> Deviation from zero in percentage, 49 | % with -100 <= position <= 100 to indicate left-sided 50 | % and right-sided deviation. 51 | % 'ConfTimeStamp' -> Time stamp (GetSecs) for when the response was made 52 | % 'RT' -> Reaction time in milliseconds. 53 | % 'answer' -> If 0, no answer has been given. Otherwise this 54 | % variable is 1. 55 | % 56 | % Author: Joern Alexander Quent 57 | % e-mail: Alex.Quent@mrc-cbu.cam.ac.uk 58 | % Version history: 59 | % 1.0 - 4th January 2016 - First draft 60 | % 1.1 - 18. Feburary 2016 - Added abort time and option to 61 | % choose between mouse and key board 62 | % 1.2 - 5th October 2016 - End points will be aligned to end 63 | % ticks 64 | % 1.3 - 6th January 2017 - Added the possibility to display an 65 | % image 66 | % 1.4 - 5th May 2017 - Added the possibility to choose a 67 | % start position 68 | % 1.5 - 7th November 2017 - Added the possibility to display 69 | % the position of the slider under the scale. 70 | % 1.6 - 27th November 2017 - The function now waits until 71 | % all keys are released before exiting. 72 | % 1.7 - 28th November 2017 - More than one screen 73 | % supported now. 74 | % 1.8 - 29th November 2017 - Fixed issue that mouse is 75 | % not properly in windowed mode. 76 | % 1.9 - 7th December 2017 - If an image is drawn, the 77 | % corresponding texture is deleted at the end. 78 | % 1.10 - 28th December 2017 - Added the possibility to 79 | % choose the type of range (0 to 100 or -100 to 100). 80 | % 1.11 - 7th May 2019 - Added the possibility to control 81 | % the slider with keys only. Use keyboard as devices and 82 | % select this keys for this function. In addition, 83 | % default for aborttime was changed to Inf and one bug 84 | % with slidercolor was fixed. 85 | % 1.12 - 13rd January 2020 - Added the possiblity to 86 | % have three anchors points (left, middle and right). To 87 | % make that possible the displayed position is now moved 88 | % above the line (under the question). The relative 89 | % position of the question was also changed to allow 90 | % multiple line questions. 91 | %% Parse input arguments 92 | % Default values 93 | TextColour = [1 1 1]; 94 | center = round([rect(3) rect(4)]/2); 95 | lineLength = 10; 96 | width = 3; 97 | scalaLength = 0.9; 98 | scalaPosition = 0.6; 99 | sliderColor = [255 0 0]; 100 | scaleColor = [0 0 0]; 101 | device = 'mouse'; 102 | aborttime = Inf; 103 | % KbName('UnifyKeyNames'); 104 | % responseKeys = [KbName('return') KbName('LeftArrow') KbName('RightArrow')]; 105 | GetMouseIndices; 106 | drawImage = 0; 107 | startPosition = 'center'; 108 | displayPos = false; 109 | rangeType = 1; 110 | stepSize = 1; 111 | 112 | i = 1; 113 | while(i<=length(varargin)) 114 | switch lower(varargin{i}) 115 | case 'linelength' 116 | i = i + 1; 117 | lineLength = varargin{i}; 118 | i = i + 1; 119 | case 'width' 120 | i = i + 1; 121 | width = varargin{i}; 122 | i = i + 1; 123 | case 'range' 124 | i = i + 1; 125 | rangeType = varargin{i}; 126 | i = i + 1; 127 | case 'startposition' 128 | i = i + 1; 129 | startPosition = varargin{i}; 130 | i = i + 1; 131 | case 'scalalength' 132 | i = i + 1; 133 | scalaLength = varargin{i}; 134 | i = i + 1; 135 | case 'scalaposition' 136 | i = i + 1; 137 | scalaPosition = varargin{i}; 138 | i = i + 1; 139 | case 'device' 140 | i = i + 1; 141 | device = varargin{i}; 142 | i = i + 1; 143 | case 'responsekeys' 144 | i = i + 1; 145 | responseKeys = varargin{i}; 146 | i = i + 1; 147 | case 'stepsize' 148 | i = i + 1; 149 | stepSize = varargin{i}; 150 | i = i + 1; 151 | case 'slidercolor' 152 | i = i + 1; 153 | sliderColor = varargin{i}; 154 | i = i + 1; 155 | case 'scalacolor' 156 | i = i + 1; 157 | scaleColor = varargin{i}; 158 | i = i + 1; 159 | case 'aborttime' 160 | i = i + 1; 161 | aborttime = varargin{i}; 162 | i = i + 1; 163 | case 'image' 164 | i = i + 1; 165 | image = varargin{i}; 166 | i = i + 1; 167 | imageSize = size(image); 168 | stimuli = Screen('MakeTexture', screenPointer, image); 169 | drawImage = 1; 170 | case 'displayposition' 171 | i = i + 1; 172 | displayPos = varargin{i}; 173 | i = i + 1; 174 | end 175 | end 176 | 177 | % Sets the default key depending on choosen device 178 | if strcmp(device, 'mouse') 179 | mouseButton = 1; % X mouse button 180 | end 181 | 182 | %% Checking number of screens and parsing size of the global screen 183 | screens = Screen('Screens'); 184 | if length(screens) > 1 % Checks for the number of screens 185 | screenNum = 1; 186 | else 187 | screenNum = 0; 188 | end 189 | globalRect = Screen('Rect', screenNum); 190 | 191 | %% Coordinates of scale lines and text bounds 192 | scalaLength_pix = (globalRect(3)*scalaLength)- (globalRect(3)*(1-scalaLength)); 193 | scalaMiddle = globalRect(3)/2; 194 | clocktime = clock; 195 | secsSeed = round(clocktime(6)); 196 | if (isnan(secsSeed) || ~any(secsSeed)), secsSeed = 30; end 197 | 198 | if strcmp(startPosition, 'right') 199 | x = globalRect(3)*scalaLength; 200 | elseif strcmp(startPosition, 'center') 201 | x = globalRect(3)/2; 202 | elseif strcmp(startPosition, 'left') 203 | x = globalRect(3)*(1-scalaLength); 204 | elseif strcmp(startPosition, 'shuffle') 205 | % Set starting coordinate x to +/- 15% of scalaLength center 206 | xArray = randInRange(scalaMiddle-(scalaLength_pix*.15),(scalaMiddle+(scalaLength_pix*.15)), [60,1]); 207 | x = xArray(secsSeed); 208 | 209 | else 210 | error('Only right, center and left are possible start positions'); 211 | end 212 | 213 | SetMouse(round(x), round(rect(4)*scalaPosition), screenPointer, 1); 214 | midTick = [center(1) rect(4)*scalaPosition - lineLength - 5 center(1) rect(4)*scalaPosition + lineLength + 5]; 215 | leftTick = [rect(3)*(1-scalaLength) rect(4)*scalaPosition - lineLength rect(3)*(1-scalaLength) rect(4)*scalaPosition + lineLength]; 216 | rightTick = [rect(3)*scalaLength rect(4)*scalaPosition - lineLength rect(3)*scalaLength rect(4)*scalaPosition + lineLength]; 217 | horzLine = [rect(3)*scalaLength rect(4)*scalaPosition rect(3)*(1-scalaLength) rect(4)*scalaPosition]; 218 | if length(anchors) == 2 219 | textBounds = [Screen('TextBounds', screenPointer, sprintf(anchors{1})); Screen('TextBounds', screenPointer, sprintf(anchors{2}))]; 220 | else 221 | textBounds = [Screen('TextBounds', screenPointer, sprintf(anchors{1})); Screen('TextBounds', screenPointer, sprintf(anchors{3}))]; 222 | end 223 | 224 | if drawImage == 1 225 | rectImage = [center(1) - imageSize(2)/2 rect(4)*(scalaPosition - 0.2) - imageSize(1) center(1) + imageSize(2)/2 rect(4)*(scalaPosition - 0.2)]; 226 | if rect(4)*(scalaPosition - 0.2) - imageSize(1) < 0 227 | error('The height of the image is too large. Either lower your scale or use the smaller image.'); 228 | end 229 | end 230 | 231 | % Calculate the range of the scale, which will be need to calculate the 232 | % position 233 | scaleRange = round(rect(3)*(1-scalaLength)):round(rect(3)*scalaLength); % Calculates the range of the scale 234 | scaleRangeShifted = round((scaleRange)-mean(scaleRange)); % Shift the range of scale so it is symmetrical around zero 235 | 236 | %% Loop for scale loop 237 | t0 = GetSecs; 238 | answer = 0; 239 | while answer == 0 240 | % Parse user input for x location 241 | if strcmp(device, 'mouse') 242 | [x,~,buttons,~,~,~] = GetMouse(screenPointer, 1); 243 | elseif strcmp(device, 'keyboard') 244 | [~, ~, keyCode] = KbCheck; 245 | if keyCode(responseKeys(2)) == 1 246 | x = x - stepSize; % Goes stepSize pixel to the left 247 | elseif keyCode(responseKeys(3)) == 1 248 | x = x + stepSize; % Goes stepSize pixel to the right 249 | end 250 | else 251 | error('Unknown device'); 252 | end 253 | 254 | % Stop at upper and lower bound 255 | if x > rect(3)*scalaLength 256 | x = rect(3)*scalaLength; 257 | elseif x < rect(3)*(1-scalaLength) 258 | x = rect(3)*(1-scalaLength); 259 | end 260 | 261 | % Draw image if provided 262 | if drawImage == 1 263 | Screen('DrawTexture', screenPointer, stimuli,[] , rectImage, 0); 264 | end 265 | 266 | % Drawing the question as text 267 | DrawFormattedText(screenPointer, question, 'center', rect(4)*(scalaPosition - 0.16), TextColour); 268 | 269 | % Drawing the anchors of the scale as text 270 | if length(anchors) == 2 271 | % Only left and right anchors 272 | DrawFormattedText(screenPointer, anchors{1}, leftTick(1, 1) - textBounds(1, 3)/2, rect(4)*scalaPosition+80, TextColour,[],[],[],[],[],[]); % Left point 273 | DrawFormattedText(screenPointer, anchors{2}, rightTick(1, 1) - textBounds(2, 3)/2, rect(4)*scalaPosition+80, TextColour,[],[],[],[],[],[]); % Right point 274 | else 275 | % Left, middle and right anchors 276 | DrawFormattedText(screenPointer, anchors{1}, leftTick(1, 1) - textBounds(1, 3)/2, rect(4)*scalaPosition+80, TextColour,[],[],[],[],[],[]); % Left point 277 | DrawFormattedText(screenPointer, anchors{2}, 'center', rect(4)*scalaPosition+80, TextColour,[],[],[],[],[],[]); % Middle point 278 | DrawFormattedText(screenPointer, anchors{3}, rightTick(1, 1) - textBounds(2, 3)/2, rect(4)*scalaPosition+80, TextColour,[],[],[],[],[],[]); % Right point 279 | end 280 | 281 | % Drawing the scale 282 | % Screen('DrawLine', screenPointer, scaleColor, midTick(1), midTick(2), midTick(3), midTick(4), width); % Mid tick 283 | Screen('DrawLine', screenPointer, scaleColor, leftTick(1), leftTick(2), leftTick(3), leftTick(4), width); % Left tick 284 | Screen('DrawLine', screenPointer, scaleColor, rightTick(1), rightTick(2), rightTick(3), rightTick(4), width); % Right tick 285 | Screen('DrawLine', screenPointer, scaleColor, horzLine(1), horzLine(2), horzLine(3), horzLine(4), width); % Horizontal line 286 | 287 | % The slider 288 | Screen('DrawLine', screenPointer, sliderColor, x, rect(4)*scalaPosition - lineLength, x, rect(4)*scalaPosition + lineLength, width); 289 | 290 | % Caculates position 291 | if rangeType == 1 292 | position = round((x)-mean(scaleRange)); % Calculates the deviation from the center 293 | position = (position/max(scaleRangeShifted))*100; % Converts the value to percentage 294 | elseif rangeType == 2 295 | position = round((x)-min(scaleRange)); % Calculates the deviation from 0. 296 | position = (position/(max(scaleRange)-min(scaleRange)))*100; % Converts the value to percentage 297 | end 298 | 299 | 300 | % Display position 301 | if displayPos 302 | DrawFormattedText(screenPointer, num2str(round(position)), 'center', rect(4)*(scalaPosition - 0.05)); 303 | end 304 | 305 | % Flip screen 306 | Screen('Flip', screenPointer); 307 | 308 | % Check if answer has been given 309 | if strcmp(device, 'mouse') 310 | secs = GetSecs; 311 | if buttons(mouseButton) == 1 312 | answer = 1; 313 | end 314 | elseif strcmp(device, 'keyboard') 315 | [~, secs, keyCode] = KbCheck; 316 | if keyCode(responseKeys(1)) == 1 317 | answer = 1; 318 | end 319 | end 320 | 321 | % Abort if answer takes too long 322 | if secs - t0 > aborttime 323 | break 324 | end 325 | end 326 | %% Wating that all keys are released and delete texture 327 | % KbReleaseWait; %Keyboard 328 | KbReleaseWait(1); %Mouse 329 | if drawImage == 1 330 | Screen('Close', stimuli); 331 | end 332 | %% Calculating the rection time and the position 333 | ConfTimeStamp = GetSecs; % NN added 12.07.2020 to current time stamp. 334 | RT = (secs - t0); % in seconds 335 | end -------------------------------------------------------------------------------- /task/helpers/struct2csv.m: -------------------------------------------------------------------------------- 1 | function [success] = struct2csv(structName,outputFile) 2 | %[success] = struct2csv(structName,outputFile) 3 | % Converst a structure structName to table and save as .csv file of name outputFile 4 | try 5 | temp_table = struct2table(structName); 6 | writetable(temp_table,outputFile) 7 | success = 1; 8 | catch ME 9 | 10 | end 11 | 12 | end 13 | 14 | -------------------------------------------------------------------------------- /task/helpers/subjectReportRRS.m: -------------------------------------------------------------------------------- 1 | function subjectReportRRS(filepath, desiredOutputDir) 2 | % function subjectReportRRS(filepath, desiredOutputDir) 3 | % 4 | % Make a single subject analysis report. Works with either Psi or Quest 5 | % - check functionality with Ndown, MCS 6 | % 7 | % Project: Respiratory resistance sensitivity task 8 | % 9 | % Niia Nikolova 10 | % Last edit: 08/01/2021 11 | 12 | showPlot = 0; % Optional flag, set to 1 to display a summary figure for each subject (session) 13 | savePlot = 0; % Optional flag to save plot output (as pdf & png) 14 | 15 | %% Setup 16 | % Check input args & load file 17 | if nargin < 1 18 | disp('No file name was passed. Please select a raw ''RRS_SUBID_'' data file.'); 19 | % Select a file & load 20 | [file, path] = uigetfile('*.mat', 'Select a subject results file.'); 21 | filepath = [path, file]; 22 | elseif nargin == 1 23 | % Do nothing 24 | end 25 | 26 | load(filepath); 27 | disp(['Preprocessing data file: ', filepath]) 28 | 29 | %% Define plotting variables 30 | plotColours = {[0.00,0.45,0.74], [0.85,0.33,0.10], [], []}; 31 | lineWidth = 2; 32 | fontSize = 10; 33 | fontName = 'Helvetica'; 34 | % fontSize = 18; 35 | 36 | %% Check which procedure was run 37 | % 1 Psi, 2 Ndown, 3 MCS, 4 QUEST 38 | procedure = vars.Procedure; 39 | 40 | % Determine number of staircases 41 | origStairs = stair; 42 | nStaircases = size(stair,2); 43 | 44 | %% Check for metacog data & set up subplot figure 45 | existMcgnData = any(~isnan(Results.ConfResp)); 46 | 47 | if showPlot 48 | figure; hold on; 49 | set(gcf,'color','w'); 50 | if procedure == 1 % Psi 51 | sgt = sgtitle(['Sub ',num2str(vars.subNo),', Psi']); else 52 | sgt = sgtitle(['Sub ',num2str(vars.subNo),', QUEST']); 53 | end 54 | sgt.FontSize = 14; 55 | spCols = 3; 56 | if existMcgnData 57 | spRows = 2; 58 | else 59 | spRows = 2; 60 | end 61 | 62 | 63 | %% Threshold estimate by trial 64 | subplot(spRows,spCols,[1,2]); hold on; 65 | end 66 | 67 | % Loop over the staircases 68 | for thisStair = 1 : nStaircases 69 | 70 | stair = origStairs(thisStair); 71 | 72 | if procedure == 1 % Psi 73 | t = 1:length(stair.PM.x)-1; % trial # 74 | stimByTrial = stair.PM.threshold; 75 | else 76 | t = 1:length(stair.PM.x); 77 | stimByTrial = stair.PM.x; 78 | end 79 | 80 | if showPlot 81 | % Plot line 82 | plot(1:length(t),stimByTrial,'Color',plotColours{thisStair},'LineWidth',lineWidth); hold on 83 | 84 | % Plot correct responses 85 | plot(t(stair.PM.response == 1),stair.PM.x(stair.PM.response == 1),'wo',... 86 | 'MarkerSize',8,... 87 | 'MarkerEdgeColor',plotColours{thisStair},... 88 | 'MarkerFaceColor',plotColours{thisStair}); 89 | hold on 90 | 91 | % Plot incorrect responses 92 | plot(t(stair.PM.response == 0),stair.PM.x(stair.PM.response == 0),'wo',... 93 | 'MarkerSize',8,... 94 | 'MarkerEdgeColor',plotColours{thisStair},... 95 | 'MarkerFaceColor',[1 1 1]); 96 | % plot(t,stair.PM.x(1:length(t)),'wo',... 97 | % 'MarkerSize',8,... 98 | % 'MarkerEdgeColor','r',... 99 | % 'MarkerFaceColor',[1 1 1]); 100 | hold on 101 | end 102 | end 103 | 104 | if showPlot 105 | % Add axis labels 106 | hold on 107 | title('Presented Stimuli & Threshold Estimate by Trial','FontWeight','Normal'); 108 | axis([0 (length(stair.PM.x)+5) 3.6 19]); 109 | yticks([3.6, 7.2, 10.8, 14.4, 18]); 110 | yticklabels({'20', '40', '60', '80', '100'}); 111 | xlabel('Trial number','fontsize',fontSize); 112 | ylabel('% Obstruction','fontsize',fontSize); 113 | set(gca,'FontName',fontName,'fontsize',fontSize) 114 | grid on 115 | grid minor 116 | hold on 117 | end 118 | 119 | 120 | 121 | %% PMF 122 | if showPlot 123 | % Plot pmf 124 | subplot(spRows,spCols,3); hold on; 125 | title('PMF fit'); 126 | xlabel('% Obstruction','fontsize',fontSize); 127 | ylabel('\psi(\itx\rm; \alpha, \beta, \gamma, \lambda)','fontsize',fontSize); 128 | xticks([3.4, 6.8, 10.2, 13.6, 17]); 129 | xticklabels({'20', '40', '60', '80', '100'}); 130 | hold on 131 | end 132 | 133 | % Loop over the staircases 134 | for thisStair = 1 : nStaircases 135 | 136 | stair = origStairs(thisStair); 137 | 138 | % Calculate Threshold and Slope 139 | if procedure == 1 % Psi 140 | estA = stair.PM.threshold(t(end)); 141 | estB = 10.^stair.PM.slope(t(end)); % slope is in log10 units of beta parameter 142 | [SL, NP, OON] = PAL_PFML_GroupTrialsbyX(stair.PM.x(1:length(stair.PM.x)),stair.PM.response,ones(size(stair.PM.response))); 143 | else 144 | estA = stair.PM.mean; 145 | estB = stair.beta; 146 | [SL, NP, OON] = PAL_PFML_GroupTrialsbyX(round(stair.PM.x(1:length(stair.PM.x))),stair.PM.response,ones(size(stair.PM.response))); 147 | end 148 | 149 | Results.estA(thisStair) = estA; 150 | Results.estB(thisStair) = estB; 151 | estA_obst = estA * (100/17); 152 | disp(['Threshold estimate: ', num2str(estA_obst)]); 153 | disp(['Slope estimate: ', num2str(estB)]); 154 | 155 | estA_pcnt = estA * (100/17); 156 | 157 | if showPlot 158 | % SL stim levels, NP num positive, OON out of num 159 | for SR = 1:length(SL(OON~=0)) 160 | plot(SL(SR),NP(SR)/OON(SR),'wo','markerfacecolor',plotColours{thisStair},'markersize',20*sqrt(OON(SR)./sum(OON))); 161 | hold on 162 | end 163 | axis([0 20 0.5 1]); 164 | 165 | % plot 166 | if procedure == 1 % Psi 167 | plot([min(stair.stimRange):.01:max(stair.stimRange)], stair.PF([stair.PM.threshold(length(stair.PM.threshold)) 10.^stair.PM.slope(length(stair.PM.threshold)) stair.priorGammaRange stair.PM.lapse(length(stair.PM.threshold))],min(stair.stimRange):.01:max(stair.stimRange)),'Color',plotColours{thisStair},'linewidth',lineWidth) 168 | % plot([min(SL):.01:max(SL)], stair.PF([stair.PM.threshold(end) 10.^stair.PM.slope(end) stair.priorGammaRange stair.PM.lapse(end)],min(stair.stimRange):.01:max(stair.stimRange)),'Color',plotColours{thisStair},'linewidth',lineWidth) 169 | text(min(stair.stimRange)+(max(stair.stimRange)-min(stair.stimRange))/1.3, .70,['\alpha ', num2str(estA_pcnt)],'color',[0 0 0],'Fontsize',fontSize) 170 | % text(min(stair.stimRange)+(max(stair.stimRange)-min(stair.stimRange))/1.3, .60,['\beta ', num2str(estB)],'color',[0 0 0],'Fontsize',fontSize) 171 | else 172 | plot([min(SL):.01:max(SL)], stair.PM.PF([stair.PM.xStaircase(end) 10.^(stair.beta) stair.PM.gamma stair.PM.lambda],min(SL):.01:max(SL)),'Color', plotColours{thisStair},'LineWidth',lineWidth) 173 | text(min(SL)+(max(SL)-min(SL))/1.3, (.5 + (thisStair*.1)),['\alpha ', num2str(estA_pcnt)],'color',[0 0 0],'Fontsize',fontSize) 174 | end 175 | 176 | % set(gca,'ytick',[0:.25:1]); 177 | set(gca,'yLim',[.5, 1]); 178 | drawnow 179 | end 180 | end 181 | 182 | % Get the stim levels at which participant performane is .3 .5 and .7 183 | if procedure == 1 184 | pptPMFvals = [stair.PM.threshold(length(stair.PM.threshold)) 10.^stair.PM.slope(length(stair.PM.threshold)) 0 stair.PM.lapse(length(stair.PM.threshold))]; 185 | inversePMFvals = [0.3, 0.5, 0.7]; 186 | inversePMFstims = stair.PF(pptPMFvals, inversePMFvals, 'inverse'); 187 | inversePMFstims = round(inversePMFstims,2); 188 | end 189 | 190 | 191 | %% Calculate overall accuracy 192 | validTrials = ~isnan(Results.Resp); %Resp, 1 correct, 0 incorrect 193 | nValidTrials = sum(validTrials); 194 | nCorrectTrials = sum(Results.Resp(validTrials)); 195 | accuracy = nCorrectTrials./nValidTrials; 196 | 197 | disp(['Session accuracy: ', num2str(accuracy)]); 198 | Results.acc = accuracy; 199 | 200 | %% Print dissy, breathless & asthma ratings 201 | dizzyR = Results.ExpRareEnd(1,2); 202 | breathlessR = Results.ExpRareEnd(1,3); 203 | asthmaR = Results.ExpRareEnd(1,4); 204 | 205 | disp(['Dizzy rating: ', num2str(round(dizzyR))]); 206 | disp(['Breathless rating: ', num2str(round(breathlessR))]); 207 | disp(['Asthma rating: ', num2str(round(asthmaR))]); 208 | 209 | %% Plot accuracy x RT 210 | RT = Results.RT; 211 | correctTrials = nanmean(RT(Results.Resp == 1)); 212 | incorrectTrials = nanmean(RT(Results.Resp == 0)); 213 | 214 | if showPlot 215 | subplot(spRows,spCols,4); hold on; 216 | title('RT x accuracy'); 217 | set(gcf,'color','w'); 218 | X = categorical({'Correct','Incorrect'}); 219 | Y = [correctTrials, incorrectTrials]; 220 | bar(X,Y, 0.5, 'FaceColor',plotColours{1},'LineWidth',0.1) 221 | set(gca,'yLim',[0, 3]); 222 | ylabel('RT (s)','fontsize',fontSize); 223 | 224 | 225 | %% Plot accuracy x confidence 226 | if existMcgnData % If confidence ratings were collected 227 | Conf = Results.ConfResp; 228 | correctTrials = nanmean(Conf(Results.Resp == 1)); 229 | incorrectTrials = nanmean(Conf(Results.Resp == 0)); 230 | 231 | subplot(spRows,spCols,5); hold on; 232 | title('Confidence x accuracy'); 233 | set(gcf,'color','w'); 234 | X = categorical({'Correct','Incorrect'}); 235 | Y = [correctTrials, incorrectTrials]; 236 | bar(X,Y, 0.5, 'FaceColor',plotColours{1},'LineWidth',0.1) 237 | set(gca,'yLim',[0, 100]); 238 | ylabel('Confidence ratings','fontsize',fontSize); 239 | end 240 | 241 | %% Plot unpleasantness rating x break # 242 | blahRateMat = Results.ExpRateTask; 243 | nRatigs = size(blahRateMat,2); 244 | blahRateVals = blahRateMat(1, :); 245 | 246 | subplot(spRows,spCols,(spCols*spRows)); hold on; 247 | title('Task unpleasantness'); 248 | set(gcf,'color','w'); 249 | X = categorical(1:nRatigs); 250 | bar(X, blahRateVals, 0.5, 'FaceColor',plotColours{2},'LineWidth',0.1) 251 | set(gca,'yLim',[0, 100]); 252 | ylabel('Unpleasantness rating','fontsize',fontSize); 253 | xlabel('Timepoint','fontsize',fontSize); 254 | 255 | 256 | end 257 | 258 | 259 | %% Save updated results file 260 | vars.DataFileName = ['a', vars.DataFileName]; 261 | % save(strcat(vars.OutputFolder, vars.DataFileName), 'stair', 'Results', 'vars', 'scr', 'keys' ); 262 | 263 | % Will save to current directory 264 | % save(vars.DataFileName, 'stair', 'Results', 'vars', 'scr', 'keys' ); 265 | 266 | % Save in /github/Respiroception/data/raw 267 | if exist('desiredOutputDir','var') 268 | outputFolder = fullfile(desiredOutputDir,vars.DataFileName); 269 | else 270 | outputFolder = fullfile(filesep,'Users', 'au657961','github','Respiroception','data','raw',vars.DataFileName); 271 | end 272 | 273 | save(outputFolder, 'stair', 'Results', 'vars', 'scr', 'keys' ); 274 | disp(['Preprocessed file saved as: ', vars.DataFileName]) 275 | 276 | %% Save figure 277 | if savePlot 278 | % PDF 279 | % figFilename = fullfile(filesep,'Users', 'au657961','github','Respiroception','data','subjectReports',[vars.DataFileName(2:end),'.pdf']); 280 | % saveas(gcf, figFilename) 281 | 282 | % PNG 283 | % In Git folder 284 | % figFilename = fullfile(filesep,'Users', 'au657961','github','Respiroception','data','subjectReports',[vars.DataFileName(2:end),'.png']); 285 | % In /aux/ 286 | figFilename = fullfile(filesep,'Volumes','aux', 'MINDLAB2019_Visceral-Mind','6_reports','RRST','VMP2',[vars.DataFileName(2:end),'.png']); 287 | saveas(gcf, figFilename) 288 | 289 | 290 | % Print outputccccc 291 | disp('Figures saved.') 292 | end -------------------------------------------------------------------------------- /task/helpers/subjectReportRRS_physio.m: -------------------------------------------------------------------------------- 1 | function subjectReportRRS_physio(filepath) 2 | % function subjectReportRRS_physio(filepath) 3 | % 4 | % Arguments: 5 | % filepath RRS data log path 6 | % 7 | % Make a single subject analysis report. Works with either Psi or Quest 8 | % - check functionality with Ndown, MCS 9 | % - includes physio (pressure & flow) plot 10 | % 11 | % Project: Respiratory resistance sensitivity task 12 | % 13 | % Niia Nikolova 14 | % Last edit: 05/06/2021 15 | 16 | showPlot = 1; % Optional flag, set to 1 to display a summary figure for each subject (session) 17 | savePlot = 0; % Optional flag to save plot output (as pdf & png) 18 | 19 | %% Setup 20 | % Check input args & load file 21 | if nargin < 1 22 | disp('No file name was passed. Please select a raw ''RRS_SUBID_'' data file.'); 23 | % Select a file & load 24 | [file, path] = uigetfile('*.mat', 'Select a subject RRS results file.'); 25 | filepath = [path, file]; 26 | subID = file(5:8); 27 | 28 | elseif nargin == 1 29 | % Do nothing 30 | end 31 | 32 | % Load RRS data log 33 | load(filepath); 34 | disp(['Processing data file: ', filepath]) 35 | 36 | % Load presure & flow logs 37 | pressureFile = strcat('p_',subID,'.csv'); 38 | 39 | filesInSubDir = dir(path); 40 | flowFile = 0; 41 | for thisFile = 3:size(filesInSubDir,1) 42 | fName = filesInSubDir(thisFile).name; 43 | flowFileTF = contains(fName,'SFM3019'); 44 | if flowFileTF 45 | flowFile = fName; 46 | end 47 | end 48 | 49 | pData = readtable([path, pressureFile]); 50 | 51 | if sum(flowFile) ~= 0 % if there is a flow file for this subject 52 | % Make a copy of the flow file as csv 53 | flowFileCSV = [flowFile(1:end-4), '.csv']; 54 | copyfile([path, flowFile], [path, flowFileCSV]) 55 | fData = readtable([path, flowFileCSV]); 56 | end 57 | 58 | %% Define plotting variables 59 | plotColours = {[0.00,0.45,0.74], [0.85,0.33,0.10], [], []}; 60 | lineWidth = 2; 61 | fontSize = 10; 62 | fontName = 'Helvetica'; 63 | % fontSize = 18; 64 | 65 | %% Check which procedure was run 66 | % 1 Psi, 2 Ndown, 3 MCS, 4 QUEST 67 | procedure = vars.Procedure; 68 | 69 | % Determine number of staircases 70 | origStairs = stair; 71 | nStaircases = size(stair,2); 72 | 73 | %% Check for metacog data & set up subplot figure 74 | existMcgnData = any(~isnan(Results.ConfResp)); 75 | 76 | if showPlot 77 | figure; hold on; 78 | set(gcf,'color','w'); 79 | if procedure == 1 % Psi 80 | sgt = sgtitle(['Sub ',num2str(vars.subNo),', Psi']); else 81 | sgt = sgtitle(['Sub ',num2str(vars.subNo),', QUEST']); 82 | end 83 | sgt.FontSize = 14; 84 | spCols = 2; 85 | spRows = 3; 86 | 87 | %% Threshold estimate by trial 88 | subplot(spRows,spCols,[1,2]); hold on; 89 | end 90 | 91 | % Loop over the staircases 92 | for thisStair = 1 : nStaircases 93 | 94 | stair = origStairs(thisStair); 95 | 96 | if procedure == 1 % Psi 97 | t = 1:length(stair.PM.x)-1; % trial # 98 | stimByTrial = stair.PM.threshold; 99 | else 100 | t = 1:length(stair.PM.x); 101 | stimByTrial = stair.PM.x; 102 | end 103 | 104 | if showPlot 105 | % Plot line 106 | plot(1:length(t),stimByTrial,'Color',plotColours{thisStair},'LineWidth',lineWidth); hold on 107 | 108 | % Plot correct responses 109 | plot(t(stair.PM.response == 1),stair.PM.x(stair.PM.response == 1),'wo',... 110 | 'MarkerSize',8,... 111 | 'MarkerEdgeColor',plotColours{thisStair},... 112 | 'MarkerFaceColor',plotColours{thisStair}); 113 | hold on 114 | 115 | % Plot incorrect responses 116 | plot(t(stair.PM.response == 0),stair.PM.x(stair.PM.response == 0),'wo',... 117 | 'MarkerSize',8,... 118 | 'MarkerEdgeColor',plotColours{thisStair},... 119 | 'MarkerFaceColor',[1 1 1]); 120 | hold on 121 | end 122 | end 123 | 124 | if showPlot 125 | % Add axis labels 126 | hold on 127 | title('Presented Stimuli & Threshold Estimate by Trial','FontWeight','Normal'); 128 | axis([0 (length(stair.PM.x)+5) 3.6 19]); 129 | yticks([3.6, 7.2, 10.8, 14.4, 18]); 130 | yticklabels({'20', '40', '60', '80', '100'}); 131 | xlabel('Trial number','fontsize',fontSize); 132 | ylabel('% Obstruction','fontsize',fontSize); 133 | set(gca,'FontName',fontName,'fontsize',fontSize) 134 | grid on 135 | grid minor 136 | hold on 137 | end 138 | 139 | 140 | %% Plot pressure data 141 | xVar = pData.Sample_(:,1); 142 | yVar = pData.FlowLinearized_Pa_(:,1); 143 | if iscell(yVar) % in some cases this var is imported as a cell, convert 144 | yVar = str2double(yVar); 145 | end 146 | 147 | % Remove yVar values between some range 148 | zeroVals = yVar < -150 | yVar > 150; 149 | yVar = yVar(zeroVals); 150 | xVar = xVar(zeroVals); 151 | 152 | %yVar( yVar < 0 ) = 0; % remove negative values 153 | xVar2 = str2double(xVar); % convert cell to double 154 | 155 | if showPlot 156 | % Plot pressure 157 | subplot(spRows,spCols,3:4); hold on; 158 | title('Pressure','FontWeight','Normal'); 159 | xlabel('Sample #','fontsize',fontSize); 160 | ylabel('Pa','fontsize',fontSize); 161 | hold on 162 | 163 | plot(xVar2, yVar) 164 | end 165 | 166 | 167 | 168 | %% Import & plot flow 169 | xVar = 1: size(fData,1); 170 | yVar = fData(:,3); 171 | yVar = table2array(yVar); 172 | 173 | % % Remove yVar values between some range 174 | % zeroVals = yVar < -150 | yVar > 150; 175 | % yVar = yVar(zeroVals); 176 | % xVar = xVar(zeroVals); 177 | 178 | yVar2 = str2double(yVar); % convert cell to double 179 | 180 | if showPlot 181 | % Plot flow 182 | subplot(spRows,spCols,5:6); hold on; 183 | title('Flow','FontWeight','Normal'); 184 | xlabel('Sample #','fontsize',fontSize); 185 | ylabel('Flow (cms)','fontsize',fontSize); 186 | hold on 187 | 188 | plot(xVar, yVar2) 189 | end 190 | 191 | 192 | %% Save figure 193 | if savePlot 194 | % % PDF 195 | % figFilename = fullfile(filesep,'Users', 'au657961','github','Respiroception','data','subjectReports',[vars.DataFileName(2:end),'.pdf']); 196 | % saveas(gcf, figFilename) 197 | 198 | % PNG 199 | figFilename = fullfile(filesep,'Users', 'au657961','github','Respiroception','data','subjectReports',[vars.DataFileName(2:end),'.png']); 200 | saveas(gcf, figFilename) 201 | 202 | % Print output 203 | disp('Figures saved.') 204 | end -------------------------------------------------------------------------------- /task/helpers/updateStaircase.m: -------------------------------------------------------------------------------- 1 | function [stair] = updateStaircase(stair, Resp) 2 | % function [stair] = updateStaircase(stair, Resp) 3 | % 4 | % Update N-down staircase with most recent response Resp 5 | % 6 | % 7 | % Niia Nikolova, 29/05/2020 8 | 9 | IsReversal = 0; 10 | 11 | switch Resp 12 | 13 | %% Up step (angry) 14 | case 0 15 | 16 | stair.xCurrent = stair.xCurrent + stair.StepSize; % Up one StepSize 17 | 18 | % Reversal if previous step was down 19 | if stair.Previous == -1 20 | IsReversal = 1; 21 | stair.ReversalCounter = stair.ReversalCounter + 1; 22 | end 23 | 24 | stair.Previous = 1; 25 | 26 | %% Down step (happy) 27 | case 1 28 | 29 | stair.xCurrent = stair.xCurrent - stair.StepSize; % Down one StepSize 30 | 31 | % Reversal if previous step was up 32 | if stair.Previous == 1 33 | IsReversal = 1; 34 | stair.ReversalCounter = stair.ReversalCounter + 1; 35 | end 36 | 37 | stair.Previous = -1; 38 | 39 | % Different rules before & after 1st reversal, eg. 2-down 1-up 40 | % if (stair.ReversalCounter == 0 && stair.HappyCounter == 1) % Before 1st reversal 41 | % stair.xCurrent = stair.xCurrent - stair.StepSize; % Down one StepSize 42 | % 43 | % elseif (stair.ReversalCounter > 0 && stair.HappyCounter < stair.NumDown) 44 | % stair.HappyCounter = stair.HappyCounter + 1; 45 | % 46 | % elseif (stair.ReversalCounter > 0 && stair.HappyCounter == stair.NumDown) 47 | % stair.xCurrent = stair.xCurrent - stair.StepSize; % Down one StepSize 48 | % stair.HappyCounter = 1; 49 | % end 50 | 51 | end 52 | 53 | stair.ReversalFlags = [stair.ReversalFlags, IsReversal]; 54 | 55 | %% Check if desired # of reversals has been reached, and if so set stop flag to 1 56 | if (stair.ReversalCounter == stair.ReversalStop) 57 | stair.stop = 1; 58 | end 59 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_2-2/moveInIncrements.m: -------------------------------------------------------------------------------- 1 | function moveInIncrements() 2 | %moveInIncrements Moves respiroception device back and forth to increments 3 | %of obstruction. Used for recording physio measures 4 | % Input: 5 | % 6 | % 7 | % 8 | % Niia Nikolova 15/06/2021 9 | 10 | 11 | % Initialize resp device 12 | unitscale = 1; % 0 units 0-100. 1 units 0-17 13 | nReps = 20; 14 | sPort = "COM14"; 15 | sBaudRate = 9600; 16 | [init, respDevice] = setupResp(sPort, sBaudRate); 17 | 18 | if init 19 | % Set some variables 20 | maxMotorDur = 1.5; % in seconds (how long motor takes to move to new position, be liberal!) 21 | resetDur = 10; % sec in takes to reset 22 | 23 | % ---------------------------------------------------------------- 24 | % First, reset the device 25 | resetResp(respDevice); 26 | % Wait for device to move (XX secs) 27 | WaitSecs(resetDur); 28 | 29 | % ---------------------------------------------------------------- 30 | % Define increments to move to 31 | moveInc = 1:1:17; 32 | nIncs = size(moveInc,2); 33 | homePos = 1; 34 | 35 | 36 | % Print start duration 37 | t = datetime('now'); 38 | DateString = datestr(t); 39 | disp(['RRST device testing, started: ', DateString]); 40 | 41 | for thisRep = 1 : nReps 42 | 43 | for thisStep = 1 : nIncs 44 | 45 | % Determine position to move to 46 | newPosition = moveInc(thisStep); 47 | 48 | % Send move command 49 | moveResp(respDevice, newPosition, unitscale); 50 | 51 | % Wait for device to move (XX secs) 52 | WaitSecs(maxMotorDur); 53 | end 54 | 55 | % Move back to 0 56 | moveResp(respDevice, homePos, unitscale); 57 | 58 | % Wait for device to move (XX secs) 59 | WaitSecs(resetDur); 60 | end 61 | 62 | else 63 | disp('Device initialization failed. Check that correct serial port number was used...'); 64 | disp(['Current serial port: ', sPort]); 65 | return 66 | 67 | end 68 | 69 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_2-2/moveResp.m: -------------------------------------------------------------------------------- 1 | function [moved, currPosition] = moveResp(respDevice, newPosition, unitscale) 2 | %moveResp Moves respiroception device to desired position 3 | % Input: 4 | % respDevice serial port, e.g. "COM10" (windows) 5 | % newPosition target position, e.g. "50" (0-100) 6 | % unitscale units to scale. 0 percent (0-100), 1 mm (0-17) 7 | % 8 | % Niia Nikolova 9 | % Last edited 06/01/2021 10 | 11 | % N.B. newPosition is rescaledunitscale to stepMotor values (31000 - 62500) 12 | % Reference positions 13 | % ~38000 max position 14 | % ~12000 not touching 15 | 16 | rescale = 1; % rescale motor step values 17 | 18 | 19 | try 20 | 21 | if rescale 22 | % Rescale new position input to motor step value 23 | [motorStepVal] = scale2motorstep(newPosition, unitscale); 24 | else 25 | motorStepVal = newPosition; 26 | end 27 | moveToHere = strcat(sprintf('%03d', motorStepVal)); 28 | 29 | % Move & update new current position output 30 | writeline(respDevice, moveToHere) 31 | currPosition = newPosition; 32 | moved = 1; 33 | 34 | catch 35 | currPosition = NaN; 36 | moved = 0; 37 | end 38 | 39 | end 40 | 41 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_2-2/moveResp2ITIpos.m: -------------------------------------------------------------------------------- 1 | function [moved, currPosition] = moveResp2ITIpos(respDevice, unitscale) 2 | %moveResp2NoLoad Moves respiroception device ~1000 steps back from no load 3 | %position (position 5 +-5) 4 | % Input: 5 | % respDevice serial port, e.g. "COM5" (windows) 6 | % unitscale units to scale. 0 percent (0-100), 1 mm (0-17) 7 | % 8 | % Niia Nikolova 05/10/2020 9 | 10 | % Add some jitter 11 | basePos = 50; 12 | jitterAmnt = 30; 13 | jitterVal = randi([-jitterAmnt, jitterAmnt]); % +/- 30% 14 | newPosition = basePos + jitterVal; 15 | 16 | % Move to jittered position 17 | [moved, currPosition] = moveResp(respDevice, newPosition, unitscale); 18 | 19 | end 20 | 21 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_2-2/moveResp2NoLoad.m: -------------------------------------------------------------------------------- 1 | function [moved, currPosition] = moveResp2NoLoad(respDevice, unitscale) 2 | %moveResp2NoLoad Moves respiroception device to no load position (not 3 | %touching tube), ~ position 20 4 | % Input: 5 | % respDevice serial port, e.g. "COM5" (windows) 6 | % unitscale units to scale. 0 percent (0-100), 1 mm (0-17) 7 | % Niia Nikolova 01/10/2020 8 | 9 | 10 | % Move to No Load position 11 | newPosition = 0; 12 | [moved, currPosition] = moveResp(respDevice, newPosition, unitscale); 13 | 14 | end 15 | 16 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_2-2/resetResp.m: -------------------------------------------------------------------------------- 1 | function resetResp(respDevice) 2 | %resetResp REcalibrates respiratory device by sending negative step value 3 | % Input: 4 | % respDevice serial port, e.g. "COM10" (windows) 5 | % 6 | % Note this takes approx. 10 seconds! 7 | % 8 | % Niia Nikolova 9 | % Last edited 05/12/2020 10 | 11 | 12 | try 13 | 14 | newPosition = -200; 15 | 16 | motorStepVal = newPosition; 17 | moveToHere = strcat(sprintf('%03d', motorStepVal)); 18 | 19 | % Move & update new current position output 20 | writeline(respDevice, moveToHere) 21 | 22 | catch 23 | disp('ERROR. Could not reset resp device.'); 24 | end 25 | 26 | end 27 | 28 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_2-2/scale2motorstep.m: -------------------------------------------------------------------------------- 1 | function [motorStepVal] = scale2motorstep(scaleVal, unitscale) 2 | %[motorStepVal] = scale2motorstep(scaleVal,unitscale) Rescale either a 3 | % percent (0-100) or mm (0-17) value [scaleVal] to 12000 - 4 | % 38000 [motorStepVal] (step motor input for moveResp.m) 5 | % 6 | % Input: 7 | % scaleVal input value 0-100 8 | % unitscale units to scale. 0 percent (0-100), 1 mm (0-17) 9 | % 10 | % Output: 11 | % motorStepVal scaled value 9,000 - 32,000 12 | % 13 | % Note that motorStepVal values can vary by device, due to the way in which 14 | % the lead screw is mounted to the motor & coupler 15 | % Green device 12,000 - 38,000 16 | % Red device (Olivia) 9,000 - 32,000 17 | % Yellow device 8,000 - 32,500 (old 30,300) 18 | % Black device 9,000 - 31,000 19 | % Black device #2 (SPICE) 9,000 - 32,000 20 | % 21 | % It is possible to fine tune this to some extent, by turning the screw 22 | % attached to the back of the wedge on the device. Turn CCW (out) to 23 | % decrease steps, and CW (in) to increas steps. 24 | % 25 | % minimum = load just touching tube (a A4 paper inserted between wedge and tube is just help in place), 26 | % max = barely possibly to inhale through the tube 27 | % 28 | % Niia Nikolova 06/01/2021 29 | 30 | 31 | stepMotRange_min = 8000; % <<====== THIS NEEDS TO BE ADJUSTED FOR EACH INDIVIDUAL MOTOR 32 | stepMotRange_max = 32500; % <<====== THIS NEEDS TO BE ADJUSTED FOR EACH INDIVIDUAL MOTOR 33 | 34 | if unitscale == 0 % percent 35 | scaleInput = [0 scaleVal 100]; 36 | elseif unitscale == 1 % mm 37 | scaleInput = [0 scaleVal 17]; 38 | else 39 | disp('Error! Invalid unit scale paramter. Enter 0 for percent obstruction or 1 for mm'); 40 | return 41 | end 42 | rescaledVals = rescale(scaleInput, stepMotRange_min, stepMotRange_max); 43 | motorStepVal = round(rescaledVals(2)); 44 | 45 | % disp(['Motor step val: ', num2str(motorStepVal)]); 46 | end 47 | 48 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_2-2/setupResp.m: -------------------------------------------------------------------------------- 1 | function [init, respDevice] = setupResp(sPort, sBaudRate) 2 | %setupResp Initializes device for respiroception (filter/load 3 | %detection) task using serial port 4 | % 5 | % Input: 6 | % sPort serial port, e.g. "COM5" (windows) 7 | % sBaudRate device baud rate (9600) 8 | % Output: 9 | % init initilaization successfull flag (1 yes, 0 no) 10 | % respDevice device object handle 11 | % Example usage: 12 | % sPort = "COM5"; 13 | % sBaudRate = 9600; 14 | % [init, respDevice] = setupResp(sPort, sBaudRate) 15 | 16 | % 17 | % To see a list of serial port devices, input serialportlist. Port name 18 | % will differ by OS. 19 | % See, https://uk.mathworks.com/help/matlab/matlab_external/create-serial-port-object.html 20 | % 21 | % Niia Nikolova 26/08/2020 22 | 23 | % Create serial port object 24 | global respDevice 25 | 26 | try 27 | 28 | respDevice = serialport(sPort,sBaudRate); 29 | init = 1; 30 | 31 | catch 32 | respDevice = NaN; 33 | init = 0; 34 | 35 | end 36 | 37 | if init 38 | disp('Serial port initialization successfull.'); 39 | else 40 | disp('Serial port initialization failed.'); 41 | return 42 | end 43 | 44 | 45 | 46 | end 47 | 48 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_2-2/testMovements.m: -------------------------------------------------------------------------------- 1 | 2 | clear all 3 | 4 | sPort = "COM6"; 5 | sBaudRate = 9600; 6 | [init, respDevice] = setupResp(sPort, sBaudRate); 7 | 8 | for repeat = 1:5 9 | 10 | for goMore = 1:3 11 | 12 | if goMore == 1 13 | newPosition = 0; 14 | elseif goMore == 2 15 | newPosition = 50; 16 | elseif goMore == 3 17 | newPosition = 100; 18 | end 19 | 20 | moveResp(respDevice, newPosition); 21 | pause(2); 22 | 23 | end 24 | 25 | 26 | 27 | end 28 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_2-2/testRespDevice.m: -------------------------------------------------------------------------------- 1 | function testRespDevice(nMinutes) 2 | %testRespDevice Moves respiroception device back and forth to random 3 | %position for a set number of minutes. To use for testing the device. 4 | % Input: 5 | % nMinutes number of minutes to move for 6 | % 7 | % 8 | % Niia Nikolova 16/11/2020 9 | 10 | % N.B. newPosition is rescaled to stepMotor values 11 | % Reference positions 12000 - 38000 12 | 13 | % Initialize resp device 14 | unitscale = 1; % units 0-100 15 | sPort = "COM15"; 16 | sBaudRate = 9600; 17 | [init, respDevice] = setupResp(sPort, sBaudRate); 18 | 19 | if init 20 | % Set some variables 21 | nSeconds = nMinutes .* 60; % # seconds to run device for 22 | maxMotorDur = 3; % in seconds (check & update before starting, 23 | % or the motor may receive commands before it is 24 | % finished moving, risking to be decalibrated) 25 | maxResetDur = 10; 26 | basePos = 9; 27 | jitterAmnt = 8; 28 | 29 | % ---------------------------------------------------------------- 30 | % First, reset the device 31 | resetResp(respDevice); 32 | % Wait for device to move (XX secs) 33 | WaitSecs(maxMotorDur); 34 | 35 | % ---------------------------------------------------------------- 36 | % Move back and forth for the desired duration (nMinutes) 37 | 38 | currDuration = 0; 39 | startT = GetSecs; 40 | tic; 41 | 42 | % Print start duration 43 | t = datetime('now'); 44 | DateString = datestr(t); 45 | disp(['RRST device testing, started: ', DateString]); 46 | 47 | 48 | while logical(currDuration <= nSeconds) 49 | 50 | currT = GetSecs; 51 | currDuration = currT - startT; 52 | 53 | % Determine a random position to move to (middle position +/- jitter) 54 | jitterVal = randi([-jitterAmnt, jitterAmnt]); % +/- jitterAmnt 55 | newPosition = basePos + jitterVal; 56 | 57 | % Send move command 58 | moveResp(respDevice, newPosition, unitscale); 59 | 60 | % Wait for device to move (XX secs) 61 | WaitSecs(maxMotorDur); 62 | 63 | end 64 | toc 65 | 66 | else 67 | disp('Device initialization failed. Check that correct serial port number was used...'); 68 | disp(['Current serial port: ', sPort]); 69 | return 70 | 71 | end 72 | 73 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_3-0/calibrateResp.m: -------------------------------------------------------------------------------- 1 | function calibrateResp(respDevice) 2 | %calibrateResp Calibrates the device. Wedge moves slowly (backwards then forwrads) 3 | % until it reaches the switch. 4 | 5 | % Input: 6 | % respDevice serial port, e.g. "COM5" (windows) 7 | % Niia Nikolova 05/2022 8 | 9 | 10 | calibrateCmd = sprintf('c'); 11 | writeline(respDevice, calibrateCmd); 12 | 13 | end 14 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_3-0/moveInIncrements.m: -------------------------------------------------------------------------------- 1 | function moveInIncrements() 2 | %moveInIncrements Moves respiroception device back and forth to increments 3 | %of obstruction. Used for recording physio measures 4 | % Input: 5 | % 6 | % 7 | % 8 | % Niia Nikolova 15/06/2021 9 | 10 | 11 | % Initialize resp device 12 | unitscale = 1; % 0 units 0-100. 1 units 0-17 13 | nReps = 20; 14 | sPort = "COM5"; 15 | sBaudRate = 9600; 16 | [init, respDevice] = setupResp(sPort, sBaudRate); 17 | 18 | if init 19 | % Set some variables 20 | maxMotorDur = 1.5; % in seconds (how long motor takes to move to new position, be liberal!) 21 | resetDur = 10; % sec in takes to reset 22 | 23 | % ---------------------------------------------------------------- 24 | % First, reset the device 25 | resetResp(respDevice); 26 | % Wait for device to move (XX secs) 27 | WaitSecs(resetDur); 28 | 29 | % ---------------------------------------------------------------- 30 | % Define increments to move to 31 | moveInc = 1:1:17; 32 | nIncs = size(moveInc,2); 33 | homePos = 1; 34 | 35 | 36 | % Print start duration 37 | t = datetime('now'); 38 | DateString = datestr(t); 39 | disp(['RRST device testing, started: ', DateString]); 40 | 41 | for thisRep = 1 : nReps 42 | 43 | for thisStep = 1 : nIncs 44 | 45 | % Determine position to move to 46 | newPosition = moveInc(thisStep); 47 | 48 | % Send move command 49 | moveResp(respDevice, newPosition, unitscale); 50 | 51 | % Wait for device to move (XX secs) 52 | WaitSecs(maxMotorDur); 53 | end 54 | 55 | % Move back to 0 56 | moveResp(respDevice, homePos, unitscale); 57 | 58 | % Wait for device to move (XX secs) 59 | WaitSecs(resetDur); 60 | end 61 | 62 | else 63 | disp('Device initialization failed. Check that correct serial port number was used...'); 64 | disp(['Current serial port: ', sPort]); 65 | return 66 | 67 | end 68 | 69 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_3-0/moveResp.m: -------------------------------------------------------------------------------- 1 | function [moved, currPosition] = moveResp(respDevice, newPosition, unitscale) 2 | %moveResp Moves respiroception device to desired position 3 | % Input: 4 | % respDevice serial port, e.g. "COM10" (windows) 5 | % newPosition target position, e.g. "50" (0-100) 6 | % unitscale units to scale. 0 percent (0-100), 1 mm (0-17) 7 | % 8 | % Niia Nikolova 9 | % Last edited 06/01/2021 10 | 11 | % N.B. newPosition is rescaledunitscale to stepMotor values (31000 - 62500) 12 | % Reference positions 13 | % ~38000 max position 14 | % ~12000 not touching 15 | 16 | rescale = 1; % rescale motor step values 17 | 18 | % Send command for move 19 | moveCmd = strcat(sprintf('a')); 20 | writeline(respDevice, moveCmd); 21 | 22 | try 23 | 24 | if rescale 25 | % Rescale new position input to motor step value 26 | [motorStepVal] = scale2motorstep(newPosition, unitscale); 27 | else 28 | motorStepVal = newPosition; 29 | end 30 | moveToHere = strcat(sprintf('%03d', motorStepVal)); 31 | 32 | % Move & update new current position output 33 | writeline(respDevice, moveToHere); 34 | currPosition = newPosition; 35 | moved = 1; 36 | 37 | catch 38 | currPosition = NaN; 39 | moved = 0; 40 | end 41 | 42 | end 43 | 44 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_3-0/moveResp2ITIpos.m: -------------------------------------------------------------------------------- 1 | function [moved, currPosition] = moveResp2ITIpos(respDevice, unitscale) 2 | %moveResp2NoLoad Moves respiroception device ~1000 steps back from no load 3 | %position (position 5 +-5) 4 | % Input: 5 | % respDevice serial port, e.g. "COM5" (windows) 6 | % unitscale units to scale. 0 percent (0-100), 1 mm (0-17) 7 | % 8 | % Niia Nikolova 05/10/2020 9 | 10 | % Add some jitter 11 | basePos = 50; 12 | jitterAmnt = 30; 13 | jitterVal = randi([-jitterAmnt, jitterAmnt]); % +/- 30% 14 | newPosition = basePos + jitterVal; 15 | 16 | % Move to jittered position 17 | [moved, currPosition] = moveResp(respDevice, newPosition, unitscale); 18 | 19 | end 20 | 21 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_3-0/moveResp2NoLoad.m: -------------------------------------------------------------------------------- 1 | function [moved, currPosition] = moveResp2NoLoad(respDevice, unitscale) 2 | %moveResp2NoLoad Moves respiroception device to no load position (not 3 | %touching tube), ~ position 20 4 | % Input: 5 | % respDevice serial port, e.g. "COM5" (windows) 6 | % unitscale units to scale. 0 percent (0-100), 1 mm (0-17) 7 | % Niia Nikolova 01/10/2020 8 | 9 | 10 | % Move to No Load position 11 | newPosition = 0; 12 | [moved, currPosition] = moveResp(respDevice, newPosition, unitscale); 13 | 14 | end 15 | 16 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_3-0/scale2motorstep.m: -------------------------------------------------------------------------------- 1 | function [motorStepVal] = scale2motorstep(scaleVal, unitscale) 2 | %[motorStepVal] = scale2motorstep(scaleVal,unitscale) Rescale either a 3 | % percent (0-100) or mm (0-17) value [scaleVal] to 12000 - 4 | % 38000 [motorStepVal] (step motor input for moveResp.m) 5 | % 6 | % Input: 7 | % scaleVal input value 0-100 8 | % unitscale units to scale. 0 percent (0-100), 1 mm (0-17) 9 | % 10 | % Output: 11 | % motorStepVal scaled value 9,000 - 32,000 12 | % 13 | % Note that motorStepVal values can vary by device, due to the way in which 14 | % the lead screw is mounted to the motor & coupler 15 | % Green device 12,000 - 38,000 16 | % Red device (Olivia) 9,000 - 32,000 17 | % Yellow device 8,000 - 32,500 (old 30,300) 18 | % Black device 9,000 - 31,000 19 | % Black device #2 (SPICE) 9,000 - 32,000 20 | % 21 | % It is possible to fine tune this to some extent, by turning the screw 22 | % attached to the back of the wedge on the device. Turn CCW (out) to 23 | % decrease steps, and CW (in) to increas steps. 24 | % 25 | % minimum = load just touching tube (a A4 paper inserted between wedge and tube is just help in place), 26 | % max = barely possibly to inhale through the tube 27 | % 28 | % Niia Nikolova 06/01/2021 29 | 30 | 31 | stepMotRange_min = 12000; % <<====== THIS NEEDS TO BE ADJUSTED FOR EACH INDIVIDUAL MOTOR 32 | stepMotRange_max = 38000; % <<====== THIS NEEDS TO BE ADJUSTED FOR EACH INDIVIDUAL MOTOR 33 | 34 | if unitscale == 0 % percent 35 | scaleInput = [0 scaleVal 100]; 36 | elseif unitscale == 1 % mm 37 | scaleInput = [0 scaleVal 17]; 38 | else 39 | disp('Error! Invalid unit scale paramter. Enter 0 for percent obstruction or 1 for mm'); 40 | return 41 | end 42 | rescaledVals = rescale(scaleInput, stepMotRange_min, stepMotRange_max); 43 | motorStepVal = round(rescaledVals(2)); 44 | 45 | % disp(['Motor step val: ', num2str(motorStepVal)]); 46 | end 47 | 48 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_3-0/setupResp.m: -------------------------------------------------------------------------------- 1 | function [init, respDevice] = setupResp(sPort, sBaudRate) 2 | %setupResp Initializes device for respiroception (filter/load 3 | %detection) task using serial port 4 | % 5 | % Input: 6 | % sPort serial port, e.g. "COM5" (windows) 7 | % sBaudRate device baud rate (9600) 8 | % Output: 9 | % init initilaization successfull flag (1 yes, 0 no) 10 | % respDevice device object handle 11 | % Example usage: 12 | % sPort = "COM5"; 13 | % sBaudRate = 9600; 14 | % [init, respDevice] = setupResp(sPort, sBaudRate) 15 | 16 | % 17 | % To see a list of serial port devices, input serialportlist. Port name 18 | % will differ by OS. 19 | % See, https://uk.mathworks.com/help/matlab/matlab_external/create-serial-port-object.html 20 | % 21 | % Niia Nikolova 26/08/2020 22 | 23 | % Create serial port object 24 | global respDevice 25 | 26 | try 27 | 28 | respDevice = serialport(sPort,sBaudRate); 29 | init = 1; 30 | 31 | catch 32 | respDevice = NaN; 33 | init = 0; 34 | 35 | end 36 | 37 | if init 38 | disp('Serial port initialization successfull.'); 39 | else 40 | disp('Serial port initialization failed.'); 41 | return 42 | end 43 | 44 | 45 | 46 | end 47 | 48 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_3-0/testMovements.m: -------------------------------------------------------------------------------- 1 | 2 | clear all 3 | 4 | sPort = "COM6"; 5 | sBaudRate = 9600; 6 | [init, respDevice] = setupResp(sPort, sBaudRate); 7 | 8 | for repeat = 1:5 9 | 10 | for goMore = 1:3 11 | 12 | if goMore == 1 13 | newPosition = 0; 14 | elseif goMore == 2 15 | newPosition = 50; 16 | elseif goMore == 3 17 | newPosition = 100; 18 | end 19 | 20 | moveResp(respDevice, newPosition); 21 | pause(2); 22 | 23 | end 24 | 25 | 26 | 27 | end 28 | -------------------------------------------------------------------------------- /task/respDeviceHelpers_3-0/testRespDevice.m: -------------------------------------------------------------------------------- 1 | function testRespDevice(nMinutes) 2 | %testRespDevice Moves respiroception device back and forth to random 3 | %position for a set number of minutes. To use for testing the device. 4 | % Input: 5 | % nMinutes number of minutes to move for 6 | % 7 | % 8 | % Niia Nikolova 16/11/2020 9 | 10 | % N.B. newPosition is rescaled to stepMotor values 11 | % Reference positions 12000 - 38000 12 | 13 | % Initialize resp device 14 | unitscale = 1; % units 0-100 15 | sPort = "COM5"; 16 | sBaudRate = 9600; 17 | [init, respDevice] = setupResp(sPort, sBaudRate); 18 | 19 | if init 20 | % Set some variables 21 | nSeconds = nMinutes .* 60; % # seconds to run device for 22 | maxMotorDur = 3; % in seconds (check & update before starting, 23 | % or the motor may receive commands before it is 24 | % finished moving, risking to be decalibrated) 25 | maxResetDur = 10; 26 | basePos = 9; 27 | jitterAmnt = 8; 28 | 29 | % ---------------------------------------------------------------- 30 | % First, reset the device 31 | resetResp(respDevice); 32 | % Wait for device to move (XX secs) 33 | WaitSecs(maxMotorDur); 34 | 35 | % ---------------------------------------------------------------- 36 | % Move back and forth for the desired duration (nMinutes) 37 | 38 | currDuration = 0; 39 | startT = GetSecs; 40 | tic; 41 | 42 | % Print start duration 43 | t = datetime('now'); 44 | DateString = datestr(t); 45 | disp(['RRST device testing, started: ', DateString]); 46 | 47 | 48 | while logical(currDuration <= nSeconds) 49 | 50 | currT = GetSecs; 51 | currDuration = currT - startT; 52 | 53 | % Determine a random position to move to (middle position +/- jitter) 54 | jitterVal = randi([-jitterAmnt, jitterAmnt]); % +/- jitterAmnt 55 | newPosition = basePos + jitterVal; 56 | 57 | % Send move command 58 | moveResp(respDevice, newPosition, unitscale); 59 | 60 | % Wait for device to move (XX secs) 61 | WaitSecs(maxMotorDur); 62 | 63 | end 64 | toc 65 | 66 | else 67 | disp('Device initialization failed. Check that correct serial port number was used...'); 68 | disp(['Current serial port: ', sPort]); 69 | return 70 | 71 | end 72 | 73 | --------------------------------------------------------------------------------