Mapping human visual contrast sensitivity and vision loss across the visual field with model-based fMRI.
The 10 matches
- [1] § Methods › Quantifying eccentricity- and location-dependent variations in sensitivity › Using subject-specific pRF estimates ↔ BackProjection_Slopes_Patient.m, lines 99 to 138 · score 0.93 · near periphery, 0.5–2.5 deg, 15–20 deg, 2.5–4.5 deg, 4.5–9.5 deg, 9.5–15 deg · Report a mismatch
- [2] § Methods › Quantifying eccentricity- and location-dependent variations in sensitivity › Using subject-specific pRF estimates ↔ BackProjection_Slopes_Controls.m, lines 160 to 200 · score 0.88 · 0.5–2.5 deg, 15–20 deg, 2.5–4.5 deg, 4.5–9.5 deg, 9.5–15 deg, fovea · Report a mismatch
- [3] § Methods › Quantifying eccentricity- and location-dependent variations in sensitivity › Using calibrated Benson retinotopic template atlas ↔ src/eccen_adjust/__init__.py, lines 61 to 151 · score 0.82 · foveal confluence, integrating Horton, surface area, numerical, Hoyt, match · Report a mismatch
- [4] § Results › Effect of eye movements on V1 cortical sensitivity ↔ BackProjection_Slopes_Controls.m, lines 160 to 200 · score 0.80 · 0.5–2.5 deg, 15–20 deg, 2.5–4.5 deg, 4.5–9.5 deg, 9.5–15 deg, eye motion · Report a mismatch
- [5] § Results › Spatial frequency preference changes in V1 neuronal populations across eccentricities ↔ BackProjection_Slopes_Patient.m, lines 99 to 138 · score 0.80 · 0.5–2.5 deg, 15–20 deg, 2.5–4.5 deg, 4.5–9.5 deg, 9.5–15 deg, peripheral · Report a mismatch
- [6] § Results › Contrast sensitivity is not equal across the visual field quadrants in V1 neurons ↔ BackProjection_Slopes_Controls.m, lines 987 to 1094 · score 0.64 · Vertical Meridian Anisotropy, Left Right Horizontal, cortical contrast sensitivity, HVA, VMA, positions · Report a mismatch
- [7] § Methods › Quantifying eccentricity- and location-dependent variations in sensitivity › Using calibrated Benson retinotopic template atlas ↔ src/eccen_adjust/__init__.py, lines 39 to 59 · score 0.64 · surface area, maximum eccentricities, cortical magnification, Horton, Hoyt, model · Report a mismatch
- [8] § Methods › Processing of the contrast sensitivity task ↔ BackProjection_Slopes_Controls.m, lines 24 to 43 · score 0.53 · FreeSurfer, SPM12, compatible, MATLAB · Report a mismatch
- [9] § Methods › Processing of the contrast sensitivity task ↔ BackProjection_Slopes_Patient.m, lines 23 to 42 · score 0.53 · FreeSurfer, SPM12, compatible, MATLAB · Report a mismatch
- [10] § Methods › Quantifying eccentricity- and location-dependent variations in sensitivity › Using calibrated Benson retinotopic template atlas ↔ src/eccen_adjust/__init__.py, lines 39 to 59 · score 0.52 · magnification model, surface area, Horton, Hoyt, 90 deg, cortical · Report a mismatch
Paper
Loaded from Europe PMC by your browser; OSCR does not store it: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
MATLAB · 1,606 lines · 82 KB · MIT · 4 matches
- %% =========================================================================
- % SECTION 1 — INITIALISE ENVIRONMENT AND SET PATHS
- % -------------------------------------------------------------------------
- % This section:
- % • Resets the MATLAB workspace
- % • Detects whether MATLAB is running on Intel vs Apple Silicon macOS
- % • Loads the appropriate versions of SPM and FreeSurfer MATLAB toolboxes
- % • Prompts the user to select the project folder
- % =========================================================================
- clear all; close all; clc; commandwindow;
- set(0,'units','pixels');
- Rect = get(0,'ScreenSize'); % Monitor resolution in pixels
- %% Detect CPU architecture (Intel vs Apple Silicon)
- % Required because SPM12 and FreeSurfer ship different binaries for each.
- if ismac()
- [~,result] = system('uname -m');
- is_silicon_mac = strcmp(strtrim(result),'arm64'); % Apple Silicon machines report "arm64"
- else
- is_silicon_mac = false;
- end
- %% Add required toolboxes based on detected architecture
- % These paths MUST be customised by the user before running.
- % -------------------------------------------------------------------------
- % For Apple Silicon:
- % - Uses a M1/M2/M3‑compatible SPM build
- % - Uses FreeSurfer 7.4.1 MATLAB tools
- %
- % For Intel macOS or other systems:
- % - Uses standard SPM12 build
- % - Uses FreeSurfer 7.1.1 MATLAB tools
- % -------------------------------------------------------------------------
- if is_silicon_mac
- % Apple Silicon versions
- addpath(genpath('~/Dropbox (Personal)/PhD_UCL/Hugo_PhD/Toolboxes/spm12-M3chip'));
- addpath(genpath(['/Applications/freesurfer/7.4.1/matlab']))
- else
- % Intel macOS / other systems
- addpath(genpath('~/Dropbox (Personal)/PhD_UCL/Hugo_PhD/Toolboxes/spm12'));
- addpath(genpath(['/Applications/freesurfer/7.1.1/matlab']))
- end
- %% Ask user to choose the main project directory (interactive GUI)
- pinfo = struct();
- disp('SELECT MAIN PROJECT FOLDER')
- pinfo.Home = uigetdir(); % User selects folder containing beta-values (BetaValues)
- %% =========================================================================
- % SECTION 2 — USER INPUT & CONDITION SELECTION
- % -------------------------------------------------------------------------
- % This section:
- % • Lets the user choose retinotopic map source (Benson vs pRF)
- % • Asks whether to generate per‑subject figures
- % • Scans the BetaValues directory and categorises files by condition
- % • Provides GUI dialogs for selecting:
- % - Experimental condition (Low/High SF, Eye Motion, Visual Loss)
- % - Visit/session (if applicable)
- % - One or multiple subjects
- % • Produces a list of filenames (selectfn{indx}) for later analysis
- % =========================================================================
- Eccentricity = 20; % Maximum eccentricity to display/use (deg)
- ContrastToPlot = [7.5, 42.2, 60, 100]; % Tested contrast levels
- %% Ask which retinotopy dataset to use
- % Choice determines which files are matched later:
- % 'benson_Neuropy' → Benson retinotopic atlas outputs
- % 'pRF_NonEq' → Subject-specific pRF mapping outputs
- pRFFolderName = input('BENSON (0) OR pRF MAP (1)? ','s');
- switch pRFFolderName
- case '0'
- pRFFolderName = 'benson_Neuropy';
- case '1'
- pRFFolderName = 'pRF_NonEq';
- end
- %% Ask whether to generate subject-level diagnostic figures
- % 0 = skip (faster)
- % 1 = create all figures (slower but visually informative)
- PlotInd = str2num(input('Plot figures for each individual (0/1)? ','s'));
- %% =========================================================================
- % Collect list of available BetaValues files & classify them
- % =========================================================================
- directory_allfiles = dir(fullfile(pinfo.Home,'BetaValues'));
- % Each index corresponds to a specific experimental label embedded in filenames.
- % Using logical masks to filter subjects based on naming conventions.
- idx_sem = contains({directory_allfiles.name},'SEM') & contains({directory_allfiles.name},pRFFolderName); % Simulated Eye Motion
- idx_RingLoss = contains({directory_allfiles.name},'Ring') & contains({directory_allfiles.name},pRFFolderName); % Ring-shaped loss
- idx_URLoss = contains({directory_allfiles.name},'UpRight') & contains({directory_allfiles.name},pRFFolderName); % Upper-right VF loss
- idx_visit2 = contains({directory_allfiles.name},'RepMeas') & contains({directory_allfiles.name},pRFFolderName); % Repeat-measures session (Visit 2)
- idx_controls = ~contains({directory_allfiles.name},'P1') & contains({directory_allfiles.name},pRFFolderName); % Controls only
- % Visit 1 = everything not labelled RingLoss, URLoss, SEM, or visit2
- idx_visit1 = idx_controls - idx_RingLoss - idx_URLoss - idx_sem - idx_visit2;
- %% =========================================================================
- % GUI dialog: Select which experimental condition to analyse
- % =========================================================================
- answer = questdlg('Choose one condition','Condition Label','Low & High SF','SimEyeMotion','SimVisualLoss','Low & High SF');
- switch answer
- case 'Low & High SF' % CASE 1: Low vs High Spatial Frequency experiment
- % Ask whether this is Visit 1 or Visit 2
- answer2 = questdlg('1st or 2nd visit?','Session Label','Visit 1','Visit 2','Visit 1');
- switch answer2
- case 'Visit 1'
- selectfn = {directory_allfiles(find(idx_visit1)).name};
- [indx,~] = listdlg('PromptString',{'Select a file.','Multiple file can be selected at a time.',''}, ...
- 'SelectionMode','multiple','ListString',selectfn);
- case 'Visit 2'
- selectfn = {directory_allfiles(find(idx_visit2)).name};
- [indx,~] = listdlg('PromptString',{'Select a file.','Multiple file can be selected at a time.',''}, ...
- 'SelectionMode','multiple','ListString',selectfn);
- end
- case 'SimEyeMotion'
- % Eye‑movement simulation condition
- selectfn = {directory_allfiles(find(idx_sem)).name};
- [indx,~] = listdlg('PromptString',{'Select a file.','Multiple file can be selected at a time.',''}, ...
- 'SelectionMode','multiple','ListString',selectfn);
- case 'SimVisualLoss'
- % Choose simulated loss type
- answer3 = questdlg('3-8deg Ring or Upper-Right VF loss?','Loss Type','Ring','Quadrant','Ring');
- switch answer3
- case 'Ring'
- selectfn = {directory_allfiles(find(idx_RingLoss)).name};
- [indx,~] = listdlg('PromptString',{'Select a file.','Multiple file can be selected at a time.',''}, ...
- 'SelectionMode','multiple','ListString',selectfn);
- case 'Quadrant'
- selectfn = {directory_allfiles(find(idx_URLoss)).name};
- [indx,~] = listdlg('PromptString',{'Select a file.','Multiple file can be selected at a time.',''}, ...
- 'SelectionMode','multiple','ListString',selectfn);
- end
- end
- % Stop if user cancels
- if isempty(indx)
- return
- end
- %% =========================================================================
- % SECTION 3 — DEFINE ECCENTRICITY BINS & POLAR ANGLE WEDGE REGIONS
- % -------------------------------------------------------------------------
- % This section sets up all visual-field sampling coordinates used for
- % extracting vertex subsets from the cortical surface.
- %
- % Steps:
- % • Define eccentricity bins depending on experimental condition
- % • Compute bin midpoints, search radii, and tick labels
- % • Define polar-angle ROIs (meridians or obliques)
- % • Build a full set of (θ, r) seed coordinates
- % • Convert seed coordinates to Cartesian (x,y)
- % • Define search radius for each seed for vertex selection
- % =========================================================================
- %% -------------------------------------------------------------------------
- % ECCENTRICITY BINNING
- % Different experimental conditions use different eccentricity bin sizes.
- % -------------------------------------------------------------------------
- if strcmp(answer,'SimVisualLoss')
- % ----------------------------- RING LOSS ------------------------------
- if strcmp(answer3,'Ring')
- answer4 = questdlg('Ring Loss: 1deg binning or less detailed','Loss Binning','1deg','Less detailed','1deg');
- switch answer4
- case '1deg'
- % High-resolution eccentricity bins (1° width)1
- ECCBoundaries = round([0.5:1:20],1); % 0.5º to 20º
- BinModeLabel = 'PerECC';
- case 'Less detailed'
- % Low-resolution bins: {0–3°, 3–8°, 8–15°, 15–20°}
- BinModeLabel = '4Bins';
- ECCBoundaries = [0 3 8 15 20];
- end
- else
- % -------------------------- QUADRANT LOSS ------------------------------
- % Quadrant VF loss always uses 1° resolution
- ECCBoundaries = round([0.5:1:20],1);
- BinModeLabel = 'PerECC';
- end
- else
- % -------------------------------------------------------------------------
- % DEFAULT CASE — Low/High SF or SimEyeMotion
- % Use clinically meaningful eccentricity bands
- % -------------------------------------------------------------------------
- % These boundary definitions correspond to known anatomical regions:
- Fovea = [0.5,2.5];
- ParaFovea = [2.5,4.5];
- PeriFovea = [4.5,9.5];
- NearPeriphery1 = [9.5,15];
- NearPeriphery2 = [15,20];
- BinModeLabel = 'ClinicallyDefined';
- ECCBoundaries = unique([Fovea,ParaFovea,PeriFovea,NearPeriphery1,NearPeriphery2]);
- end
- %% -------------------------------------------------------------------------
- % Compute eccentricity bin helper variables
- % -------------------------------------------------------------------------
- BinECC = ECCBoundaries; % Bin edges
- StepSizeECC = diff(ECCBoundaries)/2; % Half-bin width
- RadiusForSearch = diff(ECCBoundaries)/2; % Search radius = half bin
- Radius_seedroi = ECCBoundaries(1:end-1)+StepSizeECC; % Bin midpoints
- BinTick = Radius_seedroi; % Used for axis ticks later
- %% =========================================================================
- % POLAR ANGLE DEFINITIONS
- % These define which meridians (or oblique axes) to sample from.
- % -------------------------------------------------------------------------
- % MATLAB polar-angle convention for pRF outputs:
- % 0 rad = Right horizontal meridian (RHM)
- % π/2 rad = Upper vertical meridian (UVM)
- % -π/2 rad = Lower vertical meridian (LVM)
- % π rad = Left meridian (LHM)
- % =========================================================================
- % Cardinal meridians
- UVM = [pi/2]; % Upper Vertical Meridian (Ventral V1 = Positive Polar Values)
- LVM = [-pi/2]; % Lower Vertical Meridian (Dorsal V1 = Negative Polar Values)
- LHM = [-pi]; % Left Horizontal Meridian (Right V1 = Values between -90:-180/180:90)
- RHM = [0]; % Right Horizontal Meridian (Left V1= Values between -90:0:90)
- % Oblique diagonals (used for quadrant loss)
- Diagonals = [-pi/4; pi/4; 3/4*pi; -3/4*pi]; % (Lower‑Right, Upper‑Right, Upper‑Left, Lower‑Left)
- %% Determine which polar-angle set to use
- if strcmp(answer,'SimVisualLoss')
- switch answer3
- case 'Ring'
- % -------------------------------------------------- RING LOSS -----
- PolarCoords = [LHM;UVM;RHM;LVM]; % Only cardinal meridians
- % Compute wedge half-width (in degrees)
- AngleForSearch = unique(rad2deg(diff(sort(PolarCoords(1:4)))))/2;
- % Replicate polar angles for each eccentricity bin
- th = repmat(PolarCoords(1:4),length(Radius_seedroi),1);
- % Colour coding for meridian patches
- colour = repmat([0 0.7 0.3; 0.75 0.75 0; 0 0 1; 1 0 0],length(Radius_seedroi),1);
- case 'Quadrant'
- % ---------------------------------------------- QUADRANT LOSS -----
- PolarCoords = [Diagonals]; % Use oblique diagonals
- AngleForSearch = unique(rad2deg(diff(sort(PolarCoords(1:4)))))/2;
- th = repmat(PolarCoords(1:4),length(Radius_seedroi),1); % -pi/4; pi/4; 3/4*pi; -3/4*pi];
- colour = repmat([0.75 0.75 1; 1 0.75 0; 0.75 0.75 0; 0 1 1],length(Radius_seedroi),1);
- end
- else
- % -------------------------------------------------------------------------
- % DEFAULT: Use cardinal meridians for SF or eye-movement conditions
- % -------------------------------------------------------------------------
- PolarCoords = [LHM;UVM;RHM;LVM];
- AngleForSearch = unique(rad2deg(diff(sort(PolarCoords(1:4)))))/2;
- th = repmat(PolarCoords(1:4),length(Radius_seedroi),1);
- colour = repmat([0 0.7 0.3; 0.75 0.75 0; 0 0 1; 1 0 0],length(Radius_seedroi),1);
- end
- % Should eccentricity search be constrained by wedge angle (i.e., take into account cortical magnification)?
- % If MScaling == 1 -> search region is a wedge (θ ± Δθ AND r ± Δr)
- % If MScaling == 0 -> search region is a circle in (x,y) / (eccentricity) space only.
- MScaling = 1;
- %% =========================================================================
- % Build seed coordinates in polar space (θ, r) and convert to (x,y)
- % =========================================================================
- % Create radial coordinate repeated for each polar angle
- r = [];
- for i = 1:length(Radius_seedroi)
- r = [r; repmat(Radius_seedroi(i),length(th)/length(Radius_seedroi),1)];
- end
- % Convert polar -> Cartesian coordinates (for visual-space mapping)
- [x,y] = pol2cart(th,r);
- x_ycoords0 = []; x_ycoords0 = [x,y];
- % Clean tiny negative zeros produced by floating point precision
- x_ycoords0(x_ycoords0 > -1e-6 & x_ycoords0 < 1e-6) = 0; % some values are -0, correct them
- %% =========================================================================
- % Single-ROI (V1‑wide) coordinate definition
- % Instead of multiple eccentricity bins, use 4 fixed quadrants.
- % =========================================================================
- x_ycoords0_1ROI = [-max(BinECC)/2 0; 0 max(BinECC)/2; max(BinECC)/2 0; 0 -max(BinECC)/2];
- r_search_1ROI = repmat(max(BinECC)/2-min(BinECC),length(x_ycoords0_1ROI),1);
- % Convert to polar again for convenience
- [th_1ROI,r_1ROI] = cart2pol(x_ycoords0_1ROI(:,1),x_ycoords0_1ROI(:,2));
- %% =========================================================================
- % Define search radius for each seed point
- % =========================================================================
- r_search = [];
- for i = 1:length(RadiusForSearch)
- r_search = [r_search; repmat(RadiusForSearch(i),size(x_ycoords0,1)/length(RadiusForSearch),1)];
- end
- %% -------------------------------------------------------------------------
- % Angular wedge width for M-scaling (converted to radians)
- % -------------------------------------------------------------------------
- if MScaling
- a_search = AngleForSearch;
- a_search_rad = deg2rad(a_search);
- end
- %% =========================================================================
- % SECTION 4 — PLOTTING OPTIONS, COLOUR MAPS, MODEL PARAMETERS, PREALLOCATION
- % -------------------------------------------------------------------------
- % This section:
- % • Sets default plotting styles (line types, marker types, labels)
- % • Lets the user choose a heatmap colour scale (incl. colour‑blind safe)
- % • Selects the exponent strategy for the cortical contrast sensitivity model R = a + b*C^p
- % • Preallocates (initialises) containers used later for:
- % - Eccentricity‑binned data & CI
- % - Polar‑angle ROI data & CI
- % - Fitted parameters and counts
- % - pRF size summaries
- % =========================================================================
- %% -------------------------------------------------------------------------
- % General plotting preferences
- % -------------------------------------------------------------------------
- idxFigs = 0; % Counter for figure handles
- YAxisLim = [-5 20]; % Default plotting range
- lineStyle = {'-','-','--','--'}; % One per SF condition (fallback)
- MarkerType = {'o','o','s','s'}; % Markers per SF condition
- VariableLabel = {'Beta Values'}; % Y-axis label for β estimates
- %% -------------------------------------------------------------------------
- % Heatmap colour scale selection (with an accessible palette option)
- % Choices:
- % 0: MATLAB 'hot' colormap
- % 1: Custom black → yellow linear gradient
- % 2: Colour-blind friendly palette (Paul Tol-like sequence, interpolated)
- % -------------------------------------------------------------------------
- colorchoice = str2num(input('Heatmap color scale: hot scale (0) or black-to-yellow (1) or color-blindness (2)? ','s'));
- if colorchoice == 1
- % ----- Black -> Yellow continuous gradient (256 steps)
- numColors = 256;
- colorMap = zeros(numColors, 3);
- blackRGB = [0, 0, 0];
- yellowRGB = [1, 1, 0];
- % Interpolate between black and yellow
- for i = 1:numColors
- colorMap(i, :) = blackRGB + (i - 1) / (numColors - 1) * (yellowRGB - blackRGB);
- end
- % NOTE: Several plotting sections set the axes background using 'colorBck'.
- % Define a neutral light gray here to keep behaviour consistent.
- colorBck = [0.75 0.75 0.75];
- elseif colorchoice == 2
- % ----- Colour‑blind safe palette (interpolated to 256 colours)
- % RGB anchors (approx. from a colour‑blind friendly set):
- colors = [
- 100, 143, 255; % Light blue
- 120, 94, 240; % Purple
- 220, 38, 127; % Magenta
- 254, 97, 0; % Orange
- 255, 176, 0 % Yellow
- ];
- colorBck = [0.75 0.75 0.75];
- % Interpolate to produce a smooth 256‑colour map
- positions = linspace(0, 1, size(colors, 1));
- N = 256;
- colorMap = interp1(positions, colors/255, linspace(0, 1, N));
- elseif colorchoice == 0
- % ----- MATLAB 'hot' built‑in colormap
- colorMap = 'hot';
- colorBck = [0.75 0.75 0.75];
- end
- %% -------------------------------------------------------------------------
- % Model fitting option for cortical contrast sensitivity exponent p in: R = a + b * C^p
- % Choices:
- % 1: Fit exponent p from the data (nonlinear fit)
- % 2: Fix p = 0.5 (i.e., R = a + b * sqrt(C))
- % -------------------------------------------------------------------------
- FitExponent = str2num(input('Fit exponent (1) or sqrt of C = 0.5 (2): ','s'));
- %% -------------------------------------------------------------------------
- % Preallocate / initialise containers for analysis results
- % (They will be populated incrementally in the subject loop)
- % -------------------------------------------------------------------------
- % -------------------- ECCENTRICITY‑BINNED DATA ----------------------------
- % Data_ECC(sub, bin, SF, contrast) : Mean β per bin/condition
- % CIBetaVals_ECC(sub, SF, bin, contrast, 2) : 95% CI on β (lower/upper)
- % FitParams_ECC(sub, SF, bin, [a b p]) : Mean fitted cortical contrast sensitivity params
- % FitParams_ECC_nvals(sub, SF, bin) : Count of contributing vertices
- % ECC_pRFSize(sub, SF, bin) : Mean pRF size in bin
- Data_ECC = [];
- CIBetaVals_ECC = [];
- FitParams_ECC = [];
- FitParams_ECC_nvals = [];
- ECC_pRFSize = [];
- % -------------------- POLAR‑ANGLE ROI (BIN × POLAR) -----------------------
- % Data_VFrois(sub, SF, contrast, bin, polar) : Mean β
- % FitParams_VFrois(sub, SF, bin, polar, [a b p]) : Fitted cortical contrast sensitivity params
- % FitParams_VFrois_nvals(sub, SF, bin, polar) : Count of vertices
- % VFrois_pRFSize(sub, SF, bin, polar) : Mean pRF size
- Data_VFrois = [];
- FitParams_VFrois = [];
- FitParams_VFrois_nvals = [];
- VFrois_pRFSize = [];
- % -------------------- SINGLE‑ROI (V1‑WIDE) POLAR INDICES ------------------
- % Data_VFrois_1ROI(sub, SF, contrast, polar) : Mean β, pooled V1
- % FitParams_VFrois_1ROI(sub, SF, polar, [a b p]) : Fitted cortical contrast sensitivity params
- % FitParams_VFrois_1ROI_nvals(sub, SF, polar) : Count of vertices
- % VFrois_1ROI_pRFSize(sub, SF, polar) : Mean pRF size
- Data_VFrois_1ROI = [];
- FitParams_VFrois_1ROI = [];
- FitParams_VFrois_1ROI_nvals = [];
- VFrois_1ROI_pRFSize = [];
- %% =========================================================================
- % SECTION 5 — SUBJECT LOOP: LOAD CSV, ORGANISE BETA DATA, FIT PER‑VERTEX
- % CORTICAL CONTRAST SENSITIVTY
- % -------------------------------------------------------------------------
- % For each selected subject file the script:
- % 1. Loads the CSV (β-values + pRF parameters)
- % 2. Extracts subject ID, spatial frequencies, contrasts
- % 3. Reshapes β-values into [SF × Contrast × Vertex]
- % 4. Extracts pRF eccentricity, polar angle, sigma, and R²
- % 5. For every vertex:
- % • Fit cortical contrast sensitivity model R = a + b*C^p
- % • Store intercept, slope, exponent, residuals
- % =========================================================================
- close all; % Reset plots for each subject
- for subs = 1:length(selectfn(indx))
- %% ---------------------------------------------------------------------
- % Display which subject is being processed
- % ----------------------------------------------------------------------
- tmp = []; tmp = split(selectfn{indx(subs)},'_');
- disp(['Subject: ' tmp{1}])
- %% ---------------------------------------------------------------------
- % Load subject CSV table
- % Table structure includes columns such as:
- % SubID, SF, ContrastLevel, Beta,
- % pRFecc, pRFpolar, pRFsize, pRFrsquare
- % ----------------------------------------------------------------------
- T = [];
- T = readtable(char(fullfile(pinfo.Home,'BetaValues',selectfn{indx(subs)})));
- % Basic identifiers extracted from table
- SubID = char(unique(T.SubID));
- SFLabels = unique(T.SF); % Spatial frequencies tested
- ContrastLabels = unique(T.ContrastLevel); % Contrast levels tested
- Data = T.Beta; % β-values across all conditions
- %% ---------------------------------------------------------------------
- % Determine number of vertices for a single condition
- % The table repeats pRF values across contrasts/SFs; we extract one block.
- % ----------------------------------------------------------------------
- nVertsPerCond = length(T.SubID([T.ContrastLevel == ContrastLabels(1) & T.SF == SFLabels(1)]));
- %% ---------------------------------------------------------------------
- % Extract pRF parameters for the first condition (unique across table)
- % ----------------------------------------------------------------------
- pRFx_y_ECC = T.pRFecc(1:nVertsPerCond); % Eccentricity (º)
- pRFx_y_Polar = T.pRFpolar(1:nVertsPerCond); % Polar angle (rad)
- pRFSigma_ECC = T.pRFsize(1:nVertsPerCond); % pRF sigma (º)
- pRFR2_ECC = T.pRFrsquare(1:nVertsPerCond); % Model R²
- %% ---------------------------------------------------------------------
- % Reshape β-values into [SF × Contrast × Vertex]
- % Then permute to keep dimensions consistent throughout analysis.
- % ----------------------------------------------------------------------
- BetaVals_ECC = reshape(Data,nVertsPerCond,length(ContrastLabels),length(SFLabels));
- BetaVals_ECC = permute(BetaVals_ECC,[3 2 1]); % β-values: BetaVals_ECC(SF, Contrast, Vertex)
- %% =========================================================================
- % CLASSIFY VERTICES INTO VISUAL-FIELD SEED REGIONS (ECC × POLAR)
- % -------------------------------------------------------------------------
- % For each seed coordinate (θ, r) defined in Section 3:
- % • Identify vertices within eccentricity radius
- % • Optional: restrict by polar-angle wedge (M-scaling)
- % • Store included vertices in vtxidx(coord).inclvtx
- % =========================================================================
- vtxidx = struct(); % Container for all seed-based vertex lists
- for coord = 1:size(x_ycoords0,1)
- % Convert seed’s Cartesian location to polar (th_seed, rseed)
- [a,b] = cart2pol(x_ycoords0(coord,1),x_ycoords0(coord,2));
- vtxidx(coord).seed = x_ycoords0(coord,:);
- vtxidx(coord).seed_PolarRad = a;
- vtxidx(coord).seed_EccDeg = b;
- %% -------------------------------------------------------------
- % Select vertices based on eccentricity + polar constraints
- % - If MScaling=1: apply angular wedge limits
- % - Else: use simple Euclidean distance in (ECC, polar)
- % -------------------------------------------------------------
- if MScaling
- % ---- LEFT/RIGHT HORIZONTAL MERIDIANS ----
- if x_ycoords0(coord,1) < 0 && x_ycoords0(coord,2) == 0 || x_ycoords0(coord,1) > 0 && x_ycoords0(coord,2) == 0
- k = {}; k = {find(pRFx_y_ECC >= b-r_search(coord) & pRFx_y_ECC <= b+r_search(coord) & ...
- abs(pRFx_y_Polar) >= abs(a)-a_search_rad & abs(pRFx_y_Polar) <= abs(a) + a_search_rad)};
- % ---- UPPER VERTICAL MERIDIAN ----
- elseif x_ycoords0(coord,1) == 0 && x_ycoords0(coord,2) > 0
- k = {}; k = {find(pRFx_y_ECC >= b-r_search(coord) & pRFx_y_ECC <= b+r_search(coord) & ...
- pRFx_y_Polar >= a-a_search_rad & pRFx_y_Polar <= a + a_search_rad)};
- % ---- LOWER VERTICAL MERIDIAN ----
- elseif x_ycoords0(coord,1) == 0 && x_ycoords0(coord,2) < 0
- k = {}; k = {find(pRFx_y_ECC >= b-r_search(coord) & pRFx_y_ECC <= b+r_search(coord) & ...
- pRFx_y_Polar <= a+a_search_rad & pRFx_y_Polar >= a - a_search_rad)};
- % ---- LOWER OBLIQUES (LR quadrant) ----
- elseif x_ycoords0(coord,1) > 0 && x_ycoords0(coord,2) < 0 || x_ycoords0(coord,1) < 0 && x_ycoords0(coord,2) < 0
- k = {}; k = {find(pRFx_y_ECC >= b-r_search(coord) & pRFx_y_ECC <= b+r_search(coord) & ...
- pRFx_y_Polar <= a + a_search_rad & pRFx_y_Polar >= a - a_search_rad)};
- % ---- UPPER OBLIQUES (UR quadrant) ----
- elseif x_ycoords0(coord,1) > 0 && x_ycoords0(coord,2) > 0 ||x_ycoords0(coord,1) < 0 && x_ycoords0(coord,2) > 0
- k = {}; k = {find(pRFx_y_ECC >= b-r_search(coord) & pRFx_y_ECC <= b+r_search(coord) & ...
- pRFx_y_Polar >= a - a_search_rad & pRFx_y_Polar <= a + a_search_rad)};
- end
- % Distance from seed in eccentricity space
- D = {abs(pRFx_y_ECC(cell2mat(k))-b)};
- else
- % No angular wedge — circular window in ECC space
- [k,D] = rangesearch(pRFx_y,x_ycoords0(coord,:),r_search(coord),'SortIndices',false);
- end
- % Final vertex indices for this ROI
- D = cell2mat(D);
- tmp = []; tmp = cell2mat(k);
- vtxidx(coord).inclvtx = tmp;
- % --- Diagnostic printout ---
- fprintf('Coordinates %s [%s-%sº: %s vertices]: Minimum and maximum Euclidian distance from seed points = [%s - %s]º (search radius set to %sº) \n',...
- num2str(coord),num2str(min(pRFx_y_ECC(vtxidx(coord).inclvtx))),num2str(max(pRFx_y_ECC(vtxidx(coord).inclvtx))),num2str(length(vtxidx(coord).inclvtx)),...
- num2str(min(D)),num2str(max(D)),num2str(r_search(coord)))
- % figure;
- % ax = polarscatter(pRFx_y_Polar,pRFx_y_ECC,SizePoint,[0.9 0.9 0.9],'filled');
- % hold on; polarscatter(pRFx_y_Polar(vtxidx(coord).inclvtx),pRFx_y_ECC(vtxidx(coord).inclvtx),50,'filled')
- end
- %% =========================================================================
- % PER‑VERTEX CORTICAL CONTRAST SENSITIVITY FITTING: R = a + b * C^p
- % -------------------------------------------------------------------------
- % Fit the contrast-response function for each vertex and each SF.
- % =========================================================================
- % Select contrast indices corresponding to ContrastToPlot
- idxC_ToPlot = find(ismember(ContrastLabels,ContrastToPlot));
- C = ContrastLabels(idxC_ToPlot)/100; % normalised contrast (0–1)
- fprintf('\n');
- func = @(x, C) x(1) + x(2)*C.^x(3); % Vary exponent (if FitExponent = 2; set to 0.5)
- % func = @(x, C) x(1) + x(2)*C.^(x(3)+x(4))./(C.^x(4)+x(5)^x(4)); % Full Naka-Rushton equation
- % Initialise variables for each subject (Reason: differences in vertex count from pRF structure)
- x_slope_intercept = [];
- x_resnorm = [];
- x_residuals = [];
- x_exponent = [];
- % Compute slope values for each CSF condition
- for CSFCondition = 1:size(BetaVals_ECC,1)
- disp(['... ' num2str(SFLabels(CSFCondition)) 'cpd: Fitting Linear Equation (R = a+b*C) – Vertex level'])
- % Preallocate SF-specific outputs
- a = nan(1,size(BetaVals_ECC,3)); % intercept
- b = nan(1,size(BetaVals_ECC,3)); % slope
- c = nan(1,size(BetaVals_ECC,3)); % residual norm
- p = nan(1,size(BetaVals_ECC,3)); % exponent
- count = 0;
- % Loop over vertices
- for ii = 1:size(BetaVals_ECC,3)
- if ii == 1 || mod(ii,500) == 0
- disp(['Vertex #' num2str(ii)])
- end
- % Extract β-values across contrast levels for this vertex & SF
- R = [];
- R = squeeze(BetaVals_ECC(CSFCondition,:,ii))';
- if sum(isnan(R)) < 4 % requires minimal valid data
- % Call custom fitting function for fitting the square-root
- % function to the data
- [x,resnorm] = dolinearfit_SqCdata(R,C,func,FitExponent);
- a(ii) = x(1); % intercept set to 0
- b(ii) = x(2); % Slope
- p(ii) = x(3); % Exponent (if FitExponent = 1, it is estimated, else, it is set to 0.5)
- c(ii) = resnorm;
- % Save per-vertex fits
- x_slope_intercept(CSFCondition,ii,:) = [a(ii) b(ii)];
- x_resnorm(CSFCondition,ii,:) = c(ii);
- x_exponent(CSFCondition,ii,:) = p(ii);
- else
- % Save per-vertex fits
- x_slope_intercept(CSFCondition,ii,:) = [a(ii) b(ii)];
- x_resnorm(CSFCondition,ii,:) = c(ii);
- x_exponent(CSFCondition,ii,:) = p(ii);
- end
- end
- end
- %% =========================================================================
- % SECTION 6 — APPLY CORTICAL CONTRAST SENSITIVITY FITS TO ECCENTRICITY BINS (+ OPTIONAL PLOTTING)
- % -------------------------------------------------------------------------
- % This section aggregates vertex-level data within eccentricity bins and:
- % • Computes mean β-values per contrast and bin
- % • Bootstraps 95% CIs for β-values (per bin & contrast)
- % • Averages fitted cortical contrast sensitivity parameters (a, b, p) across vertices per bin
- % • Optionally plots per-subject cortical contrast sensitivity per bin
- % • Optionally back-projects bin-level slope values onto visual field
- %
- % Note:
- % - The x-axis for plots uses sqrt(C) for readability (percent ticks),
- % while the model itself can use either p=0.5 (fixed) or p fitted.
- % =========================================================================
- %% Eccentricity: Apply Linear Equation (R = a + b*C^p) within bins
- if strcmp(answer,'Low & High SF') || strcmp(answer,'SimEyeMotion') || strcmp(answer3,'Ring')
- % Colour per SF condition (2 colours repeated if >2 SFs)
- colour_ecc = repmat([120 94 240; 254 97 0]/255,round(size(BetaVals_ECC,1)/2),1);
- % Line handle container for legends (reused across subplots)
- if exist('s','var'); clear s; end
- % Loop over spatial frequency conditions
- for CSFCondition = 1:size(BetaVals_ECC,1)
- disp(['... ' num2str(SFLabels(CSFCondition)) 'cpd: Linear Equation Fit (R = a+b*C.^p) – Eccentricity bin level'])
- %% Prepare figure canvas if requested
- if PlotInd
- ncols = 5; % number of subplots per row
- [q,r] = quorem(sym(length(BinTick)),sym(ncols));
- nlines = double(q);
- if r > 0
- nlines = nlines+1;
- end
- if CSFCondition == 1
- idxFigs = idxFigs+1;
- if nlines <= 4
- figure(idxFigs); set(gcf,'Color','w','Position',[0 0 Rect(3) Rect(4)*nlines/4]);
- else
- figure(idxFigs); set(gcf,'Color','w','Position',Rect);
- end
- end
- end
- %% Iterate through eccentricity bins
- for band = 1:length(BinECC)-1
- % --------------- Mean β per contrast (within bin) ---------------
- R = [];
- R = squeeze(nanmean(BetaVals_ECC(CSFCondition,:,pRFx_y_ECC > BinECC(band) & pRFx_y_ECC <= BinECC(band+1)),3))'; % [1 × nContrast]
- Data_ECC(subs,band,CSFCondition,:) = R;
- % --------------- 95% CI for β via bootstrap ---------------------
- for c = 1:size(BetaVals_ECC,2)
- y = []; y = squeeze(BetaVals_ECC(CSFCondition,c,pRFx_y_ECC > BinECC(band) & pRFx_y_ECC <= BinECC(band+1))); % β over vertices
- if sum(isnan(y)) < length(y)-4
- CIBetaVals_ECC(subs,CSFCondition,band,c,:) = bootci(1000,@nanmean,y); % [2×1]
- else
- CIBetaVals_ECC(subs,CSFCondition,band,c,:) = [NaN;NaN];
- end
- end
- % --------------- Mean fitted params (a, b, p) per bin -----------
- % x_slope_intercept = [a b] per vertex; x_exponent = p
- FitParams_ECC(subs,CSFCondition,band,:) = [squeeze(nanmean(x_slope_intercept(CSFCondition,pRFx_y_ECC > BinECC(band) & pRFx_y_ECC <= BinECC(band+1),:),2));squeeze(nanmean(x_exponent(CSFCondition,pRFx_y_ECC > BinECC(band) & pRFx_y_ECC <= BinECC(band+1))))];
- % Number of contributing vertices (for diagnostics/reporting)
- FitParams_ECC_nvals(subs,CSFCondition,band,:) = numel(x_slope_intercept(CSFCondition,pRFx_y_ECC > BinECC(band) & pRFx_y_ECC <= BinECC(band+1),2));
- %% -------------------------- Plot per bin -----------------------
- if PlotInd
- subplot(nlines,ncols,band); hold on
- % Error bars from bootstrapped CI
- upper = abs(R-squeeze(CIBetaVals_ECC(subs,CSFCondition,band,:,2)));
- lower = abs(R-squeeze(CIBetaVals_ECC(subs,CSFCondition,band,:,1)));
- errorbar(sqrt(C),R,lower,upper,'Marker',MarkerType{CSFCondition},'LineStyle','none','LineWidth',1,'Color',colour_ecc(CSFCondition,:));
- % Overlay function fit using the mean parameters for the bin
- C_fit = linspace(min(C), max(C), 100);
- R_fit = func(FitParams_ECC(subs,CSFCondition,band,:), C_fit);
- s(CSFCondition) = plot(sqrt(C_fit), R_fit,lineStyle{CSFCondition},'Color',colour_ecc(CSFCondition,:),'LineWidth',2);
- % Axis formatting
- if CSFCondition == size(BetaVals_ECC,1)
- legend([s],[num2str(SFLabels),repmat('cpd',length(SFLabels),1)],'Location','NorthWest','box','off')
- end
- ylim([-5 15])
- xlabel('Contrast (%)');% xlabel('Contrast^(^0^.^5^)');
- xlim([0 max(C)]); % From 0-1 (normalised values)
- xticks(sqrt(C)); % display sqrt(C) ticks for readability
- xticklabels(num2str(C*100)); % label in %
- ylabel('Response (\beta-value)');
- title(['Ecc:' num2str(BinECC(band)) '–' num2str(BinECC(band+1)) 'º'])
- set(gca,'LineWidth',2,'FontSize',14)
- end
- end
- end
- %% =========================================================================
- % Eccentricity — Back-project mean slope (b) to visual space (optional)
- % -------------------------------------------------------------------------
- % For each SF condition:
- % • Take bin-level slope estimates (b)
- % • Paint them onto wedge-shaped patches in (θ, r) space
- % • Produce a heatmap (per subject) showing spatial distribution of b
- % =========================================================================
- if PlotInd
- idxFigs = idxFigs+1;
- figure(idxFigs);
- if size(BetaVals_ECC,1) > 2
- set(gcf,'Color','w','Position', [0 0 Rect(3) Rect(4)/2])
- FontSize = 22;
- else
- set(gcf,'Color','w','Position', [0 0 Rect(3)/2 Rect(4)/2])
- FontSize = 20;
- end
- % Convert original seeds back to polar (used to draw wedge patches)
- [th,r] = cart2pol(x_ycoords0(:,1),x_ycoords0(:,2));
- CLim = [0 12]; % colour scale limits for slope
- for CSFCondition = 1:size(BetaVals_ECC,1)
- disp(['... ' num2str(SFLabels(CSFCondition)) 'cpd: Linear Equation Fit (R = a+b*C.^p) – Back Projection Eccentricity bin level'])
- subplot(1,size(BetaVals_ECC,1),CSFCondition)
- % Vector of slope values (b) per bin for this subject & SF
- Data = []; Data = squeeze(FitParams_ECC(subs,CSFCondition,:,2)); % (a=1, b=2, p=3)
- % Draw a patch per seed wedge; reuse the same Data value for
- % each set of 4 polar angles (LHM, UVM, RHM, LVM) across bins.
- idx = 0;
- for coord = 1:length(x_ycoords0) % LHM, RHM, UVM, LVM
- % Define wedge bounds around seed (th ± a_search_rad, r ± r_search)
- th0 = th(coord)-a_search_rad;
- rh0 = r(coord)-r_search(coord);
- th1 = th(coord)+a_search_rad;
- rh1 = r(coord)+r_search(coord);
- a1 = linspace(th0,th0);
- r1 = linspace(rh0,rh1);
- a2 = linspace(th0,th1);
- r2 = linspace(rh1,rh1);
- a3 = linspace(th1,th1);
- r3 = linspace(rh1,rh0);
- a4 = linspace(th1,th0);
- r4 = linspace(rh0,rh0);
- [X,Y]=pol2cart([a1,a2,a3,a4],[r1,r2,r3,r4]);
- % move to next eccentricity bin every 4 patches
- if mod(coord,length(PolarCoords)) == 1 % Change Data value after each 4 coordinates (Left, Right, Up, Down)
- idx = idx+1;
- end
- s_ecc_avg(CSFCondition,coord) = patch(X,Y,Data(idx)); % Note: patch function takes text or matrix color def
- s_ecc_avg(CSFCondition,coord).LineStyle = '-';
- s_ecc_avg(CSFCondition,coord).EdgeColor = 'k';
- end
- % Axis & colour formatting
- axis([-Eccentricity Eccentricity -Eccentricity Eccentricity])
- axis square;
- yticks([-Eccentricity:Eccentricity/2:Eccentricity])
- xticks([-Eccentricity:Eccentricity/2:Eccentricity])
- ax = gca;
- ax.Color = colorBck;
- title([num2str(SFLabels(CSFCondition)) 'cpd'])
- colormap(colorMap);
- cb = colorbar; % show color bar
- cb.Ticks = round(linspace(0,max(CLim),4),2);
- ylabel(cb,['Slope'],'Rotation',270,'Position',[4 max(CLim)/2 0]);
- set(gca,'FontSize',FontSize,'LineWidth',2,'CLim',CLim);
- end
- end
- end
- %% =========================================================================
- % SECTION 7 — POLAR-ANGLE ROIs: MERIDIAN AGGREGATION, ANISOTROPY, BACK-PROJ
- % -------------------------------------------------------------------------
- % This section aggregates β-values and cortical contrast sensitivity fits along polar-angle ROIs
- % (meridians or quadrants), computes bootstrapped CIs, and produces:
- % • Per-ROI contrast-response curves (with fit overlays)
- % • HVA (Horizontal vs Vertical), VMA (UVM vs LVM), LRHM (Left vs Right)
- % • V1-wide (single-ROI) pooled measures across eccentricity
- % • Back-projected maps of slope (b) onto visual field (polar bins)
- %
- % Notes on gating:
- % - We run this section for:
- % 'Low & High SF' OR any SimVisualLoss with answer3 defined.
- % - For plotting the quadrant case, the code explicitly checks
- % 'Low & High SF' OR 'Quadrant' (no meridian anisotropy for ring).
- % =========================================================================
- if strcmp(answer,'Low & High SF') || exist('answer3')
- if strcmp(answer,'Low & High SF') || strcmp(answer3,'Quadrant')
- % Only compute meridian ROIs for:
- % • Low & High SF
- % • SimVisualLoss with Quadrant loss
- CIData_VFrois = [];
- CIData_VFrois_1ROI = [];
- for CSFCondition = 1:size(BetaVals_ECC,1)
- %% -------------------------------------------------------------
- % 7.1 Aggregate across polar-angle ROIs (per eccentricity bin)
- % -------------------------------------------------------------
- % Build Data_VFrois and FitParams_VFrois by iterating through
- % (bin, polar) seed coordinates in the same order as vtxidx.
- % The loop over idxvtx walks through each seed exactly once.
- % -------------------------------------------------------------
- idxvtx = 0;
- for bin = 1:length(Radius_seedroi) % eccentricity bins
- for coord = 1:length(x_ycoords0)/length(Radius_seedroi) % polar coords
- idxvtx = idxvtx + 1;
- % -------- Mean β for each contrast within this ROI ----
- for c = 1:size(BetaVals_ECC,2)
- % disp(['Polar = ' num2str(vtxidx(idxvtx).seed_PolarRad) ', ECC = ' num2str(vtxidx(idxvtx).seed_EccDeg) ', Contrast = ' num2str(c)])
- % Vertex list for this seed (bin, polar)
- incl = []; incl = vtxidx(idxvtx).inclvtx;
- Data_VFrois(subs,CSFCondition,c,bin,coord) = nanmean(BetaVals_ECC(CSFCondition,c,incl),3);
- % 95% bootstrap CI for β (if enough valid vertices)
- if numel(incl) > 2
- validCount = []; validCount = numel(incl) - sum(isnan(squeeze(BetaVals_ECC(CSFCondition,c,incl))));
- if validCount>2
- CIData_VFrois(subs,CSFCondition,c,bin,coord,:) = bootci(1000,@nanmean,BetaVals_ECC(CSFCondition,c,incl));
- else
- CIData_VFrois(subs,CSFCondition,c,bin,coord,:) = [NaN;NaN];
- end
- else
- CIData_VFrois(subs,CSFCondition,c,bin,coord,:) = [NaN;NaN];
- end
- end
- % -------- Mean fitted cortical contrast sensitivity params within this ROI -------
- incl = []; incl = vtxidx(idxvtx).inclvtx;
- if isempty(x_slope_intercept(CSFCondition,incl,:))
- FitParams_VFrois(subs,CSFCondition,bin,coord,:) = [NaN; NaN; NaN];
- else
- FitParams_VFrois_nvals(subs,CSFCondition,bin,coord,:) = numel(x_slope_intercept(CSFCondition,incl,2));
- FitParams_VFrois(subs,CSFCondition,bin,coord,:) = [squeeze(nanmean(x_slope_intercept(CSFCondition,incl,:),2));squeeze(nanmean(x_exponent(CSFCondition,incl)))];
- FitParams_VFrois_nvals(subs,CSFCondition,bin,coord,:) = numel(x_slope_intercept(CSFConditionincl,2));
- end
- % -------- Mean pRF size within this ROI ----------------
- VFrois_pRFSize(subs,CSFCondition,bin,coord) = nanmean(pRFSigma_ECC(vtxidx(idxvtx).inclvtx));
- end
- end
- % -------------------------- Shapes reminder --------------------
- % Data_VFrois : [sub, SF, contrast, ECC bin, Polar]
- % CIData_VFrois : [sub, SF, contrast, ECC bin, Polar, CI(2)]
- % FitParams_VFrois : [sub, SF, ECC bin, Polar, [a b p]]
- % FitParams_VFrois_nvals : [sub, SF, ECC bin, Polar]
- % VFrois_pRFSize : [sub, SF, ECC bin, Polar]
- %% -------------------------------------------------------------
- % 7.2 V1 as a SINGLE ROI (pool across eccentricities)
- % -------------------------------------------------------------
- % For each polar index, pool vertices across all bin radii,
- % then compute β, CIs, and fitted parameters.
- % -------------------------------------------------------------
- % Ensure PolarCoords(1) is positive to match seed_PolarRad polarity
- if PolarCoords(1) < 0
- PolarCoords(1) = abs(PolarCoords(1));
- end
- for coord = 1:length(x_ycoords0_1ROI) % LHM, UVM, RHM, LVM
- % Gather all vertex indices for this meridian across bins
- IA = []; IA = {vtxidx(ismember([vtxidx.seed_PolarRad]',PolarCoords(coord))).inclvtx};
- % Mean β and CI (per contrast) for pooled V1-wide ROI
- for c = 1:size(BetaVals_ECC,2) % For each contrast level
- % disp(['Polar = ' num2str(PolarCoords(coord)) ', Contrast = ' num2str(c)])
- Data_VFrois_1ROI(subs,CSFCondition,c,coord) = nanmean(BetaVals_ECC(CSFCondition,c,vertcat(IA{:})),3);
- if length(vertcat(IA{:}))>2
- if length(vertcat(IA{:})) - sum(isnan(squeeze(BetaVals_ECC(CSFCondition,c,vertcat(IA{:})))))>2
- CIData_VFrois_1ROI(subs,CSFCondition,c,coord,:) = bootci(1000,@nanmean,BetaVals_ECC(CSFCondition,c,vertcat(IA{:})));
- else
- CIData_VFrois_1ROI(subs,CSFCondition,c,coord,:) = NaN;
- end
- else
- CIData_VFrois_1ROI(subs,CSFCondition,c,coord,:) = NaN;
- end
- end
- % Mean fitted cortical contrast sensitivity params and counts
- FitParams_VFrois_1ROI(subs,CSFCondition,coord,:) = [squeeze(nanmean(x_slope_intercept(CSFCondition,vertcat(IA{:}),:),2));squeeze(nanmean(x_exponent(CSFCondition,vertcat(IA{:}))))];
- FitParams_VFrois_1ROI_nvals(subs,CSFCondition,coord,:) = numel(x_slope_intercept(CSFCondition,vertcat(IA{:}),2));
- % Mean pRF size
- VFrois_1ROI_pRFSize(subs,CSFCondition,coord) = nanmean(pRFSigma_ECC(vertcat(IA{:})));
- end
- % Restore first PolarCoords sign if it was modified
- if PolarCoords(1) > 0
- PolarCoords(1) = -(PolarCoords(1));
- end
- %% -------------------------------------------------------------
- % 7.3 CARDINALS ONLY — add HORIZONTAL vs VERTICAL pooled entries
- % -------------------------------------------------------------
- % Adds two extra pooled polar indices:
- % • HM = LHM ∪ RHM
- % • VM = UVM ∪ LVM
- % Then re-compute pooled β, CI, and fitted parameters for each.
- % -------------------------------------------------------------
- if sum(ismember(PolarCoords,[LVM,UVM,LHM,RHM])) > 1
- % Identify vtxidx positions for HM and VM (across bins)
- Horizontal_1ROI = find([vtxidx.seed_PolarRad] == 0 | abs([vtxidx.seed_PolarRad]) == pi);
- Vertical_1ROI = find(abs([vtxidx.seed_PolarRad]) == pi/2);
- tmp = []; tmp = {vtxidx(Horizontal_1ROI).inclvtx};
- Vtx_Hori = vertcat(tmp{:});
- tmp = []; tmp = {vtxidx(Vertical_1ROI).inclvtx};
- Vtx_Vert = vertcat(tmp{:});
- % Append two extra pooled polar indices at the end:
- % index = length(PolarCoords)+1 -> HM
- % index = length(PolarCoords)+2 -> VM
- for c = 1:size(BetaVals_ECC,2) % For each contrast level
- Data_VFrois_1ROI(subs,CSFCondition,c,length(PolarCoords)+1) = nanmean(BetaVals_ECC(CSFCondition,c,Vtx_Hori),3);
- Data_VFrois_1ROI(subs,CSFCondition,c,length(PolarCoords)+2) = nanmean(BetaVals_ECC(CSFCondition,c,Vtx_Vert),3);
- if length(Vtx_Hori)>2
- if length(Vtx_Vert) - sum(isnan(squeeze(BetaVals_ECC(CSFCondition,c,Vtx_Vert))))>2
- CIData_VFrois_1ROI(subs,CSFCondition,c,length(PolarCoords)+1,:) = bootci(1000,@nanmean,BetaVals_ECC(CSFCondition,c,Vtx_Hori));
- CIData_VFrois_1ROI(subs,CSFCondition,c,length(PolarCoords)+2,:) = bootci(1000,@nanmean,BetaVals_ECC(CSFCondition,c,Vtx_Vert));
- else
- CIData_VFrois_1ROI(subs,CSFCondition,c,length(PolarCoords)+1,:) = NaN;
- CIData_VFrois_1ROI(subs,CSFCondition,c,length(PolarCoords)+2,:) = NaN;
- end
- else
- CIData_VFrois_1ROI(subs,CSFCondition,c,length(PolarCoords)+1,:) = NaN;
- CIData_VFrois_1ROI(subs,CSFCondition,c,length(PolarCoords)+2,:) = NaN;
- end
- end
- % Compute fitted parameters for pooled HM and VM
- Horizontal_1ROI = find(PolarCoords == -pi | PolarCoords == 0);
- Vertical_1ROI = find(abs(PolarCoords) == pi/2);
- % HM
- FitParams_VFrois_1ROI(subs,CSFCondition,length(PolarCoords)+1,:) = squeeze(nanmean(FitParams_VFrois_1ROI(subs,CSFCondition,Horizontal_1ROI,:),3));
- FitParams_VFrois_1ROI_nvals(subs,CSFCondition,length(PolarCoords)+1) = squeeze(nanmean(FitParams_VFrois_1ROI_nvals(subs,CSFCondition,Horizontal_1ROI),3));
- VFrois_1ROI_pRFSize(subs,CSFCondition,length(PolarCoords)+1) = squeeze(nanmean(VFrois_1ROI_pRFSize(subs,CSFCondition,Horizontal_1ROI),3));
- % VM
- FitParams_VFrois_1ROI(subs,CSFCondition,length(PolarCoords)+2,:) = squeeze(nanmean(FitParams_VFrois_1ROI(subs,CSFCondition,Vertical_1ROI,:),3));
- FitParams_VFrois_1ROI_nvals(subs,CSFCondition,length(PolarCoords)+2) = squeeze(nanmean(FitParams_VFrois_1ROI_nvals(subs,CSFCondition,Vertical_1ROI),3));
- VFrois_1ROI_pRFSize(subs,CSFCondition,length(PolarCoords)+2) = squeeze(nanmean(VFrois_1ROI_pRFSize(subs,CSFCondition,Vertical_1ROI),3));
- %% ---------------------------------------------------------
- % 7.4 OPTIONAL PLOTS — pRF ROI locations + cortical contrast sensitivity per anisotropy
- % ---------------------------------------------------------
- if PlotInd
- disp(['... ' num2str(SFLabels(CSFCondition)) 'cpd: Linear Equation Fit (R = a+b*C.^p) – Along the meridians'])
- % Set up figure
- idxFigs = idxFigs+1;
- figure(idxFigs);
- set(gcf,'Color','w','Position', [0 0 Rect(3)/2 Rect(4)])
- SizePoint = 20;
- if exist('s','var')
- clear s
- end
- % 3 anisotropy panels: [HVA, VMA, LRHM]
- for ani = 1:3
- subplot(3,2,ani+(ani-1));
- % ---- Left column: pRF locations contributing to each ROI
- ax = polarscatter(pRFx_y_Polar,pRFx_y_ECC,SizePoint,[0.9 0.9 0.9],'filled');
- PolarIdx = [];
- if ani == 1 % HVA
- colour = repmat([0.4 0.72 0.16; 0.5 0 0.5],length(Radius_seedroi),1); % [LHM, RHM], [UVM, LVM]
- PolarIdx(1,:) = find([vtxidx.seed_PolarRad] == 0 | abs([vtxidx.seed_PolarRad]) == pi);
- PolarIdx(2,:) = find(abs([vtxidx.seed_PolarRad]) == pi/2);
- elseif ani == 2 % VMA
- colour = repmat([0 0 1; 1 0 0],length(Radius_seedroi),1); % UVM, LVM
- PolarIdx(1,:) = find([vtxidx.seed_PolarRad] == pi/2);
- PolarIdx(2,:) = find([vtxidx.seed_PolarRad] == -pi/2);
- elseif ani == 3 % LRHM
- colour = repmat([0 0.7 0.3; 0.75 0.75 0],length(Radius_seedroi),1); % LHM, RHM
- PolarIdx(1,:) = find(abs([vtxidx.seed_PolarRad]) == pi);
- PolarIdx(2,:) = find([vtxidx.seed_PolarRad] == 0);
- end
- hold on
- for i = 1:size(PolarIdx,1)
- tmp = {vtxidx(PolarIdx(i,:)).inclvtx};
- polarscatter(pRFx_y_Polar(vertcat(tmp{:})),pRFx_y_ECC(vertcat(tmp{:})),SizePoint,colour(i,:),'filled','MarkerEdgeColor','k');
- end
- set(ax.Parent,'FontSize',14,'LineWidth',2,'Layer','top',...
- 'GridAlpha',0.5,'GridColor',[0.15 0.15 0.15],'MinorGridAlpha',1,'MinorGridColor',[0.15 0.15 0.15],...
- 'RColor',[0.15 0.15 0.15],'RTick',[2 5:5:Eccentricity],'RTickLabelRotation',0,...
- 'ThetaColor',[0.15 0.15 0.15],'ThetaTickLabel',{'0','30','60','90','120','150','±180','-150','-120','-90','-60','-30'})
- % ---- Right column: Cortical contrast sensitivity estimates (slopes) with CI & fit curves
- PolarIdx = [];
- if ani == 1
- disp('....... Horizontal versus Vertical Anisotropy ')
- colour = [255 176 0; 100 143 255]/255; % Horizontal, Vertical
- PolarIdx(1,:) = length(PolarCoords)+1;
- PolarIdx(2,:) = length(PolarCoords)+2;
- LegendLabel = {'HM','VM'};
- TitleLabel = 'HVA';
- elseif ani == 2
- disp('....... Vertical Meridians Anisotropy ')
- colour = [32 189 84; 212 22 180]/255; % Upper, Lower
- PolarIdx(1,:) = find(PolarCoords == pi/2);
- PolarIdx(2,:) = find(PolarCoords == -pi/2);
- LegendLabel = {'UVM','LVM'};
- TitleLabel = 'VMA';
- elseif ani == 3
- disp('....... Left-Right Horizontal Meridians Anisotropy ')
- colour = [0 0 0; 128 128 128]/255; % Left, Right
- PolarIdx(1,:) = find(PolarCoords == -pi);
- PolarIdx(2,:) = find(PolarCoords == 0);
- LegendLabel = {'LHM','RHM'};
- TitleLabel = 'LRHM';
- end
- % Plot mean BETA value for each coordinates ROI
- subplot(3,2,2*ani); hold on
- if exist('e','var'); clear e; end
- % Mean β ± 95% CI across contrasts
- for i = 1:size(PolarIdx,1)
- pos = abs(squeeze(Data_VFrois_1ROI(subs,CSFCondition,:,PolarIdx(i,:))) - squeeze(CIData_VFrois_1ROI(subs,CSFCondition,:,PolarIdx(i,:),2)));
- neg = abs(squeeze(Data_VFrois_1ROI(subs,CSFCondition,:,PolarIdx(i,:))) - squeeze(CIData_VFrois_1ROI(subs,CSFCondition,:,PolarIdx(i,:),1)));
- e(i) = errorbar(sqrt(C),squeeze(Data_VFrois_1ROI(subs,CSFCondition,:,PolarIdx(i,:))),neg,pos,MarkerType{CSFCondition},'LineWidth',1,'Color',colour(i,:));
- % Overlay fit using pooled V1-wide parameters
- C_fit = linspace(min(C), max(C), 100);
- func = @(x, C) x(1) + x(2)*C.^x(3); % linear fit
- R_fit = func(squeeze(FitParams_VFrois_1ROI(subs,CSFCondition,PolarIdx(i,:),:)), C_fit);
- s(CSFCondition) = plot(sqrt(C_fit), R_fit,lineStyle{CSFCondition},'Color',colour(i,:),'LineWidth',2);
- end
- % Axis Parameters
- ylabel(VariableLabel);
- legend(e,LegendLabel,'Location','NorthWest');
- xlabel('Contrast (%)');% xlabel('Contrast^(^0^.^5^)');
- xlim([0 max(C)]); % From 0-1 (normalised values)
- xticks(sqrt(C));
- xticklabels(num2str(C*100));
- ylim(YAxisLim); % ylim([floor(min(min(Beta_VFrois))) ceil(max(max(Beta_VFrois)))])
- set(gca,'FontSize',14,'LineWidth',2,'box','off','XTickLabelRotation',45)
- axis square;
- title([TitleLabel ': ' num2str(ECCBoundaries(1)) ' – ' num2str(ECCBoundaries(end)) 'º'])
- end
- end
- end % if cardinals present
- end % CSFCondition loop
- %% -------------------------------------------------------------
- % 7.5 Polar Angle — Back-project slopes per bin & polar coord
- % -------------------------------------------------------------
- % Paints fitted slope (b) values per (bin, polar) patch.
- % -------------------------------------------------------------
- if strcmp(answer,'Low & High SF') || strcmp(answer3,'Quadrant')
- % Build a dense response vector per vertex from ROI fits
- if PlotInd
- idxCoords = 0;
- Response = zeros(size(BetaVals_ECC,1),length(pRFx_y_ECC));
- for bin = 1:length(Radius_seedroi)
- for coord = 1:length(PolarCoords) % Will ignore the added values if Cardinals
- idxCoords = idxCoords+1;
- for CSFCondition = 1:size(BetaVals_ECC,1)
- Response(CSFCondition,vtxidx(idxCoords).inclvtx) = squeeze(FitParams_VFrois(subs,CSFCondition,bin,coord,2));
- end
- end
- end
- idxFigs = idxFigs+1;
- figure(idxFigs);
- set(gcf,'Color','w','Position',[0 0 Rect(3)/2 Rect(4)/2]);
- [th,r] = cart2pol(x_ycoords0(:,1),x_ycoords0(:,2));
- for CSFCondition = 1:size(BetaVals_ECC,1)
- % Arrange slope values per (polar, ecc) in matrix form
- Data = [];
- Data = squeeze(FitParams_VFrois(subs,CSFCondition,:,1:length(PolarCoords),2)); % [bin × polar × param(b)]
- Data = Data'; % [Polar x Eccentricity]
- Data(isnan(Data)) = 0; % treat missing as 0 slope
- subplot(1,size(BetaVals_ECC,1),CSFCondition)
- % Draw wedge patches for each seed
- for coord = 1:length(x_ycoords0) % LHM, RHM, UVM, LVM
- % This function creates a patch from polar coordinates
- th0 = th(coord)-a_search_rad;
- rh0 = r(coord)-r_search(coord);
- th1 = th(coord) + a_search_rad;
- rh1 = r(coord)+r_search(coord);
- a1 = linspace(th0,th0);
- r1 = linspace(rh0,rh1);
- a2 = linspace(th0,th1);
- r2 = linspace(rh1,rh1);
- a3 = linspace(th1,th1);
- r3 = linspace(rh1,rh0);
- a4 = linspace(th1,th0);
- r4 = linspace(rh0,rh0);
- [X,Y]=pol2cart([a1,a2,a3,a4],[r1,r2,r3,r4]);
- s2vf(subs,CSFCondition,coord) = patch(X,Y,Data(coord)); % Note: patch function takes text or matrix color def
- end
- axis([-Eccentricity Eccentricity -Eccentricity Eccentricity])
- axis square;
- xticks([-Eccentricity:Eccentricity/2:Eccentricity])
- yticks([-Eccentricity:Eccentricity/2:Eccentricity])
- ax = gca;
- ax.Color = colorBck;
- ax.AmbientLightColor = [0 0 0];
- title(strcat(num2str(SFLabels(CSFCondition)),'cpd'))
- colormap(colorMap);
- cb = colorbar; % show color bar
- cb.Ticks = ceil(linspace(0,max(CLim),4));
- ylabel(cb,['Slope'],'Rotation',270,'Position',[4 max(CLim)/2 0]);
- set(gca,'FontSize',14,'LineWidth',2,'CLim',CLim);
- end
- end
- end
- %% -------------------------------------------------------------
- % 7.6 Polar Angle — Back-project (1 ROI, V1-wide)
- % -------------------------------------------------------------
- % For Low & High SF, pool across eccentricity and paint a
- % single wedge per cardinal meridian using the V1-wide slopes.
- % -------------------------------------------------------------
- if strcmp(answer,'Low & High SF')
- if PlotInd
- % Ensure PolarCoords(1) is positive for indexing below
- if PolarCoords(1) < 0; PolarCoords(1) = abs(PolarCoords(1)); end
- Response_1ROI = NaN(size(BetaVals_ECC,1),length(pRFx_y_ECC));
- % Assign pooled (across ecc) slope per meridian to all vertices in that meridian
- for coord = 1:length(PolarCoords) % Will ignore the added values for HVA (5) and VMA (6) if Cardinals
- idxCoords = find([vtxidx.seed_PolarRad] == PolarCoords(coord));
- for CSFCondition = 1:size(BetaVals_ECC,1)
- tmp = []; tmp = {vtxidx(idxCoords).inclvtx};
- Response_1ROI(CSFCondition,vertcat(tmp{:})) = nanmean(squeeze(FitParams_VFrois(subs,CSFCondition,:,coord,2)),1);
- end
- end
- idxFigs = idxFigs+1;
- figure(idxFigs);
- set(gcf,'Color','w','Position',[0 0 Rect(3)/2 Rect(4)/2]);
- [th_1ROI,r_1ROI] = cart2pol(x_ycoords0_1ROI(:,1),x_ycoords0_1ROI(:,2));
- for CSFCondition = 1:size(BetaVals_ECC,1)
- % Pooled slope per polar (mean across bins)
- Data = [];
- Data = nanmean(squeeze(FitParams_VFrois(subs,CSFCondition,:,1:length(PolarCoords),2)),1);
- Data = Data'; % [Polar x Eccentricity]
- subplot(1,size(BetaVals_ECC,1),CSFCondition)
- % Draw a single wedge per cardinal meridian
- for coord = 1:length(x_ycoords0_1ROI) % LHM, RHM, UVM, LVM
- % This function creates a patch from polar coordinates
- th0 = th_1ROI(coord)-a_search_rad;
- rh0 = r_1ROI(coord)-r_search_1ROI(coord);
- th1 = th_1ROI(coord) + a_search_rad;
- rh1 = r_1ROI(coord)+r_search_1ROI(coord);
- a1 = linspace(th0,th0);
- r1 = linspace(rh0,rh1);
- a2 = linspace(th0,th1);
- r2 = linspace(rh1,rh1);
- a3 = linspace(th1,th1);
- r3 = linspace(rh1,rh0);
- a4 = linspace(th1,th0);
- r4 = linspace(rh0,rh0);
- [X,Y]=pol2cart([a1,a2,a3,a4],[r1,r2,r3,r4]);
- s2vf(subs,CSFCondition,coord) = patch(X,Y,Data(coord)); % Note: patch function takes text or matrix color def
- end
- axis([-Eccentricity Eccentricity -Eccentricity Eccentricity])
- axis square;
- xticks([-Eccentricity:Eccentricity/2:Eccentricity])
- yticks([-Eccentricity:Eccentricity/2:Eccentricity])
- ax = gca;
- ax.Color = colorBck;
- title(strcat(num2str(SFLabels(CSFCondition)),'cpd'))
- colormap(colorMap);
- cb = colorbar; % show color bar
- cb.Ticks = ceil(linspace(0,max(CLim),4));
- ylabel(cb,['Slope'],'Rotation',270,'Position',[4 max(CLim)/2 0]);
- set(gca,'FontSize',14,'LineWidth',2,'CLim',CLim);
- end
- % Restore PolarCoords sign if modified
- if PolarCoords(1) > 0; PolarCoords(1) = -PolarCoords(1); end
- end
- end
- end
- end % if 'Low & High SF' || exist(answer3)
- end % SECTION 7
- %% =========================================================================
- % SECTION 8 — GROUP LEVEL (ONLY for "Low & High SF")
- % -------------------------------------------------------------------------
- % This section computes group-level summaries and figures if:
- % • At least 3 subjects were selected
- % • The chosen condition is 'Low & High SF'
- %
- % It produces:
- % • Group mean cortical contrast sensitivity per eccentricity bin (with 95% CI and subject overlays)
- % • Group back-projection of eccentricity-binned slopes (b) to visual space
- % • Group anisotropy plots (HVA/VMA/LRHM) from V1-wide pooled ROIs
- % • Group back-projection of V1-wide polar slopes to visual space
- %
- % Shapes reminder of inputs aggregated here:
- % Data_ECC(sub, bin, SF, contrast)
- % FitParams_ECC(sub, SF, bin, [a b p])
- %
- % Data_VFrois(sub, SF, contrast, bin, polar)
- % FitParams_VFrois(sub, SF, bin, polar, [a b p])
- %
- % Data_VFrois_1ROI(sub, SF, contrast, polar)
- % FitParams_VFrois_1ROI(sub, SF, polar, [a b p])
- % =========================================================================
- if length(unique(selectfn(indx))) >= 3 & strcmp(answer,'Low & High SF')
- idxFigs = idxFigs+1;
- disp('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%')
- disp('%%%%%%%%%%%%%%%%%%%%%%%% GROUP LEVEL %%%%%%%%%%%%%%%%%%%%%%%%')
- disp('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%')
- % Containers for group means and CIs
- MeanBeta_VFrois = []; MeanCIBeta_VFrois = [];
- MeanBeta_VFrois_1ROI = []; MeanCIBeta_VFrois_1ROI = [];
- MeanFitParams_VFrois = []; MeanFitParams_VFrois_1ROI = [];
- MeanFitParams_ECC = [];
- % Use the specific contrast set selected for plotting at group level
- C = ContrastToPlot'/100;
- %% ---------------------------------------------------------------------
- % 8.1 Group summaries for Polar ROIs across eccentricity bins
- % ----------------------------------------------------------------------
- for CSFCondition = 1:size(Data_VFrois,2)
- % Grand mean across subjects
- % Data_VFrois: [sub, SF, contrast, bin, polar]
- MeanBeta_VFrois(CSFCondition,:,:,:) = squeeze(nanmean(Data_VFrois(:,CSFCondition,:,:,:),1));
- MeanBeta_VFrois_1ROI(CSFCondition,:,:) = squeeze(nanmean(Data_VFrois_1ROI(:,CSFCondition,:,:),1));
- MeanFitParams_VFrois(CSFCondition,:,:,:) = squeeze(nanmean(FitParams_VFrois(:,CSFCondition,:,:,:),1));
- MeanFitParams_VFrois_1ROI(CSFCondition,:,:) = squeeze(nanmean(FitParams_VFrois_1ROI(:,CSFCondition,:,:),1));
- % Bootstrap CI across subjects for each (contrast, bin, polar)
- for bin = 1:length(Radius_seedroi)
- for c = 1:length(ContrastToPlot)
- for coord = 1:size(Data_VFrois,5)
- % Extract subject samples for this cell
- if sum(isnan(Data_VFrois(:,CSFCondition,c,bin,coord))) >= 3
- MeanCIBeta_VFrois(CSFCondition,c,bin,coord,:) = NaN;
- else
- MeanCIBeta_VFrois(CSFCondition,c,bin,coord,:) = squeeze(bootci(1000,@nanmean,Data_VFrois(:,CSFCondition,c,bin,coord)))';
- end
- end
- end
- end
- end
- %% ---------------------------------------------------------------------
- % 8.2 Group summaries for V1-wide pooled ROIs (single-ROI)
- % ----------------------------------------------------------------------
- for CSFCondition = 1:size(Data_VFrois,2)
- % Grand mean across subjects
- % Data_VFrois_1ROI: [sub, SF, contrast, polar]
- MeanBeta_VFrois_1ROI(CSFCondition,:,:) = squeeze(nanmean(Data_VFrois_1ROI(:,CSFCondition,:,:),1));
- MeanFitParams_VFrois_1ROI(CSFCondition,:,:) = squeeze(nanmean(FitParams_VFrois_1ROI(:,CSFCondition,:,:),1));
- % Bootstrap CI across subjects for each (contrast, bin, polar)
- for bin = 1:length(Radius_seedroi)
- for c = 1:length(ContrastToPlot)
- for coord = 1:size(Data_VFrois_1ROI,4)
- if sum(isnan(Data_VFrois_1ROI(:,CSFCondition,c,coord))) >= 3
- MeanCIBeta_VFrois_1ROI(CSFCondition,c,coord,:) = NaN;
- else
- MeanCIBeta_VFrois_1ROI(CSFCondition,c,coord,:) = squeeze(bootci(1000,@nanmean,Data_VFrois_1ROI(:,CSFCondition,c,coord)))';
- end
- end
- end
- end
- end
- %% ---------------------------------------------------------------------
- % 8.3 Group summaries for Eccentricity-binned cortical contrast sensitivity
- % ----------------------------------------------------------------------
- MeanCIBetaVals_ECC = []; % [SF, bin, contrast, CI=2]
- if exist('s','var'); clear s; end
- % Colour by SF condition (two-colour palette repeated if needed)
- colour_ecc = repmat([0 0 255; 255 0 0]/255,round(size(Data_ECC,3)/2),1);
- for CSFCondition = 1:size(Data_ECC,3)
- disp(['... ' num2str(SFLabels(CSFCondition)) 'cpd: Linear Equation Fit (R = a+b*C.^p): Eccentricity bin level'])
- % Prepare grouped figure canvas (grid by eccentricity bins)
- if CSFCondition == 1
- idxFigs = idxFigs+1;
- ncols = 5;
- [q,r] = quorem(sym(length(BinTick)),sym(ncols));
- nlines = double(q);
- if r > 0
- nlines = nlines+1;
- end
- if nlines <= 4
- figure(idxFigs); set(gcf,'Color','w','Position',[0 0 Rect(3) Rect(4)*nlines/4]);
- else
- figure(idxFigs); set(gcf,'Color','w','Position',Rect);
- end
- end
- % Loop over eccentricity bins
- for band = 1:length(BinECC)-1
- % Group mean β across subjects (per contrast)
- % Data_ECC: [sub, bin, SF, contrast]
- R = []; R = squeeze(nanmean(Data_ECC(:,band,CSFCondition,:),1));
- % Group CI across subjects for β
- y = []; y = squeeze(Data_ECC(:,band,CSFCondition,:));
- MeanCIBetaVals_ECC(CSFCondition,band,:,:) = bootci(1000,@nanmean,y)';
- % Group mean fit params across subjects (a,b,p)
- MeanFitParams_ECC(CSFCondition,band,:) = squeeze(nanmean(FitParams_ECC(:,CSFCondition,band,:),1));
- % ---- Plot in corresponding bin subplot ----
- hold on; subplot(nlines,ncols,band);
- % Error bars from group CI
- upper = []; upper = abs(R-squeeze(MeanCIBetaVals_ECC(CSFCondition,band,:,2)));
- lower = []; lower = abs(R-squeeze(MeanCIBetaVals_ECC(CSFCondition,band,:,1)));
- errorbar(sqrt(C),R,lower,upper,'Marker','o','LineStyle','none','LineWidth',1,'Color',colour_ecc(CSFCondition,:));
- % Group fit overlay
- hold on;
- C_fit = linspace(min(C), max(C), 100);
- func = @(x, C) x(1) + x(2)*C.^x(3); % linear fit
- R_fit = func(MeanFitParams_ECC(CSFCondition,band,:), C_fit);
- s(CSFCondition) = plot(sqrt(C_fit), R_fit,'-','Color',colour_ecc(CSFCondition,:),'LineWidth',2);
- % Individual subject fits overlaid faintly
- for subs = 1:size(FitParams_ECC,1)
- R_fit_ind = func(FitParams_ECC(subs,CSFCondition,band,:), C_fit);
- plot(sqrt(C_fit), R_fit_ind,'-','Color',[colour_ecc(CSFCondition,:) 0.2],'LineWidth',1);
- end
- % Formatting
- if CSFCondition == length(unique(SFLabels))
- legend([s],strcat(num2str(unique(SFLabels)),repmat('cpd',length(unique(SFLabels)),1)),'Location','NorthWest','box','off')
- end
- xlabel('Contrast (%)');% xlabel('Contrast^(^0^.^5^)');
- xlim([0 max(C)]); % From 0-1 (normalised values)
- xticks(sqrt(C));
- xticklabels(num2str(C*100));
- ylabel('\beta-value');
- ylim([-5 15]); axis square;
- title(num2str(SFLabels(CSFCondition)))
- title([num2str(BinECC(band)) '–' num2str(BinECC(band+1)) 'º'])
- set(gca,'LineWidth',2,'FontSize',14,'box','off')
- end
- end
- %% ---------------------------------------------------------------------
- % 8.4 Group back-projection to visual space — Eccentricity bins
- % ----------------------------------------------------------------------
- idxFigs = idxFigs+1;
- figure(idxFigs);
- set(gcf,'Color','w','Position', [0 0 Rect(3) Rect(4)/3])
- [th,r] = cart2pol(x_ycoords0(:,1),x_ycoords0(:,2));
- YAxisLim = [0 12]; % colour scale for slopes
- for CSFCondition = 1:size(MeanFitParams_ECC,1)
- % Mean slope (b) across eccentricity bins for this SF
- R = []; R = squeeze(nanmean(MeanFitParams_ECC(CSFCondition,:,2),1)); % slope b only
- subplot(1,size(MeanFitParams_ECC,1),CSFCondition)
- idx = 0;
- % Draw wedge patches; advance R index every 4 polar patches
- for coord = 1:length(x_ycoords0) % LHM, RHM, UVM, LVM
- th0 = th(coord)-a_search_rad;
- rh0 = r(coord)-r_search(coord);
- th1 = th(coord)+a_search_rad;
- rh1 = r(coord)+r_search(coord);
- a1 = linspace(th0,th0);
- r1 = linspace(rh0,rh1);
- a2 = linspace(th0,th1);
- r2 = linspace(rh1,rh1);
- a3 = linspace(th1,th1);
- r3 = linspace(rh1,rh0);
- a4 = linspace(th1,th0);
- r4 = linspace(rh0,rh0);
- [X,Y]=pol2cart([a1,a2,a3,a4],[r1,r2,r3,r4]);
- if mod(coord,4) == 1 % Change Data value after each 4 coordinates (Left, Right, Up, Down)
- idx = idx+1;
- s_ecc_avg(CSFCondition,coord) = patch(X,Y,R(idx)); % Note: patch function takes text or matrix color def
- else
- s_ecc_avg(CSFCondition,coord) = patch(X,Y,R(idx)); % Note: patch function takes text or matrix color def
- end
- end
- % Formatting
- axis([-Eccentricity Eccentricity -Eccentricity Eccentricity])
- axis square;
- xticks([-Eccentricity:Eccentricity/2:Eccentricity])
- yticks([-Eccentricity:Eccentricity/2:Eccentricity])
- ax = gca;
- ax.Color = [1 1 1];
- title(strcat(num2str(SFLabels(CSFCondition)),'cpd'))
- colormap(colorMap);
- cb = colorbar; % show color bar
- ylabel(cb,['Slope'],'Rotation',270,'Position',[4 YAxisLim(2)/2 0]);
- set(gca,'FontSize',22,'LineWidth',2,'CLim',YAxisLim);
- end
- %% ---------------------------------------------------------------------
- % 8.5 Group anisotropy plots — V1-wide pooled (HVA, VMA, LRHM)
- % ----------------------------------------------------------------------
- nlines = 1;
- ncols = 3;
- for CSFCondition = 1:size(MeanCIBeta_VFrois_1ROI,1)
- % Open figure
- idxFigs = idxFigs+1;
- figure(idxFigs);
- idxPrevious = 0;
- set(gcf,'Color','w','Position', [0 0 Rect(3)*2/3 Rect(4)/2])
- FontSize = 22;
- YAxisLim = [-5 15];
- if exist('s','var'); clear s; end
- % Three panels: HVA, VMA, LRHM
- for ani = 1:3
- % Plot anisotropy data
- PolarIdx = [];
- % Choose ROI pairs, colours, labels, titles
- if ani == 1
- disp(['....... ' num2str(SFLabels(CSFCondition)) 'cpd: Horizontal versus Vertical Anisotropy '])
- colour = [102 184 42; 128 0 128]/255; % Horizontal, Vertical
- PolarIdx(1,:) = length(PolarCoords)+1;
- PolarIdx(2,:) = length(PolarCoords)+2;
- LegendLabel = {'HM','VM'};
- TitleLabel = 'HVA';
- elseif ani == 2
- disp(['....... ' num2str(SFLabels(CSFCondition)) 'cpd: Vertical Meridians Anisotropy '])
- colour = [0 0 255; 255 0 0]/255; % Upper, Lower
- PolarIdx(1,:) = find(PolarCoords == pi/2);
- PolarIdx(2,:) = find(PolarCoords == -pi/2);
- LegendLabel = {'UVM','LVM'};
- TitleLabel = 'VMA';
- elseif ani == 3
- disp(['....... ' num2str(SFLabels(CSFCondition)) 'cpd: Left-Right Horizontal Meridians Anisotropy '])
- colour = [0 0 0; 128 128 128]/255; % Left, Right
- PolarIdx(1,:) = find(PolarCoords == -pi);
- PolarIdx(2,:) = find(PolarCoords == 0);
- LegendLabel = {'LHM','RHM'};
- TitleLabel = 'LRHM';
- end
- subplot(nlines,ncols,ani)
- if exist('e','var'); clear e; end
- for i = 1:size(PolarIdx,1)
- hold on
- % Mean ± 95% CI across subjects
- R = squeeze(MeanBeta_VFrois_1ROI(CSFCondition,:,PolarIdx(i,:)))';
- pos = abs(R - squeeze(MeanCIBeta_VFrois_1ROI(CSFCondition,:,PolarIdx(i,:),2))');
- neg = abs(R - squeeze(MeanCIBeta_VFrois_1ROI(CSFCondition,:,PolarIdx(i,:),1))');
- e(i) = errorbar(sqrt(C),R,neg,pos,'o','LineWidth',1,'Color',colour(i,:));
- % Fit using group mean params
- C_fit = linspace(min(C), max(C), 100);
- func = @(x, C) x(1) + x(2)*C.^x(3); % linear fit
- R_fit = func(MeanFitParams_VFrois_1ROI(CSFCondition,PolarIdx(i,:),:), C_fit);
- s(CSFCondition) = plot(sqrt(C_fit), R_fit,'-','Color',colour(i,:),'LineWidth',2);
- % Individual subjects (faint) for visual variability
- for subs = 1:size(FitParams_VFrois_1ROI,1)
- R_fit_ind = func(squeeze(FitParams_VFrois_1ROI(subs,CSFCondition,PolarIdx(i,:),:)), C_fit);
- plot(sqrt(C_fit), R_fit_ind,'-','Color',[colour(i,:) 0.2],'LineWidth',1);
- end
- end
- % Formatting
- l = legend(e,LegendLabel,'Location','NorthWest');
- ylabel(VariableLabel); ylim(YAxisLim);
- xlabel('Contrast (%)');
- xlim([0 max(C)]);
- xticks(sqrt(C));
- xticklabels(num2str(C*100));
- axis square
- title([TitleLabel ': ' num2str(ECCBoundaries(bin)) '–' num2str(ECCBoundaries(bin+1)) 'º']);
- set(gca,'FontSize',FontSize,'LineWidth',2,'box','off','XTickLabelRotation',45)
- end
- end
- %% ---------------------------------------------------------------------
- % 8.6 Group back-projection to visual space — V1-wide polar
- % ----------------------------------------------------------------------
- idxFigs = idxFigs+1;
- figure(idxFigs);
- set(gcf,'Color','w','Position', [0 0 Rect(3) Rect(4)/3])
- [th_1ROI,r_1ROI] = cart2pol(x_ycoords0_1ROI(:,1),x_ycoords0_1ROI(:,2));
- YAxisLim = [0 12];
- for CSFCondition = 1:size(MeanFitParams_VFrois_1ROI,1)
- % Group mean slope (b) per cardinal meridian
- Data = [];
- Data = squeeze(MeanFitParams_VFrois_1ROI(CSFCondition,:,2))';
- % Data needs to be length(PolarCoords)*length(Radius_seedroi)
- % Draw a wedge per meridian (pooled across eccentricities)
- subplot(1,size(MeanFitParams_VFrois_1ROI,1),CSFCondition)
- for coord = 1:length(x_ycoords0_1ROI) % LHM, RHM, UVM, LVM
- % This function creates a patch from polar coordinates
- th0 = th_1ROI(coord)-a_search_rad;
- rh0 = r_1ROI(coord)-r_search_1ROI(coord);
- th1 = th_1ROI(coord)+a_search_rad;
- rh1 = r_1ROI(coord)+r_search_1ROI(coord);
- a1 = linspace(th0,th0);
- r1 = linspace(rh0,rh1);
- a2 = linspace(th0,th1);
- r2 = linspace(rh1,rh1);
- a3 = linspace(th1,th1);
- r3 = linspace(rh1,rh0);
- a4 = linspace(th1,th0);
- r4 = linspace(rh0,rh0);
- [X,Y]=pol2cart([a1,a2,a3,a4],[r1,r2,r3,r4]);
- s_ecc_avg(CSFCondition,coord) = patch(X,Y,Data(coord));
- end
- % Formatting
- axis([-Eccentricity Eccentricity -Eccentricity Eccentricity])
- axis square;
- xticks([-Eccentricity:Eccentricity/2:Eccentricity])
- yticks([-Eccentricity:Eccentricity/2:Eccentricity])
- ax = gca;
- ax.Color = [1 1 1];
- title(strcat(num2str(SFLabels(CSFCondition)),'cpd'))
- colormap(colorMap);
- cb = colorbar;
- ylabel(cb,['Slope'],'Rotation',270,'Position',[4 YAxisLim(2)/2 0]);
- set(gca,'FontSize',22,'LineWidth',2,'CLim',YAxisLim);
- end
- end % GROUP LEVEL
BackProjection_Slopes_Controls.m at commit 0014b8f, under MIT · at the source
Overview
- Institute of Ophthalmology, University College London (UCL), London, United Kingdom
- Birkbeck/UCL Centre for NeuroImaging, London, United Kingdom
- Department of Psychology, Royal Holloway, University of London, London, United Kingdom
- eScience Institute, University of Washington, Seattle, United States
- Institute of Cognitive Neuroscience, UCL, London, United Kingdom
- John van Geest Centre for Brain Repair and MRC Mitochondrial Biology Unit, Department of Clinical Neurosciences, University of Cambridge, Cambridge, United Kingdom
- Cambridge Eye Unit, Addenbrooke’s Hospital, Cambridge University Hospitals NHS Foundation Trust, Cambridge, United Kingdom
- Moorfields Eye Hospital NHS Foundation Trust, London, United Kingdom
- Department of Experimental Psychology, UCL, London, United Kingdom
Abstract
Peripheral vision is crucial for daily activities and quality of life, yet traditional measures of visual function like visual acuity primarily assess central vision. Visual field tests can evaluate peripheral vision but require extended focus and precise fixation, often challenging for patients with severe sight loss. Functional MRI (fMRI) with population receptive field (pRF) mapping offers a non-invasive way to map scotomas but relies on single contrast levels and accurate fixation. We developed an fMRI-based approach to measure contrast sensitivity across the visual field without requiring precise fixation. Combining large-field stimulation with varying spatial frequencies and contrast levels, and either pRF mapping or a structure-based retinotopic atlas, we modeled contrast sensitivity in the primary visual cortex (V1) over a large (40 deg) expanse of the visual field. In seven normally sighted participants, we characterized differences in V1 sensitivity across eccentricities and visual quadrants, finding reliable and reproducible patterns at individual and session levels. To test robustness to fixation variability, we investigated how varying levels of eye movement affected V1 sensitivity patterns in two participants: cortical sensitivity patterns were largely preserved despite eye movements, particularly at low spatial frequencies. This suggests our approach can accommodate several degrees of fixation instability, making it suitable for populations with unstable or biased fixation for whom visual field maps are harder to acquire behaviorally (e.g., patients with dense central scotoma or strabismus). Additionally, our method effectively visualized simulated and disease-linked sensitivity loss at the cortical level. Crucially, these results could be largely recovered using the structure-based retinotopic atlas, eliminating the need for pRF mapping and precise fixation, although with reduced sensitivity. This approach, integrating large-field stimulation with a retinotopic atlas, offers a promising tool for monitoring vision loss and recovery across a range of visual impairments, addressing a significant challenge in current clinical assessments.
Reproduced under the paper's license (CC BY), from the paper cited above.
Repositories
Its files are read in the Code ↔ Paper reader above, with 10 matches between paragraphs and lines of code.
HugoCWB/ChowWingBomEtAl_2025_eLife
0014b8fa0797ece45b1bf5cadca99a20e4999f25, 16 April 2026Availability: 1 check, the latest on 5 October 2026: the link answers
- 5 October 2026: the link answers
6 files
- BackProjection_Slopes_Co
ntrols.m , MATLAB, 1,606 lines, 4 matches - BackProjection_Slopes_Pa
tient.m , MATLAB, 655 lines, 3 matches - Reliability_Slopes.m, MATLAB, 150 lines
- dolinearfit_SqCdata.m, MATLAB, 102 lines
- LICENSE, License, 21 lines
- README.md, Text, 72 lines
hugocwb/adjustecctool
85bba2af3530612050f0166f20343594395e3f30, 25 March 2026Availability: 1 check, the latest on 5 October 2026: the link answers
- 5 October 2026: the link answers
4 files
- src/
eccen_adjust/ , Python, 396 lines, 3 matches__init__.py - src/
eccen_adjust/ , Python, 120 lines__main__.py - LICENSE, License, 21 lines
- README.md, Text, 117 lines
Zenodo 18770065
Availability: 1 check, the latest on 5 October 2026: the link answers (HTTP 200)
- 5 October 2026: the link answers (HTTP 200)
The paper's code and data availability statement is in the Data section.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 3 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 6 scripts, each with its path and the digest of its content;
- 10 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
Datasets cited
- zenodo:19051439, at Zenodo; found in “Data availability”
Data availability
All post-processed and anonymized data are available on Zenodo (https://
The following datasets were generated:
Chow-Wing-Bom H, Lisi M, Benson N, Lygo-Frett F, Yu-Wai-Man P, Dick F, Maison-Mor R, Dekker T. 2026. Dataset for Chow-Wing-Bom et al. 2025, eLife. Zenodo.
Chow-Wing-Bom H, Benson N. 2026. Benson Map Eccentricity Adjustment Tool. Zenodo.
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 6 October 2026: the first record
Recorded: type, language, journal, volume, pages, dates, 8 authors, 1 keyword, 12 MeSH terms, 2 funders, 48 references.
Cite
This paper
Chow-Wing-Bom, H. T., Lisi, M., Benson, N. C., Lygo-Frett, F., Yu-Wai-Man, P., Dick, F., Maimon-Mor, R. O., & Dekker, T. M. (2026). Mapping human visual contrast sensitivity and vision loss across the visual field with model-based fMRI. eLife, 14, RP105930. https://
BibTeX
@article{chowwingbom2026
author = {Chow-Wing-Bom, Hugo T and Lisi, Matteo and Benson, Noah C and Lygo-Frett, Freya and Yu-Wai-Man, Patrick and Dick, Frederic and Maimon-Mor, Roni O and Dekker, Tessa M},
title = {{Mapping human visual contrast sensitivity and vision loss across the visual field with model-based fMRI}},
journal = {eLife},
year = {2026},
month = oct,
volume = {14},
pages = {RP105930},
publisher = {eLife Sciences Publications, Ltd},
issn = {2050-084X},
doi = {10.7554/
url = {https://
pmid = {42826108},
pmcid = {PMC13633074}
}
RIS
TY - JOUR
AU - Chow-Wing-Bom, Hugo T
AU - Lisi, Matteo
AU - Benson, Noah C
AU - Lygo-Frett, Freya
AU - Yu-Wai-Man, Patrick
AU - Dick, Frederic
AU - Maimon-Mor, Roni O
AU - Dekker, Tessa M
TI - Mapping human visual contrast sensitivity and vision loss across the visual field with model-based fMRI
T2 - eLife
J2 - eLife
PY - 2026
DA - 2026/
VL - 14
SP - RP105930
SN - 2050-084X
PB - eLife Sciences Publications, Ltd
DO - 10.7554/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.7554/
"type": "article-journal",
"title": "Mapping human visual contrast sensitivity and vision loss across the visual field with model-based fMRI",
"container-title": "eLife",
"author": [
{
"family": "Chow-Wing-Bom",
"given": "Hugo T"
},
{
"family": "Lisi",
"given": "Matteo"
},
{
"family": "Benson",
"given": "Noah C"
},
{
"family": "Lygo-Frett",
"given": "Freya"
},
{
"family": "Yu-Wai-Man",
"given": "Patrick"
},
{
"family": "Dick",
"given": "Frederic"
},
{
"family": "Maimon-Mor",
"given": "Roni O"
},
{
"family": "Dekker",
"given": "Tessa M"
}
],
"container-title-short":
"volume": "14",
"page": "RP105930",
"DOI": "10.7554/
"PMID": "42826108",
"PMCID": "PMC13633074",
"ISSN": "2050-084X",
"publisher": "eLife Sciences Publications, Ltd",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
10,
2
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1101/2024.10.29.619403 [code]
- Mapping Visual Contrast Sensitivity and Vision Loss Across the Visual Field with Model-Based fMRIJournal: bioRxiv (preprint)In common: Optimization Toolbox, Statistics and Machine Learning Toolbox, Matplotlib, 1 other tool, Zenodo 19051439, fMRI, 31 references, 5 authors
- [2] doi:10.1038/s41467-022-31041-9 [code]
- Linking individual differences in human primary visual cortex to contrast sensitivity around the visual field.Journal: Nature communicationsIn common: Statistics and Machine Learning Toolbox, systems, 12 references
- [3] doi:10.1371/journal.pcbi.1013599 [code]
- Unpacking the V1 map: Differential covariation of preferred spatial frequency and cortical magnification across spatial dimensions.Journal: PLoS computational biologyIn common: Matplotlib, NumPy, fMRI, systems, 11 references
- [4] doi:10.34133/csbj.0042 [code]
- Using Steady-State Visual Evoked Potentials to Characterize Wide-Ranging Retinopathy Linked to &
lt;i& gt;CRB1& lt;/ i& gt;: Implications for Clinical Trials. Journal: Computational and structural biotechnology journalIn common: Matplotlib, NumPy, 3 references, 2 authors - [5] doi:10.7554/elife.84205 [code]
- Cortical magnification eliminates differences in contrast sensitivity across but not around the visual field.Journal: eLifeIn common: Optimization Toolbox, Statistics and Machine Learning Toolbox, 9 references
- [6] doi:10.1038/s41467-023-37280-8 [code]
- Comparing retinotopic maps of children and adults reveals a late-stage change in how V1 samples the visual field.Journal: Nature communicationsIn common: Statistics and Machine Learning Toolbox, Matplotlib, NumPy, systems, 8 references
- [7] doi:10.1101/2025.11.27.690210 [code]
- Predicting functional topography of the human visual cortex from cortical anatomy at scaleJournal: bioRxiv (preprint)In common: Matplotlib, NumPy, systems, 5 references, author Noah C Benson
- [8] doi:10.1002/hbm.70140 [code]
- Biases in Volumetric Versus Surface Analyses in Population Receptive Field Mapping.Journal: Human brain mappingIn common: Optimization Toolbox, Statistics and Machine Learning Toolbox, Matplotlib, 1 other tool, fMRI, systems, 6 references
- [9] doi:10.1038/s41467-025-63795-3 [code]
- Spatial attention selectively alters visual cortical representation during target anticipation.Journal: Nature communicationsIn common: Optimization Toolbox, Statistics and Machine Learning Toolbox, Matplotlib, 1 other tool, 7 references
- [10] doi:10.1073/pnas.2507810122
- Visual adaptation stronger at the horizontal than the vertical meridian: Linking performance with V1 cortical surface area.Journal: Proceedings of the National Academy of Sciences of the United States of AmericaIn common: 9 references
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 3 repositories of the authors' code, each at its verified commit and with its license, 6 scripts, and 10 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:dd6139d4aa198c92…
Add the badge to the code's README
If you are an author or a maintainer of this paper's code, you can add this badge. It says what OSCR knows of the paper (its code, its validated tracing map) and links the code to this page. Paste one of these snippets under the README's title. Keep the two marks around it, so that the badge can be replaced in place and is never added twice. OSCR changes nothing in your repository.
Markdown
<!-- oscr-badge:start -->
[](https://openscicode.org/badge/doi/10.7554/elife.105930)
<!-- oscr-badge:end -->reStructuredText
.. oscr-badge:start
.. image:: https://openscicode.org/badge/doi/10.7554/elife.105930.svg
:target: https://openscicode.org/badge/doi/10.7554/elife.105930
:alt: OSCR
.. oscr-badge:endHTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.7554/elife.105930"><img src="https://openscicode.org/badge/doi/10.7554/elife.105930.svg" alt="OSCR"></a>
<!-- oscr-badge:end -->On GitHub, open the README, choose Edit (the pencil), paste the snippet under the title, then "Commit changes".
If you choose to, you can have the badge kept up to date for you, with oscr badge --write from the command line or with the GitHub Action in your repository's workflows. Every other form and option (a card, AsciiDoc, LaTeX, shields.io) is on the badge page.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: code errors and mismatches between the code and the paper about this paper and its code, from readers and its authors, beside the paper and its tracing map.
Report a mismatch between the code and the paper · Report a code error (signed in)
Reproductions: reports from readers who ran the authors' code and did not get the paper's result, what they ran, with which environment, commit and data.
Report a reproduction failure (signed in)
Versions of its code: the releases of its code tied to a version of this paper (the preprint, the accepted manuscript, the version of record…), each with the tracing map versioned with it.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
