diff --git a/mrAlign/talairach.m b/mrAlign/talairach.m index 3e5f5e759..a12b52fc4 100644 --- a/mrAlign/talairach.m +++ b/mrAlign/talairach.m @@ -239,12 +239,12 @@ function talairachControlsHandler(params) function viewVol = vol2viewVol(vol,view2vol); % warp the volume if ~isequal(view2vol,eye(4)) - disppercent(-inf,'Warping volume'); + mlrDispPercent(-inf,'Warping volume'); swapXY = [0 1 0 0;1 0 0 0;0 0 1 0;0 0 0 1]; % warpAffine3 uses yx, not xy viewVol = warpAffine3(vol,swapXY*view2vol*swapXY,nan,[],'linear'); %viewVol = warpAffine3(vol,swapXY*view2vol*swapXY,nan,[],'nearest'); - disppercent(inf); + mlrDispPercent(inf); else viewVol = vol; end @@ -371,14 +371,14 @@ function endHandler(ok) end end % read it - disppercent(-inf,sprintf('Loading %s',talinfo.filename)); + mlrDispPercent(-inf,sprintf('Loading %s',talinfo.filename)); if ~isempty(subset) [vol hdr] = mlrImageReadNifti(talinfo.filename,subset); else [vol hdr] = mlrImageReadNifti(talinfo.filename); end if doMean,vol = mean(vol,4);end - disppercent(inf); + mlrDispPercent(inf); else disp(sprintf('(talairach) Could not open file %s',talinfo.filename)); return diff --git a/mrLoadRet/Analysis/ConcatTSeries/concatTSeries.m b/mrLoadRet/Analysis/ConcatTSeries/concatTSeries.m index 1d735235b..31d64bfc2 100644 --- a/mrLoadRet/Analysis/ConcatTSeries/concatTSeries.m +++ b/mrLoadRet/Analysis/ConcatTSeries/concatTSeries.m @@ -342,16 +342,16 @@ % for means that are zero, divide by nan d.mean(d.mean==0) = nan; - disppercent(-inf, '(concatTSeries) Converting to percent signal change'); + mlrDispPercent(-inf, '(concatTSeries) Converting to percent signal change'); for i = 1:d.dim(4) d.data(:,:,:,i) = (d.data(:,:,:,i)./d.mean); if params.percentSignal == 2 % scale it to mean of 1,000 params.scaleFactor = 10000; d.data(:,:,:,i) = d.data(:,:,:,i) * params.scaleFactor; end - disppercent(i/d.dim(4)); + mlrDispPercent(i/d.dim(4)); end - disppercent(inf); + mlrDispPercent(inf); end warning on @@ -516,9 +516,9 @@ %%%%%%%%%%%%%%%%%%%%%%%%% function d = detrendTSeries(d) -disppercent(-inf,sprintf('(concatTSeries:detrendTSeries) Detrending data')); +mlrDispPercent(-inf,sprintf('(concatTSeries:detrendTSeries) Detrending data')); d.data = reshape(eventRelatedDetrend(reshape(d.data,prod(d.dim(1:3)),d.dim(4))')',d.dim(1),d.dim(2),d.dim(3),d.dim(4)); -disppercent(inf); +mlrDispPercent(inf); diff --git a/mrLoadRet/Analysis/EventRelated/eventRelatedHighpass.m b/mrLoadRet/Analysis/EventRelated/eventRelatedHighpass.m index 0b93cd47b..b59adc1e1 100644 --- a/mrLoadRet/Analysis/EventRelated/eventRelatedHighpass.m +++ b/mrLoadRet/Analysis/EventRelated/eventRelatedHighpass.m @@ -60,7 +60,7 @@ end % notch out highest frequency that we get with sense processing - if d.notchFilterForTSense + if ~fieldIsNotDefined(d,'notchFilterForTSense') && d.notchFilterForTSense if iseven(n) if d.notchFilterForTSense == 2 hipassfilter((n/2)+1) = 0; @@ -119,13 +119,13 @@ hipassfilter = d.hipassfilter; if isfield(d,'hipasscutoff') % go through the data, detrend and apply filter in fourier domain - disppercent(-inf,sprintf('(eventRelatedHighpass) Applying temporal hipass filter (cutoff=%0.03f Hz)',d.hipasscutoff)); + mlrDispPercent(-inf,sprintf('(eventRelatedHighpass) Applying temporal hipass filter (cutoff=%0.03f Hz)',d.hipasscutoff)); else - disppercent(-inf,sprintf('(eventRelatedHighpass) Applying temporal hipass filter')) + mlrDispPercent(-inf,sprintf('(eventRelatedHighpass) Applying temporal hipass filter')) end if (0) for i = 1:d.dim(1) - disppercent(i/d.dim(1)); + mlrDispPercent(i/d.dim(1)); for j = 1:d.dim(2) for k = 1:d.dim(3) timecourse = squeeze(d.data(i,j,k,:)); @@ -143,7 +143,7 @@ end % detrend and high pass filter for k = 1:d.dim(3) - disppercent(k/d.dim(3)); + mlrDispPercent(k/d.dim(3)); for j = 1:d.dim(2) timecourses = squeeze(d.data(:,j,k,:)); timecourses = eventRelatedDetrend(timecourses')'; @@ -153,14 +153,14 @@ end elseif (isfield(d,'roidata')) for i = 1:size(d.roidata,1) - disppercent(i/size(d.roidata,1)) + mlrDispPercent(i/size(d.roidata,1)) timecourse = squeeze(d.roidata(i,:)); timecourse = eventRelatedDetrend(timecourse); timecourse = ifft(fft(timecourse) .* hipassfilter'); d.roidata(i,:) = real(timecourse); end end - disppercent(inf); + mlrDispPercent(inf); end diff --git a/mrLoadRet/Analysis/EventRelated/eventRelatedMultiple.m b/mrLoadRet/Analysis/EventRelated/eventRelatedMultiple.m index 98f0189eb..d810113aa 100644 --- a/mrLoadRet/Analysis/EventRelated/eventRelatedMultiple.m +++ b/mrLoadRet/Analysis/EventRelated/eventRelatedMultiple.m @@ -225,13 +225,13 @@ d.unexplainedVariance = zeros(d.dim(1),d.dim(2),d.dim(3)); d.totalVariance = zeros(d.dim(1),d.dim(2),d.dim(3)); % display string -disppercent(-inf,'Calculating goodness of fit'); +mlrDispPercent(-inf,'Calculating goodness of fit'); % cycle through images calculating the estimated hdr and r^s of the % estimate. % onesmatrix = ones(length(d.volumes),1); for j = yvals - disppercent(max((j-min(yvals))/yvaln,0.1)); + mlrDispPercent(max((j-min(yvals))/yvaln,0.1)); for k = slices ehdr = squeeze(d.ehdr(:,j,k,:))'; % get the time series we are working on @@ -244,7 +244,7 @@ tv{j,k} = sum(timeseries.^2); end end -disppercent(inf); +mlrDispPercent(inf); % reshape matrix. for j = yvals for k = slices @@ -289,12 +289,12 @@ d.meanintensity = zeros(d.dim(1),d.dim(2),d.dim(3)); % display string -disppercent(-inf,'Calculating hdr'); +mlrDispPercent(-inf,'Calculating hdr'); % cycle through images calculating the estimated hdr and r^s of the % estimate. onesmatrix = ones(length(d.volumes),1); for j = yvals - disppercent(max((j-min(yvals))/yvaln,0.1)); + mlrDispPercent(max((j-min(yvals))/yvaln,0.1)); for k = slices % get the time series we are working on % this includes all the rows of one column from one slice @@ -310,15 +310,15 @@ d.meanintensity(:,j,k)=colmeans(:); end end -disppercent(inf); +mlrDispPercent(inf); % reshape matrix. this also seems the fastest way to do things. we % could have made a matrix in the above code and then reshaped here % but the reallocs needed to continually add space to the matrix % seems to be slower than the loops needed here to reconstruct % the matrix from the {} arrays. -disppercent(-inf,'Reshaping matrices'); +mlrDispPercent(-inf,'Reshaping matrices'); for i = xvals - disppercent((i-min(xvals))/xvaln); + mlrDispPercent((i-min(xvals))/xvaln); for j = yvals for k = slices % now reshape into a matrix @@ -328,5 +328,5 @@ end % display time took -disppercent(inf); +mlrDispPercent(inf); diff --git a/mrLoadRet/Analysis/EventRelated/eventRelatedPlot.m b/mrLoadRet/Analysis/EventRelated/eventRelatedPlot.m index 09e413071..d866395d8 100644 --- a/mrLoadRet/Analysis/EventRelated/eventRelatedPlot.m +++ b/mrLoadRet/Analysis/EventRelated/eventRelatedPlot.m @@ -156,7 +156,7 @@ function eventRelatedPlot(view,overlayNum,scan,x,y,s,roi) % first go for the quick and dirty way, which is % to load up the computed hemodynamic responses % and average them. - disppercent(-inf,'(eventRelatedPlot) Computing mean hdr'); + mlrDispPercent(-inf,'(eventRelatedPlot) Computing mean hdr'); for voxnum = 1:size(roi{roinum}.scanCoords,2) x = roi{roinum}.scanCoords(1,voxnum); y = roi{roinum}.scanCoords(2,voxnum); @@ -171,7 +171,7 @@ function eventRelatedPlot(view,overlayNum,scan,x,y,s,roi) end end end - disppercent(voxnum/size(roi{roinum}.scanCoords,2)); + mlrDispPercent(voxnum/size(roi{roinum}.scanCoords,2)); end % plot the average of the ehdrs that beat the r2 cutoff if roin @@ -196,7 +196,7 @@ function eventRelatedPlot(view,overlayNum,scan,x,y,s,roi) % put up button whose call back will be to compute the error bars figpos = get(fignum,'position'); gEventRelatedPlot.computeErrorBarsHandle = uicontrol('Parent',fignum,'Style','pushbutton','Callback',@eventRelatedPlotComputeErrorBars,'String','Compute error bars','Position',[figpos(3)/2+figpos(3)/20 figpos(4)/24 figpos(3)/2-figpos(3)/8 figpos(4)/14]); - disppercent(inf); + mlrDispPercent(inf); end drawnow; @@ -305,7 +305,7 @@ function eventRelatedSaveToWorkspace(varargin) return end -disppercent(-inf,'(eventRelatedPlot) Plotting time series'); +mlrDispPercent(-inf,'(eventRelatedPlot) Plotting time series'); gEventRelatedPlot.loadingTimecourse = 1; subplot(2,2,1:2) tSeries = squeeze(loadTSeries(gEventRelatedPlot.v,gEventRelatedPlot.scan,gEventRelatedPlot.vox(3),[],gEventRelatedPlot.vox(1),gEventRelatedPlot.vox(2))); @@ -346,7 +346,7 @@ function eventRelatedSaveToWorkspace(varargin) gEventRelatedPlot.tSeries = tSeries; % done -disppercent(inf); +mlrDispPercent(inf); gEventRelatedPlot.loadingTimecourse = 0; %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/mrLoadRet/Analysis/EventRelated/getGlmContrast.m b/mrLoadRet/Analysis/EventRelated/getGlmContrast.m index d92801cdf..6dd834231 100644 --- a/mrLoadRet/Analysis/EventRelated/getGlmContrast.m +++ b/mrLoadRet/Analysis/EventRelated/getGlmContrast.m @@ -73,7 +73,7 @@ warning('off','MATLAB:divideByZero'); % display string -disppercent(-inf,'(getGlmContrast) Calculating r2'); +mlrDispPercent(-inf,'(getGlmContrast) Calculating r2'); % cycle through images calculating the estimated hdr and r^2s of the % estimate. % @@ -85,7 +85,7 @@ % was by far the faster by a factor of about 2-3. onesmatrix = ones(length(d.volumes),1); for j = yvals - disppercent(max((j-min(yvals))/yvaln,0.1)); + mlrDispPercent(max((j-min(yvals))/yvaln,0.1)); for k = slices % get the time series we are working on % this includes all the rows of one column from one slice @@ -121,16 +121,16 @@ r2{j,k} = (1-sumOfSquaresResidual./sum(timeseries.^2)); end end -disppercent(inf); +mlrDispPercent(inf); % reshape matrix. this also seems the fastest way to do things. we % could have made a matrix in the above code and then reshaped here % but the reallocs needed to continually add space to the matrix % seems to be slower than the loops needed here to reconstruct % the matrix from the {} arrays. -disppercent(-inf,'(getGlmContrast) Reshaping matrices'); +mlrDispPercent(-inf,'(getGlmContrast) Reshaping matrices'); for i = xvals - disppercent((i-min(xvals))/xvaln); + mlrDispPercent((i-min(xvals))/xvaln); for j = yvals for k = slices % get the ehdr @@ -144,6 +144,6 @@ end % display time took -disppercent(inf); +mlrDispPercent(inf); warning('on','MATLAB:divideByZero'); diff --git a/mrLoadRet/Analysis/EventRelated/getr2.m b/mrLoadRet/Analysis/EventRelated/getr2.m index 8811fb16f..2d6d5739f 100644 --- a/mrLoadRet/Analysis/EventRelated/getr2.m +++ b/mrLoadRet/Analysis/EventRelated/getr2.m @@ -60,7 +60,7 @@ warning('off','MATLAB:divideByZero'); % display string -if verbose,disppercent(-inf,'(getr2) Calculating r2');end +if verbose,mlrDispPercent(-inf,'(getr2) Calculating r2');end % cycle through images calculating the estimated hdr and r^2s of the % estimate. % @@ -101,16 +101,16 @@ % calculate variance accounted for by the estimated hdr r2{j,k} = (1-sumOfSquaresResidual./sum(timeseries.^2)); end - if verbose,disppercent(max((j-min(yvals))/yvaln,0.1));end + if verbose,mlrDispPercent(max((j-min(yvals))/yvaln,0.1));end end -if verbose,disppercent(inf);end +if verbose,mlrDispPercent(inf);end % reshape matrix. this also seems the fastest way to do things. we % could have made a matrix in the above code and then reshaped here % but the reallocs needed to continually add space to the matrix % seems to be slower than the loops needed here to reconstruct % the matrix from the {} arrays. -if verbose,disppercent(-inf,'(getr2) Reshaping matrices');end +if verbose,mlrDispPercent(-inf,'(getr2) Reshaping matrices');end for i = xvals for j = yvals for k = slices @@ -122,10 +122,10 @@ d.r2(i,j,k) = r2{j,k}(i); end end - if verbose,disppercent((i-min(xvals))/xvaln);end + if verbose,mlrDispPercent((i-min(xvals))/xvaln);end end % display time took -if verbose,disppercent(inf);end +if verbose,mlrDispPercent(inf);end warning('on','MATLAB:divideByZero'); diff --git a/mrLoadRet/Analysis/makeCorrelationMap.m b/mrLoadRet/Analysis/makeCorrelationMap.m index bf909955a..0c1d3d071 100644 --- a/mrLoadRet/Analysis/makeCorrelationMap.m +++ b/mrLoadRet/Analysis/makeCorrelationMap.m @@ -38,7 +38,7 @@ % keep name of roi roiName = roi{1}.name; % for this case, we get all the ehdrs for the roi - disppercent(-inf,sprintf('(makeCorrelationMap) Using roi %s as source',roiName)); + mlrDispPercent(-inf,sprintf('(makeCorrelationMap) Using roi %s as source',roiName)); roi{1}.scanCoords = getROICoordinates(v,roi{1}); roiN = size(roi{1}.scanCoords,2); for i = 1:roiN @@ -46,9 +46,9 @@ yi = roi{1}.scanCoords(2,i); si = roi{1}.scanCoords(3,i); sourceHDR(i,:) = reshape(squeeze(d.ehdr(xi,yi,si,:,:)),1,d.nhdr*d.hdrlen); - disppercent(i/roiN); + mlrDispPercent(i/roiN); end - disppercent(inf); + mlrDispPercent(inf); end % now reshape the ehdr matrix as a matrix where the first dimension diff --git a/mrLoadRet/Analysis/makeFlat.m b/mrLoadRet/Analysis/makeFlat.m index 37ef02b94..05ec01ee1 100644 --- a/mrLoadRet/Analysis/makeFlat.m +++ b/mrLoadRet/Analysis/makeFlat.m @@ -4,9 +4,13 @@ % usage: makeFlat() % by: eli merriam % date: 09/27/07 -% purpose: +% purpose: Make a flat map centered on the clicked coordinates +% This is normally called from mrLoadRet's interrogator window +% with the surface base anatomy as the current base +% If makeFlat is called from a script AND the interrogator was never used in this MLR session (i.e. mouseDownBaseCoords is empty) +% then x, y, z inputs should be coordinates in the current base (which should be the surface base anatomy) % -function retval = makeFlat(view, overlayNum, scan, x, y, s, roi) +function view = makeFlat(view, overlayNum, scan, x, y, s, roi) % check arguments @@ -25,10 +29,13 @@ baseCoordMap = viewGet(view,'baseCoordMap'); baseCoordMapPath = viewGet(view,'baseCoordMapPath'); startPoint = viewGet(view,'mouseDownBaseCoords'); +if isempty(startPoint) % if there are no mouse click coordinates, this means that makeFlat was called from the command line or a script + startPoint = [x y s]; % in this case, we assume that passed-in coordinates are in the current base +end baseType = viewGet(view,'baseType'); % some other variables -defaultRadius = 75; +defaultRadius = 60; viewNum = viewGet(view, 'viewNum'); % parse the parameters @@ -129,7 +136,7 @@ % install it disp(sprintf('(makeFlat) installing new flat base anatomy: %s', params.flatFileName)); - viewSet(view, 'newBase', flatBase); + view = viewSet(view, 'newBase', flatBase); refreshMLRDisplay(viewNum); % remove the temporary off file (actually we should leave the @@ -173,15 +180,15 @@ params.startVertex-1, params.radius+distanceInc, ... fullfile(params.path, params.innerCoordsFileName), ... fullfile(params.path, params.patchFileName))); - disppercent(inf); + mlrDispPercent(inf); % flatten the patch - disppercent(-inf, sprintf('(makeFlat) Flattening surface')); + mlrDispPercent(-inf, sprintf('(makeFlat) Flattening surface')); [degenFlag result] = system(sprintf('FlattenSurface.tcl %s %s %s', ... fullfile(params.path, params.outerCoordsFileName), ... fullfile(params.path, params.patchFileName), ... fullfile(params.path, params.flatFileName))); - disppercent(inf); + mlrDispPercent(inf); % if FlattenSurface failed, most likely b/c surfcut made a bad patch % increase the distance by one and try again. @@ -278,9 +285,9 @@ % run a modified version of the mrFlatMesh code % this outputs and flattened surface -disppercent(-inf,'(makeFlat) Calling flattenSurfaceMFM'); +mlrDispPercent(-inf,'(makeFlat) Calling flattenSurfaceMFM'); surf.flat = flattenSurfaceMFM(mesh, [params.x params.y params.z], params.radius,voxelSize'); -disppercent(inf); +mlrDispPercent(inf); % we need to figure out whether the flattened patch has been flipped % during flattening @@ -294,7 +301,7 @@ %hp = patch('vertices', v, 'faces', f, 'facecolor','none','edgecolor','black'); % loop through all of the faces -disppercent(-inf,'Checking winding direction'); +mlrDispPercent(-inf,'Checking winding direction'); wrapDir = zeros(1,length(f));wrapDirFlat = zeros(1,length(f)); for iFace = 1:length(f); % grab a triangle for inner 3D suface @@ -321,9 +328,9 @@ triFlatNorm = [0 0 1]; % same formula as above wrapDirFlat(iFace) = det([cat(2,triFlat, [1 1 1]'); triFlatNorm 1]); - disppercent(iFace/length(f)); + mlrDispPercent(iFace/length(f)); end -disppercent(inf); +mlrDispPercent(inf); % now check to see if the winding directions for the flat patch and 3D % surface are the same or different. Note that because of the diff --git a/mrLoadRet/Analysis/projectOutMeanVector.m b/mrLoadRet/Analysis/projectOutMeanVector.m index 265897cc1..c96a51841 100644 --- a/mrLoadRet/Analysis/projectOutMeanVector.m +++ b/mrLoadRet/Analysis/projectOutMeanVector.m @@ -146,7 +146,7 @@ % cycle over each segment of a concat. If this is a single % scan then we will be doing the whole thing in one pass -disppercent(-inf,sprintf('(projectOutMeanVector) Projecting out vector.')); +mlrDispPercent(-inf,sprintf('(projectOutMeanVector) Projecting out vector.')); for i = 1:length(frameNums) % get this mean vector @@ -197,9 +197,9 @@ end end targetROI.sourceMeanVector(i,:) = meanVector; - disppercent(i/length(frameNums)); + mlrDispPercent(i/length(frameNums)); end -disppercent(inf); +mlrDispPercent(inf); % and clear the tseries in the targetROI if we are passing back the % data as an array @@ -301,14 +301,14 @@ % and load the data roi.n = prod(dims); if isempty(tSeries) - disppercent(-inf,sprintf('(projectOutMeanVector) Loading tSeries for scan %s:%i',viewGet(v,'groupName'),viewGet(v,'curScan'))); + mlrDispPercent(-inf,sprintf('(projectOutMeanVector) Loading tSeries for scan %s:%i',viewGet(v,'groupName'),viewGet(v,'curScan'))); roi.tSeries = loadTSeries(v); else - disppercent(-inf,sprintf('(projectOutMeanVector) Using passed in tSeries for scan %s:%i',viewGet(v,'groupName'),viewGet(v,'curScan'))); + mlrDispPercent(-inf,sprintf('(projectOutMeanVector) Using passed in tSeries for scan %s:%i',viewGet(v,'groupName'),viewGet(v,'curScan'))); roi.tSeries = tSeries; end roi.tSeries = reshape(roi.tSeries,prod(dims(1:3)),size(roi.tSeries,4)); -disppercent(inf); +mlrDispPercent(inf); %%%%%%%%%%%%%%%%%%%%%%%%%%%% %% thisLoadROITSeries %% @@ -322,13 +322,13 @@ % get it from the passed in tSeries roi = loadROITSeries(v,roi,[],[],'loadType=none'); % get roi linear coords - disppercent(-inf,sprintf('(projectOutMeanVector) Extracting data for roi %s from passed in data',roi.name)); + mlrDispPercent(-inf,sprintf('(projectOutMeanVector) Extracting data for roi %s from passed in data',roi.name)); for frameNum = 1:size(tSeries,4) linearCoords = sub2ind(size(tSeries),roi.scanCoords(1,:),roi.scanCoords(2,:),roi.scanCoords(3,:),frameNum*ones(1,roi.n)); roi.tSeries(:,frameNum) = tSeries(linearCoords); - disppercent(frameNum/size(tSeries,4)); + mlrDispPercent(frameNum/size(tSeries,4)); end - disppercent(inf); + mlrDispPercent(inf); end %%%%%%%%%%%%%%%%%%%%%%% diff --git a/mrLoadRet/Analysis/transformStatistic.m b/mrLoadRet/Analysis/transformStatistic.m index dca018f11..1b1772f5a 100644 --- a/mrLoadRet/Analysis/transformStatistic.m +++ b/mrLoadRet/Analysis/transformStatistic.m @@ -1,5 +1,11 @@ -function [convertedStatistic, fdrAdjustedStatistic, fweAdjustedStatistic] = transformStatistic(p, outputPrecision, params) %[convertedStatistic, fdrAdjustedStatistic, fweAdjustedStatistic] = transformStatistic(p, outputPrecision, params) +% +% converts p values to corresponding Z values or -log10(p) and in addition corrects for multiple tests +% across all existing values using False Discovery Rate Step-up method and Hommel Bonferroni correction +% +% jb ??/??/2011? +% +function [convertedStatistic, fdrAdjustedStatistic, fweAdjustedStatistic] = transformStatistic(p, outputPrecision, params) if ieNotDefined('outputPrecision') outputPrecision='double'; @@ -31,7 +37,7 @@ convertedStatistic = convertStatistic(p, params.testOutput, outputPrecision); -if params.fdrAdjustment +if nargout>1 && params.fdrAdjustment fdrAdjustedP = p; for iTest = 1:size(p,4) fdrAdjustedP(:,:,:,iTest) = fdrAdjust(p(:,:,:,iTest),params); @@ -41,7 +47,7 @@ fdrAdjustedStatistic = []; end -if params.fweAdjustment +if nargout>2 && params.fweAdjustment fweAdjustedP = p; for iTest = 1:size(p,4) if ismember(params.fweMethod,{'Adaptive Step-down','Adaptive Single-step'}) @@ -60,7 +66,7 @@ function p = convertStatistic(p, outputStatistic, outputPrecision) switch(outputStatistic) - case 'Z value' + case {'Z value','Z'} p = double(p); % replace zeros by epsilon to avoid infinite values p(p==0) = 1e-16; @@ -70,7 +76,7 @@ %if there was no round-off error from cdf, we could do the following: %Z = max(-norminv(p),0); %because the left side of norminv seems to be less sensitive to round-off errors, %we get -norminv(x) instead of norminv(1-x). also we'renot interested in negative Z value - case '-log10(P) value' + case {'-log10(P) value', '-log10(P)'} p = double(p); % replace zeros by epsilon to avoid infinite values p(p==0) = 1e-16; diff --git a/mrLoadRet/Edit/editOverlayGUImrParams.m b/mrLoadRet/Edit/editOverlayGUImrParams.m index 18582b549..04697a55d 100644 --- a/mrLoadRet/Edit/editOverlayGUImrParams.m +++ b/mrLoadRet/Edit/editOverlayGUImrParams.m @@ -55,16 +55,70 @@ function editOverlayGUImrParams(viewNum) {'normal', 'setRangeToMax', 'setRangeToMaxAroundZero', 'setRangeToMaxAcrossSlices', 'setRangeToMaxAcrossSlicesAndScans'}); % colormaps - colormaps = {'default','hot','hsv','pink','cool','bone','copper','flag','gray','jet'}; + % first try to get all the predefined matlab color maps + % the following method only works up to Matlab v9.13 (2022b) + colormaps = cell(1,0); + fid = fopen(fullfile(matlabroot,'toolbox','matlab','graph3d','Contents.m')); + if fid>0 + contents = textscan(fid,'%% %s %s %*[^\n]'); + fclose(fid); + if ~isempty(contents) + colormapStart = find(ismember(contents{1},'Color') & ismember(contents{2},'maps.'))+1; + colormapEnd = find(ismember(contents{1},'') & ismember(contents{2},'')); + if ~isempty(colormapStart) && ~isempty(colormapEnd) + colormapEnd = colormapEnd(find(colormapEnd>colormapStart,1,'first'))-1; + colormaps = contents{1}(colormapStart:colormapEnd)'; + end + end + end + % here's another method that still works after v9.13 (but I don't know since what version) + % however colormaps are listed in alphabetical order rather than the above (usual) order + colorMapFiles = dir(fullfile(matlabroot,'toolbox','matlab','graphics','color')); % this the folder containing .m colormap files + colormaps = union(colormaps,regexprep({colorMapFiles.name}, '\.m', ''),'stable'); % we add the names after removing the extension + colormaps = setdiff(colormaps,{'resources','colororder','validatecolor','.','..'},'stable'); % remove matlab function names that are not colormaps + if isempty(colormaps) + colormaps = {'default','hot','hsv','pink','cool','bone','copper','flag','gray','jet'}; + else + colormaps = [{'default'},colormaps]; + end + % then get the colormaps saved in global variable MLR altColormaps = viewGet(thisView,'colormaps'); if ~isempty(altColormaps) - colormaps = {colormaps{:} altColormaps{:}}; + colormaps = [colormaps(:)' altColormaps(:)']; + end + % Also, get all colormap functions located in the mrLoadRet colormap folder + functionsDirectory = [fileparts(which('mrLoadRet')) '/colormapFunctions/']; + colormapFunctionFiles = dir([functionsDirectory '*.m']); + for iFile=1:length(colormapFunctionFiles) + colormapFunctions{iFile} = stripext(colormapFunctionFiles(iFile).name); + end + colormaps = union(colormaps,colormapFunctions,'stable'); + % and finally, add user-specified colormap folder + cmapFolders = mrGetPref('colormapPaths'); + if ~isempty(cmapFolders) + cmapFolders = mlrParseAdditionalArguments(cmapFolders,','); + for i=1:length(cmapFolders) + colormapFunctionFiles = dir(fullfile(cmapFolders{i}, '*.m')); + colormapFunctions = cell(0); + cFile = 0; + for iFile=1:length(colormapFunctionFiles) + try %make sure this is a colormap function + colormap = eval(sprintf('%s(%i)', stripext(colormapFunctionFiles(iFile).name), 256)); + if size(colormap,1)==256 && size(colormap,2)==3 + cFile = cFile+1; + colormapFunctions{cFile} = stripext(colormapFunctionFiles(iFile).name); + end + catch + end + end + colormaps = union(colormaps,colormapFunctions,'stable'); + end end % set up params dialog paramsInfo = {}; paramsInfo{end+1} = {'overlayCmap', colormaps,'type=popupmenu','List of possible colormaps'}; - paramsInfo{end+1} = {'userDefinedCmap','','Allows you to call a user defined function to set the overlap colormap. You can specify additional input arguments after the function name, separating with commas. The user-defined function must be on the path, accept an integer representing the number of colors as its last input argument, and output a params.numColors*3 RGB colormap'}; + paramsInfo{end+1} = {'userDefinedCmap','','Allows you to call a user defined function to set the overlap colormap. The user-defined function must output a params.numColors x 3 RGB colormap and can be either an anonymous function accepting the number of color as single input argument or a function on the Matlab path accepting the number of colors as its last argument. In the latter case, additional input arguments must be specified after the function name, separated with commas. '}; paramsInfo{end+1} = {'numColors', numColors, 'first argument to the colormap function'}; paramsInfo{end+1} = {'numGrays', 0, 'second argument to the colormap function'}; paramsInfo{end+1} = {'flipColormap', 0, 'type=checkbox', 'check this box to reverse the direction of the colormap'}; @@ -135,12 +189,12 @@ function mrCmapParamsCancel(oldOverlay,viewNum) %iff the overlay has changed, put the old overlay params back if ~isequalwithequalnans(oldOverlay,currentOverlay) - disppercent(-inf,'(editOverlayGUImrParams) Recomputing overlay'); + mlrDispPercent(-inf,'(editOverlayGUImrParams) Recomputing overlay'); % set the new overlay thisView = viewSet(thisView,'newOverlay', oldOverlay); % and refresh refreshMLRDisplay(thisView.viewNum); - disppercent(inf); + mlrDispPercent(inf); end @@ -262,36 +316,49 @@ function mrCmapCallback(params,viewNum) %parameters for which the overlay has to be recomputed as a whole (this should be changed by adding cases to viewSet) % set which color cmap to use if ~strcmp(params.overlayCmap, 'default') - if sum(strcmp(params.overlayCmap, {'hsvDoubleCmap','cmapExtendedHSV','cmapHSV','overlapCmap','redGreenCmap','rygbCmap','bicolorCmap','coolCmap'})) + if sum(strcmp(params.overlayCmap, {'hsvDoubleCmap','cmapExtendedHSV','overlapCmap','redGreenCmap','rygbCmap','bicolorCmap','coolCmap'})) newOverlay.colormap = eval(sprintf('%s(%i,%i)', params.overlayCmap, params.numGrays, params.numColors)); else - newOverlay.colormap = eval(sprintf('%s(%i)', params.overlayCmap, params.numColors)); + try + newOverlay.colormap = eval(sprintf('%s(%i)', params.overlayCmap, params.numColors)); + catch exception + mrWarnDlg(sprintf('(editOverlay) There was an error evaluating function %s.m:\n%s\n',params.overlayCmap,getReport(exception))); + return + end end end % see if we need to call a function if ~isempty(params.userDefinedCmap) %parse the function name and its arguments - cMapFunction=textscan(params.userDefinedCmap,'%s','delimiter',','); - cMapFunction=cMapFunction{1}; - - % look for the m function - if exist(sprintf('%s.m',cMapFunction{1}),'file') - cMapFunction{1} = str2func(cMapFunction{1}); %convert function string fo function handl - for iArg =2:length(cMapFunction) %if there are additional arguments, convert numerical ones - if ~isempty(str2num(cMapFunction{iArg})) - cMapFunction{iArg} = str2num(cMapFunction{iArg}); - end + if params.userDefinedCmap(1)=='@' % if this is an anonyous function + try + cMapfunction = str2func(params.userDefinedCmap); + colormap = cMapfunction(params.numColors); + catch + fprintf('(editOverlay) Anonymous function %s returned an error\n',params.userDefinedCmap); end - cMapFunction{end+1}=params.numColors; %add number of colors as last argument - colormap = callbackEval(cMapFunction); - if isequal(size(colormap),[params.numColors 3]) - newOverlay.colormap = colormap; - else - disp(sprintf('(editOverlay) Function %s must return a %ix%i array',params.userDefinedCmap,params.numColors,3)); + else + cMapFunction=textscan(params.userDefinedCmap,'%s','delimiter',','); + cMapFunction=cMapFunction{1}; + % look for the m function + if exist(sprintf('%s.m',cMapFunction{1}),'file') + cMapFunction{1} = str2func(cMapFunction{1}); %convert function string fo function handle + for iArg =2:length(cMapFunction) %if there are additional arguments, convert numerical ones + if ~isempty(str2num(cMapFunction{iArg})) + cMapFunction{iArg} = str2num(cMapFunction{iArg}); + end + end + cMapFunction{end+1}=params.numColors; %add number of colors as last argument + colormap = callbackEval(cMapFunction); end end - end + if isequal(size(colormap),[params.numColors 3]) + newOverlay.colormap = colormap; + else + fsprintf('(editOverlay) Function %s must return a %ix%i array\n',params.userDefinedCmap,params.numColors,3); + end + end % flip the cmap if params.flipColormap @@ -327,12 +394,12 @@ function mrCmapCallback(params,viewNum) %if the overlay has changed, if ~isequalwithequalnans(newOverlay,currentOverlay) - disppercent(-inf,'(editOverlayGUImrParams) Recomputing overlay'); + mlrDispPercent(-inf,'(editOverlayGUImrParams) Recomputing overlay'); % set the new overlay thisView = viewSet(thisView,'newOverlay', newOverlay); % and refresh refreshMLRDisplay(thisView.viewNum); - disppercent(inf); + mlrDispPercent(inf); end diff --git a/mrLoadRet/Edit/setFramePeriod.m b/mrLoadRet/Edit/setFramePeriod.m index 5b2776e3a..613a99f67 100644 --- a/mrLoadRet/Edit/setFramePeriod.m +++ b/mrLoadRet/Edit/setFramePeriod.m @@ -94,6 +94,7 @@ end % set the frameperiod hdr.pixdim(5) = framePeriod*1000; + %hdr.pixdim(5) = framePeriod; % and write it back hdr = cbiWriteNiftiHeader(hdr,filename); % set the scan params diff --git a/mrLoadRet/File/importGroupScans.m b/mrLoadRet/File/importGroupScans.m index 6e40bd913..53762f86e 100644 --- a/mrLoadRet/File/importGroupScans.m +++ b/mrLoadRet/File/importGroupScans.m @@ -1,14 +1,21 @@ % importGroupScans.m % -% usage: importGroupScans() +% usage: importGroupScans(params) % by: justin gardner % date: 04/11/07 -% purpose: +% purpose: import scans into a group in the current mrTools sesssion, from a group in a different (or the same) mrTools session. +% params is a structure with the following fields: +% - fromSession: path to the source mrTools session +% - fromGroup: Name of the source group +% - scanList: Scan numbers of scans to import +% - toGroup: Name of the destination group +% - linkFiles: Whether to link scan files instead of copying them (not availale on Windows) +% - hardLink: Whether to use a hard link instead of a soft link (not availale on Windows) % -function retval = importGroupScans() +function importGroupScans(params) % check arguments -if ~any(nargin == [0]) +if ~ismember(nargin, [0, 1]) help importGroupScans return end @@ -16,17 +23,24 @@ % new view toView = newView; -% go find the group that user wants to load here -pathStr = uigetdir(viewGet(toView,'homeDir'),'Select session you want to import from'); -if (pathStr==0) - deleteView(toView); - return +if ieNotDefined('params') + params = struct(); +end + +if fieldIsNotDefined(params,'fromSession') + % go find the group that user wants to load here + params.fromSession = uigetdir(viewGet(toView,'homeDir'),'Select session you want to import from'); + if (params.fromSession==0) + deleteView(toView); + return + end end + % now look for that sessions mrSession -mrSessionPath = fullfile(pathStr,'mrSession.mat'); +mrSessionPath = fullfile(params.fromSession,'mrSession.mat'); if ~mlrIsFile(mrSessionPath) - disp(sprintf('(importGroupScans) Could not find mrSession in %s',fileparts(pathStr))); + disp(sprintf('(importGroupScans) Could not find mrSession in %s',fileparts(params.fromSession))); disp(sprintf(' Make sure you clicked on the directory')); disp(sprintf(' with the mrSession.mat file (not the group')) disp(sprintf(' directory you wanted to import)')) @@ -37,13 +51,13 @@ % check for MLR 4 session mrSession = load(mrSessionPath); if ~isfield(mrSession,'session') || ~isfield(mrSession,'groups') - mrWarnDlg(sprintf('(importGroupScans) Unknown format for mrSession in %s',fileparts(pathStr))); + mrWarnDlg(sprintf('(importGroupScans) Unknown format for mrSession in %s',fileparts(params.fromSession))); deleteView(toView); return end clear mrSession -% get info from to group +% get info from destination group toHomeDir = viewGet(toView,'homeDir'); toGroupNames = viewGet(toView,'groupNames'); @@ -52,31 +66,48 @@ % we will then set back to the old MLR. Note that % while we have switch the MLR session we cannot % get info from the toView -fromView = switchSession(pathStr); +switchSession(params.fromSession); fromView = newView; -% get the groups in the import session -for gNum = 1:viewGet(fromView,'numGroups') - fromGroups{gNum} = sprintf('%s:%s (%i scans)',getLastDir(pathStr),viewGet(fromView,'groupName',gNum),viewGet(fromView,'numScans',gNum)); -end +if fieldIsNotDefined(params,'fromGroup') -% get from which and to which group we are doing -paramsInfo = {... - {'fromGroup',fromGroups,'type=popupmenu','The group to import from'},... - {'toGroup',toGroupNames,'type=popupmenu','The group to import into'},... - {'linkFiles',1,'type=checkbox','Link rather than copy the files. This will make a soft link rather than copying the files which saves disk space.'},... - {'hardLink',0,'type=checkbox','contingent=linkFiles','Use hard links when linking files instead of soft links.'}}; - -params = mrParamsDialog(paramsInfo); -if isempty(params) - switchSession; - deleteView(toView); - return + % get the groups in the import session + for gNum = 1:viewGet(fromView,'numGroups') + fromGroups{gNum} = sprintf('%s:%s (%i scans)',getLastDir(params.fromSession),viewGet(fromView,'groupName',gNum),viewGet(fromView,'numScans',gNum)); + end + + % get from which and to which group we are doing + paramsInfo = {... + {'fromGroup',fromGroups,'type=popupmenu','The group to import from'},... + {'toGroup',toGroupNames,'type=popupmenu','The group to import into'},... + {'linkFiles',~ispc,'type=checkbox',sprintf('enable=%d',~ispc),'Link rather than copy the files (Mac/Linux only). This will make a soft link rather than copying the files which saves disk space.'},... + {'hardLink',0,'type=checkbox',sprintf('enable=%d',~ispc),'contingent=linkFiles','(Mac/Linux only) Use hard links when linking files instead of soft links.'}}; + + inputParams = params; + params = mrParamsDialog(paramsInfo); + if isempty(params) + switchSession; + deleteView(toView); + return + end + + params.fromSession = inputParams.fromSession; + fromGroupNum = find(strcmp(params.fromGroup,fromGroups)); + +else + fromGroupNum = viewGet(fromView,'groupNum',params.fromGroup); end % get whether to link or not linkType = 0; if params.linkFiles + if ispc + mrWarnDlg('(importGroupScans) Linking scan files is not implemented on Windows'); + switchSession; + deleteView(toView); + return + end + % for hard links, pass 2 if params.hardLink linkType = 2; @@ -86,10 +117,9 @@ end % now set up some variables -fromGroupNum = find(strcmp(params.fromGroup,fromGroups)); fromGroup = viewGet(fromView,'groupName',fromGroupNum); toGroup = params.toGroup; -fromDir = fullfile(fullfile(pathStr,fromGroup),'TSeries'); +fromDir = fullfile(fullfile(params.fromSession,fromGroup),'TSeries'); if ~isdir(fromDir) mrWarnDlg(sprintf('(importGroupScans) Could not find directory %s',fromDir)); switchSession; @@ -104,23 +134,27 @@ deleteView(toView); return end -fromName = getLastDir(pathStr); +fromName = getLastDir(params.fromSession); + % set the group fromView = viewSet(fromView,'curGroup',fromGroupNum); -% choose the scans to import -selectedScans = selectInList(fromView,'scans','Choose scans to import'); -if isempty(selectedScans) +if fieldIsNotDefined(params,'scanList') + % choose the scans to import + params.scanList = selectInList(fromView,'scans','Choose scans to import'); +end + +if isempty(params.scanList) switchSession; deleteView(toView); return end % get the scan and aux paramters for the chosen scans -for i = 1:length(selectedScans) - fromScanParams(i) = viewGet(fromView,'scanParams',selectedScans(i)); - fromAuxParams(i) = viewGet(fromView,'auxParams',selectedScans(i)); +for i = 1:length(params.scanList) + fromScanParams(i) = viewGet(fromView,'scanParams',params.scanList(i)); + fromAuxParams(i) = viewGet(fromView,'auxParams',params.scanList(i)); % go through auxParams and get all fields if ~isempty(fromAuxParams(i)) % get names of aux params @@ -138,18 +172,18 @@ % get the stimfiles for the selected scans -for scanNum = 1:length(selectedScans) - stimFileName{scanNum} = viewGet(fromView,'stimFileName',selectedScans(scanNum)); +for scanNum = 1:length(params.scanList) + stimFileName{scanNum} = viewGet(fromView,'stimFileName',params.scanList(scanNum)); end % now switch back to old MLR session -switchSession; +toView = switchSession; % set the group toView = viewSet(toView,'currentGroup',toGroup); % now cycle over all scans in group -disppercent(-inf,'Copying group scans'); +mlrDispPercent(-inf,'Copying group scans'); r = 0; for scanNum = 1:length(fromScanParams) startTime = clock; @@ -170,7 +204,7 @@ toStimFileNames = {}; for stimFileNum = 1:length(stimFileName{scanNum}) % get the from and to stim file names - fromStimFileName = fullfile(pathStr, 'Etc', getLastDir(stimFileName{scanNum}{stimFileNum})); + fromStimFileName = fullfile(params.fromSession, 'Etc', getLastDir(stimFileName{scanNum}{stimFileNum})); toStimFileName = fullfile(viewGet(toView,'EtcDir'),getLastDir(stimFileName{scanNum}{stimFileNum})); % if it doesn't exist already, then copy it over if mlrIsFile(toStimFileName) @@ -199,9 +233,9 @@ disp(sprintf('(importGroupScans) Pause for one second to avoid having same exact timestamps')); pause(1); end - disppercent(scanNum/length(fromScanParams)); + mlrDispPercent(scanNum/length(fromScanParams)); end -disppercent(inf); +mlrDispPercent(inf); deleteView(toView); saveSession; @@ -212,7 +246,7 @@ %%%%%%%%%%%%%%%%%%%%%%% function v = switchSession(pathStr) -% switch to the MLR session found at "pathStr" +% switch to the MLR session found at "inputParams.fromSession" if (nargin == 1) % switch the path and globals mrGlobals; @@ -232,6 +266,7 @@ global MLR; global oldMLR; MLR = oldMLR; + clear global oldMLR end diff --git a/mrLoadRet/File/importOverlay.m b/mrLoadRet/File/importOverlay.m index 9092c0c4c..3c992ea1e 100644 --- a/mrLoadRet/File/importOverlay.m +++ b/mrLoadRet/File/importOverlay.m @@ -178,7 +178,11 @@ defaultOverlay.mergeFunction = 'defaultMergeParams'; defaultOverlay.colormapType = 'normal'; defaultOverlay.range = [min_overlay max_overlay]; -defaultOverlay.clip = [min_overlay max_overlay]; +if isfield(params,'min_overlay') + defaultOverlay.clip = [params.min_overlay max_overlay]; +else + defaultOverlay.clip = [min_overlay max_overlay]; +end for iFrame=1:nFrames numFrame = params.frameList(iFrame); diff --git a/mrLoadRet/File/importSurfaceOFF.m b/mrLoadRet/File/importSurfaceOFF.m index f70e0547e..2d2f3b6ef 100644 --- a/mrLoadRet/File/importSurfaceOFF.m +++ b/mrLoadRet/File/importSurfaceOFF.m @@ -1,13 +1,16 @@ % importSurfaceOFF.m % -% usage: v = importSurfaceOFF +% usage: v = importSurfaceOFF(,) % by: justin gardner % date: 10/24/07 -% purpose: Import a pair of inner and outer cortical surfaces in OFF format +% purpose: Import a pair of inner and outer cortical surfaces in OFF format, +% for one or both hemispheres % -% base = importSurfaceOFF('/path/to/surfaces/subject_left_WM.off', bothHemiFlag); -% or [base = importSurfaceOFF(params, bothHemiFlag); %where params is a structure similar to the output of mrSurfViewer with added field 'path'] -% viewSet(getMLRView, 'newbase', base); +% base = importSurfaceOFF % gets all surface parameters from a GUI, loads only one hemisphere (default bothHemiFlag = 0) +% or: base = importSurfaceOFF('/path/to/surfaces/subject_left_WM.off', true); % uses path as default for surface parameters in GUI, loads both hemispheres +% or: base = importSurfaceOFF(params); % where params is a structure similar to the output of mrSurfViewer with added field 'path', no GUI used +% or: base = importSurfaceOFF(paramsLeft,paramsRight); % where paramsLeft and paramsRight are params structures for left and right hemispheres respectively , no GUI used +% viewSet(getMLRView, 'newbase', base); % function base = importSurfaceOFF(pathStr,bothHemiFlag) @@ -17,11 +20,15 @@ return end base = []; -if ieNotDefined('bothHemiFlag'), +if ieNotDefined('bothHemiFlag') bothHemiFlag = 0; disp(sprintf('Only loading surfaces for one hemisphere')) else disp(sprintf('Loading surfaces for both hemispheres')) + if isstruct(bothHemiFlag) + params2 = bothHemiFlag; + bothHemiFlag = true; + end end if ieNotDefined('pathStr') @@ -35,17 +42,13 @@ % Aborted if ieNotDefined('pathStr'),return,end -if isstr(pathStr); +if isstr(pathStr) % get surface name using mrSurfViewer [filepath filename] = fileparts(pathStr); thispwd = pwd; if ~isempty(filepath),cd(filepath);end params1 = mrSurfViewer(filename); elseif isstruct(pathStr) %if pathStr is a parameter structure - if bothHemiFlag - mrWarnDlg('(importSurfaceOFF) option bothHemiFlag not implemented for parameter structure input') - return; - end params1=pathStr; filepath=pathStr.path; if strcmp(params1.outerCoords,'Same as surface') @@ -63,7 +66,7 @@ % Create the base base.hdr = mlrImageReadNiftiHeader(params1.anatomy); if isempty(base.hdr) - mrWarnDlg(sprintf('(imortSurfaceOFF) Could not load anatomy file: %s',params1.anatomy)); + mrWarnDlg(sprintf('(importSurfaceOFF) Could not load anatomy file: %s',params1.anatomy)); base = []; return end @@ -80,22 +83,24 @@ % load both hemispheres if bothHemiFlag - % get the params for the other hemisphere - % was a left hemisphere passed? + % was a left hemisphere passed first? leftFlag = strfind(lower(params1.innerSurface), 'left'); - if leftFlag - prefix = params1.innerSurface(1:leftFlag-1); - postfix = params1.innerSurface(leftFlag+4:end); - rightFilename = sprintf('%sright%s', prefix, postfix); - params2 = mrSurfViewer(rightFilename); - end - % or was it a right hemispehre + % or was it a right hemispehre? rightFlag = strfind(lower(params1.innerSurface), 'right'); - if rightFlag - prefix = params1.innerSurface(1:rightFlag-1); - postfix = params1.innerSurface(rightFlag+5:end); - leftFilename = sprintf('%sleft%s', prefix, postfix); - params2 = mrSurfViewer(leftFilename); + if ieNotDefined('params2') + % get the params for the other hemisphere + if leftFlag + prefix = params1.outerCoords(1:leftFlag-1); + postfix = params1.outerCoords(leftFlag+4:end); + rightFilename = sprintf('%sright%s', prefix, postfix); + params2 = mrSurfViewer(rightFilename); + end + if rightFlag + prefix = params1.outerCoords(1:rightFlag-1); + postfix = params1.outerCoords(rightFlag+5:end); + leftFilename = sprintf('%sleft%s', prefix, postfix); + params2 = mrSurfViewer(leftFilename); + end end % and add the second curvature data1(1,:,1) = loadVFF(params1.curv); @@ -104,26 +109,75 @@ % load both inner surfaces innerSurface1 = loadSurfOFF(params1.innerSurface); innerSurface2 = loadSurfOFF(params2.innerSurface); - innerSurface = combineSurfaces(innerSurface1, innerSurface2); % load both outer surfaces outerSurface1 = loadSurfOFF(params1.outerSurface); outerSurface2 = loadSurfOFF(params2.outerSurface); - outerSurface = combineSurfaces(outerSurface1, outerSurface2); % load the inner coords if strcmp(params1.innerCoords,'Same as surface') - inner = innerSurface; + inner1 = innerSurface1; + inner2 = innerSurface2; else inner1 = loadSurfOFF(params1.innerCoords); inner2 = loadSurfOFF(params2.innerCoords); - inner = combineSurfaces(inner1,inner2); end if strcmp(params1.outerCoords,'Same as surface') - outer = outerSurface; + outer1 = outerSurface1; + outer2 = outerSurface2; else outer1 = loadSurfOFF(params1.outerCoords); outer2 = loadSurfOFF(params2.outerCoords); - outer = combineSurfaces(inner1,inner2); end + % if loading inflated surfaces, shift them along X axis so that their medialmost vertex + % aligns with the medialmost vertex of the outer coordinates + if ~isempty(strfind(params1.outerSurface,'_Inf')) + if leftFlag + medialMostXCoord = max(outer1.vtcs(:,1)); %medialmost X of left hemisphere + outerSurface1.vtcs(:,1) = outerSurface1.vtcs(:,1) - max(outerSurface1.vtcs(:,1)) + medialMostXCoord; + elseif rightFlag + medialMostXCoord = min(outer1.vtcs(:,1)); %medialmost X of right hemisphere + outerSurface1.vtcs(:,1) = outerSurface1.vtcs(:,1) - min(outerSurface1.vtcs(:,1)) + medialMostXCoord; + else + keyboard % something is weird + end + end + if ~isempty(strfind(params2.outerSurface,'_Inf')) + if leftFlag + medialMostXCoord = min(outer2.vtcs(:,1)); %medialmost X of right hemisphere + outerSurface2.vtcs(:,1) = outerSurface2.vtcs(:,1) - min(outerSurface2.vtcs(:,1)) + medialMostXCoord; + elseif rightFlag + medialMostXCoord = max(outer2.vtcs(:,1)); %medialmost X of left hemisphere + outerSurface2.vtcs(:,1) = outerSurface2.vtcs(:,1) - max(outerSurface2.vtcs(:,1)) + medialMostXCoord; + else + keyboard % something is weird + end + end + if ~isempty(strfind(params1.innerSurface,'_Inf')) + if leftFlag + medialMostXCoord = max(outer1.vtcs(:,1)); %medialmost X of left hemisphere + innerSurface1.vtcs(:,1) = innerSurface1.vtcs(:,1) - max(innerSurface1.vtcs(:,1)) + medialMostXCoord; + elseif rightFlag + medialMostXCoord = min(outer1.vtcs(:,1)); %medialmost X of right hemisphere + innerSurface1.vtcs(:,1) = innerSurface1.vtcs(:,1) - min(innerSurface1.vtcs(:,1)) + medialMostXCoord; + else + keyboard % something is weird + end + end + if ~isempty(strfind(params2.innerSurface,'_Inf')) + if leftFlag + medialMostXCoord = min(outer2.vtcs(:,1)); %medialmost X of right hemisphere + innerSurface2.vtcs(:,1) = innerSurface2.vtcs(:,1) - min(innerSurface2.vtcs(:,1)) + medialMostXCoord; + elseif rightFlag + medialMostXCoord = max(outer2.vtcs(:,1)); %medialmost X of left hemisphere + innerSurface2.vtcs(:,1) = innerSurface2.vtcs(:,1) - max(innerSurface2.vtcs(:,1)) + medialMostXCoord; + else + keyboard % something is weird + end + end + % combine left and right surfaces + innerSurface = combineSurfaces(innerSurface1, innerSurface2); + outerSurface = combineSurfaces(outerSurface1, outerSurface2); + inner = combineSurfaces(inner1,inner2); + outer = combineSurfaces(outer1,outer2); else % or else a single hemisphere... @@ -176,6 +230,7 @@ cd(thispwd); + function val = getBaseField(matFilename,fieldname) if ~exist(matFilename,'file') % if the anatomy doesn't have these fields set val = []; @@ -185,7 +240,7 @@ end -function surf = combineSurfaces(surf1, surf2); +function surf = combineSurfaces(surf1, surf2) surf.filename = sprintf('%_%s', surf1.filename, surf2.filename); surf.Nvtcs = surf1.Nvtcs + surf2.Nvtcs; surf.Ntris = surf1.Ntris + surf2.Ntris; diff --git a/mrLoadRet/File/importTSeries.m b/mrLoadRet/File/importTSeries.m index 081fd4fee..37f1de7dd 100644 --- a/mrLoadRet/File/importTSeries.m +++ b/mrLoadRet/File/importTSeries.m @@ -38,7 +38,7 @@ end % go find the file that user wants to load here - [filename, pathname] = uigetfile({'*.nii;*.img','Nifti files'},'Select nifti tSeries that you want to import','multiselect','on'); + [filename, pathname] = uigetfile({'*.nii;*.img;*.nii.gz','Nifti files'},'Select nifti tSeries that you want to import','multiselect','on'); if isnumeric(filename) return @@ -64,7 +64,7 @@ for iFile = 1:length(filename) - if ~isempty(strfind(stripext(filename{iFile}),'.')) + if ~isempty(strfind(stripext(filename{iFile}),'.')) && isempty(strfind(filename{iFile},'.nii.gz')) %make an exception for gziped NIFTI files [~,name,extension] = fileparts(filename{iFile}); mrWarnDlg(sprintf('(importTSeries) Ignoring file %s because it has a . in the filename that does not mark the file extension. If you want to use this file, consider renaming to %s',filename{iFile},setext(fixBadChars(name,{'.','_'}),extension))); else @@ -94,6 +94,7 @@ paramsInfo{end+1} = {'description','','A description for the nifti tSeries you are imporint'}; paramsInfo{end+1} = {'nFrames',nFrames,'incdec=[-1 1]',sprintf('minmax=[0 %i]',nFrames),'Number of total frames in your nfiti tSeries'}; paramsInfo{end+1} = {'junkFrames',0,'incdec=[-1 1]',sprintf('minmax=[0 %i]',nFrames),'How many frames should be junked at the beginning'}; + paramsInfo{end+1} = {'overwrite',0,'type=checkbox','If checked, existing tseries with the same name is overwritten without prompt'}; if defaultParams params = mrParamsDefault(paramsInfo); @@ -111,7 +112,7 @@ % now read the file %tSeries = mlrImageReadNifti(fullFilename); - v = saveNewTSeries(v,fullFilename,thisScanParams); + v = saveNewTSeries(v,fullFilename,thisScanParams,[],[],params.overwrite); %get the new scan params and check that frame period is a reasonable value newScanNum = viewGet(v,'nScans'); diff --git a/mrLoadRet/File/loadAnat.m b/mrLoadRet/File/loadAnat.m index 0641dc09d..27e1fe709 100644 --- a/mrLoadRet/File/loadAnat.m +++ b/mrLoadRet/File/loadAnat.m @@ -1,4 +1,4 @@ -function [view anatFilePath] = loadAnat(view,anatFileName,anatFilePath) +function [view anatFilePath] = loadAnat(view,anatFileName,anatFilePath,frameNum) % % $Id$ % view = loadAnat(view,[anatFileName],[anatFilePath]) @@ -13,6 +13,9 @@ % anatFilePath is the path of where to open up the dialog. This % will bey returned so that the GUI can open up in the same % place each time. +% +% frame (optional): if the volume is 4D, the frame number can be specified. +% frame = 0 will average all frames % % djh, 1/9/98 % 5/2005, djh, update to mrLoadRet-4.0 @@ -99,21 +102,25 @@ % Handle 4D file if (volumeDimension == 4) - paramsInfo = {{'frameNum',0,'incdec=[-1 1]',sprintf('minmax=[0 %i]',hdr.dim(5)),'This volume is a 4D file, to display it as an anatomy you need to choose a particular time point or take the mean over all time points. Setting this value to 0 will compute the mean, otherwise you can select a particular timepoint to display'}}; - params = mrParamsDialog(paramsInfo,'Choose which frame of 4D file. 0 for mean'); - drawnow - if isempty(params) - return + if ieNotDefined('frameNum') || frameNum < 0 || frameNum > hdr.dim(5) + paramsInfo = {{'frameNum',0,'incdec=[-1 1]',sprintf('minmax=[0 %i]',hdr.dim(5)),'This volume is a 4D file, to display it as an anatomy you need to choose a particular time point or take the mean over all time points. Setting this value to 0 will compute the mean, otherwise you can select a particular timepoint to display'}}; + params = mrParamsDialog(paramsInfo,'Choose which frame of 4D file. 0 for mean'); + drawnow + if isempty(params) + return + end + frameNum = params.frameNum; end + % if frameNum is set to 0, take the mean - if params.frameNum == 0 + if frameNum == 0 % load the whole thing and average across volumes [vol hdr] = mlrImageLoad(pathStr{pathNum}); if isempty(vol),return,end vol = nanmean(vol,4); else % load a single volume - [vol hdr] = mlrImageLoad(pathStr{pathNum},'volNum',params.frameNum); + [vol hdr] = mlrImageLoad(pathStr{pathNum},'volNum',frameNum); if isempty(vol),return,end end else diff --git a/mrLoadRet/File/loadROITSeries.m b/mrLoadRet/File/loadROITSeries.m index fb8624b50..1662fdfac 100644 --- a/mrLoadRet/File/loadROITSeries.m +++ b/mrLoadRet/File/loadROITSeries.m @@ -164,16 +164,16 @@ % set the n rois{end}.n = length(x); % load the tseries, voxel-by-voxel - disppercent(-inf,sprintf('(loadROITSeries) Loading tSeries for %s from %s: %i',rois{end}.name,groupName,scanNum)); + mlrDispPercent(-inf,sprintf('(loadROITSeries) Loading tSeries for %s from %s: %i',rois{end}.name,groupName,scanNum)); % for now we always load by block, but if memory is an issue, we can % switch this if statement and load voxels indiviudally from file if strcmp(loadType,'vox') % load each voxel time series indiviudally for voxnum = 1:rois{end}.n rois{end}.tSeries(voxnum,:) = squeeze(loadTSeries(view,scanNum,s(voxnum),[],x(voxnum),y(voxnum))); - disppercent(voxnum/rois{end}.n); + mlrDispPercent(voxnum/rois{end}.n); end - disppercent(inf); + mlrDispPercent(inf); elseif strcmp(loadType,'block'); % load the whole time series as a block (i.e. a block including the min and max voxels) % this is usually faster then going back and loading each time series individually @@ -184,12 +184,12 @@ % now go through and pick out the voxels that we need. for voxnum = 1:rois{end}.n rois{end}.tSeries(voxnum,:) = squeeze(tSeriesBlock(x(voxnum)-min(x)+1,y(voxnum)-min(y)+1,s(voxnum)-min(s)+1,:)); - disppercent(voxnum/rois{end}.n); + mlrDispPercent(voxnum/rois{end}.n); end clear tSeriesBlock; - disppercent(inf); + mlrDispPercent(inf); else - disppercent(inf); + mlrDispPercent(inf); disp(sprintf('(loadROITSeries) Not loading time series (loadType=%s)',loadType)); end if ~strcmp(loadType, 'none') diff --git a/mrLoadRet/File/loadSession.m b/mrLoadRet/File/loadSession.m index e69d54f4d..13a477a84 100644 --- a/mrLoadRet/File/loadSession.m +++ b/mrLoadRet/File/loadSession.m @@ -1,7 +1,7 @@ -function [session, groups, version] = loadSession(dirPathStr) -% function [session, groups, mrLoadRetVersion] = loadSession([dirPathStr]) +function [session, groups, version, mniInfo] = loadSession(dirPathStr) +% function [session, groups, mrLoadRetVersion, mniInfo] = loadSession([dirPathStr]) % -% Loads the mrSESSION and groups structures from mrSESSION.mat. +% Loads the mrSESSION and groups structures from mrSESSION.mat, + MNI non-linear transform info. % % dirPathStr: directory that contains the mrSESSION.mat file % defaults to current directory (pwd) @@ -38,6 +38,9 @@ session = []; groups = []; end +if ieNotDefined('mniInfo') + mniInfo = []; % This optional field contains linear transformation matrix and non-linear coordinate maps to go from the "magnet" coordinates (i.e. canonical base coordinates) to MNI152 coordinates +end % check that all scanParams are valid. This is useful if the % scanParams optional fields have changed, so that old session diff --git a/mrLoadRet/File/makeEmptyMLRDir.m b/mrLoadRet/File/makeEmptyMLRDir.m index 786f5c09b..fc63b4ebd 100644 --- a/mrLoadRet/File/makeEmptyMLRDir.m +++ b/mrLoadRet/File/makeEmptyMLRDir.m @@ -10,7 +10,7 @@ % the default group made. % % You can also run this without bringing up a dialog with: -% makeEmptyMLRDir(dirname,'description=empty dir','subject=me','operator=you','defaultParams=1'); +% makeEmptyMLRDir(dirname,'description=empty dir','subject=me','operator=you','defaultParams=1','noPrompt=1'); % function retval = makeEmptyMLRDir(dirname,varargin) @@ -24,7 +24,7 @@ subject = ''; operator = ''; defaultParams = []; -getArgs(varargin, {'defaultGroup=Raw','description=','subject=','operator=','defaultParams=0'}); +getArgs(varargin, {'defaultGroup=Raw','description=','subject=','operator=','defaultParams=0','noPrompt=0'}); directories = {defaultGroup fullfile(defaultGroup,'TSeries') 'Anatomy' 'Etc'}; % dirname is a file, abort @@ -33,8 +33,8 @@ return end -% existing directory. Ask user what to do -if isdir(dirname) +% existing directory. Ask user what to do (unless called with 'noPrompt=1') +if isdir(dirname) && ~noPrompt if ~askuser(sprintf('(makeEmptyMLRDir) Directory %s exists, continue?',dirname)) return end @@ -92,8 +92,8 @@ % create groups variables groups.name = defaultGroup; groups.scanParams = []; -[tf groups] = isgroup(groups); +[tf, groups] = isgroup(groups); % save the mrSession -eval(sprintf('save %s session groups',fullfile(dirname,'mrSession.mat'))); +save(fullfile(dirname,'mrSession.mat'), 'session', 'groups'); diff --git a/mrLoadRet/File/mlrExportForAnalysis.m b/mrLoadRet/File/mlrExportForAnalysis.m index c24f66b2d..f207b8a82 100644 --- a/mrLoadRet/File/mlrExportForAnalysis.m +++ b/mrLoadRet/File/mlrExportForAnalysis.m @@ -100,9 +100,9 @@ % load the time series if params.fullTimeSeries - disppercent(-inf,'(mlrExportForAnalysis) Loading time series'); + mlrDispPercent(-inf,'(mlrExportForAnalysis) Loading time series'); output.tSeries = loadTSeries(v); - disppercent(inf); + mlrDispPercent(inf); else output.tSeries = []; end diff --git a/mrLoadRet/File/mlrExportOFF.m b/mrLoadRet/File/mlrExportOFF.m index f50239dbc..7a16ce12e 100644 --- a/mrLoadRet/File/mlrExportOFF.m +++ b/mrLoadRet/File/mlrExportOFF.m @@ -86,7 +86,7 @@ if ~isempty(vertexColors) % check if it is a filename if isstr(vertexColors) - if isfile(vertexColors) + if mlrIsFile(vertexColors) [data hdr] = loadVFF(vertexColors); if isempty(data),return,end vertexColors = data(:); @@ -134,38 +134,38 @@ function writeOBJ(outfile,vtcs,tris,vertexColors) if ~isempty(vertexColors) if size(vertexColors,2) == 1 % write vertices with grayscale - disppercent(-inf,'(mlrExportOFF) Writing vertices with grayscale'); + mlrDispPercent(-inf,'(mlrExportOFF) Writing vertices with grayscale'); for iVertex = 1:nVtcs fprintf(outfile,'v %f %f %f %f\n',vtcs(iVertex,1),vtcs(iVertex,2),vtcs(iVertex,3),vertexColors(iVertex,1)); - if mod(iVertex,5000) == 1,disppercent(iVertex/nVtcs);end + if mod(iVertex,5000) == 1,mlrDispPercent(iVertex/nVtcs);end end - disppercent(inf); + mlrDispPercent(inf); else % write vertices with rgb - disppercent(-inf,'(mlrExportOFF) Writing vertices with color'); + mlrDispPercent(-inf,'(mlrExportOFF) Writing vertices with color'); for iVertex = 1:nVtcs fprintf(outfile,'v %f %f %f %f %f %f\n',vtcs(iVertex,1),vtcs(iVertex,2),vtcs(iVertex,3),vertexColors(iVertex,1),vertexColors(iVertex,2),vertexColors(iVertex,3)); - if mod(iVertex,5000) == 1,disppercent(iVertex/nVtcs);end + if mod(iVertex,5000) == 1,mlrDispPercent(iVertex/nVtcs);end end - disppercent(inf); + mlrDispPercent(inf); end else % write vertices with no colors - disppercent(-inf,'(mlrExportOFF) Writing vertices'); + mlrDispPercent(-inf,'(mlrExportOFF) Writing vertices'); for iVertex = 1:nVtcs fprintf(outfile,'v %f %f %f\n',vtcs(iVertex,1),vtcs(iVertex,2),vtcs(iVertex,3)); - if mod(iVertex,5000) == 1,disppercent(iVertex/nVtcs);end + if mod(iVertex,5000) == 1,mlrDispPercent(iVertex/nVtcs);end end - disppercent(inf); + mlrDispPercent(inf); end % write faces -disppercent(-inf,'(mlrExportOFF) Writing faces'); +mlrDispPercent(-inf,'(mlrExportOFF) Writing faces'); for iTri = 1:nTris fprintf(outfile,'f %i %i %i\n',tris(iTri,1),tris(iTri,2),tris(iTri,3)); - if mod(iTri,5000) == 1,disppercent(iTri/nTris);end + if mod(iTri,5000) == 1,mlrDispPercent(iTri/nTris);end end -disppercent(inf); +mlrDispPercent(inf); %%%%%%%%%%%%%%%%%% % writeSTL % @@ -176,7 +176,7 @@ function writeSTL(outfile,vtcs,tris,surfaceName) fprintf(outfile,'solid %s\n',surfaceName); -disppercent(-inf,'(mlrExportOFF) Converting triangles'); +mlrDispPercent(-inf,'(mlrExportOFF) Converting triangles'); nTris = size(tris,1); for iTri = 1:nTris % get vertices @@ -194,9 +194,9 @@ function writeSTL(outfile,vtcs,tris,surfaceName) fprintf(outfile,' vertex %f %f %f\n',v3(1),v3(2),v3(3)); fprintf(outfile,' endloop\n'); fprintf(outfile,'endfacet\n'); - disppercent(iTri/nTris); + mlrDispPercent(iTri/nTris); end -disppercent(inf); +mlrDispPercent(inf); fprintf(outfile,'endsolid %s\n',surfaceName); diff --git a/mrLoadRet/File/mlrExportROI.m b/mrLoadRet/File/mlrExportROI.m index 019570240..06dd853a6 100644 --- a/mrLoadRet/File/mlrExportROI.m +++ b/mrLoadRet/File/mlrExportROI.m @@ -1,23 +1,28 @@ % mlrExportROI.m % % $Id$ -% usage: mlrExportROI(v,saveFilename,) +% usage: mlrExportROI(v,saveFilename,<'baseNum',baseNum>,<'scanNum',scanNum>,<'groupNum',groupNum>,<'hdr',hdr>,<'exportToFreesurferLabel',true/false>) % by: justin gardner % date: 07/14/09 -% purpose: Export an ROI to a nifti image. Uses current roi -% and current base in view to export. Pass in a nifti -% header as hdr argument if you want to use a different header +% purpose: Export current ROI(s) to a nifti image or Freesurfer label file. Uses +% current roi(s) and current base in view to export. To use a different +% base, specify 'baseNum'. To use a scan, specify 'scanNum' and 'groupNum'. +% Pass in a nifti header as hdr argument if you want to use a different header +% 'baseNum', 'scanNum', 'groupNum' and 'hdr' are ignored if exportToFreesurferLabel is true +% If saveFileName is empty, the data are not exported, but the ROI indices into the +% export volume are returned, along with the volume dimensions (useful to create an ROI mask +% in flat volume space, and maybe other spaces not handled by getROICoordinates). % -function mlrExportROI(v,saveFilename,varargin) +function [roiBaseCoordsLinear,volDims] = mlrExportROI(v,saveFilename,varargin) % check arguments -if nargin < 2 +if nargin < 1 help mlrExportROI return end % optional arguments -getArgs(varargin,{'hdr=[]'}); +getArgs(varargin,{'baseNum=[]','scanNum=[]','groupNum=[]','hdr=[]','exportToFreesurferLabel=0'}); % get the roi we are being asked to export roiNum = viewGet(v,'currentroi'); @@ -26,12 +31,31 @@ function mlrExportROI(v,saveFilename,varargin) return end -if ischar(saveFilename) - saveFileName = {saveFilename}; +if ~ieNotDefined('saveFilename') + if ischar(saveFilename) + saveFilename = {saveFilename}; + end + if ~isequal(length(roiNum),length(saveFilename)) + mrWarnDlg('(mlrExportROI) number of file names must be identical to number of ROIs'); + return + end +else + saveFilename = []; end -if ~isequal(length(roiNum),length(saveFilename)) - mrWarnDlg('(mlrExportROI) number of file names must be identical to number of ROIs'); - return + +if ~isempty(scanNum) && ~isempty(groupNum) + exportToScanSpace = true; + exportVolumeString = 'scan'; + baseCoordMap = []; + baseType = 0; +else + exportToScanSpace = false; + exportVolumeString = 'base'; + if isempty(baseNum) + baseNum = viewGet(v,'curBase'); + end + baseType = viewGet(v,'basetype',baseNum); + baseCoordMap = viewGet(v,'basecoordmap',baseNum); end % get the base nifti header @@ -39,101 +63,167 @@ function mlrExportROI(v,saveFilename,varargin) if ~isempty(hdr) passedInHeader = true; else - hdr = viewGet(v,'basehdr'); + if exportToScanSpace + hdr = viewGet(v,'niftiHdr',scanNum,groupNum); + else + hdr = viewGet(v,'basehdr',baseNum); + end if isempty(hdr) - mrWarnDlg('(mlrExportROI) Could not get base anatomy header'); + mrWarnDlg(sprintf('(mlrExportROI) Could not get %s header',exportVolumeString)); return end end -baseCoordMap = viewGet(v,'basecoordmap'); -baseType = viewGet(v,'basetype'); -if ~isempty(baseCoordMap) && baseType==1 %for flats, use basecoordmap - [~,baseCoords,baseCoordsHomogeneous] = getBaseSlice(v,1,3,viewGet(v,'baseRotate'),viewGet(v,'curBase'),baseType); - % make sure that baseCoords are rounded (they may not be - % if we are working with a baseCoordMap's flat map - baseDims = size(baseCoords); - baseDims = baseDims ([1 2 4]); +if ~ismember(baseType,[2]) && exportToFreesurferLabel + mrWarnDlg('(mlrExportROI) Load a surface in order to export to freesurfer label format'); + % for surfaces, the required list of surface vertices is in baseCoordMap + % for flat maps (or volumes), there is no easy access to the corresponding surface vertices, so returning + return; +end +if ~isempty(baseCoordMap) && (baseType==1 || exportToFreesurferLabel) %for flats, or when exporting to freesurfer label file, use basecoordmap - baseCoordsHomogeneous = reshape(baseCoordsHomogeneous,4,prod(baseDims)); + if baseType == 1 && ~exportToFreesurferLabel + [~,baseCoords,baseCoordsHomogeneous] = getBaseSlice(v,1,3,viewGet(v,'baseRotate',baseNum),baseNum,baseType); + else + baseCoords = permute(baseCoordMap.coords,[1 2 4 5 3]); + baseCoordsHomogeneous = [permute(reshape(baseCoordMap.coords, ... + [size(baseCoordMap.coords,1)*size(baseCoordMap.coords,2) size(baseCoordMap.coords,3) size(baseCoordMap.coords,4) size(baseCoordMap.coords,5)]),... + [3 1 4 2]); ones(1,size(baseCoordMap.coords,1)*size(baseCoordMap.coords,2),size(baseCoordMap.coords,5))]; + end + + volDims = size(baseCoords); + volDims = volDims ([1 2 4]); + if baseType==1 && ~exportToFreesurferLabel + mrWarnDlg(sprintf('(mlrExportROI) Exporting ROI(s) to flat space (%d x %d x %d voxels). If you do not want this, load base volume or surface',volDims(1),volDims(2),volDims(3))); + elseif exportToFreesurferLabel + mrWarnDlg('(mlrExportROI) Vertex coordinates in label file might be incorrect.'); + end + % make sure that baseCoords are rounded (they may not be if we are working with a baseCoordMap's flat map) + baseCoordsHomogeneous = reshape(baseCoordsHomogeneous,4,prod(volDims)); baseCoordsHomogeneous = round(baseCoordsHomogeneous); baseCoordsLinear = mrSub2ind(baseCoordMap.dims,baseCoordsHomogeneous(1,:),baseCoordsHomogeneous(2,:),baseCoordsHomogeneous(3,:)); - % estimate voxel size (taken from getBaseOverlay, assuming mask is invarioant to rotation, which it should be since it is a flat map) - oldBaseVoxelSize=viewGet(v,'basevoxelsize',viewGet(v,'curBase')); - Xcoords0Mask = permute(baseCoords(:,:,1,:)==0,[1 2 4 3]); - Xcoords0Mask = convn(Xcoords0Mask,ones(5,5,5),'same'); %expand the mask a bit to make sure we don't include any edge voxels - XcoordsNaN = permute(baseCoords(:,:,1,:),[1 2 4 3]); - XcoordsNaN(Xcoords0Mask>0)=NaN; - YcoordsNaN = permute(baseCoords(:,:,2,:),[1 2 4 3]); - YcoordsNaN(Xcoords0Mask>0)=NaN; - ZcoordsNaN = permute(baseCoords(:,:,3,:),[1 2 4 3]); - ZcoordsNaN(Xcoords0Mask>0)=NaN; - newBaseVoxelSize(1) = oldBaseVoxelSize(1)*nanmean(nanmean(nanmean(sqrt(diff(XcoordsNaN,1,1).^2 + diff(YcoordsNaN,1,1).^2 + diff(ZcoordsNaN,1,1).^2)))); - newBaseVoxelSize(2) = oldBaseVoxelSize(2)*nanmean(nanmean(nanmean(sqrt(diff(XcoordsNaN,1,2).^2 + diff(YcoordsNaN,1,2).^2 + diff(ZcoordsNaN,1,2).^2)))); - newBaseVoxelSize(3) = oldBaseVoxelSize(3)*nanmean(nanmean(nanmean(sqrt(diff(XcoordsNaN,1,3).^2 + diff(YcoordsNaN,1,3).^2 + diff(ZcoordsNaN,1,3).^2)))); - if any(newBaseVoxelSize ~= oldBaseVoxelSize) - hdr.pixdim = [0 newBaseVoxelSize 0 0 0 0]'; % all pix dims must be specified here - hdr.qform44 = diag([newBaseVoxelSize 0]); - hdr.sform44 = hdr.qform44; + if baseType==1 && ~exportToFreesurferLabel + % estimate voxel size (taken from getBaseOverlay, assuming mask is invariant to rotation, which it should be since it is a flat map) + oldBaseVoxelSize=viewGet(v,'basevoxelsize',baseNum); + Xcoords0Mask = permute(baseCoords(:,:,1,:)==0,[1 2 4 3]); + Xcoords0Mask = convn(Xcoords0Mask,ones(5,5,5),'same'); %expand the mask a bit to make sure we don't include any edge voxels + XcoordsNaN = permute(baseCoords(:,:,1,:),[1 2 4 3]); + XcoordsNaN(Xcoords0Mask>0)=NaN; + YcoordsNaN = permute(baseCoords(:,:,2,:),[1 2 4 3]); + YcoordsNaN(Xcoords0Mask>0)=NaN; + ZcoordsNaN = permute(baseCoords(:,:,3,:),[1 2 4 3]); + ZcoordsNaN(Xcoords0Mask>0)=NaN; + newBaseVoxelSize(1) = oldBaseVoxelSize(1)*nanmean(nanmean(nanmean(sqrt(diff(XcoordsNaN,1,1).^2 + diff(YcoordsNaN,1,1).^2 + diff(ZcoordsNaN,1,1).^2)))); + newBaseVoxelSize(2) = oldBaseVoxelSize(2)*nanmean(nanmean(nanmean(sqrt(diff(XcoordsNaN,1,2).^2 + diff(YcoordsNaN,1,2).^2 + diff(ZcoordsNaN,1,2).^2)))); + newBaseVoxelSize(3) = oldBaseVoxelSize(3)*nanmean(nanmean(nanmean(sqrt(diff(XcoordsNaN,1,3).^2 + diff(YcoordsNaN,1,3).^2 + diff(ZcoordsNaN,1,3).^2)))); + if any(newBaseVoxelSize ~= oldBaseVoxelSize) + hdr.pixdim = [0 newBaseVoxelSize 0 0 0 0]'; % all pix dims must be specified here + hdr.qform44 = diag([newBaseVoxelSize 0]); + hdr.sform44 = hdr.qform44; + end end - else - baseDims = hdr.dim(2:4)'; + volDims = hdr.dim(2:4)'; end -if ~passedInHeader - b = viewGet(v,'base'); +if ~passedInHeader && ~exportToFreesurferLabel + b = viewGet(v,'base',baseNum); end for iRoi = 1:length(roiNum) + roiName = viewGet(v,'roiName',roiNum(iRoi)); % tell the user what is going on - disp(sprintf('(mlrExportROI) Exporting ROI to %s with dimensions set to match base %s: [%i %i %i]',saveFilename{iRoi},viewGet(v,'baseName'),baseDims(1),baseDims(2),baseDims(3))); - - % create a data structure that has all 0's - d = zeros(baseDims); - - % get roi coordinates in base coordinates - roiBaseCoords = getROICoordinates(v,roiNum(iRoi),0); + if ~isempty(saveFilename) + fileString = sprintf('to %s ',saveFilename{iRoi}); + else + fileString = ''; + end + fprintf('(mlrExportROI) Exporting ROI %s %swith dimensions set to match %s: [%i %i %i]\n',roiName,fileString,exportVolumeString,volDims(1),volDims(2),volDims(3)); - % check roiBaseCoords - if isempty(roiBaseCoords) - mrWarnDlg('(mlrExportROI) This ROI does not have any coordinates in the base'); - return + % get roi coordinates in base/scan coordinates + if exportToScanSpace + roiBaseCoords = getROICoordinates(v,roiNum(iRoi),scanNum,groupNum); + else + roiBaseCoords = getROICoordinates(v,roiNum(iRoi),0,[],sprintf('baseNum=%d',baseNum)); end - % make sure we are inside the base dimensions - xCheck = (roiBaseCoords(1,:) >= 1) & (roiBaseCoords(1,:) <= hdr.dim(2)); - yCheck = (roiBaseCoords(2,:) >= 1) & (roiBaseCoords(2,:) <= hdr.dim(3)); - sCheck = (roiBaseCoords(3,:) >= 1) & (roiBaseCoords(3,:) <= hdr.dim(4)); + if ~exportToFreesurferLabel + % create a data structure that has all 0's + d = zeros(volDims); - % only use ones that are in bounds - roiBaseCoords = roiBaseCoords(:,xCheck & yCheck & sCheck); - % convert to linear coordinates - roiBaseCoordsLinear = mrSub2ind(hdr.dim(2:4)',roiBaseCoords(1,:),roiBaseCoords(2,:),roiBaseCoords(3,:)); + % make sure we are inside the base dimensions + xCheck = (roiBaseCoords(1,:) >= 1) & (roiBaseCoords(1,:) <= hdr.dim(2)); + yCheck = (roiBaseCoords(2,:) >= 1) & (roiBaseCoords(2,:) <= hdr.dim(3)); + sCheck = (roiBaseCoords(3,:) >= 1) & (roiBaseCoords(3,:) <= hdr.dim(4)); - if ~isempty(baseCoordMap) && baseType==1 %for flats, use basecoordmap to transform ROI from canonical base to multi-depth flat map - roiBaseCoordsLinear = ismember(baseCoordsLinear,roiBaseCoordsLinear); + % only use ones that are in bounds + roiBaseCoords = roiBaseCoords(:,xCheck & yCheck & sCheck); end - % set all the roi coordinates to 1 - d(roiBaseCoordsLinear) = 1; - % if the orientation has been changed in loadAnat, undo that here. - if ~isempty(b.originalOrient) - end - % now save the nifti file - if ~passedInHeader && ~isempty(b.originalOrient) - % convert into mlrImage - [d, h] = mlrImageLoad(d,hdr); - % convert the orientation back to original - [d, h] = mlrImageOrient(b.originalOrient,d,h); - % convert back to nifti - reorientedHdr = mlrImageGetNiftiHeader(h); - cbiWriteNifti(saveFilename{iRoi},d,reorientedHdr); + if ~isempty(baseCoordMap) && (baseType==1 || exportToFreesurferLabel) %for flats and surfaces, use basecoordmap to transform ROI from canonical base to multi-depth flat map + roiBaseCoordsLinear{iRoi} = mrSub2ind(baseCoordMap.dims',roiBaseCoords(1,:),roiBaseCoords(2,:),roiBaseCoords(3,:)); + roiBaseCoordsLinear{iRoi} = ismember(baseCoordsLinear,roiBaseCoordsLinear{iRoi}); else - cbiWriteNifti(saveFilename{iRoi},d,hdr); + % convert to linear coordinates + roiBaseCoordsLinear{iRoi} = mrSub2ind(hdr.dim(2:4)',roiBaseCoords(1,:),roiBaseCoords(2,:),roiBaseCoords(3,:)); + end + + % check roiBaseCoords + if isempty(roiBaseCoords) || ~nnz(roiBaseCoordsLinear{iRoi}) + mrWarnDlg(sprintf('(mlrExportROI) This ROI (%s) does not have any coordinates in the %s',roiName,exportVolumeString)); + + elseif ~isempty(saveFilename) + + if exportToFreesurferLabel + % in order to export to label format, select vertices that are within the ROI + + % reshape to vertices * depths + roiBaseCoordsLinear{iRoi} = reshape(roiBaseCoordsLinear{iRoi}, [size(baseCoordMap.coords,1)*size(baseCoordMap.coords,2) size(baseCoordMap.coords,5)]); + % Multiple cortical depths are not taken into account: a vertex can be in the ROI at any depth: + % but let's be conservative and consider only ROI voxels in the central part of cortical ribbon + nDepths = size(roiBaseCoordsLinear{iRoi},2); + roiBaseCoordsLinear{iRoi} = find(any(roiBaseCoordsLinear{iRoi}(:,ceil((nDepths-1)/4)+1:floor(3*(nDepths-1)/4)+1),2)); + %actual coordinates in label file will be midway between inner and outer surface + vertexCoords = (baseCoordMap.innerVtcs(roiBaseCoordsLinear{iRoi},:)+baseCoordMap.outerVtcs(roiBaseCoordsLinear{iRoi},:))/2; + % change vertex coordinates to freesurfer system: 0 is in the middle of the volume and coordinates are in mm + % (this does not seem to give the correct coordinates, but coordinates are usually not needed in label files) + vertexCoords = (vertexCoords - repmat(baseCoordMap.dims/2 ,size(vertexCoords,1),1)) ./ repmat(hdr.pixdim([2 3 4])',size(vertexCoords,1),1); + % (this assumes that the base volume for this surface is either the original Freesurfer volume, or has been cropped symmetrically, which is usually the case) + + % find freesurfer subject name + if isempty(which('extractBetween')) + freesurferName = viewGet(v,'subject'); % this is an old version of Matlab and I can't be bothered to find a replacement for extractBetween() + else + freesurferName = extractBetween(baseCoordMap.path,'subjects\','\surfRelax'); + if isempty(freesurferName) + freesurferName= viewGet(v,'subject'); + elseif iscell(freesurferName) %in newer versions of Matlab, extractBetween may return a cell array + freesurferName = freesurferName{1}; + end + end + labelHeader = sprintf('#!ascii label, ROI exported from subject %s using mrTools (mrLoadRet v%.1f)\n', freesurferName, mrLoadRetVersion); + % a Freesurfer label file is text file with a list of vertex numbers and coordinates + mlrWriteFreesurferLabel(saveFilename{iRoi},labelHeader,[roiBaseCoordsLinear{iRoi}-1, vertexCoords, ones(size(vertexCoords,1),1)]) + else + % set all the roi coordinates to 1 + d(roiBaseCoordsLinear{iRoi}) = 1; + + % if the orientation has been changed in loadAnat, undo that here. + if ~passedInHeader && ~isempty(b.originalOrient) + % convert into mlrImage + [d, h] = mlrImageLoad(d,hdr); + % convert the orientation back to original + [d, h] = mlrImageOrient(b.originalOrient,d,h); + % convert back to nifti + reorientedHdr = mlrImageGetNiftiHeader(h); + cbiWriteNifti(saveFilename{iRoi},d,reorientedHdr); + else + cbiWriteNifti(saveFilename{iRoi},d,hdr); + end + end end end diff --git a/mrLoadRet/File/mlrLoadLastView.m b/mrLoadRet/File/mlrLoadLastView.m index 2837f4c6c..1b61a1b07 100644 --- a/mrLoadRet/File/mlrLoadLastView.m +++ b/mrLoadRet/File/mlrLoadLastView.m @@ -24,12 +24,12 @@ return end -if nargin == 0; +if nargin == 0 filename = 'mrLastView.mat'; end filename = setext(filename,'mat'); -if ~mlrIsFile(filename); +if ~mlrIsFile(filename) mrWarnDlg('(mlrLoadLastView) Could not find %s',filename); return end @@ -46,15 +46,7 @@ if isfield(check,'viewSettings') && isfield(check.viewSettings,'version') && (check.viewSettings.version>=2.0) % then we are ok, load the view part l = load(filename,'view'); - % return them both - if nargout == 1 - % return as single argument - v = l; - else - % or as two - v = l.view; - viewSettings = check.viewSettings; - end + l.viewSettings = check.viewSettings; else if verLessThan('matlab','8.4') % it can load, but will give lots of warnings, so tell user what is going on @@ -65,22 +57,13 @@ % mrWarnDlg(sprintf('(mlrLoadLastView) The mrLastView found: %s is from an older version of matlab, you will likely see a bunch of weird warnings here, but ignore them - they have to do with the latest matlab not having the ability to load old mat files that had figure handles in them. Send complaints to Mathworks!',filename)); % this causes lots of weird warnings, but doesn't seem to crash if isfield(l,'view') && isfield(l.view,'figure') - if ishandle(l.view.figure) - close(l.view.figure); - end - l.view.figure = []; - end - % return as either one or two arguments - if nargout == 1 - % return as single argument - v = l; - else - % or as two - v = l.view; - viewSettings = l.viewSettings; + if ishandle(l.view.figure) + close(l.view.figure); + end + l.view.figure = []; end else - % serious problems occur in 8.5 + % serious problems occur starting at 8.5 mrWarnDlg(sprintf('(mlrLoadLastView) The mrLastView found: %s is from an older version of matlab which allowed saving figure handles. The geniuses at Mathworks have busted that, so loading this file will no longer work. Moving mrLastView.mat to mrLastView.mat.old You will lose any rois that were loaded but not saved and mrLoadRet will start up without bases and analyses loaded. If you really need what was in the viewer we suggest running on an earlier version of matlab - you just then need to copy mrLastView.mat.old back to mrLastView.mat, open mrLoadRet and then quit - this will save the file back w/out the offending figure handles. Once this is done, you should be able to run the newer version of matlab with the new mrLastView.',filename),'Yes'); movefile(filename,sprintf('%s.old',filename)); return @@ -92,12 +75,16 @@ else % otherwise just load l = load('mrLastView'); - if nargout == 1 - v = l; - else - v = l.view; - viewSettings = l.viewSettings; - end +end + +% return as either one or two arguments +if nargout == 1 + % return as single argument + v = l; +else + % or as two + v = l.view; + viewSettings = l.viewSettings; end diff --git a/mrLoadRet/File/mrExport2SR.m b/mrLoadRet/File/mrExport2SR.m index 3db320617..432d318c8 100644 --- a/mrLoadRet/File/mrExport2SR.m +++ b/mrLoadRet/File/mrExport2SR.m @@ -1,62 +1,57 @@ -function[] = mrExport2SR(viewNum, pathstr) +function [overlayData,hdr] = mrExport2SR(viewNum, pathstr, baseNum) % mrExport2SR.m % % usage: [] = mrExprt2SR(viewNum, pathstr) % by: eli merriam % date: 03/20/07 -% purpose: exports a MLR overlay to a Nifti file compatible with SurfRelax -% $Id$ +% purpose: exports a MLR overlay to a Nifti file in base space (compatible with SurfRelax for surfaces) +% if baseNum is 0, the overlay is exported to scan space % -% modified by julien besle 22/01/2010 to speed up things and take getBaseSpaceOverlay.m out - -%mrGlobals % Get view -view = viewGet(viewNum,'view'); - -% Get values from the GUI -scanNum = viewGet(view,'curscan'); -baseNum = viewGet(view,'currentBase'); -overlayNum = viewGet(view,'currentOverlay'); -overlayData = viewGet(view,'overlayData',scanNum,overlayNum); - - -%basedims = viewGet(view, 'basedims'); +thisView = viewGet(viewNum,'view'); -%transform values in base space -[new_overlay_data, new_base_voxel_size] = getBaseSpaceOverlay(view, overlayData, scanNum, baseNum); +if ieNotDefined('baseNum') + baseNum = viewGet(thisView,'currentBase'); +end -if isempty(new_overlay_data) - return +% Get values from the GUI +scanNum = viewGet(thisView,'curscan'); +overlayNum = viewGet(thisView,'currentOverlay'); +overlayData = viewGet(thisView,'overlayData',scanNum,overlayNum); + +if baseNum + %transform values in base space + [overlayData, new_base_voxel_size] = getBaseSpaceOverlay(thisView, overlayData, scanNum, baseNum); + if isempty(overlayData) + return + end + hdr = viewGet(thisView,'basehdr'); + if any(new_base_voxel_size ~= viewGet(thisView,'basevoxelsize',baseNum)) + hdr.pixdim = [0 new_base_voxel_size 0 0 0 0]'; % all pix dims must be specified here + hdr.qform44 = diag([new_base_voxel_size 0]); + hdr.sform44 = hdr.qform44; + end +else + hdr = viewGet(thisView,'niftihdr',scanNum); + hdr.dim(5) = length(overlayNum); end -%write nifti file -baseVolume = viewGet(viewNum,'baseVolume'); -hdr = baseVolume.hdr; -%hdr.dim = [size(size(new_overlay_data),2); size(new_overlay_data,1); size(new_overlay_data,2); size(new_overlay_data,3); 1; 1; 1; 1]; -hdr.bitpix = 32; -hdr.datatype = 16; -hdr.is_analyze = 1; + +hdr.datatype = 16; % make sure data are written as float32 (single) hdr.scl_slope = 1; -hdr.endian = 'l'; -if any(new_base_voxel_size ~= viewGet(view,'basevoxelsize',baseNum)) - hdr.pixdim = [0 new_base_voxel_size 0 0 0 0]'; % all pix dims must be specified here - hdr.qform44 = diag([new_base_voxel_size 0]); - hdr.sform44 = hdr.qform44; +hdr.scl_inter = 0; +if viewGet(thisView,'baseType',baseNum)==2 %for surfaces, leave as it was in the original mrExport2SR + hdr.is_analyze = 1; + hdr.endian = 'l'; end - + % set the file extension niftiFileExtension = mrGetPref('niftiFileExtension'); if isempty(niftiFileExtension) niftiFileExtension = '.img'; end -cbiWriteNifti(sprintf('%s%s',stripext(pathstr),niftiFileExtension), new_overlay_data,hdr); - -return - - - - - - - +if ~ieNotDefined('pathstr') || nargout>0 + %write nifti file + mlrImageWriteNifti(sprintf('%s%s',stripext(pathstr),niftiFileExtension),overlayData,hdr) +end diff --git a/mrLoadRet/File/saveAnat.m b/mrLoadRet/File/saveAnat.m index d9c0b9525..2f5b4cadc 100644 --- a/mrLoadRet/File/saveAnat.m +++ b/mrLoadRet/File/saveAnat.m @@ -1,4 +1,4 @@ -function pathStr = saveAnat(view,anatomyName,confirm,saveAs,savePath) +function pathStr = saveAnat(view,anatomyName,confirm,saveAs,savePath,exportCorticalDepths) % % saveAnat(view,[anatomyName],[confirm],[saveAs],[savePath]) % @@ -10,6 +10,7 @@ % Default: uses 'overwritePolicy' preference. % saveAs: If 1, then asks user for where to put anatomy (default=0) % savePath: If set then saves the anatomy to the specified path +% exportCorticalDepths: if true, repeat the exported values number of cortical depth steps (only for flat maps) % % % @@ -41,24 +42,41 @@ savePath = []; end +if ieNotDefined('exportCorticalDepths') + exportCorticalDepths = false; +end + % Extract data and hdr baseVolume = viewGet(view,'baseVolume',anatomyNum); switch viewGet(view,'baseType',anatomyNum) case 0 data = baseVolume.data; + case 1 - % jg: The code below this line does not work - and I don't know why it was added or what - % it is supposed to do (is this Julien's addition?), so commenting out as it crashes in r2015a. Replacing - % with a more straightforward call - data = baseVolume.data; - %if flat, rotate and repeat map number-of-depths times -% repeatVector = [1 1 1]; -% repeatVector(viewGet(view,'basesliceindex',anatomyNum))= mrGetPref('corticalDepthBins'); -% nanData=isnan(baseVolume.data); -% data = repmat(mrImRotate(baseVolume.data,viewGet(view,'rotate'),'bilinear','crop'),repeatVector); -% data(repmat(nanData,repeatVector))=NaN; +% % % jg: The code below this line does not work - and I don't know why it was added or what +% % % it is supposed to do (is this Julien's addition?), so commenting out as it crashes in r2015a. Replacing +% % % with a more straightforward call +% % data = baseVolume.data; + +% % jb, 24/08/2022: the code below repeats the flat base values (e.g. curvature) as many times as there are cortical depth steps +% % (now only if exportCorticalDepths is true) +% % it also applies the flat rotation value before exporting so that the exported volume matches what is shown in mrLoadRet +% % the latter behaviour is consistent with how mrExport2SR exports overlays on flat maps + + %if flat, rotate asccording to the current rotate value + data = mrImRotate(baseVolume.data,viewGet(view,'rotate'),'bilinear','crop'); + + if exportCorticalDepths % repeat map number-of-depths times + repeatVector = [1 1 1]; + repeatVector(viewGet(view,'basesliceindex',anatomyNum))= mrGetPref('corticalDepthBins'); + data = repmat(data,repeatVector); + nanData=isnan(baseVolume.data); + data(repmat(nanData,repeatVector))=NaN; + end + case 2 data = baseVolume.data; + end hdr = baseVolume.hdr; diff --git a/mrLoadRet/File/saveNewTSeries.m b/mrLoadRet/File/saveNewTSeries.m index abfe81794..3e10421ff 100644 --- a/mrLoadRet/File/saveNewTSeries.m +++ b/mrLoadRet/File/saveNewTSeries.m @@ -1,4 +1,4 @@ -function [view,filename] = saveNewTSeries(view,tseries,scanParams,hdr,makeLink) +function [view,filename] = saveNewTSeries(view,tseries,scanParams,hdr,makeLink,overwrite) % % view = saveNewTSeries(view,tseries,[scanParams],[hdr],[makeLink]) % @@ -22,12 +22,12 @@ % makeLink: Optional, if tseries is passed as a filename then setting % this to 1 will cause the tseries to be linked rather than copied % defaults to 0 -% -% $Id$ +% overwrite: Optional, if tseries with identical name already exists in +% folder, setting this to 1 will overwrite without user prompt % % check arguments -if ~any(nargin == [1:5]) +if ~any(nargin == [1:6]) help saveNewTSeries return end @@ -43,6 +43,7 @@ hdr = []; end if ieNotDefined('makeLink'),makeLink=0;end +if ieNotDefined('overwrite'),overwrite=0;end if isempty(scanParams.fileName) if ischar(tseries) @@ -61,7 +62,7 @@ % see if the tseries is actually a string in which case we should % copy the nifti file. if ischar(tseries) - success = copyNiftiFile(tseries,path,makeLink); + success = copyNiftiFile(tseries,path,makeLink,overwrite); if ~success,return,end % get the number of frames diff --git a/mrLoadRet/File/saveROI.m b/mrLoadRet/File/saveROI.m index 836d24529..6ddfa2b3f 100644 --- a/mrLoadRet/File/saveROI.m +++ b/mrLoadRet/File/saveROI.m @@ -47,6 +47,11 @@ myErrorDlg(['Bad ROI name: ',roiName]); end +if isempty(roiNum) + mrWarnDlg(sprintf('(saveROI) Could not find ROI %s',roiName)); + return; +end + % Assign local variable with roiName = roi roi = viewGet(view,'roi',roiNum); % fix characters that are not allowed in variable names diff --git a/mrLoadRet/File/saveSession.m b/mrLoadRet/File/saveSession.m index 6c294eeb2..9bc2aaf6e 100644 --- a/mrLoadRet/File/saveSession.m +++ b/mrLoadRet/File/saveSession.m @@ -31,4 +31,5 @@ function saveSession(confirm) session = MLR.session; groups = MLR.groups; -save(pathStr,'session','groups'); +mniInfo = MLR.mniInfo; +save(pathStr,'session','groups','mniInfo'); diff --git a/mrLoadRet/File/scanInfo.m b/mrLoadRet/File/scanInfo.m index 5171b0977..db5c03916 100644 --- a/mrLoadRet/File/scanInfo.m +++ b/mrLoadRet/File/scanInfo.m @@ -46,7 +46,7 @@ end % grab info -disppercent(-inf,'(scanInfo) Gathering scan info'); +mlrDispPercent(-inf,'(scanInfo) Gathering scan info'); description = viewGet(view,'description',scanNum,groupNum); scanVoxelSize = viewGet(view,'scanVoxelSize',scanNum,groupNum); tr = viewGet(view,'TR',scanNum,groupNum); @@ -138,14 +138,14 @@ paramsInfo{end+1} = {extraFields{i} extraFieldsValue{i} 'editable=0','Parameter from associated mat file'}; end - disppercent(inf); + mlrDispPercent(inf); if (nargout == 1) retval = mrParamsDefault(paramsInfo); else mrParamsDialog(paramsInfo,'Scan info',nCols); end else - disppercent(inf); + mlrDispPercent(inf); disp(sprintf('%s',description)); disp(sprintf('Filename: %s GroupName: %s',filename,groupName)); for i = 1:length(originalFilename) diff --git a/mrLoadRet/GUI/getBaseSlice.m b/mrLoadRet/GUI/getBaseSlice.m index 6c722403d..ad23d0fd8 100644 --- a/mrLoadRet/GUI/getBaseSlice.m +++ b/mrLoadRet/GUI/getBaseSlice.m @@ -12,6 +12,23 @@ baseCoordsHomogeneous = []; baseIm = []; +%get defaults in put arguments +if ieNotDefined('baseNum') + baseNum = viewGet(view,'curBase'); +end +if ieNotDefined('baseType') + baseType = viewGet(view,'baseType',baseNum); +end +if ieNotDefined('rotate') + rotate = viewGet(view,'rotate'); +end +if ieNotDefined('sliceNum') + sliceNum = viewGet(view,'curslice',baseNum); +end +if ieNotDefined('sliceIndex') + sliceIndex = viewGet(view,'baseSliceIndex',baseNum); +end + % viewGet volSize = viewGet(view,'baseDims',baseNum); baseData = viewGet(view,'baseData',baseNum); diff --git a/mrLoadRet/GUI/getBaseSpaceOverlay.m b/mrLoadRet/GUI/getBaseSpaceOverlay.m index 8f8117a80..c8197e67c 100644 --- a/mrLoadRet/GUI/getBaseSpaceOverlay.m +++ b/mrLoadRet/GUI/getBaseSpaceOverlay.m @@ -11,8 +11,11 @@ % (useful to remap interpolated values into 3D space for flat maps) % -function [newOverlayData, baseVoxelSize, baseCoords] = getBaseSpaceOverlay(thisView,overlayData,scanNum,baseNum,interpMethod, depthBins, rotateAngle) +function [newOverlayData, baseVoxelSize, baseCoords] = getBaseSpaceOverlay(thisView,overlayData,scanNum,baseNum,interpMethod, depthBins, rotateAngle, groupNum) +if ieNotDefined('groupNum') + groupNum = viewGet(thisView,'curGroup'); +end if ieNotDefined('scanNum') scanNum = viewGet(thisView,'curscan'); end @@ -29,7 +32,7 @@ end basedims = viewGet(thisView, 'basedims', baseNum); -base2scan = viewGet(thisView,'base2scan',scanNum,[],baseNum); +base2scan = viewGet(thisView,'base2scan',scanNum,groupNum,baseNum); baseType = viewGet(thisView,'baseType',baseNum); baseVoxelSize = viewGet(thisView,'basevoxelsize',baseNum); @@ -46,7 +49,7 @@ % Generate coordinates with meshgrid [Ycoords,Xcoords,Zcoords] = meshgrid(1:basedims(2),1:basedims(1),1:basedims(3)); - case 1 %the base is a flat map + case {1,2} %the base is a flat map or a surface sliceIndex = viewGet(thisView,'baseSliceIndex',baseNum); if ieNotDefined('depthBins') depthBins = mrGetPref('corticalDepthBins'); @@ -78,7 +81,7 @@ end end %rotate coordinates - if rotateAngle + if rotateAngle %what happens if this is not 0 for a surface? for iDepth = 1:depthBins Xcoords(:,:,iDepth) = mrImRotate(Xcoords0(:,:,iDepth),rotateAngle,'bilinear','crop'); Ycoords(:,:,iDepth) = mrImRotate(Ycoords0(:,:,iDepth),rotateAngle,'bilinear','crop'); @@ -91,6 +94,8 @@ end end + % THERE SEEMS TO BE A PROBLEM FOR SURFACES IN THE FOLLOWING + %just as an indication, the voxel size is the mean distance between voxels consecutive in the 3 directions %mask coordinates with non-rotated coordinates (to avoid edges introduced by mrImRotate) Xcoords0Mask = Xcoords0==0; @@ -106,11 +111,16 @@ baseVoxelSize(3) = baseVoxelSize(3)*nanmean(nanmean(nanmean(sqrt(diff(XcoordsNaN,1,3).^2 + diff(YcoordsNaN,1,3).^2 + diff(ZcoordsNaN,1,3).^2)))); otherwise - mrWarnDlg('(getBaseSpaceOverlay) This function is not implemented for surfaces') + end newOverlayData = getNewSpaceOverlay(overlayData, base2scan, Xcoords, Ycoords, Zcoords, interpMethod); +if baseType==2 + mrWarnDlg('(getBaseSpaceOverlay) This function has not been tested for surfaces') + %average cortical depth dimension for surface + newOverlayData = nanmean(newOverlayData,3); +end if nargout==3 baseCoords = cat(4,Xcoords, Ycoords); diff --git a/mrLoadRet/GUI/getNewSpaceOverlay.m b/mrLoadRet/GUI/getNewSpaceOverlay.m index f6e3e9832..4b89b4fc3 100644 --- a/mrLoadRet/GUI/getNewSpaceOverlay.m +++ b/mrLoadRet/GUI/getNewSpaceOverlay.m @@ -30,7 +30,7 @@ epsilon = 1e-7; xform = round(xform./epsilon).*epsilon; -hWaitbar = mrWaitBar(0,'Resampling overlay to new space'); +hWaitbar = mrWaitBar(0,'(getNewSpaceOverlay) Resampling overlay to new space'); % Compute new overlay data by base slice nSlices = size(newXCoords,3); scanDims = size(overlayData(:,:,:,1)); diff --git a/mrLoadRet/GUI/maskOverlay.m b/mrLoadRet/GUI/maskOverlay.m index 8d44f37ea..0a7997c28 100644 --- a/mrLoadRet/GUI/maskOverlay.m +++ b/mrLoadRet/GUI/maskOverlay.m @@ -65,7 +65,7 @@ % %put slices on 4th dimensions % overlayData{cScan} = permute(overlayData{cScan},[1 2 4 3]); else - scanDims = viewGet(thisView,'scandims'); + scanDims = viewGet(thisView,'scandims',iScan); overlayData{cScan}=NaN([scanDims nOverlaysInAnalysis]); cOverlay=0; for iOverlay = overlaysToGet @@ -93,12 +93,10 @@ if ~isempty(thisOverlayData) && ~all(isnan(thisOverlayData(:))) %if there is some data in the overlay clip = viewGet(thisView,'overlayClip',iOverlay); - if diff(clip) > 0 % Find defined pixels that are within clip + if diff(clip) >= 0 % Find defined pixels that are within clip maskOverlayData(:,:,:,cOverlay) = ((thisOverlayData >= clip(1) & thisOverlayData <= clip(2))) | isnan(thisOverlayData); elseif diff(clip) < 0 % Find defined pixels that are outside clip maskOverlayData(:,:,:,cOverlay) = (thisOverlayData >= clip(1) | thisOverlayData <= clip(2)) | isnan(thisOverlayData) ; - else - maskOverlayData(:,:,:,cOverlay) = false(size(thisOverlayData)) | isnan(thisOverlayData); end end end diff --git a/mrLoadRet/GUI/mlrAdjustGUI.m b/mrLoadRet/GUI/mlrAdjustGUI.m index 9de2592ca..f8c61ed0f 100644 --- a/mrLoadRet/GUI/mlrAdjustGUI.m +++ b/mrLoadRet/GUI/mlrAdjustGUI.m @@ -545,6 +545,15 @@ function setItemProperty(args,uiControls,menuControls,plotAxes,verbose) else %if the property is not 'location', then we just set the property using set + if ~verLessThan('matlab','9.4') && strcmp(get(h,'Type'),'uimenu') % some menu property names changed starting at version 9.4 (or possibly before) + switch(propertyName) + case 'callback' + propertyName = 'menuselectedfcn'; + case 'label' + propertyName = 'text'; + end + end + % check if the property exists fieldNames = lower(fieldnames(get(h))); diff --git a/mrLoadRet/GUI/mlrGetMouseCoords.m b/mrLoadRet/GUI/mlrGetMouseCoords.m index 1c9ec5f3e..39f49a09e 100644 --- a/mrLoadRet/GUI/mlrGetMouseCoords.m +++ b/mrLoadRet/GUI/mlrGetMouseCoords.m @@ -15,6 +15,7 @@ coords.scan = []; coords.base = []; coords.tal = []; +coords.mni = []; % get the viewNum, globals and test for fig mrGlobals; @@ -122,7 +123,7 @@ %stored in the view, so use that hobj = viewGet(v,'baseHandle'); end - [pos vertex vertexIndex] = select3d(hobj); + [pos, vertex, vertexIndex] = select3d(hobj); % convert the index to the coordinates if ~isempty(pos) baseCoordMap = viewGet(v,'baseCoordMap'); @@ -146,6 +147,22 @@ coords.tal.x = talCoords(1); coords.tal.y = talCoords(2); coords.tal.z = talCoords(3); end +% transform from base coordinates to MNI coordinates. This uses deformation maps computed using SPM +mniInfo = viewGet(v,'mniInfo'); +if ~isempty(mniInfo) + % convert coords from base to T1w volume that was used to compute the deformation coords map + base2mag = viewGet(v,'base2mag'); + coordsT1w = mniInfo.mag2T1w * base2mag * [coords.base.x coords.base.y coords.base.z 1]'; + % non-linear registration from T1w volume space to mni + [T1wGridX,T1wGridY,T1wGridZ] = ndgrid(1:size(mniInfo.T1w2mniCoordMap,1),1:size(mniInfo.T1w2mniCoordMap,2),1:size(mniInfo.T1w2mniCoordMap,3)); + coords.mni.x = interpn(T1wGridX,T1wGridY,T1wGridZ, mniInfo.T1w2mniCoordMap(:,:,:,1), coordsT1w(1), coordsT1w(2), coordsT1w(3)); + coords.mni.y = interpn(T1wGridX,T1wGridY,T1wGridZ, mniInfo.T1w2mniCoordMap(:,:,:,2), coordsT1w(1), coordsT1w(2), coordsT1w(3)); + coords.mni.z = interpn(T1wGridX,T1wGridY,T1wGridZ, mniInfo.T1w2mniCoordMap(:,:,:,3), coordsT1w(1), coordsT1w(2), coordsT1w(3)); +% coords.mni.x = mniInfo.T1w2mniCoordMap(round(coordsT1w(1)),round(coordsT1w(2)),round(coordsT1w(3)),1); % +% coords.mni.y = mniInfo.T1w2mniCoordMap(round(coordsT1w(1)),round(coordsT1w(2)),round(coordsT1w(3)),2); % less accurate but faster? +% coords.mni.z = mniInfo.T1w2mniCoordMap(round(coordsT1w(1)),round(coordsT1w(2)),round(coordsT1w(3)),3); % +end + % transform from base coordinates into scan coordinates base2scan = viewGet(v,'base2scan'); if ~isempty(base2scan) diff --git a/mrLoadRet/GUI/mlrGuiSet.m b/mrLoadRet/GUI/mlrGuiSet.m index 768e35e7f..b11c8463e 100644 --- a/mrLoadRet/GUI/mlrGuiSet.m +++ b/mrLoadRet/GUI/mlrGuiSet.m @@ -83,7 +83,7 @@ function mlrGuiSet(view,field,value,varargin) % mlrGuiSet(view,'showrois',value); % figure out which menu item should be checked - onItem = find(strcmp(value,{'all','all perimeter','selected','selected perimeter','group','group perimeter','hide'})); + onItem = find(strcmpi(value,{'all','all perimeter','selected','selected perimeter','group','group perimeter','hide'})); onOrOff = {'off','off','off','off','off','off','off'}; onOrOff{onItem} = 'on'; % turn the check marks on/off @@ -285,9 +285,9 @@ function mlrGuiSet(view,field,value,varargin) else set(handles.overlayPopup,'value',1); end - % for matlab version 2014a and above, the listboxtop property is not - % correctly updated when setting the value or the strings - if ~verLessThan('matlab','8.3') && strcmp(get(handles.overlayPopup,'style'),'listbox') + % From matlab version 2014a, the listboxtop property is not correctly updated when + % setting the value or the strings. However, this seems to have been fixed in later versions (not sure when exactly) + if ~verLessThan('matlab','8.3') && verLessThan('matlab','9.7') && strcmp(get(handles.overlayPopup,'style'),'listbox') set(handles.overlayPopup,'ListboxTop',1) %need to set it manually otherwise a warning will be issued end set(handles.overlayPopup,'String',value); diff --git a/mrLoadRet/GUI/mrFlatViewer.m b/mrLoadRet/GUI/mrFlatViewer.m index 959983412..a91630317 100644 --- a/mrLoadRet/GUI/mrFlatViewer.m +++ b/mrLoadRet/GUI/mrFlatViewer.m @@ -75,7 +75,7 @@ gFlatViewer = []; gFlatViewer.mismatchWarning = 0; retval = []; -disppercent(-inf,'(mrFlatViewer) Loading surfaces'); +mlrDispPercent(-inf,'(mrFlatViewer) Loading surfaces'); % load the flat if isstr(flat{1}) @@ -288,7 +288,7 @@ else curv = putOnTopOfList(curv{i},curv); end -disppercent(inf); +mlrDispPercent(inf); % from now on, complain for mismatch of surface nodes and patches gFlatViewer.mismatchWarning = 1; @@ -1089,7 +1089,7 @@ function dispVolume(sliceIndex,slice) global gFlatViewer; roiOverlay = []; -disppercent(-inf,'(mrFlatViewer) Computing ROI Overlay'); +mlrDispPercent(-inf,'(mrFlatViewer) Computing ROI Overlay'); % get view information v = viewGet([],'view',gFlatViewer.viewNum); @@ -1137,9 +1137,9 @@ function dispVolume(sliceIndex,slice) roiOverlay(roiVertices,1) = roiColorRGB(1); roiOverlay(roiVertices,2) = roiColorRGB(2); roiOverlay(roiVertices,3) = roiColorRGB(3); - disppercent(roinum/numROIs); + mlrDispPercent(roinum/numROIs); end -disppercent(inf); +mlrDispPercent(inf); %%%%%%%%%%%%%%%%%%%%%%% %% switchAnatomy %% @@ -1168,7 +1168,7 @@ function switchAnatomy(params) end % load the anatomy and view -disppercent(-inf,sprintf('(mrFlatViewer) Load %s',params.anatFileName)); +mlrDispPercent(-inf,sprintf('(mrFlatViewer) Load %s',params.anatFileName)); if isempty(fileparts(params.anatFileName)) anatFileName=fullfile(gFlatViewer.path,params.anatFileName); else @@ -1181,7 +1181,7 @@ function switchAnatomy(params) gFlatViewer.whichSurface = 3; set(gParams.ui.varentry{1},'Value',gFlatViewer.whichSurface) refreshFlatViewer([],[],1); -disppercent(inf); +mlrDispPercent(inf); %%%%%%%%%%%%%%%%%%%% %% switchFlat %% @@ -1191,12 +1191,12 @@ function switchFlat(params) global gFlatViewer; % load the anatomy and view -disppercent(-inf,sprintf('(mrFlatViewer) Load %s',params.flatFileName)); +mlrDispPercent(-inf,sprintf('(mrFlatViewer) Load %s',params.flatFileName)); gFlatViewer.flat = loadSurfOFF(fullfile(params.path, params.flatFileName)); % switch to flat view global gParams refreshFlatViewer([],[],1); -disppercent(inf); +mlrDispPercent(inf); %%%%%%%%%%%%%%%%%%%% %% switchFile %% @@ -1222,7 +1222,7 @@ function switchFile(whichSurface,params) end % try to load it -disppercent(-inf,sprintf('(mrFlatViewer) Loading %s',filename)); +mlrDispPercent(-inf,sprintf('(mrFlatViewer) Loading %s',filename)); if filename ~= 0 if strcmp(whichSurface,'curvFileName') file = myLoadCurvature(fullfile(params.path, filename)); @@ -1275,7 +1275,7 @@ function switchFile(whichSurface,params) end refreshFlatViewer([],[],1); -disppercent(inf); +mlrDispPercent(inf); %%%%%%%%%%%%%%%%%%%%%%% %% myLoadSurface %% diff --git a/mrLoadRet/GUI/mrInterrogator.m b/mrLoadRet/GUI/mrInterrogator.m index 1be7659ae..4da85b79a 100644 --- a/mrLoadRet/GUI/mrInterrogator.m +++ b/mrLoadRet/GUI/mrInterrogator.m @@ -154,7 +154,12 @@ function mouseMoveHandler(viewNum) set(MLR.interrogator{viewNum}.hPosBase,'String',''); end -if ~isempty(coords.tal) +if ~isempty(coords.mni) + set(MLR.interrogator{viewNum}.hPosTalLabel,'visible','on'); + set(MLR.interrogator{viewNum}.hPosTalLabel,'String','MNI'); + set(MLR.interrogator{viewNum}.hPosTal,'visible','on'); + set(MLR.interrogator{viewNum}.hPosTal,'String',sprintf('[%0.1f %0.1f %0.1f]',coords.mni.x,coords.mni.y,coords.mni.z)); +elseif ~isempty(coords.tal) set(MLR.interrogator{viewNum}.hPosTalLabel,'visible','on'); set(MLR.interrogator{viewNum}.hPosTalLabel,'String','Tal'); set(MLR.interrogator{viewNum}.hPosTal,'visible','on'); @@ -265,6 +270,20 @@ function mouseDownHandler(viewNum) MLR.interrogator{viewNum}.mouseDownScanCoords = [nan nan nan]; end +% see if we have valid Talairach coordinates +if ~isempty(coords.tal) + MLR.interrogator{viewNum}.mouseDownTalCoords = [coords.tal.x coords.tal.y coords.tal.z]; +else + MLR.interrogator{viewNum}.mouseDownTalCoords = [nan nan nan]; +end + +% see if we have valid mni coordinates +if ~isempty(coords.mni) + MLR.interrogator{viewNum}.mouseDownMniCoords = [coords.mni.x coords.mni.y coords.mni.z]; +else + MLR.interrogator{viewNum}.mouseDownMniCoords = [nan nan nan]; +end + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % end the mrInterrogator @@ -430,7 +449,12 @@ function initHandler(viewNum) view = MLR.views{viewNum}; overlayNum = viewGet(view,'currentOverlay'); analysisNum = viewGet(view,'currentAnalysis'); -MLR.interrogator{viewNum}.interrogator = viewGet(view,'interrogator',overlayNum,analysisNum); +if restart % if this is a restart, get the currently set interrogator and check that it's still in the interrogator list (not sure the latter is really necessary: what if it was manually set?) + MLR.interrogator{viewNum}.interrogator = intersect(MLR.interrogator{viewNum}.interrogator,interrogatorList); +end +if fieldIsNotDefined(MLR.interrogator{viewNum}, 'interrogator') + MLR.interrogator{viewNum}.interrogator = viewGet(view,'interrogator',overlayNum,analysisNum); +end set(MLR.interrogator{viewNum}.hInterrogator,'String',MLR.interrogator{viewNum}.interrogator); %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/mrLoadRet/GUI/mrLoadRetGUI.m b/mrLoadRet/GUI/mrLoadRetGUI.m index 506cb6949..1ef4c942d 100644 --- a/mrLoadRet/GUI/mrLoadRetGUI.m +++ b/mrLoadRet/GUI/mrLoadRetGUI.m @@ -877,7 +877,7 @@ function importROIMenuItem_Callback(hObject, eventdata, handles) mrGlobals; viewNum = handles.viewNum; view = MLR.views{viewNum}; -view = importROI(view); +importROI(view); % -------------------------------------------------------------------- function saveROIMenuItem_Callback(hObject, eventdata, handles) @@ -1347,7 +1347,7 @@ function EditAnalysisInfoMenuItem_Callback(hObject, eventdata, handles) % no current anatomy, just return if isempty(viewGet(v,'curAnalysis')),return;end -disppercent(-inf,'Gathering analysis info'); +mlrDispPercent(-inf,'Gathering analysis info'); % get the current analysis a = viewGet(v,'Analysis',viewGet(v,'curAnalysis')); @@ -1388,7 +1388,7 @@ function EditAnalysisInfoMenuItem_Callback(hObject, eventdata, handles) paramsInfo{end+1} = {'params',[],'View analysis parameters','type=pushbutton','buttonString=View analysis parameters','callback',@viewAnalysisParams,'callbackArg',v}; end -disppercent(inf); +mlrDispPercent(inf); % display parameters mrParamsDialog(paramsInfo,'Analysis Info'); @@ -1527,10 +1527,21 @@ function editManyROIs(viewNum,roiList) % get name and colors for each roi roiNames{roinum} = viewGet(v,'roiName',roinum); roiNotes = viewGet(v,'roiNotes',roinum); - colors = putOnTopOfList(viewGet(v,'roiColor',roinum),color2RGB); + roiColor = viewGet(v,'roiColor',roinum); + if ischar(roiColor) + topRoiColor = roiColor; + rgbColor = [0 0 0]; + elseif isnumeric(roiColor) && length(roiColor)==3 + topRoiColor = 'User-defined RGB color'; + rgbColor = roiColor; + else + mrErrorDlg('(editManyROIs) Unknown ROI color value'); + end + colors = putOnTopOfList(topRoiColor,[color2RGB 'User-defined RGB color']); displayOn = putOnTopOfList(viewGet(v,'roiDisplayOnBase'),viewGet(v,'baseNames')); paramsInfo{end+1} = {sprintf('%sName',fixBadChars(roiNames{roinum})),roiNames{roinum},'Name of roi, avoid using punctuation and space'}; paramsInfo{end+1} = {sprintf('%sColor',fixBadChars(roiNames{roinum})),colors,'type=popupmenu',sprintf('The color that roi %s will display in',roiNames{roinum})}; + paramsInfo{end+1} = {sprintf('%sRGBcolor',fixBadChars(roiNames{roinum})),rgbColor,'type=array','minmax=[0 1]',sprintf('The RGB color triplet that roi %s will display in. This will be superseded by any string selected above.',roiNames{roinum})}; paramsInfo{end+1} = {sprintf('%sNotes',fixBadChars(roiNames{roinum})),roiNotes,sprintf('Note for roi %s',roiNames{roinum})}; paramsInfo{end+1} = {sprintf('%sDisplayOnBase',fixBadChars(roiNames{roinum})),displayOn,sprintf('Base that roi %s is best displayed on',roiNames{roinum})}; end @@ -1541,7 +1552,12 @@ function editManyROIs(viewNum,roiList) if ~isempty(params) for roinum = roiList roiName = fixBadChars(roiNames{roinum}); - v = viewSet(v,'roiColor',params.(sprintf('%sColor',roiName)),roinum); + if strcmp(params.(sprintf('%sColor',roiName)), 'User-defined RGB color') + newRoiColor = params.(sprintf('%sRGBcolor',roiName)); + else + newRoiColor = params.(sprintf('%sColor',roiName)); + end + v = viewSet(v,'roiColor',newRoiColor,roinum); v = viewSet(v,'roiName',params.(sprintf('%sName',roiName)),roinum); v = viewSet(v,'roiNotes',params.(sprintf('%sNotes',roiName)),roinum); v = viewSet(v,'roiDisplayOnBase',params.(sprintf('%sDisplayOnBase',roiName)),roinum); @@ -1918,7 +1934,7 @@ function deleteManyBasesMenuItem_Callback(hObject, eventdata, handles) mrGlobals; viewNum = handles.viewNum; view = MLR.views{viewNum}; -numBases = selectInList(view,'bases','Select bases to remove'); +numBases = selectInList(view,'bases','Select bases to remove',[]); if ~isempty(numBases) for baseNum = fliplr(numBases); view = viewSet(view,'deleteBase',baseNum); @@ -1940,7 +1956,7 @@ function deleteManyAnalysisMenuItem_Callback(hObject, eventdata, handles) mrGlobals; viewNum = handles.viewNum; view = MLR.views{viewNum}; -numAnalyses = selectInList(view,'analyses','Select analyses to remove'); +numAnalyses = selectInList(view,'analyses','Select analyses to remove',[]); if ~isempty(numAnalyses) view = viewSet(view,'deleteAnalysis',numAnalyses); refreshMLRDisplay(viewNum); @@ -1974,7 +1990,7 @@ function deleteManyOverlaysMenuItem_Callback(hObject, eventdata, handles) mrGlobals; viewNum = handles.viewNum; view = MLR.views{viewNum}; -numOverlays = selectInList(view,'overlays','Select overlays to remove'); +numOverlays = selectInList(view,'overlays','Select overlays to remove',[]); if ~isempty(numOverlays) view = viewSet(view,'deleteOverlay',numOverlays); refreshMLRDisplay(viewNum); @@ -2677,7 +2693,7 @@ function flatViewerMenuItem_Callback(hObject, eventdata, handles) flatParentSurf = fullfile(params.path,params.innerCoordsFileName); if mlrIsFile(flatParentSurf) disp('(mrLoadRetGUI) Creating missing flat off surface'); - disppercent(-inf,sprintf('(mrLoadRetGUI) Note this will create a quick flat surface good enough for rough visualization of location but is not exactly correct')); + mlrDispPercent(-inf,sprintf('(mrLoadRetGUI) Note this will create a quick flat surface good enough for rough visualization of location but is not exactly correct')); % load the parent surface flatParentSurfOFF = loadSurfOFF(flatParentSurf); if ~isempty(flatParentSurfOFF) @@ -2723,7 +2739,7 @@ function flatViewerMenuItem_Callback(hObject, eventdata, handles) flatSurf.path = params.path; % put it into the params field params.flatFileName = flatSurf; - disppercent(inf); + mlrDispPercent(inf); end end end diff --git a/mrLoadRet/GUI/mrPrint.m b/mrLoadRet/GUI/mrPrint.m old mode 100644 new mode 100755 index dceab5fa7..f31217e06 --- a/mrLoadRet/GUI/mrPrint.m +++ b/mrLoadRet/GUI/mrPrint.m @@ -1,125 +1,296 @@ % mrPrint.m % % $Id$ -% usage: mrPrint(v) +% usage: mrPrint(v,<'params', params>,<'useDefault=1'>,<'justGetParams=1'>) % by: justin gardner % date: 10/04/07 % purpose: puts a printable version of the data into the graph win % -function retval = mrPrint(v,varargin) +% To change any default parameter within a script: +% [~,printParams] = mrPrint(thisView,'justGetParams=1','useDefault=1'); +% % change parameters... +% mrPrint(thisView,'params',printParams) +% +function [f,params] = mrPrint(v,varargin) +f=gobjects(0); % check arguments if nargin < 1 help mrPrint return end +if viewGet(getMLRView,'baseMultiAxis')>0 + mrWarnDlg('(mrPrint) Not implemented for multi-axes display'); + return; +end + mrGlobals; % get input arguments -getArgs(varargin,{'useDefault=0','roiSmooth=1','roiLabels=1'}); +getArgs(varargin,{'params=[]','useDefault=0','justGetParams=0','roiSmooth=0','roiLabels=0'}); % get base type baseType = viewGet(v,'baseType'); +visibleROIs = viewGet(v,'visibleROIs'); +if baseType<2 + imageDimensions = viewGet(v,'baseDims'); + switch(baseType) + case 0 + sliceIndex = viewGet(v,'baseSliceIndex'); + imageDimensions = imageDimensions(setdiff(1:3,sliceIndex)); + imageDimensions = imageDimensions([2 1]); + case 1 + imageDimensions = imageDimensions(1:2); + end +end +overlayList = viewGet(v,'curOverlay'); +nOverlays = numel(overlayList); + +% display in graph window +f = selectGraphWin; +set(f,'Name','Print figure'); +clf(f); +set(f,'NumberTitle','off'); +set(f,'unit','normalized'); +figurePosition = get(f,'position'); +if ieNotDefined('params') + % default parameters + defaultTitle = sprintf('%s: %s',getLastDir(MLR.homeDir),viewGet(v,'description')); + defaultFontSize = 14; + defaultMosaicNrows = 0; + defaultMosaicMargins = [.1 .1]; + defaultColorbarScaleFunction = '@(x)x'; + colorbarLocs = {'South','North','East','West','OutsideSN','OutsideEW','None'}; + if nOverlays>1 + colorbarLocs = putOnTopOfList('None',colorbarLocs); + end + imageTitleLocs = {'North','South','East','West','OutsideSN','OutsideEW','None'}; + defaultColorbarTickNumber = 4; + interpreterList = {'none','tex','latex'}; + + % first get parameters that the user wants to display + paramsInfo = {}; + paramsInfo{end+1} = {'imageTitle',defaultTitle,'Title(s) of image(s). When calling mrPrint from a script and printing multiple overlays as separate images, this can be a cell array of string of same size as the number of overlays'}; + paramsInfo{end+1} = {'imageTitleLoc',imageTitleLocs,'Location of title(s). ''OutsideSN'' and ''OutsideEW'' only work for mosaic=true and place the title next at the outermost or innermost locations (South-North or East-West) across all images.'}; + paramsInfo{end+1} = {'fontSize',defaultFontSize,'Font size of the image title(s). Colorbar title(s) will be set to this value minus 2'}; + paramsInfo{end+1} = {'interpreter',interpreterList,'How to interpret special characters (default: no interpreter)'}; + paramsInfo{end+1} = {'backgroundColor',{'white','black'},'type=popupmenu','Background color, either white or black'}; + paramsInfo{end+1} = {'figurePosition',figurePosition,'minmax=[0 1]','Position and size of the figure from bottom left, normalized to the screen size ([leftborder, bottom, width, height]).'}; + if baseType == 2 % options for surfaces + % if this surface has inf in the name, then guess that it is inflated and default to thresholding + baseName = viewGet(v,'baseName'); + if ~isempty(strfind(lower(baseName),'inf')) thresholdCurvature = 1;else thresholdCurvature = 0;end + paramsInfo{end+1} = {'thresholdCurvature',thresholdCurvature,'type=checkbox','Thresholds curvature so that the surface is two tones rather than has smooth tones'}; -% grab the image -disppercent(-inf,'(mrPrint) Rerendering image'); -[img base roi overlays altBase] = refreshMLRDisplay(viewGet(v,'viewNum')); -disppercent(inf); + % compute a good threshold value + [base.im,base.coords,base.coordsHomogeneous] = getBaseSlice(v); % get the base surface + baseImg = rescale2rgb(base.im,gray(256),viewGet(v,'baseClip'),viewGet(v,'baseGamma')); % and compute mesh (gray) RGB value like in refreshMLRDisplay + thresholdValue = mean(baseImg(1,(baseImg(1,:,1)==baseImg(1,:,2))&(baseImg(1,:,3)==baseImg(1,:,2)),1)); + thresholdValue = round(thresholdValue*100)/100; -% validate rois -validROIs = {}; -for roiNum = 1:length(roi) - if ~isempty(roi{roiNum}) - validROIs{end+1} = roi{roiNum}; + paramsInfo{end+1} = {'thresholdValue',thresholdValue,'minmax=[0 1]','incdec=[-0.01 0.01]','contingent=thresholdCurvature','Threshold point - all values below this will turn to the thresholdMin value and all values above this will turn to thresholdMax if thresholdCurvature is turned on.'}; + paramsInfo{end+1} = {'thresholdMin',0.2,'minmax=[0 1]','incdec=[-0.1 0.1]','contingent=thresholdCurvature','The color that all values less than thresholdValue will turn to if thresholdCurvature is set.'}; + paramsInfo{end+1} = {'thresholdMax',0.5,'minmax=[0 1]','incdec=[-0.1 0.1]','contingent=thresholdCurvature','The color that all values greater than thresholdValue will turn to if thresholdCurvature is set.'}; + paramsInfo{end+1} = {'camLight',false,'type=checkbox','Adds a light shining onto the surface to accentuate its 3D shape. Light position is fixed with respect to the surface'}; + paramsInfo{end+1} = {'camLightAz',0,'incdec=[-10 10]','contingent=camLight','Azimuth coordinate of the light. Default = 0, corresponding to light coming from behind the viewer.'}; + paramsInfo{end+1} = {'camLightEl',0,'incdec=[-10 10]','contingent=camLight','Elevation coordinate of the light. Default = 0, corresponding to light coming from behind the viewer.'}; + else % options for flat maps and volume slices + paramsInfo{end+1} = {'cropX',[1 imageDimensions(2)],sprintf('minmax=[1 %d]',imageDimensions(2)),'incdec=[-10 10]','type=array','X coordinates of a rectangle in pixels to crop the image ([xOrigin width]), before upsampling. X origin is on the left of the image. Not implemented for surfaces'}; + paramsInfo{end+1} = {'cropY',[1 imageDimensions(1)],sprintf('minmax=[1 %d]',imageDimensions(1)),'incdec=[-10 10]','type=array','Y coordinates of a rectangle in pixels to crop the image ([yOrigin height]), before upsampling. Y origin is at the top of the image. Not implemented for surfaces'}; + paramsInfo{end+1} = {'upSampleFactor',0,'type=numeric','round=1','incdec=[-1 1]','minmax=[0 inf]','How many to upsample image by. Each time the image is upsampled it increases in dimension by a factor of 2. So, for example, setting this to 2 will increase the image size by 4'}; + if baseType == 1 + paramsInfo{end+1} = {'maskType',{'Circular','Remove black','None'},'type=popupmenu','Masks out anatomy image for flat maps. Circular finds the largest circular aperture to view the anatomy through. Remove black keeps the patch the same shape, but removes pixels at the edge that are black.'}; + end + end + if nOverlays>1 + paramsInfo{end+1} = {'mosaic',false,'type=checkbox','Displays each overlay in a separate panel'}; + paramsInfo{end+1} = {'mosaicNrows',defaultMosaicNrows,'incdec=[-1 1]',sprintf('minmax=[1 %d]',nOverlays),'contingent=mosaic','Number of rows in the mosaic. 0 = set the number of rows automatically'}; + paramsInfo{end+1} = {'mosaicMargins',defaultMosaicMargins,'incdec=[-.01 .01]','minmax=[0 1]','contingent=mosaic','X and Y margins between images, expressed as a proportion of each image''s width and height'}; + contingentString = 'contingent=mosaic'; + colorbarTitle = ''; + else + contingentString = ''; + colorbarTitle = viewGet(v,'overlayName'); + end + paramsInfo{end+1} = {'colorbarLoc',colorbarLocs,'type=popupmenu',contingentString,'Location of colorbar, select ''None'' if you do not want a colorbar. ''OutsideSN'' and ''OutsideEW'' only work for mosaic=true and place the colorbar next at the outermost or innermost locations (South-North or East-West) across all images.'}; + paramsInfo{end+1} = {'colorbarTitle',colorbarTitle,contingentString,'Title of the colorbar. When calling mrPrint from a script and printing multiple overlays as separate images, this can be a cell array of string of same size as the number of overlays'}; + if nOverlays==1 + paramsInfo{end+1} = {'colorbarScale',viewGet(v,'overlayColorRange'),'type=array',contingentString,'Lower and upper limits of the color scale to display on the color bar'}; + end + paramsInfo{end+1} = {'colorbarScaleFunction',defaultColorbarScaleFunction,'type=string',contingentString,'Anonymous function to apply to the colorbar scale values [e.g. @(x)exp(x) for data on a logarithmic scale]. This will be applied after applying the colorbarScale parameter. The function must accept and return a one-dimensional array of color scale values.'}; + paramsInfo{end+1} = {'colorbarTickNumber',defaultColorbarTickNumber,'type=numeric','round=1','incdec=[-1 1]','minmax=[2 inf]',contingentString,'Number of ticks on the colorbar'}; + if ~isempty(visibleROIs) + if ismember(viewGet(v,'showROIs'),{'all perimeter','selected perimeter','group perimeter'}) || baseType < 2 % ROI options if plotted as outline + paramsInfo{end+1} = {'roiLineWidth',mrGetPref('roiContourWidth'),'incdec=[-1 1]','minmax=[0 inf]','Line width for drawing ROIs. Set to 0 if you don''t want to display ROIs.'}; + end + if baseType == 2 % ROI options for surfaces if plotted as patch + paramsInfo{end+1} = {'roiAlpha',0.4,'minmax=[0 1]','incdec=[-0.1 0.1]','Sets the alpha of the ROIs'}; + else % other ROI options for flatmaps and images + paramsInfo{end+1} = {'roiColor',putOnTopOfList('default',color2RGB),'type=popupmenu','Color to use for drawing ROIs. Select default to use the color currently being displayed.'}; + paramsInfo{end+1} = {'roiOutOfBoundsMethod',{'Remove','Max radius'},'type=popupmenu','If there is an ROI that extends beyond the circular aperture, you can either not draw the lines (Remove) or draw them at the edge of the circular aperture (Max radius). This is only important if you are using a circular aperture.'}; + paramsInfo{end+1} = {'roiLabels',roiLabels,'type=checkbox','Print ROI name at center coordinate of ROI'}; + if baseType == 1 + paramsInfo{end+1} = {'roiSmooth',roiSmooth,'type=checkbox','Smooth the ROI boundaries'}; + paramsInfo{end+1} = {'whichROIisMask',0,'incdec=[-1 1]', sprintf('minmax=%s',mat2str([0 length(visibleROIs)])) 'Which ROI to use as a mask. 0 does no masking'}; + paramsInfo{end+1} = {'filledPerimeter',1,'type=numeric','round=1','minmax=[0 1]','incdec=[-1 1]','Fills the perimeter of the ROI when drawing','contingent=roiSmooth'}; + end + end + end + + if useDefault + params = mrParamsDefault(paramsInfo); + else + params = mrParamsDialog(paramsInfo,'Print figure options'); + end + + if ~isempty(params) % if some fields are undefined (because of parameter dependencies), use the defaults + if fieldIsNotDefined(params,'mosaic') + params.mosaic = false; + end + if fieldIsNotDefined(params,'mosaicNrows') + params.mosaicNrows = defaultMosaicNrows; + end + if fieldIsNotDefined(params,'mosaicMargins') + params.mosaicMargins = defaultMosaicMargins; + end + if fieldIsNotDefined(params,'colorbarLoc') + params.colorbarLoc = 'None'; + end + if fieldIsNotDefined(params,'colorbarTitle') + params.colorbarTitle = colorbarTitle; + end + if fieldIsNotDefined(params,'colorbarScaleFunction') + params.colorbarScaleFunction = defaultColorbarScaleFunction; + end + if fieldIsNotDefined(params,'colorbarTickNumber') + params.colorbarTickNumber = defaultColorbarTickNumber; + end end end -roi = validROIs; - -% first get parameters that the user wants to display -paramsInfo = {}; -paramsInfo{end+1} = {'title',sprintf('%s: %s',getLastDir(MLR.homeDir),viewGet(v,'description')),'Title of figure'}; -paramsInfo{end+1} = {'backgroundColor',{'white','black'},'type=popupmenu','Background color, either white or black'}; -paramsInfo{end+1} = {'colorbarLoc',{'SouthOutside','NorthOutside','EastOutside','WestOutside','None'},'type=popupmenu','Location of colorbar, select None if you do not want a colorbar'}; -paramsInfo{end+1} = {'colorbarTitle',viewGet(v,'overlayName'),'Title of the colorbar'}; -if baseType == 1 - paramsInfo{end+1} = {'maskType',{'Circular','Remove black','None'},'type=popupmenu','Masks out anatomy image. Circular finds the largest circular aperture to view the anatomy through. Remove black keeps the patch the same shape, but removes pixels at the edge that are black.'}; + +if ~isempty(params) && ~justGetParams + set(f,'color',params.backgroundColor); + set(f,'position',params.figurePosition); end -% options for surfaces -if baseType == 2 - % if this surface has inf in the name, then guess that it is inflated and default to thresholding - baseName = viewGet(v,'baseName'); - if ~isempty(strfind(lower(baseName),'inf')) thresholdCurvature = 1;else thresholdCurvature = 0;end - paramsInfo{end+1} = {'thresholdCurvature',thresholdCurvature,'type=checkbox','Thresholds curvature so that the surface is two tones rather than has smooth tones'}; - - % compute a good threshold value - grayscalePoints = find((img(1,:,1)==img(1,:,2))&(img(1,:,3)==img(1,:,2))); - thresholdValue = mean(img(1,grayscalePoints,1)); - thresholdValue = round(thresholdValue*100)/100; - - paramsInfo{end+1} = {'thresholdValue',thresholdValue,'minmax=[0 1]','incdec=[-0.01 0.01]','contingent=thresholdCurvature','Threshold point - all values below this will turn to the thresholdMin value and all values above this will turn to thresholdMax if thresholdCurvature is turned on.'}; - paramsInfo{end+1} = {'thresholdMin',0.2,'minmax=[0 1]','incdec=[-0.1 0.1]','contingent=thresholdCurvature','The color that all values less than thresholdValue will turn to if thresholdCurvature is set.'}; - paramsInfo{end+1} = {'thresholdMax',0.5,'minmax=[0 1]','incdec=[-0.1 0.1]','contingent=thresholdCurvature','The color that all values greater than thresholdValue will turn to if thresholdCurvature is set.'}; - if ~isempty(roi) - paramsInfo{end+1} = {'roiAlpha',0.4,'minmax=[0 1]','incdec=[-0.1 0.1]','Sets the alpha of the ROIs'}; +set(f,'unit','pixels'); % set units back to pixels because this is what selectGraphWin assumes + +if isempty(params) || justGetParams + close(f); + return; +end + +% ------------------- Checks on parameters +if isempty(params.colorbarTitle) || ischar(params.colorbarTitle) + params.colorbarTitle = {params.colorbarTitle}; +end +if params.mosaic + if length(params.colorbarTitle)>1 && length(params.colorbarTitle)~=nOverlays + mrWarnDlg('(mrPrint) The number of colorbar titles must match the number of overlays') + close(f) + return; + elseif length(params.colorbarTitle)==1 + params.colorbarTitle = repmat(params.colorbarTitle,1,nOverlays); end -else - % ROI options for flatmaps and images - if ~isempty(roi) - paramsInfo{end+1} = {'roiLineWidth',1,'incdec=[-1 1]','minmax=[0 inf]','Line width for drawing ROIs. Set to 0 if you don''t want to display ROIs.'}; - paramsInfo{end+1} = {'roiColor',putOnTopOfList('default',color2RGB),'type=popupmenu','Color to use for drawing ROIs. Select default to use the color currently being displayed.'}; - paramsInfo{end+1} = {'roiOutOfBoundsMethod',{'Remove','Max radius'},'type=popupmenu','If there is an ROI that extends beyond the circular aperture, you can either not draw the lines (Remove) or draw them at the edge of the circular aperture (Max radius). This is only important if you are using a circular aperture.'}; - paramsInfo{end+1} = {'roiLabels',roiLabels,'type=checkbox','Print ROI name at center coordinate of ROI'}; - if baseType == 1 - paramsInfo{end+1} = {'roiSmooth',roiSmooth,'type=checkbox','Smooth the ROI boundaries'}; - paramsInfo{end+1} = {'whichROIisMask',0,'incdec=[-1 1]', 'minmax=[0 inf]', 'Which ROI to use as a mask. 0 does no masking'}; - paramsInfo{end+1} = {'filledPerimeter',1,'type=numeric','round=1','minmax=[0 1]','incdec=[-1 1]','Fills the perimeter of the ROI when drawing','contingent=roiSmooth'}; - end +end +if ischar(params.imageTitle) + params.imageTitle = {params.imageTitle}; +end +if params.mosaic + if length(params.imageTitle) == 1 + params.imageTitle = repmat(params.imageTitle,nOverlays); % ensure that the number of titles matches the number of overlays + elseif length(params.imageTitle)~=nOverlays + mrWarnDlg('(mrPrint) The number of overlay titles must match the number of overlays') + close(f) + return; end - paramsInfo{end+1} = {'upSampleFactor',0,'type=numeric','round=1','incdec=[-1 1]','minmax=[1 inf]','How many to upsample image by. Each time the image is upsampled it increases in dimension by a factor of 2. So, for example, setting this to 2 will increase the image size by 4'}; end -if useDefault - params = mrParamsDefault(paramsInfo); -else - params = mrParamsDialog(paramsInfo,'Print figure options'); +if params.mosaic==0 + switch(lower(params.colorbarLoc)) + case 'outsideew' + mrWarndDlg('(mrPrint) Switching to colorbarLoc = ''East'' because this is not an image mosaic'); + params.colorbarLoc = 'East'; + case 'outsidesn' + mrWarndDlg('(mrPrint) Switching to colorbarLoc = ''South'' because this is not an image mosaic'); + params.colorbarLoc = 'South'; + end + switch(lower(params.imageTitleLoc)) + case 'outsideew' + mrWarndDlg('(mrPrint) Switching to imageTitleLoc = ''East'' because this is not an image mosaic'); + params.imageTitleLoc = 'East'; + case 'outsidesn' + mrWarndDlg('(mrPrint) Switching to imageTitleLoc = ''South'' because this is not an image mosaic'); + params.imageTitleLoc = 'South'; + end + end - -if isempty(params),return,end -% just so the code won't break. roiSmooth is only fro baseType = 1 -if ~isfield(params,'roiSmooth') params.roiSmooth = 0;end +% ----------------------- Grab the image(s) +if baseType<2 + cropX = params.cropX; + cropY = params.cropY; +else + cropX = [0 1]; + cropY = [0 1]; +end -% get the gui, so that we can extract colorbar -fig = viewGet(v,'figNum'); -gui = guidata(fig); +if params.mosaic + nImages = nOverlays; +else + nImages = 1; +end -% grab the colorbar data -H = get(gui.colorbar,'children'); -cmap = get(H(end),'CData'); -if size(cmap,1)>1 - mrWarnDlg('(mrPrint) printing colorbar for multiple overlays is not implemented'); +mlrDispPercent(-inf,'(mrPrint) Rerendering image'); +for iImage = 1:nImages + if nOverlays>1 && params.mosaic + v = viewSet(v,'curOverlay',overlayList(iImage)); % set each overlay in the view one by one + end + [img{iImage}, base, roi, overlays, altBase{iImage}] = refreshMLRDisplay(viewGet(v,'viewNum')); + fig = viewGet(v,'figNum'); + % get the gui, so that we can get colorbar data + if ~isempty(fig) % this won't work with the view doesn't have a GUI figure associated with it + gui = guidata(fig); + if size(overlays.cmap,3)>1 % if there are multiple overlays + cmap{iImage} = overlays.cmap; % we get the colormaps for the overlays structure, not the GUI + else % othewrise, get the colorbar data from the GUI (we do this to ensure we have the same ticks as in the mrLoadRet figure) + % grab the colorbar data + H = get(gui.colorbar,'children'); + cmap{iImage} = get(H(end),'CData'); + cmap{iImage} = squeeze(cmap{iImage}(1,:,:)); + yTicks{iImage} = get(gui.colorbar,'YTick'); + xTicks{iImage} = (get(gui.colorbar,'XTick')-0.5)/length(colormap); + xTickLabels{iImage} = str2num(get(gui.colorbar,'XTicklabel')); + end + else + cmap{iImage} = []; + end +end +roi = roi(visibleROIs); +if nOverlays>1 && params.mosaic + v = viewSet(v,'curOverlay',overlayList); % set the overlays back in the view + refreshMLRDisplay(viewGet(v,'viewNum')); end -cmap=squeeze(cmap(1,:,:)); +mlrDispPercent(inf); -% display in graph window -f = selectGraphWin; -clf(f);drawnow; -axisHandle = gca(f); -set(f,'Name','Print figure'); -set(f,'NumberTitle','off'); -set(f,'color',params.backgroundColor) +% ----------------------- Print the images + +figure(f); % bring figure to the foreground +set(f,'Pointer','watch');drawnow; + +% just so the code won't break. roiSmooth is only fro baseType = 1 +if ~isfield(params,'roiSmooth') params.roiSmooth = 0;end % value to consider to be "black" in image blackValue = 0; if baseType~=1,params.maskType = 'None';end -% get the mask +% get the mask(s) if strcmp(params.maskType,'None') - mask = zeros(size(img)); + mask = zeros(size(img{1})); elseif strcmp(params.maskType,'Remove black') mask(:,:,1) = (base.im<=blackValue); mask(:,:,2) = (base.im<=blackValue); @@ -130,7 +301,7 @@ yCenter = (size(base.im,1)/2); x = (1:size(base.im,2))-xCenter; y = (1:size(base.im,1))-yCenter; - [x y] = meshgrid(x,y); + [x, y] = meshgrid(x,y); % now compute the distance from the center for % every point d = sqrt(x.^2+y.^2); @@ -157,38 +328,49 @@ if isfield(params,'upSampleFactor') % convert upSampleFactor into power of 2 - params.upSampleFactor = 2^params.upSampleFactor; + upSampleFactor = 2^params.upSampleFactor; % up sample if called for - if params.upSampleFactor > 1 - upSampImage(:,:,1) = upSample(img(:,:,1),log2(params.upSampleFactor)); - upSampImage(:,:,2) = upSample(img(:,:,2),log2(params.upSampleFactor)); - upSampImage(:,:,3) = upSample(img(:,:,3),log2(params.upSampleFactor)); - upSampMask(:,:,1) = upBlur(double(mask(:,:,1)),log2(params.upSampleFactor)); - upSampMask(:,:,2) = upBlur(double(mask(:,:,2)),log2(params.upSampleFactor)); - upSampMask(:,:,3) = upBlur(double(mask(:,:,3)),log2(params.upSampleFactor)); - img = upSampImage; - mask = upSampMask/max(upSampMask(:));; + if upSampleFactor > 1 + upSampMask(:,:,1) = upBlur(double(mask(:,:,1)),params.upSampleFactor); + upSampMask(:,:,2) = upBlur(double(mask(:,:,2)),params.upSampleFactor); + upSampMask(:,:,3) = upBlur(double(mask(:,:,3)),params.upSampleFactor); + for iImage = 1:nImages + upSampImage(:,:,1) = upSample(img{iImage}(:,:,1),params.upSampleFactor); + upSampImage(:,:,2) = upSample(img{iImage}(:,:,2),params.upSampleFactor); + upSampImage(:,:,3) = upSample(img{iImage}(:,:,3),params.upSampleFactor); + img{iImage} = upSampImage; + end + upSampMask(upSampMask>0) = upSampMask(upSampMask>0)/max(upSampMask(:)); + mask = upSampMask; % make sure we clip to 0 and 1 mask(mask<0) = 0;mask(mask>1) = 1; - img(img<0) = 0;img(img>1) = 1; + for iImage = 1:nImages + img{iImage}(img{iImage}<0) = 0;img{iImage}(img{iImage}>1) = 1; + end % fix the parameters that are used for clipping to a circular aperture if exist('circd','var') - circd = circd*params.upSampleFactor; - xCenter = xCenter*params.upSampleFactor; - yCenter = yCenter*params.upSampleFactor; + circd = circd*upSampleFactor; + xCenter = xCenter*upSampleFactor; + yCenter = yCenter*upSampleFactor; end + cropX(1) = (cropX(1)-1)*upSampleFactor+1; + cropY(1) = (cropY(1)-1)*upSampleFactor+1; + cropX(2) = cropX(2)*upSampleFactor; + cropY(2) = cropY(2)*upSampleFactor; end end if (baseType == 1) && ~isempty(roi) && params.roiSmooth % get the roiImage and mask - [roiImage roiMask dataMask] = getROIPerimeterRGB(v,roi,size(img),params); + [roiImage, roiMask, dataMask] = getROIPerimeterRGB(v,roi,size(img{1}),params); % now set img correctly - [roiY roiX] = find(roiMask); + [roiY, roiX] = find(roiMask); for i = 1:length(roiX) for j = 1:3 - if (roiX(i) <= size(img,1)) && (roiY(i) <= size(img,2)) - img(roiX(i),roiY(i),j) = roiImage(roiY(i),roiX(i),j); + if (roiX(i) <= size(img{1},1)) && (roiY(i) <= size(img{1},2)) + for iImage = 1:nImages + img{iImage}(roiX(i),roiY(i),j) = roiImage(roiY(i),roiX(i),j); + end end end end @@ -199,248 +381,593 @@ dataMask = permute(dataMask, [2 1]); dataMask = 1-repmat(dataMask, [1 1 3]); - img = (1-dataMask) .* img; baseMask = base.RGB; baseMask = dataMask .* baseMask; - img = img + baseMask; + for iImage = 1:nImages + img{iImage} = (1-dataMask) .* img{iImage}; + img{iImage} = img{iImage} + baseMask; + end end % mask out the image if ~strcmp(params.maskType,'None') - if strcmp(params.backgroundColor,'white') - img = (1-mask).*img + mask; - else - img = (1-mask).*img; + for iImage = 1:nImages + if strcmp(params.backgroundColor,'white') + img{iImage} = (1-mask).*img{iImage} + mask; + else + img{iImage} = (1-mask).*img{iImage}; + end end end -img(img<0) = 0;img(img>1) = 1; - -% set the colormap -colormap(cmap); +for iImage = 1:nImages + img{iImage}(img{iImage}<0) = 0;img{iImage}(img{iImage}>1) = 1; +end -% now display the images -if baseType == 2 - % this is the surface display - - curBase = viewGet(v,'curBase'); - for iBase = 1:viewGet(v,'numBase') - if viewGet(v,'baseType',iBase)>=2 - if viewGet(v,'baseMultiDisplay',iBase) || isequal(iBase,curBase) - % get the img (returned by refreshMLRDisplay. This is different - % for each base when we are displaying more than one. Note - % that this code hasn't been fully tested with all options yet (jg 2/18/2015) - if iBase ~= curBase - thisimg = altBase(iBase).img; - else - thisimg = img; - end - % taken from refreshMLRDisplay - baseSurface = viewGet(v,'baseSurface',iBase); - % threshold curvature if asked for - if params.thresholdCurvature - % get all grayscale points (assuming these are the ones that are from the surface) - grayscalePoints = find((thisimg(1,:,1)==thisimg(1,:,2))&(thisimg(1,:,3)==thisimg(1,:,2))); - % get points less than 0.5 - lowThresholdPoints = grayscalePoints(thisimg(1,grayscalePoints,1) < params.thresholdValue); - hiThresholdPoints = grayscalePoints(thisimg(1,grayscalePoints,1) >= params.thresholdValue); - % set the values to the threshold values - thisimg(1,lowThresholdPoints,:) = params.thresholdMin; - thisimg(1,hiThresholdPoints,:) = params.thresholdMax; - end - % display the surface - patch('vertices', baseSurface.vtcs, 'faces', baseSurface.tris,'FaceVertexCData', squeeze(thisimg),'facecolor','interp','edgecolor','none','Parent',axisHandle); - hold on - % make sure x direction is normal to make right/right - set(axisHandle,'XDir','reverse'); - set(axisHandle,'YDir','normal'); - set(axisHandle,'ZDir','normal'); - % set the camera taret to center of surface - camtarget(axisHandle,mean(baseSurface.vtcs)) - % set the size of the field of view in degrees - % i.e. 90 would be very wide and 1 would be ver - % narrow. 9 seems to fit the whole brain nicely - camva(axisHandle,9); - setMLRViewAngle(v,axisHandle); - % draw the rois - for roiNum = 1:length(roi) - patch('vertices', baseSurface.vtcs, 'faces', baseSurface.tris,'FaceVertexCData', roi{roiNum}.overlayImage,'facecolor','interp','edgecolor','none','FaceAlpha',params.roiAlpha,'Parent',axisHandle); - end - end - end - end +% ------------- Display the images +if params.mosaicNrows==0 + figPosition = get(f,'position'); + [nImageRows,nImageCols] = getArrayDimensions(nImages,figPosition(4)/figPosition(3)); else - % display the image (this is for flat maps and images) - image(img); + nImageRows = params.mosaicNrows; + nImageCols = ceil(nImages/nImageRows); end +xOuterMargin = .2; +yOuterMargin = .2; +colorbarColWidth = .15*cropY(2)/cropX(2); % adjust width of colorbar axes according +colorbarRowWidth = .15*cropX(2)/cropY(2); % to aspect ratio of image -% set axis -axis(axisHandle,'equal'); -axis(axisHandle,'off'); -axis(axisHandle,'tight'); -hold(axisHandle,'on'); - -% calcuate directions -params.plotDirections = 0; -if params.plotDirections - % calculate gradient on baseCoords - baseCoords = viewGet(v,'cursliceBaseCoords'); - baseCoords(baseCoords==0) = nan; - - fxx = baseCoords(round(end/2),:,1); - fxx = fxx(~isnan(fxx)); - fxx = fxx(end)-fxx(1); - fxy = baseCoords(:,round(end/2),1); - fxy = fxy(~isnan(fxy)); - fxy = fxy(end)-fxy(1); - - fyx = baseCoords(round(end/2),:,2); - fyx = fyx(~isnan(fyx)); - fyx = fyx(end)-fyx(1); - fyy = baseCoords(:,round(end/2),2); - fyy = fyy(~isnan(fyy)); - fyy = fyy(end)-fyy(1); - - fzx = baseCoords(round(end/2),:,3); - fzx = fzx(~isnan(fzx)); - fzx = fzx(end)-fzx(1); - fzy = baseCoords(:,round(end/2),3); - fzy = fzy(~isnan(fzy)); - fzy = fzy(end)-fzy(1); - -%samplingSize = 4; -%[fxx fxy] = gradient(baseCoords(1:samplingSize:end,1:samplingSize:end,1)); -%[fyx fyy] = gradient(baseCoords(1:samplingSize:end,1:samplingSize:end,2)); -%[fzx fzy] = gradient(baseCoords(1:samplingSize:end,1:samplingSize:end,3)); - -% get mean direction -%fxx = mean(fxx(~isnan(fxx(:))));fxy = mean(fxy(~isnan(fxy))); -%fyx = mean(fyx(~isnan(fyx(:))));fyy = mean(fyy(~isnan(fyy))); -%fzx = mean(fzx(~isnan(fzx(:))));fzy = mean(fzy(~isnan(fzy))); - - startx = 0.15;starty = 0.8;maxlength = 0.075; - scale = maxlength/max(abs([fxx fxy fyx fyy fzx fzy])); - - annotation('textarrow',startx+[scale*fzx 0],starty+[scale*fzy 0],'String','Left','HeadStyle','none'); - annotation('arrow',startx+[0 scale*fzx],starty+[0 scale*fzy]); - annotation('textarrow',startx+[-scale*fxx 0],starty+[-scale*fxy 0],'String','Dorsal','HeadStyle','none'); - annotation('arrow',startx+[0 -scale*fxx],starty+[0 -scale*fxy]); - annotation('textarrow',startx+[scale*fyx 0],starty+[scale*fyy 0],'String','Anterior','HeadStyle','none'); - annotation('arrow',startx+[0 scale*fyx],starty+[0 scale*fyy]); +switch(lower(params.colorbarLoc)) % column and row indices of image and colorbar axes + case 'outsideew' + imageCols = 3:2:nImageCols*2+1; + colorbarCols = [2 nImageCols*2+2]; + imageRows = 2:2:nImageRows*2; + colorbarRows = []; + case 'west' + imageCols = 3:3:nImageCols*3; + colorbarCols = 2:3:nImageCols*3-1; + imageRows = 2:2:nImageRows*2; + colorbarRows = []; + case 'east' + imageCols = 2:3:nImageCols*3-1; + colorbarCols = 3:3:nImageCols*3; + imageRows = 2:2:nImageRows*2; + colorbarRows = []; + case 'outsidesn' + imageCols = 2:2:nImageCols*2; + colorbarCols = []; + imageRows = 3:2:nImageRows*2+1; + colorbarRows = [2 nImageRows*2+2]; + case 'south' + imageCols = 2:2:nImageCols*2; + colorbarCols = []; + imageRows = 2:3:nImageRows*3-1; + colorbarRows = 3:3:nImageRows*3; + case 'north' + imageCols = 2:2:nImageCols*2; + colorbarCols = []; + imageRows = 3:3:nImageRows*3; + colorbarRows = 2:3:nImageRows*3-1; + case 'none' + imageCols = 2:2:nImageCols*2; + colorbarCols = []; + imageRows = 2:2:nImageRows*2; + colorbarRows = []; end +for iImage = 1:nImages + curImageCol = ceil(iImage/nImageRows); + curImageRow = iImage-floor((iImage-1)/nImageRows)*nImageRows; + colWidths = [xOuterMargin params.mosaicMargins(1)*ones(1,numel(imageCols)*2-1+numel(colorbarCols)) xOuterMargin]; + colWidths(imageCols)=1; + colWidths(colorbarCols) = colorbarColWidth; + rowWidths = [yOuterMargin params.mosaicMargins(2)*ones(1,numel(imageRows)*2-1+numel(colorbarRows)) yOuterMargin]; + rowWidths(imageRows)=1; + rowWidths(colorbarRows) = colorbarRowWidth; + imagePosition = getSubplotPosition(imageCols(curImageCol),imageRows(curImageRow),colWidths,rowWidths,0,0); + hImage = axes('parent',f,'position',imagePosition); + + if baseType == 2 % this is the surface display + curBase = viewGet(v,'curBase'); + for iBase = 1:viewGet(v,'numBase') + if viewGet(v,'baseType',iBase)>=2 + if viewGet(v,'baseMultiDisplay',iBase) || isequal(iBase,curBase) + % get the img (returned by refreshMLRDisplay. This is different + % for each base when we are displaying more than one. Note + % that this code hasn't been fully tested with all options yet (jg 2/18/2015) + if iBase ~= curBase + thisimg = altBase{iImage}(iBase).img; + else + thisimg = img{iImage}; + end + % taken from refreshMLRDisplay + baseSurface = viewGet(v,'baseSurface',iBase); + % threshold curvature if asked for + if params.thresholdCurvature + % get all grayscale points (assuming these are the ones that are from the surface) + grayscalePoints = find((thisimg(1,:,1)==thisimg(1,:,2))&(thisimg(1,:,3)==thisimg(1,:,2))); + % get points less than 0.5 + lowThresholdPoints = grayscalePoints(thisimg(1,grayscalePoints,1) < params.thresholdValue); + hiThresholdPoints = grayscalePoints(thisimg(1,grayscalePoints,1) >= params.thresholdValue); + % set the values to the threshold values + thisimg(1,lowThresholdPoints,:) = params.thresholdMin; + thisimg(1,hiThresholdPoints,:) = params.thresholdMax; + end + % display the surface + patch('vertices', baseSurface.vtcs, 'faces', baseSurface.tris,'FaceVertexCData', squeeze(thisimg),'facecolor','interp','edgecolor','none','Parent',hImage); + hold on + % make sure x direction is normal to make right/right + set(hImage,'XDir','reverse'); + set(hImage,'YDir','normal'); + set(hImage,'ZDir','normal'); + + % set the camera taret to center of surface + camtarget(hImage,mean(baseSurface.vtcs)) + % set the size of the field of view in degrees + % i.e. 90 would be very wide and 1 would be ver + % narrow. 9 seems to fit the whole brain nicely + camva(hImage,9); + setMLRViewAngle(v,hImage); + % draw the rois + for roiNum = 1:length(roi) + if isfield(roi{roiNum},'edgeSegmentCoords') + plot3(roi{roiNum}.edgeSegmentCoords(:,:,1),roi{roiNum}.edgeSegmentCoords(:,:,2),roi{roiNum}.edgeSegmentCoords(:,:,3),'color',roi{roiNum}.color,'lineWidth',params.roiLineWidth); + else + patch('vertices', baseSurface.vtcs, 'faces', baseSurface.tris,'FaceVertexCData', roi{roiNum}.overlayImage,'facecolor','interp','edgecolor','none','FaceAlpha',params.roiAlpha,'Parent',hImage); + end + end + end + end + end + + if params.camLight % add a light + material dull + lighting phong + camlight(params.camLightAz,params.camLightEl); + end -% display the colormap -if ~strcmp(params.colorbarLoc,'None') - H = colorbar(params.colorbarLoc); - % set the colorbar ticks, making sure to switch - % them if we have a vertical as opposed to horizontal bar - if ismember(params.colorbarLoc,{'EastOutside','WestOutside'}) - set(H,'XTick',get(gui.colorbar,'YTick')); - set(H,'Ytick',get(gui.colorbar,'XTick')); - set(H,'YTickLabel',get(gui.colorbar,'XTicklabel')); else - set(H,'YTick',get(gui.colorbar,'YTick')); - set(H,'Xtick',get(gui.colorbar,'XTick')); - set(H,'XTickLabel',get(gui.colorbar,'XTicklabel')); - end - set(H,'XColor',foregroundColor); - set(H,'YColor',foregroundColor); - set(get(H,'Title'),'String',params.colorbarTitle); - set(get(H,'Title'),'Interpreter','none'); - set(get(H,'Title'),'Color',foregroundColor); - set(get(H,'Title'),'FontSize',14); -end -% create a title -H = title(params.title); -set(H,'Interpreter','none'); -set(H,'Color',foregroundColor); -set(H,'FontSize',16); - -drawnow; - -% draw the roi -if baseType ~= 2 - sliceNum = viewGet(v,'currentSlice'); - label = {}; - disppercent(-inf,'(mrPrint) Rendering ROIs'); - visibleROIs = viewGet(v,'visibleROIs'); - for rnum = 1:length(roi) - % check for lines - if params.roiLineWidth > 0 - if ~isempty(roi{rnum}) - if isfield(roi{rnum},'lines') - if ~isempty(roi{rnum}.lines.x) - % get color - if strcmp(params.roiColor,'default') - color = roi{rnum}.color; - else - color = color2RGB(params.roiColor); - end - % deal with upSample factor - roi{rnum}.lines.x = roi{rnum}.lines.x*params.upSampleFactor; - roi{rnum}.lines.y = roi{rnum}.lines.y*params.upSampleFactor; - % labels for rois, just create here - % and draw later so they are always on top - if params.roiLabels - x = roi{rnum}.lines.x; - y = roi{rnum}.lines.y; - label{end+1}.x = median(x(~isnan(x))); - label{end}.y = median(y(~isnan(y))); - label{end}.str = viewGet(v,'roiName',visibleROIs(rnum)); - label{end}.color = color; - end - % if we have a circular apertuer then we need to - % fix all the x and y points so they don't go off the end - if strcmp(params.maskType,'Circular') - % get the distance from center - x = roi{rnum}.lines.x-xCenter; - y = roi{rnum}.lines.y-yCenter; - d = sqrt(x.^2+y.^2); - if strcmp(params.roiOutOfBoundsMethod,'Max radius') - % find the angle of all points - ang = atan(y./x); - ysign = (y > 0)*2-1; - xsign = (x > 0)*2-1; - newx = circd*cos(ang); - newy = circd*sin(ang); - % now reset all points past the maximum radius - % with values at the outermost edge of the aperture - x(d>circd) = newx(d>circd); - y(d>circd) = newy(d>circd); - % set them back in the structure - roi{rnum}.lines.x = xsign.*abs(x)+xCenter; - roi{rnum}.lines.y = ysign.*abs(y)+yCenter; - else - % set all values greater than the radius to nan - x(d>circd) = nan; - y(d>circd) = nan; - - % set them back in the strucutre - roi{rnum}.lines.x = x+xCenter; - roi{rnum}.lines.y = y+yCenter; - end - end - if ~params.roiSmooth - % draw the lines - line(roi{rnum}.lines.x,roi{rnum}.lines.y,'Color',color,'LineWidth',params.roiLineWidth); - end - end - end + %crop image + cropX(1) = min(cropX(1),size(img{iImage},2)); + cropY(1) = min(cropY(1),size(img{iImage},1)); + cropX(2) = min(cropX(2), size(img{iImage},2) - cropX(1))+1; + cropY(2) = min(cropY(2), size(img{iImage},1) - cropY(1))+1; + if cropX(1)>1 || cropY(1)>1 || cropX(2)1 && params.mosaic + set(H,'tickDirection','out'); % for mosaic display, colorbars are smaller, so orient the ticks outwards + end + + else + % copied/adapted from refresMLRDisplay to handle color bars for multiple overlays + cbar = permute(NaN(size(cmap{iImage})),[3 1 2]); + for iOverlay = 1:size(cmap{iImage},3) + cbar(iOverlay,:,:) = rescale2rgb(1:size(cmap{iImage},1),cmap{iImage}(:,:,iOverlay),[1,size(cmap{iImage},1)],1); + end + hColorbar = axes('Position',H.Position); + image(hColorbar,cbar); + hColorbarRightBorder = axes('Position',H.Position, ... + 'YaxisLocation','right','XTick',[],'box','off','color','none'); + H.TickLabels = []; % hide the original colorbar's tick labels + if size(cbar,1)==1 % not currently used. This could be used to draw a colorbar for a single overlay, instead of the above (with changes needed) + set(hColorbar,'YTick',[]); + set(hColorbar,'XTick',linspace(0.5,size(cmap{iImage},1)+0.5,5)); + set(hColorbar,'XTicklabel',num2str(linspace(overlays.colorRange(1),overlays.colorRange(2),5)',3)); + set(hColorbarRightBorder,'YTick',[]); + else % multiple color bars + set(hColorbar,'XTick',[]); + set(hColorbar,'YTick',(1:size(cbar,1))); + set(hColorbar,'YTickLabel',overlays.colorRange(:,1)); + set(hColorbarRightBorder,'Ylim',[.5 size(cbar,1)+.5],'YTick',(1:size(cbar,1))); + set(hColorbarRightBorder,'YTickLabel',flipud(overlays.colorRange(:,2))); + end + end + + %color bar title (label) + set(get(H,'Label'),'String',params.colorbarTitle{iImage}); + set(get(H,'Label'),'Interpreter',params.interpreter); + set(get(H,'Label'),'Color',foregroundColor); + set(get(H,'Label'),'FontSize',params.fontSize-2); + switch(lower(colorbarLoc)) % change default position of label depending on location of colorbar + case 'south' + set(get(H,'Label'),'position',[0.5 -1]) + case 'north' + set(get(H,'Label'),'position',[0.5 2]) + case 'east' + set(get(H,'Label'),'rotation',270) + set(get(H,'Label'),'position',[4.4 0.5]); + case 'west' + set(get(H,'Label'),'position',[-1.9 0.5]) + end + end + + % create a title + switch(lower(params.imageTitleLoc)) + case 'outsidesn' + if nImageRows==1 + imageTitleLoc = 'South'; + elseif curImageRow==1 + imageTitleLoc = 'North'; + elseif curImageRow==nImageRows + imageTitleLoc = 'South'; + else + imageTitleLoc = 'None'; + end + case 'outsideew' + if nImageRows==1 + imageTitleLoc = 'East'; + elseif curImageCol==1 + imageTitleLoc = 'West'; + elseif curImageCol==nImageCols + imageTitleLoc = 'East'; + else + imageTitleLoc = 'None'; + end + otherwise + imageTitleLoc = params.imageTitleLoc; + end + if ~strcmpi(imageTitleLoc,'none') && ~isempty(params.imageTitle{iImage}) && (iImage==1 || length(params.imageTitle{iImage})>1) + H = title(params.imageTitle{iImage},'parent',hImage); + switch(lower(imageTitleLoc)) + case 'north' + % don't change the default position + case 'south' + titlePosition = get(H,'Position'); + titlePosition(2) = size(img{iImage},1)*1.1; + set(H,'Position',titlePosition); + case 'west' + set(H,'Position',[-0.03*size(img{iImage},2) size(img{iImage},1)/2 0]); + set(H,'rotation',90) + case 'east' + set(H,'Position',[size(img{iImage},2)*1.03 size(img{iImage},1)/2 0]); + set(H,'rotation',270) + end + set(H,'Interpreter',params.interpreter); + set(H,'Color',foregroundColor); + set(H,'FontSize',params.fontSize); + end + + % --------------- Draw gyrus/sulcus boundary + if baseType == 1 && ~isempty(base.gyrusSulcusBoundary) + for iBoundary = 1:length(base.gyrusSulcusBoundary) + nPoints = size(base.gyrusSulcusBoundary{iBoundary},1); + % crop if necessary + boundaryCoords = base.gyrusSulcusBoundary{iBoundary} - repmat([cropX(1)-1 cropY(1)-1],nPoints,1); + boundaryCoords = boundaryCoords(boundaryCoords(:,1)>0 & boundaryCoords(:,2)>0 & boundaryCoords(:,1)2)]; + for iSegment = 1:length(interruptions)-1 + pointsToPlot = interruptions(iSegment)+1:interruptions(iSegment+1); + line(boundaryCoords(pointsToPlot,1), boundaryCoords(pointsToPlot,2),'Color',[0.05 0.05 0.05],'LineWidth',mrGetPref('roiContourWidth'),'Parent',hImage); % don't plot in black to make it easier to uniquely select in an SVG editor (e.g. Inkscape) + end end end end - for i = 1:length(label) - h = text(label{i}.x,label{i}.y,label{i}.str); - set(h,'Color',foregroundColor); - set(h,'Interpreter','None'); - set(h,'EdgeColor',label{i}.color); - set(h,'BackgroundColor',params.backgroundColor); - set(h,'FontSize',10); - set(h,'HorizontalAlignment','center'); + % --------------- Draw the ROIs + if baseType ~= 2 + if iImage==1 + sliceNum = viewGet(v,'currentSlice'); + label = {}; + mlrDispPercent(-inf,'(mrPrint) Rendering ROIs'); + visibleROIs = viewGet(v,'visibleROIs'); + for rnum = 1:length(roi) + % check for lines + if params.roiLineWidth > 0 && ~isempty(roi{rnum}) && isfield(roi{rnum},'lines') && ~isempty(roi{rnum}.lines.x) + % get color + if strcmp(params.roiColor,'default') + color{rnum} = roi{rnum}.color; + else + color{rnum} = color2RGB(params.roiColor); + end + % deal with upSample factor + roi{rnum}.lines.x = roi{rnum}.lines.x*upSampleFactor; + roi{rnum}.lines.y = roi{rnum}.lines.y*upSampleFactor; + % labels for rois, just create here + % and draw later so they are always on top + if params.roiLabels + x = roi{rnum}.lines.x; + y = roi{rnum}.lines.y; + label{end+1}.x = median(x(~isnan(x))); + label{end}.y = median(y(~isnan(y))); + label{end}.str = viewGet(v,'roiName',visibleROIs(rnum)); + label{end}.color = color{rnum}; + end + % if we have a circular aperture then we need to + % fix all the x and y points so they don't go off the end + if strcmp(params.maskType,'Circular') + % get the distance from center + x = roi{rnum}.lines.x-xCenter; + y = roi{rnum}.lines.y-yCenter; + d = sqrt(x.^2+y.^2); + if strcmp(params.roiOutOfBoundsMethod,'Max radius') + % find the angle of all points + ang = atan(y./x); + ysign = (y > 0)*2-1; + xsign = (x > 0)*2-1; + newx = circd*cos(ang); + newy = circd*sin(ang); + % now reset all points past the maximum radius + % with values at the outermost edge of the aperture + x(d>circd) = newx(d>circd); + y(d>circd) = newy(d>circd); + % set them back in the structure + roi{rnum}.lines.x = xsign.*abs(x)+xCenter; + roi{rnum}.lines.y = ysign.*abs(y)+yCenter; + else + % set all values greater than the radius to nan + x(d>circd) = nan; + y(d>circd) = nan; + + % set them back in the strucutre + roi{rnum}.lines.x = x+xCenter; + roi{rnum}.lines.y = y+yCenter; + end + end + + % if cropping, correct x and y coordinates and remove lines falling outside the crop box + roi{rnum}.lines.x = roi{rnum}.lines.x - cropX(1) + 1; + roi{rnum}.lines.y(:,all(roi{rnum}.lines.x > cropX(2)+0.5)) = []; + roi{rnum}.lines.x(:,all(roi{rnum}.lines.x > cropX(2)+0.5)) = []; + roi{rnum}.lines.x(roi{rnum}.lines.x > cropX(2)+0.5) = cropX(2)+0.5; + roi{rnum}.lines.y(:,all(roi{rnum}.lines.x < 0.5)) = []; + roi{rnum}.lines.x(:,all(roi{rnum}.lines.x < 0.5)) = []; + roi{rnum}.lines.x(roi{rnum}.lines.x < 0.5) = 0.5; + roi{rnum}.lines.y = roi{rnum}.lines.y - cropY(1) + 1; + roi{rnum}.lines.x(:,all(roi{rnum}.lines.y > cropY(2)+0.5)) = []; + roi{rnum}.lines.y(:,all(roi{rnum}.lines.y > cropY(2)+0.5)) = []; + roi{rnum}.lines.y(roi{rnum}.lines.y > cropY(2)+0.5) = cropY(2)+0.5; + roi{rnum}.lines.x(:,all(roi{rnum}.lines.y < 0.5)) = []; + roi{rnum}.lines.y(:,all(roi{rnum}.lines.y < 0.5)) = []; + roi{rnum}.lines.y(roi{rnum}.lines.y < 0.5) = 0.5; + + end + end + end + mlrDispPercent(inf); + + % draw the lines + for rnum = 1:length(roi) + if params.roiLineWidth > 0 && ~isempty(roi{rnum}) && isfield(roi{rnum},'lines') && ~isempty(roi{rnum}.lines.x) && ~params.roiSmooth + line(roi{rnum}.lines.x,roi{rnum}.lines.y,'Color',color{rnum},'LineWidth',params.roiLineWidth,'parent',hImage); + end + end + + % draw the labels + for i = 1:length(label) + h = text(label{i}.x,label{i}.y,label{i}.str,'parent',hImage); + set(h,'Color',foregroundColor); + set(h,'Interpreter','None'); + set(h,'EdgeColor',label{i}.color); + set(h,'BackgroundColor',params.backgroundColor); + set(h,'FontSize',10); + set(h,'HorizontalAlignment','center'); + end + end - disppercent(inf); + drawnow; end +set(f,'Pointer','arrow'); % bring up print dialog global mrPrintWarning @@ -477,7 +1004,8 @@ end % make sure the image size is 2D -upSampImSize = imageSize(1:2)*params.upSampleFactor; +upsampleFactor = 2^params.upsampleFactor; +upSampImSize = imageSize(1:2)*upSampleFactor; % Initialize the output RGB image roiRGB = zeros([upSampImSize 3]); @@ -494,7 +1022,7 @@ baseName = sprintf('%s%s',baseName,fixBadChars(corticalDepth,{'.','_'})); end -disppercent(-inf,'(mrPrint) Calculating smoothed ROIs'); +mlrDispPercent(-inf,'(mrPrint) Calculating smoothed ROIs'); for r=1:length(roi) if ~isempty(roi{r}) % get the x and y image coordinates @@ -512,20 +1040,20 @@ end % upSample the coords - x = x.*params.upSampleFactor; - y = y.*params.upSampleFactor; + x = x.*upSampleFactor; + y = y.*upSampleFactor; - upSampSq = params.upSampleFactor^2; + upSampSq = upSampleFactor^2; n = length(x)*upSampSq; hiResX = zeros(1,n); hiResY = zeros(1,n); - for ii=1:params.upSampleFactor - offsetX = (ii-params.upSampleFactor-.5)+params.upSampleFactor/2; - for jj=1:params.upSampleFactor - offsetY = (jj-params.upSampleFactor-.5)+params.upSampleFactor/2; - hiResX((ii-1)*params.upSampleFactor+jj:upSampSq:end) = x+offsetX; - hiResY((ii-1)*params.upSampleFactor+jj:upSampSq:end) = y+offsetY; + for ii=1:upSampleFactor + offsetX = (ii-upSampleFactor-.5)+upSampleFactor/2; + for jj=1:upSampleFactor + offsetY = (jj-upSampleFactor-.5)+upSampleFactor/2; + hiResX((ii-1)*upSampleFactor+jj:upSampSq:end) = x+offsetX; + hiResY((ii-1)*upSampleFactor+jj:upSampSq:end) = y+offsetY; end end @@ -543,7 +1071,7 @@ % blur it some, but only need to do this % if we haven't already upsampled - if params.upSampleFactor <= 2 + if upSampleFactor <= 2 roiBits = blur(roiBits,2); roiBits(roiBits) % by: justin gardner, taken out from mrQuit by julien besle % date: 07/11/08, 2011/08/05 % purpose: saves view and view settings in session directory +% -function mrSaveView(v) +function mrSaveView(v,lastViewFile) % remember figure location try @@ -22,6 +23,10 @@ function mrSaveView(v) end end +if ieNotDefined('lastViewFile') + lastViewFile = 'mrLastView'; +end + % remember settings that are not in view mrGlobals; if isfield(MLR,'panels') @@ -32,7 +37,7 @@ function mrSaveView(v) homeDir = viewGet(v,'homeDir'); try - disppercent(-inf,sprintf('(mrSaveView) Saving %s/mrLastView',homeDir)); + mlrDispPercent(-inf,sprintf('(mrSaveView) Saving %s/%s',homeDir,lastViewFile)); % save the view in the current directory view = v; % replace view.figure with figure number (to prevent opening on loading @@ -45,14 +50,14 @@ function mrSaveView(v) viewSettings.version = 2.0; if getfield(whos('view'),'bytes')<2e9 - save(fullfile(homeDir,'mrLastView'), 'view','viewSettings', '-V6'); + save(fullfile(homeDir,lastViewFile), 'view','viewSettings', '-V6'); else mrWarnDlg('(mrSaveView) Variable view is more than 2Gb, using option -v7.3 to save'); - save(fullfile(homeDir,'mrLastView'), 'view', 'viewSettings', '-v7.3'); + save(fullfile(homeDir,lastViewFile), 'view', 'viewSettings', '-v7.3'); end % save .mrDefaults in the home directory - disppercent(inf); + mlrDispPercent(inf); catch - disppercent(inf); - mrErrorDlg('(mrQuit) Could not save mrLastView.mat'); + mlrDispPercent(inf); + mrErrorDlg(sprintf('(mrQuit) Could not save %s',lastViewFile)); end diff --git a/mrLoadRet/GUI/mrSurfViewer.m b/mrLoadRet/GUI/mrSurfViewer.m index b1a5a8d24..e3586177b 100644 --- a/mrLoadRet/GUI/mrSurfViewer.m +++ b/mrLoadRet/GUI/mrSurfViewer.m @@ -31,18 +31,28 @@ if (nargin == 1) && isstr(outerSurface) && ~mlrIsFile(outerSurface) event = outerSurface; elseif (nargin == 1) && isstr(outerSurface) + outerSurface = stripext(outerSurface); if ~isempty(strfind(outerSurface,'WM')) - innerSurface{1} = outerSurface; + stringIndex = strfind((outerSurface),'WM'); + token = outerSurface(1:stringIndex-1); + remain = outerSurface(stringIndex+2:end); clear outerSurface; - outerSurface{1} = sprintf('%sGM.off',stripext(stripext(innerSurface{1}),'WM')); + innerSurface{1} = sprintf('%sWM%s.off',token,remain); + outerSurface{1} = sprintf('%sGM%s.off',token,remain); elseif ~isempty(strfind(outerSurface,'GM')) - innerSurface{1} = sprintf('%sWM.off',stripext(stripext(outerSurface),'GM')); + stringIndex = strfind((outerSurface),'GM'); + token = outerSurface(1:stringIndex-1); + remain = outerSurface(stringIndex+2:end); clear outerSurface; - outerSurface{1} = sprintf('%sGM.off',stripext(stripext(innerSurface{1}),'WM')); + innerSurface{1} = sprintf('%sWM%s.off',token,remain); + outerSurface{1} = sprintf('%sGM%s.off',token,remain); elseif ~isempty(strfind(outerSurface,'Outer')) - innerSurface{1} = sprintf('%sInner.off',stripext(stripext(outerSurface),'Outer')); + stringIndex = strfind((outerSurface),'Outer'); + token = outerSurface(1:stringIndex-1); + remain = outerSurface(stringIndex+5:end); clear outerSurface; - outerSurface{1} = sprintf('%sOuter.off',stripext(stripext(innerSurface{1}),'Inner')); + innerSurface{1} = sprintf('%sInner%s.off',token,remain); + outerSurface{1} = sprintf('%sOuter%s.off',token,remain); else innerSurface{1} = outerSurface; clear outerSurface; @@ -108,7 +118,7 @@ filepath = ''; -disppercent(-inf,'(mrSurfViewer) Loading surfaces'); +mlrDispPercent(-inf,'(mrSurfViewer) Loading surfaces'); % load the surface gSurfViewer.outerSurface = loadSurfOFF(sprintf('%s.off',stripext(outerSurface{1}))); @@ -176,15 +186,28 @@ % load any vff file curv = {}; curvDir = dir('*.vff'); +nMaxCommonCharacters = 0; for i = 1:length(curvDir) if ~any(strcmp(curvDir(i).name,curv)) % check length of file matches our patch vffhdr = myLoadCurvature(curvDir(i).name, filepath, 1); if ~isempty(vffhdr) curv{end+1} = curvDir(i).name; + % check if the filename matches the outer surface that was put on top of the list + outerString = stripext(outerSurface{1}); + curvString = stripext(curv{end}); + nMaxChars = min(numel(outerString),numel(curvString)); + nCommonCharacters = sum(cumprod(outerString(end:-1:end-nMaxChars+1)==curvString(end:-1:end-nMaxChars+1))); + if nCommonCharacters>nMaxCommonCharacters + topCurvatureFile = numel(curv); + nMaxCommonCharacters = nCommonCharacters; + end end end end +if nMaxCommonCharacters + curv = putOnTopOfList(curv{topCurvatureFile},curv); +end % check to see if we have any possible curvatures if isempty(curv) @@ -200,7 +223,7 @@ if isempty(gSurfViewer.curv) return end -disppercent(inf); +mlrDispPercent(inf); curv{end+1} = 'Find file'; % guess any nifti file for anatomy @@ -716,11 +739,11 @@ function switchAnatomy(params) end % load the anatomy and view -disppercent(-inf,sprintf('(mrSurfViewer) Load %s',params.anatomy)); +mlrDispPercent(-inf,sprintf('(mrSurfViewer) Load %s',params.anatomy)); if initAnatomy(params.anatomy); gSurfViewer = xformSurfaces(gSurfViewer); else - disppercent(inf); + mlrDispPercent(inf); return end @@ -729,7 +752,7 @@ function switchAnatomy(params) gSurfViewer.whichSurface = 5; set(gParams.ui.varentry{1},'Value',gSurfViewer.whichSurface) refreshFlatViewer([],1); -disppercent(inf); +mlrDispPercent(inf); %%%%%%%%%%%%%%%%%%%%%%%% %% switchOverlays %% @@ -771,7 +794,7 @@ function switchFile(whichSurface,params) end % try to load it -disppercent(-inf,sprintf('(mrSurfViewer) Loading %s',filename)); +mlrDispPercent(-inf,sprintf('(mrSurfViewer) Loading %s',filename)); if filename ~= 0 if strcmp(whichSurface,'curv') file = myLoadCurvature(filename); @@ -818,7 +841,7 @@ function switchFile(whichSurface,params) gSurfViewer = xformSurfaces(gSurfViewer); refreshFlatViewer([],1); -disppercent(inf); +mlrDispPercent(inf); %%%%%%%%%%%%%%%%%%%%%%% %% myLoadSurface %% diff --git a/mrLoadRet/GUI/refreshMLRDisplay.m b/mrLoadRet/GUI/refreshMLRDisplay.m index 6372736f5..6409f5c53 100644 --- a/mrLoadRet/GUI/refreshMLRDisplay.m +++ b/mrLoadRet/GUI/refreshMLRDisplay.m @@ -1,4 +1,4 @@ -function [img base roi overlays altBase] = refreshMLRDisplay(viewNum) +function [img, base, roi, overlays, altBase, v] = refreshMLRDisplay(viewNum) % $Id: refreshMLRDisplay.m 2838 2013-08-12 12:52:20Z julien $ mrGlobals @@ -18,6 +18,10 @@ fig = viewGet(v,'figNum'); if ~isempty(fig) gui = guidata(fig); +elseif nargout>0 + gui.axis = []; +else + return % if there is no GUI and no output arguments, then there is nothing to do end baseNum = viewGet(v,'currentBase'); baseType = viewGet(v,'baseType'); @@ -49,12 +53,12 @@ v = viewSet(v,'baseCache','init'); end -if verbose>1,disppercent(-inf,'Clearing figure');,end +if verbose>1,mlrDispPercent(-inf,'Clearing figure');,end % note: This cla here is VERY important. Otherwise % we keep drawing over old things on the axis and % the rendering gets impossibly slow... -j. if ~isempty(fig), cla(gui.axis);end -if verbose>1,disppercent(inf);,end +if verbose>1,mlrDispPercent(inf);,end % check if these are inplanes (not flats or surfaces) % and see if we should draw all three possible views @@ -155,7 +159,7 @@ end % turn on 3D free rotate if we are just displaying the one 3D axis -if ~mrInterrogator('isactive',viewNum) +if ~isempty(fig) && ~mrInterrogator('isactive',viewNum) if (baseType == 2) || (baseMultiAxis == 2) mlrSetRotate3d(v,'on'); else @@ -163,7 +167,9 @@ end end -axes(gui.axis); +if ~isempty(gui.axis) + axes(gui.axis); +end % draw any other base that has multiDisplay set % do this for surfaces for now or for 3D anatomies @@ -180,28 +186,34 @@ end end -if (baseType == 0) && (baseMultiAxis>0) - % set the camera target to center of the volume - camtarget(gui.axis,baseDims([2 1 3])/2) +if ~isempty(gui.axis) + if (baseType == 0) && (baseMultiAxis>0) + % set the camera target to center of the volume + camtarget(gui.axis,baseDims([2 1 3])/2) + end end -if verbose>1,disppercent(-inf,'rendering');end -%this is really stupid: the ListboxTop property of listbox controls seems to be updated only when the control is drawn -%In cases where it is more than the number of overlay names in the box -%it has to be changed, but it is necessary to wait until drawnow before changing it -%otherwise the change is not taken into account -%Even in this case, It still outputs a warning, that has to be disabled -if strcmp(get(gui.overlayPopup,'style'),'listbox') - warning('off','MATLAB:hg:uicontrol:ListboxTopMustBeWithinStringRange'); - set(gui.overlayPopup,'ListboxTop',min(get(gui.overlayPopup,'ListboxTop'),length(get(gui.overlayPopup,'string')))); -end +if verbose>1,mlrDispPercent(-inf,'rendering');end -%draw the figure -drawnow('update'); -if strcmp(get(gui.overlayPopup,'style'),'listbox') - warning('on','MATLAB:hg:uicontrol:ListboxTopMustBeWithinStringRange'); +if ~isempty(fig) + %this is really stupid: the ListboxTop property of listbox controls seems to be updated only when the control is drawn + %In cases where it is more than the number of overlay names in the box + %it has to be changed, but it is necessary to wait until drawnow before changing it + %otherwise the change is not taken into account + %Even in this case, It still outputs a warning, that has to be disabled + if strcmp(get(gui.overlayPopup,'style'),'listbox') + warning('off','MATLAB:hg:uicontrol:ListboxTopMustBeWithinStringRange'); + set(gui.overlayPopup,'ListboxTop',min(get(gui.overlayPopup,'ListboxTop'),length(get(gui.overlayPopup,'string')))); + end + + %draw the figure + drawnow('update'); + if strcmp(get(gui.overlayPopup,'style'),'listbox') + warning('on','MATLAB:hg:uicontrol:ListboxTopMustBeWithinStringRange'); + end end -if verbose>1,disppercent(inf);end + +if verbose>1,mlrDispPercent(inf);end if verbose,toc,end % set pointer back @@ -218,7 +230,7 @@ % Get current view and baseNum. % Get interp preferences. % Get slice, scan, alpha, rotate, and sliceIndex from the gui. -if verbose>1,disppercent(-inf,'viewGet');,end +if verbose>1,mlrDispPercent(-inf,'viewGet');,end % get variables for current base, but only if they are not set in input % if the arguments sliceIndex and slice are set it means we are being % called to do a "mutliAxis" plot -one in which we are plotting each @@ -238,13 +250,13 @@ else rotate = 0; end -if verbose>1,disppercent(inf);end +if verbose>1,mlrDispPercent(inf);end fig = viewGet(v,'figNum'); %disp(sprintf('(refreshMLRDIsplay:dispBase) DEBUG: sliceIndex: %i slice: %i',sliceIndex,slice)); % Compute base coordinates and extract baseIm for the current slice -if verbose,disppercent(-inf,'extract base image');end +if verbose,mlrDispPercent(-inf,'extract base image');end base = viewGet(v,'baseCache',baseNum,slice,sliceIndex,rotate); if isempty(base) [base.im,base.coords,base.coordsHomogeneous] = ... @@ -268,7 +280,7 @@ case 'white' backgroundColor = [1 1 1]; end - if baseType==1 %make smooth transition beetween figure background and flat map + if baseType==1 %make smooth transition between figure background and flat map alpha = zeros(base.dims(1),base.dims(2)); alpha(isnan(base.im))=1; kernel = gaussianKernel2D(3) ; @@ -278,8 +290,6 @@ mask = repmat(permute(backgroundColor,[1 3 2]),[base.dims(1) base.dims(2) 1]); base.RGB = base.RGB.*alpha+(1-alpha).*mask; base.RGB=min(1,max(0,base.RGB)); - base.gyrusSulcusBoundary = edge(base.im>0.5+0)&edge(base.im<0.5+0); %we assume that 0.5 represents - % the curvature boundary, which should be the case for flat maps made from freesurfer-imported surfaces else base.RGB = reshape(base.RGB,base.dims(1)*base.dims(2),3); base.RGB(isnan(base.im),:)=repmat(backgroundColor,[nnz(isnan(base.im)) 1]); @@ -290,9 +300,9 @@ end % save extracted image v = viewSet(v,'baseCache',base,baseNum,slice,sliceIndex,rotate); - if verbose,disppercent(inf);disp('Recomputed base');end + if verbose,mlrDispPercent(inf);disp('Recomputed base');end else - if verbose,disppercent(inf);end + if verbose,mlrDispPercent(inf);end end % for surfaces and flats calculate things based on cortical depth @@ -311,7 +321,7 @@ % right now combinations of overlays are cached as is % but it would make more sense to cache them separately % because actual blending occurs after they're separately computed -if verbose,disppercent(-inf,'extract overlays images');end +if verbose,mlrDispPercent(-inf,'extract overlays images');end overlays = viewGet(v,'overlayCache',baseNum,slice,sliceIndex,rotate); if isempty(overlays) % get the transform from the base to the scan @@ -321,17 +331,17 @@ overlays = addBaseOverlays(v,baseNum,overlays); % save in cache v = viewSet(v,'overlayCache',overlays,baseNum,slice,sliceIndex,rotate); - if verbose,disppercent(inf);disp('Recomputed overlays');end + if verbose,mlrDispPercent(inf);disp('Recomputed overlays');end else - if verbose,disppercent(inf);end + if verbose,mlrDispPercent(inf);end end v = viewSet(v,'cursliceOverlayCoords',overlays.coords); % Combine base and overlays -if verbose>1,disppercent(-inf,'combine base and overlays');,end +if verbose>1,mlrDispPercent(-inf,'combine base and overlays');,end if ~isempty(base.RGB) & ~isempty(overlays.RGB) - switch(mrGetPref('colorBlending')) - case 'Alpha blend' + switch(lower(mrGetPref('colorBlending'))) + case 'alpha blend' % alpha blending (non-commutative 'over' operator, each added overlay is another layer on top) % since commutative, depends on order img = base.RGB; @@ -339,7 +349,7 @@ img = overlays.alphaMaps(:,:,:,iOverlay).*overlays.RGB(:,:,:,iOverlay)+(1-overlays.alphaMaps(:,:,:,iOverlay)).*img; end - case 'Additive' + case 'additive' %additive method (commutative: colors are blended in additive manner and then added as a layer on top of the base) % 1) additively multiply colors weighted by their alpha RGB = overlays.alphaMaps.*overlays.RGB; @@ -353,7 +363,7 @@ % 2) overlay result on base using the additively computed alpha (but not for the blended overlays because pre-multiplied) img = img+(1-alpha).*base.RGB; - case 'Contours' + case 'contours' if size(overlays.RGB,4)==1 img=base.RGB; %only display base in the background contours=overlays.overlayIm; %display first overlay as contours @@ -372,12 +382,24 @@ mrWarnDlg('(refreshMLRDisplay) Number of overlays limited to 2 for ''Contours'' display option'); end end + displayGyrusSulcusBoundary = viewGet(v,'displayGyrusSulcusBoundary'); + base.gyrusSulcusBoundary = []; % this will be needed if the bounady need to be replotted outside of refreshMLRDisplay (e.g. in mrPrint) if baseType==1 && ~isempty(displayGyrusSulcusBoundary) && displayGyrusSulcusBoundary + + gyrusSulcusBoundaryMask = edge(base.im>0.5+0)&edge(base.im<0.5+0); %we assume that 0.5 represents + % the curvature boundary, which should be the case for flat maps made from freesurfer-imported surfaces + + if isempty(which('bwconncomp')) || ~license('test','Image_Toolbox') % if bwconncomp is not available, we'll plot the gyrus/sulcus boundary as pixels on the image (in which case we're not setting the line width) + alreadyPlottedGSboundary = true; img = reshape(img,[prod(base.dims) 3]); - img(base.gyrusSulcusBoundary>0,:) = repmat([0 0 0],nnz(base.gyrusSulcusBoundary>0),1); + img(gyrusSulcusBoundaryMask>0,:) = repmat([0 0 0],nnz(gyrusSulcusBoundaryMask>0),1); img = reshape(img,[base.dims 3]); + else + alreadyPlottedGSboundary = false; % otherwise it will be plotted as lines on top of the image later + end end + cmap = overlays.cmap; cbarRange = overlays.colorRange; elseif ~isempty(base.RGB) @@ -390,7 +412,7 @@ cmap = gray(1); cbarRange = [0 1]; end -if verbose>1,disppercent(inf);,end +if verbose>1,mlrDispPercent(inf);,end % If no image at this point then return if ieNotDefined('img') @@ -404,7 +426,7 @@ nROIs = viewGet(v,'numberOfROIs'); if nROIs % if baseType <= 1 - roi = displayROIs(v,slice,sliceIndex,baseNum,base.coordsHomogeneous,base.dims,rotate,verbose); + roi = displayROIs(v,hAxis,slice,sliceIndex,baseNum,base.coordsHomogeneous,base.dims,rotate,verbose); % end else roi = []; @@ -419,8 +441,8 @@ displayColorbar(gui,cmap,cbarRange,verbose) end -% if we are not displaying then, just return -% after computing rois +% if we are not displaying then, just return %JB: this seems redundant with the previous, identical, block of code +% after computing rois % Does it ever happen that there is a figure, but the axis is empty? (a figure with just a color bar?) if isempty(hAxis) % just compute the axis (displayROIs will not draw % if hAxis is set to empty. We need the ROI x,y for @@ -435,7 +457,7 @@ end % Display the image -if verbose>1,disppercent(-inf,'Displaying image');,end +if verbose>1,mlrDispPercent(-inf,'Displaying image');,end if baseType <= 1 % set the renderer to painters (this seems % to avoid some weird gliches in the OpenGL @@ -533,10 +555,45 @@ axis(hAxis,'image'); % (if not, the aspect has already been set for the current base) end end -if verbose>1,disppercent(inf);,end -if verbose>1,disppercent(-inf,'Setting axis');,end +if verbose>1,mlrDispPercent(inf);,end +if verbose>1,mlrDispPercent(-inf,'Setting axis');,end axis(hAxis,'off'); -if verbose>1,disppercent(inf);,end +if verbose>1,mlrDispPercent(inf);,end + +% display gyrus/sulcus boundary (only if flat) +displayGyrusSulcusBoundary = viewGet(v,'displayGyrusSulcusBoundary'); +if baseType==1 && ~isempty(displayGyrusSulcusBoundary) && displayGyrusSulcusBoundary && ~alreadyPlottedGSboundary + + % get connected boundaries and plot them separately + cc = bwconncomp(gyrusSulcusBoundaryMask,8); + flatDims = size(gyrusSulcusBoundaryMask); + for iBoundary = 1:cc.NumObjects + [boundaryCoordsY, boundaryCoordsX] = ind2sub(size(gyrusSulcusBoundaryMask),cc.PixelIdxList{iBoundary}); + % now we have to order these points so that we can plot them as a single line + % start from the point that is furthest away from the center of the flat map, because that will be an extremity (in the case of an open boundary finishing at the edge of the map) + [~,startingPointIndex] = max((boundaryCoordsX-flatDims(2)/2).^2 + (boundaryCoordsY-flatDims(1)/2).^2); + % then we find consecutive points along the boundary by taking the closest point each time + nPoints = size(boundaryCoordsX,1); + sortedCoords = nan(nPoints,2); +% distances = nan(nPoints,1); + remainingCoords = [boundaryCoordsX boundaryCoordsY]; + sortedCoords(1,:) = remainingCoords(startingPointIndex,:); + remainingCoords(startingPointIndex,:) = []; % remove the starting point + for iPoint = 2:nPoints + % find next closest point + startingPointCoords = sortedCoords(iPoint-1,:); + [~,nextPoint] = min((remainingCoords(:,1)-startingPointCoords(1)).^2 + (remainingCoords(:,2)-startingPointCoords(2)).^2); + sortedCoords(iPoint,:) = remainingCoords(nextPoint,:); + remainingCoords(nextPoint,:) = []; + end + if sum((sortedCoords(1,:)-sortedCoords(end,:)).^2) <= 2 % if the last point is one pixel (or less) away from the first point, then we have a closed boundary + sortedCoords(end+1,:) = sortedCoords(1,:); % so, we close the loop + end + base.gyrusSulcusBoundary{iBoundary} = sortedCoords; + line(sortedCoords(:,1), sortedCoords(:,2),'Color',[0 0 0],'LineWidth',mrGetPref('roiContourWidth'),'Parent',hAxis); + end + +end % Display ROIs nROIs = viewGet(v,'numberOfROIs'); @@ -566,7 +623,7 @@ function displayColorbar(gui,cmap,cbarRange,verbose) else set(gui.colorbar,'Visible','on'); - if verbose>1,disppercent(-inf,'colorbar');,end + if verbose>1,mlrDispPercent(-inf,'colorbar');,end cbar = permute(NaN(size(cmap)),[3 1 2]); for iOverlay = 1:size(cmap,3) cbar(iOverlay,:,:) = rescale2rgb(1:size(cmap,1),cmap(:,:,iOverlay),[1,size(cmap,1)],1); @@ -588,7 +645,7 @@ function displayColorbar(gui,cmap,cbarRange,verbose) set(gui.colorbarRightBorder,'YTickLabel',flipud(cbarRange(:,2))); end end - if verbose>1,disppercent(inf);,end + if verbose>1,mlrDispPercent(inf);,end end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -746,6 +803,7 @@ function displayColorbar(gui,cmap,cbarRange,verbose) selectedROI = viewGet(view,'currentroi'); labelROIs = viewGet(view,'labelROIs'); +roiContourWidth = mrGetPref('roiContourWidth'); % Order in which to draw the ROIs order = viewGet(view,'visibleROIs'); @@ -758,13 +816,13 @@ function displayColorbar(gui,cmap,cbarRange,verbose) % if not found if isempty(roiCache) if verbose - disppercent(-inf,sprintf('Computing ROI base coordinates for %i:%s',r,viewGet(view,'roiName',r))); + mlrDispPercent(-inf,sprintf('Computing ROI base coordinates for %i:%s',r,viewGet(view,'roiName',r))); end % Get ROI coords transformed to the base dimensions roi{r}.roiBaseCoords = getROIBaseCoords(view,baseNum,r); % save to cache view = viewSet(view,'ROICache',roi{r},r,baseNum,rotate); - if verbose,disppercent(inf);end + if verbose,mlrDispPercent(inf);end else roi{r} = roiCache; end @@ -779,7 +837,7 @@ function displayColorbar(gui,cmap,cbarRange,verbose) baseType = viewGet(view,'baseType',baseNum); % Draw it -if verbose>1,disppercent(-inf,'Drawing ROI');,end +if verbose>1,mlrDispPercent(-inf,'Drawing ROI');,end % get which color to draw the selected ROI in selectedROIColor = mrGetPref('selectedROIColor'); @@ -819,14 +877,14 @@ function displayColorbar(gui,cmap,cbarRange,verbose) (length(roi{r}.(baseName)) < sliceIndex) || ... (isempty(roi{r}.(baseName){sliceIndex}))) if verbose - disppercent(-inf,sprintf('Computing ROI image coordinates for %i:%s',r,viewGet(view,'roiName',r))); + mlrDispPercent(-inf,sprintf('Computing ROI image coordinates for %i:%s',r,viewGet(view,'roiName',r))); end [x y s] = getROIImageCoords(view,roi{r}.roiBaseCoords,sliceIndex,baseNum,baseCoordsHomogeneous,imageDims); % keep the coordinates roi{r}.(baseName){sliceIndex}.x = x; roi{r}.(baseName){sliceIndex}.y = y; roi{r}.(baseName){sliceIndex}.s = s; - if verbose, disppercent(inf); end + if verbose, mlrDispPercent(inf); end % save in cache view = viewSet(view,'ROICache',roi{r},r,baseNum,rotate); else @@ -841,30 +899,49 @@ function displayColorbar(gui,cmap,cbarRange,verbose) doPerimeter = ismember(option,{'all perimeter','selected perimeter','group perimeter'}); if baseType == 2 baseSurface = getBaseSurface(view,baseNum); %get baseSurface coordinates, converted to different base space if necessary - if 0 %%doPerimeter - if verbose, disppercent(-inf,'(refreshMLRDisplay) Computing perimeter'); end + if doPerimeter % draw lines linking outer vertices of the ROI + % find all triangles involving at least 2 of the ROI vertices. These will be the edges of the any group of contiguous vertices + % (voxels that are not connected to any other voxels will not be represented using this method) + edgeTriangleIndices = sum(ismember(baseSurface.tris,y),2) == 2; + edgeTriangles = baseSurface.tris(edgeTriangleIndices,:)'; % (transpose so that the indices of two linked vertices are consecutive when indexing below) + edgeSegments = reshape(edgeTriangles(ismember(edgeTriangles,y)),2,[])'; % keep only the 2 vertices corresponding to the ROI edges + edgeSegments = sort(edgeSegments,2); % sort the edge vertices order of all segments so identical segments can be identified + edgeSegments = unique(edgeSegments,'rows'); % and removed + roi{r}.edgeSegmentCoords = reshape(baseSurface.vtcs(edgeSegments',:),2,[],3); % get the coordinates of the segment vertices (and keep for further use in e.g. mrPrint) + % (a more accurate method would be to compute the intersection of the volume voxels with the surface, but this is good enough for large enough ROIs) + % plot ROI outline + hAxis.NextPlot = 'add'; + hOutline = plot3(hAxis,roi{r}.edgeSegmentCoords(:,:,1),roi{r}.edgeSegmentCoords(:,:,2),roi{r}.edgeSegmentCoords(:,:,3),'color',roi{r}.color,'lineWidth',roiContourWidth); + + roiAlpha = 0; % we will draw the ROI as surface as well, for compatibility with mrInterrogator (and maybe others), but make it fully invisivble + else + roiAlpha = 0.4; + end + if 0 % this is a previous attempt at drawing a perimeter on the surface: by drawing the outer vertices as patches + % it is too long (probably because of the loop) and the result does not look so great + if verbose, mlrDispPercent(-inf,'(refreshMLRDisplay) Computing perimeter'); end baseCoordMap = viewGet(view,'baseCoordMap'); newy = []; for i = 1:length(y) - % find all the triangles that this vertex belongs to - [row col] = find(ismember(baseCoordMap.tris,y(i))); - % get all the neighboring vertices - neighboringVertices = baseCoordMap.tris(row,:); - neighboringVertices = setdiff(neighboringVertices(:),y(i)); - % if there are any neighboring vertices that are - % not in he roi then this vertex is an edge - numNeighbors(i) = length(neighboringVertices); - numROINeighbors(i) = sum(ismember(neighboringVertices,y)); - if numNeighbors(i) ~= numROINeighbors(i) - newy = union(newy,baseCoordMap.tris(row(1),:)); - end - if verbose, disppercent(i/length(y)); end; + % find all the triangles that this vertex belongs to + [row col] = find(ismember(baseCoordMap.tris,y(i))); + % get all the neighboring vertices + neighboringVertices = baseCoordMap.tris(row,:); + neighboringVertices = setdiff(neighboringVertices(:),y(i)); + % if there are any neighboring vertices that are + % not in he roi then this vertex is an edge + numNeighbors(i) = length(neighboringVertices); + numROINeighbors(i) = sum(ismember(neighboringVertices,y)); + if numNeighbors(i) ~= numROINeighbors(i) + newy = union(newy,baseCoordMap.tris(row(1),:)); + end + if verbose, mlrDispPercent(i/length(y)); end; end - if verbose, disppercent(-inf); end; - disp(sprintf('%i/%i edges',length(newy),length(y))); + if verbose, mlrDispPercent(-inf); end; + disp(sprintf('%i/%i edges',length(newy),length(y)));q y = newy; end - % display the surface + % display the ROI as a colored/transparent mask patch roiColors = zeros(size(baseSurface.vtcs)); roiColors(:) = nan; roiColors(y,1) = roi{r}.color(1); @@ -873,7 +950,7 @@ function displayColorbar(gui,cmap,cbarRange,verbose) roi{r}.vertices=y; roi{r}.overlayImage = roiColors; if ~isempty(fig) && ~isempty(hAxis) - hSurface(c) = patch('vertices', baseSurface.vtcs, 'faces', baseSurface.tris,'FaceVertexCData', roiColors,'facecolor','interp','edgecolor','none','FaceAlpha',0.4,'Parent',hAxis); + hSurface(c) = patch('vertices', baseSurface.vtcs, 'faces', baseSurface.tris,'FaceVertexCData', roiColors,'facecolor','interp','edgecolor','none','FaceAlpha',roiAlpha,'Parent',hAxis); end continue end @@ -969,7 +1046,7 @@ function displayColorbar(gui,cmap,cbarRange,verbose) view = viewSet(view,'ROICache',roi{r},r,baseNum,rotate); % now render those lines if ~isempty(hAxis) - line(roi{r}.lines.x,roi{r}.lines.y,'Color',roi{r}.color,'LineWidth',mrGetPref('roiContourWidth'),'Parent',hAxis); + line(roi{r}.lines.x,roi{r}.lines.y,'Color',roi{r}.color,'LineWidth',roiContourWidth,'Parent',hAxis); end else roi{r}.lines.x = []; @@ -1004,7 +1081,7 @@ function displayColorbar(gui,cmap,cbarRange,verbose) end -if verbose>1,disppercent(inf);,end +if verbose>1,mlrDispPercent(inf);,end return; %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/mrLoadRet/Init/mrInit.m b/mrLoadRet/Init/mrInit.m index 00d51713e..63f216582 100644 --- a/mrLoadRet/Init/mrInit.m +++ b/mrLoadRet/Init/mrInit.m @@ -1,6 +1,6 @@ % mrInit.m % -% usage: mrInit(,,,) +% usage: mrInit(,,,,<'makeReadme=0'>,<'noPrompt=1'>) % by: justin gardner % date: 06/09/08 % purpose: Init the session variables. usually just call with no arguments @@ -20,13 +20,15 @@ % and then call mrInit again to set the parameters (useful for scripting) % mrInit(sessionParams,groupParams); % +% Setting noPrompt=1 will replace any existing mrSession.mat without prompting (for scripting) +% % You can use mrSetPref to set preferences for magnet/coil and pulseSequence names % that will come down as choices in the GUI % -function [sessionParams groupParams] = mrInit(sessionParams,groupParams,varargin) +function [sessionParams, groupParams] = mrInit(sessionParams,groupParams,varargin) % check arguments -getArgs(varargin,{'justGetParams=0','defaultParams=0','makeReadme=1','magnet=[]','coil=[]','pulseSequence=[]','subject=[]','operator=[]','description=[]','stimfileMatchList=[]'}); +getArgs(varargin,{'justGetParams=0','defaultParams=0','noPrompt=0','makeReadme=1','magnet=[]','coil=[]','pulseSequence=[]','subject=[]','operator=[]','description=[]','stimfileMatchList=[]'}); minFramePeriod = .01; %frame period in sec outside which the user is prompted maxFramePeriod = 100; % that something weird's goin on @@ -93,19 +95,30 @@ if mlrIsFile('mrSession.mat') load mrSession; nScans = length(groups(1).scanParams); - for i = 1:nScans - scanNames{i} = groups(1).scanParams(i).fileName; - descriptions{i} = groups(1).scanParams(i).description; - totalFrames{i} = groups(1).scanParams(i).totalFrames; - nFrames{i} = groups(1).scanParams(i).nFrames; - junkFrames{i} = groups(1).scanParams(i).junkFrames; + if nScans>0 + for i = 1:nScans + scanNames{i} = groups(1).scanParams(i).fileName; + descriptions{i} = groups(1).scanParams(i).description; + totalFrames{i} = groups(1).scanParams(i).totalFrames; + nFrames{i} = groups(1).scanParams(i).nFrames; + junkFrames{i} = groups(1).scanParams(i).junkFrames; + end + populateScanParams = false; + else + populateScanParams = true; end + defaultGroupName = groups(1).name; else + populateScanParams = true; + defaultGroupName = 'Raw'; + end + + if populateScanParams % get info about scans that live in Raw/TSeries - scanDirName = fullfile('Raw','TSeries'); + scanDirName = fullfile(defaultGroupName,'TSeries'); scanFilenames = mlrImageGetAllFilenames(scanDirName,'mustNotHaveDotInFilename=1'); nScans=0;scanNames = {};descriptions = {};totalFrames = {};nFrames = {};junkFrames = {}; - for i = 1:length(scanFilenames); + for i = 1:length(scanFilenames) % read the nifti header imageHeader = mlrImageHeaderLoad(fullfile(scanDirName,scanFilenames{i})); if ismember(imageHeader.nDim,[3 4]) @@ -128,14 +141,15 @@ % check to see if we got any good scans if nScans == 0 - disp(sprintf('(mrInit) Could not find any valid scans in Raw/TSeries')); + disp(sprintf('(mrInit) Could not find any valid scans in %s/TSeries',defaultGroupName)); sessionParams = [];groupParams = []; return end % setup params dialog paramsInfo = {}; - paramsInfo{end+1} = {'scanNum',1,'incdec=[-1 1]',sprintf('minmax=[1 %i]',nScans),'The scanNumber','editable=0'}; + paramsInfo{end+1} = {'defaultGroupName',defaultGroupName,'type=string','The default MLR group','editable=0'}; + paramsInfo{end+1} = {'scanNum',1,'incdec=[-1 1]',sprintf('minmax=[1 %i]',nScans),'Number of scans in the default group','editable=0'}; paramsInfo{end+1} = {'name',scanNames,'group=scanNum','type=string','editable=0','Names of scans'}; paramsInfo{end+1} = {'totalFrames',totalFrames,'group=scanNum','type=numeric','Number of frames in scan','editable=0'}; paramsInfo{end+1} = {'description',descriptions,'group=scanNum','type=string','Description of scans'}; @@ -198,12 +212,24 @@ session.coil = sessionParams.coil; session.protocol = sprintf('%s: %s',sessionParams.pulseSequence,sessionParams.pulseSequenceText); % create groups variables - groups(1).name = 'Raw'; + if fieldIsNotDefined(groupParams,'defaultGroupName') + groups(1).name = 'Raw'; + else + groups(1).name = groupParams.defaultGroupName; + end scanParams = []; - tseriesDir = 'Raw/TSeries'; + tseriesDir = fullfile(groups(1).name,'TSeries'); for iScan=1:length(groupParams.totalFrames) name = fullfile(tseriesDir, groupParams.name{iScan}); hdr = mlrImageReadNiftiHeader(name); + % add in a missing qform based on voxel dimensions. This won't have correct info, but will allow the rest of the code to run + % if the sform is set, the qform shouldn't matter + if isempty(hdr.qform44) + mrWarnDlg(sprintf('(mrInit) !!!! Missing qform for scan %d (%s), making one based only on voxel dimensions !!!!',iScan,groupParams.name{iScan})); + hdr.qform44 = diag(hdr.pixdim(2:5)); + hdr.qform44(4,4) = 1; + end + scanParams(iScan).dataSize = hdr.dim([2,3,4])'; scanParams(iScan).description = groupParams.description{iScan}; scanParams(iScan).fileName = groupParams.name{iScan}; @@ -261,15 +287,15 @@ % check for mrSession if mlrIsFile('mrSession.mat') - if askuser('(mrInit) mrSession.mat already exists. Overwrite?'); - disp(sprintf('(mrInit) Copying old mrSession.mat mrSession.old.mat')); - movefile('mrSession.mat','mrSession.old.mat'); - disp(sprintf('(mrInit) Saving new mrSession')); - save mrSession session groups; - % disp(sprintf('(mrInit) Creating new Readme')); - if makeReadme - mrReadme(session, groups); - end + if noPrompt || askuser('(mrInit) mrSession.mat already exists. Replace?'); + disp(sprintf('(mrInit) Copying old mrSession.mat mrSession.old.mat')); + movefile('mrSession.mat','mrSession.old.mat'); + disp(sprintf('(mrInit) Saving new mrSession')); + save mrSession session groups; + % disp(sprintf('(mrInit) Creating new Readme')); + if makeReadme + mrReadme(session, groups); + end end else disp(sprintf('(mrInit) Saving new mrSession')); diff --git a/mrLoadRet/Plot/mlrDisplayEPI.m b/mrLoadRet/Plot/mlrDisplayEPI.m index 16ddb41f5..3fb4ec77c 100644 --- a/mrLoadRet/Plot/mlrDisplayEPI.m +++ b/mrLoadRet/Plot/mlrDisplayEPI.m @@ -359,10 +359,10 @@ function mlrDisplayEPICallback(params) for rownum = 1:4 disp(sprintf('[%0.2f %0.2f %0.2f %0.2f]',M(rownum,1),M(rownum,2),M(rownum,3),M(rownum,4))); end - disppercent(-inf,sprintf('Warping scan %i to match scan %i with transformation using %s',params.scanNum,params.warpBaseScan,gMLRDisplayEPI.interpMethod)); + mlrDispPercent(-inf,sprintf('Warping scan %i to match scan %i with transformation using %s',params.scanNum,params.warpBaseScan,gMLRDisplayEPI.interpMethod)); epiVolume = warpAffine3(epiVolume,M,NaN,0,gMLRDisplayEPI.interpMethod,warpBaseScanDims); - disppercent(inf); + mlrDispPercent(inf); epiImage = epiVolume(:,:,sliceNum(1):sliceNum(2)); else % scan2scan was identity, so no warping is necessary diff --git a/mrLoadRet/Plot/mlrSpikeDetector.m b/mrLoadRet/Plot/mlrSpikeDetector.m index 873e410cd..45ab055b1 100644 --- a/mrLoadRet/Plot/mlrSpikeDetector.m +++ b/mrLoadRet/Plot/mlrSpikeDetector.m @@ -75,7 +75,7 @@ % load file v = viewSet(v,'curGroup',groupNum); -disppercent(-inf,sprintf('(mlrSpikeDetector) Loading time series for scan %i, group %i',scanNum,groupNum)); +mlrDispPercent(-inf,sprintf('(mlrSpikeDetector) Loading time series for scan %i, group %i',scanNum,groupNum)); data = loadTSeries(v,scanNum); % Dump junk frames junkFrames = viewGet(v, 'junkframes', scanNum); @@ -83,7 +83,7 @@ data = data(:,:,:,junkFrames+1:junkFrames+nFrames); spikeInfo.dim = size(data); -disppercent(inf); +mlrDispPercent(inf); % compute timecourse means for later for slicenum = 1:spikeInfo.dim(3) @@ -95,7 +95,7 @@ % compute fourier transform of data % calculating fourier transform of data -disppercent(-inf,'(mlrSpikeDetector) Calculating FFT'); +mlrDispPercent(-inf,'(mlrSpikeDetector) Calculating FFT'); % skip some frames in the beginning to account % for saturation if junkFrames < 5 @@ -105,7 +105,7 @@ end data = data(:,:,:,startframe:spikeInfo.dim(4)); for i = 1:size(data,4) - disppercent(i/size(data,4)); + mlrDispPercent(i/size(data,4)); for j = 1:size(data,3) %first need to remove NaNs from data %let's replace them by the mean of each image @@ -115,32 +115,32 @@ data(:,:,j,i) = abs(fftshift(fft2(thisData))); end end -disppercent(inf); +mlrDispPercent(inf); % get mean and std if params.useMedian - disppercent(-inf,'(mlrSpikeDetector) Calculating median and iqr'); + mlrDispPercent(-inf,'(mlrSpikeDetector) Calculating median and iqr'); for slicenum = 1:spikeInfo.dim(3) - disppercent(slicenum/spikeInfo.dim(3)); + mlrDispPercent(slicenum/spikeInfo.dim(3)); meandata(:,:,slicenum) = squeeze(median(data(:,:,slicenum,:),4)); stddata(:,:,slicenum) = squeeze(iqr(data(:,:,slicenum,:),4)); end else - disppercent(-inf,'(mlrSpikeDetector) Calculating mean and std'); + mlrDispPercent(-inf,'(mlrSpikeDetector) Calculating mean and std'); for slicenum = 1:spikeInfo.dim(3) meandata(:,:,slicenum) = squeeze(mean(data(:,:,slicenum,:),4)); stddata(:,:,slicenum) = squeeze(std(data(:,:,slicenum,:),0,4)); end end -disppercent(inf); +mlrDispPercent(inf); % now subtract off mean and see % if there are any points above std criterion slice = [];time = [];numspikes = [];spikelocs = {};meanZvalue=[]; -disppercent(-inf,'(mlrSpikeDetector) Looking for spikes'); +mlrDispPercent(-inf,'(mlrSpikeDetector) Looking for spikes'); for i = 1:size(data,4) - disppercent(i/spikeInfo.dim(4)); + mlrDispPercent(i/spikeInfo.dim(4)); data(:,:,:,i) = squeeze(data(:,:,:,i))-meandata; % see if any voxels are larger then expected for slicenum = 1:spikeInfo.dim(3) @@ -159,7 +159,7 @@ end end end -disppercent(inf); +mlrDispPercent(inf); if length(slice) disp(sprintf('======================================================')); disp(sprintf('(mlrSpikeDetector) Found %i spikes at z>%.2f in scan %i, group %i',length(slice),params.criterion,scanNum,groupNum)); diff --git a/mrLoadRet/Plugin/GLM_v2/GLM_v2Plugin.m b/mrLoadRet/Plugin/GLM_v2/GLM_v2Plugin.m index 9f0f4a67e..e3fcdb939 100644 --- a/mrLoadRet/Plugin/GLM_v2/GLM_v2Plugin.m +++ b/mrLoadRet/Plugin/GLM_v2/GLM_v2Plugin.m @@ -259,6 +259,7 @@ mlrAdjustGUI(thisView,'set','pasteRoiMenuItem',menuLabel,'Paste ROI(s)'); mlrAdjustGUI(thisView,'set','editRoiMenuItem',menuLabel,'Edit selected ROI(s)'); %add functions + mlrAdjustGUI(thisView,'add','menu','Export to Freesurfer Label','/File/Export/ROI','callback',@exportROIfreesurferMenuItem_Callback,'label','ROI (Freesurfer Label)','tag','exportROIfreesurferMenuItem'); mlrAdjustGUI(thisView,'add','menu','Single Voxels','/ROI/Create/Contiguous Voxels','callback',@createSingleVoxelsCallBack,'label','Single Voxels','tag','createSingleVoxelsRoiMenuItem','accelerator','T'); mlrAdjustGUI(thisView,'add','menu','Single Voxels2','/ROI/Add/Contiguous Voxels','callback',@addSingleVoxelsCallBack,'label','Single Voxels','tag','addSingleVoxelsRoiMenuItem','accelerator','N'); mlrAdjustGUI(thisView,'add','menu','Single Voxels3','/ROI/Subtract/Contiguous Voxels','callback',@removeSingleVoxelsCallBack,'label','Single Voxels','tag','removeSingleVoxelsRoiMenuItem','accelerator','U'); @@ -666,6 +667,36 @@ function combineOverlaysCallback(hObject,dump) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ROI Menu Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% -------------------------------------------------------------------- +function exportROIfreesurferMenuItem_Callback(hObject, dump) + +viewNum = getfield(guidata(hObject),'viewNum'); +thisView = viewGet(viewNum,'view'); + +% get the roi we are being asked to export +roiNum = viewGet(thisView,'currentroi'); +if isempty(roiNum) + mrWarnDlg('(mlrExportROI) No current ROI to export'); + return +end + +% get current roi name +roiName = viewGet(thisView,'roiname'); +if ischar(roiName) + roiName={roiName}; +end + +pathstr = cell(0); +for iRoi = 1:length(roiName) + % put up dialog to select filename + pathstr{iRoi} = putPathStrDialog(pwd,'Specify name of Freesurfer label file to export ROI to',setext(roiName{iRoi},'.label')); + if isempty(pathstr{iRoi}) + return + end +end + +mlrExportROI(thisView, pathstr, 'exportToFreesurferLabel', true); + % -------------------------------------------------------------------- function createSingleVoxelsCallBack(hObject,dump) diff --git a/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m b/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m new file mode 100644 index 000000000..881bcc173 --- /dev/null +++ b/mrLoadRet/Plugin/GLM_v2/applyInverseBaseCoordMap.m @@ -0,0 +1,49 @@ +% function volumeData = applyInverseBaseCoordMap(surf2volumeMap,volumeDims,surfData) +% +% Transforms data from surface or flattened cortical patch space +% to volume space according to the mapping in surf2volumeMap. +% The mapping must first be computed using function inverseBaseCoordMap: +% (e.g. surf2volumeMap = inverseBaseCoordMap(baseCoordsMap,volumeDims,) ) +% +% Taken out of combineTransformOverlays.m (22/07/2020) +% + +function volumeData = applyInverseBaseCoordMap(surf2volumeMap,volumeDims,surfData) + +hWaitBar = mrWaitBar(-inf,'(applyInverseBaseCoordMap) Converting from surface to volume'); + +nVolumes = size(surfData,4); +volumeData = zeros([volumeDims nVolumes]); + +for iVolume = 1:nVolumes + thisSurfData = surfData(:,:,:,iVolume); + thisVolumeData = zeros(volumeDims); + datapoints = zeros(volumeDims); + thisSurf2volMap = surf2volumeMap; + + % first find the longest non-zero row of the sparse flat2volumeMap matrix, + % as it is often much longer than the other rows and so time can be saved by treating it differently + longestRow = find(thisSurf2volMap(:,end)); + for iRow = longestRow % in case there are several such rows (unlikely) + thisVolumeData(iRow) = thisVolumeData(iRow) + sum(thisSurfData(thisSurf2volMap(iRow,:))); + datapoints(iRow) = size(thisSurf2volMap,2); + end + thisSurf2volMap(longestRow,:) = 0; + thisSurf2volMap(:,sum(thisSurf2volMap>0)==0) = []; + + % now do the rest colum by column + maxInstances = size(thisSurf2volMap,2); + for i=1:maxInstances + mrWaitBar( ((iVolume-1)*maxInstances+i) / (maxInstances*nVolumes), hWaitBar); + thisBaseCoordsMap = thisSurf2volMap(:,i); + newData = thisSurfData(thisBaseCoordsMap(logical(thisBaseCoordsMap))); + indices = find(thisBaseCoordsMap); + notNaN = ~isnan(newData); + thisVolumeData(indices(notNaN)) = thisVolumeData(indices(notNaN)) + newData(notNaN); + datapoints(indices(notNaN)) = datapoints(indices(notNaN)) + 1; + end + datapoints = reshape(datapoints,volumeDims); + volumeData(:,:,:,iVolume) = thisVolumeData ./datapoints; +end + +mrCloseDlg(hWaitBar); diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/indexMax.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/indexMax.m index e27e565d9..4338ca834 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/indexMax.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/indexMax.m @@ -14,7 +14,7 @@ for i = 2:nargin %first check that all inputs have the same size if ~isequal(size(varargin{i}),size(varargin{1})) - error('All inputs must have the same size') + mrErrorDlg('All inputs must have the same size') else %concatenate array=cat(nDims+1,array,varargin{i}); diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/keepKlargestConnectedClusters.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/keepKlargestConnectedClusters.m new file mode 100644 index 000000000..34da2ad11 --- /dev/null +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/keepKlargestConnectedClusters.m @@ -0,0 +1,31 @@ +% function outputOverlay = keepKlargestConnectedRegions(overlay, , ) +% +% Keeps only the k largest visible 3D connected clusters in (clipped) overlay (default k = 1) +% If calling this function from combineTransformOverlays, +% the "clip" or "alphaClip" checkbox must be checked. +% For optional argument , see bwconncomp's help +% options are 6, 18, 26 (default = 6) +% +% author: julien besle (22/07/2020) + + +function outputOverlay = keepKlargestConnectedClusters(overlay,k, connectivity) + +if ieNotDefined('k') + k=1; +end +if ieNotDefined('connectivity') + connectivity=6; +end + +outputOverlay = nan(size(overlay)); + +%find connected clusters +cc = bwconncomp(~isnan(overlay),connectivity); +numPixels = cellfun(@numel,cc.PixelIdxList); +% sort them by descreasing size +[~,sizeIndex] = sort(numPixels,'descend'); +for iCluster = 1:k + outputOverlay(cc.PixelIdxList{sizeIndex(iCluster)}) = overlay(cc.PixelIdxList{sizeIndex(iCluster)}); +end + diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/multipleTestsAdjustment.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/multipleTestsAdjustment.m index faa832bd3..a8c2721cb 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/multipleTestsAdjustment.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/multipleTestsAdjustment.m @@ -1,16 +1,25 @@ -% [fdrAdjustedP,fweAdjustedP] = multipleTestsAdjustment(p) +% [fdrAdjustedP,fweAdjustedP] = multipleTestsAdjustment(p, ) % % adjusts p values using False Discovery Rate Step-up method and Hommel Bonferroni correction +% optional inputs: set fdrAdjust or fweAdjust to 0 to skip either method % % jb 15/03/2012 % % $Id: maskAwithB.m 2172 2011-06-20 12:49:44Z julien $ -function [fdrAdjustedP,fweAdjustedP] = multipleTestsAdjustment(p) +function [fdrAdjustedP,fweAdjustedP] = multipleTestsAdjustment(p, fdrAdjust, fweAdjust) -if ~ismember(nargin,[1]) +if ~ismember(nargin,[1 2 3]) help multipleTestsAdjustment; return end - -[~, fdrAdjustedP, fweAdjustedP] = transformStatistic(p); \ No newline at end of file +if ieNotDefined('fdrAdjust') + fdrAdjust=1; +end +if ieNotDefined('fweAdjust') + fweAdjust=1; +end + params.fdrAdjustment= fdrAdjust; + params.fweAdjustment= fweAdjust; + +[~, fdrAdjustedP, fweAdjustedP] = transformStatistic(p,[],params); \ No newline at end of file diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m index 306aceae7..03b6ad0af 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/spatialSmooth.m @@ -1,12 +1,23 @@ +% +% function smoothed = spatialSmooth(overlay,FWHM,keepNaNs) +% % spatialSmooth.m: spatially smooth overlay with a 3D gaussian of given FWHM (in voxels) % -% $Id: spatialSmooth.m 2733 2013-05-13 11:47:54Z julien $ -% +% If keepNaNs is true (default), existing NaNs will be preserved. If keepNaNs is false +% NaNs will be replaced by interpolated values due to smoothing from neighbouring +% voxels (when they exist) +% -function smoothed = spatialSmooth(overlay,FWHM) +function smoothed = spatialSmooth(overlay,FWHM,keepNaNs) + +if ieNotDefined('keepNaNs') + keepNaNs = true; +end smoothed = nanconvn(overlay,gaussianKernel(FWHM),'same'); -smoothed(isnan(overlay))=NaN; +if keepNaNs + smoothed(isnan(overlay))=NaN; +end function kernel = gaussianKernel(FWHM) @@ -19,7 +30,23 @@ end kernelDims = 2*w+1; kernelCenter = ceil(kernelDims/2); -[X,Y,Z] = meshgrid(1:kernelDims(1),1:kernelDims(2),1:kernelDims(3)); -kernel = exp(-((X-kernelCenter(1)).^2/(2*sigma_d(1)^2)+(Y-kernelCenter(2)).^2/(2*sigma_d(2)^2)+(Z-kernelCenter(3)).^2/(2*sigma_d(3)^2))); %Gaussian function +[X,Y,Z] = meshgrid(1:kernelDims(1),1:kernelDims(2),1:kernelDims(3)); % should I use ndgrid instead of meshgrid here? (for most cases it wouldn't matter because X and Y very often have the same pixel size and require the same amount of smoothing) +if all(sigma_d == 0) + kernel = 1; % no smoothing +elseif sigma_d(2) == 0 && sigma_d(3) == 0 + kernel = exp(-((X-kernelCenter(1)).^2/(2*sigma_d(1)^2))); % 1D Gaussian function along X +elseif sigma_d(1) == 0 && sigma_d(3) == 0 + kernel = exp(-((Y-kernelCenter(2)).^2/(2*sigma_d(2)^2))); % 1D Gaussian function along Y +elseif sigma_d(1) == 0 && sigma_d(2) == 0 + kernel = exp(-((Z-kernelCenter(3)).^2/(2*sigma_d(3)^2))); % 1D Gaussian function along Z +elseif sigma_d(3) == 0 + kernel = exp(-((X-kernelCenter(1)).^2/(2*sigma_d(1)^2)+(Y-kernelCenter(2)).^2/(2*sigma_d(2)^2))); % 2D Gaussian function in XY plane +elseif sigma_d(2) == 0 + kernel = exp(-((X-kernelCenter(1)).^2/(2*sigma_d(1)^2)+(Z-kernelCenter(3)).^2/(2*sigma_d(3)^2))); % 2D Gaussian function in XZ plane +elseif sigma_d(1) == 0 + kernel = exp(-((Y-kernelCenter(2)).^2/(2*sigma_d(2)^2)+(Z-kernelCenter(3)).^2/(2*sigma_d(3)^2))); % 2D Gaussian function in YZ plane +else + kernel = exp(-((X-kernelCenter(1)).^2/(2*sigma_d(1)^2)+(Y-kernelCenter(2)).^2/(2*sigma_d(2)^2)+(Z-kernelCenter(3)).^2/(2*sigma_d(3)^2))); % 3D Gaussian function +end kernel = kernel./sum(kernel(:)); diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/weightedMeanStd.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/weightedMeanStd.m index c4500b3b4..36181990c 100755 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/weightedMeanStd.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlayFunctions/weightedMeanStd.m @@ -1,6 +1,10 @@ % weightedMeanStd.m: computes weighted sample average and std deviation of overlay indices (indices are weighted by the overlay value) % This is useful to estimate preferred condition and (gaussian) tuning width in a set of ordered stimulus conditions -% Negative overlay values are set to 0 beofre computing meand and std deviation +% Negative overlay values are set to 0 before computing mean and std deviation +% Sample average and std deviation are also corrected for limited stimulus sampling range +% using the method described in Besle et al. (2019) Cerebral Cortex, and returned as 3rd and 4th outputs +% Optional argument: population mean range [a b], where a is the lower bound in overlay index scale +% (usually something between 0 and 1, default = 0) and b is a multiplier applied to the total number of overlays (default = 1.3) % % $Id: weightedMeanStd.m 2733 2013-05-13 11:47:54Z julien $ % @@ -10,13 +14,18 @@ function [average,stddev,correctedAverage,correctedStddev] = weightedMeanStd(varargin) - nDims = length(size(varargin{1})); array = varargin{1}; for i = 2:nargin %first check that all inputs have the same size if ~isequal(size(varargin{i}),size(varargin{1})) - error('All inputs must have the same size') + if length(varargin{i})==2 && i==nargin + populationMeanRange = varargin{i}; + mrWarnDlg('(weightedMeanStd) Last input has two values. Assuming that this is the population mean range'); + break; + else + error('(weightedMeanStd) All inputs must have the same size'); + end else %concatenate array=cat(nDims+1,array,varargin{i}); @@ -26,6 +35,9 @@ nOverlays = size(array,4); array(array<=0)=0; +if ieNotDefined('populationMeanRange') + populationMeanRange = [0 1.3]; +end %compute the weighted average indices = repmat(permute(1:nOverlays,[1 3 4 2]),[overlaySize 1]); average = sum( array .* indices, 4) ./ sum(array,4); @@ -35,7 +47,7 @@ %correct partial-sampling bias if nargout == 4 - popMean = 0:.1:round(nOverlays*1.25) ; + popMean = populationMeanRange(1):.1:nOverlays* populationMeanRange(2); popStddev = .1:.1:nOverlays; nMeans = length(popMean); nStddevs = length(popStddev); diff --git a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m index a31cb0394..6b997e9ab 100644 --- a/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m +++ b/mrLoadRet/Plugin/GLM_v2/combineTransformOverlays.m @@ -1,5 +1,4 @@ function [thisView,params] = combineTransformOverlays(thisView,params,varargin) -% [thisView,params] = combineTransformOverlays(thisView,thisView,overlayNum,scanNum,x,y,z) % % combines (masked) Overlays according to matlab or custom operators in current view and current analysis % @@ -10,20 +9,30 @@ % [v params] = combineTransformOverlays(v,[],'justGetParams=1'); % [v params] = combineTransformOverlays(v,[],'justGetParams=1','defaultParams=1'); % [v params] = combineTransformOverlays(v,[],'justGetParams=1','defaultParams=1','overlayList=[1 2]'); +% [v params] = combineTransformOverlays(v,[],'justGetParams=1','defaultParams=1','overlayList=[1 2]','scanList=[1 2]'); +% [v params] = combineTransformOverlays(v,[],'justGetParams=1','defaultParams=1','overlayList=[1 2]','roiList=[1 2]'); % % $Id$ -inputOutputTypeMenu = {'3D Array','4D Array','Scalar','Structure'}; -combinationModeMenu = {'Apply function to all overlays','Apply function to each overlay','Recursively apply to overlay pairs'}; -% other arguments eval(evalargs(varargin)); if ieNotDefined('justGetParams'),justGetParams = 0;end if ieNotDefined('defaultParams'),defaultParams = 0;end -%default params +nScans = viewGet(thisView,'nScans'); + % First get parameters if ieNotDefined('params') + if ieNotDefined('scanList') + scanList = 1:nScans; + end + if ieNotDefined('overlayList') + overlayList = viewGet(thisView,'curOverlay'); + end + if ieNotDefined('roiList') + roiList = viewGet(thisView,'curROI'); + end + %get names of combine Functions in combineFunctions directory functionsDirectory = [fileparts(which('combineTransformOverlays')) '/combineTransformOverlayFunctions/']; combineFunctionFiles = dir([functionsDirectory '*.m']); @@ -46,12 +55,15 @@ combineFunctionsMenu = putOnTopOfList(combineFunctionsMenu{2},combineFunctionsMenu); end params.customCombineFunction = '';%(''@(x)max(0,-norminv(x))'; + inputOutputTypeMenu = {'3D Array','4D Array','4D Array (multiple scans)','Scalar','Structure'}; + combinationModeMenu = {'Apply function to all overlays','Apply function to each overlay','Recursively apply to overlay pairs'}; params.nOutputOverlays = 1; params.additionalArrayArgs = ''; params.additionalArgs = ''; + params.passView = 0; params.clip = 0; params.alphaClip = 0; - params.passView = 0; + roiMaskMenu = {'None','Union','Intersection'}; params.baseSpace = 0; baseSpaceInterpMenu = {'Same as display','nearest','linear','spline','cubic'}; params.exportToNewGroup = 0; @@ -67,7 +79,7 @@ params = {... {'combineFunction',combineFunctionsMenu,'type=popupmenu','name of the function to apply. This is a list of existing combine functions in the combineFunctions directory. To use another function, select ''User Defined'' and type the function name below'},... {'customCombineFunction',params.customCombineFunction,'name of the function to apply. You can use any type of matlab function (including custom) that accepts either scalars or multidimensional arrays. Any string beginning with an @ will be considered an anonymous function and shoulde be of the form @(x)func(x), @(x,y)func(x,y) ..., where the number of variables equals the number of overlay inputs and additional arguments. '},... - {'inputOutputType',inputOutputTypeMenu,'type=popupmenu','Type of arguments accepted by the combination function. ''3D Array'' will pass each input overlay as a 3D array. ''Scalar'' will apply the function to each element of the input overlay(s). ''4D Array'' wil concatenate overlays on the 4th dimension and pass the 4D array as a single argument to the function. 3D and 4D array are faster that but not all functions accept multidimensional arrays as inputs. Use ''4D Array'' for functions that operate on one dimension of an array (e.g. mean) and specify the dimension as an additional scalar argument (usually 4). Choose ''Structure'' to pass the whole overlay structure'},... + {'inputOutputType',inputOutputTypeMenu,'type=popupmenu','Type of arguments accepted by the combination function. ''3D Array'' will pass each input overlay as a 3D array. ''Scalar'' will apply the function to each element of the input overlay(s). ''4D Array'' will concatenate overlays on the 4th dimension and pass the 4D array as a single argument to the function. . ''4D Array (multiple scans)'' will concatenate overlays across scans on the 4th dimension and pass each overlay as separate arguments (all selected scans must have the same dimensions). 3D and 4D array are faster that but not all functions accept multidimensional arrays as inputs. Use ''4D Array'' for functions that operate on one dimension of an array (e.g. mean) and specify the dimension as an additional scalar argument (usually 4). Choose ''Structure'' to pass the whole overlay structure'},... {'combinationMode',combinationModeMenu,'type=popupmenu', 'How the selected overlays are input ot the combineFunction. If ''all'', all the selected overlays are given as input at once (the number of inputs expected by the function must match the number of selected overlays). If ''each'', the combine function is run separately for each overlay and must accept only one input overlay). If ''pair'', the combineFunction is run on pairs of consecutive selected overlays and must accept two input overlays.'},... {'nOutputOverlays',params.nOutputOverlays,'incdec=[-1 1]','round=1','minmax=[0 Inf]','Number of outputs of the combineFunction'},... {'additionalArrayArgs',params.additionalArrayArgs,'constant arguments for functions that accept them. Arguments must be separated by commas. for Array input/output type, each argument will be repeated in a matrix of same dimensions of the overlay '},... @@ -75,9 +87,10 @@ {'passView',params.passView,'type=checkbox','Check this if the function requires the current mrLoadRet view'},... {'clip',params.clip,'type=checkbox','Mask overlays according to clip values'},... {'alphaClip',params.alphaClip,'type=checkbox','Mask overlays according to alpha overlay clip values'},... + {'roiMask',roiMaskMenu,'type=popupmenu','Whether to mask the overlay(s) with one or several ROIs. ''None'' will not mask. ''Union'' and ''Interssection'' will mask the overlay with the union or intersection of select ROIs. Check whether this option is compatible with ''baseSpace'''},... {'baseSpace',params.baseSpace,'type=checkbox',baseSpaceOption,'Transforms overlays into the current base volume before applying the transform/combine function, and back into overlay space afterwards. Only implemented for flat maps (all cortical depths are used).'},... {'baseSpaceInterp',baseSpaceInterpMenu,'type=popupmenu','contingent=baseSpace','Type of base space interpolation '},... - {'exportToNewGroup',params.exportToNewGroup,'type=checkbox','contingent=baseSpace','Exports results in base sapce to new group, scan and analysis. Warning: for flat maps, the data is exported to a volume in an arbitrary space. ROIs and overlays defined outside this new group will not be in register.'},... + {'exportToNewGroup',params.exportToNewGroup,'type=checkbox','contingent=baseSpace','Exports results in base space to new group, scan and analysis. Warning: for flat maps, the data is exported to a volume in an arbitrary space. ROIs and overlays defined outside this new group will not be in register.'},... {'outputName',params.outputName,'radical of the output overlay names'},... {'printHelp',0,'type=pushbutton','callback',@printHelp,'passParams=1','buttonString=Print combineFunction Help','Prints combination function help in command window'},... }; @@ -102,32 +115,71 @@ combinationModeMenu = putOnTopOfList(params.combinationMode,combinationModeMenu); combineFunctionsMenu = putOnTopOfList(params.combineFunction,combineFunctionsMenu); baseSpaceInterpMenu = putOnTopOfList(params.baseSpaceInterp,baseSpaceInterpMenu); + roiMaskMenu = putOnTopOfList(params.roiMask,roiMaskMenu); if strcmp(params.combinationMode,'Recursively apply to overlay pairs') && params.combineFunction(1)=='@' mrWarnDlg('(combineTransformOverlays) Anonymous functions cannot be applied recursively.'); elseif isempty(params.combineFunction) || (strcmp(params.combineFunction,'User Defined') && isempty(params.customCombineFunction)) mrWarnDlg('(combineTransformOverlays) Please choose a combination/transformation function.'); elseif (params.clip || params.alphaClip) && params.baseSpace && viewGet(thisView,'basetype')~=1 - mrWarnDlg('(combineTransformOverlays) Base space conversion is not yet compatible with using (alpha) masking.'); + mrWarnDlg('(combineTransformOverlays) Base space conversion for bases other than flat maps is not yet compatible with using (alpha) masking.'); + elseif ~strcmp(params.roiMask,'None') && params.baseSpace + mrWarnDlg('(combineTransformOverlays) Base space conversion is not yet compatible with ROI masking.'); %elseif %other controls here else askForParams = 0; if defaultParams - params.overlayList = viewGet(thisView,'curOverlay'); + params.overlayList = overlayList; + params.scanList = scanList; + params.roiList = roiList; else - params.overlayList = selectInList(thisView,'overlays'); - if isempty(params.overlayList) - askForParams = 1; + askForOverlays = 1; + while askForOverlays + askForOverlays = 0; + params.overlayList = selectInList(thisView,'overlays','',overlayList); + if isempty(params.overlayList) + askForParams = 1; + else + overlayList=params.overlayList; + if nScans>1 + params.scanList = selectInList(thisView,'scans','',scanList); + if isempty(params.scanList) + askForOverlays = 1; + else + scanList = params.scanList; + end + else + params.scanList = 1; + end + if ~strcmp(params.roiMask,'None') + params.roiList = selectInList(thisView,'rois','',roiList); + if isempty(params.roiList) + askForOverlays = 1; + else + roiList = params.roiList; + end + end + end end end end end +else + if ~ieNotDefined('scanList') + params.scanList = scanList; + end + if ~ieNotDefined('overlayList') + params.overlayList = overlayList; + end + if ~ieNotDefined('roiList') + params.roiList = roiList; + end + if ~ieNotDefined('passMultipleScans') + params.passMultipleScans = false; + end end -if ~ieNotDefined('overlayList') - params.overlayList = overlayList; -end if strcmp(params.combineFunction,'User Defined') params.combineFunction = params.customCombineFunction; end @@ -143,22 +195,52 @@ baseSpaceInterp=params.baseSpaceInterp; end +if ~strcmp(params.roiMask,'None') && ~isempty(params.roiList) && params.baseSpace + mrWarnDlg('(combineTransformOverlays) ROI masking is not yet implemented for operations in base space'); + %convert ROI coordinates from scan to base space. This will be done differenty depending on the base type + set(viewGet(thisView,'figNum'),'Pointer','arrow');drawnow; + return +end + +if strcmp(params.inputOutputType,'4D Array (multiple scans)') && length(params.scanList) > 1 + % check that all scans have the same dimensions and the same transform + dimensionsDiffer = false; + for iScan = params.scanList + scanDims{iScan} = viewGet(thisView,'scanDims',iScan); + base2scan{iScan} = viewGet(thisView,'base2scan',iScan); + if iScan > params.scanList(1) + if ~isequal(scanDims{iScan},scanDims{params.scanList(1)}) + mrWarnDlg(sprintf('(combineTransformOverlays) Scans %d and %d have different dimensions.',params.scanList(1),iScan)); + dimensionsDiffer = true; + end + if ~isequal(base2scan{iScan},base2scan{params.scanList(1)}) + mrWarnDlg(sprintf('(combineTransformOverlays) Scans %d and %d have different sforms.',params.scanList(1),iScan)); + dimensionsDiffer = true; + end + end + end + if dimensionsDiffer && ~params.baseSpace % won't work if computations are supposed to be in scan space, but scan spaces differ + fprintf('(combineTransformOverlays) Some scans are in different spaces and so cannot be combined unless the baseScan option is set to true. Aborting...') + return + end +end %get the overlay data -nScans = viewGet(thisView,'nScans'); overlayData = viewGet(thisView,'overlays'); overlayData = overlayData(params.overlayList); if params.baseSpace - base2scan = viewGet(thisView,'base2scan'); baseType = viewGet(thisView,'basetype'); - if any(any(abs(base2scan - eye(4))>1e-6)) || baseType > 0 %check if we're in the scan space - baseCoordsMap=cell(nScans,1); - %if not, transform the overlay to the base space - for iScan = 1:nScans - %here could probably put all overlays of a single scan in a 4D array, but the would have to put it back + baseCoordsMap=cell(nScans,1); + for iScan = params.scanList + base2scan{iScan} = viewGet(thisView,'base2scan',iScan); + if any(any(abs(base2scan{iScan} - eye(4))>1e-6)) || baseType > 0 %check if we're in the scan space + %if not, transform the overlay to the base space + %here could probably put all overlays of a single scan in a 4D array, but then would have to put it back % into the overlays structure array if inputOutputType is 'structure' for iOverlay = 1:length(overlayData) - [overlayData(iOverlay).data{iScan}, voxelSize, baseCoordsMap{iScan}] = getBaseSpaceOverlay(thisView, overlayData(iOverlay).data{iScan},[],[],baseSpaceInterp); + if ~isempty(overlayData(iOverlay).data{iScan}) + [overlayData(iOverlay).data{iScan}, voxelSize, baseCoordsMap{iScan}] = getBaseSpaceOverlay(thisView, overlayData(iOverlay).data{iScan},iScan,[],baseSpaceInterp); + end end end end @@ -172,7 +254,6 @@ if params.baseSpace && baseType==1 %this will only work for flat maps (because for volumes, getBaseSlice only gets one slice, unless base2scan is the identity) boxInfo.baseNum = viewGet(thisView,'curbase'); [~,~,boxInfo.baseCoordsHomogeneous] = getBaseSlice(thisView,viewGet(thisView,'curslice'),viewGet(thisView,'baseSliceIndex'),viewGet(thisView,'rotate'),boxInfo.baseNum,baseType); - boxInfo.base2overlay = base2scan; boxInfo.baseDims = viewGet(thisView,'basedims'); boxInfo.interpMethod = baseSpaceInterp; boxInfo.interpExtrapVal = NaN; @@ -181,32 +262,68 @@ boxInfo=[]; else mrWarnDlg('(combineTransformOverlays) (Alpha) masking is not yet implemented for conversion to bases other than flat.'); + set(viewGet(thisView,'figNum'),'Pointer','arrow');drawnow; return end end if params.clip - mask = maskOverlay(thisView,params.overlayList,1:nScans,boxInfo); - for iScan = 1:length(mask) - for iOverlay = 1:length(overlayData) - if ~isempty(overlayData(iOverlay).data{iScan}) - overlayData(iOverlay).data{iScan}(~mask{iScan}(:,:,:,iOverlay))=NaN; - end + for iScan = params.scanList + if params.baseSpace && baseType==1 + boxInfo.base2overlay = base2scan{iScan}; + end + mask = maskOverlay(thisView,params.overlayList,iScan,boxInfo); + for iOverlay = 1:length(overlayData) + if ~isempty(overlayData(iOverlay).data{iScan}) + overlayData(iOverlay).data{iScan}(~mask{1}(:,:,:,iOverlay))=NaN; end - end + end + end end if params.alphaClip alphaOverlayNum = zeros(1,length(overlayData)); for iOverlay = 1:length(overlayData) - alphaOverlayNum(iOverlay) = viewGet(thisView,'overlaynum',overlayData(iOverlay).alphaOverlay); + if ~isempty(viewGet(thisView,'overlaynum',overlayData(iOverlay).alphaOverlay)) + alphaOverlayNum(iOverlay) = viewGet(thisView,'overlaynum',overlayData(iOverlay).alphaOverlay); + end end - mask = maskOverlay(thisView,alphaOverlayNum,1:nScans,boxInfo); - for iScan = 1:length(mask) + for iScan = params.scanList + if params.baseSpace && baseType==1 + boxInfo.base2overlay = base2scan{iScan}; + end + mask = maskOverlay(thisView,alphaOverlayNum,iScan,boxInfo); for iOverlay = 1:length(overlayData) if alphaOverlayNum(iOverlay) && ~isempty(overlayData(iOverlay).data{iScan}) - overlayData(iOverlay).data{iScan}(~mask{iScan}(:,:,:,iOverlay))=NaN; + overlayData(iOverlay).data{iScan}(~mask{1}(:,:,:,iOverlay))=NaN; + end + end + end +end + +% mask overlay using ROI(s) +if ~strcmp(params.roiMask,'None') && ~isempty(params.roiList) + for iScan = params.scanList + mask = false(size(overlayData(1).data{iScan})); + roiCoords = getROICoordinates(thisView, params.roiList(1),iScan)'; + for iRoi = params.roiList(2:end) + thisRoiCoords = getROICoordinates(thisView, iRoi,iScan)'; + switch(params.roiMask) + case 'Union' + roiCoords = union(roiCoords,thisRoiCoords,'rows'); + case 'Intersection' + roiCoords = intersect(roiCoords,thisRoiCoords,'rows'); end end + roiCoordsLinear = sub2ind(size(mask),roiCoords(:,1),roiCoords(:,2),roiCoords(:,3)); + if isempty(roiCoordsLinear) + mrWarnDlg('(combineTransformOverlays) ROI mask is empty'); + set(viewGet(thisView,'figNum'),'Pointer','arrow');drawnow; + return + end + mask(roiCoordsLinear) = true; + for iOverlay = 1:length(overlayData) + overlayData(iOverlay).data{iScan}(~mask)=NaN; + end end end @@ -216,38 +333,52 @@ end %reformat input data +tempScanList = params.scanList; switch(params.inputOutputType) case 'Structure' overlayData = num2cell(overlayData); case {'3D Array','4D Array','Scalar'} newOverlayData = cell(nScans,length(params.overlayList)); for iOverlay = 1:length(params.overlayList) - for iScan = 1:nScans + for iScan = params.scanList newOverlayData{iScan,iOverlay} = overlayData(iOverlay).data{iScan}; end end overlayData = newOverlayData; + case '4D Array (multiple scans)' + newOverlayData = cell(1,length(params.overlayList)); + for iOverlay = 1:length(params.overlayList) + for iScan = params.scanList + newOverlayData{1,iOverlay} = cat(4,newOverlayData{iOverlay},overlayData(iOverlay).data{iScan}); + end + end + overlayData = newOverlayData; + tempScanList = 1; end %parse additional array inputs -additionalArrayArgs = parseArguments(params.additionalArrayArgs,','); +additionalArrayArgs = mlrParseAdditionalArguments(params.additionalArrayArgs,','); if ~isempty(additionalArrayArgs) - if all(cellfun(@isnumeric,additionalArrayArgs)) && ismember(params.inputOutputType,{'3D Array','4D Array'}) %if all arguments are numeric and the input type is Array - additionalArrayInputs = cellfun(@(x)repmat(x,[size(overlayData{1}) 1]),additionalArrayArgs,'UniformOutput',false); %convert additional arguments to arrays - else %if any additional argument is not a number - additionalArrayInputs = cellfun(@(x)num2cell(repmat(x,[size(overlayData{1}) 1])),additionalArrayArgs,'UniformOutput',false); %convert additional arguments to cell arrays - params.inputOutputType = 'Scalar'; %and force scalar - end + for iArg = 1:length(additionalArrayArgs) + for iScan = tempScanList + if all(cellfun(@isnumeric,additionalArrayArgs)) && ismember(params.inputOutputType,{'3D Array','4D Array','4D Array (multiple scans)'}) %if all arguments are numeric and the input type is Array + additionalArrayInputs(iScan,iArg) = cellfun(@(x)repmat(x,[size(overlayData{iScan,1}) 1]),additionalArrayArgs(iArg),'UniformOutput',false); %convert additional arguments to arrays + else %if any additional argument is not a number + additionalArrayInputs(iScan,iArg) = cellfun(@(x)num2cell(repmat(x,[size(overlayData{iScan,1}) 1])),additionalArrayArgs(iArg),'UniformOutput',false); %convert additional arguments to cell arrays + params.inputOutputType = 'Scalar'; %and force scalar + end + end + end else additionalArrayInputs = {}; end %parse other additional inputs -additionalArgs = parseArguments(params.additionalArgs,','); +additionalArgs = mlrParseAdditionalArguments(params.additionalArgs,','); %convert overlays to cell arrays if scalar function if strcmp(params.inputOutputType,'Scalar') - for iScan = 1:nScans + for iScan = tempScanList for iOverlay = 1:length(params.overlayList) overlayData{iScan,iOverlay} = num2cell(overlayData{iScan,iOverlay}); %convert overlays to cell arrays end @@ -261,7 +392,7 @@ if strcmp(params.combinationMode,'Recursively apply to overlay pairs') %should add additional non-array arguments also ? - nTotalargs = size(overlayData,2)+length(additionalArrayArgs); + nTotalargs = size(overlayData,2)+size(additionalArrayArgs,2); combineFunctionString = '@(x1'; for iInput = 2:nTotalargs combineFunctionString = [combineFunctionString ',x' num2str(iInput)]; @@ -307,8 +438,8 @@ functionString = [functionString 'overlayData{iScan,' num2str(iInput) ',iOperations},']; end %additional arguments -for iInput = 1:length(additionalArrayInputs) - functionString = [functionString 'additionalArrayInputs{' num2str(iInput) '},']; +for iInput = 1:size(additionalArrayInputs,2) + functionString = [functionString 'additionalArrayInputs{iScan,' num2str(iInput) '},']; end if strcmp(params.inputOutputType,'4D Array') functionString(end:end+1) = '),'; %add closing bracket to end function cat @@ -334,7 +465,7 @@ for iScan = 1:size(overlayData,1) %check for empty overlays (only if 3D array or scalar) emptyInput=false; - if ismember(params.inputOutputType,{'3D Array','4D Array','Scalar'}) + if ismember(params.inputOutputType,{'3D Array','4D Array','Scalar','4D Array (multiple scans)'}) for iInput = 1:size(overlayData,2) if isempty(overlayData{iScan,iInput,iOperations}) emptyInput=true; @@ -351,7 +482,7 @@ eval(functionString); % toc catch exception - mrWarnDlg(sprintf('There was an error evaluating function %s:\n%s',combineFunctionString,getReport(exception,'basic'))); + mrWarnDlg(sprintf('There was an error evaluating function %s:\n%s\n',combineFunctionString,getReport(exception))); set(viewGet(thisView,'figNum'),'Pointer','arrow');drawnow; return end @@ -363,11 +494,11 @@ %add 0 to all the results to convert logical to doubles, because mrLoadRet doesn't like logical overlays switch(params.inputOutputType) case 'Structure' - for jScan = 1:nScans + for jScan = params.scanList outputData{iScan,iOutput,iOperations}.data{jScan} = outputData{iScan,iOutput,iOperations}.data{jScan}+0; end - case {'3D Array','4D Array','Scalar'} - outputData{iScan,iOutput,iOperations} = outputData{iScan,iOutput,iOperations}+0; + case {'3D Array','4D Array','Scalar','4D Array (multiple scans)'} + outputData{iScan,iOutput,iOperations} = outputData{iScan,iOutput,iOperations}+0; end %check that the size is compatible if ~isequal(size(outputData{iScan,iOutput,iOperations}),size(overlayData{iScan})) @@ -378,11 +509,24 @@ end end +if strcmp(params.inputOutputType,'4D Array (multiple scans)') + newOutputData = cell(nScans, params.nOutputOverlays, size(overlayData,3)); + for iOperations = 1:size(overlayData,3) + for iScan = 1:length(params.scanList) + for iOutput = 1:params.nOutputOverlays + newOutputData{params.scanList(iScan),iOutput,iOperations} = outputData{1,iOutput,iOperations}(:,:,:,iScan); + end + end + end + outputData = newOutputData; + clear newOutputData +end + if params.nOutputOverlays %name of output overlays for iOutput=1:params.nOutputOverlays if params.nOutputOverlays>1 - name = ['Ouput ' num2str(iOutput) ' - ']; + name = ['Output ' num2str(iOutput) ' - ']; else name = ''; end @@ -400,22 +544,28 @@ outputOverlayNames{iOutput,iOperations} = [outputOverlayNames{iOutput,iOperations} overlayNames{iInput} ',']; end end - for iInput = 1:length(additionalArrayArgs) - if isnumeric(additionalArrayArgs{iInput}) - outputOverlayNames{iOutput,iOperations} = [outputOverlayNames{iOutput,iOperations} num2str(additionalArrayArgs{iInput}) ',']; - else - outputOverlayNames{iOutput,iOperations} = [outputOverlayNames{iOutput,iOperations} additionalArrayArgs{iInput} ',']; - end + if ~isempty(params.additionalArrayArgs) + outputOverlayNames{iOutput,iOperations} = [outputOverlayNames{iOutput,iOperations} params.additionalArrayArgs ',']; end - for iInput = 1:length(additionalArgs) - if isnumeric(additionalArgs{iInput}) - outputOverlayNames{iOutput,iOperations} = [outputOverlayNames{iOutput,iOperations} mat2str(additionalArgs{iInput}) ',']; - elseif isa(additionalArgs{iInput},'function_handle') - outputOverlayNames{iOutput,iOperations} = [outputOverlayNames{iOutput,iOperations} func2str(additionalArgs{iInput}) ',']; - else - outputOverlayNames{iOutput,iOperations} = [outputOverlayNames{iOutput,iOperations} additionalArgs{iInput} ',']; - end +% for iInput = 1:length(additionalArrayArgs) +% if isnumeric(additionalArrayArgs{iInput}) +% outputOverlayNames{iOutput,iOperations} = [outputOverlayNames{iOutput,iOperations} num2str(additionalArrayArgs{iInput}) ',']; +% else +% outputOverlayNames{iOutput,iOperations} = [outputOverlayNames{iOutput,iOperations} additionalArrayArgs{iInput} ',']; +% end +% end + if ~isempty(params.additionalArgs) + outputOverlayNames{iOutput,iOperations} = [outputOverlayNames{iOutput,iOperations} params.additionalArgs ',']; end +% for iInput = 1:length(additionalArgs) +% if isnumeric(additionalArgs{iInput}) +% outputOverlayNames{iOutput,iOperations} = [outputOverlayNames{iOutput,iOperations} mat2str(additionalArgs{iInput}) ',']; +% elseif isa(additionalArgs{iInput},'function_handle') +% outputOverlayNames{iOutput,iOperations} = [outputOverlayNames{iOutput,iOperations} func2str(additionalArgs{iInput}) ',']; +% else +% outputOverlayNames{iOutput,iOperations} = [outputOverlayNames{iOutput,iOperations} additionalArgs{iInput} ',']; +% end +% end outputOverlayNames{iOutput,iOperations} = [outputOverlayNames{iOutput,iOperations}(1:end-1) ')']; end end @@ -426,77 +576,18 @@ end %pre-compute coordinates map to put values back from base space to overlay space - if params.baseSpace && ~params.exportToNewGroup && any(any((base2scan - eye(4))>1e-6)) - overlayIndexMap=cell(nScans,1); - overlayCoordsMap=cell(nScans,1); - baseCoordsOverlay=cell(nScans,1); - for iScan=1:nScans - %make a coordinate map of which overlay voxel each base map voxel corresponds to (convert base coordmap to overlay coord map) - baseCoordsMap{iScan} = reshape(baseCoordsMap{iScan},numel(baseCoordsMap{iScan})/3,3); - overlayCoordsMap{iScan} = (base2scan*[baseCoordsMap{iScan}';ones(1,size(baseCoordsMap{iScan},1))])'; - overlayCoordsMap{iScan} = overlayCoordsMap{iScan}(:,1:3); - overlayCoordsMap{iScan}(all(~overlayCoordsMap{iScan},2),:)=NaN; - overlayCoordsMap{iScan} = round(overlayCoordsMap{iScan}); - scanDims = viewGet(thisView,'dims',iScan); - overlayCoordsMap{iScan}(any(overlayCoordsMap{iScan}>repmat(scanDims,size(overlayCoordsMap{iScan},1),1)|overlayCoordsMap{iScan}<1,2),:)=NaN; - %convert overlay coordinates to overlay indices for manipulation ease - overlayIndexMap{iScan} = sub2ind(scanDims, overlayCoordsMap{iScan}(:,1), overlayCoordsMap{iScan}(:,2), overlayCoordsMap{iScan}(:,3)); - - %now make a coordinate map of which base map voxels each overlay index corresponds to - %(there will be several maps because each overlay voxels might correspond to several base voxels) - - % % %METHOD 1 - % % %sort base indices - % % [sortedOverlayIndices,whichBaseIndices] = sort(overlayIndexMap{iScan}); - % % %remove NaNs (which should be at the end of the vector) - % % whichBaseIndices(isnan(sortedOverlayIndices))=[]; - % % sortedOverlayIndices(isnan(sortedOverlayIndices))=[]; - % % %find the first instance of each unique index - % % firstInstances = sortedIndices(1:end-1) ~= sortedIndices(2:end); - % % firstInstances = [true;firstInstances]; - % % %get the unique overlay indices - % % uniqueOverlayIndices = sortedOverlayIndices(firstInstances); - % % %compute the number of instances for each unique overlay index (= number - % % %of base different indices for each unique overlay index) - % % numberInstances = diff(find([firstInstances;true])); - % % maxInstances = max(numberInstances); - % % baseCoordsOverlay2{iScan} = sparse(prod(scanDims),maxInstances); - % % hWaitBar = mrWaitBar(-inf,'(combineTransformOverlays) Creating base coordinates overlay map for scan'); - % % %for each unique overlay index, find all the corresponding base indices - % % for i = 1:length(uniqueOverlayIndices) - % % mrWaitBar( i/length(uniqueOverlayIndices), hWaitBar); - % % theseBaseIndices = whichBaseIndices(sortedOverlayIndices==uniqueOverlayIndices(i)); - % % baseCoordsOverlay2{iScan}(uniqueOverlayIndices(i),1:length(theseBaseIndices))=theseBaseIndices'; - % % end - % % mrCloseDlg(hWaitBar); - - %METHOD 2 (faster) - %first find the maximum number of base voxels corresponding to a single overlay voxel (this is modified from function 'unique') - %sort base non-NaN indices - sortedIndices = sort(overlayIndexMap{iScan}(~isnan(overlayIndexMap{iScan}))); - %find the first instance of each unique index - firstInstances = sortedIndices(1:end-1) ~= sortedIndices(2:end); - firstInstances = [true;firstInstances]; - %compute the number of instances for each unique overlay index - %(= number of base different indices for each unique overlay index) - numberInstances = diff(find([firstInstances;true])); - maxInstances = max(numberInstances); - baseCoordsOverlay{iScan} = sparse(prod(scanDims),maxInstances); - %Now for each set of unique overlay indices, find the corresponding base indices - hWaitBar = mrWaitBar(-inf,'(combineTransformOverlays) Creating base coordinates overlay map for scan'); - for i=1:maxInstances - mrWaitBar( i/maxInstances, hWaitBar); - %find set of unique instances of overlay indices - [uniqueOverlayIndices, whichBaseIndices]= unique(overlayIndexMap{iScan}); - %remove NaNs - whichBaseIndices(isnan(uniqueOverlayIndices))=[]; - uniqueOverlayIndices(isnan(uniqueOverlayIndices))=[]; - %for each overlay voxel found, set the corresponding base index - baseCoordsOverlay{iScan}(uniqueOverlayIndices,i)=whichBaseIndices; - %remove instances that were found from the overlay index map before going through the loop again - overlayIndexMap{iScan}(whichBaseIndices)=NaN; + baseCoordsOverlay=cell(nScans,1); + for iScan=params.scanList + if params.baseSpace && ~params.exportToNewGroup && (any(any((base2scan{iScan} - eye(4))>1e-6)) || baseType > 0) + if viewGet(thisView,'basetype')==1 + scanDims{iScan} = viewGet(thisView,'dims',iScan); + if ~isempty(baseCoordsMap{iScan}) + %make a coordinate map of which overlay voxel each base map voxel corresponds to (convert base coordmap to overlay coord map) + baseCoordsOverlay{iScan} = inverseBaseCoordMap(baseCoordsMap{iScan},scanDims{iScan},base2scan{iScan}); + end + else + keyboard %not implemented (actually it might work for surfaces) end - mrCloseDlg(hWaitBar); end end @@ -535,7 +626,13 @@ tseriesDir = viewGet(thisView,'tseriesDir'); scanFileName = [baseName mrGetPref('niftiFileExtension')]; newPathStr = fullfile(tseriesDir,scanFileName); - [bytes,hdr] = cbiWriteNifti(newPathStr,repmat(base.im,[1 1 size(outputData{1},3)]),hdr); + %find an non-empty scan to get the size of the third dimension of overlays + for iScan = 1:size(outputData,1) + if ~isempty(outputData{iScan}) + firstNonEmptyScan = iScan; + end + end + [bytes,hdr] = cbiWriteNifti(newPathStr,repmat(base.im,[1 1 size(outputData{firstNonEmptyScan},3)]),hdr); % Add it scanParams.fileName = scanFileName; thisView = viewSet(thisView,'newScan',scanParams); @@ -575,76 +672,65 @@ defaultOverlay.clip = []; defaultOverlay.range = []; defaultOverlay.name = []; - defaultOverlay.data = []; + defaultOverlay.data = cell(1,nScans); for iOverlay = 1:size(outputData,2) switch(params.inputOutputType) - case {'3D Array','4D Array','Scalar'} + case {'3D Array','4D Array','Scalar','4D Array (multiple scans)'} outputOverlay(iOverlay) = defaultOverlay; - outputOverlay(iOverlay).data = outputData(:,iOverlay); + outputOverlay(iOverlay).data = outputData(:,iOverlay)'; + maxValue = -inf; + minValue = inf; for iOutput = 1:size(outputData,1) - isNotEmpty(iOutput) = ~isempty(outputData{iOutput,iOverlay}); + if ~isempty(outputData{iOutput,iOverlay}) && ~all(isnan(outputData{iOutput,iOverlay}(:))) + maxValue = max(maxValue,max(outputData{iOutput,iOverlay}(outputData{iOutput,iOverlay}-inf))); + end end - allScansData = cell2mat(outputData(isNotEmpty,iOverlay)); case 'Structure' outputOverlay(iOverlay) = copyFields(defaultOverlay,outputData{iOverlay}); - allScansData = cell2mat(outputOverlay(iOverlay).data); + maxValue = max(outputOverlay(iOverlay).data(outputOverlay(iOverlay).data-inf)); end - if ~params.exportToNewGroup && params.baseSpace && any(any((base2scan - eye(4))>1e-6)) %put back into scan/overlay space - for iScan=1:nScans + for iScan=params.scanList + if ~params.exportToNewGroup && params.baseSpace && (any(any((base2scan{iScan} - eye(4))>1e-6)) || baseType > 0) %put back into scan/overlay space if ~isempty(outputOverlay(iOverlay).data{iScan}) if viewGet(thisView,'basetype')==1 - data = zeros(scanDims); - datapoints=zeros(prod(scanDims),1); - for i=1:size(baseCoordsOverlay{iScan},2) - thisBaseCoordsMap = full(baseCoordsOverlay{iScan}(:,i)); - data(logical(thisBaseCoordsMap)) = data(logical(thisBaseCoordsMap)) + ... - outputOverlay(iOverlay).data{iScan}(thisBaseCoordsMap(logical(thisBaseCoordsMap))); - datapoints = datapoints+logical(thisBaseCoordsMap); - end - datapoints = reshape(datapoints,scanDims); - outputOverlay(iOverlay).data{iScan} = data ./datapoints; + outputOverlay(iOverlay).data{iScan} = applyInverseBaseCoordMap(baseCoordsOverlay{iScan},scanDims{iScan},outputOverlay(iOverlay).data{iScan}); else keyboard %not implemented end end end end - maxValue = max(allScansData(allScansData-inf)); outputOverlay(iOverlay).clip = [minValue maxValue]; outputOverlay(iOverlay).range = [minValue maxValue]; outputOverlay(iOverlay).name = outputOverlayNames{iOverlay}; end + + % if we're exporting to a new group and there are several scans, we split the different scans of each overlay into separate overlays + if params.exportToNewGroup && nScans>1 + defaultOverlay.params.scanList = 1; + cOverlay = 0; + for iOverlay = 1:size(outputData,2) + for iScan = params.scanList + if ~isempty(outputOverlay(iOverlay).data{iScan}) + cOverlay = cOverlay+1; + outputOverlay2(cOverlay) = defaultOverlay; + outputOverlay2(cOverlay).data = outputOverlay(iOverlay).data(iScan); + outputOverlay2(cOverlay).clip = outputOverlay(iOverlay).clip; + outputOverlay2(cOverlay).range = outputOverlay(iOverlay).range; + outputOverlay2(cOverlay).name = ['Scan ' num2str(iScan) ' - ' outputOverlay(iOverlay).name]; + end + end + end + outputOverlay = outputOverlay2; + end + thisView = viewSet(thisView,'newoverlay',outputOverlay); - refreshMLRDisplay(thisView.viewNum); end set(viewGet(thisView,'figNum'),'Pointer','arrow');drawnow; -function [arguments, nArgs] = parseArguments(argumentString, separator) - -%parse string of arguments separated by separator and put them into a cell array of numerical and string arguments -%non-numerical values that are not between quotes are converted into strings -% -% Julien Besle, 08/07/2010 -nArgs = 0; -arguments = cell(0); -remain = argumentString; -while ~isempty(remain) - nArgs = nArgs+1; - [token,remain] = strtok(remain, separator); - try - arguments{nArgs} = eval(token); - catch exception - if ismember(exception.identifier,{'MATLAB:UndefinedFunction','MATLAB:minrhs'}) - arguments{nArgs} = token; - else - mrErrorDlg(['(parseArguments) could not read argument: ' exception.message]); - end - end - -end - function printHelp(params) if strcmp(params.combineFunction,'User Defined') @@ -658,4 +744,3 @@ function printHelp(params) disp(helpString); end end - diff --git a/mrLoadRet/Plugin/GLM_v2/interrogatorFunctions/printClickedCoordinates.m b/mrLoadRet/Plugin/GLM_v2/interrogatorFunctions/printClickedCoordinates.m new file mode 100644 index 000000000..3fbafea0d --- /dev/null +++ b/mrLoadRet/Plugin/GLM_v2/interrogatorFunctions/printClickedCoordinates.m @@ -0,0 +1,32 @@ +% dummyInterrogator +% +% usage: [ ] = printClickedCoordinates(thisView,overlayNum,scanNum,x,y,z,roi) +% by: julien besle +% date: 2023-10-06 +% +% purpose: print the coordinates of the mouse-clicked point in the main mrLoadRet window in various coordinate systems +% this is especially useful for surfaces, where coordinates are not displayed in the mrLoadRet window when hovering the mouse + +function printClickedCoordinates(thisView,overlayNum,scanNum,x,y,z,roi) + +scanCoords = viewGet(thisView,'mouseDownScanCoords'); +baseCoords = viewGet(thisView,'mouseDownBaseCoords'); +talCoords = viewGet(thisView,'mouseDownTalCoords'); +mniCoords = viewGet(thisView,'mouseDownMniCoords'); + +fprintf('Clicked coordinates: ') +if any(~isnan(scanCoords)) + fprintf('\tScan: %s', num2str(scanCoords,'%d ')) +end +if any(~isnan(baseCoords)) + fprintf('\tBase: %s', num2str(baseCoords,'%d ')) +end +if any(~isnan(talCoords)) + fprintf('\tTalairach: %s', num2str(talCoords,'%.1f ')) +end +if any(~isnan(mniCoords)) + fprintf('\tMNI: %s', num2str(mniCoords,'%.1f ')) +end +fprintf('\n') + +return; diff --git a/mrLoadRet/Plugin/GLM_v2/interrogatorFunctions/roisConfidenceInterval.m b/mrLoadRet/Plugin/GLM_v2/interrogatorFunctions/roisConfidenceInterval.m index 78164b10f..10f6b36dd 100644 --- a/mrLoadRet/Plugin/GLM_v2/interrogatorFunctions/roisConfidenceInterval.m +++ b/mrLoadRet/Plugin/GLM_v2/interrogatorFunctions/roisConfidenceInterval.m @@ -184,7 +184,7 @@ function roisConfidenceInterval(thisView,overlayNum,scanNum,x,y,z,roi) fTests = analysisParams.testParams.fTests; contrasts = analysisParams.testParams.contrasts; %-------------- get the HRF model -------------- - [analysisParams.hrfParams,d.hrf] = feval(analysisParams.hrfModel, analysisParams.hrfParams, d.tr/d.designSupersampling,0,1); + [analysisParams.hrfParams,d.hrf] = feval(analysisParams.hrfModel, analysisParams.hrfParams, d.tr/d.designSupersampling,d.tr/d.estimationSupersampling,1); nEstimates = size(d.hrf,2); case 'Deconvolution' diff --git a/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m b/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m new file mode 100644 index 000000000..35f0b4db1 --- /dev/null +++ b/mrLoadRet/Plugin/GLM_v2/inverseBaseCoordMap.m @@ -0,0 +1,52 @@ +% function surf2volumeMap = inverseBaseCoordMap(coordsMap,volumeDims,) +% +% Computes a correspondence map between each voxel in a given volume and corresponding +% voxels of a flat map or surface and outputs it as a sparse matrix +% By default this is computed for the volume on which the flat map or surface was based. +% If an xform (rotation matrix) is provided, it is computed for the corresponding volume. +% In either case, volumeDims gives the dimensions of the destination volume (after rotation). +% +% To transform data from flat space to volume space, call : +% volumeData = applyInverseBaseCoordMap(surf2volumeMap,volumeDims,surfData) +% +% Taken out of combineTransformOverlays.m (22/07/2020) + + +function surf2volumeMap = inverseBaseCoordMap(coordsMap,volumeDims,xform) + +if ieNotDefined('xform') + xform = eye(4); +end +coordsMap = reshape(coordsMap,numel(coordsMap)/3,3); % reshape into a simple coordinate matrix +coordsMap = (xform*[coordsMap';ones(1,size(coordsMap,1))])'; % convert coordinates from flat/surf base anatomy to requested volume coordinates +coordsMap = coordsMap(:,1:3); +coordsMap(all(~coordsMap,2),:)=NaN; % ignore any row that has at least one zero +coordsMap = round(coordsMap); +coordsMap(any(coordsMap>repmat(volumeDims,size(coordsMap,1),1)|coordsMap<1,2),:)=NaN; % ignore any row that's outside the requested volume +% convert volume coordinates to linear indices for manipulation ease +volIndexMap = sub2ind(volumeDims, coordsMap(:,1), coordsMap(:,2), coordsMap(:,3)); +clearvars('coordsMap'); % save memory + +% now make an inverse coordinate map of which surface voxels/vertices indices correspond to each volume index +% (there will be several maps because each volume voxels might correspond to several surface voxel/vertex indices) + +% first find the maximum number of surface points corresponding to a single volume voxel (this is modified from function 'unique') +% sort volume indices +[sortedVolIndices,whichSurfIndices] = sort(volIndexMap); +whichSurfIndices(isnan(sortedVolIndices)) = []; % remove NaNs +sortedVolIndices(isnan(sortedVolIndices)) = []; % remove NaNs +nSurfVoxels = numel(sortedVolIndices); +% find the first instance of each unique index (except the very first) +firstInstances = sortedVolIndices(1:end-1) ~= sortedVolIndices(2:end); +firstInstances = [true;firstInstances]; +% compute the number of instances for each unique volume index +% (= number of different base indices for each unique volume index) +numberInstances = diff(find([firstInstances;true])); +maxInstances = max(numberInstances); +% number each instance of a unique index from 1 to number of instances +instanceIndices = ones(nSurfVoxels,1); +firstInstances(1) = false; +instanceIndices(firstInstances) = -numberInstances(1:end-1)+1; +instanceIndices = cumsum(instanceIndices); +% fill the sparse matrix +surf2volumeMap = sparse(sortedVolIndices,instanceIndices,whichSurfIndices,prod(volumeDims),maxInstances); diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/fweAdjust.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/fweAdjust.m index 9b334e8c6..2556dec84 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/fweAdjust.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/fweAdjust.m @@ -1,7 +1,7 @@ % fweAdjust.m % % $Id$ -% usage: adjustedP = fweAdjust(p,params,) +% usage: adjustedP = fweAdjust(p,params,) % by: julien besle, % date: 17/01/2011 % purpose: adjusts p-values for various familywise error control procedure @@ -14,6 +14,14 @@ function adjustedPdata = fweAdjust(p, params, numberTrueH0, lambda) +%this function assumes that p is a column vector +%if this is not the case, transpose +if size(p,1)==1 + p=p'; + transposed=true; +else + transposed=false; +end isNotNan = ~isnan(p); sizePdata = size(p); @@ -49,13 +57,17 @@ case 'Hommel' % Hommel (1988) Biometrika (1988), 75, 2, pp. 383-6 -%%% % p-adjustment algorithm provided by Wright (1992) -% % % % adjustedP=p; -% % % % for m=numberH0:-1:2 -% % % % cMin = min(m*p(numberH0-m+1:numberH0)./(m+(numberH0-m+1:numberH0)-numberH0)); -% % % % adjustedP(numberH0-m+1:numberH0) = max(adjustedP(numberH0-m+1:numberH0),cMin); -% % % % adjustedP(1:numberH0-m) = max(adjustedP(1:numberH0-m),min(cMin,m*p(1:numberH0-m))); -% % % % end + % p-adjustment algorithm provided by Wright (1992) +% % original version (note that this version assumes that p is a column vector) +% p=p'; +% adjustedP=p; +% for m=numberH0:-1:2 +% cMin = min(m*p(numberH0-m+1:numberH0)./(m+(numberH0-m+1:numberH0)-numberH0)); +% adjustedP(numberH0-m+1:numberH0) = max(adjustedP(numberH0-m+1:numberH0),cMin); +% adjustedP(1:numberH0-m) = max(adjustedP(1:numberH0-m),min(cMin,m*p(1:numberH0-m))); +% end +% p=p'; +% adjustedP = adjustedP'; %I think this version is clearer: adjustedP=p; @@ -73,3 +85,7 @@ adjustedP(sortingIndex) = adjustedP; adjustedPdata = NaN(sizePdata); adjustedPdata(isNotNan) = adjustedP; + +if transposed + adjustedPdata = adjustedPdata'; +end diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getEstimates.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getEstimates.m index f95bd8e55..49c25587d 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getEstimates.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getEstimates.m @@ -165,7 +165,7 @@ extendedContrasts = extendedContrasts(any(extendedContrasts,2),:); end else - hdrContrasts = params.contrasts; + hdrContrasts = kron(params.contrasts,hrf); extendedContrasts = params.contrasts; end diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmEVParamsGUI.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmEVParamsGUI.m index f4a5b29f9..027a907af 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmEVParamsGUI.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmEVParamsGUI.m @@ -360,7 +360,7 @@ function plotExperimentalDesign(thisScanParams,params,scanParams,thisView,unique d = loadScan(thisView, iScan, viewGet(thisView,'groupNum',params.groupName), 0); d = getStimvol(d,params.scanParams{iScan}); - [params.hrfParams,d.hrf] = feval(params.hrfModel, params.hrfParams, d.tr/d.designSupersampling,params.scanParams{iScan}.acquisitionDelay,1); + [params.hrfParams,d.hrf] = feval(params.hrfModel, params.hrfParams, d.tr/d.designSupersampling,params.scanParams{iScan}.acquisitionDelay,1,d.tr/d.estimationSupersampling); d = eventRelatedPreProcess(d,params.scanParams{iScan}.preprocess); d = makeDesignMatrix(d,params,1,iScan); if strcmp(params.EVnames{end},'Not used') diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmStatistics.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmStatistics.m index 7ac327215..d4516cf43 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmStatistics.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmStatistics.m @@ -1,6 +1,6 @@ % getGlmStatistics.m % -% usage: [d, out] = getGlmStatistics(d, params, verbose, precision, actualData, computeTtests) +% usage: [d, out] = getGlmStatistics(d, params, verbose, precision, actualData, smoothingVoxels) % by: Julien Besle % date: 18/01/10 % $Id$ @@ -21,7 +21,7 @@ % reference for bootstrap and FWE adjustment testing % - Westfall, P.H., and S.S. Young. Resampling-based multiple testing. Wiley-Interscience, 1993 -function [d, out] = getGlmStatistics(d, params, verbose, precision, actualData) +function [d, out] = getGlmStatistics(d, params, verbose, precision, actualData, smoothingVoxels) %DEBUG % lastwarn('',''); @@ -404,16 +404,7 @@ if isfield(d,'roiPositionInBox') %if the data are not spatially organized, we need to temporarily put them in a volume timeseries = reshapeToRoiBox(timeseries,d.roiPositionInBox|d.marginVoxels,precision); end - switch params.smoothingPlane %planes other than sagittal will only work for ROIs because it's the only case in which the data is 3D at this point in the loop - case {'Sagittal'} - timeseries = convn(timeseries,permute(gaussianKernel2D(params.spatialSmoothing),[4 3 1 2]),'same'); - case {'Axial'} - timeseries = convn(timeseries,permute(gaussianKernel2D(params.spatialSmoothing),[4 1 2 3]),'same'); - case {'Coronal'} - timeseries = convn(timeseries,permute(gaussianKernel2D(params.spatialSmoothing),[4 1 3 2]),'same'); - case '3D' - timeseries = convn(timeseries,permute(gaussianKernel(params.spatialSmoothing),[4 1 2 3]),'same'); - end + timeseries = convn(timeseries,permute(gaussianKernel(smoothingVoxels),[4 1 2 3]),'same'); if isfield(d,'roiPositionInBox') %if the data are not spatially organized %put the data back in a new matrix with only the voxels of interest timeseries = reshape(timeseries,d.dim(4), numel(d.roiPositionInBox)); @@ -720,10 +711,10 @@ end thisContrastBetaSte = sqrt(thisContrastBetaSte); thisStatistic(:,1:numberTtests) = thisContrastBetas ./ thisContrastBetaSte; - switch(params.tTestSide) - case 'Both' + switch(lower(params.tTestSide)) + case 'both' thisStatistic(:,1:numberTtests) = abs(thisStatistic(:,1:numberTtests)); - case 'Left' + case 'left' thisStatistic(:,1:numberTtests) = -1 *thisStatistic(:,1:numberTtests); end thisParametricP(:,1:numberTtests) = T2p(thisStatistic(:,1:numberTtests),d.rdf,params); @@ -1071,7 +1062,7 @@ function p = T2p(T,rdf,params) p = 1 - cdf('t', double(T), rdf); %here use doubles to deal with small Ps -if strcmp(params.tTestSide,'Both') +if strcmp(lower(params.tTestSide),'both') p = 2*p; end %we do not allow probabilities of 0 and replace them by minP @@ -1094,6 +1085,7 @@ p = max(count/nResamples,1/(nResamples+1)); p(isnan(count)) = NaN; %NaNs must remain NaNs (they became 1e-16 when using max) + function tfceS = applyTfce(S,roiPositionInBox,precision) %reshape to volume to apply TFCE and then reshape back to one dimension tfceS = applyFslTFCE(permute(reshapeToRoiBox(S',roiPositionInBox,precision),[2 3 4 1]),'',0); @@ -1102,36 +1094,42 @@ %put NaNs back tfceS(isnan(S)) = NaN; + %this function computes the sum of squared errors between the dampened oscillator %model (for xdata) and the sample autocorrelation function (ydata) function sse = minimizeDampenedOscillator(params, xdata,ydata) FittedCurve = params(1)^2 - exp(params(2) * xdata) .* cos(params(3)*xdata); ErrorVector = FittedCurve - ydata; sse = sum(ErrorVector.^2); - - - -function kernel = gaussianKernel(FWHM) - -sigma_d = FWHM/2.35482; -w = ceil(FWHM); %deals with resolutions that are not integer -%make the gaussian kernel large enough for FWHM -kernelDims = 2*[w w w]+1; -kernelCenter = ceil(kernelDims/2); -[X,Y,Z] = meshgrid(1:kernelDims(1),1:kernelDims(2),1:kernelDims(3)); -kernel = exp(-((X-kernelCenter(1)).^2+(Y-kernelCenter(2)).^2+(Z-kernelCenter(3)).^2)/(2*sigma_d^2)); %Gaussian function -kernel = kernel./sum(kernel(:)); -function kernel = gaussianKernel2D(FWHM) +function kernel = gaussianKernel(FWHM) sigma_d = FWHM/2.35482; w = ceil(FWHM); %deals with resolutions that are not integer %make the gaussian kernel large enough for FWHM -kernelDims = 2*[w w]+1; +if length(w)==1 + w = [w w w]; + sigma_d = [sigma_d sigma_d sigma_d]; +end +kernelDims = 2*w+1; kernelCenter = ceil(kernelDims/2); -[X,Y] = meshgrid(1:kernelDims(1),1:kernelDims(2)); -kernel = exp(-((X-kernelCenter(1)).^2+(Y-kernelCenter(2)).^2)/(2*sigma_d^2)); %Gaussian function +[X,Y,Z] = ndgrid(1:kernelDims(1),1:kernelDims(2),1:kernelDims(3)); % using ndgrid here and not meshgrid because the first dimension represents the L/R axis +if all(sigma_d == 0) + kernel = 1; % no smoothing +elseif sigma_d(2) == 0 && sigma_d(3) == 0 + kernel = exp(-((X-kernelCenter(1)).^2/(2*sigma_d(1)^2))); % 1D Gaussian function along X +elseif sigma_d(1) == 0 && sigma_d(3) == 0 + kernel = exp(-((Y-kernelCenter(2)).^2/(2*sigma_d(2)^2))); % 1D Gaussian function along Y +elseif sigma_d(1) == 0 && sigma_d(2) == 0 + kernel = exp(-((Z-kernelCenter(3)).^2/(2*sigma_d(3)^2))); % 1D Gaussian function along Z +elseif sigma_d(3) == 0 + kernel = exp(-((X-kernelCenter(1)).^2/(2*sigma_d(1)^2)+(Y-kernelCenter(2)).^2/(2*sigma_d(2)^2))); % 2D Gaussian function in XY plane +elseif sigma_d(2) == 0 + kernel = exp(-((X-kernelCenter(1)).^2/(2*sigma_d(1)^2)+(Z-kernelCenter(3)).^2/(2*sigma_d(3)^2))); % 2D Gaussian function in XZ plane +elseif sigma_d(1) == 0 + kernel = exp(-((Y-kernelCenter(2)).^2/(2*sigma_d(2)^2)+(Z-kernelCenter(3)).^2/(2*sigma_d(3)^2))); % 2D Gaussian function in YZ plane +else + kernel = exp(-((X-kernelCenter(1)).^2/(2*sigma_d(1)^2)+(Y-kernelCenter(2)).^2/(2*sigma_d(2)^2)+(Z-kernelCenter(3)).^2/(2*sigma_d(3)^2))); % 3D Gaussian function +end kernel = kernel./sum(kernel(:)); - - diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmTestParamsGUI.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmTestParamsGUI.m index 3a3552c28..216a11e6d 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmTestParamsGUI.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/getGlmTestParamsGUI.m @@ -49,9 +49,9 @@ %here we assume that all scans in this group have the same sampling parameters %(which are needed to determine the number of components in the deconvolution case) framePeriod = viewGet(thisView,'framePeriod',params.scanNum(1),viewGet(thisView,'groupNum',params.groupName)); - [hrfParams,hrf] = feval(params.hrfModel, params.hrfParams,framePeriod/params.scanParams{params.scanNum(1)}.estimationSupersampling,[],1); + [~,hrf] = feval(params.hrfModel, params.hrfParams,framePeriod/params.scanParams{params.scanNum(1)}.designSupersampling,[],1,framePeriod/params.scanParams{params.scanNum(1)}.estimationSupersampling); nComponents = size(hrf,2); - if fieldIsNotDefined(params, 'componentsToTest') || ~isequal(nComponents,length(params.componentsToTest)); + if fieldIsNotDefined(params, 'componentsToTest') || ~isequal(nComponents,length(params.componentsToTest)) params.componentsToTest = ones(1,nComponents); end if fieldIsNotDefined(params, 'componentsCombination') @@ -207,7 +207,7 @@ end %check consistency of parameters - if params.numberContrasts && params.computeTtests && ~strcmp(params.tTestSide,'Both') && ... + if params.numberContrasts && params.computeTtests && ~strcmp(lower(params.tTestSide),'both') && ... nnz(params.componentsToTest)>1 && strcmp(params.componentsCombination,'Or') mrWarnDlg('(getTestParamsGUI) One-sided T-tests on several EV components with ''Or'' combination are not implemented','Yes'); elseif ~orthogonal %if there is at least one restriction matrix with non-orthogonal contrasts diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysis.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysis.m index 07425f4b0..246e99ff8 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysis.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysis.m @@ -61,29 +61,40 @@ computePermutations = numberTests && (params.permutationTests || (params.parametricTests && params.permutationFweAdjustment)); if params.covCorrection %number of voxels to get around the ROI/subset box in case the covariance matrix is estimated - voxelsMargin = repmat(floor(params.covEstimationAreaSize/2),1,3); + % Parameter covEstimationAreaSize is specified in voxels, so we assume that data are isometric (unlike for spatial smoothing below) + voxelsMargin = repmat(floor(params.covEstimationAreaSize/2),length(params.scanNum),3); switch(params.covEstimationPlane) case {'Sagittal'} - voxelsMargin(1)=0; + voxelsMargin(:,1)=0; case {'Axial'} - voxelsMargin(3)=0; + voxelsMargin(:,3)=0; case {'Coronal'} - voxelsMargin(2)=0; + voxelsMargin(:,2)=0; end else - voxelsMargin = [0 0 0]; + voxelsMargin = zeros(length(params.scanNum),3); end -if params.spatialSmoothing %we'll also need a margin if we're spatially smoothing - switch(params.smoothingPlane) + +if params.spatialSmoothing + % calculate smoothing size parameter in voxels in each of the three dimensions + cScan = 0; + for iScan = params.scanNum + cScan = cScan+1; + smoothingVoxels(cScan,:) = params.spatialSmoothing./viewGet(thisView,'scanvoxelsize',iScan); + switch(params.smoothingPlane) case {'Sagittal'} - voxelsMargin = max(voxelsMargin, [0 params.spatialSmoothing params.spatialSmoothing]); + smoothingVoxels(cScan,1) = 0; case {'Axial'} - voxelsMargin = max(voxelsMargin,[params.spatialSmoothing params.spatialSmoothing 0]); + smoothingVoxels(cScan,3) = 0; case {'Coronal'} - voxelsMargin = max(voxelsMargin,[params.spatialSmoothing 0 params.spatialSmoothing]); - case '3D' - voxelsMargin = max(voxelsMargin,repmat(params.spatialSmoothing,1,3)); + smoothingVoxels(cScan,2) = 0; + end end + + %we'll also need a margin if we're spatially smoothing + voxelsMargin = max(voxelsMargin,ceil(smoothingVoxels)); +else + smoothingVoxels = zeros(length(params.scanNum),3); end %--------------------------------------------------------- Main loop over scans --------------------------------------------------- figNum = viewGet(thisView,'figNum'); @@ -147,8 +158,9 @@ end end - +cScan = 0; for iScan = params.scanNum + cScan = cScan+1; numVolumes = viewGet(thisView,'nFrames',iScan); scanDims{iScan} = viewGet(thisView,'dims',iScan); @@ -163,7 +175,7 @@ else roiList = 1:viewGet(thisView,'numberOfRois'); end - [subsetBox{iScan}, whichRoi, marginVoxels] = getRoisBox(thisView,iScan,voxelsMargin,roiList); + [subsetBox{iScan}, whichRoi, marginVoxels] = getRoisBox(thisView,iScan,voxelsMargin(cScan,:),roiList); usedVoxelsInBox = marginVoxels | any(whichRoi,4); %clear('whichRoi','marginVoxels'); if params.covCorrection && ~strcmp(params.covEstimationBrainMask,'None') @@ -322,7 +334,7 @@ end %create model HRF - [params.hrfParams,d.hrf] = feval(params.hrfModel, params.hrfParams, d.tr/d.designSupersampling,scanParams{iScan}.acquisitionDelay,1); + [params.hrfParams,d.hrf] = feval(params.hrfModel, params.hrfParams, d.tr/d.designSupersampling,scanParams{iScan}.acquisitionDelay,1,d.tr/d.estimationSupersampling); d.volumes = 1:d.dim(4); %make a copy of d @@ -378,7 +390,7 @@ end % compute estimates and statistics - [d, out] = getGlmStatistics(d, params, verbose, precision, actualData);%, computeTtests,computeBootstrap); + [d, out] = getGlmStatistics(d, params, verbose, precision, actualData, smoothingVoxels(cScan,:));%, computeTtests,computeBootstrap); if iPerm==1 @@ -761,13 +773,17 @@ ordered_abs_betas = [ordered_abs_betas; contrast{iScan}(:)]; end ordered_abs_betas = ordered_abs_betas(~isnan(ordered_abs_betas)); - min_beta = min(min(min(min(min(ordered_abs_betas))))); - max_beta = max(max(max(max(max(ordered_abs_betas))))); - ordered_abs_betas = sort(abs(ordered_abs_betas)); - beta_perc95 = 0; - beta_perc95 = max(beta_perc95,ordered_abs_betas(round(numel(ordered_abs_betas)*.95))); %take the 95th percentile for the min/max - thisOverlay.range = [-beta_perc95 beta_perc95]; - thisOverlay.clip = [min_beta max_beta]; + if ~isempty(ordered_abs_betas) + min_beta = min(min(min(min(min(ordered_abs_betas))))); + max_beta = max(max(max(max(max(ordered_abs_betas))))); + ordered_abs_betas = sort(abs(ordered_abs_betas)); + beta_perc95 = 0; + beta_perc95 = max(beta_perc95,ordered_abs_betas(round(numel(ordered_abs_betas)*.95))); %take the 95th percentile for the min/max + thisOverlay.range = [-beta_perc95 beta_perc95]; + thisOverlay.clip = [min_beta max_beta]; + else + mrWarnDlg('(glmAnalysis) Analysis results are all NaNs. Check the Raw/motion compensated scans'); + end thisOverlay.colormap = jet(256); for iContrast = 1:numberContrasts overlays(end+1)=thisOverlay; diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysisGUI.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysisGUI.m index c32413da9..a93739df1 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysisGUI.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmAnalysisGUI.m @@ -94,7 +94,7 @@ askForParams = 1; % put group name on top of list to make it the default groupNames = putOnTopOfList(params.groupName,viewGet(thisView,'groupNames')); -hrfModelMenu = putOnTopOfList(params.hrfModel,{'hrfDoubleGamma','hrfFslFlobs','hrfDeconvolution','hrfBoxcar'}); +hrfModelMenu = putOnTopOfList(params.hrfModel,{'hrfDoubleGamma','hrfFslFlobs','hrfDeconvolution','hrfBoxcar','hrfCustom'}); analysisVolumeMenu = {'Whole volume'}; if nRois analysisVolumeMenu{end+1} = 'Loaded ROI(s)'; @@ -116,7 +116,7 @@ {'saveName',params.saveName,'File name to try to save the analysis as'},... {'hrfModel',hrfModelMenu,'type=popupmenu','Name of the function that defines the Hemodynamic Response Function model that will be convolved with the design matrix',},... {'analysisVolume',analysisVolumeMenu,'type=popupmenu','The analysis can be performed either on the whole scan volume, or only in the ROIs currently loaded/visible in the view, or only in a cubic subset of voxels, the coordinates of which will have to be specified in the scan parameter menu.'},... - {'spatialSmoothing',params.spatialSmoothing, 'minmax=[0 inf]','Width at half-maximum in voxels of a 2D/3D gaussian kernel that will be convolved with each slice at each time-point. If 0, no spatial smoothing is applied'},... + {'spatialSmoothing',params.spatialSmoothing, 'minmax=[0 inf]','Width at half-maximum in millimeters of a 2D/3D gaussian kernel that will be convolved with each slice at each time-point. If 0, no spatial smoothing is applied'},... {'smoothingPlane',smoothingPlaneMenu, 'Plane in which to perform 2D spatial smoothing. If ''3D'', a 3D Gaussian kernel is used.'},... {'covCorrection',params.covCorrection,'type=checkbox','(EXPERIMENTAL) Correction for temporally-correlated noise. Correcting for noise correlation is important for single-subject level statistics but significantly increases analysis time. Uncorrected correlated noise biases statistical significance of contrasts/F-tests but should not affect parameter estimates (contrasts values).'},... {'covEstimationAreaSize',params.covEstimationAreaSize, 'minmax=[1 inf]','contingent=covCorrection','round=1','For correlated-noise correction: dimensions in voxels of a square spatial window around each voxel on which the noise covariance is estimated'},... diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m index 91a25ea53..ed386dc19 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/glmPlot.m @@ -116,7 +116,6 @@ function glmPlot(thisView,overlayNum,scanNum,x,y,s,roi) set(fignum,'Name',['glmPlot: ' analysisParams.saveName]); %set plotting dimension -maxNumberSte = 3; subplotXgrid = [1.1 ones(1,length(roi)) .1 .4]; subplotYgrid = [.8*plotBetaWeights .6*logical(numberContrasts)*plotBetaWeights 1 logical(numberContrasts) .1 .1]; xMargin = .05; @@ -153,7 +152,6 @@ function glmPlot(thisView,overlayNum,scanNum,x,y,s,roi) hEhdr = []; hDeconv = []; for iPlot = 1:length(roi)+1 - hEhdrSte = zeros(numberEVs+numberContrasts,plotBetaWeights+1,maxNumberSte); if iPlot==1 %this is the voxel data titleString{1}=sprintf('Voxel (%i,%i,%i)',x,y,s); titleString{2}=sprintf('r2=%0.3f',r2data(x,y,s)); @@ -209,6 +207,8 @@ function glmPlot(thisView,overlayNum,scanNum,x,y,s,roi) e.hdrSte(:,:,3) = sqrt(mean(f.hdrSte.^2,3)); e.contrastHdrSte(:,:,3) = sqrt(mean(f.contrastHdrSte.^2,3)); + + % 4) as the std error of an estimate from the mean time-series (by rerunning the glm analysis) %buttonString{4} = 'ROI estimate standard error from ROI time-series'; %later... @@ -285,16 +285,35 @@ function glmPlot(thisView,overlayNum,scanNum,x,y,s,roi) %&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& PLOT DATA &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& - + if verLessThan('matlab','8.4') + hEhdrSte = zeros(numberEVs+numberContrasts,plotBetaWeights+1,size(e.betaSte,3)); + else + if ~isfield(analysisParams,'componentsToTest') + nComponents = 1; + else + nComponents = length(analysisParams.componentsToTest); + end + plotContrasts = 1*(numberContrasts>0); + hEhdrSte = gobjects(nComponents*(plotBetaWeights+plotContrasts)+numberEVs+numberContrasts,size(e.betaSte,3)); + end + for iSte = 1:size(e.betaSte,3) if plotBetaWeights % plot the beta weights - [h,hEhdrSte(1:numberEVs,1,iSte)] = plotBetas(betaAxes,e.betas,e.betaSte(:,:,iSte),iSte~=1); + if verLessThan('matlab','8.4') + [h,hEhdrSte(1:numberEVs,1,iSte)] = plotBetas(betaAxes,e.betas,e.betaSte(:,:,iSte),iSte~=1); + else + [h,hEhdrSte(1:nComponents,iSte)] = plotBetas(betaAxes,e.betas,e.betaSte(:,:,iSte),iSte~=1); + end if iSte==1 && iPlot==1,hBeta=h;end; if numberContrasts % plot the contrast estimates - [h,hEhdrSte(numberEVs+1:numberEVs+numberContrasts,1,iSte)] = plotBetas(contrastAxes,e.contrastBetas,e.contrastBetaSte(:,:,iSte),iSte~=1); + if verLessThan('matlab','8.4') + [h,hEhdrSte(numberEVs+1:numberEVs+numberContrasts,1,iSte)] = plotBetas(contrastAxes,e.contrastBetas,e.contrastBetaSte(:,:,iSte),iSte~=1); + else + [h,hEhdrSte(nComponents*plotBetaWeights+(1:nComponents),iSte)] = plotBetas(contrastAxes,e.contrastBetas,e.contrastBetaSte(:,:,iSte),iSte~=1); + end if iSte==1 && iPlot==1,hContrastBeta=h;end; % if iPlot>1 && iSte ==1 % disp(titleString{1}); @@ -303,15 +322,27 @@ function glmPlot(thisView,overlayNum,scanNum,x,y,s,roi) end end % plot the hemodynamic response for voxel - [h,hEhdrSte(1:numberEVs,plotBetaWeights+1,iSte)]=plotEhdr(ehdrAxes,e.time,e.hdr,e.hdrSte(:,:,iSte),[],[],iSte~=1); + if verLessThan('matlab','8.4') + [h,hEhdrSte(1:numberEVs,plotBetaWeights+1,iSte)]=plotEhdr(ehdrAxes,e.time,e.hdr,e.hdrSte(:,:,iSte),[],[],iSte~=1); + else + [h,hEhdrSte(nComponents*(plotBetaWeights+plotContrasts)+(1:numberEVs),iSte)]=plotEhdr(ehdrAxes,e.time,e.hdr,e.hdrSte(:,:,iSte),[],[],iSte~=1); + end if iSte==1, hHdr = h; hEhdr = [hEhdr;h];end if numberContrasts - [h,hEhdrSte(numberEVs+1:numberEVs+numberContrasts,plotBetaWeights+1,iSte)] = plotEhdr(hdrContrastAxes,e.time,e.contrastHdr, e.contrastHdrSte(:,:,iSte),'','',iSte~=1); + if verLessThan('matlab','8.4') + [h,hEhdrSte(numberEVs+(1:numberContrasts),plotBetaWeights+1,iSte)] = plotEhdr(hdrContrastAxes,e.time,e.contrastHdr, e.contrastHdrSte(:,:,iSte),'','',iSte~=1); + else + [h,hEhdrSte(nComponents*(plotBetaWeights+plotContrasts)+numberEVs+(1:numberContrasts),iSte)] = plotEhdr(hdrContrastAxes,e.time,e.contrastHdr, e.contrastHdrSte(:,:,iSte),'','',iSte~=1); + end if iSte==1, hContrastHdr =h; hEhdr = [hEhdr;h];end; end if iSte~=1 - set(hEhdrSte(:,:,iSte),'visible','off'); + if verLessThan('matlab','8.4') + set(hEhdrSte(:,:,iSte),'visible','off'); + else + set(hEhdrSte(:,iSte),'visible','off'); + end end end @@ -367,20 +398,24 @@ function glmPlot(thisView,overlayNum,scanNum,x,y,s,roi) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Finalize axes if ~isempty(e.betas) - %plot baselines of histograms if plotBetaWeights - plot(betaAxes,get(betaAxes,'Xlim'),[0 0],'--k','lineWidth',1); + if verLessThan('matlab','8.4') %only plot baseline in case it has been deleted before (if ver<8.4) + %plot baseline of first histogram + plot(betaAxes,get(betaAxes,'Xlim'),[0 0],'--k','lineWidth',1); maxSte = max(e.betaSte,[],3); makeScaleEditButton(fignum,betaAxes,... [nanmin(nanmin((e.betas-maxSte))),nanmax(nanmax((e.betas+maxSte)))]); + end if iPlot==1 ylabel(betaAxes,{'Beta' 'Estimates'}); lhandle = legend(hBeta,EVnames,'position',legendBetaPosition); set(lhandle,'Interpreter','none','box','off'); end if numberContrasts - %plot baseline - plot(contrastAxes,get(contrastAxes,'Xlim'),[0 0],'--k','lineWidth',1); + if verLessThan('matlab','8.4') %only plot baseline in case it has been deleted before (if ver<8.4) + %plot baseline of second histogram + plot(contrastAxes,get(contrastAxes,'Xlim'),[0 0],'--k','lineWidth',1); + end maxSte = max(e.contrastBetaSte,[],3); if isnan(maxSte) maxSte = 0; @@ -480,7 +515,7 @@ function eventRelatedPlotTSeries(handle,eventData,thisView,analysisParams, d, ro %in this case, we want it because it is useful to zoom on the time-series set(fignum,'toolbar','figure'); drawnow; -disppercent(-inf,'(glmPlot) Plotting time series'); +mlrDispPercent(-inf,'(glmPlot) Plotting time series'); if isnumeric(roi) %if roi is numeric, it's the coordinates of a single voxel actualTSeries = squeeze(loadTSeries(thisView,[],roi(3),[],roi(1),roi(2))); @@ -621,7 +656,7 @@ function eventRelatedPlotTSeries(handle,eventData,thisView,analysisParams, d, ro 'value',1,'callback',{@plotModelTSeries,hActualTSeries,actualTSeries,hModelTSeries,d.scm,ehdr,d.emptyEVcomponents,hPanel,hSubtractFromTseries,hActualFFT,hModelFFT}); end -disppercent(inf); +mlrDispPercent(inf); %delete(handle); %don't delete the button to plot the time-series @@ -735,12 +770,20 @@ function initializeFigure(fignum,numberColors) % if size(econt,2)==1 set(hAxes,'nextPlot','add'); - h=zeros(size(econt,1),1); + if verLessThan('matlab','8.4') + h=zeros(size(econt,1),1); + else + h=gobjects(size(econt,1),1); + end for iEv = 1:size(econt,1) h(iEv) = bar(hAxes,iEv,econt(iEv),'faceColor',colorOrder(iEv,:),'edgecolor','none'); end - %delete baseline - delete(get(h(iEv),'baseline')); + if verLessThan('matlab','8.4') + %delete baseline + delete(get(h(iEv),'baseline')); + else + set(get(h(iEv),'baseline'),'LineStyle','--','lineWidth',1); + end set(hAxes,'xTickLabel',{}) set(hAxes,'xTick',[]) else @@ -756,7 +799,13 @@ function initializeFigure(fignum,numberColors) end if ~ieNotDefined('econtste') - hSte = errorbar(hAxes,(1:size(econt,1))', econt, econtste, 'k','lineStyle','none'); + if size(econt,2)==1 + hSte = errorbar(hAxes,(1:size(econt,1))', econt, econtste, 'k','lineStyle','none'); + else + scaling = 0.79; + barXpositions = repmat(1:size(econt,2),size(econt,1),1) + repmat( (0:size(econt,1)-1)'/size(econt,1)*scaling ,1,size(econt,2)) - (size(econt,1)-1)/size(econt,1)*scaling/2; + hSte = errorbar(hAxes,barXpositions, econt, econtste, 'k','lineStyle','none')'; + end end @@ -803,8 +852,12 @@ function makeVisible(handle,eventdata,hAxes) case 'popupmenu' set(hAxes,'visible','off') handleNum = get(handle,'value'); - if handleNum ~= length(get(handle,'string')); - set(hAxes(:,:,handleNum),'visible','on'); + if handleNum ~= length(get(handle,'string')) + if verLessThan('matlab','8.4') + set(hAxes(:,:,handleNum),'visible','on'); + else + set(hAxes(:,handleNum),'visible','on'); + end end end diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfBoxcar.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfBoxcar.m index e4a620fa9..97a8520ba 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfBoxcar.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfBoxcar.m @@ -6,9 +6,9 @@ % date: 13/04/2010 % purpose: returns a canonical hrf modeled as a boxcar function % -function [params,hrf] = hrfBoxcar(params, sampleDuration, notUsed, defaultParams) +function [params,hrf] = hrfBoxcar(params, sampleDuration, ~, defaultParams, ~) -if ~any(nargin == [1 2 3 4])% 5]) +if ~any(nargin == [1 2 3 4 5]) help hrfBoxcar return end diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfCustom.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfCustom.m new file mode 100644 index 000000000..a2964a805 --- /dev/null +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfCustom.m @@ -0,0 +1,63 @@ +% hrfDeconvolution.m +% +% $Id$ +% usage: [params,hrf] = hrfCustom(params, sampleDuration, sampleDelay, defaultParams) +% by: julien besle +% date: 13/04/2010 +% purpose: returns the HRF specified as values in the parameters. If a time vector is specified +% checks that times correspond to actual TR and acquistion time (sampleDuration and sampleDelay) +% otherwise, assumes that HRF sample times correspond to those TR and acquisition times +% +function [params,hrf] = hrfCustom(params, sampleDuration, sampleDelay, defaultParams, ~) + +if ~any(nargin == [1 2 3 4 5]) + help hrfCustom + return +end + +if ieNotDefined('defaultParams'),defaultParams = 0;end +if ieNotDefined('sampleDelay') + sampleDelay=sampleDuration/2; +end + +if ieNotDefined('params') + params = struct; +end +if fieldIsNotDefined(params,'description') + params.description = 'Custom HRF'; +end +if fieldIsNotDefined(params,'hrf') + [~, params.hrf] = hrfDoubleGamma([],sampleDuration,sampleDelay,1); + params.hrf = params.hrf'; +end +if fieldIsNotDefined(params,'hrfTimes') + params.hrfTimes = sampleDelay+sampleDuration*(0:length(params.hrf)-1); +end + +paramsInfo = {... + {'description', params.description, 'comment describing the hdr model'},... + {'hrf',params.hrf,'values of the the HRF'},... + {'hrfTimes',params.hrfTimes,'Times of the HRF samples'},... +}; + +if defaultParams + params = mrParamsDefault(paramsInfo); +else + params = mrParamsDialog(paramsInfo, 'Set Custom HRF parameters'); +end + +if nargout==1 + return +end + +%check that the times correspond to +if ~isequal(sampleDelay+sampleDuration*(0:length(params.hrf)-1), params.hrfTimes) + mrWarnDlg('(hrfCustom) HRF times are not compatible with TR and acquisition time'); + keyoard +else + if size(params.hrf,1)==1 + hrf = params.hrf'; + else + hrf = params.hrf; + end +end \ No newline at end of file diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfDeconvolution.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfDeconvolution.m index 67c28b4a6..1a90002b3 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfDeconvolution.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfDeconvolution.m @@ -6,9 +6,9 @@ % date: 13/04/2010 % purpose: returns a deconvolution matrix given the design sampling period and the estimation sampling period % -function [params,hrf] = hrfDeconvolution(params, sampleDuration, notUsed, defaultParams) +function [params,hrf] = hrfDeconvolution(params, designSamplingPeriod, ~, defaultParams, estimationSamplingPeriod) -if ~any(nargin == [1 2 3 4])% 5]) +if ~any(nargin == [1 2 3 4 5]) help hrfDeconvolution return end @@ -16,6 +16,7 @@ %estimationSampling = varargin{1}; if ieNotDefined('defaultParams'),defaultParams = 0;end +if ieNotDefined('estimationSamplingPeriod'),estimationSamplingPeriod = designSamplingPeriod;end if ieNotDefined('params') params = struct; @@ -42,4 +43,8 @@ return end -hrf = eye(round(params.hdrlenS/sampleDuration)); +hrf = eye(round(params.hdrlenS/designSamplingPeriod)); + +% if the estimation sampling period is a multiple of the design sampling period +downsamplingFactor = round(estimationSamplingPeriod/designSamplingPeriod); % downsampling factor from design to estimation sampling rates +hrf = hrf(:,1:downsamplingFactor:end); % (although this is rounded, we generally assume that the estimation sampling period is a multiple of the design sampling period) diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfDoubleGamma.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfDoubleGamma.m old mode 100644 new mode 100755 index 5cc2d6370..1d68fbb2c --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfDoubleGamma.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfDoubleGamma.m @@ -6,7 +6,7 @@ % date: 14/06/07, 09/02/2010 % purpose: returns a canonical hrf that's a difference of two gamma distribution function % -function [params, hrf] = hrfDoubleGamma(params, sampleDuration, sampleDelay, defaultParams) +function [params, hrf] = hrfDoubleGamma(params, sampleDuration, sampleDelay, defaultParams, ~) threshold = 1e-3; %threshold for removing trailing zeros at the end of the model @@ -16,6 +16,18 @@ sampleDelay=sampleDuration/2; end +% this is to check for a bug whereby the ms to s conversion hasn't occurred +% correctly. The manifestation of this bug hasn't been extensively tested, +% but has been identified in multiple datasets involving TRs of 1s, or +% after manually altering the framePeriod using setFramePeriod.m +mytmp = ceil(log10(abs(sampleDuration))); +if mytmp >= 3 + disp('caught a sampleDuration bug, converting to seconds...') + sampleDuration = sampleDuration ./ 1000; + sampleDelay = sampleDelay ./ 1000; +end + + if ieNotDefined('params') params = struct; end diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfFslFlobs.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfFslFlobs.m index 8b5d11c82..3b06246d7 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfFslFlobs.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/hrfFslFlobs.m @@ -6,9 +6,9 @@ % date: 14/06/07, 09/02/2010 % purpose: reads a basis set from a flobs file % -function [params, hrf] = hrfFslFlobs(params, sampleDuration, sampleDelay, defaultParams) +function [params, hrf] = hrfFslFlobs(params, sampleDuration, sampleDelay, defaultParams, ~) -if ~any(nargin == [1 2 3 4])% 5]) +if ~any(nargin == [1 2 3 4 5]) help hrfDoubleGamma return end diff --git a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/makeContrastNames.m b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/makeContrastNames.m index 9b8c723f8..2f306d9e6 100644 --- a/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/makeContrastNames.m +++ b/mrLoadRet/Plugin/GLM_v2/newGlmAnalysis/makeContrastNames.m @@ -23,22 +23,23 @@ elseif nnz(contrasts(iContrast,:))==2 && sum(contrasts(iContrast,:))==0 %if the contrast is a comparison of 2 EVs EV1 = find(contrasts(iContrast,:),1,'first'); EV2 = find(contrasts(iContrast,:),1,'last'); - switch(tTestSide) - case 'Both' + switch(lower(tTestSide)) + case 'both' connector = ' VS '; - case 'Right' + case 'right' if contrasts(iContrast,EV1)>contrasts(iContrast,EV2) connector = ' > '; else connector = ' < '; end - - case 'Left' + case 'left' if contrasts(iContrast,EV1)>contrasts(iContrast,EV2) connector = ' < '; else connector = ' > '; end + case 'no test' + connector = ' - '; end contrastNames{iContrast} = [EVnames{EV1} connector EVnames{EV2}]; diff --git a/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/fillHolesInROI.m b/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/fillHolesInROI.m new file mode 100644 index 000000000..c0b0b8eeb --- /dev/null +++ b/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/fillHolesInROI.m @@ -0,0 +1,84 @@ +% fillHolesInROI.m +% +% usage: transformedRoi = fillHolesInROI(roi,) +% by: julien besle +% date: 06/04/2021 +% +% purpose: fills holes in ROI using a simple algorithm +% input: - connectivity: number of neighboring voxels each voxel can have in 2D or 3D (default = 6) +% 6: contiguous voxel faces in 3D +% 18: contiguous voxel faces and edges in 3D +% 26: contiguous voxel faces, edges and corners in 3D +% 4: contiguous faces in X-Y plane +% 8: contiguous faces and edges in X-Y plane + +function roi = fillHolesInROI(roi,connectivity) + +if ~ismember(nargin,[1 2]) + help fillHolesInROI; + return +end + +if ieNotDefined('connectivity') + connectivity = 6; +end +if ~ismember(connectivity,[4 6 8 18 26]) + mrWarnDlg(['(expandROI) unknown connectivity value ' connectivity]); + roi=[]; + return +end + +boxCoords = [min(roi.coords(1:3,:),[],2)-[1 1 1]' max(roi.coords(1:3,:),[],2)+[1 1 1]']; + +%shift coordinates so that the boxes starts at 1 on all dimensions +voxelShift = -boxCoords(:,1)+1; + +boxCoords = boxCoords+repmat(voxelShift,1,2); +roiCoords = roi.coords(1:3,:)+repmat(voxelShift,1,size(roi.coords,2)); + +volume = zeros(boxCoords(:,2)'); +volume(sub2ind(boxCoords(:,2)',roiCoords(1,:),roiCoords(2,:),roiCoords(3,:)))=1; + +% if trim +% volume = 1-volume; +% end + +switch(connectivity) + case 4 + kernel = zeros(3,3,3); + kernel(:,:,2) = [0 1 0;1 1 1;0 1 0]; + case 8 + kernel = zeros(3,3,3); + kernel(:,:,2) = ones(3,3,1); + case 6 + kernel(:,:,1) = [0 0 0;0 1 0;0 0 0]; + kernel(:,:,2) = [0 1 0;1 1 1;0 1 0]; + kernel(:,:,3) = [0 0 0;0 1 0;0 0 0]; + case 18 + kernel(:,:,1) = [0 1 0;1 1 1;0 1 0]; + kernel(:,:,2) = [1 1 1;1 1 1;1 1 1]; + kernel(:,:,3) = [0 1 0;1 1 1;0 1 0]; + case 26 + kernel = ones(3,3,3); + +end + +volume = logical(volume); +holesRemain = true; +while holesRemain + volume2 = logical(convn(volume,kernel,'same')); % fills voxels neighboring any filled voxel + volume2 = ~volume2; + volume2 = logical(convn(volume2,kernel,'same')); % empty voxels neighboring any empty voxel + volume2 = ~volume2; + + if isequal(volume,volume2) %until all holes have been filled + holesRemain = false; + else + volume = volume2; + end +end + +[newCoordsX,newCoordsY,newCoordsZ] = ind2sub(boxCoords(:,2)',find(volume)); +roi.coords = [newCoordsX-voxelShift(1) newCoordsY-voxelShift(2) newCoordsZ-voxelShift(3)]'; + + diff --git a/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m b/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m new file mode 100644 index 000000000..ad2b4ac05 --- /dev/null +++ b/mrLoadRet/Plugin/GLM_v2/transformROIFunctions/makeROIsExactlyContiguous.m @@ -0,0 +1,64 @@ +% makeROIsExactlyContiguous.m +% +% usage: transformedRois = makeROIsExactlyContiguous(rois) +% by: julien besle +% date: 11/01/2011 +% +% purpose: make two or more ROIs mutually exclusive +% +function rois = makeROIsExactlyContiguous(rois) + +if ~ismember(nargin,[1]) + help makeROIsExactlyContiguous; + return +end + +if numel(rois) < 2 + mrWarnDlg('(makeROIsExactlyContiguous) You need to provide at least 2 ROIs '); + return +end + +% Check that transformation matrices are identical for all ROIs +for iRoi = 2:length(rois) + if any(any( (rois(1).xform - rois(iRoi).xform) > 10e-6)) + mrWarnDlg('(makeROIsExactlyContiguous) All ROIs must be converted to the same space (set the roiSpace option to something other than ''Native'').'); + return + end +end + +for iRoi = 1:length(rois) + for jRoi = 1:length(rois) + if iRoi ~= jRoi + % first ensure that all voxel coordinates are rounded and unique + coords1 = unique(round(rois(iRoi).coords'),'rows'); + coords2 = unique(round(rois(jRoi).coords'),'rows'); + [commonCoordinates, indexROI1, indexROI2] = intersect(coords1,coords2,'rows'); % indexROI1, indexROI2 used to be used below + if ~isempty(commonCoordinates) + %remove common coordinates from ROIs 1 and 2 + coords1 = setdiff(coords1,commonCoordinates,'rows'); + coords2 = setdiff(coords2,commonCoordinates,'rows'); + %attribute common coordinates to one or the other ROI depending on distance + belongsToROI1 = false(size(commonCoordinates,1),1); + for iCoords = 1:size(commonCoordinates,1) + %compute distance between these coordinates and all coordinates unique to either both ROI + distanceCoords1 = sqrt(sum((repmat(commonCoordinates(iCoords,1:3),size(coords1,1),1) - coords1(:,1:3)).^2,2)); + distanceCoords2 = sqrt(sum((repmat(commonCoordinates(iCoords,1:3),size(coords2,1),1) - coords2(:,1:3)).^2,2)); + %identify closest ROI + if min(distanceCoords1) < min(distanceCoords2) + belongsToROI1(iCoords) = true; + end + end + % % delete coords that belong to the other ROI + % rois(iRoi).coords(:,indexROI1(~belongsToROI1'))=[]; + % rois(jRoi).coords(:,indexROI2(belongsToROI1'))=[]; + % instead of deleting common voxels, replace all voxels in each ROI by its unique voxels + % and the common voxels that have been attributed to it + % (replacing is necessary because coordinates might have been rounded and duplicates removed) + rois(iRoi).coords = [coords1; commonCoordinates(belongsToROI1,:)]'; + rois(jRoi).coords = [coords2; commonCoordinates(~belongsToROI1,:)]'; + end + end + end +end + + diff --git a/mrLoadRet/Plugin/GLM_v2/transformROIs.m b/mrLoadRet/Plugin/GLM_v2/transformROIs.m index d6e722f8c..d007337a3 100644 --- a/mrLoadRet/Plugin/GLM_v2/transformROIs.m +++ b/mrLoadRet/Plugin/GLM_v2/transformROIs.m @@ -1,70 +1,107 @@ -function transformROIs(thisView) +function [thisView,params] = transformROIs(thisView,params,varargin) % transformROIs(thisView) % -% transforms ROI(s) using pre-definde or custom functions +% transforms ROI(s) using pre-defined or custom functions % -% jb 11/01/2011 +% To just get a default parameter structure: +% v = newView; +% [v params] = transformROIs(v,[],'justGetParams=1'); +% [v params] = transformROIs(v,[],'justGetParams=1','defaultParams=1'); +% [v params] = transformROIs(v,[],'justGetParams=1','defaultParams=1','roiList=[1 2]'); % -% $Id: transformROIs.m 1982 2010-12-20 21:12:20Z julien $ - -%default params -%get names of combine Functions in transformFunctions directory -functionsDirectory = [fileparts(which('transformROIs')) '/transformROIFunctions/']; -transformFunctionFiles = dir([functionsDirectory '*.m']); -for iFile=1:length(transformFunctionFiles) - transformFunctions{iFile} = stripext(transformFunctionFiles(iFile).name); -end - -%get names of transform functions in additional folder(s) -roiTransformPaths = commaDelimitedToCell(mrGetPref('roiTransformPaths')); -for i = 1:length(roiTransformPaths) - roiTransformFiles = dir([roiTransformPaths{i} '/*.m']); - for iFile=1:length(roiTransformFiles) - transformFunctions{end+1} = stripext(roiTransformFiles(iFile).name); - end -end -transformFunctions = sort(transformFunctions); %re-order in alphabetical order +% To run: +% v = transformROIs(v,params) +% v = transformROIs(v,params,'noPrompt') % to overwrite the ROI(s) without asking +% +% jb 11/01/2011 -params.transformFunction = [{'User Defined'} transformFunctions]; -params.customTransformFunction = ''; +eval(evalargs(varargin)); +if ieNotDefined('justGetParams'),justGetParams = 0;end +if ieNotDefined('defaultParams'),defaultParams = 0;end +if ieNotDefined('noPrompt'),noPrompt = 0;end currentBaseString = ['Current Base (' viewGet(thisView,'basename') ')']; -roiSpaceMenu = {'Native','Current scan',currentBaseString}; -passRoiModeMenu = {'One ROI at a time','All ROIs at once'}; - -askForParams = 1; -while askForParams - params = {... - {'transformFunction',params.transformFunction,'type=popupmenu','name of the function to apply. This is a list of existing functions in the transformROIFunctions directory. To get help for a specific function, type ''help functionName''. To use another function, select ''User Defined'' and type the function name below'},... - {'customTransformFunction',params.customTransformFunction,'name of the function to apply. You can use any custom matlab function on the path that accepts an ROI structure as argument and output a new ROI structure.'},... - {'passRoiMode',passRoiModeMenu,'Sepcifies if the transform function accepts one or several ROIs as inputs'},... - {'roiSpace',roiSpaceMenu,'In which space should the coordinates be converted before being passed'},... - {'additionalArgs','','Additional arguments to the transform function. These arguments will be input at the end of each function call. They must be separated by commas. '},... - {'printHelp',0,'type=pushbutton','callback',@printHelp,'passParams=1','buttonString=Print transformFunction Help','Prints transformation function help in command window'},... - }; - params = mrParamsDialog(params, 'Choose an ROI transformation function'); - % Abort if params empty - if ieNotDefined('params'),return,end - - if strcmp(params.transformFunction,'User Defined') - params.transformFunction = params.customTransformFunction; + +if ieNotDefined('params') + %default params + %get names of combine Functions in transformFunctions directory + functionsDirectory = [fileparts(which('transformROIs')) '/transformROIFunctions/']; + transformFunctionFiles = dir([functionsDirectory '*.m']); + for iFile=1:length(transformFunctionFiles) + transformFunctions{iFile} = stripext(transformFunctionFiles(iFile).name); end - if 0 - %control here - %elseif - %other control here - else - askForParams = 0; - roiList = selectInList(thisView,'rois'); - if isempty(roiList) - askForParams = 1; + %get names of transform functions in additional folder(s) + roiTransformPaths = commaDelimitedToCell(mrGetPref('roiTransformPaths')); + for i = 1:length(roiTransformPaths) + roiTransformFiles = dir([roiTransformPaths{i} '/*.m']); + for iFile=1:length(roiTransformFiles) + transformFunctions{end+1} = stripext(roiTransformFiles(iFile).name); + end + end + transformFunctions = sort(transformFunctions); %re-order in alphabetical order + + params.transformFunction = [{'User Defined'} transformFunctions]; + params.customTransformFunction = ''; + params.roiNameSuffix = ''; + params.newRoiName = ''; + + roiSpaceMenu = {'Native','Current scan',currentBaseString}; + passRoiModeMenu = {'One ROI at a time','All ROIs at once'}; + + askForParams = 1; + while askForParams + params = {... + {'transformFunction',params.transformFunction,'type=popupmenu','name of the function to apply. This is a list of existing functions in the transformROIFunctions directory. To get help for a specific function, type ''help functionName''. To use another function, select ''User Defined'' and type the function name below'},... + {'customTransformFunction',params.customTransformFunction,'name of the function to apply. You can use any custom matlab function on the path that accepts an ROI structure as argument and output a new ROI structure.'},... + {'passRoiMode',passRoiModeMenu,'Specifies if the transform function accepts one or several ROIs as inputs'},... + {'newRoiName',params.newRoiName,'transformed ROI name (leave blank if ROI name should stay the same).'},... + {'roiNameSuffix',params.roiNameSuffix,'suffix that will be appended to the transformed ROI name (leave blank if not suffix should be appended).'},... + {'roiSpace',roiSpaceMenu,'In which space should the coordinates be converted before being passed'},... + {'additionalArgs','','Additional arguments to the transform function. These arguments will be input at the end of each function call. They must be separated by commas.'},... + {'printHelp',0,'type=pushbutton','callback',@printHelp,'passParams=1','buttonString=Print transformFunction Help','Prints transformation function help in command window'},... + }; + + % Initialize analysis parameters with default values + if defaultParams + params = mrParamsDefault(params); + else + params = mrParamsDialog(params, 'ROI transformation parameters'); + end + % Abort if params empty + if ieNotDefined('params'),return,end + + if strcmp(params.transformFunction,'User Defined') + params.transformFunction = params.customTransformFunction; + end + + if 0 + %control here + %elseif + %other control here + else + askForParams = 0; + if defaultParams + params.roiList = viewGet(thisView,'curROI'); + else + params.roiList = selectInList(thisView,'rois'); + if isempty(params.roiList) + askForParams = 1; + end + end end end end -switch(params.roiSpace) - case currentBaseString +if ~ieNotDefined('roiList') + params.roiList = roiList; +end + +% if just getting params then return +if justGetParams,return,end + +switch(lower(params.roiSpace)) + case {lower(currentBaseString),'current base'} baseNum = viewGet(thisView,'currentbase'); newXform = viewGet(thisView,'baseXform',baseNum); newSformCode = viewGet(thisView,'baseSformCode',baseNum); @@ -73,7 +110,7 @@ function transformROIs(thisView) newVoxelSize = viewGet(thisView,'baseVoxelSize',baseNum); whichVolume = 0; - case 'Current scan' + case 'current scan' newXform = viewGet(thisView,'scanXform'); newSformCode = viewGet(thisView,'scanSformCode'); newVol2mag = viewGet(thisView,'scanVol2mag'); @@ -85,7 +122,7 @@ function transformROIs(thisView) needToRefresh = 0; cRoi = 0; -for iRoi=roiList +for iRoi=params.roiList cRoi = cRoi+1; roi = viewGet(thisView,'roi',iRoi); if ~strcmp(params.roiSpace,'Native') @@ -111,8 +148,7 @@ function transformROIs(thisView) end %parse other additional inputs -additionalArgs = parseArguments(params.additionalArgs,','); -%construct function call +additionalArgs = parseArguments(params.additionalArgs,','); %construct function call functionString=''; for iArg = 1:length(additionalArgs) functionString = [functionString ',' additionalArgs{iArg}]; @@ -126,11 +162,20 @@ function transformROIs(thisView) try roi = eval([params.transformFunction '(rois{iCall}' functionString]); catch exception - mrWarnDlg(sprintf('(transformROI) There was an error evaluating function %s:\n%s',functionString,getReport(exception,'basic'))); + mrWarnDlg(sprintf('(transformROI) There was an error evaluating function %s:\n%s\n',functionString,getReport(exception))); return end for iRoi = 1:length(roi) - thisView = viewSet(thisView,'newROI',roi(iRoi)); + if ~fieldIsNotDefined(params,'newRoiName') + roi(iRoi).name = params.newRoiName; + if length(roi)>1 + roi(iRoi).name = [roi(iRoi).name '_' num2str(iRoi)]; + end + end + if ~fieldIsNotDefined(params,'roiNameSuffix') + roi(iRoi).name = [roi(iRoi).name params.roiNameSuffix]; + end + thisView = viewSet(thisView,'newROI',roi(iRoi),noPrompt); needToRefresh = 1; end end @@ -139,9 +184,9 @@ function transformROIs(thisView) end function [arguments, nArgs] = parseArguments(argumentString, separator) - -%parse string of arguments separated by separator and put them into a cell array of -% - strings if numerical + +%parse string of arguments separated by separator and put them into a cell array of +% - strings if numerical % - strings with double quotes for non-numerical values % so that it can be used with eval % Julien Besle, 08/07/2010 @@ -154,9 +199,9 @@ function transformROIs(thisView) if ~isempty(str2num(token)) arguments{nArgs} = token; else - arguments{nArgs} = ['''' token '''']; + arguments{nArgs} = ['''' token '''']; end -end +end function printHelp(params) diff --git a/mrLoadRet/Plugin/mlrAnatomy/mlrAnatomyPlugin.m b/mrLoadRet/Plugin/mlrAnatomy/mlrAnatomyPlugin.m index 3b8595ca2..b70cd7491 100644 --- a/mrLoadRet/Plugin/mlrAnatomy/mlrAnatomyPlugin.m +++ b/mrLoadRet/Plugin/mlrAnatomy/mlrAnatomyPlugin.m @@ -505,7 +505,35 @@ function mlrAnatomyImportFreesurferLabel(hObject,eventdata) % get view v = viewGet(getfield(guidata(hObject),'viewNum'),'view'); -disp(sprintf('(mlrAnatomyImportFreesurferLabel) Not yet implemented')); +%get file names +[labelFilenames,pathname] = uigetfile({'*.label','Freesurfer label files'; '*.*', 'All Files (*.*)'},'Select Freesurfer label file(s)', '*.label','multiselect','on'); +if isnumeric(labelFilenames) + return +elseif ischar(labelFilenames) + labelFilenames = {labelFilenames}; +end + +leftSurfaceNames = []; +rightSurfaceNames = []; +for iROI = 1:length(labelFilenames) + %create volume ROI from label file + % we assume that all labels correspond to the same surface to avoid asking for the surface paths for each ROI + [roi,leftSurfaceNames,rightSurfaceNames] = mlrImportFreesurferLabel(fullfile(pathname,labelFilenames{iROI}),... + 'leftSurfaceNames',leftSurfaceNames,'rightSurfaceNames',rightSurfaceNames); + if isempty(roi) + mrWarnDlg(sprintf('Could not import label file %s',labelFilenames{iROI})); + if isempty(leftSurfaceNames) && isempty(rightSurfaceNames) + return; + end + else % Add ROI to view + v = viewSet(v,'newROI',roi); + v = viewSet(v,'currentROI',viewGet(getMLRView,'nrois')); + end + +end + +refreshMLRDisplay(v); + return %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -989,7 +1017,7 @@ function mlrAnatomyFascicleIntersect(hObject,eventdata) b.fascicles.intersect(iIntersect).d = d; v = viewSet(v,'base',b,baseNum); -disppercent(-inf,sprintf('(mlrAnatomyPlugin) Converting %i fascicles',f.n)); +mlrDispPercent(-inf,sprintf('(mlrAnatomyPlugin) Converting %i fascicles',f.n)); for iFascicle = 1:f.n if d(iFascicle) > 1 % number of vertices and triangles @@ -1011,9 +1039,9 @@ function mlrAnatomyFascicleIntersect(hObject,eventdata) nRunningTotalVertices = nRunningTotalVertices + nVertices; nRunningTotalTris= nRunningTotalTris + nTris; end - disppercent(iFascicle/f.n); + mlrDispPercent(iFascicle/f.n); end -disppercent(inf); +mlrDispPercent(inf); % make right length b.coordMap.tris = b.coordMap.tris(1:nRunningTotalTris,:); @@ -1360,7 +1388,7 @@ function mlrAnatomyFascicleMinmax(hObject,eventdata) nRunningTotalVertices = 0; nRunningTotalTris = 0; -disppercent(-inf,sprintf('(mlrAnatomyPlugin) Converting %i fascicles',f.n)); +mlrDispPercent(-inf,sprintf('(mlrAnatomyPlugin) Converting %i fascicles',f.n)); for iFascicle = 1:f.n if dispList(iFascicle) @@ -1383,9 +1411,9 @@ function mlrAnatomyFascicleMinmax(hObject,eventdata) nRunningTotalVertices = nRunningTotalVertices + nVertices; nRunningTotalTris= nRunningTotalTris + nTris; end - disppercent(iFascicle/f.n); + mlrDispPercent(iFascicle/f.n); end -disppercent(inf); +mlrDispPercent(inf); % make right length b.coordMap.tris = b.coordMap.tris(1:nRunningTotalTris,:); diff --git a/mrLoadRet/Plugin/pRF/pRF.m b/mrLoadRet/Plugin/pRF/pRF.m old mode 100644 new mode 100755 index 42e9d91f7..974046536 --- a/mrLoadRet/Plugin/pRF/pRF.m +++ b/mrLoadRet/Plugin/pRF/pRF.m @@ -468,7 +468,7 @@ function pRFSaveForExport(v,params,fit,scanNum,x,y,z) % save the stim image stimFilename = fullfile(exportDir,'stim.nii.gz'); -if isfile(stimFilename) +if mlrIsFile(stimFilename) disp(sprintf('(pRF) Removing already existing %s',getLastDir(stimFilename))); system(sprintf('rm -f %s',stimFilename)); end @@ -481,7 +481,7 @@ function pRFSaveForExport(v,params,fit,scanNum,x,y,z) % and save it maskFilename = fullfile(exportDir,'mask.nii.gz'); -if isfile(maskFilename) +if mlrIsFile(maskFilename) disp(sprintf('(pRF) Removing already existing %s',getLastDir(maskFilename))); system(sprintf('rm -f %s',maskFilename)); end @@ -493,7 +493,7 @@ function pRFSaveForExport(v,params,fit,scanNum,x,y,z) % save data boldFilename = fullfile(exportDir,'bold.nii.gz'); -if isfile(boldFilename) +if mlrIsFile(boldFilename) disp(sprintf('(pRF) Removing already existing %s',getLastDir(boldFilename))); system(sprintf('rm -f %s',boldFilename)); end diff --git a/mrLoadRet/Plugin/pRF/pRFFit.m b/mrLoadRet/Plugin/pRF/pRFFit.m old mode 100644 new mode 100755 index e81a57df7..8bf6cdd3b --- a/mrLoadRet/Plugin/pRF/pRFFit.m +++ b/mrLoadRet/Plugin/pRF/pRFFit.m @@ -143,7 +143,7 @@ if ~isfield(fitParams.prefit,'modelResponse') % get number of workers nProcessors = mlrNumWorkers; - disppercent(-inf,sprintf('(pRFFit) Computing %i prefit model responses using %i processors',fitParams.prefit.n,nProcessors)); + mlrDispPercent(-inf,sprintf('(pRFFit) Computing %i prefit model responses using %i processors',fitParams.prefit.n,nProcessors)); % first convert the x/y and width parameters into sizes % on the actual screen fitParams.prefit.x = fitParams.prefit.x*fitParams.stimWidth; @@ -161,7 +161,7 @@ disp(sprintf('(pRFFit) Computing prefit model response %i/%i: Center [%6.2f,%6.2f] rfHalfWidth=%5.2f',i,fitParams.prefit.n,fitParams.prefit.x(i),fitParams.prefit.y(i),fitParams.prefit.rfHalfWidth(i))); end end - disppercent(inf); + mlrDispPercent(inf); fitParams.prefit.modelResponse = allModelResponse; clear allModelResponse; end diff --git a/mrLoadRet/Plugin/pRF/pRFGUI.m b/mrLoadRet/Plugin/pRF/pRFGUI.m old mode 100644 new mode 100755 diff --git a/mrLoadRet/Plugin/pRF/pRFGetStimImageFromStimfile.m b/mrLoadRet/Plugin/pRF/pRFGetStimImageFromStimfile.m old mode 100644 new mode 100755 index 47afd1b7b..4b2b13a6e --- a/mrLoadRet/Plugin/pRF/pRFGetStimImageFromStimfile.m +++ b/mrLoadRet/Plugin/pRF/pRFGetStimImageFromStimfile.m @@ -135,7 +135,7 @@ imageHeight = s.myscreen.imageHeight; [stim.x stim.y] = ndgrid(-imageWidth/2:imageWidth/(screenWidth-1):imageWidth/2,-imageHeight/2:imageHeight/(screenHeight-1):imageHeight/2); - if verbose,disppercent(-inf,'(pRFGetStimImageFromStimfile) Computing stimulus images');end + if verbose,mlrDispPercent(-inf,'(pRFGetStimImageFromStimfile) Computing stimulus images');end warnOnStimfileMissingInfo = true; for iImage = 1:length(stim.t) im = createMaskImage(s,stim.t(iImage)); @@ -160,9 +160,9 @@ im = zeros(screenWidth,screenHeight); end stim.im(1:screenWidth,1:screenHeight,iImage) = im; - if verbose,disppercent(iImage/length(stim.t));end + if verbose,mlrDispPercent(iImage/length(stim.t));end end - if verbose,disppercent(inf);end + if verbose,mlrDispPercent(inf);end % close screen mglSetParam('offscreenContext',0); diff --git a/mrLoadRet/Plugin/pRF/pRFMergeParams.m b/mrLoadRet/Plugin/pRF/pRFMergeParams.m old mode 100644 new mode 100755 diff --git a/mrLoadRet/Plugin/pRF/pRFPlot.m b/mrLoadRet/Plugin/pRF/pRFPlot.m old mode 100644 new mode 100755 diff --git a/mrLoadRet/Plugin/pRF/pRFPlugin.m b/mrLoadRet/Plugin/pRF/pRFPlugin.m old mode 100644 new mode 100755 diff --git a/mrLoadRet/Plugin/pRF_somato/pRFGetSomatoStimImageFromStimfile.m b/mrLoadRet/Plugin/pRF_somato/pRFGetSomatoStimImageFromStimfile.m new file mode 100755 index 000000000..6ab01d2ad --- /dev/null +++ b/mrLoadRet/Plugin/pRF_somato/pRFGetSomatoStimImageFromStimfile.m @@ -0,0 +1,280 @@ +% pRFGetSomatoStimImageFromStimfile +% +% $Id:$ +% usage: stim = pRFGetSomatoStimImageFromStimfile(stimfile,) +% +% by: ds - based mostly on code by justin gardner +% date: 2016/02 +% purpose: Pass in a stimfile (can be either a string filename, or a strucutre +% with myscreen/task) created with mgl / task code + +% implementation here is based on how information is obtained from +% mglRetinotopy stimfile. +% +% Will +% create a volume of dimensions x,y,t with the stimulus image (load +% in mlrVol to view). stim.x and stim.y are the X and Y coordinates +% (units??). stim.t is the array of times at which image is taken. +% +% Optionally arguments: +% +% timePoints: array for which the stim image should be computed. +% +% Note for developers - this function needs to keep up-to-date with +% any changes in the display loop of mglRetinotopy to interpret +% the stimfiles correctly +% +function stim = pRFGetSomatoStimImageFromStimfile(stimfile,varargin) + +% set default return arguments +stim = []; + +% check arguments +if nargin < 1 + help pRFGetSomatoStimImageFromStimfile + return +end + +% parse arguments +timePoints = [];screenWidth = [];screenHeight = [];volTrigRatio = []; +xFlip = [];yFlip = [];timeShift = [];verbose = []; +getArgs(varargin,{'timePoints=[]','screenWidth=[]','screenHeight=[]','volTrigRatio=[]','xFlip=0','yFlip=0','timeShift=0','verbose=1','saveStimImage=0','recomputeStimImage=0'}); + +% handle cell array +if iscell(stimfile) && ((length(stimfile)>1) || (length(stimfile{1})>1)) + for i = 1:length(stimfile) + % get current volTrigRatio + if isempty(volTrigRatio) + thisVolTrigRatio = []; + else + thisVolTrigRatio = volTrigRatio{i}; + end + stim{i} = pRFGetSomatoStimImageFromStimfile(stimfile{i},'timePoints',timePoints,'screenWidth',screenWidth,'screenHeight',screenHeight,'volTrigRatio',thisVolTrigRatio,'xFlip',xFlip,'yFlip',yFlip,'timeShift',timeShift,'verbose',verbose,'saveStimImage',saveStimImage,'recomputeStimImage',recomputeStimImage); + if isempty(stim{i}),stim = [];return;end + end + return +end + +% check volTrigRatio +if iscell(volTrigRatio) + if length(volTrigRatio) > 1 + disp(sprintf('(pRFGetSomatoStimImageFromStimfile) volTrigRatio should not be of length greater than one (length=%i) using only the first value of %i',length(volTrigRatio),volTrigRatio{1})); + end + volTrigRatio = volTrigRatio{1}; +end + +% get the stimfile +s = getStimfile(stimfile); +if isempty(s),return,end + +% check that we have a stimfile that is interpretable +% by this program +% THIS IS DONE TO CHECK THE RETINOTOPY CODE maps onto analysis... +% [tf s taskNum] = checkStimfile(s); +% if ~tf,return,end + +% check to see if a stimImage exists +% use s{1} here - [ma] +if ~isfield(s,'pRFStimImage') + % somato stim image needs to be obtaine from task variables... + % for now this is done in separate step. + disp('your stim files need to contain pRFStimImage struct') + keyboard +else + % stim image was stored, just reclaim it + disp(sprintf('(pRFGetSomatoStimImageFromStimfile) Loaded stim image from stimfile.')); + stim = s.pRFStimImage; +end + +if timeShift + disp(sprintf('(pRFGetSomatoStimImageFromStimfile) Time shifting stimulus image by %i',timeShift)); + stim.im = circshift(stim.im,[0 0 timeShift]); +end + +%%%%%%%%%%%%%%%%%%%%% +% getStimfile % +%%%%%%%%%%%%%%%%%%%%% +function s = getStimfile(stimfile) + +s = []; + +% deal with a cell array of stimfiles (like in an average) +if iscell(stimfile) + for i = 1:length(stimfile) + s{i} = getStimfile(stimfile{i}); + if isempty(s{i}),return;end + end + return +end + +% load stimfile +if isstr(stimfile) + stimfile = setext(stimfile,'mat'); + if ~mlrIsFile(stimfile) + disp(sprintf('(pRFGetSomatoStimImageFromStimfile) Could not open stimfile: %s',stimfile)); + return + end + s = load(stimfile); +elseif isstruct(stimfile) + % see if this is a myscreen + if isfield(stimfile,'imageWidth') + % check for task field + if isfield(stimfile,'task') + s.task = stimfile.task; + stimfile = rmfield(stimfile,'task'); + end + % check for stimulus field + if isfield(stimfile,'stimulus') + s.stimulus = stimfile.stimulus; + stimfile = rmfield(stimfile,'stimulus'); + end + % set myscreen field + s.myscreen = stimfile; + % else a variable with myscreen, task and stimulus or pRFStimImage + elseif isfield(stimfile,'myscreen') || isfield(stimfile,'pRFStimImage') + % copy fields over + if isfield(stimfile,'myscreen') + s.myscreen = stimfile.myscreen; + end + if isfield(stimfile,'task') + s.task = stimfile.task; + end + if isfield(stimfile,'stimulus') + s.stimulus = stimfile.stimulus; + end + if isfield(stimfile,'pRFStimImage') + s.pRFStimImage = stimfile.pRFStimImage; + end + end +end + +% if you have a pRFStimImage then don't bother with the rest of the fields +if ~isfield(s,'pRFStimImage') + % check fields + checkFields = {'myscreen','task','stimulus'}; + for i = 1:length(checkFields) + if ~isfield(s,checkFields{i}) + stimfileName = ''; + if isfield(s,'myscreen') && isfield(s.myscreen,'stimfile') + stimfileName = getLastDir(s.myscreen.stimfile); + end + disp(sprintf('(pRFGetSomatoStimImageFromStimfile) !!! Missing variable: %s in stimfile %s !!!',checkFields{i},stimfileName)); + s = []; + return + end + end +end + +%%%%%%%%%%%%%%%%%%%%%%% +% checkStimfile % +%%%%%%%%%%%%%%%%%%%%%%% +function [tf s taskNum] = checkStimfile(s) + +tf = true; +s = cellArray(s); +taskNum = []; + +stimulusType = []; +barAngle = []; +direction = []; + +for i = 1:length(s) + thiss = s{i}; + if isempty(thiss) + disp(sprintf('(pRFGetsomatoStimImageFromStimfile) Missing stimfile')); + tf = false; + return + end + % if this has a pRFStimImage then we are ok + if isfield(thiss,'pRFStimImage') + continue; + end + dispstr = sprintf('%s: vols=%i',thiss.myscreen.stimfile,thiss.myscreen.volnum); + % first check if this is a retinotpy stimfile - it should + % have a task which is mglRetinotopy + taskNum = []; + for iTask = 1:2 + if (length(thiss.task) >= iTask) && (isequal(thiss.task{iTask}{1}.taskFilename,'mglRetinotopy.m') || isequal(thiss.task{iTask}{1}.taskFilename,'gruRetinotopy.m')) + taskNum = iTask; + end + end + if isempty(taskNum) + disp(sprintf('(pRFGetSomatoStimImageFromStimfile:checkStimfile) Stimfile: %s',dispstr)); + disp(sprintf('(pRFGetSomatoStimImageFromStimfile:checkStimfile) The stimfile does not appear to have been created by mglRetinotopy')); + tf = false; + return + end + + % check for proper saved fields + missing = ''; + if ~isfield(thiss.task{taskNum}{1},'randVars') missing = 'randVars';end + if ~isfield(thiss.task{taskNum}{1},'parameter') missing = 'parameter';end + if ~any(strcmp('maskPhase',thiss.myscreen.traceNames)) missing = 'maskPhase';end + if ~any(strcmp('blank',thiss.myscreen.traceNames)) missing = 'blank';end + if ~isempty(missing) + disp(sprintf('(pRFGetSomatoStimImageFromStimfile:checkStimfile) Stimfile: %s',dispstr)); + disp(sprintf('(pRFGetSomatoStimImageFromStimfile:checkStimfile) The stimfile does not appear to have been created by the latest version of mglRetinotopy which contains the field %s necessary for reconstructing the stimulus. Consider running a dummy run with a newer version of mglRetinotpy with the same parameters (see mglSimulateRun to simulate backticks) and then use that stimfile instead of this one.',missing)); + tf = false; + return + end + + % check for necessary variables + e = getTaskParameters(thiss.myscreen,thiss.task{taskNum}{1}); + + % now check for each variable that we need + varnames = {'blank'}; + for i = 1:length(varnames) + varval = getVarFromParameters(varnames{i},e); + if isempty(varval) + disp(sprintf('(pRFGetSomatoStimImageFromStimfile:checkStimfile) Stimfile: %s',dispstr)); + disp(sprintf('(pRFGetSomatoStimImageFromStimfile:checkStimfile) The stimfile does not appear to have been created by the latest version of mglRetinotopy which contains the variable %s necessary for reconstructing the stimulus. Consider running a dummy run with a newer version of mglRetinotpy with the same parameters (see mglSimulateRun to simulate backticks) and then use that stimfile instead of this one',varnames{i})); + tf = false; + return + end + end + + % check for matching stimfiles + if ~isempty(stimulusType) && (stimulusType ~= thiss.stimulusType) + disp(sprintf('(pRFGetSomatoStimImageFromStimfile:checkStimfile) !!! Stimfile %s does not match previous one !!! Have you averaged together scans with different stimulus conditions?')); + end + if any(thiss.stimulus.stimulusType == [3 4]) + varval = getVarFromParameters('barAngle',e); + if ~isempty(barAngle) && ~isequal(varval,barAngle) + disp(sprintf('(pRFGetSomatoStimImageFromStimfile:checkStimfile) !!! Stimfile %s does not match previous one !!! The barAngles are different! Have you averaged together scans with different stimulus conditions?')); + end + barAngle = varval; + else + if ~isempty(direction) && (thiss.stimulus.direction ~= direction) + disp(sprintf('(pRFGetSomatoStimImageFromStimfile:checkStimfile) !!! Stimfile %s does not match previous one !!! The directions are different! Have you averaged together scans with different stimulus conditions?')); + end + direction = thiss.stimulus.direction; + end +end + +s = s{end}; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% saveStimImageToStimfile % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function saveStimImageToStimfile(stim,stimfile) + +% make sure stimfile is a cell array +stimfile = cellArray(stimfile); + +% first reload the stimfile +for iStimfile = 1:length(stimfile) + if isfield(stimfile{iStimfile},'filename') + s = load(stimfile{iStimfile}.filename); + if isempty(s) + disp(sprintf('(pRFGetSomatoStimImageFromStimfile:saveStimImageToStimfile) Could not load stimfile %s. Unable to save stim image back to stimfile',stimfile{iStimfile}.filename)); + else + % append the stim image and save back + s.pRFStimImage = stim; + save(stimfile{iStimfile}.filename,'-struct','s'); + disp(sprintf('(pRFGetSomatoStimImageFromStimfile:saveStimImageToStimfile) Saved stimImage to %s.',stimfile{iStimfile}.filename)); + end + else + disp(sprintf('(pRFGetSomatoStimImageFromStimfile:saveStimImageToStimfile) Missing filename in stimfile structure, could not save stimImage back to stimfile')); + end +end + diff --git a/mrLoadRet/Plugin/pRF_somato/pRFMergeParams.m b/mrLoadRet/Plugin/pRF_somato/pRFMergeParams.m new file mode 100755 index 000000000..95d9154ed --- /dev/null +++ b/mrLoadRet/Plugin/pRF_somato/pRFMergeParams.m @@ -0,0 +1,111 @@ +% defaultMergeParams.m +% +% usage: defaultMergeParams(groupName,oldParams,newParams,oldData,newData) +% by: justin gardner +% date: 05/21/07 +% purpose: default function merge parameters. called by +% saveAnalysis and saveOverlay +% +function [mergedParams mergedData] = pRFMergeParams(groupName,oldParams,newParams,oldData,newData) + +% check arguments +if ~any(nargin == [3 5]) + help defaultMergeParams + return +end + +% default arguments +if ieNotDefined('oldData') + oldData = []; +end +if ieNotDefined('newData') + newData = []; +end + +mergedParams = newParams; +mergedData = newData; + +% deal with cell array of parameters +if iscell(newParams) + for i = 1:length(oldParams) + if isempty(newParams{i}) && ~isempty(oldParams{i}) + mergedParams{i} = oldParams{i}; + if length(oldData) >= i + mergedData{i} = oldData{i}; + end + end + end + % merge any overalys - allowing nan points to be + % overwritten by whoever has data (this allows an + % old overlay in which partial information was + % calculated to be merged into new overlay data + for iData = 1:length(mergedData) + % see if we have both old and new data + if (length(newData) >= iData) && (length(oldData) >= iData) && ~isempty(newData{iData}) && ~isempty(oldData{iData}) && isnumeric(newData{iData}) && isnumeric(oldData{iData}) + % oldData has points not in merged data + oldDataNotInMerged = find(~isnan(oldData{iData}) & isnan(mergedData{iData})); + mergedData{iData}(oldDataNotInMerged) = oldData{iData}(oldDataNotInMerged); + % newData has points not in merged data + newDataNotInMerged = find(~isnan(newData{iData}) & isnan(mergedData{iData})); + mergedData{iData}(newDataNotInMerged) = newData{iData}(newDataNotInMerged); + end + end + return +end + +% get scan numbers +if isfield(oldParams,'scanList') && isfield(newParams,'scanList') + scanListName = 'scanList'; +elseif isfield(oldParams,'scanNum') && isfield(newParams,'scanNum') + scanListName = 'scanNum'; +else + scanListName = ''; +end + +% get list of fields to copy +scanFields = {}; +if isfield(oldParams,'scanParams') && isfield(newParams,'scanParams') + scanFields{end+1} = 'scanParams'; +end + +if ~isempty(scanListName) + % go through the scan list of the old params + for i = 1:length(oldParams.(scanListName)) + % get this scan number + thisScanNum = oldParams.(scanListName)(i); + % check to see if it exist in the newParams + if isempty(find(mergedParams.(scanListName) == thisScanNum)) + % add the scan number to the merged params if it doesn't + mergedParams.(scanListName)(end+1) = thisScanNum; + % and tseies filename + mergedParams.tseriesFile{end+1} = oldParams.tseriesFile{i}; + % add the fields that need to be copied + for iFields = 1:length(scanFields) + mergedParams.(scanFields{iFields})(thisScanNum) = ... + oldParams.(scanFields{iFields})(thisScanNum); + end + % and copy the data + if ~isempty(newData) + if length(oldData)>=thisScanNum + mergedData{thisScanNum} = oldData{thisScanNum}; + else + mergedData{thisScanNum} = []; + end + end + else + % does exist, so merge the two, get which points are missing + [dump missingPoints] = setdiff(oldData{thisScanNum}.linearCoords,newData{thisScanNum}.linearCoords); + % grab old and new linear coords and make sure that they are both row + % vectors + oldLinearCoords = oldData{thisScanNum}.linearCoords(missingPoints); + oldLinearCoords = oldLinearCoords(:)'; + newLinearCoords = newData{thisScanNum}.linearCoords; + newLinearCoords = newLinearCoords(:)'; + % and combine + mergedData{thisScanNum}.linearCoords = [newLinearCoords oldLinearCoords]; + mergedData{thisScanNum}.params = [newData{thisScanNum}.params oldData{thisScanNum}.params(:,missingPoints)]; + mergedData{thisScanNum}.r = [newData{thisScanNum}.r' oldData{thisScanNum}.r(missingPoints,:)']'; + end + end +end + diff --git a/mrLoadRet/Plugin/pRF_somato/pRF_somato.m b/mrLoadRet/Plugin/pRF_somato/pRF_somato.m new file mode 100755 index 000000000..76dfc7bc6 --- /dev/null +++ b/mrLoadRet/Plugin/pRF_somato/pRF_somato.m @@ -0,0 +1,691 @@ +% pRF_somato.m +% +% +% usage: pRF_somato(v,params,varargin) +% by: only slightly modified from pRF.m by justin gardner +% date: +% purpose: compute pRF analysis on MLR data +% +% if you just want a default parameter structure you +% can do: +% +% v = newView; +% [v params] = pRF_somato(v,[],'justGetParams=1','defaultParams=1','scanList=1') +% +% Note that justGetParams,defualtParams and scanList are independent parameters, so +% if you want, say to bring up the GUI to set the params, but not run the analysis, you +% can do: +% [v params] = pRF_somato(v,[],'justGetParams=1'); +% +function [v d] = pRF_somato(v,params,varargin) + +% check arguments +if nargin < 1 + help pRF_somato + return +end + +d = []; +% a version number in case we make major changes +pRFVersion = 1; + +% params defaults to empty +if nargin < 2,params =[];end + +% other arguments +justGetParams=[];defaultParams=[];scanList=[]; +groupNum=[]; +getArgs(varargin,{'justGetParams=0','defaultParams=0','scanList=[]','groupNum=[]', 'crossVal=[]'}); + +% first get parameters +if isempty(params) + % get group + if isempty(groupNum),groupNum = viewGet(v,'curGroup');end + % put up the gui + params = pRF_somatoGUI('v',v,'groupNum',groupNum,'defaultParams',defaultParams,'scanList',scanList); +end + +% just return parameters +if justGetParams,d = params;return,end + +% Reconcile params with current status of group and ensure that it has +% the required fields. +params = defaultReconcileParams([],params); + +% Abort if params empty +if isempty(params),return,end + +% check the params +%params = checkPRFparams(params); + +% set the group +v = viewSet(v,'curGroup',params.groupName); + +% create the parameters for the r2 overlay + +% mod = 'somato'; +% overlayNames = getMetaData(v,params,mod,'overlayNames'); +% theOverlays = getMetaData(v,params,mod,'theOverlays'); + + +dateString = datestr(now); +r2.name = 'r2'; +r2.groupName = params.groupName; +r2.function = 'pRF_somato'; +r2.reconcileFunction = 'defaultReconcileParams'; +r2.data = cell(1,viewGet(v,'nScans')); +r2.date = dateString; +r2.params = cell(1,viewGet(v,'nScans')); +r2.range = [0 1]; +r2.clip = [0 1]; +%colormap is made with a little bit less on the dark end +r2.colormap = hot(312); +r2.colormap = r2.colormap(end-255:end,:); +r2.alpha = 1; +r2.colormapType = 'normal'; +r2.interrogator = 'myOverlayStats'; +r2.mergeFunction = 'pRFMergeParams'; + +% at this point we need to decide on which parameters we want to estimate +% from data + +% for pRF_somato e.g. +% prefDigit (1, 2, 3) +% rfHalfWidth...? +% etc. +% +% create the parameters for the prefDigit overlay +prefDigit = r2; +prefDigit.name = 'prefDigit'; + +prefDigit.range = [1 5]; +prefDigit.clip = [1 5]; +prefDigit.colormapType = 'normal'; +%prefDigit.colormap = rainbow_colors(4); %This colour map and 1-3 range look much better when delineating 3 fingers. Change for more fingers! +prefDigit.colormap = digits(256); + +prefPD = r2; +prefPD.name = 'prefPD'; +prefPD.range = [1 5]; +prefPD.clip = [1 5]; +prefPD.colormapType = 'normal'; +%prefPD.colormap = rainbow_colors(4); +prefPD.colormap = digits(256); + + +% create the paramteres for the rfHalfWidth overlay +% deal with the sigma. + +if strcmpi(params.pRFFit.rfType,'gaussian-hdr-double') + rfHalfWidthX = r2; + rfHalfWidthX.name = 'rfHalfWidthX'; + rfHalfWidthX.range = [0 5]; + rfHalfWidthX.clip = [0 5]; + rfHalfWidthX.colormapType = 'normal'; + rfHalfWidthX.colormap = pink(256); + + rfHalfWidthY = r2; + rfHalfWidthY.name = 'rfHalfWidthY'; + rfHalfWidthY.range = [0 5]; + rfHalfWidthY.clip = [0 5]; + rfHalfWidthY.colormapType = 'normal'; + rfHalfWidthY.colormap = pink(256); +else + rfHalfWidth = r2; + rfHalfWidth.name = 'rfHalfWidth'; + rfHalfWidth.range = [0 5]; + rfHalfWidth.clip = [0 5]; + rfHalfWidth.colormapType = 'normal'; + rfHalfWidth.colormap = pink(256); + +end + + + + +% % consider creating other parameters for the somatosensory +% % Maybe get the haemodynamic delay? + +% hrfDelay = r2; +% hrfDelay.name = 'hrfDelay'; +% hrfDelay.range = [0 5]; +% hrfDelay.clip = [0 inf]; +% hrfDelay.colormapType = 'normal'; +% hrfDelay.colormap = hot(256); + +% % make space to keep rawParams in d +%rawParametersFromFit = cell(1,viewGet(v,'nScans')); + + +% get number of workers +nProcessors = mlrNumWorkers; + +% code snippet for clearing precomputed prefit +%global gpRFFitStimImage;gpRFFitStimImage = []; + +dispHeader +disp(sprintf('(pRF_somato) Running on scans %s:%s (restrict %s)',params.groupName,num2str(params.scanNum,'%i '),params.restrict )); + +for scanNum = params.scanNum + % see how long it took + tic; + + % get voxels that we are restricted to + [x y z] = getVoxelRestriction(v,params,scanNum); + if isempty(x) + disp(sprintf('(pRF_somato) No voxels to analyze with current restriction')); + return + end + + % get total number of voxels + n = length(x); + + % get scan dims + scanDims = viewGet(v,'scanDims',scanNum); + + % init overlays + r2.data{scanNum} = nan(scanDims); + prefDigit.data{scanNum} = nan(scanDims); + prefPD.data{scanNum} = nan(scanDims); + if strcmpi(params.pRFFit.rfType,'gaussian-hdr-double') + rfHalfWidthX.data{scanNum} = nan(scanDims); + rfHalfWidthY.data{scanNum} = nan(scanDims); + else + rfHalfWidth.data{scanNum} = nan(scanDims); + end + +% for iOverlay = 1:numel(overlayNames) +% % +% theOverlays{iOverlay}.data{scanNum} = nan(scanDims); +% end + + + % default all variables that will be returned + % by pRFFIt, so that we can call it the + % second time and save some time + concatInfo = []; + stim = []; + + % save pRF parameters + pRFAnal.d{scanNum}.ver = pRFVersion; + pRFAnal.d{scanNum}.linearCoords = []; + pRFAnal.d{scanNum}.params = []; + + % get some information from pRFFit that will be used again in + % the fits, including concatInfo, stim, prefit, etc. + fit = pRF_somatoFit(v,scanNum,[],[],[],'fitTypeParams',params.pRFFit,'returnPrefit',true); + if isempty(fit),return,end + + % here we now how many fit params there will be, so make space. + + + stim = fit.stim; + pRFAnal.d{scanNum}.stim = cellArray(stim); + pRFAnal.d{scanNum}.stimX = fit.stimX; + pRFAnal.d{scanNum}.stimY = fit.stimY; + pRFAnal.d{scanNum}.stimT = fit.stimT; + concatInfo = fit.concatInfo; + pRFAnal.d{scanNum}.concatInfo = fit.concatInfo; + prefit = fit.prefit; + paramsInfo = fit.paramsInfo; + pRFAnal.d{scanNum}.paramsInfo = paramsInfo; + % grab all these fields and stick them onto a structure called paramsInfo + % preallocate some space + % fudge this for now + tf = strcmpi('gaussian-tips', params.pRFFit.rfType); + if tf == 1 + rawParams = nan(fit.nParams+1,n); + else + rawParams = nan(fit.nParams,n); + end + %thisWeights = nan(numel(fit.stimX),n); + rawParams = nan(fit.nParams,n); + thisRawParamsCoords = nan(3,n); + r = nan(n,fit.concatInfo.n); + thisr2 = nan(1,n); + + if strcmpi(params.pRFFit.rfType,'gaussian-hdr-double') + thisRfHalfWidthX = nan(1,n); + thisRfHalfWidthY = nan(1,n); + + else + thisRfHalfWidth = nan(1,n); + end + + thisResid = nan(numel(concatInfo.whichScan),n); % this may break if using Nelder-Mead + thistSeries = nan(numel(concatInfo.whichScan),n); + thismodelResponse = nan(numel(concatInfo.whichScan),n); + + %thisData = nan(numel(overlayNames), n); + + % get some info about the scan to pass in (which prevents + % pRFFit from calling viewGet - which is problematic for distributed computing + framePeriod = viewGet(v,'framePeriod'); + junkFrames = viewGet(v,'junkFrames',scanNum); + + % compute pRF for each voxel in the restriction + if params.pRFFit.prefitOnly,algorithm='prefit-only';else algorithm=params.pRFFit.algorithm;end + + % disp info about fitting + dispHeader; + disp(sprintf('(pRF_somato) Scan %s:%i (restrict %s) running on %i processor(s)',params.groupName,scanNum,params.restrict,nProcessors)); + disp(sprintf('(pRF_somato) Computing %s fits using %s for %i voxels',params.pRFFit.rfType,algorithm,n)); + dispHeader; + + % this is a bit arbitrary but is the number of voxels to read in at a time. + % should probably be either calculated based on memory demands or a + % user settings. The bigger the number the less overhead and will run faster + % but consume more memory. The overhead is not terribly significant though + % as tested on my machine - maybe a few percent faster with full n, but + % on many machines without enough memory that will crash it so keeping + % this preliminary value in for now. + blockSize = n; + tic; + % break into blocks of voxels to go easy on memory + % if blockSize = n then this just does on block at a time. + for blockStart = 1:blockSize:n + + % display information about what we are doing + % get blockEnd + blockEnd = min(blockStart + blockSize-1,n); + blockSize = blockEnd-blockStart+1; + + % load ROI + loadROI = makeEmptyROI(v,'scanNum',scanNum,'groupNum',params.groupName); + loadROI.coords(1,1:blockSize) = x(blockStart:blockEnd); + loadROI.coords(2,1:blockSize) = y(blockStart:blockEnd); + loadROI.coords(3,1:blockSize) = z(blockStart:blockEnd); + % load all time series for block, we do this to pass into pRFFit. Generally + % the purpose here is that if we run on distributed computing, we + % can't load each voxel's time series one at a time. If this is + % too large for memory then you can comment this out and not + % pass it into pRFFit and pRFFit will load the tSeries itself + loadROI = loadROITSeries(v,loadROI,scanNum,params.groupName); + % reorder x,y,z coordinates since they can get scrambled in loadROITSeries + + blockEnd = size(loadROI.scanCoords,2); % HACK TO STOP NANS + blockSize = blockEnd; + n = blockEnd; + + x(blockStart:blockEnd) = loadROI.scanCoords(1,1:blockSize); + y(blockStart:blockEnd) = loadROI.scanCoords(2,1:blockSize); + z(blockStart:blockEnd) = loadROI.scanCoords(3,1:blockSize); + % keep the linear coords + pRFAnal.d{scanNum}.linearCoords = [pRFAnal.d{scanNum}.linearCoords sub2ind(scanDims,x(blockStart:blockEnd),y(blockStart:blockEnd),z(blockStart:blockEnd))]; + + if blockStart ~= 1 + % display time update + dispHeader(sprintf('(pRF_somato) %0.1f%% done in %s (Estimated time remaining: %s)',100*blockStart/n,mlrDispElapsedTime(toc),mlrDispElapsedTime((toc*n/blockStart) - toc))); + end + + + %import hrfprf code from pRF.m + if params.pRFFit.HRFpRF == 1 + disp('Give me your HRFs. Remember, these should be outputted from prfhrfRefit and then ideally from deconvRealDataWiener') + myfilename_hrf = uigetfile; + thehrfs = load(myfilename_hrf); + + myVar = thehrfs.hrf_struct.yf; + + end + + + if params.pRFFit.HRFpRF == 1 + + %sliceFix = 128.*128.*12; + %thehrfs.hrf_struct.volumeIndices = thehrfs.hrf_struct.volumeIndices + sliceFix; + + for ii = blockStart:blockEnd + myVoxel = find(thehrfs.hrf_struct.volumeIndices == sub2ind(scanDims,x(ii),y(ii),z(ii))); + if isempty(myVoxel) + fprintf('\ncaught an empty, x %d y %d z %d, idx %f\n', x(ii), y(ii), z(ii), myVoxel); + + fit = []; + elseif myVoxel > length(thehrfs.hrf_struct.yf) + disp('caught one') + fit = []; + else + + fit = pRF_somatoFit(v,scanNum,x(ii),y(ii),z(ii),'stim',stim,'concatInfo',concatInfo,... + 'prefit',prefit,'fitTypeParams',params.pRFFit,'dispIndex',ii,'dispN',n,... + 'tSeries',loadROI.tSeries(ii-blockStart+1,:)','framePeriod',framePeriod,'junkFrames',junkFrames,... + 'paramsInfo',paramsInfo, 'hrfprf', myVar(:,myVoxel)); + end + if ~isempty(fit) + thisr2(ii) = fit.r2; + thisPrefDigit(ii) = fit.prefDigit; + thisPrefPD(ii) = fit.prefPD; + thisRfHalfWidth(ii) = fit.std; + thisResid(:,ii) = fit.residual; + thistSeries(:,ii) = fit.tSeries; + thismodelResponse(:,ii) = fit.modelResponse; + +% +% tempVar = zeros(length(overlayNames),1); +% for iOverlay = 1:numel(overlayNames) +% +% test = strcmpi(fieldnames(fit), overlayNames(iOverlay) ); +% %pos = find(test==1); +% bla = struct2cell(fit); +% val = cell2mat(bla(test==1)); +% % this is temporary, gets overwritten each time +% tempVar(iOverlay,1) = val; +% end +% % now put the values for this voxel into some sort of order :) +% thisData(:,ii) = tempVar; + + % keep parameters + rawParams(:,ii) = fit.params(:); + r(ii,:) = fit.r; + %thisr2(ii) = fit.r2; + thisRawParamsCoords(:,ii) = [x(ii) y(ii) z(ii)]; + %myrawHrfs(:,ii) = fit.myhrf.hrf; %save out prfs hrfs + end + end + + else + + parfor ii = blockStart:blockEnd + + fit = pRF_somatoFit(v,scanNum,x(ii),y(ii),z(ii),'stim',stim,'concatInfo',concatInfo, ... + 'prefit',prefit,'fitTypeParams',params.pRFFit,'dispIndex',ii,'dispN',n,... + 'tSeries',loadROI.tSeries(ii-blockStart+1,:)','framePeriod',framePeriod,... + 'junkFrames',junkFrames,'paramsInfo',paramsInfo); + %fit = pRF_somatoFit(v,scanNum,x(ii),y(ii),z(ii),'stim',stim,'concatInfo',concatInfo,'prefit',prefit,'fitTypeParams',params.pRFFit,'dispIndex',ii,'dispN',n,'tSeries',loadROI.tSeries(ii-blockStart+1,:)','framePeriod',framePeriod,'junkFrames',junkFrames,'paramsInfo',paramsInfo, 'crossVal', myVar(:,ii)); + + + + if ~isempty(fit) + % tempVar = zeros(length(overlayNames),1); + % for iOverlay = 1:numel(overlayNames) + % + % test = strcmpi(fieldnames(fit), overlayNames(iOverlay) ); + % %pos = find(test==1); + % bla = struct2cell(fit); + % val = cell2mat(bla(test==1)); + % % this is temporary, gets overwritten each time + % tempVar(iOverlay,1) = val; + % end + % % now put the values for this voxel into some sort of order :) + % thisData(:,ii) = tempVar; + + thisr2(ii) = fit.r2; + thisPrefDigit(ii) = fit.prefDigit; + thisPrefPD(ii) = fit.prefPD; + if strcmpi(params.pRFFit.rfType,'gaussian-hdr-double') + thisRfHalfWidthX(ii) = fit.rfHalfWidthX; + thisRfHalfWidthY(ii) = fit.rfHalfWidthY; + else + thisRfHalfWidth(ii) = fit.rfHalfWidth; + end + + + % keep parameters + rawParams(:,ii) = fit.params(:); + r(ii,:) = fit.r; + %thisr2(ii) = fit.r2; + thisRawParamsCoords(:,ii) = [x(ii) y(ii) z(ii)]; + if ~strcmpi(algorithm,'nelder-mead') + thisResid(:,ii) = fit.residual; + end + thistSeries(:,ii) = fit.tSeries; + thismodelResponse(:,ii) = fit.modelResponse; + %myrawHrfs(:,ii) = fit.myhrf.hrf; %save out prfs hrfs + end + + end + + end + %% debugging, show rf model each time +% if strcmpi('gaussian', fit.rfType) +% tt = exp(-(((pRFAnal.d{2}.stimX-fit.x).^2)/(2*(fit.std^2))+((pRFAnal.d{2}.stimY-fit.y).^2)/(2*(fit.std^2)))); +% if ii == 1 +% figure +% plot(pRFAnal.d{2}.stimX, tt) +% elseif ii > 1 +% hold on +% plot(pRFAnal.d{2}.stimX, tt) +% end +% +% elseif strcmpi('gaussian-tips', fit.rfType) +% X = pRFAnal.d{2}.stimX; +% pone = [fit.amp fit.meanOne fit.std 0]; +% Z = gauss(pone,X); +% if ii == 1 +% figure +% plot(X,Z) +% elseif ii > 1 +% hold on +% plot(X,Z) +% end +% +% +% end + %% + % set overlays and info for d + for ii = 1:n + r2.data{scanNum}(x(ii),y(ii),z(ii)) = thisr2(ii); + prefDigit.data{scanNum}(x(ii),y(ii),z(ii)) = thisPrefDigit(ii); + prefPD.data{scanNum}(x(ii),y(ii),z(ii)) = thisPrefPD(ii); + if strcmpi(params.pRFFit.rfType,'gaussian-hdr-double') + rfHalfWidthX.data{scanNum}(x(ii),y(ii),z(ii)) = thisRfHalfWidthX(ii); + rfHalfWidthY.data{scanNum}(x(ii),y(ii),z(ii)) = thisRfHalfWidthY(ii); + else + rfHalfWidth.data{scanNum}(x(ii),y(ii),z(ii)) = thisRfHalfWidth(ii); + end + +% for iOverlay = 1:length(overlayNames) +% theOverlays{iOverlay}.data{scanNum}(x(ii),y(ii),z(ii)) = thisData(iOverlay,ii); +% end + end + end + % display time update + dispHeader; + disp(sprintf('(pRF_somato) Fitting %i voxels took %s.',n,mlrDispElapsedTime(toc))); + dispHeader; + + + pRFAnal.d{scanNum}.time = toc; %speed testing + + pRFAnal.d{scanNum}.params = rawParams; + pRFAnal.d{scanNum}.r = r; + pRFAnal.d{scanNum}.r2 = thisr2; + pRFAnal.d{scanNum}.maxr2 = max(thisr2); % saves out maximum voxel peak (for curiosity) + pRFAnal.d{scanNum}.rawCoords = thisRawParamsCoords; % this is where we save it, so we can access it via the d structure + %pRFAnal.d{scanNum}.weights = thisWeights; + if ~strcmpi(algorithm,'nelder-mead') + pRFAnal.d{scanNum}.myresid = thisResid; + end + pRFAnal.d{scanNum}.mytSeries = thistSeries; + pRFAnal.d{scanNum}.mymodelResp = thismodelResponse; + + + iScan = find(params.scanNum == scanNum); + thisParams.scanNum = params.scanNum(iScan); +% for iOverlay = 1:length(overlayNames) +% theOverlays{iOverlay}.params{scanNum} = thisParams; +% end + + r2.params{scanNum} = thisParams; + prefDigit.params{scanNum} = thisParams; + prefPD.params{scanNum} = thisParams; + if strcmpi(params.pRFFit.rfType,'gaussian-hdr-double') + rfHalfWidthX.params{scanNum} = thisParams; + rfHalfWidthY.params{scanNum} = thisParams; + + else + rfHalfWidth.params{scanNum} = thisParams; + + end + + + % display how long it took + disp(sprintf('(pRF_somato) Fitting for %s:%i took in total: %s',params.groupName,scanNum,mlrDispElapsedTime(toc))); +end + +% install analysis +pRFAnal.name = params.saveName; +pRFAnal.type = 'pRFAnal'; +pRFAnal.groupName = params.groupName; +pRFAnal.function = 'pRF_somato'; +pRFAnal.reconcileFunction = 'defaultReconcileParams'; +pRFAnal.mergeFunction = 'pRFMergeParams'; +pRFAnal.guiFunction = 'pRF_somatoGUI'; +pRFAnal.params = params; + +if strcmpi(params.pRFFit.rfType,'gaussian-hdr-double') + pRFAnal.overlays = [r2 prefDigit prefPD rfHalfWidthX rfHalfWidthY]; +else + pRFAnal.overlays = [r2 prefDigit prefPD rfHalfWidth ]; +end +%pRFAnal.overlays = []; +% for iOverlay = 1:numel(theOverlays) +% eval(sprintf('%s = struct(theOverlays{iOverlay});',overlayNames{iOverlay})); +% eval(sprintf('pRFAnal.overlays = [pRFAnal.overlays %s];',overlayNames{iOverlay})); +% end + +pRFAnal.curOverlay = 1; +pRFAnal.date = date; +v = viewSet(v,'newAnalysis',pRFAnal); + +% if we are going to merge, temporarily set overwritePolicy +if isfield(params,'mergeAnalysis') && params.mergeAnalysis + saveMethod = mrGetPref('overwritePolicy'); + mrSetPref('overwritePolicy','Merge'); +end +% Save it +saveAnalysis(v,pRFAnal.name); +% now set policy back +if isfield(params,'mergeAnalysis') && params.mergeAnalysis + mrSetPref('overwritePolicy',saveMethod); +end + +if ~isempty(viewGet(v,'fignum')) + refreshMLRDisplay(viewGet(v,'viewNum')); +end + +%set(viewGet(v,'figNum'),'Pointer','arrow');drawnow + +% for output +if nargout > 1 + for ii = 1:length(d) + pRFAnal.d{ii}.r2 = r2.data{ii}; + end + % make d strucutre + if length(pRFAnal.d) == 1 + d = pRFAnal.d{1}; + else + d = pRFAnal.d; + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% getVoxelRestriction % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [x y z] = getVoxelRestriction(v,params,scanNum) + +x = [];y = [];z = []; + +if strncmp(params.restrict,'Base: ',6) + % get the base name + baseName = params.restrict(7:end); + baseNums = []; + if strcmp(baseName,'ALL') + for iBase = 1:viewGet(v,'numBase') + % if the base is a surface or flat then add to the list + if any(viewGet(v,'baseType',iBase) == [1 2]) + baseNums(end+1) = iBase; + end + end + else + baseNums = viewGet(v,'baseNum',baseName); + end + % cycle through all bases that we are going to run on + scanCoords = []; + for iBase = 1:length(baseNums) + % get the baseNum + baseNum = baseNums(iBase); + if isempty(baseNum) + disp(sprintf('(pRF_somato) Could not find base to restrict to: %s',params.restrict)); + continue + end + % get the base + base = viewGet(v,'base',baseNum); + if isempty(base) + disp(sprintf('(pRF_somato) Could not find base to restrict to: %s',params.restrict)); + return; + end + % if flat or surface + if any(base.type == [1 2]) + % get base coordinates from the coordMap + for corticalDepth = 0:0.1:1 + if base.type == 1 + % flat map + baseCoords = (base.coordMap.innerCoords + corticalDepth * (base.coordMap.outerCoords-base.coordMap.innerCoords)); + baseCoords = reshape(baseCoords,prod(size(base.data)),3)'; + else + % surface + baseCoords = (base.coordMap.innerVtcs + corticalDepth * (base.coordMap.outerVtcs-base.coordMap.innerVtcs))'; + end + % convert to 4xn array + baseCoords(4,:) = 1; + % and convert to scan coordinates + base2scan = viewGet(v,'base2scan',scanNum,params.groupName,baseNum); + scanCoords = [scanCoords round(base2scan*baseCoords)]; + end + end + end + % check against scandims + scanDims = viewGet(v,'scanDims',scanNum,params.groupName); + scanCoords = mrSub2ind(scanDims,scanCoords(1,:),scanCoords(2,:),scanCoords(3,:)); + % remove duplicates and nans + scanCoords = scanCoords(~isnan(scanCoords)); + scanCoords = unique(scanCoords); + % convert back to x,y,z coordinates + [x y z] = ind2sub(scanDims,scanCoords); +elseif strncmp(params.restrict,'ROI: ',5) + % get the roi name + roiName = params.restrict(6:end); + scanCoords = getROICoordinates(v,roiName,scanNum,params.groupName,'straightXform=1'); + if isempty(scanCoords),return,end + x = scanCoords(1,:);y = scanCoords(2,:);z = scanCoords(3,:); +elseif strncmp(params.restrict,'None',4) + scanDims = viewGet(v,'scanDims',scanNum,params.groupName); + [x y z] = ndgrid(1:scanDims(1),1:scanDims(2),1:scanDims(3)); + x = x(:);y = y(:);z = z(:); +else + return +end + +%check if we have already computed Voxels +if isfield(params,'computedVoxels') && (length(params.computedVoxels)>=scanNum) && ~isempty(params.computedVoxels{scanNum}) + % get scan dims + scanDims = viewGet(v,'scanDims',scanNum,params.groupName); + % convert x, y, z to linear coords + linearCoords = sub2ind(scanDims,x,y,z); + % get new ones + newLinearCoords = setdiff(linearCoords,params.computedVoxels{scanNum}); + if length(newLinearCoords) ~= length(linearCoords) + % show what we are doing + disp(sprintf('(pRF) Dropping %i voxels that have been already computed',length(linearCoords)-length(newLinearCoords))); + % convert back to x, y, z + [x y z] = ind2sub(scanDims,newLinearCoords); + end +end +%%%%%%%%%%%%%%%%%%%%%%%% +% checkPRFparams % +%%%%%%%%%%%%%%%%%%%%%%%% +function params = checkPRFparams(params) + + +% check the pRFFit params +checkFields = {{'stimImageDiffTolerance',5}}; +for iFit = 1:length(params.pRFFit) + + % set defaults + for iField = 1:length(checkFields) + if ~isfield(params.pRFFit(iFit),checkFields{iField}{1}) + params.pRFFit(iFit).(checkFields{iField}{1}) = checkFields{iField}{2}; + end + end +end diff --git a/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m b/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m new file mode 100755 index 000000000..569192372 --- /dev/null +++ b/mrLoadRet/Plugin/pRF_somato/pRF_somatoFit.m @@ -0,0 +1,2560 @@ +% pR_somatoFFit +% +% usage: pRF_somatoFit(v,scanNum,x,y,s,) +% by: ds / completely base on code by justin gardner +% date: 201602 [orig 11/14/2011] +% purpose: interrogator that fits pRF model to selected voxel +% +function fit = pRF_somatoFit(varargin) + +fit = []; +% parse input arguments - note that this is set +% up so that it can also be called as an interrogator + +[v, scanNum, x, y, z, fitParams, tSeries, hrfprf] = parseArgs(varargin); +%[v, scanNum, x, y, z, fitParams, tSeries] = parseArgs(varargin); +if isempty(v),return,end + +% get concat info +if ~isfield(fitParams,'concatInfo') || isempty(fitParams.concatInfo) + fitParams.concatInfo = viewGet(v,'concatInfo',scanNum); +end + +if ~ieNotDefined('hrfprf') + hrfprfcheck = 1; +else + hrfprfcheck = 0; +end + +% if there is no concatInfo, then make one that will +% treat the scan as a single scan +if isempty(fitParams.concatInfo) + nFrames = viewGet(v,'nFrames',scanNum); + fitParams.concatInfo.isConcat = false; + fitParams.concatInfo.n = 1; + fitParams.concatInfo.whichScan = ones(1,nFrames); + fitParams.concatInfo.whichVolume = 1:nFrames; + fitParams.concatInfo.runTransition = [1 nFrames]; + fitParams.concatInfo.totalJunkedFrames = viewGet(v,'totalJunkedFrames',scanNum); + if length(fitParams.concatInfo.totalJunkedFrames > 1) + % first check for consistency in totalJunkedFrames + if length(unique(fitParams.concatInfo.totalJunkedFrames)) > 1 + disp(sprintf('(pRFFit) totalJunkedFrames are different for different members of component scans - could be an average in which different scans with different number of junked frames were removed. This could cause a problem in computing what the stimulus was for the average. The total junked frames count was: %s, but we will use %i as the actual value for computing the stimulus',num2str(fitParams.concatInfo.totalJunkedFrames),floor(median(fitParams.concatInfo.totalJunkedFrames)))); + end + fitParams.concatInfo.totalJunkedFrames = floor(median(fitParams.concatInfo.totalJunkedFrames)); + end +else + fitParams.concatInfo.isConcat = true; + if ~isfield(fitParams.concatInfo,'totalJunkedFrames') + fitParams.concatInfo.totalJunkedFrames = viewGet(v,'totalJunkedFrames',scanNum); + end +end + +% get the stimulus movie if it wasn't passed in +if ~isfield(fitParams,'stim') || isempty(fitParams.stim) + fitParams.stim = getStim(v,scanNum,fitParams); +end +if isempty(fitParams.stim),return,end + +% if we are being called to just return the stim image +% then return it here +if fitParams.justGetStimImage + fit = fitParams.stim; + return +end + +% get the tSeries +if ~isempty(x) + % if tSeries was not passed in then load it + if isempty(tSeries) + % load using loadTSeries + tSeries = squeeze(loadTSeries(v,scanNum,z,[],x,y)); + end + + % convert to percent tSeries. Note that we detrend here which is not necessary for concats, + % but useful for raw/motionCorrected time series. Also, it is very important that + % the tSeries is properly mean subtracted + if ~isfield(fitParams.concatInfo,'hipassfilter') + tSeries = percentTSeries(tSeries,'detrend','Linear','spatialNormalization','Divide by mean','subtractMean', 'Yes', 'temporalNormalization', 'No'); + end + + % if there are any nans in the tSeries then don't fit + if any(isnan(tSeries)) + if fitParams.verbose + disp(sprintf('(pRF_somatoFit) Nan found in tSeries for voxel [%i %i %i] in scan %s:%i. Abandoning fit',x,y,z,viewGet(v,'groupName'),scanNum)); + end + fit=[];return + end +else + tSeries = []; +end + +% handle junk frames (i.e. ones that have not already been junked) +if ~isempty(fitParams.junkFrames) && ~isequal(fitParams.junkFrames,0) + % drop junk frames + disp(sprintf('(pRF_somatoFit) Dropping %i junk frames',fitParams.junkFrames)); + tSeries = tSeries(fitParams.junkFrames+1:end); + if ~isfield(fitParams.concatInfo,'totalJunkedFramesIncludesJunked'); + fitParams.concatInfo.totalJunkedFrames = fitParams.concatInfo.totalJunkedFrames+fitParams.junkFrames; + fitParams.concatInfo.totalJunkedFramesIncludesJunked = 1; + end +end + + +% set up the fit routine params +fitParams = setFitParams(fitParams); + +% just return model response for already calculated params +if fitParams.getModelResponse + + + if ~ieNotDefined('hrfprf') + fitParams.hrfprf = hrfprf; + % get model fit + [residual, fit.modelResponse, fit.rfModel, ~, realhrf] = getModelResidual(fitParams.params,tSeries,fitParams, [], hrfprfcheck); + % get the real hrf from the inputted ones + fit.p = getFitParams(fitParams.params,fitParams); + fit.canonical = realhrf; + %fit.canonical = getCanonicalHRF(fit.p.canonical,fitParams.framePeriod); + % return tSeries + fit.tSeries = tSeries; + return; + else + + % get model fit + [residual fit.modelResponse fit.rfModel] = getModelResidual(fitParams.params,tSeries,fitParams, [], hrfprfcheck); + % get the canonical + fit.p = getFitParams(fitParams.params,fitParams); + fit.canonical = getCanonicalHRF(fit.p.canonical,fitParams.framePeriod); + % return tSeries + fit.tSeries = tSeries; + return; + + end +end + +% return some fields +fit.stim = fitParams.stim; +fit.stimX = fitParams.stimX; +fit.stimY = fitParams.stimY; +fit.stimT = fitParams.stimT; +fit.concatInfo = fitParams.concatInfo; +fit.nParams = fitParams.nParams; +paramsInfoFields = {'minParams','maxParams','initParams','paramNames','paramDescriptions'}; +for iField = 1:length(paramsInfoFields) + fit.paramsInfo.(paramsInfoFields{iField}) = fitParams.(paramsInfoFields{iField}); +end + +% test to see if scan lengths and stim lengths match +tf = true; +for iScan = 1:fit.concatInfo.n + sLength = fit.concatInfo.runTransition(iScan,2) - fit.concatInfo.runTransition(iScan,1) + 1; + if sLength ~= size(fitParams.stim{iScan}.im,3) + mrWarnDlg(sprintf('(pRF_somatoFit) Data length of %i for scan %i (concatNum:%i) does not match stimfile length %i',fit.concatInfo.runTransition(iScan,2),scanNum,iScan,size(fitParams.stim{iScan}.im,3))); + tf = false; + end +end + +if ~tf,fit = [];return,end + +% do prefit. This computes (or is passed in precomputed) model responses +% for a variety of parameters and calculates the correlation between +% the models and the time series. The one that has the best correlation +% is then used as the initial parameters for the nonlinear fit. This +% helps prevent getting stuck in local minima +if isfield(fitParams,'prefit') && ~isempty(fitParams.prefit) + params = fitParams.initParams; + % calculate model if not already calculated + if ~isfield(fitParams.prefit,'modelResponse') + % get number of workers + nProcessors = mlrNumWorkers; + mlrDispPercent(-inf,sprintf('(pRF_somatoFit) Computing %i prefit model responses using %i processors',fitParams.prefit.n,nProcessors)); + % first convert the x/y and width parameters into sizes + % on the actual screen + %fitParams.prefit.x = fitParams.prefit.x;% *fitParams.stimWidth; + %fitParams.prefit.y = fitParams.prefit.y; %*fitParams.stimHeight; + %fitParams.prefit.rfHalfWidth = fitParams.prefit.rfHalfWidth; % *max(fitParams.stimWidth,fitParams.stimHeight); + %fitParams.prefit.x = fitParams.prefit.x *fitParams.stimWidth; + %fitParams.prefit.y = fitParams.prefit.y *fitParams.stimHeight; + %fitParams.prefit.rfHalfWidth = fitParams.prefit.rfHalfWidth *max(fitParams.stimWidth,fitParams.stimHeight); + %fitParams.prefit.hrfDelay = fitParams.prefit.hrfDelay; %*max(fitParams.stimWidth,fitParams.stimHeight); + % init modelResponse + allModelResponse = nan(fitParams.prefit.n,fitParams.concatInfo.runTransition(end,end)); + % compute all the model response, using parfor loop + % parfor i = 1:fitParams.prefit.n + + parfor i = 1:fitParams.prefit.n %parfor + % fit the model with these parameters + %[residual modelResponse rfModel] = getModelResidual([fitParams.prefit.x(i) fitParams.prefit.y(i) fitParams.prefit.rfHalfWidth(i) fitParams.prefit.hrfDelay(i) params(4:end)],tSeries,fitParams,1); + %[residual modelResponse rfModel] = getModelResidual([fitParams.prefit.x(i) fitParams.prefit.y(i) fitParams.prefit.rfHalfWidth(i) params(4:end)],tSeries,fitParams,1, crossValcheck); + [residual modelResponse rfModel] = getModelResidual([fitParams.prefit.x(i) fitParams.prefit.y(i) fitParams.prefit.rfHalfWidth(i) params(4:end)],tSeries,fitParams,1); + % normalize to 0 mean unit length + allModelResponse(i,:) = (modelResponse-mean(modelResponse))./sqrt(sum(modelResponse.^2))'; + if fitParams.verbose + disp(sprintf('(pRF_somatoFit) Computing prefit model response %i/%i: Center [%6.2f,%6.2f] rfHalfWidth=%5.2f ',i,fitParams.prefit.n,fitParams.prefit.x(i),fitParams.prefit.y(i),fitParams.prefit.rfHalfWidth(i) )); + end + end + mlrDispPercent(inf); + fitParams.prefit.modelResponse = allModelResponse; + clear allModelResponse; + end + % save in global, so that when called as an interrogator + % we don't have to keep computing fitParams + global gpRFFitTypeParams + gpRFFitTypeParams.prefit = fitParams.prefit; + % return some computed fields + fit.prefit = fitParams.prefit; + if fitParams.returnPrefit,return,end + % normalize tSeries to 0 mean unit length + tSeriesNorm = (tSeries-mean(tSeries))/sqrt(sum(tSeries.^2)); + % calculate r for all modelResponse by taking inner product + r = fitParams.prefit.modelResponse*tSeriesNorm; + % get best r2 for all the models + [maxr bestModel] = max(r); + fitParams.initParams(1) = fitParams.prefit.x(bestModel); + fitParams.initParams(2) = fitParams.prefit.y(bestModel); + fitParams.initParams(3) = fitParams.prefit.rfHalfWidth(bestModel); + %fitParams.initParams(4) = fitParams.prefit.hrfDelay(bestModel); + if fitParams.prefitOnly + % return if we are just doing a prefit + fit = getFitParams(fitParams.initParams,fitParams); + fit.rfType = fitParams.rfType; + fit.params = fitParams.initParams; + fit.r2 = maxr^2; + fit.r = maxr; + % [fit.polarAngle fit.eccentricity] = cart2pol(fit.x,fit.y); + fit.prefDigit = fit.y; % Is this flipped? + fit.prefPD = fit.x; + fit.rfHalfWidth = fit.std; + %fit.hrfDelay = fit.params(4); + + + %% anon function see pRFFit.m + % mod = 'somato'; % this variable should be set in the GUI - the user can choose the stimulus / modality + % overlayNames = getMetaData(v,params,mod,'overlayNames'); + % % r2 + % eval(sprintf('fit.%s = fit.r2',overlayNames{1})); + % % x + % eval(sprintf('fit.%s = fit.x',overlayNames{2})); + % + % if numel(overlayNames) == 4 + % % y + % eval(sprintf('fit.%s = fit.y',overlayNames{3})); + % % hw + % eval(sprintf('fit.%s = fit.std',overlayNames{4})); + % else + % % hw + % eval(sprintf('fit.%s = fit.std',overlayNames{3})); + % end + %%%%%% + + % display + if fitParams.verbose + % disp(sprintf('%s[%2.f %2.f %2.f] r2=%0.2f polarAngle=%6.1f eccentricity=%6.1f rfHalfWidth=%6.1f',fitParams.dispstr,x,y,z,fit.r2,r2d(fit.polarAngle),fit.eccentricity,fit.std)); + disp(sprintf('%s[%2.f %2.f %2.f] r2=%0.2f prefDigit=%6.1f prefPD=%6.1f rfHalfWidth=%6.1f ',fitParams.dispstr,x,y,z,fit.r2,fit.prefDigit,fit.prefPD,fit.std )); + + end + return + end +end + +% this works if we want to run the pRF on already computed values, e.g. +% cross validation. But in the case where we want to fit a few parameters, +% e.g. fit for the first 3 gaussian parameters, but give it some +% precomputed HRF params, then we need to be selective in our params +% +% An easy way would be to let the fit happen as normal, but then overwrite +% the params(end-5:end) with those precomputed HRF params - but this is +% very dirty and slow... +% +% +if ~ieNotDefined('hrfprf') + fitParams.hrfprf = hrfprf; + if strcmp(lower(fitParams.algorithm),'levenberg-marquardt') + [params resnorm residual exitflag output lambda jacobian] = lsqnonlin(@getModelResidual,fitParams.initParams,fitParams.minParams,fitParams.maxParams,fitParams.optimParams,tSeries,fitParams); + elseif strcmp(lower(fitParams.algorithm),'nelder-mead') + [params fval exitflag] = fminsearch(@getModelResidual,fitParams.initParams,fitParams.optimParams,(tSeries-mean(tSeries))/var(tSeries.^2),fitParams); + %[params fval exitflag] = fmincon(@getModelResidual,fitParams.initParams,[],[],[],[],[-5 -5 -5],[5 5 5],[],fitParams.optimParams,(tSeries-mean(tSeries))/var(tSeries.^2),fitParams); + else + disp(sprintf('(pRF_somatoFit) Unknown optimization algorithm: %s',fitParams.algorithm)); + return + end + +else + + % now do nonlinear fit + if strcmp(lower(fitParams.algorithm),'levenberg-marquardt') + [params resnorm residual exitflag output lambda jacobian] = lsqnonlin(@getModelResidual,fitParams.initParams,fitParams.minParams,fitParams.maxParams,fitParams.optimParams,tSeries,fitParams); + elseif strcmp(lower(fitParams.algorithm),'nelder-mead') + [params fval exitflag] = fminsearch(@getModelResidual,fitParams.initParams,fitParams.optimParams,(tSeries-mean(tSeries))/var(tSeries.^2),fitParams); + else + disp(sprintf('(pRF_somatoFit) Unknown optimization algorithm: %s',fitParams.algorithm)); + return + end + +end + +% set output arguments +fit = getFitParams(params,fitParams); +fit.rfType = fitParams.rfType; +fit.params = params; + +% compute r^2 +[residual modelResponse rfModel fit.r] = getModelResidual(params,tSeries,fitParams, [], hrfprfcheck); +%fit.r = r; +if strcmp(lower(fitParams.algorithm),'levenberg-marquardt') + fit.r2 = 1-sum((residual-mean(residual)).^2)/sum((tSeries-mean(tSeries)).^2); +elseif strcmp(lower(fitParams.algorithm),'nelder-mead') + fit.r2 = residual^2; +end + +fit.modelResponse = modelResponse; +fit.tSeries = tSeries; +if strcmp(lower(fitParams.algorithm),'levenberg-marquardt') % this may crash if running Nelder-Mead + fit.residual = residual; +end + +% compute polar coordinates +% [fit.polarAngle fit.eccentricity] = cart2pol(fit.x,fit.y); +switch fit.rfType + case {'gaussian-1D'} % WITHIN DIGIT MODEL + + + % hang on, this is stupid + % this is ignoring the fact that we made the rf model Gaussian in + % the first place!!! + + % if size(fitParams.stimX,1) == 4 + % [~, index] = max([fit.amp1 fit.amp2 fit.amp3 fit.amp4]); + % fit.prefDigit = index; + % allMeans = [fit.meanOne fit.meanTwo fit.meanThr fit.meanFour]; + % fit.prefPD = allMeans(index);% Take the prefDigit and specify the + % allStd = [fit.stdOne fit.stdTwo fit.stdThr fit.stdFour]; + % fit.rfHalfWidth = allStd(index); + % else + % [~, index] = max([fit.amp1 fit.amp2 fit.amp3]); + % fit.prefDigit = index; + % allMeans = [fit.meanOne fit.meanTwo fit.meanThr]; + % fit.prefPD = allMeans(index);% Take the prefDigit and specify the + % allStd = [fit.stdOne fit.stdTwo fit.stdThr ]; + % fit.rfHalfWidth = allStd(index); + % end + %keyboard + %[ma] 2019 + + if size(fitParams.stimX,2) == 5 % for TW Touchmap + [~,index] = max(rfModel); + else + [~,index] = max(max(rfModel)); % max digit amp of Gaussian + end + % ds thinks.. this is the correct way to read off the "other" direction + % reason the above is not quite correct is that at this point the RF model is flipped. + % so other way of changing this would be to apply rules to transpose(rfModel) ?! + %[~,index] = max(max(rfModel,[],2),[], 1); % max digit amp of Gaussian + + fit.prefDigit = index; + %figure, plot(rfModel) + %disp(fit.prefDigit) + %fit.prefPD = index; + + % %keyboard + % % this finds prefPD from X, not y axis! + % % problem: r2 for 1D Gaussian is wrong, because it's using a + % % non-interp Gaussian model for the fit, here we do it too late, + % % just for outputs. + % % You'd have to change the stimfile to have 100 x points. + thisX = linspace(1,size(rfModel,2),100); + + if size(fitParams.stimX,2) == 5 + Pone = [fit.amp1 fit.meanOne fit.stdOne 0]; + thisStd = fit.stdOne; + else + + + if fit.prefDigit == 1 + Pone = [fit.amp1 fit.meanOne fit.stdOne 0]; + thisStd = fit.stdOne; + elseif fit.prefDigit == 2 + Pone = [fit.amp2 fit.meanTwo fit.stdTwo 0]; + thisStd = fit.stdTwo; + elseif fit.prefDigit == 3 + Pone = [fit.amp3 fit.meanThr fit.stdThr 0]; + thisStd = fit.stdThr; + elseif fit.prefDigit == 4 + Pone = [fit.amp4 fit.meanFour fit.stdFour 0]; + thisStd = fit.stdFour; + elseif fit.prefDigit == 5 + Pone = [fit.amp5 fit.meanFive fit.stdFive 0]; + thisStd = fit.stdFive; + end + + end + + % if fit.prefPD == 1 + % Pone = [fit.amp1 fit.meanOne fit.stdOne 0]; + % thisStd = fit.stdOne; + % elseif fit.prefPD == 2 + % Pone = [fit.amp2 fit.meanTwo fit.stdTwo 0]; + % thisStd = fit.stdTwo; + % elseif fit.prefPD == 3 + % Pone = [fit.amp3 fit.meanThr fit.stdThr 0]; + % thisStd = fit.stdThr; + % elseif fit.prefPD == 4 + % Pone = [fit.amp4 fit.meanFour fit.stdFour 0]; + % thisStd = fit.stdFour; + % end + + thisZ = gauss(Pone,thisX)'; + %thisZ = gauss(Pone,thisX); + [~,index2] = max(thisZ); + fit.prefPD = thisX(index2); % this is trickier, because we need to rewrap the PD values onto a 1-4 grid + %fit.prefDigit = thisX(index2); + %%%%%fit.prefPD = mean(rfModel(:,index)); % mean of that digit = PD + fit.prefPD = abs(fit.prefPD - (size(rfModel,2)+1) ); % I'm not sure why this is necessary, but it needs to be unflipped outside of mrTools + %fit.prefDigit = abs(fit.prefDigit - (size(rfModel,2)+1)); + + + + %fit.rfHalfWidth = std(rfModel(:,index)); % std of digit Gaussian + fit.rfHalfWidth = thisStd; + + case {'gaussian-1D-transpose'} % BETWEEN DIGIT MODEL + + + if size(fitParams.stimX,2) == 5 || size(fitParams.stimX,2) == 4 % for TW Touchmap + [~,index] = max(rfModel); + else + [~,index] = max(max(rfModel,[],2),[], 1); % max digit amp of Gaussian + end + + + %[~,index] = max(max(rfModel,[],2),[], 1); % max digit amp of Gaussian + fit.prefPD = index; + + thisX = linspace(1,size(rfModel,2),100); + + if size(fitParams.stimX,2) == 5 || size(fitParams.stimX,2) == 4 + Pone = [fit.amp1 fit.meanOne fit.stdOne 0]; + thisStd = fit.stdOne; + else + + + if fit.prefPD == 1 + Pone = [fit.amp1 fit.meanOne fit.stdOne 0]; + thisStd = fit.stdOne; + elseif fit.prefPD == 2 + Pone = [fit.amp2 fit.meanTwo fit.stdTwo 0]; + thisStd = fit.stdTwo; + elseif fit.prefPD == 3 + Pone = [fit.amp3 fit.meanThr fit.stdThr 0]; + thisStd = fit.stdThr; + elseif fit.prefPD == 4 + Pone = [fit.amp4 fit.meanFour fit.stdFour 0]; + thisStd = fit.stdFour; + elseif fit.prefPD == 5 + Pone = [fit.amp5 fit.meanFive fit.stdFive 0]; + thisStd = fit.stdFive; + + end + + end + thisZ = gauss(Pone,thisX)'; + [~,index2] = max(thisZ); + + % weird buglet here wrt colormaps for base/tips pRF - ma jan2020 + fit.prefDigit = thisX(index2); % original + %prefDigit_tmp = thisX(index2); + %fit.prefDigit = abs(prefDigit_tmp - (size(rfModel,2)+1) ); + + + % ignore this for base/tips pRF fitting + + % this is trickier, because we need to rewrap the PD values onto a 1-4 grid + if size(fitParams.stimX,2) == 5 || size(fitParams.stimX,2) == 4 + fit.prefPD = fit.prefPD; + else + fit.prefPD = abs(fit.prefPD - (size(rfModel,2)+1) ); + end + fit.rfHalfWidth = thisStd; + + + + case {'gaussian','gaussian-hdr'} + fit.prefDigit = fit.y; + fit.prefPD = fit.x; + + fit.rfHalfWidth = fit.std; + case {'gaussian-hdr-double'} + + % test + % stimsY = meshgrid(1:0.03:4,1:0.03:4); + % stimsX = transpose(stimsY); + % bloop = exp(-(((stimsX-fit.x).^2)/(2*(fit.stdx^2))+((stimsY-fit.y).^2)/(2*(fit.stdy^2)))); + % figure, imagesc(bloop) + if size(fitParams.stimX,2) == 5 + fit.prefDigit = fit.x; + fit.prefPD = fit.y; + else + + + fit.prefDigit = fit.y; + fit.prefPD = fit.x; % for some reason this is flipped top to bottom. + end + + fit.rfHalfWidthX = fit.stdx; + fit.rfHalfWidthY = fit.stdy; + + case {'gaussian-1D-orthotips'} + + + fit.prefDigit = mean(rfModel); + fit.prefPD = fit.y; + fit.rfHalfWidth = std(rfModel); + + % case {'gaussian-tips', 'gaussian-tips-hdr'} + % fit.prefDigit = fit.meanOne; + % fit.prefPD = fit.y; + % fit.rfHalfWidth = fit.std; + % + case {'sixteen-hdr','nine-param','nine-param-hdr', 'five-hdr'} + %figure, imagesc(rfModel) + [~,fit.prefDigit] = max(mean(rfModel,1)); % in 1st dim + [~,fit.prefPD] = max(mean(flipud(rfModel),2)); %orthog dim + %[~,fit.prefPD] = max(mean(rfModel,2)); %orthog dim + fit.rfHalfWidth = std(rfModel(:)); + otherwise + fit.prefDigit = fit.x; %Flipped this, otherwise prefPD and prefDigit are incorrectly labelled in GUI, i.e. fit.prefDigit = fit.y + fit.prefPD = fit.y; + fit.rfHalfWidth = fit.std; + +end + + +% display +if fitParams.verbose + + if strcmpi(fit.rfType,'gaussian-hdr-double') + disp(sprintf('%s[%2.f %2.f %2.f] r2=%0.2f prefDigit=%6.1f prefPD=%6.1f STDX=%6.1f STDY=%6.1f ',fitParams.dispstr,x,y,z,fit.r2,fit.prefDigit,fit.prefPD,fit.rfHalfWidthX, fit.rfHalfWidthY )); + + else + + % disp(sprintf('%s[%2.f %2.f %2.f] r2=%0.2f polarAngle=%6.1f eccentricity=%6.1f rfHalfWidth=%6.1f',fitParams.dispstr,x,y,z,fit.r2,r2d(fit.polarAngle),fit.eccentricity,fit.std)); + disp(sprintf('%s[%2.f %2.f %2.f] r2=%0.2f prefDigit=%6.1f prefPD=%6.1f rfHalfWidth=%6.1f ',fitParams.dispstr,x,y,z,fit.r2,fit.prefDigit,fit.prefPD,fit.rfHalfWidth )); + end + + +end + +end + + +%%%%%%%%%%%%%%%%%%%%%% +%% setFitParams % +%%%%%%%%%%%%%%%%%%%%%% +function fitParams = setFitParams(fitParams); + +% set rfType +if ~isfield(fitParams,'rfType') || isempty(fitParams.rfType) + fitParams.rfType = 'gaussian'; +end + +% get stimulus x,y and t +fitParams.stimX = fitParams.stim{1}.x; +fitParams.stimY = fitParams.stim{1}.y; +fitParams.stimT = fitParams.stim{1}.t; + +% set stimulus extents +fitParams.stimExtents(1) = min(fitParams.stimX(:)); +fitParams.stimExtents(3) = max(fitParams.stimX(:)); +fitParams.stimExtents(2) = min(fitParams.stimY(:)); +fitParams.stimExtents(4) = max(fitParams.stimY(:)); +% need to change this to 4 for more params?? +%fitParams.stimWidth = fitParams.stimExtents(3)-fitParams.stimExtents(1); +%fitParams.stimHeight = fitParams.stimExtents(4)-fitParams.stimExtents(2); + +if ~isfield(fitParams,'initParams') + % check the rfType to get the correct min/max arrays + switch (fitParams.rfType) + case 'gaussian' + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {'x','y','rfWidth'}; + fitParams.paramDescriptions = {'RF x position (digit)','RF y position (PD)','RF width (std of gaussian)'}; + fitParams.paramIncDec = [1 1 1 ]; + fitParams.paramMin = [0 0 0 ]; + fitParams.paramMax = [inf inf 10 ]; + % set min/max and init + fitParams.minParams = [fitParams.stimExtents(1) fitParams.stimExtents(2) 0 ]; + fitParams.maxParams = [fitParams.stimExtents(3) fitParams.stimExtents(4) inf ]; + fitParams.initParams = [0 0 4 ]; + case 'gaussian-hdr' + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {'x','y','rfWidth','timelag','tau'}; + fitParams.paramDescriptions = {'RF x position (digit)','RF y position (PD)','RF width (std of gaussian)','Time before start of rise of hemodynamic function','Width of the hemodynamic function (tau parameter of gamma)'}; + fitParams.paramIncDec = [1 1 1 0.1 0.5]; + fitParams.paramMin = [-inf -inf 0 0 0]; + fitParams.paramMax = [inf inf inf 10 10]; + % set min/max and init + fitParams.minParams = [fitParams.stimExtents(1) fitParams.stimExtents(2) 0 0 0]; + fitParams.maxParams = [fitParams.stimExtents(3) fitParams.stimExtents(4) inf 10 10]; + fitParams.initParams = [0 0 4 fitParams.timelag fitParams.tau]; + % add on parameters for difference of gamma + if fitParams.diffOfGamma + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {fitParams.paramNames{:} 'amp2' 'timelag2','tau2'}; + fitParams.paramDescriptions = {fitParams.paramDescriptions{:} 'Amplitude of second gamma for HDR' 'Timelag for second gamma for HDR','tau for second gamma for HDR'}; + fitParams.paramIncDec = [fitParams.paramIncDec(:)' 0.1 0.1 0.5]; + fitParams.paramMin = [fitParams.paramMin(:)' 0 0 0]; + fitParams.paramMax = [fitParams.paramMax(:)' 20 20 20 ]; + % set min/max and init + fitParams.minParams = [fitParams.minParams 0 0 0]; + fitParams.maxParams = [fitParams.maxParams 20 20 20 ]; + fitParams.initParams = [fitParams.initParams fitParams.amplitudeRatio fitParams.timelag2 fitParams.tau2]; + end + + case 'gaussian-hdr-double' + fitParams.paramNames = {'x','y','stdx','stdy','timelag','tau'}; + fitParams.paramDescriptions = {'RF x position (digit)','RF y position (PD)','stdx','stdy','Time before start of rise of hemodynamic function','Width of the hemodynamic function (tau parameter of gamma)'}; + fitParams.paramIncDec = [1 1 1 1 0.1 0.5]; + fitParams.paramMin = [-inf -inf 0 0 0 0]; + fitParams.paramMax = [inf inf inf inf 10 10]; + % set min/max and init + fitParams.minParams = [fitParams.stimExtents(1) fitParams.stimExtents(2) 0 0 0 0]; + fitParams.maxParams = [fitParams.stimExtents(3) fitParams.stimExtents(4) inf inf 10 10]; + fitParams.initParams = [0 0 1 1 fitParams.timelag fitParams.tau]; + % add on parameters for difference of gamma + if fitParams.diffOfGamma + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {fitParams.paramNames{:} 'amp2' 'timelag2','tau2'}; + fitParams.paramDescriptions = {fitParams.paramDescriptions{:} 'Amplitude of second gamma for HDR' 'Timelag for second gamma for HDR','tau for second gamma for HDR'}; + fitParams.paramIncDec = [fitParams.paramIncDec(:)' 0.1 0.1 0.5]; + fitParams.paramMin = [fitParams.paramMin(:)' 0 0 0]; + fitParams.paramMax = [fitParams.paramMax(:)' 20 20 20 ]; + % set min/max and init + fitParams.minParams = [fitParams.minParams 0 0 0]; + fitParams.maxParams = [fitParams.maxParams 20 20 20 ]; + fitParams.initParams = [fitParams.initParams fitParams.amplitudeRatio fitParams.timelag2 fitParams.tau2]; + end + + + case 'gaussian-surround' + fitParams.paramNames = {'x','y','rfWidth','timelag','tau','surrAmp', 'surrWidth'}; + fitParams.paramDescriptions = {'RF x position (digit)','RF y position (PD)','RF width (std of gaussian)','Time before start of rise of hemodynamic function','Width of the hemodynamic function (tau parameter of gamma)','amplitude of the surround', 'width of the surround'}; + fitParams.paramIncDec = [1 1 1 0.1 0.5 0.5 1]; + fitParams.paramMin = [-inf -inf 0 0 0 -inf -inf]; + fitParams.paramMax = [inf inf inf inf inf inf inf]; + % set min/max and init + fitParams.minParams = [fitParams.stimExtents(1) fitParams.stimExtents(2) 0 0 0 -inf -inf]; + fitParams.maxParams = [fitParams.stimExtents(3) fitParams.stimExtents(4) inf 3 inf inf inf]; + fitParams.initParams = [0 0 4 fitParams.timelag fitParams.tau 0 0]; + % add on parameters for difference of gamma + if fitParams.diffOfGamma + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {fitParams.paramNames{:} 'amp2' 'timelag2','tau2'}; + fitParams.paramDescriptions = {fitParams.paramDescriptions{:} 'Amplitude of second gamma for HDR' 'Timelag for second gamma for HDR','tau for second gamma for HDR'}; + fitParams.paramIncDec = [fitParams.paramIncDec(:)' 0.1 0.1 0.5]; + fitParams.paramMin = [fitParams.paramMin(:)' 0 0 0]; + fitParams.paramMax = [fitParams.paramMax(:)' inf inf inf]; + % set min/max and init + fitParams.minParams = [fitParams.minParams 0 0 0]; + fitParams.maxParams = [fitParams.maxParams inf 6 inf]; + fitParams.initParams = [fitParams.initParams fitParams.amplitudeRatio fitParams.timelag2 fitParams.tau2]; + end + % nine params + case 'nine-param' + fitParams.rfType = 'nine-param'; + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {'a1','a2','a3','b1','b2','b3','c1','c2','c3'}; + fitParams.paramDescriptions = {'Weight (a1)','Weight (a2)','Weight (a3)', ... + 'Weight (b1)','Weight (b2)','Weight (b3)', ... + 'Weight (c1)','Weight (c2)','Weight (c3)'}; + fitParams.paramIncDec = [1 1 1 1 1 1 1 1 1]; + %fitParams.paramIncDec = [-0.5+rand(1,9)]; + fitParams.paramMin = [-inf -inf -inf -inf -inf -inf -inf -inf -inf]; + fitParams.paramMax = [inf inf inf inf inf inf inf inf inf]; + % set min/max and init + fitParams.minParams = fitParams.stimExtents([ones(1,9), 2*ones(1,9)]) ; + fitParams.maxParams = fitParams.stimExtents([3*ones(1,9), 4*ones(1,9)]); + fitParams.initParams = [ones(1,9)]; + %fitParams.initParams = [-0.5+rand(1,9)]; + case 'nine-param-hdr' + fitParams.rfType = 'nine-param'; + + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {'a1','a2','a3','b1','b2','b3','c1','c2','c3','timelag','tau'}; + fitParams.paramDescriptions = {'Weight (a1)','Weight (a2)','Weight (a3)', ... + 'Weight (b1)','Weight (b2)','Weight (b3)', ... + 'Weight (c1)','Weight (c2)','Weight (c3)','Time before start of rise of hemodynamic function', 'Width of the hemodynamic function (tau parameter of gamma)'}; + fitParams.paramIncDec = [1 1 1 1 1 1 1 1 1 0.1 0.5]; + %fitParams.paramIncDec = [-0.5+rand(1,9)]; + %fitParams.paramMin = [-inf -inf -inf -inf -inf -inf -inf -inf -inf 0 0]; + %fitParams.paramMax = [inf inf inf inf inf inf inf inf inf inf inf]; + fitParams.paramMin = [zeros(1,9), 0 0]; + fitParams.paramMax = [4.*ones(1,9) 10 10]; + + % set min/max and init + %fitParams.minParams = [fitParams.stimExtents([ones(1,9), 2*ones(1,9)]) 0 0] ; + %fitParams.maxParams = [fitParams.stimExtents([3*ones(1,9), 4*ones(1,9)]) 10 10 ]; + fitParams.minParams = [ones(1,9), 0 0]; + fitParams.maxParams = [3*ones(1,9), 10 10]; + + % random seed + %fitParams.initParams = [[0.5+3 .* rand(1,9)] fitParams.timelag fitParams.tau]; + fitParams.initParams = [ones(1,9) fitParams.timelag fitParams.tau]; + %fitParams.initParams = [-0.5+rand(1,9) fitParams.timelag fitParams.tau]; + % add on parameters for difference of gamma + + if fitParams.diffOfGamma + + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {fitParams.paramNames{:} 'amp2' 'timelag2','tau2'}; + fitParams.paramDescriptions = {fitParams.paramDescriptions{:} 'Amplitude of second gamma for HDR' 'Timelag for second gamma for HDR','tau for second gamma for HDR'}; + fitParams.paramIncDec = [fitParams.paramIncDec(:)' 0.1 0.1 0.5]; + fitParams.paramMin = [fitParams.paramMin(:)' 0 0 0]; + fitParams.paramMax = [fitParams.paramMax(:)' 20 20 20]; + % set min/max and init + fitParams.minParams = [fitParams.minParams 0 0 0]; + fitParams.maxParams = [fitParams.maxParams 20 20 20]; + fitParams.initParams = [fitParams.initParams fitParams.amplitudeRatio fitParams.timelag2 fitParams.tau2]; + end + + % Want to implement a 1D Gaussian ALONG a digit, across maybe 5 + % discrete sites. That would be 15 stim sites (5x3). We want 6 + % params, a mean and SD for the gaussian at each site. + + case {'gaussian-1D','gaussian-1D-transpose'} + + if size(fitParams.stimX,1) == 4 + % Not PROPERLY implemented yet + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {'mean1', 'mean2', 'mean3', 'mean4'... + 'sd1', 'sd2', 'sd3','sd4',... + 'amp2', 'amp3','amp4',... + 'timelag','tau'}; + fitParams.paramDescriptions = {'mean of digit1', 'mean of digit2', 'mean of digit3', 'mean of digit4', ... + 'SD1', 'SD2', 'SD3','SD4', ... + 'amplitude2', 'amplitude3', 'amplitude4', ... + 'Time before start of rise of hemodynamic function','Width of the hemodynamic function (tau parameter of gamma)'}; + fitParams.paramIncDec = [1 1 1 1 1 1 1 1 1 1 1 0.1 0.5]; + fitParams.paramMin = [0.5 0.5 0.5 0.5 0 0 0 0 -inf -inf -inf 0 0]; %Centers only go between 0 and 3 (in units of 'digit') + fitParams.paramMax = [4.5 4.5 4.5 4.5 inf inf inf inf inf inf inf inf inf]; + % set min/max and init + fitParams.minParams = [0.5 0.5 0.5 0.5 0 0 0 0 -inf -inf -inf 0 0]; + fitParams.maxParams = [4.5 4.5 4.5 4.5 inf inf inf inf inf inf inf 3 inf]; + fitParams.initParams = [0.5 0.5 0.5 0.5 1 1 1 1 1 1 1 fitParams.timelag fitParams.tau]; + + % adding a random initialisation + %fitParams.initParams = [ [0.5+4 .* rand(1,4)] [0+10 .* rand(1,7)] fitParams.timelag fitParams.tau]; + % adding a fixed initilaistaon + %fitParams.initParams = [4.5 4.5 4.5 4.5 1 1 1 1 1 1 1 fitParams.timelag fitParams.tau]; + + % add on parameters for difference of gamma + if fitParams.diffOfGamma + %disp(sprintf('(diffOfGamma) !!! Not implemented yet!!!')); + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {fitParams.paramNames{:} 'amplitude2' 'timelag2','tau2'}; + fitParams.paramDescriptions = {fitParams.paramDescriptions{:} 'Amplitude of second gamma for HDR' 'Timelag for second gamma for HDR','tau for second gamma for HDR'}; + fitParams.paramIncDec = [fitParams.paramIncDec(:)' 0.1 0.1 0.5]; + fitParams.paramMin = [fitParams.paramMin(:)' 0 0 0]; + fitParams.paramMax = [fitParams.paramMax(:)' inf inf inf]; + % set min/max and init + fitParams.minParams = [fitParams.minParams 0 0 0]; + fitParams.maxParams = [fitParams.maxParams inf 6 inf]; + fitParams.initParams = [fitParams.initParams fitParams.amplitudeRatio fitParams.timelag2 fitParams.tau2]; + end + + elseif size(fitParams.stimX,1) == 3 %Gaussian 1D 3x3 + fitParams.paramNames = {'mean1', 'mean2', 'mean3'... + 'sd1', 'sd2', 'sd3',... + 'amp2', 'amp3',... + 'timelag','tau'}; + fitParams.paramDescriptions = {'mean of digit1', 'mean of digit2', 'mean of digit3', ... + 'SD1', 'SD2', 'SD3', ... + 'amplitude2', 'amplitude3', ... + 'Time before start of rise of hemodynamic function','Width of the hemodynamic function (tau parameter of gamma)'}; + fitParams.paramIncDec = [1 1 1 1 1 1 1 1 0.1 0.5]; + fitParams.paramMin = [0.5 0.5 0.5 0 0 0 -inf -inf 0 0]; %Centers only go between 0 and 3 (in units of 'digit') + fitParams.paramMax = [4.5 4.5 4.5 inf inf inf inf inf inf inf]; + % set min/max and init + fitParams.minParams = [0.5 0.5 0.5 0 0 0 -inf -inf 0 0]; + fitParams.maxParams = [4.5 4.5 4.5 inf inf inf inf inf 3 inf]; + fitParams.initParams = [0.5 0.5 0.5 1 1 1 1 1 fitParams.timelag fitParams.tau]; + % random initilaisation + %fitParams.initParams = [ [0.5+3 .* rand(1,3)] [0+10 .* rand(1,5)] fitParams.timelag fitParams.tau]; + % add on parameters for difference of gamma + if fitParams.diffOfGamma + %disp(sprintf('(diffOfGamma) !!! Not implemented yet!!!')); + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {fitParams.paramNames{:} 'amplitude2' 'timelag2','tau2'}; + fitParams.paramDescriptions = {fitParams.paramDescriptions{:} 'Amplitude of second gamma for HDR' 'Timelag for second gamma for HDR','tau for second gamma for HDR'}; + fitParams.paramIncDec = [fitParams.paramIncDec(:)' 0.1 0.1 0.5]; + fitParams.paramMin = [fitParams.paramMin(:)' 0 0 0]; + fitParams.paramMax = [fitParams.paramMax(:)' inf inf inf]; + % set min/max and init + fitParams.minParams = [fitParams.minParams 0 0 0]; + fitParams.maxParams = [fitParams.maxParams inf 6 inf]; + fitParams.initParams = [fitParams.initParams fitParams.amplitudeRatio fitParams.timelag2 fitParams.tau2]; + end + + elseif size(fitParams.stimX,2) == 5 || size(fitParams.stimX,2) == 4% pRF on patients TW + + % fitParams.paramNames = {'mean1', 'mean2', 'mean3', 'mean4', 'mean5',... + % 'sd1', 'sd2', 'sd3','sd4', 'sd5',... + % 'amp2', 'amp3','amp4','amp5',... + % 'timelag','tau'}; + fitParams.paramNames = {'mean1',... + 'sd1', ... + 'amp1',... + 'timelag','tau'}; + % fitParams.paramDescriptions = {'mean of digit1', 'mean of digit2', 'mean of digit3', 'mean of digit4', 'mean of digit5', ... + % 'SD1', 'SD2', 'SD3','SD4','SD5', ... + % 'amplitude2', 'amplitude3', 'amplitude4', 'amplitude5',... + % 'Time before start of rise of hemodynamic function','Width of the hemodynamic function (tau parameter of gamma)'}; + fitParams.paramDescriptions = {'mean of digit1', ... + 'SD1', ... + 'amplitude1',... + 'Time before start of rise of hemodynamic function','Width of the hemodynamic function (tau parameter of gamma)'}; + % fitParams.paramIncDec = [1 1 1 1 1 1 1 1 1 1 1 1 1 1 0.1 0.5]; + % fitParams.paramMin = [0.5 0.5 0.5 0.5 0.5 0 0 0 0 0 -inf -inf -inf -inf 0 0]; %Centers only go between 0 and 3 (in units of 'digit') + % fitParams.paramMax = [5.5 5.5 5.5 5.5 5.5 inf inf inf inf inf inf inf inf inf inf inf inf]; + % % set min/max and init + % fitParams.minParams = [0.5 0.5 0.5 0.5 0.5 0 0 0 0 0 -inf -inf -inf -inf 0 0]; + % fitParams.maxParams = [5.5 5.5 5.5 5.5 5.5 inf inf inf inf inf inf inf inf inf 3 inf]; + % fitParams.initParams = [0.5 0.5 0.5 0.5 0.5 1 1 1 1 1 1 1 1 1 fitParams.timelag fitParams.tau]; + + fitParams.paramIncDec = [1 1 1 0.1 0.5]; + fitParams.paramMin = [0.5 0 -inf 0 0]; + fitParams.paramMax = [5.5 inf inf inf inf]; + % set min/max and init + fitParams.minParams = [0.5 0 -inf 0 0]; + fitParams.maxParams = [5.5 inf inf 3 inf]; + fitParams.initParams = [0.5 1 1 fitParams.timelag fitParams.tau]; + + % random initilaisation + %fitParams.initParams = [ [0.5+3 .* rand(1,3)] [0+10 .* rand(1,5)] fitParams.timelag fitParams.tau]; + % add on parameters for difference of gamma + if fitParams.diffOfGamma + %disp(sprintf('(diffOfGamma) !!! Not implemented yet!!!')); + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {fitParams.paramNames{:} 'amplitude2' 'timelag2','tau2'}; + fitParams.paramDescriptions = {fitParams.paramDescriptions{:} 'Amplitude of second gamma for HDR' 'Timelag for second gamma for HDR','tau for second gamma for HDR'}; + fitParams.paramIncDec = [fitParams.paramIncDec(:)' 0.1 0.1 0.5]; + fitParams.paramMin = [fitParams.paramMin(:)' 0 0 0]; + fitParams.paramMax = [fitParams.paramMax(:)' inf inf inf]; + % set min/max and init + fitParams.minParams = [fitParams.minParams 0 0 0]; + fitParams.maxParams = [fitParams.maxParams inf 6 inf]; + fitParams.initParams = [fitParams.initParams fitParams.amplitudeRatio fitParams.timelag2 fitParams.tau2]; + end + + + + + end + + + case 'gaussian-1D-orthotips' + + %if size(fitParams.stimX,1) == 4 + fitParams.paramNames = {'mean1', 'sd','amp','timelag','tau'}; + fitParams.paramDescriptions = {'mean of G1','SD1','amplitude1','Time before start of rise of hemodynamic function','Width of the hemodynamic function (tau parameter of gamma)'}; + fitParams.paramIncDec = [1 1 1 0.1 0.5]; + fitParams.paramMin = [0.5 -inf -inf 0 0]; %Centers only go between 0 and 3 (in units of 'digit') + fitParams.paramMax = [5.5 inf inf inf inf]; + % set min/max and init + fitParams.minParams = [0.5 0 -inf 0 0]; + fitParams.maxParams = [5.5 inf inf 3 inf]; + fitParams.initParams = [0.5 1 1 fitParams.timelag fitParams.tau]; + + if fitParams.diffOfGamma + + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {fitParams.paramNames{:} 'amplitude2' 'timelag2','tau2'}; + fitParams.paramDescriptions = {fitParams.paramDescriptions{:} 'Amplitude of second gamma for HDR' 'Timelag for second gamma for HDR','tau for second gamma for HDR'}; + fitParams.paramIncDec = [fitParams.paramIncDec(:)' 0.1 0.1 0.5]; + fitParams.paramMin = [fitParams.paramMin(:)' 0 0 0]; + fitParams.paramMax = [fitParams.paramMax(:)' inf inf inf]; + % set min/max and init + fitParams.minParams = [fitParams.minParams 0 0 0]; + fitParams.maxParams = [fitParams.maxParams inf 6 inf]; + fitParams.initParams = [fitParams.initParams fitParams.amplitudeRatio fitParams.timelag2 fitParams.tau2]; + end + + %else + % fitParams.paramNames = {'mean1', 'sd1','amp1','timelag','tau'}; + % fitParams.paramDescriptions = {'mean of G1','SD1','amplitude1','Time before start of rise of hemodynamic function','Width of the hemodynamic function (tau parameter of gamma)'}; + % fitParams.paramIncDec = [1 1 1 0.1 0.5]; + % fitParams.paramMin = [0.5 -inf -inf 0 0]; %Centers only go between 0 and 3 (in units of 'digit') + % fitParams.paramMax = [3.5 inf inf inf inf]; + % % set min/max and init + % fitParams.minParams = [0.5 -inf -inf 0 0]; + % fitParams.maxParams = [3.5 inf inf 3 inf]; + % fitParams.initParams = [0.5 1 1 fitParams.timelag fitParams.tau]; + % + % if fitParams.diffOfGamma + % %disp(sprintf('(diffOfGamma) !!! Not implemented yet!!!')); + % % parameter names/descriptions and other information for allowing user to set them + % fitParams.paramNames = {fitParams.paramNames{:} 'amplitude2' 'timelag2','tau2'}; + % fitParams.paramDescriptions = {fitParams.paramDescriptions{:} 'Amplitude of second gamma for HDR' 'Timelag for second gamma for HDR','tau for second gamma for HDR'}; + % fitParams.paramIncDec = [fitParams.paramIncDec(:)' 0.1 0.1 0.5]; + % fitParams.paramMin = [fitParams.paramMin(:)' 0 0 0]; + % fitParams.paramMax = [fitParams.paramMax(:)' inf inf inf]; + % % set min/max and init + % fitParams.minParams = [fitParams.minParams 0 0 0]; + % fitParams.maxParams = [fitParams.maxParams inf 6 inf]; + % fitParams.initParams = [fitParams.initParams fitParams.amplitudeRatio fitParams.timelag2 fitParams.tau2]; + % end + + %end + + case 'sixteen-hdr' + fitParams.rfType = 'sixteen-hdr'; + + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {'a1','a2','a3', 'a4','b1','b2','b3', 'b4','c1','c2','c3', ... + 'c4', 'd1', 'd2', 'd3', 'd4','timelag','tau'}; + fitParams.paramDescriptions = {'Weight (a1)','Weight (a2)','Weight (a3)', ... + 'Weight (a4)', 'Weight (b1)','Weight (b2)','Weight (b3)', ... + 'Weight (b4)', 'Weight (c1)','Weight (c2)','Weight (c3)',... + 'Weight (c4)', ... + 'Weight (d1', 'Weight (d2)', 'Weight (d3)', 'Weight (d4)'... + 'Time before start of rise of hemodynamic function', 'Width of the hemodynamic function (tau parameter of gamma)'}; + fitParams.paramIncDec = [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0.1 0.5]; + %fitParams.paramIncDec = [-0.5+rand(1,9)]; + %fitParams.paramMin = [-inf -inf -inf -inf -inf -inf -inf -inf -inf 0 0]; + %fitParams.paramMax = [inf inf inf inf inf inf inf inf inf inf inf]; + fitParams.paramMin = [zeros(1,16) 0 0]; + fitParams.paramMax = [4.*ones(1,16) 10 10]; + + % set min/max and init + fitParams.minParams = [zeros(1,16) 0 0] ; + fitParams.maxParams = [4.*ones(1,16) 10 10 ]; + fitParams.initParams = [ones(1,16) fitParams.timelag fitParams.tau]; + + % try random seed + %fitParams.initParams = [[1+3 .* rand(1,16)] fitParams.timelag fitParams.tau]; + % try max seed + %fitParams.initParams = [4.*ones(1,16) fitParams.timelag fitParams.tau]; + + % add on parameters for difference of gamma + + if fitParams.diffOfGamma + + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {fitParams.paramNames{:} 'amp2' 'timelag2','tau2'}; + fitParams.paramDescriptions = {fitParams.paramDescriptions{:} 'Amplitude of second gamma for HDR' 'Timelag for second gamma for HDR','tau for second gamma for HDR'}; + fitParams.paramIncDec = [fitParams.paramIncDec(:)' 0.1 0.1 0.5]; + fitParams.paramMin = [fitParams.paramMin(:)' 0 0 0]; + fitParams.paramMax = [fitParams.paramMax(:)' 20 20 20]; + % set min/max and init + fitParams.minParams = [fitParams.minParams 0 0 0]; + fitParams.maxParams = [fitParams.maxParams 20 20 20]; + fitParams.initParams = [fitParams.initParams fitParams.amplitudeRatio fitParams.timelag2 fitParams.tau2]; + end + + % case 'gaussian-tips' + % fitParams.paramNames = {'mean','amp','rfwidth'}; + % fitParams.paramDescriptions = {'mean','amp','rfwidth'}; + % fitParams.paramIncDec = [1 1 1 ]; + % fitParams.paramMin = [0 0 0]; %Centers only go between 0 and 3 (in units of 'digit') + % fitParams.paramMax = [inf inf inf]; + % % set min/max and init + % fitParams.minParams = [ 0 0 0 ]; + % fitParams.maxParams = [inf inf inf]; + % fitParams.initParams = [1 1 1]; + % case 'gaussian-tips-hdr' + % fitParams.paramNames = {'mean','amp','rfwidth', 'timelag', 'tau'}; + % fitParams.paramDescriptions = {'mean','amp','rfwidth', 'timlag', 'tau'}; + % fitParams.paramIncDec = [1 1 1 0.1 0.5 ]; + % fitParams.paramMin = [0 0 0 0 0]; %Centers only go between 0 and 3 (in units of 'digit') + % fitParams.paramMax = [inf inf inf inf inf]; + % % set min/max and init + % fitParams.minParams = [ 0 0 0 0 0]; + % fitParams.maxParams = [inf inf inf inf inf]; + % fitParams.initParams = [1 1 1 fitParams.timelag fitParams.tau]; + % % add on parameters for difference of gamma + % if fitParams.diffOfGamma + % % parameter names/descriptions and other information for allowing user to set them + % fitParams.paramNames = {fitParams.paramNames{:} 'amp2' 'timelag2','tau2'}; + % fitParams.paramDescriptions = {fitParams.paramDescriptions{:} 'Amplitude of second gamma for HDR' 'Timelag for second gamma for HDR','tau for second gamma for HDR'}; + % fitParams.paramIncDec = [fitParams.paramIncDec(:)' 0.1 0.1 0.5]; + % fitParams.paramMin = [fitParams.paramMin(:)' 0 0 0]; + % fitParams.paramMax = [fitParams.paramMax(:)' inf inf inf]; + % % set min/max and init + % fitParams.minParams = [fitParams.minParams 0 0 0]; + % fitParams.maxParams = [fitParams.maxParams inf 6 inf]; + % fitParams.initParams = [fitParams.initParams fitParams.amplitudeRatio fitParams.timelag2 fitParams.tau2]; + % end + + case 'five-hdr' + fitParams.rfType = 'five-hdr'; + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {'a1','a2','a3','a4', 'a5', 'timelag','tau'}; + fitParams.paramDescriptions = {'Weight (a1)','Weight (a2)','Weight (a3)', ... + 'Weight (a4)','Weight (a5)', ... + 'Time before start of rise of hemodynamic function', ... + 'Width of the hemodynamic function (tau parameter of gamma)'}; + fitParams.paramIncDec = [1 1 1 1 1 0.1 0.5]; + fitParams.paramMin = [zeros(1,5), 0 0]; + fitParams.paramMax = [5.*ones(1,5) inf inf]; + % set min/max and init + fitParams.minParams = [1 1 1 1 1 0 0] ; + fitParams.maxParams = [5 5 5 5 5 3 inf ]; + fitParams.initParams = [ones(1,5) fitParams.timelag fitParams.tau]; + %randInit = 1 + (5-1).*rand(5,1); + %randInit = randInit'; + %fitParams.initParams = [randInit fitParams.timelag fitParams.tau]; + if fitParams.diffOfGamma + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {fitParams.paramNames{:} 'amp2' 'timelag2','tau2'}; + fitParams.paramDescriptions = {fitParams.paramDescriptions{:} 'Amplitude of second gamma for HDR' 'Timelag for second gamma for HDR','tau for second gamma for HDR'}; + fitParams.paramIncDec = [fitParams.paramIncDec(:)' 0.1 0.1 0.5]; + fitParams.paramMin = [fitParams.paramMin(:)' 0 0 0]; + fitParams.paramMax = [fitParams.paramMax(:)' inf inf inf]; + % set min/max and init + fitParams.minParams = [fitParams.minParams 0 0 0]; + fitParams.maxParams = [fitParams.maxParams inf 6 inf]; + fitParams.initParams = [fitParams.initParams fitParams.amplitudeRatio fitParams.timelag2 fitParams.tau2]; + end + + case 'four-hdr' + fitParams.rfType = 'four-hdr'; + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {'a1','a2','a3','a4', 'timelag','tau'}; + fitParams.paramDescriptions = {'Weight (a1)','Weight (a2)','Weight (a3)', ... + 'Weight (a4)', ... + 'Time before start of rise of hemodynamic function', ... + 'Width of the hemodynamic function (tau parameter of gamma)'}; + fitParams.paramIncDec = [1 1 1 1 0.1 0.5]; + fitParams.paramMin = [zeros(1,4), 0 0]; + fitParams.paramMax = [4.*ones(1,4) inf inf]; + % set min/max and init + fitParams.minParams = [1 1 1 1 0 0] ; + fitParams.maxParams = [4 4 4 4 3 inf ]; + fitParams.initParams = [ones(1,4) fitParams.timelag fitParams.tau]; + if fitParams.diffOfGamma + % parameter names/descriptions and other information for allowing user to set them + fitParams.paramNames = {fitParams.paramNames{:} 'amp2' 'timelag2','tau2'}; + fitParams.paramDescriptions = {fitParams.paramDescriptions{:} 'Amplitude of second gamma for HDR' 'Timelag for second gamma for HDR','tau for second gamma for HDR'}; + fitParams.paramIncDec = [fitParams.paramIncDec(:)' 0.1 0.1 0.5]; + fitParams.paramMin = [fitParams.paramMin(:)' 0 0 0]; + fitParams.paramMax = [fitParams.paramMax(:)' inf inf inf]; + % set min/max and init + fitParams.minParams = [fitParams.minParams 0 0 0]; + fitParams.maxParams = [fitParams.maxParams inf 6 inf]; + fitParams.initParams = [fitParams.initParams fitParams.amplitudeRatio fitParams.timelag2 fitParams.tau2]; + end + + + + + otherwise + disp(sprintf('(pRF_somatoFit:setFitParams) Unknown rfType %s',rfType)); + return + end + + % round constraints + fitParams.minParams = round(fitParams.minParams*10)/10; + fitParams.maxParams = round(fitParams.maxParams*10)/10; + + % handle constraints here + % Check if fit algorithm is one that allows constraints + algorithmsWithConstraints = {'levenberg-marquardt'}; + if any(strcmp(fitParams.algorithm,algorithmsWithConstraints)) + % if constraints allowed then allow user to adjust them here (if they set defaultConstraints) + if isfield(fitParams,'defaultConstraints') && ~fitParams.defaultConstraints + % create a dialog to allow user to set constraints + paramsInfo = {}; + for iParam = 1:length(fitParams.paramNames) + paramsInfo{end+1} = {sprintf('min%s',fitParams.paramNames{iParam}) fitParams.minParams(iParam) sprintf('Minimum for parameter %s (%s)',fitParams.paramNames{iParam},fitParams.paramDescriptions{iParam}) sprintf('incdec=[%f %f]',-fitParams.paramIncDec(iParam),fitParams.paramIncDec(iParam)) sprintf('minmax=[%f %f]',fitParams.paramMin(iParam),fitParams.paramMax(iParam))}; + paramsInfo{end+1} = {sprintf('max%s',fitParams.paramNames{iParam}) fitParams.maxParams(iParam) sprintf('Maximum for parameter %s (%s)',fitParams.paramNames{iParam},fitParams.paramDescriptions{iParam}) sprintf('incdec=[%f %f]',-fitParams.paramIncDec(iParam),fitParams.paramIncDec(iParam)) sprintf('minmax=[%f %f]',fitParams.paramMin(iParam),fitParams.paramMax(iParam))}; + end + params = mrParamsDialog(paramsInfo,'Set parameter constraints'); + % if params is not empty then set them + if isempty(params) + disp(sprintf('(pRF_somatoFit) Using default constraints')); + else + % get the parameter constraints back from the dialog entries + for iParam = 1:length(fitParams.paramNames) + fitParams.minParams(iParam) = params.(sprintf('min%s',fitParams.paramNames{iParam})); + fitParams.maxParams(iParam) = params.(sprintf('max%s',fitParams.paramNames{iParam})); + end + end + end + % Now display parameter constraints + for iParam = 1:length(fitParams.paramNames) + disp(sprintf('(pRF_somatoFit) Parameter %s [min:%f max:%f] (%i:%s)',fitParams.paramNames{iParam},fitParams.minParams(iParam),fitParams.maxParams(iParam),iParam,fitParams.paramDescriptions{iParam})); + end + else + % no constraints allowed + disp(sprintf('(pRF_somatoFit) !!! Fit constraints ignored for algorithm: %s (if you want to constrain the fits, then use: %s) !!!',fitParams.algorithm,cell2mat(algorithmsWithConstraints))); + end +end + +fitParams.nParams = length(fitParams.initParams); + +% optimization parameters +if ~isfield(fitParams,'algorithm') || isempty(fitParams.algorithm) + fitParams.algorithm = 'nelder-mead'; +end +fitParams.optimParams = optimset('MaxIter',inf,'Display',fitParams.optimDisplay); + +% compute number of frames +fitParams.nFrames = size(fitParams.stim{1}.im,3); + +% parameters for converting the stimulus +params = {'xFlip','yFlip','timeShiftStimulus'}; +for i = 1:length(params) + if ~isfield(fitParams,params{i}) || isempty(fitParams.(params{i})) + fitParams.(params{i}) = 0; + end +end + +end + +%%%%%%%%%%%%%%%%%%%%%%%%%% +%% getModelResidual %% +%%%%%%%%%%%%%%%%%%%%%%%%%% +function [residual, modelResponse, rfModel, r, hrf] = getModelResidual(params,tSeries,fitParams,justGetModel, hrfprfcheck) + +%residual = []; +if nargin < 4, justGetModel = 0;end + + +% get the model response +% convert parameter array into a parameter strucutre +p = getFitParams(params,fitParams); + +% compute an RF +rfModel = getRFModel(p,fitParams); +% % % include somato model here: +% rfModel = getSomatoRFModel(p, fitParams); + +%tempfix for crossVal +if ieNotDefined('hrfprfcheck') + hrfprfcheck = 0; +end + +% if crossValcheck == 1 +% rfModel = rfModel'; +% else +% end +% init model response +modelResponse = [];residual = []; + +% create the model for each concat +for i = 1:fitParams.concatInfo.n + % get model response + thisModelResponse = convolveModelWithStimulus(rfModel,fitParams.stim{i}); + + % get a model hrf + + if isfield(fitParams, 'hrfprf') + hrf.hrf = fitParams.hrfprf; + hrf.time = 0:fitParams.framePeriod:p.canonical.lengthInSeconds; % this if 24 seconds, tr 2s fyi... + % normalize to amplitude of 1 + hrf.hrf = hrf.hrf / max(hrf.hrf); + else + hrf = getCanonicalHRF(p.canonical,fitParams.framePeriod); + end + + + %hrf = getCanonicalHRF(p.canonical,fitParams.framePeriod); + + % and convolve in time. + thisModelResponse = convolveModelResponseWithHRF(thisModelResponse,hrf); + + % drop junk frames here + thisModelResponse = thisModelResponse(fitParams.concatInfo.totalJunkedFrames(i)+1:end); + + % apply concat filtering + if isfield(fitParams,'applyFiltering') && fitParams.applyFiltering + thisModelResponse = applyConcatFiltering(thisModelResponse,fitParams.concatInfo,i); + else + % with no filtering, just remove mean + thisModelResponse = thisModelResponse - mean(thisModelResponse); + end + + %if ~justGetModel + %if isempty(justGetModel) + if isempty(justGetModel) + justGetModel = 0; + end + + if justGetModel == 0 + % compute correlation of this portion of the model response with time series + thisTSeries = tSeries(fitParams.concatInfo.runTransition(i,1):fitParams.concatInfo.runTransition(i,2)); + thisTSeries = thisTSeries - mean(thisTSeries); + + % check here for length + if length(thisTSeries) ~= length(thisModelResponse) + disp(sprintf('(pRFFit:getModelResidual) Voxel tSeries length of %i does not match model length of %i. This can happen, for instance, if the tSense factor was not set correctly or junk frames was not set correctly.',length(thisTSeries),length(thisModelResponse))); + keyboard + end + + r(i) = corr(thisTSeries(:),thisModelResponse(:)); + + if fitParams.betaEachScan + % scale and offset the model to best match the tSeries + [thisModelResponse thisResidual] = scaleAndOffset(thisModelResponse',thisTSeries(:)); + else + thisResidual = []; + end + else + thisResidual = []; + end + + % make into a column array + modelResponse = [modelResponse;thisModelResponse(:)]; + residual = [residual;thisResidual(:)]; +end + +% return model only +if justGetModel,return,end + +% scale the whole time series +if ~isfield(fitParams, 'hrfprf') + if ~fitParams.betaEachScan + [modelResponse residual] = scaleAndOffset(modelResponse,tSeries(:)); + end +end + + +% display the fit +if fitParams.dispFit + dispModelFit(params,fitParams,modelResponse,tSeries,rfModel); +end + + +% scale and offset (manual) +if fitParams.getModelResponse == 1 + + if ~any(isnan(modelResponse)) + mref = mean(tSeries); + stdRef = std(tSeries); + mSig = mean(modelResponse); + stdSig = std(modelResponse); + modelResponse = ((modelResponse - mSig)/stdSig) * stdRef + mref; + + residual = tSeries-modelResponse; + else + residual = tSeries; + end + +elseif fitParams.getModelResponse ~= 1 + if hrfprfcheck == 1 + if ~any(isnan(modelResponse)) + % warning('off', 'MATLAB:rankDeficientMatrix'); + X = modelResponse(:); + X(:,2) = 1; + + b = X \ tSeries; % backslash linear regression + %b = pinv(X) * tSeries; + modelResponse = X * b; + residual = tSeries-modelResponse; + else + residual = tSeries; + end + %modelResponse = newSig; + end +end + + + +% for nelder-mead just compute correlation and return 1-4 +if strcmp(lower(fitParams.algorithm),'nelder-mead') + residual = -corr(modelResponse,tSeries); + % disp(sprintf('(pRFFit:getModelResidual) r: %f',residual)); +end + +end + + +%%%%%%%%%%%%%%%%%%%%%% +%% dispModelFit % +%%%%%%%%%%%%%%%%%%%%%% +function dispModelFit(params,fitParams,modelResponse,tSeries,rfModel) + +mlrSmartfig('pRFFit_getModelResidual','reuse'); +clf +subplot(4,4,[1:3 5:7 9:11 13:15]); +%plot(fitParams.stimT(fitParams.junkFrames+1:end),tSeries,'k-'); +plot(tSeries,'k-'); +hold on +%plot(fitParams.stimT(fitParams.junkFrames+1:end),modelResponse,'r-'); +plot(modelResponse,'r-'); +xlabel('Time (sec)'); +ylabel('BOLD (% sig change)'); +p = getFitParams(params,fitParams); +titleStr = sprintf('x: %s y: %s rfHalfWidth: %s',mlrnum2str(p.x),mlrnum2str(p.y),mlrnum2str(p.std)); +titleStr = sprintf('%s\n(timelag: %s tau: %s exponent: %s)',titleStr,mlrnum2str(p.canonical.timelag),mlrnum2str(p.canonical.tau),mlrnum2str(p.canonical.exponent)); +if p.canonical.diffOfGamma + titleStr = sprintf('%s - %s x (timelag2: %s tau2: %s exponent2: %s)',titleStr,mlrnum2str(p.canonical.amplitudeRatio),mlrnum2str(p.canonical.timelag2),mlrnum2str(p.canonical.tau2),mlrnum2str(p.canonical.exponent2)); +end +title(titleStr); +axis tight + +subplot(4,4,[8 12 16]); +imagesc(fitParams.stimX(:,1),fitParams.stimY(1,:),flipud(rfModel')); +axis image; +hold on +hline(0);vline(0); + +subplot(4,4,4);cla +p = getFitParams(params,fitParams); +canonical = getCanonicalHRF(p.canonical,fitParams.framePeriod); +plot(canonical.time,canonical.hrf,'k-') +if exist('myaxis') == 2,myaxis;end + +end + +%%%%%%%%%%%%%%%%%%%%%%%% +%% scaleAndOffset % +%%%%%%%%%%%%%%%%%%%%%%%% +function [modelResponse residual] = scaleAndOffset(modelResponse,tSeries) + +designMatrix = modelResponse; +designMatrix(:,2) = 1; + +% get beta weight for the modelResponse +if ~any(isnan(modelResponse)) + beta = pinv(designMatrix)*tSeries; + beta(1) = max(beta(1),0); + modelResponse = designMatrix*beta; + residual = tSeries-modelResponse; +else + residual = tSeries; +end +end + +%%%%%%%%%%%%%%%%%%%%%% +%% getFitParams %% +%%%%%%%%%%%%%%%%%%%%%% +function p = getFitParams(params,fitParams) + +p.rfType = fitParams.rfType; + +switch (fitParams.rfType) + case 'gaussian' + p.x = params(1); + p.y = params(2); + p.std = params(3); + % use a fixed single gaussian + p.canonical.type = 'gamma'; + p.canonical.lengthInSeconds = 25; + p.canonical.timelag = fitParams.timelag; + p.canonical.tau = fitParams.tau; + p.canonical.exponent = fitParams.exponent; + p.canonical.offset = 0; + p.canonical.diffOfGamma = fitParams.diffOfGamma; + p.canonical.amplitudeRatio = fitParams.amplitudeRatio; + p.canonical.timelag2 = fitParams.timelag2; + p.canonical.tau2 = fitParams.tau2; + p.canonical.exponent2 = fitParams.exponent2; + p.canonical.offset2 = 0; + case 'gaussian-hdr' + p.x = params(1); + p.y = params(2); + p.std = params(3); + % use a fixed single gaussian + p.canonical.type = 'gamma'; + p.canonical.lengthInSeconds = 25; + p.canonical.timelag = params(4); + p.canonical.tau = params(5); + p.canonical.exponent = fitParams.exponent; + p.canonical.offset = 0; + p.canonical.diffOfGamma = fitParams.diffOfGamma; + if fitParams.diffOfGamma + p.canonical.amplitudeRatio = params(6); + p.canonical.timelag2 = params(7); + p.canonical.tau2 = params(8); + p.canonical.exponent2 = fitParams.exponent2; + p.canonical.offset2 = 0; + end + case 'gaussian-hdr-double' + p.x = params(1); + p.y = params(2); + p.stdx = params(3); + p.stdy = params(4); + % use a fixed single gaussian + p.canonical.type = 'gamma'; + p.canonical.lengthInSeconds = 25; + p.canonical.timelag = params(5); + p.canonical.tau = params(6); + p.canonical.exponent = fitParams.exponent; + p.canonical.offset = 0; + p.canonical.diffOfGamma = fitParams.diffOfGamma; + if fitParams.diffOfGamma + p.canonical.amplitudeRatio = params(7); + p.canonical.timelag2 = params(8); + p.canonical.tau2 = params(9); + p.canonical.exponent2 = fitParams.exponent2; + p.canonical.offset2 = 0; + end + case 'gaussian-surround' + p.x = params(1); + p.y = params(2); + p.std = params(3); + p.surrAmp = params(6); + p.surrWidth = params(7); + % use a fixed single gaussian + p.canonical.type = 'gamma'; + p.canonical.lengthInSeconds = 25; + p.canonical.timelag = params(4); + p.canonical.tau = params(5); + p.canonical.exponent = fitParams.exponent; + p.canonical.offset = 0; + p.canonical.diffOfGamma = fitParams.diffOfGamma; + if fitParams.diffOfGamma + p.canonical.amplitudeRatio = params(8); + p.canonical.timelag2 = params(9); + p.canonical.tau2 = params(10); + p.canonical.exponent2 = fitParams.exponent2; + p.canonical.offset2 = 0; + end + case 'nine-param' + p.weights = params(1:9); + % [~, p.x] = max(mean(params([1 2 3; 4 5 6; 7 8 9]),1)); + % [~, p.y] = max(mean(params([1 2 3; 4 5 6; 7 8 9]),2)); + % + % [~, p.x] = max(mean(params([1 2 3; 4 5 6; 7 8 9]))); + % [~, p.y] = max(mean(params([1 2 3; 4 5 6; 7 8 9]'))); + % use a fixed single gaussian + p.canonical.type = 'gamma'; + p.canonical.lengthInSeconds = 25; + p.canonical.timelag = fitParams.timelag; + p.canonical.tau = fitParams.tau; + p.canonical.exponent = fitParams.exponent; + p.canonical.offset = 0; + p.canonical.diffOfGamma = fitParams.diffOfGamma; + p.canonical.amplitudeRatio = fitParams.amplitudeRatio; + p.canonical.timelag2 = fitParams.timelag2; + p.canonical.tau2 = fitParams.tau2; + p.canonical.exponent2 = fitParams.exponent2; + p.canonical.offset2 = 0; + case 'nine-param-hdr' + p.weights = params(1:9); + % [~, p.x] = max(mean(params([1 2 3; 4 5 6; 7 8 9]),1)); + % [~, p.y] = max(mean(params([1 2 3; 4 5 6; 7 8 9]),2)); + % [~, p.x] = max(mean(params([1 2 3; 4 5 6; 7 8 9]))); + % [~, p.y] = max(mean(params([1 2 3; 4 5 6; 7 8 9]'))); + % p.std = var(params(:)); + % + + + %pRFweights = p.weights; + + %[com, momentOfInertia ] = centerOfMass(pRFweights); + + %p.x = com(1); + %p.y = com(2); + %p.std = momentOfInertia(1); + + p.canonical.type = 'gamma'; + p.canonical.lengthInSeconds = 25; + p.canonical.timelag = params(10); + p.canonical.tau = params(11); + p.canonical.exponent = fitParams.exponent; + p.canonical.offset = 0; + p.canonical.diffOfGamma = fitParams.diffOfGamma; + if fitParams.diffOfGamma + p.canonical.amplitudeRatio = params(12); + p.canonical.timelag2 = params(13); + p.canonical.tau2 = params(14); + p.canonical.exponent2 = fitParams.exponent2; + p.canonical.offset2 = 0; + end + case {'gaussian-1D','gaussian-1D-transpose'} + + if size(fitParams.stimX,1) == 4 + p.meanOne = params(1); + p.meanTwo = params(2); + p.meanThr = params(3); + p.meanFour = params(4); + p.amp1 = 1; + p.amp2 = params(9); + p.amp3 = params(10); + p.amp4 = params(11); + p.stdOne = params(5); + p.stdTwo = params(6); + p.stdThr = params(7); + p.stdFour = params(8); + + % use a fixed single gaussian + p.canonical.type = 'gamma'; + p.canonical.lengthInSeconds = 25; + p.canonical.timelag = params(12); + p.canonical.tau = params(13); + p.canonical.exponent = fitParams.exponent; + p.canonical.offset = 0; + p.canonical.diffOfGamma = fitParams.diffOfGamma; + if fitParams.diffOfGamma + p.canonical.amplitudeRatio = params(14); + p.canonical.timelag2 = params(15); + p.canonical.tau2 = params(16); + p.canonical.exponent2 = fitParams.exponent2; + p.canonical.offset2 = 0; + end + + elseif size(fitParams.stimX,1) == 3 + p.meanOne = params(1); + p.meanTwo = params(2); + p.meanThr = params(3); + + p.amp1 = 1; + p.amp2 = params(7); + p.amp3 = params(8); + + p.stdOne = params(4); + p.stdTwo = params(5); + p.stdThr = params(6); + + + % use a fixed single gaussian + p.canonical.type = 'gamma'; + p.canonical.lengthInSeconds = 25; + p.canonical.timelag = params(9); + p.canonical.tau = params(10); + p.canonical.exponent = fitParams.exponent; + p.canonical.offset = 0; + p.canonical.diffOfGamma = fitParams.diffOfGamma; + if fitParams.diffOfGamma + p.canonical.amplitudeRatio = params(11); + p.canonical.timelag2 = params(12); + p.canonical.tau2 = params(13); + p.canonical.exponent2 = fitParams.exponent2; + p.canonical.offset2 = 0; + end + + elseif size(fitParams.stimX,2) == 5 || size(fitParams.stimX,2) == 4 + % p.meanOne = params(1); + % p.meanTwo = params(2); + % p.meanThr = params(3); + % p.meanFour = params(4); + % p.meanFive = params(5); + % p.amp1 = 1; + % p.amp2 = params(11); + % p.amp3 = params(12); + % p.amp4 = params(13); + % p.amp5 = params(14); + % p.stdOne = params(6); + % p.stdTwo = params(7); + % p.stdThr = params(8); + % p.stdFour = params(9); + % p.stdFive = params(10); + % + % % use a fixed single gaussian + % p.canonical.type = 'gamma'; + % p.canonical.lengthInSeconds = 25; + % p.canonical.timelag = params(15); + % p.canonical.tau = params(16); + % p.canonical.exponent = fitParams.exponent; + % p.canonical.offset = 0; + % p.canonical.diffOfGamma = fitParams.diffOfGamma; + % if fitParams.diffOfGamma + % p.canonical.amplitudeRatio = params(17); + % p.canonical.timelag2 = params(18); + % p.canonical.tau2 = params(19); + % p.canonical.exponent2 = fitParams.exponent2; + % p.canonical.offset2 = 0; + % end + p.meanOne = params(1); + p.amp1 = params(3); + p.stdOne = params(2); + + % use a fixed single gaussian + p.canonical.type = 'gamma'; + p.canonical.lengthInSeconds = 25; + p.canonical.timelag = params(4); + p.canonical.tau = params(5); + p.canonical.exponent = fitParams.exponent; + p.canonical.offset = 0; + p.canonical.diffOfGamma = fitParams.diffOfGamma; + if fitParams.diffOfGamma + p.canonical.amplitudeRatio = params(6); + p.canonical.timelag2 = params(7); + p.canonical.tau2 = params(8); + p.canonical.exponent2 = fitParams.exponent2; + p.canonical.offset2 = 0; + end + + + end + + case 'sixteen-hdr' + p.weights = params(1:16); + %[~, p.x] = max(mean(params([1 2 3 4; 5 6 7 8; 9 10 11 12; 13 14 15 16]))); + + %[~, p.y] = max(mean(params([1 2 3 4; 5 6 7 8; 9 10 11 12; 13 14 15 16]'))); + %p.std = var(params(1:16)); + p.canonical.type = 'gamma'; + p.canonical.lengthInSeconds = 25; + p.canonical.timelag = params(17); + p.canonical.tau = params(18); + p.canonical.exponent = fitParams.exponent; + p.canonical.offset = 0; + p.canonical.diffOfGamma = fitParams.diffOfGamma; + if fitParams.diffOfGamma + p.canonical.amplitudeRatio = params(19); + p.canonical.timelag2 = params(20); + p.canonical.tau2 = params(21); + p.canonical.exponent2 = fitParams.exponent2; + p.canonical.offset2 = 0; + end + + case 'gaussian-1D-orthotips' + + p.meanOne = params(1); + p.std = params(2); + p.amp = params(3); + p.y = 1; + + % use a fixed single gaussian + p.canonical.type = 'gamma'; + p.canonical.lengthInSeconds = 25; + p.canonical.timelag = params(4); + p.canonical.tau = params(5); + p.canonical.exponent = fitParams.exponent; + p.canonical.offset = 0; + p.canonical.diffOfGamma = fitParams.diffOfGamma; + if fitParams.diffOfGamma + p.canonical.amplitudeRatio = params(6); + p.canonical.timelag2 = params(7); + p.canonical.tau2 = params(8); + p.canonical.exponent2 = fitParams.exponent2; + p.canonical.offset2 = 0; + end + % + % case 'gaussian-tips' + % p.meanOne = params(1); + % p.amp = params(2); + % p.std = params(3); + % p.y = 1; + % % use a fixed single gaussian + % p.canonical.type = 'gamma'; + % p.canonical.lengthInSeconds = 25; + % p.canonical.timelag = fitParams.timelag; + % p.canonical.tau = fitParams.tau; + % p.canonical.exponent = fitParams.exponent; + % p.canonical.offset = 0; + % p.canonical.diffOfGamma = fitParams.diffOfGamma; + % p.canonical.amplitudeRatio = fitParams.amplitudeRatio; + % p.canonical.timelag2 = fitParams.timelag2; + % p.canonical.tau2 = fitParams.tau2; + % p.canonical.exponent2 = fitParams.exponent2; + % p.canonical.offset2 = 0; + % + % case 'gaussian-tips-hdr' + % p.meanOne = params(1); + % p.amp = params(2); + % p.std = params(3); + % p.y = 1; + % % use a fixed single gaussian + % p.canonical.type = 'gamma'; + % p.canonical.lengthInSeconds = 25; + % p.canonical.timelag = params(4); + % p.canonical.tau = params(5); + % p.canonical.exponent = fitParams.exponent; + % p.canonical.offset = 0; + % p.canonical.diffOfGamma = fitParams.diffOfGamma; + % if fitParams.diffOfGamma + % p.canonical.amplitudeRatio = params(6); + % p.canonical.timelag2 = params(7); + % p.canonical.tau2 = params(8); + % p.canonical.exponent2 = fitParams.exponent2; + % p.canonical.offset2 = 0; + % end + % + case 'five-hdr' + p.weights = params(1:5); + %[~, p.x] = max(params([1 2 3 4 5])); + %[~, p.y] = max(mean(params([1 2 3 4 5]'))); + %p.x = max(mean(params([1 2 3 4 5]))); + %p.y = 1; + %p.std = var(params(1:5)); + + p.canonical.type = 'gamma'; + p.canonical.lengthInSeconds = 25; + p.canonical.timelag = params(6); + p.canonical.tau = params(7); + p.canonical.exponent = fitParams.exponent; + p.canonical.offset = 0; + p.canonical.diffOfGamma = fitParams.diffOfGamma; + if fitParams.diffOfGamma + p.canonical.amplitudeRatio = params(8); + p.canonical.timelag2 = params(9); + p.canonical.tau2 = params(10); + p.canonical.exponent2 = fitParams.exponent2; + p.canonical.offset2 = 0; + end + + case 'four-hdr' + p.weights = params(1:4); + [~, p.x] = max(params([1 2 3 4 ])); + %[~, p.y] = max(mean(params([1 2 3 4 5]'))); + %p.x = max(mean(params([1 2 3 4 5]))); + p.y = 1; + p.std = var(params(1:4)); + + p.canonical.type = 'gamma'; + p.canonical.lengthInSeconds = 25; + p.canonical.timelag = params(5); + p.canonical.tau = params(6); + p.canonical.exponent = fitParams.exponent; + p.canonical.offset = 0; + p.canonical.diffOfGamma = fitParams.diffOfGamma; + if fitParams.diffOfGamma + p.canonical.amplitudeRatio = params(7); + p.canonical.timelag2 = params(8); + p.canonical.tau2 = params(9); + p.canonical.exponent2 = fitParams.exponent2; + p.canonical.offset2 = 0; + end + + + otherwise + disp(sprintf('(pRFFit) Unknown rfType %s',rfType)); +end + +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% convolveModelWithStimulus %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function modelResponse = convolveModelWithStimulus(rfModel,stim) + +% get number of frames +nFrames = size(stim.im,3); + +% preallocate memory +modelResponse = zeros(1,nFrames); + +% check matrix dims +if isequal(size(rfModel), size(stim.im(:,:,1)) ) + + for frameNum = 1:nFrames + % multipy the stimulus frame by frame with the rfModel + % and take the sum + modelResponse(frameNum) = sum(sum(rfModel.*stim.im(:,:,frameNum))); + end + +elseif ~isequal(size(rfModel), size(stim.im(:,:,1)) ) + + rfModel = rfModel'; + + for frameNum = 1:nFrames + % multipy the stimulus frame by frame with the rfModel + % and take the sum + modelResponse(frameNum) = sum(sum(rfModel.*stim.im(:,:,frameNum))); + end +end + +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% convolveModelResponseWithHRF %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function modelTimecourse = convolveModelResponseWithHRF(modelTimecourse,hrf) + +n = length(modelTimecourse); +modelTimecourse = conv(modelTimecourse,hrf.hrf); +modelTimecourse = modelTimecourse(1:n); + +end + +%%%%%%%%%%%%%%%%%%%%% +%% getGammaHRF %% +%%%%%%%%%%%%%%%%%%%%% +function fun = getGammaHRF(time,p) + +fun = thisGamma(time,1,p.timelag,p.offset,p.tau,p.exponent)/100; +% add second gamma if this is a difference of gammas fit +if p.diffOfGamma + fun = fun - thisGamma(time,p.amplitudeRatio,p.timelag2,p.offset2,p.tau2,p.exponent2)/100; +end + +end + +%%%%%%%%%%%%%%%%%%% +%% thisGamma %% +%%%%%%%%%%%%%%%%%%% +function gammafun = thisGamma(time,amplitude,timelag,offset,tau,exponent) + +exponent = round(exponent); +% gamma function +gammafun = (((time-timelag)/tau).^(exponent-1).*exp(-(time-timelag)/tau))./(tau*factorial(exponent-1)); + +% negative values of time are set to zero, +% so that the function always starts at zero +gammafun(find((time-timelag) < 0)) = 0; + +% normalize the amplitude +if (max(gammafun)-min(gammafun))~=0 + gammafun = (gammafun-min(gammafun)) ./ (max(gammafun)-min(gammafun)); +end +gammafun = (amplitude*gammafun+offset); + +end + + +%%%%%%%%%%%%%%%%%%%%%%%%% +%% getCanonicalHRF %% +%%%%%%%%%%%%%%%%%%%%%%%%% +function hrf = getCanonicalHRF(params,sampleRate) + +hrf.time = 0:sampleRate:params.lengthInSeconds; +hrf.hrf = getGammaHRF(hrf.time,params); + +% normalize to amplitude of 1 +hrf.hrf = hrf.hrf / max(hrf.hrf); + +end + +%%%%%%%%%%%%%%%%%%%% +%% getRFModel %% +%%%%%%%%%%%%%%%%%%%% +function rfModel = getRFModel(params,fitParams) + +rfModel = []; + +% now generate the rfModel +switch fitParams.rfType + case {'gaussian','gaussian-hdr','gaussian-hdr-double','gaussian-1D','gaussian-1D-transpose','gaussian-surround', 'gaussian-1D-orthotips'} + rfModel = makeRFGaussian(params,fitParams); + case {'nine-param','nine-param-hdr'} + rfModel = makeRFNineParam(params,fitParams); + case {'sixteen-hdr'} + rfModel = makeRFSixteenParam(params, fitParams); + case {'five-hdr', 'four-hdr'} + rfModel = makeRFFiveParam(params, fitParams); + %rfModel = params.weights'; + otherwise + disp(sprintf('(pRFFit:getRFModel) Unknown rfType: %s',fitParams.rfType)); +end + +end + +%%%%%%%%%%%%%%%%%%%%%%%% +%% makeRFGaussian %% +%%%%%%%%%%%%%%%%%%%%%%%% +function rfModel = makeRFGaussian(params,fitParams) + +% compute rf +switch fitParams.rfType + case {'gaussian-1D','gaussian-1D-transpose'} + %params.x = max(params.x); + %rfModel = exp(-(((fitParams.stimX-params.x).^2)/(2*(params.std^2))+((fitParams.stimY-params.y).^2)/(2*(params.std^2)))); + %oneD = fitParams.stimX(:,1); + + % Want to implement a one dimensional gaussian along a digit + % This will give a 1x3 matrix, need a 3x3 for the stimulus + % convolution. Not sure if this makes sense... + %rfModel = normpdf(oneD, mean(oneD), std(oneD)); + %rfModel = [rfModel rfModel rfModel]; + %x = zeros(3); + %rfModel = [x(:,1) rfModel x(:,1)]; %digit 2 + %rfModel = [rfModel x(:,1:2)]; % digit1 + %rfModel = [x(:,1:2), rfModel]; % digit3 + %rfModel = normpdf(fitParams.stimX, params.mean, params.std); + + % Usage: p = [height center SD offset] + + if size(fitParams.stimX,1) == 4 + X = linspace(1,4,4); + params.amp1 = 1; + pone = [params.amp1 params.meanOne params.stdOne 0]; + ptwo = [params.amp2 params.meanTwo params.stdTwo 0]; + pthr = [params.amp3 params.meanThr params.stdThr 0]; + pFour = [params.amp4 params.meanFour params.stdFour 0]; + %[R,Y] = meshgrid(1:4,1:4); + % + % this WAS the version % Z = [gauss(pone,X); gauss(ptwo,X); gauss(pthr,X); gauss(pFour,X)]; + % @DS suggests this .. this means each digit inhabits a COLUMN + % in the model and should be consistent with pRFStimImage + % convention. + Z = [gauss(pone,X)', gauss(ptwo,X)', gauss(pthr,X)', gauss(pFour,X)']; + + if strcmpi(fitParams.rfType, 'gaussian-1D-transpose') + %rfModel = (Z); %reality check (flip base to tip) using flipud + rfModel = transpose(Z); % try for orthoGaussian + else + rfModel = Z; + end + + %surf(R,Y,Z) + elseif size(fitParams.stimX,1) == 3 + X = linspace(1,3,3); + params.amp1 = 1; + pone = [params.amp1 params.meanOne params.stdOne 0]; + ptwo = [params.amp2 params.meanTwo params.stdTwo 0]; + pthr = [params.amp3 params.meanThr params.stdThr 0]; + + %[R,Y] = meshgrid(1:3,1:3); + Z = [gauss(pone,X)', gauss(ptwo,X)', gauss(pthr,X)']; + %rfModel = (Z); + if strcmpi(fitParams.rfType, 'gaussian-1D-transpose') + %rfModel = (Z); %reality check (flip base to tip) using flipud + rfModel = transpose(Z); % try for orthoGaussian + else + rfModel = Z; + end + %rfModel = transpose(Z); + % add a transpose here, if we want to do 1D in the orthogonal + % direction! + + + elseif size(fitParams.stimX,2) == 5 || size(fitParams.stimX,2) == 4 +% X = linspace(1,5,5); +% params.amp1 = 1; +% pone = [params.amp1 params.meanOne params.stdOne 0]; +% ptwo = [params.amp2 params.meanTwo params.stdTwo 0]; +% pthr = [params.amp3 params.meanThr params.stdThr 0]; +% pFour = [params.amp4 params.meanFour params.stdFour 0]; +% pFive = [params.amp5 params.meanFive params.stdFive 0]; +% Z = [gauss(pone,X)', gauss(ptwo,X)', gauss(pthr,X)', gauss(pFour,X)', gauss(pFive,X)']; +% +% if strcmpi(fitParams.rfType, 'gaussian-1D-transpose') +% %rfModel = (Z); %reality check (flip base to tip) using flipud +% rfModel = transpose(Z); % try for orthoGaussian +% else +% rfModel = Z; +% end + X = linspace(1,size(fitParams.stimX,2),size(fitParams.stimX,2)); + %params.amp1 = 1; + pone = [params.amp1 params.meanOne params.stdOne 0]; + %ptwo = [params.amp2 params.meanTwo params.stdTwo 0]; + %pthr = [params.amp3 params.meanThr params.stdThr 0]; + %pFour = [params.amp4 params.meanFour params.stdFour 0]; + %pFive = [params.amp5 params.meanFive params.stdFive 0]; + %Z = [gauss(pone,X)', gauss(ptwo,X)', gauss(pthr,X)', gauss(pFour,X)', gauss(pFive,X)']; + Z = gauss(pone,X)'; + + if strcmpi(fitParams.rfType, 'gaussian-1D-transpose') + %rfModel = (Z); %reality check (flip base to tip) using flipud + rfModel = transpose(Z); % try for orthoGaussian + else + rfModel = Z; + end + + + + + + + end + + + case {'gaussian-surround'} + % adds inhibitory surround to the 2D gaussian + p = [1.5 params.x params.y sigma1 sigma2]; + [ZDOG, X, Y] = dogFit(1:3,1:3,p); + rfModel = ZDOG; + %surf(X,Y,ZDOG) + + case {'gaussian-1D-orthotips'} % CHECK THIS! + + mylen = length(fitParams.stimX); + + % if size(fitParams.stimX,1) == 4 + X = 1:mylen; + pone = [params.amp params.meanOne params.std 0]; + Z = gauss(pone,X); + rfModel = Z; %reality check (flip base to tip) using flipud + %surf(R,Y,Z) + % else + % X = linspace(1,1,mylen); + % params.amp1 = 1; + % pone = [params.amp1 params.meanOne params.stdOne 0]; + % %[R,Y] = meshgrid(1:3,1:3); + % Z = gauss(pone,X)'; + % rfModel = Z; + % % add a transpose here, if we want to do 1D in the orthogonal + % % direction! + % end + + % + % case {'gaussian-tips'} + % X = 1:5; + % params.y = 1; + % pone = [params.amp params.meanOne params.std 0]; + % [R,Y] = meshgrid(1:5,1:1); + % Z = gauss(pone,X); + % rfModel = Z; + % case {'gaussian-tips-hdr'} + % X = 1:5; + % params.y = 1; + % pone = [params.amp params.meanOne params.std 0]; + % [R,Y] = meshgrid(1:5,1:1); + % Z = gauss(pone,X); + % rfModel = Z; + + %figure; plot(R,Z) + + case {'gaussian-hdr-double'} + rfModel = exp(-(((fitParams.stimX-params.x).^2)/(2*(params.stdx^2))+((fitParams.stimY-params.y).^2)/(2*(params.stdy^2)))); + % do we need to flip this top to bottom? + + otherwise + rfModel = exp(-(((fitParams.stimX-params.x).^2)/(2*(params.std^2))+((fitParams.stimY-params.y).^2)/(2*(params.std^2)))); + % try adding std + + +end + +end + +%%%%%%%%%%%%%%%%%%%%%%%% +%% makeSomatoPRF %% +%%%%%%%%%%%%%%%%%%%%%%%% +function rfModel = makeRFNineParam(params,fitParams) +% makeRFNineParam - turn parameters into a pRF (here, 3x3) +% +% 9 parameters -- all independent, needs fixing... +% +% this function makes an appropriately shaped pRF from parameters + +% turn the list into a grid +rfModel = reshape(params.weights, [3 3]); + +% other versions of this might take another list of params, pIn (e.g. x0, +% y0, sigma0) into a 3x3 pOut. It depends what shape we want to impose. +end + +%%%%%%%%%%%%%%%%%%%%%%%% +%% makeSomatoPRF16 %% +%%%%%%%%%%%%%%%%%%%%%%%% +function rfModel = makeRFSixteenParam(params,fitParams) +% makeRFNineParam - turn parameters into a pRF (here,4x4) +% +% 16 parameters -- all independent, needs fixing... +% +% this function makes an appropriately shaped pRF from parameters + +% turn the list into a grid +rfModel = reshape(params.weights, [4 4]); + +% other versions of this might take another list of params, pIn (e.g. x0, +% y0, sigma0) into a 3x3 pOut. It depends what shape we want to impose. + +end + +function rfModel = makeRFFiveParam(params,fitParams) +rfModel = reshape(params.weights, [1 5]); +end + +%%%%%%%%%%%%%%%%%%% +%% parseArgs % +%%%%%%%%%%%%%%%%%%% +function [v ,scanNum, x, y, s, fitParams, tSeries, hrfprf] = parseArgs(args) + +v = [];scanNum=[];x=[];y=[];s=[];fitParams=[];tSeries = [];hrfprf = []; + +% check for calling convention from interrogator +if (length(args) >= 7) && isnumeric(args{6}) + v = args{1}; + %overlayNum = args{2}; + scanNum = args{3}; + x = args{4}; + y = args{5}; + s = args{6}; + %roi = args{7}; + hrfprf = args{7}; + fitParams.dispFit = true; + fitParams.optimDisplay = 'final'; + fitParams.algorithm = 'nelder-mead'; + fitParams.getModelResponse = false; + fitParams.prefit = []; + fitParams.xFlipStimulus = 0; + fitParams.yFlipStimulus = 0; + fitParams.timeShiftStimulus = 0; + fitParams.betaEachScan = false; + fitParams.justGetStimImage = false; + fitParams.returnPrefit = false; + fitParams.verbose = 1; + fitParams.timelag = 1; + fitParams.tau = 0.6; + fitParams.exponent = 6; + clearConstraints = false; + getArgs({args{8:end}},{'fitTypeParams=[]'}); + if isempty(fitTypeParams) + % no fit type params, check if we have them set in + % the global (this is useful so that when called as an + % interrogator we don't have to keep setting them + global gpRFFitTypeParams + % if user is holding shift, then reget parameters + if ~isempty(gcf) && any(strcmp(get(gcf,'CurrentModifier'),'shift')) + gpRFFitTypeParams = []; + end + % get the parameters from the user interface if not already set + if isempty(gpRFFitTypeParams) + fitTypeParams = pRFGUI('pRFFitParamsOnly=1','v',v); + if isempty(fitTypeParams) + v = []; + return + end + gpRFFitTypeParams = fitTypeParams; + % flag to clear the constraints + clearConstraints = true; + else + % otherwise grab old ones + disp(sprintf('(pRFFit) Using already set parameters to compute pRFFit. If you want to use different parameters, hold shift down as you click the next voxel')); + fitTypeParams = gpRFFitTypeParams; + end + end + if ~isempty(fitTypeParams) + % if fitTypeParams is passed in (usually from pRF / pRFGUI) then + % grab parameters off that structure + fitTypeParamsFields = fieldnames(fitTypeParams); + for i = 1:length(fitTypeParamsFields) + fitParams.(fitTypeParamsFields{i}) = fitTypeParams.(fitTypeParamsFields{i}); + end + end + + % normal calling convention +elseif length(args) >= 5 + v = args{1}; + scanNum = args{2}; + x = args{3}; + y = args{4}; + s = args{5}; + % parse anymore argumnets + dispFit=[];stim = [];getModelResponse = [];params = [];concatInfo = [];prefit = []; + xFlip=[];yFlip=[];timeShiftStimulus=[];rfType=[];betaEachScan=[];fitTypeParams = []; + dispIndex = [];dispN = [];returnPrefit = [];tSeries=[];quickPrefit=[];junkFrames=[]; + verbose = [];justGetStimImage = [];framePeriod = []; + getArgs({args{6:end}},{'dispFit=0','stim=[]','getModelResponse=0','params=[]','concatInfo=[]','prefit=[]','xFlipStimulus=0','yFlipStimulus=0','timeShiftStimulus=0','rfType=gaussian','betaEachScan=0','fitTypeParams=[]','justGetStimImage=[]','verbose=1','dispIndex=[]','dispN=[]','returnPrefit=0','quickPrefit=0','tSeries=[]','junkFrames=[]','framePeriod=[]','paramsInfo=[]', 'hrfprf=[]'}); + % default to display fit + fitParams.dispFit = dispFit; + fitParams.stim = stim; + fitParams.optimDisplay = 'off'; + fitParams.getModelResponse = getModelResponse; + fitParams.params = params; + fitParams.concatInfo = concatInfo; + fitParams.prefit = prefit; + fitParams.xFlipStimulus = xFlipStimulus; + fitParams.yFlipStimulus = yFlipStimulus; + fitParams.timeShiftStimulus = timeShiftStimulus; + fitParams.rfType = rfType; + fitParams.betaEachScan = betaEachScan; + fitParams.justGetStimImage = justGetStimImage; + fitParams.verbose = verbose; + fitParams.returnPrefit = returnPrefit; + fitParams.junkFrames = junkFrames; + fitParams.framePeriod = framePeriod; + % now read in all the fields in the paramsInfo + if ~isempty(paramsInfo) + paramsInfoFields = fieldnames(paramsInfo); + for iField = 1:length(paramsInfoFields) + fitParams.(paramsInfoFields{iField}) = paramsInfo.(paramsInfoFields{iField}); + end + end + if ~isempty(fitTypeParams) + % if fitTypeParams is passed in (usually from pRF / pRFGUI) then + % grab parameters off that structure + fitTypeParamsFields = fieldnames(fitTypeParams); + for i = 1:length(fitTypeParamsFields) + fitParams.(fitTypeParamsFields{i}) = fitTypeParams.(fitTypeParamsFields{i}); + end + end + if ~isempty(dispIndex) && ~isempty(dispN) + % create a display string. Note that we use sprintf twice here so that + % we can create a string with the proper amount of space padding the index + % so that each row always displays as the same length string + prefitOnlyStr = ''; + if isfield(fitParams,'prefitOnly') && fitParams.prefitOnly + prefitOnlyStr = ' (prefit only)'; + end + fitParams.dispstr = sprintf(sprintf('Voxel %%%i.f/%%i%%s: ',length(sprintf('%i',dispN))),dispIndex,dispN,prefitOnlyStr); + end + if getModelResponse && isempty(params) + disp(sprintf('(pRF_somatoFit) Must pass in params when using getModelResponse')); + fitParams.getModelResponse = false; + end +else + help pRFFit; +end + +% some default parameters +if ~isfield(fitParams,'prefitOnly') || isempty(fitParams.prefitOnly) + fitParams.prefitOnly = false; +end +if ~isfield(fitParams,'dispstr') + fitParams.dispstr = ''; +end +if ~isfield(fitParams,'quickPrefit') || isempty(fitParams.quickPrefit) + fitParams.quickPrefit = false; +end +if ~isfield(fitParams,'verbose') || isempty(fitParams.verbose) + fitParams.verbose = true; +end + +% get some info about the scanNum +if ~isfield(fitParams,'framePeriod') || isempty(fitParams.framePeriod) + fitParams.framePeriod = viewGet(v,'framePeriod'); +end +if ~isfield(fitParams,'junkFrames') || isempty(fitParams.junkFrames) + fitParams.junkFrames = viewGet(v,'junkFrames',scanNum); +end + + +if isempty(fitParams.prefit) || (fitParams.prefit.quickPrefit ~= fitParams.quickPrefit) + % set the values over which to first prefit + % the best of these parameters will then be used + % to init the non-linear optimization. Note that the + % values here are expressed as a factor of the screen + % dimensions (1 being the width/height of the screen) + % Later when the prefit is calculated, they will be multiplied + % by the screenWidth and screenHeight + + % make sure here that x and y points go through 0 symmetrically + %[prefitx prefity prefitrfHalfWidth prefithrfDelay] = ndgrid(1:1:3,1:1:3,[0.0125 0.025 0.05 0.1 0.25 0.5 0.75], 6); + + %[prefitx prefity prefitrfHalfWidth] = [1 2 3 4 5, 1 1 1 1, 1]; + + %[prefitx prefity prefitrfHalfWidth prefithrfDelay] = ndgrid(1:1:3,1:1:3,[0.0125 0.025 0.05 0.1 0.25 0.5 0.75], 6); + % [prefitx prefity prefitrfHalfWidth prefithrfDelay] = ... + % ndgrid(1:1:4,1:1:4,[0.0125 0.025 0.05 0.1 0.25 0.5 0.75], 6); + + switch fitParams.rfType +% case 'five-hdr' +% if fitParams.verbose,fprintf('\n(pRF_somatoFit) Doing quick prefit');end +% if fitParams.quickPrefit +% prefitx = [1 2 3 4 5]; +% prefity = [1 1 1 1 1]; +% prefitrfHalfWidth = [1 1 1 1 1]; +% else +% prefitx = [1 2 3 4 5]; +% prefity = [1 1 1 1 1]; +% prefitrfHalfWidth = [1 1 1 1 1]; +% end + + case 'four-hdr' + if fitParams.verbose,fprintf('\n(pRF_somatoFit) Doing quick prefit');end + if fitParams.quickPrefit + prefitx = [1 2 3 4 ]; + prefity = [1 1 1 1 ]; + prefitrfHalfWidth = [1 1 1 1 ]; + else + prefitx = [1 2 3 4 ]; + prefity = [1 1 1 1 ]; + prefitrfHalfWidth = [1 1 1 1 ]; + end + + case {'gaussian','gaussian-hdr','gaussian-hdr-double','gaussian-1D','gaussian-surround','gaussian-1D-transpose'} + if fitParams.verbose,fprintf('\n(pRF_somatoFit) Doing quick prefit');end + % set the values over which to first prefit + % the best of these parameters will then be used + % to init the non-linear optimization. Note that the + % values here are expressed as a factor of the screen + % dimensions (1 being the width/height of the screen) + % Later when the prefit is calculated, they will be multiplied + % by the screenWidth and screenHeight + if fitParams.quickPrefit + + % make sure here that x and y points go through 0 symmetrically + %[prefitx prefity prefitrfHalfWidth] = ndgrid(-0.375:0.125:0.375,-0.375:0.125:0.375,[0.025 0.05 0.15 0.4]); + else + + % trying something here... + % 16 + [prefitx prefity prefitrfHalfWidth] = ndgrid(0:0.1:5.5,0:0.1:5.5,[0 1 2 3 4 5 6]); + % 9 + %[prefitx prefity prefitrfHalfWidth] = ndgrid(0:0.1:3.5,0:0.1:3.5,[0 1 2 3 4 5 6]); + %[prefitx prefity prefitrfHalfWidth] = ndgrid(-0.4:0.025:0.4,-0.4:0.025:0.4,[0.0125 0.025 0.05 0.1 0.25 0.5 0.75]); + end + + case{'gaussian-1D-orthotips'} + + [prefitx prefity prefitrfHalfWidth] = ndgrid(0:0.1:5.5, [1 1 1 1 1], [0 1 2 3 4 5 6]); + + + + % if fitParams.quickPrefit + % prefitx = [1 2 3 4]; + % prefity = [1 1 1 1]; + % prefitrfHalfWidth = [1 1 1 1]; + % else + % prefitx = [1 2 3 4 ]; + % prefity = [1 1 1 1 ]; + % prefitrfHalfWidth = [1 1 1 1 ]; + % end + + % IGNORE THIS + % if fitParams.quickPrefit + % prefitx = [1 2 3 4; 1 2 3 4; 1 2 3 4; 1 2 3 4]; + % prefity = [1 2 3 4; 1 2 3 4; 1 2 3 4; 1 2 3 4]' + % prefitrfHalfWidth = [1 1 1 1; 1 1 1 1; 1 1 1 1; 1 1 1 1]; + % else + % prefitx = [1 2 3 4; 1 2 3 4; 1 2 3 4; 1 2 3 4]; + % prefity = [1 2 3 4; 1 2 3 4; 1 2 3 4; 1 2 3 4]'; + % prefitrfHalfWidth = [1 1 1 1; 1 1 1 1; 1 1 1 1; 1 1 1 1]; + % end + + case {'sixteen-hdr','five-hdr'} + if fitParams.verbose,fprintf('\n(pRF_somatoFit) Doing quick prefit');end + % set the values over which to first prefit + % the best of these parameters will then be used + % to init the non-linear optimization. Note that the + % values here are expressed as a factor of the screen + % dimensions (1 being the width/height of the screen) + % Later when the prefit is calculated, they will be multiplied + % by the screenWidth and screenHeight + if fitParams.quickPrefit + + % make sure here that x and y points go through 0 symmetrically + %[prefitx prefity prefitrfHalfWidth] = ndgrid(-0.375:0.125:0.375,-0.375:0.125:0.375,[0.025 0.05 0.15 0.4]); + else + [prefitx prefity prefitrfHalfWidth] = ndgrid(0:0.1:5.5,0:0.1:5.5,[0 1 2 3 4 5 6]); + %[prefitx prefity prefitrfHalfWidth] = ndgrid(-0.4:0.025:0.4,-0.4:0.025:0.4,[0.0125 0.025 0.05 0.1 0.25 0.5 0.75]); + end + + + % if fitParams.quickPrefit + % prefitx = [1 2 3 4; 1 2 3 4; 1 2 3 4; 1 2 3 4]; + % prefity = [1 2 3 4; 1 2 3 4; 1 2 3 4; 1 2 3 4]'; + % prefitrfHalfWidth = [1 1 1 1; 1 1 1 1; 1 1 1 1; 1 1 1 1]; + % else + % prefitx = [1 2 3 4; 1 2 3 4; 1 2 3 4; 1 2 3 4]; + % prefity = [1 2 3 4; 1 2 3 4; 1 2 3 4; 1 2 3 4]'; + % prefitrfHalfWidth = [1 1 1 1; 1 1 1 1; 1 1 1 1; 1 1 1 1]; + % end + case 'nine-param-hdr' + + if fitParams.verbose,fprintf('\n(pRF_somatoFit) Doing quick prefit');end + % set the values over which to first prefit + % the best of these parameters will then be used + % to init the non-linear optimization. Note that the + % values here are expressed as a factor of the screen + % dimensions (1 being the width/height of the screen) + % Later when the prefit is calculated, they will be multiplied + % by the screenWidth and screenHeight + if fitParams.quickPrefit + + % make sure here that x and y points go through 0 symmetrically + %[prefitx prefity prefitrfHalfWidth] = ndgrid(-0.375:0.125:0.375,-0.375:0.125:0.375,[0.025 0.05 0.15 0.4]); + else + prefitx = [1 2 3 ; 1 2 3 ; 1 2 3 ]; + prefity = [1 2 3 ; 1 2 3 ; 1 2 3 ]'; + prefitrfHalfWidth = [1 1 1 ; 1 1 1; 1 1 1; 1 1 1]; + %[prefitx, prefity, prefitrfHalfWidth] = ndgrid(0:0.1:4.5,0:0.1:4.5,[0 1 2 3 4 5 6]); + %[prefitx prefity prefitrfHalfWidth] = ndgrid(-0.4:0.025:0.4,-0.4:0.025:0.4,[0.0125 0.025 0.05 0.1 0.25 0.5 0.75]); + end + % if fitParams.verbose,fprintf('\n(pRF_somatoFit) Doing quick prefit');end + % if fitParams.quickPrefit + % prefitx = [1 2 3 ; 1 2 3 ; 1 2 3 ]; + % prefity = [1 2 3 ; 1 2 3 ; 1 2 3 ]'; + % prefitrfHalfWidth = [1 1 1 ; 1 1 1; 1 1 1; 1 1 1]; + % else + % prefitx = [1 2 3 ; 1 2 3 ; 1 2 3 ]; + % prefity = [1 2 3 ; 1 2 3 ; 1 2 3 ]'; + % prefitrfHalfWidth = [1 1 1 ; 1 1 1; 1 1 1; 1 1 1]; + % end + + end + + fitParams.prefit.quickPrefit = fitParams.quickPrefit; + fitParams.prefit.n = length(prefitx(:)); + fitParams.prefit.x = prefitx(:); + fitParams.prefit.y = prefity(:); + fitParams.prefit.rfHalfWidth = prefitrfHalfWidth(:); + %fitParams.prefit.hrfDelay = prefithrfDelay(:); +end +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% checkStimForAverages % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [stim ignoreMismatchStimfiles] = checkStimForAverages(v,scanNum,groupNum,stim,concatInfo,stimImageDiffTolerance) + +ignoreMismatchStimfiles = false; + +% this function will check for some bad casses (like concat of concats etc) +% it will also check that all the component scans of an average have the +% same stim image and warn if they do not. It will then replace the stim cell +% array for the average with a single stim file, so that processing +% can continue as normal for pRFFit + +% if not a cell, then ok, return +if ~iscell(stim),return,end + +% first check for bad shiftList or refverseLIst +p = viewGet(v,'params',scanNum,groupNum); +if isfield(p,'params') && isfield(p.params,'shiftList') && any(p.params.shiftList~=0) + disp(sprintf('(pRF_somatoFit) Component scan %s:%i has a shiftList that is non-zero (%s). pRFFit does not handle non-zero shifts in averages.',viewGet(v,'groupName',groupNum),scanNum,mlrnum2str(p.params.shiftList))); + keyboard +end +if isfield(p,'params') && isfield(p.params,'reverseList') && any(p.params.reverseList~=0) + disp(sprintf('(pRF_somatoFit) Component scan %s:%i has a reverseList that is non-zero (%s). pRFFit does not handle time-reversed time series in averages.',viewGet(v,'groupName',groupNum),scanNum,mlrnum2str(p.params.shiftList))); + keyboard +end + +% if is a cell, check to see if this is a concat or not +if ~isempty(concatInfo) && (concatInfo.isConcat) + % this is a concat, so check each one of the elements + [originalScanNum originalGroupNum] = viewGet(v,'originalScanNum',scanNum,groupNum); + for i = 1:length(stim) + % get concatInfo for original scan + concatInfo = viewGet(v,'concatInfo',originalScanNum(i),originalGroupNum(i)); + if ~isempty(concatInfo) + disp(sprintf('(pRF_somatoFit:checkStimForAverages) Detected concatenation of concatenations. pRFFit not implemented yet to handle this')); + stim = []; + keyboard + return; + end + % check this next scan + [stim{i} ignoreMismatchStimfiles] = checkStimForAverages(v,originalScanNum(i),originalGroupNum(i),stim{i},concatInfo,stimImageDiffTolerance); + % if user has accepted all then set stimImageDiffTOlerance to infinity + if isinf(ignoreMismatchStimfiles),stimImageDiffTolerance = inf;end + if isempty(stim{i}),stim = [];return,end + end +else + % this for orignals + [originalScanNum originalGroupNum] = viewGet(v,'originalScanNum',scanNum,groupNum); + % if it is an original than check each element + if ~isempty(originalScanNum) + % check that this is not an average of a concat + for i = 1:length(stim) + % get concatInfo for original scan + concatInfo = viewGet(v,'concatInfo',originalScanNum(i),originalGroupNum(i)); + if ~isempty(concatInfo) + disp(sprintf('(pRF_somatoFit:checkStimForAverages) Detected average of a concatenations. pRFFit not implemented yet to handle this')); + keyboard + stim = []; + return; + end + % see if it is an average of an average + originalOfOriginalScanNum = viewGet(v,'originalScanNum',originalScanNum(i),originalGroupNum(i)); + if length(originalOfOriginalScanNum) > 1 + disp(sprintf('(pRF_somatoFit:checkStimForAverages) Detected average of an average. pRFFit not implemented yet to handle this')); + keyboard + stim = []; + return; + end + end + % ok, not an average of a concatenation/average so check all the stim files + % and warn if there are any inconsistencies + for i = 1:length(stim) + if ~isequalwithequalnans(stim{1}.im,stim{i}.im) + dispHeader + disp(sprintf('(pRF_somatoFit:checkStimForAverages) !!! Average for %s:%i component scan %i does not match stimulus for other scans. If you wish to continue then this will use the stimfile associated with the first scan in the average !!!',viewGet(v,'groupName',groupNum),scanNum,originalScanNum(i))); + % display which volumes are different + diffVols = []; + for iVol = 1:size(stim{1}.im,3) + if ~isequalwithequalnans(stim{1}.im(:,:,iVol),stim{i}.im(:,:,iVol)) + diffVols(end+1) = iVol; + end + end + disp(sprintf('(pRF_somatoFit) Stimulus files are different at %i of %i vols (%0.1f%%): %s',length(diffVols),size(stim{1}.im,3),100*length(diffVols)/size(stim{1}.im,3),num2str(diffVols))); + if 100*(length(diffVols)/size(stim{1}.im,3)) < stimImageDiffTolerance + disp(sprintf('(pRF_somatoFit) This could be for minor timing inconsistencies, so igorning. Set stimImageDiffTolerance lower if you want to stop the code when this happens')); + else + % ask user if they want to continue (only if there is a difference of more than 10 vols + ignoreMismatchStimfiles = askuser('Do you wish to continue',1); + if ~ignoreMismatchStimfiles + stim = []; + return; + end + end + dispHeader + end + end + % if we passed the above, this is an average of identical + % scans, so just keep the first stim image since they are all the same + stim = stim{1}; + end +end + +end + +%%%%%%%%%%%%%%%%% +%% getStim % +%%%%%%%%%%%%%%%%% +function stim = getStim(v,scanNum,fitParams) + +% get stimfile +stimfile = viewGet(v,'stimfile',scanNum); +% get volume to trigger ratio +volTrigRatio = viewGet(v,'auxParam','volTrigRatio',scanNum); +% check if global matches +groupNum = viewGet(v,'curGroup'); +global gpRFFitStimImage +if (isfield(fitParams,'recomputeStimImage') && fitParams.recomputeStimImage) || isempty(gpRFFitStimImage) || (gpRFFitStimImage.scanNum ~= scanNum) || (gpRFFitStimImage.groupNum ~= groupNum) || (gpRFFitStimImage.xFlip ~= fitParams.xFlipStimulus) || (gpRFFitStimImage.yFlip ~= fitParams.yFlipStimulus) || (gpRFFitStimImage.timeShift ~= fitParams.timeShiftStimulus) + disp(sprintf('(pRF_somatoFit) Computing stim image')); + % if no save stim then create one + stim = pRFGetSomatoStimImageFromStimfile(stimfile,'volTrigRatio',volTrigRatio,'xFlip',fitParams.xFlipStimulus,'yFlip',fitParams.yFlipStimulus,'timeShift',fitParams.timeShiftStimulus,'verbose',fitParams.verbose,'saveStimImage',fitParams.saveStimImage,'recomputeStimImage',fitParams.recomputeStimImage); + % check for averages + stim = checkStimForAverages(v,scanNum,viewGet(v,'curGroup'),stim,fitParams.concatInfo,fitParams.stimImageDiffTolerance); + if isempty(stim),return,end + % make into cell array + stim = cellArray(stim); + % save stim image in global + gpRFFitStimImage.scanNum = scanNum; + gpRFFitStimImage.groupNum = groupNum; + gpRFFitStimImage.xFlip = fitParams.xFlipStimulus; + gpRFFitStimImage.yFlip = fitParams.yFlipStimulus; + gpRFFitStimImage.timeShift = fitParams.timeShiftStimulus; + gpRFFitStimImage.stim = stim; +else + % otherwise load from global + disp(sprintf('(pRF_somatoFit) Using precomputed stim image')); + stim = gpRFFitStimImage.stim; +end + +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% applyConcatFiltering % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function tSeries = applyConcatFiltering(tSeries,concatInfo,runnum) + +% apply the same filter as original data +% check for what filtering was done +tSeries = tSeries(:); + +% apply detrending (either if concatInfo does not say what it did or if +% the filterType field has detrend in it) +if ~isfield(concatInfo,'filterType') || ~isempty(findstr('detrend',lower(concatInfo.filterType))) + tSeries = eventRelatedDetrend(tSeries); +end + +% apply hipass filter +if isfield(concatInfo,'hipassfilter') && ~isempty(concatInfo.hipassfilter{runnum}) + % check for length match + if ~isequal(length(tSeries),length(concatInfo.hipassfilter{runnum})) + disp(sprintf('(pRFFit:applyConcatFiltering) Mismatch dimensions of tSeries (length: %i) and concat filter (length: %i)',length(tSeries),length(concatInfo.hipassfilter{runnum}))); + else + tSeries = real(ifft(fft(tSeries) .* repmat(concatInfo.hipassfilter{runnum}', 1, size(tSeries,2)) )); + end +end + +% project out the mean vector +if isfield(concatInfo,'projection') && ~isempty(concatInfo.projection{runnum}) + projectionWeight = concatInfo.projection{runnum}.sourceMeanVector * tSeries; + tSeries = tSeries - concatInfo.projection{runnum}.sourceMeanVector'*projectionWeight; +end + +% now remove mean +tSeries = tSeries-repmat(mean(tSeries,1),size(tSeries,1),1); + +% make back into the right dimensions +tSeries = tSeries(:)'; + + +end +%%%%%%%%%%%%% +%% r2d %% +%%%%%%%%%%%%% +% function degrees = r2d(angle) +% +% degrees = (angle/(2*pi))*360; +% +% % if larger than 360 degrees then subtract +% % 360 degrees +% while (sum(degrees>360)) +% degrees = degrees - (degrees>360)*360; +% end +% +% % if less than 360 degreees then add +% % 360 degrees +% while (sum(degrees<-360)) +% degrees = degrees + (degrees<-360)*360; +% end diff --git a/mrLoadRet/Plugin/pRF_somato/pRF_somatoGUI.m b/mrLoadRet/Plugin/pRF_somato/pRF_somatoGUI.m new file mode 100755 index 000000000..7f45a27a9 --- /dev/null +++ b/mrLoadRet/Plugin/pRF_somato/pRF_somatoGUI.m @@ -0,0 +1,306 @@ +% pRFGUI_somato.m +% +% $Id:$ +% usage: pRFGUI_somato() +% by: justin gardner / adapted for somatosensory +% date: 11/20/11 +% purpose: GUI for getting params for pRF +% +%% +function params = pRF_somatoGUI(varargin) + +% get the arguments +params=[];groupNum=[];defaultParams=[];scanList = [];v = [];pRFFitParamsOnly=[]; +getArgs(varargin,{'params=[]','groupNum=[]','defaultParams=0','scanList=[]','v=[]','pRFFitParamsOnly=0'}); + +% if called with params, then just display +if ~isempty(params) + retval = dispParams(params); + if isempty(retval),params = [];end + return +end + +% get a view +deleteViewOnExit = false; +if isempty(v),v = newView;deleteViewOnExit = true;end + +% get the group names put on top passed in group if set +groupNames = putOnTopOfList(viewGet(v,'groupName',groupNum),viewGet(v,'groupNames')); + +% get possible restrictions of the analysis (i.e. restrict only to volumes +% in base coordinates, rois, etc.) +restrict = {'None'};putOnTop='';nBases = 0; +curBase = viewGet(v,'curbase'); +for iBase = 1:viewGet(v,'numbase') + b = viewGet(v,'base',iBase); + if b.type > 0 + % add any surface or flat map to base list + restrict{end+1} = sprintf('Base: %s',b.name); + if isequal(iBase,curBase) + putOnTop{1} = restrict{end}; + end + % update number of bases we have found + nBases = nBases+1; + end +end +% if we have found more than 1 base then add the possibility of +% running on all bases +if nBases >= 2 + restrict{end+1} = sprintf('Base: ALL'); +end +% get rois +roiNames = viewGet(v,'roiNames'); +curROI = viewGet(v,'curROI'); +for iRoi = 1:length(roiNames) + restrict{end+1} = sprintf('ROI: %s',roiNames{iRoi}); + if iRoi == curROI + putOnTop{end+1} = restrict{end}; + end +end +if ~isempty(putOnTop) + for i = 1:length(putOnTop) + restrict = putOnTopOfList(putOnTop{i},restrict); + end +end + +% check if we have an old pRF analysis loaded which +% the user might want to continue running (i.e. add voxels to) +analysisNames = viewGet(v,'analysisNames'); +pRFAnalyses = {}; +for i = 1:length(analysisNames) + if isequal(viewGet(v,'analysisType',i),'pRFAnal') + pRFAnalyses{end+1} = analysisNames{i}; + end + % put current analysis on top of list + pRFAnalyses = putOnTopOfList(viewGet(v,'analysisName'),pRFAnalyses); +end + +% set the parameter string +paramsInfo = {}; +if ~pRFFitParamsOnly + paramsInfo{end+1} = {'groupName',groupNames,'Name of group from which to do pRF analysis'}; + paramsInfo{end+1} = {'saveName','pRF_somato','File name to try to save as'}; + paramsInfo{end+1} = {'restrict',restrict,'Restrict to the analysis to some subset of voxels. If you choose a base anatomy then it will restrict to the voxels that are on the base. If you choose an roi it will restrict the analysis to the voxels in the roi'}; + + % if we give the option to continue an analysis + if ~isempty(pRFAnalyses) && ~defaultParams + continueParamsInfo{1} = {'continueAnalysis',0,'type=checkbox','Continue running a previously run analysis with same parameters. This is usually done on a different restriction set and will look at the old analysis to make sure not to compute voxels that have already been run. In the end will merge the analyses'}; + continueParamsInfo{2} = {'continueWhich',pRFAnalyses,'Which analysis to continue','contingent=continueAnalysis'}; + % add on restrict + for i = 3:length(paramsInfo) + continueParamsInfo{2+i-2} = paramsInfo{i}; + end + for i = 3:length(continueParamsInfo) + continueParamsInfo{i}{end+1} = 'contingent=continueAnalysis'; + end + % put up dialog box with possibility to continue analysis + continueParams = mrParamsDialog(continueParamsInfo,'Continue existing analysis?'); + if ~isempty(continueParams) && continueParams.continueAnalysis + % get the parameters to continue from + params = viewGet(v,'analysisParams',viewGet(v,'analysisNum',continueParams.continueWhich)); + % copy relevant ones (i.e. what new thing to restrict on) + params.restrict = continueParams.restrict; + % tell pRF to set merge analysis instead of asking + params.mergeAnalysis = true; + % now get a list of all finished voxels + a = viewGet(v,'analysis',viewGet(v,'analysisNum',continueParams.continueWhich)); + if isfield(a,'d') + for i = 1:length(a.d) + if isfield(a.d{i},'linearCoords') + params.computedVoxels{i} = a.d{i}.linearCoords; + end + end + end + return + end + end +end + +%all of these parameters are for pRFFit +paramsInfo{end+1} = {'rfType',... + {'gaussian-1D','gaussian-1D-transpose', 'nine-param-hdr','sixteen-hdr','gaussian','gaussian-hdr','gaussian-hdr-double','gaussian-surround','six-param','nine-param', 'gaussian-1D-orthotips', 'five-hdr', 'four-hdr'},... + 'Type of pRF fit. Gaussian fits a gaussian with x,y,width as parameters to each voxel. gaussian-hdr fits also the hemodynamic response with the parameters of the hdr as below.'}; +paramsInfo{end+1} = {'betaEachScan',false,'type=checkbox','Compute a separate beta weight (scaling) for each scan in the concanetation. This may be useful if there is some reason to believe that different scans have different magnitude responses, this will allow the fit to scale the magnitude for each scan'}; +paramsInfo{end+1} = {'algorithm',{'levenberg-marquardt','nelder-mead'},'Which algorithm to use for optimization. Levenberg-marquardt seems to get stuck in local minimum, so the default is nelder-mead. However, levenberg-marquardt can set bounds for parameters, so may be better for when you are trying to fit the hdr along with the rf, since the hdr parameters can fly off to strange values.'}; +paramsInfo{end+1} = {'defaultConstraints',1,'type=checkbox','Sets how to constrain the search (i.e. what are the allowed range of stimulus parameters). The default is to constrain so that the x,y of the RF has to be within the stimulus extents (other parameter constrains will print to the matlab window). If you click this off a dialog box will come up after the stimulus has been calculated from the stimfiles allowing you to specify the constraints on the parameters of the model. You may want to custom constrain the parameters if you know something about the RFs you are trying to model (like how big they are) to keep the nonlinear fits from finding unlikely parameter estimates. Note that nelder-mead is an unconstrained fit so this will not do anything.'}; +paramsInfo{end+1} = {'prefitOnly',false,'type=checkbox','Check this if you want to ONLY do a prefit and not optimize further. The prefit computes a preset set of model parameters (x,y,rfHalfWidth) and picks the one that produces a mdoel with the highest correlation with the time series. You may want to do this to get a quick but accurate fit so that you can draw a set of ROIs for a full analysis'}; +paramsInfo{end+1} = {'quickPrefit',false,'type=checkbox','Check this if you want to do a quick prefit - this samples fewer x,y and rfWidth points. It is faster (especially if coupled with prefitOnly for a fast check), but the optimization routines may be more likely to get trapped into local minima or have to search a long time for the minimum'}; +paramsInfo{end+1} = {'verbose',true,'type=checkbox','Display verbose information during fits'}; +%paramsInfo{end+1} = {'yFlipStimulus',0,'type=checkbox','Flip the stimulus image in the y-dimension. Useful if the subject viewed a stimulus through a mirror which caused the stimulus to be upside down in the y-dimension'}; +%paramsInfo{end+1} = {'xFlipStimulus',0,'type=checkbox','Flip the stimulus image in the x-dimension. Useful if the subject viewed a stimulus which was flipped in the x-dimension'}; +paramsInfo{end+1} = {'timeShiftStimulus',0,'incdec=[-1 1]','Time shift the stimulus, this is useful if the stimulus created is not correct and needs to be shifted in time (i.e. number of volumes)'}; +if ~isempty(v) + paramsInfo{end+1} = {'dispStim',0,'type=pushbutton','buttonString=Display stimulus','Display the stimulus for scan number: dispStimScan with the current parameters','callback',@pRFGUIDispStimulus,'passParams=1','callbackArg',v}; + paramsInfo{end+1} = {'dispStimScan',viewGet(v,'curScan'),'incdec=[-1 1]',sprintf('minmax=[1 %i]',viewGet(v,'nScans')),'round=1','Sets which scans stimulus will be displayed when you press Display stimulus button'}; +end +paramsInfo{end+1} = {'timelag',1,'minmax=[0 inf]','incdec=[-0.5 0.5]','The timelag of the gamma function used to model the HDR. If using gaussian-hdr, this is just the initial value and the actual value will be fit.'}; +paramsInfo{end+1} = {'tau',0.6,'minmax=[0 inf]','incdec=[-0.1 0.1]','The tau (width) of the gamma function used to model the HDR. If using gaussian-hdr, this is just the initial value and the actual value will be fit.'}; +paramsInfo{end+1} = {'exponent',6,'minmax=[0 inf]','incdec=[-1 1]','The exponent of the gamma function used to model the HDR. This is always a fixed param.'}; +paramsInfo{end+1} = {'diffOfGamma',true,'type=checkbox','Set to true if you want the HDR to be a difference of gamma functions - i.e. have a positive and a delayed negative component'}; +paramsInfo{end+1} = {'amplitudeRatio',0.3,'minmax=[0 inf]','incdec=[-0.1 0.1]','Ratio of amplitude of 1st gamma to second gamma','contingent=diffOfGamma'}; +paramsInfo{end+1} = {'timelag2',2,'minmax=[0 inf]','incdec=[-0.5 0.5]','Time lag of 2nd ggamma for when you are using a difference of gamma functions','contingent=diffOfGamma'}; +paramsInfo{end+1} = {'tau2',1.2,'minmax=[0 inf]','incdec=[-0.1 0.1]','The tau (width) of the second gamma function.','contingent=diffOfGamma'}; +paramsInfo{end+1} = {'exponent2',6,'minmax=[0 inf]','incdec=[-1 1]','The exponent of the 2nd gamma function.','contingent=diffOfGamma'}; +paramsInfo{end+1} = {'dispHDR',0,'type=pushbutton','buttonString=Display HDR','Display the HDR with the current parameters','callback',@pRFGUIDispHDR,'passParams=1'}; +paramsInfo{end+1} = {'saveStimImage',0,'type=checkbox','Save the stim image back to the stimfile. This is useful in that the next time the stim image will not have to be recomputed but can be directly read from the file (it will get saved as a variable called stimImage'}; +paramsInfo{end+1} = {'recomputeStimImage',0,'type=checkbox','Even if there is an already computed stim image (see saveStimImage) above, this will force a recompute of the image. This is useful if there is an update to the code that creates the stim images and need to make sure that the stim image is recreated'}; +paramsInfo{end+1} = {'applyFiltering',1,'type=checkbox','If set to 1 then applies the same filtering that concatenation does to the model. Does not do any filtering applied by averages. If this is not a concat then does nothing besides mean subtraction. If turned off, will still do mean substraction on model.'}; +paramsInfo{end+1} = {'stimImageDiffTolerance',5,'minmax=[0 100]','incdec=[-1 1]','When averaging the stim images should be the same, but some times we are off by a frame here and there due to inconsequential timing inconsistenices. Set this to a small value, like 5 to ignore that percentage of frames of the stimulus that differ within an average. If this threshold is exceeded, the code will ask you if you want to continue - otherwise it will just print out to the buffer the number of frames that have the problem'}; + +paramsInfo{end+1} = {'HRFpRF',false,'type=checkbox','Set to true if you want to load in pre-computed HRFs using prfhrfRefit'}; +%paramsInfo{end+1} = {'Modality',{'somato'}}; + +% Get parameter values +if defaultParams + params = mrParamsDefault(paramsInfo); +else + params = mrParamsDialog(paramsInfo,'Set pRF parameters'); +end + +% if empty user hit cancel +if isempty(params) + if deleteViewOnExit,deleteView(v);end + return +end + +% just getting pRFFItParams, so we are done +if pRFFitParamsOnly,return,end + +% get scans +v = viewSet(v,'groupName',params.groupName); +if ~isempty(scanList) + params.scanNum = scanList; +elseif defaultParams + params.scanNum = 1:viewGet(v,'nScans'); +else + params.scanNum = selectScans(v); +end +if isempty(params.scanNum) + params = []; + if deleteViewOnExit,deleteView(v);end + return +end + +if deleteViewOnExit,deleteView(v);end + +% if we go here, split out the params that get passed to pRFFit +pRFFitParams = false; +for i = 1:length(paramsInfo) + % Everything after the rfType is a param for pRFFit + if pRFFitParams || strcmp(paramsInfo{i}{1},'rfType') + % move params into pRFFit field + params.pRFFit.(paramsInfo{i}{1}) = params.(paramsInfo{i}{1}); + params = rmfield(params,paramsInfo{i}{1}); + % all the next fields will be moved as well + pRFFitParams = true; + % remove these from the paramsInfo field + if strcmp(paramsInfo{i}{1},'rfType') + params.paramInfo = {params.paramInfo{1:i-1}}; + params.pRFFit.paramInfo = {params.paramInfo{i:end}}; + end + end +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% just display parameters +%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function retval = dispParams(params) + +paramsInfo = {}; +% grab the parameters that are indicated in paramsInfo +if isfield(params,'paramInfo') + % get the paramsInfo + topParamsInfo = params.paramInfo; + % go through each one + for i = 1:length(topParamsInfo) + % if it exists in the params filed then add it + if isfield(params,topParamsInfo{i}{1}) + % and it to paramInfo + paramsInfo{end+1} = params.paramInfo{i}; + % add the value from params + paramsInfo{end}{2} = params.(topParamsInfo{i}{1}); + % make it non editable + paramsInfo{end}{end+1} = 'editable=0'; + end + end +end + +% add the pRFFit +if isfield(params,'pRFFit') + pRFFitFieldNames = fieldnames(params.pRFFit); + for iField = 1:length(pRFFitFieldNames) + fieldName = pRFFitFieldNames{iField}; + if ~any(strcmp(fieldName,{'paramInfo','dispHDR','dispStim'})) + paramsInfo{end+1} = {fieldName,params.pRFFit.(fieldName),'editable=0'}; + end + end +end +retval = mrParamsDialog(paramsInfo,'pRF parameters'); +retval = []; + +%%%%%%%%%%%%%%%%%%%%%%% +% pRFGUIDispHDR % +%%%%%%%%%%%%%%%%%%%%%%% +function retval = pRFGUIDispHDR(params) + +retval = []; + +% compute for 25 seconds +t = 0:0.1:25; + +% get the first gamma function +hdr = thisGamma(t,1,params.timelag,0,params.tau,params.exponent); +titleStr = sprintf('(timelag: %s tau: %s exponent: %s)',mlrnum2str(params.timelag),mlrnum2str(params.tau),mlrnum2str(params.exponent)); + +% if difference of gamma subtract second gamma from this one +if params.diffOfGamma + hdr = hdr - thisGamma(t,params.amplitudeRatio,params.timelag2,0,params.tau2,params.exponent2); + titleStr = sprintf('%s - %s x (timelag2: %s tau2: %s exponent2: %s)',titleStr,mlrnum2str(params.amplitudeRatio),mlrnum2str(params.timelag2),mlrnum2str(params.tau2),mlrnum2str(params.exponent2)); +end +hdr = hdr/max(hdr); + +% display +mlrSmartfig('pRFGUIDispHDR','reuse');clf; +plot(t,hdr,'k.-'); +title(titleStr); +xlabel('Time (sec)'); +ylabel('Amplitude'); + + +%%%%%%%%%%%%%%%%%%% +%% thisGamma %% +%%%%%%%%%%%%%%%%%%% +function gammafun = thisGamma(time,amplitude,timelag,offset,tau,exponent) + +exponent = round(exponent); +% gamma function +gammafun = (((time-timelag)/tau).^(exponent-1).*exp(-(time-timelag)/tau))./(tau*factorial(exponent-1)); + +% negative values of time are set to zero, +% so that the function always starts at zero +gammafun(find((time-timelag) < 0)) = 0; + +% normalize the amplitude +if (max(gammafun)-min(gammafun))~=0 + gammafun = (gammafun-min(gammafun)) ./ (max(gammafun)-min(gammafun)); +end +gammafun = (amplitude*gammafun+offset); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% pRFGUIDispStimulus % +%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function retval = pRFGUIDispStimulus(v,params) + +retval = []; + +stim = pRFFit(v,params.dispStimScan,[],[],[],'justGetStimImage=1','fitTypeParams',params); +if ~isempty(stim) + % concatenate all stim images + im = []; + for i = 1:length(stim) + im = cat(3,im,stim{i}.im); + end + % display using mlrVol + disp(sprintf('(pRFGUI) Flipping image in y dimension so that appears in mlrVol the way it was presented')); + im = mlrImageXform(im,'flipY'); + mlrVol(im,'imageOrientation=1'); +end diff --git a/mrLoadRet/Plugin/pRF_somato/pRF_somatoPlot.m b/mrLoadRet/Plugin/pRF_somato/pRF_somatoPlot.m new file mode 100755 index 000000000..ee34e937f --- /dev/null +++ b/mrLoadRet/Plugin/pRF_somato/pRF_somatoPlot.m @@ -0,0 +1,411 @@ +% pRF_somatoPlot.m +% +% +% usage: pRF_somatoPlot(v,overlayNum,scanNum,x,y,z,,roi) +% originally by: justin gardner, mods by asghar / schluppeck +% date: 11/22/11 , 2016 +% purpose: plot function for displaying results of pRF analysis +% +function pRF_somatoPlot(v,overlayNum,scanNum,x,y,z,roi) + +% check arguments +if ~any(nargin == [7]) + help pRFPlot + return +end + +% load in the hrfs here... +%thehrfs = load('hrf_grt_concat.mat'); + +% see if the shift key is down +%shiftDown = any(strcmp(get(viewGet(v,'figureNumber'),'CurrentModifier'),'shift')); +shiftDown = any(strcmp(get(viewGet(v,'figureNumber'),'SelectionType'),'extend')); + +% check if pRF has been run +a = viewGet(v,'Analysis'); +if ~isfield(a,'type') || ~strcmp(a.type,'pRFAnal') + disp(sprintf('(pRF_somatoPlot) pRF analysis has not been run on this scan')); + return +end + +% get the d +d = viewGet(v,'d',scanNum); +if isempty(d),disp(sprintf('(pRF_somatoPlot) Could not find d structure for this scan'));return,end + +% get the parametrs of the pRF fit +r2 = viewGet(v,'overlayData',scanNum,viewGet(v,'overlayNum','r2')); +if isempty(r2) + disp(sprintf('(pRF_somatoPlot) pRF analysis has not been run on this scan')); + return +end +thisR2 = r2(x,y,z); +%polarAngle = viewGet(v,'overlayData',scanNum,viewGet(v,'overlayNum','polarAngle')); +%thisPolarAngle = polarAngle(x,y,z); +%eccentricity = viewGet(v,'overlayData',scanNum,viewGet(v,'overlayNum','eccentricity')); +%thisEccentricity = eccentricity(x,y,z); +rfHalfWidth = viewGet(v,'overlayData',scanNum,viewGet(v,'overlayNum','rfHalfWidth')); +thisRfHalfWidth = rfHalfWidth(x,y,z); +%hrfDelay = viewGet(v,'overlayData',scanNum,viewGet(v,'overlayNum','hrfDelay')); +%thisHrfDelay = hrfDelay(x,y,z); + + +% roi +if ~shiftDown + %pRFPlotROI(v,roi,d,a,r2,eccentricity,polarAngle,rfHalfWidth,hrfDelay); + pRFPlotROI(v,roi,d,a,r2,rfHalfWidth); +end + +% get the params that have been run +scanDims = viewGet(v,'scanDims',scanNum); +whichVoxel = find(d.linearCoords == sub2ind(scanDims,x,y,z)); +%r = d.r(whichVoxel,:); +r = d.r(whichVoxel); + +% if no voxel has been found in precomputed analysis then do fit (or if shift is down) +if isempty(whichVoxel) || shiftDown + % check if shift is being held down, in which case we reget parameters + if shiftDown + fit = pRFFit(v,overlayNum,scanNum,x,y,z,roi); + else + fit = pRFFit(v,overlayNum,scanNum,x,y,z,roi,'fitTypeParams',a.params.pRFFit); + end + if isempty(fit),return,end + % set the overlays + r2(x,y,z) = fit.r2; + %polarAngle(x,y,z) = fit.polarAngle; + %eccentricity(x,y,z) = fit.eccentricity; + rfHalfWidth(x,y,z) = fit.std; + %hrfDelay(x,y,z) = fit.params(10); + % reset the overlays + v = viewSet(v,'overlayDataReplace',r2,'r2'); + %v = viewSet(v,'overlayDataReplace',polarAngle,'polarAngle'); + %v = viewSet(v,'overlayDataReplace',eccentricity,'eccentricity'); + v = viewSet(v,'overlayDataReplace',rfHalfWidth,'rfHalfWidth'); + %v = viewSet(v,'overlayDataReplace',hrfDelay,'hrfDelay'); + % now refresh the display + refreshMLRDisplay(viewGet(v,'viewNum')); + return +end + +params = d.params(:,whichVoxel); +if isfield(d,'paramsInfo') + paramsInfo = d.paramsInfo; +else + paramsInfo = []; +end + +if exist('thehrfs', 'var') + hrfprf = 1; + + sliceFix = 128.*128.*12; + thehrfs.idx_empty = thehrfs.idx_empty + sliceFix; + + whichVoxel_hrf = find(thehrfs.idx_empty == sub2ind(scanDims,x,y,z)); + myVar = thehrfs.clean_lkj; + m = pRF_somatoFit(v,scanNum,x,y,z,'stim',d.stim,'getModelResponse=1','params',params,'concatInfo',d.concatInfo,'fitTypeParams',a.params.pRFFit,'paramsInfo',paramsInfo, 'hrfprf', myVar(:,whichVoxel_hrf)); + +else + % get params + %m = pRF_somatoFit(v,scanNum,x,y,z,'stim',d.stim,'getModelResponse=1','params',params,'concatInfo',d.concatInfo,'fitTypeParams',a.params.pRFFit,'paramsInfo',paramsInfo, 'crossVal', crossVal); + m = pRF_somatoFit(v,scanNum,x,y,z,'stim',d.stim,'getModelResponse=1','params',params,'concatInfo',d.concatInfo,'fitTypeParams',a.params.pRFFit,'paramsInfo',paramsInfo); + % and plot, set a global so that we can use the mouse to display + % different time points + +end + +global gpRFPlot; +gpRFPlot.fignum = selectGraphWin; + +% clear callbacks +set(gpRFPlot.fignum,'WindowButtonMotionFcn',''); + +% keep the stim +gpRFPlot.d = d; +gpRFPlot.rfModel = m.rfModel; + +% keep the axis that has the time series +gpRFPlot.a = subplot(5,5,[1:4 6:9 11:14 16:19]); +% plot the rectangle that shows the current stimuli +% FIX: Start time +gpRFPlot.t = 50; +gpRFPlot.hRect = rectangle('Position',[gpRFPlot.t-4 min(m.tSeries) 4 max(m.tSeries)-min(m.tSeries)],'FaceColor',[0.7 0.7 0.7],'EdgeColor',[0.7 0.7 0.7]); +hold on +% plot time series +plot(m.tSeries,'k.-'); +axis tight +% plot model +% DS -- need to figure out what's going on here.. +if mean(m.modelResponse(:)) < 0.1 + plot(m.modelResponse+1,'r-'); + disp('de-meaned data! beta each scan problems...') +else + plot(m.modelResponse,'r-'); +end + if d.concatInfo.n > 1 + vline(d.concatInfo.runTransition(2:end,1)); +end +xlabel('Time (volumes)'); +ylabel('BOLD (%)'); +% convert coordinates back to x,y for display +%[thisx thisy] = pol2cart(thisPolarAngle,thisEccentricity); +%title(sprintf('[%i %i %i] r^2=%0.2f polarAngle=%0.2f eccentricity=%0.2f rfHalfWidth=%0.2f hrfDelay=%0.2f %s [x=%0.2f y=%0.2f]\n%s',x,y,z,thisR2,r2d(thisPolarAngle),thisEccentricity,thisRfHalfWidth,thisHrfDelay,a.params.pRFFit.rfType,thisx,thisy,num2str(r,'%0.2f '))); +title(sprintf('[%i %i %i] r^2=%0.2f rfHalfWidth=%0.2f %s\n%s',x,y,z,thisR2,thisRfHalfWidth,a.params.pRFFit.rfType,num2str(r,'%0.2f '))); +% plot the rf +a = subplot(5,5,[10 15 20]); +imagesc(d.stimX(:,1),d.stimY(1,:),flipud(m.rfModel')); +colormap gray +%colorbar +set(a,'Box','off'); +set(a,'Color',[0.8 0.8 0.8]); +set(a,'TickDir','out'); +axis equal +axis tight +hold on +hline(0,'w:');vline(0,'w:'); +% plot the canonical +subplot(5,5,5);cla +%plot(m.canonical.time,m.canonical.hrf,'k-'); +plot(m.canonical.hrf, 'k-') +title(sprintf('lag: %0.2f tau: %0.2f',m.p.canonical.timelag,m.p.canonical.tau)); + +% display the stimulus images +plotStim(gpRFPlot.t); + +% now set callback +set(gpRFPlot.fignum,'WindowButtonMotionFcn',@pRFPlotMoveMouse); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% mlrprFPloMoveMouse % +%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function pRFPlotMoveMouse(hWindow,event) + +global gpRFPlot; +if ~ishandle(gpRFPlot.a),return,end + +currentPoint = get(gpRFPlot.a ,'CurrentPoint'); +coord = round(currentPoint(1,1:2)); +a = axis(gpRFPlot.a); +if (coord(1) >= a(1)) && (coord(1) <= a(2)) && (coord(2) >= a(3)) && (coord(2) <= a(4)) + % move rectangle + pos = get(gpRFPlot.hRect,'Position'); + pos(1) = coord(1)-4; + set(gpRFPlot.hRect,'Position',pos); + % redisplay stimulus images + plotStim(coord(1)) +end + +%%%%%%%%%%%%%%%%%% +% plotStim % +%%%%%%%%%%%%%%%%%% +function plotStim(t) + +global gpRFPlot; + +for i = 1:5 + a = subplot(5,5,20+i,'Parent',gpRFPlot.fignum); + cla(a); + thist = t-5+i; + if thist >= 1 + im = []; + % get the scan and volume + thisScan = gpRFPlot.d.concatInfo.whichScan(thist); + thisVolume = gpRFPlot.d.concatInfo.whichVolume(thist); + junkFrames = gpRFPlot.d.concatInfo.totalJunkedFrames(thisScan); + % im(:,:,3) = flipud(0.7*gpRFPlot.d.stim{thisScan}.im(:,:,thisVolume+junkFrames)') + im(:,:,3) = 0.7*gpRFPlot.d.stim{thisScan}.im(:,:,thisVolume+junkFrames); %remove ticks. WD still swapped... + im(:,:,2) = 0.7*gpRFPlot.d.stim{thisScan}.im(:,:,thisVolume+junkFrames); + %tempfix crossVal + %im(:,:,1) = 0.7*gpRFPlot.d.stim{thisScan}.im(:,:,thisVolume+junkFrames)+0.3*gpRFPlot.rfModel'; + im(:,:,1) = 0.7*gpRFPlot.d.stim{thisScan}.im(:,:,thisVolume+junkFrames)+0.3*gpRFPlot.rfModel; + % swap and flip so that it will display correctly + image(gpRFPlot.d.stimX(:,1),gpRFPlot.d.stimY(1,:),im,'Parent',a); + axis image + hold(a,'on'); + hline(0,'w:',a);vline(0,'w:',a); + title(a,sprintf('t=%i',thist)); + end +end + +%%%%%%%%%%%%%%%%%%%% +% pRFPlotROI % +%%%%%%%%%%%%%%%%%%%% +function pRFPlotROI(v,roi,d,a,r2,rfHalfWidth) + +if length(roi) + % check for already plotted + minr2 = viewGet(v,'overlayMin','r2'); + scanNum = viewGet(v,'curScan'); + groupNum = viewGet(v,'curGroup'); + global gpRFPlotROI + checkParams = {'roi','minr2','a','scanNum','groupNum'}; + replot = false; + % if shift key is down then replot + f = viewGet(v,'fignum'); + if ~isempty(f) && any(strcmp(get(f,'CurrentModifier'),'shift')),replot=true;end + for i = 1:length(checkParams) + if ~isfield(gpRFPlotROI,checkParams{i}) || ~isequal(gpRFPlotROI.(checkParams{i}),eval(checkParams{i})) + replot = true; + end + gpRFPlotROI.(checkParams{i}) = eval(checkParams{i}); + end + if ~replot, return, end + disp(sprintf('(pRFPlot) Displaying ROI fig')); + mlrSmartfig('pRFPlotROI','reuse');clf + + minX = min(d.stimX(:)); + maxX = max(d.stimX(:)); + minY = min(d.stimY(:)); + maxY = max(d.stimY(:)); + + % see what kind of fit we have. + if strcmp(a.params.pRFFit.rfType,'gaussian-hdr') + % plot also the hdr parameters + numRowsPerROI = 2; + numCols = 3; + % set up fields for plotting extra hdr parameters + if a.params.pRFFit.diffOfGamma + plotParams = [4 5 6 7 8]; + plotParamsNames = {'timelag','tau','amplitudeRatio','timelag2','tau2'}; + numCols = 5; + else + plotParams = [4 5]; + plotParamsNames = {'timelag','tau'}; + end + else + numRowsPerROI = 1; + numCols = 3; + plotParams = []; + plotParamsNames = {}; + end + + + for roiNum = 1:length(roi) + % get coordinates + % roiCoords = getROICoordinates(v,roi{roiNum},[],[],'straightXform=1'); + roiCoords = getROICoordinates(v,roi{roiNum}); + roiCoordsLinear = sub2ind(viewGet(v,'scanDims'),roiCoords(1,:),roiCoords(2,:),roiCoords(3,:)); + % get values for the roi + thisr2 = r2(roiCoordsLinear); + % only use voxels above current r2 min + roiCoordsLinear = roiCoordsLinear(find(thisr2 >minr2)); + % sort them + [thisr2sorted r2index] = sort(r2(roiCoordsLinear)); + roiCoordsLinear = roiCoordsLinear(r2index); + % get values for these voxels + thisr2 = r2(roiCoordsLinear); + %thisEccentricity = eccentricity(roiCoordsLinear); + %thisPolarAngle = polarAngle(roiCoordsLinear); + thisRfHalfWidth = rfHalfWidth(roiCoordsLinear); + %thisHrfDelay = hrfDelay(roiCoordsLinear); + % convert to cartesian + %[thisX thisY] = pol2cart(thisPolarAngle,thisEccentricity); + c = [1 1 1]; + + % plot RF coverage +% subplot(length(roi)*numRowsPerROI,numCols,1+(roiNum-1)*numCols*numRowsPerROI); +% for i = 1:length(thisX) +% if ~isnan(thisr2(i)) +% plotCircle(thisX(i),thisY(i),thisRfHalfWidth(i),1-c*thisr2(i)/max(thisr2)); +% hold on +% end +% end +% xaxis(minX,maxX); +% yaxis(minY,maxY); +% axis square +% hline(0); +% vline(0); +% xlabel('x (deg)'); +% ylabel('y (deg)'); +% title(sprintf('%s rf (r2 cutoff: %0.2f)',roi{roiNum}.name,minr2)); +% +% % plot RF centers +% subplot(length(roi)*numRowsPerROI,numCols,2+(roiNum-1)*numCols*numRowsPerROI); +% for i = 1:length(thisX) +% if ~isnan(thisr2(i)) +% plot(thisX(i),thisY(i),'k.','Color',1-c*thisr2(i)/max(thisr2), 'markersize', 10); +% hold on +% end +% end +% xaxis(minX,maxX); +% yaxis(minY,maxY); +% axis square +% hline(0); +% vline(0); +% xlabel('x (deg)'); +% ylabel('y (deg)'); +% title(sprintf('%s centers',roi{roiNum}.name)); + + % plot eccentricity vs. rfHalfWidth + %subplot(length(roi)*numRowsPerROI,numCols,3+(roiNum-1)*numCols*numRowsPerROI); + %for i = 1:length(thisX) + % if ~isnan(thisr2(i)) + % plot(thisEccentricity(i),thisRfHalfWidth(i),'k.','Color',1-c*thisr2(i)/max(thisr2), 'markersize', 10); + % hold on + % end + %end + %hold on + % limit the fit to the central 6 deg (b/c it is often off for higher eccentricities) + %eccLimit = 6; + %ind = thisEccentricity <= eccLimit; + %if any(ind) +% regfit = myregress(thisEccentricity(ind),thisRfHalfWidth(ind),0,0); + % w = diag(thisr2(ind)); + % x = thisEccentricity(ind); + % x = [x(:) ones(size(x(:)))]; + % y = thisRfHalfWidth(ind); + % beta = ((x'*w*x)^-1)*(x'*w)*y'; + % maxXaxis = min(maxX,maxY); + % xaxis(0,maxXaxis); + % yaxis(0,maxXaxis); + % if ~isempty(beta) + %plot([0 maxXaxis],[0 maxXaxis]*beta(1)+beta(2),'k-'); + % end + % xlabel('Eccentricity (deg)'); + % ylabel('RF half width (deg)'); +% title(sprintf('slope: %0.2f (%s) offset: %0.2f (%s) (r2=%0.2f)',beta(1),pvaldisp(regfit.pm),beta(2),pvaldisp(regfit.pb),regfit.r2)); + % axis square + %else + % disp(sprintf('(pRFPlot) No matching fits to plot with eccentricity less than %f',eccLimit)); + %end + % plot hdr parameters, first get the voxels to plot + [temp dCoords] = intersect(d.linearCoords,roiCoordsLinear); + for i = 1:length(plotParams) + subplot(length(roi)*numRowsPerROI,numCols,numCols+i+(roiNum-1)*numCols*numRowsPerROI); + hist(d.params(plotParams(i),dCoords)); + xlabel(plotParamsNames{i}); + ylabel('n'); + if exist('plotmean')==2 + plotmean(d.params(plotParams(i),dCoords)); + end + end + end +end + +%%%%%%%%%%%%%%%%%%%% +% plotCircle % +%%%%%%%%%%%%%%%%%%%% +function h = plotCircle(xCenter,yCenter,radius,c) + +a = 0:0.01:2*pi; +h = plot(xCenter+radius*cos(a),yCenter+radius*sin(a),'k-','Color',c); + + +%%%%%%%%%%%%% +%% r2d %% +%%%%%%%%%%%%% +function degrees = r2d(angle) + +degrees = (angle/(2*pi))*360; + +% if larger than 360 degrees then subtract +% 360 degrees +while (sum(degrees>360)) + degrees = degrees - (degrees>360)*360; +end + +% if less than 360 degreees then add +% 360 degrees +while (sum(degrees<-360)) + degrees = degrees + (degrees<-360)*360; +end + diff --git a/mrLoadRet/Plugin/pRF_somato/pRF_somatoPlugin.m b/mrLoadRet/Plugin/pRF_somato/pRF_somatoPlugin.m new file mode 100755 index 000000000..deb595d60 --- /dev/null +++ b/mrLoadRet/Plugin/pRF_somato/pRF_somatoPlugin.m @@ -0,0 +1,62 @@ +% pRF_somatoPlugin.m +% +% $Id:$ +% usage: pRF_somatoPlugin(action,) +% by: justin gardner +% date: 11/24/10 +% purpose: Plugin function for pRF directory. +% +function retval = pRF_somatoPlugin(action,v) + +% check arguments +if ~any(nargin == [1 2]) + help pRFPlugin + return +end + +switch action + case {'install','i'} + % check for a valid view + if (nargin ~= 2) || ~isview(v) + disp(sprintf('(pRF_somatoPlugin) Need a valid view to install plugin')); + else + % if the view is valid, then use mlrAdjustGUI to adjust the GUI for this plugin. + + % this installs a new menu item called 'Select Plugins' under /Edit/ROI with the + % separator turned on above it. It sets the callback to selectPlugins defined below. + mlrAdjustGUI(v,'add','menu','pRF Somato Analysis','/Analysis/Correlation Analysis','Callback',@callpRF_somato,'Separator','off'); + + % Install default interrogators + mlrAdjustGUI(v,'add','interrogator',{'pRF_somatoFit'}); + + % This is a command that could be used to install some default colormaps + % that will show up when you do /Edit/Overlay + %mlrAdjustGUI(v,'add','colormap','gray'); + + % This is a command that could be used to set a property of an existing menu item + %mlrAdjustGUI(v,'set','Plots/Mean Time Series','Separator','on'); + + % return true to indicate successful plugin + retval = true; + end + % return a help string + case {'help','h','?'} + retval = 'Runs population receptive field analysis (somatosensory).'; + otherwise + disp(sprintf('pRF_SomatoPlugin) Unknown command %s')); +end + +end + +%%%%%%%%%%%%%%%%%%%%%%% +% selectPlugins % +%%%%%%%%%%%%%%%%%%%%%%% +function callpRF_somato(hObject,eventdata) + +% code-snippet to get the view from the hObject variable. Not needed for this callback. +v = viewGet(getfield(guidata(hObject),'viewNum'),'view'); +v = pRF_somato(v); + +end + + diff --git a/mrLoadRet/PluginAlt/Nottingham/classificationAnalysis/eventRelatedROIClassification.m b/mrLoadRet/PluginAlt/Nottingham/classificationAnalysis/eventRelatedROIClassification.m index c2aa87492..70742aaee 100644 --- a/mrLoadRet/PluginAlt/Nottingham/classificationAnalysis/eventRelatedROIClassification.m +++ b/mrLoadRet/PluginAlt/Nottingham/classificationAnalysis/eventRelatedROIClassification.m @@ -372,7 +372,7 @@ end end if params.nonParaTest - disppercent(-inf,sprintf('(roiClassification) Shufflling %s from scan %i',viewGet(view,'roiname',roi_n(r)),scanNum)); + mlrDispPercent(-inf,sprintf('(roiClassification) Shufflling %s from scan %i',viewGet(view,'roiname',roi_n(r)),scanNum)); for s=1:params.numShuff s_lab=lab{scanNum}(randperm(length(lab{scanNum}))); for i=1:size(d.concatInfo.runTransition,1) @@ -381,9 +381,9 @@ end sm_acc{scanNum}{r}(s)=mean(s_acc); th_95{scanNum}{r} = prctile(sm_acc{scanNum}{r},95); - disppercent(s/params.numShuff); + mlrDispPercent(s/params.numShuff); end - disppercent(inf); + mlrDispPercent(inf); end end roiClass.d{scanNum}=d; diff --git a/mrLoadRet/PluginAlt/Nottingham/classificationAnalysis/roiClassification.m b/mrLoadRet/PluginAlt/Nottingham/classificationAnalysis/roiClassification.m index e43af3ad9..351198ca2 100644 --- a/mrLoadRet/PluginAlt/Nottingham/classificationAnalysis/roiClassification.m +++ b/mrLoadRet/PluginAlt/Nottingham/classificationAnalysis/roiClassification.m @@ -273,7 +273,7 @@ end end if params.nonParaTest - disppercent(-inf,sprintf('(roiClassification) Shufflling %s from scan %i',viewGet(view,'roiname',roi_n(r)),scanNum)); + mlrDispPercent(-inf,sprintf('(roiClassification) Shufflling %s from scan %i',viewGet(view,'roiname',roi_n(r)),scanNum)); for s=1:params.numShuff s_lab=lab(randperm(length(lab))); for i=1:size(d.concatInfo.runTransition,1) @@ -282,9 +282,9 @@ end sm_acc{scanNum}{r}(s)=mean(s_acc); th_95{scanNum}{r} = prctile(sm_acc{scanNum}{r},95); - disppercent(s/params.numShuff); + mlrDispPercent(s/params.numShuff); end - disppercent(inf); + mlrDispPercent(inf); end end end diff --git a/mrLoadRet/PluginAlt/Nottingham/classificationAnalysis/searchlightClassification.m b/mrLoadRet/PluginAlt/Nottingham/classificationAnalysis/searchlightClassification.m index 0ae35a297..14c937fa5 100644 --- a/mrLoadRet/PluginAlt/Nottingham/classificationAnalysis/searchlightClassification.m +++ b/mrLoadRet/PluginAlt/Nottingham/classificationAnalysis/searchlightClassification.m @@ -166,13 +166,13 @@ % % works out which roi coords are indexed by the spotlights mm=nan([viewGet(thisView,'scanDims',scanNum),length(lab)]); - disppercent(-inf, '(searchlightClassification) Creating 4D TSeries....'); + mlrDispPercent(-inf, '(searchlightClassification) Creating 4D TSeries....'); for i=1:length(d.roiCoords{1}) mm(d.roiCoords{1}(1,i),d.roiCoords{1}(2,i),d.roiCoords{1}(3,i),:) = m_(i,:); - disppercent(i/length(d.roiCoords{1})); + mlrDispPercent(i/length(d.roiCoords{1})); end clear m_ - disppercent(inf); + mlrDispPercent(inf); d.data=[]; %initialise overlays per scan @@ -211,7 +211,7 @@ display('Matabpool already open/No paralell computing') end - disppercent(-inf,'(searchlightClassification) Classifying based on spotlight....'); + mlrDispPercent(-inf,'(searchlightClassification) Classifying based on spotlight....'); for i_sphere=1:size(d.roiCoords{1},2) @@ -250,10 +250,10 @@ svmCorr(i_sphere,:)=svmLab(:)'==lab; - disppercent(i_sphere/length(d.roiCoords{1})); + mlrDispPercent(i_sphere/length(d.roiCoords{1})); end - disppercent(inf); + mlrDispPercent(inf); clear mm_ diff --git a/mrLoadRet/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m b/mrLoadRet/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m index d20902b4e..29cd45850 100644 --- a/mrLoadRet/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m +++ b/mrLoadRet/PluginAlt/Nottingham/viewGUI/viewGUIPlugin.m @@ -164,12 +164,14 @@ mlrAdjustGUI(thisView,'set','copyOverlayMenuItem','location','/Overlays/'); mlrAdjustGUI(thisView,'set','editOverlayMenuItem','location','/Overlays/'); mlrAdjustGUI(thisView,'set','overlayInfoMenuItem','location','/Overlays/'); + drawnow; % this is needed for the below menu to appear in the correct location + mlrAdjustGUI(thisView,'add','menu','exportOverlayScanMenuItem','/Overlays/','label','Export to NIFTI (scan space)','tag','exportOverlayScanMenuItem','callback',@exportOverlayScanMenuItem_Callback); mlrAdjustGUI(thisView,'set','exportOverlayMenuItem','location','/Overlays/'); mlrAdjustGUI(thisView,'set','importOverlayMenuItem','location','/Overlays/'); mlrAdjustGUI(thisView,'set','fileOverlayMenu','location','/Overlays/'); mlrAdjustGUI(thisView,'set','loadOverlayMenuItem','location','/Overlays/'); %rename menu items - mlrAdjustGUI(thisView,'set','exportOverlayMenuItem','label','Export'); + mlrAdjustGUI(thisView,'set','exportOverlayMenuItem','label','Export to NIFTI (base space)'); mlrAdjustGUI(thisView,'set','importOverlayMenuItem','label','Import'); mlrAdjustGUI(thisView,'set','copyOverlayMenuItem','label','Copy...'); mlrAdjustGUI(thisView,'set','pasteOverlayMenuItem','label','Paste'); @@ -197,17 +199,25 @@ mlrAdjustGUI(thisView,'add','menu','duplicateROIMenuItem','/ROI/','label','Duplicate selected','tag','duplicateROIMenuItem','callback',@duplicateRoiMenuItem_Callback); mlrAdjustGUI(thisView,'set','editRoiMenu','location','/ROI/'); mlrAdjustGUI(thisView,'set','infoROIMenuItem','location','/ROI/'); - mlrAdjustGUI(thisView,'set','exportROIMenuItem','location','/ROI/'); - mlrAdjustGUI(thisView,'set','Import Freesurfer Label','location','/ROI/'); + mlrAdjustGUI(thisView,'add','menu','exportROIMenu','/ROI/','label','Export','tag','exportROIMenu'); + mlrAdjustGUI(thisView,'set','exportROIfreesurferMenuItem','location','/ROI/Export/'); + mlrAdjustGUI(thisView,'set','exportROIMenuItem','location','/ROI/Export/'); + mlrAdjustGUI(thisView,'add','menu','importROIMenu','/ROI/','label','Import','tag','importROIMenu','separator','on'); + mlrAdjustGUI(thisView,'set','importROIMenuItem','location','/ROI/Import/'); + mlrAdjustGUI(thisView,'set','importROIMenuItem','separator','off'); + mlrAdjustGUI(thisView,'set','Import Freesurfer Label','location','/ROI/Import/'); mlrAdjustGUI(thisView,'set','Import Freesurfer Label','separator','off'); - mlrAdjustGUI(thisView,'set','importROIMenuItem','location','/ROI/'); + mlrAdjustGUI(thisView,'add','menu','importROIfromNiftiMenuItem','/ROI/Import/','label','from Nifti file','tag','importROIfromNiftiMenuItem','callback',@importROIfromNifti_Callback); mlrAdjustGUI(thisView,'set','fileRoiMenu','location','/ROI/'); mlrAdjustGUI(thisView,'set','loadFromVolumeDirectoryROIMenuItem','location','/ROI/'); mlrAdjustGUI(thisView,'set','loadROIMenuItem','location','/ROI/'); mlrAdjustGUI(thisView,'set','createRoiMenu','location','/ROI/'); mlrAdjustGUI(thisView,'set','convertCorticalDepthRoiMenuItem','location','/ROI/Restrict'); %rename menu items - mlrAdjustGUI(thisView,'set','exportROIMenuItem','label','Export'); + mlrAdjustGUI(thisView,'set','Import Freesurfer Label','label','from Freesurfer label file'); + mlrAdjustGUI(thisView,'set','importROIMenuItem','label','from mrLoadRet v3.1-v4.5'); + mlrAdjustGUI(thisView,'set','exportROIfreesurferMenuItem','label','to Freesurfer Label format'); + mlrAdjustGUI(thisView,'set','exportROIMenuItem','label','to NIFTI format'); % mlrAdjustGUI(thisView,'set','copyRoiMenuItem','label','Copy selected'); % mlrAdjustGUI(thisView,'set','pasteRoiMenuItem','label','Paste'); mlrAdjustGUI(thisView,'set','editRoiMenu','label','Edit'); @@ -296,5 +306,24 @@ function saveViewMenuItem_Callback(hObject, eventdata) mrSaveView(v); +% -------------------------------------------------------------------- +function exportOverlayScanMenuItem_Callback(hObject, eventdata, handles) %not sure why the third input argument is needed +pathstr = putPathStrDialog(pwd,'Specify name of Nifti file to export overlay to',mrGetPref('niftiFileExtension')); +if ~isempty(pathstr) + mrGlobals; + handles = guidata(hObject); % somehow this fails if "handles" is not an input to this function, even though it gets overwritten here + viewNum = handles.viewNum; + mrExport2SR(viewNum, pathstr,0); +end + +% -------------------------------------------------------------------- +function importROIfromNifti_Callback(hObject, eventdata) +mrGlobals; +handles = guidata(hObject); +viewNum = handles.viewNum; +v = MLR.views{viewNum}; +params.from = 'nifti'; +importROI(v,params); + diff --git a/mrLoadRet/PluginAlt/mlrAnatDB/mlrAnatDBPlugin.m b/mrLoadRet/PluginAlt/mlrAnatDB/mlrAnatDBPlugin.m index 042cf6f52..7eca9f2fd 100644 --- a/mrLoadRet/PluginAlt/mlrAnatDB/mlrAnatDBPlugin.m +++ b/mrLoadRet/PluginAlt/mlrAnatDB/mlrAnatDBPlugin.m @@ -380,18 +380,18 @@ function mlrAnatDBReversepRF(hObject,eventdata) % f = figure; stimulusOverlap = zeros(dims(1),dims(2),dims(3),length(stimulusCoords)); -disppercent(-inf,sprintf('Computing %i voxels...',total)); +mlrDispPercent(-inf,sprintf('Computing %i voxels...',total)); count = 0; for x = 1:dims(1) for y = 1:dims(2) for z = 1:dims(3) stimulusOverlap(x,y,z,:) = computeOverlap(data,stimulusCoords,x,y,z,0); count = count+1; - disppercent(count/total); + mlrDispPercent(count/total); end end end -disppercent(inf); +mlrDispPercent(inf); if isempty(names) warning('implement me!'); diff --git a/mrLoadRet/PluginAlt/mlrAnatDB/mlrAnatDBPush.m b/mrLoadRet/PluginAlt/mlrAnatDB/mlrAnatDBPush.m index 5f2553007..a786d4543 100644 --- a/mrLoadRet/PluginAlt/mlrAnatDB/mlrAnatDBPush.m +++ b/mrLoadRet/PluginAlt/mlrAnatDB/mlrAnatDBPush.m @@ -36,28 +36,28 @@ if ~isempty(localRepo) cd(localRepo) if isequal(pushType,'background') - disppercent(-inf,sprintf('(mlrAnatDBPush) Pushing repo %s in the background. You should be able to work immediately, but if you shutdown matlab before the push has finished it may fail (in which case you should run mlrAnatDBPush again.',localRepoLargeFiles)); + mlrDispPercent(-inf,sprintf('(mlrAnatDBPush) Pushing repo %s in the background. You should be able to work immediately, but if you shutdown matlab before the push has finished it may fail (in which case you should run mlrAnatDBPush again.',localRepoLargeFiles)); mysystem(sprintf('hg push --new-branch &')); else - disppercent(-inf,sprintf('(mlrAnatDBPush) Pushing repo %s',localRepo)); + mlrDispPercent(-inf,sprintf('(mlrAnatDBPush) Pushing repo %s',localRepo)); mysystem(sprintf('hg push --new-branch')); end cd(curpwd); - disppercent(inf); + mlrDispPercent(inf); end % push them if they exist if ~isempty(localRepoLargeFiles) cd(localRepoLargeFiles) if isequal(pushType,'background') - disppercent(-inf,sprintf('(mlrAnatDBPush) Pushing repo %s in the background. You should be able to work immediately, but if you shutdown matlab before the push has finished it may fail (in which case you should run mlrAnatDBPush again.',localRepoLargeFiles)); + mlrDispPercent(-inf,sprintf('(mlrAnatDBPush) Pushing repo %s in the background. You should be able to work immediately, but if you shutdown matlab before the push has finished it may fail (in which case you should run mlrAnatDBPush again.',localRepoLargeFiles)); mysystem(sprintf('hg push --new-branch &')); else - disppercent(-inf,sprintf('(mlrAnatDBPush) Pushing repo %s. This may take a few minutes',localRepoLargeFiles)); + mlrDispPercent(-inf,sprintf('(mlrAnatDBPush) Pushing repo %s. This may take a few minutes',localRepoLargeFiles)); mysystem(sprintf('hg push --new-branch')); end cd(curpwd); - disppercent(inf); + mlrDispPercent(inf); end diff --git a/mrLoadRet/PluginAlt/mlrAnatDB/mlrAnatDBPut.m b/mrLoadRet/PluginAlt/mlrAnatDB/mlrAnatDBPut.m index dc80253df..5f42f8e40 100644 --- a/mrLoadRet/PluginAlt/mlrAnatDB/mlrAnatDBPut.m +++ b/mrLoadRet/PluginAlt/mlrAnatDB/mlrAnatDBPut.m @@ -509,7 +509,7 @@ % commit to repo if largefiles - disppercent(-inf,sprintf('(mlrAnatDBAddCommitPush) Committing files to repo %s. This may take a minute or two...',pwd)); + mlrDispPercent(-inf,sprintf('(mlrAnatDBAddCommitPush) Committing files to repo %s. This may take a minute or two...',pwd)); else disp(sprintf('(mlrAnatDBAddCommitPush) Committing files to repo %s',pwd)); end @@ -530,7 +530,7 @@ % commit [status,result] = mysystem(sprintf('hg commit -m ''%s''',comments)); -if largefiles,disppercent(inf);,end +if largefiles,mlrDispPercent(inf);,end %%%%%%%%%%%%%%%%%%%%%% % getFilenames % diff --git a/mrLoadRet/PluginAlt/mlrLife/mlrLifePlugin.m b/mrLoadRet/PluginAlt/mlrLife/mlrLifePlugin.m index e8755cf1e..ede52992e 100644 --- a/mrLoadRet/PluginAlt/mlrLife/mlrLifePlugin.m +++ b/mrLoadRet/PluginAlt/mlrLife/mlrLifePlugin.m @@ -109,13 +109,13 @@ function mlrLifeImportFascicles(hObject,eventdata) % Build a patch from the frame nTotalVertices = 0;nTotalTris = 0; -disppercent(-inf,'(mlrLifeImportFascicles) Making fascicle surface'); +mlrDispPercent(-inf,'(mlrLifeImportFascicles) Making fascicle surface'); for i = 1:nFascicles fasciclePatches{i} = surf2patch(X{i},Y{i},Z{i},'triangles'); % compute how many vertices and tris we have all together nTotalVertices = size(fasciclePatches{i}.vertices,1) + nTotalVertices; nTotalTris = size(fasciclePatches{i}.faces,1) + nTotalTris; - disppercent(i/nFascicles); + mlrDispPercent(i/nFascicles); % calculate bounding box xMin = min(xMin,min(fasciclePatches{i}.vertices(:,1))); xMax = max(xMax,max(fasciclePatches{i}.vertices(:,1))); @@ -124,7 +124,7 @@ function mlrLifeImportFascicles(hObject,eventdata) zMin = min(zMin,min(fasciclePatches{i}.vertices(:,3))); zMax = max(zMax,max(fasciclePatches{i}.vertices(:,3))); end -disppercent(inf); +mlrDispPercent(inf); % display bounding box of coordinates disp(sprintf('(mlrLifePlugin) Bounding box x: %0.1f %0.1f y: %0.1f %0.1f z: %0.1f %0.1f',xMin,xMax,yMin,yMax,zMin,zMax)); @@ -160,7 +160,7 @@ function mlrLifeImportFascicles(hObject,eventdata) nRunningTotalTris = 0; % now put all fascicles vertices and triangles into one coordMap -disppercent(-inf,sprintf('(mlrLifePlugin) Converting %i fascicles',nFascicles)); +mlrDispPercent(-inf,sprintf('(mlrLifePlugin) Converting %i fascicles',nFascicles)); for iFascicle = 1:nFascicles % number of vertices and triangles nVertices = size(fasciclePatches{iFascicle}.vertices,1); @@ -180,9 +180,9 @@ function mlrLifeImportFascicles(hObject,eventdata) % update runing totals nRunningTotalVertices = nRunningTotalVertices + nVertices; nRunningTotalTris= nRunningTotalTris + nTris; - disppercent(iFascicle/nFascicles); + mlrDispPercent(iFascicle/nFascicles); end -disppercent(inf); +mlrDispPercent(inf); % copy the inner to outer since they are all the same for fascicles fascicleBase.coordMap.outerCoords = fascicleBase.coordMap.innerCoords; diff --git a/mrLoadRet/ROI/combineROIs.m b/mrLoadRet/ROI/combineROIs.m index b2721e3b8..858eeda79 100644 --- a/mrLoadRet/ROI/combineROIs.m +++ b/mrLoadRet/ROI/combineROIs.m @@ -73,7 +73,7 @@ elseif isempty(coords2) newCoords = coords2; else - newCoords = intersect(coords1,coords2,'rows'); + newCoords = intersect(round(coords1),round(coords2),'rows'); end case 'union' if isempty(coords1) @@ -89,7 +89,7 @@ elseif isempty(coords2) newCoords = coords1; else - newCoords = setxor(coords1,coords2,'rows'); + newCoords = setxor(round(coords1),round(coords2),'rows'); end case 'a not b' if isempty(coords1) @@ -97,7 +97,7 @@ elseif isempty(coords2) newCoords = coords1; else - newCoords = setdiff(coords1,coords2,'rows'); + newCoords = setdiff(round(coords1),round(coords2),'rows'); end otherwise error('unknown action: %s',action); diff --git a/mrLoadRet/ROI/convertROICorticalDepth.m b/mrLoadRet/ROI/convertROICorticalDepth.m index b3884d456..7dcfd6b41 100644 --- a/mrLoadRet/ROI/convertROICorticalDepth.m +++ b/mrLoadRet/ROI/convertROICorticalDepth.m @@ -1,7 +1,7 @@ % convertROICorticalDepth.m % % $Id$ -% usage: convertROICorticalDepth() +% usage: convertROICorticalDepth(v, params, <'justGetParams=0/1','defaultParams=0/1', 'roiList', roiList>) % by: justin gardner % date: 10/15/07 % purpose: used to extend or restrict ROI coordinates across @@ -10,27 +10,39 @@ % 12/8/08 Modified by Taosheng Liu to take params. If params is set, GUI % will not show for setting params, also it assumes then all ROIs % associated with a view will be converted. +% +% To just get a default parameter structure: +% +% v = newView; +% [v params] = convertROICorticalDepth(v,[],'justGetParams=1'); +% [v params] = convertROICorticalDepth(v,[],'justGetParams=1','defaultParams=1'); +% [v params] = convertROICorticalDepth(v,[],'justGetParams=1','defaultParams=1','roiList=[1 2]'); + function [v params] = convertROICorticalDepth(v,params,varargin) % check arguments -if ~any(nargin == [1 2 3 4]) +if nargin < 1 help convertROICorticalDepth return end -eval(evalargs(varargin,[],[],{'justGetParams','defaultParams'})); +% optional arguments +getArgs(varargin); + if ieNotDefined('justGetParams'),justGetParams = 0;end if ieNotDefined('defaultParams'),defaultParams = 0;end +if ieNotDefined('distanceThreshold'), distanceThreshold = 2; end %distance threshold (in mm) to exclude + % number of rois numrois = viewGet(v,'numberofrois'); if numrois == 0 mrWarnDlg('(convertROICorticalDepth) No currently loaded ROIs'); return end -roinames = viewGet(v,'roiNames'); if ieNotDefined('params') + askForParams = 1; % get cortical depth corticalDepth = viewGet(v,'corticalDepth'); referenceDepth= mean(corticalDepth); @@ -43,139 +55,203 @@ end depthStep = 1/(mrGetPref('corticalDepthBins')-1); incdecString = sprintf('incdec=[-%f %f]',depthStep,depthStep); - paramsInfo = {}; - paramsInfo{end+1} = {'conversionType',{'Project through depth','Restrict to reference depth'},'type=popupmenu','If you set project through depth, then this will add all the voxels from each cortical depth that are in the same position as the ones at the reference depth. If you set to restrict to reference depth, this will remove any voxels that are not on the reference depth (note that you will still see some voxels on other depths, but those are voxels that exist at the reference depth--also, voxels that do not exist on this flat map will not be affected)'}; - paramsInfo{end+1} = {'referenceDepth',referenceDepth,'min=0','max=1',incdecString,'The cortical depth to start from'}; - paramsInfo{end+1} = {'minDepth',minDepth,'min=0','max=1',incdecString,'The start depth'}; - paramsInfo{end+1} = {'depthStep',depthStep,'min=0','max=1',incdecString,'The depth step (i.e. we will go from minDepth:depthStep:maxDepth (skipping the reference depth), including or excluding voxels'}; - paramsInfo{end+1} = {'maxDepth',maxDepth,'min=0','max=1',incdecString,'The end depth'}; - paramsInfo{end+1} = {'excludeOtherVoxels',1,'type=checkbox','If ROI voxels exist oustide the projected surface, they will be remove. Uncheck to keep them. this option is ignored if restriction is selected'}; - if defaultParams - params = mrParamsDefault(paramsInfo); - else - % put up some parameter choices - params = mrParamsDialog(paramsInfo,'ROI cortical depth conversion'); - end - % now select rois - % put up a dialog with rois to select - paramsDialog = {}; - for roinum = 1:length(roinames) - helpinfo = sprintf('Convert cortical depth of ROI %i: %s',roinum,roinames{roinum}); - paramsDialog{end+1} = {fixBadChars(roinames{roinum}),0,'type=checkbox',helpinfo}; + while askForParams + paramsInfo = {}; + paramsInfo{end+1} = {'conversionType',{'Project through depth','Restrict to reference depth'},'type=popupmenu','If you set project through depth, then this will add all the voxels from each cortical depth that are in the same position as the ones at the reference depth. If you set to restrict to reference depth, this will remove any voxels that are not on the reference depth (note that you will still see some voxels on other depths, but those are voxels that exist at the reference depth--also, voxels that do not exist on this flat map will not be affected)'}; + paramsInfo{end+1} = {'referenceDepth',referenceDepth,'min=0','max=1',incdecString,'The cortical depth to start from'}; + paramsInfo{end+1} = {'minDepth',minDepth,'max=1',incdecString,'The minimum depth. Negative values will extend the ROI into white matter'}; + paramsInfo{end+1} = {'depthStep',depthStep,'min=0','max=1',incdecString,'The depth step (i.e. we will go from minDepth:depthStep:maxDepth (skipping the reference depth), including or excluding voxels'}; + paramsInfo{end+1} = {'maxDepth',maxDepth,'min=0','max=1',incdecString,'The maximum depth'}; + paramsInfo{end+1} = {'excludeOtherVoxels',1,'type=checkbox','If ROI voxels exist oustide the projected surface, they will be removed. Uncheck to keep them. This option is ignored if restriction is selected'}; + paramsInfo{end+1} = {'allowProjectionThroughSulci',1,'type=checkbox','Voxels will be kept even if they also belong to another part of the cortical surface through a sulcus. Uncheck to exclude these voxels. Note that voxels projected to another part of the cortex through white matter (for instance in case minDepth is negative) will be excluded too. If the ROI is projected based on a flat map, only voxels on the flat map, not the whole surface, will be excluded. This option is ignored if restriction is selected'}; + if defaultParams + params = mrParamsDefault(paramsInfo); + else + % put up some parameter choices + params = mrParamsDialog(paramsInfo,'ROI cortical depth conversion'); + end + % Abort if params empty + if ieNotDefined('params'),return,end + + if 0 + %checks on params here if needed + else + askForParams = 0; + % now select rois + % put up a dialog with rois to select + if defaultParams + params.roiList = viewGet(v,'curROI'); + else + params.roiList = selectInList(v,'rois'); + if isempty(params.roiList) + askForParams = 1; + end + end + end end - paramsDialog{end+1} = {'all',0,'type=checkbox','Select all ROIs'}; - % put up dialog - whichROI = mrParamsDialog(paramsDialog,sprintf('Select ROIs to convert cortical depth')); -else - disp('(convertROICorticalDepth) coverting all ROIs in the view'); - whichROI.all=1; end if isempty(params),return,end + +if ~ieNotDefined('roiList') + params.roiList = roiList; +end + % just return parameters if justGetParams, return, end +%remember what ROIs were selected in the view for later currentROI = viewGet(v,'currentROI'); -% now go through and do conversion -if ~isempty(whichROI) - needToRefresh = 0; - % now go through and convert anything the user selected - for roinum = 1:length(roinames) - if whichROI.all || whichROI.(fixBadChars(roinames{roinum})) - needToRefresh = 1; - disppercent(-inf,sprintf('(convertROICorticalDepth) Processing ROI %i:%s',roinum,roinames{roinum})); - % get the roi - v = viewSet(v,'curROI',roinum); - % now try to figure out what base this was created on - roiCreatedOnBase = viewGet(v,'roiCreatedOnBase',roinames{roinum}); - if isempty(roiCreatedOnBase) - disp(sprintf('(convertROICorticalDepth) Converting %s based on base:%s because roiCreatedOnBase has not been set.',roinames{roinum},viewGet(v,'baseName'))); - baseNum = viewGet(v,'curBase'); - else - % get the basenumber for the base that this was created on - baseNum = viewGet(v,'baseNum',roiCreatedOnBase); - if isempty(baseNum) - disp(sprintf('(convertROICorticalDepth) Converting %s based on base:%s because base:%s which this roi was created on is not loaded',roinames{roinum},viewGet(v,'baseName'),roiCreatedOnBase)); - baseNum = viewGet(v,'curBase'); - end - end - % get the roi transformation in order to set the coordinates later - base2roi = viewGet(v,'base2roi',roinum,baseNum); - % get the roiBaseCoords - roiBaseCoords = getROICoordinates(v,roinum,[],[],'baseNum',baseNum); - if isempty(roiBaseCoords) - disppercent(inf); - mrWarnDlg(sprintf('(convertROICorticalDepth) %s has no coordinates on this flat',roinames{roinum})); - continue; - end - % get base info - baseVoxelSize = viewGet(v,'baseVoxelSize',baseNum); - baseCoordMap = viewGet(v,'baseCoordMap',baseNum,params.referenceDepth); - baseDims = baseCoordMap.dims; - baseCoordMap = round(baseCoordMap.coords); - referenceBaseCoordMap = mrSub2ind(baseDims,baseCoordMap(:,:,:,1),baseCoordMap(:,:,:,2),baseCoordMap(:,:,:,3)); - referenceBaseCoordMap = referenceBaseCoordMap(:); - % get roi linear coordinates - roiBaseCoordsLinear = mrSub2ind(baseDims,roiBaseCoords(1,:),roiBaseCoords(2,:),roiBaseCoords(3,:)); - % now find which baseCoords are in the current roi - [isInROI roiInBase] = ismember(referenceBaseCoordMap,roiBaseCoordsLinear); - % get the roi base coordinates that are found in base - roiInBase = unique(setdiff(roiInBase,0)); - % if we don't find most of the coordinates, then - % probably good to complain and give up - if (length(roiInBase)/length(roiBaseCoordsLinear)) < 0.1 - disppercent(inf); - mrWarnDlg(sprintf('(convertROICorticalDepth) !!! %s has less than %0.0f%% coordinates on surface %s. Perhaps you need to load the base that it was orignally created on. !!!',roinames{roinum},ceil(100*(length(roiInBase)/length(roiBaseCoordsLinear))),viewGet(v,'baseName',baseNum))); - continue; - end - % make sure to keep the voxels at the reference depth - roiBaseCoordsReferenceLinear = roiBaseCoordsLinear(ismember(roiBaseCoordsLinear,referenceBaseCoordMap)); - - if strcmp(params.conversionType,'Project through depth') - %clear all voxels if we're not keeping voxels outside the projection - if params.excludeOtherVoxels - % remove everything from the ROI - roiBaseCoords(4,:) = 1; - v = modifyROI(v,roiBaseCoords,base2roi,baseVoxelSize,0); - end - roiBaseCoordsLinear=[]; - % now get each cortical depth, and add/remove voxels - corticalDepths = params.minDepth:params.depthStep:params.maxDepth; - baseCoordMap = viewGet(v,'baseCoordMap',baseNum,corticalDepths); - for iDepth = 1:size(baseCoordMap.coords,5) - % get the coordinates at this depth - baseCoords = round(baseCoordMap.coords(:,:,:,:,iDepth)); - baseCoords = mrSub2ind(baseDims,baseCoords(:,:,:,1),baseCoords(:,:,:,2),baseCoords(:,:,:,3)); - baseCoords = baseCoords(:); - % add the coordinates to our list - roiBaseCoordsLinear = union(roiBaseCoordsLinear,baseCoords(isInROI)); - end - roiBaseCoordsLinear = roiBaseCoordsLinear(~isnan(roiBaseCoordsLinear)); - % now convert back to regular coords - roiBaseCoords = []; - [roiBaseCoords(1,:) roiBaseCoords(2,:) roiBaseCoords(3,:)] = ind2sub(baseDims,roiBaseCoordsLinear); - roiBaseCoords(4,:) = 1; - % add them to the ROI - v = modifyROI(v,roiBaseCoords,base2roi,baseVoxelSize,1); - else - % get current coords - curROICoords = viewGet(v,'roiCoords',roinum); - % remove them from the ROI - v = modifyROI(v,roiBaseCoords,base2roi,baseVoxelSize,0); - % but make sure we have the voxels at the reference depth - roiBaseCoords = []; - [roiBaseCoords(1,:) roiBaseCoords(2,:) roiBaseCoords(3,:)] = ind2sub(baseDims,roiBaseCoordsReferenceLinear); - roiBaseCoords(4,:) = 1; - v = modifyROI(v,roiBaseCoords,base2roi,baseVoxelSize,1); - % and save for undo (note we do this instead of allowing - % modifyROI to do it since we have called modifyROI twice) - v = viewSet(v,'prevROIcoords',curROICoords); - end - disppercent(inf); +% now go through and convert anything the user selected +for roinum = params.roiList + roiName = viewGet(v,'roiname', roinum); + mlrDispPercent(-inf,sprintf('(convertROICorticalDepth) Processing ROI %i:%s',roinum,roiName)); + % get the roi + v = viewSet(v,'curROI',roinum); + % now try to figure out what base this was created on + roiCreatedOnBase = viewGet(v,'roiCreatedOnBase',roiName); + if isempty(roiCreatedOnBase) + fprintf('(convertROICorticalDepth) Converting %s based on base:%s because roiCreatedOnBase has not been set.\n',roiName,viewGet(v,'baseName')); + baseNum = viewGet(v,'curBase'); + else + % get the basenumber for the base that this was created on + baseNum = viewGet(v,'baseNum',roiCreatedOnBase); + if isempty(baseNum) + fprintf('(convertROICorticalDepth) Converting %s based on base:%s because base:%s which this roi was created on is not loaded\n',roiName,viewGet(v,'baseName'),roiCreatedOnBase); + baseNum = viewGet(v,'curBase'); end + if viewGet(v,'basetype',baseNum)==0 + fprintf('(convertROICorticalDepth) Converting %s based on base:%s because base:%s which this roi was created on is not a surface or a flat map\n',roiName,viewGet(v,'baseName'),roiCreatedOnBase); + baseNum = viewGet(v,'curBase'); + end + end + % check the base type to see if it's compatible with the current implementation of params.allowProjectionThroughSulci + if ~params.allowProjectionThroughSulci + if viewGet(v,'basetype',baseNum) == 2 + mrWarnDlg('(convertROICorticalDepth) Unchecking allowProjectionThroughSulci parameters is not yet implemented for surface bases') + return + end + end + % get the roi transformation in order to set the coordinates later + base2roi = viewGet(v,'base2roi',roinum,baseNum); + % get the roiBaseCoords + roiBaseCoords = getROICoordinates(v,roinum,[],[],'baseNum',baseNum); + if isempty(roiBaseCoords) + mlrDispPercent(inf); + mrWarnDlg(sprintf('(convertROICorticalDepth) %s has no coordinates on this flat',roiName)); + continue; end - v = viewSet(v,'currentROI',currentROI); - if needToRefresh - refreshMLRDisplay(viewGet(v,'viewNum')); + nVoxelsOriginalROI = size(roiBaseCoords,2); + % get base info, including (rounded) base coordinates corresponding to the reference cortical depth + baseVoxelSize = viewGet(v,'baseVoxelSize',baseNum); + baseCoordMap = viewGet(v,'baseCoordMap',baseNum,params.referenceDepth); + mapDims = size(baseCoordMap.coords); + baseDims = baseCoordMap.dims; + baseCoordMap = round(baseCoordMap.coords); + referenceBaseCoordMap = mrSub2ind(baseDims,baseCoordMap(:,:,:,1),baseCoordMap(:,:,:,2),baseCoordMap(:,:,:,3)); + referenceBaseCoordMap = referenceBaseCoordMap(:); + % get roi linear coordinates + roiBaseCoordsLinear = mrSub2ind(baseDims,roiBaseCoords(1,:),roiBaseCoords(2,:),roiBaseCoords(3,:)); + % now find which baseCoords are in the current roi at the reference depth + [isInROI, roiInBase] = ismember(referenceBaseCoordMap,roiBaseCoordsLinear); + % get the roi base coordinates that are found in base at the reference depth + roiInBase = unique(setdiff(roiInBase,0)); + % (note that here we could have used ismember(roiBaseCoordsLinear,referenceBaseCoordMap) instead, which is perhaps easier to understand) + + % if we don't find most of the coordinates, then + % probably good to complain and give up + if (length(roiInBase)/length(roiBaseCoordsLinear)) < 0.1 + mlrDispPercent(inf); + mrWarnDlg(sprintf('(convertROICorticalDepth) !!! %s has less than %0.0f%% coordinates on surface %s. Perhaps you need to load the base that it was orignally created on. !!!',roiName,ceil(100*(length(roiInBase)/length(roiBaseCoordsLinear))),viewGet(v,'baseName',baseNum))); + continue; + end + % make sure to keep the voxels at the reference depth + roiBaseCoordsReferenceLinear = roiBaseCoordsLinear(ismember(roiBaseCoordsLinear,referenceBaseCoordMap)); + % (Note that we could have used roiBaseCoordsLinear(roiInBase) instead here) + + % if excluding voxels that belong to two distant locations of the cortex, we need to compute the shortest distance of all elements + % of the flat map or surface to the ROI, in flat/surface space. (This is not yet implemented for surfaces and would require computing the Dijkstra distance) + if ~params.allowProjectionThroughSulci + [flatCoordsX, flatCoordsY] = meshgrid(1:mapDims(1),1:mapDims(2)); %compute the coordinates of the points on the flat map (i.e. in flat space). + % (Distance calculations could be done using the actual surface locations, but this would require computing Dijkstra distances. + % Easier like this and sufficient for our purposes, until the same is implemented for surfaces) + % find coordinates of the ROI on the flat map + flatCoordsRoiX = flatCoordsX(isInROI); + flatCoordsRoiY = flatCoordsY(isInROI); + % for each pixel of the flat map that corresponds to a base voxels, but that does not belong to the ROI, + % compute its shortest distance to any pixel in the ROI (in flat space) + minDistanceToROI = zeros(mapDims(1)*mapDims(2),1); + for iPixel = find(~isInROI & ~isnan(referenceBaseCoordMap))' + minDistanceToROI(iPixel) = min(sqrt((flatCoordsX(iPixel) - flatCoordsRoiX).^2 + (flatCoordsY(iPixel) - flatCoordsRoiY).^2)); + end + % now find flat map pixels that are a minimum distance from any pixel in the ROI. + % first compute approximate pixel size (based on surface coordinates only at the reference depth) + % separate x,y and z coordinates of flat map in base space and convert to mm + xBaseCoordMap = baseVoxelSize(1)*baseCoordMap(:,:,1,1); + yBaseCoordMap = baseVoxelSize(2)*baseCoordMap(:,:,1,2); + zBaseCoordMap = baseVoxelSize(3)*baseCoordMap(:,:,1,3); + % remove pixels that do not index a location on the surface + xBaseCoordMap(isnan(referenceBaseCoordMap))=NaN; + yBaseCoordMap(isnan(referenceBaseCoordMap))=NaN; + zBaseCoordMap(isnan(referenceBaseCoordMap))=NaN; + %compute pixel size in pixels + pixelSize(1) = nanmean(nanmean(sqrt(diff(xBaseCoordMap,1,1).^2 + diff(yBaseCoordMap,1,1).^2 + diff(zBaseCoordMap,1,1).^2))); + pixelSize(2) = nanmean(nanmean(sqrt(diff(xBaseCoordMap,1,2).^2 + diff(yBaseCoordMap,1,2).^2 + diff(zBaseCoordMap,1,2).^2))); + %compute distance threshold in pixels 2 based on approximate pixel size + isFarEnoughFromROI = minDistanceToROI > (distanceThreshold / min(pixelSize)); + end + + if strcmp(params.conversionType,'Project through depth') + roiBaseCoordsLinear=[]; + % now get each cortical depth, and add/remove voxels + corticalDepths = params.minDepth:params.depthStep:params.maxDepth; + % (negative cortical depths mean that the flat/surface base (and subsequently the ROI) will be extended into white matter + baseCoordMap = viewGet(v,'baseCoordMap',baseNum,corticalDepths); + for iDepth = 1:size(baseCoordMap.coords,5) + % get the (rounded) base coordinates at this depth + baseCoords = round(baseCoordMap.coords(:,:,:,:,iDepth)); + baseCoords = mrSub2ind(baseDims,baseCoords(:,:,:,1),baseCoords(:,:,:,2),baseCoords(:,:,:,3)); + baseCoords = baseCoords(:); + % find the baseCoords that are in the ROI at this depth and add them to our list + roiBaseCoordsLinear = union(roiBaseCoordsLinear,baseCoords(isInROI)); + end + roiBaseCoordsLinear = roiBaseCoordsLinear(~isnan(roiBaseCoordsLinear)); + % exclude any projected voxel that ends up in a different part of cortex either through a sulcus or through white matter + if ~params.allowProjectionThroughSulci + % get the (rounded) base coordinates at all depths between 0 and 1 + baseCoords = round(baseCoordMap.coords(:,:,:,:,corticalDepths>=0 & corticalDepths<=1)); + baseCoords = mrSub2ind(baseDims,baseCoords(:,:,:,1,:),baseCoords(:,:,:,2,:),baseCoords(:,:,:,3,:)); + baseCoords = reshape(baseCoords,size(baseCoords,1)*size(baseCoords,2)*size(baseCoords,3),size(baseCoords,5)); + % exclude any voxel that is also part of the flat map (or surface) at a location away form the ROI + roiBaseCoordsLinear = setdiff(roiBaseCoordsLinear,baseCoords(isFarEnoughFromROI,:)); + end + % now convert back to regular coords + additionalRoiBaseCoords = []; + [additionalRoiBaseCoords(1,:), additionalRoiBaseCoords(2,:), additionalRoiBaseCoords(3,:)] = ind2sub(baseDims,roiBaseCoordsLinear); + additionalRoiBaseCoords(4,:) = 1; + %clear all existing voxels if we're not keeping voxels outside the projection + if params.excludeOtherVoxels + % remove everything from the ROI + roiBaseCoords(4,:) = 1; + v = modifyROI(v,roiBaseCoords,base2roi,baseVoxelSize,0); + end + % add the projected voxels to the ROI + v = modifyROI(v,additionalRoiBaseCoords,base2roi,baseVoxelSize,1); + else + % get current coords + curROICoords = viewGet(v,'roiCoords',roinum); + % remove them from the ROI + v = modifyROI(v,roiBaseCoords,base2roi,baseVoxelSize,0); + % but make sure we have the voxels at the reference depth + additionalRoiBaseCoords = []; + [additionalRoiBaseCoords(1,:), additionalRoiBaseCoords(2,:), additionalRoiBaseCoords(3,:)] = ind2sub(baseDims,roiBaseCoordsReferenceLinear); + additionalRoiBaseCoords(4,:) = 1; + v = modifyROI(v,additionalRoiBaseCoords,base2roi,baseVoxelSize,1); + % and save for undo (note we do this instead of allowing + % modifyROI to do it since we have called modifyROI twice) + v = viewSet(v,'prevROIcoords',curROICoords); end + mlrDispPercent(inf); + fprintf(1,'(convertROICorticalDepth) Number of voxels in original ROI: %d\t Number of voxels in modified ROI: %d\n',nVoxelsOriginalROI,size(additionalRoiBaseCoords,2)); end +v = viewSet(v,'currentROI',currentROI); diff --git a/mrLoadRet/ROI/drawROI.m b/mrLoadRet/ROI/drawROI.m index 482b3591a..722b0d7f7 100644 --- a/mrLoadRet/ROI/drawROI.m +++ b/mrLoadRet/ROI/drawROI.m @@ -6,7 +6,11 @@ % % descriptor: option for how the new coordinates are to be specified. % Current options are: -% 'rectangle'[default] +% 'rectangle'[default]: rectangle defined from two opposite voxels +% 'single voxels': list of single voxels +% 'contiguous': all contiguous unmasked voxels +% 'polygon': area enclosed within a list of voxels/vertices +% 'line': connected line of voxels % % sgn: If sgn~=0 [default, adds user-specified coordinates to selected ROI % in current slice. If sgn==0, removes those coordinates from the ROI. @@ -38,11 +42,13 @@ % baseCoords contains the mapping from pixels in the displayed slice to % voxels in the current base volume. +[~,~,~,~,~,thisView] = refreshMLRDisplay(thisView);% first run refreshMLRDisplay to update view field 'curslicebasecoords' +% (ideally this field should be updated when changing the slice/cortical depth WITHOUT a call to refreshMLRDisplay) baseCoords = viewGet(thisView,'cursliceBaseCoords'); -baseSliceDims = [size(baseCoords,1),size(baseCoords,2)]; if isempty(baseCoords) - mrWarnDlg('Load base anatomy before drawing an ROI'); + mrErrorDlg('Load base anatomy before drawing an ROI'); end +baseSliceDims = [size(baseCoords,1),size(baseCoords,2)]; % turn off 3d rotate if viewGet(thisView,'baseType') == 2 @@ -71,16 +77,17 @@ switch descriptor case 'single voxels' - disp('Use mouse left button to add/remove a voxel. End selection with Alt, Command key or right-click') - [mouseY,mouseX] = ginput(1); + disp('(drawROI) Use mouse left button to add/remove a voxel. End selection with Alt, Command key or right-click') + region = getrect(fig); + mouseX = region(2); + mouseY = region(1); voxelXcoords = [.5 .5;-.5 -.5; -.5 .5; -.5 .5]'; voxelYcoords = [.5 -.5;.5 -.5; .5 .5; -.5 -.5]'; selectionY=[]; selectionX=[]; hSelection=[]; hold on; - key=1; - while ~any(ismember(get(fig,'CurrentModifier'),{'command','alt'})) && key==1 + while ~any(ismember(get(fig,'CurrentModifier'),{'command','alt'})) && strcmp(get(fig,'SelectionType'),'normal') if ~isempty(selectionY) [dump,index] = ismember(round([mouseY mouseX]), [selectionY' selectionX'],'rows'); else @@ -97,7 +104,9 @@ index = length(selectionY); hSelection(:,index)=plot(selectionY(index)+voxelXcoords,selectionX(index)+voxelYcoords,'w');%,'linewidth',mrGetPref('roiContourWidth')); end - [mouseY,mouseX,key] = ginput(1); + region = getrect(fig); + mouseX = region(2); + mouseY = region(1); end baseX = baseCoords(:,:,1); @@ -115,8 +124,10 @@ case 'contiguous' disp('Hold Alt or Command key to select all connected regions') - [mouseY,mouseX] = ginput(1); - + region = getrect(fig); + mouseX = region(2); + mouseY = region(1); + if any(ismember(get(fig,'CurrentModifier'),{'command','alt'})) selectAcrossVolume=true; else @@ -236,9 +247,10 @@ case 'rectangle' % Get region from user. - region = round(ginput(2)); + region = getrect(fig); + region = round([region(1:2);region(1:2)+region(3:4)]); - % Note: ginput hands them back in x, y order (1st col is x and 2nd col is + % Note: getrect hands them back in x, y order (1st col is x and 2nd col is % y). But we use them in the opposite order (y then x), so flip 'em. region = fliplr(region); % Check if outside image @@ -271,17 +283,17 @@ % this is sometimes very slow if you have a lot % of lines already drawn on the figure. % i.e. if you have rois already being displayed - polyIm = roipoly; + polyIm = roipoly; % this might not work if drawROI is called from a script. The current image in the current figure would have to be specified somehow else % this doesn't have to redraw lines all the time % so it is faster % but has the disadvantage that you don't get % to see the lines connecting the points. - [x y a] = getimage; + [x y a] = getimage(fig); if strcmp(roiPolygonMethod,'getptsNoDoubleClick') - [xi yi] = getptsNoDoubleClick; + [xi yi] = getptsNoDoubleClick(fig); else - [xi yi] = getpts; + [xi yi] = getpts(fig); end % draw the lines temporarily if ~isempty(xi) @@ -302,7 +314,11 @@ case 'line' % grab two points from the image; - [xi yi] = getpts; + if strcmp(mrGetPref('roiPolygonMethod'),'getptsNoDoubleClick') + [xi, yi] = getptsNoDoubleClick(fig); + else + [xi, yi] = getpts(fig); + end xii=[]; yii=[]; for p=1:length(xi)-1 @@ -312,7 +328,7 @@ end if ~isempty(xii) - line(xii, yii); + line(xii, yii,'parent',gui.axis); drawnow; end diff --git a/mrLoadRet/ROI/findLinePoints.m b/mrLoadRet/ROI/findLinePoints.m index 9b57c4f96..46ce4e039 100755 --- a/mrLoadRet/ROI/findLinePoints.m +++ b/mrLoadRet/ROI/findLinePoints.m @@ -1,4 +1,4 @@ -function [x, y] = findLinePoints(p1,p2); +function [x, y] = findLinePoints(p1,p2) % % [x, y] = findLinePoints(p1,p2) % @@ -16,18 +16,15 @@ x1 = p1(1); y1 = p1(2); x2 = p2(1); y2 = p2(2); -if y2 == y1 - if x1 == x2 - error; - return; - end - x = [x1:x2]; y = y1*ones(1,length(x)); +if y2 == y1 && x1 == x2 + x = x1; + y = y1; +elseif y2 == y1 + x = [x1:x2]; + y = y1*ones(1,length(x)); elseif x1 == x2 - if y1 == y2 - error; - return; - end - y = [y1:y2]; x = x1*ones(1,length(y)); + y = [y1:y2]; + x = x1*ones(1,length(y)); else slope = (y2-y1)/(x2-x1); b = y1 - slope*x1; diff --git a/mrLoadRet/ROI/getROICoordinates.m b/mrLoadRet/ROI/getROICoordinates.m index 7d8116d26..7553d57fe 100644 --- a/mrLoadRet/ROI/getROICoordinates.m +++ b/mrLoadRet/ROI/getROICoordinates.m @@ -6,7 +6,7 @@ % date: 04/02/07 % purpose: get roi coordinates in scan coordinates % if scanNum is 0, then will compute in the current base -% coordinates, unless basenum is specified, in which case. +% coordinates, unless basenum is specified. % if roinum is a structure, works on the structure % rather than the roinum % if roinum is a string, will load the roi from diff --git a/mrLoadRet/ROI/importROI.m b/mrLoadRet/ROI/importROI.m index 74b699ce1..577458aea 100644 --- a/mrLoadRet/ROI/importROI.m +++ b/mrLoadRet/ROI/importROI.m @@ -3,21 +3,44 @@ % usage: importROI(view,pathStr) % by: justin gardner % date: 03/16/07 -% purpose: import roi from mrLoadRet 3.1 version to 4.5 +% purpose: import roi from mrLoadRet 3.1 version to 4.5 or from Nifti volume % -function view = importROI(view,pathStr) +function [thisView,params] = importROI(thisView,params,varargin) % check arguments -if ~any(nargin == [1 2]) +if nargin < 1 help importROI return end -mrMsgBox('(importROI) Note that to import an ROI from the old mrLoadRet, you MUST have the same anatomy image loaded that the ROI was defined on. For example, if it is an inplane ROI, you must have that inplane as the current anatomy. If it is a 3D volume, you will need that 3D volume. Also, make sure that the anatomy has been correctly registered to your base anatomy by mrAlign and has its sform set appropriately. Old mrLoadRet ROIs do not have any alignment information and are just a list of voxels for a particular anatomy image.'); +if ieNotDefined('params') + params = struct; +end +if ischar(params) + pathStr = params; %for backwards compatibility + params = struct; +end + +if fieldIsNotDefined(params,'from') + params.from='mrLoadRet'; +end + +% other arguments +eval(evalargs(varargin)); +if ieNotDefined('justGetParams'),justGetParams = 0;end +if ieNotDefined('defaultParams'),defaultParams = 0;end + + +switch(lower(params.from)) + case 'mrloadret' + filterSpec={'*.mat','MAT files'; '*.*','All files'}; + case 'nifti' + filterSpec={'*.nii;*.nii.gz;*.img','NIFTI files'; '*.*','All files'}; +end mrGlobals; % Complete pathStr -if ieNotDefined('pathStr') +if ieNotDefined('pathStr') && ~justGetParams && ~defaultParams % start in an roi directory %startPathStr = fullfile(viewGet(view,'viewType'),'ROIs'); startPathStr = mrGetPref('importROIPath'); @@ -26,54 +49,160 @@ end if ~isdir(startPathStr),startPathStr='';,end % get the user defined path - pathStr = mlrGetPathStrDialog(startPathStr,'Choose roi files to import','*.mat','on'); + pathStr = mlrGetPathStrDialog(startPathStr,'Choose roi files to import',filterSpec,'on'); +elseif justGetParams + pathStr = 'none'; end if isempty(pathStr),disp('No ROI selected');,return,end mrSetPref('importROIPath',fileparts(pathStr{1})); % get some info -baseNum = viewGet(view,'currentBase'); -xform = viewGet(view,'basexform',baseNum); -voxelSize = viewGet(view,'baseVoxelSize',baseNum); -baseDims = viewGet(view,'baseDims',baseNum); - -for roinum = 1:length(pathStr) - % try to load the roi - l = load(pathStr{roinum}); - if isfield(l,'ROI') - clear ROI; - ROI.name = l.ROI.name; - ROI.viewType = view.viewType; - ROI.color = l.ROI.color; - if isfield(l.ROI,'viewType') && ~strcmp(l.ROI.viewType,'Inplane') - % not sure why gray rois are different from inplane but - % this seems to work in conversion - ROI.coords(1,:) = l.ROI.coords(3,:); - ROI.coords(2,:) = baseDims(2)-l.ROI.coords(2,:)+1; - ROI.coords(3,:) = baseDims(3)-l.ROI.coords(1,:)+1; - else - % there is just an x/y flip for the inplane ROIs - ROI.coords(1,:) = l.ROI.coords(2,:); - ROI.coords(2,:) = l.ROI.coords(1,:); - ROI.coords(3,:) = l.ROI.coords(3,:); +baseNum = viewGet(thisView,'currentBase'); +baseXform = viewGet(thisView,'basexform',baseNum); +baseVoxelSize = viewGet(thisView,'baseVoxelSize',baseNum); +baseDims = viewGet(thisView,'baseDims',baseNum); + +switch(lower(params.from)) + case 'mrloadret' + + mrMsgBox('(importROI) Note that to import an ROI from the old mrLoadRet, you MUST have the same anatomy image loaded that the ROI was defined on. For example, if it is an inplane ROI, you must have that inplane as the current anatomy. If it is a 3D volume, you will need that 3D volume. Also, make sure that the anatomy has been correctly registered to your base anatomy by mrAlign and has its sform set appropriately. Old mrLoadRet ROIs do not have any alignment information and are just a list of voxels for a particular anatomy image.'); + + + for roinum = 1:length(pathStr) + % try to load the roi + l = load(pathStr{roinum}); + if isfield(l,'ROI') + clear ROI; + ROI.name = l.ROI.name; + ROI.viewType = thisView.viewType; + ROI.color = l.ROI.color; + if isfield(l.ROI,'viewType') && ~strcmp(l.ROI.viewType,'Inplane') + % not sure why gray rois are different from inplane but + % this seems to work in conversion + ROI.coords(1,:) = l.ROI.coords(3,:); + ROI.coords(2,:) = baseDims(2)-l.ROI.coords(2,:)+1; + ROI.coords(3,:) = baseDims(3)-l.ROI.coords(1,:)+1; + else + % there is just an x/y flip for the inplane ROIs + ROI.coords(1,:) = l.ROI.coords(2,:); + ROI.coords(2,:) = l.ROI.coords(1,:); + ROI.coords(3,:) = l.ROI.coords(3,:); + end + ROI.coords(4,:) = 1; + ROI.xform = baseXform; + ROI.voxelSize = baseVoxelSize; + ROI.date = datestr(now); + % Add it to the view + thisView = viewSet(thisView,'newROI',ROI); + %ROI.coords + else + disp(sprintf('(importROI) No ROI variable found in mat file')); + end + end + + case 'nifti' + + scanDims = viewGet(thisView,'scanDims'); + scanXform = viewGet(thisView,'scanXform'); + scanVoxelSize = viewGet(thisView,'scanVoxelSize'); + + for roinum = 1:length(pathStr) % NB: if scripting, only one ROI can be imported at a time + if ~justGetParams + [data,hdr] = mlrImageLoad(pathStr{roinum}); + [~,name] = fileparts(pathStr{roinum}); + + % make sure it has only 1 frame + if hdr.nDim == 3 + hdr.nDim = 4; + hdr.dim(4) = 1; + end + + if hdr.dim(4) ~= 1 + mrWarnDlg(sprintf('(importOverlay) Could not import image because it has %d frames',hdr.dim(4))); + return + end + + importXformOptions = cell(0); + if isequal(scanDims,hdr.dim(1:3)) + importXformOptions = putOnTopOfList('scanXform',importXformOptions); + end + if isequal(baseDims,hdr.dim(1:3)) + importXformOptions = putOnTopOfList('baseXform',importXformOptions); + end + if ~fieldIsNotDefined(hdr,'qform') + if ~isequal(hdr.sform,eye(4)) % not sure whether sform is always defined + importXformOptions = putOnTopOfList('niftiQform',importXformOptions); + end + end + if ~fieldIsNotDefined(hdr,'sform') + if ~isequal(hdr.qform,eye(4)) % not sure whether qform is always defined + importXformOptions = putOnTopOfList('niftiSform',importXformOptions); + end + end + else + importXformOptions = {'niftiSform'}; + end + colors = putOnTopOfList('black',color2RGB); + + paramsInfo = {{'name',name,'The name of the nifti file that you are importing'}}; + paramsInfo{end+1} = {'importXform',importXformOptions,'type=popupmenu','What transformation matrix to use when importing'}; + paramsInfo{end+1} = {'notes','','A description for the ROI you are importing (optional).'}; + paramsInfo{end+1} = {'color',colors,'type=popupmenu','Color of the ROI'}; + + if defaultParams + tempParams = mrParamsDefault(paramsInfo); + else + tempParams = mrParamsDialog(paramsInfo); + end + if isempty(tempParams),return,end + params = copyFields(tempParams,params); + params = rmfield(params,{'paramInfo'}); + if justGetParams,return,end + + ROI.name = params.name; + ROI.viewType = 'Volume'; + ROI.color = params.color; + switch(params.importXform) + case 'scanXform' + if ~isequal(scanDims,hdr.dim(1:3)) + mrWarnDlg(sprintf('(importROI) Could not import ROI because its dimensions differ from the current scan dimensions (%s vs %s)', num2str(hdr.dim([1 2 3])'),num2str(scanDims)) ); + return + else + ROI.xform = scanXform; + ROI.voxelSize = scanVoxelSize; + end + case 'baseXform' + if ~isequal(baseDims,hdr.dim(1:3)) + mrWarnDlg(sprintf('(importROI) Could not import ROI because its dimensions differ from the current base dimensions (%s vs %s)', num2str(hdr.dim([1 2 3])'),num2str(baseDims)) ); + return + else + ROI.xform = baseXform; + ROI.voxelSize = baseVoxelSize; + end + case 'niftiSform' + ROI.xform = hdr.sform; + ROI.voxelSize = hdr.pixdim(1:3); + case 'niftiQform' + ROI.xform = hdr.qform; + ROI.voxelSize = hdr.pixdim(1:3); + end + [ROI.coords(:,1),ROI.coords(:,2),ROI.coords(:,3)] = ind2sub(hdr.dim(1:3),find(data)); + ROI.coords = ROI.coords'; + ROI.coords(4,:) = 1; + ROI.date = datestr(now); + ROI.notes = params.notes; + % Add it to the view + thisView = viewSet(thisView,'newROI',ROI); + end - ROI.coords(4,:) = 1; - ROI.xform = xform; - ROI.voxelSize = voxelSize; - ROI.date = datestr(now); - % Add it to the view - view = viewSet(view,'newROI',ROI); - %ROI.coords - else - disp(sprintf('(importROI) No ROI variable found in mat file')); - end end + if exist('ROI','var') - ROInum = viewGet(view,'ROInum',ROI.name); + ROInum = viewGet(thisView,'ROInum',ROI.name); if (ROInum > 0) - view = viewSet(view,'currentROI',ROInum); - view = viewSet(view,'prevROIcoords',[]); + thisView = viewSet(thisView,'currentROI',ROInum); + thisView = viewSet(thisView,'prevROIcoords',[]); end - refreshMLRDisplay(viewGet(view,'viewNum')); + refreshMLRDisplay(viewGet(thisView,'viewNum')); end diff --git a/mrLoadRet/ROI/makeEmptyROI.m b/mrLoadRet/ROI/makeEmptyROI.m index 6b14dbc35..a82241974 100644 --- a/mrLoadRet/ROI/makeEmptyROI.m +++ b/mrLoadRet/ROI/makeEmptyROI.m @@ -1,10 +1,14 @@ % makeEmptyROI.m % % $Id:$ -% usage: makeEmptyROI(v,,) +% usage: makeEmptyROI(v,<'scanNum'>,<'groupNum'>,<'name=...'>) +% makeEmptyROI(v,<'scanNum=...'>,<'groupNum=...'>,<'name=...'>) +% makeEmptyROI(v,<'scanNum',scanNum>,<'groupNum',groupNum>,<'name',name>)) % by: justin gardner % date: 12/31/11 -% purpose: creates an empty roi with coordiantes set for teh scan and group +% purpose: creates an empty roi with coordinates set for the scan and group +% if scanNum is not defined, the current scan of the current group is used +% if scanNum = 0, the current base is used instead % function roi = makeEmptyROI(v,varargin) @@ -18,7 +22,7 @@ % get arguments scanNum = [];groupNum = []; -getArgs(varargin,{'scanNum=[]','groupNum=[]'}); +getArgs(varargin,{'scanNum=[]','groupNum=[]','name=[]'}); % make a name if ieNotDefined('name') @@ -34,11 +38,26 @@ name=sprintf('ROI%.0f',maxnum+1); end roi.name = name; -roi.voxelSize = viewGet(v,'scanVoxelSize',scanNum,groupNum); -if viewGet(v,'scanSformCode',scanNum,groupNum) - roi.xform = viewGet(v,'scanSform',scanNum,groupNum); +if isequal(scanNum,0) + baseNum = viewGet(v,'currentBase'); + roi.sformCode = viewGet(v,'baseSformCode',baseNum); + % if the baseSformCode == 0 then use the bases Qform matrix + % since it means that the alignment has not been set. + if viewGet(v,'baseSformCode',baseNum) == 0 + roi.xform = viewGet(v,'baseQform',baseNum); + roi.sformCode = 0; + else + roi.xform = viewGet(v,'baseSform',baseNum); + end + roi.baseNum = viewGet(v,'currentBase'); + roi.voxelSize = viewGet(v,'baseVoxelSize',baseNum); else - roi.xform = viewGet(v,'scanQform',scanNum,groupNum); + roi.voxelSize = viewGet(v,'scanVoxelSize',scanNum,groupNum); + if viewGet(v,'scanSformCode',scanNum,groupNum) + roi.xform = viewGet(v,'scanSform',scanNum,groupNum); + else + roi.xform = viewGet(v,'scanQform',scanNum,groupNum); + end end [tf roi] = isroi(roi); diff --git a/mrLoadRet/ROI/makeROIsFromSurfaces.m b/mrLoadRet/ROI/makeROIsFromSurfaces.m new file mode 100644 index 000000000..df4104fca --- /dev/null +++ b/mrLoadRet/ROI/makeROIsFromSurfaces.m @@ -0,0 +1,164 @@ +% +% function thisView = makeROIsFromSurfaces(thisView,params,<'justGetParams'>) +% +% Purpose: make ROIs based on cortical surfaces. Two non-overlapping ROIs will be made: +% - one white-matter ROI with voxels within the inner (gw/wm boundary) surface, +% - one grey matter ROI with voxels between the innner and outer (pial) surfaces +% +% Usage: [~,params] = makeROIsFromSurfaces(thisView,[],'justGetParams') % to get default parameters +% % then set the parameters and use them to create the ROIs, e.g.: +% params.roiSpace = 'current scan' +% thisView = makeROIsFromSurfaces(thisView,params); % make the ROIs and add them to the view +% refreshMLRDisplay(thisView); % display the ROIs in the GUI +% +% Params: - names: name of the ROIs. If left empty, the names will be self-explanatory +% - colors: color of the ROIs +% - surfaceName: name of the base containing the surfaces (default: current base or next available surface base in the view) +% - roiSpace: space in which the roi coordinates will be saved: options are 'surface base': the +% base corresponding to the surface coordinates (default), 'current base' or 'current scan'. +% - tolerance: this is a parameter that is directly passed on to function inpolyhedron.m, and determines how far the center of +% a voxel can be from (outside) the suface and still be considered to be located within an closed surface (default = 0). +% It's unclear in what units this parameter is expressed, but a larger value will result in more voxels being included. +% With the default value, voxels that have at least 50% of their volume within the surface should be included. However +% this is not exact because inpolyhedron.m does not take the voxels size into account. It can also take negative values. +% + +function [thisView, params] = makeROIsFromSurfaces(thisView,params,varargin) + +eval(evalargs(varargin)); +if ieNotDefined('justGetParams'),justGetParams = 0;end +% if ieNotDefined('defaultParams'),defaultParams = 0;end + +if ~ismember(nargin,[1 2 3]) + help('makeROIsFromSurfaces'); + return +end + +if ieNotDefined('thisView') + mrWarnDlg('(makeSphereROIatCoords) No valid mrLoadRet view was provided'); + return +end + +% set default parameters +if ieNotDefined('params') + params = struct; +end + +if fieldIsNotDefined(params,'colors') + params.colors = {'green','orange'}; % color of the ROIs +end +if fieldIsNotDefined(params,'roiSpace') + params.roiSpace = 'surface base'; % space in which the coordinates are specified: options are 'surface base': the base corresponding to the surface coordinates (default), 'current base' or 'current scan'. +end +if fieldIsNotDefined(params,'surfaceName') + params.surfaceName = ''; + for iBase = [viewGet(thisView,'curbase'):viewGet(thisView,'numbase') viewGet(thisView,'curbase')-1:-1:1] + if viewGet(thisView,'baseType',iBase) == 2 + params.surfaceName = viewGet(thisView,'baseName',iBase); + break; + end + end + if isempty(params.surfaceName) + mrWarnDlg('(makeROIsFromSurfaces) Could not find any surface base in view'); + end +end +if fieldIsNotDefined(params,'names') % name of the ROIs. + [~,roiBaseName] = fileparts(params.surfaceName); % remove .off extension + roiBaseName = fixBadChars(roiBaseName); + params.names{1} = [roiBaseName '_gm']; + params.names{2} = [roiBaseName '_wm']; +end +if fieldIsNotDefined(params,'tolerance') + params.tolerance = 0; +end + +if justGetParams + return; +end + +surfBaseNum = viewGet(thisView,'basenum',params.surfaceName); +if isempty(surfBaseNum) + mrWarnDlg(sprintf('(makeSphereROIatCoords) Could not find base %s in view.',params.surfaceName)); + return +elseif viewGet(thisView,'baseType',surfBaseNum) ~= 2 + mrWarnDlg(sprintf('(makeSphereROIatCoords) %s is not a surface base.',params.surfaceName)); + return +end + +surfBaseCoordMap = viewGet(thisView,'baseCoordMap',surfBaseNum); % get the surface vertex coords and other surface info + +% get the xform from the surface base space to the ROI space, as weel as voxel size in that space +switch(params.roiSpace) + case 'surface base' % if the ROI and surface space are the same + surf2roi = eye(4); % the xform is the identity matric + roi.xform = viewGet(thisView,'basexform',surfBaseNum); + roiVolDims = surfBaseCoordMap.dims; + roi.voxelSize = viewGet(thisView,'baseVoxelSize',surfBaseNum); + + case 'current base' + surf2roi = viewGet(thisView,'base2base',[],surfBaseNum); + roi.xform = viewGet(thisView,'basexform'); + roi.voxelSize = viewGet(thisView,'baseVoxelSize'); % check that this works for flat maps? + switch(viewGet(thisView,'baseType')) + case 0 + roiVolDims = viewGet(thisView,'baseDims'); + case {1,2} + baseCoordMap = viewGet(thisView,'baseCoordMap'); + roiVolDims = baseCoordMap.dims; + if viewGet(thisView,'baseType') == 1 + keyboard; % this function hasn't been tested with flat maps + end + end + + case 'current scan' + surf2roi = viewGet(thisView,'base2scan',[],[],surfBaseNum); + roi.xform = viewGet(thisView,'scanxform'); + roiVolDims = viewGet(thisView,'scanDims'); + roi.voxelSize = viewGet(thisView,'scanVoxelSize'); + + otherwise + mrWarnDlg(sprintf('(makeSphereROIatCoords) Unknow roiSpace parameter %s.',params.roiSpace)); + return +end + +% Identify voxels within the inner surface (WM) using inpolyhedron function (obtained from Matlab Exchange). +% First, determine whether surface normals will be pointing out or in after transformation to the ROI space +% I think that Freesurfer (surfRelax?)'s convention is that they are pointing out, but if the transformation +% to ROI space does not preserve orientation (e.g. functional space can have left/right flipped), then that +% convention ends up reversed. I'm not sure how to determine the original mesh's convention, or even if it's +% possible (I suspect not) but, if we assume that the normals point up in the original mesh, then we can look +% at the determinant of the xfrom from surface to ROI space to see whether it preserves orientation or not. +% A negative determinant means that the convention has been flipped. +flipNormals = det(surf2roi)<0; +% now convert the surfaces to ROI space and find the voxels within the inner surface +nVtcs = size(surfBaseCoordMap.innerCoords,2); +innerCoords = surf2roi * [permute(surfBaseCoordMap.innerCoords,[4 2 1 3]); ones(1,nVtcs)]; % convert inner surface coordinates to ROI space +innerSurf.vertices = innerCoords(1:3,:)'; +innerSurf.faces = surfBaseCoordMap.tris; +wmCoordsMask = inpolyhedron(innerSurf,1:roiVolDims(2),1:roiVolDims(1),1:roiVolDims(3),'flipnormals',flipNormals,'tol',params.tolerance); +wmCoordsMask = permute(wmCoordsMask,[2 1 3]); % permute X and Y because inpolyhedron uses the meshgrid convention + +% do the same for the outer surface +outerCoords = surf2roi * [permute(surfBaseCoordMap.outerCoords,[4 2 1 3]); ones(1,nVtcs)]; +outerSurf.vertices = outerCoords(1:3,:)'; +outerSurf.faces = surfBaseCoordMap.tris; +gmCoordsMask = inpolyhedron(outerSurf,1:roiVolDims(2),1:roiVolDims(1),1:roiVolDims(3),'flipnormals',flipNormals,'tol',params.tolerance); +gmCoordsMask = permute(gmCoordsMask,[2 1 3]); % permute X and Y because inpolyhedron uses the meshgrid convention +gmCoordsMask(wmCoordsMask) = false; % subtract WM voxels to get on GM voxels + +% make ROI structures and add them to the view +roi.createdBy = 'makeROIsFromSurfaces'; +[~, roi] = isroi(roi); % add all the optional fields +% gm ROI +roi.color = params.colors{1}; +roi.name = params.names{1}; +roi.coords = ones(4,nnz(gmCoordsMask)); +[roi.coords(1,:), roi.coords(2,:), roi.coords(3,:)] = ind2sub(roiVolDims,find(gmCoordsMask)); +thisView= viewSet(thisView,'newROI',roi); +% wm ROI +roi.color = params.colors{2}; +roi.name = params.names{2}; +roi.coords = ones(4,nnz(wmCoordsMask)); +[roi.coords(1,:), roi.coords(2,:), roi.coords(3,:)] = ind2sub(roiVolDims,find(wmCoordsMask)); +thisView= viewSet(thisView,'newROI',roi); + diff --git a/mrLoadRet/ROI/makeSphereROIatCoords.m b/mrLoadRet/ROI/makeSphereROIatCoords.m new file mode 100644 index 000000000..6060049de --- /dev/null +++ b/mrLoadRet/ROI/makeSphereROIatCoords.m @@ -0,0 +1,121 @@ +% +% function thisView = makeSphereROIatCoords(thisView,params,<'justGetParams'>) +% +% Purpose: make a spherical ROI centered at specified coordinates and add it to the mrLoadRet view +% +% Usage: [~,params] = makeSphereROIatCoords(thisView,[],'justGetParams') % to get default parameters +% % then set the parameters and use them to create the ROI in the desired location, e.g.: +% params.centerCoords = [-45 -57 -12]; % specify the coordinates in MNI coordinates (default) +% params.name = 'myROI'; % specify the ROI name +% thisView = makeSphereROIatCoords(thisView,params); % make the sphere ROI and add it to the view +% refreshMLRDisplay(thisView); % display the ROI in the GUI +% +% Params: - name: name of the ROI. if left empty, the name will be ROI1, ROI2, etc..., whichever number +% if available (not yet used in the view) +% - color: color of the ROI +% - centerCoords: coordinates of the center of the sphere (see coordSpace for the coordinate system used) +% - coordsSpace: space in which the coordinates are specified: options are 'base': the current base +% or 'MNI' (default): MNI space (mniInfo must be defined, see mlrImportSPMnormalization) +% - radius: radius of the ROI in mm (in magnet space, i.e. distances in the participant's space) +% + +function [thisView, params] = makeSphereROIatCoords(thisView,params,varargin) + +eval(evalargs(varargin)); +if ieNotDefined('justGetParams'),justGetParams = 0;end +% if ieNotDefined('defaultParams'),defaultParams = 0;end + +if ~ismember(nargin,[1 2 3]) + help('makeSphereROIatCoords'); + return +end + +if ieNotDefined('thisView') + mrWarnDlg('(makeSphereROIatCoords) No valid mrLoadRet view was provided'); + return +end + +% set default parameters +if ieNotDefined('params') + params = struct; +end + +if fieldIsNotDefined(params,'name') + params.name = ''; % name of the ROI. if left empty, the name will be ROI1, ROI2, etc..., whichever number if available (not yet used in the view) +end +if fieldIsNotDefined(params,'color') + params.color = 'black'; % color of the ROI +end +if fieldIsNotDefined(params,'centerCoords') + params.centerCoords = [0 0 0]; % coordinates of the center of the sphere (see coordSpace for the coordinate system used) +end +if fieldIsNotDefined(params,'coordsSpace') + params.coordsSpace = 'MNI'; % space in which the coordinates are specified: options are 'base': the current base (default) or 'MNI': MNI space (mniInfo must be defined, see mlrImportSPMnormalization) +end +if fieldIsNotDefined(params,'radius') + params.radius = 10; % radius of the ROI in mm (in magnet space, i.e. distances in the participant's space) +end + +if justGetParams + return; +end + +base2mag = viewGet(thisView,'base2mag'); +switch lower(params.coordsSpace) + case 'mni' + mniInfo = viewGet(thisView,'mniInfo'); + if isempty(mniInfo) + mrWarnDlg('(makeSphereROIatCoords) MNI normalization info is not defined for this participant/session. You must first run mlrImportSPMnormalization.') + return; + end + + mniCoords = params.centerCoords; + mniVolCoords = mniInfo.mni2mnivol*[mniCoords 1]'; + + [mniVolGridX,mniVolGridY,mniVolGridZ] = ndgrid(1:size(mniInfo.mnivol2magCoordMap,1),1:size(mniInfo.mnivol2magCoordMap,2),1:size(mniInfo.mnivol2magCoordMap,3)); + magCenterCoords(1) = interpn(mniVolGridX,mniVolGridY,mniVolGridZ, mniInfo.mnivol2magCoordMap(:,:,:,1), mniVolCoords(1), mniVolCoords(2), mniVolCoords(3)); + magCenterCoords(2) = interpn(mniVolGridX,mniVolGridY,mniVolGridZ, mniInfo.mnivol2magCoordMap(:,:,:,2), mniVolCoords(1), mniVolCoords(2), mniVolCoords(3)); + magCenterCoords(3) = interpn(mniVolGridX,mniVolGridY,mniVolGridZ, mniInfo.mnivol2magCoordMap(:,:,:,3), mniVolCoords(1), mniVolCoords(2), mniVolCoords(3)); + % coords.mag.x = mniInfo.mnivol2magCoordMap(round(mniVolCoords(1)),round(mniVolCoords(2)),round(mniVolCoords(3)),1); % + % coords.mag.y = mniInfo.mnivol2magCoordMap(round(mniVolCoords(1)),round(mniVolCoords(2)),round(mniVolCoords(3)),2); % less accurate but faster? + % coords.mag.z = mniInfo.mnivol2magCoordMap(round(mniVolCoords(1)),round(mniVolCoords(2)),round(mniVolCoords(3)),3); % + + case 'base' + magCenterCoords = base2mag * [params.centerCoords 1]'; + +end + +roi = makeEmptyROI(thisView, 'scanNum', 0, 'name',params.name); +roi.color = params.color; + +% find all coordinates within radius of center coordinates +switch(viewGet(thisView,'baseType')) + case 0 + baseDims = viewGet(thisView,'basedims'); + case {1,2} + baseCoordMap = viewGet(thisView,'baseCoordMap'); + baseDims = baseCoordMap.dims; +end + +[allBaseCoordsX,allBaseCoordsY,allBaseCoordsZ] = ndgrid(1:baseDims(1),1:baseDims(2),1:baseDims(3)); +allMagCoords = base2mag * [allBaseCoordsX(:) allBaseCoordsY(:) allBaseCoordsZ(:) ones(prod(baseDims),1)]'; +whichCoords = sqrt((allMagCoords(1,:) - magCenterCoords(1)).^2 + (allMagCoords(2,:)- magCenterCoords(2)).^2 +(allMagCoords(3,:) - magCenterCoords(3)).^2 ) < params.radius; +if ~nnz(whichCoords) % If we get no voxel, this may be because the radius is smaller than the mean distance between voxels + % the maximum distance between the center of a voxel and any point within that voxel is the distance between the centre and one corner of that voxels + voxelDistanceToCorner = sqrt(sum((viewGet(thisView,'baseVoxelSize')/2).^2)); + % check whether the center of the sphere falls within this distance from any voxel + whichCoords = sqrt((allMagCoords(1,:) - magCenterCoords(1)).^2 + (allMagCoords(2,:)- magCenterCoords(2)).^2 +(allMagCoords(3,:) - magCenterCoords(3)).^2 ) < voxelDistanceToCorner; + if nnz(whichCoords) % if yes + % Find the closet voxel and use that as the ROI + [~,whichCoords] = min(sqrt((allMagCoords(1,:) - magCenterCoords(1)).^2 + (allMagCoords(2,:)- magCenterCoords(2)).^2 +(allMagCoords(3,:) - magCenterCoords(3)).^2 )); + mrWarnDlg('(makeSphereROIatCoords) The specified radius is small compared to the voxel size of the current base. Returning single voxel closest to the specified center coordinates.'); + else % otherwise, the entire sphere must be outside the base volume + mrWarnDlg('(makeSphereROIatCoords) The sphere has no coordinates in the current base volume. No ROI was added to the view.'); + return; + end +end +roi.coords = unique(round(base2mag \ allMagCoords(:,whichCoords))','rows')'; + +% Add it to the view +thisView= viewSet(thisView,'newROI',roi); + diff --git a/mrLoadRet/ROI/modifyROI.m b/mrLoadRet/ROI/modifyROI.m index 88603a6c4..648819067 100644 --- a/mrLoadRet/ROI/modifyROI.m +++ b/mrLoadRet/ROI/modifyROI.m @@ -41,11 +41,15 @@ % Merge/remove coordinates if sgn<0 coords=newCoords; + fprintf('(modifyROI) '); elseif sgn==0 coords = removeCoords(newCoords,curCoords); + fprintf('(modifROI) Removed %d voxels (%.1f mm^3) from %s. ', size(curCoords,2) - size(coords,2), (size(curCoords,2) - size(coords,2)) * prod(roiVoxelSize), viewGet(view,'roiName')); elseif sgn>0 coords = mergeCoords(curCoords,newCoords); + fprintf('(modifyROI) Added %d voxels (%.1f mm^3) to %s. ', size(coords,2) - size(curCoords,2), (size(coords,2) - size(curCoords,2)) * prod(roiVoxelSize), viewGet(view,'roiName')); end +fprintf('New ROI size is %d voxels (%.1f mm^3).\n',size(coords,2), size(coords,2) * prod(roiVoxelSize)); view = viewSet(view,'ROIcoords',coords); return; @@ -64,7 +68,7 @@ % djh, 7/98 % djh, 2/2001, dumped coords2Indices & replaced with union(coords1',coords2','rows') -if ~isempty(coords1) & ~isempty(coords2) +if ~isempty(coords1) && ~isempty(coords2) coords = union(coords1',coords2','rows'); coords = coords'; else @@ -88,7 +92,7 @@ % djh, 2/2001, dumped coords2Indices & replaced with setdiff(coords1',coords2','rows') if ~isempty(coords1) && ~isempty(coords2) - coords = setdiff(coords2',coords1','rows'); + coords = setdiff(round(coords2'),round(coords1'),'rows'); % JB (01/05/2019): added rounding in case ROI coordinates are not integers (which can happen, but does not make sense) coords = coords'; else coords=coords2; diff --git a/mrLoadRet/ROI/newROI.m b/mrLoadRet/ROI/newROI.m index bdb1d7b06..96e289a0d 100644 --- a/mrLoadRet/ROI/newROI.m +++ b/mrLoadRet/ROI/newROI.m @@ -1,6 +1,6 @@ -function [view userCancel] = newROI(view,name,select,color,xform,voxelSize,coords,xformCode,vol2tal,vol2mag) +function [view, userCancel] = newROI(view,name,select,color,xform,voxelSize,coords,xformCode,vol2tal,vol2mag,notes) -% function view = newROI(view,[name],[select],[color],[xform],[voxelSize],[coords],[xformCode],[vol2tal],[vol2mag]) +% function view = newROI(view,[name],[select],[color],[xform],[voxelSize],[coords],[xformCode],[vol2tal],[vol2mag],[notes]) % % Makes new empty ROI, adds it to view.ROIs, and selects it. % @@ -22,27 +22,13 @@ % djh, 7/2005 (modified from mrLoadRet-3.1) userCancel = 1; -if isempty(viewGet(view,'curBase')) & ieNotDefined('xform') & ieNotDefined('voxelSize') +if isempty(viewGet(view,'curBase')) && ieNotDefined('xform') && ieNotDefined('voxelSize') mrErrorDlg('You must load a base anatomy before creating an ROI.'); end -if ieNotDefined('name') - % go through roi names and get the largest numbered - % roi name, i.e. ROI4 then make then new name ROI5 - maxnum = 0; - for i = 1:length(view.ROIs) - if regexp(view.ROIs(i).name,'^ROI\d+$') - maxnum = max(maxnum,str2num(view.ROIs(i).name(4:end))); - end - end - name=sprintf('ROI%.0f',maxnum+1); -end if ieNotDefined('select') select = 1; end -if ieNotDefined('color') - color = 'black'; -end if ieNotDefined('sformCode') baseNum = viewGet(view,'currentBase'); sformCode = viewGet(view,'baseSformCode',baseNum); @@ -73,24 +59,48 @@ baseNum = viewGet(view,'currentBase'); vol2tal = viewGet(view,'baseVol2tal',baseNum); end +if ieNotDefined('notes') + notes = ''; +end -colors = putOnTopOfList(color,color2RGB); -roiParams{1} = {'name',name,'Name of roi, avoid using punctuation and space'}; -roiParams{2} = {'color',colors,'type=popupmenu','The color that the roi will display in'}; -roiParams{3} = {'notes','','Brief notes about the ROI'}; -params = mrParamsDialog(roiParams,'Create a new ROI'); -if isempty(params),return,end + +if ieNotDefined('color') || ieNotDefined('name') + if ieNotDefined('name') + % go through roi names and get the largest numbered + % roi name, i.e. ROI4 then make then new name ROI5 + maxnum = 0; + for i = 1:length(view.ROIs) + if regexp(view.ROIs(i).name,'^ROI\d+$') + maxnum = max(maxnum,str2num(view.ROIs(i).name(4:end))); + end + end + name=sprintf('ROI%.0f',maxnum+1); + end + if ieNotDefined('color') + color = 'black'; + end + colors = putOnTopOfList(color,color2RGB); + roiParams{1} = {'name',name,'Name of roi, avoid using punctuation and space'}; + roiParams{2} = {'color',colors,'type=popupmenu','The color that the roi will display in'}; + roiParams{3} = {'notes','','Brief notes about the ROI'}; + params = mrParamsDialog(roiParams,'Create a new ROI'); + if isempty(params),return,end + + name = params.name; + color = params.color; + notes = params.notes; +end % Set required fields. Additional (optional) optional fields are set by % isroi which is called by viewSet newROI. -ROI.name = params.name; +ROI.name = name; ROI.viewType = view.viewType; -ROI.color = params.color; +ROI.color = color; ROI.xform = xform; ROI.sformCode = sformCode; ROI.voxelSize = voxelSize; ROI.coords = coords; -ROI.notes = params.notes; +ROI.notes = notes; ROI.vol2mag = vol2mag; ROI.vol2tal = vol2tal; @@ -101,7 +111,7 @@ ROI.createdFromSession = getLastDir(viewGet(view,'homeDir')); % Add it to the view -[view tf]= viewSet(view,'newROI',ROI); +[view, tf]= viewSet(view,'newROI',ROI); % The user could still have canceled (when there is a name conflict) % so check for that diff --git a/mrLoadRet/ROI/restrictROI.m b/mrLoadRet/ROI/restrictROI.m index d75d70c51..a2b0ab4da 100644 --- a/mrLoadRet/ROI/restrictROI.m +++ b/mrLoadRet/ROI/restrictROI.m @@ -25,14 +25,14 @@ % Save prevCoords for undo thisView = viewSet(thisView,'prevROIcoords',ROIcoords); -% Transform ROI roiScanCoords to overlay -roiScanCoords = round( viewGet(thisView,'scan2roi',ROInum,scan) \ ROIcoords); +% Transform ROI coords (in ROI space) to overlay (in overlay/scan space) +roiScanCoords = round( viewGet(thisView,'scan2roi',ROInum,scan) \ ROIcoords); coordsInfo.base2overlay = eye(4); coordsInfo.baseCoordsHomogeneous = roiScanCoords; coordsInfo.baseDims = [size(ROIcoords,2) 1 1]; -%find which voxels are not clipped in the current overlay(s) and overlayAlpha (in overlay space) +%find which voxels are not clipped in the current overlay(s) and overlayAlpha (in overlay/scan space) overlayList = viewGet(thisView,'curOverlay'); nOverlays = length(overlayList); cOverlay=0; @@ -44,7 +44,7 @@ alphaOverlayList(cOverlay) = thisAlphaOverlay; end end -roiMask = maskOverlay(thisView,[overlayList alphaOverlayList],scan,coordsInfo); +roiMask = maskOverlay(thisView,[overlayList alphaOverlayList],scan,coordsInfo); % this returns a mask in ROI space, for the coordinates specified in coordsInfo (in overlay/scan space) roiMask = reshape(roiMask{1},[size(roiMask{1},1) nOverlays, 2]); % keep the corresponding voxels in ROI space cOverlay=0; @@ -55,10 +55,16 @@ end end %Keep voxels that are non-zero in any of the overlays, but non-zero both in overlay and alphaOverlay, -ROIcoords = ROIcoords(:,any(all(roiMask,3),2)); - - -thisView = viewSet(thisView,'roiCoords',ROIcoords,ROInum); +% we'll do this differently depending on whether this is the (unique) currently selected ROI or not +if isequal(ROInum, viewGet(thisView,'curRoi')) % in this is the current ROI and only one ROi is selected, we use modifyROI to remove the voxels + % This will update the old ROI coordinates in the view and allows the user to use Undo + ROIcoordsToRemove = ROIcoords(:,~any(all(roiMask,3),2)); + ROIvoxelSize = viewGet(thisView,'roiVoxelSize',ROInum); + thisView = modifyROI(thisView,ROIcoordsToRemove,eye(4),ROIvoxelSize,0); +else % if there either are several selected ROIs or we're restricting an ROI that is not currently selected, we change the ROI coordinates in the view (no Undo possible) + ROIcoords = ROIcoords(:,any(all(roiMask,3),2)); + thisView = viewSet(thisView,'roiCoords',ROIcoords,ROInum); +end return diff --git a/mrLoadRet/ROI/xformROIcoords.m b/mrLoadRet/ROI/xformROIcoords.m index 1334541c6..3ee55d3c9 100644 --- a/mrLoadRet/ROI/xformROIcoords.m +++ b/mrLoadRet/ROI/xformROIcoords.m @@ -39,12 +39,7 @@ if isequal(xformRound,eye(4)) && isequal(inputVoxSizeRound,outputVoxSizeRound) % This is where coordinates get rounded - may need to change % this if we keep roi coordinates at finer than 1x1x1 mm resolution - coords = round(coords); - % get unique coordinates, do it as a linear array since it is faster - maxCoord = repmat(max(coords(:)),1,3); - coordsLinear = unique(mrSub2ind(maxCoord,coords(1,:),coords(2,:),coords(3,:))); - [newcoords(1,:) newcoords(2,:) newcoords(3,:)] = ind2sub(maxCoord,coordsLinear); - newcoords(4,:) = 1; + newcoords = unique(round(coords)','rows','stable')'; return end diff --git a/mrLoadRet/View/isview.m b/mrLoadRet/View/isview.m index 026242fae..705d813ff 100644 --- a/mrLoadRet/View/isview.m +++ b/mrLoadRet/View/isview.m @@ -1,5 +1,5 @@ -function [tf, view, unknownFields] = isview(view) -% function [tf view] = isview(view) +function [tf, view, unknownFields] = isview(view,mlrGlobals) +% function [tf view] = isview(view,,) % % Checks to see if it is a valid view structure. Can be called with % either one or two output arguments: @@ -13,10 +13,18 @@ % If called with two output arguments then an attempt is made to make it % into a valid view structure by setting optional fields to default % values. +% +% if optional argument mlrGlobals is true (default), checks for consistency with +% the view saved in global variable MLR % % djh, 2007 -mrGlobals +if ieNotDefined('mlrGlobals') + mlrGlobals = true; +end +if mlrGlobals + mrGlobals +end unknownFields = []; if (nargout >= 2) % Add optional fields and return true if the view with optional fields is @@ -95,19 +103,21 @@ return end -% confirm that there is view in MLR.views with the viewNum -if isempty(view.viewNum) || (view.viewNum < 1) || (view.viewNum > length(MLR.views)) || isempty(MLR.views{view.viewNum}) - tf = false; - return -end +if mlrGlobals + % confirm that there is view in MLR.views with the viewNum + if isempty(view.viewNum) || (view.viewNum < 1) || (view.viewNum > length(MLR.views)) || isempty(MLR.views{view.viewNum}) + tf = false; + return + end -% Confirm that MLR.views{viewNum} and view have the same fields -names1 = fieldnames(orderfields(MLR.views{view.viewNum})); -names2 = fieldnames(view); -if length(names1) == length(names2) - tf = all(strcmp(names1,names2)); -else - tf = false; + % Confirm that MLR.views{viewNum} and view have the same fields + names1 = fieldnames(orderfields(MLR.views{view.viewNum})); + names2 = fieldnames(view); + if length(names1) == length(names2) + tf = all(strcmp(names1,names2)); + else + tf = false; + end end %see if there are any unknown fields diff --git a/mrLoadRet/View/mrOpenWindow.m b/mrLoadRet/View/mrOpenWindow.m index 4c3d524bc..b185f78a6 100644 --- a/mrLoadRet/View/mrOpenWindow.m +++ b/mrLoadRet/View/mrOpenWindow.m @@ -1,4 +1,4 @@ -function view = mrOpenWindow(viewType,mrLastView) +function view = mrOpenWindow(viewType,mrLastView,noGUI) % % view = openWindow(viewType) % @@ -6,6 +6,8 @@ % $Id: mrOpenWindow.m 2838 2013-08-12 12:52:20Z julien $ if ieNotDefined('viewType'),viewType = 'Volume';end +if ieNotDefined('noGUI'), noGUI = false;end + % note we don't use ieNotDefined here, because % if mrLastView is empty then the user doesn't % want to ignore mrLastView @@ -19,94 +21,100 @@ view = newView(viewType); % view is empty if it failed to initialize -if ~isempty(view) +if isempty(view) + return +end + +if noGUI + % skip all the GUI-related stuff +else + fig = mrLoadRetGUI('viewNum',view.viewNum); set(fig,'CloseRequestFcn',@mrQuit); view = viewSet(view,'figure',fig); -else - return -end -% set the location of the figure -figloc = mrGetFigLoc('mrLoadRetGUI'); -if ~isempty(figloc) - %deal with multiple monitors - [whichMonitor,figloc]=getMonitorNumber(figloc,getMonitorPositions); - set(fig,'Position',figloc); -end + + % set the location of the figure + figloc = mrGetFigLoc('mrLoadRetGUI'); + if ~isempty(figloc) + %deal with multiple monitors + [whichMonitor,figloc]=getMonitorNumber(figloc,getMonitorPositions); + set(fig,'Position',figloc); + end + + set(fig,'Renderer','painters') + % set the keyoard accelerator + %mrAcceleratorKeys('init',view.viewNum); -set(fig,'Renderer','painters') -% set the keyoard accelerator -%mrAcceleratorKeys('init',view.viewNum); - -% set the position of the main base viewer -gui = guidata(fig); -gui.marginSize = 0.01; -gui.anatPosition = [0.3 0.2+gui.marginSize 1-0.3-gui.marginSize 1-0.2-2*gui.marginSize]; - -% create 3 axis for display all three orientations at once -% set up the position of each of the 3 axis. Start them -% in an arbitrary position, being careful not to overlap -gui.sliceAxis(1) = subplot('Position',[0 0 0.01 0.01],'Parent',fig); -axis(gui.sliceAxis(1),'off'); -set(gui.sliceAxis(1),'HandleVisibility','off'); -gui.sliceAxis(2) = subplot('Position',[0.02 0 0.01 0.01],'Parent',fig); -axis(gui.sliceAxis(2),'off'); -set(gui.sliceAxis(2),'HandleVisibility','off'); -gui.sliceAxis(3) = subplot('Position',[0.02 0.02 0.01 0.01],'Parent',fig); -axis(gui.sliceAxis(3),'off'); -set(gui.sliceAxis(3),'HandleVisibility','off'); - -% save the axis handles -guidata(fig,gui); - -% add controls for multiAxis -mlrAdjustGUI(view,'add','control','axisSingle','style','radio','value',0,'position', [0.152 0.725 0.1 0.025],'String','Single','Callback',@multiAxisCallback); -mlrAdjustGUI(view,'add','control','axisMulti','style','radio','value',0,'position', [0.152 0.695 0.1 0.025],'String','Multi','Callback',@multiAxisCallback); -mlrAdjustGUI(view,'add','control','axis3D','style','radio','value',0,'position', [0.152 0.665 0.1 0.025],'String','3D','Callback',@multiAxisCallback); - -% Initialize the scan slider -nScans = viewGet(view,'nScans'); -mlrGuiSet(view,'nScans',nScans); -mlrGuiSet(view,'scan',min(1,nScans)); -% Initialize the slice slider -mlrGuiSet(view,'nSlices',0); -% init showROIs to all perimeter -view = viewSet(view,'showROIs','all perimeter'); -view = viewSet(view,'labelROIs',1); - -%get colormaps in the colormapFunctions directory -colorMapsFolder = [fileparts(which('mrLoadRet')) '/colormapFunctions/']; -colorMapFiles = dir([colorMapsFolder '*.m']); -if ~isempty(colorMapFiles) - colorMapList = cell(1,length(colorMapFiles)); - for iFile=1:length(colorMapFiles) - colorMapList{iFile} = stripext(colorMapFiles(iFile).name); + % set the position of the main base viewer + gui = guidata(fig); + gui.marginSize = 0.01; + gui.anatPosition = [0.3 0.2+gui.marginSize 1-0.3-gui.marginSize 1-0.2-2*gui.marginSize]; + + % create 3 axis for display all three orientations at once + % set up the position of each of the 3 axis. Start them + % in an arbitrary position, being careful not to overlap + gui.sliceAxis(1) = subplot('Position',[0 0 0.01 0.01],'Parent',fig); + axis(gui.sliceAxis(1),'off'); + set(gui.sliceAxis(1),'HandleVisibility','off'); + gui.sliceAxis(2) = subplot('Position',[0.02 0 0.01 0.01],'Parent',fig); + axis(gui.sliceAxis(2),'off'); + set(gui.sliceAxis(2),'HandleVisibility','off'); + gui.sliceAxis(3) = subplot('Position',[0.02 0.02 0.01 0.01],'Parent',fig); + axis(gui.sliceAxis(3),'off'); + set(gui.sliceAxis(3),'HandleVisibility','off'); + + % save the axis handles + guidata(fig,gui); + + % add controls for multiAxis + mlrAdjustGUI(view,'add','control','axisSingle','style','radio','value',0,'position', [0.152 0.725 0.1 0.025],'String','Single','Callback',@multiAxisCallback); + mlrAdjustGUI(view,'add','control','axisMulti','style','radio','value',0,'position', [0.152 0.695 0.1 0.025],'String','Multi','Callback',@multiAxisCallback); + mlrAdjustGUI(view,'add','control','axis3D','style','radio','value',0,'position', [0.152 0.665 0.1 0.025],'String','3D','Callback',@multiAxisCallback); + + % Initialize the scan slider + nScans = viewGet(view,'nScans'); + mlrGuiSet(view,'nScans',nScans); + mlrGuiSet(view,'scan',min(1,nScans)); + % Initialize the slice slider + mlrGuiSet(view,'nSlices',0); + % init showROIs to all perimeter + view = viewSet(view,'showROIs','all perimeter'); + view = viewSet(view,'labelROIs',1); + + %get colormaps in the colormapFunctions directory + colorMapsFolder = [fileparts(which('mrLoadRet')) '/colormapFunctions/']; + colorMapFiles = dir([colorMapsFolder '*.m']); + if ~isempty(colorMapFiles) + colorMapList = cell(1,length(colorMapFiles)); + for iFile=1:length(colorMapFiles) + colorMapList{iFile} = stripext(colorMapFiles(iFile).name); + end + % install default colormaps + % that will show up when you do /Edit/Overlay + mlrAdjustGUI(view,'add','colormap',colorMapList); + else + disp(['(mrOpenWindow) No colormap found in folder ' colorMapsFolder]); end - % install default colormaps - % that will show up when you do /Edit/Overlay - mlrAdjustGUI(view,'add','colormap',colorMapList); -else - disp(['(mrOpenWindow) No colormap found in folder ' colorMapsFolder]); -end -% add a menu item to export analysis struct -mlrAdjustGUI(view,'add','menu','Export for Analysis','/File/Export/','Callback',@mlrExportForAnalysis); + % add a menu item to export analysis struct + mlrAdjustGUI(view,'add','menu','Export for Analysis','/File/Export/','Callback',@mlrExportForAnalysis); -% add a menu item to export surfaces to wavefront off -mlrAdjustGUI(view,'add','menu','Export surface','/File/Base anatomy/Use current scan','Callback',@mlrExportSurface,'Separator','on'); -mlrAdjustGUI(view,'set','Export surface','Enable','off'); + % add a menu item to export surfaces to wavefront off + mlrAdjustGUI(view,'add','menu','Export surface','/File/Base anatomy/Use current scan','Callback',@mlrExportSurface,'Separator','on'); + mlrAdjustGUI(view,'set','Export surface','Enable','off'); -% Add plugins -if ~isempty(which('mlrPlugin')), view = mlrPlugin(view);end + % Add plugins + if ~isempty(which('mlrPlugin')), view = mlrPlugin(view);end +end baseLoaded = 0; if ~isempty(mrLastView) && mlrIsFile(sprintf('%s.mat',stripext(mrLastView))) - disppercent(-inf,sprintf('(mrOpenWindow) Loading %s',mrLastView)); + mlrDispPercent(-inf,sprintf('(mrOpenWindow) Loading %s',mrLastView)); [mrLastView, lastViewSettings]=mlrLoadLastView(mrLastView); - disppercent(inf); + mlrDispPercent(inf); % if the old one exists, then set up fields -% disppercent(-inf,'(mrOpenWindow) Restoring last view'); +% mlrDispPercent(-inf,'(mrOpenWindow) Restoring last view'); if ~isempty(mrLastView) %Add any missing field to make sure things don't crash [~,mrLastView,unknownFields]=isview(mrLastView); @@ -119,7 +127,7 @@ end % open up base anatomy from last session if isfield(mrLastView,'baseVolumes') - disppercent(-inf,sprintf('(mrOpenWindow) installing Base Anatomies')); + mlrDispPercent(-inf,sprintf('(mrOpenWindow) installing Base Anatomies')); if length(mrLastView.baseVolumes) >= 1 baseLoaded = 1; % Add it to the list of base volumes and select it @@ -136,12 +144,15 @@ %try to load [view,baseLoaded] = loadAnatomy(view); end - disppercent(inf); + mlrDispPercent(inf); end % change group view = viewSet(view,'curGroup',mrLastView.curGroup); - nScans = viewGet(view,'nScans'); - mlrGuiSet(view,'nScans',nScans); + if ~noGUI + nScans = viewGet(view,'nScans'); + mlrGuiSet(view,'nScans',nScans); % JB: I dont' think this call to mlrGuiSet belongs in this function. + % It's probably not even necessary since the number of scans should have been set by viewSet(v,'curGroup') + end if baseLoaded % slice orientation from last run view = viewSet(view,'curBase',mrLastView.curBase); @@ -154,7 +165,7 @@ if isfield(mrLastView,'analyses') for anum = 1:length(mrLastView.analyses) view = viewSet(view,'newAnalysis',mrLastView.analyses{anum}); -% disppercent(anum /length(mrLastView.analyses)); +% mlrDispPercent(anum /length(mrLastView.analyses)); end view = viewSet(view,'curAnalysis',mrLastView.curAnalysis); end @@ -173,7 +184,7 @@ % read ROIs into current view if isfield(mrLastView,'ROIs') - disppercent(-inf,sprintf('(mrOpenWindow) installing ROIs')); + mlrDispPercent(-inf,sprintf('(mrOpenWindow) installing ROIs')); for roinum = 1:length(mrLastView.ROIs) view = viewSet(view,'newROI',mrLastView.ROIs(roinum)); end @@ -187,31 +198,36 @@ if ~fieldIsNotDefined(mrLastView,'roiGroup') view = viewSet(view,'roiGroup',mrLastView.roiGroup); end - disppercent(inf); + mlrDispPercent(inf); end - % check panels that need to be hidden - if ~isempty(lastViewSettings) && isfield(lastViewSettings,'panels') - for iPanel = 1:length(lastViewSettings.panels) - % if it is not displaying then turn it off - if ~lastViewSettings.panels{iPanel}{4} - panelName = lastViewSettings.panels{iPanel}{1}; - mlrGuiSet(view,'hidePanel',panelName); - % turn off check - mlrAdjustGUI(view,'set',panelName,'Checked','off'); - end + + if noGUI + % skip GUI stuff + else + % check panels that need to be hidden + if ~isempty(lastViewSettings) && isfield(lastViewSettings,'panels') + for iPanel = 1:length(lastViewSettings.panels) + % if it is not displaying then turn it off + if ~lastViewSettings.panels{iPanel}{4} + panelName = lastViewSettings.panels{iPanel}{1}; + mlrGuiSet(view,'hidePanel',panelName); + % turn off check + mlrAdjustGUI(view,'set',panelName,'Checked','off'); + end + end end + % add here, to load more info... + % and refresh + mlrDispPercent(-inf,sprintf('(mrOpenWindow) Refreshing MLR display')); + refreshMLRDisplay(view.viewNum); + mlrDispPercent(inf); end - % add here, to load more info... - % and refresh - disppercent(-inf,sprintf('(mrOpenWindow) Refreshing MLR display')); - refreshMLRDisplay(view.viewNum); - disppercent(inf); end else [view,baseLoaded] = loadAnatomy(view); - if baseLoaded + if ~noGUI && baseLoaded refreshMLRDisplay(view.viewNum); end end diff --git a/mrLoadRet/View/viewGet.m b/mrLoadRet/View/viewGet.m index d17b4f0d7..7f6367879 100644 --- a/mrLoadRet/View/viewGet.m +++ b/mrLoadRet/View/viewGet.m @@ -102,6 +102,10 @@ case {'protocol'} % subject = viewGet(view,'protocol') val = MLR.session.protocol; + + case {'mniinfo'} + % mniInfo = viewGet(view,'mniInfo') + val = MLR.mniInfo; % subdirectories case {'homedir','homedirectory','sessiondirectory'} @@ -289,9 +293,6 @@ groupName = varargin{1}; groupNames = {MLR.groups(:).name}; val = find(strcmp(groupName,groupNames)); - if isempty(val) - disp(sprintf('(viewGet) Could not find group: %s',groupName)); - end % if passed in a valid number just return that number elseif isnumeric(varargin{1}) && isequal(size(varargin{1}),[1 1]) if (varargin{1} >= 1) && (varargin{1} <= viewGet(view,'nGroups')) @@ -770,6 +771,26 @@ end end end + case{'mousedowntalcoords'} + % talCoords = viewGet(view,'mouseDownTalCoords') + viewNum = viewGet(view,'viewNum'); + if isfield(MLR,'interrogator') + if length(MLR.interrogator) >= viewNum + if isfield(MLR.interrogator{viewNum},'mouseDownTalCoords') + val = MLR.interrogator{viewNum}.mouseDownTalCoords; + end + end + end + case{'mousedownmnicoords'} + % mniCoords = viewGet(view,'mouseDownMniCoords') + viewNum = viewGet(view,'viewNum'); + if isfield(MLR,'interrogator') + if length(MLR.interrogator) >= viewNum + if isfield(MLR.interrogator{viewNum},'mouseDownMniCoords') + val = MLR.interrogator{viewNum}.mouseDownMniCoords; + end + end + end case {'spikeinfo'} % eyepos = viewGet(view,'spikeinfo',scanNum,[groupNum]); val = []; @@ -1417,7 +1438,7 @@ % if numeric there is nothing to do, just return value if isnumeric(baseName) val = baseName; - else + elseif ~isempty(view.baseVolumes) % otherwise look up the baseNum baseNames = {view.baseVolumes(:).name}; val = find(strcmp(baseName,baseNames)); @@ -1508,7 +1529,7 @@ [tf val] = isbase(val); end case {'basecoordmappath'} - % basedata = viewGet(view,'baseCoordMapPath',[baseNum],[corticalDepth]) + % basedata = viewGet(view,'baseCoordMapPath',[baseNum]) b = getBaseNum(view,varargin); n = viewGet(view,'numberofbasevolumes'); val = []; @@ -1524,17 +1545,19 @@ innerCoordsFilename = view.baseVolumes(b).coordMap.innerCoordsFileName; subjectDir = ''; % tell user what we are doing - disp(sprintf('(viewGet:baseCoordMapPath) Surface directory %s for base %s does not exist, searching in volumeDirectory: %s',val,viewGet(view,'baseName',b),volumeDirectory)); - for i = 1:length(volumeDirectoryList) + baseName = viewGet(view,'baseName',b); + oneTimeWarning(['viewGetBaseCoordMap_' baseName],sprintf('(viewGet:baseCoordMapPath) Surface directory %s for base %s does not exist, searching in volumeDirectory: %s',val,baseName,volumeDirectory)); + maxChars = 0; + for i = 1:length(volumeDirectoryList) % for each volume directory in the list, see if the directory name % matches the first part of the baseVolumes anatomy (this assumes % that people use a convention like calling the directory s001 and % calling the anatomy file s001anatomy or something like that. matchName = strfind(view.baseVolumes(b).coordMap.anatFileName,volumeDirectoryList(i).name); - if ~isempty(matchName) && isequal(matchName(1),1) - % we have a match, for the subject directory under the volume direcotry - subjectDir = fullfile(volumeDirectory,volumeDirectoryList(i).name); - break; + if ~isempty(matchName) && isequal(matchName(1),1) ... % we have a match, for the subject directory under the volume directory + && length(volumeDirectoryList(i).name)>maxChars % and this name has more characters than any previous match + maxChars = length(volumeDirectoryList(i).name); + subjectDir = fullfile(volumeDirectory,volumeDirectoryList(i).name); end end % not found, give up @@ -1591,21 +1614,25 @@ else corticalDepths = varargin{2}; end - if b & (b > 0) & (b <= n) + if b && (b > 0) && (b <= n) val = view.baseVolumes(b).coordMap; - % see if the coordMap is calculated for the correct number of cortical depth bins - if ~isempty(val) && (~isfield(val,'corticalDepths') || ~isequal(val.corticalDepths,corticalDepths)) - if isfield(val,'innerCoords') && isfield(val,'outerCoords') - % if not, then we have to do it - % val.coords = (1-corticalDepth)*val.innerCoords + corticalDepth*val.outerCoords; - val.coords = NaN([size(val.innerCoords) length(corticalDepths)]); - cDepth=0; - for iDepth = corticalDepths; - cDepth=cDepth+1; - val.coords(:,:,:,:,cDepth) = val.innerCoords + iDepth*(val.outerCoords-val.innerCoords); + if ~isempty(val) + % see if the coordMap is calculated for the correct number of cortical depth bins + if (~isfield(val,'corticalDepths') || ~isequal(val.corticalDepths,corticalDepths)) + if isfield(val,'innerCoords') && isfield(val,'outerCoords') + % if not, then we have to do it + % val.coords = (1-corticalDepth)*val.innerCoords + corticalDepth*val.outerCoords; + val.coords = NaN([size(val.innerCoords) length(corticalDepths)]); + cDepth=0; + for iDepth = corticalDepths + cDepth=cDepth+1; + val.coords(:,:,:,:,cDepth) = val.innerCoords + iDepth*(val.outerCoords-val.innerCoords); + end + val.corticalDepths = corticalDepths; end - val.corticalDepths = corticalDepths; end + % Correct path if necessary + val.path = viewGet(view,'basecoordmappath',varargin{:}); end end case {'basesurface'} @@ -2319,9 +2346,9 @@ % basevoxelsize = viewGet(view,'basevoxelsize',[baseNum]) [b baseVolume] = getBaseNum(view,varargin); if ~isempty(baseVolume) - val = baseVolume.hdr.pixdim([2,3,4])';; + val = baseVolume.hdr.pixdim([2,3,4])'; end - + % ROI case {'visiblerois'} % roiList = viewGet(view,'visiblerois') diff --git a/mrLoadRet/View/viewSet.m b/mrLoadRet/View/viewSet.m index 387d0ddba..bdeb47560 100644 --- a/mrLoadRet/View/viewSet.m +++ b/mrLoadRet/View/viewSet.m @@ -96,6 +96,9 @@ % view = viewSet(view,'viewtype',string); view.viewType = val; + case {'mniinfo'} + MLR.mniInfo = val; + % ------------------------------------------- % Group @@ -281,15 +284,29 @@ % view = viewSet(view,'deleteGroup',groupNum); groupnum = viewGet(view,'groupNum',val); groupName = viewGet(view,'groupName',groupnum); - nScans = viewGet(view,'nScans'); + nScans = viewGet(view,'nScans', groupnum); % confirm with user if nScans > 0 - queststr = sprintf('There are %i scans in group %s. Are you sure you want to delete?',nScans,groupName); + queststr = sprintf('There are %i scans in group %s. Are you sure you want to delete?',nScans,groupName); + % adding this sub if to check if we want to delete the group in a + % script, without having a questdlg pop up. Pass varargin 'quiet', + % sets answer to 'Yes' + elseif ~isempty(varargin) + if strcmpi(varargin{1},'quiet') || nScans == 0 + disp('Caught quiet flag, deleting empty group...') + queststr = 'Yes'; + end else queststr = sprintf('Are you sure you want to delete empty group: %s?',groupName); end - if ~strcmp(questdlg(queststr,'Delete group'),'Yes') - return + % Check the queststr again, if we are verbose, then it will pop up + % with another questdlg (as in previous version), but if it's 'Yes', + % then no pop up, and carry on. + if ~strcmpi(queststr,'Yes') + deleteGroup = questdlg(queststr); + if ~strcmp(deleteGroup,'Yes') + return + end end if strcmp(groupName,'Raw') mrWarnDlg('Cannot delete Raw group'); @@ -850,22 +867,24 @@ mlrGuiSet(view,'rotate',baseRotate); end baseTilt = viewGet(view,'baseTilt',baseNum); - if baseType == 2 - % allow export - mlrAdjustGUI(view,'set','Export surface','Enable','on'); - % allow tilt - mlrGuiSet(view,'baseTilt',baseTilt); - if ~mrInterrogator('isactive',viewGet(view,'viewNum')); - % turn on free rotation - mlrSetRotate3d(view,1); - else - mlrSetRotate3d(view,0); - end - else - % do not allow export - mlrAdjustGUI(view,'set','Export surface','Enable','off'); - % otherwise turn off free rotation - mlrSetRotate3d(view,0); + if ~isempty(viewGet(view,'fignum')) + if baseType == 2 + % allow export + mlrAdjustGUI(view,'set','Export surface','Enable','on'); + % allow tilt + mlrGuiSet(view,'baseTilt',baseTilt); + if ~mrInterrogator('isactive',viewGet(view,'viewNum')); + % turn on free rotation + mlrSetRotate3d(view,1); + else + mlrSetRotate3d(view,0); + end + else + % do not allow export + mlrAdjustGUI(view,'set','Export surface','Enable','off'); + % otherwise turn off free rotation + mlrSetRotate3d(view,0); + end end end % see if there are any registered callbacks @@ -1553,7 +1572,7 @@ analysisNum = varargin{1}; end analysis = viewGet(view,'analysis',analysisNum); - disppercent(-inf,['(viewSet:newOverlay) Installing overlays for ' analysis.name]); + mlrDispPercent(-inf,['(viewSet:newOverlay) Installing overlays for ' analysis.name]); nOverlays = viewGet(view,'numberofOverlays',analysisNum); newOverlayNum = nOverlays; @@ -1647,9 +1666,9 @@ end end end - disppercent(iOverlay/length(val)); + mlrDispPercent(iOverlay/length(val)); end - disppercent(inf); + mlrDispPercent(inf); % Update the gui overlayNames = viewGet(view,'overlayNames',analysisNum); @@ -1815,18 +1834,29 @@ case {'overlaycmap'} % view = viewSet(view,'overlaycmap',cmapName,[overlayNum]); - if ieNotDefined('varargin') - overlayNum = viewGet(view,'currentOverlay'); - else - overlayNum = varargin{1}; - end - analysisNum = viewGet(view,'currentAnalysis'); - if ~isempty(analysisNum) & ~isempty(overlayNum) & ... - ~isempty(view.analyses{analysisNum}.overlays) - evalstr = [val,'(256)']; - view.analyses{analysisNum}.overlays(overlayNum).colormap = eval(evalstr); - end + if ieNotDefined('varargin') + overlayNum = viewGet(view,'currentOverlay'); + else + overlayNum = varargin{1}; + end + analysisNum = viewGet(view,'currentAnalysis'); + if ischar(val)% if the cMap is an already defined one + if ~isempty(analysisNum) & ~isempty(overlayNum) & ... + ~isempty(view.analyses{analysisNum}.overlays) + evalstr = [val,'(256)']; + for iOverlay = 1:length(overlayNum) + view.analyses{analysisNum}.overlays(overlayNum(iOverlay)).colormap = eval(evalstr); + end + end + elseif ~ischar(val) && size(val,2)==3 % if you want to add a new user defined color map + for iOverlay = 1:length(overlayNum) + view.analyses{analysisNum}.overlays(overlayNum(iOverlay)).colormap = val; + end + else + mrWarnDlg(sprintf('Unknown Color Map')); + end + case {'overlaymin'} % view = viewSet(view,'overlaymin',number,[overlayNum]); curOverlay = viewGet(view,'currentOverlay'); @@ -1931,12 +1961,15 @@ if ~isempty(analysisNum) & ~isempty(overlayNum) & ... ~isempty(view.analyses{analysisNum}.overlays) for iOverlay = overlayNum - view.analyses{analysisNum}.overlays(iOverlay).colorRange = val; + if iOverlay > 0 && iOverlay <= length(view.analyses{analysisNum}.overlays) + view.analyses{analysisNum}.overlays(iOverlay).colorRange = val; + end end end - case {'alpha'} + case {'alpha','overlayalpha'} % view = viewSet(view,'alpha',number,[overlayNum]); + % view = viewSet(view,'overlayalpha',number,[overlayNum]); curOverlay = viewGet(view,'currentOverlay'); if ~isempty(varargin) overlayNum = varargin{1}; @@ -2077,7 +2110,8 @@ roinums = val; numrois = viewGet(view,'numberofrois'); curroi = viewGet(view,'currentROI'); - % Remove it and reset currentROI + roinums = intersect(roinums,1:numrois,'stable'); %make sure all ROIs to delete actually exist in the view + % Remove it/them and reset currentROI remainingRois = setdiff(1:numrois,roinums); curroi = find(ismember(remainingRois,curroi)); view.ROIs=view.ROIs(remainingRois); @@ -2105,7 +2139,8 @@ currentRoi = viewGet(view,'currentRoi'); if ~isequal(roiNum,currentRoi); numROIs = viewGet(view,'numberofROIs'); - if (roiNum > 0) & (roiNum <= numROIs) + roiNum = intersect(roiNum,1:numROIs,'stable'); %make sure all ROIs to select actually exist in the view + if ~isempty(roiNum) view.curROI = roiNum; % update popup menu mlrGuiSet(view,'roi',roiNum); @@ -2122,6 +2157,7 @@ else ROInum = viewGet(view,'currentROI'); end + ROInum = intersect(ROInum,1:viewGet(view,'numberofrois'),'stable'); %make sure all ROIs to set actually exist in the view if ~isempty(ROInum) view.ROIs(ROInum).coords = val; view.ROIs(ROInum).date = datestr(now); @@ -2139,8 +2175,11 @@ else roiNum = curRoi; end + roiNum = intersect(roiNum,1:viewGet(view,'numberofrois'),'stable'); %make sure all ROIs to set actually exist in the view if ~isempty(roiNum) - view.ROIs(roiNum).color = val; + for iRoi = roiNum + view.ROIs(iRoi).color = val; + end end case {'roinotes'} @@ -2151,6 +2190,7 @@ else roiNum = curRoi; end + roiNum = intersect(roiNum,1:viewGet(view,'numberofrois'),'stable'); %make sure all ROIs to set actually exist in the view if ~isempty(roiNum) view.ROIs(roiNum).notes = val; end @@ -2166,6 +2206,7 @@ else roiNum = curRoi; end + roiNum = intersect(roiNum,1:viewGet(view,'numberofrois'),'stable'); %make sure all ROIs to set actually exist in the view if ~isempty(roiNum) view.ROIs(roiNum).vol2mag = val; end @@ -2181,6 +2222,7 @@ else roiNum = curRoi; end + roiNum = intersect(roiNum,1:viewGet(view,'numberofrois'),'stable'); %make sure all ROIs to set actually exist in the view if ~isempty(roiNum) view.ROIs(roiNum).vol2tal = val; end @@ -2194,6 +2236,7 @@ else roiNum = curRoi; end + roiNum = intersect(roiNum,1:viewGet(view,'numberofrois'),'stable'); %make sure all ROIs to set actually exist in the view if ~isempty(roiNum) view.ROIs(roiNum).sformCode = val; end @@ -2207,6 +2250,7 @@ else roiNum = curRoi; end + roiNum = intersect(roiNum,1:viewGet(view,'numberofrois'),'stable'); %make sure all ROIs to set actually exist in the view if ~isempty(roiNum) view.ROIs(roiNum).xform = val; end @@ -2219,6 +2263,7 @@ else roiNum = curRoi; end + roiNum = intersect(roiNum,1:viewGet(view,'numberofrois'),'stable'); %make sure all ROIs to set actually exist in the view if ~isempty(roiNum) view.ROIs(roiNum).voxelSize = val; end @@ -2232,6 +2277,7 @@ else roiNum = curRoi; end + roiNum = intersect(roiNum,1:viewGet(view,'numberofrois'),'stable'); %make sure all ROIs to set actually exist in the view if ~isempty(roiNum) view.ROIs(roiNum).createdBy = val; end @@ -2245,6 +2291,7 @@ else roiNum = curRoi; end + roiNum = intersect(roiNum,1:viewGet(view,'numberofrois'),'stable'); %make sure all ROIs to set actually exist in the view if ~isempty(roiNum) view.ROIs(roiNum).branchNum = val; end @@ -2258,6 +2305,7 @@ else roiNum = curRoi; end + roiNum = intersect(roiNum,1:viewGet(view,'numberofrois'),'stable'); %make sure all ROIs to set actually exist in the view if ~isempty(roiNum) view.ROIs(roiNum).createdOnBase = val; end @@ -2271,6 +2319,7 @@ else roiNum = curRoi; end + roiNum = intersect(roiNum,1:viewGet(view,'numberofrois'),'stable'); %make sure all ROIs to set actually exist in the view if ~isempty(roiNum) view.ROIs(roiNum).createdFromSession = val; end @@ -2284,6 +2333,7 @@ else roiNum = curRoi; end + roiNum = intersect(roiNum,1:viewGet(view,'numberofrois'),'stable'); %make sure all ROIs to set actually exist in the view if ~isempty(roiNum) view.ROIs(roiNum).displayOnBase = val; end @@ -2297,6 +2347,7 @@ else roiNum = curRoi; end + roiNum = intersect(roiNum,1:viewGet(view,'numberofrois'),'stable'); %make sure all ROIs to set actually exist in the view if ~isempty(roiNum) view.ROIs(roiNum).subjectID = val; end @@ -2309,20 +2360,21 @@ else roiNum = curRoi; end - % check to make sure the name is unique - roiNames = viewGet(view,'roiNames'); - nameMatch = find(strcmp(val,roiNames)); - while ~isempty(nameMatch) && (nameMatch~=roiNum) - paramsInfo{1} = {'roiName',val,'Change the name to a unique ROI name'}; - params = mrParamsDialog(paramsInfo,'Non unique ROI name, please change'); - if isempty(params),tf=0;return,end - val = params.roiName; - nameMatch = find(strcmp(val,roiNames)); - end + roiNum = intersect(roiNum,1:viewGet(view,'numberofrois'),'stable'); %make sure all ROIs to set actually exist in the view if ~isempty(roiNum) + % check to make sure the name is unique + roiNames = viewGet(view,'roiNames'); + nameMatch = find(strcmp(val,roiNames)); + while ~isempty(nameMatch) && (nameMatch~=roiNum) + paramsInfo{1} = {'roiName',val,'Change the name to a unique ROI name'}; + params = mrParamsDialog(paramsInfo,'Non unique ROI name, please change'); + if isempty(params),tf=0;return,end + val = params.roiName; + nameMatch = find(strcmp(val,roiNames)); + end view.ROIs(roiNum).name = val; + mlrGuiSet(view,'roipopup',{view.ROIs(:).name}); end - mlrGuiSet(view,'roipopup',{view.ROIs(:).name}); % ------------------------------------------- % Figure and GUI diff --git a/mrLoadRet/colormapFunctions/randomHSV.m b/mrLoadRet/colormapFunctions/randomHSV.m new file mode 100644 index 000000000..00bdea3cd --- /dev/null +++ b/mrLoadRet/colormapFunctions/randomHSV.m @@ -0,0 +1,12 @@ +% randomHSV.m +% +% usage: colorMap = randomHSV(numberColors) +% by: julien besle +% date: 13/02/11 +% purpose: returns a colorMap with colors randomly drawn from the HSV color map (without replacement) +% + +function colorMap = randomHSV(numberColors) + +colorMap = hsv(numberColors); +colorMap = colorMap(randperm(numberColors,numberColors),:); \ No newline at end of file diff --git a/mrLoadRet/groupAnalysis/mlrGroupAverage.m b/mrLoadRet/groupAnalysis/mlrGroupAverage.m new file mode 100644 index 000000000..300e6719a --- /dev/null +++ b/mrLoadRet/groupAnalysis/mlrGroupAverage.m @@ -0,0 +1,303 @@ + +% [thisView, params, uniqueLevels] = mlrGroupAverage(thisView,params,<'justGetParams'>) +% +% goal: averages volumes in "group" scan across subjects according to conditions +% (or combination of conditions) specified in .mat file linked to the scan. +% Optionally, computes averages only for a subset of (combinations of) conditions, +% or linear combinations of these averages (using contrasts parameter) +% A "group" scan is a scan in which each volume corresponds to some +% single-subject estimate map for a given condition, concatenated across +% multiple subjects and normalized to a common template space. +% The mat file must contain at least one cell array of strings (factor) of length equal +% to the number of volumes and specifying which volume corresponds to which +% condition and/or subject. Multiple cell arrays of equal length can be used +% to describe more complex factorial designs and calculate averages for combinations +% of conditions. +% +% usage: +% [~,params] = mlrGroupAverage(thisView, [],'justGetParams') %returns default parameters +% ... % modify parameters +% [~, params, uniqueLevels] = mlrGroupAverage(thisView,params,'justGetParams') % get levels (optional) +% ... % modify params.contrasts +% thisView = mlrSphericalNormGroup(thisView, params) % runs function with modified params +% +% parameters: +% params.groupNum: group in which to run the analysis (default: current group) +% params.analysisName: name of analysis where overlays will be saved (default: 'Group averages') +% params.scanList: list of scans to process (default: all scans in group) +% params.factors: factors whose levels will be used to average (default: all factors found in mat files associated with scans +% params.averagingMode: how levels will be combined. Options are 'marginal' (default) or 'interaction' +% 'marginal': all marginal means corresponding to each level of each facotr will be computed +% 'interaction': means corresponding to all level combinations across all factors will be computed +% params.contrasts: each row specifies which levels (or combination of levels) to include in the corresponding contrast, with +% each column corresponding to a given level or combination thereof, according to the combinationMode parameter +% using the order in which factors were specified, the order in which levels appear within each factor across scans +% params.outputSampleSize : whether to output a map of the the voxelwise number of subjects entering in the average for +% each average overlay (non-NaN values in subject overlays) (default = false) +% +% author: julien besle (10/08/2020) + +function [thisView, params, uniqueLevels] = mlrGroupAverage(thisView,params,varargin) + +eval(evalargs(varargin)); +if ieNotDefined('justGetParams'),justGetParams = 0;end + +if ieNotDefined('params') + params = struct; +end + +if fieldIsNotDefined(params,'groupNum') + params.groupNum = viewGet(thisView,'curGroup'); +end +nScans = viewGet(thisView,'nScans',params.groupNum); +if fieldIsNotDefined(params,'analysisName') + params.analysisName = 'Group averages'; +end +if fieldIsNotDefined(params,'scanList') + params.scanList = 1:nScans; +end +if fieldIsNotDefined(params,'factors') + params.factors = {}; +end +if fieldIsNotDefined(params,'averagingMode') + params.averagingMode = 'marginal'; % options are 'marginal' or 'interaction' +end +if fieldIsNotDefined(params,'contrasts') + params.contrasts = []; +end +if fieldIsNotDefined(params,'outputSampleSize') + params.outputSampleSize = false; +end + +uniqueLevels = {}; + +%read log files associated with scans +cScan = 0; +noLinkedFile=false; +for iScan = params.scanList + if iScan < 0 || iScan > nScans + mrWarnDlg(sprintf('(mlrGroupAverage) Scan %d does not exist in group %d', iScan, params.groupNum)); + noLinkedFile = true; + else + cScan= cScan+1; + logFileName = viewGet(thisView,'stimfilename',iScan,params.groupNum); + if isempty(logFileName) + mrWarnDlg(sprintf('(mlrGroupTtest) No mat file linked to scan %d, group %d', iScan, params.groupNum)); + noLinkedFile = true; + else + factors{cScan} = load(logFileName{1}); + if isempty(factors{cScan}) + mrWarnDlg(sprintf('(mlrGroupTtest) Cannot open file %s for scan %d, group %d', logFileName{1}, iScan, params.groupNum)); + end + if iScan == params.scanList(1) + commonFactors = fieldnames(factors{cScan}); + allFactors = commonFactors; + else + commonFactors = intersect(commonFactors,fieldnames(factors{cScan})); + allFactors = union(allFactors,fieldnames(factors{cScan})); + end + end + end +end +if noLinkedFile + return; +end + +if fieldIsNotDefined(params,'factors') + params.factors = allFactors; +elseif ischar(params.factors) + params.factors = {params.factors}; +end +if ~ismember(params.averagingMode,{'marginal','interaction'}) + mrWarnDlg(sprintf('(mlrGroupAverage)Unknown combination mode ''%s''',params.averagingMode)); + return; +end + +if strcmp(params.averagingMode,'interaction') + if ~all(ismember(params.factors,commonFactors)) + mrWarnDlg('(mlrGroupAverage) Cannot compute averages because factors are missing in some scans'); + return; + end + whichFactors = {1: length(params.factors)}; +else + whichFactors = num2cell(1:length(params.factors)); +end + +if justGetParams && fieldIsNotDefined(params,'factors'), return; end + +currentGroup = viewGet(thisView,'curGroup'); +thisView = viewSet(thisView,'curGroup',params.groupNum); + +compatibleLogfile = true; +for iScan = 1:length(params.scanList) + tseriesPath{iScan} = viewGet(thisView,'tseriespathstr',params.scanList(iScan)); + hdr{iScan} = cbiReadNiftiHeader(tseriesPath{iScan}); + for iFactor = 1:length(params.factors) + % check that the field exists for this scan + if ~isfield(factors{iScan},params.factors{iFactor}) + mrWarnDlg(sprintf('(mlrGroupAverage) Variable ''%s'' does not exist in scan %d', params.factors{iFactor}, params.scanList(iScan))); + compatibleLogfile = false; + else + % check that the number of volumes matches the number of elements in the factor variables + if length(factors{iScan}.(params.factors{iFactor})) ~= hdr{iScan}.dim(5) + mrWarnDlg(sprintf('(mlrGroupAverage) Scan %d: Mismatched number of volumes between .mat variable ''%s'' (%d) and time series file (%d)', ... + params.scanList(iScan),params.factors{iFactor},length(params.factors{iFactor}),hdr{iScan}.dim(5))); + compatibleLogfile = false; + end + if size(factors{iScan}.(params.factors{iFactor}),1)==1 + factors{iScan}.(params.factors{iFactor}) = factors{iScan}.(params.factors{iFactor})'; % make sure the factor is a column cell array + end + levels{iScan}(:,iFactor) = factors{iScan}.(params.factors{iFactor}); + end + end + if iScan ==1 + allLevels = levels{iScan}; + else + allLevels = [allLevels; levels{iScan}]; + end +end + +if ~compatibleLogfile + thisView = viewSet(thisView,'curGroup',currentGroup); + return; +end + +for iFactor = 1:length(params.factors) + % get unique level numbers for each factor. This is necessary because unique.m with option 'rows' + [~,~,allFactorLevelNums(:,iFactor)]= unique(allLevels(:,iFactor),'stable'); % does not support cell arrays + % get corresponding unique level numbers for all volumes of each scan + for iScan = 1:length(params.scanList) + [~,levelNums{iScan}(:,iFactor)] = ismember(levels{iScan}(:,iFactor),unique(allLevels(:,iFactor),'stable')); + end +end +nLevels = []; +nLevelsAcrossFactors = 0; +for iFactor = 1:length(whichFactors) % for each factor or combination of factors + % count the unique levels or combination of levels + [uniqueLevelNums,uniqueLevelIndices]=unique(allFactorLevelNums(:,whichFactors{iFactor}),'rows'); + % find the unique overlay number for each volume in each scan + for iScan = 1:length(params.scanList) + [~,whichOverlay{iScan}(:,iFactor)] = ismember(levelNums{iScan}(:,whichFactors{iFactor}),uniqueLevelNums,'rows'); + whichOverlay{iScan}(:,iFactor) = nLevelsAcrossFactors + whichOverlay{iScan}(:,iFactor); + end + % get corresponding unique level names + for iLevel = 1:size(uniqueLevelNums,1) + uniqueLevels{sum(nLevels)+iLevel} = [allLevels{uniqueLevelIndices(iLevel),whichFactors{iFactor}}]; + end + nLevels(iFactor) = size(uniqueLevelNums,1); + nLevelsAcrossFactors = nLevelsAcrossFactors + nLevels(iFactor); +end + +if fieldIsNotDefined(params,'contrasts') || size(params.contrasts,2)~=sum(nLevels) + params.contrasts = eye(sum(nLevels)); +end + +if justGetParams + thisView = viewSet(thisView,'curGroup',currentGroup); + return; +end + + +%-------------------------------------- Compute averages (or contrasts) +contrastNames = makeContrastNames(params.contrasts,uniqueLevels,'no test'); +nContrasts = size(params.contrasts,1); +nonZeroContrastLevels = find(any(params.contrasts~=0)); +nnzContrastLevels = numel(nonZeroContrastLevels); +minOverlay = inf(nContrasts+nnzContrastLevels,1); +maxOverlay = -1*minOverlay; +cScan = 0; +outputPrecision = mrGetPref('defaultPrecision'); +for iScan = 1:viewGet(thisView,'nScans') + if ismember(iScan,params.scanList) + hWaitBar = mrWaitBar(-inf,sprintf('(mlrGroupAverage) Computing averages for scan %d... ',iScan)); + + cScan = cScan+1; + + levelsData = zeros(prod(hdr{cScan}.dim(2:4)),nnzContrastLevels); + sampleSize = zeros(prod(hdr{cScan}.dim(2:4)),nnzContrastLevels); + cLevel = 0; + dLevel = 0; + for iFactor = 1:length(whichFactors) + for iLevel = 1:nLevels(iFactor) %for each overlay + cLevel = cLevel + 1; + if ismember(cLevel,nonZeroContrastLevels) + dLevel = dLevel+1; + mrWaitBar( dLevel/nnzContrastLevels, hWaitBar); + for iVolume = find(ismember(whichOverlay{cScan}(:,iFactor),cLevel,'rows'))' %for each volume in the scan matching this (combination of) condition(s) + data = cbiReadNifti(tseriesPath{cScan},{[],[],[],iVolume},'double'); % read the data + isNotNaN = ~isnan(data); + % add non-NaN values to the appropriate overlay(s) + levelsData(isNotNaN,dLevel) = levelsData(isNotNaN,dLevel) + data(isNotNaN); + sampleSize(:,dLevel) = sampleSize(:,dLevel) + isNotNaN(:); + end + % divide by the number of added overlays + levelsData(:,dLevel) = levelsData(:,dLevel)./sampleSize(:,dLevel); + end + end + end + + end + + for iContrast = 1:nContrasts + if ismember(iScan,params.scanList) + nonZeroLevels = find(params.contrasts(iContrast,nonZeroContrastLevels)); % need to only use levels involved in this particular contrast to avoid NaNs in the other levels + overlays(iContrast).data{iScan} = cast(reshape(levelsData(:,nonZeroLevels)*params.contrasts(iContrast,nonZeroContrastLevels(nonZeroLevels))',... + hdr{cScan}.dim(2:4)'),outputPrecision); + overlays(iContrast).name = contrastNames{iContrast}; + % get min and max + minOverlay(iContrast) = min(minOverlay(iContrast),min(overlays(iContrast).data{iScan}(:))); + maxOverlay(iContrast) = max(maxOverlay(iContrast),max(overlays(iContrast).data{iScan}(:))); + else + overlays(iContrast).data{iScan} = []; + end + end + + if params.outputSampleSize + for iLevel = 1:nnzContrastLevels + if ismember(iScan,params.scanList) + overlays(nContrasts+iLevel).data{iScan} = cast(reshape(sampleSize(:,iLevel),hdr{cScan}.dim(2:4)'),outputPrecision); + overlays(nContrasts+iLevel).name = ['N: ' uniqueLevels{nonZeroContrastLevels(iLevel)}]; + % get min and max + minOverlay(nContrasts+iLevel) = min(minOverlay(nContrasts+iLevel),min(overlays(nContrasts+iLevel).data{iScan}(:))); + maxOverlay(nContrasts+iLevel) = max(maxOverlay(nContrasts+iLevel),max(overlays(nContrasts+iLevel).data{iScan}(:))); + else + overlays(nContrasts+iLevel).data{iScan} = []; + end + end + end + + if ismember(iScan,params.scanList) + mrCloseDlg(hWaitBar); + end + +end + +%add overlays' missing fields +for iOutput = 1:nContrasts + params.outputSampleSize*nnzContrastLevels + overlays(iOutput).range = [minOverlay(iOutput) maxOverlay(iOutput)]; + overlays(iOutput).groupName = viewGet(thisView,'groupName'); + overlays(iOutput).params = params; + overlays(iOutput).type = 'Group average'; + overlays(iOutput).function = 'mlrGroupAverage'; + overlays(iOutput).interrogator = ''; + + if iOutput<=nContrasts + allScanData = []; % determine the 1st-99th percentile range + for iScan = params.scanList + allScanData = [allScanData;overlays(iOutput).data{iScan}(~isnan(overlays(iOutput).data{iScan}))]; + end + allScanData = sort(allScanData); + overlays(iOutput).colorRange = allScanData(round([0.01 0.99]*numel(allScanData)))'; + end +end + +% set or create analysis +analysisNum = viewGet(thisView,'analysisNum',params.analysisName); +if isempty(analysisNum) + thisView = newAnalysis(thisView,params.analysisName); +else + thisView = viewSet(thisView,'curAnalysis',analysisNum); +end +% add overlays to view +thisView = viewSet(thisView,'newOverlay',overlays); +thisView = viewSet(thisView,'clipAcrossOverlays',false); diff --git a/mrLoadRet/groupAnalysis/mlrGroupTtest.m b/mrLoadRet/groupAnalysis/mlrGroupTtest.m new file mode 100644 index 000000000..c86649063 --- /dev/null +++ b/mrLoadRet/groupAnalysis/mlrGroupTtest.m @@ -0,0 +1,578 @@ + +% [thisView, params, uniqueLevels] = mlrGroupTtest(thisView,params,<'justGetParams'>) +% +% goal: test contrast(s) against 0 across subjects according to conditions +% specified in .mat file linked to a group scan, using a paired T-test. +% A "group" scan is a scan in which each volume corresponds to some +% single-subject estimate map for a given condition, concatenated across +% multiple subjects and normalized to a common template space. +% The mat file must contain at least one cell array of strings (factor) of length equal +% to the number of volumes and specifying which volume corresponds to which +% condition and/or subject. +% Note: when subject-level OLS estimates are used, testing contrasts requires +% an assumption of homoscedasticity of the within-subject variance. Also, the variance +% maybe be overestimated, leading to conservative tests. However results by Mumford & +% Nichols (Neuroimage 47, 2009) suggest that group-level T tests on OLS estimates are +% fairly robust to violations of homoscdasticity and do not underperform too much +% +% usage: +% [~, params] = mlrGroupTtest(thisView, [],'justGetParams') %returns default parameters +% ... % modify parameters +% [~, params, uniqueLevels] = mlrGroupTtest(thisView,params,'justGetParams') % get levels +% ... % modify params.contrasts +% thisView = mlrSphericalNormGroup(thisView, params) % runs function with modified params and contrasts +% +% Input parameters: +% params.groupNum: group in which to run the analysis (default: current group) +% params.analysisName: name of analysis where overlays will be saved (default: 'Group averages') +% params.scanList: list of scans to process (default: all scans in group) +% params.factors: factors whose levels will be used to code for the contrast +% params.combinationMode: how factor levels will be combined. Options are 'marginal' (default) or 'interaction' +% 'marginal': Each level of each factor will be computed +% 'interaction': means corresponding to all level combinations across all factors will be computed +% params.contrasts: each row specify which levels (or combination of levels) to include in the corresponding contrast, with +% each column corresponding to a given level or combination thereof, according to the combinationMode parameter +% using the order in which factors were specified, the order in which levels appear within each factor across scans +% params.smoothingFWHM: FWHM of the Gaussian smoothing kernel in voxels of the specified base/scan space. Set to 0 for no smoothing (default). +% params.smoothingSpace: space in which to smooth the data, 0 = current scan, any other number: base number (default: current base, +% unless smoothingFWHM>0, in which case, current base) +% This is only implemented for flat bases. Any other base (surface or volume) will be ignored and everything will be done in scan space +% params.testSide: 'both', 'left' or 'right' +% params.pThreshold: criterion for masking overlays, expressed as a probability value (default = 0.05) +% params.testOutput: '-10log(P)' (default), 'P', 'Z'. P values are not corrected for multiple tests +% params.fweAdjustment: default = false (uses default method in transformStatistic.m) +% params.fdrAdjustment: default = true (uses default method in transformStatistic.m) +% params.thresholdCorrection: which p-value to use to set the transparency and clip contrast estimates: 'FDR' (default),'FWE','Uncorrected','None' +% params.outputContrastEstimates: output contrast estimates in addition to statistics (default = true) +% params.outputContrastSte: output contrast standard errors in addition to statistics (default = false) +% params.outputStatistic: output T statistic in addition to statistics (default = false) +% params.outputSampleSize : whether to output a map of the voxelwise number of subjects entering in the t-test for +% each average overlay (non-NaN values in subject overlays) (default = false) +% +% Output: - contrast estimates +% - P or T overlay are added to the specified analysis in the view +% - params structure (gives default parameters if 'justGetParams') +% - uniqueLevels: list of unique levels/combinations of levels in the order to be used for contrast matrix, +% according to given scan and parameter structure +% +% author: julien besle (17/03/2021) + +function [thisView, params, uniqueLevels] = mlrGroupTtest(thisView,params,varargin) + +eval(evalargs(varargin)); +if ieNotDefined('justGetParams'),justGetParams = 0;end + +if ieNotDefined('params') + params = struct; +end + +if fieldIsNotDefined(params,'groupNum') + params.groupNum = viewGet(thisView,'curGroup'); +end +nScans = viewGet(thisView,'nScans',params.groupNum); +if fieldIsNotDefined(params,'analysisName') + params.analysisName = 'Group T-tests'; +end +if fieldIsNotDefined(params,'scanList') + params.scanList = 1:nScans; +end +if fieldIsNotDefined(params,'factors') + params.factors = {}; +end +if fieldIsNotDefined(params,'combinationMode') + params.combinationMode = 'marginal'; % options are 'marginal' or 'interaction' +end +if fieldIsNotDefined(params,'contrasts') + params.contrasts = []; +end +if fieldIsNotDefined(params,'smoothingFWHM') + params.smoothingFWHM = 0; +end +if fieldIsNotDefined(params,'smoothingSpace') + params.smoothingSpace = viewGet(thisView,'curbase'); +end +if fieldIsNotDefined(params,'testSide') + params.testSide = 'both'; +end +if fieldIsNotDefined(params,'pThreshold') + params.pThreshold = 0.05; +end +if fieldIsNotDefined(params,'testOutput') + params.testOutput = '-log10(P)'; +end +if fieldIsNotDefined(params,'fweAdjustment') + params.fweAdjustment= false; +end +if fieldIsNotDefined(params,'fdrAdjustment') + params.fdrAdjustment= true; +end +if fieldIsNotDefined(params,'thresholdCorrection') + params.thresholdCorrection = 'FDR'; +end +if fieldIsNotDefined(params,'outputStatistic') + params.outputStatistic = false; +end +if fieldIsNotDefined(params,'outputContrastEstimates') + params.outputContrastEstimates = true; +end +if fieldIsNotDefined(params,'outputContrastSte') + params.outputContrastSte = false; +end +if fieldIsNotDefined(params,'outputSampleSize') + params.outputSampleSize = false; +end + +uniqueLevels = {}; + +%read log files associated with scans +cScan = 0; +noLinkedFile=false; +for iScan = params.scanList + if iScan < 0 || iScan > nScans + mrWarnDlg(sprintf('(mlrGroupAverage) Scan %d does not exist in group %d', iScan, params.groupNum)); + noLinkedFile = true; + else + cScan= cScan+1; + logFileName = viewGet(thisView,'stimfilename',iScan,params.groupNum); + if isempty(logFileName) + mrWarnDlg(sprintf('(mlrGroupTtest) No mat file linked to scan %d, group %d', iScan, params.groupNum)); + noLinkedFile = true; + else + factors{cScan} = load(logFileName{1}); + if isempty(factors{cScan}) + mrWarnDlg(sprintf('(mlrGroupTtest) Cannot open file %s for scan %d, group %d', logFileName{1}, iScan, params.groupNum)); + end + if iScan == params.scanList(1) + commonFactors = fieldnames(factors{cScan}); + allFactors = commonFactors; + else + commonFactors = intersect(commonFactors,fieldnames(factors{cScan})); + allFactors = union(allFactors,fieldnames(factors{cScan})); + end + end + end +end +if noLinkedFile + return; +end + +if fieldIsNotDefined(params,'factors') + params.factors = allFactors; +elseif ischar(params.factors) + params.factors = {params.factors}; +end +if ~ismember(params.combinationMode,{'marginal','interaction'}) + mrWarnDlg(sprintf('(mlrGroupTtest)Unknown combination mode ''%s''',params.combinationMode)); + return +end + +if strcmp(params.combinationMode,'interaction') + if ~all(ismember(params.factors,commonFactors)) + mrWarnDlg('(mlrGroupTtest) Cannot run t-tests because factors are missing in some scans'); + return; + end + whichFactors = {1: length(params.factors)}; +else + whichFactors = num2cell(1:length(params.factors)); +end + +if justGetParams && fieldIsNotDefined(params,'factors'), return; end + +% CHECK THAT THERE IS EQUAL N FOR ALL LEVELS AND COMBINATION OF LEVELS HERE? + +currentGroup = viewGet(thisView,'curGroup'); +thisView = viewSet(thisView,'curGroup',params.groupNum); + +compatibleLogfile = true; +for iScan = 1:length(params.scanList) + tseriesPath{iScan} = viewGet(thisView,'tseriespathstr',params.scanList(iScan)); + hdr{iScan} = cbiReadNiftiHeader(tseriesPath{iScan}); + for iFactor = 1:length(params.factors) + % check that the field exists for this scan + if ~isfield(factors{iScan},params.factors{iFactor}) + mrWarnDlg(sprintf('(mlrGroupTtest) Variable ''%s'' does not exist in scan %d', params.factors{iFactor}, params.scanList(iScan))); + compatibleLogfile = false; + else + % check that the number of volumes matches the number of elements in the factor variables + if length(factors{iScan}.(params.factors{iFactor})) ~= hdr{iScan}.dim(5) + mrWarnDlg(sprintf('(mlrGroupTtest) Scan %d: Mismatched number of volumes between .mat variable ''%s'' (%d) and time series file (%d)', ... + params.scanList(iScan),params.factors{iFactor},length(params.factors{iFactor}),hdr{iScan}.dim(5))); + compatibleLogfile = false; + end + if size(factors{iScan}.(params.factors{iFactor}),1)==1 + factors{iScan}.(params.factors{iFactor}) = factors{iScan}.(params.factors{iFactor})'; % make sure the factor is a column cell array + end + levels{iScan}(:,iFactor) = factors{iScan}.(params.factors{iFactor}); + end + end + if iScan ==1 + allLevels = levels{iScan}; + else + allLevels = [allLevels; levels{iScan}]; + end +end + +if ~compatibleLogfile + thisView = viewSet(thisView,'curGroup',currentGroup); + return; +end + +for iFactor = 1:length(params.factors) + % get unique level numbers for each factor. This is necessary because unique.m with option 'rows' + [~,~,allFactorLevelNums(:,iFactor)]= unique(allLevels(:,iFactor),'stable'); % does not support cell arrays + % get corresponding unique level numbers for all volumes of each scan + for iScan = 1:length(params.scanList) + [~,levelNums{iScan}(:,iFactor)] = ismember(levels{iScan}(:,iFactor),unique(allLevels(:,iFactor),'stable')); + end +end +nLevels = []; +nLevelsAcrossFactors = 0; +for iFactor = 1:length(whichFactors) % for each factor or combination of factors + % count the unique levels or combination of levels + [uniqueLevelNums,uniqueLevelIndices]=unique(allFactorLevelNums(:,whichFactors{iFactor}),'rows'); + % find the unique overlay number for each volume in each scan + for iScan = 1:length(params.scanList) + [~,whichOverlay{iScan}(:,iFactor)] = ismember(levelNums{iScan}(:,whichFactors{iFactor}),uniqueLevelNums,'rows'); + whichOverlay{iScan}(:,iFactor) = nLevelsAcrossFactors + whichOverlay{iScan}(:,iFactor); + end + % get corresponding unique level names + for iLevel = 1:size(uniqueLevelNums,1) + uniqueLevels{sum(nLevels)+iLevel} = [allLevels{uniqueLevelIndices(iLevel),whichFactors{iFactor}}]; + end + nLevels(iFactor) = size(uniqueLevelNums,1); + nLevelsAcrossFactors = nLevelsAcrossFactors + nLevels(iFactor); +end + +if fieldIsNotDefined(params,'contrasts') || size(params.contrasts,2)~=sum(nLevels) + params.contrasts = eye(sum(nLevels)); +end + +if justGetParams + thisView = viewSet(thisView,'curGroup',currentGroup); + return; +end + +if all(params.smoothingFWHM==0) && params.smoothingSpace~=0 + mrWarnDlg('(mlrGroupTtest) Smoothing is set to 0, so all analyses will be done in current scan space'); + params.smoothingSpace = 0; +end +baseType = viewGet(thisView,'baseType',params.smoothingSpace); +if any(params.smoothingFWHM>0) + if baseType==0 + mrWarnDlg('(mlrGroupTtest) Smoothing will be done in current scan space'); + params.smoothingSpace = 0; + elseif baseType==2 + mrWarnDlg('(mlrGroupTtest) Smoothing not implemented for surfaces or vol, switching smoothing space to current scan'); + params.smoothingSpace = 0; + end +end + +%-------------------------------------- Compute contrasts and run t-tests +sampleSizesDiffer = false; +contrastNames = makeContrastNames(params.contrasts,uniqueLevels,params.testSide); +nContrasts = size(params.contrasts,1); +nOverlays = nContrasts* (1 + params.fweAdjustment + params.fdrAdjustment + ... + params.outputStatistic + params.outputContrastEstimates + params.outputContrastSte + params.outputSampleSize); +minOverlay = inf(nOverlays,1); +maxOverlay = -1*minOverlay; +cScan = 0; +for iScan = 1:viewGet(thisView,'nScans') + if ismember(iScan,params.scanList) + cScan = cScan+1; + + % Check for equal Ns + cLevel = 0; + for iFactor = 1:length(whichFactors) + for iOverlay = 1:nLevels(iFactor) + cLevel = cLevel+1; + sampleSize = nnz(ismember(whichOverlay{cScan}(:,iFactor),cLevel,'rows')); %for each volume (subject) in the scan matching this (combination of) levels(s) + if cLevel == 1 + maxSampleSize = sampleSize; + else + if maxSampleSize~=sampleSize + mrWarnDlg('(mlrGroupTtest) The number of subject overlays should be identical at all levels or combinations of levels'); + return; + end + end + end + end + + waitString = sprintf('(mlrGroupTtest) Running group-level t-tests for scan %d... ',iScan); + if params.smoothingSpace > 0 && baseType == 1 + fprintf('%s\n',waitString); + else + hWaitBar = mrWaitBar(-inf,waitString); + end + + % compute the contrast estimates, statistics and p values + for iContrast = 1:nContrasts % for each contrast, need to read the data in + if params.smoothingSpace == 0 || baseType ~= 1 + mrWaitBar( iContrast/nContrasts, hWaitBar); + end + nonZeroContrastLevels = find(params.contrasts(iContrast,:)~=0); + subjectContrastEstimates = zeros(prod(hdr{cScan}.dim(2:4)),maxSampleSize); + sampleSize = zeros(prod(hdr{cScan}.dim(2:4)),numel(nonZeroContrastLevels)); + cLevel = 0; + dLevel = 0; + for iFactor = 1:length(whichFactors) + for iLevel = 1:nLevels(iFactor) + cLevel = cLevel+1; + if ismember(cLevel,nonZeroContrastLevels) + dLevel = dLevel+1; + volumes = find(ismember(whichOverlay{cScan}(:,iFactor),cLevel,'rows'))'; %for each volume in the scan matching this (combination of) condition(s) + for iVolume = 1:length(volumes) + data = cbiReadNifti(tseriesPath{cScan},{[],[],[],volumes(iVolume)},'double'); % read the data + isNotNaN = ~isnan(data); + % add non-NaN values to the appropriate overlay(s) + subjectContrastEstimates(isNotNaN,iVolume) = subjectContrastEstimates(isNotNaN,iVolume) + params.contrasts(iContrast,cLevel)*data(isNotNaN); + sampleSize(:,dLevel) = sampleSize(:,dLevel) + isNotNaN(:); + end + end + end + end + if numel(nonZeroContrastLevels)>1 + for i = 2:dLevel + if nnz(diff(sampleSize(:,[1 i]),1,2)) + sampleSizesDiffer = true; + end + end + if sampleSizesDiffer + keyboard % Ns are not equal across levels at all voxels + end + sampleSize = reshape(sampleSize(:,1),hdr{cScan}.dim(2:4)'); + end + subjectContrastEstimates(subjectContrastEstimates==0)=NaN; % replace zeros by NaNs to avoid infinite values later on + + % need to reshape first + sampleSize = reshape(sampleSize,hdr{cScan}.dim(2:4)'); + subjectContrastEstimates = reshape(subjectContrastEstimates,[hdr{cScan}.dim(2:4)' maxSampleSize]); + + % apply smoothing + if any(params.smoothingFWHM>0) + + waitString = sprintf('(mlrGroupTtest) Smoothing data for scan %d',iScan); + if params.smoothingSpace > 0 && baseType == 1 + fprintf('%s, contrast %d\n',waitString, iContrast); + base2scan = viewGet(thisView,'base2scan',iScan,[],params.smoothingSpace); + [subjectContrastEstimates, ~, baseCoordsMap] = getBaseSpaceOverlay(thisView, subjectContrastEstimates,iScan,params.smoothingSpace,'linear'); + %pre-compute coordinates map to put values back from flat base to scan space + scanDims = viewGet(thisView,'dims',iScan); + %make a coordinate map of which scan voxel each base map voxel corresponds to (convert base coordmap to scan coord map) + flat2scan = inverseBaseCoordMap(baseCoordsMap,scanDims,base2scan); + else + mrWaitBar( iContrast/nContrasts, hWaitBar, waitString); + end + + for iVolume = 1:maxSampleSize + subjectContrastEstimates(:,:,:,iVolume) = spatialSmooth(subjectContrastEstimates(:,:,:,iVolume),params.smoothingFWHM); + end + + if params.smoothingSpace > 0 && baseType == 1 + % transform data back into scan space + subjectContrastEstimates = applyInverseBaseCoordMap(flat2scan,scanDims,subjectContrastEstimates); + end + end + + % compute contrast estimates and std error + contrastEstimates = nansum(subjectContrastEstimates,4)./sampleSize; + contrastSte = sqrt(nansum((subjectContrastEstimates-repmat(contrastEstimates,[1 1 1 maxSampleSize])).^2,4) ./ ... % sum of squared errors + (sampleSize-1)) ./ ... % divided by N-1 + sqrt(sampleSize); % divided by sqrt(N) + %compute p + t = contrastEstimates./contrastSte; + p = nan(size(t)); + switch(params.testSide) + case 'both' % two-tailed + p(t>=0) = 2 * (1 - cdf('t', double(t(t>=0)), sampleSize(t>=0)-1)); %here use doubles to deal with small Ps + p(t<0) = 2 * cdf('t', double(t(t<0)), sampleSize(t<0)-1); + case 'left' % left tailed + p = cdf('t', double(t), sampleSize-1); + case 'right' % right-tailed + p = 1 - cdf('t', double(t), sampleSize-1); + end + % we do not allow probabilities of 0 and replace them by minP + % (this can occur because cdf cannot return values less than 1e-16) + p(~isnan(p)) = max(p(~isnan(p)),1e-16); + + waitString = sprintf('(mlrGroupTtest) Correcting p-values for scan %d',iScan); + if params.smoothingSpace > 0 && baseType == 1 + fprintf('%s, contrast %d\n',waitString, iContrast); + else + mrWaitBar( iContrast/nContrasts, hWaitBar, waitString); + end + outputPrecision = mrGetPref('defaultPrecision'); + [p, fdrAdjustedP, fweAdjustedP] = transformStatistic(p,outputPrecision, params); + + nOutputs=0; + if params.outputContrastEstimates + overlays(nOutputs*nContrasts+iContrast).data{iScan} = cast(contrastEstimates,outputPrecision); + overlays(nOutputs*nContrasts+iContrast).name = contrastNames{iContrast}; + % get min and max + minOverlay(nOutputs*nContrasts+iContrast) = min(minOverlay(nOutputs*nContrasts+iContrast),min(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + maxOverlay(nOutputs*nContrasts+iContrast) = max(maxOverlay(nOutputs*nContrasts+iContrast),max(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + nOutputs = nOutputs+1; + nOutputContrast = nOutputs; + else + nOutputContrast = 0; + end + + if params.outputContrastSte + overlays(nOutputs*nContrasts+iContrast).data{iScan} = cast(contrastSte,outputPrecision); + overlays(nOutputs*nContrasts+iContrast).name = ['Std error: ' contrastNames{iContrast}]; + % get min and max + minOverlay(nOutputs*nContrasts+iContrast) = min(minOverlay(nOutputs*nContrasts+iContrast),min(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + maxOverlay(nOutputs*nContrasts+iContrast) = max(maxOverlay(nOutputs*nContrasts+iContrast),max(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + nOutputs = nOutputs+1; + end + + overlays(nOutputs*nContrasts+iContrast).data{iScan} = p; + overlays(nOutputs*nContrasts+iContrast).name = [params.testOutput ': ' contrastNames{iContrast}]; + % get min and max + minOverlay(nOutputs*nContrasts+iContrast) = min(minOverlay(nOutputs*nContrasts+iContrast),min(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + maxOverlay(nOutputs*nContrasts+iContrast) = max(maxOverlay(nOutputs*nContrasts+iContrast),max(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + nOutputs = nOutputs+1; + nOutputP = nOutputs; + + if params.fweAdjustment + overlays(nOutputs*nContrasts+iContrast).data{iScan} = fweAdjustedP; + overlays(nOutputs*nContrasts+iContrast).name = ['FWE-corrected ' params.testOutput ': ' contrastNames{iContrast}]; + % get min and max + minOverlay(nOutputs*nContrasts+iContrast) = min(minOverlay(nOutputs*nContrasts+iContrast),min(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + maxOverlay(nOutputs*nContrasts+iContrast) = max(maxOverlay(nOutputs*nContrasts+iContrast),max(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + nOutputs = nOutputs+1; + nOutputFweP = nOutputs; + else + nOutputFweP = 0; + end + + if params.fdrAdjustment + overlays(nOutputs*nContrasts+iContrast).data{iScan} = fdrAdjustedP; + overlays(nOutputs*nContrasts+iContrast).name = ['FDR-corrected ' params.testOutput ': ' contrastNames{iContrast}]; + % get min and max + minOverlay(nOutputs*nContrasts+iContrast) = min(minOverlay(iLevel),min(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + maxOverlay(nOutputs*nContrasts+iContrast) = max(maxOverlay(iLevel),max(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + nOutputs = nOutputs+1; + nOutputFdrP = nOutputs; + else + nOutputFdrP = 0; + end + + if params.outputStatistic + overlays(nOutputs*nContrasts+iContrast).data{iScan} = cast(t,outputPrecision); + overlays(nOutputs*nContrasts+iContrast).name = ['T: ' contrastNames{iContrast}]; + % get min and max + minOverlay(nOutputs*nContrasts+iContrast) = min(minOverlay(iLevel),min(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + maxOverlay(nOutputs*nContrasts+iContrast) = max(maxOverlay(iLevel),max(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + nOutputs = nOutputs+1; + end + + if params.outputSampleSize + if sampleSizesDiffer || iContrast == 1 + if ~sampleSizesDiffer + overlays(nOutputs*nContrasts+iContrast).name = 'Sample size'; + else + overlays(nOutputs*nContrasts+iContrast).name = ['N: ' contrastNames{iContrast}]; + end + overlays(nOutputs*nContrasts+iContrast).data{iScan} = cast(sampleSize,outputPrecision); + % get min and max + minOverlay(nOutputs*nContrasts+iContrast) = min(minOverlay(iLevel),min(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + maxOverlay(nOutputs*nContrasts+iContrast) = max(maxOverlay(iLevel),max(overlays(nOutputs*nContrasts+iContrast).data{iScan}(:))); + end + nOutputs = nOutputs+1; + nOutputSampleSize = nOutputs; + else + nOutputSampleSize = 0; + end + end + + if params.smoothingSpace == 0 || baseType ~= 1 + mrCloseDlg(hWaitBar); + else + fprintf('(mlrGroupTtest) Scan %d done\n\n',iScan); + end + else + for iOverlay = 1:nOverlays + overlays(iOverlay).data{iScan} = []; + end + end +end + +%add overlays' missing fields +switch(params.testOutput) + case 'P' + clipThreshold = [0 params.pThreshold]; + alphaOverlayExponent = -1; + case 'Z' + clipThreshold = [norminv(1-params.pThreshold) inf]; + alphaOverlayExponent = .5; + case '-log10(P)' + clipThreshold = [-log10(params.pThreshold) inf]; + alphaOverlayExponent = .5; +end +for iOutput = 1:nOutputs + for iContrast = 1:nContrasts + iOverlay = (iOutput-1)*nContrasts + iContrast; + overlays(iOverlay).range = [minOverlay(iOverlay) maxOverlay(iOverlay)]; + overlays(iOverlay).groupName = viewGet(thisView,'groupName'); + overlays(iOverlay).params = params; + overlays(iOverlay).type = 'Group t-test'; + overlays(iOverlay).function = 'mlrGroupTtest'; + overlays(iOverlay).interrogator = ''; + switch(iOutput) + case {nOutputP,nOutputFweP,nOutputFdrP} + overlays(iOverlay).clip(1) = max(overlays(iOverlay).range(1),clipThreshold(1)); + overlays(iOverlay).clip(2) = min(overlays(iOverlay).range(2),clipThreshold(2)); + switch params.testOutput + case 'P' + overlays.colormap = statsColorMap(256); + overlays(iOverlay).colorRange = [0 1]; + case 'Z' + overlays(iOverlay).colorRange = [0 norminv(1-1e-16)]; %1e-16 is smallest non-zero P value output by cdf in getGlmStatistics (local functions T2p and F2p) + case '-log10(P)' + overlays(iOverlay).colorRange = [0 -log10(1e-16)]; + end + case nOutputContrast + switch params.thresholdCorrection + case 'FWE' + if params.fweAdjustment + overlays(iOverlay).alphaOverlay = overlays((nOutputFweP-1)*nContrasts + iContrast).name; + elseif params.fdrAdjustment + overlays(iOverlay).alphaOverlay = overlays((nOutputFdrP-1)*nContrasts + iContrast).name; + else + overlays(iOverlay).alphaOverlay = overlays((nOutputP-1)*nContrasts + iContrast).name; + end + case 'FDR' + if params.fdrAdjustment + overlays(iOverlay).alphaOverlay = overlays((nOutputFdrP-1)*nContrasts + iContrast).name; + else + overlays(iOverlay).alphaOverlay = overlays((nOutputP-1)*nContrasts + iContrast).name; + end + case 'Uncorrected' + overlays(iOverlay).alphaOverlay = overlays((nOutputP-1)*nContrasts + iContrast).name; + end + overlays(iOverlay).alphaOverlayExponent = alphaOverlayExponent; + end + if ~ismember(iOutput, [nOutputP nOutputFweP nOutputFdrP nOutputSampleSize]) % for overlays other and P-values and sample size + allScanData = []; % determine the 1st-99th percentile range + for iScan = params.scanList + allScanData = [allScanData;overlays(iOverlay).data{iScan}(~isnan(overlays(iOverlay).data{iScan}))]; + end + allScanData = sort(allScanData); + overlays(iOverlay).colorRange = allScanData(round([0.01 0.99]*numel(allScanData)))'; + end + end +end + +% remove any overlay with no data (this happens when asking for sample sizes and they are identical at all levels) +overlays(isinf(minOverlay)) = []; + +% set or create analysis +analysisNum = viewGet(thisView,'analysisNum',params.analysisName); +if isempty(analysisNum) + thisView = newAnalysis(thisView,params.analysisName); +else + thisView = viewSet(thisView,'curAnalysis',analysisNum); +end +% add overlays to view +thisView = viewSet(thisView,'newOverlay',overlays); +thisView = viewSet(thisView,'clipAcrossOverlays',false); diff --git a/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m new file mode 100644 index 000000000..e4f5a497e --- /dev/null +++ b/mrLoadRet/groupAnalysis/mlrSphericalNormGroup.m @@ -0,0 +1,795 @@ +% function [success,params] = mlrSphericalNormGroup(params,<'justGetParams'>) +% +% goal: Exports overlay and ROI data from multiple subjects located in the same study folder +% into a common template space using Freesurfer's spherical normalization (keeping +% only data located within the cortical sheet) and creates a new template MLR folder +% (within the same folder) in which scans are concatenated overlays and ROI masks across all subjects. +% Optionally concatenates subject data with their left-right-flipped counterpart. In this case +% a twice left-right-flipped Freesurfer template surfer must be used (see surfRelaxFlipLR.m) +% Optionally computes group-average overlays and ROI probability maps. +% +% usage: +% params = mlrSphericalNormGroup(params,'justGetParams') %returns default parameters +% ... % modify parameters +% mlrSphericalNormGroup(params) % runs function with modified params +% +% parameters: +% params.studyDir This is the folder in which subject-specific mrLoadRet folders are located (default: current folder) +% params.mrLoadRetSubjectIDs Names of the subjects (mrLoadRet folders) to include (default: all subfolders of current folder) +% params.mrLoadRetSubjectLastView Name of the last saved view in each subject's MLR folder (default: mrLastView.mat) +% params.freesurferSubjectsFolder Location of the Freesurfer subjects directory (default: from mrGetPref) +% params.freesurferSubjectIDs Freesurfer subject IDs corresponding to the mrLoadRet subject IDs +% params.fsSubjectSurfSuffix Suffix(es) to add to the Freesurfer subject surface file names. Can be a single string or a cell array of strings (default: '') +% params.freesurferTemplateID Freesurfer subject ID of the destination template (could be one of the subjects) (default: fsaverage) +% params.mrLoadRetTemplateID Name of the mrLoadRet directory where the normalized data will be located (default: 'Spherical Normalization') +% params.mrLoadRetTemplateLastView Name of the saved last view in the template MLR folder (default: mrLastView.mat) +% params.subjectOverlayGroups Group names or numbers of the overlays to normalize +% params.subjectOverlayAnalyses Analysis names numbers of the overlays to normalize +% params.subjectOverlays Names or numbers or the overlay to normalize +% params.subjectROIs Names or numbers or the ROIs to normalize +% params.subjectROIsides What hemisphere the ROI is in (1 = left, 2 = right). When averaging across left and right hemispheres, left and right ROIs should be matched and given in the same order. +% params.subjectROIbase Export space of the ROI (by default, same as the overlay scan space) +% params.binarizeROIs Binarize ROIs after resampling to template space, or keep interpolated values (default = false) +% params.templateOverlayGroupNums In what new group(s) of the template mrLoadRet folder the group-normalized overlays (concatenated in a scan) will be imported. +% (must be an index into params.templateOverlayGroupNames) +% params.templateOverlayGroupNames Name(s) of the template group(s) (must not already exist) +% params.combineLeftAndRight If true, data will also be left-right flipped and combined across all hemispheres of all subjects (default: false) +% params.lrFlippedFreesurferTemplateID If combineLeftAndRight is true, name of the Freesurfer folder containing the left-right flipped surfaces of the left-right-flipped template +% params.lrFlipTransformFunctions Voxelwise transformation to apply to the overlay data when left-right flipping, useful for orientation sensitive measures +% params.lrFlipTransform Which overlays to transform. 0 for no transformation (default) +% params.computeGroupAverage If true, normalized overlays will be averaged across subjects (optionally hemispheres) (default: true) +% params.templateOverlayNewNames New names of the converted overlays +% params.computeROIprobabilityMaps If true, normalized overlays will be averaged across subjects (optionally hemispheres) (default: true) +% params.templateROInewNames New names of the converted ROIs +% params.cropScans Whether to crop concatenated scans to smallest volume including all subject's data (default = true) +% params.dryRun If true, just check whether the overlays and surfaces exist (default: false) +% +% author: julien besle (28/07/2020) + +function [success,params] = mlrSphericalNormGroup(params,varargin) + +success = true; +eval(evalargs(varargin)); +if ieNotDefined('justGetParams'),justGetParams = 0;end + +if ieNotDefined('params') + params = struct; +end + +if fieldIsNotDefined(params,'studyDir') + params.studyDir = pwd; % this is the folder in which subject-specific mrLoadRet folders are located +end +if fieldIsNotDefined(params,'mrLoadRetSubjectIDs') + params.mrLoadRetSubjectIDs = dir(params.studyDir); % names of the subjects (mrLoadRet folders) to include + params.mrLoadRetSubjectIDs = params.mrLoadRetSubjectIDs([params.mrLoadRetSubjectIDs(:).isdir] & ~ismember({params.mrLoadRetSubjectIDs(:).name},{'.','..','Spherical Normalization'})); + params.mrLoadRetSubjectIDs = {params.mrLoadRetSubjectIDs(:).name}; +end +if fieldIsNotDefined(params,'mrLoadRetSubjectLastView') + params.mrLoadRetSubjectLastView = 'mrLastView.mat'; +end +if fieldIsNotDefined(params,'freesurferSubjectsFolder') + params.freesurferSubjectsFolder = mrGetPref('volumeDirectory'); %location of the Freesurfer subjects directory +end +if fieldIsNotDefined(params,'freesurferSubjectIDs') + params.freesurferSubjectIDs = {''}; % Freesurfer subject IDs corresponding to the mrLoadRet subject IDs +end +if fieldIsNotDefined(params,'fsSubjectSurfSuffix') + params.fsSubjectSurfSuffix = {''}; % Suffix(es) to add to the Freesurfer subject surfaces +end +if fieldIsNotDefined(params,'freesurferTemplateID') + params.freesurferTemplateID = 'fsaverage'; % Freesurfer subject IF of the destination template (could be one of the subjects) +end +if fieldIsNotDefined(params,'mrLoadRetTemplateID') + params.mrLoadRetTemplateID = 'Spherical Normalization'; % Name of the mrLoadRet directory where the normalized data will be located +end +if fieldIsNotDefined(params,'mrLoadRetTemplateLastView') + params.mrLoadRetTemplateLastView = 'mrLastView.mat'; +end +if fieldIsNotDefined(params,'subjectOverlays') + params.subjectOverlays = []; % overlay names or numbers to normalize +end +if fieldIsNotDefined(params,'subjectOverlayGroups') + params.subjectOverlayGroups = ones(size(params.subjectOverlays)); % group numbers of the overlays to normalize +end +if fieldIsNotDefined(params,'subjectOverlayScans') + params.subjectOverlayScans = ones(size(params.subjectOverlays)); % scan numbers of the overlays to normalize (within a given subjects, all scans should have identical xform) +end +if fieldIsNotDefined(params,'subjectOverlayAnalyses') + params.subjectOverlayAnalyses = ones(size(params.subjectOverlays)); % analysis numbers of the overlays to normalize +end +if fieldIsNotDefined(params,'subjectROIs') + params.subjectROIs = []; % ROI names or numbers to normalize +end +if fieldIsNotDefined(params,'subjectROIsides') + params.subjectROIsides = []; % What hemisphere the ROI is in (1 = left, 2 = right). When averaging across left and right hemispheres, left and right ROIs should be matched and given in the same order. +end +if fieldIsNotDefined(params,'subjectROIbase') + params.subjectROIbase = []; % export space of the ROI (by default, same as the overlay scan space) +end +if fieldIsNotDefined(params,'binarizeROIs') + params.binarizeROIs = false; % Binarize ROIs after resampling to template space, or keep interpolated values (default = false) +end +if fieldIsNotDefined(params,'templateOverlayGroupNums') + params.templateOverlayGroupNums = []; % in what group of the group mrLoadRet folder the group-normalized overlays (concatenated in a scan) will be imported +end +if fieldIsNotDefined(params,'templateOverlayGroupNames') + params.templateOverlayGroupNames = params.freesurferTemplateID; % Names of the group of the template mrLoadRet folder the group-normalized overlays (concatenated in a scan) will be imported +end +if fieldIsNotDefined(params,'templateROIgroupName') + params.templateROIgroupName = 'ROI group'; % Name of the group of the template mrLoadRet folder the group-normalized ROI masks (concatenated in a scan) will be imported +end +if fieldIsNotDefined(params,'combineLeftAndRight') + params.combineLeftAndRight = false; % if true, data will also be left-right flipped and combined across all hemispheres of all subjects +end +if fieldIsNotDefined(params,'lrFlippedFreesurferTemplateID') + params.lrFlippedFreesurferTemplateID = ''; % if combineLeftAndRight is true, name of the Freesurfer folder containing the left-right flipped surfaces of the left-right-flipped template +end +if fieldIsNotDefined(params,'lrFlipTransformFunctions') + params.lrFlipTransformFunctions = {}; +end +if fieldIsNotDefined(params,'lrFlipTransform') + params.lrFlipTransform = 0; +end +if fieldIsNotDefined(params,'computeGroupAverage') + params.computeGroupAverage = true; % if true, normalized overlays will be averaged across subjects (optionally hemispheres) +end +if fieldIsNotDefined(params,'templateOverlayNewNames') + params.templateOverlayNewNames = {}; % New names of the converted overlays +end +if fieldIsNotDefined(params,'computeROIprobabilityMaps') + params.computeROIprobabilityMaps = true; % if true, normalized ROI masks will be averaged across subjects (optionally hemispheres) +end +if fieldIsNotDefined(params,'templateROInewNames') + params.templateROInewNames = {}; % New names of the converted ROIs. When combining across left and right hemispheres, the number of new ROI names should match the number of left/right ROI pairs and be given in the same order +end +if fieldIsNotDefined(params,'cropScans') + params.cropScans = true; +end +if fieldIsNotDefined(params,'dryRun') + params.dryRun = false; % if true, just check whether the overlays and surfaces exist +end + +if justGetParams + return; +else + success = false; +end + + +if ischar(params.subjectOverlays) + params.subjectOverlays = {params.subjectOverlays}; +end +if ischar(params.subjectROIs) + params.subjectROIs = {params.subjectROIs}; +end +if ischar(params.subjectROIbase) + params.subjectROIbase = {params.subjectROIbase}; +end +if ischar(params.fsSubjectSurfSuffix) + params.fsSubjectSurfSuffix = {params.fsSubjectSurfSuffix}; +end + + +nSubjects = length(params.mrLoadRetSubjectIDs); +nOverlays = length(params.subjectOverlays); +nROIs = length(params.subjectROIs); +nTemplateGroups = max(params.templateOverlayGroupNums); +if isempty(nTemplateGroups) + nTemplateGroups = 0; +end + +if numel(params.subjectOverlayGroups)==1 + params.subjectOverlayGroups = repmat(params.subjectOverlayGroups,1,nOverlays); +end +if numel(params.subjectOverlayScans)==1 + params.subjectOverlayScans = repmat(params.subjectOverlayScans,1,nOverlays); +end +if numel(params.subjectOverlayAnalyses)==1 + params.subjectOverlayAnalyses = repmat(params.subjectOverlayAnalyses,1,nOverlays); +end +if numel(params.templateOverlayGroupNums)==1 + params.templateOverlayGroupNums = repmat(params.templateOverlayGroupNums,1,nOverlays); +end +if numel(params.subjectROIbase)==1 + params.subjectROIbase = repmat(params.subjectROIbase,1,nSubjects); +end +if numel(params.fsSubjectSurfSuffix)==1 + params.fsSubjectSurfSuffix = repmat(params.fsSubjectSurfSuffix,1,nSubjects); +end +if numel(params.lrFlipTransform)==1 + params.lrFlipTransform = repmat(params.lrFlipTransform,1,nOverlays); +end +if numel(params.computeGroupAverage)==1 + params.computeGroupAverage = repmat(params.computeGroupAverage,1,nTemplateGroups); +end + + + +if nSubjects ~= length(params.freesurferSubjectIDs) + mrWarnDlg('(mlrSphericalNormGroup) There must be the same number of mrLoadRet and freesurfer subject IDs') + return; +end +if nSubjects ~= length(params.fsSubjectSurfSuffix) + mrWarnDlg('(mlrSphericalNormGroup) The subject surface suffix must be a single string or a cells of strings of length equal to the number of subjects') + return; +end +if nOverlays ~= length(params.subjectOverlayGroups) + mrWarnDlg(sprintf('(mlrSphericalNormGroup) The number of subject groups must be a single value or a vector of same length as the number of overlays (%d)',nOverlays)) + return; +end +if nOverlays ~= length(params.subjectOverlayScans) + mrWarnDlg(sprintf('(mlrSphericalNormGroup) The number of subject scans must be a single value or a vector of same length as the number of overlays (%d)',nOverlays)) + return; +end +if nOverlays ~= length(params.subjectOverlayAnalyses) + mrWarnDlg(sprintf('(mlrSphericalNormGroup) The number of subject analyses must be a single value or a vector of same length as the number of overlays (%d)',nOverlays)) + return; +end +if nOverlays ~= length(params.templateOverlayGroupNums) + mrWarnDlg(sprintf('(mlrSphericalNormGroup) The template group number must be a scalar or a vector of same length as the number of subject overlays (%d)',nOverlays)) + return; +end +if nROIs ~= length(params.subjectROIsides) + mrWarnDlg(sprintf('(mlrSphericalNormGroup) The number of ROI sides must match the number of ROIs (%d)',nROIs)) + return; +end +if params.combineLeftAndRight + if nnz(params.subjectROIsides==1)~=nnz(params.subjectROIsides==2) + mrWarnDlg('(mlrSphericalNormGroup) When combining across left and right hemispheres, left and right ROIs should be matched (and be given in the same order)') + return; + end + if length(params.templateROInewNames) ~= nROIs/2 + mrWarnDlg(sprintf('(mlrSphericalNormGroup) When combining ROIs across left and right hemispheres, provide new ROI names that do not include ''left'' or ''right''. Their number and order should match that of left/right ROI pairs (%d)',nROIs/2)); + return; + end +else + if ~isempty(params.templateROInewNames) && length(params.templateROInewNames) ~= nROIs + mrWarnDlg(sprintf('(mlrSphericalNormGroup) The number of new ROI names should match the number of ROIs (%d)',nROIs)); + return; + end +end +if isempty(params.subjectOverlays) && isempty(params.subjectROIbase) + mrWarnDlg('(mlrSphericalNormGroup) If exporting only ROIs and no overlay, you must specify an ROI base space.') + return; +end +if ~isempty(params.subjectROIbase) && nSubjects ~= length(params.subjectROIbase) + mrWarnDlg(sprintf('(mlrSphericalNormGroup) The number of ROI bases must be a scalar or a vector of same length as the number of subjects (%d)',nROIs)) + return; +end +if nOverlays ~= length(params.lrFlipTransform) + mrWarnDlg(sprintf('(mlrSphericalNormGroup) lrFlipTransform must be a scalar or a vector of same length as the number of subject overlays and ROIs (%d)',nOverlays)) + return; +end +if ~isempty(params.templateOverlayGroupNums) && min(params.templateOverlayGroupNums)~=1 || (length(unique(params.templateOverlayGroupNums))>1 && any(diff(unique(params.templateOverlayGroupNums)))~=1) + mrWarnDlg('(mlrSphericalNormGroup) Template group numbers must be consecutive and start at 1') + return; +end +if nTemplateGroups > length(params.templateOverlayGroupNames) + mrWarnDlg(sprintf('(mlrSphericalNormGroup) The largest template group number (%d) cannot be larger than the number of template group names',nTemplateGroups,length(params.templateOverlayGroupNames))) + return; +end +if strcmp(params.templateROIgroupName,'ROIs') + mrWarnDlg('(mlrSphericalNormGroup) The template ROI group cannot be named ''ROIs'''); + return; +end +if nTemplateGroups > length(params.computeGroupAverage) + mrWarnDlg(sprintf('(mlrSphericalNormGroup) computeGroupAverage must be a scalar or have the same number of elements as the number of template groups (%d)',nTemplateGroups)) + return; +end +if params.combineLeftAndRight && isempty(params.lrFlippedFreesurferTemplateID) + mrWarnDlg('(mlrSphericalNormGroup) If combining left and right hemispheres, a left-right-flipped group Freesurfer ID must be specified') + return; +end +if max(params.lrFlipTransform)>length(params.lrFlipTransformFunctions) + mrWarnDlg(sprintf('(mlrSphericalNormGroup) The largest LR-flip function number (%d) cannot be larger than the number LR-flip transform functions (%d)',max(params.lrFlipTransform),length(params.lrFlipTransformFunctions))) + return; +end +if ismember(params.mrLoadRetTemplateID,params.mrLoadRetSubjectIDs) + mrWarnDlg('(mlrSphericalNormGroup) The name of the mrLoadRet group folder cannot be the same as one of the subjects'' mrLoadRet ID') + return; +end +if ~isempty(params.templateOverlayNewNames) && length(params.templateOverlayNewNames)~=nOverlays + mrWarnDlg(sprintf('(mlrSphericalNormGroup) There should be %d replacement overlay names',nOverlays)); + return; +end + +badCharFixlist = {{'<','_lt_'},{'>','_gt_'},{':',';'},{'"',''''},{'/','_'},{'/','_'},{'|','_'},{'?','!'},{'*','%'}}; %characters that are not accepted in (Windows) file names +sides = {'left','right'}; +if params.combineLeftAndRight + whichROI = zeros(1,nROIs); + whichROI(params.subjectROIsides==1) = 1:nROIs/2; + whichROI(params.subjectROIsides==2) = 1:nROIs/2; +end + +if params.dryRun + fprintf('\n(mlrSphericalNormGroup) THIS IS A DRY RUN: no data will be exported or written to disc\n'); +end + +% create mrLoadRet group folder +mrLoadRetTemplateFolder = fullfile(params.studyDir,params.mrLoadRetTemplateID); +temporaryTseriesFolder = fullfile(params.studyDir,'tempTSeries'); +if ~params.dryRun + mkdir(temporaryTseriesFolder); +end +for iGroup = 1: nTemplateGroups + (nROIs>0) + if iGroup > nTemplateGroups + templateGroupFolder{iGroup} = params.templateROIgroupName; + else + templateGroupFolder{iGroup} = params.templateOverlayGroupNames{iGroup}; + end + templateTseriesFolder{iGroup} = fullfile(mrLoadRetTemplateFolder,templateGroupFolder{iGroup},'TSeries'); +end +if ~params.dryRun + if ~exist(mrLoadRetTemplateFolder,'dir') + initMrLoadRetGroup = true; + makeEmptyMLRDir(mrLoadRetTemplateFolder,'description=Spherical normalization folder','subject=group',... + 'operator=Created by mlrSphericalNormGroup','defaultParams=1',sprintf('defaultGroup=%s', templateGroupFolder{1})); + for iGroup = 2: nTemplateGroups + (nROIs>0) + mkdir(mrLoadRetTemplateFolder,templateGroupFolder{iGroup}); + mkdir(templateTseriesFolder{iGroup}); + end + else + initMrLoadRetGroup = false; + cd(mrLoadRetTemplateFolder) + thisView = mrLoadRet([],'No GUI'); + groupExists = false; + for iGroup = 1: nTemplateGroups + (nROIs>0) + if isempty(viewGet(thisView,'groupnum',templateGroupFolder{iGroup})) + mkdir(mrLoadRetTemplateFolder,templateGroupFolder{iGroup}); + mkdir(templateTseriesFolder{iGroup}); + else + mrWarnDlg(sprintf('(mlrSphericalNormGroup) Group ''%s'' already exists in template folder ''%s''',templateGroupFolder{iGroup},params.mrLoadRetTemplateID)); + groupExists = true; + end + end + deleteView(thisView); + if groupExists + return; + end + end +end + +mrQuit(0); % make sure there are no open MLR views + +% export subject overlays to subject-specific scan spaces +cNiftiConcat = zeros(nTemplateGroups+(nROIs>0),3); +multipleScans = length(unique(params.subjectOverlayScans))>1; +for iSubj = 1:nSubjects + fprintf('\n(mlrSphericalNormGroup) Exporting scan data for subject %s ...\n',params.mrLoadRetSubjectIDs{iSubj}); + % some OSs don't deal with files that have more than 259 characters (including path) + maxNcharacters = 259 - (length(temporaryTseriesFolder)+1) - (length(params.mrLoadRetSubjectIDs{iSubj})+1) ... + - (max(params.combineLeftAndRight*(length(params.lrFlippedFreesurferTemplateID)),length(params.freesurferTemplateID))+1) ... + - multipleScans*6 - 4; + cd(fullfile(params.studyDir,params.mrLoadRetSubjectIDs{iSubj})); + thisView = mrLoadRet(params.mrLoadRetSubjectLastView,'No GUI'); + + cOverlay = 0; + exportNames = cell(0); + convertNames = cell(0,1+params.combineLeftAndRight); + overlayExists = false(1,nOverlays); + roiExists = false(1,nROIs); + roiExportList = []; + for iOverlay = 1:nOverlays + if iscell(params.subjectOverlayGroups) + if ischar(params.subjectOverlayGroups{iOverlay}) + groupNum = viewGet(thisView,'groupNum',params.subjectOverlayGroups{iOverlay}); + if isempty(groupNum) + mrWarnDlg(sprintf('(mlrSphericalNormGroup) Cannot find group ''%s''',params.subjectOverlayGroups{iOverlay})); + end + else + groupNum = params.subjectOverlayGroups{iOverlay}; + end + else + groupNum = params.subjectOverlayGroups(iOverlay); + end + if ~isempty(groupNum) + if groupNum > viewGet(thisView,'nGroups') + mrWarnDlg(sprintf('(mlrSphericalNormGroup) Cannot find group %d',groupNum)); + else + thisView = viewSet(thisView,'curGroup',groupNum); + groupName = viewGet(thisView,'groupName'); + + scanNum = viewGet(thisView,'nscans'); + if params.subjectOverlayScans(iOverlay) > scanNum + mrWarnDlg(sprintf('(mlrSphericalNormGroup) Cannot find scan %d in group %d',params.subjectOverlayScans(iOverlay),groupNum)); + else + thisView = viewSet(thisView,'curScan',scanNum); + if iOverlay==1 + scanHdr = viewGet(thisView,'niftiHdr'); + thisScanHdr = scanHdr; + else + thisScanHdr = viewGet(thisView,'niftiHdr'); + end + if any(any(abs(scanHdr.sform44 - thisScanHdr.sform44)>1e-4)) || ... + any(abs(scanHdr.dim(2:4) - thisScanHdr.dim(2:4))>1e-4) || ... + any(abs(scanHdr.pixdim(2:4) - thisScanHdr.pixdim(2:4))>1e-4) + mrWarnDlg(sprintf('(mlrSphericalNormGroup) Header for scan %d in group %d differs from the rest',params.subjectOverlayScans(iOverlay),groupNum)); + else + if iscell(params.subjectOverlayAnalyses) + if ischar(params.subjectOverlayAnalyses{iOverlay}) + analysisNum = viewGet(thisView,'AnalysisNum',params.subjectOverlayAnalyses{iOverlay}); + if isempty(analysisNum) + mrWarnDlg(sprintf('(mlrSphericalNormGroup) Cannot find analysis ''%s'' in group ''%s''',params.subjectOverlayAnalyses{iOverlay},groupName)); + end + else + analysisNum = params.subjectOverlayAnalyses{iOverlay}; + end + else + analysisNum = params.subjectOverlayAnalyses(iOverlay); + end + if ~isempty(analysisNum) + if analysisNum > viewGet(thisView,'nAnalyses') + mrWarnDlg(sprintf('(mlrSphericalNormGroup) Cannot find analysis %d in group ''%s''',analysisNum,groupName)); + else + thisView = viewSet(thisView,'curAnalysis',analysisNum); + analysisName = viewGet(thisView,'analysisName'); + if iscell(params.subjectOverlays) + if ischar(params.subjectOverlays{iOverlay}) + overlayNum = viewGet(thisView,'overlayNum',params.subjectOverlays{iOverlay}); + if isempty(overlayNum) + mrWarnDlg(sprintf('(mlrSphericalNormGroup) Cannot find overlay ''%s'' in group ''%s'', analysis ''%s''',params.subjectOverlays{iOverlay},groupName,analysisName)); + end + else + overlayNum = params.subjectOverlays(iOverlay); + end + else + overlayNum = params.subjectOverlays(iOverlay); + end + if ~isempty(overlayNum) + if overlayNum > viewGet(thisView,'nOverlays') + mrWarnDlg(sprintf('(mlrSphericalNormGroup) Cannot find overlay %d in group ''%s'', analysis ''%s''...',overlayNum,groupName,analysisName)); + else + thisView = viewSet(thisView,'curOverlay',overlayNum); + overlayName = viewGet(thisView,'overlayName'); + overlayExists(iOverlay)=true; + fprintf('(mlrSphericalNormGroup) Will export Group %d, Analysis %d, overlay %d: ''%s''\n',groupNum,analysisNum,overlayNum,overlayName); + cOverlay = cOverlay + 1; + end + end + end + end + end + end + + if overlayExists(iOverlay) + if length(groupName) > 6 && isequal(groupName(end-5:end),'Volume') + mrWarnDlg(sprintf('(mlrSphericalNormGrousprintf) Subject %s: group %s seems to be a volume version of a flat base and needs to be converted using flatVol2OriginalVolume',... + params.mrLoadRetSubjectIDs{iSubj},groupName)); + end + overlayBaseName{iOverlay} = fixBadChars(viewGet(thisView,'overlayName'),badCharFixlist,[],maxNcharacters); + if multipleScans + overlayBaseName{iOverlay} = sprintf('%s_Scan%d',overlayBaseName{iOverlay},scanNum); + end + if iSubj==1 + if isempty(params.templateOverlayNewNames) + templateOverlayNewNames{cOverlay} = overlayBaseName{iOverlay}; + else + templateOverlayNewNames{cOverlay} = fixBadChars(params.templateOverlayNewNames{iOverlay},badCharFixlist,[],maxNcharacters); + end + end + % export overlays to NIFTI in scan space + exportNames{cOverlay} = fullfile(temporaryTseriesFolder,sprintf('%s_%s.nii',overlayBaseName{iOverlay},params.mrLoadRetSubjectIDs{iSubj})); + convertNames{cOverlay,1} = fullfile(temporaryTseriesFolder,sprintf('%s_%s_%s.nii',overlayBaseName{iOverlay},params.mrLoadRetSubjectIDs{iSubj},params.freesurferTemplateID)); + if params.combineLeftAndRight + convertNames{cOverlay,2} = fullfile(temporaryTseriesFolder,sprintf('%s_%s_%s.nii',overlayBaseName{iOverlay},params.mrLoadRetSubjectIDs{iSubj},params.lrFlippedFreesurferTemplateID)); + end + if ~params.dryRun + mrExport2SR(thisView,exportNames{cOverlay},0); + end + end + end + end + end + + % export ROIs to masks in subject's anatomical space + if ~isempty(params.subjectROIbase{iSubj}); % first make sure we know what space to export it to + roiBaseNum = viewGet(thisView,'baseNum',params.subjectROIbase{iSubj}); + if isempty(roiBaseNum) + mrWarnDlg(sprintf('(mlrSphericalNormGroup) Cannot find base %s...',params.subjectROIbase{iSubj})); + end + else + roiBaseNum = []; + end + cROI = 0; + for iRoi = 1:nROIs + if iscell(params.subjectROIs) + if ischar(params.subjectROIs{iRoi}) + roiNum = viewGet(thisView,'roiNum',params.subjectROIs{iRoi}); + if isempty(roiNum) + mrWarnDlg(sprintf('(mlrSphericalNormGroup) Cannot find ROI ''%s''',params.subjectROIs{iRoi})); + end + else + roiNum = params.subjectROIs(iOverlay); + end + else + roiNum = params.subjectROIs(iOverlay); + end + if ~isempty(roiNum) + if roiNum > viewGet(thisView,'nROIs') + mrWarnDlg(sprintf('(mlrSphericalNormGroup) Cannot find ROI %d...',overlayNum)); + else + thisView = viewSet(thisView,'curROI',roiNum); + roiName = viewGet(thisView,'roiName'); + roiExists(iRoi)=true; + fprintf('(mlrSphericalNormGroup) Will export ROI %d: ''%s''\n',roiNum,roiName); + cROI = cROI + 1; + roiExportList(cROI) = roiNum; + end + end + if roiExists(iRoi) + roiBaseName{iRoi} = fixBadChars(roiName,badCharFixlist,[],maxNcharacters); + if iSubj==1 + if isempty(params.templateROInewNames) + templateOverlayNewNames{cOverlay+cROI} = roiBaseName{iRoi}; + else + if params.combineLeftAndRight + templateOverlayNewNames{cOverlay+cROI} = params.templateROInewNames{whichROI(iRoi)}; + else + templateOverlayNewNames{cOverlay+cROI} = params.templateROInewNames{iRoi}; + end + templateOverlayNewNames{cOverlay+cROI} = fixBadChars(templateOverlayNewNames{cOverlay+cROI},badCharFixlist,[],maxNcharacters); + end + end + % export overlays to NIFTI in scan space + exportNames{cOverlay+cROI} = fullfile(temporaryTseriesFolder,sprintf('%s_%s.nii',roiBaseName{iRoi},params.mrLoadRetSubjectIDs{iSubj})); + convertNames{cOverlay+cROI,1} = fullfile(temporaryTseriesFolder,sprintf('%s_%s_%s.nii',roiBaseName{iRoi},params.mrLoadRetSubjectIDs{iSubj},params.freesurferTemplateID)); + if params.combineLeftAndRight + convertNames{cOverlay+cROI,2} = fullfile(temporaryTseriesFolder,sprintf('%s_%s_%s.nii',roiBaseName{iRoi},params.mrLoadRetSubjectIDs{iSubj},params.lrFlippedFreesurferTemplateID)); + end + if ~params.dryRun + if isempty(roiBaseNum) + mlrExportROI(thisView,exportNames{cOverlay+cROI},'scanNum',scanNum,'grouNnum',groupNum); % export to scan space + else + mlrExportROI(thisView,exportNames{cOverlay+cROI},'baseNum',roiBaseNum); + end + end + end + + end + + deleteView(thisView); % close view without saving (note that because there should be only one view in global variable MLR), this deletes MLR too + + if all(overlayExists) && all(roiExists) || params.dryRun + % transform subject scans to group space + fprintf('\n(mlrSphericalNormGroup) Converting %s data to %s template space ...\n',params.mrLoadRetSubjectIDs{iSubj},params.freesurferTemplateID); + fsSphericalParams = freesurferSphericalNormalizationVolumes([],'justGetParams'); + fsSphericalParams.sourceVol = exportNames; + fsSphericalParams.fsSourceSubj = params.freesurferSubjectIDs{iSubj}; + fsSphericalParams.fsSourceSurfSuffix = params.fsSubjectSurfSuffix{iSubj}; + fsSphericalParams.fsDestSubj = params.freesurferTemplateID; + fsSphericalParams.destVol = convertNames(:,1); + fsSphericalParams.interpMethod = 'linear'; + fsSphericalParams.outputBinaryData = params.binarizeROIs; + fsSphericalParams.dryRun = params.dryRun; + fsSphericalParamsOut = freesurferSphericalNormalizationVolumes(fsSphericalParams); + if params.combineLeftAndRight + fprintf('\n(mlrSphericalNormGroup) Converting %s data to %s template space ...\n',params.mrLoadRetSubjectIDs{iSubj},params.lrFlippedFreesurferTemplateID); + fsSphericalParams.fsDestSubj = params.lrFlippedFreesurferTemplateID; + fsSphericalParams.destVol = convertNames(:,2); + freesurferSphericalNormalizationVolumes(fsSphericalParams); + end + else + return + end + + if ~params.dryRun + + % delete subject-space exported NIFTI files + for iFile = 1:nOverlays+nROIs + delete(exportNames{iFile}); + end + + % Concatenate normalized subject data + fprintf('\n(mlrSphericalNormGroup) Concatenating transformed data for subject %s...\n',params.mrLoadRetSubjectIDs{iSubj}); + for iGroup = 1:nTemplateGroups + (nROIs>0) + if iGroup > nTemplateGroups + niftiFiles = nOverlays+ (1:nROIs); + nScans(iGroup) = 1+2*params.combineLeftAndRight; + else + niftiFiles = find(params.templateOverlayGroupNums==iGroup); + nScans(iGroup) = 1+params.combineLeftAndRight; + end + for iFile = niftiFiles + cNiftiConcat(iGroup,1) = cNiftiConcat(iGroup,1)+1; + if iSubj==1 && iFile == niftiFiles(1) + if iGroup>nTemplateGroups + templateScanName{iGroup,1} = sprintf('Concatenation of ROIs %s', mat2str(roiExportList)); + else + templateScanName{iGroup,1} = sprintf('Concatenation of overlays-analyses-groups %s-%s-%s', ... + mat2str(params.subjectOverlays(niftiFiles)),mat2str(params.subjectOverlayAnalyses(niftiFiles)),mat2str(params.subjectOverlayGroups(niftiFiles))); + end + templateScanName{iGroup,1} = fixBadChars(templateScanName{iGroup,1}); % replace spaces by underscores + scanFileName{iGroup,1} = fullfile(templateTseriesFolder{iGroup},[templateScanName{iGroup,1} '.nii']); + if params.combineLeftAndRight + templateScanName{iGroup,2} = [templateScanName{iGroup,1} '_LRcombined']; + if iGroup>nTemplateGroups + templateScanName{iGroup,3} = [templateScanName{iGroup,2} 'OnRight']; + templateScanName{iGroup,2} = [templateScanName{iGroup,2} 'OnLeft']; + scanFileName{iGroup,3} = fullfile(templateTseriesFolder{iGroup},[templateScanName{iGroup,3} '.nii']); + end + scanFileName{iGroup,2} = fullfile(templateTseriesFolder{iGroup},[templateScanName{iGroup,2} '.nii']); + end + end + [data,hdr] = cbiReadNifti(convertNames{iFile,1}); + hdr.time_units = 'subjects/conditions'; % I dont think this is is doing anything + cbiWriteNifti(scanFileName{iGroup,1},data,hdr,'',{[],[],[],cNiftiConcat(iGroup,1)}); + % for log file + if iGroup > nTemplateGroups && params.combineLeftAndRight % for the ROI group, add the side to the name if combining between left and right + logfile{iGroup,1}.overlay{cNiftiConcat(iGroup,1),1} = [sides{params.subjectROIsides(iFile-nOverlays)} templateOverlayNewNames{iFile}]; + else + logfile{iGroup,1}.overlay{cNiftiConcat(iGroup,1),1} = templateOverlayNewNames{iFile}; + end + logfile{iGroup,1}.subject{cNiftiConcat(iGroup,1),1} = params.mrLoadRetSubjectIDs{iSubj}; + if iGroup > nTemplateGroups % for the ROI group, add a side variable + logfile{iGroup,1}.hemisphere{cNiftiConcat(iGroup,1),1} = sides{params.subjectROIsides(iFile-nOverlays)}; + end + + if params.combineLeftAndRight + if iGroup > nTemplateGroups % for the ROI group, split the data between left and right hemispheres + if params.subjectROIsides(iFile-nOverlays)==1 + normalScanNum = 2; % put normally-oriented left-hemisphere ROIs in the 2nd scan + reverseScanNum = 3; % but reverse-oriented right-hemisphere ROIs in a 3rd scan + else + normalScanNum = 3; % for right-hemisphere ROIs + reverseScanNum = 2; % do the reverse + end + else + normalScanNum = 2; + reverseScanNum = 2; + end + cNiftiConcat(iGroup,normalScanNum) = cNiftiConcat(iGroup,normalScanNum)+1; + cbiWriteNifti(scanFileName{iGroup,normalScanNum},data,hdr,'',{[],[],[],cNiftiConcat(iGroup,normalScanNum)}); + logfile{iGroup,normalScanNum}.overlay{cNiftiConcat(iGroup,normalScanNum),1} = templateOverlayNewNames{iFile}; + logfile{iGroup,normalScanNum}.leftRightDirection{cNiftiConcat(iGroup,normalScanNum),1} = 'normal'; + logfile{iGroup,normalScanNum}.subject{cNiftiConcat(iGroup,normalScanNum),1} = params.mrLoadRetSubjectIDs{iSubj}; + if iGroup > nTemplateGroups % for the ROI group, add the side + logfile{iGroup,normalScanNum}.hemisphere{cNiftiConcat(iGroup,normalScanNum),1} = sides{params.subjectROIsides(iFile-nOverlays)}; + end + cNiftiConcat(iGroup,reverseScanNum) = cNiftiConcat(iGroup,reverseScanNum)+1; + [data,hdr] = cbiReadNifti(convertNames{iFile,2}); + if iGroup <= nTemplateGroups + if params.lrFlipTransform(iFile) + data = params.lrFlipTransformFunctions{params.lrFlipTransform(iFile)}(data); + end + end + hdr.time_units = 'subjects/conditions'; % I dont think this is is doing anything + cbiWriteNifti(scanFileName{iGroup,reverseScanNum},data,hdr,'',{[],[],[],cNiftiConcat(iGroup,reverseScanNum)}); + logfile{iGroup,reverseScanNum}.overlay{cNiftiConcat(iGroup,reverseScanNum),1} = templateOverlayNewNames{iFile}; + logfile{iGroup,reverseScanNum}.leftRightDirection{cNiftiConcat(iGroup,reverseScanNum),1} = 'reversed'; + logfile{iGroup,reverseScanNum}.subject{cNiftiConcat(iGroup,reverseScanNum),1} = params.mrLoadRetSubjectIDs{iSubj}; + if iGroup > nTemplateGroups % for the ROI group, add the side + logfile{iGroup,reverseScanNum}.hemisphere{cNiftiConcat(iGroup,reverseScanNum),1} = sides{params.subjectROIsides(iFile-nOverlays)}; + end + end + % delete temporary converted subject files + for iCombine = 1:1+params.combineLeftAndRight + delete(convertNames{iFile,iCombine}); + end + end + end + end +end + +if ~params.dryRun + + if params.cropScans + for iGroup = 1:nTemplateGroups+(nROIs>0) + for iScan = 1:nScans(iGroup) + fprintf('\n(mlrSphericalNormGroup) Cropping scan %d\n',iScan); + cropBox = [inf -inf;inf -inf;inf -inf]; + croppedScanFileName = fullfile(templateTseriesFolder{iGroup},[templateScanName{iGroup,iScan} '_cropped.nii']); + scanHdr = cbiReadNiftiHeader(scanFileName{iGroup,iScan}); + for iVolume = 1:scanHdr.dim(5) + data = cbiReadNifti(scanFileName{iGroup,iScan},{[],[],[],iVolume}); + [X,Y,Z] = ind2sub(scanHdr.dim(2:4)',find(~isnan(data)&data~=0)); % can probably do better than this by finding main axes + cropBox(:,1) = min(cropBox(:,1), floor([min(X);min(Y);min(Z)])); % of coordinates by svd and applying rotation before cropping + cropBox(:,2) = max(cropBox(:,2), floor([max(X);max(Y);max(Z)])); % but this would involve resampling the data + end + scanHdr.dim(2:4) = diff(cropBox,[],2)+1; + cropXform = eye(4); + cropXform(1:3,4) = -cropBox(:,1)+1; + scanHdr.qform44 = cropXform\scanHdr.qform44; + scanHdr.sform44 = cropXform\scanHdr.sform44; + for iVolume = 1:scanHdr.dim(5) + data = cbiReadNifti(scanFileName{iGroup,iScan},{cropBox(1,:),cropBox(2,:),cropBox(3,:),iVolume}); + cbiWriteNifti(croppedScanFileName,data,scanHdr,'',{[],[],[],iVolume}); + end + movefile(croppedScanFileName,scanFileName{iGroup,iScan}); + end + end + end + + % initialize mrLoadRet view/import group + fprintf('\n(mlrSphericalNormGroup) Importing group data into MLR folder %s\n',params.mrLoadRetTemplateID); + cd(mrLoadRetTemplateFolder); + if initMrLoadRetGroup + scanList{1} = 1:nScans(1); + subjectSessionParams = load(fullfile(params.studyDir,params.mrLoadRetSubjectIDs{1},'mrSession.mat')); + [sessionParams, groupParams] = mrInit([],[],'justGetParams=1','defaultParams=1'); + for iScan = scanList{1} + groupParams.description{iScan} = templateScanName{1,iScan}; + end + sessionParams.magnet = subjectSessionParams.session.magnet; + sessionParams.coil = subjectSessionParams.session.coil; + [sessionParams.pulseSequence,sessionParams.pulseSequenceText] = strtok(subjectSessionParams.session.protocol,':'); + mrInit(sessionParams,groupParams,'makeReadme=0','noPrompt=1'); + templateGroupNums{1} = 1; + % load base anatomies: + % load whole-head MPRAGE anatomy + thisView = mrLoadRet(params.mrLoadRetTemplateLastView,'No GUI'); + [fsPath,filename,ext] = fileparts(fsSphericalParamsOut.destSurfRelaxVolume); + thisView = loadAnat(thisView,[filename ext],fsPath); + thisView = viewSet(thisView,'basesliceindex',3); %set to axial view + thisView = viewSet(thisView,'rotate',90); + % import surfaces + sides = {'left','right'}; + for iSide=1:2 + fprintf('(mlrSphericalNormGroup) Importing %s surface for %s\n',sides{iSide},params.freesurferTemplateID); + importSurfParams.path = fsPath; + importSurfParams.outerSurface = [params.freesurferTemplateID '_' sides{iSide} '_Inf.off']; % in order to view half-inflated surfaces, set the outer surface coordinates (outerCoords) to be the GM surface + importSurfParams.outerCoords = [params.freesurferTemplateID '_' sides{iSide} '_GM.off']; % and the outer surface (outerSurface) to be the inflated surface + importSurfParams.innerSurface = [params.freesurferTemplateID '_' sides{iSide} '_WM.off']; + importSurfParams.innerCoords = [params.freesurferTemplateID '_' sides{iSide} '_WM.off']; + importSurfParams.anatomy = [filename ext]; + importSurfParams.curv = [params.freesurferTemplateID '_' sides{iSide} '_Curv.vff']; + base = importSurfaceOFF(importSurfParams); + thisView = viewSet(thisView, 'newbase', base); %once the base has been imported into matlab, set it in the view + thisView = viewSet(thisView,'corticalDepth',[0.2 0.8]); %set the range of of cortical depths used for displaying the overlays + end + + else + thisView = mrLoadRet(params.mrLoadRetTemplateLastView,'No GUI'); + end + + % import (other) groups + for iGroup = 1+initMrLoadRetGroup:nTemplateGroups+(nROIs>0) + thisView = viewSet(thisView,'newGroup',templateGroupFolder{iGroup}); + templateGroupNums{iGroup} = viewGet(thisView,'groupNum',templateGroupFolder{iGroup}); + thisView = viewSet(thisView,'curGroup',templateGroupNums{iGroup}); + for iScan = 1:nScans(iGroup) + [~,importParams] = importTSeries(thisView,[],'justGetParams=1','defaultParams=1',['pathname=' scanFileName{iGroup,iScan}]); + importParams.description = templateScanName{iGroup,iScan}; + importParams.overwrite = 1; + thisView = importTSeries(thisView,importParams); + end + scanList{iGroup} = viewGet(thisView,'nScans')-(nScans(iGroup):-1:1)+1; + end + + for iGroup = 1:nTemplateGroups+(nROIs>0) + % create log files indicating which volumes correspond to which overlays and subjects + for iScan = 1:nScans(iGroup) + logfileStruct = logfile{iGroup,iScan}; + logFilename = [templateScanName{iGroup,iScan} '.mat']; + save(fullfile(mrLoadRetTemplateFolder,'Etc',logFilename),'-struct','logfileStruct'); + % link log file to concatenated scan + fprintf('(mlrSphericalNormGroup) Linking %s to scan %d\n',logFilename,scanList{iGroup}(iScan)); + viewSet(thisView,'stimfilename',logFilename, scanList{iGroup}(iScan),templateGroupNums{iGroup}); + end + + if ( iGroup>nTemplateGroups && params.computeROIprobabilityMaps ) || params.computeGroupAverage(iGroup) + thisView = viewSet(thisView,'curGroup',templateGroupNums{iGroup}); + [~,averageParams] = mlrGroupAverage(thisView,[],'justGetParams'); + if iGroup>nTemplateGroups + averageParams.analysisName = 'ROI probability maps'; + end + averageParams.factors = {'overlay'}; + averageParams.scanList = scanList{iGroup}; + thisView = mlrGroupAverage(thisView,averageParams); + thisView = viewSet(thisView,'curOverlay',1); + thisView = viewSet(thisView,'clipAcrossOverlays',false); + end + end + + mrSaveView(thisView); + deleteView(thisView); + +end + +if params.dryRun + success = true; +elseif length(dir(temporaryTseriesFolder))==2 + rmdir(temporaryTseriesFolder); + success = true; +else + mrWarnDlg(sprintf('(mlrSphericalNormGroup) Temporary files left in %s',temporaryTseriesFolder)); +end diff --git a/mrLoadRet/mrGlobals.m b/mrLoadRet/mrGlobals.m index 55eae6d6e..7cd59e3ed 100644 --- a/mrLoadRet/mrGlobals.m +++ b/mrLoadRet/mrGlobals.m @@ -42,13 +42,14 @@ MLR.homeDir = pwd; % Load session and groups structures from mrSESSION.mat - [session, groups] = loadSession(MLR.homeDir); + [session, groups, ~, mniInfo] = loadSession(MLR.homeDir); % check session if isempty(session) oneTimeWarning(sprintf('mrSession_%s',fixBadChars(MLR.homeDir)),sprintf('(mrGlobals) Could not find mrSession in %s',MLR.homeDir)); end MLR.session = session; MLR.groups = groups; + MLR.mniInfo = mniInfo; % Initialize MLR.views MLR.views = {}; diff --git a/mrLoadRet/mrLoadRet.m b/mrLoadRet/mrLoadRet.m index 7f63d5374..0a7c9f1bb 100644 --- a/mrLoadRet/mrLoadRet.m +++ b/mrLoadRet/mrLoadRet.m @@ -16,12 +16,19 @@ % to load a different mrLastView % mrLoadRet('mrLastView2'); % -function [v]= mrLoadRet(mrLastView) +% to load without the GUI +% mrLoadRet([],'No GUI'); + +function [v]= mrLoadRet(mrLastView,mode) % default to -if nargin < 1 +if ieNotDefined('mrLastView') mrLastView = 'mrLastView.mat'; end +if ieNotDefined('mode') + mode = 'normal'; +end + if ~mlrIsFile('mrSession.mat') disp('(mrLoadRet) No mrSession.mat found in current directory'); return @@ -48,4 +55,4 @@ end % Open inplane window -v = mrOpenWindow('Volume',mrLastView); +v = mrOpenWindow('Volume',mrLastView,strcmp(mode,'No GUI')); diff --git a/mrLoadRet/mrLoadRetVersion.m b/mrLoadRet/mrLoadRetVersion.m index 81de60b75..833b0551e 100644 --- a/mrLoadRet/mrLoadRetVersion.m +++ b/mrLoadRet/mrLoadRetVersion.m @@ -4,7 +4,7 @@ ver = 4.7; % Change this after testing Matlab upgrades -expectedMatlabVersion = [7.2 7.3 7.4 7.5 7.6 7.7 7.8 7.9 7.10 7.11 7.12 7.13 7.14 8.0 8.1 8.2]; +expectedMatlabVersion = [7.2 7.3 7.4 7.5 7.6 7.7 7.8 7.9 7.10 7.11 7.12 7.13 7.14 8.0 8.1 8.2 8.4 8.5 9.0 9.1 9.2 9.4 9.5 9.7 9.8 9.13 9.14 9.15]; % expected toolbox if verLessThan('matlab','8.5') diff --git a/mrUtilities/File/Caret/mlrImportCaret.m b/mrUtilities/File/Caret/mlrImportCaret.m index 29eae5c39..f0015b1b4 100644 --- a/mrUtilities/File/Caret/mlrImportCaret.m +++ b/mrUtilities/File/Caret/mlrImportCaret.m @@ -292,9 +292,9 @@ function dispXform(name,xform) function checkAllCaretFiles(surfRelaxDir,caretFileDir) % check directory -disppercent(-inf,sprintf('(mlrImportCaret) Checking %s directory for topo and coord files',caretFileDir)); +mlrDispPercent(-inf,sprintf('(mlrImportCaret) Checking %s directory for topo and coord files',caretFileDir)); [topoFiles coordFiles] = listCaretDir(sprintf('dirname=%s',caretFileDir),'noDisplay=1'); -disppercent(inf); +mlrDispPercent(inf); % return if files not found if isempty(topoFiles) diff --git a/mrUtilities/File/FreeSurfer/freeSurfer2off.m b/mrUtilities/File/FreeSurfer/freeSurfer2off.m index 264d44118..f3ea75719 100644 --- a/mrUtilities/File/FreeSurfer/freeSurfer2off.m +++ b/mrUtilities/File/FreeSurfer/freeSurfer2off.m @@ -7,7 +7,7 @@ % purpose: Converts vertices from free surfer conventions and saves as an off. Note that % freeSurfer is 1 based and coordinates start in the middle of the volume. We % therefore have to add half the volume size (in mm) to the coordinates to convert. -% The default is to assume that the volumeSize is 176x256x256 and the pixelSize 1x1x1. +% The default is to assume that the volumeSize is 176x256x256 and the pixelSize 1x1x1. % Note that the script mlrImportFreeSurfer crops volumes to that size. % function [] = freeSurfer2off(fsSurf, offSurf, volumeSize, pixelSize) @@ -31,10 +31,13 @@ [vertices, triangles] = freesurfer_read_surf(fsSurf); % subtract 1 for OFF compatibility -triangles = triangles' -1; -vertices = vertices' -1; - -% center image (including -1 for OFF compatibility) +triangles = triangles' -1; % JB (01/08/2020): this -1 is related to 0-indexing used in the surfRelax format. +vertices = vertices' -1; % I dont' think this -1 represents the same thing because it is subtracted from coordinates in mm + % Instead, I think it's necessary because Freesurfer (mri_convert) always puts ones in hdr.pixdim(6:8) and + % surfRelax assumes (maybe wrongly?) that these represent x,y,z offsets and uses them to convert the coordinates to "world2 coordinates" + % This is then undone when converting back to array coordinates using xformSurfaceWorld2Array or mlrXFormFromHeader(hdr,'world2array') + % (but this time using the actual values in hdr.pixdim(6:8), which are always 1 +% center image vertices(1,:) = vertices(1,:) + pixelSize(1)*volumeSize(1)/2; % higher, more right vertices(2,:) = vertices(2,:) + pixelSize(2)*volumeSize(2)/2; % higher, more anterior vertices(3,:) = vertices(3,:) + pixelSize(3)*volumeSize(3)/2; % higher, more superior @@ -42,7 +45,7 @@ % triangles(1) is number of vert/triangle: 3 % triangles(2:4) are the vertices of that triangles % triangles(5) is color: 0 -triangles = cat(1, repmat(3,1,length(triangles)), triangles, repmat(0,1,length(triangles))); +triangles = cat(1, repmat(3,1,length(triangles)), triangles, zeros(1,length(triangles))); % write the OFF format file fid = fopen(offSurf, 'w', 'ieee-be'); diff --git a/mrUtilities/File/FreeSurfer/mlrGetSurfaceNames.m b/mrUtilities/File/FreeSurfer/mlrGetSurfaceNames.m index 0f0536698..fcf471fa0 100644 --- a/mrUtilities/File/FreeSurfer/mlrGetSurfaceNames.m +++ b/mrUtilities/File/FreeSurfer/mlrGetSurfaceNames.m @@ -5,7 +5,7 @@ % date: 09/05/19 % purpose: uses mrSurfViewer to have user select surfaces % -function [leftSurfaces rightSurfaces] = mlrGetSurfaceNames(varargin) +function [leftSurfaces, rightSurfaces] = mlrGetSurfaceNames(varargin) getArgs(varargin,{'surfPath=[]'}); @@ -14,7 +14,11 @@ titleStr = 'Choose left gray matter (outer) surface for this subject'; disp(sprintf('(mlrImportFreesurferLAbel) %s',titleStr)); surfPath = mlrGetPathStrDialog(mrGetPref('volumeDirectory'),titleStr,'*.off'); - if isempty(surfPath),return,end + if isempty(surfPath) + leftSurfaces = []; + rightSurfaces = []; + return; + end end % make sure this is a GM (not WM or Inf) diff --git a/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m b/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m old mode 100644 new mode 100755 index ffdbb3edd..5d08533d8 --- a/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m +++ b/mrUtilities/File/FreeSurfer/mlrImportFreeSurfer.m @@ -19,14 +19,18 @@ return end -mriConvert = 'mri_convert'; -[retval retstr] = system('which mri_convert'); -if retval == 1 - warnMessage = '(mlrImportFreeSurfer) Could not find FreeSurfer command mri_convert which is needed to convert the FreeSurfer anatomy file to a nifti file. This is usually in the bin directory under your freesurfer installation. You may need to install freesurfer and add that to your path. See instructions on wiki http://gru.stanford.edu/doku.php/mrTools/howTo#installation'; - if ispc - warnMessage = [warnMessage '. This is probably because you''re running mrTools on a Windows PC.']; +if ~ispc + mriConvert = 'mri_convert'; + [retval retstr] = system('which mri_convert'); + if retval == 1 + warnMessage = '(mlrImportFreeSurfer) Could not find FreeSurfer command mri_convert which is needed to convert the FreeSurfer anatomy file to a nifti file. This is usually in the bin directory under your freesurfer installation. You may need to install freesurfer and add that to your path. See instructions on wiki http://gru.stanford.edu/doku.php/mrTools/howTo#installation'; + if ispc + warnMessage = [warnMessage '. This is probably because you''re running mrTools on a Windows PC.']; + end + mrWarnDlg(warnMessage,'Yes'); + mriConvert = []; end - mrWarnDlg(warnMessage,'Yes'); +else mriConvert = []; end @@ -52,6 +56,9 @@ volumeCropSize = [176 256 256]; end defaultPixelSize = [1 1 1]; +if ~exist('pixelSize') + pixelSize = defaultPixelSize; +end paramsInfo = {... {'freeSurferDir',pwd,'directory where the freeSurfer files live'}, ... @@ -65,7 +72,7 @@ {'volumeCropSize',volumeCropSize, 'Size to crop the volume anatomy to (in voxels).'},... }; if isempty(mriConvert) %if mri_convert wasn't found, ask for the resolution - paramsInfo{end+1} = {'pixelSize',defaultPixelSize,'Resolution of the volume anatomy (in mm). This is normally read from the converted anatomy file, but mri_convert is not available.'}; + paramsInfo{end+1} = {'pixelSize',pixelSize,'Resolution of the volume anatomy (in mm). This is normally read from the converted anatomy file, but mri_convert is not available.'}; end % get the parameters from the user @@ -116,19 +123,29 @@ ); else % note that if the full volume size is an odd number of voxels, the qform/sform will be wrong - % because the crop should happen at non-integer cenre coordinates, which mri_convert does not allow + % because the crop should happen at non-integer centre coordinates, which mri_convert does not allow % in this case, do not crop disp(sprintf('(mlrImportFreeSurfer) Odd number of voxels in original freesurfer anatomical volume (%s), not going to crop...',mat2str(volumeSize))); params.volumeCropSize = volumeSize; end system(commandString); else - disp(sprintf('\n(mlrImportFreeSurfer) To convert the canonical anatomy from Freesurfer to NIFTI format, run: \n\t mri_convert%s \nin the appropriate terminal\n',commandString)); - disp('Note that if the original freesurfer anatomical volume is not ...x256x256 x 1mm, there will likely be a mismatch between the volume and the surfaces.'); - disp('If you know the dimensions of the freesurfer volume (in voxels), you are strongly encouraged to re-run mlrImportFreesurfer with these values as the cropSize field and to check for any mismatch between the converted volume and the converted surfaces.\n'); - mrWarnDlg(sprintf('(mlrImportFreeSurfer) !!!! Canonical anatomy not created !!!!')); + if mlrIsFile(outFile) + fprintf('\n(mlrImportFreesurfer) Getting voxel and volume dimensions from existing %s file\n', strcat(params.baseName, '_', 'mprage_pp', niftiExt)); + hdr = cbiReadNiftiHeader(outFile); + params.volumeCropSize = hdr.dim(2:4); + fprintf('Voxel dimensions = %s\n',mat2str(hdr.pixdim(2:4))) + fprintf('Volume dimensions = %s\n',mat2str(hdr.dim(2:4))) + else + disp(sprintf('\n(mlrImportFreeSurfer) To convert the canonical anatomy from Freesurfer to NIFTI format, run: \n\t mri_convert%s \nin the appropriate terminal\n',commandString)); + disp('Note that if the original freesurfer anatomical volume is not ...x256x256 x 1mm, there will likely be a mismatch between the volume and the surfaces.'); + disp('If you know the dimensions of the freesurfer volume (in voxels), you are strongly encouraged to re-run mlrImportFreesurfer with these values as the cropSize field and to check for any mismatch between the converted volume and the converted surfaces.\n'); + mrWarnDlg(sprintf('(mlrImportFreeSurfer) !!!! Canonical anatomy not created !!!!')); + end end + end + if ~mlrIsFile(outFile) if fieldIsNotDefined(params,'pixelSize') disp('(mlrImportFreeSurfer) Could not determine voxel size. Assuming 1x1x1 mm.'); @@ -160,6 +177,11 @@ % convert outer surface surfFile = fullfile(params.freeSurferDir, 'surf', strcat(hemi{i}, '.', params.gmFile)); outFile = fullfile(params.outDir, strcat(params.baseName, '_', hemiNames{i}, '_GM.off')); + if ispc && ~mlrIsFile(surfFile) + if mlrIsFile([surfFile '.T1']) + surfFile = [surfFile '.T1']; + end + end if mlrIsFile(surfFile) freeSurfer2off(surfFile, outFile, params.volumeCropSize, pixelSize); else diff --git a/mrUtilities/File/FreeSurfer/mlrImportFreesurferLabel.m b/mrUtilities/File/FreeSurfer/mlrImportFreesurferLabel.m index c322568c5..990e8c0cc 100644 --- a/mrUtilities/File/FreeSurfer/mlrImportFreesurferLabel.m +++ b/mrUtilities/File/FreeSurfer/mlrImportFreesurferLabel.m @@ -14,7 +14,7 @@ % % See: http://gru.stanford.edu/doku.php/mrtools/atlas#freesurfer_labels % -function roi = mlrImportFreesurferLabel(filename,varargin) +function [roi,leftSurfaceNames,rightSurfaceNames] = mlrImportFreesurferLabel(filename,varargin) % check arguments if nargin < 1 @@ -35,7 +35,7 @@ % look for label files in directory for iFile = 1:length(listing) % see if it is a label - if strcmp('label',getext(listing(iFile).name)); + if strcmp('label',getext(listing(iFile).name)) labelNames{end+1} = listing(iFile).name; end end @@ -70,28 +70,12 @@ end % check the file -if ~isfile(filename) +if ~mlrIsFile(filename) disp(sprintf('(mlrImportFreesurferLabel) Could not find file %s',filename)); return end - -% open file -f = fopen(filename); - -% get header -header = fgetl(f); - -% get number of vertices -nVertices = str2num(fgetl(f)); -% get each vertex row-by-row -for iVertex = 1:nVertices - vertex(iVertex,:) = str2num(fgetl(f)); -end - -% close file -fclose(f); - +[vertex,header] = mlrReadFreesurferLabel(filename); % decide if this is left or right if isempty(hemi) @@ -113,15 +97,18 @@ % choose which surface names to use if strcmp(hemi,'rh') - surfaceNames = rightSurfaceNames; - if isempty(surfaceNames) - [~,surfaceNames] = mlrGetSurfaceNames; + if isempty(rightSurfaceNames) + [~,rightSurfaceNames] = mlrGetSurfaceNames; end + surfaceNames = rightSurfaceNames; else - surfaceNames = leftSurfaceNames; - if isempty(surfaceNames) - [surfaceNames,~] = mlrGetSurfaceNames; + if isempty(leftSurfaceNames) + [leftSurfaceNames,~] = mlrGetSurfaceNames; end + surfaceNames = leftSurfaceNames; +end +if isempty(surfaceNames); + return; end % load the surface diff --git a/mrUtilities/File/FreeSurfer/mlrReadFreesurferLabel.m b/mrUtilities/File/FreeSurfer/mlrReadFreesurferLabel.m new file mode 100644 index 000000000..576d339cc --- /dev/null +++ b/mrUtilities/File/FreeSurfer/mlrReadFreesurferLabel.m @@ -0,0 +1,26 @@ +function [vertices,header] = mlrReadFreesurferLabel(filename) + +% open file +fileID = fopen(filename); +if fileID == -1 + mrErrorDlg(sprintf('(mlrReadFreesurferLabel) Could not open file %s',filename)); +end + +% get header +header = fgetl(fileID); + +% get number of vertices +nVertices = str2num(fgetl(fileID)); +% get each vertex row-by-row +for iVertex = 1:nVertices + nextLine = fgetl(fileID); + if ischar(nextLine) + vertices(iVertex,:) = str2num(nextLine); + else + fclose(fileID); + mrErrorDlg(sprintf('(mlrReadFreesurferLabel) There was an issue reading line %d of label file %s',iVertex,filename)); + end +end + +% close file +fclose(fileID); \ No newline at end of file diff --git a/mrUtilities/File/FreeSurfer/mlrWriteFreesurferLabel.m b/mrUtilities/File/FreeSurfer/mlrWriteFreesurferLabel.m new file mode 100644 index 000000000..d36c8fe8e --- /dev/null +++ b/mrUtilities/File/FreeSurfer/mlrWriteFreesurferLabel.m @@ -0,0 +1,13 @@ +function mlrWriteFreesurferLabel(filename,header,vertices) + +% open file + fileID = fopen(filename,'w'); +if fileID == -1 + mrErrorDlg(sprintf('(mlrReadFreesurferLabel) Could not open file %s for writing',filename)); +end + +fprintf(fileID,header); +fprintf(fileID,'%d\n', size(vertices,1)); +fprintf(fileID,'%d %.3f %.3f %.3f %.10f\n', vertices'); +fclose(fileID); + diff --git a/mrUtilities/File/Nifti/cbiCreateNiftiHeader.m b/mrUtilities/File/Nifti/cbiCreateNiftiHeader.m index ec86eb396..4eca3e143 100644 --- a/mrUtilities/File/Nifti/cbiCreateNiftiHeader.m +++ b/mrUtilities/File/Nifti/cbiCreateNiftiHeader.m @@ -89,7 +89,7 @@ if ~isempty(findstr('sform44',varargin{currarg})) use_sform=1; end - if (~isempty(findstr('quatern',varargin{currarg})) | ~isempty(findstr('qoffset',varargin{currarg}))) + if (~isempty(findstr('quatern',varargin{currarg})) || ~isempty(findstr('qoffset',varargin{currarg}))) use_quatern=1; end if ~isempty(findstr('qform44',varargin{currarg})) @@ -112,10 +112,10 @@ use_srow=0; end - if (~use_qform & ~use_quatern) + if (~use_qform && ~use_quatern) use_qform=1; end - if (~use_sform & ~use_srow) + if (~use_sform && ~use_srow) use_sform=1; end @@ -131,11 +131,13 @@ else hdr.slice_end=0; end - % Set pixdim fields, if not set - for n=2:l+1 - if (hdr.pixdim(n)==0) - hdr.pixdim(n)=1; - end + end + % make sure number of dimensions is consistent with non-zero/non-singleton dimensions + hdr.dim(1) = find(hdr.dim>1,1,'last')-1; + % Set pixdim fields, if not set + for n=2:hdr.dim(1)+1 + if (hdr.pixdim(n)==0) + hdr.pixdim(n)=1; end end @@ -145,7 +147,7 @@ pixdim=hdr.pixdims(2:4); qfac=hdr.pixdims(1); qoffs=[hdr.qoffset_x;hdr.qoffset_y;hdr.qoffset_z]; - if (length(quatern)==3 & length(qoffs)==3 & prod(pixdim)~=0) + if (length(quatern)==3 && length(qoffs)==3 && prod(pixdim)~=0) % Calculate qform44 hdr.qform44=cbiQuaternionToHomogeneous( quatern, pixdim, qfac, qoffset ); else @@ -161,7 +163,7 @@ end if (use_qform) % Make sure qform44 is valid - if (~isempty(hdr.qform44) & ~((hdr.qform_code==0) & isequal(hdr.qform44,eye(4))) & ~isequal(hdr.qform44,zeros(4,4)) & size(hdr.qform44)==[4,4]) + if (~isempty(hdr.qform44) && ~((hdr.qform_code==0) && isequal(hdr.qform44,eye(4))) && ~isequal(hdr.qform44,zeros(4,4)) && all(size(hdr.qform44)==[4,4])) hdr=cbiSetNiftiQform( hdr, hdr.qform44 ); else % Else set all to null values @@ -176,7 +178,7 @@ end if (use_srow) % Make sure srows are valid - if (length(hdr.srow_x)==4 & length(hdr.srow_y)==4 & length(hdr.srow_z)==4 ) + if (length(hdr.srow_x)==4 && length(hdr.srow_y)==4 && length(hdr.srow_z)==4 ) % Calculate sform44 hdr.sform44=eye(4); hdr.sform44(1,:)=hdr.srow_x; @@ -192,7 +194,7 @@ end if (use_sform) % Make sure sform44 are valid - if (~isempty(hdr.sform44) & ~((hdr.sform_code==0) & isequal(hdr.sform44,eye(4))) & ~isequal(hdr.sform44,zeros(4)) & size(hdr.sform44)==[4,4]) + if (~isempty(hdr.sform44) && ~((hdr.sform_code==0) && isequal(hdr.sform44,eye(4))) && ~isequal(hdr.sform44,zeros(4)) && all(size(hdr.sform44)==[4,4])) % Calculate srows hdr.srow_x=hdr.sform44(1,:); hdr.srow_y=hdr.sform44(2,:); diff --git a/mrUtilities/File/Nifti/cbiReadNifti.m b/mrUtilities/File/Nifti/cbiReadNifti.m index 1f378e469..2b127a9be 100644 --- a/mrUtilities/File/Nifti/cbiReadNifti.m +++ b/mrUtilities/File/Nifti/cbiReadNifti.m @@ -196,14 +196,33 @@ else % Use fseek to load segments into array volSize=prod(headerdim(1:3)); - % Initialize data array - if ( prod(loadSize(1:3))>1 & (loadSize(1)1 && (loadSize(1) offset to seek after every read + readOffset=volSize-readSize; + if (strfind(hdr.matlab_datatype,'complex')) + % Every voxel corresponds to two elements in the file + readOrigin=readOrigin*2; + readSize=readSize*2; + readOffset=readOffset*2; + end + % Position file at first voxel + fseek(fPtr,readOrigin*bytesPerElement,'cof'); + if ( prod(loadSize(1:3))>1 && (loadSize(1) offset to seek after every read - readOffset=volSize-readSize; + data=zeros(prod(loadSize),1); % Current position in data array currPos=1; - if (strfind(hdr.matlab_datatype,'complex')) - % Every voxel corresponds to two elements in the file - readOrigin=readOrigin*2; - readSize=readSize*2; - readOffset=readOffset*2; - end - % Position file at first voxel - fseek(fPtr,readOrigin*bytesPerElement,'cof'); for t=subset{4}(1):subset{4}(2) % Read one temporal chunk of data [d,count]=fread(fPtr,readSize,readformat); diff --git a/mrUtilities/File/Nifti/cbiWriteNifti.m b/mrUtilities/File/Nifti/cbiWriteNifti.m index 25534c297..222c1d3de 100644 --- a/mrUtilities/File/Nifti/cbiWriteNifti.m +++ b/mrUtilities/File/Nifti/cbiWriteNifti.m @@ -11,8 +11,9 @@ % Should be a recognized Matlab (or nifti) data type string. % subset: 4x1 cell array describing image subset to save. 1-offset (Matlab-style). % Only the following options are supported: -% - to save a single z-slice (e.g. 4): subset={[],[],4,[]} -% - to save a single/multiple volumes of a time series (e.g. volumes 2-9): subset={[],[],[],[2 9]} +% - to save a single z-slice, e.g. 4: subset={[],[],4,[]} +% - to save a single/multiple volumes of a time series, e.g. volumes 2-9: subset={[],[],[],[2 9]} +% - to append a single/multiple volumes to a time series, e.g. append 3 volumes to a 9-volume file: subset={[],[],[],[10 12]} % short_nan: NaN handling for signed short (int16) data. If 1, will treat save NaN's as -32768 (smallest % representable number) reserving -32767..32767 for scaled data; otherwise will save NaN as 0. % Default is 1 (use -32768 as NaN). @@ -82,14 +83,50 @@ mrErrorDlg('Not a valid NIFTI-1 file name extension. Legal values are .nii, .hdr, .img'); end +% deal with subset and append options +[dataSize(1),dataSize(2),dataSize(3),dataSize(4)] = size(data); %size of the new data to save +subsetIndices = zeros(4,2); +for n=1:4 + if (length(subset{n})==1) + subsetIndices(n,:)=[subset{n} subset{n}]; + elseif (length(subset{n})>2) + mrErrorDlg('subset should be a scalar or 2-vector'); + elseif (isempty(subset{n})) + subsetIndices(n,:)=[1 dataSize(n)]; + else + subsetIndices(n,:) = subset{n}; + end +end +subsetSize = diff(subsetIndices,1,2)+1; +if any(subsetSize'~=dataSize) + mrErrorDlg(sprintf('(cbiWriteNifti) Mistmatch between data and subset dimensions (%s vs %s)',mat2str(dataSize),mat2str(subsetSize'))); +end + +if ~isequal(subsetIndices,[ones(4,1) dataSize']) % if writing a subset or appending volumes, need to check header of existing file + hdrDestination = cbiReadNiftiHeader(fname); + if isempty(hdrDestination) + mrErrorDlg(sprintf('(cbiWriteNifti) Cannot read %s',fname)); + end + hdrDestDim = hdrDestination.dim(2:5); + hdrDestDim(hdrDestDim==0)=1; + if ~isequal(subsetIndices(1:2,:),[ones(2,1) hdrDestDim(1:2)]) + mrErrorDlg('no support for saving subvolumes of data on x and y dimensions; only entire z-slices or volumes may be saved.'); + elseif subsetIndices(3,2)>hdrDestDim(3) + mrErrorDlg('z subset index larger than file image dimensions!'); + elseif subsetIndices(4,2)>hdrDestDim(4) && subsetIndices(4,1)-hdrDestDim(4)~=1 + mrErrorDlg('(cbiWriteNifti) When appending data on the 4th dimension, the first frame index must be exactly adjacent to the last frame in the file'); + end +end + % Ensure header matches data +hdr.dim(2:5) = max(dataSize',subsetIndices(:,2)); if ~ieNotDefined('prec') - hdr=cbiCreateNiftiHeader(hdr,data,'matlab_datatype',prec); + hdr=cbiCreateNiftiHeader(hdr,'matlab_datatype',prec); else - hdr=cbiCreateNiftiHeader(hdr,data); + hdr=cbiCreateNiftiHeader(hdr); end if (~strcmp(class(data),hdr.matlab_datatype)) - if verbose, disp(['(cbiWriteNifti) Scaling data from ' class(data) ' to ' hdr.matlab_datatype]);end; + if verbose, disp(['(cbiWriteNifti) Scaling data from ' class(data) ' to ' hdr.matlab_datatype]);end % disp('To avoid this, cast data to desired format before calling cbiWriteNifti, e.g.') % disp('cbiWriteNifti(''myfilename'',int16(data),hdr,''int16'')') end @@ -111,63 +148,16 @@ no_overwrite=0; [hdr,fid]=cbiWriteNiftiHeader(hdr,fname,no_overwrite,hdr.single_file); -% Prepare to write data -if (~hdr.single_file) - fid=fopen(hdr.img_name,'wb',hdr.endian); - if fid == -1,mrErrorDlg(sprintf('(cbiWriteNiftiHeader) Could not open file %s',fname));end -end - headerdim=hdr.dim(2:5); % Matlab 1-offset - hdr.dim(1) is actually hdr.dim(0) headerdim(headerdim==0)=1; % Force null dimensions to be 1 -is5D=0; if (hdr.dim(6)>1) if (hdr.dim(5)>1) mrErrorDlg('No support for 5D data with multiple time points!'); end - is5D=1; headerdim(4)=hdr.dim(6); - if verbose, disp('5D data set detected');end; -end - -loadSize=zeros(4,1); -for n=1:4 - if (length(subset{n})==1) - subset{n}=[subset{n} subset{n}]; - elseif (length(subset{n})>2) - mrErrorDlg('subset should be a scalar or 2-vector'); - end - if (isempty(subset{n})) - loadSize(n)=headerdim(n); - subset{n}=[1 loadSize(n)]; - else - loadSize(n)=subset{n}(2)-subset{n}(1)+1; - end + if verbose, disp('5D data set detected');end end -if (any(loadSize>headerdim(1:4))) - mrErrorDlg('subset index larger than image dimensions!'); -elseif (any(loadSize(1:2) offset to seek after every read -readOffset=volSize-readSize; -% Current position in data array -currPos=1; -if (strfind(hdr.matlab_datatype,'complex')) - % Every voxel corresponds to two elements in the file - readOrigin=readOrigin*2; - readSize=readSize*2; - readOffset=readOffset*2; +% Prepare to write data +if (~hdr.single_file) + if subsetIndices(4,1)>headerdim(4) + permission = 'a'; + else + permission = 'wb'; + end + fid=fopen(hdr.img_name,permission,hdr.endian); + if fid == -1,mrErrorDlg(sprintf('(cbiWriteNiftiHeader) Could not open file %s',fname));end end -% Position file at first voxel -byteswritten=0; -fseek(fid,readOrigin*bytesPerElement,'cof'); -for t=subset{4}(1):subset{4}(2) - % Extract current subset of data - saveData=data(currPos:currPos+readSize-1); - if (strfind(hdr.matlab_datatype,'complex')) - % Separate complex data into real and imaginary parts - realData=real(saveData); - imagData=imag(saveData); - saveData=zeros(2*prod(size(saveData)),1); - saveData(1:2:readSize/2-1)=realData; - saveData(2:2:readSize/2)=imagData; + +try + % Write emptiness so that we can move to the right offset. (This library does not currently support extensions, which would otherwise go here) + % 11/10/05 PJ + if ftell(fid) < hdr.vox_offset + % fwrite(fid,0,sprintf('integer*%d',(hdr.vox_offset-ftell(fid)))); + c=hdr.vox_offset-ftell(fid); + if (fwrite(fid,zeros(c,1),'uint8')~=c) + mrErrorDlg('error writing extension padding') + end end - % Write converted subset to file - count=fwrite(fid,saveData,writeFormat); - byteswritten=byteswritten+count; - if (count~=readSize) - fclose(fid); - mrErrorDlg(['Error writing to file ' hdr.img_name]); + % Move to beginning of data + status = fseek(fid,hdr.vox_offset,'bof'); + if status + ferror(fid) end - if (readOffset>0) - % fseek to next time point - fseek(fid,readOffset*bytesPerElement,'cof'); + + % Move to correct location + readOrigin=sub2ind([headerdim(1:3)' max(headerdim(4),subsetIndices(4,2))],subsetIndices(1,1),subsetIndices(2,1),subsetIndices(3,1),subsetIndices(4,1))-1; % now we're in C-land, hence 0-offset + + % Elements to write every time point + volSize=prod(headerdim(1:3)); + writeSize=prod(diff(subsetIndices(1:3,:),1,2)+1); + % Difference between volSize and readSize => offset to seek after every write + readOffset=volSize-writeSize; + % Current position in data array + currPos=1; + if (strfind(hdr.matlab_datatype,'complex')) + % Every voxel corresponds to two elements in the file + readOrigin=readOrigin*2; + writeSize=writeSize*2; + readOffset=readOffset*2; end - currPos=currPos+readSize; -end + % Position file at first voxel + byteswritten=0; + fseek(fid,readOrigin*bytesPerElement,'cof'); + for t=subsetIndices(4,1):subsetIndices(4,2) + % Extract current subset of data + saveData=data(currPos:currPos+writeSize-1); + if (strfind(hdr.matlab_datatype,'complex')) + % Separate complex data into real and imaginary parts + realData=real(saveData); + imagData=imag(saveData); + saveData=zeros(2*numel(saveData),1); + saveData(1:2:writeSize/2-1)=realData; + saveData(2:2:writeSize/2)=imagData; + end + % Write converted subset to file + count=fwrite(fid,saveData,writeFormat); + byteswritten=byteswritten+count; + if (count~=writeSize) + fclose(fid); + mrErrorDlg(['Error writing to file ' hdr.img_name]); + end + if (readOffset>0) + % fseek to next time point + fseek(fid,readOffset*bytesPerElement,'cof'); + end + currPos=currPos+writeSize; + end +catch exception + %if anything went wrong, we still want to close the file + fclose(fid); + rethrow(exception); +end fclose(fid); return -function [data,hdr]=convertData(data,hdr,short_nan); +function [data,hdr]=convertData(data,hdr,short_nan) % Scales and shifts data (using hdr.scl_slope and hdr.scl_inter) % and changes NaN's to 0 or MAXINT for non-floating point formats % Returns hdr with scale factor changed (bug fixed 20060824) @@ -268,8 +288,8 @@ end % Scale and shift data if scale factor is nonzero - if (~isnan(hdr.scl_slope) & hdr.scl_slope~=0) - if (hdr.scl_slope~=1 | hdr.scl_inter~=0) + if (~isnan(hdr.scl_slope) && hdr.scl_slope~=0) + if (hdr.scl_slope~=1 || hdr.scl_inter~=0) data=double(data); data=(data-hdr.scl_inter)./hdr.scl_slope; switch (hdr.matlab_datatype) diff --git a/mrUtilities/File/Nifti/copyNiftiFile.m b/mrUtilities/File/Nifti/copyNiftiFile.m index a6cd6a7b0..e34963706 100644 --- a/mrUtilities/File/Nifti/copyNiftiFile.m +++ b/mrUtilities/File/Nifti/copyNiftiFile.m @@ -8,20 +8,22 @@ % checks for file existence. If makeLink is set to 1, will % link the files rather than copy them. makeLink set to 2 will make a hard link. % If there is an associated .mat file (i.e. same name) that will be copied as well +% Set overwrite to 1 to overwrite existing files without asking % -function success = copyNiftiFile(fromFilename,toFilename,makeLink) +function success = copyNiftiFile(fromFilename,toFilename,makeLink,overwrite) % set initial return value success = 0; % check arguments -if ~any(nargin == [2 3]) +if ~any(nargin == [2 3 4]) help copyNiftiFile return end % default to copy if ieNotDefined('makeLink'),makeLink = 0;end +if ieNotDefined('overwrite'),overwrite = 0;end % get calling function name [st,i] = dbstack; @@ -55,7 +57,11 @@ thisFromFilename = sprintf('%s.%s',stripext(fromFilename),ext); thisToFilename = sprintf('%s.%s',stripext(toFilename),ext); % check if toFile exists - r = 0; + if overwrite + r = inf; + else + r = 0; + end if (extensionNum == 1) && mlrIsFile(thisToFilename) if ~isinf(r) r = askuser(sprintf('(%s) File %s already exists, overwrite',callingFunction,getLastDir(toFilename)),1); @@ -64,12 +70,14 @@ return end end - if makeLink - disp(sprintf('(%s) Linking file %s to %s',callingFunction,thisFromFilename,thisToFilename)); - linkFile(thisFromFilename,thisToFilename,makeLink); - else - disp(sprintf('(%s) Copying file %s to %s',callingFunction,thisFromFilename,thisToFilename)); - copyfile(thisFromFilename,thisToFilename); + if ~isequal(thisFromFilename,thisToFilename) + if makeLink + disp(sprintf('(%s) Linking file %s to %s',callingFunction,thisFromFilename,thisToFilename)); + linkFile(thisFromFilename,thisToFilename,makeLink); + else + disp(sprintf('(%s) Copying file %s to %s',callingFunction,thisFromFilename,thisToFilename)); + copyfile(thisFromFilename,thisToFilename); + end end end success = 1; diff --git a/mrUtilities/File/Nifti/mlrXFormFromHeader.m b/mrUtilities/File/Nifti/mlrXFormFromHeader.m index 6e7228114..8862f3cbb 100644 --- a/mrUtilities/File/Nifti/mlrXFormFromHeader.m +++ b/mrUtilities/File/Nifti/mlrXFormFromHeader.m @@ -28,6 +28,10 @@ % Note, that one change was made from Jonas' original code, in that % filename can be a passed in hdr -jlg % +% Note (JB, 25/07/2020): this function assumes that the data are in LPI orientation, which might +% not be the case for volumes outside of mrLoadRet. So it should only be used with conversion type +% 'array2world' or 'world2array' (to convert surfRelax coordinates to mrLoadRet volumes coordinates). +% For any other use, see shiftOriginXform, which correctly takes into account the actual orientation. if (nargin<2) help(mfilename) @@ -49,7 +53,7 @@ case {'array2nifti', 'a2n', 'array2qform', 'a2q'} xform = hdr.qform44; % Add -1 for Matlab 1-offset - xform(1:3,4)=xform(1:3,4) - 1; + xform(1:3,4)=xform(1:3,4) - 1; % this assumes LPI orientation case {'world2array', 'w2a'} xform = inv(array2world(hdr)); @@ -57,18 +61,18 @@ case {'nifti2array', 'n2a', 'qform2array', 'q2a'} xform = hdr.qform44; % Add -1 for Matlab 1-offset - xform(1:3,4)=xform(1:3,4) - 1; + xform(1:3,4)=xform(1:3,4) - 1; % this assumes LPI orientation xform=inv(xform); case {'array2sform', 'a2s'} xform = hdr.sform44; % Add -1 for Matlab 1-offset - xform(1:3,4)=xform(1:3,4) - 1; + xform(1:3,4)=xform(1:3,4) - 1; % this assumes LPI orientation case {'sform2array', 's2a'} xform = hdr.sform44; % Add -1 for Matlab 1-offset - xform(1:3,4)=xform(1:3,4) - 1; + xform(1:3,4)=xform(1:3,4) - 1; % this assumes LPI orientation xform = inv(xform); otherwise diff --git a/mrUtilities/File/SPM/mlrImportSPMnormalization.m b/mrUtilities/File/SPM/mlrImportSPMnormalization.m new file mode 100644 index 000000000..d347ff9cb --- /dev/null +++ b/mrUtilities/File/SPM/mlrImportSPMnormalization.m @@ -0,0 +1,89 @@ +% function mlrImportSPMnormalization(studyDir,subjectNames,overwrite) +% +% Imports MNI non-linear registration information into mrLoadRet session (mrSession.mat) so it can be used +% in mrLoadRet (e.g. to display MNI coordinates). This function can be run for group of participants (mrLoadRet +% sessions) within a given study folder. It assumes that the non-linear registration was computed using SPM12 +% and that the linear and non-linear transforms were saved as NIFTI images/headers in folder "SPMnormalize" +% within each participant's mrLoadRet Folder. +% +% Inputs: - studyDir: path of study folder containg participant's data. Each participant's data folder +% is assumed to have an mrLoadRet folder structure +% - subjectNames: name of all participants' folder (cell array of strings) +% - overwrite: whether to overwrite any existing MNI info for each participant (default: false) +% + +function mlrImportSPMnormalization(studyDir,subjectNames,overwrite) + +if ~ismember(nargin,[0 2 3]) + help('mlrImportSPMnormalization'); + return +end + +if ieNotDefined('overwrite') + overwrite = false; +end + +if ieNotDefined('studyDir') || ieNotDefined('subjectNames') % if the function is called without input, use the current folder + [studyDir,subjectNames] = fileparts(pwd); +end + +if ischar(subjectNames) + subjectNames = {subjectNames}; +end + +cwd = pwd; + +for subject = subjectNames + + fprintf(sprintf('(mlrImportSPMnormalization) Importing MNI normalization info for %s...\n',subject{1})); + + cd(fullfile(studyDir,subject{1})); + + thisView = newView; % create a new view (no need to load the currently save view, and this is much faster) + if isempty(thisView) + mrWarnDlg('(mlrImportSPMnormalization) No mrSession.mat file found. Are you sure this folder is an mrLoadRet participant/session folder?'); + continue + end + + mniInfo = viewGet(thisView,'mniInfo'); + if ~isempty(mniInfo) + mrWarnDlg('(mlrImportSPMnormalization) MNI non-linear registration is already defined for this participant.'); + if overwrite + mrWarnDlg('(mlrImportSPMnormalization) MNI info will be overwritten.'); + else + mrWarnDlg('(mlrImportSPMnormalization) Set ''overwrite'' input variable to ''true'' to overwrite.'); + mrQuit(0,thisView); + continue; + end + end + + if exist(fullfile(studyDir,subject{1},'SPMnormalize'),'dir') + mniCoordMapFilename = dir(fullfile(studyDir,subject{1},'SPMnormalize')); + mniCoordMapFilename = {mniCoordMapFilename(~cellfun(@isempty,regexp({mniCoordMapFilename.name},'^iy_'))).name}; % find all filenames starting with 'iy_', which is the default name for deformation fields from T1w to MNI output by SPM12 + end + + if ~exist(fullfile(studyDir,subject{1},'SPMnormalize'),'dir') || isempty(mniCoordMapFilename) % deformation fields from MNI to T1w + mrWarnDlg(sprintf('(mlrImportSPMnormalization) There is no MNI normalization deformation map (SPMnormalize/y_*.nii) for this subject (%s). You first need to run mlrSPMnormalization',subject{1})); + mrQuit(0,thisView); + continue + elseif numel(mniCoordMapFilename)>2 + keyboard; % there are more than one deformation maps. Think what to do + end + + %read deformation coord map + mniInfo = struct(); + [mniInfo.T1w2mniCoordMap,hdrToMNI] = mlrImageReadNifti(fullfile(studyDir,subject{1},'SPMnormalize',mniCoordMapFilename{1})); % T1w2mniCoordMap is the (non-linear) deformation coordinates map going from the T1w volume coordinates to MNI coordinates + mniInfo.mag2T1w = inv(hdrToMNI.sform44 * shiftOriginXform); % mag2T1w is the (linear) transformation matrix from magnet coordinates to T1w volume coordinates (this should be applied before the non-linear transform to go from magnet to MNI coordinates) + if exist(fullfile(studyDir,subject{1},'SPMnormalize',mniCoordMapFilename{1}(2:end)), 'file') + [mniInfo.mnivol2magCoordMap,hdrFromMNI] = mlrImageReadNifti(fullfile(studyDir,subject{1},'SPMnormalize',mniCoordMapFilename{1}(2:end))); % mnivol2magCoordMap is the inverse (non-linear) deformation coordinates map going from MNI volume coordinates to magnet coordinates + mniInfo.mni2mnivol = inv(hdrFromMNI.sform44 * shiftOriginXform); % mni2mnivol is the (linear) transformation matrix from MNI coordinates to the volume coordinates of the inverse deformation coordinates map (this should be applied before the non-linear transform to go from MNI to magnet coordinates) + end + + viewSet(thisView,'mniInfo',mniInfo); + saveSession; % save mrSession.nat, because this is where we store mniInfo + mrQuit(0,thisView); % quit mrLoadRet without saveing the view: this removes MLR from the global scope without modifying the view currently saved in mrLastView.mat + disp(['(mlrImportSPMnormalization) Imported MNI registration info for ', subject{1}]) % add a print statement saying which subject has been processed + +end + +cd(cwd); diff --git a/mrUtilities/File/SPM/mlrSegmentNormalizeSPM12.m b/mrUtilities/File/SPM/mlrSegmentNormalizeSPM12.m new file mode 100644 index 000000000..0e5f56151 --- /dev/null +++ b/mrUtilities/File/SPM/mlrSegmentNormalizeSPM12.m @@ -0,0 +1,131 @@ +% function mlrSegmentNormalizeSPM12(studyDir,subjectNames,T1regexp) +% +% Runs SPM12 segmentation and normalization preprocessing on T1-weighted volumes. For each participant, +% the T1-weighted volume is assumed to be located in the "Anatomy" folder of an mrLoadRet folder structure +% [Not sure how standard this is, maybe the freesurfer subject folder should be used by default]. +% The T1-weighted volume is segmented and normalized to the MNI152 average brain. The output (including +% the segmented T1, the forward and inverse deformation fields and the normalized T1) is placed in a new +% subfolder "SPMnormalize" in each participant's folder. +% +% Inputs: - studyDir: path of study folder containg participant's data. Each participant's data folder +% is assumed to have an mrLoadRet folder structure +% - subjectNames: name of all participants' folder (cell array of strings) +% - T1baseName: regular expression uniquely identifying each participant's T1-weigthed NIFTI file name +% across all participants, each assumed to be located in the participant's Anatomy subfolder +% + +function mlrSegmentNormalizeSPM12(studyDir,subjectNames,T1regexp) + +if nargin ~= 3 + help("mlrSegmentNormalizeSPM12"); + return; +end + +if ischar(subjectNames) + subjectNames = {subjectNames}; +end + +% check that SPM is installed +spmInstallPath = which('spm'); +if isempty(spmInstallPath) + mrErrorDlg('(mlrSegmentNormalizeSPM12) SPM is not installed or has not been added to Matlab''s path'); +else + spmInstallPath = fileparts(spmInstallPath); + SPMversion = regexp(spmInstallPath, '\w+$', 'match', 'once'); + if ~strcmp(SPMversion,'spm12') + mrWarnDlg(sprintf('(mlrSegmentNormalizeSPM12) This function has only been tested with SPM 12. You are using %s',SPMversion)); + end +end + +% get full path of study folder because SPM does not deal well with ~ expansion +cwd = pwd; % easiest way to do this is to cd and pwd +cd(studyDir); +studyDir = pwd; +cd(cwd); % go back to current folder + +% this loop will perform preprocessing steps for all subjects specified in the subjectNames list +startTime = tic; +for subject = subjectNames + + fprintf('\nStarting segmentation/normalization for %s', subject{1}) % add a print statement to tell you which subject is being processed + + anatDir = fullfile(studyDir, subject{1}, 'Anatomy'); % this combines the root with a specific subject directory to create the full path to the folder containing anatomical data + % find the T1-weighted WH volume and make sure it's unique + anatFileName = dir(anatDir); + anatFileName = {anatFileName(~cellfun(@isempty,regexp({anatFileName.name},T1regexp))).name}; +% anatFileName = spm_select('List', anatDir, T1regexp) % not using spm_select because it cannot deal with a ~ in the path (although I'm now forcing the path to be fully expanded) + if numel(anatFileName) > 1 + mrWarnDlg('(mlrSegmentNormalizeSPM12) Non-unique T1-weigthed file in Anatomy folder. Skipping this participant'); + continue + end + + % copy T1w into new folder + normDir = fullfile(studyDir, subject{1}, 'SPMnormalize'); % this combines the root with a specific subject directory to create the full path to the folder containing anatomical data + if ~exist(normDir,'dir') + mkdir(normDir); + end + copyfile(fullfile(anatDir,anatFileName{1}),normDir); + anatFilePath = fullfile(normDir,anatFileName{1}); + + % the following was created using the SPM batch processing GUI, saving the batch as a script (_job.m file) + % and modified to work in this loop and be installation independent + matlabbatch{1}.spm.spatial.preproc.channel.vols = cellstr(anatFilePath); % modified to use the current participant's T1-weighted as input + matlabbatch{1}.spm.spatial.preproc.channel.biasreg = 0.001; + matlabbatch{1}.spm.spatial.preproc.channel.biasfwhm = 60; + matlabbatch{1}.spm.spatial.preproc.channel.write = [0 0]; + matlabbatch{1}.spm.spatial.preproc.tissue(1).tpm = {[spmInstallPath '/tpm/TPM.nii,1']}; % modified to use the current machine's SPM installation + matlabbatch{1}.spm.spatial.preproc.tissue(1).ngaus = 1; + matlabbatch{1}.spm.spatial.preproc.tissue(1).native = [1 0]; + matlabbatch{1}.spm.spatial.preproc.tissue(1).warped = [0 0]; + matlabbatch{1}.spm.spatial.preproc.tissue(2).tpm = {[spmInstallPath '/tpm/TPM.nii,2']}; % modified to use the current machine's SPM installation + matlabbatch{1}.spm.spatial.preproc.tissue(2).ngaus = 1; + matlabbatch{1}.spm.spatial.preproc.tissue(2).native = [1 0]; + matlabbatch{1}.spm.spatial.preproc.tissue(2).warped = [0 0]; + matlabbatch{1}.spm.spatial.preproc.tissue(3).tpm = {[spmInstallPath '/tpm/TPM.nii,3']}; % modified to use the current machine's SPM installation + matlabbatch{1}.spm.spatial.preproc.tissue(3).ngaus = 2; + matlabbatch{1}.spm.spatial.preproc.tissue(3).native = [1 0]; + matlabbatch{1}.spm.spatial.preproc.tissue(3).warped = [0 0]; + matlabbatch{1}.spm.spatial.preproc.tissue(4).tpm = {[spmInstallPath '/tpm/TPM.nii,4']}; % modified to use the current machine's SPM installation + matlabbatch{1}.spm.spatial.preproc.tissue(4).ngaus = 3; + matlabbatch{1}.spm.spatial.preproc.tissue(4).native = [1 0]; + matlabbatch{1}.spm.spatial.preproc.tissue(4).warped = [0 0]; + matlabbatch{1}.spm.spatial.preproc.tissue(5).tpm = {[spmInstallPath '/tpm/TPM.nii,5']}; % modified to use the current machine's SPM installation + matlabbatch{1}.spm.spatial.preproc.tissue(5).ngaus = 4; + matlabbatch{1}.spm.spatial.preproc.tissue(5).native = [1 0]; + matlabbatch{1}.spm.spatial.preproc.tissue(5).warped = [0 0]; + matlabbatch{1}.spm.spatial.preproc.tissue(6).tpm = {[spmInstallPath '/tpm/TPM.nii,6']}; % modified to use the current machine's SPM installation + matlabbatch{1}.spm.spatial.preproc.tissue(6).ngaus = 2; + matlabbatch{1}.spm.spatial.preproc.tissue(6).native = [0 0]; + matlabbatch{1}.spm.spatial.preproc.tissue(6).warped = [0 0]; + matlabbatch{1}.spm.spatial.preproc.warp.mrf = 1; + matlabbatch{1}.spm.spatial.preproc.warp.cleanup = 1; + matlabbatch{1}.spm.spatial.preproc.warp.reg = [0 0.001 0.5 0.05 0.2]; + matlabbatch{1}.spm.spatial.preproc.warp.affreg = 'mni'; + matlabbatch{1}.spm.spatial.preproc.warp.fwhm = 0; + matlabbatch{1}.spm.spatial.preproc.warp.samp = 3; + matlabbatch{1}.spm.spatial.preproc.warp.write = [1 1]; + matlabbatch{1}.spm.spatial.preproc.warp.vox = NaN; + matlabbatch{1}.spm.spatial.preproc.warp.bb = [NaN NaN NaN + NaN NaN NaN]; + matlabbatch{2}.spm.spatial.normalise.write.subj.def(1) = cfg_dep('Segment: Forward Deformations', substruct('.','val', '{}',{1}, '.','val', '{}',{1}, '.','val', '{}',{1}), substruct('.','fordef', '()',{':'})); + matlabbatch{2}.spm.spatial.normalise.write.subj.resample = cellstr(anatFilePath); % modified to use the current participant's T1-weighted as base name for output + matlabbatch{2}.spm.spatial.normalise.write.woptions.bb = [-78 -112 -70 + 78 76 85]; + matlabbatch{2}.spm.spatial.normalise.write.woptions.vox = [1 1 1]; + matlabbatch{2}.spm.spatial.normalise.write.woptions.interp = 4; + matlabbatch{2}.spm.spatial.normalise.write.woptions.prefix = 'w'; + +% save preprocessing_batch matlabbatch % save the setup into a matfile called preprocessing_batch.mat + spm_jobman('run',matlabbatch) % execute the batch + clear matlabbatch % clear matlabbatch + + % delete original T1w file + delete(anatFilePath) + + disp(['Completed preprocessing for ', subject{1}]) % add a print statement telling you which subject has been processed + + +end + +fprintf('\n') +toc(startTime) diff --git a/mrUtilities/File/Varian/fid2nifti.m b/mrUtilities/File/Varian/fid2nifti.m index ebbccdbe0..3beaadc9a 100644 --- a/mrUtilities/File/Varian/fid2nifti.m +++ b/mrUtilities/File/Varian/fid2nifti.m @@ -159,7 +159,7 @@ disp(sprintf('(fid2nifti) Num receivers (%i) does not match data dim (%i)',numReceivers,fid.dim(end))); end % display what we are doing - if verbose,disppercent(-inf,sprintf('(fid2nifti) Taking sum of squares of %i coils',numReceivers));end + if verbose,mlrDispPercent(-inf,sprintf('(fid2nifti) Taking sum of squares of %i coils',numReceivers));end % merge the coils for volNum = 1:size(fid.data,4) sumOfSquares = zeros(fid.dim(1:3)); @@ -167,9 +167,9 @@ sumOfSquares = sumOfSquares+fid.data(:,:,:,volNum,receiverNum).^2; end data(:,:,:,volNum) = sqrt(sumOfSquares); - if verbose,disppercent(volNum/size(fid.data,4));end + if verbose,mlrDispPercent(volNum/size(fid.data,4));end end - if verbose,disppercent(inf);end + if verbose,mlrDispPercent(inf);end fid.data = data; fid.dim = size(data); end diff --git a/mrUtilities/File/Varian/getfid.m b/mrUtilities/File/Varian/getfid.m index 4ad7b8ec3..ce6ec7f2b 100644 --- a/mrUtilities/File/Varian/getfid.m +++ b/mrUtilities/File/Varian/getfid.m @@ -46,9 +46,9 @@ getArgs(varargin,{'verbose=0','zeropad=0','movepro=0','kspace=0','swapReceiversAndSlices=1','movepss=0'}); % read the k-space data from the fid -if (verbose),disppercent(-inf,sprintf('(getfid) Reading %s...',fidname));end +if (verbose),mlrDispPercent(-inf,sprintf('(getfid) Reading %s...',fidname));end d = getfidk(fidname,'verbose',verbose); -if (verbose),disppercent(inf,sprintf('done.\n',fidname));end +if (verbose),mlrDispPercent(inf,sprintf('done.\n',fidname));end % if it is empty then something has failed if (isempty(d.data)) return @@ -105,7 +105,7 @@ end % everything is ok, then transform data -if(verbose),disppercent(-inf,'(getfid) Transforming data');end +if(verbose),mlrDispPercent(-inf,'(getfid) Transforming data');end % preallocate space for data if zeropad @@ -143,7 +143,7 @@ end end % percent done - if (verbose) disppercent(calcPercentDone(i,size(d.data,3),j,size(d.data,4)));end + if (verbose) mlrDispPercent(calcPercentDone(i,size(d.data,3),j,size(d.data,4)));end end end end @@ -159,7 +159,7 @@ d.info = info; -if (verbose), disppercent(inf); end +if (verbose), mlrDispPercent(inf); end %%%%%%%%%%%%%%% % myfft % diff --git a/mrUtilities/File/Varian/getfidk.m b/mrUtilities/File/Varian/getfidk.m index 1eda884a1..063dc07e8 100644 --- a/mrUtilities/File/Varian/getfidk.m +++ b/mrUtilities/File/Varian/getfidk.m @@ -96,7 +96,7 @@ % read the data from fid block structure kNum = 1;clear i; d.data = nan(info.dim(1),numPhaseEncodeLines,numSlices,numReceivers,numVolumes); -if verbose,disppercent(-inf,'(getfidk) Reordering data');end +if verbose,mlrDispPercent(-inf,'(getfidk) Reordering data');end if info.compressedFid % if intlv is set to y then it means that shots are interleaved - i.e. a shot is taken on % each slice and then you come back. Thus each block contains the data from all slices @@ -134,7 +134,7 @@ d.data(:,lineorder((shotNum-1)*etl+1:shotNum*etl),:,receiverNum,volNum) = reshape(blockData((shotNum-1)*subblockSize+1:shotNum*subblockSize),info.dim(1),etl,numSlices); end end - if verbose,disppercent(calcPercentDone(volNum,numVolumes,receiverNum,numReceivers));end + if verbose,mlrDispPercent(calcPercentDone(volNum,numVolumes,receiverNum,numReceivers));end end else % do processing for non itls compressed data @@ -147,7 +147,7 @@ kNum = kNum+1; end end - if verbose,disppercent(calcPercentDone(sliceNum,numSlices,receiverNum,numReceivers,volNum,numVolumes));end + if verbose,mlrDispPercent(calcPercentDone(sliceNum,numSlices,receiverNum,numReceivers,volNum,numVolumes));end end end else @@ -163,7 +163,7 @@ end end end - if verbose,disppercent(calcPercentDone(kLine,numPhaseEncodeLines,volNum,numVolumes,receiverNum,numReceivers,sliceNum,numSlices));end + if verbose,mlrDispPercent(calcPercentDone(kLine,numPhaseEncodeLines,volNum,numVolumes,receiverNum,numReceivers,sliceNum,numSlices));end end end @@ -171,5 +171,5 @@ d = rmfield(d,'real'); d = rmfield(d,'imag'); -if verbose,disppercent(inf);end +if verbose,mlrDispPercent(inf);end diff --git a/mrUtilities/File/mlrImage/mlrImageHeaderLoad.m b/mrUtilities/File/mlrImage/mlrImageHeaderLoad.m index 865910e5d..d329a993d 100644 --- a/mrUtilities/File/mlrImage/mlrImageHeaderLoad.m +++ b/mrUtilities/File/mlrImage/mlrImageHeaderLoad.m @@ -294,7 +294,11 @@ uncompressedExists = false; if ~mlrIsFile(uncompressedFilename) % uncompress the file first - system(sprintf('gunzip -c %s > %s',filename,uncompressedFilename)); + if ~ispc + system(sprintf('gunzip -c %s > %s',filename,uncompressedFilename)); + else + gunzip(filename); + end else % uncompressed file already exists, so no need to gunzip uncompressedExists = true; @@ -305,7 +309,11 @@ % remove uncompressed (but only if it wasn't preexistent) if ~uncompressedExists - system(sprintf('rm -f %s',uncompressedFilename)); + if ~ispc + system(sprintf('rm -f %s',uncompressedFilename)); + else + delete(uncompressedFilename) + end end % check that it was loaded properly diff --git a/mrUtilities/File/mlrImage/mlrImageHeaderSave.m b/mrUtilities/File/mlrImage/mlrImageHeaderSave.m index 7b9cd1836..8446692be 100644 --- a/mrUtilities/File/mlrImage/mlrImageHeaderSave.m +++ b/mrUtilities/File/mlrImage/mlrImageHeaderSave.m @@ -37,7 +37,11 @@ compressFile = true; % need to uncompress the file (so that we can just write the header) if mlrIsFile(filename) - system(sprintf('gunzip %s',filename)); + if ~ispc + system(sprintf('gunzip %s',filename)); + else + gunzip(filename); %might need to delete the compressed file + end end % strip off the gz filename = stripext(filename); @@ -74,7 +78,12 @@ % now compress if asked for if compressFile - system(sprintf('gzip %s',filename)); + if ~ispc + system(sprintf('gzip %s',filename)); + else + gzip(filename); + delete(filename); % delete the uncompressed file + end end % see if we need to save out a matlab extension diff --git a/mrUtilities/File/mlrImage/mlrImageLoad.m b/mrUtilities/File/mlrImage/mlrImageLoad.m index 5764506b3..3b4384444 100644 --- a/mrUtilities/File/mlrImage/mlrImageLoad.m +++ b/mrUtilities/File/mlrImage/mlrImageLoad.m @@ -131,7 +131,11 @@ uncompressedExists = false; if ~mlrIsFile(uncompressedFilename) % uncompress the file first - system(sprintf('gunzip -c %s > %s',filename,uncompressedFilename)); + if ~ispc + system(sprintf('gunzip -c %s > %s',filename,uncompressedFilename)); + else + gunzip(filename); + end else % uncompressed file already exists, so no need to gunzip uncompressedExists = true; @@ -141,7 +145,11 @@ volNum = []; % remove uncompressed (but only if it wasn't preexistent) if ~uncompressedExists - system(sprintf('rm -f %s',uncompressedFilename)); + if ~ispc + system(sprintf('rm -f %s',uncompressedFilename)); + else + delete(uncompressedFilename) + end end end case {'hdr','nii'} diff --git a/mrUtilities/File/mlrImage/mlrImageParseArgs.m b/mrUtilities/File/mlrImage/mlrImageParseArgs.m index 4c7e108d2..c65361235 100644 --- a/mrUtilities/File/mlrImage/mlrImageParseArgs.m +++ b/mrUtilities/File/mlrImage/mlrImageParseArgs.m @@ -131,7 +131,7 @@ [iArg imageArgs{end}.altArgs] = getOtherArgs(args,iArg,altArgs,imageArgs{end}.altArgs); end end - elseif isview(args{iArg}) + elseif isview(args{iArg},false) % if we have a view then collect any additional qualifying % arguments-look for scanNum and groupNum args that can get passed to getArgs jArg = iArg+1; @@ -191,6 +191,12 @@ iArg = iArg+1; end end + elseif iscell(args{iArg}) + % expand cell into args cell array + args = [args(1:iArg-1) args{iArg} args(iArg+1:end)]; + nArgs = length(args); + else + mrErrorDlg('(mlrImageParseArg) Unknown argument type)'); end end diff --git a/mrUtilities/File/mlrImage/mlrImageReslice.m b/mrUtilities/File/mlrImage/mlrImageReslice.m index 51e0ac90d..d9ea4cce3 100644 --- a/mrUtilities/File/mlrImage/mlrImageReslice.m +++ b/mrUtilities/File/mlrImage/mlrImageReslice.m @@ -59,13 +59,13 @@ end % load the images -disppercent(-inf,'Loading images'); +mlrDispPercent(-inf,'Loading images'); [fromData fromHeader] = mlrImageLoad(imageArgs{1}); if isempty(fromData),return,end -disppercent(0.5); +mlrDispPercent(0.5); [toData toHeader] = mlrImageLoad(imageArgs{2}); if isempty(toData),return,end -disppercent(inf); +mlrDispPercent(inf); % xform according to sforms if both exist if ~isempty(fromHeader.sform) && ~isempty(toHeader.sform) diff --git a/mrUtilities/File/mlrImage/mlrImageSave.m b/mrUtilities/File/mlrImage/mlrImageSave.m index 92c7bd24c..782c37a3b 100644 --- a/mrUtilities/File/mlrImage/mlrImageSave.m +++ b/mrUtilities/File/mlrImage/mlrImageSave.m @@ -28,7 +28,13 @@ if strcmp(ext,'gz') compressFile = true; % remove the file if it already exists - if mlrIsFile(filename),system(sprintf('rm -f %s',filename));end + if mlrIsFile(filename) + if ~ispc + system(sprintf('rm -f %s',filename)); + else + delete(filename); + end + end % strip off the gz filename = stripext(filename); % get the extension @@ -86,6 +92,11 @@ % now compress if asked for if compressFile - system(sprintf('gzip -f %s',filename)); + if ~ispc + system(sprintf('gzip -f %s',filename)); + else + gzip(filename); + delete(filename); % delete the uncompressed file + end end diff --git a/mrUtilities/File/mlrImage/mlrVol.m b/mrUtilities/File/mlrImage/mlrVol.m index 15f420962..69196b398 100644 --- a/mrUtilities/File/mlrImage/mlrVol.m +++ b/mrUtilities/File/mlrImage/mlrVol.m @@ -1428,7 +1428,7 @@ function controlsCallback(sysNum) gVol{sysNum}.vols(iVol).data = angle(gVol{sysNum}.vols(iVol).complexData); case {'fft2 magnitude','fft2 phase'} % nDims hard coded to 5 here - disppercent(-inf,'(mlrVol) Transforming data'); + mlrDispPercent(-inf,'(mlrVol) Transforming data'); nSlice = size(gVol{sysNum}.vols(iVol).data,3); nVolume = size(gVol{sysNum}.vols(iVol).data,4); nReceiver = size(gVol{sysNum}.vols(iVol).data,5); @@ -1444,14 +1444,14 @@ function controlsCallback(sysNum) gVol{sysNum}.vols(iVol).data(:,:,iSlice,iVolume,iReceiver) = abs(fftSlice); end % percent correct - disppercent(calcPercentDone(iSlice,nSlice,iVolume,nVolume,iReceiver,nReceiver)); + mlrDispPercent(calcPercentDone(iSlice,nSlice,iVolume,nVolume,iReceiver,nReceiver)); end end end - disppercent(inf); + mlrDispPercent(inf); case {'fft3 magnitude','fft3 phase'} % nDims hard coded to 5 here - disppercent(-inf,'(mlrVol) Transforming data'); + mlrDispPercent(-inf,'(mlrVol) Transforming data'); nVolume = size(gVol{sysNum}.vols(iVol).data,4); nReceiver = size(gVol{sysNum}.vols(iVol).data,5); for iVolume = 1:nVolume @@ -1465,10 +1465,10 @@ function controlsCallback(sysNum) gVol{sysNum}.vols(iVol).data(:,:,:,iVolume,iReceiver) = abs(fftVolume); end % percent done - disppercent(calcPercentDone(iVolume,nVolume,iReceiver,nReceiver)); + mlrDispPercent(calcPercentDone(iVolume,nVolume,iReceiver,nReceiver)); end end - disppercent(inf); + mlrDispPercent(inf); end % clear cache gVol{sysNum}.vols(iVol).c = mrCache('init',2*max(gVol{sysNum}.vols(iVol).h.dim(1:3))); diff --git a/mrUtilities/File/neuropythy/mlrImportNeuropythy.m b/mrUtilities/File/neuropythy/mlrImportNeuropythy.m index 021458b4b..e3e810919 100644 --- a/mrUtilities/File/neuropythy/mlrImportNeuropythy.m +++ b/mrUtilities/File/neuropythy/mlrImportNeuropythy.m @@ -204,7 +204,7 @@ return else name = setext(name,'mat'); - if ~isfile(name) + if ~mlrIsFile(name) disp(sprintf('(mlrImortNeuropythy) Could not find file %s',name)); return end @@ -226,7 +226,7 @@ % replace tilde if it is there lh_labels = mlrReplaceTilde(lh_labels); % check if it is afile - if ~isfile(lh_labels) + if ~mlrIsFile(lh_labels) disp(sprintf('(mlrImportNeuropythy) %s is not a text file with the area labels in it',lh_labels)); return end @@ -245,7 +245,7 @@ % replace tilde if it is there rh_labels = mlrReplaceTilde(rh_labels); % check if it is afile - if ~isfile(rh_labels) + if ~mlrIsFile(rh_labels) disp(sprintf('(mlrImportNeuropythy) %s is not a text file with the area labels in it',lh_labels)); return end diff --git a/mrUtilities/ImageProcessing/corrDn.m b/mrUtilities/ImageProcessing/corrDn.m index 18e976026..824566cb3 100644 --- a/mrUtilities/ImageProcessing/corrDn.m +++ b/mrUtilities/ImageProcessing/corrDn.m @@ -31,7 +31,7 @@ %% NOTE: THIS CODE IS NOT ACTUALLY USED! (MEX FILE IS CALLED INSTEAD) -fprintf(1,'Warning: You should compile the MEX code for "corrDn", found in the MEX subdirectory. It is MUCH faster.\n'); +%fprintf(1,'Warning: You should compile the MEX code for "corrDn", found in the MEX subdirectory. It is MUCH faster.\n'); %------------------------------------------------------------ %% OPTIONAL ARGS: diff --git a/mrUtilities/ImageProcessing/mrUpSample.m b/mrUtilities/ImageProcessing/mrUpSample.m index 654df83e4..ad43f3502 100644 --- a/mrUtilities/ImageProcessing/mrUpSample.m +++ b/mrUtilities/ImageProcessing/mrUpSample.m @@ -77,31 +77,31 @@ [nrows ncols nslices nframes] = size(data); - disppercent(-inf,'(mrUpSample) upsampling the rows'); + mlrDispPercent(-inf,'(mrUpSample) upsampling the rows'); data = reshape(data,[nrows ncols*nslices*nframes]); data = upConv(data, filt, 'zero', [2 1]); nrows = nrows*2; data = reshape(data,[nrows ncols nslices nframes]); - disppercent(inf); + mlrDispPercent(inf); - disppercent(-inf,'(mrUpSample) upsampling the columns'); + mlrDispPercent(-inf,'(mrUpSample) upsampling the columns'); data = permute(data,[2 1 3 4]); data = reshape(data,[ncols nrows*nslices*nframes]); data = upConv(data, filt, 'zero', [2 1]); ncols = ncols*2; data = reshape(data,[ncols nrows nslices nframes]); data = permute(data,[2 1 3 4]); - disppercent(inf); + mlrDispPercent(inf); if size(data,3) > 1 - disppercent(-inf,'(mrUpSample) upsampling the slices'); + mlrDispPercent(-inf,'(mrUpSample) upsampling the slices'); data = permute(data,[3 2 1 4]); data = reshape(data,[nslices ncols*nrows*nframes]); data = upConv(data, filt, 'zero', [2 1]); nslices = nslices*2; data = reshape(data,[nslices ncols nrows nframes]); data = permute(data,[3 2 1 4]); - disppercent(inf); + mlrDispPercent(inf); end end else diff --git a/mrUtilities/ImageProcessing/upConv.m b/mrUtilities/ImageProcessing/upConv.m index 4ec953d60..8be452d67 100644 --- a/mrUtilities/ImageProcessing/upConv.m +++ b/mrUtilities/ImageProcessing/upConv.m @@ -37,7 +37,8 @@ %% THIS CODE IS NOT ACTUALLY USED! (MEX FILE IS CALLED INSTEAD) global upConvWarning -if ieNotDefined('upConvWarning') +%if ieNotDefined('upConvWarning') +if false fprintf(1,'===================================================================\n'); fprintf(1,'(upConv) Warning: You should compile the MEX code for "upConv", found in the MEX subdirectory. It is much faster.\n'); fprintf(1,'===================================================================\n'); diff --git a/mrUtilities/MatlabUtilities/getptsNoDoubleClick.m b/mrUtilities/MatlabUtilities/getptsNoDoubleClick.m index 1f25f49dd..6529bcba0 100644 --- a/mrUtilities/MatlabUtilities/getptsNoDoubleClick.m +++ b/mrUtilities/MatlabUtilities/getptsNoDoubleClick.m @@ -33,11 +33,9 @@ % getpts('FirstButtonDown') % getpts('NextButtonDown') -% Copyright 1993-2004 The MathWorks, Inc. -% $Revision$ $Date$ +% Copyright 1993-2011 The MathWorks, Inc. global GETPTS_FIG GETPTS_AX GETPTS_H1 GETPTS_H2 -global GETPTS_PT1 if ((nargin >= 1) && (ischar(varargin{1}))) % Callback invocation: 'KeyPress', 'FirstButtonDown', or @@ -53,9 +51,8 @@ GETPTS_AX = gca; GETPTS_FIG = ancestor(GETPTS_AX, 'figure'); else - if (~ishandle(varargin{1})) - eid = 'Images:getpts:expectedHandle'; - error(eid, '%s', 'First argument is not a valid handle'); + if (~ishghandle(varargin{1})) + error(message('images:getpts:expectedHandle')); end switch get(varargin{1}, 'Type') @@ -71,13 +68,13 @@ GETPTS_FIG = ancestor(GETPTS_AX, 'figure'); otherwise - eid = 'Images:getpts:expectedFigureOrAxesHandle'; - error(eid, '%s', 'First argument should be a figure or axes handle'); + error(message('images:getpts:expectedFigureOrAxesHandle')); end end % Bring target figure forward +GETPTS_FIG.Visible = 'on'; % make sure Live Editor figures are shown figure(GETPTS_FIG); % Remember initial figure state @@ -102,8 +99,7 @@ 'Color', 'c', ... 'LineStyle', 'none', ... 'Marker', '+', ... - 'MarkerSize', markerSize, ... - 'EraseMode', 'xor'); + 'MarkerSize', markerSize); GETPTS_H2 = line('Parent', GETPTS_AX, ... 'XData', [], ... @@ -113,8 +109,7 @@ 'Color', 'm', ... 'LineStyle', 'none', ... 'Marker', 'x', ... - 'MarkerSize', markerSize, ... - 'EraseMode', 'xor'); + 'MarkerSize', markerSize); % We're ready; wait for the user to do the drag % Wrap the call to waitfor in try-catch so we'll @@ -135,7 +130,7 @@ if (errCatch == 1) errStatus = 'trap'; -elseif (~ishandle(GETPTS_H1) || ... +elseif (~ishghandle(GETPTS_H1) || ... ~strcmp(get(GETPTS_H1, 'UserData'), 'Completed')) errStatus = 'unknown'; @@ -157,15 +152,15 @@ end % Delete the animation objects -if (ishandle(GETPTS_H1)) +if (ishghandle(GETPTS_H1)) delete(GETPTS_H1); end -if (ishandle(GETPTS_H2)) +if (ishghandle(GETPTS_H2)) delete(GETPTS_H2); end % Restore the figure state -if (ishandle(GETPTS_FIG)) +if (ishghandle(GETPTS_FIG)) uirestore(state); end @@ -181,15 +176,13 @@ case 'trap' % An error was trapped during the waitfor - eid = 'Images:getpts:interruptedMouseSelection'; - error(eid, '%s', 'Interruption during mouse point selection.'); + error(message('images:getpts:interruptedMouseSelection')); case 'unknown' % User did something to cause the point selection to % terminate abnormally. For example, we would get here % if the user closed the figure in the middle of the selection. - eid = 'Images:getpts:interruptedMouseSelection'; - error(eid, '%s', 'Interruption during mouse point selection.'); + error(message('images:getpts:interruptedMouseSelection')); end @@ -198,8 +191,7 @@ %-------------------------------------------------- function KeyPress %#ok -global GETPTS_FIG GETPTS_AX GETPTS_H1 GETPTS_H2 -global GETPTS_PT1 +global GETPTS_FIG GETPTS_H1 GETPTS_H2 key = get(GETPTS_FIG, 'CurrentCharacter'); switch key @@ -245,13 +237,13 @@ 'Visible', 'on'); % jg: never interpret double click -%if (~strcmp(get(GETPTS_FIG, 'SelectionType'), 'normal')) - % We're done! -% set(GETPTS_H1, 'UserData', 'Completed'); -%else +% if (~strcmp(get(GETPTS_FIG, 'SelectionType'), 'normal')) +% % We're done! +% set(GETPTS_H1, 'UserData', 'Completed'); +% else % jg: call getptsNoDoubleClick set(GETPTS_FIG, 'WindowButtonDownFcn', 'getptsNoDoubleClick(''NextButtonDown'');'); -%end +% end %-------------------------------------------------- % Subfunction NextButtonDown @@ -278,10 +270,10 @@ end % jg: never interpret double click -%if (~strcmp(get(GETPTS_FIG, 'SelectionType'), 'normal')) - % We're done! -% set(GETPTS_H1, 'UserData', 'Completed'); -%end +% if (~strcmp(get(GETPTS_FIG, 'SelectionType'), 'normal')) +% % We're done! +% set(GETPTS_H1, 'UserData', 'Completed'); +% end @@ -309,7 +301,7 @@ NaN NaN NaN NaN NaN 1 2 NaN 2 1 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN]; - + function [x,y] = getcurpt(axHandle) %GETCURPT Get current point. % [X,Y] = GETCURPT(AXHANDLE) gets the x- and y-coordinates of @@ -321,7 +313,6 @@ % pixel that the user clicked on. % Copyright 1993-2003 The MathWorks, Inc. -% $Revision$ $Date$ pt = get(axHandle, 'CurrentPoint'); x = pt(1,1); diff --git a/mrUtilities/MatlabUtilities/disppercent.m b/mrUtilities/MatlabUtilities/mlrDispPercent.m similarity index 86% rename from mrUtilities/MatlabUtilities/disppercent.m rename to mrUtilities/MatlabUtilities/mlrDispPercent.m index 5c8f5f629..7fc4c0c85 100644 --- a/mrUtilities/MatlabUtilities/disppercent.m +++ b/mrUtilities/MatlabUtilities/mlrDispPercent.m @@ -1,47 +1,47 @@ -% disppercent.m +% mlrDispPercent.m % % by: justin gardner % date: 10/05/04 -% usage: disppercent(percentdone,message) +% usage: mlrDispPercent(percentdone,message) % purpose: display percent done % Start by calling with a negative value: -% disppercent(-inf,'Message to display'); +% mlrDispPercent(-inf,'Message to display'); % % Update by calling with percent done: -% disppercent(0.5); +% mlrDispPercent(0.5); % % Finish by calling with inf (elapsedTime is in seconds): -% elapsedTime = disppercent(inf); +% elapsedTime = mlrDispPercent(inf); % % If you want to change the message before calling with inf: -% disppercent(0.5,'New message to display'); +% mlrDispPercent(0.5,'New message to display'); % % Also, if you have an inner loop within an outer loop, you % can call like the following: % n1 = 15;n2 = 10; -% disppercent(-1/n1); % init with how much the outer loop increments +% mlrDispPercent(-1/n1); % init with how much the outer loop increments % for i = 1:n1 % for j = 1:n2 % pause(0.1); -% disppercent((i-1)/n1,j/n2); +% mlrDispPercent((i-1)/n1,j/n2); % end -% disppercent(i/n1,sprintf('Made it through %i/%i iterations of outer loop',i,n1)); +% mlrDispPercent(i/n1,sprintf('Made it through %i/%i iterations of outer loop',i,n1)); % end -% disppercent(inf); +% mlrDispPercent(inf); % % e.g.: % -%disppercent(-inf,'Doing stuff');for i = 1:30;pause(0.1);disppercent(i/30);end;elapsedTime = disppercent(inf); -function retval = disppercent(percentdone,mesg) +%mlrDispPercent(-inf,'Doing stuff');for i = 1:30;pause(0.1);mlrDispPercent(i/30);end;elapsedTime = mlrDispPercent(inf); +function retval = mlrDispPercent(percentdone,mesg) retval = nan; % check command line arguments if ((nargin ~= 1) && (nargin ~= 2)) - help disppercent; + help mlrDispPercent; return end -% global for disppercent +% global for mlrDispPercent global gDisppercent; % if this is an init then remember time @@ -145,6 +145,7 @@ mrDisp(sprintf('\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b%s%05.2f%% (%s)',newmesg,floor(10000*percentdone)/100,disptime(elapsedTime*(1/percentdone - 1)))); % display only if we have update by a least a percent or if at least 1 second has elapsed since last display elseif (gDisppercent.percentdone ~= floor(100*percentdone)) || floor(elapsedTime)~=gDisppercent.elapsedTime + pause(0.0001); % a brief pause is necessary to correctly display the message and counter (tested on Windows and Linux) mrDisp(sprintf('\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b%05.2f%% (%s)',floor(10000*percentdone)/100,disptime(elapsedTime*(1/percentdone - 1)))); end end diff --git a/mrUtilities/MatlabUtilities/mlrParseAdditionalArguments.m b/mrUtilities/MatlabUtilities/mlrParseAdditionalArguments.m new file mode 100644 index 000000000..3d79a31a6 --- /dev/null +++ b/mrUtilities/MatlabUtilities/mlrParseAdditionalArguments.m @@ -0,0 +1,19 @@ +function [arguments, nArgs] = mlrParseAdditionalArguments(argumentString, separator) + +%parse string of arguments separated by separator and put them into a cell array of numerical and string arguments +%non-numerical values that are not between quotes are converted into strings +% +% Julien Besle, 08/07/2010 +nArgs = 0; +arguments = cell(0); +remain = argumentString; +while ~isempty(remain) + nArgs = nArgs+1; + [token,remain] = strtok(remain, separator); + try + arguments{nArgs} = eval(token); + catch + arguments{nArgs} = token; + end + +end \ No newline at end of file diff --git a/mrUtilities/MatlabUtilities/mrCloseDlg.m b/mrUtilities/MatlabUtilities/mrCloseDlg.m index 5e6957e03..e9e08e98a 100644 --- a/mrUtilities/MatlabUtilities/mrCloseDlg.m +++ b/mrUtilities/MatlabUtilities/mrCloseDlg.m @@ -10,7 +10,7 @@ function mrCloseDlg(h) if ishandle(h) close(h); -elseif isfield(h,'disppercent') - disppercent(inf); +elseif isfield(h,'mlrDispPercent') + mlrDispPercent(inf); end diff --git a/mrUtilities/MatlabUtilities/mrDisp.c b/mrUtilities/MatlabUtilities/mrDisp.c new file mode 100644 index 000000000..913091a0f --- /dev/null +++ b/mrUtilities/MatlabUtilities/mrDisp.c @@ -0,0 +1,60 @@ +#ifdef documentation +========================================================================= + + program: mydisp.c + by: justin gardner + purpose: print w/out newline character + date: 07/08/03 + compile: mex mydisp.c + +========================================================================= +#endif + +//////////////////// +// include section +//////////////////// +#include +#include +#include +#include +#include "mex.h" + +/////////////////// +// define section +/////////////////// +#define STRSIZE 2048 + +/////////////////// +// function decls +/////////////////// +void usageError(); + +////////////////////////////////////////// +// function mexFunction called by matlab +////////////////////////////////////////// +void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) +{ + char str[STRSIZE]; + + // check input arguments + if (nrhs != 1){ + usageError(); + return; + } + + // get string + mxGetString(prhs[0], str, mxGetN(prhs[0])+1); + + // print string + printf("%s",str); + fflush(stdout); +} + +//////////////////////// +// function usageError +//////////////////////// +void usageError() +{ + printf("USAGE: mydisp('string')\n"); +} + diff --git a/mrUtilities/MatlabUtilities/mrDisp.m b/mrUtilities/MatlabUtilities/mrDisp.m index ab1f36671..1aa7fb9e1 100644 --- a/mrUtilities/MatlabUtilities/mrDisp.m +++ b/mrUtilities/MatlabUtilities/mrDisp.m @@ -9,6 +9,6 @@ function mrDisp(str) % if this is being called it means the mex file doesn't exist, % so just print out (this won't flush though--preventing updating -% text print outs like for disppercent) +% text print outs like for mlrDispPercent) fprintf(1,str); diff --git a/mrUtilities/MatlabUtilities/mrDisp.mexa64 b/mrUtilities/MatlabUtilities/mrDisp.mexa64 new file mode 100644 index 000000000..cb079a728 Binary files /dev/null and b/mrUtilities/MatlabUtilities/mrDisp.mexa64 differ diff --git a/mrUtilities/MatlabUtilities/mrDisp.mexglx b/mrUtilities/MatlabUtilities/mrDisp.mexglx new file mode 100755 index 000000000..de88a2884 Binary files /dev/null and b/mrUtilities/MatlabUtilities/mrDisp.mexglx differ diff --git a/mrUtilities/MatlabUtilities/mrDisp.mexw32 b/mrUtilities/MatlabUtilities/mrDisp.mexw32 new file mode 100644 index 000000000..16d8db763 Binary files /dev/null and b/mrUtilities/MatlabUtilities/mrDisp.mexw32 differ diff --git a/mrUtilities/MatlabUtilities/mrDisp.mexw64 b/mrUtilities/MatlabUtilities/mrDisp.mexw64 new file mode 100644 index 000000000..f513b6339 Binary files /dev/null and b/mrUtilities/MatlabUtilities/mrDisp.mexw64 differ diff --git a/mrUtilities/MatlabUtilities/mrGetPref.m b/mrUtilities/MatlabUtilities/mrGetPref.m index e05837a55..3de8bf969 100644 --- a/mrUtilities/MatlabUtilities/mrGetPref.m +++ b/mrUtilities/MatlabUtilities/mrGetPref.m @@ -41,54 +41,55 @@ 'maxArrayWidthForParamsDialog','maxArrayHeightForParamsDialog',... 'mlrVolDisplayControls','mlrVolOverlayAlpha','motionCompDefaultParams','colorNames',... 'mlrPath','vistaPath','lastPath'... - 'overlayCombineTransformPaths','roiTransformPaths',... + 'overlayCombineTransformPaths','roiTransformPaths','colormapPaths'... }; % set the defaults for preference we have defaults for. Note that the "find" in % here is to make sure that the prefDefaults list matches the prefNames order prefDefaults{length(prefNames)} = []; -prefDefaults{find(strcmp('overwritePolicy',prefNames))} = {'Ask','Merge','Rename','Overwrite'}; -prefDefaults{find(strcmp('verbose',prefNames))} = {'No','Yes'}; -prefDefaults{find(strcmp('graphWindow',prefNames))} = {'Replace','Make new'}; -prefDefaults{find(strcmp('checkParamsConsistency',prefNames))} = {'Yes','No'}; -prefDefaults{find(strcmp('maxBlocksize',prefNames))} = 250000000; -prefDefaults{find(strcmp('roiCacheSize',prefNames))} = 100; -prefDefaults{find(strcmp('baseCacheSize',prefNames))} = 50; -prefDefaults{find(strcmp('overlayCacheSize',prefNames))} = 50; -prefDefaults{find(strcmp('defaultPrecision',prefNames))} = 'double'; -prefDefaults{find(strcmp('interrogatorPaths',prefNames))} = ''; -prefDefaults{find(strcmp('volumeDirectory',prefNames))} = ''; -prefDefaults{find(strcmp('niftiFileExtension',prefNames))} = {'.img','.nii'}; -prefDefaults{find(strcmp('fslPath',prefNames))} = 'FSL not installed'; -prefDefaults{find(strcmp('selectedROIColor',prefNames))} = color2RGB; -prefDefaults{find(strcmp('selectedROIColor',prefNames))}{end+1} = 'none'; -prefDefaults{find(strcmp('roiContourWidth',prefNames))} = 1; -prefDefaults{find(strcmp('roiCorticalDepthDisplayRatio',prefNames))} = .5; -prefDefaults{find(strcmp('roiPolygonMethod',prefNames))} = {'getpts','roipoly','getptsNoDoubleClick'}; -prefDefaults{find(strcmp('interpMethod',prefNames))} = {'nearest','linear','spline','cubic'}; -prefDefaults{find(strcmp('corticalDepthBins',prefNames))} = 11; -prefDefaults{find(strcmp('multiSliceProjectionMethod',prefNames))} = {'Average','Maximum Intensity Projection'}; -prefDefaults{find(strcmp('colorBlending',prefNames))} = {'Additive','Alpha blend','Contours'}; -prefDefaults{find(strcmp('overlayRangeBehaviour',prefNames))} = {'Classic','New'}; -prefDefaults{find(strcmp('baseNaNsColor',prefNames))} = {'Black','White','Transparent'}; -prefDefaults{find(strcmp('pluginPaths',prefNames))} = ''; -prefDefaults{find(strcmp('selectedPlugins',prefNames))} = ''; -prefDefaults{find(strcmp('statisticalTestOutput',prefNames))} = {'P value','Z value','-log10(P) value'}; -prefDefaults{find(strcmp('site',prefNames))} = 'NYU'; -prefDefaults{find(strcmp('magnet',prefNames))} = {{'Allegra 3T','other'}}; -prefDefaults{find(strcmp('coil',prefNames))} = {{'LifeService','Siemens birdcage','Nova birdcage','Nova surface','Nova quadrapus','Nova visual array','other'}}; -prefDefaults{find(strcmp('pulseSequence',prefNames))} = {{'cbi_ep2d_bold','other'}}; -prefDefaults{find(strcmp('maxArrayWidthForParamsDialog',prefNames))} = 25; -prefDefaults{find(strcmp('maxArrayHeightForParamsDialog',prefNames))} = 50; -prefDefaults{find(strcmp('mlrVolDisplayControls',prefNames))} = false; -prefDefaults{find(strcmp('mlrVolOverlayAlpha',prefNames))} = 0.8; -prefDefaults{find(strcmp('motionCompDefaultParams',prefNames))} = []; -prefDefaults{find(strcmp('colorNames',prefNames))} = {}; -prefDefaults{find(strcmp('mlrPath',prefNames))} = ''; -prefDefaults{find(strcmp('vistaPath',prefNames))} = ''; -prefDefaults{find(strcmp('lastPath',prefNames))} = ''; -prefDefaults{find(strcmp('overlayCombineTransformPaths',prefNames))} = ''; -prefDefaults{find(strcmp('roiTransformPaths',prefNames))} = ''; +prefDefaults{strcmp('overwritePolicy',prefNames)} = {'Ask','Merge','Rename','Overwrite'}; +prefDefaults{strcmp('verbose',prefNames)} = {'No','Yes'}; +prefDefaults{strcmp('graphWindow',prefNames)} = {'Replace','Make new'}; +prefDefaults{strcmp('checkParamsConsistency',prefNames)} = {'Yes','No'}; +prefDefaults{strcmp('maxBlocksize',prefNames)} = 250000000; +prefDefaults{strcmp('roiCacheSize',prefNames)} = 100; +prefDefaults{strcmp('baseCacheSize',prefNames)} = 50; +prefDefaults{strcmp('overlayCacheSize',prefNames)} = 50; +prefDefaults{strcmp('defaultPrecision',prefNames)} = 'double'; +prefDefaults{strcmp('interrogatorPaths',prefNames)} = ''; +prefDefaults{strcmp('volumeDirectory',prefNames)} = ''; +prefDefaults{strcmp('niftiFileExtension',prefNames)} = {'.img','.nii'}; +prefDefaults{strcmp('fslPath',prefNames)} = 'FSL not installed'; +prefDefaults{strcmp('selectedROIColor',prefNames)} = color2RGB; +prefDefaults{strcmp('selectedROIColor',prefNames)}{end+1} = 'none'; +prefDefaults{strcmp('roiContourWidth',prefNames)} = 1; +prefDefaults{strcmp('roiCorticalDepthDisplayRatio',prefNames)} = .5; +prefDefaults{strcmp('roiPolygonMethod',prefNames)} = {'getpts','roipoly','getptsNoDoubleClick'}; +prefDefaults{strcmp('interpMethod',prefNames)} = {'nearest','linear','spline','cubic'}; +prefDefaults{strcmp('corticalDepthBins',prefNames)} = 11; +prefDefaults{strcmp('multiSliceProjectionMethod',prefNames)} = {'Average','Maximum Intensity Projection'}; +prefDefaults{strcmp('colorBlending',prefNames)} = {'Additive','Alpha blend','Contours'}; +prefDefaults{strcmp('overlayRangeBehaviour',prefNames)} = {'Classic','New'}; +prefDefaults{strcmp('baseNaNsColor',prefNames)} = {'Black','White','Transparent'}; +prefDefaults{strcmp('pluginPaths',prefNames)} = ''; +prefDefaults{strcmp('selectedPlugins',prefNames)} = ''; +prefDefaults{strcmp('statisticalTestOutput',prefNames)} = {'P value','Z value','-log10(P) value'}; +prefDefaults{strcmp('site',prefNames)} = 'NYU'; +prefDefaults{strcmp('magnet',prefNames)} = {{'Allegra 3T','other'}}; +prefDefaults{strcmp('coil',prefNames)} = {{'LifeService','Siemens birdcage','Nova birdcage','Nova surface','Nova quadrapus','Nova visual array','other'}}; +prefDefaults{strcmp('pulseSequence',prefNames)} = {{'cbi_ep2d_bold','other'}}; +prefDefaults{strcmp('maxArrayWidthForParamsDialog',prefNames)} = 25; +prefDefaults{strcmp('maxArrayHeightForParamsDialog',prefNames)} = 50; +prefDefaults{strcmp('mlrVolDisplayControls',prefNames)} = false; +prefDefaults{strcmp('mlrVolOverlayAlpha',prefNames)} = 0.8; +prefDefaults{strcmp('motionCompDefaultParams',prefNames)} = []; +prefDefaults{strcmp('colorNames',prefNames)} = {}; +prefDefaults{strcmp('mlrPath',prefNames)} = ''; +prefDefaults{strcmp('vistaPath',prefNames)} = ''; +prefDefaults{strcmp('lastPath',prefNames)} = ''; +prefDefaults{strcmp('overlayCombineTransformPaths',prefNames)} = ''; +prefDefaults{strcmp('roiTransformPaths',prefNames)} = ''; +prefDefaults{strcmp('colormapPaths',prefNames)} = ''; if nargin == 0 if nargout > 0 @@ -138,7 +139,7 @@ else % not set yet, take the top most possibility in the default % list, otherwise return empty - if ~isempty(prefNum) && ~isempty(prefDefaults{prefNum}) + if ~isempty(prefNum) && (~isempty(prefDefaults{prefNum}) || ischar(prefDefaults{prefNum})) if iscell(prefDefaults{prefNum}) value = prefDefaults{prefNum}{1}; else diff --git a/mrUtilities/MatlabUtilities/mrWaitBar.m b/mrUtilities/MatlabUtilities/mrWaitBar.m index 8ad767ab3..ed1294586 100644 --- a/mrUtilities/MatlabUtilities/mrWaitBar.m +++ b/mrUtilities/MatlabUtilities/mrWaitBar.m @@ -31,11 +31,11 @@ waitbar(x,t,newMessage); end drawnow; -elseif isfield(t,'disppercent') +elseif isfield(t,'mlrDispPercent') if ieNotDefined('newMessage') - disppercent(x); + mlrDispPercent(x); else - disppercent(x,newMessage); + mlrDispPercent(x,newMessage); end % initial call @@ -59,13 +59,13 @@ end drawnow; else - % otherwise use disppercent + % otherwise use mlrDispPercent if ieNotDefined('newMessage') - disppercent(-inf,t); + mlrDispPercent(-inf,t); else - disppercent(-inf,[t '; ' newMessage]); + mlrDispPercent(-inf,[t '; ' newMessage]); end - h.disppercent = 1; + h.mlrDispPercent = 1; end end return diff --git a/mrUtilities/Plot/getSubplotPosition.m b/mrUtilities/Plot/getSubplotPosition.m index 9d33525f6..bfa7500d4 100644 --- a/mrUtilities/Plot/getSubplotPosition.m +++ b/mrUtilities/Plot/getSubplotPosition.m @@ -2,7 +2,7 @@ % getSubplotPosition.m % % $Id$ -% usage: position =getSubplotPosition(X,Y,verticalGrid,horizontalGrid,xMargin,yMargin) +% usage: position = getSubplotPosition(X,Y,verticalGrid,horizontalGrid,xMargin,yMargin) % by: julien besle % date: 28/11/2010 % purpose: returns normalized position for subplot or uicontrol in a virtual grid of given dimensions @@ -10,8 +10,8 @@ % - horizontalGrid and verticalGrid are vector specifying the relative dimensions of the rows and columns % in the grid (in arbitrary units,from left to right and top to bottom) % - xMargin and yMargin are the width left blank between the rows and columns of the grid (same units as horizontal and vertical Grid). -% They are independent from the dimensions of the grid and are removed from it -% output: - position vector [left bottom width height] to use as input to subplot or uicontrol 'position' property. +% They are independent from the dimensions of the grid and are removed from it. Right and top outside margins are set to xMargin/2 and yMargin/2. +% output: - position vector [left bottom width height] to use as input to axes or uicontrol 'position' property. % if no margin is specified, it is better to use this position as 'outerposition' % % example: h = axes('outerposition',getSubplotPosition(2,2:3,[1 1 .5],[.5 1 1 .5],.1)) @@ -32,12 +32,12 @@ if ieNotDefined('yMargin') yMargin = 0; end -figureWidth= sum(horizontalGrid); -figureHeigth= sum(verticalGrid); +figureWidth= sum(horizontalGrid) + xMargin*(length(horizontalGrid)+0.5); +figureHeigth= sum(verticalGrid) + yMargin*(length(verticalGrid)+0.5); -position(1) = (sum(horizontalGrid(1:X(1)-1))+xMargin/2)/ figureWidth; -position(3) = (sum(horizontalGrid(X(1):X(end))) + (X(end)-X(1)-1)*xMargin)/ figureWidth; +position(1) = (sum(horizontalGrid(1:X(1)-1)) + (X(1)) * xMargin)/ figureWidth; +position(3) = (sum(horizontalGrid(X(1):X(end))) + (X(end)-X(1))*xMargin)/ figureWidth; -position(2) = 1 - (sum(verticalGrid(1:Y(end)))-yMargin/2) / figureHeigth; -position(4) = (sum(verticalGrid(Y(1):Y(end))) + (Y(end)-Y(1)-1)*yMargin) / figureHeigth; +position(2) = 1 - (sum(verticalGrid(1:Y(end))) + (Y(end)-0.5) * yMargin) / figureHeigth; +position(4) = (sum(verticalGrid(Y(1):Y(end))) + (Y(end)-Y(1))*yMargin) / figureHeigth; diff --git a/mrUtilities/make/mlrMake.m b/mrUtilities/make/mlrMake.m index 960bce09d..0337aed62 100644 --- a/mrUtilities/make/mlrMake.m +++ b/mrUtilities/make/mlrMake.m @@ -55,7 +55,7 @@ disp(sprintf('(mlrMake) Skipping: %s because files is not in use',compiledFunctionList{iFile})); skippedFiles{end+1} = compiledFunctionList{iFile}; % check for file - elseif isfile(compiledFunctionList{iFile}) + elseif mlrIsFile(compiledFunctionList{iFile}) % display what we are doing disp(sprintf('(mlrMake) mex: %s',compiledFunctionList{iFile})); % mex the file diff --git a/mrUtilities/surfUtils/calcCurvature.m b/mrUtilities/surfUtils/calcCurvature.m index 2c4a921f8..7f7b5cfb7 100644 --- a/mrUtilities/surfUtils/calcCurvature.m +++ b/mrUtilities/surfUtils/calcCurvature.m @@ -82,7 +82,7 @@ % allocate space for m m = zeros(1,innerSurf.Nvtcs); -disppercent(-inf,'(calcCurvature) Calculating curvature'); +mlrDispPercent(-inf,'(calcCurvature) Calculating curvature'); if isempty(vertexList),vertexList = 1:innerSurf.Nvtcs;end for iVertex = vertexList % find neighbors of this vertex. @@ -151,9 +151,9 @@ pCurvature = eig(A); % get the mean curvature m(iVertex) = mean(pCurvature); - disppercent(iVertex/length(vertexList)); + mlrDispPercent(iVertex/length(vertexList)); end -disppercent(inf); +mlrDispPercent(inf); % invert colors m = -m; diff --git a/mrUtilities/surfUtils/calcSurfaceNormals.m b/mrUtilities/surfUtils/calcSurfaceNormals.m index 5d8eb4108..58ee43ac4 100644 --- a/mrUtilities/surfUtils/calcSurfaceNormals.m +++ b/mrUtilities/surfUtils/calcSurfaceNormals.m @@ -15,12 +15,22 @@ return end +if ~isfield(surf,'tris') && isfield(surf,'faces') + surf = renameStructField(surf,'faces','tris'); +end +if ~isfield(surf,'vtcs') && isfield(surf,'vertices') + surf = renameStructField(surf,'vertices','vtcs'); +end +if ~isfield(surf,'Nvtcs') + surf.Nvtcs = size(surf.vtcs,1); +end + % first compute the normals to each triangle face. % this is done with the cross product of two edge vectors % % % %---------- LOOP VERSION % % % triNormals = zeros(surf.Ntris,3); -% % % disppercent(-inf,'(calcSurfaceNormal) Computing triangle normals'); +% % % mlrDispPercent(-inf,'(calcSurfaceNormal) Computing triangle normals'); % % % for iTri = 1:surf.Ntris % % % % get the three vertices of this triangle % % % vertex1 = surf.vtcs(surf.tris(iTri,1),:); @@ -29,9 +39,9 @@ % % % % and compute the surface normal using the cross product % % % triNormals(iTri,:) = cross(vertex2-vertex1,vertex2-vertex3); % % % triNormals(iTri,:) = triNormals(iTri,:)/norm(triNormals(iTri,:)); -% % % disppercent(iTri/surf.Ntris); +% % % mlrDispPercent(iTri/surf.Ntris); % % % end -% % % disppercent(inf); +% % % mlrDispPercent(inf); %----------- VECTORIZED VERSION (much faster) % get 2 vector sides for all triangles @@ -71,14 +81,14 @@ % % % %---------- LOOP VERSION -% % % disppercent(-inf,'(calcSurfaceNormal) Computing vertex normals'); +% % % mlrDispPercent(-inf,'(calcSurfaceNormal) Computing vertex normals'); % % % vertexNormals = zeros(surf.Nvtcs,3); % % % for iVtx = 1:surf.Nvtcs % % % % get which triangles this vertex belongs to % % % [triNums edgeNums] = find(iVtx == surf.tris); % % % % and then get the mean of the normals to those triangls % % % vertexNormals(iVtx,:) = mean(triNormals(triNums,:)); -% % % disppercent(iVtx/surf.Nvtcs); +% % % mlrDispPercent(iVtx/surf.Nvtcs); % % % end -% % % disppercent(inf); +% % % mlrDispPercent(inf); diff --git a/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m b/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m new file mode 100644 index 000000000..e0e8ee6f1 --- /dev/null +++ b/mrUtilities/surfUtils/freesurferSphericalNormalizationVolumes.m @@ -0,0 +1,387 @@ +% function freesurferSphericalNormalizationVolumes(params,<'justGetParams'>) +% +% goal: Transforms data in source volume(s) from source to destination space using Freesurfer's +% spherical normalization between fsSourceSubj and fsDestSubj freesrufer subjects, and keeping only data +% located within the cortical sheet, and saving in Nifti file destVol. +% The destination space is the space of the surfRelax T1 volume, +% unless optional argument destVolTemplate is specified. +% Source and destination volumes are sampled using the space specified by their sform +% rotation matrix, or their qform if the sform is not set. +% By default, both hemispheres are transformed +% +% input parameters: +% params.sourceVol (mandatory): volume to convert (source) +% params.fsSourceSubj (mandatory): freesurfer subject ID cooresponding to source volume +% params.fsSourceSurfSuffix (optional): suffix to add to the surface file names (e.g. fsSourceSubj_left_GMsuffix.off) (default = '') +% params.fsDestSubj (optional): Freesurfer subject ID corresponding to destination volume (default: 'fsaverage') +% params.destVol (mandatory): name of destination file(default: surfRelax anatomical scan of destination Freesurfer subject) +% params.destVolTemplate (optional): template volume for destination space (default: surfRelax anatomical scan of source Freesurfer subject) +% params.sourceSurfRelaxVolume (optional): surfRelax anatomical scan corresponding to the source volume, in case there are several volumes +% in the surfRelax folder (default: surfRelax anatomical scan of source Freesurfer subject) +% params.destSurfRelaxVolume (optional): surfRelax anatomical scan corresponding to the destination volumes, in case there are several +% volumes in the surfRelax folder (default: surfRelax anatomical scan of destination Freesurfer subject) +% params.hemisphere (optional): 'left','right or 'both' (default: 'both') +% params.interpMethod (optional): interpolation method for ressampling the source volume to the source surface (default from mrGetPref) +% params.outputBinaryData (optional): whether to binarize resampled data if the source data were binary, useful for ROI masks (default = false) +% params.recomputeRemapping (optional): whether to recompute the mapping between the source and destination surfaces (default: false) +% params.cropDestVolume (optional): whether to crop the destination volume to the smallest volume containing the surfaces (default = true) +% params.dryRun (optional): if true, just checks that the input files exist (default: false) +% +% author: julien besle (24/07/2020) + +function params = freesurferSphericalNormalizationVolumes(params,varargin) + +eval(evalargs(varargin)); +if ieNotDefined('justGetParams'),justGetParams = 0;end + +if ieNotDefined('params') + params = struct; +end + +if fieldIsNotDefined(params,'sourceVol') + params.sourceVol = {''}; +end +if fieldIsNotDefined(params,'fsSourceSubj') + params.fsSourceSubj = ''; +end +if fieldIsNotDefined(params,'fsSourceSurfSuffix') + params.fsSourceSurfSuffix = ''; +end +if fieldIsNotDefined(params,'fsDestSubj') + params.fsDestSubj = 'fsaverage'; +end +if fieldIsNotDefined(params,'destVol') + params.destVol = {''}; +end +if fieldIsNotDefined(params,'destVolTemplate') + params.destVolTemplate = ''; +end +if fieldIsNotDefined(params,'sourceSurfRelaxVolume') + params.sourceSurfRelaxVolume = ''; +end +if fieldIsNotDefined(params,'destSurfRelaxVolume') + params.destSurfRelaxVolume = ''; +end +if fieldIsNotDefined(params,'hemisphere') + params.hemisphere = 'both'; +end +if fieldIsNotDefined(params,'interpMethod') + params.interpMethod = mrGetPref('interpMethod'); +end +if fieldIsNotDefined(params,'outputBinaryData') + params.outputBinaryData = false; +end +if fieldIsNotDefined(params,'recomputeRemapping') + params.recomputeRemapping = false; +end +if fieldIsNotDefined(params,'cropDestVolume') + params.cropDestVolume = true; +end +if fieldIsNotDefined(params,'dryRun') + params.dryRun = false; +end + +if justGetParams, return; end + +corticalDepthStep = 0.1; +corticalDepths = 0:corticalDepthStep:1; +nDepths = length(corticalDepths); + +if ischar(params.sourceVol) + params.sourceVol = {params.sourceVol}; +end +% load source volume data +nSources = length(params.sourceVol); +for iSource = 1:nSources + if ~exist(params.sourceVol{iSource},'file') + if ~params.dryRun + mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Could not find source volume %s',params.sourceVol{iSource})); + return; + end + else + sourceHdr = mlrImageReadNiftiHeader(params.sourceVol{iSource}); + if isempty(sourceHdr) + if ~params.dryRun + return; + end + else + %check that dimensions and sforms are identical + xformMistmatch = false; + if iSource==1 + sourceXform = getXform(sourceHdr); + else + if ~isequal(sourceXform,getXform(sourceHdr)) + mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Rotation matrices do not match between sources %s and %s',params.sourceVol{1},params.sourceVol{iSource})); + xformMistmatch = true; + end + end + end + end +end +if ~params.dryRun && xformMistmatch + return; +end + +if fieldIsNotDefined(params,'destVol') + params.destVol = cell(size(params.sourceVol)); +elseif ischar(params.destVol) + [~,~,extension] = fileparts(params.destVol); + if isempty(extension) + % we assume it is a suffix to add to the source vol name + destSuffix = params.destVol; + params.destVol = cell(1,nSources); + for iSource = 1:nSources + [path,file,extension] = fileparts(params.sourceVol{iSource}); + params.destVol{iSource}=[path,file,destSuffix,extension]; + end + else + params.destVol = {params.destVol}; + end +end +if length(params.destVol) ~= nSources + mrWarnDlg('(freesurferSphericalNormalizationVolumes) The number of source and destination volumes must match'); + if ~params.dryRun + return; + end +end + +% first (re)compute mapping between source and destination surfaces (if needed) +[sourcePath, destPath] = remapSurfaces(params.fsSourceSubj,params.fsDestSubj,params.recomputeRemapping,params.dryRun,params.fsSourceSurfSuffix); +if (isempty(sourcePath) || isempty(destPath)) && params.dryRun + return; +end + +% load source surfRelax volume hdr +if fieldIsNotDefined(params,'sourceSurfRelaxVolume') + params.sourceSurfRelaxVolume = fullfile(sourcePath,'surfRelax',[params.fsSourceSubj '_mprage_pp.nii']); + if ~exist(params.sourceSurfRelaxVolume,'file') + params.sourceSurfRelaxVolume = fullfile(sourcePath,'surfRelax',[params.fsSourceSubj '_mprage_pp.img']); + if ~exist(params.sourceSurfRelaxVolume,'file') + sourceSurfRelaxVolumes = [dir(fullfile(sourcePath,'surfRelax','*.nii')); dir(fullfile(sourcePath,'surfRelax','*.img'))]; + switch(length(sourceSurfRelaxVolumes)) + case 0 + mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Could not find source surfRelax volume in %s',fullfile(destPath,'surfRelax'))); + if ~params.dryRun + return; + end + case 1 + params.sourceSurfRelaxVolume = sourceSurfRelaxVolumes(1).name; + otherwise + mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Multiple volumes in %s, specify a source surfRelax volume',dir(fullfile(sourcePath,'surfRelax')))); + if ~params.dryRun + return; + end + end + end + end +elseif ~exist(params.sourceSurfRelaxVolume,'file') + mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Could not find source surfRelax volume %s',params.sourceSurfRelaxVolume)); + if ~params.dryRun + return; + end +end +if ~params.dryRun + sourceSurfRelaxHdr = mlrImageReadNiftiHeader(params.sourceSurfRelaxVolume); + sourceSurfRelaxXform = getXform(sourceSurfRelaxHdr); +end + +% load destination surfRelax volume hdr +if fieldIsNotDefined(params,'destSurfRelaxVolume') + params.destSurfRelaxVolume = fullfile(destPath,'surfRelax',[params.fsDestSubj '_mprage_pp.nii']); + if ~exist(params.destSurfRelaxVolume,'file') + params.destSurfRelaxVolume = fullfile(destPath,'surfRelax',[params.fsDestSubj '_mprage_pp.img']); + if ~exist(params.destSurfRelaxVolume,'file') + destSurfRelaxVolumes = [dir(fullfile(destPath,'surfRelax','*.nii')); dir(fullfile(destPath,'surfRelax','*.img'))]; + switch(length(destSurfRelaxVolumes)) + case 0 + mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Could not find destination volume in %s',dir(fullfile(destPath,'surfRelax')))); + if ~params.dryRun + return; + end + case 1 + params.destSurfRelaxVolume = fullfile(destPath,'surfRelax',destSurfRelaxVolumes(1).name); + otherwise + mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Multiple volumes in %s, specify a destination surfRelax volume',dir(fullfile(destPath,'surfRelax')))); + if ~params.dryRun + return; + end + end + end + end +elseif ~exist(params.destSurfRelaxVolume,'file') + mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Could not find destination surfRelax volume %s',params.destSurfRelaxVolume)); + if ~params.dryRun + return; + end +end +if ~params.dryRun + destSurfRelaxHdr = mlrImageReadNiftiHeader(params.destSurfRelaxVolume); + destSurfRelaxXform = getXform(destSurfRelaxHdr); +end + +% load destination volume header +if fieldIsNotDefined(params,'destVolTemplate') + params.destVolTemplate = params.destSurfRelaxVolume; +elseif ~exist(params.destVolTemplate,'file') + mrWarnDlg(sprintf('(freesurferSphericalNormalizationVolumes) Cannot find destination volume template %s',params.destVolTemplate)); + if ~params.dryRun + return; + end +end +if ~params.dryRun + destHdr = mlrImageReadNiftiHeader(params.destVolTemplate); + destXform = getXform(destHdr); + destHdr.datatype = 16; % make sure data get exported as float32 (single) and that NaNs get saved as NaNs + uncroppedDestDims = destHdr.dim(2:4)'; +end + +if params.dryRun + return; +end + +% Load original source and remapped destination surface +switch(params.hemisphere) + case 'both' + side = {'left','right'}; + otherwise + side = {params.hemisphere}; +end +surfs = {'WM','GM'}; +nSides = length(side); +if params.cropDestVolume + cropBox = [inf -inf; inf -inf; inf -inf]; +end +for iSide=1:nSides + for iSurf = 1:2 + %get surfaces in OFF format + sourceSurf{iSurf,iSide} = loadSurfOFF([sourcePath '/surfRelax/' params.fsSourceSubj '_' side{iSide} '_' surfs{iSurf} params.fsSourceSurfSuffix '.off']); + destSurf{iSurf} = loadSurfOFF([sourcePath '/surfRelax/' params.fsSourceSubj '_' side{iSide} '_' surfs{iSurf} params.fsSourceSurfSuffix '_' params.fsDestSubj '.off']); % same surface mesh as source, but with destination coordinates + % convert vertices coordinates to surfRelax volume array coordinates + sourceSurf{iSurf,iSide} = xformSurfaceWorld2Array(sourceSurf{iSurf,iSide},sourceSurfRelaxHdr); + destSurf{iSurf} = xformSurfaceWorld2Array(destSurf{iSurf},destSurfRelaxHdr); + + % subdivide meshes by adding face centroids (this avoids missing voxels in the cortical sheet for most regions) + sourceSurf{iSurf,iSide} = subdivideMesh(sourceSurf{iSurf,iSide},1); + destSurf{iSurf} = subdivideMesh(destSurf{iSurf},1); + + % convert to source and destination array coordinates + sourceSurf{iSurf,iSide}.vtcs = (sourceXform\sourceSurfRelaxXform*[sourceSurf{iSurf,iSide}.vtcs';ones(1,sourceSurf{iSurf,iSide}.Nvtcs)])'; + destSurf{iSurf}.vtcs = (destXform\destSurfRelaxXform*[destSurf{iSurf}.vtcs';ones(1,destSurf{iSurf}.Nvtcs)])'; + sourceSurf{iSurf,iSide}.vtcs = sourceSurf{iSurf,iSide}.vtcs(:,1:3); + destSurf{iSurf}.vtcs = destSurf{iSurf}.vtcs(:,1:3); + end + if params.cropDestVolume + % get original (not-remapped) destination surfaces and apply same transformations as above (except subdividing) + originalDestGMsurf = loadSurfOFF([destPath '/surfRelax/' params.fsDestSubj '_' side{iSide} '_GM.off']); + originalDestGMsurf = xformSurfaceWorld2Array(originalDestGMsurf,destSurfRelaxHdr); + originalDestGMsurf.vtcs = (destXform\destSurfRelaxXform*[originalDestGMsurf.vtcs';ones(1,originalDestGMsurf.Nvtcs)])'; + originalDestGMsurf.vtcs = originalDestGMsurf.vtcs(:,1:3); + end + + % compute intermediate depth coordinates for destination mesh + destCoords = zeros(destSurf{1}.Nvtcs,3,nDepths); + for iDepth = 1:nDepths + destCoords(:,:,iDepth) = (1-corticalDepths(iDepth))*destSurf{1}.vtcs + corticalDepths(iDepth)*destSurf{2}.vtcs; + end + + if params.cropDestVolume % find smallest volume including the outer surface + cropBox(:,1) = min(cropBox(:,1),floor(min(originalDestGMsurf.vtcs))'); + cropBox(:,2) = max(cropBox(:,2),ceil(max(originalDestGMsurf.vtcs))'); + end + + % compute mapping between destination surface and destination volume + destCoords = permute(destCoords,[1 4 3 2]); + surf2volumeMap{iSide} = inverseBaseCoordMap(destCoords,uncroppedDestDims); + +end +clearvars('destCoords'); %save memory + +if params.cropDestVolume % crop destination volume + destHdr.dim(2:4) = diff(cropBox,[],2)+1; + cropXform = eye(4); + cropXform(1:3,4) = -cropBox(:,1)+1; + destHdr.qform44 = cropXform\destHdr.qform44; + destHdr.sform44 = cropXform\destHdr.sform44; +end + + +% compute intermediate depth coordinates for source mesh +for iSide = 1:nSides + sourceCoords{iSide} = zeros(sourceSurf{1,iSide}.Nvtcs,3,nDepths,nSides); + for iDepth = 1:nDepths + sourceCoords{iSide}(:,:,iDepth) = (1-corticalDepths(iDepth))*sourceSurf{1,iSide}.vtcs + corticalDepths(iDepth)*sourceSurf{2,iSide}.vtcs; + end + sourceCoords{iSide} = reshape(permute(sourceCoords{iSide},[1 4 3 2]),[],3); +end + +% interpolate data to destination volume +for iSource = 1:nSources + destData = nan(uncroppedDestDims); + % get source volume data + [sourceData,sourceHdr] = mlrImageReadNifti(params.sourceVol{iSource}); + dataAreBinary = isequal(unique(sourceData(~isnan(sourceData))),[0 1]'); + if dataAreBinary + interpMethod = 'linear'; % nearest doesn't work well for binary masks + else + interpMethod = params.interpMethod; + end + for iSide = 1:nSides %for each hemisphere + % get surface data from source volume + surfData = interpn((1:sourceHdr.dim(2))',(1:sourceHdr.dim(3))',(1:sourceHdr.dim(4))',... + sourceData,sourceCoords{iSide}(:,1),sourceCoords{iSide}(:,2),sourceCoords{iSide}(:,3),interpMethod); + % transform surface data to destination volume + thisData = applyInverseBaseCoordMap(surf2volumeMap{iSide},uncroppedDestDims,surfData); + if dataAreBinary && params.outputBinaryData % re-binarize binary data + binaryThreshold = 0; %empirical (and conservative) threshold + thisData(thisData<=binaryThreshold)=0; + thisData(thisData>binaryThreshold)=1; + end + destData(~isnan(thisData)) = thisData(~isnan(thisData)); % we assume left and right surfaces sample exclusive sets of voxels, which is not exactly true at the midline + end + % write out the data + if isempty(params.destVol{iSource}) + [path,file,extension] = fileparts(params.sourceVol{iSource}); + [filename,pathname] = uiputfile(fullfile(path,[file '_' params.fsDestSubj extension]),'Volume save name'); + if ~isnumeric(filename) + params.destVol{iSource} = fullfile(pathname,filename); + else + return; + end + end + if params.cropDestVolume + destData = destData(cropBox(1,1):cropBox(1,2),cropBox(2,1):cropBox(2,2),cropBox(3,1):cropBox(3,2)); + end + mlrImageWriteNifti(params.destVol{iSource},destData,destHdr); +end + + +function surf = subdivideMesh(surf,n) + +for i = 1:n + % calculate face centroids + nFaces = size(surf.tris,1); + faceCentroids = squeeze(mean(reshape(surf.vtcs(surf.tris,:),nFaces,3,3),2)); + surf.vtcs = [surf.vtcs;faceCentroids]; + + % replace old by new faces + surf.tris = [surf.tris(:) reshape(circshift(surf.tris,1,2),[],1) reshape(repmat(surf.Nvtcs+(1:surf.Ntris)',1,3),[],1)]; + + % update number of vertices and faces + surf.Nvtcs = surf.Nvtcs + surf.Ntris; + surf.Ntris = surf.Ntris * 3; +end + + +function xform = getXform(hdr) + +if fieldIsNotDefined(hdr,'sform44') + if fieldIsNotDefined(hdr,'qform44') + keyboard + else + xform = hdr.qform44 * shiftOriginXform; % convert from Nifti 0-indexing to Matlab 1-indexing + end +else + xform = hdr.sform44 * shiftOriginXform; % convert from Nifti 0-indexing to Matlab 1-indexing +end + + diff --git a/mrUtilities/surfUtils/inpolyhedron.m b/mrUtilities/surfUtils/inpolyhedron.m new file mode 100644 index 000000000..2ac2c1592 --- /dev/null +++ b/mrUtilities/surfUtils/inpolyhedron.m @@ -0,0 +1,461 @@ +function IN = inpolyhedron(varargin) +%INPOLYHEDRON Tests if points are inside a 3D triangulated (faces/vertices) surface +% +% IN = INPOLYHEDRON(FV,QPTS) tests if the query points (QPTS) are inside +% the patch/surface/polyhedron defined by FV (a structure with fields +% 'vertices' and 'faces'). QPTS is an N-by-3 set of XYZ coordinates. IN +% is an N-by-1 logical vector which will be TRUE for each query point +% inside the surface. By convention, surface normals point OUT from the +% object (see FLIPNORMALS option below if to reverse this convention). +% +% INPOLYHEDRON(FACES,VERTICES,...) takes faces/vertices separately, rather than in +% an FV structure. +% +% IN = INPOLYHEDRON(..., X, Y, Z) voxelises a mask of 3D gridded query points +% rather than an N-by-3 array of points. X, Y, and Z coordinates of the grid +% supplied in XVEC, YVEC, and ZVEC respectively. IN will return as a 3D logical +% volume with SIZE(IN) = [LENGTH(YVEC) LENGTH(XVEC) LENGTH(ZVEC)], equivalent to +% syntax used by MESHGRID. INPOLYHEDRON handles this input faster and with a lower +% memory footprint than using MESHGRID to make full X, Y, Z query points matrices. +% +% INPOLYHEDRON(...,'PropertyName',VALUE,'PropertyName',VALUE,...) tests query +% points using the following optional property values: +% +% TOL - Tolerance on the tests for "inside" the surface. You can think of +% tol as the distance a point may possibly lie above/below the surface, and still +% be perceived as on the surface. Due to numerical rounding nothing can ever be +% done exactly here. Defaults to ZERO. Note that in the current implementation TOL +% only affects points lying above/below a surface triangle (in the Z-direction). +% Points coincident with a vertex in the XY plane are considered INside the surface. +% More formal rules can be implemented with input/feedback from users. +% +% GRIDSIZE - Internally, INPOLYHEDRON uses a divide-and-conquer algorithm to +% split all faces into a chessboard-like grid of GRIDSIZE-by-GRIDSIZE regions. +% Performance will be a tradeoff between a small GRIDSIZE (few iterations, more +% data per iteration) and a large GRIDSIZE (many iterations of small data +% calculations). The sweet-spot has been experimentally determined (on a win64 +% system) to be correlated with the number of faces/vertices. You can overwrite +% this automatically computed choice by specifying a GRIDSIZE parameter. +% +% FACENORMALS - By default, the normals to the FACE triangles are computed as the +% cross-product of the first two triangle edges. You may optionally specify face +% normals here if they have been pre-computed. +% +% FLIPNORMALS - (Defaults FALSE). To match a wider convention, triangle +% face normals are presumed to point OUT from the object's surface. If +% your surface normals are defined pointing IN, then you should set the +% FLIPNORMALS option to TRUE to use the reverse of this convention. +% +% Example: +% tmpvol = zeros(20,20,20); % Empty voxel volume +% tmpvol(5:15,8:12,8:12) = 1; % Turn some voxels on +% tmpvol(8:12,5:15,8:12) = 1; +% tmpvol(8:12,8:12,5:15) = 1; +% fv = isosurface(tmpvol, 0.99); % Create the patch object +% fv.faces = fliplr(fv.faces); % Ensure normals point OUT +% % Test SCATTERED query points +% pts = rand(200,3)*12 + 4; % Make some query points +% in = inpolyhedron(fv, pts); % Test which are inside the patch +% figure, hold on, view(3) % Display the result +% patch(fv,'FaceColor','g','FaceAlpha',0.2) +% plot3(pts(in,1),pts(in,2),pts(in,3),'bo','MarkerFaceColor','b') +% plot3(pts(~in,1),pts(~in,2),pts(~in,3),'ro'), axis image +% % Test STRUCTURED GRID of query points +% gridLocs = 3:2.1:19; +% [x,y,z] = meshgrid(gridLocs,gridLocs,gridLocs); +% in = inpolyhedron(fv, gridLocs,gridLocs,gridLocs); +% figure, hold on, view(3) % Display the result +% patch(fv,'FaceColor','g','FaceAlpha',0.2) +% plot3(x(in), y(in), z(in),'bo','MarkerFaceColor','b') +% plot3(x(~in),y(~in),z(~in),'ro'), axis image +% +% See also: UNIFYMESHNORMALS (on the file exchange) + +% TODO-list +% - Optmise overall memory footprint. (need examples with MEM errors) +% - Implement an "ignore these" step to speed up calculations for: +% * Query points outside the convex hull of the faces/vertices input +% - Get a better/best gridSize calculation. User feedback? +% - Detect cases where X-rays or Y-rays would be better than Z-rays? + +% +% Author: Sven Holcombe +% - 10 Jun 2012: Version 1.0 +% - 28 Aug 2012: Version 1.1 - Speedup using accumarray +% - 07 Nov 2012: Version 2.0 - BEHAVIOUR CHANGE +% Query points coincident with a VERTEX are now IN an XY triangle +% - 18 Aug 2013: Version 2.1 - Gridded query point handling with low memory footprint. +% - 10 Sep 2013: Version 3.0 - BEHAVIOUR CHANGE +% NEW CONVENTION ADOPTED to expect face normals pointing IN +% Vertically oriented faces are now ignored. Speeds up +% computation and fixes bug where presence of vertical faces +% produced NaN distance from a query pt to facet, making all +% query points under facet erroneously NOT IN polyhedron. +% - 25 Sep 2013: Version 3.1 - Dropped nested unique call which was made +% mostly redundant via v2.1 gridded point handling. Also +% refreshed grid size selection via optimisation. +% - 25 Feb 2014: Version 3.2 - Fixed indeterminate behaviour for query +% points *exactly* in line with an "overhanging" vertex. +% - 11 Nov 2016: Version 3.3 - Used quoted semicolons ':' inside function +% handle calls to conform with new 2015b interpreter +%% + +% FACETS is an unpacked arrangement of faces/vertices. It is [3-by-3-by-N], +% with 3 1-by-3 XYZ coordinates of N faces. +[facets, qPts, options] = parseInputs(varargin{:}); +numFaces = size(facets,3); +if ~options.griddedInput % SCATTERED QUERY POINTS + numQPoints = size(qPts,1); +else % STRUCTURED QUERY POINTS + numQPoints = prod(cellfun(@numel,qPts(1:2))); +end + +% Precompute 3d normals to all facets (triangles). Do this via the cross +% product of the first edge vector with the second. Normalise the result. +allEdgeVecs = facets([2 3 1],:,:) - facets(:,:,:); +if isempty(options.facenormals) + allFacetNormals = bsxfun(@times, allEdgeVecs(1,[2 3 1],:), allEdgeVecs(2,[3 1 2],:)) - ... + bsxfun(@times, allEdgeVecs(2,[2 3 1],:), allEdgeVecs(1,[3 1 2],:)); + allFacetNormals = bsxfun(@rdivide, allFacetNormals, sqrt(sum(allFacetNormals.^2,2))); +else + allFacetNormals = permute(options.facenormals,[3 2 1]); +end +if options.flipnormals + allFacetNormals = -allFacetNormals; +end +% We use a Z-ray intersection so we don't even need to consider facets that +% are purely vertically oriented (have zero Z-component). +isFacetUseful = allFacetNormals(:,3,:) ~= 0; + +%% Setup grid referencing system +% Function speed can be thought of as a function of grid size. A small number of grid +% squares means iterating over fewer regions (good) but with more faces/qPts to +% consider each time (bad). For any given mesh/queryPt configuration, there will be a +% sweet spot that minimises computation time. There will also be a constraint from +% memory available - low grid sizes means considering many queryPt/faces at once, +% which will require a larger memory footprint. Here we will let the user specify +% gridsize directly, or we will estimate the optimum size based on prior testing. +if ~isempty(options.gridsize) + gridSize = options.gridsize; +else + % Coefficients (with 95% confidence bounds): + p00 = -47; p10 = 12.83; p01 = 20.89; + p20 = 0.7578; p11 = -6.511; p02 = -2.586; + p30 = -0.1802; p21 = 0.2085; p12 = 0.7521; + p03 = 0.09984; p40 = 0.005815; p31 = 0.007775; + p22 = -0.02129; p13 = -0.02309; + GSfit = @(x,y)p00 + p10*x + p01*y + p20*x^2 + p11*x*y + p02*y^2 + p30*x^3 + p21*x^2*y + p12*x*y^2 + p03*y^3 + p40*x^4 + p31*x^3*y + p22*x^2*y^2 + p13*x*y^3; + gridSize = min(150 ,max(1, ceil(GSfit(log(numQPoints),log(numFaces))))); + if isnan(gridSize), gridSize = 1; end +end + +%% Find candidate qPts -> triangles pairs +% We have a large set of query points. For each query point, find potential +% triangles that would be pierced by vertical rays through the qPt. First, +% a simple filter by XY bounding box + +% Calculate the bounding box of each facet +minFacetCoords = permute(min(facets(:,1:2,:),[],1),[3 2 1]); +maxFacetCoords = permute(max(facets(:,1:2,:),[],1),[3 2 1]); + +% Set rescale values to rescale all vertices between 0(-eps) and 1(+eps) +scalingOffsetsXY = min(minFacetCoords,[],1) - eps; +scalingRangeXY = max(maxFacetCoords,[],1) - scalingOffsetsXY + 2*eps; + +% Based on scaled min/max facet coords, get the [lowX lowY highX highY] "grid" index +% of all faces +lowToHighGridIdxs = floor(bsxfun(@rdivide, ... + bsxfun(@minus, ... % Use min/max coordinates of each facet (+/- the tolerance) + [minFacetCoords-options.tol maxFacetCoords+options.tol],... + [scalingOffsetsXY scalingOffsetsXY]),... + [scalingRangeXY scalingRangeXY]) * gridSize) + 1; + +% Build a grid of cells. In each cell, place the facet indices that encroach into +% that grid region. Similarly, each query point will be assigned to a grid region. +% Note that query points will be assigned only one grid region, facets can cover many +% regions. Furthermore, we will add a tolerance to facet region assignment to ensure +% a query point will be compared to facets even if it falls only on the edge of a +% facet's bounding box, rather than inside it. +cells = cell(gridSize); +[unqLHgrids,~,facetInds] = unique(lowToHighGridIdxs,'rows'); +tmpInds = accumarray(facetInds(isFacetUseful),find(isFacetUseful),[size(unqLHgrids,1),1],@(x){x}); +for xi = 1:gridSize + xyMinMask = xi >= unqLHgrids(:,1) & xi <= unqLHgrids(:,3); + for yi = 1:gridSize + cells{yi,xi} = cat(1,tmpInds{xyMinMask & yi >= unqLHgrids(:,2) & yi <= unqLHgrids(:,4)}); + % The above line (with accumarray) is faster with equiv results than: + % % cells{yi,xi} = find(ismember(facetInds, xyInds)); + end +end +% With large number of facets, memory may be important: +clear lowToHightGridIdxs LHgrids facetInds tmpInds xyMinMask minFacetCoords maxFacetCoords + +%% Compute edge unit vectors and dot products + +% Precompute the 2d unit vectors making up each facet's edges in the XY plane. +allEdgeUVecs = bsxfun(@rdivide, allEdgeVecs(:,1:2,:), sqrt(sum(allEdgeVecs(:,1:2,:).^2,2))); + +% Precompute the inner product between edgeA.edgeC, edgeB.edgeA, edgeC.edgeB +allEdgeEdgeDotPs = sum(allEdgeUVecs .* -allEdgeUVecs([3 1 2],:,:),2) - 1e-9; + +%% Gather XY query locations +% Since query points are most likely given as a (3D) grid of query locations, we only +% need to consider the unique XY locations when asking which facets a vertical ray +% through an XY location would pierce. +if ~options.griddedInput % SCATTERED QUERY POINTS + qPtsXY = @(varargin)qPts(:,1:2); + qPtsXYZViaUnqIndice = @(ind)qPts(ind,:); + outPxIndsViaUnqIndiceMask = @(ind,mask)ind(mask); + outputSize = [size(qPts,1),1]; + reshapeINfcn = @(INMASK)INMASK; + minFacetDistanceFcn = @minFacetToQptDistance; +else % STRUCTURED QUERY POINTS + [xmat,ymat] = meshgrid(qPts{1:2}); + qPtsXY = [xmat(:) ymat(:)]; + % A standard set of Z locations will be shifted around by different + % unqQpts XY coordinates. + zCoords = qPts{3}(:) * [0 0 1]; + qPtsXYZViaUnqIndice = @(ind)bsxfun(@plus, zCoords, [qPtsXY(ind,:) 0]); + % From a given indice and mask, we will turn on/off the IN points under + % that indice based on the mask. The easiest calculation is to setup + % the IN matrix as a numZpts-by-numUnqPts mask. At the end, we must + % unpack/reshape this 2D mask to a full 3D logical mask + numZpts = size(zCoords,1); + baseZinds = 1:numZpts; + outPxIndsViaUnqIndiceMask = @(ind,mask)(ind-1)*numZpts + baseZinds(mask); + outputSize = [numZpts, size(qPtsXY,1)]; + reshapeINfcn = @(INMASK)reshape(INMASK', cellfun(@numel, qPts([2 1 3]))); + minFacetDistanceFcn = @minFacetToQptsDistance; +end + +% Start with every query point NOT inside the polyhedron. We will +% iteratively find those query points that ARE inside. +IN = false(outputSize); +% Determine with grids each query point falls into. +qPtGridXY = floor(bsxfun(@rdivide, bsxfun(@minus, qPtsXY(':',':'), scalingOffsetsXY),... + scalingRangeXY) * gridSize) + 1; +[unqQgridXY,~,qPtGridInds] = unique(qPtGridXY,'rows'); +% We need only consider grid indices within those already set up +ptsToConsidMask = ~any(qPtGridXY<1 | qPtGridXY>gridSize, 2); +if ~any(ptsToConsidMask) + IN = reshapeINfcn(IN); + return; +end +% Build the reference list +cellQptContents = accumarray(qPtGridInds(ptsToConsidMask),find(ptsToConsidMask), [],@(x){x}); +gridsToCheck = unqQgridXY(~any(unqQgridXY<1 | unqQgridXY>gridSize, 2),:); +cellQptContents(cellfun('isempty',cellQptContents)) = []; +gridIndsToCheck = sub2ind(size(cells), gridsToCheck(:,2), gridsToCheck(:,1)); + +% For ease of multiplication, reshape qPt XY coords to [1-by-2-by-1-by-N] +qPtsXY = permute(qPtsXY(':',':'),[4 2 3 1]); + +% There will be some grid indices with query points but without facets. +emptyMask = cellfun('isempty',cells(gridIndsToCheck))'; +for i = find(~emptyMask) + % We get all the facet coordinates (ie, triangle vertices) of triangles + % that intrude into this grid location. The size is [3-by-2-by-N], for + % the [3vertices-by-XY-by-Ntriangles] + allFacetInds = cells{gridIndsToCheck(i)}; + candVerts = facets(:,1:2,allFacetInds); + % We need the XY coordinates of query points falling into this grid. + allqPtInds = cellQptContents{i}; + queryPtsXY = qPtsXY(:,:,:,allqPtInds); + + % Get unit vectors pointing from each triangle vertex to my query point(s) + vert2ptVecs = bsxfun(@minus, queryPtsXY, candVerts); + vert2ptUVecs = bsxfun(@rdivide, vert2ptVecs, sqrt(sum(vert2ptVecs.^2,2))); + % Get unit vectors pointing around each triangle (along edge A, edge B, edge C) + edgeUVecs = allEdgeUVecs(:,:,allFacetInds); + % Get the inner product between edgeA.edgeC, edgeB.edgeA, edgeC.edgeB + edgeEdgeDotPs = allEdgeEdgeDotPs(:,:,allFacetInds); + % Get inner products between each edge unit vec and the UVs from qPt to vertex + edgeQPntDotPs = sum(bsxfun(@times, edgeUVecs, vert2ptUVecs),2); + qPntEdgeDotPs = sum(bsxfun(@times,vert2ptUVecs, -edgeUVecs([3 1 2],:,:)),2); + % If both inner products 2 edges to the query point are greater than the inner + % product between the two edges themselves, the query point is between the V + % shape made by the two edges. If this is true for all 3 edge pair, the query + % point is inside the triangle. + resultIN = all(bsxfun(@gt, edgeQPntDotPs, edgeEdgeDotPs) & bsxfun(@gt, qPntEdgeDotPs, edgeEdgeDotPs),1); + resultONVERTEX = any(any(isnan(vert2ptUVecs),2),1); + result = resultIN | resultONVERTEX; + qPtHitsTriangles = any(result,3); + % If NONE of the query points pierce ANY triangles, we can skip forward + if ~any(qPtHitsTriangles), continue, end + + % In the next step, we'll need to know the indices of ALL the query points at + % each of the distinct XY coordinates. Let's get their indices into "qPts" as a + % cell of length M, where M is the number of unique XY points we had found. + for ptNo = find(qPtHitsTriangles(:))' + % Which facets does it pierce? + piercedFacetInds = allFacetInds(result(1,1,:,ptNo)); + + % Get the 1-by-3-by-N set of triangle normals that this qPt pierces + piercedTriNorms = allFacetNormals(:,:,piercedFacetInds); + + % Pick the first vertex as the "origin" of a plane through the facet. Get the + % vectors from each query point to each facet origin + facetToQptVectors = bsxfun(@minus, ... + qPtsXYZViaUnqIndice(allqPtInds(ptNo)),... + facets(1,:,piercedFacetInds)); + + % Calculate how far you need to go up/down to pierce the facet's plane. + % Positive direction means "inside" the facet, negative direction means + % outside. + facetToQptDists = bsxfun(@rdivide, ... + sum(bsxfun(@times,piercedTriNorms,facetToQptVectors),2), ... + abs(piercedTriNorms(:,3,:))); + + % Since it's possible for two triangles sharing the same vertex to + % be the same distance away, I want to sum up all the distances of + % triangles that are closest to the query point. Simple case: The + % closest triangle is unique Edge case: The closest triangle is one + % of many the same distance and direction away. Tricky case: The + % closes triangle has another triangle the equivalent distance + % but facing the opposite direction + IN( outPxIndsViaUnqIndiceMask(allqPtInds(ptNo), ... + minFacetDistanceFcn(facetToQptDists), , ) % by: julien besle % date: 15/05/2014 % purpose: remaps surface vertices of one Freesurfer subject to another % (usually fsaverage, but not necessarily) using the spherical -% registration output of recon-all (subj/surf/?h.sphere.reg)% +% registration output of recon-all (subj/surf/?h.sphere.reg)% +% Existing remapping will no be re-computed and overwritten +% unless optional argument force is set to true +% Set dryRun to true to check that all necessary files exist without actually computing anything +% Optionally check specifically for surfaces with suffix fsSubjectSuffix in filename +% % output: left and right GM and WM surfaces with mesh of one subject % and coordinates of the other, and vice-versa -function remapSurface(fssubject,fsaverage) +function [subjPath,fsaveragePath] = remapSurfaces(fsSubject,fsAverage,force, dryRun, fsSubjectSuffix) +freesurferSubjdir = []; +subjPath = []; +fsaveragePath = []; if isunix || ismac - if isempty(getenv('SUBJECTS_DIR')) - mrErrorDlg('(remapSurfaces) FreeSurfer environment variable SUBJECTS_DIR is not set'); - % implement another way to get the subjects folder + freesurferSubjdir = getenv('SUBJECTS_DIR'); +end +if ieNotDefined('force') + force = false; +end +if ieNotDefined('dryRun') + dryRun = false; +end +if ieNotDefined('fsSubjectSuffix') + fsSubjectSuffix = ''; +end + +if isempty(freesurferSubjdir) || ispc + freesurferSubjdir = mrGetPref('volumeDirectory'); + if isempty(freesurferSubjdir) + mrWarnDlg('(remapSurfaces) Cannot find the location of Freesurfer subject directory. Check your MR preferences using mrGetPref, or set Freesurfer''s environment variable SUBJECTS_DIR (Linux or Mac)'); + if ~dryRun + return + end end - subjPath = [getenv('SUBJECTS_DIR') '/' fssubject]; - if isempty(dir(subjPath)) - mrWarnDlg(['(remapSurfaces) Freesurfer subject ' fssubject ' does not exist']); + fprintf('(remapSurfaces) Assuming that the Freesurfer subject directory is %s\n',freesurferSubjdir); +end + +subjPath = [freesurferSubjdir '/' fsSubject]; +if isempty(dir(subjPath)) + mrWarnDlg(['(remapSurfaces) Freesurfer subject ' fsSubject ' does not exist']); + subjPath = []; + if ~dryRun return end - fsaveragePath = [getenv('SUBJECTS_DIR') '/' fsaverage]; - if isempty(dir(fsaveragePath)) - mrWarnDlg(['(remapSurfaces) Freesurfer subject ' fsaverage ' does not exist']); +end +fsaveragePath = [freesurferSubjdir '/' fsAverage]; +if isempty(dir(fsaveragePath)) + mrWarnDlg(['(remapSurfaces) Freesurfer subject ' fsAverage ' does not exist']); + fsaveragePath = []; + if ~dryRun return end -else - mrErrorDlg('(remapSurfaces) Not implemented for platforms other than Unix or Mac'); - % implement another way to get the subjects folder end if isempty(dir([subjPath '/surfRelax'])) mrWarnDlg(['(remapSurfaces) surfRelax folder does not exist in ' subjPath '. You must first run mlrImportFreesurfer.']); - return + subjPath = []; + if ~dryRun + return + end end if isempty(dir([fsaveragePath '/surfRelax'])) mrWarnDlg(['(remapSurfaces) surfRelax folder does not exist in ' fsaveragePath '. You must first run mlrImportFreesurfer.']); + fsaveragePath = []; + if ~dryRun + return + end +end + +surfaceToCheck = [fsSubject '_left_GM' fsSubjectSuffix '_' fsAverage '.off']; +if exist(fullfile(subjPath,'/surfRelax/',surfaceToCheck),'file') && ~force + mrWarnDlg(sprintf('(remapSurfaces) Mapping between surface %s and Freesurfer subject %s already exists, use optional argument to recompute',surfaceToCheck,fsAverage)); + if ~dryRun + return + end +end +if dryRun && ~force return end - + side = {'left','right'}; surfs = {'GM','WM'}; fsSide = {'lh','rh'}; -disp('(remapSurfaces) Will process:'); +disp('(remapSurfaces) Will remap following surfaces:'); for iSide=1:2 for iSurf = 1:2 %find all OFF files for a given side and surface (WM or GM) - subjFiles{iSide,iSurf} = dir([subjPath '/surfRelax/' fssubject '_' side{iSide} '_' surfs{iSurf} '*.off']); + subjFiles{iSide,iSurf} = dir([subjPath '/surfRelax/' fsSubject '_' side{iSide} '_' surfs{iSurf} '*.off']); %find OFF files to exclude (already processed or flat maps) - toExclude = dir([subjPath '/surfRelax/' fssubject '_' side{iSide} '_' surfs{iSurf} '*Colin*.off']); - toExclude = [toExclude; dir([subjPath '/surfRelax/' fssubject '_' side{iSide} '_' surfs{iSurf} '*MNI*.off'])]; - toExclude = [toExclude; dir([subjPath '/surfRelax/' fssubject '_' side{iSide} '_' surfs{iSurf} '*Flat*.off'])]; - + toExclude = dir([subjPath '/surfRelax/' fsSubject '_' side{iSide} '_' surfs{iSurf} '*Colin*.off']); + toExclude = [toExclude; dir([subjPath '/surfRelax/' fsSubject '_' side{iSide} '_' surfs{iSurf} '*MNI*.off'])]; + toExclude = [toExclude; dir([subjPath '/surfRelax/' fsSubject '_' side{iSide} '_' surfs{iSurf} '*Flat*.off'])]; + [~,toKeep]=setdiff({subjFiles{iSide,iSurf}(:).name},{toExclude(:).name}); subjFiles{iSide,iSurf} = {subjFiles{iSide,iSurf}(toKeep).name}; disp(subjFiles{iSide,iSurf}'); -% if length(subjFiles{iSide,iSurf})>2 -% dir([subjPath '/surfRelax/' fssubject '_' side{iSide} '_' surfs{iSurf} '*.off']) -% filelist = input('Which files do you wish to transform (input index vector) ?') -% subjFiles{iSide,iSurf} = subjFiles{iSide,iSurf}(filelist); -% end end - disp([subjPath '/surfRelax/' fssubject '_' side{iSide} '_Curv.vff']) + disp([subjPath '/surfRelax/' fsSubject '_' side{iSide} '_Curv.vff']) +end + +if dryRun + return end for iSide=1:2 %get reg sphere surfaces [vertSphereSubj, triSphereSubj] = freesurfer_read_surf([subjPath '/surf/' fsSide{iSide} '.sphere.reg']); [vertSphereAverage, triSphereAverage] = freesurfer_read_surf([fsaveragePath '/surf/' fsSide{iSide} '.sphere.reg']); - + % compute re-gridding matrix (expresses the vertices coordinates in one % sphere as a linear combination of face vertices in the other) [averageToSubj,subjToAverage] = findCorrespondingVertices(vertSphereSubj,vertSphereAverage,triSphereSubj,triSphereAverage); for iSurf = 1:2 %get surfaces in OFF format - averageSurf = loadSurfOFF([fsaveragePath '/surfRelax/' fsaverage '_' side{iSide} '_' surfs{iSurf} '.off']); - + averageSurf = loadSurfOFF([fsaveragePath '/surfRelax/' fsAverage '_' side{iSide} '_' surfs{iSurf} '.off']); + for jSurf = subjFiles{iSide,iSurf} pattern = ['_' side{iSide} '_' surfs{iSurf}]; fssubjectPrefix = jSurf{1}([1:strfind(jSurf{1},pattern)-1 strfind(jSurf{1},pattern)+length(pattern):end-4]); - subjSurf = loadSurfOFF([subjPath '/surfRelax/' jSurf{1}]); + subjSurf = loadSurfOFF(fullfile(subjPath,'/surfRelax/',jSurf{1})); %apply regridding matrix to surfaces thisAverageSurf = averageSurf; tmpVtcs = averageToSubj*thisAverageSurf.vtcs; - thisAverageSurf.vtcs = subjToAverage*subjSurf.vtcs; - subjSurf.vtcs = tmpVtcs; + thisAverageSurf.vtcs = subjToAverage*subjSurf.vtcs; % same mesh as average surface, but coordinates of the subject's surface + subjSurf.vtcs = tmpVtcs; % same mesh as subject's surface, but coordinates of the average surface %change file name [path,filename,extension]=fileparts(subjSurf.filename); - subjSurf.filename = [path '/' filename '_' fsaverage extension]; + subjSurf.filename = [path '/' filename '_' fsAverage extension]; [path,filename,extension]=fileparts(thisAverageSurf.filename); thisAverageSurf.filename = [path '/' filename '_' fssubjectPrefix extension]; @@ -106,22 +151,21 @@ function remapSurface(fssubject,fsaverage) end end - + %interpolate curvature data - subjCurv = loadVFF([subjPath '/surfRelax/' fssubject '_' side{iSide} '_Curv.vff'])'; + subjCurv = loadVFF([subjPath '/surfRelax/' fsSubject '_' side{iSide} '_Curv.vff'])'; subjToAverageCurv = subjToAverage*subjCurv; subjToAverageCurv(subjToAverageCurv>max(subjCurv))=max(subjCurv); %clip the data to original min/max (because saveVFF normalizes them) subjToAverageCurv(subjToAverageCurvmax(averageCurv))=max(subjCurv); averageToSubjCurv(averageToSubjCurv) +% +% goal: flip X (left-Right) coordinates of surfRelax surfaces and corresponding volume +% for use in averaging left and right hemisphere data (see e.g. mlrSphericalNormGroup.m) +% This should be run after: +% 1. LR flipping a T1-weighted volume +% 2. Creating surfaces from this LR-flipped volume with Freesurfer +% 3. Converting these surfaces to surfRelax format using mlrImportFreeSurfer.m +% Resulting surfaces (twice LR-flipped) will be spherically-normalized to the reverse hemispheres +% of fsaverage (left surface normalized to right fsaverage surface and vice-versa), +% enabling averaging of left and right normalized surface +% Original surfaces and volumes (once LF-flipped) are saved in subfolder 'surfRelax/Original (once-flipped) files/' +% +% input: - freeSurferID: ID of the L/R-flipped freesurfer subject/template to flip +% - force (optional): if true, re-creates files even they already exist and saves old files in subfolder 'surfRelax/Old twice-flipped files' +% +% author: julien besle (31/07/2020) + +function surfRelaxFlipLR(freeSurferID,force) + +if isunix || ismac + freesurferSubjdir = getenv('SUBJECTS_DIR'); +end +if ieNotDefined('force') + force = false; +end + +if ieNotDefined('freesurferSubjdir') + freesurferSubjdir = mrGetPref('volumeDirectory'); + if isempty(freesurferSubjdir) + mrWarnDlg('(surfRelaxFlipLR) Cannot find the location of Freesurfer subject directory. Check your MR preferences using mrGetPref, or set Freesurfer''s environment variable SUBJECTS_DIR (Linux or Mac)'); + return + end + fprintf('(surfRelaxFlipLR) Assuming that the Freesurfer subject directory is %s\n',freesurferSubjdir); +end + +if isempty(dir(fullfile(freesurferSubjdir,freeSurferID))) + mrWarnDlg(['(surfRelaxFlipLR) Freesurfer subject ' freeSurferID ' does not exist']); + return +end + +surfRelaxPath = fullfile(freesurferSubjdir,freeSurferID,'surfRelax'); +if isempty(dir(surfRelaxPath)) + mrWarnDlg(['(surfRelaxFlipLR) surfRelax folder does not exist in Freesurfer subject ' freeSurferID '. You must first run mlrImportFreesurfer.']); + return +end + +side = {'left','right'}; +surfs = {'GM','WM','Inf'}; +niftiExt = mrGetPref('niftiFileExtension'); +for iSide = 1:2 + for iSurf = 1:length(surfs) + surfaceFile{iSide,iSurf} = sprintf('%s_%s_%s.off',freeSurferID,side{iSide},surfs{iSurf}); + end + curvatureFile{iSide} = sprintf('%s_%s_Curv.vff',freeSurferID,side{iSide}); +end +volumeFile = sprintf('%s_mprage_pp%s',freeSurferID,niftiExt); + +cwd = pwd; +cd(surfRelaxPath); + +onceFlippedFolder = 'Original (once-flipped) files'; +twiceFlippedFolder = 'Old twice-flipped files'; +if exist(onceFlippedFolder,'dir') + if force + fprintf('(surfRelaxFlipLR) Moving previous twice-flipped files to folder ''%s''\n',twiceFlippedFolder); + mkdir(surfRelaxPath,twiceFlippedFolder); + for iSide = 1:2 + for iSurf = 1:length(surfs) + movefile(surfaceFile{iSide,iSurf},twiceFlippedFolder); + end + movefile(curvatureFile{iSide},twiceFlippedFolder); + end + movefile(volumeFile,twiceFlippedFolder); + else + mrWarnDlg(sprintf('(surfRelaxFlipLR) Freesurfer subject %s has already been flipped. Use optional argument to recompute',freeSurferID)); + return; + end +else + fprintf('(surfRelaxFlipLR) Moving original (once-flipped) files to folder ''%s''\n',onceFlippedFolder); + mkdir(surfRelaxPath,onceFlippedFolder); + for iSide = 1:2 + for iSurf = 1:length(surfs) + movefile(surfaceFile{iSide,iSurf},onceFlippedFolder); + end + movefile(curvatureFile{iSide},twiceFlippedFolder); + end + movefile(volumeFile,onceFlippedFolder); +end + +% flip volume and surfaces +fprintf('(surfRelaxFlipLR) Left-right flipping surfRelax volume (%s)\n',volumeFile); +[volume,hdr] = mlrImageLoad(fullfile(onceFlippedFolder,volumeFile)); +volume = flip(volume,1); +mlrImageSave(volumeFile,volume,hdr); +fprintf('(surfRelaxFlipLR) Left-right flipping surfRelax surfaces\n'); +for iSide = 1:2 + for iSurf = 1:length(surfs) + surf = loadSurfOFF(fullfile(onceFlippedFolder,surfaceFile{iSide,iSurf})); + % convert from surfRelax to array coordinates + world2array = mlrXFormFromHeader(mlrImageReadNiftiHeader(fullfile(onceFlippedFolder,volumeFile)),'world2array'); + surf.vtcs = world2array*[surf.vtcs'; ones(1,surf.Nvtcs)]; + surf.vtcs(1,:) = hdr.dim(1) + 1 - surf.vtcs(1,:); % flip X coordinates + % convert back to surfRelax coordinates + surf.vtcs = world2array\surf.vtcs; + surf.vtcs = surf.vtcs(1:3,:)'; + writeOFF(surf, surfaceFile{iSide,iSurf}); + end + copyfile(fullfile(onceFlippedFolder,curvatureFile{iSide}),curvatureFile{iSide}); +end + +cd(cwd);