├── .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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------